From 5db7f564dbca6f726db2a5c6f91eca91dd78e950 Mon Sep 17 00:00:00 2001 From: purocean Date: Mon, 17 Jun 2024 19:20:30 +0800 Subject: [PATCH] feat: optimize wiki links use experience --- help/FEATURES.md | 1 + help/FEATURES_ZH-CN.md | 1 + src/renderer/others/setting-schema.ts | 8 ++ .../plugins/editor-path-completion.ts | 14 +++- src/renderer/plugins/markdown-link.ts | 76 +++++++++++++++++-- src/renderer/plugins/markdown-wiki-links.ts | 4 +- src/renderer/services/markdown.ts | 1 + src/renderer/support/args.ts | 1 + src/renderer/types.ts | 1 + src/share/i18n/languages/en.ts | 1 + src/share/i18n/languages/zh-CN.ts | 1 + src/share/i18n/languages/zh-TW.ts | 1 + 12 files changed, 100 insertions(+), 10 deletions(-) diff --git a/help/FEATURES.md b/help/FEATURES.md index b6219c590..03c78b9dd 100644 --- a/help/FEATURES.md +++ b/help/FEATURES.md @@ -69,6 +69,7 @@ Type '/' in the editor to get prompts *[HTML]: Hyper Text Markup Language *[W3C]: World Wide Web Consortium The HTML specification is maintained by the W3C. ++ Wiki Link: Supports using `[[filename#anchor|display text]]` syntax to link documents, such as [[README#Highlights|Highlights]] ## Github Alerts diff --git a/help/FEATURES_ZH-CN.md b/help/FEATURES_ZH-CN.md index 82248f619..7b3e7190a 100644 --- a/help/FEATURES_ZH-CN.md +++ b/help/FEATURES_ZH-CN.md @@ -70,6 +70,7 @@ define: *[HTML]: Hyper Text Markup Language *[W3C]: World Wide Web Consortium The HTML specification is maintained by the W3C. ++ Wiki 链接:支持使用 `[[文件名#锚点|显示文本]]` 语法来链接文档,如 [[README#Highlights|特色功能]] ## Github Alerts diff --git a/src/renderer/others/setting-schema.ts b/src/renderer/others/setting-schema.ts index 90fcd4311..f0ece2260 100644 --- a/src/renderer/others/setting-schema.ts +++ b/src/renderer/others/setting-schema.ts @@ -280,6 +280,14 @@ const schema: SettingSchema = ({ group: 'render', required: true, }, + 'render.md-wiki-links': { + defaultValue: true, + title: 'T_setting-panel.schema.render.md-wiki-links', + type: 'boolean', + format: 'checkbox', + group: 'render', + required: true, + }, 'render.md-typographer': { defaultValue: false, title: 'T_setting-panel.schema.render.md-typographer', diff --git a/src/renderer/plugins/editor-path-completion.ts b/src/renderer/plugins/editor-path-completion.ts index 6297282d2..c99deac4c 100644 --- a/src/renderer/plugins/editor-path-completion.ts +++ b/src/renderer/plugins/editor-path-completion.ts @@ -155,7 +155,7 @@ class CompletionProvider implements Monaco.languages.CompletionItemProvider { private readonly linkStartPattern = /\[([^\]]*?)\]\(\s*([^\s()]*)$/ /// [[...| - private readonly wikiLinkStartPattern = /\[\[\s*([^\s[\]]*)$/ + private readonly wikiLinkStartPattern = /\[\[\s*([^[\]]*)$/ /// [...| private readonly referenceLinkStartPattern = /\[\s*([^\s[\]]*)$/ @@ -297,10 +297,18 @@ class CompletionProvider implements Monaco.languages.CompletionItemProvider { for (const item of items) { i++ const isDir = item.type === 'dir' - const label = isDir ? item.name + '/' : item.name + let label = isDir ? item.name + '/' : item.name + let insertText = this.ctx.utils.encodeMarkdownLink(label) + + // Remove extension for wiki links + if (context.kind === CompletionContextKind.WikiLink) { + label = label.replace(/\.(md|markdown)$/, '') + insertText = label.replaceAll(']', ']').replaceAll('[', '[') + } + yield { label, - insertText: this.ctx.utils.encodeMarkdownLink(label), + insertText, kind: isDir ? this.monaco.languages.CompletionItemKind.Folder : this.monaco.languages.CompletionItemKind.File, range: { insert: insertRange, diff --git a/src/renderer/plugins/markdown-link.ts b/src/renderer/plugins/markdown-link.ts index d86f83b81..624b7c422 100644 --- a/src/renderer/plugins/markdown-link.ts +++ b/src/renderer/plugins/markdown-link.ts @@ -6,12 +6,14 @@ import { removeQuery, sleep } from '@fe/utils' import { isElectron, isWindows } from '@fe/support/env' import { useToast } from '@fe/support/ui/toast' import { DOM_ATTR_NAME, DOM_CLASS_NAME } from '@fe/support/args' -import { basename, dirname, join, resolve } from '@fe/utils/path' +import { basename, dirname, join, normalizeSep, resolve } from '@fe/utils/path' import { switchDoc } from '@fe/services/document' import { getAttachmentURL, getRepo, openExternal, openPath } from '@fe/services/base' import { getRenderIframe } from '@fe/services/view' import { getAllCustomEditors } from '@fe/services/editor' +import { fetchTree } from '@fe/support/api' import type { Doc } from '@share/types' +import type { Components } from '@fe/types' async function getElement (id: string) { id = id.replaceAll('%28', '(').replaceAll('%29', ')') @@ -28,6 +30,55 @@ async function getElement (id: string) { return _find(id) || _find(id.toUpperCase()) } +async function getFirstMatchPath (repo: string, dir: string, path: string) { + if (path.includes('/')) { + return path + } + + const findInDir = (items: Components.Tree.Node[]): string | null => { + for (const item of items) { + const p = normalizeSep(item.path) + if ( + item.type === 'file' && + (p === normalizeSep(join(dir, path)) || + p === normalizeSep(join(dir, `${path}.md`))) + ) { + return item.path + } + + if (item.children) { + const found = findInDir(item.children) + if (found) { + return found + } + } + } + + return null + } + + const findByName = (items: Components.Tree.Node[]): string | null => { + for (const item of items) { + if (item.type === 'file' && (item.name === path || item.name === `${path}.md`)) { + return item.path + } + + if (item.children) { + const found = findByName(item.children) + if (found) { + return found + } + } + } + + return null + } + + const tree = await fetchTree(repo, { by: 'mtime', order: 'desc' }) + + return findInDir(tree) || findByName(tree) +} + function getAnchorElement (target: HTMLElement) { let cur: HTMLElement | null = target while (cur && cur.tagName !== 'A' && cur.tagName !== 'ARTICLE') { @@ -92,18 +143,33 @@ function handleLink (link: HTMLAnchorElement): boolean { const tmp = decodeURI(href).split('#') - let path = tmp[0] - if (!path.startsWith('/')) { // to absolute path - path = join(dirname(filePath || ''), path) + const _switchDoc = async () => { + let path = normalizeSep(tmp[0]) + + const dir = dirname(filePath || '') + + // wiki link + if (link.getAttribute(DOM_ATTR_NAME.WIKI_LINK)) { + path = (await getFirstMatchPath(fileRepo, dir, path)) || path + } + + if (!path.startsWith('/')) { // to absolute path + path = join(dir, path) + } + + const file: Doc = { path, type: 'file', name: basename(path), repo: fileRepo } + + return switchDoc(file) } + const path = normalizeSep(tmp[0]) const file: Doc = { path, type: 'file', name: basename(path), repo: fileRepo } const isMarkdownFile = /(\.md$|\.md#)/.test(href) const supportOpenDirectly = isMarkdownFile || getAllCustomEditors().some(x => x.when?.({ doc: file })) if (supportOpenDirectly) { - switchDoc(file).then(async () => { + _switchDoc().then(async () => { const hash = tmp.slice(1).join('#') // jump anchor if (hash) { diff --git a/src/renderer/plugins/markdown-wiki-links.ts b/src/renderer/plugins/markdown-wiki-links.ts index f859a1dd7..203bc416d 100644 --- a/src/renderer/plugins/markdown-wiki-links.ts +++ b/src/renderer/plugins/markdown-wiki-links.ts @@ -1,4 +1,4 @@ -import { Plugin } from '@fe/context' +import ctx, { Plugin } from '@fe/context' import type StateInline from 'markdown-it/lib/rules_inline/state_inline' const reMatch = /^\s*([^[#|]*)?(?:#([^|]*))?(?:\|([^\]]*))?\s*$/ @@ -46,7 +46,7 @@ function wikiLinks (state: StateInline, silent?: boolean) { } if (!silent) { - state.push('link_open', 'a', 1).attrs = [['href', url]] + state.push('link_open', 'a', 1).attrs = [['href', url], [ctx.args.DOM_ATTR_NAME.WIKI_LINK, 'true']] state.push('text', '', 0).content = text state.push('link_close', 'a', -1) } diff --git a/src/renderer/services/markdown.ts b/src/renderer/services/markdown.ts index 759159421..3524d5dc3 100644 --- a/src/renderer/services/markdown.ts +++ b/src/renderer/services/markdown.ts @@ -36,6 +36,7 @@ markdown.render = (src: string, env?: any) => { ;(getSetting('render.md-sup', true) ? enabledRules : disabledRules).push('sup') ;(getSetting('render.md-sub', true) ? enabledRules : disabledRules).push('sub') + ;(getSetting('render.md-wiki-links', true) ? enabledRules : disabledRules).push('wiki-links') markdown.enable(enabledRules, true) markdown.disable(disabledRules, true) diff --git a/src/renderer/support/args.ts b/src/renderer/support/args.ts index 143f46922..1c6582ec2 100644 --- a/src/renderer/support/args.ts +++ b/src/renderer/support/args.ts @@ -38,6 +38,7 @@ export const DOM_ATTR_NAME = { ONLY_CHILD: 'auto-center', TOKEN_IDX: 'data-token-idx', DISPLAY_NONE: 'display-none', + WIKI_LINK: 'wiki-link', } export const DOM_CLASS_NAME = { diff --git a/src/renderer/types.ts b/src/renderer/types.ts index f56e01021..aa47dfa64 100644 --- a/src/renderer/types.ts +++ b/src/renderer/types.ts @@ -326,6 +326,7 @@ export interface BuildInSettings { 'render.md-html': boolean, 'render.md-breaks': boolean, 'render.md-linkify': boolean, + 'render.md-wiki-links': boolean, 'render.md-typographer': boolean, 'render.md-emoji': boolean, 'render.md-sub': boolean, diff --git a/src/share/i18n/languages/en.ts b/src/share/i18n/languages/en.ts index 4a33ae2c9..fe2bf5e68 100644 --- a/src/share/i18n/languages/en.ts +++ b/src/share/i18n/languages/en.ts @@ -395,6 +395,7 @@ const data = { 'md-html': 'Enable HTML', 'md-breaks': 'Convert \\n to <br>', 'md-linkify': 'Auto convert URL-like text to links', + 'md-wiki-links': 'Enable Wiki Links - [[link]]', 'md-typographer': 'Enable some language-neutral replacement + quotes beautification', 'md-sup': 'Enable sup syntax: 29^th^', 'md-sub': 'Enable sub syntax: H~2~O', diff --git a/src/share/i18n/languages/zh-CN.ts b/src/share/i18n/languages/zh-CN.ts index ed62e665f..be7d35780 100644 --- a/src/share/i18n/languages/zh-CN.ts +++ b/src/share/i18n/languages/zh-CN.ts @@ -386,6 +386,7 @@ const data: BaseLanguage = { 'md-html': '启用 HTML', 'md-breaks': '将 \\n 转换为 <br>', 'md-linkify': '自动将类似 URL 的文本转换为链接', + 'md-wiki-links': '启用 Wiki 链接 - [[link]]', 'md-typographer': '启用排版美化,如 (c) -> ©', 'md-sup': '启用上标语法: 29^th^', 'md-sub': '启用下标语法: H~2~O', diff --git a/src/share/i18n/languages/zh-TW.ts b/src/share/i18n/languages/zh-TW.ts index f6ccdf151..35fee4765 100644 --- a/src/share/i18n/languages/zh-TW.ts +++ b/src/share/i18n/languages/zh-TW.ts @@ -386,6 +386,7 @@ const data: BaseLanguage = { 'md-html': '啟用 HTML', 'md-breaks': '將 \\n 轉換為 <br>', 'md-linkify': '自動將類似 URL 的文字轉換為連結', + 'md-wiki-links': '啟用 Wiki 連結 - [[link]]', 'md-typographer': '啟用排版美化,如 (c) -> ©', 'md-sup': '啟用上標語法: 29^th^', 'md-sub': '啟用下標語法: H~2~O',