mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-28 05:54:43 -04:00
Add YAML-metadata for notes and change the document title accordingly (#310)
* Added yaml-frontmatter extracting and error handling * add tests * changed document-title, so the editor can change the title to the title of the yaml metadata. closes #303 * extracted first line parsing in a core rule of markdown-it document title will now be determined like this: 1. yaml metadata title 2. opengraph title 3. first level one heading 4. 'Untitled' * added documentTitle e2e test Co-authored-by: Erik Michelson <github@erik.michelson.eu> Co-authored-by: Philip Molares <philip@mauricedoepke.de> Co-authored-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> Co-authored-by: mrdrogdrog <mr.drogdrog@gmail.com>
This commit is contained in:
parent
07fed5c67e
commit
29709d2ba4
13 changed files with 499 additions and 20 deletions
|
@ -2,12 +2,16 @@ import React, { useEffect } from 'react'
|
|||
import { useSelector } from 'react-redux'
|
||||
import { ApplicationState } from '../../../redux'
|
||||
|
||||
export const DocumentTitle: React.FC = () => {
|
||||
export interface DocumentTitleProps {
|
||||
title?: string
|
||||
}
|
||||
|
||||
export const DocumentTitle: React.FC<DocumentTitleProps> = ({ title }) => {
|
||||
const branding = useSelector((state: ApplicationState) => state.backendConfig.branding)
|
||||
|
||||
useEffect(() => {
|
||||
document.title = `CodiMD ${branding.name ? ` @ ${branding.name}` : ''}`
|
||||
}, [branding])
|
||||
document.title = `${title ? title + ' - ' : ''}CodiMD ${branding.name ? ` @ ${branding.name}` : ''}`
|
||||
}, [branding, title])
|
||||
|
||||
return null
|
||||
}
|
||||
|
|
|
@ -1,18 +1,31 @@
|
|||
import React, { Fragment, useEffect, useState } from 'react'
|
||||
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import useMedia from 'use-media'
|
||||
import { ApplicationState } from '../../redux'
|
||||
import { setEditorModeConfig } from '../../redux/editor/methods'
|
||||
import { DocumentTitle } from '../common/document-title/document-title'
|
||||
import { Splitter } from '../common/splitter/splitter'
|
||||
import { InfoBanner } from '../landing/layout/info-banner'
|
||||
import { EditorWindow } from './editor-window/editor-window'
|
||||
import { MarkdownRenderWindow } from './renderer-window/markdown-render-window'
|
||||
import { EditorMode } from './task-bar/editor-view-mode'
|
||||
import { TaskBar } from './task-bar/task-bar'
|
||||
import { YAMLMetaData } from './yaml-metadata/yaml-metadata'
|
||||
|
||||
const Editor: React.FC = () => {
|
||||
export const Editor: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const untitledNote = t('editor.untitledNote')
|
||||
const editorMode: EditorMode = useSelector((state: ApplicationState) => state.editorConfig.editorMode)
|
||||
const [markdownContent, setMarkdownContent] = useState(`# Embedding demo
|
||||
const [markdownContent, setMarkdownContent] = useState(`---
|
||||
title: Features
|
||||
description: Many features, such wow!
|
||||
robots: noindex
|
||||
tags: codimd, demo, react
|
||||
opengraph:
|
||||
title: Features
|
||||
---
|
||||
# Embedding demo
|
||||
[TOC]
|
||||
|
||||
## MathJax
|
||||
|
@ -55,12 +68,36 @@ https://asciinema.org/a/117928
|
|||
|
||||
## Code highlighting
|
||||
\`\`\`javascript=
|
||||
|
||||
let a = 1
|
||||
\`\`\`
|
||||
|
||||
`)
|
||||
const isWide = useMedia({ minWidth: 576 })
|
||||
const [firstDraw, setFirstDraw] = useState(true)
|
||||
const [documentTitle, setDocumentTitle] = useState(untitledNote)
|
||||
const noteMetadata = useRef<YAMLMetaData>()
|
||||
const firstHeading = useRef<string>()
|
||||
|
||||
const updateDocumentTitle = useCallback(() => {
|
||||
if (noteMetadata.current?.title && noteMetadata.current?.title !== '') {
|
||||
setDocumentTitle(noteMetadata.current.title)
|
||||
} else if (noteMetadata.current?.opengraph && noteMetadata.current?.opengraph.get('title') && noteMetadata.current?.opengraph.get('title') !== '') {
|
||||
setDocumentTitle(noteMetadata.current.opengraph.get('title') ?? untitledNote)
|
||||
} else {
|
||||
setDocumentTitle(firstHeading.current ?? untitledNote)
|
||||
}
|
||||
}, [untitledNote])
|
||||
|
||||
const onMetadataChange = useCallback((metaData: YAMLMetaData | undefined) => {
|
||||
noteMetadata.current = metaData
|
||||
updateDocumentTitle()
|
||||
}, [updateDocumentTitle])
|
||||
|
||||
const onFirstHeadingChange = useCallback((newFirstHeading: string | undefined) => {
|
||||
firstHeading.current = newFirstHeading
|
||||
updateDocumentTitle()
|
||||
}, [updateDocumentTitle])
|
||||
|
||||
useEffect(() => {
|
||||
setFirstDraw(false)
|
||||
|
@ -75,17 +112,16 @@ let a = 1
|
|||
return (
|
||||
<Fragment>
|
||||
<InfoBanner/>
|
||||
<DocumentTitle title={documentTitle}/>
|
||||
<div className={'d-flex flex-column vh-100'}>
|
||||
<TaskBar/>
|
||||
<Splitter
|
||||
showLeft={editorMode === EditorMode.EDITOR || editorMode === EditorMode.BOTH}
|
||||
left={<EditorWindow onContentChange={content => setMarkdownContent(content)} content={markdownContent}/>}
|
||||
showRight={editorMode === EditorMode.PREVIEW || (editorMode === EditorMode.BOTH)}
|
||||
right={<MarkdownRenderWindow content={markdownContent} wide={editorMode === EditorMode.PREVIEW}/>}
|
||||
right={<MarkdownRenderWindow content={markdownContent} wide={editorMode === EditorMode.PREVIEW} onMetadataChange={onMetadataChange} onFirstHeadingChange={onFirstHeadingChange}/>}
|
||||
containerClassName={'overflow-hidden'}/>
|
||||
</div>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export { Editor }
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import equal from 'deep-equal'
|
||||
import { DomElement } from 'domhandler'
|
||||
import yaml from 'js-yaml'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import abbreviation from 'markdown-it-abbr'
|
||||
import anchor from 'markdown-it-anchor'
|
||||
|
@ -7,6 +8,7 @@ import markdownItContainer from 'markdown-it-container'
|
|||
import definitionList from 'markdown-it-deflist'
|
||||
import emoji from 'markdown-it-emoji'
|
||||
import footnote from 'markdown-it-footnote'
|
||||
import frontmatter from 'markdown-it-front-matter'
|
||||
import imsize from 'markdown-it-imsize'
|
||||
import inserted from 'markdown-it-ins'
|
||||
import marked from 'markdown-it-mark'
|
||||
|
@ -16,11 +18,16 @@ import subscript from 'markdown-it-sub'
|
|||
import superscript from 'markdown-it-sup'
|
||||
import taskList from 'markdown-it-task-lists'
|
||||
import toc from 'markdown-it-toc-done-right'
|
||||
import React, { ReactElement, useEffect, useMemo, useState } from 'react'
|
||||
import React, { ReactElement, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Alert } from 'react-bootstrap'
|
||||
import ReactHtmlParser, { convertNodeToElement, Transform } from 'react-html-parser'
|
||||
import { Trans } from 'react-i18next'
|
||||
import MathJaxReact from 'react-mathjax'
|
||||
import { TocAst } from '../../../external-types/markdown-it-toc-done-right/interface'
|
||||
import { slugify } from '../../../utils/slugify'
|
||||
import { InternalLink } from '../../common/links/internal-link'
|
||||
import { ShowIf } from '../../common/show-if/show-if'
|
||||
import { RawYAMLMetadata, YAMLMetaData } from '../yaml-metadata/yaml-metadata'
|
||||
import { createRenderContainer, validAlertLevels } from './container-plugins/alert'
|
||||
import { highlightedCode } from './markdown-it-plugins/highlighted-code'
|
||||
import { linkifyExtra } from './markdown-it-plugins/linkify-extra'
|
||||
|
@ -57,11 +64,34 @@ export interface MarkdownRendererProps {
|
|||
wide?: boolean
|
||||
className?: string
|
||||
onTocChange?: (ast: TocAst) => void
|
||||
onMetaDataChange?: (yamlMetaData: YAMLMetaData | undefined) => void
|
||||
onFirstHeadingChange?: (firstHeading: string | undefined) => void
|
||||
}
|
||||
|
||||
export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({ content, className, onTocChange, wide }) => {
|
||||
export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({ content, onMetaDataChange, onFirstHeadingChange, onTocChange, className, wide }) => {
|
||||
const [tocAst, setTocAst] = useState<TocAst>()
|
||||
const [lastTocAst, setLastTocAst] = useState<TocAst>()
|
||||
const [yamlError, setYamlError] = useState(false)
|
||||
const rawMetaRef = useRef<RawYAMLMetadata>()
|
||||
const oldMetaRef = useRef<RawYAMLMetadata>()
|
||||
const firstHeadingRef = useRef<string>()
|
||||
const oldFirstHeadingRef = useRef<string>()
|
||||
|
||||
useEffect(() => {
|
||||
if (onMetaDataChange && !equal(oldMetaRef.current, rawMetaRef.current)) {
|
||||
if (rawMetaRef.current) {
|
||||
const newMetaData = new YAMLMetaData(rawMetaRef.current)
|
||||
onMetaDataChange(newMetaData)
|
||||
} else {
|
||||
onMetaDataChange(undefined)
|
||||
}
|
||||
oldMetaRef.current = rawMetaRef.current
|
||||
}
|
||||
if (onFirstHeadingChange && !equal(firstHeadingRef.current, oldFirstHeadingRef.current)) {
|
||||
onFirstHeadingChange(firstHeadingRef.current || undefined)
|
||||
oldFirstHeadingRef.current = firstHeadingRef.current
|
||||
}
|
||||
})
|
||||
|
||||
const markdownIt = useMemo(() => {
|
||||
const md = new MarkdownIt('default', {
|
||||
|
@ -70,6 +100,32 @@ export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({ content, cla
|
|||
langPrefix: '',
|
||||
typographer: true
|
||||
})
|
||||
if (onFirstHeadingChange) {
|
||||
md.core.ruler.after('normalize', 'extract first L1 heading', (state) => {
|
||||
const lines = state.src.split('\n')
|
||||
const linkAltTextRegex = /!?\[([^\]]*)]\([^)]*\)/
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('# ')) {
|
||||
firstHeadingRef.current = line.replace('# ', '').replace(linkAltTextRegex, '$1')
|
||||
return true
|
||||
}
|
||||
}
|
||||
firstHeadingRef.current = undefined
|
||||
return true
|
||||
})
|
||||
}
|
||||
if (onMetaDataChange) {
|
||||
md.use(frontmatter, (rawMeta: string) => {
|
||||
try {
|
||||
const meta: RawYAMLMetadata = yaml.safeLoad(rawMeta) as RawYAMLMetadata
|
||||
setYamlError(false)
|
||||
rawMetaRef.current = meta
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
setYamlError(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
md.use(taskList)
|
||||
md.use(emoji)
|
||||
md.use(abbreviation)
|
||||
|
@ -79,6 +135,19 @@ export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({ content, cla
|
|||
md.use(inserted)
|
||||
md.use(marked)
|
||||
md.use(footnote)
|
||||
if (onMetaDataChange) {
|
||||
md.use(frontmatter, (rawMeta: string) => {
|
||||
try {
|
||||
const meta: RawYAMLMetadata = yaml.safeLoad(rawMeta) as RawYAMLMetadata
|
||||
setYamlError(false)
|
||||
rawMetaRef.current = meta
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
setYamlError(true)
|
||||
rawMetaRef.current = ({} as RawYAMLMetadata)
|
||||
}
|
||||
})
|
||||
}
|
||||
md.use(imsize)
|
||||
// noinspection CheckTagEmptyBody
|
||||
md.use(anchor, {
|
||||
|
@ -126,7 +195,7 @@ export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({ content, cla
|
|||
})
|
||||
|
||||
return md
|
||||
}, [])
|
||||
}, [onMetaDataChange, onFirstHeadingChange])
|
||||
|
||||
useEffect(() => {
|
||||
if (onTocChange && tocAst && !equal(tocAst, lastTocAst)) {
|
||||
|
@ -155,6 +224,10 @@ export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({ content, cla
|
|||
new QuoteOptionsReplacer(),
|
||||
new MathjaxReplacer()
|
||||
]
|
||||
if (onMetaDataChange) {
|
||||
// This is used if the front-matter callback is never called, because the user deleted everything regarding metadata from the document
|
||||
rawMetaRef.current = undefined
|
||||
}
|
||||
const html: string = markdownIt.render(content)
|
||||
|
||||
const transform: Transform = (node, index) => {
|
||||
|
@ -162,10 +235,17 @@ export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({ content, cla
|
|||
return tryToReplaceNode(node, index, allReplacers, subNodeConverter) || convertNodeToElement(node, index, transform)
|
||||
}
|
||||
return ReactHtmlParser(html, { transform: transform })
|
||||
}, [content, markdownIt])
|
||||
}, [content, markdownIt, onMetaDataChange])
|
||||
|
||||
return (
|
||||
<div className={`markdown-body ${className || ''} d-flex flex-column align-items-center ${wide ? 'wider' : ''}`}>
|
||||
<ShowIf condition={yamlError}>
|
||||
<Alert variant='warning' dir='auto'>
|
||||
<Trans i18nKey='editor.invalidYaml'>
|
||||
<InternalLink text='yaml-metadata' href='/n/yaml-metadata' className='text-dark'/>
|
||||
</Trans>
|
||||
</Alert>
|
||||
</ShowIf>
|
||||
<MathJaxReact.Provider>
|
||||
{result}
|
||||
</MathJaxReact.Provider>
|
||||
|
|
|
@ -6,13 +6,16 @@ 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'
|
||||
import { YAMLMetaData } from '../yaml-metadata/yaml-metadata'
|
||||
|
||||
interface RenderWindowProps {
|
||||
content: string
|
||||
onMetadataChange: (metaData: YAMLMetaData | undefined) => void
|
||||
onFirstHeadingChange: (firstHeading: string | undefined) => void
|
||||
wide?: boolean
|
||||
}
|
||||
|
||||
export const MarkdownRenderWindow: React.FC<RenderWindowProps> = ({ content, wide }) => {
|
||||
export const MarkdownRenderWindow: React.FC<RenderWindowProps> = ({ content, onMetadataChange, onFirstHeadingChange, wide }) => {
|
||||
const [tocAst, setTocAst] = useState<TocAst>()
|
||||
const renderer = useRef<HTMLDivElement>(null)
|
||||
const { width } = useResizeObserver({ ref: renderer })
|
||||
|
@ -26,7 +29,10 @@ export const MarkdownRenderWindow: React.FC<RenderWindowProps> = ({ content, wid
|
|||
className={'flex-fill'}
|
||||
content={content}
|
||||
wide={wide}
|
||||
onTocChange={(tocAst) => setTocAst(tocAst)}/>
|
||||
onTocChange={(tocAst) => setTocAst(tocAst)}
|
||||
onMetaDataChange={onMetadataChange}
|
||||
onFirstHeadingChange={onFirstHeadingChange}
|
||||
/>
|
||||
|
||||
<div className={`col-md d-flex flex-column ${realWidth < 1280 ? 'justify-content-end' : ''}`}>
|
||||
<ShowIf condition={realWidth >= 1280 && !!tocAst}>
|
||||
|
|
203
src/components/editor/yaml-metadata/yaml-metadata.test.ts
Normal file
203
src/components/editor/yaml-metadata/yaml-metadata.test.ts
Normal file
|
@ -0,0 +1,203 @@
|
|||
import yaml from 'js-yaml'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import frontmatter from 'markdown-it-front-matter'
|
||||
import { RawYAMLMetadata, YAMLMetaData } from './yaml-metadata'
|
||||
|
||||
describe('yaml tests', () => {
|
||||
let raw: RawYAMLMetadata | undefined
|
||||
let finished: YAMLMetaData | undefined
|
||||
const md = new MarkdownIt('default', {
|
||||
html: true,
|
||||
breaks: true,
|
||||
langPrefix: '',
|
||||
typographer: true
|
||||
})
|
||||
md.use(frontmatter, (rawMeta: string) => {
|
||||
raw = yaml.safeLoad(rawMeta) as RawYAMLMetadata
|
||||
finished = new YAMLMetaData(raw)
|
||||
})
|
||||
|
||||
// generate default YAMLMetadata
|
||||
md.render('---\n---')
|
||||
const defaultYAML = finished
|
||||
|
||||
const testMetadata = (input: string, expectedRaw: Partial<RawYAMLMetadata>, expectedFinished: Partial<YAMLMetaData>) => {
|
||||
md.render(input)
|
||||
expect(raw).not.toBe(undefined)
|
||||
expect(raw).toEqual(expectedRaw)
|
||||
expect(finished).not.toBe(undefined)
|
||||
expect(finished).toEqual({
|
||||
...defaultYAML,
|
||||
...expectedFinished
|
||||
})
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
raw = undefined
|
||||
finished = undefined
|
||||
})
|
||||
|
||||
it('title only', () => {
|
||||
testMetadata(`---
|
||||
title: test
|
||||
___
|
||||
`,
|
||||
{
|
||||
title: 'test'
|
||||
},
|
||||
{
|
||||
title: 'test'
|
||||
})
|
||||
})
|
||||
|
||||
it('robots only', () => {
|
||||
testMetadata(`---
|
||||
robots: index, follow
|
||||
___
|
||||
`,
|
||||
{
|
||||
robots: 'index, follow'
|
||||
},
|
||||
{
|
||||
robots: 'index, follow'
|
||||
})
|
||||
})
|
||||
|
||||
it('tags only', () => {
|
||||
testMetadata(`---
|
||||
tags: test123, abc
|
||||
___
|
||||
`,
|
||||
{
|
||||
tags: 'test123, abc'
|
||||
},
|
||||
{
|
||||
tags: ['test123', 'abc']
|
||||
})
|
||||
})
|
||||
|
||||
it('breaks only', () => {
|
||||
testMetadata(`---
|
||||
breaks: false
|
||||
___
|
||||
`,
|
||||
{
|
||||
breaks: false
|
||||
},
|
||||
{
|
||||
breaks: false
|
||||
})
|
||||
})
|
||||
|
||||
/*
|
||||
it('slideOptions nothing', () => {
|
||||
testMetadata(`---
|
||||
slideOptions:
|
||||
___
|
||||
`,
|
||||
{
|
||||
slideOptions: null
|
||||
},
|
||||
{
|
||||
slideOptions: {
|
||||
theme: 'white',
|
||||
transition: 'none'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('slideOptions.theme only', () => {
|
||||
testMetadata(`---
|
||||
slideOptions:
|
||||
theme: sky
|
||||
___
|
||||
`,
|
||||
{
|
||||
slideOptions: {
|
||||
theme: 'sky',
|
||||
transition: undefined
|
||||
}
|
||||
},
|
||||
{
|
||||
slideOptions: {
|
||||
theme: 'sky',
|
||||
transition: 'none'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('slideOptions full', () => {
|
||||
testMetadata(`---
|
||||
slideOptions:
|
||||
transition: zoom
|
||||
theme: sky
|
||||
___
|
||||
`,
|
||||
{
|
||||
slideOptions: {
|
||||
theme: 'sky',
|
||||
transition: 'zoom'
|
||||
}
|
||||
},
|
||||
{
|
||||
slideOptions: {
|
||||
theme: 'sky',
|
||||
transition: 'zoom'
|
||||
}
|
||||
})
|
||||
})
|
||||
*/
|
||||
|
||||
it('opengraph nothing', () => {
|
||||
testMetadata(`---
|
||||
opengraph:
|
||||
___
|
||||
`,
|
||||
{
|
||||
opengraph: null
|
||||
},
|
||||
{
|
||||
opengraph: new Map<string, string>()
|
||||
})
|
||||
})
|
||||
|
||||
it('opengraph title only', () => {
|
||||
testMetadata(`---
|
||||
opengraph:
|
||||
title: Testtitle
|
||||
___
|
||||
`,
|
||||
{
|
||||
opengraph: {
|
||||
title: 'Testtitle'
|
||||
}
|
||||
},
|
||||
{
|
||||
opengraph: new Map<string, string>(Object.entries({ title: 'Testtitle' }))
|
||||
})
|
||||
})
|
||||
|
||||
it('opengraph more attributes', () => {
|
||||
testMetadata(`---
|
||||
opengraph:
|
||||
title: Testtitle
|
||||
image: https://dummyimage.com/48.png
|
||||
image:type: image/png
|
||||
___
|
||||
`,
|
||||
{
|
||||
opengraph: {
|
||||
title: 'Testtitle',
|
||||
image: 'https://dummyimage.com/48.png',
|
||||
'image:type': 'image/png'
|
||||
}
|
||||
},
|
||||
{
|
||||
opengraph: new Map<string, string>(Object.entries({
|
||||
title: 'Testtitle',
|
||||
image: 'https://dummyimage.com/48.png',
|
||||
'image:type': 'image/png'
|
||||
}))
|
||||
})
|
||||
})
|
||||
})
|
53
src/components/editor/yaml-metadata/yaml-metadata.ts
Normal file
53
src/components/editor/yaml-metadata/yaml-metadata.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
// import { RevealOptions } from 'reveal.js'
|
||||
|
||||
type iso6391 = 'aa' | 'ab' | 'af' | 'am' | 'ar' | 'ar-ae' | 'ar-bh' | 'ar-dz' | 'ar-eg' | 'ar-iq' | 'ar-jo' | 'ar-kw' | 'ar-lb' | 'ar-ly' | 'ar-ma' | 'ar-om' | 'ar-qa' | 'ar-sa' | 'ar-sy' | 'ar-tn' | 'ar-ye' | 'as' | 'ay' | 'de-at' | 'de-ch' | 'de-li' | 'de-lu' | 'div' | 'dz' | 'el' | 'en' | 'en-au' | 'en-bz' | 'en-ca' | 'en-gb' | 'en-ie' | 'en-jm' | 'en-nz' | 'en-ph' | 'en-tt' | 'en-us' | 'en-za' | 'en-zw' | 'eo' | 'es' | 'es-ar' | 'es-bo' | 'es-cl' | 'es-co' | 'es-cr' | 'es-do' | 'es-ec' | 'es-es' | 'es-gt' | 'es-hn' | 'es-mx' | 'es-ni' | 'es-pa' | 'es-pe' | 'es-pr' | 'es-py' | 'es-sv' | 'es-us' | 'es-uy' | 'es-ve' | 'et' | 'eu' | 'fa' | 'fi' | 'fj' | 'fo' | 'fr' | 'fr-be' | 'fr-ca' | 'fr-ch' | 'fr-lu' | 'fr-mc' | 'fy' | 'ga' | 'gd' | 'gl' | 'gn' | 'gu' | 'ha' | 'he' | 'hi' | 'hr' | 'hu' | 'hy' | 'ia' | 'id' | 'ie' | 'ik' | 'in' | 'is' | 'it' | 'it-ch' | 'iw' | 'ja' | 'ji' | 'jw' | 'ka' | 'kk' | 'kl' | 'km' | 'kn' | 'ko' | 'kok' | 'ks' | 'ku' | 'ky' | 'kz' | 'la' | 'ln' | 'lo' | 'ls' | 'lt' | 'lv' | 'mg' | 'mi' | 'mk' | 'ml' | 'mn' | 'mo' | 'mr' | 'ms' | 'mt' | 'my' | 'na' | 'nb-no' | 'ne' | 'nl' | 'nl-be' | 'nn-no' | 'no' | 'oc' | 'om' | 'or' | 'pa' | 'pl' | 'ps' | 'pt' | 'pt-br' | 'qu' | 'rm' | 'rn' | 'ro' | 'ro-md' | 'ru' | 'ru-md' | 'rw' | 'sa' | 'sb' | 'sd' | 'sg' | 'sh' | 'si' | 'sk' | 'sl' | 'sm' | 'sn' | 'so' | 'sq' | 'sr' | 'ss' | 'st' | 'su' | 'sv' | 'sv-fi' | 'sw' | 'sx' | 'syr' | 'ta' | 'te' | 'tg' | 'th' | 'ti' | 'tk' | 'tl' | 'tn' | 'to' | 'tr' | 'ts' | 'tt' | 'tw' | 'uk' | 'ur' | 'us' | 'uz' | 'vi' | 'vo' | 'wo' | 'xh' | 'yi' | 'yo' | 'zh' | 'zh-cn' | 'zh-hk' | 'zh-mo' | 'zh-sg' | 'zh-tw' | 'zu'
|
||||
|
||||
export interface RawYAMLMetadata {
|
||||
title: string | undefined
|
||||
description: string | undefined
|
||||
tags: string | undefined
|
||||
robots: string | undefined
|
||||
lang: string | undefined
|
||||
dir: string | undefined
|
||||
breaks: boolean | undefined
|
||||
GA: string | undefined
|
||||
disqus: string | undefined
|
||||
type: string | undefined
|
||||
slideOptions: any
|
||||
opengraph: any
|
||||
}
|
||||
|
||||
export class YAMLMetaData {
|
||||
title: string
|
||||
description: string
|
||||
tags: string[]
|
||||
robots: string
|
||||
lang: iso6391
|
||||
dir: 'ltr' | 'rtl'
|
||||
breaks: boolean
|
||||
GA: string
|
||||
disqus: string
|
||||
type: 'slide' | ''
|
||||
// slideOptions: RevealOptions
|
||||
opengraph: Map<string, string>
|
||||
|
||||
constructor (rawData: RawYAMLMetadata) {
|
||||
this.title = rawData?.title ?? ''
|
||||
this.description = rawData?.description ?? ''
|
||||
this.robots = rawData?.robots ?? ''
|
||||
this.breaks = rawData?.breaks ?? true
|
||||
this.GA = rawData?.GA ?? ''
|
||||
this.disqus = rawData?.disqus ?? ''
|
||||
|
||||
this.type = (rawData?.type as YAMLMetaData['type']) ?? ''
|
||||
this.lang = (rawData?.lang as iso6391) ?? 'en'
|
||||
this.dir = (rawData?.dir as YAMLMetaData['dir']) ?? 'ltr'
|
||||
|
||||
/* this.slideOptions = (rawData?.slideOptions as RevealOptions) ?? {
|
||||
transition: 'none',
|
||||
theme: 'white'
|
||||
} */
|
||||
this.tags = rawData?.tags?.split(',').map(entry => entry.trim()) ?? []
|
||||
this.opengraph = rawData?.opengraph ? new Map<string, string>(Object.entries(rawData.opengraph)) : new Map<string, string>()
|
||||
}
|
||||
}
|
5
src/external-types/markdown-it-front-matter/index.d.ts
vendored
Normal file
5
src/external-types/markdown-it-front-matter/index.d.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
declare module 'markdown-it-front-matter' {
|
||||
import MarkdownIt from 'markdown-it/lib'
|
||||
const markdownItFrontMatter: MarkdownIt.PluginSimple
|
||||
export = markdownItFrontMatter
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue