From 769f047b0fc7a2835d3e5866a7785e7469e68378 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Thu, 17 Dec 2020 12:12:10 +0100 Subject: [PATCH] Fix timing problem on jumping to linked line number in source view --- .../ui-components/src/ErrorNotification.tsx | 2 +- .../ui-components/src/SyntaxHighlighter.tsx | 34 ++++------- .../ui-components/src/useScrollToElement.ts | 57 +++++++++++++++++++ 3 files changed, 70 insertions(+), 23 deletions(-) create mode 100644 scm-ui/ui-components/src/useScrollToElement.ts diff --git a/scm-ui/ui-components/src/ErrorNotification.tsx b/scm-ui/ui-components/src/ErrorNotification.tsx index 689b9f1469..9bb685175b 100644 --- a/scm-ui/ui-components/src/ErrorNotification.tsx +++ b/scm-ui/ui-components/src/ErrorNotification.tsx @@ -36,7 +36,7 @@ type Props = WithTranslation & { const LoginLink: FC = () => { const [t] = useTranslation("commons"); const location = useLocation(); - const from = encodeURIComponent(location.pathname + location.hash); + const from = encodeURIComponent(location.hash ? location.pathname + location.hash : location.pathname); return {t("errorNotification.loginLink")}; }; diff --git a/scm-ui/ui-components/src/SyntaxHighlighter.tsx b/scm-ui/ui-components/src/SyntaxHighlighter.tsx index 8c784c2e90..907ac3f342 100644 --- a/scm-ui/ui-components/src/SyntaxHighlighter.tsx +++ b/scm-ui/ui-components/src/SyntaxHighlighter.tsx @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import React, { FC, useEffect, useState } from "react"; +import React, { FC, useState } from "react"; import { PrismAsyncLight as ReactSyntaxHighlighter } from "react-syntax-highlighter"; import { defaultLanguage, determineLanguage } from "./languages"; @@ -30,6 +30,7 @@ import highlightingTheme from "./syntax-highlighting"; import { useLocation } from "react-router-dom"; import { withContextPath } from "./urls"; import createSyntaxHighlighterRenderer from "./SyntaxHighlighterRenderer"; +import useScrollToElement from "./useScrollToElement"; const LINE_NUMBER_URL_HASH_REGEX = /^#line-(.*)$/; @@ -44,27 +45,16 @@ const SyntaxHighlighter: FC = ({ language = defaultLanguage, showLineNumb const location = useLocation(); const [contentRef, setContentRef] = useState(); - useEffect(() => { - const match = location.hash.match(LINE_NUMBER_URL_HASH_REGEX); - if (contentRef && match) { - const lineNumber = match[1]; - // We defer the content check until after the syntax-highlighter has rendered - setTimeout(() => { - let tries = 0; - let element = contentRef.querySelector(`#line-${lineNumber}`); - if (!element && tries < 10) { - setInterval(() => { - tries += 1; - element = contentRef.querySelector(`#line-${lineNumber}`); - }, 200); - } - - if (element && element.scrollIntoView) { - element.scrollIntoView(); - } - }); - } - }, [value, contentRef]); + useScrollToElement( + contentRef, + () => { + const match = location.hash.match(LINE_NUMBER_URL_HASH_REGEX); + if (match) { + return `#line-${match[1]}`; + } + }, + value + ); const createLinePermaLink = (lineNumber: number) => window.location.protocol + diff --git a/scm-ui/ui-components/src/useScrollToElement.ts b/scm-ui/ui-components/src/useScrollToElement.ts new file mode 100644 index 0000000000..778a69d88b --- /dev/null +++ b/scm-ui/ui-components/src/useScrollToElement.ts @@ -0,0 +1,57 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { useEffect } from "react"; + +const useScrollToElement = ( + contentRef: HTMLElement | null | undefined, + elementSelectorResolver: () => string | undefined, + ...dependencies: any +) => { + useEffect(() => { + const elementSelector = elementSelectorResolver(); + if (contentRef && elementSelector) { + // We defer the content check until after the syntax-highlighter has rendered + let tries = 0; + const intervalId = setInterval(() => { + if (tries >= 15) { + clearInterval(intervalId); + } else { + tries++; + const element = contentRef.querySelector(elementSelector); + if (element && element.scrollIntoView) { + clearInterval(intervalId); + element.scrollIntoView(); + } + } + }, 200); + + return () => { + clearInterval(intervalId); + }; + } + }, [contentRef, ...dependencies]); +}; + +export default useScrollToElement;