Skip to content

Commit

Permalink
Merge branch 'mde-action-buttons' into 'main'
Browse files Browse the repository at this point in the history
Mde action buttons

See merge request reportcreator/reportcreator!439
  • Loading branch information
MWedl committed Jan 30, 2024
2 parents 11eeaac + f420437 commit 2caa87b
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 30 deletions.
5 changes: 5 additions & 0 deletions frontend/src/components/Markdown/Toolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
<markdown-toolbar-button @click="codemirrorAction(toggleStrong)" title="Bold" icon="mdi-format-bold" :disabled="props.disabled" :active="isTypeInSelection(props.editorState, 'strong')" />
<markdown-toolbar-button @click="codemirrorAction(toggleEmphasis)" title="Italic" icon="mdi-format-italic" :disabled="props.disabled" :active="isTypeInSelection(props.editorState, 'emphasis')" />
<markdown-toolbar-button @click="codemirrorAction(toggleStrikethrough)" title="Strikethrough" icon="mdi-format-strikethrough" :disabled="props.disabled" :active="isTypeInSelection(props.editorState, 'strikethrough')" />
<markdown-toolbar-button @click="codemirrorAction(toggleFootnote)" title="Footnote" icon="mdi-format-superscript" :disabled="props.disabled" :active="isTypeInSelection(props.editorState, 'inlineFootnote')" />
<span class="separator" />
<markdown-toolbar-button @click="codemirrorAction(toggleListUnordered)" title="Bullet List" icon="mdi-format-list-bulleted" :disabled="props.disabled" :active="isTypeInSelection(props.editorState, 'listUnordered')" />
<markdown-toolbar-button @click="codemirrorAction(toggleListOrdered)" title="Numbered List" icon="mdi-format-list-numbered" :disabled="props.disabled" :active="isTypeInSelection(props.editorState, 'listOrdered')" />
<markdown-toolbar-button @click="codemirrorAction(toggleTaskList)" title="Checklist" icon="mdi-format-list-checkbox" :disabled="props.disabled" :active="isTaskListInSelection(props.editorState)" />
<markdown-toolbar-button @click="codemirrorAction(insertCodeBlock)" title="Code" icon="mdi-code-tags" :disabled="props.disabled" :active="isTypeInSelection(props.editorState, 'codeFenced')" />
<markdown-toolbar-button @click="codemirrorAction(insertTable)" title="Table" icon="mdi-table" :disabled="props.disabled" :active="isTypeInSelection(props.editorState, 'table')" />
<span class="separator" />
Expand Down Expand Up @@ -56,11 +58,14 @@ import {
toggleLink,
toggleListOrdered,
toggleListUnordered,
toggleTaskList,
toggleStrikethrough,
toggleStrong,
toggleFootnote,
undo,
undoDepth,
isTypeInSelection,
isTaskListInSelection,
// @ts-ignore
} from 'reportcreator-markdown/editor';
import type { VToolbar } from 'vuetify/lib/components/index.mjs';
Expand Down
36 changes: 35 additions & 1 deletion frontend/test/markdownEditorCommands.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { describe, test, expect } from 'vitest'
import {
EditorState, EditorSelection, EditorView,
markdown, syntaxHighlighting, markdownHighlightStyle, markdownHighlightCodeBlocks,
toggleStrong, toggleEmphasis, toggleStrikethrough, toggleListUnordered, toggleListOrdered, toggleLink, insertCodeBlock, insertTable, insertNewlineContinueMarkup
toggleStrong, toggleEmphasis, toggleStrikethrough, toggleFootnote,
toggleListUnordered, toggleListOrdered, toggleTaskList,
toggleLink, insertCodeBlock, insertTable, insertNewlineContinueMarkup
} from 'reportcreator-markdown/editor';

function createEditorState(textWithSelection, cursorMarker = '|') {
Expand Down Expand Up @@ -93,6 +95,21 @@ describe('toggleListOrdered', () => {
}
});

describe('toggleTaskList', () => {
for (const [before, after] of Object.entries({
"|a\nb|": "|* [ ] a\n* [ ] b|",
"a|aa\nb": "* [ ] a|aa\nb",
"|* [ ] a\n* [ ] b|": "|a\nb|",
"* [ ] a|\n* [ ] b": "a|\n* [ ] b",
"|1. a\n2. b|": "|* [ ] a\n* [ ] b|",
"1. |a\n2. b": "* [ ] |a\n2. b",
"|": "|* [ ] ",
"* [ ] |": "|",
})) {
testCommand(toggleTaskList, before, after);
}
})

describe('toggleLink', () => {
for (const [before, after] of Object.entries({
"a | b": "a [|](https://) b",
Expand All @@ -108,6 +125,23 @@ describe('toggleLink', () => {
}
});

describe('toggleFootnote', () => {
for (const [before, after] of Object.entries({
"a | b": "a ^[|text|] b",
"a |text| b": "a ^[|text|] b",
"a ^[|text|] b": "a |text| b",
"a |^[text]| b": "a |text| b",
"a ^[te|xt] b": "a te|xt b",
"|a ^[text] b|": "|a text b|",
"|a ^[te|xt] b": "|a te|xt b",
"a ^[|] b": "a | b",
"|a ^|[text] b": "|a |text b",
"a ^|[text]| b": "a |text| b",
})) {
testCommand(toggleFootnote, before, after);
}
});

describe('insertCodeBlock', () => {
for (const [before, after] of Object.entries({
"a\n|\nb": "a\n\n```\n|\n```\nb",
Expand Down
90 changes: 90 additions & 0 deletions packages/markdown/editor/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,15 @@ export function toggleStrikethrough({state, dispatch}) {
});
}

export function toggleFootnote({state, dispatch}) {
return toggleMarkerType({state, dispatch}, {
type: 'inlineFootnote',
markerTypes: ['inlineFootnoteMarker', 'inlineFootnoteStartMarker', 'inlineFootnoteEndMarker'],
startMarker: '^[',
endMarker: ']'
});
}

export function toggleListUnordered({state, dispatch}) {
return toggleMarkdownAction({state, dispatch}, {
isInSelection: n => n.name === 'listUnordered',
Expand Down Expand Up @@ -496,6 +505,87 @@ export function toggleListOrdered({state, dispatch}) {
}



function isTaskListItem(node, doc) {
const contentNode = node.firstChild?.nextSibling;
if (node.name !== 'listItem' || node.firstChild?.name !== 'listItemPrefix' || !contentNode) {
return false;
}
const content = doc.slice(contentNode.from, contentNode.to).text[0] || '';
return content.startsWith('[ ]') || content.startsWith('[x]');
}

function isTaskList(node, doc) {
return node.name === 'listUnordered' && getChildren(node).some(c => isTaskListItem(c, doc));
}

export function isTaskListInSelection(state) {
if (!state) {
return false;
}
let tree = syntaxTree(state);
return state.selection.ranges.some(range => getIntersectionNodes(tree, range, n => isTaskList(n, state.doc)).length > 0);
}

export function toggleTaskList({state, dispatch}) {
return toggleMarkdownAction({state, dispatch}, {
isInSelection: n => isTaskList(n, state.doc),
enable: (range, tree) => {
// Add marker to start of each line
// If line is a listItem of an listOrdered: replace the marker
const changes = [];
let newRange = range;
for (const line of linesInRange(state.doc, range)) {
const listItemNumber = getIntersectionNodes(tree, line, n => n.name === 'listItem' && !isTaskListItem(n, state.doc))
.flatMap(n => getChildren(n))
.filter(n => n.name === 'listItemPrefix')
.find(n => intersectsRange(line, n));

if (listItemNumber) {
const change = {from: listItemNumber.from, to: listItemNumber.to, insert: '* [ ] '};
newRange = moveRangeInsert(moveRangeDelete(newRange, range, change), range, change);
changes.push(change);
} else {
const change = {from: line.from, insert: '* [ ] '};
newRange = moveRangeInsert(newRange, range, change);
changes.push(change);
}
}
return {
range: newRange,
changes,
};
},
disable: (range, foundNodes) => {
const removeMarkers = foundNodes
.flatMap(n => getChildren(n)) // Get all listItems
.filter(i => intersectsRange(range, i)) // Get selected listItems
.flatMap(n => getChildren(n))
.map(n => {
if (n.name === 'listItemPrefix') {
return n;
}
const taskListCheck = (state.doc.slice(n.from, n.to).text[0] || '').match(/^(?<check>\[[ |x]\]\s*)/)?.groups.check;
if (taskListCheck) {
// Remove taskListCheck and the following space
return {from: n.from, to: n.from + taskListCheck.length};
}
return null;
})
.filter(n => !!n);
let newRange = range;
const changes = [];
for (const cn of removeMarkers) {
const change = {from: cn.from, to: cn.to};
newRange = moveRangeDelete(newRange, range, change)
changes.push(change);
}
return { range: newRange, changes };
}
});
}


export function toggleLink({state, dispatch}) {
return toggleMarkdownAction({state, dispatch}, {
isInSelection: n => n.name === 'link',
Expand Down
47 changes: 20 additions & 27 deletions packages/markdown/editor/index.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,21 @@
import { EditorState, EditorSelection } from '@codemirror/state';
import { EditorView, ViewUpdate, tooltips, scrollPastEnd, keymap, lineNumbers } from '@codemirror/view';
import { history, historyKeymap, defaultKeymap, indentWithTab, undo, redo, undoDepth, redoDepth } from '@codemirror/commands';
import { forceLinting, setDiagnostics } from '@codemirror/lint';
import { MergeView } from '@codemirror/merge';
import { syntaxHighlighting, indentUnit } from '@codemirror/language';
import { vueLanguage } from '@codemirror/lang-vue';
import { cssLanguage } from '@codemirror/lang-css';
import { markdown } from 'reportcreator-markdown/editor/language';
import { createEditorExtensionToggler } from 'reportcreator-markdown/editor/utils';
import { spellcheck, spellcheckTheme } from 'reportcreator-markdown/editor/spellcheck';
import { highlightTodos } from 'reportcreator-markdown/editor/todos';
import { markdownHighlightStyle, markdownHighlightCodeBlocks } from 'reportcreator-markdown/editor/highlight';
import { toggleStrong, toggleEmphasis, toggleStrikethrough, toggleListUnordered, toggleListOrdered, toggleLink, insertCodeBlock, insertTable, isTypeInSelection, insertNewlineContinueMarkup } from 'reportcreator-markdown/editor/commands';
export { EditorState, EditorSelection } from '@codemirror/state';
export { EditorView, ViewUpdate, tooltips, scrollPastEnd, keymap, lineNumbers } from '@codemirror/view';
export { history, historyKeymap, defaultKeymap, indentWithTab, undo, redo, undoDepth, redoDepth } from '@codemirror/commands';
export { forceLinting, setDiagnostics } from '@codemirror/lint';
export { MergeView } from '@codemirror/merge';
export { syntaxHighlighting, indentUnit } from '@codemirror/language';
export { vueLanguage } from '@codemirror/lang-vue';
export { cssLanguage } from '@codemirror/lang-css';
export { markdown } from 'reportcreator-markdown/editor/language';
export { createEditorExtensionToggler } from 'reportcreator-markdown/editor/utils';
export { spellcheck, spellcheckTheme } from 'reportcreator-markdown/editor/spellcheck';
export { highlightTodos } from 'reportcreator-markdown/editor/todos';
export { markdownHighlightStyle, markdownHighlightCodeBlocks } from 'reportcreator-markdown/editor/highlight';
export {
toggleStrong, toggleEmphasis, toggleStrikethrough, toggleFootnote,
toggleListUnordered, toggleListOrdered, toggleTaskList,
toggleLink, insertCodeBlock, insertTable,
isTypeInSelection, isTaskListInSelection,
insertNewlineContinueMarkup
} from 'reportcreator-markdown/editor/commands';
import 'highlight.js/styles/default.css';

export {
EditorState, EditorView, ViewUpdate, EditorSelection, MergeView,
tooltips, scrollPastEnd, forceLinting, setDiagnostics,
history, historyKeymap, keymap, undo, redo, undoDepth, redoDepth,
vueLanguage, cssLanguage,
createEditorExtensionToggler, spellcheck, spellcheckTheme, highlightTodos,
lineNumbers, indentUnit, defaultKeymap, indentWithTab, markdown,
syntaxHighlighting, markdownHighlightStyle, markdownHighlightCodeBlocks,
toggleStrong, toggleEmphasis, toggleStrikethrough,
toggleListUnordered, toggleListOrdered,
toggleLink, insertCodeBlock, insertTable, isTypeInSelection, insertNewlineContinueMarkup
}
5 changes: 3 additions & 2 deletions packages/markdown/editor/language.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@ const nodeTypes = [
'codeText', 'codeFenced', 'table', 'blockQuote',
'heading1', 'heading2', 'heading3', 'heading4', 'heading5', 'heading6',
'link', 'image', 'resource', 'label', 'labelMarker', 'labelText',
'inlineFootnote', 'textAttributes', 'templateVariable', 'todo',
'inlineFootnote', 'inlineFootnoteMarker', 'inlineFootnoteStartMarker', 'inlineFootnoteEndMarker',
'textAttributes', 'templateVariable', 'todo',
'htmlText', 'htmlFlow',
'data', 'paragraph', 'content', 'document', 'lineEnding',
'listOrdered', 'listUnordered', 'listItem', 'listItemPrefix', 'blockQuotePrefix', 'listItemMarker',
'listOrdered', 'listUnordered', 'listItem', 'listItemPrefix', 'blockQuotePrefix', 'listItemMarker', 'taskListCheck',
];
const nodeSet = new NodeSet([NodeType.none].concat(nodeTypes.map((type, idx) => NodeType.define({id: idx + 1, name: type}))))
.extend(markdownHighlighting)
Expand Down

0 comments on commit 2caa87b

Please sign in to comment.