From 485822e6fefb2f26da0cdb116d9292dfa17aa5a3 Mon Sep 17 00:00:00 2001 From: purocean Date: Fri, 14 Jun 2024 20:07:06 +0800 Subject: [PATCH] feat: add support for Wiki Links in Markdown syntax This commit modifies the Markdown syntax in the editor to support Wiki Links. It adds a new token pattern for [[...]] syntax and updates the completion provider to handle Wiki Link contexts. Now, when a user types [[, the editor will suggest Wiki Link completions. The changes include: - Adding a new token pattern for [[...]] syntax - Updating the completion provider to handle Wiki Link contexts These modifications enhance the editing experience by allowing users to easily create Wiki Links within their Markdown documents. --- src/renderer/plugins/editor-md-syntax.ts | 2 ++ .../plugins/editor-path-completion.ts | 32 +++++++++++++++++-- src/renderer/services/markdown.ts | 31 ++++++++++++++++++ 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/src/renderer/plugins/editor-md-syntax.ts b/src/renderer/plugins/editor-md-syntax.ts index 7342126ce..7490926e2 100644 --- a/src/renderer/plugins/editor-md-syntax.ts +++ b/src/renderer/plugins/editor-md-syntax.ts @@ -199,6 +199,7 @@ export default { { label: '/ __ Bold', insertText: '__$1__' }, { label: '/ ~~ Delete', insertText: '~~$1~~' }, { label: '/ == Mark', insertText: '==$1==' }, + { label: '/ [[]] Wiki Link', insertText: '[[$1]]' }, { label: '/ ``` Fence', insertText: '```$1\n$2\n```\n' }, { label: '/ ||| Table', insertText: '| ${1:TH} | ${2:TH} | ${3:TH} |\n| -- | -- | -- |\n| TD | TD | TD |' }, { label: '/ ||| Small Table', insertText: '| ${1:TH} | ${2:TH} | ${3:TH} |\n| -- | -- | -- |\n| TD | TD | TD |\n{.small}' }, @@ -211,6 +212,7 @@ export default { ctx.editor.tapMarkdownMonarchLanguage(mdLanguage => { mdLanguage.tokenizer.root.unshift( [/==\S.*\S?==/, 'keyword'], + [/\[\[[^[\]]+\]\]/, 'string'], [/~\S[^~]*\S?~/, 'string'], [/\^\S[^^]*\S?\^/, 'string'], ) diff --git a/src/renderer/plugins/editor-path-completion.ts b/src/renderer/plugins/editor-path-completion.ts index 68e2f871a..6297282d2 100644 --- a/src/renderer/plugins/editor-path-completion.ts +++ b/src/renderer/plugins/editor-path-completion.ts @@ -9,6 +9,8 @@ import type Token from 'markdown-it/lib/token' enum CompletionContextKind { Link, // [...](|) + WikiLink, // [[|]] + ReferenceLink, // [...][|] LinkDefinition, // []: | // TODO: not implemented @@ -89,6 +91,7 @@ class CompletionProvider implements Monaco.languages.CompletionItemProvider { } case CompletionContextKind.LinkDefinition: + case CompletionContextKind.WikiLink: case CompletionContextKind.Link: { const items: Monaco.languages.CompletionItem[] = [] @@ -151,6 +154,9 @@ class CompletionProvider implements Monaco.languages.CompletionItemProvider { /// [...](...| private readonly linkStartPattern = /\[([^\]]*?)\]\(\s*([^\s()]*)$/ + /// [[...| + private readonly wikiLinkStartPattern = /\[\[\s*([^\s[\]]*)$/ + /// [...| private readonly referenceLinkStartPattern = /\[\s*([^\s[\]]*)$/ @@ -186,6 +192,19 @@ class CompletionProvider implements Monaco.languages.CompletionItemProvider { } } + const wikiLinkPrefixMatch = linePrefixText.match(this.wikiLinkStartPattern) + if (wikiLinkPrefixMatch) { + const prefix = wikiLinkPrefixMatch[1] + const suffix = lineSuffixText.match(/^[^\]]*/) + return { + kind: CompletionContextKind.WikiLink, + linkPrefix: prefix, + linkTextStartPosition: position.delta(0, -prefix.length), + linkSuffix: suffix ? suffix[0] : '', + anchorInfo: this.getAnchorContext(prefix), + } + } + const definitionLinkPrefixMatch = linePrefixText.match(this.definitionPattern) if (definitionLinkPrefixMatch) { const prefix = definitionLinkPrefixMatch[1] @@ -230,14 +249,21 @@ class CompletionProvider implements Monaco.languages.CompletionItemProvider { if (!anchorMatch) { return undefined } + + let beforeAnchor = anchorMatch[1] + + if (anchorMatch[1] && !this.ctx.utils.path.extname(beforeAnchor)) { + beforeAnchor += '.md' + } + return { - beforeAnchor: anchorMatch[1], + beforeAnchor, anchorPrefix: anchorMatch[2], } } private async * providePathSuggestions (position: Monaco.Position, context: CompletionContext): AsyncIterable { - const valueBeforeLastSlash = context.linkPrefix.substring(0, context.linkPrefix.lastIndexOf('/') + 1) || '.' // keep the last slash + const valueBeforeLastSlash = context.linkPrefix.substring(0, context.linkPrefix.lastIndexOf('/') + 1) // keep the last slash const currentFile = this.ctx.store.state.currentFile if (!currentFile) { @@ -246,7 +272,7 @@ class CompletionProvider implements Monaco.languages.CompletionItemProvider { const parentDir = this.ctx.utils.path.resolve( this.ctx.utils.path.dirname(currentFile.path), - valueBeforeLastSlash + valueBeforeLastSlash || '.' ) const pathSegmentStart = position.delta(0, valueBeforeLastSlash.length - context.linkPrefix.length) diff --git a/src/renderer/services/markdown.ts b/src/renderer/services/markdown.ts index 759159421..6b9fb242b 100644 --- a/src/renderer/services/markdown.ts +++ b/src/renderer/services/markdown.ts @@ -73,3 +73,34 @@ markdown.core.ruler.after('normalize', 'after_normalize', state => { state.env.tokens = state.tokens return true }) + +markdown.linkify.add('[[', { + validate: /^\s*([^[\]]+)\s*\]\]/, + normalize: (match) => { + const parts = match.raw.slice(2, -2).split('|') + const url = parts[0].trim() + + // external link + if (/^[a-zA-Z]{1,8}:\/\/.*/.test(url)) { + match.url = url + match.text = parts[1] || url + return + } + + const [path, hash] = url.split('#') + const hashStr = hash ? `#${hash}` : '' + + const name = parts[1] || (path.split('/').pop() + hashStr) + match.text = name || url + + // has extension name + if (/\.[^/]+$/.test(path)) { + match.url = url + } else if (path) { + match.url = `${path}.md${hashStr}` + } else { + match.url = hashStr + match.text = name || hash || url + } + } +})