From 5c748355c56c38e1e8ab911ef1d50891ef996aa4 Mon Sep 17 00:00:00 2001 From: sanyuan <494130947@qq.com> Date: Sat, 17 Sep 2022 11:59:01 +0800 Subject: [PATCH] feat: impl useAsideAnchor to optimize UX --- .eslintignore | 2 +- .eslintrc.js => .eslintrc.cjs | 6 +- package.json | 4 + pnpm-lock.yaml | 60 +++++++++++++ src/client/runtime/app.tsx | 2 +- .../components/Aside/index.module.scss | 2 +- .../theme-default/components/Aside/index.tsx | 83 +++-------------- .../theme-default/layout/DocLayout/index.tsx | 2 - src/client/theme-default/logic/index.ts | 2 + .../theme-default/logic/useAsideAnchor.ts | 89 +++++++++++++++++++ src/client/theme-default/styles/base.css | 1 - src/client/type.d.ts | 2 +- src/node/plugin-island/config.ts | 3 +- src/node/plugin-island/index.ts | 2 +- src/node/plugin-island/islandTransform.ts | 2 +- src/node/plugin-mdx/rehypePlugins/shiki.ts | 2 +- 16 files changed, 179 insertions(+), 85 deletions(-) rename .eslintrc.js => .eslintrc.cjs (69%) create mode 100644 src/client/theme-default/logic/useAsideAnchor.ts diff --git a/.eslintignore b/.eslintignore index 8e17b81a..f1ed9439 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,5 +2,5 @@ **/node_modules/** package.json *.d.ts -.eslintrc.js +.eslintrc.cjs tsconfig.json diff --git a/.eslintrc.js b/.eslintrc.cjs similarity index 69% rename from .eslintrc.js rename to .eslintrc.cjs index a534ae50..d25e9349 100644 --- a/.eslintrc.js +++ b/.eslintrc.cjs @@ -18,13 +18,15 @@ module.exports = { ecmaVersion: 'latest', sourceType: 'module' }, - plugins: ['react', '@typescript-eslint', 'prettier'], + plugins: ['react', '@typescript-eslint', 'prettier', 'react-hooks'], rules: { 'prettier/prettier': 'error', quotes: ['error', 'single'], semi: ['error', 'always'], 'react/react-in-jsx-scope': 'off', - '@typescript-eslint/no-non-null-assertion': 'off' + '@typescript-eslint/no-non-null-assertion': 'off', + 'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks + 'react-hooks/exhaustive-deps': 'warn' // Checks effect dependencies }, settings: { react: { diff --git a/package.json b/package.json index 61dc4fb3..a08086b2 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,11 @@ "@typescript-eslint/parser": "^5.36.1", "es-module-lexer": "^1.0.3", "eslint": "^8.23.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-react": "^7.31.1", + "eslint-plugin-react-hooks": "^4.6.0", + "prettier": "^2.7.1", "resolve": "^1.22.1", "rollup": "^2.78.1", "tsup": "^6.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 753978e9..8e305382 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,7 +36,10 @@ specifiers: es-module-lexer: ^1.0.3 esbuild: 0.15.5 eslint: ^8.23.0 + eslint-config-prettier: ^8.5.0 + eslint-plugin-prettier: ^4.2.1 eslint-plugin-react: ^7.31.1 + eslint-plugin-react-hooks: ^4.6.0 fast-glob: 3.2.11 fs-extra: 10.1.0 github-slugger: ^1.4.0 @@ -47,6 +50,7 @@ specifiers: ora: ^6.1.2 picocolors: ^1.0.0 polka: ^0.5.2 + prettier: ^2.7.1 react: ^18.2.0 react-dom: ^18.2.0 react-helmet-async: ^1.3.0 @@ -140,7 +144,11 @@ devDependencies: '@typescript-eslint/parser': 5.36.1_sorwav4hsh5vncerguqybud76i es-module-lexer: 1.0.3 eslint: 8.23.0 + eslint-config-prettier: 8.5.0_eslint@8.23.0 + eslint-plugin-prettier: 4.2.1_tgumt6uwl2md3n6uqnggd6wvce eslint-plugin-react: 7.31.1_eslint@8.23.0 + eslint-plugin-react-hooks: 4.6.0_eslint@8.23.0 + prettier: 2.7.1 resolve: 1.22.1 rollup: 2.78.1 tsup: 6.2.3_typescript@4.7.4 @@ -1890,6 +1898,41 @@ packages: engines: {node: '>=12'} dev: false + /eslint-config-prettier/8.5.0_eslint@8.23.0: + resolution: {integrity: sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + dependencies: + eslint: 8.23.0 + dev: true + + /eslint-plugin-prettier/4.2.1_tgumt6uwl2md3n6uqnggd6wvce: + resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==} + engines: {node: '>=12.0.0'} + peerDependencies: + eslint: '>=7.28.0' + eslint-config-prettier: '*' + prettier: '>=2.0.0' + peerDependenciesMeta: + eslint-config-prettier: + optional: true + dependencies: + eslint: 8.23.0 + eslint-config-prettier: 8.5.0_eslint@8.23.0 + prettier: 2.7.1 + prettier-linter-helpers: 1.0.0 + dev: true + + /eslint-plugin-react-hooks/4.6.0_eslint@8.23.0: + resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + dependencies: + eslint: 8.23.0 + dev: true + /eslint-plugin-react/7.31.1_eslint@8.23.0: resolution: {integrity: sha512-j4/2xWqt/R7AZzG8CakGHA6Xa/u7iR8Q3xCxY+AUghdT92bnIDOBEefV456OeH0QvBcroVc0eyvrrLSyQGYIfg==} engines: {node: '>=4'} @@ -2106,6 +2149,10 @@ packages: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true + /fast-diff/1.2.0: + resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==} + dev: true + /fast-json-stable-stringify/2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} dev: true @@ -3642,6 +3689,19 @@ packages: engines: {node: '>= 0.8.0'} dev: true + /prettier-linter-helpers/1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + dependencies: + fast-diff: 1.2.0 + dev: true + + /prettier/2.7.1: + resolution: {integrity: sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==} + engines: {node: '>=10.13.0'} + hasBin: true + dev: true + /prop-types/15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} dependencies: diff --git a/src/client/runtime/app.tsx b/src/client/runtime/app.tsx index 30c45dd9..44573df6 100644 --- a/src/client/runtime/app.tsx +++ b/src/client/runtime/app.tsx @@ -39,8 +39,8 @@ export function App({ useLayoutEffect(() => { async function refetchData() { - const pageData = await waitForApp(pathname); try { + const pageData = await waitForApp(pathname); setPageData(pageData); } catch (e) { console.log(e); diff --git a/src/client/theme-default/components/Aside/index.module.scss b/src/client/theme-default/components/Aside/index.module.scss index 485858a5..0207a3b3 100644 --- a/src/client/theme-default/components/Aside/index.module.scss +++ b/src/client/theme-default/components/Aside/index.module.scss @@ -51,7 +51,7 @@ } .outline-link:hover, -.outline-link.active { +.outline-link:global(.aside-active) { color: var(--island-c-text-1); transition: color 0.25s; } diff --git a/src/client/theme-default/components/Aside/index.tsx b/src/client/theme-default/components/Aside/index.tsx index 1f266d79..1003872a 100644 --- a/src/client/theme-default/components/Aside/index.tsx +++ b/src/client/theme-default/components/Aside/index.tsx @@ -1,25 +1,20 @@ import { useState, useEffect, useRef } from 'react'; import styles from './index.module.scss'; -import { throttle } from 'lodash-es'; import { ComponentPropsWithIsland, Header } from 'shared/types/index'; - -function isBottom() { - return ( - document.documentElement.scrollTop + window.innerHeight >= - document.documentElement.scrollHeight - ); -} +import { useAsideAnchor } from '../../logic'; export function Aside( props: ComponentPropsWithIsland<{ headers: Header[]; pagePath: string }> ) { const [headers, setHeaders] = useState(props.headers || []); - // For outline text highlight - const [activeIndex, setActiveIndex] = useState(0); const markerRef = useRef(null); - const SCROLL_INTO_HEIGHT = 150; - const NAV_HEIGHT = 72; + const asideRef = useRef(null); + const prevActiveLinkRef = useRef(null); + + useEffect(() => { + setHeaders(props.headers); + }, [props.headers]); useEffect(() => { // handle hmr @@ -32,57 +27,9 @@ export function Aside( }); }); } - }, []); - - function setActiveLink() { - const links = document.querySelectorAll( - '.island-doc .header-anchor' - ); - if (isBottom()) { - setActiveIndex(links.length - 1); - markerRef.current!.style.top = `${33 + (headers.length - 1) * 28}px`; - } else { - // Compute current index - for (let i = 0; i < links.length; i++) { - const topDistance = links[i].getBoundingClientRect().top; - if (topDistance > NAV_HEIGHT && topDistance < SCROLL_INTO_HEIGHT) { - const id = links[i].getAttribute('href'); - const index = headers.findIndex( - (item: any) => item.id === id?.slice(1) - ); - if (index > -1 && index !== activeIndex) { - setActiveIndex(index); - markerRef.current!.style.top = `${33 + index * 28}px`; - } else { - setActiveIndex(0); - markerRef.current!.style.top = '33px'; - } - break; - } - } - } - } - - useEffect(() => { - if (!headers.length && markerRef.current) { - markerRef.current.style.display = 'none'; - } - const onScroll = throttle( - function listen() { - if (!headers.length) { - return; - } - setActiveLink(); - }, - 100, - { trailing: true } - ); - window.addEventListener('scroll', onScroll); + }, [props.pagePath]); - return () => { - window.removeEventListener('scroll', onScroll); - }; - }, []); + useAsideAnchor(prevActiveLinkRef, headers, asideRef, markerRef); const renderHeader = (header: any, index: number) => { const isNested = header.depth > 2; @@ -90,15 +37,7 @@ export function Aside(
  • { - e.preventDefault(); - setTimeout(() => { - setActiveLink(); - }, 300); - }} - className={`${styles.outlineLink} ${ - index == activeIndex ? styles.active : '' - } ${isNested ? styles.nested : ''}`} + className={`${styles.outlineLink} ${isNested ? styles.nested : ''}`} > {header.text} @@ -109,7 +48,7 @@ export function Aside( return (
    -
    +
    ON THIS PAGE