From 35d27c4f83fc40b3ae791e89ecaa079b05273473 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Fri, 24 Nov 2023 00:40:04 +0100 Subject: [PATCH] Double click on title to mark entry as read --- client/js/templates/Item.jsx | 64 +++++++++++++++++---- client/styles/main.scss | 1 + docs/content/docs/administration/options.md | 6 ++ src/controllers/About.php | 1 + src/helpers/Configuration.php | 2 + 5 files changed, 64 insertions(+), 10 deletions(-) diff --git a/client/js/templates/Item.jsx b/client/js/templates/Item.jsx index 5bc164c3be..aa542d52c7 100644 --- a/client/js/templates/Item.jsx +++ b/client/js/templates/Item.jsx @@ -46,6 +46,30 @@ function setupLightbox({ })); } +function useMultiClickHandler(handler, delay = 400) { + const [state, setState] = useState({ clicks: 0, args: [] }); + + useEffect(() => { + const timer = setTimeout(() => { + setState({ clicks: 0, args: [] }); + + if (state.clicks > 0 && typeof handler[state.clicks] === 'function') { + handler[state.clicks](...state.args); + } + }, delay); + + return () => clearTimeout(timer); + }, [handler, delay, state.clicks, state.args]); + + return (...args) => { + setState((prevState) => ({ clicks: prevState.clicks + 1, args })); + + if (typeof handler[0] === 'function') { + handler[0](...args); + } + }; +} + function stopPropagation(event) { event.stopPropagation(); } @@ -104,7 +128,7 @@ function closeFullScreen({ event, history, location, entryId }) { } // show/hide entry -function handleClick({ event, history, location, expanded, id, target }) { +function handleToggleOpenClick({ event, history, location, expanded, id, target }) { const expected = selfoss.isMobile() ? '.entry' : '.entry-title'; if (target !== expected) { return; @@ -123,6 +147,14 @@ function handleClick({ event, history, location, expanded, id, target }) { } } +// mark entry read/unread +function handleToggleReadClick({ event, unread, id }) { + event.preventDefault(); + event.stopPropagation(); + + selfoss.entriesPage.markEntryRead(id, unread == 1); +} + // load images function loadImages({ event, setImagesLoaded, contentBlock }) { event.preventDefault(); @@ -362,15 +394,31 @@ export default function Item({ currentTime, item, selected, expanded, setNavExpa }, [configuration, expanded, item.id, item.unread, previouslyExpanded]); const entryOnClick = useCallback( - (event) => handleClick({ event, history, location, expanded, id: item.id, target: '.entry' }), + (event) => handleToggleOpenClick({ event, history, location, expanded, id: item.id, target: '.entry' }), [history, location, expanded, item.id] ); const titleOnClick = useCallback( - (event) => handleClick({ event, history, location, expanded, id: item.id, target: '.entry-title' }), + (event) => handleToggleOpenClick({ event, history, location, expanded, id: item.id, target: '.entry-title' }), [history, location, expanded, item.id] ); + const titleOnMultiClick = useMultiClickHandler({ + 0: (event) => { + event.preventDefault(); + event.stopPropagation(); + }, + 1: titleOnClick, + 2: useCallback( + (event) => { + if (canWrite && !selfoss.isSmartphone()) { + handleToggleReadClick({ event, unread: item.unread, id: item.id }); + } + }, + [canWrite, item.unread, item.id] + ) + }); + const starOnClick = useCallback( (event) => { event.preventDefault(); @@ -381,12 +429,8 @@ export default function Item({ currentTime, item, selected, expanded, setNavExpa ); const markReadOnClick = useCallback( - (event) => { - event.preventDefault(); - event.stopPropagation(); - selfoss.entriesPage.markEntryRead(item.id, item.unread == 1); - }, - [item] + (event) => handleToggleReadClick({ event, unread: item.unread, id: item.id }), + [item.unread, item.id] ); const loadImagesOnClick = useCallback( @@ -444,7 +488,7 @@ export default function Item({ currentTime, item, selected, expanded, setNavExpa {/* title */}

+### `double_click_mark_as_read` +
+ +set this to `1` to mark an item as read when double clicking on it. +
+ ### `env_prefix`
diff --git a/src/controllers/About.php b/src/controllers/About.php index 5e0af5f716..bc30f3fefc 100644 --- a/src/controllers/About.php +++ b/src/controllers/About.php @@ -51,6 +51,7 @@ public function about(): void { 'autoHideReadOnMobile' => $this->configuration->autoHideReadOnMobile, // bool 'scrollToArticleHeader' => $this->configuration->scrollToArticleHeader, // bool 'showThumbnails' => $this->configuration->showThumbnails, // bool + 'doubleClickMarkAsRead' => $this->configuration->doubleClickMarkAsRead, // bool 'htmlTitle' => trim($this->configuration->htmlTitle), // string 'allowPublicUpdate' => $this->configuration->allowPublicUpdateAccess, // bool 'publicMode' => $this->configuration->public, // bool diff --git a/src/helpers/Configuration.php b/src/helpers/Configuration.php index 38da68716c..3974f79406 100644 --- a/src/helpers/Configuration.php +++ b/src/helpers/Configuration.php @@ -148,6 +148,8 @@ class Configuration { public bool $showThumbnails = true; + public bool $doubleClickMarkAsRead = false; + public int $readingSpeedWpm = 0; /**