diff --git a/app/components/ScrollToAnchor.js b/app/components/ScrollToAnchor.js new file mode 100644 index 000000000..349ab8762 --- /dev/null +++ b/app/components/ScrollToAnchor.js @@ -0,0 +1,23 @@ +// @flow +import * as React from 'react'; +import { withRouter } from 'react-router-dom'; + +class ScrollToAnchor extends React.Component<*> { + componentDidUpdate(prevProps) { + if (this.props.location.hash === prevProps.location.hash) return; + if (window.location.hash === '') return; + + // Delay on timeout to ensure that the DOM is updated first + setImmediate(() => { + const id = window.location.hash.replace('#', ''); + const element = document.getElementById(id); + if (element) element.scrollIntoView(); + }); + } + + render() { + return this.props.children; + } +} + +export default withRouter(ScrollToAnchor); diff --git a/app/index.js b/app/index.js index d0f085aae..8a92ce64c 100644 --- a/app/index.js +++ b/app/index.js @@ -33,6 +33,7 @@ import Error404 from 'scenes/Error404'; import ErrorBoundary from 'components/ErrorBoundary'; import ScrollToTop from 'components/ScrollToTop'; +import ScrollToAnchor from 'components/ScrollToAnchor'; import Layout from 'components/Layout'; import Auth from 'components/Auth'; import RouteSidebarHidden from 'components/RouteSidebarHidden'; @@ -62,84 +63,94 @@ if (element) { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/scenes/Document/Document.js b/app/scenes/Document/Document.js index f1cddbc35..839595122 100644 --- a/app/scenes/Document/Document.js +++ b/app/scenes/Document/Document.js @@ -231,6 +231,12 @@ class DocumentScene extends React.Component { }; onClickLink = (href: string) => { + // on page hash + if (href[0] === '#') { + window.location.href = href; + return; + } + if (isInternalUrl(href)) { // relative let navigateTo = href; @@ -239,7 +245,7 @@ class DocumentScene extends React.Component { if (href[0] !== '/') { try { const url = new URL(href); - navigateTo = url.pathname; + navigateTo = url.pathname + url.hash; } catch (err) { navigateTo = href; } @@ -251,6 +257,10 @@ class DocumentScene extends React.Component { } }; + onShowToast = (message: string) => { + this.props.ui.showToast(message, 'success'); + }; + render() { const { location, match } = this.props; const Editor = this.editorComponent; @@ -328,6 +338,7 @@ class DocumentScene extends React.Component { onChange={this.onChange} onSave={this.onSave} onCancel={this.onDiscard} + onShowToast={this.onShowToast} readOnly={!this.isEditing} toc /> diff --git a/package.json b/package.json index 83f1c8efe..c3689af4d 100644 --- a/package.json +++ b/package.json @@ -158,7 +158,7 @@ "react-waypoint": "^7.3.1", "redis": "^2.6.2", "redis-lock": "^0.1.0", - "rich-markdown-editor": "2.0.11", + "rich-markdown-editor": "3.1.1", "safestart": "1.1.0", "sequelize": "4.28.6", "sequelize-cli": "^2.7.0", diff --git a/yarn.lock b/yarn.lock index 5c1f403ec..1036fee73 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8858,9 +8858,9 @@ retry-axios@0.3.2, retry-axios@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/retry-axios/-/retry-axios-0.3.2.tgz#5757c80f585b4cc4c4986aa2ffd47a60c6d35e13" -rich-markdown-editor@2.0.11: - version "2.0.11" - resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-2.0.11.tgz#209fd57ad8f61244e09cbe167ba64fc2c3b37da6" +rich-markdown-editor@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-3.1.1.tgz#ee97b3ca87836c960de0912b852df32c8054873d" dependencies: "@tommoor/slate-drop-or-paste-images" "^0.8.1" babel-plugin-transform-async-to-generator "^6.24.1"