Introduce Markdown extensions (#1614)

* Introduce markdown extensions

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2021-11-15 17:04:49 +01:00 committed by GitHub
parent e9defd60dc
commit 8a8bacc0aa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
148 changed files with 1878 additions and 1128 deletions

View file

@ -0,0 +1,41 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React, { useCallback } from 'react'
export interface TaskListProps {
onTaskCheckedChange?: (lineInMarkdown: number, checked: boolean) => void
checked: boolean
lineInMarkdown?: number
}
/**
* Renders a task list checkbox.
*
* @param onTaskCheckedChange A callback that is executed if the checkbox was clicked. If this prop is omitted then the checkbox will be disabled.
* @param checked Determines if the checkbox should be rendered as checked
* @param lineInMarkdown Defines the line in the markdown code this checkbox is mapped to. The information is send with the onTaskCheckedChange callback.
*/
export const TaskListCheckbox: React.FC<TaskListProps> = ({ onTaskCheckedChange, checked, lineInMarkdown }) => {
const onChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>): void => {
if (onTaskCheckedChange && lineInMarkdown !== undefined) {
onTaskCheckedChange(lineInMarkdown, event.currentTarget.checked)
}
},
[lineInMarkdown, onTaskCheckedChange]
)
return (
<input
disabled={onTaskCheckedChange === undefined}
className='task-list-item-checkbox'
type='checkbox'
checked={checked}
onChange={onChange}
/>
)
}

View file

@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { MarkdownExtension } from '../markdown-extension'
import type MarkdownIt from 'markdown-it'
import type { ComponentReplacer } from '../../replace-components/component-replacer'
import type { TaskCheckedChangeHandler } from './task-list-replacer'
import { TaskListReplacer } from './task-list-replacer'
import markdownItTaskLists from '@hedgedoc/markdown-it-task-lists'
/**
* Adds support for interactive checkbox lists to the markdown rendering using the github checklist syntax.
*/
export class TaskListMarkdownExtension extends MarkdownExtension {
constructor(private frontmatterLinesToSkip?: number, private onTaskCheckedChange?: TaskCheckedChangeHandler) {
super()
}
public configureMarkdownIt(markdownIt: MarkdownIt): void {
markdownItTaskLists(markdownIt, {
enabled: true,
label: true,
lineNumber: true
})
}
public buildReplacers(): ComponentReplacer[] {
return [new TaskListReplacer(this.frontmatterLinesToSkip, this.onTaskCheckedChange)]
}
}

View file

@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { Element } from 'domhandler'
import type { ReactElement } from 'react'
import React from 'react'
import { ComponentReplacer } from '../../replace-components/component-replacer'
import { TaskListCheckbox } from './task-list-checkbox'
export type TaskCheckedChangeHandler = (lineInMarkdown: number, checked: boolean) => void
/**
* Detects task lists and renders them as checkboxes that execute a callback if clicked.
*/
export class TaskListReplacer extends ComponentReplacer {
onTaskCheckedChange?: (lineInMarkdown: number, checked: boolean) => void
constructor(frontmatterLinesToSkip?: number, onTaskCheckedChange?: TaskCheckedChangeHandler) {
super()
this.onTaskCheckedChange = (lineInMarkdown, checked) => {
if (onTaskCheckedChange === undefined || frontmatterLinesToSkip === undefined) {
return
}
onTaskCheckedChange(frontmatterLinesToSkip + lineInMarkdown, checked)
}
}
public replace(node: Element): ReactElement | undefined {
if (node.attribs?.class !== 'task-list-item-checkbox') {
return
}
const lineInMarkdown = Number(node.attribs['data-line'])
if (isNaN(lineInMarkdown)) {
return undefined
}
return (
<TaskListCheckbox
onTaskCheckedChange={this.onTaskCheckedChange}
checked={node.attribs.checked !== undefined}
lineInMarkdown={lineInMarkdown}
/>
)
}
}