From 7621db10e90fdfed7782d864cd810c0a4220c7e7 Mon Sep 17 00:00:00 2001 From: mantou132 <709922234@qq.com> Date: Fri, 5 Jan 2024 15:20:39 +0800 Subject: [PATCH] [gem] Support scroll position restore Break change: - `GemRouteElement.locationStore` update by location hash --- .../duoyun-ui/src/elements/page-loadbar.ts | 2 + packages/duoyun-ui/src/lib/utils.ts | 14 ++- .../gem-book/docs/zh/002-guide/003-cli.md | 2 +- packages/gem-book/docs/zh/003-plugins.md | 4 +- packages/gem-book/src/element/elements/404.ts | 7 +- .../gem-book/src/element/elements/main.ts | 69 ++++++++------- .../gem-book/src/element/elements/meta.ts | 17 ++-- .../gem-book/src/element/elements/plugin.ts | 18 +++- .../gem-book/src/element/elements/sidebar.ts | 7 +- packages/gem-book/src/element/helper/theme.ts | 10 +-- packages/gem-book/src/element/index.ts | 35 +++----- packages/gem-book/src/element/lib/utils.ts | 10 --- packages/gem-book/src/element/store.ts | 15 ++-- packages/gem-book/src/plugins/api.ts | 1 + packages/gem-book/src/plugins/comment.ts | 16 ++-- packages/gem-book/src/plugins/raw.ts | 5 ++ packages/gem-examples/src/ref-route/index.ts | 2 +- .../gem/docs/en/004-blog/005-improve-route.md | 52 ++++++++--- .../gem/docs/zh/004-blog/005-improve-route.md | 61 +++++++++---- packages/gem/src/elements/base/route.ts | 87 +++++++++++++------ packages/gem/src/helper/i18n.ts | 2 +- packages/gem/src/lib/element.ts | 14 +-- packages/gem/src/lib/history.ts | 36 ++++++-- packages/gem/src/lib/utils.ts | 9 +- 24 files changed, 307 insertions(+), 188 deletions(-) diff --git a/packages/duoyun-ui/src/elements/page-loadbar.ts b/packages/duoyun-ui/src/elements/page-loadbar.ts index c85a687a..55a68c54 100644 --- a/packages/duoyun-ui/src/elements/page-loadbar.ts +++ b/packages/duoyun-ui/src/elements/page-loadbar.ts @@ -38,6 +38,7 @@ export class DuoyunPageLoadbarElement extends GemElement { static instance?: DuoyunPageLoadbarElement; static timer = 0; + /**在延时时间内结束将不会显示加载条 */ static async start({ delay = 100 }: { delay?: number } = {}) { clearInterval(Loadbar.timer); Loadbar.timer = window.setTimeout(() => { @@ -51,6 +52,7 @@ export class DuoyunPageLoadbarElement extends GemElement { } static async end() { + // 能同时取消 setTimeout ID clearInterval(Loadbar.timer); const instance = Loadbar.instance; if (instance) { diff --git a/packages/duoyun-ui/src/lib/utils.ts b/packages/duoyun-ui/src/lib/utils.ts index 5d30d2b3..a4a4aefa 100644 --- a/packages/duoyun-ui/src/lib/utils.ts +++ b/packages/duoyun-ui/src/lib/utils.ts @@ -377,7 +377,15 @@ export function useCacheStore>( } /**@deprecated use `useCacheStore` */ -export const createCacheStore = (...args: Parameters) => { - const [store, , save] = useCacheStore(...args); +export function createCacheStore>( + storageKey: string, + initStore: T, + options?: { + cacheExcludeKeys?: (keyof T)[]; + prefix?: string | (() => string | undefined); + depStore?: Store; + }, +) { + const [store, , save] = useCacheStore(storageKey, initStore, options); return [store, save] as const; -}; +} diff --git a/packages/gem-book/docs/zh/002-guide/003-cli.md b/packages/gem-book/docs/zh/002-guide/003-cli.md index 41519e37..7c59d78a 100644 --- a/packages/gem-book/docs/zh/002-guide/003-cli.md +++ b/packages/gem-book/docs/zh/002-guide/003-cli.md @@ -8,7 +8,7 @@ npx gem-book -h `gem-book` 命令会自动从项目根目录查找配置文件 `gem-book.cli.{js|json|mjs}`,支持大部分命令行选项(同时提供时合并命令行选项),例如: - + > [!TIP] > 如果用 `json` 格式,可以添加 `"$schema": "https://unpkg.com/gem-book/schema.json"` 以获取类型提示, diff --git a/packages/gem-book/docs/zh/003-plugins.md b/packages/gem-book/docs/zh/003-plugins.md index 7e13d2bb..12904b27 100644 --- a/packages/gem-book/docs/zh/003-plugins.md +++ b/packages/gem-book/docs/zh/003-plugins.md @@ -38,12 +38,12 @@ yarn add gem-book 用于显示远端代码,如果提供的 `src` 只包含路径,则会从当前项目的 GitHub 上读取内容(受 [`sourceDir`](./002-guide/003-cli.md#--source-dir),[`sourceBranch`](./002-guide/003-cli.md#--source-branch) 影响),比如: - + ```md - + ``` ## `` diff --git a/packages/gem-book/src/element/elements/404.ts b/packages/gem-book/src/element/elements/404.ts index edfe6d23..6f175aaa 100644 --- a/packages/gem-book/src/element/elements/404.ts +++ b/packages/gem-book/src/element/elements/404.ts @@ -1,7 +1,7 @@ -import { connectStore, customElement, GemElement, history, html } from '@mantou/gem'; +import { connectStore, customElement, GemElement, html } from '@mantou/gem'; import { getGithubPath, getUserLink } from '../lib/utils'; -import { bookStore } from '../store'; +import { bookStore, locationStore } from '../store'; import { selfI18n } from '../helper/i18n'; import { icons } from './icons'; @@ -14,8 +14,7 @@ import '@mantou/gem/elements/title'; export class Meta extends GemElement { #getMdFullPath = () => { const { links = [] } = bookStore; - const { path } = history.getParams(); - const parts = path.replace(/\/$/, '').split('/'); + const parts = locationStore.path.replace(/\/$/, '').split('/'); const newFile = parts.pop(); const parentPath = parts.join('/'); const link = links.find(({ originLink }) => getUserLink(originLink).startsWith(parentPath)); diff --git a/packages/gem-book/src/element/elements/main.ts b/packages/gem-book/src/element/elements/main.ts index d3c93049..54330a51 100644 --- a/packages/gem-book/src/element/elements/main.ts +++ b/packages/gem-book/src/element/elements/main.ts @@ -1,4 +1,13 @@ -import { html, GemElement, customElement, css, property, createCSSSheet, adoptedStyle, history } from '@mantou/gem'; +import { + html, + GemElement, + customElement, + css, + property, + createCSSSheet, + adoptedStyle, + connectStore, +} from '@mantou/gem'; import { Renderer, parse } from 'marked'; import { mediaQuery } from '@mantou/gem/helper/mediaquery'; @@ -52,9 +61,10 @@ const linkStyle = css` @customElement('gem-book-main') @adoptedStyle(style) +@connectStore(locationStore) export class Main extends GemElement { - @property content: string; - @property renderer: Renderer; + @property content?: string; + @property renderer?: Renderer; // homepage/footer 等内置元素渲染在 main 前面,不能使用自定义渲染器 static instance?: Main; @@ -75,29 +85,23 @@ export class Main extends GemElement { constructor() { super(); Main.instance = this; - this.memo(() => { - const [, , _sToken, _frontmatter, _eToken, mdBody] = - this.content.match(/^(([\r\n\s]*---\s*(?:\r\n|\n))(.*?)((?:\r\n|\n)---\s*(?:\r\n|\n)?))?(.*)$/s) || []; - this.#content = mdBody; - }); + this.memo( + () => { + const [, , _sToken, _frontmatter, _eToken, mdBody = ''] = + this.content?.match(/^(([\r\n\s]*---\s*(?:\r\n|\n))(.*?)((?:\r\n|\n)---\s*(?:\r\n|\n)?))?(.*)$/s) || []; + this.#content = Main.parseMarkdown(mdBody); + }, + () => [this.content], + ); } - #content = ''; + #content: Element[] = []; - #hashChangeHandle = () => { - const { hash, path } = history.getParams(); - // 确保是页内跳转或者新页(mounted)跳转 - if (hash && path === locationStore.path) { - this.shadowRoot?.querySelector(`[id="${decodeURIComponent(hash.slice(1))}"]`)?.scrollIntoView({ - block: 'start', - }); - } - }; - - #updateToc = () => + #updateToc = () => { updateTocStore({ elements: [...this.shadowRoot!.querySelectorAll('h2,h3')], }); + }; render() { return html` @@ -315,7 +319,7 @@ export class Main extends GemElement { } } - ${Main.parseMarkdown(this.#content)} + ${this.#content} @@ -325,23 +329,30 @@ export class Main extends GemElement { mounted() { this.effect( () => { + checkBuiltInPlugin(this.shadowRoot!); + this.#updateToc(); + const mo = new MutationObserver(this.#updateToc); mo.observe(this.shadowRoot!, { childList: true, subtree: true, }); - checkBuiltInPlugin(this.shadowRoot!); - this.#hashChangeHandle(); - window.addEventListener('hashchange', this.#hashChangeHandle); - - return () => { - window.removeEventListener('hashchange', this.#hashChangeHandle); - mo.disconnect(); - }; + return () => mo.disconnect(); }, () => [this.content], ); + + this.effect( + ([hash]) => { + if (hash) { + this.shadowRoot?.querySelector(`[id="${hash.slice(1)}"]`)?.scrollIntoView({ + block: 'start', + }); + } + }, + () => [locationStore.hash], + ); } } diff --git a/packages/gem-book/src/element/elements/meta.ts b/packages/gem-book/src/element/elements/meta.ts index b33d7655..56a77036 100644 --- a/packages/gem-book/src/element/elements/meta.ts +++ b/packages/gem-book/src/element/elements/meta.ts @@ -1,18 +1,25 @@ -import { connectStore, customElement, GemElement, html, history } from '@mantou/gem'; +import { connectStore, customElement, GemElement, html } from '@mantou/gem'; import { mediaQuery } from '@mantou/gem/helper/mediaquery'; -import { getAlternateUrl, getURL } from '../lib/utils'; -import { bookStore } from '../store'; +import { getRemotePath, getURL } from '../lib/utils'; +import { bookStore, locationStore } from '../store'; import '@mantou/gem/elements/reflect'; +function getAlternateUrl(lang: string, pathname?: string) { + const { origin } = location; + const { path, query, hash } = locationStore; + const fullPath = getRemotePath(pathname || path, lang); + if (pathname) return `${origin}${fullPath}`; + return `${origin}${fullPath}${query}${hash}`; +} + @customElement('gem-book-meta') @connectStore(bookStore) export class Meta extends GemElement { render() { const { langList, lang = '', routes, homePage, getCurrentLink, currentLinks } = bookStore; - const { path } = history.getParams(); - const route = routes?.find((route) => route.pattern === path && route.redirect); + const route = routes?.find((route) => route.pattern === locationStore.path && route.redirect); const canonicalLink = getAlternateUrl( lang && langList && !location.pathname.startsWith(`/${lang}`) ? langList[0].code : lang, route?.redirect, diff --git a/packages/gem-book/src/element/elements/plugin.ts b/packages/gem-book/src/element/elements/plugin.ts index abde0237..1e965928 100644 --- a/packages/gem-book/src/element/elements/plugin.ts +++ b/packages/gem-book/src/element/elements/plugin.ts @@ -5,7 +5,7 @@ import { mediaQuery } from '@mantou/gem/helper/mediaquery'; import { logger } from '@mantou/gem/helper/logger'; import { theme } from '../helper/theme'; -import { bookStore } from '../store'; +import { bookStore, locationStore } from '../store'; import { BookConfig } from '../../common/config'; import { icons } from '../elements/icons'; @@ -17,6 +17,7 @@ export class GemBookPluginElement extends GemElement { static theme = theme; static icons = icons; static mediaQuery = mediaQuery; + static locationStore = locationStore; static config = new Proxy>( {}, { @@ -56,6 +57,21 @@ export class GemBookPluginElement extends GemElement { return bookStore.getCurrentLink?.(); } + static caches?: Map; + + cacheState(getDeps: () => string[]) { + if (!this.state) throw new Error('Only cache state'); + const cons = this.constructor as typeof GemBookPluginElement; + const caches = cons.caches || (cons.caches = new Map()); + this.memo( + () => { + Object.assign(this.state!, caches.get(getDeps().join())); + return () => caches.set(getDeps().join(), this.state); + }, + () => getDeps(), + ); + } + @globalemitter error: Emitter = logger.error; /**获取资源的远端 GitHub raw 地址,如果使用 `DEV_MODE`,则返回本机服务的 URL */ diff --git a/packages/gem-book/src/element/elements/sidebar.ts b/packages/gem-book/src/element/elements/sidebar.ts index 71353296..d7e8d2e6 100644 --- a/packages/gem-book/src/element/elements/sidebar.ts +++ b/packages/gem-book/src/element/elements/sidebar.ts @@ -10,7 +10,6 @@ import { useStore, refobject, RefObject, - history, } from '@mantou/gem'; import { mediaQuery } from '@mantou/gem/helper/mediaquery'; @@ -18,7 +17,6 @@ import { NavItem } from '../../common/config'; import { capitalize, isSameOrigin } from '../lib/utils'; import { theme } from '../helper/theme'; import { bookStore, locationStore } from '../store'; -import { GemBookElement } from '..'; import { icons } from './icons'; import { tocStore } from './toc'; @@ -63,7 +61,6 @@ export class SideBar extends GemElement { { type = 'file', link, title, children, sidebarIgnore }: NavItem, isTop = false, ): TemplateResult | null => { - const { path } = history.getParams(); const { homePage, config } = bookStore; const { homeMode, onlyFile } = config || {}; if (sidebarIgnore || (homeMode && homePage === link)) { @@ -91,7 +88,7 @@ export class SideBar extends GemElement { > ${title ? capitalize(title) : 'No title'} - ${!onlyFile && path === link + ${!onlyFile && locationStore.path === link ? html`