diff --git a/app/components/Editor/components/Contents.js b/app/components/Editor/components/Contents.js index b7bcfb9cd..0215bb7fb 100644 --- a/app/components/Editor/components/Contents.js +++ b/app/components/Editor/components/Contents.js @@ -64,6 +64,8 @@ class Contents extends Component { } render() { + const { editor } = this.props; + // If there are one or less headings in the document no need for a minimap if (this.headings.size <= 1) return null; @@ -71,7 +73,7 @@ class Contents extends Component { {this.headings.map(heading => { - const slug = headingToSlug(heading); + const slug = headingToSlug(editor.value.document, heading); const active = this.activeHeading === slug; return ( diff --git a/app/components/Editor/components/Heading.js b/app/components/Editor/components/Heading.js index 03b03d168..32f38d6b1 100644 --- a/app/components/Editor/components/Heading.js +++ b/app/components/Editor/components/Heading.js @@ -27,7 +27,7 @@ function Heading(props: Props) { const parentIsDocument = parent instanceof Document; const firstHeading = parentIsDocument && parent.nodes.first() === node; const showPlaceholder = placeholder && firstHeading && !node.text; - const slugish = headingToSlug(node); + const slugish = headingToSlug(editor.value.document, node); const showHash = readOnly && !!slugish; const Component = component; const emoji = editor.props.emoji || ''; diff --git a/app/components/Editor/headingToSlug.js b/app/components/Editor/headingToSlug.js index eb06dfcde..59f3fdbba 100644 --- a/app/components/Editor/headingToSlug.js +++ b/app/components/Editor/headingToSlug.js @@ -1,9 +1,25 @@ // @flow import { escape } from 'lodash'; -import { Node } from 'slate'; +import { Document, Block, Node } from 'slate'; import slug from 'slug'; -export default function headingToSlug(node: Node) { - const level = node.type.replace('heading', 'h'); - return escape(`${level}-${slug(node.text)}-${node.key}`); +// finds the index of this heading in the document compared to other headings +// with the same slugified text +function indexOfType(document, heading) { + const slugified = escape(slug(heading.text)); + const headings = document.nodes.filter((node: Block) => { + if (!node.text) return false; + return node.type.match(/^heading/) && slugified === escape(slug(node.text)); + }); + + return headings.indexOf(heading); +} + +// calculates a unique slug for this heading based on it's text and position +// in the document that is as stable as possible +export default function headingToSlug(document: Document, node: Node) { + const slugified = escape(slug(node.text)); + const index = indexOfType(document, node); + if (index === 0) return slugified; + return `${slugified}-${index}`; }