diff --git a/docs/.vitepress/config.js b/docs/.vitepress/config.js index 8d920ad0..2deaf495 100644 --- a/docs/.vitepress/config.js +++ b/docs/.vitepress/config.js @@ -43,6 +43,7 @@ export default { { text: 'Toggle', link: '/components/toggle' }, { text: 'Promotion', link: '/components/promotion' }, { text: 'Markdown', link: '/components/markdown' }, + { text: 'Markdown Editor', link: '/components/markdown-editor' }, { text: 'Copy Code', link: '/components/copy-code' }, { text: 'Notifications', link: '/components/notifications' }, { text: 'Share Modal', link: '/components/share-modal' }, diff --git a/docs/components/markdown-editor.md b/docs/components/markdown-editor.md new file mode 100644 index 00000000..878b432a --- /dev/null +++ b/docs/components/markdown-editor.md @@ -0,0 +1,36 @@ +# Markdown Editor + + +The Markdown editor allows for easy formatting of Markdown text whether the user is familiar with Markdown or not. It includes standard shortcuts such as `CTRL+B` for bold, `CTRL+I` for italic, and more. + +## Full editor + + + + +```vue + + + +``` + +## Without heading buttons + + + + +```vue + + + +``` diff --git a/lib/assets/icons/bold.svg b/lib/assets/icons/bold.svg new file mode 100644 index 00000000..e6b178fa --- /dev/null +++ b/lib/assets/icons/bold.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/assets/icons/heading-1.svg b/lib/assets/icons/heading-1.svg new file mode 100644 index 00000000..ab433d8d --- /dev/null +++ b/lib/assets/icons/heading-1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/assets/icons/heading-2.svg b/lib/assets/icons/heading-2.svg new file mode 100644 index 00000000..f22c89bd --- /dev/null +++ b/lib/assets/icons/heading-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/assets/icons/heading-3.svg b/lib/assets/icons/heading-3.svg new file mode 100644 index 00000000..383b1e96 --- /dev/null +++ b/lib/assets/icons/heading-3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/assets/icons/italic.svg b/lib/assets/icons/italic.svg new file mode 100644 index 00000000..ec7e4019 --- /dev/null +++ b/lib/assets/icons/italic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/assets/icons/list-bulleted.svg b/lib/assets/icons/list-bulleted.svg new file mode 100644 index 00000000..a41d2a2b --- /dev/null +++ b/lib/assets/icons/list-bulleted.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/assets/icons/list-ordered.svg b/lib/assets/icons/list-ordered.svg new file mode 100644 index 00000000..5deb6ad9 --- /dev/null +++ b/lib/assets/icons/list-ordered.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/assets/icons/redo.svg b/lib/assets/icons/redo.svg new file mode 100644 index 00000000..c6dbd45a --- /dev/null +++ b/lib/assets/icons/redo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/assets/icons/strikethrough.svg b/lib/assets/icons/strikethrough.svg new file mode 100644 index 00000000..96d97812 --- /dev/null +++ b/lib/assets/icons/strikethrough.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/assets/icons/text-quote.svg b/lib/assets/icons/text-quote.svg new file mode 100644 index 00000000..dc44f982 --- /dev/null +++ b/lib/assets/icons/text-quote.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/assets/icons/underline.svg b/lib/assets/icons/underline.svg new file mode 100644 index 00000000..5d56f309 --- /dev/null +++ b/lib/assets/icons/underline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/assets/icons/youtube.svg b/lib/assets/icons/youtube.svg new file mode 100644 index 00000000..5de99f86 --- /dev/null +++ b/lib/assets/icons/youtube.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/assets/styles/classes.scss b/lib/assets/styles/classes.scss index 68ba413b..e6643be7 100644 --- a/lib/assets/styles/classes.scss +++ b/lib/assets/styles/classes.scss @@ -1014,6 +1014,7 @@ a, video { aspect-ratio: 16 / 9; width: 850px; + max-width: 100%; height: auto; } diff --git a/lib/assets/styles/defaults.scss b/lib/assets/styles/defaults.scss index 39864069..37d1b921 100644 --- a/lib/assets/styles/defaults.scss +++ b/lib/assets/styles/defaults.scss @@ -43,9 +43,11 @@ a.uncolored { } input[type='text'], +input[type='url'], input[type='number'], input[type='password'], -textarea { +textarea, +.cm-content { border-radius: var(--radius-md); box-sizing: border-box; // safari iOS rounds inputs by default diff --git a/lib/components/base/MarkdownEditor.vue b/lib/components/base/MarkdownEditor.vue new file mode 100644 index 00000000..629af93c --- /dev/null +++ b/lib/components/base/MarkdownEditor.vue @@ -0,0 +1,643 @@ + + + + + diff --git a/lib/components/index.js b/lib/components/index.js index a9d6a77c..aa0cdb76 100644 --- a/lib/components/index.js +++ b/lib/components/index.js @@ -12,6 +12,7 @@ export { default as DropArea } from './base/DropArea.vue' export { default as DropdownSelect } from './base/DropdownSelect.vue' export { default as EnvironmentIndicator } from './base/EnvironmentIndicator.vue' export { default as FileInput } from './base/FileInput.vue' +export { default as MarkdownEditor } from './base/MarkdownEditor.vue' export { default as Notifications } from './base/Notifications.vue' export { default as OverflowMenu } from './base/OverflowMenu.vue' export { default as Page } from './base/Page.vue' @@ -69,6 +70,7 @@ export { default as PayPalIcon } from '@/assets/external/paypal.svg?component' export { default as RedditIcon } from '@/assets/external/reddit.svg?component' export { default as TwitterIcon } from '@/assets/external/twitter.svg?component' export { default as WindowsIcon } from '@/assets/external/windows.svg?component' +export { default as YouTubeIcon } from '@/assets/icons/youtube.svg?component' // Icons export { default as AlignLeftIcon } from '@/assets/icons/align-left.svg?component' @@ -170,6 +172,7 @@ export { default as TerminalSquareIcon } from '@/assets/icons/terminal-square.sv export { default as TransferIcon } from '@/assets/icons/transfer.svg?component' export { default as TrashIcon } from '@/assets/icons/trash.svg?component' export { default as UndoIcon } from '@/assets/icons/undo.svg?component' +export { default as RedoIcon } from '@/assets/icons/redo.svg?component' export { default as UnknownIcon } from '@/assets/icons/unknown.svg?component' export { default as UnknownDonationIcon } from '@/assets/icons/unknown-donation.svg?component' export { default as UpdatedIcon } from '@/assets/icons/updated.svg?component' @@ -182,3 +185,15 @@ export { default as VersionIcon } from '@/assets/icons/version.svg?component' export { default as WikiIcon } from '@/assets/icons/wiki.svg?component' export { default as XIcon } from '@/assets/icons/x.svg?component' export { default as XCircleIcon } from '@/assets/icons/x-circle.svg?component' + +// Editor Icons +export { default as BoldIcon } from '@/assets/icons/bold.svg?component' +export { default as ItalicIcon } from '@/assets/icons/italic.svg?component' +export { default as UnderlineIcon } from '@/assets/icons/underline.svg?component' +export { default as StrikethroughIcon } from '@/assets/icons/strikethrough.svg?component' +export { default as ListBulletedIcon } from '@/assets/icons/list-bulleted.svg?component' +export { default as ListOrderedIcon } from '@/assets/icons/list-ordered.svg?component' +export { default as TextQuoteIcon } from '@/assets/icons/text-quote.svg?component' +export { default as Heading1Icon } from '@/assets/icons/heading-1.svg?component' +export { default as Heading2Icon } from '@/assets/icons/heading-2.svg?component' +export { default as Heading3Icon } from '@/assets/icons/heading-3.svg?component' diff --git a/lib/components/search/SearchFilter.vue b/lib/components/search/SearchFilter.vue index 77ce4e19..6f39c81c 100644 --- a/lib/components/search/SearchFilter.vue +++ b/lib/components/search/SearchFilter.vue @@ -1,24 +1,25 @@ diff --git a/lib/helpers/codemirror.ts b/lib/helpers/codemirror.ts new file mode 100644 index 00000000..6e5480ea --- /dev/null +++ b/lib/helpers/codemirror.ts @@ -0,0 +1,370 @@ +import { insertNewlineAndIndent } from '@codemirror/commands' +import { deleteMarkupBackward } from '@codemirror/lang-markdown' +import { getIndentation, indentString, syntaxTree } from '@codemirror/language' +import { type EditorState, type Transaction } from '@codemirror/state' +import { type EditorView, type Command, type KeyBinding } from '@codemirror/view' + +const toggleBold: Command = ({ state, dispatch }) => { + return toggleAround(state, dispatch, '**', '**') +} + +const toggleItalic: Command = ({ state, dispatch }) => { + return toggleAround(state, dispatch, '_', '_') +} + +const toggleStrikethrough: Command = ({ state, dispatch }) => { + return toggleAround(state, dispatch, '~~', '~~') +} + +const toggleCodeBlock: Command = ({ state, dispatch }) => { + const lineBreak = state.lineBreak + const codeBlockMark = lineBreak + '```' + lineBreak + return toggleAround(state, dispatch, codeBlockMark, codeBlockMark) +} + +const toggleHeader: Command = ({ state, dispatch }) => { + return toggleLineStart(state, dispatch, '# ') +} + +const toggleHeader2: Command = ({ state, dispatch }) => { + return toggleLineStart(state, dispatch, '## ') +} + +const toggleHeader3: Command = ({ state, dispatch }) => { + return toggleLineStart(state, dispatch, '### ') +} + +const toggleHeader4: Command = ({ state, dispatch }) => { + return toggleLineStart(state, dispatch, '#### ') +} + +const toggleHeader5: Command = ({ state, dispatch }) => { + return toggleLineStart(state, dispatch, '##### ') +} + +const toggleHeader6: Command = ({ state, dispatch }) => { + return toggleLineStart(state, dispatch, '###### ') +} + +const toggleQuote: Command = ({ state, dispatch }) => { + return toggleLineStart(state, dispatch, '> ') +} + +const toggleBulletList: Command = ({ state, dispatch }) => { + return toggleLineStart(state, dispatch, '- ') +} + +const toggleOrderedList: Command = ({ state, dispatch }) => { + return toggleLineStart(state, dispatch, '1. ') +} + +const yankSelection = ({ state }: EditorView): string => { + const { from, to } = state.selection.main + const selectedText = state.doc.sliceString(from, to) + return selectedText +} + +const replaceSelection = ({ state, dispatch }: EditorView, text: string) => { + const { from, to } = state.selection.main + const transaction = state.update({ + changes: { from, to, insert: text }, + selection: { anchor: from + text.length, head: from + text.length }, + }) + dispatch(transaction) + return true +} + +type Dispatch = (tr: Transaction) => void + +const surroundedByText = ( + state: EditorState, + open: string, + close: string +): 'inclusive' | 'exclusive' | 'none' => { + const { from, to } = state.selection.main + + // Check for inclusive surrounding first + const selectedText = state.doc.sliceString(from, to) + if (selectedText.startsWith(open) && selectedText.endsWith(close)) { + return 'inclusive' + } + + // Then check for exclusive surrounding + const beforeText = state.doc.sliceString(Math.max(0, from - open.length), from) + const afterText = state.doc.sliceString(to, to + close.length) + if (beforeText === open && afterText === close) { + return 'exclusive' + } + + // Return 'none' if no surrounding detected + return 'none' +} + +// TODO: Node based toggleAround so that we can support nested delimiters +const toggleAround = ( + state: EditorState, + dispatch: Dispatch, + open: string, + close: string +): boolean => { + const { from, to } = state.selection.main + + const isSurrounded = surroundedByText(state, open, close) + + if (isSurrounded !== 'none') { + const isInclusive = isSurrounded === 'inclusive' + let transaction: Transaction + + if (isInclusive) { + // Remove delimiters on the inside edges of the selected text + transaction = state.update({ + changes: [ + { from, to: from + open.length, insert: '' }, + { from: to - close.length, to, insert: '' }, + ], + }) + } else { + // Remove delimiters on the outside edges of the selected text + transaction = state.update({ + changes: [ + { from: from - open.length, to: from, insert: '' }, + { from: to, to: to + close.length, insert: '' }, + ], + }) + } + + dispatch(transaction) + return true + } + + // Add delimiters around the selected text + const transaction = state.update({ + changes: [ + { from, insert: open }, + { from: to, insert: close }, + ], + selection: { anchor: from + open.length, head: to + open.length }, + }) + + dispatch(transaction) + return true +} + +const toggleLineStart = (state: EditorState, dispatch: Dispatch, text: string): boolean => { + const lines = state.doc.lineAt(state.selection.main.from) + const lineBreak = state.lineBreak + + const range = { + from: lines.from, + to: state.selection.main.to, + } + + const selectedText = state.doc.sliceString(range.from, range.to) + const shouldRemove = selectedText.startsWith(text) + + let transaction: Transaction | undefined + + if (shouldRemove) { + const numOfSelectedLinesThatNeedToBeRemoved = selectedText.split(lineBreak + text).length + const modifiedText = selectedText.substring(text.length).replaceAll(lineBreak + text, lineBreak) + transaction = state.update({ + changes: { from: range.from, to: range.to, insert: modifiedText }, + selection: { + anchor: state.selection.main.from - text.length, + head: state.selection.main.to - text.length * numOfSelectedLinesThatNeedToBeRemoved, + }, + }) + } else { + const modifiedText = text + selectedText.replaceAll(lineBreak, lineBreak + text) + const lengthDiff = modifiedText.length - selectedText.length + transaction = state.update({ + changes: { from: range.from, to: range.to, insert: modifiedText }, + selection: { + anchor: state.selection.main.from + text.length, + head: state.selection.main.to + lengthDiff, + }, + }) + } + + if (!transaction) return false + + dispatch(transaction) + return true +} + +const continueNodeTypes = ['ListItem', 'Blockquote'] +const blackListedNodeTypes = ['CodeBlock'] + +const getListStructure = (state: EditorState, head: number) => { + const tree = syntaxTree(state) + const headNode = tree.resolve(head, -1) + const stack = [] + + let node: typeof headNode.parent = headNode + while (node) { + if (continueNodeTypes.includes(node.name)) { + stack.push(node) + } + if (blackListedNodeTypes.includes(node.name)) { + return null + } + if (node.name === 'Document') { + break + } + + node = node.parent + } + + return stack +} + +const insertNewlineContinueMark: Command = (view): boolean => { + const { state, dispatch } = view + const { + selection: { + main: { head }, + }, + } = state + + // Get the current list structure to examine + const stack = getListStructure(state, head) + if (!stack || stack.length === 0) { + // insert a newline as normal so that mobile works + return insertNewlineAndIndent(view) + } + + const lastNode = stack[stack.length - 1] + + // Get the necessary indentation + const indentation = getIndentation(state, head) + const indentStr = indentation ? indentString(state, indentation) : '' + + // Initialize a transaction variable + let transaction: Transaction | undefined + + const lineContent = state.doc.lineAt(head).text + + // Identify the patterns that should cancel the list continuation + // TODO: Implement Node based cancellation + const cancelPatterns = ['```', '# ', '> '] + + const listMark = lastNode.getChild('ListMark') + if (listMark) { + cancelPatterns.push(state.doc.sliceString(listMark.from, listMark.to) + ' ') + } + + // Skip if current line matches any of the cancel patterns + if (cancelPatterns.includes(lineContent)) { + transaction = createSimpleTransaction(state) + dispatch(transaction) + return true + } + + switch (lastNode.name) { + case 'ListItem': + if (!listMark) return false + transaction = createListTransaction(state, indentStr, listMark.from, listMark.to) + break + case 'Blockquote': + transaction = createBlockquoteTransaction(state, indentStr) + break + } + + if (transaction) { + dispatch(transaction) + return true + } + + return false +} + +// Creates a transaction for a simple line break +const createSimpleTransaction = (state: EditorState) => { + const { + lineBreak, + selection: { + main: { head }, + }, + } = state + const line = state.doc.lineAt(head) + return state.update({ + changes: { + from: line.from, + to: line.to, + insert: lineBreak, + }, + }) +} + +// Creates a transaction for continuing a list item +const createListTransaction = (state: EditorState, indentStr: string, from: number, to: number) => { + const { + lineBreak, + selection: { + main: { head }, + }, + } = state + const listMarkContent = state.doc.sliceString(from, to) + const insert = `${lineBreak}${indentStr}${incrementMark(listMarkContent)} ` + return state.update({ + changes: { from: head, insert }, + selection: { anchor: head + insert.length, head: head + insert.length }, + }) +} + +// Creates a transaction for continuing a blockquote +const createBlockquoteTransaction = (state: EditorState, indentStr: string) => { + const { + lineBreak, + selection: { + main: { head }, + }, + } = state + const insert = `${lineBreak}${indentStr}> ` + return state.update({ + changes: { from: head, insert }, + selection: { anchor: head + insert.length, head: head + insert.length }, + }) +} + +const incrementMark = (mark: string): string => { + const numberedListRegex = /^(\d+)\.$/ + const match = numberedListRegex.exec(mark) + if (match) { + const number = parseInt(match[1]) + return (number + 1).toString() + '.' + } + return mark +} + +const commands = { + toggleBold, + toggleItalic, + toggleStrikethrough, + toggleCodeBlock, + toggleHeader, + toggleHeader2, + toggleHeader3, + toggleHeader4, + toggleHeader5, + toggleHeader6, + toggleQuote, + toggleBulletList, + toggleOrderedList, + insertNewlineContinueMark, + + // Utility + yankSelection, + replaceSelection, +} + +export const markdownCommands = commands +export const modrinthMarkdownEditorKeymap: KeyBinding[] = [ + { key: 'Enter', run: insertNewlineContinueMark }, + { key: 'Backspace', run: deleteMarkupBackward }, + { key: 'Mod-b', run: toggleBold }, + { key: 'Mod-i', run: toggleItalic }, + { key: 'Mod-e', run: toggleCodeBlock }, + { key: 'Mod-s', run: toggleStrikethrough }, + { key: 'Mod-Shift-.', run: toggleQuote }, +] diff --git a/package.json b/package.json index 7b2c3f06..7714c5ea 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,11 @@ "docs:preview": "vitepress preview docs" }, "dependencies": { + "@codemirror/commands": "^6.3.0", + "@codemirror/lang-markdown": "^6.2.2", + "@codemirror/language": "^6.9.1", + "@codemirror/state": "^6.3.0", + "@codemirror/view": "^6.21.3", "chart.js": "^4.3.3", "dayjs": "^1.11.7", "floating-vue": "^2.0.0-beta.20", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0db35ca5..71233945 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,6 +1,25 @@ lockfileVersion: '6.0' +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + dependencies: + '@codemirror/commands': + specifier: ^6.3.0 + version: 6.3.0 + '@codemirror/lang-markdown': + specifier: ^6.2.2 + version: 6.2.2 + '@codemirror/language': + specifier: ^6.9.1 + version: 6.9.1 + '@codemirror/state': + specifier: ^6.3.0 + version: 6.3.0 + '@codemirror/view': + specifier: ^6.21.3 + version: 6.21.3 chart.js: specifier: ^4.3.3 version: 4.3.3 @@ -233,6 +252,110 @@ packages: '@babel/helper-validator-identifier': 7.19.1 to-fast-properties: 2.0.0 + /@codemirror/autocomplete@6.10.2(@codemirror/language@6.9.1)(@codemirror/state@6.3.0)(@codemirror/view@6.21.3)(@lezer/common@1.1.0): + resolution: {integrity: sha512-3dCL7b0j2GdtZzWN5j7HDpRAJ26ip07R4NGYz7QYthIYMiX8I4E4TNrYcdTayPJGeVQtd/xe7lWU4XL7THFb/w==} + peerDependencies: + '@codemirror/language': ^6.0.0 + '@codemirror/state': ^6.0.0 + '@codemirror/view': ^6.0.0 + '@lezer/common': ^1.0.0 + dependencies: + '@codemirror/language': 6.9.1 + '@codemirror/state': 6.3.0 + '@codemirror/view': 6.21.3 + '@lezer/common': 1.1.0 + dev: false + + /@codemirror/commands@6.3.0: + resolution: {integrity: sha512-tFfcxRIlOWiQDFhjBSWJ10MxcvbCIsRr6V64SgrcaY0MwNk32cUOcCuNlWo8VjV4qRQCgNgUAnIeo0svkk4R5Q==} + dependencies: + '@codemirror/language': 6.9.1 + '@codemirror/state': 6.3.0 + '@codemirror/view': 6.21.3 + '@lezer/common': 1.1.0 + dev: false + + /@codemirror/lang-css@6.2.1(@codemirror/view@6.21.3): + resolution: {integrity: sha512-/UNWDNV5Viwi/1lpr/dIXJNWiwDxpw13I4pTUAsNxZdg6E0mI2kTQb0P2iHczg1Tu+H4EBgJR+hYhKiHKko7qg==} + dependencies: + '@codemirror/autocomplete': 6.10.2(@codemirror/language@6.9.1)(@codemirror/state@6.3.0)(@codemirror/view@6.21.3)(@lezer/common@1.1.0) + '@codemirror/language': 6.9.1 + '@codemirror/state': 6.3.0 + '@lezer/common': 1.1.0 + '@lezer/css': 1.1.3 + transitivePeerDependencies: + - '@codemirror/view' + dev: false + + /@codemirror/lang-html@6.4.6: + resolution: {integrity: sha512-E4C8CVupBksXvgLSme/zv31x91g06eZHSph7NczVxZW+/K+3XgJGWNT//2WLzaKSBoxpAjaOi5ZnPU1SHhjh3A==} + dependencies: + '@codemirror/autocomplete': 6.10.2(@codemirror/language@6.9.1)(@codemirror/state@6.3.0)(@codemirror/view@6.21.3)(@lezer/common@1.1.0) + '@codemirror/lang-css': 6.2.1(@codemirror/view@6.21.3) + '@codemirror/lang-javascript': 6.2.1 + '@codemirror/language': 6.9.1 + '@codemirror/state': 6.3.0 + '@codemirror/view': 6.21.3 + '@lezer/common': 1.1.0 + '@lezer/css': 1.1.3 + '@lezer/html': 1.3.6 + dev: false + + /@codemirror/lang-javascript@6.2.1: + resolution: {integrity: sha512-jlFOXTejVyiQCW3EQwvKH0m99bUYIw40oPmFjSX2VS78yzfe0HELZ+NEo9Yfo1MkGRpGlj3Gnu4rdxV1EnAs5A==} + dependencies: + '@codemirror/autocomplete': 6.10.2(@codemirror/language@6.9.1)(@codemirror/state@6.3.0)(@codemirror/view@6.21.3)(@lezer/common@1.1.0) + '@codemirror/language': 6.9.1 + '@codemirror/lint': 6.4.2 + '@codemirror/state': 6.3.0 + '@codemirror/view': 6.21.3 + '@lezer/common': 1.1.0 + '@lezer/javascript': 1.4.8 + dev: false + + /@codemirror/lang-markdown@6.2.2: + resolution: {integrity: sha512-wmwM9Y5n/e4ndU51KcYDaQnb9goYdhXjU71dDW9goOc1VUTIPph6WKAPdJ6BzC0ZFy+UTsDwTXGWSP370RH69Q==} + dependencies: + '@codemirror/autocomplete': 6.10.2(@codemirror/language@6.9.1)(@codemirror/state@6.3.0)(@codemirror/view@6.21.3)(@lezer/common@1.1.0) + '@codemirror/lang-html': 6.4.6 + '@codemirror/language': 6.9.1 + '@codemirror/state': 6.3.0 + '@codemirror/view': 6.21.3 + '@lezer/common': 1.1.0 + '@lezer/markdown': 1.1.0 + dev: false + + /@codemirror/language@6.9.1: + resolution: {integrity: sha512-lWRP3Y9IUdOms6DXuBpoWwjkR7yRmnS0hKYCbSfPz9v6Em1A1UCRujAkDiCrdYfs1Z0Eu4dGtwovNPStIfkgNA==} + dependencies: + '@codemirror/state': 6.3.0 + '@codemirror/view': 6.21.3 + '@lezer/common': 1.1.0 + '@lezer/highlight': 1.1.6 + '@lezer/lr': 1.3.13 + style-mod: 4.1.0 + dev: false + + /@codemirror/lint@6.4.2: + resolution: {integrity: sha512-wzRkluWb1ptPKdzlsrbwwjYCPLgzU6N88YBAmlZi8WFyuiEduSd05MnJYNogzyc8rPK7pj6m95ptUApc8sHKVA==} + dependencies: + '@codemirror/state': 6.3.0 + '@codemirror/view': 6.21.3 + crelt: 1.0.6 + dev: false + + /@codemirror/state@6.3.0: + resolution: {integrity: sha512-5fIS19U46PEqczbBL6gBAtju9MFDT9TjIC/q2MYblHCEKiU8jhV3cRFhvQu5tQvbtxc5KLWxSnzMNh3ZqeaXVg==} + dev: false + + /@codemirror/view@6.21.3: + resolution: {integrity: sha512-8l1aSQ6MygzL4Nx7GVYhucSXvW4jQd0F6Zm3v9Dg+6nZEfwzJVqi4C2zHfDljID+73gsQrWp9TgHc81xU15O4A==} + dependencies: + '@codemirror/state': 6.3.0 + style-mod: 4.1.0 + w3c-keyname: 2.2.8 + dev: false + /@docsearch/css@3.4.0: resolution: {integrity: sha512-Hg8Xfma+rFwRi6Y/pfei4FJoQ1hdVURmmNs/XPoMTCPAImU+d5yxj+M+qdLtNjWRpfWziU4dQcqY94xgFBn2dg==} dev: true @@ -580,6 +703,51 @@ packages: resolution: {integrity: sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==} dev: false + /@lezer/common@1.1.0: + resolution: {integrity: sha512-XPIN3cYDXsoJI/oDWoR2tD++juVrhgIago9xyKhZ7IhGlzdDM9QgC8D8saKNCz5pindGcznFr2HBSsEQSWnSjw==} + dev: false + + /@lezer/css@1.1.3: + resolution: {integrity: sha512-SjSM4pkQnQdJDVc80LYzEaMiNy9txsFbI7HsMgeVF28NdLaAdHNtQ+kB/QqDUzRBV/75NTXjJ/R5IdC8QQGxMg==} + dependencies: + '@lezer/highlight': 1.1.6 + '@lezer/lr': 1.3.13 + dev: false + + /@lezer/highlight@1.1.6: + resolution: {integrity: sha512-cmSJYa2us+r3SePpRCjN5ymCqCPv+zyXmDl0ciWtVaNiORT/MxM7ZgOMQZADD0o51qOaOg24qc/zBViOIwAjJg==} + dependencies: + '@lezer/common': 1.1.0 + dev: false + + /@lezer/html@1.3.6: + resolution: {integrity: sha512-Kk9HJARZTc0bAnMQUqbtuhFVsB4AnteR2BFUWfZV7L/x1H0aAKz6YabrfJ2gk/BEgjh9L3hg5O4y2IDZRBdzuQ==} + dependencies: + '@lezer/common': 1.1.0 + '@lezer/highlight': 1.1.6 + '@lezer/lr': 1.3.13 + dev: false + + /@lezer/javascript@1.4.8: + resolution: {integrity: sha512-QRmw/5xrcyRLyWr3JT3KCzn2XZr5NYNqQMGsqnYy+FghbQn9DZPuj6JDkE6uSXvfMLpdapu8KBIaeoJFaR4QVw==} + dependencies: + '@lezer/highlight': 1.1.6 + '@lezer/lr': 1.3.13 + dev: false + + /@lezer/lr@1.3.13: + resolution: {integrity: sha512-RLAbau/4uSzKgIKj96mI5WUtG1qtiR0Frn0Ei9zhPj8YOkHM+1Bb8SgdVvmR/aWJCFIzjo2KFnDiRZ75Xf5NdQ==} + dependencies: + '@lezer/common': 1.1.0 + dev: false + + /@lezer/markdown@1.1.0: + resolution: {integrity: sha512-JYOI6Lkqbl83semCANkO3CKbKc0pONwinyagBufWBm+k4yhIcqfCF8B8fpEpvJLmIy7CAfwiq7dQ/PzUZA340g==} + dependencies: + '@lezer/common': 1.1.0 + '@lezer/highlight': 1.1.6 + dev: false + /@microsoft/api-extractor-model@7.28.2: resolution: {integrity: sha512-vkojrM2fo3q4n4oPh4uUZdjJ2DxQ2+RnDQL/xhTWSRUNPF6P4QyrvY357HBxbnltKcYu+nNNolVqc6TIGQ73Ig==} dependencies: @@ -1423,6 +1591,10 @@ packages: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true + /crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + dev: false + /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -2589,6 +2761,10 @@ packages: engines: {node: '>=8'} dev: true + /style-mod@4.1.0: + resolution: {integrity: sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA==} + dev: false + /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -2971,6 +3147,10 @@ packages: '@vue/server-renderer': 3.3.4(vue@3.3.4) '@vue/shared': 3.3.4 + /w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + dev: false + /watchpack@2.4.0: resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==} engines: {node: '>=10.13.0'} diff --git a/tsconfig.json b/tsconfig.json index 517d8284..ab504927 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,7 +19,10 @@ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "paths": { + "@/*": ["./lib/*"] + } }, "include": ["lib/**/*.js", "lib/**/*.ts", "lib/**/*.d.ts", "lib/**/*.tsx", "lib/**/*.vue"], "references": [{ "path": "./tsconfig.node.json" }]