From 3e72472a4df46f378a5422c1761d39e9652d9897 Mon Sep 17 00:00:00 2001 From: Moy Date: Wed, 10 Jun 2026 12:38:39 +0800 Subject: [PATCH] feat: allow built-in matcher reordering --- src/copyMatcher.ts | 41 +++++++++++++++++++++++-------- src/i18n.ts | 18 +++++++++++++- src/main.ts | 3 ++- src/settingTab.ts | 61 +++++++++++++++++++++++++++++++++++++++++++--- src/type.ts | 13 +++++++++- 5 files changed, 120 insertions(+), 16 deletions(-) diff --git a/src/copyMatcher.ts b/src/copyMatcher.ts index 1470b6e..4977f22 100644 --- a/src/copyMatcher.ts +++ b/src/copyMatcher.ts @@ -1,13 +1,14 @@ -import { ContextType, EasyCopySettings } from './type'; +import { BuiltinCopyMatcherId, ContextType, EasyCopySettings } from './type'; -export type BuiltinCopyMatcherId = - | 'bold' - | 'italic' - | 'highlight' - | 'strikethrough' - | 'inline-code' - | 'inline-latex' - | 'wiki-link'; +export const DEFAULT_BUILTIN_COPY_MATCHER_IDS: BuiltinCopyMatcherId[] = [ + 'bold', + 'italic', + 'highlight', + 'strikethrough', + 'inline-code', + 'inline-latex', + 'wiki-link', +]; export interface CopyMatcher { id: BuiltinCopyMatcherId; @@ -16,6 +17,23 @@ export interface CopyMatcher { enabled: boolean; } +export function normalizeBuiltinMatcherOrder(order: readonly string[] | undefined): BuiltinCopyMatcherId[] { + const knownIds = new Set(DEFAULT_BUILTIN_COPY_MATCHER_IDS); + const orderedIds: BuiltinCopyMatcherId[] = []; + + for (const id of order ?? []) { + if (knownIds.has(id) && !orderedIds.includes(id as BuiltinCopyMatcherId)) { + orderedIds.push(id as BuiltinCopyMatcherId); + } + } + + for (const id of DEFAULT_BUILTIN_COPY_MATCHER_IDS) { + if (!orderedIds.includes(id)) orderedIds.push(id); + } + + return orderedIds; +} + export function buildBuiltinCopyMatchers(settings: EasyCopySettings, isIosApp: boolean): CopyMatcher[] { // iOS 16.4 之前不支持后视(Lookbehinds),但支持前视(Lookaheads) // 所以针对 iOS 平台使用只带前视的正则表达式,其他平台使用完整版本 @@ -25,7 +43,7 @@ export function buildBuiltinCopyMatchers(settings: EasyCopySettings, isIosApp: b const boldRegex = /(?:\*\*([^*]+)\*\*|__([^_]+)__)/g; - return [ + const matchers: CopyMatcher[] = [ { id: 'bold', type: ContextType.BOLD, regex: boldRegex, enabled: !settings.customizeTargets || settings.enableBold }, { id: 'italic', type: ContextType.ITALIC, regex: italicRegex, enabled: !settings.customizeTargets || settings.enableItalic }, { id: 'highlight', type: ContextType.HIGHLIGHT, regex: /==([^=]+)==/g, enabled: !settings.customizeTargets || settings.enableHighlight }, @@ -34,4 +52,7 @@ export function buildBuiltinCopyMatchers(settings: EasyCopySettings, isIosApp: b { id: 'inline-latex', type: ContextType.INLINELATEX, regex: /\$([^$]+)\$/g, enabled: !settings.customizeTargets || settings.enableInlineLatex }, { id: 'wiki-link', type: ContextType.WIKILINK, regex: /\[\[([^\]]+)\]\]/g, enabled: !settings.customizeTargets || settings.enableWikiLink }, ]; + + const matcherById = new Map(matchers.map(matcher => [matcher.id, matcher])); + return normalizeBuiltinMatcherOrder(settings.matcherOrder).map(id => matcherById.get(id)!); } diff --git a/src/i18n.ts b/src/i18n.ts index d6208a3..a56193c 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -49,7 +49,8 @@ export type TranslationKey = | 'error-block-id-empty' | 'error-block-id-invalid' | 'enable-display-name-regex' | 'enable-display-name-regex-desc' | 'display-name-regex-from' | 'display-name-regex-from-desc' - | 'display-name-regex-to' | 'display-name-regex-to-desc'; + | 'display-name-regex-to' | 'display-name-regex-to-desc' + | 'matcher-priority' | 'matcher-priority-desc' | 'move-up' | 'move-down' | 'reset-order'; // 本地化翻译字典 export const translations: Record> = { @@ -172,6 +173,11 @@ export const translations: Record> = { 'display-name-regex-from-desc': 'Regular expression pattern to match in the display text (e.g. ^\\d+\\.\\s* to remove leading numbers)', 'display-name-regex-to': 'Replace with', 'display-name-regex-to-desc': 'Replacement string, supports capture groups like $1, $2 (e.g. $1 to keep only the first captured group)', + 'matcher-priority': 'Copy target priority', + 'matcher-priority-desc': 'When multiple targets match the cursor, targets higher in this list are copied first.', + 'move-up': 'Move up', + 'move-down': 'Move down', + 'reset-order': 'Reset order', }, [Language.ZH]: { // 复制 Block ID @@ -290,6 +296,11 @@ export const translations: Record> = { 'display-name-regex-from-desc': '用于匹配显示文本的正则表达式(如 ^\\d+\\.\\s* 可去除开头的序号)', 'display-name-regex-to': '替换为', 'display-name-regex-to-desc': '替换字符串,支持捕获组引用如 $1、$2(如 $1 只保留第一个捕获组的内容)', + 'matcher-priority': '复制对象优先级', + 'matcher-priority-desc': '当多种复制对象同时匹配光标位置时,列表中更靠上的对象会优先生效。', + 'move-up': '上移', + 'move-down': '下移', + 'reset-order': '重置顺序', }, [Language.ZH_TW]: { // 复制 Block ID @@ -409,6 +420,11 @@ export const translations: Record> = { 'display-name-regex-from-desc': '用於匹配顯示文本的正規表示式(如 ^\\d+\\.\\s* 可去除開頭的序號)', 'display-name-regex-to': '替換為', 'display-name-regex-to-desc': '替換字符串,支持捕獲組引用如 $1、$2', + 'matcher-priority': '複製對象優先級', + 'matcher-priority-desc': '當多種複製對象同時匹配游標位置時,列表中更靠上的對象會優先生效。', + 'move-up': '上移', + 'move-down': '下移', + 'reset-order': '重置順序', } }; diff --git a/src/main.ts b/src/main.ts index 41a984e..9c244e3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,7 +4,7 @@ import { ContextData, ContextType, DEFAULT_SETTINGS, EasyCopySettings, LinkForma import { EasyCopySettingTab } from './settingTab'; import { BlockIdInputModal } from './blockIdModal'; import { detectCodeBlockFromLines } from './codeBlockDetect'; -import { buildBuiltinCopyMatchers } from './copyMatcher'; +import { buildBuiltinCopyMatchers, normalizeBuiltinMatcherOrder } from './copyMatcher'; import { buildHeadingLink, buildBlockLink, buildFileLink, buildExplicitPasteLink } from './linkBuilder'; import { CopyMetadata, buildBlockCopyMetadata, buildHeadingCopyMetadata, buildFileCopyMetadata } from './copyMetadata'; import { decidePasteResolution, shouldOmitAliasForSameFile, shouldRegisterPasteHandler } from './pasteResolution'; @@ -161,6 +161,7 @@ export default class EasyCopy extends Plugin { async loadSettings() { this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + this.settings.matcherOrder = normalizeBuiltinMatcherOrder(this.settings.matcherOrder); } async saveSettings() { diff --git a/src/settingTab.ts b/src/settingTab.ts index d0535c3..30eed51 100644 --- a/src/settingTab.ts +++ b/src/settingTab.ts @@ -7,12 +7,23 @@ import { } from "obsidian"; import * as ObsidianModule from "obsidian"; import EasyCopy from "./main"; -import { LinkFormat, BlockIdInsertPosition, CodeBlockBehavior } from "./type"; +import { BuiltinCopyMatcherId, LinkFormat, BlockIdInsertPosition, CodeBlockBehavior } from "./type"; +import { DEFAULT_BUILTIN_COPY_MATCHER_IDS, normalizeBuiltinMatcherOrder } from "./copyMatcher"; interface SettingsContainer { addSetting(cb: (setting: Setting) => void): void; } +const BUILTIN_MATCHER_LABEL_KEYS: Record = { + 'bold': 'enable-bold', + 'italic': 'enable-italic', + 'highlight': 'enable-highlight', + 'strikethrough': 'enable-strikethrough', + 'inline-code': 'enable-inline-code', + 'inline-latex': 'enable-inline-latex', + 'wiki-link': 'enable-wikilink', +}; + function createSettingsGroup(containerEl: HTMLElement, heading?: string): SettingsContainer { // Check if SettingGroup is available (API 1.11.0+) // requireApiVersion is the official Obsidian API method for version checking @@ -345,6 +356,8 @@ export class EasyCopySettingTab extends PluginSettingTab { // 只有当自定义复制对象选项开启时才显示具体的复制对象选项 if (this.plugin.settings.customizeTargets) { + this.plugin.settings.matcherOrder = normalizeBuiltinMatcherOrder(this.plugin.settings.matcherOrder); + targetGroup.addSetting(setting => setting .setName(this.plugin.t('enable-inline-code')) .setDesc(this.plugin.t('enable-inline-code-desc')) @@ -424,8 +437,38 @@ export class EasyCopySettingTab extends PluginSettingTab { this.plugin.settings.enableWikiLink = value; void this.plugin.saveSettings(); this.display(); // 切换后刷新界面以显示/隐藏下方选项 - }))); - + }))); + + targetGroup.addSetting(setting => setting + .setName(this.plugin.t('matcher-priority')) + .setDesc(this.plugin.t('matcher-priority-desc')) + .addButton(button => button + .setButtonText(this.plugin.t('reset-order')) + .onClick(() => { + this.plugin.settings.matcherOrder = [...DEFAULT_BUILTIN_COPY_MATCHER_IDS]; + void this.plugin.saveSettings(); + this.display(); + }))); + + this.plugin.settings.matcherOrder.forEach((matcherId, index) => { + targetGroup.addSetting(setting => setting + .setName(this.plugin.t(BUILTIN_MATCHER_LABEL_KEYS[matcherId])) + .addButton(button => button + .setButtonText('↑') + .setTooltip(this.plugin.t('move-up')) + .setDisabled(index === 0) + .onClick(() => { + this.moveMatcher(matcherId, -1); + })) + .addButton(button => button + .setButtonText('↓') + .setTooltip(this.plugin.t('move-down')) + .setDisabled(index === this.plugin.settings.matcherOrder.length - 1) + .onClick(() => { + this.moveMatcher(matcherId, 1); + }))); + }); + } targetGroup.addSetting(setting => setting @@ -514,4 +557,16 @@ export class EasyCopySettingTab extends PluginSettingTab { }))); } } + + private moveMatcher(matcherId: BuiltinCopyMatcherId, direction: -1 | 1): void { + const matcherOrder = normalizeBuiltinMatcherOrder(this.plugin.settings.matcherOrder); + const currentIndex = matcherOrder.indexOf(matcherId); + const nextIndex = currentIndex + direction; + if (nextIndex < 0 || nextIndex >= matcherOrder.length) return; + + [matcherOrder[currentIndex], matcherOrder[nextIndex]] = [matcherOrder[nextIndex], matcherOrder[currentIndex]]; + this.plugin.settings.matcherOrder = matcherOrder; + void this.plugin.saveSettings(); + this.display(); + } } diff --git a/src/type.ts b/src/type.ts index 404b868..6d471f3 100644 --- a/src/type.ts +++ b/src/type.ts @@ -34,6 +34,15 @@ export enum ContextType { CODEBLOCK = 'code-block', // 光标在代码块内 } +export type BuiltinCopyMatcherId = + | 'bold' + | 'italic' + | 'highlight' + | 'strikethrough' + | 'inline-code' + | 'inline-latex' + | 'wiki-link'; + export interface ContextData { type: ContextType; curLine: string; @@ -62,6 +71,7 @@ export interface EasyCopySettings { enableInlineLatex: boolean; enableLink: boolean; enableWikiLink: boolean; // 是否启用 Wiki 链接复制 + matcherOrder: BuiltinCopyMatcherId[]; // 复制对象匹配优先级 keepWikiBrackets: boolean; // 复制 wiki-link 时保留 [[ ]] autoEmbedBlockLink: boolean; // 复制块链接时自动添加 !(嵌入块) enableCalloutCopy: boolean; // 是否启用复制 Callout 内文本 @@ -99,6 +109,7 @@ export const DEFAULT_SETTINGS: EasyCopySettings = { enableInlineLatex: true, enableLink: true, enableWikiLink: true, + matcherOrder: ['bold', 'italic', 'highlight', 'strikethrough', 'inline-code', 'inline-latex', 'wiki-link'], keepWikiBrackets: true, autoEmbedBlockLink: false, enableCalloutCopy: true, @@ -113,4 +124,4 @@ export const DEFAULT_SETTINGS: EasyCopySettings = { enableDisplayNameRegex: false, displayNameRegexFrom: '', displayNameRegexTo: '', -} \ No newline at end of file +}