From cef8d2a5326ff74fc08de75e9884ac00df3620e7 Mon Sep 17 00:00:00 2001 From: Xinglan Liu Date: Thu, 11 Jul 2024 02:38:51 +0800 Subject: [PATCH] generate copy without wikilinks (#1365) * add generate standalone note command * fix embeded wikilinks * refactor convertLinksFormat function & add 4 user command interfaces * change user interface * modify createUpdateLinkEdit to accomplish convert * only images can be embedded * keey filename when using in page anchor * give a default value to alias in link format combination branch * add tests to createUpdateLinkEdit about changint links' type and isEmbed * get target from getIdentifier --------- Co-authored-by: Riccardo --- packages/foam-vscode/package.json | 8 + .../core/janitor/convert-links-format.test.ts | 71 +++++++ .../src/core/janitor/convert-links-format.ts | 83 ++++++++ .../janitor/generate-link-references.test.ts | 2 +- .../foam-vscode/src/core/janitor/index.ts | 1 + .../src/core/services/markdown-link.test.ts | 181 +++++++++++++++++ .../src/core/services/markdown-link.ts | 28 ++- .../commands/convert-links-format-in-note.ts | 188 ++++++++++++++++++ .../src/features/commands/index.ts | 1 + .../file-with-different-link-formats.md | 21 ++ 10 files changed, 575 insertions(+), 9 deletions(-) create mode 100644 packages/foam-vscode/src/core/janitor/convert-links-format.test.ts create mode 100644 packages/foam-vscode/src/core/janitor/convert-links-format.ts create mode 100644 packages/foam-vscode/src/features/commands/convert-links-format-in-note.ts create mode 100644 packages/foam-vscode/test-data/__scaffold__/file-with-different-link-formats.md diff --git a/packages/foam-vscode/package.json b/packages/foam-vscode/package.json index ae27dc969..b2e5a452c 100644 --- a/packages/foam-vscode/package.json +++ b/packages/foam-vscode/package.json @@ -346,6 +346,14 @@ "command": "foam-vscode.open-resource", "title": "Foam: Open Resource" }, + { + "command": "foam-vscode.convert-link-style-inplace", + "title": "Foam: convert link style in place" + }, + { + "command": "foam-vscode.convert-link-style-incopy", + "title": "Foam: convert link format in copy" + }, { "command": "foam-vscode.views.orphans.group-by:folder", "title": "Group By Folder", 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 new file mode 100644 index 000000000..b0c8b3eae --- /dev/null +++ b/packages/foam-vscode/src/core/janitor/convert-links-format.test.ts @@ -0,0 +1,71 @@ +import { convertLinkFormat } from '.'; +import { TEST_DATA_DIR } from '../../test/test-utils'; +import { MarkdownResourceProvider } from '../services/markdown-provider'; +import { Resource } from '../model/note'; +import { FoamWorkspace } from '../model/workspace'; +import { Logger } from '../utils/log'; +import fs from 'fs'; +import { URI } from '../model/uri'; +import { createMarkdownParser } from '../services/markdown-parser'; +import { FileDataStore } from '../../test/test-datastore'; + +Logger.setLevel('error'); + +describe('generateStdMdLink', () => { + let _workspace: FoamWorkspace; + // TODO slug must be reserved for actual slugs, not file names + const findBySlug = (slug: string): Resource => { + return _workspace + .list() + .find(res => res.uri.getName() === slug) as Resource; + }; + + beforeAll(async () => { + /** Use fs for reading files in units where vscode.workspace is unavailable */ + const readFile = async (uri: URI) => + (await fs.promises.readFile(uri.toFsPath())).toString(); + const dataStore = new FileDataStore( + readFile, + TEST_DATA_DIR.joinPath('__scaffold__').toFsPath() + ); + const parser = createMarkdownParser(); + const mdProvider = new MarkdownResourceProvider(dataStore, parser); + _workspace = await FoamWorkspace.fromProviders([mdProvider], dataStore); + }); + + it('initialised test graph correctly', () => { + expect(_workspace.list().length).toEqual(11); + }); + + it('can generate markdown links correctly', async () => { + const note = findBySlug('file-with-different-link-formats'); + const actual = note.links + .filter(link => link.type === 'wikilink') + .map(link => convertLinkFormat(link, 'link', _workspace, note)); + const expected: string[] = [ + '[first-document](first-document.md)', + '[second-document](second-document.md)', + '[[non-exist-file]]', + '[#one section]()', + '[another name]()', + '[an alias](first-document.md)', + '[first-document](first-document.md)', + ]; + expect(actual.length).toEqual(expected.length); + const _ = actual.map((LinkReplace, index) => { + expect(LinkReplace.newText).toEqual(expected[index]); + }); + }); + + it('can generate wikilinks correctly', async () => { + const note = findBySlug('file-with-different-link-formats'); + const actual = note.links + .filter(link => link.type === 'link') + .map(link => convertLinkFormat(link, 'wikilink', _workspace, note)); + const expected: string[] = ['[[first-document|file]]']; + expect(actual.length).toEqual(expected.length); + const _ = actual.map((LinkReplace, index) => { + expect(LinkReplace.newText).toEqual(expected[index]); + }); + }); +}); diff --git a/packages/foam-vscode/src/core/janitor/convert-links-format.ts b/packages/foam-vscode/src/core/janitor/convert-links-format.ts new file mode 100644 index 000000000..c85e0adc8 --- /dev/null +++ b/packages/foam-vscode/src/core/janitor/convert-links-format.ts @@ -0,0 +1,83 @@ +import { Resource, ResourceLink } from '../model/note'; +import { URI } from '../model/uri'; +import { Range } from '../model/range'; +import { FoamWorkspace } from '../model/workspace'; +import { isNone } from '../utils'; +import { MarkdownLink } from '../services/markdown-link'; + +export interface LinkReplace { + newText: string; + range: Range /* old range */; +} + +/** + * convert a link based on its workspace and the note containing it. + * According to targetFormat parameter to decide output format. If link.type === targetFormat, then it simply copy + * the rawText into LinkReplace. Therefore, it's recommended to filter before conversion. + * If targetFormat isn't supported, or the target resource pointed by link cannot be found, the function will throw + * exception. + * @param link + * @param targetFormat 'wikilink' | 'link' + * @param workspace + * @param note + * @returns LinkReplace { newText: string; range: Range; } + */ +export function convertLinkFormat( + link: ResourceLink, + targetFormat: 'wikilink' | 'link', + workspace: FoamWorkspace, + note: Resource | URI +): LinkReplace { + const resource = note instanceof URI ? workspace.find(note) : note; + const targetUri = workspace.resolveLink(resource, link); + /* If it's already the target format or a placeholder, no transformation happens */ + if (link.type === targetFormat || targetUri.scheme === 'placeholder') { + return { + newText: link.rawText, + range: link.range, + }; + } + + let { target, section, alias } = MarkdownLink.analyzeLink(link); + let sectionDivider = section ? '#' : ''; + + if (isNone(targetUri)) { + throw new Error( + `Unexpected state: link to: "${link.rawText}" is not resolvable` + ); + } + + const targetRes = workspace.find(targetUri); + let relativeUri = targetRes.uri.relativeTo(resource.uri.getDirectory()); + + if (targetFormat === 'wikilink') { + return MarkdownLink.createUpdateLinkEdit(link, { + target: workspace.getIdentifier(relativeUri), + type: 'wikilink', + }); + } + + if (targetFormat === 'link') { + /* if alias is empty, construct one as target#section */ + if (alias === '') { + /* in page anchor have no filename */ + if (relativeUri.getBasename() === resource.uri.getBasename()) { + target = ''; + } + alias = `${target}${sectionDivider}${section}`; + } + + /* if it's originally an embedded note, the markdown link shouldn't be embedded */ + 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/janitor/generate-link-references.test.ts b/packages/foam-vscode/src/core/janitor/generate-link-references.test.ts index 708f4be4c..eaebf7cc5 100644 --- a/packages/foam-vscode/src/core/janitor/generate-link-references.test.ts +++ b/packages/foam-vscode/src/core/janitor/generate-link-references.test.ts @@ -36,7 +36,7 @@ describe('generateLinkReferences', () => { }); it('initialised test graph correctly', () => { - expect(_workspace.list().length).toEqual(10); + expect(_workspace.list().length).toEqual(11); }); it('should add link references to a file that does not have them', async () => { diff --git a/packages/foam-vscode/src/core/janitor/index.ts b/packages/foam-vscode/src/core/janitor/index.ts index bded3032d..9e836e97a 100644 --- a/packages/foam-vscode/src/core/janitor/index.ts +++ b/packages/foam-vscode/src/core/janitor/index.ts @@ -1,2 +1,3 @@ export { generateLinkReferences } from './generate-link-references'; export { generateHeading } from './generate-headings'; +export { convertLinkFormat } from './convert-links-format'; 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..b81efed1b 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 remain 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`); } } diff --git a/packages/foam-vscode/src/features/commands/convert-links-format-in-note.ts b/packages/foam-vscode/src/features/commands/convert-links-format-in-note.ts new file mode 100644 index 000000000..1e2bd5c84 --- /dev/null +++ b/packages/foam-vscode/src/features/commands/convert-links-format-in-note.ts @@ -0,0 +1,188 @@ +import { commands, ExtensionContext, window, workspace, Uri } from 'vscode'; +import { isMdEditor } from '../../utils'; +import { Foam } from '../../core/model/foam'; +import { FoamWorkspace } from '../../core/model/workspace'; +import { fromVsCodeUri, toVsCodeRange } from '../../utils/vsc-utils'; +import { ResourceParser } from '../../core/model/note'; +import { IMatcher } from '../../core/services/datastore'; +import { convertLinkFormat } from '../../core/janitor'; + +type LinkFormat = 'wikilink' | 'link'; + +enum ConvertOption { + Wikilink2MDlink, + MDlink2Wikilink, +} + +interface IConfig { + from: string; + to: string; +} + +const Config: { [key in ConvertOption]: IConfig } = { + [ConvertOption.Wikilink2MDlink]: { + from: 'wikilink', + to: 'link', + }, + [ConvertOption.MDlink2Wikilink]: { + from: 'link', + to: 'wikilink', + }, +}; + +export default async function activate( + context: ExtensionContext, + foamPromise: Promise +) { + const foam = await foamPromise; + + /* + commands: + foam-vscode.convert-link-style-inplace + foam-vscode.convert-link-style-incopy + */ + context.subscriptions.push( + commands.registerCommand('foam-vscode.convert-link-style-inplace', () => { + return convertLinkAdapter( + foam.workspace, + foam.services.parser, + foam.services.matcher, + true + ); + }), + commands.registerCommand('foam-vscode.convert-link-style-incopy', () => { + return convertLinkAdapter( + foam.workspace, + foam.services.parser, + foam.services.matcher, + false + ); + }) + ); +} + +async function convertLinkAdapter( + fWorkspace: FoamWorkspace, + fParser: ResourceParser, + fMatcher: IMatcher, + isInPlace: boolean +) { + const convertOption = await pickConvertStrategy(); + if (!convertOption) { + window.showInformationMessage('Convert canceled'); + return; + } + + if (isInPlace) { + await convertLinkInPlace(fWorkspace, fParser, fMatcher, convertOption); + } else { + await convertLinkInCopy(fWorkspace, fParser, fMatcher, convertOption); + } +} + +async function pickConvertStrategy(): Promise { + const options = { + 'to wikilink': ConvertOption.MDlink2Wikilink, + 'to markdown link': ConvertOption.Wikilink2MDlink, + }; + return window.showQuickPick(Object.keys(options)).then(name => { + if (name) { + return Config[options[name]]; + } else { + return undefined; + } + }); +} + +/** + * convert links based on its workspace and the note containing it. + * Changes happen in-place + * @param fWorkspace + * @param fParser + * @param fMatcher + * @param convertOption + * @returns void + */ +async function convertLinkInPlace( + fWorkspace: FoamWorkspace, + fParser: ResourceParser, + fMatcher: IMatcher, + convertOption: IConfig +) { + const editor = window.activeTextEditor; + const doc = editor.document; + + if (!isMdEditor(editor) || !fMatcher.isMatch(fromVsCodeUri(doc.uri))) { + return; + } + // const eol = getEditorEOL(); + let text = doc.getText(); + + const resource = fParser.parse(fromVsCodeUri(doc.uri), text); + + const textReplaceArr = resource.links + .filter(link => link.type === convertOption.from) + .map(link => + convertLinkFormat( + link, + convertOption.to as LinkFormat, + fWorkspace, + resource + ) + ) + /* transform .range property into vscode range */ + .map(linkReplace => ({ + ...linkReplace, + range: toVsCodeRange(linkReplace.range), + })); + + /* reorder the array such that the later range comes first */ + textReplaceArr.sort((a, b) => b.range.start.compareTo(a.range.start)); + + await editor.edit(editorBuilder => { + textReplaceArr.forEach(edit => { + editorBuilder.replace(edit.range, edit.newText); + }); + }); +} + +/** + * convert links based on its workspace and the note containing it. + * Changes happen in a copy + * 1. prepare a copy file, and makt it the activeTextEditor + * 2. call to convertLinkInPlace + * @param fWorkspace + * @param fParser + * @param fMatcher + * @param convertOption + * @returns void + */ +async function convertLinkInCopy( + fWorkspace: FoamWorkspace, + fParser: ResourceParser, + fMatcher: IMatcher, + convertOption: IConfig +) { + const editor = window.activeTextEditor; + const doc = editor.document; + + if (!isMdEditor(editor) || !fMatcher.isMatch(fromVsCodeUri(doc.uri))) { + return; + } + // const eol = getEditorEOL(); + let text = doc.getText(); + + const resource = fParser.parse(fromVsCodeUri(doc.uri), text); + const basePath = doc.uri.path.split('/').slice(0, -1).join('/'); + + const fileUri = Uri.file( + `${ + basePath ? basePath + '/' : '' + }${resource.uri.getName()}.copy${resource.uri.getExtension()}` + ); + const encoder = new TextEncoder(); + await workspace.fs.writeFile(fileUri, encoder.encode(text)); + await window.showTextDocument(fileUri); + + await convertLinkInPlace(fWorkspace, fParser, fMatcher, convertOption); +} diff --git a/packages/foam-vscode/src/features/commands/index.ts b/packages/foam-vscode/src/features/commands/index.ts index 7e24a09b6..5ed245f07 100644 --- a/packages/foam-vscode/src/features/commands/index.ts +++ b/packages/foam-vscode/src/features/commands/index.ts @@ -10,3 +10,4 @@ export { default as openResource } from './open-resource'; export { default as updateGraphCommand } from './update-graph'; export { default as updateWikilinksCommand } from './update-wikilinks'; export { default as createNote } from './create-note'; +export { default as generateStandaloneNote } from './convert-links-format-in-note'; diff --git a/packages/foam-vscode/test-data/__scaffold__/file-with-different-link-formats.md b/packages/foam-vscode/test-data/__scaffold__/file-with-different-link-formats.md new file mode 100644 index 000000000..7c282c0da --- /dev/null +++ b/packages/foam-vscode/test-data/__scaffold__/file-with-different-link-formats.md @@ -0,0 +1,21 @@ +# File with different link formats + +markdown link [home page](https://foambubble.github.io/) + +wikilink to file [[first-document]]. + +markdown format link to local [file](first-document.md) + +embedded wikilink to file ![[second-document]]. + +wikilink to placeholder [[non-exist-file]] + +in-note anchor [[file-with-different-link-formats#one section]] + +alias to anchor [[file-with-different-link-formats#one section|another name]] + +alias [[first-document|an alias]] + +dupilcated wikilink to file [[first-document]] + +# one section \ No newline at end of file