From 8f3c0dce1e62df976189e2c10678a0513554a07f Mon Sep 17 00:00:00 2001 From: Konstantin Schaper Date: Fri, 21 Aug 2020 18:27:48 +0200 Subject: [PATCH] initial implementation --- scm-ui/ui-components/src/repos/Diff.tsx | 53 ++++++++++++++++++--- scm-ui/ui-components/src/repos/DiffFile.tsx | 16 ++++++- 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/scm-ui/ui-components/src/repos/Diff.tsx b/scm-ui/ui-components/src/repos/Diff.tsx index 5a0571e95f..f5086f2881 100644 --- a/scm-ui/ui-components/src/repos/Diff.tsx +++ b/scm-ui/ui-components/src/repos/Diff.tsx @@ -22,34 +22,75 @@ * SOFTWARE. */ import React from "react"; -import DiffFile from "./DiffFile"; +import DiffFile, {escapeWhitespace} from "./DiffFile"; import { DiffObjectProps, File, FileControlFactory } from "./DiffTypes"; import Notification from "../Notification"; import { WithTranslation, withTranslation } from "react-i18next"; +import {RouteComponentProps, withRouter} from "react-router-dom"; -type Props = WithTranslation & +type Props = RouteComponentProps & WithTranslation & DiffObjectProps & { diff: File[]; fileControlFactory?: FileControlFactory; }; -class Diff extends React.Component { +type State = { + contentRef?: HTMLElement; +} + +function getAnchorSelector(uriHashContent: string) { + return "#" + escapeWhitespace(decodeURIComponent(uriHashContent)); +} + +class Diff extends React.Component { static defaultProps: Partial = { sideBySide: false }; + constructor(props: Readonly) { + super(props); + this.state = { + contentRef: undefined + }; + } + + componentDidUpdate() { + const { contentRef } = this.state; + + // we have to use componentDidUpdate, because we have to wait until all + // children are rendered and componentDidMount is called before the + // changeset content was rendered. + const hash = this.props.location.hash; + const match = hash && hash.match(/^#diff-(.*)$/); + if (contentRef && match) { + const selector = getAnchorSelector(match[1]); + const element = contentRef.querySelector(selector); + if (element && element.scrollIntoView) { + element.scrollIntoView(); + } + } + } + render() { const { diff, t, ...fileProps } = this.props; + const updateContentRef = (el: HTMLElement | null) => { + if (el !== null && this.state.contentRef === undefined) { + this.setState({ contentRef: el }); + } + }; + return ( - <> +
{diff.length === 0 ? ( {t("diff.noDiffFound")} ) : ( diff.map((file, index) => ) )} - +
); } } -export default withTranslation("repos")(Diff); +export default withRouter(withTranslation("repos")(Diff)); diff --git a/scm-ui/ui-components/src/repos/DiffFile.tsx b/scm-ui/ui-components/src/repos/DiffFile.tsx index 637128fae9..91f87e821b 100644 --- a/scm-ui/ui-components/src/repos/DiffFile.tsx +++ b/scm-ui/ui-components/src/repos/DiffFile.tsx @@ -90,6 +90,10 @@ const ChangeTypeTag = styled(Tag)` margin-left: 0.75rem; `; +export function escapeWhitespace(path: string) { + return path.toLowerCase().replace(/\W/g, "-"); +} + class DiffFile extends React.Component { static defaultProps: Partial = { defaultCollapse: false, @@ -350,6 +354,16 @@ class DiffFile extends React.Component { } }; + getAnchorId(file: File) { + let path: string; + if (file.type === "delete") { + path = file.oldPath; + } else { + path = file.newPath; + } + return escapeWhitespace(path); + } + renderFileTitle = (file: File) => { if (file.oldPath !== file.newPath && (file.type === "copy" || file.type === "rename")) { return ( @@ -454,7 +468,7 @@ class DiffFile extends React.Component { } return ( - + {errorModal}