From 436343ce1b9e84a1359fc27d52770d28e41dc3ab Mon Sep 17 00:00:00 2001 From: linonetwo Date: Fri, 22 Sep 2023 23:14:22 +0800 Subject: [PATCH] refactor: use history mechanism for block level navigation --- core/modules/story.js | 4 ++-- core/modules/storyviews/classic.js | 15 ++++++++++++++- core/modules/utils/dom/scroller.js | 20 +++++++++++++++++++- core/modules/widgets/anchor.js | 5 +++-- core/modules/widgets/navigator.js | 8 ++++---- 5 files changed, 42 insertions(+), 10 deletions(-) diff --git a/core/modules/story.js b/core/modules/story.js index 16ff5a74a..c7b09ed5d 100644 --- a/core/modules/story.js +++ b/core/modules/story.js @@ -90,12 +90,12 @@ Story.prototype.saveStoryList = function(storyList) { )); }; -Story.prototype.addToHistory = function(navigateTo,navigateFromClientRect) { +Story.prototype.addToHistory = function(navigateTo,fromPageRect,anchor) { var titles = $tw.utils.isArray(navigateTo) ? navigateTo : [navigateTo]; // Add a new record to the top of the history stack var historyList = this.wiki.getTiddlerData(this.historyTitle,[]); $tw.utils.each(titles,function(title) { - historyList.push({title: title, fromPageRect: navigateFromClientRect}); + historyList.push({title: title, fromPageRect: fromPageRect, anchor: anchor}); }); this.wiki.setTiddlerData(this.historyTitle,historyList,{"current-tiddler": titles[titles.length-1]}); }; diff --git a/core/modules/storyviews/classic.js b/core/modules/storyviews/classic.js index c2848c435..0b9d554c0 100644 --- a/core/modules/storyviews/classic.js +++ b/core/modules/storyviews/classic.js @@ -26,13 +26,26 @@ ClassicStoryView.prototype.navigateTo = function(historyInfo) { } var listItemWidget = this.listWidget.children[listElementIndex], targetElement = listItemWidget.findFirstDomNode(); + // If anchor is provided, find the element the anchor pointing to + var foundAnchor = false; + if(targetElement && historyInfo.anchor) { + var anchorElement = targetElement.querySelector("[data-anchor-id='" + historyInfo.anchor + "']"); + if(anchorElement) { + targetElement = anchorElement.parentNode; + var isBefore = anchorElement.dataset.anchorPreviousSibling === "true"; + if(isBefore) { + targetElement = targetElement.previousSibling; + } + foundAnchor = true; + } + } // Abandon if the list entry isn't a DOM element (it might be a text node) if(!targetElement || targetElement.nodeType === Node.TEXT_NODE) { return; } if(duration) { // Scroll the node into view - this.listWidget.dispatchEvent({type: "tm-scroll", target: targetElement}); + this.listWidget.dispatchEvent({type: "tm-scroll", target: targetElement, highlight: foundAnchor}); } else { targetElement.scrollIntoView(); } diff --git a/core/modules/utils/dom/scroller.js b/core/modules/utils/dom/scroller.js index 905bb2750..4166507d6 100644 --- a/core/modules/utils/dom/scroller.js +++ b/core/modules/utils/dom/scroller.js @@ -49,7 +49,9 @@ Handle an event */ PageScroller.prototype.handleEvent = function(event) { if(event.type === "tm-scroll") { - var options = {}; + var options = { + highlight: event.highlight, + }; if($tw.utils.hop(event.paramObject,"animationDuration")) { options.animationDuration = event.paramObject.animationDuration; } @@ -65,14 +67,21 @@ PageScroller.prototype.handleEvent = function(event) { /* Handle a scroll event hitting the page document + +options: +- animationDuration: total time of scroll animation +- highlight: highlight the element after scrolling, to make it evident. Usually to focus an anchor in the middle of the tiddler. */ PageScroller.prototype.scrollIntoView = function(element,callback,options) { var self = this, duration = $tw.utils.hop(options,"animationDuration") ? parseInt(options.animationDuration) : $tw.utils.getAnimationDuration(), + highlight = options.highlight || false, srcWindow = element ? element.ownerDocument.defaultView : window; // Now get ready to scroll the body this.cancelScroll(srcWindow); this.startTime = Date.now(); + // toggle class to allow trigger the highlight animation + $tw.utils.removeClass(element,"tc-focus-highlight"); // Get the height of any position:fixed toolbars var toolbar = srcWindow.document.querySelector(".tc-adjust-top-of-scroll"), offset = 0; @@ -121,6 +130,15 @@ PageScroller.prototype.scrollIntoView = function(element,callback,options) { srcWindow.scrollTo(scrollPosition.x + (endX - scrollPosition.x) * t,scrollPosition.y + (endY - scrollPosition.y) * t); if(t < 1) { self.idRequestFrame = self.requestAnimationFrame.call(srcWindow,drawFrame); + } else { + // the animation is end. + if(highlight) { + element.focus({ focusVisible: true }); + // Using setTimeout to ensure the removal takes effect before adding the class again. + setTimeout(function() { + $tw.utils.addClass(element,"tc-focus-highlight"); + }, 50); + } } }; drawFrame(); diff --git a/core/modules/widgets/anchor.js b/core/modules/widgets/anchor.js index 201caa222..c9c6af091 100644 --- a/core/modules/widgets/anchor.js +++ b/core/modules/widgets/anchor.js @@ -23,8 +23,9 @@ AnchorWidget.prototype.render = function(parent,nextSibling) { this.idNode = this.document.createElement("span"); this.idNode.setAttribute("data-anchor-id",this.id); this.idNode.setAttribute("data-anchor-title",this.tiddlerTitle); - if(this.before) { - this.idNode.setAttribute("data-before","true"); + // if the actual block is before this node, we need to add a flag to the node + if(this.previousSibling) { + this.idNode.setAttribute("data-anchor-previous-sibling","true"); } this.idNode.className = "tc-anchor"; parent.insertBefore(this.idNode,nextSibling); diff --git a/core/modules/widgets/navigator.js b/core/modules/widgets/navigator.js index 84d69dd6b..423f65459 100755 --- a/core/modules/widgets/navigator.js +++ b/core/modules/widgets/navigator.js @@ -138,9 +138,10 @@ NavigatorWidget.prototype.addToStory = function(title,fromTitle) { Add a new record to the top of the history stack title: a title string or an array of title strings fromPageRect: page coordinates of the origin of the navigation +anchor:optional anchor id in this tiddler */ -NavigatorWidget.prototype.addToHistory = function(title,fromPageRect) { - this.story.addToHistory(title,fromPageRect,this.historyTitle); +NavigatorWidget.prototype.addToHistory = function(title,fromPageRect,anchor) { + this.story.addToHistory(title,fromPageRect,anchor); }; /* @@ -150,9 +151,8 @@ NavigatorWidget.prototype.handleNavigateEvent = function(event) { event = $tw.hooks.invokeHook("th-navigating",event); if(event.navigateTo) { this.addToStory(event.navigateTo,event.navigateFromTitle); - event = $tw.hooks.invokeHook("th-navigating-add-history",event); if(!event.navigateSuppressNavigation) { - this.addToHistory(event.navigateTo,event.navigateFromClientRect); + this.addToHistory(event.navigateTo,event.navigateFromClientRect,event.toAnchor); } } $tw.hooks.invokeHook("th-navigated",event);