mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-17 00:24:43 -04:00
Add toc sidebar+dropdown (#272)
* Replace markdown-it-table-of-contents with markdown-it-toc-done-right Co-authored-by: Erik Michelson <github@erik.michelson.eu> Co-authored-by: Philip Molares <philip.molares@udo.edu> Extract render window code Co-authored-by: Erik Michelson <github@erik.michelson.eu> Co-authored-by: Philip Molares <philip.molares@udo.edu> add new package fix stickyness Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> show toc sidebar only if there is enough space Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * add min height class Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Move markdown toc into own component Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * add sidebar buttons Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Use other button color Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Change name of component Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Fix merge issues and make toc work again Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * pin dependencies Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * remove blank line Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * pin dependency Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Fix anchors Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Add use memo Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Add change log entry for removal of custom slugify
This commit is contained in:
parent
8ab7776a82
commit
50b04c8403
17 changed files with 505 additions and 110 deletions
|
@ -54,7 +54,7 @@ export const Splitter: React.FC<SplitterProps> = ({ containerClassName, left, ri
|
|||
</div>
|
||||
</ShowIf>
|
||||
<ShowIf condition={showRight}>
|
||||
<div className='splitter right'>
|
||||
<div className='splitter right overflow-y-scroll'>
|
||||
{right}
|
||||
</div>
|
||||
</ShowIf>
|
||||
|
|
|
@ -6,7 +6,7 @@ import { setEditorModeConfig } from '../../redux/editor/methods'
|
|||
import { Splitter } from '../common/splitter/splitter'
|
||||
import { InfoBanner } from '../landing/layout/info-banner'
|
||||
import { EditorWindow } from './editor-window/editor-window'
|
||||
import { MarkdownRenderer } from './markdown-renderer/markdown-renderer'
|
||||
import { MarkdownRenderWindow } from './renderer-window/markdown-render-window'
|
||||
import { EditorMode } from './task-bar/editor-view-mode'
|
||||
import { TaskBar } from './task-bar/task-bar'
|
||||
|
||||
|
@ -78,7 +78,7 @@ let a = 1
|
|||
showLeft={editorMode === EditorMode.EDITOR || editorMode === EditorMode.BOTH}
|
||||
left={<EditorWindow onContentChange={content => setMarkdownContent(content)} content={markdownContent}/>}
|
||||
showRight={editorMode === EditorMode.PREVIEW || (editorMode === EditorMode.BOTH)}
|
||||
right={<MarkdownRenderer content={markdownContent} wide={editorMode === EditorMode.PREVIEW}/>}
|
||||
right={<MarkdownRenderWindow content={markdownContent} wide={editorMode === EditorMode.PREVIEW}/>}
|
||||
containerClassName={'overflow-hidden'}/>
|
||||
</div>
|
||||
</Fragment>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
@import '../../../../node_modules/github-markdown-css/github-markdown.css';
|
||||
|
||||
.markdown-body {
|
||||
position: relative;
|
||||
font-family: 'Source Sans Pro', "twemoji", sans-serif;
|
||||
|
||||
.alert > p, .alert > ul {
|
||||
|
@ -42,4 +43,5 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import equal from 'deep-equal'
|
||||
import { DomElement } from 'domhandler'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import abbreviation from 'markdown-it-abbr'
|
||||
|
@ -13,11 +14,13 @@ import mathJax from 'markdown-it-mathjax'
|
|||
import markdownItRegex from 'markdown-it-regex'
|
||||
import subscript from 'markdown-it-sub'
|
||||
import superscript from 'markdown-it-sup'
|
||||
import toc from 'markdown-it-table-of-contents'
|
||||
import taskList from 'markdown-it-task-lists'
|
||||
import React, { ReactElement, useMemo } from 'react'
|
||||
import toc from 'markdown-it-toc-done-right'
|
||||
import React, { ReactElement, useEffect, useMemo, useState } from 'react'
|
||||
import ReactHtmlParser, { convertNodeToElement, Transform } from 'react-html-parser'
|
||||
import MathJaxReact from 'react-mathjax'
|
||||
import { TocAst } from '../../../external-types/markdown-it-toc-done-right/interface'
|
||||
import { slugify } from '../../../utils/slugify'
|
||||
import { createRenderContainer, validAlertLevels } from './container-plugins/alert'
|
||||
import { highlightedCode } from './markdown-it-plugins/highlighted-code'
|
||||
import { linkifyExtra } from './markdown-it-plugins/linkify-extra'
|
||||
|
@ -38,87 +41,102 @@ import { replaceYouTubeLink } from './regex-plugins/replace-youtube-link'
|
|||
import { ComponentReplacer, SubNodeConverter } from './replace-components/ComponentReplacer'
|
||||
import { GistReplacer } from './replace-components/gist/gist-replacer'
|
||||
import { HighlightedCodeReplacer } from './replace-components/highlighted-fence/highlighted-fence-replacer'
|
||||
import { PossibleWiderReplacer } from './replace-components/possible-wider/possible-wider-replacer'
|
||||
import { ImageReplacer } from './replace-components/image/image-replacer'
|
||||
import { MathjaxReplacer } from './replace-components/mathjax/mathjax-replacer'
|
||||
import { PdfReplacer } from './replace-components/pdf/pdf-replacer'
|
||||
import { PossibleWiderReplacer } from './replace-components/possible-wider/possible-wider-replacer'
|
||||
import { QuoteOptionsReplacer } from './replace-components/quote-options/quote-options-replacer'
|
||||
import { TocReplacer } from './replace-components/toc/toc-replacer'
|
||||
import { VimeoReplacer } from './replace-components/vimeo/vimeo-replacer'
|
||||
import { YoutubeReplacer } from './replace-components/youtube/youtube-replacer'
|
||||
|
||||
export interface MarkdownPreviewProps {
|
||||
export interface MarkdownRendererProps {
|
||||
content: string
|
||||
wide?: boolean
|
||||
className?: string
|
||||
onTocChange?: (ast: TocAst) => void
|
||||
}
|
||||
|
||||
const createMarkdownIt = (): MarkdownIt => {
|
||||
const md = new MarkdownIt('default', {
|
||||
html: true,
|
||||
breaks: true,
|
||||
langPrefix: '',
|
||||
typographer: true
|
||||
})
|
||||
md.use(taskList)
|
||||
md.use(emoji)
|
||||
md.use(abbreviation)
|
||||
md.use(definitionList)
|
||||
md.use(subscript)
|
||||
md.use(superscript)
|
||||
md.use(inserted)
|
||||
md.use(marked)
|
||||
md.use(footnote)
|
||||
md.use(imsize)
|
||||
// noinspection CheckTagEmptyBody
|
||||
md.use(anchor, {
|
||||
permalink: true,
|
||||
permalinkBefore: true,
|
||||
permalinkClass: 'heading-anchor text-dark',
|
||||
permalinkSymbol: '<i class="fa fa-link"></i>'
|
||||
})
|
||||
md.use(toc, {
|
||||
includeLevel: [1, 2, 3],
|
||||
markerPattern: /^\[TOC]$/i
|
||||
})
|
||||
md.use(mathJax({
|
||||
beforeMath: '<codimd-mathjax>',
|
||||
afterMath: '</codimd-mathjax>',
|
||||
beforeInlineMath: '<codimd-mathjax inline>',
|
||||
afterInlineMath: '</codimd-mathjax>',
|
||||
beforeDisplayMath: '<codimd-mathjax>',
|
||||
afterDisplayMath: '</codimd-mathjax>'
|
||||
}))
|
||||
md.use(markdownItRegex, replaceLegacyYoutubeShortCode)
|
||||
md.use(markdownItRegex, replaceLegacyVimeoShortCode)
|
||||
md.use(markdownItRegex, replaceLegacyGistShortCode)
|
||||
md.use(markdownItRegex, replaceLegacySlideshareShortCode)
|
||||
md.use(markdownItRegex, replaceLegacySpeakerdeckShortCode)
|
||||
md.use(markdownItRegex, replacePdfShortCode)
|
||||
md.use(markdownItRegex, replaceYouTubeLink)
|
||||
md.use(markdownItRegex, replaceVimeoLink)
|
||||
md.use(markdownItRegex, replaceGistLink)
|
||||
md.use(highlightedCode)
|
||||
md.use(markdownItRegex, replaceQuoteExtraAuthor)
|
||||
md.use(markdownItRegex, replaceQuoteExtraColor)
|
||||
md.use(markdownItRegex, replaceQuoteExtraTime)
|
||||
md.use(linkifyExtra)
|
||||
md.use(MarkdownItParserDebugger)
|
||||
export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({ content, className, onTocChange, wide }) => {
|
||||
const [tocAst, setTocAst] = useState<TocAst>()
|
||||
const [lastTocAst, setLastTocAst] = useState<TocAst>()
|
||||
|
||||
validAlertLevels.forEach(level => {
|
||||
md.use(markdownItContainer, level, { render: createRenderContainer(level) })
|
||||
})
|
||||
const markdownIt = useMemo(() => {
|
||||
const md = new MarkdownIt('default', {
|
||||
html: true,
|
||||
breaks: true,
|
||||
langPrefix: '',
|
||||
typographer: true
|
||||
})
|
||||
md.use(taskList)
|
||||
md.use(emoji)
|
||||
md.use(abbreviation)
|
||||
md.use(definitionList)
|
||||
md.use(subscript)
|
||||
md.use(superscript)
|
||||
md.use(inserted)
|
||||
md.use(marked)
|
||||
md.use(footnote)
|
||||
md.use(imsize)
|
||||
// noinspection CheckTagEmptyBody
|
||||
md.use(anchor, {
|
||||
permalink: true,
|
||||
permalinkBefore: true,
|
||||
permalinkClass: 'heading-anchor text-dark',
|
||||
permalinkSymbol: '<i class="fa fa-link"></i>'
|
||||
})
|
||||
md.use(mathJax({
|
||||
beforeMath: '<codimd-mathjax>',
|
||||
afterMath: '</codimd-mathjax>',
|
||||
beforeInlineMath: '<codimd-mathjax inline>',
|
||||
afterInlineMath: '</codimd-mathjax>',
|
||||
beforeDisplayMath: '<codimd-mathjax>',
|
||||
afterDisplayMath: '</codimd-mathjax>'
|
||||
}))
|
||||
md.use(markdownItRegex, replaceLegacyYoutubeShortCode)
|
||||
md.use(markdownItRegex, replaceLegacyVimeoShortCode)
|
||||
md.use(markdownItRegex, replaceLegacyGistShortCode)
|
||||
md.use(markdownItRegex, replaceLegacySlideshareShortCode)
|
||||
md.use(markdownItRegex, replaceLegacySpeakerdeckShortCode)
|
||||
md.use(markdownItRegex, replacePdfShortCode)
|
||||
md.use(markdownItRegex, replaceYouTubeLink)
|
||||
md.use(markdownItRegex, replaceVimeoLink)
|
||||
md.use(markdownItRegex, replaceGistLink)
|
||||
md.use(highlightedCode)
|
||||
md.use(markdownItRegex, replaceQuoteExtraAuthor)
|
||||
md.use(markdownItRegex, replaceQuoteExtraColor)
|
||||
md.use(markdownItRegex, replaceQuoteExtraTime)
|
||||
md.use(toc, {
|
||||
placeholder: '(\\[TOC\\]|\\[toc\\])',
|
||||
listType: 'ul',
|
||||
level: [1, 2, 3],
|
||||
callback: (code: string, ast: TocAst): void => {
|
||||
setTocAst(ast)
|
||||
},
|
||||
slugify: slugify
|
||||
})
|
||||
md.use(linkifyExtra)
|
||||
md.use(MarkdownItParserDebugger)
|
||||
|
||||
return md
|
||||
}
|
||||
validAlertLevels.forEach(level => {
|
||||
md.use(markdownItContainer, level, { render: createRenderContainer(level) })
|
||||
})
|
||||
|
||||
const tryToReplaceNode = (node: DomElement, index: number, allReplacers: ComponentReplacer[], nodeConverter: SubNodeConverter) => {
|
||||
return allReplacers
|
||||
.map((componentReplacer) => componentReplacer.getReplacement(node, index, nodeConverter))
|
||||
.find((replacement) => !!replacement)
|
||||
}
|
||||
return md
|
||||
}, [])
|
||||
|
||||
const MarkdownRenderer: React.FC<MarkdownPreviewProps> = ({ content, wide }) => {
|
||||
const markdownIt = useMemo(createMarkdownIt, [])
|
||||
useEffect(() => {
|
||||
if (onTocChange && tocAst && !equal(tocAst, lastTocAst)) {
|
||||
onTocChange(tocAst)
|
||||
setLastTocAst(tocAst)
|
||||
}
|
||||
}, [tocAst, onTocChange, lastTocAst])
|
||||
|
||||
const tryToReplaceNode = (node: DomElement, index: number, allReplacers: ComponentReplacer[], nodeConverter: SubNodeConverter) => {
|
||||
return allReplacers
|
||||
.map((componentReplacer) => componentReplacer.getReplacement(node, index, nodeConverter))
|
||||
.find((replacement) => !!replacement)
|
||||
}
|
||||
|
||||
const result: ReactElement[] = useMemo(() => {
|
||||
const allReplacers: ComponentReplacer[] = [
|
||||
|
@ -134,6 +152,7 @@ const MarkdownRenderer: React.FC<MarkdownPreviewProps> = ({ content, wide }) =>
|
|||
new MathjaxReplacer()
|
||||
]
|
||||
const html: string = markdownIt.render(content)
|
||||
|
||||
const transform: Transform = (node, index) => {
|
||||
const subNodeConverter = (subNode: DomElement, subIndex: number) => convertNodeToElement(subNode, subIndex, transform)
|
||||
return tryToReplaceNode(node, index, allReplacers, subNodeConverter) || convertNodeToElement(node, index, transform)
|
||||
|
@ -142,14 +161,10 @@ const MarkdownRenderer: React.FC<MarkdownPreviewProps> = ({ content, wide }) =>
|
|||
}, [content, markdownIt])
|
||||
|
||||
return (
|
||||
<div className={'bg-light container-fluid flex-fill h-100 overflow-y-scroll pb-5'}>
|
||||
<div className={`markdown-body d-flex flex-column align-items-center container-fluid ${wide ? 'wider' : ''}`}>
|
||||
<MathJaxReact.Provider>
|
||||
{result}
|
||||
</MathJaxReact.Provider>
|
||||
</div>
|
||||
<div className={`markdown-body ${className || ''} d-flex flex-column align-items-center ${wide ? 'wider' : ''}`}>
|
||||
<MathJaxReact.Provider>
|
||||
{result}
|
||||
</MathJaxReact.Provider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { MarkdownRenderer }
|
||||
|
|
67
src/components/editor/markdown-toc/markdown-toc.scss
Normal file
67
src/components/editor/markdown-toc/markdown-toc.scss
Normal file
|
@ -0,0 +1,67 @@
|
|||
.markdown-toc {
|
||||
width: 100%;
|
||||
max-width: 200px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
&.sticky {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
>ul>li {
|
||||
>a {
|
||||
padding: 4px 20px;
|
||||
}
|
||||
>ul>li {
|
||||
> a {
|
||||
padding: 1px 0 1px 30px;
|
||||
}
|
||||
>ul>li {
|
||||
> a {
|
||||
padding: 1px 0 1px 38px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
font-size: 0.9em;
|
||||
|
||||
a {
|
||||
color: #767676;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: pre;
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
li.active {
|
||||
a {
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
color: #000000;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.markdown-toc-sidebar-button {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
right: 20px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
flex-direction: column;
|
||||
|
||||
&>.dropup {
|
||||
position: sticky;
|
||||
bottom: 20px;
|
||||
right: 0px;
|
||||
}
|
||||
}
|
59
src/components/editor/markdown-toc/markdown-toc.tsx
Normal file
59
src/components/editor/markdown-toc/markdown-toc.tsx
Normal file
|
@ -0,0 +1,59 @@
|
|||
import React, { Fragment, ReactElement, useMemo } from 'react'
|
||||
import { TocAst } from '../../../external-types/markdown-it-toc-done-right/interface'
|
||||
import { slugify } from '../../../utils/slugify'
|
||||
import { ShowIf } from '../../common/show-if/show-if'
|
||||
import './markdown-toc.scss'
|
||||
|
||||
export interface MarkdownTocProps {
|
||||
ast: TocAst
|
||||
maxDepth?: number
|
||||
sticky?: boolean
|
||||
}
|
||||
|
||||
const convertLevel = (toc: TocAst, levelsToShowUnderThis: number, headerCounts: Map<string, number>, wrapInListItem: boolean): ReactElement | null => {
|
||||
if (levelsToShowUnderThis < 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const rawName = toc.n.trim()
|
||||
const nameCount = (headerCounts.get(rawName) ?? 0) + 1
|
||||
const slug = `#${slugify(rawName)}${nameCount > 1 ? `-${nameCount}` : ''}`
|
||||
|
||||
headerCounts.set(rawName, nameCount)
|
||||
|
||||
const content = (
|
||||
<Fragment>
|
||||
<ShowIf condition={toc.l > 0}>
|
||||
<a href={slug}>{rawName}</a>
|
||||
</ShowIf>
|
||||
<ShowIf condition={toc.c.length > 0}>
|
||||
<ul>
|
||||
{
|
||||
toc.c.map(child =>
|
||||
(convertLevel(child, levelsToShowUnderThis - 1, headerCounts, true)))
|
||||
}
|
||||
</ul>
|
||||
</ShowIf>
|
||||
</Fragment>
|
||||
)
|
||||
|
||||
if (wrapInListItem) {
|
||||
return (
|
||||
<li key={slug}>
|
||||
{content}
|
||||
</li>
|
||||
)
|
||||
} else {
|
||||
return content
|
||||
}
|
||||
}
|
||||
|
||||
export const MarkdownToc: React.FC<MarkdownTocProps> = ({ ast, maxDepth = 3, sticky }) => {
|
||||
const tocTree = useMemo(() => convertLevel(ast, maxDepth, new Map<string, number>(), false), [ast, maxDepth])
|
||||
|
||||
return (
|
||||
<div className={`markdown-toc ${sticky ? 'sticky' : ''}`}>
|
||||
{tocTree}
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
import React, { useRef, useState } from 'react'
|
||||
import { Dropdown } from 'react-bootstrap'
|
||||
import useResizeObserver from 'use-resize-observer'
|
||||
import { TocAst } from '../../../external-types/markdown-it-toc-done-right/interface'
|
||||
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
||||
import { ShowIf } from '../../common/show-if/show-if'
|
||||
import { MarkdownRenderer } from '../markdown-renderer/markdown-renderer'
|
||||
import { MarkdownToc } from '../markdown-toc/markdown-toc'
|
||||
|
||||
interface RenderWindowProps {
|
||||
content: string
|
||||
wide?: boolean
|
||||
}
|
||||
|
||||
export const MarkdownRenderWindow: React.FC<RenderWindowProps> = ({ content, wide }) => {
|
||||
const [tocAst, setTocAst] = useState<TocAst>()
|
||||
const renderer = useRef<HTMLDivElement>(null)
|
||||
const { width } = useResizeObserver({ ref: renderer })
|
||||
|
||||
const realWidth = width || 0
|
||||
|
||||
return (
|
||||
<div className={'bg-light flex-fill pb-5 flex-row d-flex min-h-100'} ref={renderer}>
|
||||
<div className={'col-md'}/>
|
||||
<MarkdownRenderer
|
||||
className={'flex-fill'}
|
||||
content={content}
|
||||
wide={wide}
|
||||
onTocChange={(tocAst) => setTocAst(tocAst)}/>
|
||||
|
||||
<div className={`col-md d-flex flex-column ${realWidth < 1280 ? 'justify-content-end' : ''}`}>
|
||||
<ShowIf condition={realWidth >= 1280 && !!tocAst}>
|
||||
<MarkdownToc ast={tocAst as TocAst} sticky={true}/>
|
||||
</ShowIf>
|
||||
<ShowIf condition={realWidth < 1280 && !!tocAst}>
|
||||
<div className={'markdown-toc-sidebar-button'}>
|
||||
<Dropdown drop={'up'}>
|
||||
<Dropdown.Toggle id="toc-overlay-button" variant={'secondary'} className={'no-arrow'}>
|
||||
<ForkAwesomeIcon icon={'bars'}/>
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu>
|
||||
<div className={'p-2'}>
|
||||
<MarkdownToc ast={tocAst as TocAst}/>
|
||||
</div>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</ShowIf>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -13,6 +13,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
.dropdown-toggle.no-arrow::after {
|
||||
content: initial;
|
||||
.dropup .dropdown-toggle, .dropdown-toggle {
|
||||
&.no-arrow::after {
|
||||
content: initial;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
declare module 'markdown-it-table-of-contents' {
|
||||
import MarkdownIt from 'markdown-it/lib'
|
||||
import { TOCOptions } from './interface'
|
||||
const markdownItTableOfContents: MarkdownIt.PluginWithOptions<TOCOptions>
|
||||
export = markdownItTableOfContents
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
export interface TOCOptions {
|
||||
includeLevel: number[]
|
||||
containerClass: string
|
||||
slugify: (s: string) => string
|
||||
markerPattern: RegExp
|
||||
listType: 'ul' | 'ol'
|
||||
format: (headingAsString: string) => string
|
||||
forceFullToc: boolean
|
||||
containerHeaderHtml: string
|
||||
containerFooterHtml: string
|
||||
transformLink: (link: string) => string
|
||||
}
|
6
src/external-types/markdown-it-toc-done-right/index.d.ts
vendored
Normal file
6
src/external-types/markdown-it-toc-done-right/index.d.ts
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
declare module 'markdown-it-toc-done-right' {
|
||||
import MarkdownIt from 'markdown-it/lib'
|
||||
import { TocOptions } from './interface'
|
||||
const markdownItTocDoneRight: MarkdownIt.PluginWithOptions<TocOptions>
|
||||
export = markdownItTocDoneRight
|
||||
}
|
19
src/external-types/markdown-it-toc-done-right/interface.ts
Normal file
19
src/external-types/markdown-it-toc-done-right/interface.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
export interface TocOptions {
|
||||
placeholder: string
|
||||
slugify: (s: string) => string
|
||||
containerClass: string
|
||||
containerId: string
|
||||
listClass: string
|
||||
itemClass: string
|
||||
linkClass: string
|
||||
level: number | number[]
|
||||
listType: 'ol' | 'ul'
|
||||
format: (s: string) => string
|
||||
callback: (tocCode: string, ast: TocAst) => void
|
||||
}
|
||||
|
||||
export interface TocAst {
|
||||
l: number
|
||||
n: string
|
||||
c: TocAst[]
|
||||
}
|
|
@ -36,3 +36,7 @@ body {
|
|||
.overflow-y-scroll {
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.min-h-100 {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
|
3
src/utils/slugify.ts
Normal file
3
src/utils/slugify.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export const slugify = (url:string) => {
|
||||
return encodeURIComponent(String(url).trim().toLowerCase().replace(/\s+/g, '-'))
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue