Просмотр исходного кода

Merge pull request #7354 from weseek/imprv/anchor-link

imprv: Anchor link
Yuki Takei 3 лет назад
Родитель
Сommit
c35c6d0b68

+ 0 - 5
packages/app/src/client/util/smooth-scroll.ts

@@ -1,5 +0,0 @@
-// option object for react-scroll
-export const DEFAULT_AUTO_SCROLL_OPTS = {
-  smooth: 'easeOutQuint',
-  duration: 1200,
-};

+ 2 - 5
packages/app/src/components/ContentLinkButtons.tsx

@@ -3,17 +3,14 @@ import React from 'react';
 import { IUserHasId } from '@growi/core';
 import { IUserHasId } from '@growi/core';
 import { Link as ScrollLink } from 'react-scroll';
 import { Link as ScrollLink } from 'react-scroll';
 
 
-import { DEFAULT_AUTO_SCROLL_OPTS } from '~/client/util/smooth-scroll';
 import { RecentlyCreatedIcon } from '~/components/Icons/RecentlyCreatedIcon';
 import { RecentlyCreatedIcon } from '~/components/Icons/RecentlyCreatedIcon';
 
 
 import styles from './ContentLinkButtons.module.scss';
 import styles from './ContentLinkButtons.module.scss';
 
 
-const OFFSET = -120;
-
 const BookMarkLinkButton = React.memo(() => {
 const BookMarkLinkButton = React.memo(() => {
 
 
   return (
   return (
-    <ScrollLink to="bookmarks-list" offset={OFFSET} {...DEFAULT_AUTO_SCROLL_OPTS}>
+    <ScrollLink to="bookmarks-list" offset={-120}>
       <button
       <button
         type="button"
         type="button"
         className="btn btn-outline-secondary btn-sm px-2"
         className="btn btn-outline-secondary btn-sm px-2"
@@ -30,7 +27,7 @@ BookMarkLinkButton.displayName = 'BookMarkLinkButton';
 const RecentlyCreatedLinkButton = React.memo(() => {
 const RecentlyCreatedLinkButton = React.memo(() => {
 
 
   return (
   return (
-    <ScrollLink to="recently-created-list" offset={OFFSET} {...DEFAULT_AUTO_SCROLL_OPTS}>
+    <ScrollLink to="recently-created-list" offset={-120}>
       <button
       <button
         type="button"
         type="button"
         className="btn btn-outline-secondary btn-sm px-3"
         className="btn btn-outline-secondary btn-sm px-3"

+ 1 - 2
packages/app/src/components/Fab.tsx

@@ -6,7 +6,6 @@ import { animateScroll } from 'react-scroll';
 import { useRipple } from 'react-use-ripple';
 import { useRipple } from 'react-use-ripple';
 import StickyEvents from 'sticky-events';
 import StickyEvents from 'sticky-events';
 
 
-import { DEFAULT_AUTO_SCROLL_OPTS } from '~/client/util/smooth-scroll';
 import { usePageCreateModal } from '~/stores/modal';
 import { usePageCreateModal } from '~/stores/modal';
 import { useCurrentPagePath } from '~/stores/page';
 import { useCurrentPagePath } from '~/stores/page';
 import { useIsAbleToChangeEditorMode } from '~/stores/ui';
 import { useIsAbleToChangeEditorMode } from '~/stores/ui';
@@ -96,7 +95,7 @@ export const Fab = (): JSX.Element => {
 
 
   const ScrollToTopButton = useCallback(() => {
   const ScrollToTopButton = useCallback(() => {
     const clickHandler = () => {
     const clickHandler = () => {
-      animateScroll.scrollToTop(DEFAULT_AUTO_SCROLL_OPTS);
+      animateScroll.scrollToTop({ duration: 200 });
     };
     };
 
 
     return (
     return (

+ 1 - 2
packages/app/src/components/PageSideContents.tsx

@@ -4,7 +4,6 @@ import { IPageHasId, pagePathUtils } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import { Link } from 'react-scroll';
 import { Link } from 'react-scroll';
 
 
-import { DEFAULT_AUTO_SCROLL_OPTS } from '~/client/util/smooth-scroll';
 import { useDescendantsPageListModal } from '~/stores/modal';
 import { useDescendantsPageListModal } from '~/stores/modal';
 
 
 import CountBadge from './Common/CountBadge';
 import CountBadge from './Common/CountBadge';
@@ -57,7 +56,7 @@ export const PageSideContents = (props: PageSideContentsProps): JSX.Element => {
       {/* Comments */}
       {/* Comments */}
       { !isTopPagePath && (
       { !isTopPagePath && (
         <div className={`mt-2 grw-page-accessories-control ${styles['grw-page-accessories-control']}`}>
         <div className={`mt-2 grw-page-accessories-control ${styles['grw-page-accessories-control']}`}>
-          <Link to={'page-comments'} offset={-100} {...DEFAULT_AUTO_SCROLL_OPTS}>
+          <Link to={'page-comments'} offset={-120}>
             <button
             <button
               type="button"
               type="button"
               className="btn btn-block btn-outline-secondary grw-btn-page-accessories rounded-pill d-flex justify-content-between align-items-center"
               className="btn btn-block btn-outline-secondary grw-btn-page-accessories rounded-pill d-flex justify-content-between align-items-center"

+ 14 - 1
packages/app/src/components/ReactMarkdownComponents/Header.tsx

@@ -85,7 +85,7 @@ export const Header = (props: HeaderProps): JSX.Element => {
     activateByHash(window.location.href);
     activateByHash(window.location.href);
   }, [activateByHash]);
   }, [activateByHash]);
 
 
-  // update isActive when hash is changed
+  // update isActive when hash is changed by next router
   useEffect(() => {
   useEffect(() => {
     router.events.on('hashChangeComplete', activateByHash);
     router.events.on('hashChangeComplete', activateByHash);
 
 
@@ -94,6 +94,19 @@ export const Header = (props: HeaderProps): JSX.Element => {
     };
     };
   }, [activateByHash, router.events]);
   }, [activateByHash, router.events]);
 
 
+  // update isActive when hash is changed
+  useEffect(() => {
+    const activeByHashWrapper = (e: HashChangeEvent) => {
+      activateByHash(e.newURL);
+    };
+
+    window.addEventListener('hashchange', activeByHashWrapper);
+
+    return () => {
+      window.removeEventListener('hashchange', activeByHashWrapper);
+    };
+  }, [activateByHash, router.events]);
+
   const showEditButton = !isGuestUser && !isSharedUser && shareLinkId == null;
   const showEditButton = !isGuestUser && !isSharedUser && shareLinkId == null;
 
 
   return (
   return (

+ 1 - 8
packages/app/src/components/ReactMarkdownComponents/NextLink.tsx

@@ -1,7 +1,5 @@
 import Link, { LinkProps } from 'next/link';
 import Link, { LinkProps } from 'next/link';
-import { Link as ScrollLink } from 'react-scroll';
 
 
-import { DEFAULT_AUTO_SCROLL_OPTS } from '~/client/util/smooth-scroll';
 import { useSiteUrl } from '~/stores/context';
 import { useSiteUrl } from '~/stores/context';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
@@ -42,13 +40,8 @@ export const NextLink = ({
 
 
   // when href is an anchor link
   // when href is an anchor link
   if (isAnchorLink(href)) {
   if (isAnchorLink(href)) {
-    const to = href.slice(1);
     return (
     return (
-      <Link href={href} scroll={false}>
-        <ScrollLink href={href} to={to} className={className} offset={-100} {...DEFAULT_AUTO_SCROLL_OPTS}>
-          {children}
-        </ScrollLink>
-      </Link>
+      <a href={href} className={className}>{children}</a>
     );
     );
   }
   }
 
 

+ 4 - 1
packages/app/src/services/renderer/rehype-plugins/relative-links.ts

@@ -17,6 +17,9 @@ const defaultHrefResolver: IHrefResolver = (relativeHref, basePath) => {
   return relativeUrl.pathname;
   return relativeUrl.pathname;
 };
 };
 
 
+const isAnchorLink = (href: string): boolean => {
+  return href.toString().length > 0 && href[0] === '#';
+};
 
 
 export type RelativeLinksPluginParams = {
 export type RelativeLinksPluginParams = {
   pagePath?: string,
   pagePath?: string,
@@ -42,7 +45,7 @@ export const relativeLinks: Plugin<[RelativeLinksPluginParams]> = (options = {})
       }
       }
 
 
       const href = anchor.properties.href;
       const href = anchor.properties.href;
-      if (href == null || typeof href !== 'string' || isAbsolute(href)) {
+      if (href == null || typeof href !== 'string' || isAbsolute(href) || isAnchorLink(href)) {
         return;
         return;
       }
       }
 
 

+ 5 - 0
packages/app/src/services/renderer/renderer.tsx

@@ -122,6 +122,10 @@ const generateCommonOptions = (pagePath: string|undefined): RendererOptions => {
       pukiwikiLikeLinker,
       pukiwikiLikeLinker,
       growiDirective,
       growiDirective,
     ],
     ],
+    remarkRehypeOptions: {
+      clobberPrefix: 'mdcont-',
+      allowDangerousHtml: true,
+    },
     rehypePlugins: [
     rehypePlugins: [
       [relativeLinksByPukiwikiLikeLinker, { pagePath }],
       [relativeLinksByPukiwikiLikeLinker, { pagePath }],
       [relativeLinks, { pagePath }],
       [relativeLinks, { pagePath }],
@@ -324,6 +328,7 @@ export const generateSSRViewOptions = (
 
 
   // add rehype plugins
   // add rehype plugins
   rehypePlugins.push(
   rehypePlugins.push(
+    slug,
     [lsxGrowiPlugin.rehypePlugin, { pagePath, isSharedPage: config.isSharedPage }],
     [lsxGrowiPlugin.rehypePlugin, { pagePath, isSharedPage: config.isSharedPage }],
     rehypeSanitizePlugin,
     rehypeSanitizePlugin,
     katex,
     katex,

+ 2 - 0
packages/app/src/styles/organisms/_wiki.scss

@@ -30,6 +30,8 @@
     &:first-child {
     &:first-child {
       margin-top: 0;
       margin-top: 0;
     }
     }
+
+    scroll-margin-top: 120px;
   }
   }
 
 
   h1 {
   h1 {

+ 13 - 23
yarn.lock

@@ -4282,11 +4282,6 @@
   dependencies:
   dependencies:
     "@types/unist" "*"
     "@types/unist" "*"
 
 
-"@types/mdurl@^1.0.0":
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9"
-  integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==
-
 "@types/mime-types@^2.1.0":
 "@types/mime-types@^2.1.0":
   version "2.1.1"
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/@types/mime-types/-/mime-types-2.1.1.tgz#d9ba43490fa3a3df958759adf69396c3532cf2c1"
   resolved "https://registry.yarnpkg.com/@types/mime-types/-/mime-types-2.1.1.tgz#d9ba43490fa3a3df958759adf69396c3532cf2c1"
@@ -14440,18 +14435,15 @@ mdast-util-mdx-expression@^1.1.0:
     mdast-util-to-markdown "^1.0.0"
     mdast-util-to-markdown "^1.0.0"
 
 
 mdast-util-to-hast@^12.1.0:
 mdast-util-to-hast@^12.1.0:
-  version "12.1.2"
-  resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-12.1.2.tgz#5c793b04014746585254c7ce0bc2d117201a5d1d"
-  integrity sha512-Wn6Mcj04qU4qUXHnHpPATYMH2Jd8RlntdnloDfYLe1ErWRHo6+pvSl/DzHp6sCZ9cBSYlc8Sk8pbwb8xtUoQhQ==
+  version "12.3.0"
+  resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz#045d2825fb04374e59970f5b3f279b5700f6fb49"
+  integrity sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==
   dependencies:
   dependencies:
     "@types/hast" "^2.0.0"
     "@types/hast" "^2.0.0"
     "@types/mdast" "^3.0.0"
     "@types/mdast" "^3.0.0"
-    "@types/mdurl" "^1.0.0"
     mdast-util-definitions "^5.0.0"
     mdast-util-definitions "^5.0.0"
-    mdurl "^1.0.0"
-    micromark-util-sanitize-uri "^1.0.0"
+    micromark-util-sanitize-uri "^1.1.0"
     trim-lines "^3.0.0"
     trim-lines "^3.0.0"
-    unist-builder "^3.0.0"
     unist-util-generated "^2.0.0"
     unist-util-generated "^2.0.0"
     unist-util-position "^4.0.0"
     unist-util-position "^4.0.0"
     unist-util-visit "^4.0.0"
     unist-util-visit "^4.0.0"
@@ -14526,10 +14518,6 @@ mdast-util-wiki-link@^0.0.2:
     "@babel/runtime" "^7.12.1"
     "@babel/runtime" "^7.12.1"
     mdast-util-to-markdown "^0.6.5"
     mdast-util-to-markdown "^0.6.5"
 
 
-mdurl@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
-
 media-typer@0.3.0:
 media-typer@0.3.0:
   version "0.3.0"
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
   resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@@ -15013,6 +15001,15 @@ micromark-util-sanitize-uri@^1.0.0:
     micromark-util-encode "^1.0.0"
     micromark-util-encode "^1.0.0"
     micromark-util-symbol "^1.0.0"
     micromark-util-symbol "^1.0.0"
 
 
+micromark-util-sanitize-uri@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.1.0.tgz#f12e07a85106b902645e0364feb07cf253a85aee"
+  integrity sha512-RoxtuSCX6sUNtxhbmsEFQfWzs8VN7cTctmBPvYivo98xb/kDEoTCtJQX5wyzIYEmk/lvNFTat4hL8oW0KndFpg==
+  dependencies:
+    micromark-util-character "^1.0.0"
+    micromark-util-encode "^1.0.0"
+    micromark-util-symbol "^1.0.0"
+
 micromark-util-subtokenize@^1.0.0:
 micromark-util-subtokenize@^1.0.0:
   version "1.0.2"
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz#ff6f1af6ac836f8bfdbf9b02f40431760ad89105"
   resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz#ff6f1af6ac836f8bfdbf9b02f40431760ad89105"
@@ -23069,13 +23066,6 @@ unique-string@^2.0.0:
   dependencies:
   dependencies:
     crypto-random-string "^2.0.0"
     crypto-random-string "^2.0.0"
 
 
-unist-builder@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-3.0.0.tgz#728baca4767c0e784e1e64bb44b5a5a753021a04"
-  integrity sha512-GFxmfEAa0vi9i5sd0R2kcrI9ks0r82NasRq5QHh2ysGngrc6GiqD5CDf1FjPenY4vApmFASBIIlk/jj5J5YbmQ==
-  dependencies:
-    "@types/unist" "^2.0.0"
-
 unist-types@^1.1.5:
 unist-types@^1.1.5:
   version "1.1.5"
   version "1.1.5"
   resolved "https://registry.yarnpkg.com/unist-types/-/unist-types-1.1.5.tgz#f001c0af2c3b0ac6e4f5d9aa114e7afe046d7abe"
   resolved "https://registry.yarnpkg.com/unist-types/-/unist-types-1.1.5.tgz#f001c0af2c3b0ac6e4f5d9aa114e7afe046d7abe"