mirror of
https://github.com/moyf/easy-copy.git
synced 2026-07-22 05:43:47 +00:00
feat: extend paste-time path resolution to explicit Wiki/Markdown
Previously the paste handler only fired when linkFormat was "Follow Obsidian settings". Users on explicit Wiki or Markdown got no paste-time cleanup — pasting a heading into the same file produced the verbose [[Filename#Heading|Heading]] instead of [[#Heading]]. Drop the OBSIDIAN-only gate in shouldRegisterPasteHandler and decidePasteResolution. handlePaste branches by linkFormat: OBSIDIAN keeps using app.fileManager.generateMarkdownLink (full newLinkFormat support); explicit Wiki/Markdown uses app.metadataCache.fileToLinktext (shortest-unique paths only). The same-file alias-redundancy fix from the previous commit applies to both branches. Add buildExplicitPasteLink in linkBuilder.ts as a pure string-producer for the explicit-format branch — caller decides omitAlias via shouldOmitAliasForSameFile, formatter consumes the boolean. Toggle now shown for all three linkFormat options; description updated to reflect format-aware behavior across en/zh/zh-tw. Tests: new buildExplicitPasteLink suite covering wiki/md, same-file vs cross-file, block links, and encoding behavior. pasteResolution tests updated for the broadened gate (linkFormat field removed from PasteResolutionInput; OBSIDIAN-only assertions dropped). Trade-off: explicit Wiki/Markdown get shortest-path-only resolution; users wanting relative/absolute path styles keep using "Follow Obsidian settings" (which honors the vault's newLinkFormat config). Designed, carefully reviewed, and edited by @lightmotive in collaboration with Claude Code.
This commit is contained in:
parent
7e467f389c
commit
adef562ef6
7 changed files with 299 additions and 73 deletions
|
|
@ -109,7 +109,7 @@ export const translations: Record<Language, Record<TranslationKey, string>> = {
|
|||
'markdown-link': 'Markdown link',
|
||||
'wiki-link': 'Wiki link',
|
||||
'resolve-link-path-on-paste': 'Resolve link path on paste',
|
||||
'resolve-link-path-on-paste-desc': 'When using "Follow Obsidian settings", regenerate the link path (shortest/relative/absolute) at paste time based on the destination file. Won\'t interfere with other paste-handling plugins; if one handles the paste first, this feature may be bypassed depending on how that plugin handles the paste.',
|
||||
'resolve-link-path-on-paste-desc': 'Regenerate the link path at paste time based on the destination file. Under "Follow Obsidian settings", uses your vault\'s path style (shortest/relative/absolute); under explicit Wiki/Markdown, uses shortest-unique paths only. Won\'t interfere with other paste-handling plugins; if one handles the paste first, this feature may be bypassed depending on how that plugin handles the paste.',
|
||||
'customize-targets': 'Customize targets',
|
||||
'customize-targets-desc': 'Enable to customize which elements can be copied (disable to copy all elements)',
|
||||
'enable-inline-code': 'Enable inline code',
|
||||
|
|
@ -209,7 +209,7 @@ export const translations: Record<Language, Record<TranslationKey, string>> = {
|
|||
'markdown-link': 'Markdown链接',
|
||||
'wiki-link': 'Wiki链接',
|
||||
'resolve-link-path-on-paste': '粘贴时解析链接路径',
|
||||
'resolve-link-path-on-paste-desc': '在使用"跟随 Obsidian 设置"时,根据目标文件在粘贴时重新生成链接路径(最短/相对/绝对)。不会干扰其他处理粘贴的插件;若有其他插件先处理粘贴,此功能可能被绕过,具体取决于该插件的处理方式。',
|
||||
'resolve-link-path-on-paste-desc': '粘贴时根据目标文件重新生成链接路径。使用"跟随 Obsidian 设置"时,会沿用 Obsidian 的路径风格(最短/相对/绝对);选择明确的 Wiki/Markdown 格式时,仅使用最短唯一路径。不会干扰其他处理粘贴的插件;若有其他插件先处理粘贴,此功能可能被绕过,具体取决于该插件的处理方式。',
|
||||
'customize-targets': '自定义复制对象',
|
||||
'customize-targets-desc': '启用后可以自定义哪些元素可以被复制(不启用则默认可复制所有元素)',
|
||||
'enable-inline-code': '启用行内代码',
|
||||
|
|
@ -319,7 +319,7 @@ export const translations: Record<Language, Record<TranslationKey, string>> = {
|
|||
'markdown-link': 'Markdown連結',
|
||||
'wiki-link': 'Wiki連結',
|
||||
'resolve-link-path-on-paste': '貼上時解析連結路徑',
|
||||
'resolve-link-path-on-paste-desc': '在使用「跟隨 Obsidian 設定」時,根據目標檔案在貼上時重新生成連結路徑(最短/相對/絕對)。不會干擾其他處理貼上的外掛;若有其他外掛先處理貼上,此功能可能被略過,具體取決於該外掛的處理方式。',
|
||||
'resolve-link-path-on-paste-desc': '貼上時根據目標檔案重新生成連結路徑。使用「跟隨 Obsidian 設定」時,會沿用 Obsidian 的路徑風格(最短/相對/絕對);選擇明確的 Wiki/Markdown 格式時,僅使用最短唯一路徑。不會干擾其他處理貼上的外掛;若有其他外掛先處理貼上,此功能可能被略過,具體取決於該外掛的處理方式。',
|
||||
'customize-targets': '自定義複製對象',
|
||||
'customize-targets-desc': '啟用後可以自定義哪些元素可以被複製(不啟用則默认可複製所有元素)',
|
||||
'enable-inline-code': '啟用行內代碼',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { buildHeadingLink, buildBlockLink, buildFileLink, extractBlockDisplayText, encodeMarkdownLinkUrl, sanitizeHeadingForLink } from './linkBuilder';
|
||||
import {
|
||||
buildBlockLink,
|
||||
buildExplicitPasteLink,
|
||||
buildFileLink,
|
||||
buildHeadingLink,
|
||||
encodeMarkdownLinkUrl,
|
||||
extractBlockDisplayText,
|
||||
sanitizeHeadingForLink,
|
||||
} from './linkBuilder';
|
||||
import { LinkFormat } from './type';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -1131,3 +1139,203 @@ describe('buildFileLink', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// buildExplicitPasteLink
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('buildExplicitPasteLink', () => {
|
||||
describe('wiki format', () => {
|
||||
it('cross-file with subpath + alias produces [[path#H|alias]]', () => {
|
||||
expect(buildExplicitPasteLink({
|
||||
format: LinkFormat.WIKILINK,
|
||||
path: 'SomeThing',
|
||||
subpath: '#Other Heading',
|
||||
alias: 'Other Heading',
|
||||
sameFile: false,
|
||||
omitAlias: false,
|
||||
})).toBe('[[SomeThing#Other Heading|Other Heading]]');
|
||||
});
|
||||
|
||||
it('cross-file with subpath, omitAlias=true produces [[path#H]]', () => {
|
||||
expect(buildExplicitPasteLink({
|
||||
format: LinkFormat.WIKILINK,
|
||||
path: 'SomeThing',
|
||||
subpath: '#Other Heading',
|
||||
alias: 'Other Heading',
|
||||
sameFile: false,
|
||||
omitAlias: true,
|
||||
})).toBe('[[SomeThing#Other Heading]]');
|
||||
});
|
||||
|
||||
it('same-file with subpath drops path portion to [[#H|alias]]', () => {
|
||||
expect(buildExplicitPasteLink({
|
||||
format: LinkFormat.WIKILINK,
|
||||
path: 'SomeThing',
|
||||
subpath: '#Other Heading',
|
||||
alias: 'Other Heading',
|
||||
sameFile: true,
|
||||
omitAlias: false,
|
||||
})).toBe('[[#Other Heading|Other Heading]]');
|
||||
});
|
||||
|
||||
it('same-file with subpath + omitAlias produces [[#H]]', () => {
|
||||
expect(buildExplicitPasteLink({
|
||||
format: LinkFormat.WIKILINK,
|
||||
path: 'SomeThing',
|
||||
subpath: '#Other Heading',
|
||||
alias: 'Other Heading',
|
||||
sameFile: true,
|
||||
omitAlias: true,
|
||||
})).toBe('[[#Other Heading]]');
|
||||
});
|
||||
|
||||
it('cross-file file link (empty subpath) with alias', () => {
|
||||
expect(buildExplicitPasteLink({
|
||||
format: LinkFormat.WIKILINK,
|
||||
path: 'SomeThing',
|
||||
subpath: '',
|
||||
alias: 'Some Thing',
|
||||
sameFile: false,
|
||||
omitAlias: false,
|
||||
})).toBe('[[SomeThing|Some Thing]]');
|
||||
});
|
||||
|
||||
it('cross-file file link with empty alias yields [[path]]', () => {
|
||||
expect(buildExplicitPasteLink({
|
||||
format: LinkFormat.WIKILINK,
|
||||
path: 'SomeThing',
|
||||
subpath: '',
|
||||
alias: '',
|
||||
sameFile: false,
|
||||
omitAlias: false,
|
||||
})).toBe('[[SomeThing]]');
|
||||
});
|
||||
|
||||
it('block-link cross-file: omitAlias=false (caller-decided) keeps alias', () => {
|
||||
expect(buildExplicitPasteLink({
|
||||
format: LinkFormat.WIKILINK,
|
||||
path: 'SomeThing',
|
||||
subpath: '#^abc123',
|
||||
alias: 'The quick brown',
|
||||
sameFile: false,
|
||||
omitAlias: false,
|
||||
})).toBe('[[SomeThing#^abc123|The quick brown]]');
|
||||
});
|
||||
|
||||
it('block-link same-file produces [[#^id|alias]]', () => {
|
||||
expect(buildExplicitPasteLink({
|
||||
format: LinkFormat.WIKILINK,
|
||||
path: 'SomeThing',
|
||||
subpath: '#^abc123',
|
||||
alias: 'The quick brown',
|
||||
sameFile: true,
|
||||
omitAlias: false,
|
||||
})).toBe('[[#^abc123|The quick brown]]');
|
||||
});
|
||||
|
||||
it('does not encode spaces in path (wiki-link convention)', () => {
|
||||
expect(buildExplicitPasteLink({
|
||||
format: LinkFormat.WIKILINK,
|
||||
path: 'My Note',
|
||||
subpath: '#Some Heading',
|
||||
alias: 'Some Heading',
|
||||
sameFile: false,
|
||||
omitAlias: false,
|
||||
})).toBe('[[My Note#Some Heading|Some Heading]]');
|
||||
});
|
||||
});
|
||||
|
||||
describe('markdown format', () => {
|
||||
it('cross-file with subpath + alias produces [alias](path#H)', () => {
|
||||
expect(buildExplicitPasteLink({
|
||||
format: LinkFormat.MDLINK,
|
||||
path: 'SomeThing',
|
||||
subpath: '#Other Heading',
|
||||
alias: 'Other Heading',
|
||||
sameFile: false,
|
||||
omitAlias: false,
|
||||
})).toBe('[Other Heading](SomeThing#Other%20Heading)');
|
||||
});
|
||||
|
||||
it('same-file produces [alias](#H) with no path', () => {
|
||||
expect(buildExplicitPasteLink({
|
||||
format: LinkFormat.MDLINK,
|
||||
path: 'SomeThing',
|
||||
subpath: '#Other Heading',
|
||||
alias: 'Other Heading',
|
||||
sameFile: true,
|
||||
omitAlias: false,
|
||||
})).toBe('[Other Heading](#Other%20Heading)');
|
||||
});
|
||||
|
||||
it('omitAlias=true preserves alias as display (MD never produces empty [])', () => {
|
||||
expect(buildExplicitPasteLink({
|
||||
format: LinkFormat.MDLINK,
|
||||
path: 'SomeThing',
|
||||
subpath: '#Other Heading',
|
||||
alias: 'Other Heading',
|
||||
sameFile: true,
|
||||
omitAlias: true,
|
||||
})).toBe('[Other Heading](#Other%20Heading)');
|
||||
});
|
||||
|
||||
it('cross-file file link (empty subpath) with alias', () => {
|
||||
expect(buildExplicitPasteLink({
|
||||
format: LinkFormat.MDLINK,
|
||||
path: 'SomeThing',
|
||||
subpath: '',
|
||||
alias: 'Some Thing',
|
||||
sameFile: false,
|
||||
omitAlias: false,
|
||||
})).toBe('[Some Thing](SomeThing)');
|
||||
});
|
||||
|
||||
it('encodes spaces in path as %20', () => {
|
||||
expect(buildExplicitPasteLink({
|
||||
format: LinkFormat.MDLINK,
|
||||
path: 'My Note',
|
||||
subpath: '#Some Heading',
|
||||
alias: 'Some Heading',
|
||||
sameFile: false,
|
||||
omitAlias: false,
|
||||
})).toBe('[Some Heading](My%20Note#Some%20Heading)');
|
||||
});
|
||||
|
||||
it('encodes spaces in same-file fragment', () => {
|
||||
expect(buildExplicitPasteLink({
|
||||
format: LinkFormat.MDLINK,
|
||||
path: 'irrelevant',
|
||||
subpath: '#Some Heading',
|
||||
alias: 'Some Heading',
|
||||
sameFile: true,
|
||||
omitAlias: false,
|
||||
})).toBe('[Some Heading](#Some%20Heading)');
|
||||
});
|
||||
|
||||
it('block-link cross-file', () => {
|
||||
expect(buildExplicitPasteLink({
|
||||
format: LinkFormat.MDLINK,
|
||||
path: 'SomeThing',
|
||||
subpath: '#^abc123',
|
||||
alias: 'The quick brown',
|
||||
sameFile: false,
|
||||
omitAlias: false,
|
||||
})).toBe('[The quick brown](SomeThing#^abc123)');
|
||||
});
|
||||
|
||||
it('empty alias yields []() — caller is expected to provide non-empty alias', () => {
|
||||
// Documenting boundary: buildHeadingCopyMetadata clears alias when
|
||||
// isNoteLink+filename===heading. handlePaste should treat that as
|
||||
// the degenerate self-ref case (skip) before reaching here.
|
||||
expect(buildExplicitPasteLink({
|
||||
format: LinkFormat.MDLINK,
|
||||
path: 'SomeThing',
|
||||
subpath: '',
|
||||
alias: '',
|
||||
sameFile: false,
|
||||
omitAlias: false,
|
||||
})).toBe('[](SomeThing)');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -205,6 +205,39 @@ export function buildBlockLink(options: BuildBlockLinkOptions): string {
|
|||
return link;
|
||||
}
|
||||
|
||||
// --- Explicit Paste Link ---
|
||||
//
|
||||
// Used by the paste handler when the user has explicitly chosen Wiki/Markdown
|
||||
// (not "Follow Obsidian settings"). Obsidian's app.fileManager.generateMarkdownLink
|
||||
// honors the vault's useMarkdownLinks config which would override an explicit
|
||||
// choice — so we build the link manually instead, with the path string already
|
||||
// resolved by app.metadataCache.fileToLinktext.
|
||||
//
|
||||
// Pure string-producer: the caller decides `omitAlias` via shouldOmitAliasForSameFile.
|
||||
// Formatter only consumes the flag — Strategy: caller owns policy, formatter owns syntax.
|
||||
|
||||
export interface BuildExplicitPasteLinkOptions {
|
||||
format: LinkFormat.WIKILINK | LinkFormat.MDLINK;
|
||||
path: string; // from fileToLinktext (shortest unique vault path)
|
||||
subpath: string; // '#Heading' or '#^blockid' or ''
|
||||
alias: string; // display text or ''
|
||||
sameFile: boolean; // sourcePath === destPath — drops path portion
|
||||
omitAlias: boolean; // true → render without alias (caller decision)
|
||||
}
|
||||
|
||||
export function buildExplicitPasteLink(opts: BuildExplicitPasteLinkOptions): string {
|
||||
const linkTarget = opts.sameFile ? opts.subpath : `${opts.path}${opts.subpath}`;
|
||||
const aliasToUse = opts.omitAlias ? '' : opts.alias;
|
||||
|
||||
if (opts.format === LinkFormat.WIKILINK) {
|
||||
return aliasToUse ? `[[${linkTarget}|${aliasToUse}]]` : `[[${linkTarget}]]`;
|
||||
}
|
||||
// MDLINK — alias IS the visible link text. When omitAlias forces it empty,
|
||||
// preserve the original alias as display so we never produce [](path#H).
|
||||
const display = aliasToUse || opts.alias || '';
|
||||
return `[${display}](${encodeMarkdownLinkUrl(linkTarget)})`;
|
||||
}
|
||||
|
||||
// --- File Link ---
|
||||
|
||||
export interface BuildFileLinkOptions {
|
||||
|
|
|
|||
47
src/main.ts
47
src/main.ts
|
|
@ -3,7 +3,7 @@ import { Language, TranslationKey, I18n } from './i18n';
|
|||
import { ContextData, ContextType, DEFAULT_SETTINGS, EasyCopySettings, LinkFormat, BlockIdInsertPosition } from './type';
|
||||
import { EasyCopySettingTab } from './settingTab';
|
||||
import { BlockIdInputModal } from './blockIdModal';
|
||||
import { buildHeadingLink, buildBlockLink, buildFileLink } from './linkBuilder';
|
||||
import { buildHeadingLink, buildBlockLink, buildFileLink, buildExplicitPasteLink } from './linkBuilder';
|
||||
import { CopyMetadata, buildBlockCopyMetadata, buildHeadingCopyMetadata, buildFileCopyMetadata } from './copyMetadata';
|
||||
import { decidePasteResolution, shouldOmitAliasForSameFile, shouldRegisterPasteHandler } from './pasteResolution';
|
||||
|
||||
|
|
@ -169,8 +169,9 @@ export default class EasyCopy extends Plugin {
|
|||
|
||||
/**
|
||||
* Intercept paste when the clipboard contains an Easy Copy link.
|
||||
* Uses Obsidian's generateMarkdownLink() to resolve the correct path
|
||||
* format (shortest/relative/absolute) for the destination file.
|
||||
* Under "Follow Obsidian settings" uses generateMarkdownLink() (full
|
||||
* path-style support); under explicit Wiki/Markdown uses fileToLinktext()
|
||||
* (shortest-unique paths only) so the user's format choice is honored.
|
||||
*
|
||||
* Per the editor-paste contract (obsidian.d.ts): yield if another handler
|
||||
* has already preventDefault'd, and call preventDefault to claim the event.
|
||||
|
|
@ -180,7 +181,6 @@ export default class EasyCopy extends Plugin {
|
|||
const decision = decidePasteResolution({
|
||||
defaultPrevented: evt.defaultPrevented,
|
||||
resolveLinkPathOnPaste: this.settings.resolveLinkPathOnPaste,
|
||||
linkFormat: this.settings.linkFormat,
|
||||
lastCopyMeta: this.lastCopyMeta,
|
||||
clipboardText,
|
||||
now: Date.now(),
|
||||
|
|
@ -207,22 +207,43 @@ export default class EasyCopy extends Plugin {
|
|||
if (!(sourceFile instanceof TFile)) return;
|
||||
|
||||
try {
|
||||
const effectiveFormat = this.getEffectiveLinkFormat();
|
||||
const omitAlias = shouldOmitAliasForSameFile({
|
||||
effectiveLinkFormat: this.getEffectiveLinkFormat(),
|
||||
effectiveLinkFormat: effectiveFormat,
|
||||
sourceFilePath: meta.sourceFilePath,
|
||||
destFilePath: destFile.path,
|
||||
subpath: meta.subpath,
|
||||
alias: meta.alias,
|
||||
useHeadingAsDisplayText: this.settings.useHeadingAsDisplayText,
|
||||
});
|
||||
const aliasArg = omitAlias ? undefined : (meta.alias || undefined);
|
||||
|
||||
let link = this.app.fileManager.generateMarkdownLink(
|
||||
sourceFile,
|
||||
destFile.path,
|
||||
meta.subpath || undefined,
|
||||
aliasArg,
|
||||
);
|
||||
let link: string;
|
||||
if (this.settings.linkFormat === LinkFormat.OBSIDIAN) {
|
||||
// generateMarkdownLink honors vault config (useMarkdownLinks +
|
||||
// newLinkFormat) — full path-style support: shortest/relative/absolute.
|
||||
const aliasArg = omitAlias ? undefined : (meta.alias || undefined);
|
||||
link = this.app.fileManager.generateMarkdownLink(
|
||||
sourceFile,
|
||||
destFile.path,
|
||||
meta.subpath || undefined,
|
||||
aliasArg,
|
||||
);
|
||||
} else {
|
||||
// Explicit Wiki/Markdown: build manually so the user's format choice is
|
||||
// honored. fileToLinktext returns the shortest-unique vault path
|
||||
// (no relative/absolute support — documented trade-off).
|
||||
// omitMdExtension=true is REQUIRED — defaulting false would yield
|
||||
// paths with ".md" and break wiki-link convention ([[Note.md]]).
|
||||
const path = this.app.metadataCache.fileToLinktext(sourceFile, destFile.path, true);
|
||||
link = buildExplicitPasteLink({
|
||||
format: effectiveFormat as LinkFormat.WIKILINK | LinkFormat.MDLINK,
|
||||
path,
|
||||
subpath: meta.subpath,
|
||||
alias: meta.alias,
|
||||
sameFile: meta.sourceFilePath === destFile.path,
|
||||
omitAlias,
|
||||
});
|
||||
}
|
||||
|
||||
if (meta.isEmbed) {
|
||||
link = '!' + link;
|
||||
|
|
@ -233,7 +254,7 @@ export default class EasyCopy extends Plugin {
|
|||
editor.replaceSelection(link);
|
||||
}
|
||||
} catch {
|
||||
// generateMarkdownLink failed — let normal paste proceed
|
||||
// link generation failed — let normal paste proceed
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,39 +9,12 @@ import { CopyMetadata } from './copyMetadata';
|
|||
import { LinkFormat } from './type';
|
||||
|
||||
describe('shouldRegisterPasteHandler', () => {
|
||||
it('returns true when toggle is on AND linkFormat is OBSIDIAN', () => {
|
||||
expect(shouldRegisterPasteHandler({
|
||||
resolveLinkPathOnPaste: true,
|
||||
linkFormat: LinkFormat.OBSIDIAN,
|
||||
})).toBe(true);
|
||||
it('returns true when toggle is on', () => {
|
||||
expect(shouldRegisterPasteHandler({ resolveLinkPathOnPaste: true })).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false when toggle is off', () => {
|
||||
expect(shouldRegisterPasteHandler({
|
||||
resolveLinkPathOnPaste: false,
|
||||
linkFormat: LinkFormat.OBSIDIAN,
|
||||
})).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false when linkFormat is MDLINK even if toggle is on', () => {
|
||||
expect(shouldRegisterPasteHandler({
|
||||
resolveLinkPathOnPaste: true,
|
||||
linkFormat: LinkFormat.MDLINK,
|
||||
})).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false when linkFormat is WIKILINK even if toggle is on', () => {
|
||||
expect(shouldRegisterPasteHandler({
|
||||
resolveLinkPathOnPaste: true,
|
||||
linkFormat: LinkFormat.WIKILINK,
|
||||
})).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false when both conditions fail', () => {
|
||||
expect(shouldRegisterPasteHandler({
|
||||
resolveLinkPathOnPaste: false,
|
||||
linkFormat: LinkFormat.MDLINK,
|
||||
})).toBe(false);
|
||||
expect(shouldRegisterPasteHandler({ resolveLinkPathOnPaste: false })).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -59,7 +32,6 @@ const TTL = 5 * 60 * 1000;
|
|||
const baseInput: PasteResolutionInput = {
|
||||
defaultPrevented: false,
|
||||
resolveLinkPathOnPaste: true,
|
||||
linkFormat: LinkFormat.OBSIDIAN,
|
||||
lastCopyMeta: META,
|
||||
clipboardText: META.clipboardText,
|
||||
now: META.timestamp + 1000,
|
||||
|
|
@ -79,11 +51,6 @@ describe('decidePasteResolution', () => {
|
|||
expect(decidePasteResolution({ ...baseInput, resolveLinkPathOnPaste: false })).toBe('skip');
|
||||
});
|
||||
|
||||
it('skips when linkFormat is not OBSIDIAN', () => {
|
||||
expect(decidePasteResolution({ ...baseInput, linkFormat: LinkFormat.MDLINK })).toBe('skip');
|
||||
expect(decidePasteResolution({ ...baseInput, linkFormat: LinkFormat.WIKILINK })).toBe('skip');
|
||||
});
|
||||
|
||||
it('skips when there is no lastCopyMeta', () => {
|
||||
expect(decidePasteResolution({ ...baseInput, lastCopyMeta: null })).toBe('skip');
|
||||
});
|
||||
|
|
@ -125,11 +92,10 @@ describe('decidePasteResolution', () => {
|
|||
})).toBe('skip');
|
||||
});
|
||||
|
||||
it('toggle takes precedence over linkFormat and meta state', () => {
|
||||
it('toggle takes precedence over meta state', () => {
|
||||
expect(decidePasteResolution({
|
||||
...baseInput,
|
||||
resolveLinkPathOnPaste: false,
|
||||
linkFormat: LinkFormat.MDLINK,
|
||||
lastCopyMeta: null,
|
||||
})).toBe('skip');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ import { EasyCopySettings, LinkFormat } from './type';
|
|||
* The bridge between settings state and "is Easy Copy in the paste plugin chain."
|
||||
*/
|
||||
export function shouldRegisterPasteHandler(
|
||||
settings: Pick<EasyCopySettings, 'resolveLinkPathOnPaste' | 'linkFormat'>,
|
||||
settings: Pick<EasyCopySettings, 'resolveLinkPathOnPaste'>,
|
||||
): boolean {
|
||||
return settings.resolveLinkPathOnPaste && settings.linkFormat === LinkFormat.OBSIDIAN;
|
||||
return settings.resolveLinkPathOnPaste;
|
||||
}
|
||||
|
||||
export type PasteResolutionAction =
|
||||
|
|
@ -20,7 +20,6 @@ export type PasteResolutionAction =
|
|||
export interface PasteResolutionInput {
|
||||
defaultPrevented: boolean;
|
||||
resolveLinkPathOnPaste: boolean;
|
||||
linkFormat: LinkFormat;
|
||||
lastCopyMeta: CopyMetadata | null;
|
||||
clipboardText: string | undefined;
|
||||
now: number;
|
||||
|
|
@ -30,7 +29,6 @@ export interface PasteResolutionInput {
|
|||
export function decidePasteResolution(input: PasteResolutionInput): PasteResolutionAction {
|
||||
if (input.defaultPrevented) return 'skip';
|
||||
if (!input.resolveLinkPathOnPaste) return 'skip';
|
||||
if (input.linkFormat !== LinkFormat.OBSIDIAN) return 'skip';
|
||||
if (!input.lastCopyMeta) return 'skip';
|
||||
if (input.now - input.lastCopyMeta.timestamp > input.ttlMs) return 'reset-and-skip';
|
||||
if (input.clipboardText !== input.lastCopyMeta.clipboardText) return 'reset-and-skip';
|
||||
|
|
|
|||
|
|
@ -123,20 +123,20 @@ export class EasyCopySettingTab extends PluginSettingTab {
|
|||
this.display();
|
||||
})));
|
||||
|
||||
// Only relevant when linkFormat is OBSIDIAN; the resolver intercepts paste
|
||||
// to regenerate the path (shortest/relative/absolute) for the destination file.
|
||||
if (this.plugin.settings.linkFormat === LinkFormat.OBSIDIAN) {
|
||||
formatGroup.addSetting(setting => setting
|
||||
.setName(this.plugin.t('resolve-link-path-on-paste'))
|
||||
.setDesc(this.plugin.t('resolve-link-path-on-paste-desc'))
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.resolveLinkPathOnPaste)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.resolveLinkPathOnPaste = value;
|
||||
await this.plugin.saveSettings();
|
||||
this.plugin.syncPasteHandlerRegistration();
|
||||
})));
|
||||
}
|
||||
// The resolver intercepts paste to regenerate the link target for the
|
||||
// destination. Under "Follow Obsidian settings" it uses the vault's
|
||||
// path style (shortest/relative/absolute); under explicit Wiki/Markdown
|
||||
// it falls back to shortest-unique paths only.
|
||||
formatGroup.addSetting(setting => setting
|
||||
.setName(this.plugin.t('resolve-link-path-on-paste'))
|
||||
.setDesc(this.plugin.t('resolve-link-path-on-paste-desc'))
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.resolveLinkPathOnPaste)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.resolveLinkPathOnPaste = value;
|
||||
await this.plugin.saveSettings();
|
||||
this.plugin.syncPasteHandlerRegistration();
|
||||
})));
|
||||
|
||||
formatGroup.addSetting(setting => setting
|
||||
.setName(this.plugin.t('use-heading-as-display'))
|
||||
|
|
|
|||
Loading…
Reference in a new issue