From 7325c759ca1d2c32d6f0e1493293415cdb82621e Mon Sep 17 00:00:00 2001 From: svenadlung Date: Tue, 16 Nov 2021 22:31:34 +0100 Subject: [PATCH] Add TableOfContent demo for React --- .../TableOfContents/React/Component.jsx | 63 +++++++++++++++++++ .../TableOfContents/React/Component.scss | 43 +++++++++++++ .../TableOfContents/React/TableOfContents.js | 40 ++++++++++++ .../TableOfContents/React/index.html | 15 +++++ .../TableOfContents/React/index.jsx | 30 +++++++++ .../TableOfContents/React/styles.scss | 6 ++ 6 files changed, 197 insertions(+) create mode 100644 demos/src/GuideNodeViews/TableOfContents/React/Component.jsx create mode 100644 demos/src/GuideNodeViews/TableOfContents/React/Component.scss create mode 100644 demos/src/GuideNodeViews/TableOfContents/React/TableOfContents.js create mode 100644 demos/src/GuideNodeViews/TableOfContents/React/index.html create mode 100644 demos/src/GuideNodeViews/TableOfContents/React/index.jsx create mode 100644 demos/src/GuideNodeViews/TableOfContents/React/styles.scss diff --git a/demos/src/GuideNodeViews/TableOfContents/React/Component.jsx b/demos/src/GuideNodeViews/TableOfContents/React/Component.jsx new file mode 100644 index 00000000..e5302385 --- /dev/null +++ b/demos/src/GuideNodeViews/TableOfContents/React/Component.jsx @@ -0,0 +1,63 @@ +import React, { useState, useEffect, useCallback } from 'react' +import { NodeViewWrapper } from '@tiptap/react' +import './Component.scss' + +export default ({ editor }) => { + const [items, setItems] = useState([]) + + const handleUpdate = useCallback(() => { + const headings = [] + const transaction = editor.state.tr + + editor.state.doc.descendants((node, pos) => { + if (node.type.name === 'heading') { + const id = `heading-${headings.length + 1}` + + if (node.attrs.id !== id) { + transaction.setNodeMarkup(pos, undefined, { + ...node.attrs, + id, + }) + } + + headings.push({ + level: node.attrs.level, + text: node.textContent, + id, + }) + } + }) + + transaction.setMeta('preventUpdate', true) + + editor.view.dispatch(transaction) + + setItems(headings) + }, [editor]) + + useEffect(handleUpdate, []) + + useEffect(() => { + if (!editor) { + return null + } + + editor.on('update', handleUpdate) + + return () => { + editor.off('update', handleUpdate) + } + }, [editor]) + + return ( + + + + ) +} diff --git a/demos/src/GuideNodeViews/TableOfContents/React/Component.scss b/demos/src/GuideNodeViews/TableOfContents/React/Component.scss new file mode 100644 index 00000000..f1629179 --- /dev/null +++ b/demos/src/GuideNodeViews/TableOfContents/React/Component.scss @@ -0,0 +1,43 @@ +.toc { + background: rgba(black, 0.1); + border-radius: 0.5rem; + opacity: 0.75; + padding: 0.75rem; + + &__list { + list-style: none; + padding: 0; + + &::before { + content: "Table of Contents"; + display: block; + font-size: 0.75rem; + font-weight: 700; + letter-spacing: 0.025rem; + opacity: 0.5; + text-transform: uppercase; + } + } + + &__item { + a:hover { + opacity: 0.5; + } + + &--3 { + padding-left: 1rem; + } + + &--4 { + padding-left: 2rem; + } + + &--5 { + padding-left: 3rem; + } + + &--6 { + padding-left: 4rem; + } + } +} diff --git a/demos/src/GuideNodeViews/TableOfContents/React/TableOfContents.js b/demos/src/GuideNodeViews/TableOfContents/React/TableOfContents.js new file mode 100644 index 00000000..1e5e356c --- /dev/null +++ b/demos/src/GuideNodeViews/TableOfContents/React/TableOfContents.js @@ -0,0 +1,40 @@ +import { Node, mergeAttributes } from '@tiptap/core' +import { ReactNodeViewRenderer } from '@tiptap/react' +import Component from './Component.jsx' + +export default Node.create({ + name: 'tableOfContents', + + group: 'block', + + atom: true, + + parseHTML() { + return [ + { + tag: 'toc', + }, + ] + }, + + renderHTML({ HTMLAttributes }) { + return ['toc', mergeAttributes(HTMLAttributes)] + }, + + addNodeView() { + return ReactNodeViewRenderer(Component) + }, + + addGlobalAttributes() { + return [ + { + types: ['heading'], + attributes: { + id: { + default: null, + }, + }, + }, + ] + }, +}) diff --git a/demos/src/GuideNodeViews/TableOfContents/React/index.html b/demos/src/GuideNodeViews/TableOfContents/React/index.html new file mode 100644 index 00000000..7b996f50 --- /dev/null +++ b/demos/src/GuideNodeViews/TableOfContents/React/index.html @@ -0,0 +1,15 @@ + + + + + + + +
+ + + diff --git a/demos/src/GuideNodeViews/TableOfContents/React/index.jsx b/demos/src/GuideNodeViews/TableOfContents/React/index.jsx new file mode 100644 index 00000000..3de6fbb9 --- /dev/null +++ b/demos/src/GuideNodeViews/TableOfContents/React/index.jsx @@ -0,0 +1,30 @@ +import React from 'react' +import { useEditor, EditorContent } from '@tiptap/react' +import StarterKit from '@tiptap/starter-kit' +import TableOfContents from './TableOfContents.js' +import './styles.scss' + +export default () => { + const editor = useEditor({ + extensions: [StarterKit, TableOfContents], + content: ` + +

1 heading

+

paragraph

+

1.1 heading

+

paragraph

+

1.2 heading

+

paragraph

+

2 heading

+

paragraph

+

2.1 heading

+

paragraph

+ `, + }) + + if (!editor) { + return null + } + + return +} diff --git a/demos/src/GuideNodeViews/TableOfContents/React/styles.scss b/demos/src/GuideNodeViews/TableOfContents/React/styles.scss new file mode 100644 index 00000000..46b51a4e --- /dev/null +++ b/demos/src/GuideNodeViews/TableOfContents/React/styles.scss @@ -0,0 +1,6 @@ +/* Basic editor styles */ +.ProseMirror { + > * + * { + margin-top: 0.75em; + } +}