diff --git a/components/comment.module.css b/components/comment.module.css index 6e80226f..cd205557 100644 --- a/components/comment.module.css +++ b/components/comment.module.css @@ -77,6 +77,7 @@ } .hunk { + overflow: visible; margin-bottom: 0; margin-top: 0.15rem; } diff --git a/components/form.js b/components/form.js index ad69b270..aa3b9ca1 100644 --- a/components/form.js +++ b/components/form.js @@ -103,7 +103,7 @@ export function MarkdownInput ({ label, topLevel, groupClassName, ...props }) {
- {tab === 'preview' && {meta.value}} + {tab === 'preview' && {meta.value}}
@@ -254,11 +254,13 @@ export function Form ({ {error && setError(undefined)} dismissible>{error}} {storageKeyPrefix ? React.Children.map(children, (child) => { - // if child has a type it's a dom element + // if child has a type that's a string, it's a dom element and can't get a prop if (child) { - return React.cloneElement(child, { - storageKeyPrefix - }) + let childProps = {} + if (typeof child.type !== 'string') { + childProps = { storageKeyPrefix } + } + return React.cloneElement(child, childProps) } }) : children} diff --git a/components/text.js b/components/text.js index a67cafa5..80495d12 100644 --- a/components/text.js +++ b/components/text.js @@ -2,14 +2,15 @@ import styles from './text.module.css' import ReactMarkdown from 'react-markdown' import gfm from 'remark-gfm' import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter' -/* Use `…/dist/cjs/…` if you’re not in ESM! */ import { atomDark } from 'react-syntax-highlighter/dist/cjs/styles/prism' import mention from '../lib/remark-mention' import sub from '../lib/remark-sub' import remarkDirective from 'remark-directive' import { visit } from 'unist-util-visit' import reactStringReplace from 'react-string-replace' -import { useEffect, useState } from 'react' +import React, { useEffect, useState } from 'react' +import GithubSlugger from 'github-slugger' +import Link from '../svgs/link.svg' function myRemarkPlugin () { return (tree) => { @@ -28,19 +29,41 @@ function myRemarkPlugin () { } } -export default function Text ({ topLevel, nofollow, children }) { +function Heading ({ h, slugger, noFragments, topLevel, children, node, ...props }) { + const id = noFragments + ? undefined + : slugger.slug(children.reduce( + (acc, cur) => { + if (typeof cur !== 'string') { + return acc + } + return acc + cur.replace(/[^\w\-\s]+/gi, '') + }, '')) + + return ( +
+ {React.createElement(h, { id, ...props }, children)} + {!noFragments && topLevel && } +
+ ) +} + +export default function Text ({ topLevel, noFragments, nofollow, children }) { // all the reactStringReplace calls are to facilitate search highlighting + const slugger = new GithubSlugger() + + const HeadingWrapper = (props) => Heading({ topLevel, slugger, noFragments, ...props}) return (
topLevel ?

{children}

:

{children}

, - h2: ({children, node, ...props}) => topLevel ?

{children}

:

{children}

, - h3: ({children, node, ...props}) => topLevel ?

{children}

:
{children}
, - h4: ({children, node, ...props}) => topLevel ?

{children}

:
{children}
, - h5: ({children, node, ...props}) => topLevel ?
{children}
:
{children}
, - h6: 'h6', + h1: (props) => HeadingWrapper({ h: topLevel ? 'h1' : 'h3', ...props }), + h2: (props) => HeadingWrapper({ h: topLevel ? 'h2' : 'h4', ...props }), + h3: (props) => HeadingWrapper({ h: topLevel ? 'h3' : 'h5', ...props }), + h4: (props) => HeadingWrapper({ h: topLevel ? 'h4' : 'h6', ...props }), + h5: (props) => HeadingWrapper({ h: topLevel ? 'h5' : 'h6', ...props }), + h6: (props) => HeadingWrapper({ h: 'h6', ...props }), table: ({ node, ...props }) =>
@@ -64,7 +87,8 @@ export default function Text ({ topLevel, nofollow, children }) { ) }, a: ({ node, href, children, ...props }) => { - children = children?.map(e => typeof e === 'string' + children = children?.map(e => + typeof e === 'string' ? reactStringReplace(e, /:high\[([^\]]+)\]/g, (match, i) => { return {match} }) @@ -82,7 +106,7 @@ export default function Text ({ topLevel, nofollow, children }) { ) }, - img: ({node, ...props}) => + img: ({ node, ...props }) => }} remarkPlugins={[gfm, mention, sub, remarkDirective, myRemarkPlugin]} > @@ -119,11 +143,13 @@ function ZoomableImage ({ src, topLevel, ...props }) { } } - return + return ( + + ) } diff --git a/components/text.module.css b/components/text.module.css index f86ed7a4..83f0c2a4 100644 --- a/components/text.module.css +++ b/components/text.module.css @@ -11,6 +11,28 @@ } } +.heading { + position: relative; + margin-left: -22px; + padding-left: 22px; +} + +.headingLink { + display: none; + position: absolute; + left: 0px; + top: 0px; + height: 100%; +} + +.headingLink svg { + align-self: center; +} + +.heading:hover>.headingLink { + display: flex; +} + .text hr { border-top: 1px solid var(--theme-clickToContextColor); } diff --git a/package-lock.json b/package-lock.json index 9903b757..d25d4d6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3912,6 +3912,11 @@ "get-intrinsic": "^1.1.1" } }, + "github-slugger": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.4.0.tgz", + "integrity": "sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ==" + }, "glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", diff --git a/package.json b/package.json index 088335ab..44e5a229 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "cross-fetch": "^3.1.5", "domino": "^2.1.6", "formik": "^2.2.6", + "github-slugger": "^1.4.0", "graphql": "^15.5.0", "graphql-type-json": "^0.3.2", "ln-service": "^52.8.0", diff --git a/svgs/link.svg b/svgs/link.svg new file mode 100644 index 00000000..f8fecf3b --- /dev/null +++ b/svgs/link.svg @@ -0,0 +1 @@ + \ No newline at end of file