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:
Philip Molares 2020-09-30 23:50:32 +02:00 committed by GitHub
parent 192f66d169
commit 1ab9b58031
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 47 additions and 47 deletions

View file

@ -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}`)
})
}) })
}) })

View file

@ -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])

View file

@ -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>
) )
} }

View file

@ -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

View file

@ -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
})
}
}