mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-26 11:43:59 -05:00
Extract clean title from first heading (#616)
* removed first-header-extractor get first heading from markdown rende * don't remove editor or renderer just hide them this way both are always up to date and can be shown very fast * extracted image alt attribute into first title. too * added tests as suggested by @mrdrogdrog
This commit is contained in:
parent
192f66d169
commit
1ab9b58031
5 changed files with 47 additions and 47 deletions
|
@ -63,5 +63,23 @@ describe('Document Title', () => {
|
||||||
.type(`# ${title} [link](https://codimd.org)`)
|
.type(`# ${title} [link](https://codimd.org)`)
|
||||||
cy.title().should('eq', `${title} link - HedgeDoc @ ${branding.name}`)
|
cy.title().should('eq', `${title} link - HedgeDoc @ ${branding.name}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('markdown syntax removed first', () => {
|
||||||
|
cy.get('.CodeMirror textarea')
|
||||||
|
.type(`# ${title} 1*2*3 4*5**`)
|
||||||
|
cy.title().should('eq', `${title} 123 4*5** - HedgeDoc @ ${branding.name}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('markdown syntax removed second', () => {
|
||||||
|
cy.get('.CodeMirror textarea')
|
||||||
|
.type(`# ${title} **1 2*`)
|
||||||
|
cy.title().should('eq', `${title} *1 2 - HedgeDoc @ ${branding.name}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('markdown syntax removed third', () => {
|
||||||
|
cy.get('.CodeMirror textarea')
|
||||||
|
.type(`# ${title} _asd_`)
|
||||||
|
cy.title().should('eq', `${title} asd - HedgeDoc @ ${branding.name}`)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -54,7 +54,7 @@ export const Editor: React.FC = () => {
|
||||||
} else if (noteMetadata.current?.opengraph && noteMetadata.current?.opengraph.get('title') && noteMetadata.current?.opengraph.get('title') !== '') {
|
} else if (noteMetadata.current?.opengraph && noteMetadata.current?.opengraph.get('title') && noteMetadata.current?.opengraph.get('title') !== '') {
|
||||||
setDocumentTitle(noteMetadata.current.opengraph.get('title') ?? untitledNote)
|
setDocumentTitle(noteMetadata.current.opengraph.get('title') ?? untitledNote)
|
||||||
} else {
|
} else {
|
||||||
setDocumentTitle(firstHeading.current ?? untitledNote)
|
setDocumentTitle((firstHeading.current ?? untitledNote).trim())
|
||||||
}
|
}
|
||||||
}, [untitledNote])
|
}, [untitledNote])
|
||||||
|
|
||||||
|
|
|
@ -43,21 +43,17 @@ export const Splitter: React.FC<SplitterProps> = ({ containerClassName, left, ri
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ShowIf condition={showLeft}>
|
<div className={`splitter left ${!showLeft ? 'd-none' : ''}`} style={{ flexBasis: `calc(${realSplit}% - 5px)` }}>
|
||||||
<div className={'splitter left'} style={{ flexBasis: `calc(${realSplit}% - 5px)` }}>
|
|
||||||
{left}
|
{left}
|
||||||
</div>
|
</div>
|
||||||
</ShowIf>
|
|
||||||
<ShowIf condition={showLeft && showRight}>
|
<ShowIf condition={showLeft && showRight}>
|
||||||
<div className='splitter separator'>
|
<div className='splitter separator'>
|
||||||
<SplitDivider onGrab={() => setDoResizing(true)}/>
|
<SplitDivider onGrab={() => setDoResizing(true)}/>
|
||||||
</div>
|
</div>
|
||||||
</ShowIf>
|
</ShowIf>
|
||||||
<ShowIf condition={showRight}>
|
<div className={`splitter right ${!showRight ? 'd-none' : ''}`}>
|
||||||
<div className={'splitter right'}>
|
|
||||||
{right}
|
{right}
|
||||||
</div>
|
</div>
|
||||||
</ShowIf>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import mathJax from 'markdown-it-mathjax'
|
||||||
import plantuml from 'markdown-it-plantuml'
|
import plantuml from 'markdown-it-plantuml'
|
||||||
import markdownItRegex from 'markdown-it-regex'
|
import markdownItRegex from 'markdown-it-regex'
|
||||||
import toc from 'markdown-it-toc-done-right'
|
import toc from 'markdown-it-toc-done-right'
|
||||||
import React, { useCallback, useMemo, useRef, useState } from 'react'
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { Alert } from 'react-bootstrap'
|
import { Alert } from 'react-bootstrap'
|
||||||
import { Trans } from 'react-i18next'
|
import { Trans } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
@ -20,7 +20,6 @@ import { slugify } from '../editor/table-of-contents/table-of-contents'
|
||||||
import { RawYAMLMetadata, YAMLMetaData } from '../editor/yaml-metadata/yaml-metadata'
|
import { RawYAMLMetadata, YAMLMetaData } from '../editor/yaml-metadata/yaml-metadata'
|
||||||
import { BasicMarkdownRenderer } from './basic-markdown-renderer'
|
import { BasicMarkdownRenderer } from './basic-markdown-renderer'
|
||||||
import { createRenderContainer, validAlertLevels } from './markdown-it-plugins/alert-container'
|
import { createRenderContainer, validAlertLevels } from './markdown-it-plugins/alert-container'
|
||||||
import { firstHeaderExtractor } from './markdown-it-plugins/first-header-extractor'
|
|
||||||
import { highlightedCode } from './markdown-it-plugins/highlighted-code'
|
import { highlightedCode } from './markdown-it-plugins/highlighted-code'
|
||||||
import { LineMarkers, lineNumberMarker } from './markdown-it-plugins/line-number-marker'
|
import { LineMarkers, lineNumberMarker } from './markdown-it-plugins/line-number-marker'
|
||||||
import { plantumlError } from './markdown-it-plugins/plantuml-error'
|
import { plantumlError } from './markdown-it-plugins/plantuml-error'
|
||||||
|
@ -119,15 +118,28 @@ export const FullMarkdownRenderer: React.FC<FullMarkdownRendererProps & Addition
|
||||||
const tocAst = useRef<TocAst>()
|
const tocAst = useRef<TocAst>()
|
||||||
usePostTocAstOnChange(tocAst, onTocChange)
|
usePostTocAstOnChange(tocAst, onTocChange)
|
||||||
|
|
||||||
const configureMarkdownIt = useCallback((md: MarkdownIt): void => {
|
const extractInnerText = useCallback((node: ChildNode) => {
|
||||||
if (onFirstHeadingChange) {
|
let innerText = ''
|
||||||
md.use(firstHeaderExtractor(), {
|
if (node.childNodes && node.childNodes.length > 0) {
|
||||||
firstHeaderFound: (firstHeader: string | undefined) => {
|
node.childNodes.forEach((child) => { innerText += extractInnerText(child) })
|
||||||
firstHeadingRef.current = firstHeader
|
} else if (node.nodeName === 'IMG') {
|
||||||
}
|
innerText += (node as HTMLImageElement).getAttribute('alt')
|
||||||
})
|
} else {
|
||||||
|
innerText += node.textContent
|
||||||
}
|
}
|
||||||
|
return innerText
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (onFirstHeadingChange && documentElement.current) {
|
||||||
|
const firstHeading = documentElement.current.getElementsByTagName('h1').item(0)
|
||||||
|
if (firstHeading) {
|
||||||
|
onFirstHeadingChange(extractInnerText(firstHeading))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [content, extractInnerText, onFirstHeadingChange])
|
||||||
|
|
||||||
|
const configureMarkdownIt = useCallback((md: MarkdownIt): void => {
|
||||||
if (onMetaDataChange) {
|
if (onMetaDataChange) {
|
||||||
md.use(frontmatter, (rawMeta: string) => {
|
md.use(frontmatter, (rawMeta: string) => {
|
||||||
try {
|
try {
|
||||||
|
@ -210,7 +222,7 @@ export const FullMarkdownRenderer: React.FC<FullMarkdownRendererProps & Addition
|
||||||
currentLineMarkers.current = lineMarkers
|
currentLineMarkers.current = lineMarkers
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [onFirstHeadingChange, onMetaDataChange, plantumlServer])
|
}, [onMetaDataChange, plantumlServer])
|
||||||
|
|
||||||
const clearMetadata = useCallback(() => {
|
const clearMetadata = useCallback(() => {
|
||||||
rawMetaRef.current = undefined
|
rawMetaRef.current = undefined
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
import MarkdownIt from 'markdown-it/lib'
|
|
||||||
|
|
||||||
export interface FirstHeaderExtractorOptions {
|
|
||||||
firstHeaderFound: (firstHeader: string|undefined) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const firstHeaderExtractor: () => MarkdownIt.PluginWithOptions<FirstHeaderExtractorOptions> = () => {
|
|
||||||
return (md, options) => {
|
|
||||||
if (!options?.firstHeaderFound) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
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('# ')) {
|
|
||||||
const headerLine = line.replace('# ', '').replace(linkAltTextRegex, '$1')
|
|
||||||
options.firstHeaderFound(headerLine)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
options.firstHeaderFound(undefined)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue