From 668f6c322a8363c2dfdbf42244a81410d312f055 Mon Sep 17 00:00:00 2001 From: Christian Dreier Date: Fri, 30 Jun 2023 15:17:11 +0200 Subject: [PATCH] Scroll to search result If the currently selected search result is outside the visible view, this result will be scroll into that view. --- app/index.js | 52 ++++++++++++++++---------------- app/lib/renderer/common.js | 17 ++++++++++- app/lib/search/searchRenderer.js | 27 ++++++++++++++--- 3 files changed, 65 insertions(+), 31 deletions(-) diff --git a/app/index.js b/app/index.js index 707d412..ea7d0ec 100644 --- a/app/index.js +++ b/app/index.js @@ -91,10 +91,6 @@ function chooseTheme(isDark) { return isDark ? common.DARK_THEME : common.LIGHT_THEME } -function scrollTo(position) { - renderer.contentElement().scrollTop = position -} - function reload(isFileModification, encoding) { ipc.send( ipc.messages.reloadPrepared, @@ -256,7 +252,6 @@ ipc.listen(ipc.messages.fileOpen, async file => { renderer.contentElement().innerHTML = documentRendering.renderContent(content) renderer.rawTextElement().innerHTML = documentRendering.renderRawText(content) - search.highlightTerm() populateToc(content, "toc") // Alter local references to be relativ to the document @@ -289,30 +284,31 @@ ipc.listen(ipc.messages.fileOpen, async file => { } }) + search.highlightTerm() + const scrollPosition = file.scrollPosition const internalTarget = file.internalTarget let titlePrefix = filePath - if (scrollPosition) { - scrollTo(scrollPosition) - } - if (internalTarget) { - const targetElement = document.getElementById(internalTarget.replace("#", "")) - if (targetElement) { - if (!scrollPosition) { - const containerElement = renderer.contentElement().children[0] - scrollTo( - targetElement.getBoundingClientRect().top - - (containerElement.getBoundingClientRect().top - - Number(containerElement.style.paddingTop.replace("px", ""))), - ) + if (search.isActive()) { + search.scrollToResult() + } else { + if (scrollPosition) { + renderer.scrollTo(scrollPosition) + } + if (internalTarget) { + const targetElement = document.getElementById(internalTarget.replace("#", "")) + if (targetElement) { + if (!scrollPosition) { + renderer.scrollTo(renderer.elementYPosition(targetElement)) + } + titlePrefix += internalTarget + } else { + titlePrefix += ` ("${internalTarget}" not found)` } - titlePrefix += internalTarget - } else { - titlePrefix += ` ("${internalTarget}" not found)` } - } - if (!scrollPosition && !internalTarget) { - scrollTo(0) + if (!scrollPosition && !internalTarget) { + renderer.scrollTo(0) + } } document.title = `${titlePrefix} - ${TITLE} ${remote.app.getVersion()}` @@ -329,7 +325,11 @@ ipc.listen(ipc.messages.fileOpen, async file => { ipc.listen(ipc.messages.prepareReload, reload) -ipc.listen(ipc.messages.restorePosition, scrollTo) +ipc.listen(ipc.messages.restorePosition, position => { + if (!search.isActive()) { + renderer.scrollTo(position) + } +}) ipc.listen(ipc.messages.changeZoom, zoomFactor => electron.webFrame.setZoomFactor(zoomFactor)) @@ -352,5 +352,5 @@ ipc.listen(ipc.messages.print, () => { toc.setVisibility(true) } - scrollTo(scrollPosition) + renderer.scrollTo(scrollPosition) }) diff --git a/app/lib/renderer/common.js b/app/lib/renderer/common.js index d8435c7..394edc6 100644 --- a/app/lib/renderer/common.js +++ b/app/lib/renderer/common.js @@ -1,7 +1,22 @@ let _document +function contentElement() { + return _document.getElementById("content-body") +} + exports.init = document => (_document = document) -exports.contentElement = () => _document.getElementById("content-body") +exports.contentElement = contentElement exports.rawTextElement = () => _document.getElementById("raw-text") + +exports.scrollTo = position => (contentElement().scrollTop = position) + +exports.elementYPosition = element => { + const containerElement = renderer.contentElement().children[0] + return ( + element.getBoundingClientRect().top - + (containerElement.getBoundingClientRect().top - + Number(containerElement.style.paddingTop.replace("px", ""))) + ) +} diff --git a/app/lib/search/searchRenderer.js b/app/lib/search/searchRenderer.js index 217a8e2..9274414 100644 --- a/app/lib/search/searchRenderer.js +++ b/app/lib/search/searchRenderer.js @@ -19,7 +19,7 @@ let _searchIndex = 0 let _searchResultCount = 0 // A String.prototype.replaceAll() alternative, that is case insensitive at the input -// ("pattern parameter") but preserves the case during replacing. +// ("pattern parameter") but preserves the upper/lower case during replacing. function replaceAll(text, pattern, replacement) { const output = [] let lastIndex = text.length - 1 @@ -27,8 +27,7 @@ function replaceAll(text, pattern, replacement) { // Based on https://stackoverflow.com/a/1499916 (Remove HTML Tags in Javascript with Regex) const tagMatches = [...text.matchAll(/(<([^>]+)>)/g)] - const matches = [...text.matchAll(pattern)] - for (const match of matches.toReversed()) { + for (const match of [...text.matchAll(pattern)].toReversed()) { const term = match[0] if ( tagMatches.some(tagMatch => { @@ -47,7 +46,7 @@ function replaceAll(text, pattern, replacement) { lastIndex = match.index } output.push(text.substring(0, lastIndex)) - return output.reverse().join("") + return output.toReversed().join("") } function deactivate() { @@ -131,4 +130,24 @@ exports.highlightTerm = () => { } } +exports.scrollToResult = () => { + if (!_isActive) { + return + } + + const resultElement = _document.getElementById(SELECTED_SEARCH_RESULT_ID) + const resultElementPosition = renderer.elementYPosition(resultElement) + + const contentElement = renderer.contentElement() + const scrollPosition = contentElement.scrollTop + + if ( + resultElementPosition < scrollPosition || + resultElementPosition + resultElement.clientHeight > + scrollPosition + contentElement.clientHeight + ) { + renderer.scrollTo(resultElementPosition) + } +} + exports.deactivate = deactivate