diff --git a/packages/foam-vscode/src/core/janitor/convert-links-format.test.ts b/packages/foam-vscode/src/core/janitor/convert-links-format.test.ts index 489b2b89e..b0c8b3eae 100644 --- a/packages/foam-vscode/src/core/janitor/convert-links-format.test.ts +++ b/packages/foam-vscode/src/core/janitor/convert-links-format.test.ts @@ -46,8 +46,8 @@ describe('generateStdMdLink', () => { '[first-document](first-document.md)', '[second-document](second-document.md)', '[[non-exist-file]]', - '[#one section](<#one section>)', - '[another name](<#one section>)', + '[#one section]()', + '[another name]()', '[an alias](first-document.md)', '[first-document](first-document.md)', ]; diff --git a/packages/foam-vscode/src/core/janitor/convert-links-format.ts b/packages/foam-vscode/src/core/janitor/convert-links-format.ts index 5e4f35629..c85e0adc8 100644 --- a/packages/foam-vscode/src/core/janitor/convert-links-format.ts +++ b/packages/foam-vscode/src/core/janitor/convert-links-format.ts @@ -40,8 +40,6 @@ export function convertLinkFormat( let { target, section, alias } = MarkdownLink.analyzeLink(link); let sectionDivider = section ? '#' : ''; - let aliasDivider = alias ? '|' : ''; - let embed = link.isEmbed ? '!' : ''; if (isNone(targetUri)) { throw new Error( @@ -53,16 +51,10 @@ export function convertLinkFormat( let relativeUri = targetRes.uri.relativeTo(resource.uri.getDirectory()); if (targetFormat === 'wikilink') { - /* remove extension if possible, then get the basename to prevent from filename conflict */ - if (relativeUri.path.endsWith(workspace.defaultExtension)) { - relativeUri = relativeUri.changeExtension('*', ''); - } - target = relativeUri.getBasename(); - - return { - newText: `${embed}[[${target}${sectionDivider}${section}${aliasDivider}${alias}]]`, - range: link.range, - }; + return MarkdownLink.createUpdateLinkEdit(link, { + target: workspace.getIdentifier(relativeUri), + type: 'wikilink', + }); } if (targetFormat === 'link') { @@ -75,27 +67,15 @@ export function convertLinkFormat( alias = `${target}${sectionDivider}${section}`; } - /* construct url */ - let url = relativeUri.path; - /* in page anchor have no filename */ - if (relativeUri.getBasename() === resource.uri.getBasename()) { - url = ''; - } - if (sectionDivider === '#') { - url = `${url}${sectionDivider}${section}`; - } - if (url.indexOf(' ') > 0) { - url = `<${url}>`; - } - /* if it's originally an embedded note, the markdown link shouldn't be embedded */ - if (embed && targetRes.type === 'note') { - embed = ''; - } - return { - newText: `${embed}[${alias}](${url})`, - range: link.range, - }; + const isEmbed = targetRes.type === 'image' ? link.isEmbed : false; + + return MarkdownLink.createUpdateLinkEdit(link, { + alias: alias, + target: relativeUri.path, + isEmbed: isEmbed, + type: 'link', + }); } throw new Error( `Unexpected state: targetFormat: ${targetFormat} is not supported` diff --git a/packages/foam-vscode/src/core/services/markdown-link.test.ts b/packages/foam-vscode/src/core/services/markdown-link.test.ts index 0377072f3..b49f7cafc 100644 --- a/packages/foam-vscode/src/core/services/markdown-link.test.ts +++ b/packages/foam-vscode/src/core/services/markdown-link.test.ts @@ -254,4 +254,185 @@ describe('MarkdownLink', () => { expect(edit.range).toEqual(link.range); }); }); + + describe('convert wikilink to link', () => { + it('should generate default alias if no one', () => { + const wikilink = parser.parse(getRandomURI(), `[[wikilink]]`).links[0]; + const wikilinkEdit = MarkdownLink.createUpdateLinkEdit(wikilink, { + type: 'link', + }); + expect(wikilinkEdit.newText).toEqual(`[wikilink](wikilink)`); + expect(wikilinkEdit.range).toEqual(wikilink.range); + + const wikilinkWithSection = parser.parse( + getRandomURI(), + `[[wikilink#section]]` + ).links[0]; + const wikilinkWithSectionEdit = MarkdownLink.createUpdateLinkEdit( + wikilinkWithSection, + { + type: 'link', + } + ); + expect(wikilinkWithSectionEdit.newText).toEqual( + `[wikilink#section](wikilink#section)` + ); + expect(wikilinkWithSectionEdit.range).toEqual(wikilinkWithSection.range); + }); + + it('should use alias in the wikilik the if there has one', () => { + const wikilink = parser.parse( + getRandomURI(), + `[[wikilink#section|alias]]` + ).links[0]; + const wikilinkEdit = MarkdownLink.createUpdateLinkEdit(wikilink, { + type: 'link', + }); + expect(wikilinkEdit.newText).toEqual(`[alias](wikilink#section)`); + expect(wikilinkEdit.range).toEqual(wikilink.range); + }); + }); + + describe('convert link to wikilink', () => { + it('should reorganize target, section, and alias in wikilink manner', () => { + const link = parser.parse(getRandomURI(), `[link](to/path.md)`).links[0]; + const linkEdit = MarkdownLink.createUpdateLinkEdit(link, { + type: 'wikilink', + }); + expect(linkEdit.newText).toEqual(`[[to/path.md|link]]`); + expect(linkEdit.range).toEqual(link.range); + + const linkWithSection = parser.parse( + getRandomURI(), + `[link](to/path.md#section)` + ).links[0]; + const linkWithSectionEdit = MarkdownLink.createUpdateLinkEdit( + linkWithSection, + { + type: 'wikilink', + } + ); + expect(linkWithSectionEdit.newText).toEqual( + `[[to/path.md#section|link]]` + ); + expect(linkWithSectionEdit.range).toEqual(linkWithSection.range); + }); + + it('should use alias in the wikilik the if there has one', () => { + const wikilink = parser.parse( + getRandomURI(), + `[[wikilink#section|alias]]` + ).links[0]; + const wikilinkEdit = MarkdownLink.createUpdateLinkEdit(wikilink, { + type: 'link', + }); + expect(wikilinkEdit.newText).toEqual(`[alias](wikilink#section)`); + expect(wikilinkEdit.range).toEqual(wikilink.range); + }); + }); + + describe('convert to its original type', () => { + it('should remaind unchanged', () => { + const link = parser.parse(getRandomURI(), `[link](to/path.md#section)`) + .links[0]; + const linkEdit = MarkdownLink.createUpdateLinkEdit(link, { + type: 'link', + }); + expect(linkEdit.newText).toEqual(`[link](to/path.md#section)`); + expect(linkEdit.range).toEqual(link.range); + + const wikilink = parser.parse( + getRandomURI(), + `[[wikilink#section|alias]]` + ).links[0]; + const wikilinkEdit = MarkdownLink.createUpdateLinkEdit(wikilink, { + type: 'wikilink', + }); + expect(wikilinkEdit.newText).toEqual(`[[wikilink#section|alias]]`); + expect(wikilinkEdit.range).toEqual(wikilink.range); + }); + }); + + describe('change isEmbed property', () => { + it('should change isEmbed only', () => { + const wikilink = parser.parse(getRandomURI(), `[[wikilink]]`).links[0]; + const wikilinkEdit = MarkdownLink.createUpdateLinkEdit(wikilink, { + isEmbed: true, + }); + expect(wikilinkEdit.newText).toEqual(`![[wikilink]]`); + expect(wikilinkEdit.range).toEqual(wikilink.range); + + const link = parser.parse(getRandomURI(), `![link](to/path.md)`).links[0]; + const linkEdit = MarkdownLink.createUpdateLinkEdit(link, { + isEmbed: false, + }); + expect(linkEdit.newText).toEqual(`[link](to/path.md)`); + expect(linkEdit.range).toEqual(link.range); + }); + + it('should be unchanged if the update value is the same as the original one', () => { + const embeddedWikilink = parser.parse(getRandomURI(), `![[wikilink]]`) + .links[0]; + const embeddedWikilinkEdit = MarkdownLink.createUpdateLinkEdit( + embeddedWikilink, + { + isEmbed: true, + } + ); + expect(embeddedWikilinkEdit.newText).toEqual(`![[wikilink]]`); + expect(embeddedWikilinkEdit.range).toEqual(embeddedWikilink.range); + + const link = parser.parse(getRandomURI(), `[link](to/path.md)`).links[0]; + const linkEdit = MarkdownLink.createUpdateLinkEdit(link, { + isEmbed: false, + }); + expect(linkEdit.newText).toEqual(`[link](to/path.md)`); + expect(linkEdit.range).toEqual(link.range); + }); + }); + + describe('insert angles', () => { + it('should insert angles when meeting space in links', () => { + const link = parser.parse(getRandomURI(), `![link](to/path.md)`).links[0]; + const linkAddSection = MarkdownLink.createUpdateLinkEdit(link, { + section: 'one section', + }); + expect(linkAddSection.newText).toEqual( + `![link]()` + ); + expect(linkAddSection.range).toEqual(link.range); + + const linkChangingTarget = parser.parse( + getRandomURI(), + `[link](to/path.md#one-section)` + ).links[0]; + const linkEdit = MarkdownLink.createUpdateLinkEdit(linkChangingTarget, { + target: 'to/another path.md', + }); + expect(linkEdit.newText).toEqual( + `[link]()` + ); + expect(linkEdit.range).toEqual(linkChangingTarget.range); + + const wikilink = parser.parse(getRandomURI(), `[[wikilink#one section]]`) + .links[0]; + const wikilinkEdit = MarkdownLink.createUpdateLinkEdit(wikilink, { + type: 'link', + }); + expect(wikilinkEdit.newText).toEqual( + `[wikilink#one section]()` + ); + expect(wikilinkEdit.range).toEqual(wikilink.range); + }); + + it('should not insert angles in wikilink', () => { + const wikilink = parser.parse(getRandomURI(), `[[wikilink#one section]]`) + .links[0]; + const wikilinkEdit = MarkdownLink.createUpdateLinkEdit(wikilink, { + target: 'another wikilink', + }); + expect(wikilinkEdit.newText).toEqual(`[[another wikilink#one section]]`); + expect(wikilinkEdit.range).toEqual(wikilink.range); + }); + }); }); diff --git a/packages/foam-vscode/src/core/services/markdown-link.ts b/packages/foam-vscode/src/core/services/markdown-link.ts index e5c88bf3b..b0ba35e16 100644 --- a/packages/foam-vscode/src/core/services/markdown-link.ts +++ b/packages/foam-vscode/src/core/services/markdown-link.ts @@ -38,7 +38,13 @@ export abstract class MarkdownLink { public static createUpdateLinkEdit( link: ResourceLink, - delta: { target?: string; section?: string; alias?: string } + delta: { + target?: string; + section?: string; + alias?: string; + type?: 'wikilink' | 'link'; + isEmbed?: boolean; + } ) { const { target, section, alias } = MarkdownLink.analyzeLink(link); const newTarget = delta.target ?? target; @@ -46,21 +52,27 @@ export abstract class MarkdownLink { const newAlias = delta.alias ?? alias ?? ''; const sectionDivider = newSection ? '#' : ''; const aliasDivider = newAlias ? '|' : ''; - const embed = link.isEmbed ? '!' : ''; - if (link.type === 'wikilink') { + const embed = delta.isEmbed ?? link.isEmbed ? '!' : ''; + const type = delta.type ?? link.type; + if (type === 'wikilink') { return { newText: `${embed}[[${newTarget}${sectionDivider}${newSection}${aliasDivider}${newAlias}]]`, range: link.range, }; } - if (link.type === 'link') { + if (type === 'link') { + const defaultAlias = () => { + return `${newTarget}${sectionDivider}${newSection}`; + }; + const useAngles = + newTarget.indexOf(' ') > 0 || newSection.indexOf(' ') > 0; return { - newText: `${embed}[${newAlias}](${newTarget}${sectionDivider}${newSection})`, + newText: `${embed}[${newAlias ? newAlias : defaultAlias()}](${ + useAngles ? '<' : '' + }${newTarget}${sectionDivider}${newSection}${useAngles ? '>' : ''})`, range: link.range, }; } - throw new Error( - `Unexpected state: link of type ${link.type} is not supported` - ); + throw new Error(`Unexpected state: link of type ${type} is not supported`); } }