mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-13 22:54:42 -04:00
Add slide mode with reveal.js
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
29565f8f89
commit
36e445e631
70 changed files with 1225 additions and 323 deletions
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { TocAst } from 'markdown-it-toc-done-right'
|
||||
import { ImageClickHandler } from './replace-components/image/image-replacer'
|
||||
import { Ref } from 'react'
|
||||
|
||||
export interface CommonMarkdownRendererProps {
|
||||
onFirstHeadingChange?: (firstHeading: string | undefined) => void
|
||||
onTaskCheckedChange?: (lineInMarkdown: number, checked: boolean) => void
|
||||
onTocChange?: (ast?: TocAst) => void
|
||||
baseUrl?: string
|
||||
onImageClick?: ImageClickHandler
|
||||
outerContainerRef?: Ref<HTMLDivElement>
|
||||
useAlternativeBreaks?: boolean
|
||||
lineOffset?: number
|
||||
className?: string
|
||||
content: string
|
||||
}
|
|
@ -4,12 +4,11 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { Ref, useCallback, useMemo, useRef } from 'react'
|
||||
import React, { useMemo, useRef } from 'react'
|
||||
import { DocumentLengthLimitReachedAlert } from './document-length-limit-reached-alert'
|
||||
import { useConvertMarkdownToReactDom } from './hooks/use-convert-markdown-to-react-dom'
|
||||
import './markdown-renderer.scss'
|
||||
import { ComponentReplacer } from './replace-components/ComponentReplacer'
|
||||
import { AdditionalMarkdownRendererProps, LineMarkerPosition } from './types'
|
||||
import { LineMarkerPosition } from './types'
|
||||
import { useComponentReplacers } from './hooks/use-component-replacers'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { LineMarkers } from './replace-components/linemarker/line-number-marker'
|
||||
|
@ -18,28 +17,16 @@ import { useExtractFirstHeadline } from './hooks/use-extract-first-headline'
|
|||
import { TocAst } from 'markdown-it-toc-done-right'
|
||||
import { useOnRefChange } from './hooks/use-on-ref-change'
|
||||
import { BasicMarkdownItConfigurator } from './markdown-it-configurator/basic-markdown-it-configurator'
|
||||
import { ImageClickHandler } from './replace-components/image/image-replacer'
|
||||
import { useTrimmedContent } from './hooks/use-trimmed-content'
|
||||
import { CommonMarkdownRendererProps } from './common-markdown-renderer-props'
|
||||
|
||||
export interface BasicMarkdownRendererProps {
|
||||
additionalReplacers?: () => ComponentReplacer[]
|
||||
onBeforeRendering?: () => void
|
||||
onAfterRendering?: () => void
|
||||
onFirstHeadingChange?: (firstHeading: string | undefined) => void
|
||||
export interface DocumentMarkdownRendererProps extends CommonMarkdownRendererProps {
|
||||
onLineMarkerPositionChanged?: (lineMarkerPosition: LineMarkerPosition[]) => void
|
||||
onTaskCheckedChange?: (lineInMarkdown: number, checked: boolean) => void
|
||||
onTocChange?: (ast?: TocAst) => void
|
||||
baseUrl?: string
|
||||
onImageClick?: ImageClickHandler
|
||||
outerContainerRef?: Ref<HTMLDivElement>
|
||||
useAlternativeBreaks?: boolean
|
||||
frontmatterLineOffset?: number
|
||||
}
|
||||
|
||||
export const BasicMarkdownRenderer: React.FC<BasicMarkdownRendererProps & AdditionalMarkdownRendererProps> = ({
|
||||
export const DocumentMarkdownRenderer: React.FC<DocumentMarkdownRendererProps> = ({
|
||||
className,
|
||||
content,
|
||||
additionalReplacers,
|
||||
onFirstHeadingChange,
|
||||
onLineMarkerPositionChanged,
|
||||
onTaskCheckedChange,
|
||||
|
@ -48,7 +35,7 @@ export const BasicMarkdownRenderer: React.FC<BasicMarkdownRendererProps & Additi
|
|||
onImageClick,
|
||||
outerContainerRef,
|
||||
useAlternativeBreaks,
|
||||
frontmatterLineOffset
|
||||
lineOffset
|
||||
}) => {
|
||||
const markdownBodyRef = useRef<HTMLDivElement>(null)
|
||||
const currentLineMarkers = useRef<LineMarkers[]>()
|
||||
|
@ -64,17 +51,12 @@ export const BasicMarkdownRenderer: React.FC<BasicMarkdownRendererProps & Additi
|
|||
? undefined
|
||||
: (lineMarkers) => (currentLineMarkers.current = lineMarkers),
|
||||
useAlternativeBreaks,
|
||||
offsetLines: frontmatterLineOffset
|
||||
lineOffset,
|
||||
headlineAnchors: true
|
||||
}).buildConfiguredMarkdownIt(),
|
||||
[onLineMarkerPositionChanged, useAlternativeBreaks, frontmatterLineOffset]
|
||||
[onLineMarkerPositionChanged, useAlternativeBreaks, lineOffset]
|
||||
)
|
||||
|
||||
const baseReplacers = useComponentReplacers(onTaskCheckedChange, onImageClick, baseUrl, frontmatterLineOffset)
|
||||
const replacers = useCallback(
|
||||
() => baseReplacers().concat(additionalReplacers ? additionalReplacers() : []),
|
||||
[additionalReplacers, baseReplacers]
|
||||
)
|
||||
|
||||
const replacers = useComponentReplacers(onTaskCheckedChange, onImageClick, baseUrl, lineOffset)
|
||||
const markdownReactDom = useConvertMarkdownToReactDom(trimmedContent, markdownIt, replacers)
|
||||
|
||||
useTranslation()
|
||||
|
@ -99,4 +81,4 @@ export const BasicMarkdownRenderer: React.FC<BasicMarkdownRendererProps & Additi
|
|||
)
|
||||
}
|
||||
|
||||
export default BasicMarkdownRenderer
|
||||
export default DocumentMarkdownRenderer
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { useCallback } from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { AbcReplacer } from '../replace-components/abc/abc-replacer'
|
||||
import { AsciinemaReplacer } from '../replace-components/asciinema/asciinema-replacer'
|
||||
import { ComponentReplacer } from '../replace-components/ComponentReplacer'
|
||||
|
@ -41,8 +41,8 @@ export const useComponentReplacers = (
|
|||
onImageClick?: ImageClickHandler,
|
||||
baseUrl?: string,
|
||||
frontmatterLinesToSkip?: number
|
||||
): (() => ComponentReplacer[]) =>
|
||||
useCallback(
|
||||
): ComponentReplacer[] =>
|
||||
useMemo(
|
||||
() => [
|
||||
new LinemarkerReplacer(),
|
||||
new GistReplacer(),
|
||||
|
|
|
@ -11,6 +11,7 @@ import { LineKeys } from '../types'
|
|||
import { buildTransformer } from '../utils/html-react-transformer'
|
||||
import { calculateNewLineNumberMapping } from '../utils/line-number-mapping'
|
||||
import convertHtmlToReact from '@hedgedoc/html-to-react'
|
||||
import { Document } from 'domhandler'
|
||||
|
||||
/**
|
||||
* Renders markdown code into react elements
|
||||
|
@ -18,24 +19,19 @@ import convertHtmlToReact from '@hedgedoc/html-to-react'
|
|||
* @param markdownCode The markdown code that should be rendered
|
||||
* @param markdownIt The configured {@link MarkdownIt markdown it} instance that should render the code
|
||||
* @param replacers A function that provides a list of {@link ComponentReplacer component replacers}
|
||||
* @param onBeforeRendering A callback that gets executed before the rendering
|
||||
* @param onAfterRendering A callback that gets executed after the rendering
|
||||
* @param preprocessNodes A function that processes nodes after parsing the html code that is generated by markdown it.
|
||||
* @return The React DOM that represents the rendered markdown code
|
||||
*/
|
||||
export const useConvertMarkdownToReactDom = (
|
||||
markdownCode: string,
|
||||
markdownIt: MarkdownIt,
|
||||
replacers: () => ComponentReplacer[],
|
||||
onBeforeRendering?: () => void,
|
||||
onAfterRendering?: () => void
|
||||
replacers: ComponentReplacer[],
|
||||
preprocessNodes?: (nodes: Document) => Document
|
||||
): ValidReactDomElement[] => {
|
||||
const oldMarkdownLineKeys = useRef<LineKeys[]>()
|
||||
const lastUsedLineId = useRef<number>(0)
|
||||
|
||||
return useMemo(() => {
|
||||
if (onBeforeRendering) {
|
||||
onBeforeRendering()
|
||||
}
|
||||
const html = markdownIt.render(markdownCode)
|
||||
const contentLines = markdownCode.split('\n')
|
||||
const { lines: newLines, lastUsedLineId: newLastUsedLineId } = calculateNewLineNumberMapping(
|
||||
|
@ -46,12 +42,7 @@ export const useConvertMarkdownToReactDom = (
|
|||
oldMarkdownLineKeys.current = newLines
|
||||
lastUsedLineId.current = newLastUsedLineId
|
||||
|
||||
const currentReplacers = replacers()
|
||||
const transformer = currentReplacers.length > 0 ? buildTransformer(newLines, currentReplacers) : undefined
|
||||
const rendering = convertHtmlToReact(html, { transform: transformer })
|
||||
if (onAfterRendering) {
|
||||
onAfterRendering()
|
||||
}
|
||||
return rendering
|
||||
}, [onBeforeRendering, markdownIt, markdownCode, replacers, onAfterRendering])
|
||||
const transformer = replacers.length > 0 ? buildTransformer(newLines, replacers) : undefined
|
||||
return convertHtmlToReact(html, { transform: transformer, preprocessNodes: preprocessNodes })
|
||||
}, [markdownIt, markdownCode, replacers, preprocessNodes])
|
||||
}
|
||||
|
|
53
src/components/markdown-renderer/hooks/use-reveal.ts
Normal file
53
src/components/markdown-renderer/hooks/use-reveal.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import Reveal from 'reveal.js'
|
||||
import { Logger } from '../../../utils/logger'
|
||||
import { SlideOptions } from '../../common/note-frontmatter/types'
|
||||
|
||||
const log = new Logger('reveal.js')
|
||||
|
||||
export const useReveal = (content: string, slideOptions?: SlideOptions): void => {
|
||||
const [deck, setDeck] = useState<Reveal>()
|
||||
const [isInitialized, setIsInitialized] = useState<boolean>(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitialized) {
|
||||
return
|
||||
}
|
||||
setIsInitialized(true)
|
||||
log.debug('Initialize with slide options', slideOptions)
|
||||
const reveal = new Reveal({})
|
||||
reveal
|
||||
.initialize()
|
||||
.then(() => {
|
||||
reveal.layout()
|
||||
reveal.slide(0, 0, 0)
|
||||
setDeck(reveal)
|
||||
log.debug('Initialisation finished')
|
||||
})
|
||||
.catch((error) => {
|
||||
log.error('Error while initializing reveal.js', error)
|
||||
})
|
||||
}, [isInitialized, slideOptions])
|
||||
|
||||
useEffect(() => {
|
||||
if (!deck) {
|
||||
return
|
||||
}
|
||||
log.debug('Sync deck')
|
||||
deck.layout()
|
||||
}, [content, deck])
|
||||
|
||||
useEffect(() => {
|
||||
if (!deck || slideOptions === undefined || Object.keys(slideOptions).length === 0) {
|
||||
return
|
||||
}
|
||||
log.debug('Apply config', slideOptions)
|
||||
deck.configure(slideOptions)
|
||||
}, [deck, slideOptions])
|
||||
}
|
|
@ -35,12 +35,15 @@ import { highlightedCode } from '../markdown-it-plugins/highlighted-code'
|
|||
import { quoteExtraColor } from '../markdown-it-plugins/quote-extra-color'
|
||||
import { quoteExtra } from '../markdown-it-plugins/quote-extra'
|
||||
import { documentTableOfContents } from '../markdown-it-plugins/document-table-of-contents'
|
||||
import { addSlideSectionsMarkdownItPlugin } from '../markdown-it-plugins/reveal-sections'
|
||||
|
||||
export interface ConfiguratorDetails {
|
||||
onToc: (toc: TocAst) => void
|
||||
onLineMarkers?: (lineMarkers: LineMarkers[]) => void
|
||||
useAlternativeBreaks?: boolean
|
||||
offsetLines?: number
|
||||
lineOffset?: number
|
||||
headlineAnchors?: boolean
|
||||
slideSections?: boolean
|
||||
}
|
||||
|
||||
export class BasicMarkdownItConfigurator<T extends ConfiguratorDetails> {
|
||||
|
@ -73,7 +76,6 @@ export class BasicMarkdownItConfigurator<T extends ConfiguratorDetails> {
|
|||
protected configure(markdownIt: MarkdownIt): void {
|
||||
this.configurations.push(
|
||||
plantumlWithError,
|
||||
headlineAnchors,
|
||||
KatexReplacer.markdownItPlugin,
|
||||
YoutubeReplacer.markdownItPlugin,
|
||||
VimeoReplacer.markdownItPlugin,
|
||||
|
@ -101,8 +103,16 @@ export class BasicMarkdownItConfigurator<T extends ConfiguratorDetails> {
|
|||
spoilerContainer
|
||||
)
|
||||
|
||||
if (this.options.headlineAnchors) {
|
||||
this.configurations.push(headlineAnchors)
|
||||
}
|
||||
|
||||
if (this.options.slideSections) {
|
||||
this.configurations.push(addSlideSectionsMarkdownItPlugin)
|
||||
}
|
||||
|
||||
if (this.options.onLineMarkers) {
|
||||
this.configurations.push(lineNumberMarker(this.options.onLineMarkers, this.options.offsetLines ?? 0))
|
||||
this.configurations.push(lineNumberMarker(this.options.onLineMarkers, this.options.lineOffset ?? 0))
|
||||
}
|
||||
|
||||
this.postConfigurations.push(linkifyExtra, MarkdownItParserDebugger)
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import MarkdownIt from 'markdown-it/lib'
|
||||
import Token from 'markdown-it/lib/token'
|
||||
import StateCore from 'markdown-it/lib/rules_core/state_core'
|
||||
|
||||
/**
|
||||
* This functions adds a 'section close' token at currentTokenIndex in the state's token array,
|
||||
* replacing the current token, if replaceCurrentToken is true.
|
||||
* It also returns the currentTokenIndex, that will be increased only if the previous token was not replaced.
|
||||
*
|
||||
* @param {number} currentTokenIndex - the current position in the tokens array
|
||||
* @param {StateCore} state - the state core
|
||||
* @param {boolean} replaceCurrentToken - if the currentToken should be replaced
|
||||
*/
|
||||
const addSectionClose = (currentTokenIndex: number, state: StateCore, replaceCurrentToken: boolean): void => {
|
||||
const sectionCloseToken = new Token('section', 'section', -1)
|
||||
state.tokens.splice(currentTokenIndex, replaceCurrentToken ? 1 : 0, sectionCloseToken)
|
||||
}
|
||||
|
||||
/**
|
||||
* This functions adds a 'section open' token at insertIndex in the state's token array.
|
||||
*
|
||||
* @param {number} insertIndex - the index at which the token should be added
|
||||
* @param {StateCore} state - the state core
|
||||
*/
|
||||
const addSectionOpen = (insertIndex: number, state: StateCore): void => {
|
||||
const sectionOpenToken = new Token('section', 'section', 1)
|
||||
state.tokens.splice(insertIndex, 0, sectionOpenToken)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a plugin to the given {@link MarkdownIt markdown it instance} that
|
||||
* replaces splits the content by horizontal lines and groups these blocks into
|
||||
* html section tags.
|
||||
*
|
||||
* @param markdownIt The {@link MarkdownIt markdown it instance} to which the plugin should be added
|
||||
*/
|
||||
export const addSlideSectionsMarkdownItPlugin: MarkdownIt.PluginSimple = (markdownIt: MarkdownIt): void => {
|
||||
markdownIt.core.ruler.push('reveal.sections', (state) => {
|
||||
let sectionBeginIndex = 0
|
||||
let lastSectionWasBranch = false
|
||||
|
||||
for (let currentTokenIndex = 0; currentTokenIndex < state.tokens.length; currentTokenIndex++) {
|
||||
const currentToken = state.tokens[currentTokenIndex]
|
||||
|
||||
if (currentToken.type !== 'hr') {
|
||||
continue
|
||||
}
|
||||
|
||||
addSectionOpen(sectionBeginIndex, state)
|
||||
currentTokenIndex += 1
|
||||
|
||||
if (currentToken.markup === '---' && lastSectionWasBranch) {
|
||||
lastSectionWasBranch = false
|
||||
addSectionClose(currentTokenIndex, state, false)
|
||||
currentTokenIndex += 1
|
||||
} else if (currentToken.markup === '----' && !lastSectionWasBranch) {
|
||||
lastSectionWasBranch = true
|
||||
addSectionOpen(sectionBeginIndex, state)
|
||||
currentTokenIndex += 1
|
||||
}
|
||||
|
||||
addSectionClose(currentTokenIndex, state, true)
|
||||
sectionBeginIndex = currentTokenIndex + 1
|
||||
}
|
||||
|
||||
addSectionOpen(sectionBeginIndex, state)
|
||||
addSectionClose(state.tokens.length, state, false)
|
||||
if (lastSectionWasBranch) {
|
||||
addSectionClose(state.tokens.length, state, false)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
107
src/components/markdown-renderer/process-reveal-comment-nodes.ts
Normal file
107
src/components/markdown-renderer/process-reveal-comment-nodes.ts
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { DataNode, Document, Element, hasChildren, isComment, isTag, Node } from 'domhandler'
|
||||
import { Logger } from '../../utils/logger'
|
||||
|
||||
const log = new Logger('reveal.js > Comment Node Preprocessor')
|
||||
const revealCommandSyntax = /^\s*\.(\w*):(.*)$/g
|
||||
const dataAttributesSyntax = /\s*([\w-]*)=(?:"((?:[^"\\]|\\"|\\)*)"|'([^']*)')/g
|
||||
|
||||
/**
|
||||
* Travels through the given {@link Document}, searches for reveal command comments and applies them.
|
||||
*
|
||||
* @param doc The document that should be changed
|
||||
* @return The edited document
|
||||
*/
|
||||
export const processRevealCommentNodes = (doc: Document): Document => {
|
||||
visitNode(doc)
|
||||
return doc
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the given {@link Node} if it is a comment node. If the node has children then all child nodes will be processed.
|
||||
* @param node The node to process.
|
||||
*/
|
||||
const visitNode = (node: Node): void => {
|
||||
if (isComment(node)) {
|
||||
processCommentNode(node)
|
||||
} else if (hasChildren(node)) {
|
||||
node.childNodes.forEach((childNode) => visitNode(childNode))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the given {@link DataNode html comment} by parsing it, finding the element that should be changed and applies the contained changes.
|
||||
*
|
||||
* @param node The node that contains the reveal command.
|
||||
*/
|
||||
const processCommentNode = (node: DataNode): void => {
|
||||
const regexResult = node.data.split(revealCommandSyntax)
|
||||
if (regexResult.length === 1) {
|
||||
return
|
||||
}
|
||||
|
||||
const parentNode: Element | null = findTargetElement(node, regexResult[1])
|
||||
if (!parentNode) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const dataAttribute of regexResult[2].matchAll(dataAttributesSyntax)) {
|
||||
const attributeName = dataAttribute[1]
|
||||
const attributeValue = dataAttribute[2] ?? dataAttribute[3]
|
||||
if (attributeValue) {
|
||||
log.debug(
|
||||
`Add attribute "${attributeName}"=>"${attributeValue}" to node`,
|
||||
parentNode,
|
||||
'because of',
|
||||
regexResult[1],
|
||||
'selector'
|
||||
)
|
||||
parentNode.attribs[attributeName] = attributeValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the ancestor element that should be changed based on the given selector.
|
||||
*
|
||||
* @param node The node whose ancestor should be found.
|
||||
* @param selector The found ancestor node or null if no node could be found.
|
||||
*/
|
||||
const findTargetElement = (node: Node, selector: string): Element | null => {
|
||||
if (selector === 'slide') {
|
||||
return findNearestAncestorSection(node)
|
||||
} else if (selector === 'element') {
|
||||
return findParentElement(node)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent node if it is an {@link Element}.
|
||||
*
|
||||
* @param node the found node or null if no parent node exists or if the parent node isn't an {@link Element}.
|
||||
*/
|
||||
const findParentElement = (node: Node): Element | null => {
|
||||
return node.parentNode !== null && isTag(node.parentNode) ? node.parentNode : null
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for the nearest ancestor of the node that is a section element.
|
||||
*
|
||||
* @param node the found section node or null if no section ancestor could be found.
|
||||
*/
|
||||
const findNearestAncestorSection = (node: Node): Element | null => {
|
||||
let currentNode = node.parentNode
|
||||
while (currentNode != null) {
|
||||
if (isTag(currentNode) && currentNode.tagName === 'section') {
|
||||
break
|
||||
}
|
||||
currentNode = node.parentNode
|
||||
}
|
||||
return currentNode
|
||||
}
|
|
@ -11,6 +11,8 @@
|
|||
@import '../../../../../../node_modules/highlight.js/styles/github-dark';
|
||||
}
|
||||
|
||||
position: relative;
|
||||
|
||||
code.hljs {
|
||||
overflow-x: auto;
|
||||
background-color: rgba(27, 31, 35, .05);
|
||||
|
@ -50,10 +52,10 @@
|
|||
.linenumber {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
&.showGutter .codeline {
|
||||
margin: 0 0 0 16px;
|
||||
.codeline {
|
||||
margin: 0 0 0 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&.wrapLines .codeline {
|
||||
|
|
|
@ -18,13 +18,13 @@ export type LineNumberMarkerOptions = (lineMarkers: LineMarkers[]) => void
|
|||
* This plugin adds markers to the dom, that are used to map line numbers to dom elements.
|
||||
* It also provides a list of line numbers for the top level dom elements.
|
||||
*/
|
||||
export const lineNumberMarker: (options: LineNumberMarkerOptions, offsetLines: number) => MarkdownIt.PluginSimple =
|
||||
(options, offsetLines = 0) =>
|
||||
export const lineNumberMarker: (options: LineNumberMarkerOptions, lineOffset: number) => MarkdownIt.PluginSimple =
|
||||
(options, lineOffset = 0) =>
|
||||
(md: MarkdownIt) => {
|
||||
// add app_linemarker token before each opening or self-closing level-0 tag
|
||||
md.core.ruler.push('line_number_marker', (state) => {
|
||||
const lineMarkers: LineMarkers[] = []
|
||||
tagTokens(state.tokens, lineMarkers, offsetLines)
|
||||
tagTokens(state.tokens, lineMarkers, lineOffset)
|
||||
if (options) {
|
||||
options(lineMarkers)
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ export const lineNumberMarker: (options: LineNumberMarkerOptions, offsetLines: n
|
|||
tokens.splice(tokenPosition, 0, startToken)
|
||||
}
|
||||
|
||||
const tagTokens = (tokens: Token[], lineMarkers: LineMarkers[], offsetLines: number) => {
|
||||
const tagTokens = (tokens: Token[], lineMarkers: LineMarkers[], lineOffset: number) => {
|
||||
for (let tokenPosition = 0; tokenPosition < tokens.length; tokenPosition++) {
|
||||
const token = tokens[tokenPosition]
|
||||
if (token.hidden) {
|
||||
|
@ -72,14 +72,14 @@ export const lineNumberMarker: (options: LineNumberMarkerOptions, offsetLines: n
|
|||
const endLineNumber = token.map[1] + 1
|
||||
|
||||
if (token.level === 0) {
|
||||
lineMarkers.push({ startLine: startLineNumber + offsetLines, endLine: endLineNumber + offsetLines })
|
||||
lineMarkers.push({ startLine: startLineNumber + lineOffset, endLine: endLineNumber + lineOffset })
|
||||
}
|
||||
|
||||
insertNewLineMarker(startLineNumber, endLineNumber, tokenPosition, token.level, tokens)
|
||||
tokenPosition += 1
|
||||
|
||||
if (token.children) {
|
||||
tagTokens(token.children, lineMarkers, offsetLines)
|
||||
tagTokens(token.children, lineMarkers, lineOffset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ export const MarkmapFrame: React.FC<MarkmapFrameProps> = ({ code }) => {
|
|||
}, [code])
|
||||
|
||||
return (
|
||||
<div data-cy={'markmap'}>
|
||||
<div data-cy={'markmap'} className={'position-relative'}>
|
||||
<div className={'svg-container'} ref={diagramContainer} />
|
||||
<div className={'text-right button-inside'}>
|
||||
<LockButton
|
||||
|
|
41
src/components/markdown-renderer/slide-theme.scss
Normal file
41
src/components/markdown-renderer/slide-theme.scss
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
// Default mixins and settings -----------------
|
||||
@import "../../../node_modules/reveal.js/css/theme/template/mixins";
|
||||
@import "../../../node_modules/reveal.js/css/theme/template/settings";
|
||||
// ---------------------------------------------
|
||||
|
||||
|
||||
// Override theme settings (see ../template/settings.scss)
|
||||
$backgroundColor: #191919;
|
||||
|
||||
$mainColor: #fff;
|
||||
$headingColor: #fff;
|
||||
|
||||
$mainFontSize: 42px;
|
||||
$mainFont: 'Source Sans Pro', Helvetica, sans-serif;
|
||||
$headingFont: 'Source Sans Pro', Helvetica, sans-serif;
|
||||
$headingTextShadow: none;
|
||||
$headingLetterSpacing: normal;
|
||||
$headingTextTransform: uppercase;
|
||||
$headingFontWeight: 600;
|
||||
$linkColor: #42affa;
|
||||
$linkColorHover: lighten($linkColor, 15%);
|
||||
$selectionBackgroundColor: lighten($linkColor, 25%);
|
||||
|
||||
$heading1Size: 2.5em;
|
||||
$heading2Size: 1.6em;
|
||||
$heading3Size: 1.3em;
|
||||
$heading4Size: 1.0em;
|
||||
|
||||
// Change text colors against light slide backgrounds
|
||||
@include light-bg-text-color(#222);
|
||||
|
||||
|
||||
// Theme template ------------------------------
|
||||
@import "../../../node_modules/reveal.js/css/theme/template/theme";
|
||||
// ---------------------------------------------
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { Fragment, useMemo, useRef } from 'react'
|
||||
import { useConvertMarkdownToReactDom } from './hooks/use-convert-markdown-to-react-dom'
|
||||
import './markdown-renderer.scss'
|
||||
import { useComponentReplacers } from './hooks/use-component-replacers'
|
||||
import { useExtractFirstHeadline } from './hooks/use-extract-first-headline'
|
||||
import { TocAst } from 'markdown-it-toc-done-right'
|
||||
import { useOnRefChange } from './hooks/use-on-ref-change'
|
||||
import { useTrimmedContent } from './hooks/use-trimmed-content'
|
||||
import { useReveal } from './hooks/use-reveal'
|
||||
import './slideshow.scss'
|
||||
import { ScrollProps } from '../editor-page/synced-scroll/scroll-props'
|
||||
import { DocumentLengthLimitReachedAlert } from './document-length-limit-reached-alert'
|
||||
import { BasicMarkdownItConfigurator } from './markdown-it-configurator/basic-markdown-it-configurator'
|
||||
import { SlideOptions } from '../common/note-frontmatter/types'
|
||||
import { processRevealCommentNodes } from './process-reveal-comment-nodes'
|
||||
import { CommonMarkdownRendererProps } from './common-markdown-renderer-props'
|
||||
|
||||
export interface SlideshowMarkdownRendererProps extends CommonMarkdownRendererProps {
|
||||
slideOptions: SlideOptions
|
||||
}
|
||||
|
||||
export const SlideshowMarkdownRenderer: React.FC<SlideshowMarkdownRendererProps & ScrollProps> = ({
|
||||
className,
|
||||
content,
|
||||
onFirstHeadingChange,
|
||||
onTaskCheckedChange,
|
||||
onTocChange,
|
||||
baseUrl,
|
||||
onImageClick,
|
||||
useAlternativeBreaks,
|
||||
lineOffset,
|
||||
slideOptions
|
||||
}) => {
|
||||
const markdownBodyRef = useRef<HTMLDivElement>(null)
|
||||
const tocAst = useRef<TocAst>()
|
||||
const [trimmedContent, contentExceedsLimit] = useTrimmedContent(content)
|
||||
|
||||
const markdownIt = useMemo(
|
||||
() =>
|
||||
new BasicMarkdownItConfigurator({
|
||||
onToc: (toc) => (tocAst.current = toc),
|
||||
useAlternativeBreaks,
|
||||
lineOffset,
|
||||
headlineAnchors: false,
|
||||
slideSections: true
|
||||
}).buildConfiguredMarkdownIt(),
|
||||
[lineOffset, useAlternativeBreaks]
|
||||
)
|
||||
const replacers = useComponentReplacers(onTaskCheckedChange, onImageClick, baseUrl, lineOffset)
|
||||
const markdownReactDom = useConvertMarkdownToReactDom(
|
||||
trimmedContent,
|
||||
markdownIt,
|
||||
replacers,
|
||||
processRevealCommentNodes
|
||||
)
|
||||
|
||||
useExtractFirstHeadline(markdownBodyRef, content, onFirstHeadingChange)
|
||||
useOnRefChange(tocAst, onTocChange)
|
||||
useReveal(content, slideOptions)
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<DocumentLengthLimitReachedAlert show={contentExceedsLimit} />
|
||||
<div className={'reveal'}>
|
||||
<div ref={markdownBodyRef} className={`${className ?? ''} slides`}>
|
||||
{markdownReactDom}
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default SlideshowMarkdownRenderer
|
14
src/components/markdown-renderer/slideshow.scss
Normal file
14
src/components/markdown-renderer/slideshow.scss
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
@import "../../../node_modules/reveal.js/css/reveal";
|
||||
@import "../../../node_modules/reveal.js/dist/theme/fonts/league-gothic/league-gothic.css";
|
||||
@import "slide-theme";
|
||||
|
||||
//Fix to make transitions work with bootstrap
|
||||
.reveal [hidden] {
|
||||
display: block !important;
|
||||
}
|
5
src/components/markdown-renderer/types.d.ts
vendored
5
src/components/markdown-renderer/types.d.ts
vendored
|
@ -13,8 +13,3 @@ export interface LineMarkerPosition {
|
|||
line: number
|
||||
position: number
|
||||
}
|
||||
|
||||
export interface AdditionalMarkdownRendererProps {
|
||||
className?: string
|
||||
content: string
|
||||
}
|
||||
|
|
|
@ -5,5 +5,7 @@
|
|||
*/
|
||||
|
||||
.button-inside {
|
||||
margin-top: -31px;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue