diff --git a/packages/foam-vscode/package.json b/packages/foam-vscode/package.json index a90191fb2..c8770ea9d 100644 --- a/packages/foam-vscode/package.json +++ b/packages/foam-vscode/package.json @@ -578,11 +578,15 @@ "default": "full-card", "enum": [ "full-inline", - "full-card" + "full-card", + "content-inline", + "content-card" ], "enumDescriptions": [ "Include the section with title and style inline", - "Include the section with title and style it within a container" + "Include the section with title and style it within a container", + "Include the section without title and style inline", + "Include the section without title and style it within a container" ] }, "foam.graph.titleMaxLength": { diff --git a/packages/foam-vscode/src/features/preview/wikilink-embed.spec.ts b/packages/foam-vscode/src/features/preview/wikilink-embed.spec.ts index 948ec1cad..a335d3b7e 100644 --- a/packages/foam-vscode/src/features/preview/wikilink-embed.spec.ts +++ b/packages/foam-vscode/src/features/preview/wikilink-embed.spec.ts @@ -15,7 +15,7 @@ import { const parser = createMarkdownParser(); describe('Displaying included notes in preview', () => { - it('should render an included note in flat mode', async () => { + it('should render an included note in full inline mode', async () => { const note = await createFile('This is the text of note A', [ 'preview', 'note-a.md', @@ -47,7 +47,7 @@ describe('Displaying included notes in preview', () => { await deleteFile(note); }); - it('should render an included note in container mode', async () => { + it('should render an included note in full card mode', async () => { const note = await createFile('This is the text of note A', [ 'preview', 'note-a.md', @@ -75,7 +75,7 @@ describe('Displaying included notes in preview', () => { await deleteFile(note); }); - it('should render an included section', async () => { + it('should render an included section in full inline mode', async () => { // here we use createFile as the test note doesn't fill in // all the metadata we need const note = await createFile( @@ -121,7 +121,7 @@ This is the third section of note E await deleteFile(note); }); - it('should render an included section in container mode', async () => { + it('should render an included section in full card mode', async () => { const note = await createFile( ` # Section 1 @@ -163,6 +163,168 @@ This is the third section of note E await deleteFile(note); }); + it('should not render the title of a note in content inline mode', async () => { + const note = await createFile( + ` +# Title +## Section 1 + +This is the first section of note E`, + ['note-e.md'] + ); + const parser = createMarkdownParser([]); + const ws = new FoamWorkspace().set(parser.parse(note.uri, note.content)); + + await withModifiedFoamConfiguration( + CONFIG_EMBED_NOTE_IN_CONTAINER, + null, + async () => { + await withModifiedFoamConfiguration( + CONFIG_EMBED_NOTE_TYPE, + 'content-inline', + () => { + const md = markdownItWikilinkEmbed(MarkdownIt(), ws, parser); + + expect( + md.render(`This is the root node. + +![[note-e]]`) + ).toMatch( + `
This is the root node.
+This is the first section of note E
+` + ); + } + ); + } + ); + + await deleteFile(note); + }); + + it('should not render the title of a note in content card mode', async () => { + const note = await createFile( + `# Title +## Section 1 + +This is the first section of note E + `, + ['note-e.md'] + ); + const parser = createMarkdownParser([]); + const ws = new FoamWorkspace().set(parser.parse(note.uri, note.content)); + + await withModifiedFoamConfiguration( + CONFIG_EMBED_NOTE_IN_CONTAINER, + null, + async () => { + await withModifiedFoamConfiguration( + CONFIG_EMBED_NOTE_TYPE, + 'content-card', + () => { + const md = markdownItWikilinkEmbed(MarkdownIt(), ws, parser); + + const res = md.render(`This is the root node. ![[note-e.md]]`); + + expect(res).toContain('This is the root node'); + expect(res).toContain('embed-container-note'); + expect(res).toContain('Section 1'); + expect(res).toContain('This is the first section of note E'); + expect(res).not.toContain('Title'); + } + ); + } + ); + + await deleteFile(note); + }); + + it('should not render the section title, but still render subsection titles in content inline mode', async () => { + const note = await createFile( + `# Title + + +## Section 1 +This is the first section of note E + +### Subsection a +This is the first subsection of note E + `, + ['note-e.md'] + ); + const parser = createMarkdownParser([]); + const ws = new FoamWorkspace().set(parser.parse(note.uri, note.content)); + + await withModifiedFoamConfiguration( + CONFIG_EMBED_NOTE_IN_CONTAINER, + null, + async () => { + await withModifiedFoamConfiguration( + CONFIG_EMBED_NOTE_TYPE, + 'content-inline', + () => { + const md = markdownItWikilinkEmbed(MarkdownIt(), ws, parser); + + expect( + md.render(`This is the root node. + +![[note-e#Section 1]]`) + ).toMatch( + `This is the root node.
+This is the first section of note E
+This is the first subsection of note E
+` + ); + } + ); + } + ); + + await deleteFile(note); + }); + + it('should not render the subsection title in content mode if you link to it and regardless of its level', async () => { + const note = await createFile( + `# Title +## Section 1 +This is the first section of note E + +### Subsection a +This is the first subsection of note E`, + ['note-e.md'] + ); + const parser = createMarkdownParser([]); + const ws = new FoamWorkspace().set(parser.parse(note.uri, note.content)); + + await withModifiedFoamConfiguration( + CONFIG_EMBED_NOTE_IN_CONTAINER, + null, + async () => { + await withModifiedFoamConfiguration( + CONFIG_EMBED_NOTE_TYPE, + 'content-inline', + () => { + const md = markdownItWikilinkEmbed(MarkdownIt(), ws, parser); + + expect( + md.render(`This is the root node. + +![[note-e#Subsection a]]`) + ).toMatch( + `This is the root node.
+This is the first subsection of note E
+` + ); + } + ); + } + ); + + await deleteFile(note); + }); + it('should fallback to the bare text when the note is not found', () => { const md = markdownItWikilinkEmbed( MarkdownIt(), @@ -187,14 +349,27 @@ This is the third section of note E const ws = new FoamWorkspace() .set(parser.parse(noteA.uri, noteA.content)) .set(parser.parse(noteB.uri, noteB.content)); - const md = markdownItWikilinkEmbed(MarkdownIt(), ws, parser); - const res = md.render(noteBText); - expect(res).toContain('This is the text of note B which includes'); - expect(res).toContain('This is the text of note A which includes'); - expect(res).toContain('Cyclic link detected for wikilink'); + await withModifiedFoamConfiguration( + CONFIG_EMBED_NOTE_IN_CONTAINER, + null, + async () => { + await withModifiedFoamConfiguration( + CONFIG_EMBED_NOTE_TYPE, + 'full-card', + () => { + const md = markdownItWikilinkEmbed(MarkdownIt(), ws, parser); + const res = md.render(noteBText); + + expect(res).toContain('This is the text of note B which includes'); + expect(res).toContain('This is the text of note A which includes'); + expect(res).toContain('Cyclic link detected for wikilink'); + } + ); + } + ); - deleteFile(noteA); - deleteFile(noteB); + await deleteFile(noteA); + await deleteFile(noteB); }); }); diff --git a/packages/foam-vscode/src/features/preview/wikilink-embed.ts b/packages/foam-vscode/src/features/preview/wikilink-embed.ts index a25232e6e..ae8a57428 100644 --- a/packages/foam-vscode/src/features/preview/wikilink-embed.ts +++ b/packages/foam-vscode/src/features/preview/wikilink-embed.ts @@ -50,9 +50,14 @@ export const markdownItWikilinkEmbed = ( switch (includedNote.type) { case 'note': { - const { noteScope: _, noteStyle } = retrieveNoteConfig(); + const { noteScope, noteStyle } = retrieveNoteConfig(); - const extractor: EmbedNoteExtractor = fullExtractor; + const extractor: EmbedNoteExtractor = + noteScope === 'full' + ? fullExtractor + : noteScope === 'content' + ? contentExtractor + : fullExtractor; const formatter: EmbedNoteFormatter = noteStyle === 'card' @@ -162,6 +167,32 @@ function fullExtractor( return noteText; } +function contentExtractor( + note: Resource, + parser: ResourceParser, + workspace: FoamWorkspace +): string { + let noteText = readFileSync(note.uri.toFsPath()).toString(); + let section = Resource.findSection(note, note.uri.fragment); + if (!note.uri.fragment) { + // if there's no fragment(section), the wikilink is linking to the entire note, + // in which case we need to remove the title. We could just use rows.shift() + // but should the note start with blank lines, it will only remove the first blank line + // leaving the title + // A better way is to find where the actual title starts by assuming it's at section[0] + // then we treat it as the same case as link to a section + section = note.sections.length ? note.sections[0] : null; + } + let rows = noteText.split('\n'); + if (isSome(section)) { + rows = rows.slice(section.range.start.line, section.range.end.line); + } + rows.shift(); + noteText = rows.join('\n'); + noteText = withLinksRelativeToWorkspaceRoot(noteText, parser, workspace); + return noteText; +} + /** * A type of function that renders note content with the desired style in html */