mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-12-22 11:51:36 +00:00
fix(cheatsheet): refactor cheatsheet to use app extensions as source
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
9d49401b4d
commit
24b0070909
53 changed files with 1164 additions and 275 deletions
|
@ -1,16 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
describe('Help Dialog', () => {
|
||||
beforeEach(() => {
|
||||
cy.visitTestNote()
|
||||
})
|
||||
|
||||
it('ToDo-List', () => {
|
||||
cy.getByCypressId('editor-help-button').click()
|
||||
cy.get('input[type="checkbox"]').should('exist').should('not.be.checked')
|
||||
})
|
||||
})
|
|
@ -267,13 +267,6 @@
|
|||
"features": "Features",
|
||||
"yamlMetadata": "YAML Metadata",
|
||||
"slideExample": "Slide Example"
|
||||
},
|
||||
"cheatsheet": {
|
||||
"title": "Cheatsheet",
|
||||
"example": "Example",
|
||||
"syntax": "Syntax",
|
||||
"exampleAlert": "This is an alert area.",
|
||||
"highlightedText": "Highlight"
|
||||
}
|
||||
},
|
||||
"onlineStatus": {
|
||||
|
@ -630,5 +623,253 @@
|
|||
"help": "The primary user interface language"
|
||||
}
|
||||
}
|
||||
},
|
||||
"cheatsheet": {
|
||||
"button": "Open Cheatsheet",
|
||||
"modal":{
|
||||
"title": "Cheatsheet",
|
||||
"headlines": {
|
||||
"description": "Description",
|
||||
"exampleInput": "Example Input",
|
||||
"exampleOutput": "Example Output",
|
||||
"selectTopic": "Select Topic",
|
||||
"readMoreLink": "Read More"
|
||||
},
|
||||
"noSelection": "Select an entry on the left side to show the instructions."
|
||||
},
|
||||
"categories": {
|
||||
"basic": "Basics",
|
||||
"other": "Other",
|
||||
"embedding": "Embedding",
|
||||
"charts": "Charts & Diagrams"
|
||||
},
|
||||
"basics": {
|
||||
"basicFormatting": {
|
||||
"title": "Basic",
|
||||
"description": "These are the basic markdown formatting rules.",
|
||||
"example": "**Bold**\n__Bold__\n\n*Italic*\n_Italic_\n\n++Underline++\n~~Strikethrough~~\n\nSub~script~\n\nSuper^script^\n\n==Marked=="
|
||||
},
|
||||
"abbreviation": {
|
||||
"title": "Abbreviation",
|
||||
"description": "Abbreviation definitions create tooltips for matching words. They can be placed defined in the document.",
|
||||
"example": "*[HTML]: Hyper Text Markup Language\n*[W3C]: World Wide Web Consortium\nThe HTML specification\nis maintained by the W3C."
|
||||
},
|
||||
"footnote": {
|
||||
"title": "Footnotes",
|
||||
"description": "Footnotes can be used to add extra information at the bottom of the page. They can be defined anywhere in the document.",
|
||||
"example": "Here is a footnote reference,[^1] and another.[^longnote]\n\n[^1]: Here is the footnote.\n\n[^longnote]: Here's one with multiple blocks. Subsequent paragraphs are indented to show that they belong to the previous footnote."
|
||||
},
|
||||
"headlines": {
|
||||
"title": "Headlines",
|
||||
"hashtag": {
|
||||
"title": "Hashtag",
|
||||
"description": "Headlines can be used to structure your document into sections. Every headline creates a linkable anchor.",
|
||||
"example": "# Headline 1\n\n## Headline 2\n\n### Headline 3\n\n#### Headline 4\n\n##### Headline 5\n\n###### Headline 6"
|
||||
},
|
||||
"equal": {
|
||||
"title": "Equals sign",
|
||||
"description": "An alternative form for the first and second level headline is to append a line of only equals signs or dashes after the headline text. ",
|
||||
"example": "Headline 1\n==========\n\nHeadline 2\n----------"
|
||||
}
|
||||
},
|
||||
"code": {
|
||||
"title": "Code",
|
||||
"inline": {
|
||||
"title": "Inline",
|
||||
"description": "You can define an inline code block by wrapping it in backticks.",
|
||||
"example": "This is `code`."
|
||||
},
|
||||
"block": {
|
||||
"title": "Block",
|
||||
"description": "You can define a code block by putting a line of three backticks before and after the code part.\n\nIt is also possible to define a language for syntax highlighting. For more information check the entry \"Other > Code Highlighting\"",
|
||||
"example": "```\nthis is a code block\n```"
|
||||
}
|
||||
},
|
||||
"lists": {
|
||||
"title": "Lists",
|
||||
"unordered": {
|
||||
"title": "Unordered",
|
||||
"description": "You can create unordered lists by prepending lines with dashes or asterisks.",
|
||||
"example": "- A\n- B\n- C\n\n* A\n* B\n* C"
|
||||
},
|
||||
"ordered": {
|
||||
"title": "Ordered",
|
||||
"description": "You can create ordered lists by prepending lines with numbers followed by a dot. Ordered lists will always start with one. Ordered lists that aren't separated by another element will continue the last list.",
|
||||
"example": "1. A\n2. B\n3. C\n\n\n4. D\n5. E\n\nText\n\n100. A\n101. B\n102. C"
|
||||
}
|
||||
},
|
||||
"images": {
|
||||
"title": "Images",
|
||||
"basic": {
|
||||
"title": "Basic",
|
||||
"description": "Images can be defined using the link syntax with an exclamation mark in front of it. The text in the brackets will be used as alt-text.",
|
||||
"example": "![This is an icon](/icons/apple-touch-icon.png)"
|
||||
},
|
||||
"size": {
|
||||
"title": "Size",
|
||||
"description": "The size syntax allows you to set both dimensions of an image. If you omit one of the dimensions then image will be scaled to the relative size.",
|
||||
"example": "![Image](/icons/apple-touch-icon.png =100x50)\n\n![Image](/icons/apple-touch-icon.png =60x)\n\n![Image](/icons/apple-touch-icon.png =x50)"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"title": "Links",
|
||||
"description": "You can create text links by either using the link syntax, by writing the plain URL or by writing it in angle brackets.",
|
||||
"example": "[Example link](https://example.org)\n\nhttps://example.org\n\n<https://example.org>"
|
||||
}
|
||||
},
|
||||
"abcjs": {
|
||||
"title": "abcjs",
|
||||
"description": "You can render music sheets with abcjs by using music standard notation in a codeblock with `abc` as language.",
|
||||
"example": "```abc\nX:1\nT:Speed the Plough\nM:4/4\nC:Trad.\nK:G\n|:GABc dedB|dedB dedB|c2ec B2dB|c2A2 A2BA|\nGABc dedB|dedB dedB|c2ec B2dB|A2F2 G4:|\n|:g2gf gdBd|g2f2 e2d2|c2ec B2dB|c2A2 A2df|\ng2gf g2Bd|g2f2 e2d2|c2ec B2dB|A2F2 G4:|\n```"
|
||||
},
|
||||
"alert": {
|
||||
"title": "Alert boxes",
|
||||
"description": "Use alert boxes to bring extra attention to parts of your document.",
|
||||
"example": ":::success\nThis is a success\n:::\n\n:::danger\nThis is a danger\n:::\n\n:::warning\nThis is a warning\n:::\n\n:::info\nThis is a info\n:::"
|
||||
},
|
||||
"blockquoteTags": {
|
||||
"name": {
|
||||
"title": "Name",
|
||||
"description": "Use name tags to indicate who wrote a specific quote e.g. if you're commenting on the text.",
|
||||
"example": "> Imagination is more important than knowledge.\n> [name=Albert Einstein]"
|
||||
},
|
||||
"color": {
|
||||
"title": "Color",
|
||||
"description": "Use color tags to tint the border of the quote.",
|
||||
"example": "> This is the default color\n\n> This is red! [color=red]\n\n> This is blue! [color=#0000ff]"
|
||||
},
|
||||
"time": {
|
||||
"title": "Time",
|
||||
"description": "Use time tags to specify when a time stamp",
|
||||
"example": "The password is: changeme\n\n> Please change it ASAP [time=2020-03-05]"
|
||||
},
|
||||
"title": "Blockquote Tags",
|
||||
"description": "Use name, time or color tags to specify your blockquotes. Color tags modify the border color.",
|
||||
"example": "> [name=Max] Named quote\n\n> [time=today] Time quote\n\n> [color=red] quote\n\n> [name=Max] [color=green] [time=today] Combined quote"
|
||||
},
|
||||
"bootstrapIcon": {
|
||||
"title": "Bootstrap Icons",
|
||||
"description": "You can use bootstrap icons in your document by putting the name of an icon between colons but prefixed with `bi-`. A listing of all bootstrap icons can be found on their website.",
|
||||
"example": ":bi-music-note-beamed::bi-music-note-beamed::bi-music-note-beamed:\nAround the :bi-globe-americas:\nAround the :bi-globe-asia-australia:\nAround the :bi-globe-central-south-asia:\nAround the :bi-globe-europe-africa:"
|
||||
},
|
||||
"emoji": {
|
||||
"title": "Emojis",
|
||||
"description": "You can add colored emojis by either placing the unicode character or by using a shortcode. HedgeDoc supports every emoji that Twemoji supports. You can also use the emoji picker in the editor toolbar.",
|
||||
"example": "I :heart: :hedgehog:"
|
||||
},
|
||||
"csv": {
|
||||
"title" : "CSV",
|
||||
"table" : {
|
||||
"title": "Table",
|
||||
"description" : "You can render a CSV text as table by using a code block with `csv` as language. You must specify the delimiter.",
|
||||
"example" : "```csv delimiter=;\nUsername; Identifier;First name;Last name\n\"booker12; rbooker\";9012;Rachel;Booker\ngrey07;2070;Laura;Grey\njohnson81;4081;Craig;Johnson\njenkins46;9346;Mary;Jenkins\nsmith79;5079;Jamie;Smith\n```"
|
||||
},
|
||||
"header": {
|
||||
"title": "Header",
|
||||
"description": "By adding the header keyword you can define that the first line is used as table header",
|
||||
"example": "```csv delimiter=; header\nUsername; Identifier;First name;Last name\n\"booker12; rbooker\";9012;Rachel;Booker\ngrey07;2070;Laura;Grey\njohnson81;4081;Craig;Johnson\njenkins46;9346;Mary;Jenkins\nsmith79;5079;Jamie;Smith\n```"
|
||||
}
|
||||
},
|
||||
"flowchart": {
|
||||
"title": "flowchart.js",
|
||||
"description": "Render flowcharts diagrams using flowchart.js by using a code block with `flow` as language.",
|
||||
"example": "```flow\nst=>start: Start\ne=>end: End\nop=>operation: My Operation\nop2=>operation: lalala\ncond=>condition: Yes or No?\n\nst->op->op2->cond\ncond(yes)->e\ncond(no)->op2\n```"
|
||||
},
|
||||
"gist": {
|
||||
"title": "GitHub Gist",
|
||||
"description": "Embed GitHub Gists by placing a gist URL on a single line.",
|
||||
"example": "# This is my gist\nhttps://gist.github.com/schacon/1\nThis is just the link to a gist: https://gist.github.com/schacon/1\n[Using the link tag will also not trigger the embedding](https://gist.github.com/schacon/1)"
|
||||
},
|
||||
"graphviz": {
|
||||
"title": "GraphViz",
|
||||
"description": "Render GraphViz diagrams by using the DOT language in a code block with `graphviz` as language.",
|
||||
"example": "```graphviz\ngraph {\n a -- b\n a -- b\n b -- a [color=blue]\n}\n```"
|
||||
},
|
||||
"katex": {
|
||||
"title": "KaTeX",
|
||||
"description": "You can render LaTeX mathematical expressions using KaTeX by encapsulating them in dollar signs.",
|
||||
"example": "The *Gamma function* satisfying $\\Gamma(n) = (n-1)!\\quad\\forall n\\in\\mathbb N$ is via the Euler integral\n\n$$\nx = {-b \\pm \\sqrt{b^2-4ac} \\over 2a}.\n$$\n\n$$\n\\Gamma(z) = \\int_0^\\infty t^{z-1}e^{-t}dt\\,.\n$$"
|
||||
},
|
||||
"asciinema": {
|
||||
"title": "Asciinema",
|
||||
"description": "Embed an Asciinema video by placing the URL in a single line.",
|
||||
"example": "https://asciinema.org/a/117928\n"
|
||||
},
|
||||
"mermaid": {
|
||||
"title": "Mermaid",
|
||||
"description": "Render diagrams and charts using mermaid.js by adding a code block with the language `mermaid`",
|
||||
"example": "```mermaid\ngantt\n title A Gantt Diagram\n\n section Section\n A task: a1, 2014-01-01, 30d\n Another task: after a1, 20d\n\n section Another\n Task in sec: 2014-01-12, 12d\n Another task: 24d\n```"
|
||||
},
|
||||
"imagePlaceholder": {
|
||||
"title": "Image Placeholder",
|
||||
"description": "You can use image placeholders to indicate spots where images should be placed. To do this use an image link with `https://` as URL. You can upload the actual image directly from the renderer. Placeholders also support size definition, alt text and title.",
|
||||
"example": "![This is a placeholder image](https://)"
|
||||
},
|
||||
"iframeCapsule": {
|
||||
"title": "Iframe capsule",
|
||||
"description": "To protect viewers every iframe has to be activated explicitly. Before this, no information is fetched. Adding additional privileges using the sandbox attribute is not allowed.",
|
||||
"example": "<iframe src=\"https://example.org/\"></iframe>"
|
||||
},
|
||||
"plantuml": {
|
||||
"title": "PlantUML",
|
||||
"description": "Render diagrams and charts using PlantUML by adding a code block with the language `plantuml`. PlantUML diagrams are not rendered in your browser like the other charts, but by an external server.",
|
||||
"example": "```plantuml\n@startuml\nparticipant Alice\nparticipant \"The **Famous** Bob\" as Bob\n\nAlice -> Bob : hello --there--\n... Some ~~long delay~~ ...\nBob -> Alice : ok\nnote left\n This is **bold**\n This is //italics//\n This is \"\"monospaced\"\"\n This is --stroked--\n This is __underlined__\n This is ~~waved~~\nend note\n\nAlice -> Bob : A //well formatted// message\nnote right of Alice\n This is <back:cadetblue><size:18>displayed</size></back>\n __left of__ Alice.\nend note\nnote left of Bob\n <u:red>This</u> is <color #118888>displayed</color>\n **<color purple>left of</color> <s:red>Alice</strike> Bob**.\nend note\nnote over Alice, Bob\n <w:#FF33FF>This is hosted</w> by <img sourceforge.jpg>\nend note\n@enduml\n```"
|
||||
},
|
||||
"spoiler": {
|
||||
"title": "Spoiler",
|
||||
"description": "Hide information by using a spoiler tag.",
|
||||
"example": ":::spoiler This spoiler contains a surprise.\nSURPRISE!\n:::"
|
||||
},
|
||||
"toc": {
|
||||
"title": "Table Of Contents",
|
||||
"basic": {
|
||||
"title": "Basic",
|
||||
"description": "Add a table of contents that is automatically generated using your headlines by adding `[toc]` into a single line.",
|
||||
"example": "[toc]\n\n# This is a first headline\n\n## This is a second headline"
|
||||
},
|
||||
"levelLimit": {
|
||||
"title": "Level limits",
|
||||
"description": "You can limit the levels of the headlines the TOC should show",
|
||||
"example": "[toc:2:3]\n\n# This is a first headline\n\n## This is a second headline\n\n### This is a third headline\n\n#### This is a fourth headline"
|
||||
}
|
||||
},
|
||||
"vegaLite": {
|
||||
"title": "Vega Lite",
|
||||
"description": "Render diagrams and charts using vega lite by adding a code block with the language `vega-lite`",
|
||||
"example": "```vega-lite\n{\n \"$schema\": \"https://vega.github.io/schema/vega-lite/v5.json\",\n \"description\": \"Reproducing http://robslink.com/SAS/democd91/pyramid_pie.htm\",\n \"data\": {\n \"values\": [\n {\"category\": \"Sky\", \"value\": 75, \"order\": 3},\n {\"category\": \"Shady side of a pyramid\", \"value\": 10, \"order\": 1},\n {\"category\": \"Sunny side of a pyramid\", \"value\": 15, \"order\": 2}\n ]\n },\n \"mark\": {\"type\": \"arc\", \"outerRadius\": 80},\n \"encoding\": {\n \"theta\": {\n \"field\": \"value\", \"type\": \"quantitative\",\n \"scale\": {\"range\": [2.35619449, 8.639379797]},\n \"stack\": true\n },\n \"color\": {\n \"field\": \"category\", \"type\": \"nominal\",\n \"scale\": {\n \"domain\": [\"Sky\", \"Shady side of a pyramid\", \"Sunny side of a pyramid\"],\n \"range\": [\"#416D9D\", \"#674028\", \"#DEAC58\"]\n },\n \"legend\": {\n \"orient\": \"none\",\n \"title\": null,\n \"columns\": 1,\n \"legendX\": 200,\n \"legendY\": 80\n }\n },\n \"order\": {\n \"field\": \"order\"\n }\n },\n \"view\": {\"stroke\": null}\n}\n```"
|
||||
},
|
||||
"vimeo": {
|
||||
"title": "Vimeo",
|
||||
"description": "Embed a Vimeo video by placing the URL in a single line.",
|
||||
"example": "https://vimeo.com/23237102"
|
||||
},
|
||||
"youtube": {
|
||||
"title": "YouTube",
|
||||
"description": "Embed a YouTube video by placing the URL in a single line.",
|
||||
"example": "https://www.youtube.com/watch?v=YE7VzlLtp-4"
|
||||
},
|
||||
"taskList": {
|
||||
"title": "Task Lists",
|
||||
"description": "You can turn any listing into a task list by adding brackets. The checkboxes in the rendering change the markdown content if clicked.",
|
||||
"example": "- [ ] ToDos\n - [X] Buy some salad\n - [ ] Brush teeth\n - [x] Drink some water\n - [ ] **Click my box** and see the source code, if you're allowed to edit!\n"
|
||||
},
|
||||
"codeHighlighting": {
|
||||
"title": "Code Highlighting",
|
||||
"language": {
|
||||
"title": "Language",
|
||||
"description": "Specify a language after the start tag of a code block to activate code highlighting.",
|
||||
"example": "```js\nvar s = \"JavaScript syntax highlighting\";\nalert(s);\nfunction $initHighlight(block, cls) {\n try {\n if (cls.search(/\\bno\\-highlight\\b/) != -1)\n return process(block, true, 0x0F) +\n ' class=\"\"';\n } catch (e) {\n /* handle exception */\n }\n for (var i = 0 / 2; i < classes.length; i++) {\n if (checkCondition(classes[i]) === undefined)\n return /\\d+[\\s/]/g;\n }\n}\n```"
|
||||
},
|
||||
"lineWrapping": {
|
||||
"title": "Line wrapping",
|
||||
"description": "Set an exclamation mark to activate line wrapping",
|
||||
"example": "```text\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n```\n\n```text!\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n```"
|
||||
},
|
||||
"lineNumbers": {
|
||||
"title": "Line numbers",
|
||||
"description": "Set a equals sign after the language to show line numbers. You can specify a start line number after the equal sign or a plus to continue the line numbers from the last code block.",
|
||||
"example": "```markdown=12\nline1\n```\n```markdown=+\nline2\n```\n```markdown=\nline3\n```"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import { ShowIf } from '../../common/show-if/show-if'
|
|||
import { SignInButton } from '../../landing-layout/navigation/sign-in-button'
|
||||
import { UserDropdown } from '../../landing-layout/navigation/user-dropdown'
|
||||
import { SettingsButton } from '../../layout/settings-dialog/settings-button'
|
||||
import { CheatsheetButton } from './cheatsheet/cheatsheet-button'
|
||||
import { HelpButton } from './help-button/help-button'
|
||||
import { NavbarBranding } from './navbar-branding'
|
||||
import { ReadOnlyModeButton } from './read-only-mode-button'
|
||||
|
@ -47,6 +48,7 @@ export const AppBar: React.FC<AppBarProps> = ({ mode }) => {
|
|||
<ReadOnlyModeButton />
|
||||
</ShowIf>
|
||||
<HelpButton />
|
||||
<CheatsheetButton />
|
||||
</ShowIf>
|
||||
</Nav>
|
||||
<Nav className='d-flex gap-2 align-items-center text-secondary justify-content-end'>
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { CheatsheetExtension } from '../../cheatsheet/cheatsheet-extension'
|
||||
import { EntryList } from './entry-list'
|
||||
import React, { useMemo } from 'react'
|
||||
import { Accordion } from 'react-bootstrap'
|
||||
import { Trans } from 'react-i18next'
|
||||
|
||||
export interface GroupAccordionProps {
|
||||
extensions: CheatsheetExtension[]
|
||||
selectedEntry: CheatsheetExtension | undefined
|
||||
onStateChange: (value: CheatsheetExtension) => void
|
||||
}
|
||||
|
||||
const sortCategories = (
|
||||
[keyA]: [string, CheatsheetExtension[]],
|
||||
[keyB]: [string, CheatsheetExtension[]]
|
||||
): -1 | 0 | 1 => {
|
||||
if (keyA === keyB) {
|
||||
return 0
|
||||
} else if (keyA > keyB || keyA === 'other') {
|
||||
return 1
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
type CheatsheetGroupMap = Map<string, CheatsheetExtension[]>
|
||||
|
||||
const reduceCheatsheetExtensionByCategory = (
|
||||
state: CheatsheetGroupMap,
|
||||
extension: CheatsheetExtension
|
||||
): CheatsheetGroupMap => {
|
||||
const groupKey = extension.categoryI18nKey ?? 'other'
|
||||
const list = state.get(groupKey) ?? []
|
||||
list.push(extension)
|
||||
if (!state.has(groupKey)) {
|
||||
state.set(groupKey, list)
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders {@link EntryList entry lists} grouped by category.
|
||||
*
|
||||
* @param extensions The extensions which should be listed
|
||||
* @param selectedEntry The entry that should be displayed as selected
|
||||
* @param onStateChange A callback that should be executed if a new entry was selected
|
||||
*/
|
||||
export const CategoryAccordion: React.FC<GroupAccordionProps> = ({ extensions, selectedEntry, onStateChange }) => {
|
||||
const groupEntries = useMemo(() => {
|
||||
const groupings = extensions.reduce(reduceCheatsheetExtensionByCategory, new Map<string, CheatsheetExtension[]>())
|
||||
return Array.from(groupings.entries()).sort(sortCategories)
|
||||
}, [extensions])
|
||||
|
||||
const elements = useMemo(() => {
|
||||
return groupEntries.map(([groupKey, groupExtensions]) => (
|
||||
<Accordion.Item eventKey={groupKey} key={groupKey}>
|
||||
<Accordion.Header>
|
||||
<Trans i18nKey={`cheatsheet.categories.${groupKey}`}></Trans>
|
||||
</Accordion.Header>
|
||||
<Accordion.Body className={'p-0'}>
|
||||
<EntryList selectedEntry={selectedEntry} extensions={groupExtensions} onStateChange={onStateChange} />
|
||||
</Accordion.Body>
|
||||
</Accordion.Item>
|
||||
))
|
||||
}, [groupEntries, onStateChange, selectedEntry])
|
||||
|
||||
return <Accordion defaultActiveKey={groupEntries[0][0]}>{elements}</Accordion>
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useBooleanState } from '../../../../hooks/common/use-boolean-state'
|
||||
import { cypressId } from '../../../../utils/cypress-attribute'
|
||||
import { CommonModal } from '../../../common/modals/common-modal'
|
||||
import { CheatsheetModalBody } from './cheatsheet-modal-body'
|
||||
import React, { Fragment } from 'react'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import { QuestionCircle as IconQuestionCircle } from 'react-bootstrap-icons'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
/**
|
||||
* Shows a button that opens the cheatsheet dialog.
|
||||
*/
|
||||
export const CheatsheetButton: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const [modalVisibility, showModal, closeModal] = useBooleanState()
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Button
|
||||
{...cypressId('open.cheatsheet-button')}
|
||||
title={t('cheatsheet.button') ?? undefined}
|
||||
className={'mx-2'}
|
||||
variant='outline-dark'
|
||||
size={'sm'}
|
||||
onClick={showModal}>
|
||||
<Trans i18nKey={'cheatsheet.button'}></Trans>
|
||||
</Button>
|
||||
<CommonModal
|
||||
modalSize={'xl'}
|
||||
titleIcon={IconQuestionCircle}
|
||||
show={modalVisibility}
|
||||
onHide={closeModal}
|
||||
showCloseButton={true}
|
||||
titleI18nKey={'cheatsheet.modal.title'}>
|
||||
<CheatsheetModalBody />
|
||||
</CommonModal>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import HighlightedCode from '../../../../extensions/extra-integrations/highlighted-code-fence/highlighted-code'
|
||||
import { HtmlToReact } from '../../../common/html-to-react/html-to-react'
|
||||
import { ExtensionEventEmitterProvider } from '../../../markdown-renderer/hooks/use-extension-event-emitter'
|
||||
import { RendererType } from '../../../render-page/window-post-message-communicator/rendering-message'
|
||||
import type { CheatsheetEntry } from '../../cheatsheet/cheatsheet-extension'
|
||||
import { EditorToRendererCommunicatorContextProvider } from '../../render-context/editor-to-renderer-communicator-context-provider'
|
||||
import { RenderIframe } from '../../renderer-pane/render-iframe'
|
||||
import { ReadMoreLinkItem } from './read-more-link-item'
|
||||
import { useComponentsFromAppExtensions } from './use-components-from-app-extensions'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { ListGroupItem } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
interface CheatsheetRendererProps {
|
||||
rootI18nKey?: string
|
||||
extension: CheatsheetEntry
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the cheatsheet entry with description, example and rendered example.
|
||||
*
|
||||
* @param extension The extension to render
|
||||
* @param rootI18nKey An additional i18n namespace
|
||||
*/
|
||||
export const CheatsheetEntryPane: React.FC<CheatsheetRendererProps> = ({ extension, rootI18nKey }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [content, setContent] = useState('')
|
||||
|
||||
const lines = useMemo(() => content.split('\n'), [content])
|
||||
|
||||
const i18nPrefix = useMemo(
|
||||
() => `cheatsheet.${rootI18nKey ? `${rootI18nKey}.` : ''}${extension.i18nKey}.`,
|
||||
[extension.i18nKey, rootI18nKey]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setContent(t(`${i18nPrefix}example`) ?? '')
|
||||
}, [extension, i18nPrefix, t])
|
||||
|
||||
const cheatsheetExtensionComponents = useComponentsFromAppExtensions(setContent)
|
||||
|
||||
const descriptionElements = useMemo(() => {
|
||||
const content = t(`${i18nPrefix}description`)
|
||||
const markdownIt = new MarkdownIt('default')
|
||||
return <HtmlToReact htmlCode={markdownIt.render(content)}></HtmlToReact>
|
||||
}, [i18nPrefix, t])
|
||||
|
||||
return (
|
||||
<EditorToRendererCommunicatorContextProvider>
|
||||
<ExtensionEventEmitterProvider>
|
||||
{cheatsheetExtensionComponents}
|
||||
<ListGroupItem>
|
||||
<h4>
|
||||
<Trans i18nKey={'cheatsheet.modal.headlines.description'} />
|
||||
</h4>
|
||||
{descriptionElements}
|
||||
</ListGroupItem>
|
||||
<ReadMoreLinkItem url={extension.readMoreUrl}></ReadMoreLinkItem>
|
||||
<ListGroupItem>
|
||||
<h4>
|
||||
<Trans i18nKey={'cheatsheet.modal.headlines.exampleInput'} />
|
||||
</h4>
|
||||
<HighlightedCode code={content} wrapLines={true} language={'markdown'} startLineNumber={1} />
|
||||
</ListGroupItem>
|
||||
<ListGroupItem>
|
||||
<h4>
|
||||
<Trans i18nKey={'cheatsheet.modal.headlines.exampleOutput'} />
|
||||
</h4>
|
||||
<RenderIframe
|
||||
frameClasses={'w-100'}
|
||||
adaptFrameHeightToContent={true}
|
||||
rendererType={RendererType.SIMPLE}
|
||||
markdownContentLines={lines}></RenderIframe>
|
||||
</ListGroupItem>
|
||||
</ExtensionEventEmitterProvider>
|
||||
</EditorToRendererCommunicatorContextProvider>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { optionalAppExtensions } from '../../../../extensions/extra-integrations/optional-app-extensions'
|
||||
import type { CheatsheetEntry, CheatsheetExtension } from '../../cheatsheet/cheatsheet-extension'
|
||||
import { isCheatsheetGroup } from '../../cheatsheet/cheatsheet-extension'
|
||||
import { CategoryAccordion } from './category-accordion'
|
||||
import { CheatsheetEntryPane } from './cheatsheet-entry-pane'
|
||||
import { TopicSelection } from './topic-selection'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { Col, ListGroup, Modal, Row } from 'react-bootstrap'
|
||||
import { Trans } from 'react-i18next'
|
||||
|
||||
/**
|
||||
* Renders the tab content for the cheatsheet.
|
||||
*/
|
||||
export const CheatsheetModalBody: React.FC = () => {
|
||||
const [selectedExtension, setSelectedExtension] = useState<CheatsheetExtension>()
|
||||
const [selectedEntry, setSelectedEntry] = useState<CheatsheetEntry>()
|
||||
|
||||
const changeExtension = useCallback((value: CheatsheetExtension) => {
|
||||
setSelectedExtension(value)
|
||||
setSelectedEntry(isCheatsheetGroup(value) ? value.entries[0] : value)
|
||||
}, [])
|
||||
|
||||
const extensions = useMemo(
|
||||
() => optionalAppExtensions.flatMap((extension) => extension.buildCheatsheetExtensions()),
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<Modal.Body>
|
||||
<Row className={`mt-2`}>
|
||||
<Col xs={3}>
|
||||
<CategoryAccordion
|
||||
extensions={extensions}
|
||||
selectedEntry={selectedExtension}
|
||||
onStateChange={changeExtension}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={9}>
|
||||
<ListGroup>
|
||||
<TopicSelection
|
||||
extension={selectedExtension}
|
||||
selectedEntry={selectedEntry}
|
||||
setSelectedEntry={setSelectedEntry}
|
||||
/>
|
||||
{selectedEntry !== undefined ? (
|
||||
<CheatsheetEntryPane
|
||||
rootI18nKey={isCheatsheetGroup(selectedExtension) ? selectedExtension.i18nKey : undefined}
|
||||
extension={selectedEntry}
|
||||
/>
|
||||
) : (
|
||||
<span>
|
||||
<Trans i18nKey={'cheatsheet.modal.noSelection'}></Trans>
|
||||
</span>
|
||||
)}
|
||||
</ListGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
</Modal.Body>
|
||||
)
|
||||
}
|
|
@ -7,3 +7,9 @@
|
|||
.table-cheatsheet > tr > td {
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
|
||||
.sticky {
|
||||
position: sticky;
|
||||
top: 1rem;
|
||||
bottom: 1rem;
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { CheatsheetExtension } from '../../cheatsheet/cheatsheet-extension'
|
||||
import styles from './cheatsheet.module.scss'
|
||||
import React, { useMemo } from 'react'
|
||||
import { ListGroup, ListGroupItem } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
interface CheatsheetListProps {
|
||||
selectedEntry: CheatsheetExtension | undefined
|
||||
extensions: CheatsheetExtension[]
|
||||
onStateChange: (value: CheatsheetExtension) => void
|
||||
}
|
||||
|
||||
const compareString = (value1: string, value2: string): -1 | 0 | 1 => {
|
||||
return value1 === value2 ? 0 : value1 < value2 ? -1 : 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a list of cheatsheet entries.
|
||||
*
|
||||
* @param extensions The extensions whose cheatsheet entries should be listed
|
||||
* @param selectedEntry The cheatsheet entry that should be rendered as selected.
|
||||
* @param onStateChange A callback that is executed when a new entry has been selected
|
||||
*/
|
||||
export const EntryList: React.FC<CheatsheetListProps> = ({ extensions, selectedEntry, onStateChange }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const listItems = useMemo(
|
||||
() =>
|
||||
extensions
|
||||
.map((extension) => [extension, t(`cheatsheet.${extension.i18nKey}.title`)] as [CheatsheetExtension, string])
|
||||
.sort(([, title1], [, title2]) => compareString(title1.toLowerCase(), title2.toLowerCase()))
|
||||
.map(([cheatsheetExtension, title]) => (
|
||||
<ListGroupItem
|
||||
key={cheatsheetExtension.i18nKey}
|
||||
action
|
||||
active={cheatsheetExtension.i18nKey == selectedEntry?.i18nKey}
|
||||
onClick={() => onStateChange(cheatsheetExtension)}>
|
||||
{title}
|
||||
</ListGroupItem>
|
||||
)),
|
||||
[extensions, onStateChange, selectedEntry, t]
|
||||
)
|
||||
|
||||
return <ListGroup className={styles.sticky}>{listItems}</ListGroup>
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { ExternalLink } from '../../../common/links/external-link'
|
||||
import React from 'react'
|
||||
import { ListGroupItem } from 'react-bootstrap'
|
||||
import { Trans } from 'react-i18next'
|
||||
|
||||
export interface ReadMoreLinkGroupProps {
|
||||
url: URL | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the read more URL as external link.
|
||||
*
|
||||
* @param url The URL to display. If the URL is undefined then nothing will be rendered.
|
||||
*/
|
||||
export const ReadMoreLinkItem: React.FC<ReadMoreLinkGroupProps> = ({ url }) => {
|
||||
return !url ? null : (
|
||||
<ListGroupItem>
|
||||
<h4>
|
||||
<Trans i18nKey={'cheatsheet.modal.headlines.readMoreLink'} />
|
||||
</h4>
|
||||
<ExternalLink className={'text-dark'} text={url.toString()} href={url.toString()}></ExternalLink>
|
||||
</ListGroupItem>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { CheatsheetEntry, CheatsheetExtension } from '../../cheatsheet/cheatsheet-extension'
|
||||
import { isCheatsheetGroup } from '../../cheatsheet/cheatsheet-extension'
|
||||
import React, { useMemo } from 'react'
|
||||
import { Button, ButtonGroup, ListGroupItem } from 'react-bootstrap'
|
||||
import { Trans } from 'react-i18next'
|
||||
|
||||
interface EntrySelectionProps {
|
||||
extension: CheatsheetExtension | undefined
|
||||
selectedEntry: CheatsheetEntry | undefined
|
||||
setSelectedEntry: (value: CheatsheetEntry) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a button group that contains the topics of the given extension.
|
||||
* If the extension has no topics then the selection won't be displayed.
|
||||
*
|
||||
* @param extension The extension whose topics should be displayed
|
||||
* @param selectedEntry The currently selected cheatsheet entry that should be displayed as active
|
||||
* @param setSelectedEntry A callback that should be executed if a new topic has been selected
|
||||
*/
|
||||
export const TopicSelection: React.FC<EntrySelectionProps> = ({ extension, selectedEntry, setSelectedEntry }) => {
|
||||
const listItems = useMemo(() => {
|
||||
if (!isCheatsheetGroup(extension)) {
|
||||
return null
|
||||
}
|
||||
return extension.entries.map((entry) => (
|
||||
<Button
|
||||
key={entry.i18nKey}
|
||||
variant={selectedEntry?.i18nKey === entry.i18nKey ? 'primary' : 'outline-primary'}
|
||||
onClick={() => setSelectedEntry(entry)}>
|
||||
<Trans i18nKey={`cheatsheet.${extension.i18nKey}.${entry.i18nKey}.title`}></Trans>
|
||||
</Button>
|
||||
))
|
||||
}, [extension, selectedEntry?.i18nKey, setSelectedEntry])
|
||||
|
||||
return !listItems ? null : (
|
||||
<ListGroupItem>
|
||||
<h4>
|
||||
<Trans i18nKey={'cheatsheet.modal.headlines.selectTopic'} />
|
||||
</h4>
|
||||
<ButtonGroup className={'mb-2'}>{listItems}</ButtonGroup>
|
||||
</ListGroupItem>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { optionalAppExtensions } from '../../../../extensions/extra-integrations/optional-app-extensions'
|
||||
import type { CheatsheetExtensionComponentProps } from '../../cheatsheet/cheatsheet-extension'
|
||||
import { isCheatsheetGroup } from '../../cheatsheet/cheatsheet-extension'
|
||||
import type { ReactElement } from 'react'
|
||||
import React, { Fragment, useMemo } from 'react'
|
||||
|
||||
/**
|
||||
* Generates react elements from components which are provided by cheatsheet extensions.
|
||||
*/
|
||||
export const useComponentsFromAppExtensions = (
|
||||
setContent: CheatsheetExtensionComponentProps['setContent']
|
||||
): ReactElement => {
|
||||
return useMemo(() => {
|
||||
return (
|
||||
<Fragment key={'app-extensions'}>
|
||||
{optionalAppExtensions
|
||||
.flatMap((extension) => extension.buildCheatsheetExtensions())
|
||||
.flatMap((extension) => (isCheatsheetGroup(extension) ? extension.entries : extension))
|
||||
.map((extension) => {
|
||||
if (extension.cheatsheetExtensionComponent) {
|
||||
return React.createElement(extension.cheatsheetExtensionComponent, { key: extension.i18nKey, setContent })
|
||||
}
|
||||
})}
|
||||
</Fragment>
|
||||
)
|
||||
}, [setContent])
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { TaskCheckedEventPayload } from '../../../../extensions/extra-integrations/task-list/event-emitting-task-list-checkbox'
|
||||
import { TaskListCheckboxAppExtension } from '../../../../extensions/extra-integrations/task-list/task-list-checkbox-app-extension'
|
||||
import { WaitSpinner } from '../../../common/wait-spinner/wait-spinner'
|
||||
import { eventEmitterContext } from '../../../markdown-renderer/hooks/use-extension-event-emitter'
|
||||
import type { Listener } from 'eventemitter2'
|
||||
import { EventEmitter2 } from 'eventemitter2'
|
||||
import React, { Suspense, useEffect, useMemo } from 'react'
|
||||
|
||||
export interface CheatsheetLineProps {
|
||||
markdown: string
|
||||
onTaskCheckedChange: (newValue: boolean) => void
|
||||
}
|
||||
|
||||
const HighlightedCode = React.lazy(
|
||||
() => import('../../../../extensions/extra-integrations/highlighted-code-fence/highlighted-code')
|
||||
)
|
||||
const DocumentMarkdownRenderer = React.lazy(() => import('../../../markdown-renderer/document-markdown-renderer'))
|
||||
|
||||
/**
|
||||
* Renders one line in the {@link CheatsheetTabContent cheat sheet}.
|
||||
* This line shows an minimal markdown example and how it would be rendered.
|
||||
*
|
||||
* @param markdown The markdown to be shown and rendered
|
||||
* @param onTaskCheckedChange A callback to call if a task would be clicked
|
||||
*/
|
||||
export const CheatsheetLine: React.FC<CheatsheetLineProps> = ({ markdown, onTaskCheckedChange }) => {
|
||||
const lines = useMemo(() => markdown.split('\n'), [markdown])
|
||||
const eventEmitter = useMemo(() => new EventEmitter2(), [])
|
||||
|
||||
useEffect(() => {
|
||||
const handler = eventEmitter.on(
|
||||
TaskListCheckboxAppExtension.EVENT_NAME,
|
||||
({ checked }: TaskCheckedEventPayload) => onTaskCheckedChange(checked),
|
||||
{ objectify: true }
|
||||
) as Listener
|
||||
return () => {
|
||||
handler.off()
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
<tr>
|
||||
<td colSpan={2}>
|
||||
<WaitSpinner />
|
||||
</td>
|
||||
</tr>
|
||||
}>
|
||||
<tr>
|
||||
<td>
|
||||
<eventEmitterContext.Provider value={eventEmitter}>
|
||||
<DocumentMarkdownRenderer markdownContentLines={lines} baseUrl={'https://example.org'} />
|
||||
</eventEmitterContext.Provider>
|
||||
</td>
|
||||
<td className={'markdown-body'}>
|
||||
<HighlightedCode code={markdown} wrapLines={true} startLineNumber={1} language={'markdown'} />
|
||||
</td>
|
||||
</tr>
|
||||
</Suspense>
|
||||
)
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { CheatsheetLine } from './cheatsheet-line'
|
||||
import styles from './cheatsheet.module.scss'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { Table } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
/**
|
||||
* Renders the content of the cheat sheet for the {@link HelpModal}.
|
||||
*/
|
||||
export const CheatsheetTabContent: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const [checked, setChecked] = useState<boolean>(false)
|
||||
const codes = useMemo(
|
||||
() => [
|
||||
`**${t('editor.editorToolbar.bold')}**`,
|
||||
`*${t('editor.editorToolbar.italic')}*`,
|
||||
`++${t('editor.editorToolbar.underline')}++`,
|
||||
`~~${t('editor.editorToolbar.strikethrough')}~~`,
|
||||
'H~2~O',
|
||||
'19^th^',
|
||||
`==${t('editor.help.cheatsheet.highlightedText')}==`,
|
||||
`# ${t('editor.editorToolbar.header')}`,
|
||||
`\`${t('editor.editorToolbar.code')}\``,
|
||||
'```javascript=\nvar x = 5;\n```',
|
||||
`> ${t('editor.editorToolbar.blockquote')}`,
|
||||
`- ${t('editor.editorToolbar.unorderedList')}`,
|
||||
`1. ${t('editor.editorToolbar.orderedList')}`,
|
||||
`- [${checked ? 'x' : ' '}] ${t('editor.editorToolbar.checkList')}`,
|
||||
`[${t('editor.editorToolbar.link')}](https://example.com)`,
|
||||
`![${t('editor.editorToolbar.image')}](/icons/apple-touch-icon.png)`,
|
||||
':smile:',
|
||||
':bi-bootstrap:',
|
||||
`:::info\n${t('editor.help.cheatsheet.exampleAlert')}\n:::`
|
||||
],
|
||||
[checked, t]
|
||||
)
|
||||
|
||||
return (
|
||||
<Table className={`table-condensed ${styles['table-cheatsheet']}`}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<Trans i18nKey='editor.help.cheatsheet.example' />
|
||||
</th>
|
||||
<th>
|
||||
<Trans i18nKey='editor.help.cheatsheet.syntax' />
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{codes.map((code) => (
|
||||
<CheatsheetLine markdown={code} key={code} onTaskCheckedChange={setChecked} />
|
||||
))}
|
||||
</tbody>
|
||||
</Table>
|
||||
)
|
||||
}
|
||||
|
||||
export default CheatsheetTabContent
|
|
@ -5,12 +5,11 @@
|
|||
*/
|
||||
import { useBooleanState } from '../../../../hooks/common/use-boolean-state'
|
||||
import { cypressId } from '../../../../utils/cypress-attribute'
|
||||
import { UiIcon } from '../../../common/icons/ui-icon'
|
||||
import { IconButton } from '../../../common/icon-button/icon-button'
|
||||
import { HelpModal } from './help-modal'
|
||||
import React, { Fragment } from 'react'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import { QuestionCircle as IconQuestionCircle } from 'react-bootstrap-icons'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
/**
|
||||
* Renders the button to open the {@link HelpModal}.
|
||||
|
@ -21,15 +20,16 @@ export const HelpButton: React.FC = () => {
|
|||
|
||||
return (
|
||||
<Fragment>
|
||||
<Button
|
||||
<IconButton
|
||||
icon={IconQuestionCircle}
|
||||
{...cypressId('editor-help-button')}
|
||||
title={t('editor.documentBar.help') ?? undefined}
|
||||
className='ms-2 text-secondary'
|
||||
className='ms-2'
|
||||
size='sm'
|
||||
variant='outline-light'
|
||||
variant='outline-dark'
|
||||
onClick={showModal}>
|
||||
<UiIcon icon={IconQuestionCircle} />
|
||||
</Button>
|
||||
<Trans i18nKey={'editor.documentBar.help'} />
|
||||
</IconButton>
|
||||
<HelpModal show={modalVisibility} onHide={closeModal} />
|
||||
</Fragment>
|
||||
)
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
*/
|
||||
import type { ModalVisibilityProps } from '../../../common/modals/common-modal'
|
||||
import { CommonModal } from '../../../common/modals/common-modal'
|
||||
import { CheatsheetTabContent } from './cheatsheet-tab-content'
|
||||
import { LinksTabContent } from './links-tab-content'
|
||||
import { ShortcutTabContent } from './shortcuts-tab-content'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
|
@ -14,7 +13,6 @@ import { QuestionCircle as IconQuestionCircle } from 'react-bootstrap-icons'
|
|||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
export enum HelpTabStatus {
|
||||
Cheatsheet = 'cheatsheet.title',
|
||||
Shortcuts = 'shortcuts.title',
|
||||
Links = 'links.title'
|
||||
}
|
||||
|
@ -31,13 +29,11 @@ export enum HelpTabStatus {
|
|||
* @param onHide A callback when the modal should be closed again
|
||||
*/
|
||||
export const HelpModal: React.FC<ModalVisibilityProps> = ({ show, onHide }) => {
|
||||
const [tab, setTab] = useState<HelpTabStatus>(HelpTabStatus.Cheatsheet)
|
||||
const [tab, setTab] = useState<HelpTabStatus>(HelpTabStatus.Shortcuts)
|
||||
const { t } = useTranslation()
|
||||
|
||||
const tabContent = useMemo(() => {
|
||||
switch (tab) {
|
||||
case HelpTabStatus.Cheatsheet:
|
||||
return <CheatsheetTabContent />
|
||||
case HelpTabStatus.Shortcuts:
|
||||
return <ShortcutTabContent />
|
||||
case HelpTabStatus.Links:
|
||||
|
@ -48,15 +44,9 @@ export const HelpModal: React.FC<ModalVisibilityProps> = ({ show, onHide }) => {
|
|||
const modalTitle = useMemo(() => t('editor.documentBar.help') + ' - ' + t(`editor.help.${tab}`), [t, tab])
|
||||
|
||||
return (
|
||||
<CommonModal modalSize={'lg'} titleIcon={IconQuestionCircle} show={show} onHide={onHide} title={modalTitle}>
|
||||
<CommonModal modalSize={'xl'} titleIcon={IconQuestionCircle} show={show} onHide={onHide} title={modalTitle}>
|
||||
<Modal.Body>
|
||||
<nav className='nav nav-tabs'>
|
||||
<Button
|
||||
variant={'light'}
|
||||
className={`nav-link nav-item ${tab === HelpTabStatus.Cheatsheet ? 'active' : ''}`}
|
||||
onClick={() => setTab(HelpTabStatus.Cheatsheet)}>
|
||||
<Trans i18nKey={'editor.help.cheatsheet.title'} />
|
||||
</Button>
|
||||
<Button
|
||||
variant={'light'}
|
||||
className={`nav-link nav-item ${tab === HelpTabStatus.Shortcuts ? 'active' : ''}`}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type React from 'react'
|
||||
|
||||
export interface CheatsheetExtensionComponentProps {
|
||||
setContent: (dispatcher: string | ((prevState: string) => string)) => void
|
||||
}
|
||||
|
||||
export type CheatsheetExtension = CheatsheetEntry | CheatsheetGroup
|
||||
|
||||
export const isCheatsheetGroup = (extension: CheatsheetExtension | undefined): extension is CheatsheetGroup => {
|
||||
return (extension as CheatsheetGroup)?.entries !== undefined
|
||||
}
|
||||
|
||||
export interface CheatsheetGroup {
|
||||
i18nKey: string
|
||||
categoryI18nKey?: string
|
||||
entries: CheatsheetEntry[]
|
||||
}
|
||||
|
||||
export interface CheatsheetEntry {
|
||||
i18nKey: string
|
||||
categoryI18nKey?: string
|
||||
cheatsheetExtensionComponent?: React.FC<CheatsheetExtensionComponentProps>
|
||||
|
||||
readMoreUrl?: URL
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { AppExtension } from '../../../../extensions/base/app-extension'
|
||||
import type { CheatsheetExtension } from '../../../editor-page/cheatsheet/cheatsheet-extension'
|
||||
import type { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import { BasicMarkdownSyntaxMarkdownExtension } from './basic-markdown-syntax-markdown-extension'
|
||||
|
||||
export class BasicMarkdownSyntaxAppExtension extends AppExtension {
|
||||
buildMarkdownRendererExtensions(): MarkdownRendererExtension[] {
|
||||
return [new BasicMarkdownSyntaxMarkdownExtension()]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [
|
||||
{
|
||||
i18nKey: 'basics.basicFormatting',
|
||||
categoryI18nKey: 'basic'
|
||||
},
|
||||
{
|
||||
i18nKey: 'basics.abbreviation',
|
||||
categoryI18nKey: 'basic'
|
||||
},
|
||||
{
|
||||
i18nKey: 'basics.footnote',
|
||||
categoryI18nKey: 'basic'
|
||||
},
|
||||
{
|
||||
i18nKey: 'basics.headlines',
|
||||
categoryI18nKey: 'basic',
|
||||
entries: [
|
||||
{
|
||||
i18nKey: 'hashtag'
|
||||
},
|
||||
{
|
||||
i18nKey: 'equal'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
i18nKey: 'basics.code',
|
||||
categoryI18nKey: 'basic',
|
||||
entries: [{ i18nKey: 'inline' }, { i18nKey: 'block' }]
|
||||
},
|
||||
{
|
||||
i18nKey: 'basics.lists',
|
||||
categoryI18nKey: 'basic',
|
||||
entries: [{ i18nKey: 'unordered' }, { i18nKey: 'ordered' }]
|
||||
},
|
||||
{
|
||||
i18nKey: 'basics.images',
|
||||
categoryI18nKey: 'basic',
|
||||
entries: [{ i18nKey: 'basic' }, { i18nKey: 'size' }]
|
||||
},
|
||||
{
|
||||
i18nKey: 'basics.links',
|
||||
categoryI18nKey: 'basic'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { MarkdownRendererExtension } from './base/markdown-renderer-extension'
|
||||
import { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import { imageSize } from '@hedgedoc/markdown-it-plugins'
|
||||
import type MarkdownIt from 'markdown-it'
|
||||
import abbreviation from 'markdown-it-abbr'
|
||||
|
@ -17,7 +17,7 @@ import superscript from 'markdown-it-sup'
|
|||
/**
|
||||
* Adds some common markdown syntaxes to the markdown rendering.
|
||||
*/
|
||||
export class GenericSyntaxMarkdownExtension extends MarkdownRendererExtension {
|
||||
export class BasicMarkdownSyntaxMarkdownExtension extends MarkdownRendererExtension {
|
||||
public configureMarkdownIt(markdownIt: MarkdownIt): void {
|
||||
abbreviation(markdownIt)
|
||||
definitionList(markdownIt)
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { AppExtension } from '../../../../extensions/base/app-extension'
|
||||
import type { CheatsheetExtension } from '../../../editor-page/cheatsheet/cheatsheet-extension'
|
||||
import type { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import { BootstrapIconMarkdownExtension } from './bootstrap-icon-markdown-extension'
|
||||
|
||||
export class BootstrapIconAppExtension extends AppExtension {
|
||||
buildMarkdownRendererExtensions(): MarkdownRendererExtension[] {
|
||||
return [new BootstrapIconMarkdownExtension()]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [{ i18nKey: 'bootstrapIcon', readMoreUrl: new URL('https://icons.getbootstrap.com/') }]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { AppExtension } from '../../../../extensions/base/app-extension'
|
||||
import type { CheatsheetExtension } from '../../../editor-page/cheatsheet/cheatsheet-extension'
|
||||
import type { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import { EmojiMarkdownExtension } from './emoji-markdown-extension'
|
||||
|
||||
export class EmojiAppExtension extends AppExtension {
|
||||
buildMarkdownRendererExtensions(): MarkdownRendererExtension[] {
|
||||
return [new EmojiMarkdownExtension()]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [
|
||||
{
|
||||
i18nKey: 'emoji',
|
||||
readMoreUrl: new URL('https://twemoji.twitter.com/')
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { AppExtension } from '../../../../extensions/base/app-extension'
|
||||
import type { CheatsheetExtension } from '../../../editor-page/cheatsheet/cheatsheet-extension'
|
||||
import type { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import { IframeCapsuleMarkdownExtension } from './iframe-capsule-markdown-extension'
|
||||
|
||||
export class IframeCapsuleAppExtension extends AppExtension {
|
||||
buildMarkdownRendererExtensions(): MarkdownRendererExtension[] {
|
||||
return [new IframeCapsuleMarkdownExtension()]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [
|
||||
{
|
||||
i18nKey: 'iframeCapsule',
|
||||
categoryI18nKey: 'embedding'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { AppExtension } from '../../../../extensions/base/app-extension'
|
||||
import type { CheatsheetExtension } from '../../../editor-page/cheatsheet/cheatsheet-extension'
|
||||
import type { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import { ImagePlaceholderMarkdownExtension } from './image-placeholder-markdown-extension'
|
||||
|
||||
export class ImagePlaceholderAppExtension extends AppExtension {
|
||||
buildMarkdownRendererExtensions(): MarkdownRendererExtension[] {
|
||||
return [new ImagePlaceholderMarkdownExtension()]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [
|
||||
{
|
||||
i18nKey: 'imagePlaceholder'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { AppExtension } from '../../../../extensions/base/app-extension'
|
||||
import type { CheatsheetExtension } from '../../../editor-page/cheatsheet/cheatsheet-extension'
|
||||
import type { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import { TableOfContentsMarkdownExtension } from './table-of-contents-markdown-extension'
|
||||
import type EventEmitter2 from 'eventemitter2'
|
||||
|
||||
export class TableOfContentsAppExtension extends AppExtension {
|
||||
buildMarkdownRendererExtensions(eventEmitter?: EventEmitter2): MarkdownRendererExtension[] {
|
||||
return [new TableOfContentsMarkdownExtension(eventEmitter)]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [
|
||||
{
|
||||
i18nKey: 'toc',
|
||||
entries: [
|
||||
{
|
||||
i18nKey: 'basic'
|
||||
},
|
||||
{
|
||||
i18nKey: 'levelLimit'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { tocSlugify } from '../../editor-page/table-of-contents/toc-slugify'
|
||||
import { MarkdownRendererExtension } from './base/markdown-renderer-extension'
|
||||
import { tocSlugify } from '../../../editor-page/table-of-contents/toc-slugify'
|
||||
import { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import type { TocAst } from '@hedgedoc/markdown-it-plugins'
|
||||
import { toc } from '@hedgedoc/markdown-it-plugins'
|
||||
import equal from 'fast-deep-equal'
|
|
@ -1,20 +1,14 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { optionalAppExtensions } from '../../../extensions/extra-integrations/optional-app-extensions'
|
||||
import type { MarkdownRendererExtension } from '../extensions/base/markdown-renderer-extension'
|
||||
import { BootstrapIconMarkdownExtension } from '../extensions/bootstrap-icons/bootstrap-icon-markdown-extension'
|
||||
import { DebuggerMarkdownExtension } from '../extensions/debugger-markdown-extension'
|
||||
import { EmojiMarkdownExtension } from '../extensions/emoji/emoji-markdown-extension'
|
||||
import { GenericSyntaxMarkdownExtension } from '../extensions/generic-syntax-markdown-extension'
|
||||
import { IframeCapsuleMarkdownExtension } from '../extensions/iframe-capsule/iframe-capsule-markdown-extension'
|
||||
import { ImagePlaceholderMarkdownExtension } from '../extensions/image-placeholder/image-placeholder-markdown-extension'
|
||||
import { ProxyImageMarkdownExtension } from '../extensions/image/proxy-image-markdown-extension'
|
||||
import { LinkAdjustmentMarkdownExtension } from '../extensions/link-replacer/link-adjustment-markdown-extension'
|
||||
import { LinkifyFixMarkdownExtension } from '../extensions/linkify-fix/linkify-fix-markdown-extension'
|
||||
import { TableOfContentsMarkdownExtension } from '../extensions/table-of-contents-markdown-extension'
|
||||
import { UploadIndicatingImageFrameMarkdownExtension } from '../extensions/upload-indicating-image-frame/upload-indicating-image-frame-markdown-extension'
|
||||
import { useExtensionEventEmitter } from './use-extension-event-emitter'
|
||||
import { useMemo } from 'react'
|
||||
|
@ -35,14 +29,8 @@ export const useMarkdownExtensions = (
|
|||
return [
|
||||
...optionalAppExtensions.flatMap((extension) => extension.buildMarkdownRendererExtensions(extensionEventEmitter)),
|
||||
...additionalExtensions,
|
||||
new TableOfContentsMarkdownExtension(),
|
||||
new IframeCapsuleMarkdownExtension(),
|
||||
new ImagePlaceholderMarkdownExtension(),
|
||||
new UploadIndicatingImageFrameMarkdownExtension(),
|
||||
new LinkAdjustmentMarkdownExtension(baseUrl),
|
||||
new EmojiMarkdownExtension(),
|
||||
new BootstrapIconMarkdownExtension(),
|
||||
new GenericSyntaxMarkdownExtension(),
|
||||
new LinkifyFixMarkdownExtension(),
|
||||
new DebuggerMarkdownExtension(),
|
||||
new ProxyImageMarkdownExtension()
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { ShowIf } from '../common/show-if/show-if'
|
||||
import { TableOfContentsMarkdownExtension } from '../markdown-renderer/extensions/table-of-contents-markdown-extension'
|
||||
import { TableOfContentsMarkdownExtension } from '../markdown-renderer/extensions/table-of-contents/table-of-contents-markdown-extension'
|
||||
import { useExtensionEventEmitterHandler } from '../markdown-renderer/hooks/use-extension-event-emitter'
|
||||
import styles from './markdown-document.module.scss'
|
||||
import { WidthBasedTableOfContents } from './width-based-table-of-contents'
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { CheatsheetExtension } from '../../components/editor-page/cheatsheet/cheatsheet-extension'
|
||||
import type { Linter } from '../../components/editor-page/editor-pane/linter/linter'
|
||||
import type { MarkdownRendererExtension } from '../../components/markdown-renderer/extensions/base/markdown-renderer-extension'
|
||||
import type { EventEmitter2 } from 'eventemitter2'
|
||||
|
@ -22,4 +23,8 @@ export abstract class AppExtension {
|
|||
public buildEditorExtensionComponent(): React.FC {
|
||||
return Fragment
|
||||
}
|
||||
|
||||
public buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { CheatsheetExtension } from '../../../components/editor-page/cheatsheet/cheatsheet-extension'
|
||||
import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension'
|
||||
import { AppExtension } from '../../base/app-extension'
|
||||
import { AbcjsMarkdownExtension } from './abcjs-markdown-extension'
|
||||
|
@ -11,4 +12,8 @@ export class AbcjsAppExtension extends AppExtension {
|
|||
buildMarkdownRendererExtensions(): MarkdownRendererExtension[] {
|
||||
return [new AbcjsMarkdownExtension()]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [{ i18nKey: 'abcjs', categoryI18nKey: 'charts', readMoreUrl: new URL('https://www.abcjs.net/') }]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { CheatsheetExtension } from '../../../components/editor-page/cheatsheet/cheatsheet-extension'
|
||||
import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension'
|
||||
import { AppExtension } from '../../base/app-extension'
|
||||
import { AlertMarkdownExtension } from './alert-markdown-extension'
|
||||
|
@ -14,4 +15,8 @@ export class AlertAppExtension extends AppExtension {
|
|||
buildMarkdownRendererExtensions(): MarkdownRendererExtension[] {
|
||||
return [new AlertMarkdownExtension()]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [{ i18nKey: 'alert' }]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { CheatsheetExtension } from '../../../components/editor-page/cheatsheet/cheatsheet-extension'
|
||||
import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension'
|
||||
import { AppExtension } from '../../base/app-extension'
|
||||
import { AsciinemaMarkdownExtension } from './asciinema-markdown-extension'
|
||||
|
@ -16,4 +17,8 @@ export class AsciinemaAppExtension extends AppExtension {
|
|||
buildMarkdownRendererExtensions(): MarkdownRendererExtension[] {
|
||||
return [new AsciinemaMarkdownExtension()]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [{ i18nKey: 'asciinema', categoryI18nKey: 'embedding', readMoreUrl: new URL('https://asciinema.org/') }]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { CheatsheetExtension } from '../../../components/editor-page/cheatsheet/cheatsheet-extension'
|
||||
import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension'
|
||||
import { AppExtension } from '../../base/app-extension'
|
||||
import { BlockquoteExtraTagMarkdownExtension } from './blockquote-extra-tag-markdown-extension'
|
||||
|
@ -14,4 +15,8 @@ export class BlockquoteAppExtension extends AppExtension {
|
|||
buildMarkdownRendererExtensions(): MarkdownRendererExtension[] {
|
||||
return [new BlockquoteExtraTagMarkdownExtension()]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [{ i18nKey: 'blockquoteTags', entries: [{ i18nKey: 'name' }, { i18nKey: 'color' }, { i18nKey: 'time' }] }]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { CheatsheetExtension } from '../../../components/editor-page/cheatsheet/cheatsheet-extension'
|
||||
import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension'
|
||||
import { AppExtension } from '../../base/app-extension'
|
||||
import { CsvTableMarkdownExtension } from './csv-table-markdown-extension'
|
||||
|
@ -14,4 +15,8 @@ export class CsvTableAppExtension extends AppExtension {
|
|||
buildMarkdownRendererExtensions(): MarkdownRendererExtension[] {
|
||||
return [new CsvTableMarkdownExtension()]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [{ i18nKey: 'csv', entries: [{ i18nKey: 'table' }, { i18nKey: 'header' }] }]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { CheatsheetExtension } from '../../../components/editor-page/cheatsheet/cheatsheet-extension'
|
||||
import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension'
|
||||
import { AppExtension } from '../../base/app-extension'
|
||||
import { FlowchartMarkdownExtension } from './flowchart-markdown-extension'
|
||||
|
@ -14,4 +15,8 @@ export class FlowchartAppExtension extends AppExtension {
|
|||
buildMarkdownRendererExtensions(): MarkdownRendererExtension[] {
|
||||
return [new FlowchartMarkdownExtension()]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [{ i18nKey: 'flowchart', categoryI18nKey: 'charts', readMoreUrl: new URL('https://flowchart.js.org/') }]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { CheatsheetExtension } from '../../../components/editor-page/cheatsheet/cheatsheet-extension'
|
||||
import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension'
|
||||
import { AppExtension } from '../../base/app-extension'
|
||||
import { GistMarkdownExtension } from './gist-markdown-extension'
|
||||
|
@ -14,4 +15,8 @@ export class GistAppExtension extends AppExtension {
|
|||
buildMarkdownRendererExtensions(): MarkdownRendererExtension[] {
|
||||
return [new GistMarkdownExtension()]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [{ i18nKey: 'gist', categoryI18nKey: 'embedding', readMoreUrl: new URL('https://gist.github.com/') }]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { CheatsheetExtension } from '../../../components/editor-page/cheatsheet/cheatsheet-extension'
|
||||
import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension'
|
||||
import { AppExtension } from '../../base/app-extension'
|
||||
import { GraphvizMarkdownExtension } from './graphviz-markdown-extension'
|
||||
|
@ -14,4 +15,8 @@ export class GraphvizAppExtension extends AppExtension {
|
|||
buildMarkdownRendererExtensions(): MarkdownRendererExtension[] {
|
||||
return [new GraphvizMarkdownExtension()]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [{ i18nKey: 'graphviz', categoryI18nKey: 'charts', readMoreUrl: new URL('https://graphviz.org/') }]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { CheatsheetExtension } from '../../../components/editor-page/cheatsheet/cheatsheet-extension'
|
||||
import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension'
|
||||
import { AppExtension } from '../../base/app-extension'
|
||||
import { HighlightedCodeMarkdownExtension } from './highlighted-code-markdown-extension'
|
||||
|
@ -14,4 +15,13 @@ export class HighlightedCodeFenceAppExtension extends AppExtension {
|
|||
buildMarkdownRendererExtensions(): MarkdownRendererExtension[] {
|
||||
return [new HighlightedCodeMarkdownExtension()]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [
|
||||
{
|
||||
i18nKey: 'codeHighlighting',
|
||||
entries: [{ i18nKey: 'language' }, { i18nKey: 'lineNumbers' }, { i18nKey: 'lineWrapping' }]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { CheatsheetExtension } from '../../../components/editor-page/cheatsheet/cheatsheet-extension'
|
||||
import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension'
|
||||
import { AppExtension } from '../../base/app-extension'
|
||||
import { KatexMarkdownExtension } from './katex-markdown-extension'
|
||||
|
@ -16,4 +17,8 @@ export class KatexAppExtension extends AppExtension {
|
|||
buildMarkdownRendererExtensions(): MarkdownRendererExtension[] {
|
||||
return [new KatexMarkdownExtension()]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [{ i18nKey: 'katex', readMoreUrl: new URL('https://katex.org/') }]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,8 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { HtmlToReact } from '../../../components/common/html-to-react/html-to-react'
|
||||
import { testId } from '../../../utils/test-id'
|
||||
import convertHtmlToReact from '@hedgedoc/html-to-react'
|
||||
import { sanitize } from 'dompurify'
|
||||
import KaTeX from 'katex'
|
||||
import 'katex/dist/katex.min.css'
|
||||
import React, { useMemo } from 'react'
|
||||
|
@ -26,10 +25,12 @@ export const KatexFrame: React.FC<KatexFrameProps> = ({ expression, block = fals
|
|||
const dom = useMemo(() => {
|
||||
try {
|
||||
const katexHtml = KaTeX.renderToString(expression, {
|
||||
displayMode: block === true,
|
||||
displayMode: block,
|
||||
throwOnError: true
|
||||
})
|
||||
return convertHtmlToReact(sanitize(katexHtml, { ADD_TAGS: ['semantics', 'annotation'] }))
|
||||
return (
|
||||
<HtmlToReact htmlCode={katexHtml} domPurifyConfig={{ ADD_TAGS: ['semantics', 'annotation'] }}></HtmlToReact>
|
||||
)
|
||||
} catch (error) {
|
||||
return (
|
||||
<Alert className={block ? '' : 'd-inline-block'} variant={'danger'}>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { CheatsheetExtension } from '../../../components/editor-page/cheatsheet/cheatsheet-extension'
|
||||
import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension'
|
||||
import { AppExtension } from '../../base/app-extension'
|
||||
import { MermaidMarkdownExtension } from './mermaid-markdown-extension'
|
||||
|
@ -14,4 +15,8 @@ export class MermaidAppExtension extends AppExtension {
|
|||
buildMarkdownRendererExtensions(): MarkdownRendererExtension[] {
|
||||
return [new MermaidMarkdownExtension()]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [{ i18nKey: 'mermaid', categoryI18nKey: 'charts', readMoreUrl: new URL('https://mermaid.js.org/') }]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,12 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { BasicMarkdownSyntaxAppExtension } from '../../components/markdown-renderer/extensions/basic-markdown-syntax/basic-markdown-syntax-app-extension'
|
||||
import { BootstrapIconAppExtension } from '../../components/markdown-renderer/extensions/bootstrap-icons/bootstrap-icon-app-extension'
|
||||
import { EmojiAppExtension } from '../../components/markdown-renderer/extensions/emoji/emoji-app-extension'
|
||||
import { IframeCapsuleAppExtension } from '../../components/markdown-renderer/extensions/iframe-capsule/iframe-capsule-app-extension'
|
||||
import { ImagePlaceholderAppExtension } from '../../components/markdown-renderer/extensions/image-placeholder/image-placeholder-app-extension'
|
||||
import { TableOfContentsAppExtension } from '../../components/markdown-renderer/extensions/table-of-contents/table-of-contents-app-extension'
|
||||
import type { AppExtension } from '../base/app-extension'
|
||||
import { AbcjsAppExtension } from './abcjs/abcjs-app-extension'
|
||||
import { AlertAppExtension } from './alert/alert-app-extension'
|
||||
|
@ -48,5 +54,11 @@ export const optionalAppExtensions: AppExtension[] = [
|
|||
new YoutubeAppExtension(),
|
||||
new TaskListCheckboxAppExtension(),
|
||||
new HighlightedCodeFenceAppExtension(),
|
||||
new ForkAwesomeHtmlTagAppExtension()
|
||||
new ForkAwesomeHtmlTagAppExtension(),
|
||||
new BootstrapIconAppExtension(),
|
||||
new EmojiAppExtension(),
|
||||
new TableOfContentsAppExtension(),
|
||||
new ImagePlaceholderAppExtension(),
|
||||
new IframeCapsuleAppExtension(),
|
||||
new BasicMarkdownSyntaxAppExtension()
|
||||
]
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { CheatsheetExtension } from '../../../components/editor-page/cheatsheet/cheatsheet-extension'
|
||||
import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension'
|
||||
import { AppExtension } from '../../base/app-extension'
|
||||
import { PlantumlMarkdownExtension } from './plantuml-markdown-extension'
|
||||
|
@ -16,4 +17,8 @@ export class PlantumlAppExtension extends AppExtension {
|
|||
buildMarkdownRendererExtensions(): MarkdownRendererExtension[] {
|
||||
return [new PlantumlMarkdownExtension()]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [{ i18nKey: 'plantuml', categoryI18nKey: 'charts', readMoreUrl: new URL('https://plantuml.com/') }]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { CheatsheetExtension } from '../../../components/editor-page/cheatsheet/cheatsheet-extension'
|
||||
import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension'
|
||||
import { AppExtension } from '../../base/app-extension'
|
||||
import { SpoilerMarkdownExtension } from './spoiler-markdown-extension'
|
||||
|
@ -16,4 +17,8 @@ export class SpoilerAppExtension extends AppExtension {
|
|||
buildMarkdownRendererExtensions(): MarkdownRendererExtension[] {
|
||||
return [new SpoilerMarkdownExtension()]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [{ i18nKey: 'spoiler' }]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the markdown line prefix for a task list checkbox.
|
||||
*
|
||||
* @param state The check state of the checkbox.
|
||||
*/
|
||||
export const createCheckboxContent = (state: boolean) => {
|
||||
return `[${state ? 'x' : ' '}]`
|
||||
}
|
|
@ -13,7 +13,7 @@ type EventEmittingTaskListCheckboxProps = Omit<TaskListProps, 'onTaskCheckedChan
|
|||
|
||||
export interface TaskCheckedEventPayload {
|
||||
lineInMarkdown: number
|
||||
checked: boolean
|
||||
newCheckedState: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -25,7 +25,10 @@ export const EventEmittingTaskListCheckbox: React.FC<EventEmittingTaskListCheckb
|
|||
const emitter = useExtensionEventEmitter()
|
||||
const sendEvent: TaskCheckedChangeHandler = useCallback(
|
||||
(lineInMarkdown: number, checked: boolean) => {
|
||||
emitter?.emit(TaskListCheckboxAppExtension.EVENT_NAME, { lineInMarkdown, checked } as TaskCheckedEventPayload)
|
||||
emitter?.emit(TaskListCheckboxAppExtension.EVENT_NAME, {
|
||||
lineInMarkdown,
|
||||
newCheckedState: checked
|
||||
} as TaskCheckedEventPayload)
|
||||
},
|
||||
[emitter]
|
||||
)
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { Optional } from '@mrdrogdrog/optional'
|
||||
|
||||
const TASK_REGEX = /^(\s*(?:[-*+]|\d+[.)]) )(\[[ xX]?])/
|
||||
|
||||
/**
|
||||
* Checks if the given markdown content contains a task list checkbox at the given line index.
|
||||
*
|
||||
* @param markdownContent The content that should be checked
|
||||
* @param lineIndex The index of the line that should be checked for a task list checkbox
|
||||
* @return An {@link Optional} that contains the start and end index of the found checkbox
|
||||
*/
|
||||
export const findCheckBox = (
|
||||
markdownContent: string,
|
||||
lineIndex: number
|
||||
): Optional<[startIndex: number, endIndex: number]> => {
|
||||
const lines = markdownContent.split('\n')
|
||||
const lineStartIndex = findStartIndexOfLine(lines, lineIndex)
|
||||
return Optional.ofNullable(TASK_REGEX.exec(lines[lineIndex])).map(([, beforeCheckbox, oldCheckbox]) => [
|
||||
lineStartIndex + beforeCheckbox.length,
|
||||
lineStartIndex + beforeCheckbox.length + oldCheckbox.length
|
||||
])
|
||||
}
|
||||
|
||||
const findStartIndexOfLine = (lines: string[], wantedLineIndex: number): number => {
|
||||
return lines
|
||||
.map((value) => value.length)
|
||||
.filter((value, index) => index < wantedLineIndex)
|
||||
.reduce((state, lineLength) => state + lineLength + 1, 0)
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { CheatsheetExtensionComponentProps } from '../../../components/editor-page/cheatsheet/cheatsheet-extension'
|
||||
import { useExtensionEventEmitterHandler } from '../../../components/markdown-renderer/hooks/use-extension-event-emitter'
|
||||
import { createCheckboxContent } from './create-checkbox-content'
|
||||
import type { TaskCheckedEventPayload } from './event-emitting-task-list-checkbox'
|
||||
import { findCheckBox } from './find-check-box'
|
||||
import { TaskListCheckboxAppExtension } from './task-list-checkbox-app-extension'
|
||||
import type React from 'react'
|
||||
|
||||
/**
|
||||
* Receives task-checkbox-change events and modify the current editor content.
|
||||
*/
|
||||
export const SetCheckboxInCheatsheet: React.FC<CheatsheetExtensionComponentProps> = ({ setContent }) => {
|
||||
useExtensionEventEmitterHandler(TaskListCheckboxAppExtension.EVENT_NAME, (event: TaskCheckedEventPayload) => {
|
||||
setContent((previousContent) => {
|
||||
return findCheckBox(previousContent, event.lineInMarkdown)
|
||||
.map(
|
||||
([startIndex, endIndex]) =>
|
||||
previousContent.slice(0, startIndex) +
|
||||
createCheckboxContent(event.newCheckedState) +
|
||||
previousContent.slice(endIndex)
|
||||
)
|
||||
.orElse(previousContent)
|
||||
})
|
||||
})
|
||||
return null
|
||||
}
|
|
@ -3,10 +3,16 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useChangeEditorContentCallback } from '../../../components/editor-page/change-content-context/use-change-editor-content-callback'
|
||||
import type { ContentEdits } from '../../../components/editor-page/editor-pane/tool-bar/formatters/types/changes'
|
||||
import { useExtensionEventEmitterHandler } from '../../../components/markdown-renderer/hooks/use-extension-event-emitter'
|
||||
import { store } from '../../../redux'
|
||||
import { createCheckboxContent } from './create-checkbox-content'
|
||||
import type { TaskCheckedEventPayload } from './event-emitting-task-list-checkbox'
|
||||
import { findCheckBox } from './find-check-box'
|
||||
import { TaskListCheckboxAppExtension } from './task-list-checkbox-app-extension'
|
||||
import { useSetCheckboxInEditor } from './use-set-checkbox-in-editor'
|
||||
import type React from 'react'
|
||||
import { useCallback } from 'react'
|
||||
|
||||
/**
|
||||
* Receives task-checkbox-change events and modify the current editor content.
|
||||
|
@ -16,3 +22,45 @@ export const SetCheckboxInEditor: React.FC = () => {
|
|||
useExtensionEventEmitterHandler(TaskListCheckboxAppExtension.EVENT_NAME, changeCallback)
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a callback that changes the state of a checkbox in a given line in the current codemirror instance.
|
||||
*/
|
||||
export const useSetCheckboxInEditor = () => {
|
||||
const changeEditorContent = useChangeEditorContentCallback()
|
||||
|
||||
return useCallback(
|
||||
({ lineInMarkdown, newCheckedState }: TaskCheckedEventPayload): void => {
|
||||
changeEditorContent?.(({ markdownContent }) => {
|
||||
const correctedLineIndex = lineInMarkdown + store.getState().noteDetails.frontmatterRendererInfo.lineOffset
|
||||
const edits = findCheckBox(markdownContent, correctedLineIndex)
|
||||
.map(([startIndex, endIndex]) => createCheckboxContentEdit(startIndex, endIndex, newCheckedState))
|
||||
.orElse([])
|
||||
return [edits, undefined]
|
||||
})
|
||||
},
|
||||
[changeEditorContent]
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ContentEdits content edit} for the change of a checkbox at a given position.
|
||||
*
|
||||
* @param checkboxStartIndex The start index of the old checkbox code
|
||||
* @param checkboxEndIndex The end index of the old checkbox code
|
||||
* @param newCheckboxState The new status of the checkbox
|
||||
* @return the created {@link ContentEdits edit}
|
||||
*/
|
||||
const createCheckboxContentEdit = (
|
||||
checkboxStartIndex: number,
|
||||
checkboxEndIndex: number,
|
||||
newCheckboxState: boolean
|
||||
): ContentEdits => {
|
||||
return [
|
||||
{
|
||||
from: checkboxStartIndex,
|
||||
to: checkboxEndIndex,
|
||||
insert: createCheckboxContent(newCheckboxState)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { CheatsheetExtension } from '../../../components/editor-page/cheatsheet/cheatsheet-extension'
|
||||
import { AppExtension } from '../../base/app-extension'
|
||||
import { SetCheckboxInCheatsheet } from './set-checkbox-in-cheatsheet'
|
||||
import { SetCheckboxInEditor } from './set-checkbox-in-editor'
|
||||
import { TaskListMarkdownExtension } from './task-list-markdown-extension'
|
||||
import type { EventEmitter2 } from 'eventemitter2'
|
||||
|
@ -22,4 +24,8 @@ export class TaskListCheckboxAppExtension extends AppExtension {
|
|||
buildEditorExtensionComponent(): React.FC {
|
||||
return SetCheckboxInEditor
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [{ i18nKey: 'taskList', cheatsheetExtensionComponent: SetCheckboxInCheatsheet }]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useChangeEditorContentCallback } from '../../../components/editor-page/change-content-context/use-change-editor-content-callback'
|
||||
import type { ContentEdits } from '../../../components/editor-page/editor-pane/tool-bar/formatters/types/changes'
|
||||
import { store } from '../../../redux'
|
||||
import type { TaskCheckedEventPayload } from './event-emitting-task-list-checkbox'
|
||||
import { Optional } from '@mrdrogdrog/optional'
|
||||
import { useCallback } from 'react'
|
||||
|
||||
const TASK_REGEX = /(\s*(?:[-*+]|\d+[.)]) )(\[[ xX]?])/
|
||||
|
||||
/**
|
||||
* Provides a callback that changes the state of a checkbox in a given line in the current codemirror instance.
|
||||
*/
|
||||
export const useSetCheckboxInEditor = () => {
|
||||
const changeEditorContent = useChangeEditorContentCallback()
|
||||
|
||||
return useCallback(
|
||||
({ lineInMarkdown, checked }: TaskCheckedEventPayload): void => {
|
||||
changeEditorContent?.(({ markdownContent }) => {
|
||||
const lines = markdownContent.split('\n')
|
||||
const correctedLineIndex = lineInMarkdown + store.getState().noteDetails.frontmatterRendererInfo.lineOffset
|
||||
const lineStartIndex = findStartIndexOfLine(lines, correctedLineIndex)
|
||||
const edits = Optional.ofNullable(TASK_REGEX.exec(lines[correctedLineIndex]))
|
||||
.map(([, beforeCheckbox, oldCheckbox]) => {
|
||||
const checkboxStartIndex = lineStartIndex + beforeCheckbox.length
|
||||
return createCheckboxContentEdit(checkboxStartIndex, oldCheckbox, checked)
|
||||
})
|
||||
.orElse([])
|
||||
return [edits, undefined]
|
||||
})
|
||||
},
|
||||
[changeEditorContent]
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the start position of the wanted line index if the given lines would be concat with new-line-characters.
|
||||
*
|
||||
* @param lines The lines to search through
|
||||
* @param wantedLineIndex The index of the line whose start position should be found
|
||||
* @return the found start position
|
||||
*/
|
||||
const findStartIndexOfLine = (lines: string[], wantedLineIndex: number): number => {
|
||||
return lines
|
||||
.map((value) => value.length)
|
||||
.filter((value, index) => index < wantedLineIndex)
|
||||
.reduce((state, lineLength) => state + lineLength + 1, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ContentEdits content edit} for the change of a checkbox at a given position.
|
||||
*
|
||||
* @param checkboxStartIndex The start index of the checkbox
|
||||
* @param oldCheckbox The old checkbox that should be replaced
|
||||
* @param newCheckboxState The new status of the checkbox
|
||||
* @return the created {@link ContentEdits edit}
|
||||
*/
|
||||
const createCheckboxContentEdit = (
|
||||
checkboxStartIndex: number,
|
||||
oldCheckbox: string,
|
||||
newCheckboxState: boolean
|
||||
): ContentEdits => {
|
||||
return [
|
||||
{
|
||||
from: checkboxStartIndex,
|
||||
to: checkboxStartIndex + oldCheckbox.length,
|
||||
insert: `[${newCheckboxState ? 'x' : ' '}]`
|
||||
}
|
||||
]
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { CheatsheetExtension } from '../../../components/editor-page/cheatsheet/cheatsheet-extension'
|
||||
import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension'
|
||||
import { AppExtension } from '../../base/app-extension'
|
||||
import { VegaLiteMarkdownExtension } from './vega-lite-markdown-extension'
|
||||
|
@ -14,4 +15,10 @@ export class VegaLiteAppExtension extends AppExtension {
|
|||
buildMarkdownRendererExtensions(): MarkdownRendererExtension[] {
|
||||
return [new VegaLiteMarkdownExtension()]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [
|
||||
{ i18nKey: 'vegaLite', categoryI18nKey: 'charts', readMoreUrl: new URL('https://vega.github.io/vega-lite/') }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { CheatsheetExtension } from '../../../components/editor-page/cheatsheet/cheatsheet-extension'
|
||||
import type { Linter } from '../../../components/editor-page/editor-pane/linter/linter'
|
||||
import { SingleLineRegexLinter } from '../../../components/editor-page/editor-pane/linter/single-line-regex-linter'
|
||||
import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension'
|
||||
|
@ -28,4 +29,8 @@ export class VimeoAppExtension extends AppExtension {
|
|||
)
|
||||
]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [{ i18nKey: 'vimeo', categoryI18nKey: 'embedding', readMoreUrl: new URL('https://vimeo.com/') }]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { CheatsheetExtension } from '../../../components/editor-page/cheatsheet/cheatsheet-extension'
|
||||
import type { Linter } from '../../../components/editor-page/editor-pane/linter/linter'
|
||||
import { SingleLineRegexLinter } from '../../../components/editor-page/editor-pane/linter/single-line-regex-linter'
|
||||
import type { MarkdownRendererExtension } from '../../../components/markdown-renderer/extensions/base/markdown-renderer-extension'
|
||||
|
@ -28,4 +29,8 @@ export class YoutubeAppExtension extends AppExtension {
|
|||
)
|
||||
]
|
||||
}
|
||||
|
||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||
return [{ i18nKey: 'youtube', categoryI18nKey: 'embedding', readMoreUrl: new URL('https://youtube.com/') }]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue