Compare commits

...

21 Commits

Author SHA1 Message Date
Riccardo Ferretti
8bd679c751 v0.25.5 2023-11-30 19:19:26 +01:00
Riccardo Ferretti
8986ee286c Preparation for next release 2023-11-30 19:19:03 +01:00
Riccardo
51b1af1981 Using note title in preview panel (#1309)
See conversation in https://github.com/foambubble/foam/pull/1150
2023-11-30 19:17:50 +01:00
Daniel Carosone
4276e8043f extension descriptions now reflect .vscode dir (#1302)
* extension descriptions now reflect .vscode dir

including that image pasting is now supported natively

* Markdown AiO is still recommended

---------

Co-authored-by: Daniel Carosone <dan@geek.com.au>
2023-10-26 10:24:51 +02:00
Riccardo Ferretti
8d1e9b15ce Readding markdown-all-in-one in extension list and fixing script 2023-10-26 10:23:45 +02:00
jonathan berger
bfcfad32e8 Add more detail to Support for sections. (#1296)
* Add more detail to Support for `sections`.

* Removed extra quote

---------

Co-authored-by: Riccardo <code@riccardoferretti.com>
2023-10-25 12:53:00 +02:00
allcontributors[bot]
abe18cc961 add dcarosone as a contributor for doc (#1301)
* update docs/index.md [skip ci]

* update readme.md [skip ci]

* update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2023-10-25 11:43:29 +02:00
Riccardo Ferretti
9490aa2dad Fixed typo 2023-10-25 11:36:34 +02:00
Riccardo Ferretti
6c3f4588b3 Explicitly create .vscode folder in sync script 2023-10-25 11:26:07 +02:00
Riccardo Ferretti
21b8c5b827 Updated default extensions and settings for foam docs and template
Also monitoring the folder to trigger the appropriate sync job
2023-10-25 11:22:46 +02:00
Daniel Carosone
c66ed74aea replicate .vscode dir to template (#1300)
fixes #1299

Co-authored-by: Daniel Carosone <dan@geek.com.au>
2023-10-25 11:17:43 +02:00
Riccardo Ferretti
3876811fb6 v0.25.4 2023-10-19 17:41:13 +02:00
Riccardo Ferretti
d9299ee9d4 Prepare for next release 2023-10-19 17:40:29 +02:00
Joe Taber
23e21a62f3 Copy assets to foam-template repo (#1238) 2023-10-12 17:22:38 +02:00
Riccardo
e7c8d5a4eb Added support for linking to sections in current file (#1289)
And improved support for links to sections that need to be slugified
2023-10-12 17:01:13 +02:00
Daniel Wang
3ef1b69b2e Fix embedded notes not generating proper reference links (#1286) 2023-09-25 10:09:13 +02:00
Riccardo Ferretti
5859b2a9c6 v0.25.3 2023-09-07 13:07:26 +02:00
Riccardo Ferretti
54086fdd7e Preparation for next release 2023-09-07 13:07:06 +02:00
Daniel Wang
a308dfd109 Handle a case where the embedded note does not exist (#1283) 2023-09-07 09:03:25 +02:00
Daniel Wang
e327115673 Feat #879: Note Embedding Syntax (#1281)
* Removed deprecated preview.embedNoteInContainer

* Integrate explicit type modifiers to wikilink syntax

* Add to documentation
2023-09-06 00:08:56 +02:00
Riccardo Ferretti
c019767476 Fix #1242 - attachments are not considered in the context of orphan notes
A note that only has outbound links to attachments is now still considered an orphan.
2023-09-03 11:46:31 +02:00
27 changed files with 467 additions and 213 deletions

View File

@@ -1058,6 +1058,15 @@
"contributions": [
"doc"
]
},
{
"login": "dcarosone",
"name": "Daniel Carosone",
"avatar_url": "https://avatars.githubusercontent.com/u/11495017?v=4",
"profile": "https://github.com/dcarosone",
"contributions": [
"doc"
]
}
],
"contributorsPerLine": 7,

View File

@@ -6,6 +6,8 @@ on:
- master
paths:
- docs/user/**/*
- docs/.vscode/**/*
- docs/assets/**/*
workflow_dispatch:
jobs:
@@ -20,11 +22,15 @@ jobs:
- uses: actions/checkout@v3
with:
path: foam
- name: Copy and fixup user docs files
- name: Copy and fixup user docs files
id: copy
run: |
rm -r foam-template/docs
rm -r foam-template/assets
rm -r foam-template/.vscode
cp -r foam/docs/user foam-template/docs
cp -r foam/docs/assets foam-template/assets
cp -r foam/docs/.vscode foam-template/.vscode
# Strip autogenerated wikileaks references because
# they are not an appropriate default user experience.

View File

@@ -5,17 +5,11 @@
// Foam's own extension
"foam.foam-vscode",
// Prettier for auto formatting code
"esbenp.prettier-vscode",
// GitLens for seeing version history inline
"eamodio.gitlens",
// Tons of markdown goodies (lists, tables of content, so much more)
"yzhang.markdown-all-in-one",
// Graph visualizer
"tchayen.markdown-links",
// Prettier for auto formatting code
"esbenp.prettier-vscode",
// Understated grayscale theme (light and dark variants)
"philipbe.theme-gray-matter"

View File

@@ -1,4 +0,0 @@
{
"purpose": "this file exists to tell the foam-vscode plugin that it's currently in a foam workspace",
"future": "we may use this for custom configuration"
}

View File

@@ -3,6 +3,6 @@
[
{
"key": "cmd+shift+n",
"command": "vscodeMarkdownNotes.newNote"
"command": "foam-vscode.create-note"
}
]

View File

@@ -5,7 +5,6 @@
"editor.overviewRulerBorder": false,
"editor.lineHeight": 24,
"foam.edit.linkReferenceDefinitions": "withExtensions",
"vscodeMarkdownNotes.noteCompletionConvention": "noExtension",
"[markdown]": {
"editor.quickSuggestions": {
"other": true,
@@ -13,21 +12,11 @@
"strings": false
}
},
"cSpell.language": "en-GB",
"git.enableSmartCommit": true,
"git.postCommitCommand": "sync",
"spellright.language": [
"en"
],
"spellright.documentTypes": [
"markdown",
"plaintext"
],
"files.exclude": {
"_site/**": true
},
"files.insertFinalNewline": true,
"markdown.styles": [
".vscode/custom-tag-style.css"
]
"markdown.styles": [".vscode/custom-tag-style.css"]
}

View File

@@ -1 +0,0 @@
Backlinking

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 KiB

View File

@@ -255,6 +255,7 @@ If that sounds like something you're interested in, I'd love to have you along o
<td align="center" valign="top" width="14.28%"><a href="https://amnesiak.org/me"><img src="https://avatars.githubusercontent.com/u/952059?v=4?s=60" width="60px;" alt="Tony Cheneau"/><br /><sub><b>Tony Cheneau</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=tcheneau" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/nicholas-l"><img src="https://avatars.githubusercontent.com/u/12977174?v=4?s=60" width="60px;" alt="Nicholas Latham"/><br /><sub><b>Nicholas Latham</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=nicholas-l" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://thara.dev"><img src="https://avatars.githubusercontent.com/u/1532891?v=4?s=60" width="60px;" alt="Tomochika Hara"/><br /><sub><b>Tomochika Hara</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=thara" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dcarosone"><img src="https://avatars.githubusercontent.com/u/11495017?v=4?s=60" width="60px;" alt="Daniel Carosone"/><br /><sub><b>Daniel Carosone</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dcarosone" title="Documentation">📖</a></td>
</tr>
</tbody>
</table>

View File

@@ -0,0 +1,29 @@
# Built-In Note Embedding Types
When embedding a note, there are a few ways to modify the scope of the content as well as its display style. The following are Foam keywords that are used to describe note embedding.
Note, this only applies to note embedding, not embedding of attachments or images.
![Note Embed Types GIF](../../assets/images/note-embed-type-demo.gif)
## Scope
- `full` - the entire note in the case of `![[note]]` or the entire section in the case of `![[note#section1]]`
- `content` - everything excluding the title of the section. So the entire note minus the title for `![[note]]`, or the entire section minus the section header for `![[note#section1]]`
## Style
- `card` - outlines the embedded note with a border
- `inline` - adds the note continuously as if the text were part of the calling note
## Default Setting
Foam expresses note display type as `<scope>-<style>`.
By default, Foam configures note embedding to be `full-card`. That is, whenever the standard embedding syntax is used, `![[note]]`, the note will have `full` scope and `card` style display. This setting is stored under `foam.preview.embedNoteStyle` and can be modified.
## Explicit Modifiers
Prepend the wikilink with one of the scope or style keywords, or a combination of the two to explicitly modify a note embedding if you would like to override the default setting.
For example, given your `foam.embedNoteStyle` is set to `content-card`, embedding a note with standard syntax `![[note-a]]` would show a bordered note without its title. Say, for a specific `note-b` you would like to display the title. You can simply use one of the above keywords to override your default setting like so: `full![[note-b]]`. In this case, `full` overrides the default `content` scope and because a style is not specified, it falls back to the default style setting, `card`. If you would like it to be inline, override that as well: `full-inline![[note-b]]`.

View File

@@ -8,13 +8,16 @@ Including a note can be done by adding an `!` before a wikilink definition. For
## Custom styling
Displaying the inclusion of notes allows for some custom styling, see [[custom-markdown-preview-styles]]
To modify how an embedded note looks and the scope of its content, see [[built-in-note-embedding-types]]
For more fine-grained custom styling, see [[custom-markdown-preview-styles]]
## Future possibilities
Work on this feature is evolving and progressing. See the [[inclusion-of-notes]] proposal for the current discussion.
[//begin]: # "Autogenerated link references for markdown compatibility"
[custom-markdown-preview-styles]: custom-markdown-preview-styles.md "Custom Markdown Preview Styles"
[inclusion-of-notes]: ../../dev/proposals/inclusion-of-notes.md "Inclusion of notes Proposal "
[//end]: # "Autogenerated link references"
[//begin]: # 'Autogenerated link references for markdown compatibility'
[built-in-note-embedding-types]: built-in-note-embedding-types.md 'Built-In Note Embedding Types'
[custom-markdown-preview-styles]: custom-markdown-preview-styles.md 'Custom Markdown Preview Styles'
[inclusion-of-notes]: ../../dev/proposals/inclusion-of-notes.md 'Inclusion of notes Proposal '
[//end]: # 'Autogenerated link references'

View File

@@ -20,7 +20,10 @@ Remember, with `CTRL/CMD+click` on a wikilink you can navigate to the note, or c
## Support for sections
Foam supports autocompletion, navigation, embedding and diagnostics for note sections. Just use the standard wiki syntax of `[[resource#Section Title]]`.
Foam supports autocompletion, navigation, embedding and diagnostics for note sections. Just use the standard wiki syntax of `[[resource#Section Title]]`.
- If it's an external file, `[your link will need the filename](other-file.md#that-section-I-want-to-link-to)`, but
- if it's an anchor within the same document, `[you just need an octothorpe and the section name](#that-section-above)`.
- Doesn't matter what heading-level the anchor is; whether you're linking to an `H1` like `# MEN WALK ON MOON` or an `H2` like `## Astronauts Land on Plain`, the link syntax uses a single octothorpe: `[Walk!](#men-walk-on-moon)` and `[Land!](#astronauts-land-on-plain-collect-rocks-plant-flag)`. Autocomplete is your friend here.
## Markdown compatibility

View File

@@ -6,11 +6,11 @@ This list is subject to change.
- [Foam for VSCode](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode) (alpha)
- [Markdown All In One](https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one)
- [Paste Image](https://marketplace.visualstudio.com/items?itemName=mushan.vscode-paste-image)
- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
## Extensions For Additional Features
These extensions are not (yet?) defined in `.vscode/extensions.json`, but have been used by others and shown to play nice with Foam.
These extensions are not defined in `.vscode/extensions.json`, but have been used by others and shown to play nice with Foam.
- [Emojisense](https://marketplace.visualstudio.com/items?itemName=bierner.emojisense)
- [Markdown Emoji](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-emoji) (adds `:smile:` syntax, works with emojisense to provide autocomplete for this syntax)

View File

@@ -2,8 +2,12 @@
This #recipe allows you to paste images on to your notes.
You can directly link and paste images that are copied to the clipboard using either the [Paste
Image](https://marketplace.visualstudio.com/items?itemName=mushan.vscode-paste-image)
extension, or the [Markdown Image](https://marketplace.visualstudio.com/items?itemName=hancel.markdown-image) extension.
VScode (since
[1.79](https://code.visualstudio.com/updates/v1_79#_copy-external-media-files-into-workspace-on-drop-or-paste-for-markdown))
now has the ability to paste images from the clipboard, or drag-and-drop image
files, directly into markdown documents. The file will be created in the
workspace, and a link generated in Markdown format.
The former does not have MDX support (yet), the latter does.
VSCode settings under `Markdown > Copy Files` and `Markdown > Editor > Drop` can
be used to configure where the files get placed in your workspace, how they're
named, how conflicts with existing files are handled, and more.

View File

@@ -4,5 +4,5 @@
],
"npmClient": "yarn",
"useWorkspaces": true,
"version": "0.25.2"
"version": "0.25.5"
}

View File

@@ -4,6 +4,27 @@ All notable changes to the "foam-vscode" extension will be documented in this fi
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
## [0.25.5] - 2023-11-30
Fixes and Improvements:
- Using note title in preview (#1309)
## [0.25.4] - 2023-09-19
Fixes and Improvements:
- Added support for linking sections within same document (#1289)
- Fixed note embedding bug (#1286 - thanks @badsketch)
## [0.25.3] - 2023-09-07
Fixes and Improvements:
- Fixed incorrect handling of embedding of non-existing notes (#1283 - thanks @badsketch)
- Introduced Note Embedding Sytanx (#1281 - thanks @badsketch)
- Attachments are not considered when computing orphan notes (#1242)
## [0.25.2] - 2023-09-02
Fixes and Improvements:

View File

@@ -8,7 +8,7 @@
"type": "git"
},
"homepage": "https://github.com/foambubble/foam",
"version": "0.25.2",
"version": "0.25.5",
"license": "MIT",
"publisher": "foam",
"engines": {
@@ -609,12 +609,6 @@
],
"description": "Whether or not to navigate to the target daily note when a daily note snippet is selected."
},
"foam.preview.embedNoteInContainer": {
"type": "boolean",
"default": true,
"description": "Wrap embedded notes in a container when displayed in preview panel",
"deprecationMessage": "*DEPRECATED* use foam.preview.embedNoteType instead."
},
"foam.preview.embedNoteType": {
"type": "string",
"default": "full-card",

View File

@@ -4,7 +4,14 @@ import { FoamWorkspace } from '../model/workspace';
import { IDisposable } from '../common/lifecycle';
import { ResourceProvider } from '../model/provider';
const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp'];
export const imageExtensions = [
'.png',
'.jpg',
'.jpeg',
'.gif',
'.svg',
'.webp',
];
const asResource = (uri: URI): Resource => {
const type = imageExtensions.includes(uri.getExtension())

View File

@@ -316,4 +316,40 @@ describe('Generation of markdown references', () => {
'../dir3/page-c.md',
]);
});
it('should generate links for embedded notes that are formatted properly', () => {
const workspace = createTestWorkspace();
const noteA = createNoteFromMarkdown(
'Link to ![[page-b]] and [[page-c]]',
'/dir1/page-a.md'
);
workspace
.set(noteA)
.set(createNoteFromMarkdown('Content of note B', '/dir2/page-b.md'))
.set(createNoteFromMarkdown('Content of note C', '/dir3/page-c.md'));
const references = createMarkdownReferences(workspace, noteA.uri, true);
expect(references.map(r => [r.url, r.label])).toEqual([
['../dir2/page-b.md', 'page-b'],
['../dir3/page-c.md', 'page-c'],
]);
});
it('should not generate links for placeholders', () => {
const workspace = createTestWorkspace();
const noteA = createNoteFromMarkdown(
'Link to ![[page-b]] and [[page-c]] and [[does-not-exist]] and ![[does-not-exist-either]]',
'/dir1/page-a.md'
);
workspace
.set(noteA)
.set(createNoteFromMarkdown('Content of note B', '/dir2/page-b.md'))
.set(createNoteFromMarkdown('Content of note C', '/dir3/page-c.md'));
const references = createMarkdownReferences(workspace, noteA.uri, true);
expect(references.map(r => r.url)).toEqual([
'../dir2/page-b.md',
'../dir3/page-c.md',
]);
});
});

View File

@@ -139,10 +139,12 @@ export function createMarkdownReferences(
// [wikilink-text]: path/to/file.md "Page title"
return {
label:
link.rawText.indexOf('[[') > -1
? link.rawText.substring(2, link.rawText.length - 2)
: link.rawText,
// embedded looks like ![[note-a]]
// regular note looks like [[note-a]]
label: link.rawText.substring(
link.isEmbed ? 3 : 2,
link.rawText.length - 2
),
url: relativeUri.path,
title: target.title,
};

View File

@@ -1,12 +1,14 @@
import * as vscode from 'vscode';
import { Foam } from '../../core/model/foam';
import { createMatcherAndDataStore } from '../../services/editor';
import { getOrphansConfig } from '../../settings';
import { getAttachmentsExtensions, getOrphansConfig } from '../../settings';
import { GroupedResourcesTreeDataProvider } from './utils/grouped-resources-tree-data-provider';
import { ResourceTreeItem, UriTreeItem } from './utils/tree-view-utils';
import { IMatcher } from '../../core/services/datastore';
import { FoamWorkspace } from '../../core/model/workspace';
import { FoamGraph } from '../../core/model/graph';
import { URI } from '../../core/model/uri';
import { imageExtensions } from '../../core/services/attachment-provider';
const EXCLUDE_TYPES = ['image', 'attachment'];
export default async function activate(
@@ -66,6 +68,13 @@ export class OrphanTreeView extends GroupedResourcesTreeDataProvider {
.filter(
uri =>
!EXCLUDE_TYPES.includes(this.workspace.find(uri)?.type) &&
this.graph.getConnections(uri).length === 0
this.graph.getBacklinks(uri).length === 0 &&
this.graph.getLinks(uri).filter(c => !isAttachment(c.target))
.length === 0
);
}
function isAttachment(uri: URI) {
const ext = [...getAttachmentsExtensions(), ...imageExtensions];
return ext.includes(uri.getExtension());
}

View File

@@ -9,7 +9,6 @@ import {
import {
default as markdownItWikilinkEmbed,
CONFIG_EMBED_NOTE_TYPE,
CONFIG_EMBED_NOTE_IN_CONTAINER,
} from './wikilink-embed';
const parser = createMarkdownParser();
@@ -22,25 +21,19 @@ describe('Displaying included notes in preview', () => {
]);
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,
'full-inline',
() => {
const md = markdownItWikilinkEmbed(MarkdownIt(), ws, parser);
CONFIG_EMBED_NOTE_TYPE,
'full-inline',
() => {
const md = markdownItWikilinkEmbed(MarkdownIt(), ws, parser);
expect(
md.render(`This is the root node.
expect(
md.render(`This is the root node.
![[note-a]]`)
).toMatch(
`<p>This is the root node.</p>
).toMatch(
`<p>This is the root node.</p>
<p><p>This is the text of note A</p>
</p>`
);
}
);
}
);
@@ -55,21 +48,15 @@ describe('Displaying included notes in preview', () => {
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,
'full-card',
() => {
const md = markdownItWikilinkEmbed(MarkdownIt(), ws, parser);
CONFIG_EMBED_NOTE_TYPE,
'full-card',
() => {
const md = markdownItWikilinkEmbed(MarkdownIt(), ws, parser);
const res = md.render(`This is the root node. ![[note-a]]`);
expect(res).toContain('This is the root node');
expect(res).toContain('embed-container-note');
expect(res).toContain('This is the text of note A');
}
);
const res = md.render(`This is the root node. ![[note-a]]`);
expect(res).toContain('This is the root node');
expect(res).toContain('embed-container-note');
expect(res).toContain('This is the text of note A');
}
);
await deleteFile(note);
@@ -96,24 +83,18 @@ This is the third section of note E
const md = markdownItWikilinkEmbed(MarkdownIt(), ws, parser);
await withModifiedFoamConfiguration(
CONFIG_EMBED_NOTE_IN_CONTAINER,
null,
async () => {
await withModifiedFoamConfiguration(
CONFIG_EMBED_NOTE_TYPE,
'full-inline',
() => {
expect(
md.render(`This is the root node.
CONFIG_EMBED_NOTE_TYPE,
'full-inline',
() => {
expect(
md.render(`This is the root node.
![[note-e#Section 2]]`)
).toMatch(
`<p>This is the root node.</p>
).toMatch(
`<p>This is the root node.</p>
<p><h1>Section 2</h1>
<p>This is the second section of note E</p>
</p>`
);
}
);
}
);
@@ -139,24 +120,18 @@ This is the third section of note E
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,
'full-card',
() => {
const md = markdownItWikilinkEmbed(MarkdownIt(), ws, parser);
CONFIG_EMBED_NOTE_TYPE,
'full-card',
() => {
const md = markdownItWikilinkEmbed(MarkdownIt(), ws, parser);
const res = md.render(
`This is the root node. ![[note-e-container#Section 3]]`
);
expect(res).toContain('This is the root node');
expect(res).toContain('embed-container-note');
expect(res).toContain('Section 3');
expect(res).toContain('This is the third section of note E');
}
const res = md.render(
`This is the root node. ![[note-e-container#Section 3]]`
);
expect(res).toContain('This is the root node');
expect(res).toContain('embed-container-note');
expect(res).toContain('Section 3');
expect(res).toContain('This is the third section of note E');
}
);
@@ -176,26 +151,20 @@ This is the first section of note E`,
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);
CONFIG_EMBED_NOTE_TYPE,
'content-inline',
() => {
const md = markdownItWikilinkEmbed(MarkdownIt(), ws, parser);
expect(
md.render(`This is the root node.
expect(
md.render(`This is the root node.
![[note-e]]`)
).toMatch(
`<p>This is the root node.</p>
).toMatch(
`<p>This is the root node.</p>
<p><h2>Section 1</h2>
<p>This is the first section of note E</p>
</p>`
);
}
);
}
);
@@ -216,24 +185,18 @@ This is the first section of note E
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);
CONFIG_EMBED_NOTE_TYPE,
'content-card',
() => {
const md = markdownItWikilinkEmbed(MarkdownIt(), ws, parser);
const res = md.render(`This is the root node. ![[note-e.md]]`);
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');
}
);
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');
}
);
@@ -257,27 +220,21 @@ This is the first subsection of note E
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);
CONFIG_EMBED_NOTE_TYPE,
'content-inline',
() => {
const md = markdownItWikilinkEmbed(MarkdownIt(), ws, parser);
expect(
md.render(`This is the root node.
expect(
md.render(`This is the root node.
![[note-e#Section 1]]`)
).toMatch(
`<p>This is the root node.</p>
).toMatch(
`<p>This is the root node.</p>
<p><p>This is the first section of note E</p>
<h3>Subsection a</h3>
<p>This is the first subsection of note E</p>
</p>`
);
}
);
}
);
@@ -299,25 +256,19 @@ This is the first subsection of note E`,
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);
CONFIG_EMBED_NOTE_TYPE,
'content-inline',
() => {
const md = markdownItWikilinkEmbed(MarkdownIt(), ws, parser);
expect(
md.render(`This is the root node.
expect(
md.render(`This is the root node.
![[note-e#Subsection a]]`)
).toMatch(
`<p>This is the root node.</p>
).toMatch(
`<p>This is the root node.</p>
<p><p>This is the first subsection of note E</p>
</p>`
);
}
);
}
);
@@ -325,6 +276,82 @@ This is the first subsection of note E`,
await deleteFile(note);
});
it('should allow a note embedding type to be overridden if a modifier is passed in', async () => {
const note = await createFile(
`
# Section 1
This is the first section of note E
# Section 2
This is the second section of note E
# Section 3
This is the third section of note E
`,
['note-e.md']
);
const parser = createMarkdownParser([]);
const ws = new FoamWorkspace().set(parser.parse(note.uri, note.content));
const md = markdownItWikilinkEmbed(MarkdownIt(), ws, parser);
await withModifiedFoamConfiguration(
CONFIG_EMBED_NOTE_TYPE,
'full-inline',
() => {
expect(
md.render(`This is the root node.
content![[note-e#Section 2]]
full![[note-e#Section 3]]`)
).toMatch(
`<p>This is the root node.</p>
<p><p>This is the second section of note E</p>
</p>
<p><h1>Section 3</h1>
<p>This is the third section of note E</p>
</p>
`
);
}
);
await deleteFile(note);
});
it('should allow a note embedding type to be overridden if two modifiers are passed in', async () => {
const note = await createFile(
`
# Section 1
This is the first section of note E
# Section 2
This is the second section of note E
`,
['note-e.md']
);
const parser = createMarkdownParser([]);
const ws = new FoamWorkspace().set(parser.parse(note.uri, note.content));
const md = markdownItWikilinkEmbed(MarkdownIt(), ws, parser);
await withModifiedFoamConfiguration(
CONFIG_EMBED_NOTE_TYPE,
'full-inline',
() => {
const res = md.render(`This is the root node.
content-card![[note-e#Section 2]]`);
expect(res).toContain('This is the root node');
expect(res).toContain('embed-container-note');
expect(res).toContain('This is the second section of note E');
expect(res).not.toContain('Section 2');
}
);
await deleteFile(note);
});
it('should fallback to the bare text when the note is not found', () => {
const md = markdownItWikilinkEmbed(
MarkdownIt(),
@@ -337,6 +364,27 @@ This is the first subsection of note E`,
);
});
it('should render the bare text for an embedded note that is embedding a note that is not found', async () => {
const note = await createFile(
'This is the text of note A which includes ![[does-not-exist]]',
['note.md']
);
const ws = new FoamWorkspace().set(parser.parse(note.uri, note.content));
await withModifiedFoamConfiguration(
CONFIG_EMBED_NOTE_TYPE,
'full-inline',
() => {
const md = markdownItWikilinkEmbed(MarkdownIt(), ws, parser);
expect(md.render(`This is the root node. ![[note]]`)).toMatch(
`<p>This is the root node. <p>This is the text of note A which includes ![[does-not-exist]]</p>
</p>`
);
}
);
});
it('should display a warning in case of cyclical inclusions', async () => {
const noteA = await createFile(
'This is the text of note A which includes ![[note-b]]',
@@ -351,21 +399,15 @@ This is the first subsection of note E`,
.set(parser.parse(noteB.uri, noteB.content));
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);
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');
}
);
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');
}
);

View File

@@ -1,4 +1,8 @@
import { retrieveNoteConfig } from './wikilink-embed';
import {
WIKILINK_EMBED_REGEX,
WIKILINK_EMBED_REGEX_GROUPS,
retrieveNoteConfig,
} from './wikilink-embed';
import * as config from '../../services/config';
describe('Wikilink Note Embedding', () => {
@@ -6,25 +10,81 @@ describe('Wikilink Note Embedding', () => {
jest.clearAllMocks();
});
describe('Wikilink Parsing', () => {
it('should match a wikilink item including a modifier and wikilink', () => {
// no configuration
expect('![[note-a]]').toMatch(WIKILINK_EMBED_REGEX);
// one of the configurations
expect('full![[note-a]]').toMatch(WIKILINK_EMBED_REGEX);
expect('content![[note-a]]').toMatch(WIKILINK_EMBED_REGEX);
expect('inline![[note-a]]').toMatch(WIKILINK_EMBED_REGEX);
expect('card![[note-a]]').toMatch(WIKILINK_EMBED_REGEX);
// any combination of configurations
expect('full-inline![[note-a]]').toMatch(WIKILINK_EMBED_REGEX);
expect('full-card![[note-a]]').toMatch(WIKILINK_EMBED_REGEX);
expect('content-inline![[note-a]]').toMatch(WIKILINK_EMBED_REGEX);
expect('content-card![[note-a]]').toMatch(WIKILINK_EMBED_REGEX);
});
it('should only match the wikilink if there are unrecognized keywords', () => {
const match1 = 'random-word![[note-a]]'.match(WIKILINK_EMBED_REGEX);
expect(match1[0]).toEqual('![[note-a]]');
expect(match1[1]).toEqual('![[note-a]]');
const match2 = 'foo![[note-a#section 1]]'.match(WIKILINK_EMBED_REGEX);
expect(match2[0]).toEqual('![[note-a#section 1]]');
expect(match2[1]).toEqual('![[note-a#section 1]]');
});
it('should group the wikilink into modifier and wikilink', () => {
const match1 = 'content![[note-a]]'.match(WIKILINK_EMBED_REGEX_GROUPS);
expect(match1[0]).toEqual('content![[note-a]]');
expect(match1[1]).toEqual('content');
expect(match1[2]).toEqual('note-a');
const match2 = 'full-inline![[note-a#section 1]]'.match(
WIKILINK_EMBED_REGEX_GROUPS
);
expect(match2[0]).toEqual('full-inline![[note-a#section 1]]');
expect(match2[1]).toEqual('full-inline');
expect(match2[2]).toEqual('note-a#section 1');
const match3 = '![[note-a#section 1]]'.match(WIKILINK_EMBED_REGEX_GROUPS);
expect(match3[0]).toEqual('![[note-a#section 1]]');
expect(match3[1]).toEqual(undefined);
expect(match3[2]).toEqual('note-a#section 1');
});
});
describe('Config Parsing', () => {
it('should use preview.embedNoteType if deprecated preview.embedNoteInContainer not used', () => {
it('should use preview.embedNoteType if an explicit modifier is not passed in', () => {
jest
.spyOn(config, 'getFoamVsCodeConfig')
.mockReturnValueOnce('full-card')
.mockReturnValueOnce(false);
.mockReturnValueOnce('full-card');
const { noteScope, noteStyle } = retrieveNoteConfig();
const { noteScope, noteStyle } = retrieveNoteConfig(undefined);
expect(noteScope).toEqual('full');
expect(noteStyle).toEqual('card');
});
it('should use preview.embedNoteInContainer if set', () => {
it('should use explicit modifier over user settings if passed in', () => {
jest
.spyOn(config, 'getFoamVsCodeConfig')
.mockReturnValueOnce('full-inline')
.mockReturnValueOnce(true);
.mockReturnValueOnce('full-inline')
.mockReturnValueOnce('full-inline');
const { noteScope, noteStyle } = retrieveNoteConfig();
let { noteScope, noteStyle } = retrieveNoteConfig('content-card');
expect(noteScope).toEqual('content');
expect(noteStyle).toEqual('card');
({ noteScope, noteStyle } = retrieveNoteConfig('content'));
expect(noteScope).toEqual('content');
expect(noteStyle).toEqual('inline');
({ noteScope, noteStyle } = retrieveNoteConfig('card'));
expect(noteScope).toEqual('full');
expect(noteStyle).toEqual('card');
});

View File

@@ -4,7 +4,7 @@
import { readFileSync } from 'fs';
import { workspace as vsWorkspace } from 'vscode';
import markdownItRegex from 'markdown-it-regex';
import { isSome } from '../../utils';
import { isSome, isNone } from '../../utils';
import { FoamWorkspace } from '../../core/model/workspace';
import { Logger } from '../../core/utils/log';
import { Resource, ResourceParser } from '../../core/model/note';
@@ -14,7 +14,13 @@ import { MarkdownLink } from '../../core/services/markdown-link';
import { Position } from '../../core/model/position';
import { TextEdit } from '../../core/services/text-edit';
export const CONFIG_EMBED_NOTE_IN_CONTAINER = 'preview.embedNoteInContainer';
export const WIKILINK_EMBED_REGEX =
/((?:(?:full|content)-(?:inline|card)|full|content|inline|card)?!\[\[[^[\]]+?\]\])/;
// we need another regex because md.use(regex, replace) only permits capturing one group
// so we capture the entire possible wikilink item (ex. content-card![[note]]) using WIKILINK_EMBED_REGEX and then
// use WIKILINK_EMBED_REGEX_GROUPER to parse it into the modifier(content-card) and the wikilink(note)
export const WIKILINK_EMBED_REGEX_GROUPS =
/((?:\w+)|(?:(?:\w+)-(?:\w+)))?!\[\[([^[\]]+?)\]\]/;
export const CONFIG_EMBED_NOTE_TYPE = 'preview.embedNoteType';
const refsStack: string[] = [];
@@ -25,9 +31,13 @@ export const markdownItWikilinkEmbed = (
) => {
return md.use(markdownItRegex, {
name: 'embed-wikilinks',
regex: /!\[\[([^[\]]+?)\]\]/,
replace: (wikilink: string) => {
regex: WIKILINK_EMBED_REGEX,
replace: (wikilinkItem: string) => {
try {
const [_, noteEmbedModifier, wikilink] = wikilinkItem.match(
WIKILINK_EMBED_REGEX_GROUPS
);
const includedNote = workspace.find(wikilink);
if (!includedNote) {
@@ -50,7 +60,8 @@ export const markdownItWikilinkEmbed = (
switch (includedNote.type) {
case 'note': {
const { noteScope, noteStyle } = retrieveNoteConfig();
const { noteScope, noteStyle } =
retrieveNoteConfig(noteEmbedModifier);
const extractor: EmbedNoteExtractor =
noteScope === 'full'
@@ -89,7 +100,7 @@ Embed for attachments is not supported
return html;
} catch (e) {
Logger.error(
`Error while including [[${wikilink}]] into the current document of the Preview panel`,
`Error while including ${wikilinkItem} into the current document of the Preview panel`,
e
);
return '';
@@ -111,6 +122,11 @@ function withLinksRelativeToWorkspaceRoot(
.map(link => {
const info = MarkdownLink.analyzeLink(link);
const resource = workspace.find(info.target);
// embedded notes that aren't created are still collected
// return null so it can be filtered in the next step
if (isNone(resource)) {
return null;
}
const pathFromRoot = vsWorkspace.asRelativePath(
toVsCodeUri(resource.uri)
);
@@ -118,6 +134,7 @@ function withLinksRelativeToWorkspaceRoot(
target: pathFromRoot,
});
})
.filter(linkEdits => !isNone(linkEdits))
.sort((a, b) => Position.compareTo(b.range.start, a.range.start));
const text = edits.reduce(
(text, edit) => TextEdit.apply(text, edit),
@@ -126,17 +143,22 @@ function withLinksRelativeToWorkspaceRoot(
return text;
}
export function retrieveNoteConfig(): {
export function retrieveNoteConfig(explicitModifier: string | undefined): {
noteScope: string;
noteStyle: string;
} {
let config = getFoamVsCodeConfig<string>(CONFIG_EMBED_NOTE_TYPE); // ex. full-inline
let [noteScope, noteStyle] = config.split('-');
// **DEPRECATED** setting to be removed
// for now it overrides the above to preserve user settings if they have it set
if (getFoamVsCodeConfig<boolean>(CONFIG_EMBED_NOTE_IN_CONTAINER, false)) {
noteStyle = 'card';
// an explicit modifier will always override corresponding user setting
if (explicitModifier !== undefined) {
if (['full', 'content'].includes(explicitModifier)) {
noteScope = explicitModifier;
} else if (['card', 'inline'].includes(explicitModifier)) {
noteStyle = explicitModifier;
} else {
[noteScope, noteStyle] = explicitModifier.split('-');
}
}
return { noteScope, noteStyle };
}

View File

@@ -26,9 +26,9 @@ describe('Link generation in preview', () => {
markdownItRemoveLinkReferences,
].reduce((acc, extension) => extension(acc, ws), MarkdownIt());
it('generates a link to a note', () => {
it('generates a link to a note using the note title as link', () => {
expect(md.render(`[[note-a]]`)).toEqual(
`<p><a class='foam-note-link' title='${noteA.title}' href='/path/to/note-a.md' data-href='/path/to/note-a.md'>note-a</a></p>\n`
`<p><a class='foam-note-link' title='${noteA.title}' href='/path/to/note-a.md' data-href='/path/to/note-a.md'>${noteA.title}</a></p>\n`
);
});
@@ -48,13 +48,22 @@ describe('Link generation in preview', () => {
const note = `[[note-a]]
[note-a]: <note-a.md> "Note A"`;
expect(md.render(note)).toEqual(
`<p><a class='foam-note-link' title='${noteA.title}' href='/path/to/note-a.md' data-href='/path/to/note-a.md'>note-a</a>\n[note-a]: &lt;note-a.md&gt; &quot;Note A&quot;</p>\n`
`<p><a class='foam-note-link' title='${noteA.title}' href='/path/to/note-a.md' data-href='/path/to/note-a.md'>${noteA.title}</a>\n[note-a]: &lt;note-a.md&gt; &quot;Note A&quot;</p>\n`
);
});
it('generates a link to a section within the note', () => {
expect(md.render(`[[#sec]]`)).toEqual(
`<p><a class='foam-note-link' title='sec' href='#sec' data-href='#sec'>#sec</a></p>\n`
);
expect(md.render(`[[#Section Name]]`)).toEqual(
`<p><a class='foam-note-link' title='Section Name' href='#section-name' data-href='#section-name'>#Section Name</a></p>\n`
);
});
it('generates a link to a note with a specific section', () => {
expect(md.render(`[[note-b#sec2]]`)).toEqual(
`<p><a class='foam-note-link' title='My second note#sec2' href='/path2/to/note-b.md#sec2' data-href='/path2/to/note-b.md#sec2'>note-b#sec2</a></p>\n`
`<p><a class='foam-note-link' title='My second note#sec2' href='/path2/to/note-b.md#sec2' data-href='/path2/to/note-b.md#sec2'>${noteB.title}#sec2</a></p>\n`
);
});
@@ -66,7 +75,7 @@ describe('Link generation in preview', () => {
it('generates a link to a note if the note exists, but the section does not exist', () => {
expect(md.render(`[[note-b#nonexistentsec]]`)).toEqual(
`<p><a class='foam-note-link' title='My second note#nonexistentsec' href='/path2/to/note-b.md#nonexistentsec' data-href='/path2/to/note-b.md#nonexistentsec'>note-b#nonexistentsec</a></p>\n`
`<p><a class='foam-note-link' title='My second note#nonexistentsec' href='/path2/to/note-b.md#nonexistentsec' data-href='/path2/to/note-b.md#nonexistentsec'>${noteB.title}#nonexistentsec</a></p>\n`
);
});

View File

@@ -9,6 +9,7 @@ import { toVsCodeUri } from '../../utils/vsc-utils';
import { MarkdownLink } from '../../core/services/markdown-link';
import { Range } from '../../core/model/range';
import { isEmpty } from 'lodash';
import { toSlug } from '../../utils/slug';
export const markdownItWikilinkNavigation = (
md: markdownit,
@@ -26,18 +27,32 @@ export const markdownItWikilinkNavigation = (
isEmbed: false,
});
const formattedSection = section ? `#${section}` : '';
const linkSection = section ? `#${toSlug(section)}` : '';
const label = isEmpty(alias) ? `${target}${formattedSection}` : alias;
// [[#section]] links
if (target.length === 0) {
// we don't have a good way to check if the section exists within the
// open file, so we just create a regular link for it
return getResourceLink(section, linkSection, label);
}
const resource = workspace.find(target);
if (isNone(resource)) {
return getPlaceholderLink(label);
}
const link = `${vscode.workspace.asRelativePath(
const resourceLabel = isEmpty(alias)
? `${resource.title}${formattedSection}`
: alias;
const resourceLink = `/${vscode.workspace.asRelativePath(
toVsCodeUri(resource.uri)
)}${formattedSection}`;
const title = `${resource.title}${formattedSection}`;
return `<a class='foam-note-link' title='${title}' href='/${link}' data-href='/${link}'>${label}</a>`;
)}`;
return getResourceLink(
`${resource.title}${formattedSection}`,
`${resourceLink}${linkSection}`,
resourceLabel
);
} catch (e) {
Logger.error(
`Error while creating link for [[${wikilink}]] in Preview panel`,
@@ -52,4 +67,7 @@ export const markdownItWikilinkNavigation = (
const getPlaceholderLink = (content: string) =>
`<a class='foam-placeholder-link' title="Link to non-existing resource" href="javascript:void(0);">${content}</a>`;
const getResourceLink = (title: string, link: string, label: string) =>
`<a class='foam-note-link' title='${title}' href='${link}' data-href='${link}'>${label}</a>`;
export default markdownItWikilinkNavigation;

View File

@@ -5,7 +5,7 @@
👀*This is an early stage project under rapid development. For updates join the [Foam community Discord](https://foambubble.github.io/join-discord/g)! 💬*
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-115-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-116-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
[![Visual Studio Marketplace Installs](https://img.shields.io/visual-studio-marketplace/i/foam.foam-vscode?label=VS%20Code%20Installs)](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode)
@@ -347,6 +347,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center" valign="top" width="14.28%"><a href="https://amnesiak.org/me"><img src="https://avatars.githubusercontent.com/u/952059?v=4?s=60" width="60px;" alt="Tony Cheneau"/><br /><sub><b>Tony Cheneau</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=tcheneau" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/nicholas-l"><img src="https://avatars.githubusercontent.com/u/12977174?v=4?s=60" width="60px;" alt="Nicholas Latham"/><br /><sub><b>Nicholas Latham</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=nicholas-l" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://thara.dev"><img src="https://avatars.githubusercontent.com/u/1532891?v=4?s=60" width="60px;" alt="Tomochika Hara"/><br /><sub><b>Tomochika Hara</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=thara" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dcarosone"><img src="https://avatars.githubusercontent.com/u/11495017?v=4?s=60" width="60px;" alt="Daniel Carosone"/><br /><sub><b>Daniel Carosone</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dcarosone" title="Documentation">📖</a></td>
</tr>
</tbody>
</table>