diff --git a/cypress/integration/autocompletion.spec.ts b/cypress/integration/autocompletion.spec.ts index 6585bf24e..863617c57 100644 --- a/cypress/integration/autocompletion.spec.ts +++ b/cypress/integration/autocompletion.spec.ts @@ -63,7 +63,7 @@ describe('Autocompletion', () => { cy.get('.CodeMirror-code > div:nth-of-type(1) > .CodeMirror-line > span > span') .should('have.text', ':::success') cy.get('.CodeMirror-code > div:nth-of-type(3) > .CodeMirror-line > span span') - .should('have.text', ':::') + .should('have.text', '::: ') cy.get('.markdown-body > div.alert') .should('exist') }) @@ -78,7 +78,7 @@ describe('Autocompletion', () => { cy.get('.CodeMirror-code > div:nth-of-type(1) > .CodeMirror-line > span > span') .should('have.text', ':::success') cy.get('.CodeMirror-code > div:nth-of-type(3) > .CodeMirror-line > span span') - .should('have.text', ':::') + .should('have.text', '::: ') cy.get('.markdown-body > div.alert') .should('exist') }) diff --git a/cypress/integration/toolbar.spec.ts b/cypress/integration/toolbar.spec.ts index 2e3a96b38..6d4e39138 100644 --- a/cypress/integration/toolbar.spec.ts +++ b/cypress/integration/toolbar.spec.ts @@ -322,7 +322,7 @@ describe('Toolbar', () => { cy.get('.fa-caret-square-o-down') .click() cy.get('.CodeMirror-code > div:nth-of-type(2) > .CodeMirror-line > span span') - .should('have.text', '
') + .should('have.text', ':::spoiler Toggle label') }) it('comment', () => { diff --git a/src/components/editor/editor-pane/autocompletion/container.ts b/src/components/editor/editor-pane/autocompletion/container.ts index 2fc9e3530..6a8c732da 100644 --- a/src/components/editor/editor-pane/autocompletion/container.ts +++ b/src/components/editor/editor-pane/autocompletion/container.ts @@ -5,12 +5,20 @@ */ import { Editor, Hint, Hints, Pos } from 'codemirror' +import { validAlertLevels } from '../../../markdown-renderer/markdown-it-plugins/alert-container' import { findWordAtCursor, Hinter } from './index' const wordRegExp = /^:::((\w|-|_|\+)*)$/ -const allSupportedConatiner = ['success', 'info', 'warning', 'danger'] +const spoilerSuggestion: Hint = { + text: ':::spoiler Toggle label\nToggled content\n::: \n', + displayText: 'spoiler' +} +const suggestions = validAlertLevels.map((suggestion: string): Hint => ({ + text: ':::' + suggestion + '\n\n::: \n', + displayText: suggestion +})).concat(spoilerSuggestion) -const containerHint = (editor: Editor): Promise< Hints| null > => { +const containerHint = (editor: Editor): Promise => { return new Promise((resolve) => { const searchTerm = findWordAtCursor(editor) const searchResult = wordRegExp.exec(searchTerm.text) @@ -18,16 +26,12 @@ const containerHint = (editor: Editor): Promise< Hints| null > => { resolve(null) return } - const suggestions = allSupportedConatiner const cursor = editor.getCursor() if (!suggestions) { resolve(null) } else { resolve({ - list: suggestions.map((suggestion: string): Hint => ({ - text: ':::' + suggestion + '\n\n:::\n', - displayText: suggestion - })), + list: suggestions.filter((suggestion) => suggestion.displayText?.startsWith(searchResult[1])), from: Pos(cursor.line, searchTerm.start), to: Pos(cursor.line, searchTerm.end) }) diff --git a/src/components/editor/editor-pane/autocompletion/index.ts b/src/components/editor/editor-pane/autocompletion/index.ts index 7856abb49..3c8f3d492 100644 --- a/src/components/editor/editor-pane/autocompletion/index.ts +++ b/src/components/editor/editor-pane/autocompletion/index.ts @@ -64,5 +64,5 @@ export const allHinters: Hinter[] = [ ImageHinter, LinkAndExtraTagHinter, PDFHinter, - CollapsableBlockHinter + CollapsableBlockHinter, ] diff --git a/src/components/editor/editor-pane/tool-bar/utils/toolbarButtonUtils.test.ts b/src/components/editor/editor-pane/tool-bar/utils/toolbarButtonUtils.test.ts index 8fd7c2776..a2a97e076 100644 --- a/src/components/editor/editor-pane/tool-bar/utils/toolbarButtonUtils.test.ts +++ b/src/components/editor/editor-pane/tool-bar/utils/toolbarButtonUtils.test.ts @@ -929,7 +929,7 @@ describe('test collapsable block', () => { listSelections: mockListSelections(cursor, true), getLine: (): string => (textFirstLine), replaceRange: (replacement: string | string[]) => { - expect(replacement).toEqual(`${textFirstLine}\n
\n Toggle label\n Toggled content\n
`) + expect(replacement).toEqual(`${textFirstLine}\n:::spoiler Toggle label\n Toggled content\n:::`) done() } }) @@ -941,7 +941,7 @@ describe('test collapsable block', () => { getCursor: () => cursor.from, listSelections: mockListSelections(firstLine, false), getLine: (): string => (textFirstLine), - replaceRange: expectFromToReplacement(firstLine, `${textFirstLine}\n
\n Toggle label\n Toggled content\n
`, done) + replaceRange: expectFromToReplacement(firstLine, `${textFirstLine}\n:::spoiler Toggle label\n Toggled content\n:::`, done) }) addCollapsableBlock(editor) }) @@ -952,7 +952,7 @@ describe('test collapsable block', () => { listSelections: mockListSelections(multiline, false), getLine: (): string => '2nd line', replaceRange: (replacement: string | string[]) => { - expect(replacement).toEqual('2nd line\n
\n Toggle label\n Toggled content\n
') + expect(replacement).toEqual('2nd line\n:::spoiler Toggle label\n Toggled content\n:::') done() } }) @@ -965,7 +965,7 @@ describe('test collapsable block', () => { listSelections: mockListSelections(multilineOffset, false), getLine: (): string => '2nd line', replaceRange: (replacement: string | string[]) => { - expect(replacement).toEqual('2nd line\n
\n Toggle label\n Toggled content\n
') + expect(replacement).toEqual('2nd line\n:::spoiler Toggle label\n Toggled content\n:::') done() } }) diff --git a/src/components/editor/editor-pane/tool-bar/utils/toolbarButtonUtils.ts b/src/components/editor/editor-pane/tool-bar/utils/toolbarButtonUtils.ts index 9606cc2d7..0ae0e9538 100644 --- a/src/components/editor/editor-pane/tool-bar/utils/toolbarButtonUtils.ts +++ b/src/components/editor/editor-pane/tool-bar/utils/toolbarButtonUtils.ts @@ -28,7 +28,7 @@ export const addTaskList = (editor: Editor): void => createList(editor, () => '- export const addImage = (editor: Editor): void => addLink(editor, '!') export const addLine = (editor: Editor): void => changeLines(editor, line => `${line}\n----`) -export const addCollapsableBlock = (editor: Editor): void => changeLines(editor, line => `${line}\n
\n Toggle label\n Toggled content\n
`) +export const addCollapsableBlock = (editor: Editor): void => changeLines(editor, line => `${line}\n:::spoiler Toggle label\n Toggled content\n:::`) export const addComment = (editor: Editor): void => changeLines(editor, line => `${line}\n> []`) export const addTable = (editor: Editor, rows: number, columns: number): void => { const rowArray = createNumberRangeArray(rows) diff --git a/src/components/markdown-renderer/markdown-it-configurator/BasicMarkdownItConfigurator.tsx b/src/components/markdown-renderer/markdown-it-configurator/BasicMarkdownItConfigurator.tsx index 77bf338f5..a3db4ce72 100644 --- a/src/components/markdown-renderer/markdown-it-configurator/BasicMarkdownItConfigurator.tsx +++ b/src/components/markdown-renderer/markdown-it-configurator/BasicMarkdownItConfigurator.tsx @@ -16,6 +16,7 @@ import superscript from 'markdown-it-sup' import { alertContainer } from '../markdown-it-plugins/alert-container' import { linkifyExtra } from '../markdown-it-plugins/linkify-extra' import { MarkdownItParserDebugger } from '../markdown-it-plugins/parser-debugger' +import { spoilerContainer } from '../markdown-it-plugins/spoiler-container' import { tasksLists } from '../markdown-it-plugins/tasks-lists' import { twitterEmojis } from '../markdown-it-plugins/twitter-emojis' import { MarkdownItConfigurator } from './MarkdownItConfigurator' @@ -33,7 +34,8 @@ export class BasicMarkdownItConfigurator extends MarkdownItConfigurator { footnote, imsize, tasksLists, - alertContainer + alertContainer, + spoilerContainer ) this.postConfigurations.push( linkifyExtra, diff --git a/src/components/markdown-renderer/markdown-it-plugins/alert-container.ts b/src/components/markdown-renderer/markdown-it-plugins/alert-container.ts index 283871c95..3feae4d03 100644 --- a/src/components/markdown-renderer/markdown-it-plugins/alert-container.ts +++ b/src/components/markdown-renderer/markdown-it-plugins/alert-container.ts @@ -10,7 +10,7 @@ import Renderer from 'markdown-it/lib/renderer' import Token from 'markdown-it/lib/token' import { MarkdownItPlugin } from '../replace-components/ComponentReplacer' -type RenderContainerReturn = (tokens: Token[], index: number, options: MarkdownIt.Options, env: unknown, self: Renderer) => void; +export type RenderContainerReturn = (tokens: Token[], index: number, options: MarkdownIt.Options, env: unknown, self: Renderer) => string; type ValidAlertLevels = ('warning' | 'danger' | 'success' | 'info') export const validAlertLevels: ValidAlertLevels[] = ['success', 'danger', 'info', 'warning'] diff --git a/src/components/markdown-renderer/markdown-it-plugins/spoiler-container.ts b/src/components/markdown-renderer/markdown-it-plugins/spoiler-container.ts new file mode 100644 index 000000000..280914dff --- /dev/null +++ b/src/components/markdown-renderer/markdown-it-plugins/spoiler-container.ts @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import MarkdownIt from 'markdown-it' +import { escapeHtml } from 'markdown-it/lib/common/utils' +import markdownItContainer from 'markdown-it-container' +import Token from 'markdown-it/lib/token' +import { MarkdownItPlugin } from '../replace-components/ComponentReplacer' +import { RenderContainerReturn } from './alert-container' + +export const spoilerRegEx = /^spoiler\s+(.*)$/; + +const createSpoilerContainer = (): RenderContainerReturn => { + return (tokens: Token[], index: number) => { + const matches = spoilerRegEx.exec(tokens[index].info.trim()) + + if (tokens[index].nesting === 1 && matches && matches[1]) { + // opening tag + return `
${escapeHtml(matches[1])}` + + } else { + // closing tag + return '
\n' + } + } +} + +export const spoilerContainer: MarkdownItPlugin = (markdownIt: MarkdownIt) => { + markdownItContainer(markdownIt, 'spoiler', { + validate: (params: string) => spoilerRegEx.test(params), + render: createSpoilerContainer() + }) +}