mirror of
https://github.com/foambubble/foam.git
synced 2026-01-10 22:48:09 -05:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34c179123e | ||
|
|
c34394a0ea | ||
|
|
26c38a06ff | ||
|
|
d4623a2d91 | ||
|
|
2f9507dc87 | ||
|
|
4db09070a8 | ||
|
|
5f99d9d5c6 | ||
|
|
9c1480197c | ||
|
|
f1a6426046 | ||
|
|
6de8baa6b5 | ||
|
|
27ff023a26 | ||
|
|
690eb10856 | ||
|
|
1cb8174a9f | ||
|
|
2141bac24a | ||
|
|
6abef8f8e7 | ||
|
|
c797a00223 | ||
|
|
7c01fb13f0 | ||
|
|
abbc2bbb14 | ||
|
|
bb7fee24bb | ||
|
|
43ef3a3e2b | ||
|
|
da69a3057f | ||
|
|
2d06f26bbf | ||
|
|
edbe128e1e |
@@ -688,6 +688,15 @@
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "pderaaij",
|
||||
"name": "Paul de Raaij",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/495374?v=4",
|
||||
"profile": "http://www.paulderaaij.nl",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
@@ -203,14 +203,14 @@ GEM
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
mercenary (0.3.6)
|
||||
mini_portile2 (2.5.0)
|
||||
mini_portile2 (2.5.1)
|
||||
minima (2.5.1)
|
||||
jekyll (>= 3.5, < 5.0)
|
||||
jekyll-feed (~> 0.9)
|
||||
jekyll-seo-tag (~> 2.1)
|
||||
minitest (5.14.2)
|
||||
multipart-post (2.1.1)
|
||||
nokogiri (1.11.1)
|
||||
nokogiri (1.11.5)
|
||||
mini_portile2 (~> 2.5.0)
|
||||
racc (~> 1.4)
|
||||
octokit (4.19.0)
|
||||
|
||||
BIN
docs/assets/images/template-picker-annotated.png
Normal file
BIN
docs/assets/images/template-picker-annotated.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 82 KiB |
@@ -40,3 +40,110 @@ In addition, you can also use variables provided by Foam:
|
||||
| `FOAM_TITLE` | The title of the note. If used, Foam will prompt you to enter a title for the note. |
|
||||
|
||||
**Note:** neither the defaulting feature (eg. `${variable:default}`) nor the format feature (eg. `${variable/(.*)/${1:/upcase}/}`) (available to other variables) are available for these Foam-provided variables.
|
||||
|
||||
## Metadata
|
||||
|
||||
Templates can also contain metadata about the templates themselves. The metadata is defined in YAML "Frontmatter" blocks within the templates.
|
||||
|
||||
| Name | Description |
|
||||
| ------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `filepath` | The filepath to use when creating the new note. If the filepath is a relative filepath, it is relative to the current workspace. |
|
||||
| `name` | A human readable name to show in the template picker. |
|
||||
| `description` | A human readable description to show in the template picker. |
|
||||
|
||||
Foam-specific variables (e.g. `$FOAM_TITLE`) can be used within template metadata. However, VS Code snippet variables are ([currently](https://github.com/foambubble/foam/pull/655)) not supported.
|
||||
|
||||
### `filepath` attribute
|
||||
|
||||
The `filepath` metadata attribute allows you to define a relative or absolute filepath to use when creating a note using the template.
|
||||
If the filepath is a relative filepath, it is relative to the current workspace.
|
||||
|
||||
#### Example of relative `filepath`
|
||||
|
||||
For example, `filepath` can be used to customize `.foam/templates/new-note.md`, overriding the default `Foam: Create New Note` behaviour of opening the file in the same directory as the active file:
|
||||
|
||||
```yaml
|
||||
---
|
||||
# This will create the note in the "journal" subdirectory of the current workspace,
|
||||
# regardless of which file is the active file.
|
||||
foam_template:
|
||||
filepath: 'journal/$FOAM_TITLE.md'
|
||||
---
|
||||
```
|
||||
|
||||
#### Example of absolute `filepath`
|
||||
|
||||
`filepath` can be an absolute filepath, so that the notes get created in the same location, regardless of which file or workspace the editor currently has open.
|
||||
The format of an absolute filepath may vary depending on the filesystem used.
|
||||
|
||||
```yaml
|
||||
---
|
||||
foam_template:
|
||||
# Unix / MacOS filesystems
|
||||
filepath: '/Users/john.smith/foam/journal/$FOAM_TITLE.md'
|
||||
|
||||
# Windows filesystems
|
||||
filepath: 'C:\Users\john.smith\Documents\foam\journal\$FOAM_TITLE.md'
|
||||
---
|
||||
```
|
||||
|
||||
### `name` and `description` attributes
|
||||
|
||||
These attributes provide a human readable name and description to be shown in the template picker (e.g. When a user uses the `Foam: Create New Note From Template` command):
|
||||
|
||||

|
||||
|
||||
### Adding template metadata to an existing YAML Frontmatter block
|
||||
|
||||
If your template already has a YAML Frontmatter block, you can add the Foam template metadata to it.
|
||||
|
||||
#### Limitations
|
||||
|
||||
Foam only supports adding the template metadata to *YAML* Frontmatter blocks. If the existing Frontmatter block uses some other format (e.g. JSON), you will have to add the template metadata to its own YAML Frontmatter block.
|
||||
|
||||
Further, the template metadata must be provided as a [YAML block mapping](https://yaml.org/spec/1.2/spec.html#id2798057), with the attributes placed on the lines immediately following the `foam_template` line:
|
||||
|
||||
```yaml
|
||||
---
|
||||
existing_frontmatter: "Existing Frontmatter block"
|
||||
foam_template: # this is a YAML "Block" mapping ("Flow" mappings aren't supported)
|
||||
name: My Note Template # Attributes must be on the lines immediately following `foam_template`
|
||||
description: This is my note template
|
||||
filepath: `journal/$FOAM_TITLE.md`
|
||||
---
|
||||
This is the rest of the template
|
||||
```
|
||||
|
||||
Due to the technical limitations of parsing the complex YAML format, unless the metadata is provided this specific form, Foam is unable to correctly remove the template metadata before creating the resulting note.
|
||||
|
||||
If this limitation proves inconvenient to you, please let us know. We may be able to extend our parsing capabilities to cover your use case. In the meantime, you can add the template metadata without this limitation by providing it in its own YAML Frontmatter block.
|
||||
|
||||
### Adding template metadata to its own YAML Frontmatter block
|
||||
|
||||
You can add the template metadata to its own YAML Frontmatter block at the start of the template:
|
||||
|
||||
```yaml
|
||||
---
|
||||
foam_template:
|
||||
name: My Note Template
|
||||
description: This is my note template
|
||||
filepath: `journal/$FOAM_TITLE.md`
|
||||
---
|
||||
This is the rest of the template
|
||||
```
|
||||
|
||||
If the note already has a Frontmatter block, a Foam-specific Frontmatter block can be added to the start of the template. The Foam-specific Frontmatter block must always be placed at the very beginning of the file, and only whitespace can separate the two Frontmatter blocks.
|
||||
|
||||
```yaml
|
||||
---
|
||||
foam_template:
|
||||
name: My Note Template
|
||||
description: This is my note template
|
||||
filepath: `journal/$FOAM_TITLE.md`
|
||||
---
|
||||
|
||||
---
|
||||
existing_frontmatter: "Existing Frontmatter block"
|
||||
---
|
||||
This is the rest of the template
|
||||
```
|
||||
|
||||
@@ -9,6 +9,8 @@ There are two ways of creating a tag:
|
||||
- adding a `#tag` anywhere in the text of the note
|
||||
- using the `tags: tag1, tag2` property in frontmatter
|
||||
|
||||
Tags can also be hierarchical, so you can have `#parent/child`.
|
||||
|
||||
## Navigating tags
|
||||
|
||||
It's possible to navigate tags via the Tag Explorer panel.
|
||||
|
||||
@@ -201,6 +201,7 @@ If that sounds like something you're interested in, I'd love to have you along o
|
||||
<td align="center"><a href="https://github.com/daniel-vera-g"><img src="https://avatars.githubusercontent.com/u/28257108?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Daniel VG</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=daniel-vera-g" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Barabazs"><img src="https://avatars.githubusercontent.com/u/31799121?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Barabas</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Barabazs" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://enginveske@gmail.com"><img src="https://avatars.githubusercontent.com/u/43685404?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Engincan VESKE</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=EngincanV" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.paulderaaij.nl"><img src="https://avatars.githubusercontent.com/u/495374?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Paul de Raaij</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=pderaaij" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ This principle may seem like it contradicts [Foam wants you to own your thoughts
|
||||
|
||||
- **Foam is a collection of ideas.** Foam was released to the public not to share the few good ideas in it, but to learn many good ideas from others. As you improve your own workflow, share your work on your own Foam blog.
|
||||
- **Foam is open for contributions.** If you use a tool or workflow that you like that fits these principles, please contribute them back to the Foam template as [[recipes]], [[recommended-extensions]] or documentation in [this workspace](https://github.com/foambubble/foam). See also: [[contribution-guide]].
|
||||
- **Foam is open source.** Feel free to fork it, improve it and remix it. Just don't sell it, as per our [license](license).
|
||||
- **Foam is open source.** Feel free to fork it, improve it and remix it. Just don't sell it, as per our [license](LICENSE.txt).
|
||||
- **Foam is not Roam.** This project was inspired by Roam Research, but we're not limited by what Roam does. No idea is too big (though if it doesn't fit with Foam's core workflow, we might make it a [[recipes]] page instead).
|
||||
|
||||
## Foam is for hackers, not only for programmers
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
],
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.13.3"
|
||||
"version": "0.13.5"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "foam-core",
|
||||
"repository": "https://github.com/foambubble/foam",
|
||||
"version": "0.13.3",
|
||||
"version": "0.13.4",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"dist"
|
||||
@@ -33,7 +33,7 @@
|
||||
"fast-array-diff": "^1.0.0",
|
||||
"github-slugger": "^1.3.0",
|
||||
"glob": "^7.1.6",
|
||||
"lodash": "^4.17.19",
|
||||
"lodash": "^4.17.21",
|
||||
"micromatch": "^4.0.2",
|
||||
"remark-frontmatter": "^2.0.0",
|
||||
"remark-parse": "^8.0.2",
|
||||
|
||||
@@ -177,7 +177,7 @@ const titlePlugin: ParserPlugin = {
|
||||
},
|
||||
onDidFindProperties: (props, note) => {
|
||||
// Give precendence to the title from the frontmatter if it exists
|
||||
note.title = props.title ?? note.title;
|
||||
note.title = props.title?.toString() ?? note.title;
|
||||
},
|
||||
onDidVisitTree: (tree, note) => {
|
||||
if (note.title === '') {
|
||||
@@ -313,8 +313,6 @@ export function createMarkdownParser(
|
||||
...note.properties,
|
||||
...yamlProperties,
|
||||
};
|
||||
// Give precendence to the title from the frontmatter if it exists
|
||||
note.title = note.properties.title ?? note.title;
|
||||
// Update the start position of the note by exluding the metadata
|
||||
note.source.contentStart = Position.create(
|
||||
node.position!.end.line! + 2,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { isSome } from './core';
|
||||
const HASHTAG_REGEX = /(^|\s)#([0-9]*[\p{L}_-][\p{L}\p{N}_-]*)/gmu;
|
||||
const WORD_REGEX = /(^|\s)([0-9]*[\p{L}_-][\p{L}\p{N}_-]*)/gmu;
|
||||
const HASHTAG_REGEX = /(^|\s)#([0-9]*[\p{L}/_-][\p{L}\p{N}/_-]*)/gmu;
|
||||
const WORD_REGEX = /(^|\s)([0-9]*[\p{L}/_-][\p{L}\p{N}/_-]*)/gmu;
|
||||
|
||||
export const extractHashtags = (text: string): Set<string> => {
|
||||
return isSome(text)
|
||||
|
||||
@@ -171,6 +171,26 @@ date: 20-12-12
|
||||
expect(note.title).toBe('Note Title');
|
||||
});
|
||||
|
||||
it('should support numbers', () => {
|
||||
const note1 = createNoteFromMarkdown('/157.md', `hello`);
|
||||
expect(note1.title).toBe('157');
|
||||
|
||||
const note2 = createNoteFromMarkdown('/157.md', `# 158`);
|
||||
expect(note2.title).toBe('158');
|
||||
|
||||
const note3 = createNoteFromMarkdown(
|
||||
'/157.md',
|
||||
`
|
||||
---
|
||||
title: 159
|
||||
---
|
||||
|
||||
# 158
|
||||
`
|
||||
);
|
||||
expect(note3.title).toBe('159');
|
||||
});
|
||||
|
||||
it('should not break on empty titles (see #276)', () => {
|
||||
const note = createNoteFromMarkdown(
|
||||
'/Hello Page.md',
|
||||
@@ -316,6 +336,19 @@ this is some #text that includes #tags we #care-about.
|
||||
new Set(['text', 'tags', 'care-about', 'hello', 'world', 'this_is_good'])
|
||||
);
|
||||
});
|
||||
|
||||
it('can find nested tags as array in yaml', () => {
|
||||
const noteA = createNoteFromMarkdown(
|
||||
'/dir1/page-a.md',
|
||||
`
|
||||
---
|
||||
tags: [hello, world, parent/child]
|
||||
---
|
||||
# this is a heading
|
||||
`
|
||||
);
|
||||
expect(noteA.tags).toEqual(new Set(['hello', 'world', 'parent/child']));
|
||||
});
|
||||
});
|
||||
|
||||
describe('parser plugins', () => {
|
||||
|
||||
@@ -19,6 +19,11 @@ describe('hashtag extraction', () => {
|
||||
new Set(['hello-world', 'this_planet'])
|
||||
);
|
||||
});
|
||||
it('supports nested tags', () => {
|
||||
expect(extractHashtags('#parent/child on #planet')).toEqual(
|
||||
new Set(['parent/child', 'planet'])
|
||||
);
|
||||
});
|
||||
it('ignores tags that only have numbers in text', () => {
|
||||
expect(
|
||||
extractHashtags('this #123 tag should be ignore, but not #123four')
|
||||
|
||||
@@ -3870,9 +3870,9 @@ has@^1.0.3:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
hosted-git-info@^2.1.4:
|
||||
version "2.8.8"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
|
||||
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
|
||||
version "2.8.9"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
|
||||
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
|
||||
|
||||
html-encoding-sniffer@^1.0.2:
|
||||
version "1.0.2"
|
||||
@@ -4944,10 +4944,10 @@ lodash.sortby@^4.7.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
|
||||
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
|
||||
|
||||
lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.4:
|
||||
version "4.17.19"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
|
||||
integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
|
||||
lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.4:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
||||
log-symbols@^2.2.0:
|
||||
version "2.2.0"
|
||||
|
||||
@@ -4,6 +4,26 @@ 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.13.5] - 2021-06-05
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Improved support for nested tags (#661 - thanks @pderaaij)
|
||||
- Allow YAML metadata in templates (#655 - thanks @movermeyer)
|
||||
- Fixed template exclusion globs (#665)
|
||||
|
||||
## [0.13.4] - 2021-05-26
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Added support for nested tags (#643 - thanks @pderaaij)
|
||||
- Improved the flow of creating note from template (#645 - thanks @movermeyer)
|
||||
- Fixed handling of title property in YAML (#647 - thanks @pderaaij and #546)
|
||||
|
||||
Internal:
|
||||
|
||||
- Updated various dependencies
|
||||
|
||||
## [0.13.3] - 2021-05-09
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"type": "git"
|
||||
},
|
||||
"homepage": "https://github.com/foambubble/foam",
|
||||
"version": "0.13.3",
|
||||
"version": "0.13.5",
|
||||
"license": "MIT",
|
||||
"publisher": "foam",
|
||||
"engines": {
|
||||
@@ -395,7 +395,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"dateformat": "^3.0.3",
|
||||
"foam-core": "^0.13.3",
|
||||
"foam-core": "^0.13.4",
|
||||
"gray-matter": "^4.0.2",
|
||||
"markdown-it-regex": "^0.2.0",
|
||||
"micromatch": "^4.0.2",
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { SnippetString, window } from 'vscode';
|
||||
import { window, workspace } from 'vscode';
|
||||
import {
|
||||
resolveFoamVariables,
|
||||
resolveFoamTemplateVariables,
|
||||
substituteFoamVariables,
|
||||
determineDefaultFilepath,
|
||||
} from './create-from-template';
|
||||
import path from 'path';
|
||||
import { isWindows } from '../utils';
|
||||
import { URI } from 'foam-core';
|
||||
|
||||
describe('substituteFoamVariables', () => {
|
||||
test('Does nothing if no Foam-specific variables are used', () => {
|
||||
@@ -58,15 +62,15 @@ describe('resolveFoamVariables', () => {
|
||||
});
|
||||
|
||||
test('Resolves FOAM_TITLE', async () => {
|
||||
const foam_title = 'My note title';
|
||||
const foamTitle = 'My note title';
|
||||
const variables = ['FOAM_TITLE'];
|
||||
|
||||
jest
|
||||
.spyOn(window, 'showInputBox')
|
||||
.mockImplementationOnce(jest.fn(() => Promise.resolve(foam_title)));
|
||||
.mockImplementationOnce(jest.fn(() => Promise.resolve(foamTitle)));
|
||||
|
||||
const expected = new Map<string, string>();
|
||||
expected.set('FOAM_TITLE', foam_title);
|
||||
expected.set('FOAM_TITLE', foamTitle);
|
||||
|
||||
const givenValues = new Map<string, string>();
|
||||
expect(await resolveFoamVariables(variables, givenValues)).toEqual(
|
||||
@@ -75,14 +79,14 @@ describe('resolveFoamVariables', () => {
|
||||
});
|
||||
|
||||
test('Resolves FOAM_TITLE without asking the user when it is provided', async () => {
|
||||
const foam_title = 'My note title';
|
||||
const foamTitle = 'My note title';
|
||||
const variables = ['FOAM_TITLE'];
|
||||
|
||||
const expected = new Map<string, string>();
|
||||
expected.set('FOAM_TITLE', foam_title);
|
||||
expected.set('FOAM_TITLE', foamTitle);
|
||||
|
||||
const givenValues = new Map<string, string>();
|
||||
givenValues.set('FOAM_TITLE', foam_title);
|
||||
givenValues.set('FOAM_TITLE', foamTitle);
|
||||
expect(await resolveFoamVariables(variables, givenValues)).toEqual(
|
||||
expected
|
||||
);
|
||||
@@ -100,8 +104,8 @@ describe('resolveFoamTemplateVariables', () => {
|
||||
`;
|
||||
|
||||
const expectedMap = new Map<string, string>();
|
||||
const expectedSnippet = new SnippetString(input);
|
||||
const expected = [expectedMap, expectedSnippet];
|
||||
const expectedString = input;
|
||||
const expected = [expectedMap, expectedString];
|
||||
|
||||
expect(await resolveFoamTemplateVariables(input)).toEqual(expected);
|
||||
});
|
||||
@@ -115,9 +119,75 @@ describe('resolveFoamTemplateVariables', () => {
|
||||
`;
|
||||
|
||||
const expectedMap = new Map<string, string>();
|
||||
const expectedSnippet = new SnippetString(input);
|
||||
const expected = [expectedMap, expectedSnippet];
|
||||
const expectedString = input;
|
||||
const expected = [expectedMap, expectedString];
|
||||
|
||||
expect(await resolveFoamTemplateVariables(input)).toEqual(expected);
|
||||
});
|
||||
|
||||
test('Allows extra variables to be provided; only resolves the unique set', async () => {
|
||||
const foamTitle = 'My note title';
|
||||
|
||||
jest
|
||||
.spyOn(window, 'showInputBox')
|
||||
.mockImplementationOnce(jest.fn(() => Promise.resolve(foamTitle)));
|
||||
|
||||
const input = `
|
||||
# $FOAM_TITLE
|
||||
`;
|
||||
|
||||
const expectedOutput = `
|
||||
# My note title
|
||||
`;
|
||||
|
||||
const expectedMap = new Map<string, string>();
|
||||
expectedMap.set('FOAM_TITLE', foamTitle);
|
||||
|
||||
const expected = [expectedMap, expectedOutput];
|
||||
|
||||
expect(
|
||||
await resolveFoamTemplateVariables(input, new Set(['FOAM_TITLE']))
|
||||
).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('determineDefaultFilepath', () => {
|
||||
test('Absolute filepath metadata is unchanged', () => {
|
||||
const absolutePath = isWindows
|
||||
? 'c:\\absolute_path\\journal\\My Note Title.md'
|
||||
: '/absolute_path/journal/My Note Title.md';
|
||||
|
||||
const resolvedValues = new Map<string, string>();
|
||||
const templateMetadata = new Map<string, string>();
|
||||
templateMetadata.set('filepath', absolutePath);
|
||||
|
||||
const resultFilepath = determineDefaultFilepath(
|
||||
resolvedValues,
|
||||
templateMetadata
|
||||
);
|
||||
|
||||
expect(URI.toFsPath(resultFilepath)).toMatch(absolutePath);
|
||||
});
|
||||
|
||||
test('Relative filepath metadata is appended to current directory', () => {
|
||||
const relativePath = isWindows
|
||||
? 'journal\\My Note Title.md'
|
||||
: 'journal/My Note Title.md';
|
||||
|
||||
const resolvedValues = new Map<string, string>();
|
||||
const templateMetadata = new Map<string, string>();
|
||||
templateMetadata.set('filepath', relativePath);
|
||||
|
||||
const resultFilepath = determineDefaultFilepath(
|
||||
resolvedValues,
|
||||
templateMetadata
|
||||
);
|
||||
|
||||
const expectedPath = path.join(
|
||||
workspace.workspaceFolders[0].uri.fsPath,
|
||||
relativePath
|
||||
);
|
||||
|
||||
expect(URI.toFsPath(resultFilepath)).toMatch(expectedPath);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import {
|
||||
window,
|
||||
commands,
|
||||
ExtensionContext,
|
||||
workspace,
|
||||
QuickPickItem,
|
||||
SnippetString,
|
||||
Uri,
|
||||
window,
|
||||
workspace,
|
||||
} from 'vscode';
|
||||
import * as path from 'path';
|
||||
import { FoamFeature } from '../types';
|
||||
import { TextEncoder } from 'util';
|
||||
import { focusNote } from '../utils';
|
||||
import { existsSync } from 'fs';
|
||||
import { isAbsolute } from 'path';
|
||||
import { extractFoamTemplateFrontmatterMetadata } from '../utils/template-frontmatter-parser';
|
||||
|
||||
const templatesDir = Uri.joinPath(
|
||||
workspace.workspaceFolders[0].uri,
|
||||
@@ -29,7 +32,13 @@ export class UserCancelledOperation extends Error {
|
||||
|
||||
const knownFoamVariables = new Set(['FOAM_TITLE']);
|
||||
|
||||
const defaultTemplateDefaultText: string = '# ${FOAM_TITLE}'; // eslint-disable-line no-template-curly-in-string
|
||||
const defaultTemplateDefaultText: string = `---
|
||||
foam_template:
|
||||
name: New Note
|
||||
description: Foam's default new note template
|
||||
---
|
||||
# \${FOAM_TITLE}
|
||||
`;
|
||||
const defaultTemplateUri = Uri.joinPath(templatesDir, 'new-note.md');
|
||||
|
||||
const templateContent = `# \${1:$TM_FILENAME_BASE}
|
||||
@@ -51,9 +60,19 @@ For a full list of features see [the VS Code snippets page](https://code.visuals
|
||||
2. create a note from this template by running the \`Foam: Create New Note From Template\` command
|
||||
`;
|
||||
|
||||
async function getTemplates(): Promise<string[]> {
|
||||
const templates = await workspace.findFiles('.foam/templates/**.md');
|
||||
return templates.map(template => path.basename(template.path));
|
||||
async function templateMetadata(
|
||||
templateUri: Uri
|
||||
): Promise<Map<string, string>> {
|
||||
const contents = await workspace.fs
|
||||
.readFile(templateUri)
|
||||
.then(bytes => bytes.toString());
|
||||
const [templateMetadata] = extractFoamTemplateFrontmatterMetadata(contents);
|
||||
return templateMetadata;
|
||||
}
|
||||
|
||||
async function getTemplates(): Promise<Uri[]> {
|
||||
const templates = await workspace.findFiles('.foam/templates/**.md', null);
|
||||
return templates;
|
||||
}
|
||||
|
||||
async function offerToCreateTemplate(): Promise<void> {
|
||||
@@ -153,12 +172,71 @@ export function substituteFoamVariables(
|
||||
return templateText;
|
||||
}
|
||||
|
||||
function sortTemplatesMetadata(
|
||||
t1: Map<string, string>,
|
||||
t2: Map<string, string>
|
||||
) {
|
||||
// Sort by name's existence, then name, then path
|
||||
|
||||
if (t1.get('name') === undefined && t2.get('name') !== undefined) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (t1.get('name') !== undefined && t2.get('name') === undefined) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const pathSortOrder = t1
|
||||
.get('templatePath')
|
||||
.localeCompare(t2.get('templatePath'));
|
||||
|
||||
if (t1.get('name') === undefined && t2.get('name') === undefined) {
|
||||
return pathSortOrder;
|
||||
}
|
||||
|
||||
const nameSortOrder = t1.get('name').localeCompare(t2.get('name'));
|
||||
|
||||
return nameSortOrder || pathSortOrder;
|
||||
}
|
||||
|
||||
async function askUserForTemplate() {
|
||||
const templates = await getTemplates();
|
||||
if (templates.length === 0) {
|
||||
return offerToCreateTemplate();
|
||||
}
|
||||
return await window.showQuickPick(templates, {
|
||||
|
||||
const templatesMetadata = (
|
||||
await Promise.all(
|
||||
templates.map(async templateUri => {
|
||||
const metadata = await templateMetadata(templateUri);
|
||||
metadata.set('templatePath', path.basename(templateUri.path));
|
||||
return metadata;
|
||||
})
|
||||
)
|
||||
).sort(sortTemplatesMetadata);
|
||||
|
||||
const items: QuickPickItem[] = await Promise.all(
|
||||
templatesMetadata.map(metadata => {
|
||||
const label = metadata.get('name') || metadata.get('templatePath');
|
||||
const description = metadata.get('name')
|
||||
? metadata.get('templatePath')
|
||||
: null;
|
||||
const detail = metadata.get('description');
|
||||
const item = {
|
||||
label: label,
|
||||
description: description,
|
||||
detail: detail,
|
||||
};
|
||||
Object.keys(item).forEach(key => {
|
||||
if (!item[key]) {
|
||||
delete item[key];
|
||||
}
|
||||
});
|
||||
return item;
|
||||
})
|
||||
);
|
||||
|
||||
return await window.showQuickPick(items, {
|
||||
placeHolder: 'Select a template to use.',
|
||||
});
|
||||
}
|
||||
@@ -184,18 +262,21 @@ async function askUserForFilepathConfirmation(
|
||||
}
|
||||
|
||||
export async function resolveFoamTemplateVariables(
|
||||
templateText: string
|
||||
): Promise<[Map<string, string>, SnippetString]> {
|
||||
templateText: string,
|
||||
extraVariablesToResolve: Set<string> = new Set()
|
||||
): Promise<[Map<string, string>, string]> {
|
||||
const givenValues = new Map<string, string>();
|
||||
const variables = findFoamVariables(templateText.toString());
|
||||
const variables = findFoamVariables(templateText.toString()).concat(
|
||||
...extraVariablesToResolve
|
||||
);
|
||||
const uniqVariables = [...new Set(variables)];
|
||||
|
||||
const resolvedValues = await resolveFoamVariables(variables, givenValues);
|
||||
const resolvedValues = await resolveFoamVariables(uniqVariables, givenValues);
|
||||
const subbedText = substituteFoamVariables(
|
||||
templateText.toString(),
|
||||
resolvedValues
|
||||
);
|
||||
const snippet = new SnippetString(subbedText);
|
||||
return [resolvedValues, snippet];
|
||||
return [resolvedValues, subbedText];
|
||||
}
|
||||
|
||||
async function writeTemplate(templateSnippet: SnippetString, filepath: Uri) {
|
||||
@@ -214,16 +295,43 @@ function currentDirectoryFilepath(filename: string) {
|
||||
return Uri.joinPath(currentDir, filename);
|
||||
}
|
||||
|
||||
export function determineDefaultFilepath(
|
||||
resolvedValues: Map<string, string>,
|
||||
templateMetadata: Map<string, string>
|
||||
) {
|
||||
let defaultFilepath: Uri;
|
||||
if (templateMetadata.get('filepath')) {
|
||||
const filepathFromMetadata = templateMetadata.get('filepath');
|
||||
if (isAbsolute(filepathFromMetadata)) {
|
||||
defaultFilepath = Uri.file(filepathFromMetadata);
|
||||
} else {
|
||||
defaultFilepath = Uri.joinPath(
|
||||
workspace.workspaceFolders[0].uri,
|
||||
filepathFromMetadata
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const defaultSlug = resolvedValues.get('FOAM_TITLE') || 'New Note';
|
||||
defaultFilepath = currentDirectoryFilepath(`${defaultSlug}.md`);
|
||||
}
|
||||
return defaultFilepath;
|
||||
}
|
||||
|
||||
async function createNoteFromDefaultTemplate(): Promise<void> {
|
||||
const templateUri = defaultTemplateUri;
|
||||
const templateText = existsSync(templateUri.fsPath)
|
||||
? await workspace.fs.readFile(templateUri).then(bytes => bytes.toString())
|
||||
: defaultTemplateDefaultText;
|
||||
|
||||
let resolvedValues, templateSnippet;
|
||||
let resolvedValues: Map<string, string>,
|
||||
templateWithResolvedVariables: string;
|
||||
try {
|
||||
[resolvedValues, templateSnippet] = await resolveFoamTemplateVariables(
|
||||
templateText
|
||||
[
|
||||
resolvedValues,
|
||||
templateWithResolvedVariables,
|
||||
] = await resolveFoamTemplateVariables(
|
||||
templateText,
|
||||
new Set(['FOAM_TITLE'])
|
||||
);
|
||||
} catch (err) {
|
||||
if (err instanceof UserCancelledOperation) {
|
||||
@@ -233,9 +341,17 @@ async function createNoteFromDefaultTemplate(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
const defaultSlug = resolvedValues.get('FOAM_TITLE') || 'New Note';
|
||||
const defaultFilename = `${defaultSlug}.md`;
|
||||
const defaultFilepath = currentDirectoryFilepath(defaultFilename);
|
||||
const [
|
||||
templateMetadata,
|
||||
templateWithFoamFrontmatterRemoved,
|
||||
] = extractFoamTemplateFrontmatterMetadata(templateWithResolvedVariables);
|
||||
const templateSnippet = new SnippetString(templateWithFoamFrontmatterRemoved);
|
||||
|
||||
const defaultFilepath = determineDefaultFilepath(
|
||||
resolvedValues,
|
||||
templateMetadata
|
||||
);
|
||||
const defaultFilename = path.basename(defaultFilepath.path);
|
||||
|
||||
let filepath = defaultFilepath;
|
||||
if (existsSync(filepath.fsPath)) {
|
||||
@@ -259,17 +375,20 @@ async function createNoteFromTemplate(
|
||||
if (selectedTemplate === undefined) {
|
||||
return;
|
||||
}
|
||||
templateFilename = selectedTemplate as string;
|
||||
templateFilename =
|
||||
(selectedTemplate as QuickPickItem).description ||
|
||||
(selectedTemplate as QuickPickItem).label;
|
||||
const templateUri = Uri.joinPath(templatesDir, templateFilename);
|
||||
const templateText = await workspace.fs
|
||||
.readFile(templateUri)
|
||||
.then(bytes => bytes.toString());
|
||||
|
||||
let resolvedValues, templateSnippet;
|
||||
let resolvedValues, templateWithResolvedVariables;
|
||||
try {
|
||||
[resolvedValues, templateSnippet] = await resolveFoamTemplateVariables(
|
||||
templateText
|
||||
);
|
||||
[
|
||||
resolvedValues,
|
||||
templateWithResolvedVariables,
|
||||
] = await resolveFoamTemplateVariables(templateText);
|
||||
} catch (err) {
|
||||
if (err instanceof UserCancelledOperation) {
|
||||
return;
|
||||
@@ -278,9 +397,17 @@ async function createNoteFromTemplate(
|
||||
}
|
||||
}
|
||||
|
||||
const defaultSlug = resolvedValues.get('FOAM_TITLE') || 'New Note';
|
||||
const defaultFilename = `${defaultSlug}.md`;
|
||||
const defaultFilepath = currentDirectoryFilepath(defaultFilename);
|
||||
const [
|
||||
templateMetadata,
|
||||
templateWithFoamFrontmatterRemoved,
|
||||
] = extractFoamTemplateFrontmatterMetadata(templateWithResolvedVariables);
|
||||
const templateSnippet = new SnippetString(templateWithFoamFrontmatterRemoved);
|
||||
|
||||
const defaultFilepath = determineDefaultFilepath(
|
||||
resolvedValues,
|
||||
templateMetadata
|
||||
);
|
||||
const defaultFilename = path.basename(defaultFilepath.path);
|
||||
|
||||
const filepath = await askUserForFilepathConfirmation(
|
||||
defaultFilepath,
|
||||
|
||||
@@ -2,7 +2,6 @@ import * as vscode from 'vscode';
|
||||
import markdownItRegex from 'markdown-it-regex';
|
||||
import { Foam, FoamWorkspace, Logger, URI } from 'foam-core';
|
||||
import { FoamFeature } from '../types';
|
||||
import fp from 'lodash/fp';
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: async (
|
||||
@@ -12,13 +11,11 @@ const feature: FoamFeature = {
|
||||
const foam = await foamPromise;
|
||||
|
||||
return {
|
||||
extendMarkdownIt: (md: markdownit) => {
|
||||
const markdownItExtends = fp.compose(
|
||||
markdownItWithFoamLinks,
|
||||
markdownItWithFoamTags
|
||||
);
|
||||
return markdownItExtends(md, foam.workspace);
|
||||
},
|
||||
extendMarkdownIt: (md: markdownit) =>
|
||||
[markdownItWithFoamTags, markdownItWithFoamLinks].reduce(
|
||||
(acc, extension) => extension(acc, foam.workspace),
|
||||
md
|
||||
),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
152
packages/foam-vscode/src/features/tags-tree-view/index.spec.ts
Normal file
152
packages/foam-vscode/src/features/tags-tree-view/index.spec.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import {
|
||||
cleanWorkspace,
|
||||
closeEditors,
|
||||
createTestNote,
|
||||
} from '../../test/test-utils';
|
||||
|
||||
import { Tag, TagReference, TagsProvider } from '.';
|
||||
|
||||
import {
|
||||
bootstrap,
|
||||
createConfigFromFolders,
|
||||
Foam,
|
||||
FileDataStore,
|
||||
FoamConfig,
|
||||
MarkdownResourceProvider,
|
||||
Matcher,
|
||||
} from 'foam-core';
|
||||
|
||||
describe('Tags tree panel', () => {
|
||||
let _foam: Foam;
|
||||
let provider: TagsProvider;
|
||||
|
||||
const config: FoamConfig = createConfigFromFolders([]);
|
||||
const mdProvider = new MarkdownResourceProvider(
|
||||
new Matcher(
|
||||
config.workspaceFolders,
|
||||
config.includeGlobs,
|
||||
config.ignoreGlobs
|
||||
)
|
||||
);
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanWorkspace();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
_foam.dispose();
|
||||
await cleanWorkspace();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
_foam = await bootstrap(config, new FileDataStore(), [mdProvider]);
|
||||
provider = new TagsProvider(_foam, _foam.workspace);
|
||||
await closeEditors();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
_foam.dispose();
|
||||
});
|
||||
|
||||
it('correctly provides a tag from a set of notes', async () => {
|
||||
const noteA = createTestNote({
|
||||
tags: new Set(['test']),
|
||||
uri: './note-a.md',
|
||||
});
|
||||
_foam.workspace.set(noteA);
|
||||
provider.refresh();
|
||||
|
||||
const treeItems = (await provider.getChildren()) as Tag[];
|
||||
|
||||
treeItems.map(item => expect(item.tag).toContain('test'));
|
||||
});
|
||||
|
||||
it('correctly handles a parent and child tag', async () => {
|
||||
const noteA = createTestNote({
|
||||
tags: new Set(['parent/child']),
|
||||
uri: './note-a.md',
|
||||
});
|
||||
_foam.workspace.set(noteA);
|
||||
provider.refresh();
|
||||
|
||||
const parentTreeItems = (await provider.getChildren()) as Tag[];
|
||||
const parentTagItem = parentTreeItems.pop();
|
||||
expect(parentTagItem.title).toEqual('parent');
|
||||
|
||||
const childTreeItems = (await provider.getChildren(parentTagItem)) as Tag[];
|
||||
|
||||
childTreeItems.forEach(child => {
|
||||
if (child instanceof Tag) {
|
||||
expect(child.title).toEqual('child');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('correctly handles a single parent and multiple child tag', async () => {
|
||||
const noteA = createTestNote({
|
||||
tags: new Set(['parent/child']),
|
||||
uri: './note-a.md',
|
||||
});
|
||||
_foam.workspace.set(noteA);
|
||||
const noteB = createTestNote({
|
||||
tags: new Set(['parent/subchild']),
|
||||
uri: './note-b.md',
|
||||
});
|
||||
_foam.workspace.set(noteB);
|
||||
provider.refresh();
|
||||
|
||||
const parentTreeItems = (await provider.getChildren()) as Tag[];
|
||||
const parentTagItem = parentTreeItems.filter(
|
||||
item => item instanceof Tag
|
||||
)[0];
|
||||
|
||||
expect(parentTagItem.title).toEqual('parent');
|
||||
expect(parentTreeItems).toHaveLength(1);
|
||||
|
||||
const childTreeItems = (await provider.getChildren(parentTagItem)) as Tag[];
|
||||
|
||||
childTreeItems.forEach(child => {
|
||||
if (child instanceof Tag) {
|
||||
expect(['child', 'subchild']).toContain(child.title);
|
||||
expect(child.title).not.toEqual('parent');
|
||||
}
|
||||
});
|
||||
expect(childTreeItems).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('correctly handles a single parent and child tag in the same note', async () => {
|
||||
const noteC = createTestNote({
|
||||
tags: new Set(['main', 'main/subtopic']),
|
||||
title: 'Test note',
|
||||
uri: './note-c.md',
|
||||
});
|
||||
|
||||
_foam.workspace.set(noteC);
|
||||
|
||||
provider.refresh();
|
||||
|
||||
const parentTreeItems = (await provider.getChildren()) as Tag[];
|
||||
const parentTagItem = parentTreeItems.filter(
|
||||
item => item instanceof Tag
|
||||
)[0];
|
||||
|
||||
expect(parentTagItem.title).toEqual('main');
|
||||
|
||||
const childTreeItems = (await provider.getChildren(parentTagItem)) as Tag[];
|
||||
|
||||
childTreeItems
|
||||
.filter(item => item instanceof TagReference)
|
||||
.forEach(item => {
|
||||
expect(item.title).toEqual('Test note');
|
||||
});
|
||||
|
||||
childTreeItems
|
||||
.filter(item => item instanceof Tag)
|
||||
.forEach(item => {
|
||||
expect(['main/subtopic']).toContain(item.tag);
|
||||
expect(item.title).toEqual('subtopic');
|
||||
});
|
||||
|
||||
expect(childTreeItems).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
@@ -3,6 +3,7 @@ import { Foam, Resource, URI, FoamWorkspace } from 'foam-core';
|
||||
import { FoamFeature } from '../../types';
|
||||
import { getNoteTooltip, getContainsTooltip, isSome } from '../../utils';
|
||||
|
||||
const TAG_SEPARATOR = '/';
|
||||
const feature: FoamFeature = {
|
||||
activate: async (
|
||||
context: vscode.ExtensionContext,
|
||||
@@ -67,19 +68,45 @@ export class TagsProvider implements vscode.TreeDataProvider<TagTreeItem> {
|
||||
|
||||
getChildren(element?: Tag): Thenable<TagTreeItem[]> {
|
||||
if (element) {
|
||||
const references: TagReference[] = element.notes
|
||||
const nestedTagItems: TagTreeItem[] = this.tags
|
||||
.filter(item => item.tag.indexOf(element.title + TAG_SEPARATOR) > -1)
|
||||
.map(
|
||||
item =>
|
||||
new Tag(
|
||||
item.tag,
|
||||
item.tag.substring(item.tag.indexOf(TAG_SEPARATOR) + 1),
|
||||
item.notes
|
||||
)
|
||||
)
|
||||
.sort((a, b) => a.title.localeCompare(b.title));
|
||||
|
||||
const references: TagTreeItem[] = element.notes
|
||||
.map(({ uri }) => this.foam.workspace.get(uri))
|
||||
.map(note => new TagReference(element.tag, note));
|
||||
.filter(note => note.tags.has(element.tag))
|
||||
.map(note => new TagReference(element.tag, note))
|
||||
.sort((a, b) => a.title.localeCompare(b.title));
|
||||
|
||||
return Promise.resolve([
|
||||
new TagSearch(element.tag),
|
||||
...references.sort((a, b) => a.title.localeCompare(b.title)),
|
||||
new TagSearch(element.title),
|
||||
...nestedTagItems,
|
||||
...references,
|
||||
]);
|
||||
}
|
||||
if (!element) {
|
||||
const tags: Tag[] = this.tags.map(
|
||||
({ tag, notes }) => new Tag(tag, notes)
|
||||
);
|
||||
const tags: Tag[] = this.tags
|
||||
.map(({ tag, notes }) => {
|
||||
const parentTag =
|
||||
tag.indexOf(TAG_SEPARATOR) > 0
|
||||
? tag.substring(0, tag.indexOf(TAG_SEPARATOR))
|
||||
: tag;
|
||||
|
||||
return new Tag(parentTag, parentTag, notes);
|
||||
})
|
||||
.filter(
|
||||
(value, index, array) =>
|
||||
array.findIndex(tag => tag.title === value.title) === index
|
||||
);
|
||||
|
||||
return Promise.resolve(tags.sort((a, b) => a.tag.localeCompare(b.tag)));
|
||||
}
|
||||
}
|
||||
@@ -102,9 +129,10 @@ type TagMetadata = { title: string; uri: URI };
|
||||
export class Tag extends vscode.TreeItem {
|
||||
constructor(
|
||||
public readonly tag: string,
|
||||
public readonly title: string,
|
||||
public readonly notes: TagMetadata[]
|
||||
) {
|
||||
super(tag, vscode.TreeItemCollapsibleState.Collapsed);
|
||||
super(title, vscode.TreeItemCollapsibleState.Collapsed);
|
||||
this.description = `${this.notes.length} reference${
|
||||
this.notes.length !== 1 ? 's' : ''
|
||||
}`;
|
||||
@@ -116,6 +144,7 @@ export class Tag extends vscode.TreeItem {
|
||||
}
|
||||
|
||||
export class TagSearch extends vscode.TreeItem {
|
||||
public readonly title: string;
|
||||
constructor(public readonly tag: string) {
|
||||
super(`Search #${tag}`, vscode.TreeItemCollapsibleState.None);
|
||||
const searchString = `#${tag}`;
|
||||
|
||||
@@ -47,6 +47,7 @@ export const createTestNote = (params: {
|
||||
title?: string;
|
||||
definitions?: NoteLinkDefinition[];
|
||||
links?: Array<{ slug: string } | { to: string }>;
|
||||
tags?: Set<string>;
|
||||
text?: string;
|
||||
root?: URI;
|
||||
}): Resource => {
|
||||
@@ -57,7 +58,7 @@ export const createTestNote = (params: {
|
||||
properties: {},
|
||||
title: params.title ?? path.parse(strToUri(params.uri).path).base,
|
||||
definitions: params.definitions ?? [],
|
||||
tags: new Set(),
|
||||
tags: params.tags ?? new Set(),
|
||||
links: params.links
|
||||
? params.links.map((link, index) => {
|
||||
const range = Range.create(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FoamGraph, FoamWorkspace, URI } from 'foam-core';
|
||||
import { FoamWorkspace } from 'foam-core';
|
||||
import { OPEN_COMMAND } from '../features/utility-commands';
|
||||
import {
|
||||
GroupedResoucesConfigGroupBy,
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
import {
|
||||
extractFoamTemplateFrontmatterMetadata,
|
||||
removeFoamMetadata,
|
||||
} from './template-frontmatter-parser';
|
||||
|
||||
describe('extractFoamTemplateFrontmatterMetadata', () => {
|
||||
test('Returns an empty object if there is not frontmatter', () => {
|
||||
const input = `# $FOAM_TITLE`;
|
||||
const expectedMetadata = new Map<string, string>();
|
||||
const expected = [expectedMetadata, input];
|
||||
const result = extractFoamTemplateFrontmatterMetadata(input);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
test('Returns an empty object if `foam_template` is not used', () => {
|
||||
const input = `---
|
||||
foo: bar
|
||||
---
|
||||
|
||||
# $FOAM_TITLE
|
||||
`;
|
||||
|
||||
const expectedMetadata = new Map<string, string>();
|
||||
const expected = [expectedMetadata, input];
|
||||
const result = extractFoamTemplateFrontmatterMetadata(input);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
test('Returns an empty object if foam_template is not a YAML mapping', () => {
|
||||
const input = `---json
|
||||
{
|
||||
"foo": "bar",
|
||||
"foam_template": 4
|
||||
}
|
||||
---
|
||||
|
||||
# $FOAM_TITLE
|
||||
`;
|
||||
|
||||
const expectedMetadata = new Map<string, string>();
|
||||
const expected = [expectedMetadata, input];
|
||||
const result = extractFoamTemplateFrontmatterMetadata(input);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
test('Returns an empty object if frontmatter is not YAML', () => {
|
||||
const input = `---json
|
||||
{
|
||||
"foo": "bar",
|
||||
"foam_template": {
|
||||
"filepath": "journal/$CURRENT_YEAR-$CURRENT_MONTH-$CURRENT_DATE_$FOAM_TITLE.md"
|
||||
}
|
||||
}
|
||||
---
|
||||
|
||||
# $FOAM_TITLE
|
||||
`;
|
||||
|
||||
const expectedMetadata = new Map<string, string>();
|
||||
const expected = [expectedMetadata, input];
|
||||
const result = extractFoamTemplateFrontmatterMetadata(input);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
test('Returns the `foam_template` metadata when it is used in its own frontmatter block', () => {
|
||||
const input = `---
|
||||
foam_template:
|
||||
name: My Note Template
|
||||
description: This is my note template
|
||||
filepath: journal/$CURRENT_YEAR-$CURRENT_MONTH-$CURRENT_DATE_$FOAM_TITLE.md
|
||||
---
|
||||
|
||||
# $FOAM_TITLE
|
||||
`;
|
||||
|
||||
const output = `
|
||||
# $FOAM_TITLE
|
||||
`;
|
||||
|
||||
const expectedMetadata = new Map<string, string>();
|
||||
expectedMetadata.set('name', 'My Note Template');
|
||||
expectedMetadata.set('description', 'This is my note template');
|
||||
expectedMetadata.set(
|
||||
'filepath',
|
||||
'journal/$CURRENT_YEAR-$CURRENT_MONTH-$CURRENT_DATE_$FOAM_TITLE.md'
|
||||
);
|
||||
const expected = [expectedMetadata, output];
|
||||
const result = extractFoamTemplateFrontmatterMetadata(input);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
test('Returns the `foam_template` metadata when it is used in its own frontmatter block (and there is another frontmatter block after)', () => {
|
||||
const input = `---
|
||||
foam_template:
|
||||
filepath: journal/$CURRENT_YEAR-$CURRENT_MONTH-$CURRENT_DATE_$FOAM_TITLE.md
|
||||
description: This is my note template
|
||||
name: My Note Template
|
||||
---
|
||||
|
||||
---
|
||||
foo: bar
|
||||
# A YAML comment
|
||||
metadata: &info
|
||||
title: The Gentlemen
|
||||
year: 2019
|
||||
more_metadata: *info
|
||||
---
|
||||
|
||||
# $FOAM_TITLE
|
||||
`;
|
||||
|
||||
const output = `---
|
||||
foo: bar
|
||||
# A YAML comment
|
||||
metadata: &info
|
||||
title: The Gentlemen
|
||||
year: 2019
|
||||
more_metadata: *info
|
||||
---
|
||||
|
||||
# $FOAM_TITLE
|
||||
`;
|
||||
|
||||
const expectedMetadata = new Map<string, string>();
|
||||
expectedMetadata.set('name', 'My Note Template');
|
||||
expectedMetadata.set('description', 'This is my note template');
|
||||
expectedMetadata.set(
|
||||
'filepath',
|
||||
'journal/$CURRENT_YEAR-$CURRENT_MONTH-$CURRENT_DATE_$FOAM_TITLE.md'
|
||||
);
|
||||
const expected = [expectedMetadata, output];
|
||||
const result = extractFoamTemplateFrontmatterMetadata(input);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
test('Returns the `foam_template` metadata when it is used in a shared frontmatter block', () => {
|
||||
const input = `---
|
||||
foo: bar
|
||||
foam_template:
|
||||
name: My Note Template
|
||||
filepath: journal/$CURRENT_YEAR-$CURRENT_MONTH-$CURRENT_DATE_$FOAM_TITLE.md
|
||||
description: This is my note template
|
||||
# A YAML comment
|
||||
metadata: &info
|
||||
title: The Gentlemen
|
||||
year: 2019
|
||||
more_metadata: *info
|
||||
---
|
||||
|
||||
# $FOAM_TITLE`;
|
||||
|
||||
const output = `---
|
||||
foo: bar
|
||||
# A YAML comment
|
||||
metadata: &info
|
||||
title: The Gentlemen
|
||||
year: 2019
|
||||
more_metadata: *info
|
||||
---
|
||||
|
||||
# $FOAM_TITLE`;
|
||||
|
||||
const expectedMetadata = new Map<string, string>();
|
||||
expectedMetadata.set('name', 'My Note Template');
|
||||
expectedMetadata.set('description', 'This is my note template');
|
||||
expectedMetadata.set(
|
||||
'filepath',
|
||||
'journal/$CURRENT_YEAR-$CURRENT_MONTH-$CURRENT_DATE_$FOAM_TITLE.md'
|
||||
);
|
||||
const expected = [expectedMetadata, output];
|
||||
const result = extractFoamTemplateFrontmatterMetadata(input);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeFoamMetadata', () => {
|
||||
test('Removes Foam specific frontmatter without messing up non-Foam frontmatter', () => {
|
||||
const input = `---
|
||||
foo: bar
|
||||
foam_template: &foam_template # A YAML comment
|
||||
description: This is my note template
|
||||
filepath: journal/$CURRENT_YEAR-$CURRENT_MONTH-$CURRENT_DATE_$FOAM_TITLE.md # A YAML comment
|
||||
name: My Note Template
|
||||
# A YAML comment
|
||||
metadata: &info
|
||||
title: The Gentlemen
|
||||
year: 2019
|
||||
more_metadata: *info
|
||||
---
|
||||
|
||||
# $FOAM_TITLE`;
|
||||
|
||||
const expected = `---
|
||||
foo: bar
|
||||
# A YAML comment
|
||||
metadata: &info
|
||||
title: The Gentlemen
|
||||
year: 2019
|
||||
more_metadata: *info
|
||||
---
|
||||
|
||||
# $FOAM_TITLE`;
|
||||
|
||||
const result = removeFoamMetadata(input);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import matter from 'gray-matter';
|
||||
|
||||
export function extractFoamTemplateFrontmatterMetadata(
|
||||
contents: string
|
||||
): [Map<string, string>, string] {
|
||||
// Need to pass in empty options object, in order to bust a cache
|
||||
// See https://github.com/jonschlinkert/gray-matter/issues/124
|
||||
const parsed = matter(contents, {});
|
||||
let metadata = new Map<string, string>();
|
||||
|
||||
if (parsed.language !== 'yaml') {
|
||||
// We might allow this in the future, once it has been tested adequately.
|
||||
// But for now we'll be safe and prevent people from using anything else.
|
||||
return [metadata, contents];
|
||||
}
|
||||
|
||||
const frontmatter = parsed.data;
|
||||
const frontmatterKeys = Object.keys(frontmatter);
|
||||
const foamMetadata = frontmatter['foam_template'];
|
||||
|
||||
if (typeof foamMetadata !== 'object') {
|
||||
return [metadata, contents];
|
||||
}
|
||||
|
||||
const containsFoam = foamMetadata !== undefined;
|
||||
const onlyFoam = containsFoam && frontmatterKeys.length === 1;
|
||||
metadata = new Map<string, string>(
|
||||
Object.entries((foamMetadata as object) || {})
|
||||
);
|
||||
|
||||
let newContents = contents;
|
||||
if (containsFoam) {
|
||||
if (onlyFoam) {
|
||||
// We'll remove the entire frontmatter block
|
||||
newContents = parsed.content;
|
||||
|
||||
// If there is another frontmatter block, we need to remove
|
||||
// the leading space left behind.
|
||||
const anotherFrontmatter = matter(newContents.trimStart()).matter !== '';
|
||||
if (anotherFrontmatter) {
|
||||
newContents = newContents.trimStart();
|
||||
}
|
||||
} else {
|
||||
// We'll remove only the Foam bits
|
||||
newContents = removeFoamMetadata(contents);
|
||||
}
|
||||
}
|
||||
|
||||
return [metadata, newContents];
|
||||
}
|
||||
|
||||
export function removeFoamMetadata(contents: string) {
|
||||
return contents.replace(
|
||||
/^\s*foam_template:.*?\n(?:\s*(?:filepath|name|description):.*\n)+/gm,
|
||||
''
|
||||
);
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
5
packages/foam-vscode/static/dataviz/force-graph.1.40.5.min.js
vendored
Normal file
5
packages/foam-vscode/static/dataviz/force-graph.1.40.5.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<script data-replace src="./d3.v6.min.js"></script>
|
||||
<script data-replace src="./force-graph.1.34.1.min.js"></script>
|
||||
<script data-replace src="./force-graph.1.40.5.min.js"></script>
|
||||
<style>
|
||||
body {
|
||||
overflow: hidden;
|
||||
|
||||
@@ -2552,16 +2552,16 @@ flatted@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138"
|
||||
integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==
|
||||
|
||||
foam-core@^0.12.0:
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/foam-core/-/foam-core-0.12.0.tgz#2bbdc5b4883daba0b9ff038183013fab927a1451"
|
||||
integrity sha512-R0pFgibZbp/4PcZ7F17n+OQrKvl/kuCoCkUB/dEtZSJD1n8ND1lZp7yllQT8Y8+S1EEzoJD46mK8DttTwAsdMg==
|
||||
foam-core@^0.13.4:
|
||||
version "0.13.4"
|
||||
resolved "https://registry.yarnpkg.com/foam-core/-/foam-core-0.13.4.tgz#2520f9aa70f4fd23e1f6d8614445d44c68931034"
|
||||
integrity sha512-5EydR6kIPAZdHSaTWVaQjLXb+GqFkV3jYW989RIRRX7ad7g0dzIigtd3iXtU2bUX6e+Wne7pjlxeLWvYICbvZA==
|
||||
dependencies:
|
||||
detect-newline "^3.1.0"
|
||||
fast-array-diff "^1.0.0"
|
||||
github-slugger "^1.3.0"
|
||||
glob "^7.1.6"
|
||||
lodash "^4.17.19"
|
||||
lodash "^4.17.21"
|
||||
micromatch "^4.0.2"
|
||||
remark-frontmatter "^2.0.0"
|
||||
remark-parse "^8.0.2"
|
||||
@@ -2786,9 +2786,9 @@ has-values@^1.0.0:
|
||||
kind-of "^4.0.0"
|
||||
|
||||
hosted-git-info@^2.1.4:
|
||||
version "2.8.8"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
|
||||
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
|
||||
version "2.8.9"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
|
||||
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
|
||||
|
||||
html-encoding-sniffer@^2.0.1:
|
||||
version "2.0.1"
|
||||
@@ -3822,16 +3822,11 @@ lodash.sortby@^4.7.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
|
||||
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
|
||||
|
||||
lodash@4.x:
|
||||
lodash@4.x, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
||||
lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19:
|
||||
version "4.17.20"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
|
||||
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
|
||||
|
||||
loose-envify@^1.0.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||
@@ -5586,9 +5581,9 @@ write@1.0.3:
|
||||
mkdirp "^0.5.1"
|
||||
|
||||
ws@^7.2.3:
|
||||
version "7.3.1"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8"
|
||||
integrity sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==
|
||||
version "7.4.6"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c"
|
||||
integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==
|
||||
|
||||
xml-name-validator@^3.0.0:
|
||||
version "3.0.0"
|
||||
|
||||
@@ -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 -->
|
||||
[](#contributors-)
|
||||
[](#contributors-)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
[](https://foambubble.github.io/join-discord/g)
|
||||
@@ -157,6 +157,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<td align="center"><a href="https://github.com/daniel-vera-g"><img src="https://avatars.githubusercontent.com/u/28257108?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Daniel VG</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=daniel-vera-g" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Barabazs"><img src="https://avatars.githubusercontent.com/u/31799121?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Barabas</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Barabazs" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://enginveske@gmail.com"><img src="https://avatars.githubusercontent.com/u/43685404?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Engincan VESKE</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=EngincanV" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.paulderaaij.nl"><img src="https://avatars.githubusercontent.com/u/495374?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Paul de Raaij</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=pderaaij" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -7356,7 +7356,7 @@ lodash.uniq@^4.5.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
|
||||
|
||||
lodash@4.x, lodash@^4.11.2, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.4, lodash@^4.2.1:
|
||||
lodash@4.x, lodash@^4.11.2, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.2.1:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
||||
Reference in New Issue
Block a user