mirror of
https://github.com/foambubble/foam.git
synced 2026-01-11 06:58:11 -05:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f6004dd6d | ||
|
|
1258cef04c | ||
|
|
7adbffcfc2 | ||
|
|
8395a408f2 | ||
|
|
721a6cc935 | ||
|
|
1659fe37d9 | ||
|
|
32f9120864 | ||
|
|
cec0aecd06 | ||
|
|
c59584d342 | ||
|
|
ea930d3d17 | ||
|
|
130b839ee9 | ||
|
|
1b4dc8a8b1 | ||
|
|
7fcbf1acf1 | ||
|
|
011706904b | ||
|
|
c14e1e6349 | ||
|
|
97629a2ce6 | ||
|
|
6b3eef43a8 | ||
|
|
d4adce6730 | ||
|
|
ae65f53540 | ||
|
|
8998204298 | ||
|
|
92f6e001e5 | ||
|
|
889f93a7e1 | ||
|
|
4db8c55969 | ||
|
|
7e740fec0f | ||
|
|
beae852c21 | ||
|
|
85e857d973 | ||
|
|
667eee0e10 | ||
|
|
b6e68b3605 | ||
|
|
b9b0f9b515 | ||
|
|
95399977ec | ||
|
|
f759e7cd6e | ||
|
|
5839455535 | ||
|
|
7e4ae82fe1 | ||
|
|
e47155424f | ||
|
|
903a191394 | ||
|
|
5c6212dc96 | ||
|
|
bca9756e2b | ||
|
|
2e435a3828 | ||
|
|
a0f83e7510 | ||
|
|
641024b01b | ||
|
|
c5a7d02656 | ||
|
|
b76196cd23 | ||
|
|
61ae468a70 | ||
|
|
6119278915 |
@@ -697,6 +697,24 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "bronson",
|
||||
"name": "Scott Bronson",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1776?v=4",
|
||||
"profile": "https://github.com/bronson",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "rafo",
|
||||
"name": "Rafael Riedel",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/41793?v=4",
|
||||
"profile": "http://rafaelriedel.de",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/bug.md
vendored
2
.github/ISSUE_TEMPLATE/bug.md
vendored
@@ -20,4 +20,4 @@ labels: bug
|
||||
Feel free to attach any of the following that might help with debugging the issue:
|
||||
- screenshots
|
||||
- a zip with a minimal repo to reproduce the issue
|
||||
- the Foam log in VsCode (see [instructions](https://github.com/foambubble/foam/blob/master/docs/foam-logging-in-vscode.md))
|
||||
- the Foam log in VsCode (see [instructions](https://github.com/foambubble/foam/blob/master/docs/features/foam-logging-in-vscode.md))
|
||||
|
||||
3
docs/.vscode/extensions.json
vendored
3
docs/.vscode/extensions.json
vendored
@@ -14,9 +14,6 @@
|
||||
// Tons of markdown goodies (lists, tables of content, so much more)
|
||||
"yzhang.markdown-all-in-one",
|
||||
|
||||
// [[wiki-links]], backlinking etc
|
||||
"kortina.vscode-markdown-notes",
|
||||
|
||||
// Graph visualizer
|
||||
"tchayen.markdown-links",
|
||||
|
||||
|
||||
@@ -19,21 +19,21 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
document
|
||||
.querySelectorAll(".markdown-body a[title]:not([href^=http])")
|
||||
.forEach((a) => {
|
||||
// filter to only wiki-links
|
||||
// filter to only wikilinks
|
||||
var prev = a.previousSibling;
|
||||
var next = a.nextSibling;
|
||||
if (
|
||||
prev instanceof Text && prev.textContent.endsWith('[') &&
|
||||
prev instanceof Text && prev.textContent.endsWith('[') &&
|
||||
next instanceof Text && next.textContent.startsWith(']')
|
||||
) {
|
||||
|
||||
|
||||
// remove surrounding brackets
|
||||
prev.textContent = prev.textContent.slice(0, -1);
|
||||
next.textContent = next.textContent.slice(1);
|
||||
|
||||
// add CSS list for styling
|
||||
a.classList.add('wikilink');
|
||||
|
||||
|
||||
// replace page-link with "Page Title"...
|
||||
a.innerText = a.title;
|
||||
|
||||
|
||||
@@ -56,16 +56,12 @@ Tests in `foam-vscode` live alongside the code in `src`.
|
||||
|
||||
This guide assumes you read the previous instructions and you're set up to work on Foam.
|
||||
|
||||
1. Now we'll use the launch configuration defined at [`.vscode/launch.json`](https://github.com/foambubble/foam/blob/master/.vscode/launch.json) to start a new extension host of VS Code. From the root, or the `foam-vscode` workspace, press f5.
|
||||
1. Now we'll use the launch configuration defined at [`.vscode/launch.json`](https://github.com/foambubble/foam/blob/master/.vscode/launch.json) to start a new extension host of VS Code. Open the "Run and Debug" Activity (the icon with the bug on the far left) and select "Run VSCode Extension" in the pop-up menu. Now hit F5 or click the green arrow "play" button to fire up a new copy of VS Code with your extension installed.
|
||||
|
||||
2. In the new extension host of VS Code that launched, open a Foam workspace (e.g. your personal one, or a test-specific one created from [foam-template](https://github.com/foambubble/foam-template)). This is strictly not necessary, but the extension won't auto-run unless it's in a workspace with a `.vscode/foam.json` file.
|
||||
|
||||
3. Test a command to make sure it's working as expected. Open the Command Palette (Ctrl/Cmd + Shift + P) and select "Foam: Update Markdown Reference List". If you see no errors, it's good to go!
|
||||
|
||||
For more resources related to the VS Code Extension, check out the links below:
|
||||
|
||||
- [[tutorial-adding-a-new-command-to-the-vs-code-extension]]
|
||||
|
||||
---
|
||||
|
||||
Feel free to modify and submit a PR if this guide is out-of-date or contains errors!
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
`foam-core`'s primary responsibility is to build an API on top of a workspace of markdown files, which allows us to:
|
||||
|
||||
- Treat files as a graph, based on links
|
||||
- Can be either [[wiki-links]] or relative `[markdown](links.md)` style
|
||||
- Can be either [[wikilinks]] or relative `[markdown](links.md)` style
|
||||
- We need to know about the edges (connections) as well as nodes
|
||||
- What link points to what other file, etc.
|
||||
- Needs to have the exact link text, e.g. even if `[[some-page]]` or `[[some-page.md]]` or `[[Some Page]]` point to the same document (`./some-page.md`), we need to know which format was used, so [[link-reference-definitions]] can be generated correctly
|
||||
@@ -61,7 +61,7 @@ Here are some example use cases that the core should support. They don't need to
|
||||
|
||||
- Adding and editing page content
|
||||
- [[materialized-backlinks]]
|
||||
- [[link-reference-definitions]] for [[wiki-links]]
|
||||
- [[link-reference-definitions]] for [[wikilinks]]
|
||||
- [Frontmatter](https://jekyllrb.com/docs/front-matter/)
|
||||
- Finding all documents with `#tag`
|
||||
- Finding all documents with instances of `[[link]]`
|
||||
@@ -99,7 +99,7 @@ Useful for knowing what needs to be supported. See [[feature-comparison]].
|
||||
[workspace-janitor]: ../features/workspace-janitor.md "Janitor"
|
||||
[cli]: ../features/cli.md "Command Line Interface"
|
||||
[build-vs-assemble]: build-vs-assemble.md "Build vs Assemble"
|
||||
[wiki-links]: ../wiki-links.md "Wiki Links"
|
||||
[wikilinks]: ../wikilinks.md "Wikilinks"
|
||||
[link-reference-definitions]: ../features/link-reference-definitions.md "Link Reference Definitions"
|
||||
[materialized-backlinks]: materialized-backlinks.md "Materialized Backlinks (stub)"
|
||||
[todo]: todo.md "Todo"
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
# Foam File Format
|
||||
|
||||
This file is an example of a valid Foam file. Essentially it's just a markdown file with a bit of additional support for mediawiki-style `[[wiki-links]]`.
|
||||
This file is an example of a valid Foam file. Essentially it's just a markdown file with a bit of additional support for MediaWiki-style `[[wikilinks]]`.
|
||||
|
||||
Here are a few specific constraints, mainly because our tooling is a bit fragmented. Most of these should be eventually lifted, and our requirement should just be "Markdown with `[[wiki-links]]`:
|
||||
Here are a few specific constraints, mainly because our tooling is a bit fragmented. Most of these should be eventually lifted, and our requirement should just be "Markdown with `[[wikilinks]]`:
|
||||
|
||||
- **The first top level `# Heading` will be used as title for the note.**
|
||||
- If not available, we will use the file name
|
||||
- **File name should have extension `.md`**
|
||||
- This is a temporary limitation and will be lifted in future versions.
|
||||
- At least `.mdx` will be supported, but ideally we'll support any file that you can map to `Markdown` language mode in VS Code
|
||||
- **In addition to normal Markdown Links syntax you can use `[[media-wiki]]` links.** See [[wiki-links]] for more details.
|
||||
- **In addition to normal Markdown Links syntax you can use `[[MediaWiki]]` links.** See [[wikilinks]] for more details.
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[wiki-links]: ../wiki-links.md "Wiki Links"
|
||||
[wikilinks]: ../wikilinks.md "Wikilinks"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
|
||||
### File-by-file Insertion
|
||||
|
||||
For the time being, if you want to get [[wiki-links]] into all files within the workspace, you'll need to generate the link reference definitions yourself file-by-file (with the assistance of Foam).
|
||||
For the time being, if you want to get [[wikilinks]] into all files within the workspace, you'll need to generate the link reference definitions yourself file-by-file (with the assistance of Foam).
|
||||
|
||||
### Wikilinks don't work on GitHub
|
||||
|
||||
> **TL;DR;** [workaround](#workaround) in the end of the chapter.
|
||||
|
||||
If you click any of the wiki-links on GitHub web UI (such as the `README.md` of a project), you'll notice that the links break with a 404 error.
|
||||
If you click any of the wikilinks on GitHub web UI (such as the `README.md` of a project), you'll notice that the links break with a 404 error.
|
||||
|
||||
At the time of writing (June 28 2020) this is a known, but unsolved error. To understand why this is the case, we need to understand what we are trading off.
|
||||
|
||||
@@ -59,7 +59,7 @@ Problem space in essence:
|
||||
- may clutter the search results
|
||||
- During build-time (when converting markdown to html for publishing purposes)
|
||||
- link reference definitions are needed, if the files are published via such tools (or to such platforms) that don't understand wikilinks
|
||||
- link reference definitions might have to be in different formats depending on the publish target (e.g. Github pages vs Github UI)
|
||||
- link reference definitions might have to be in different formats depending on the publish target (e.g. GitHub pages vs GitHub UI)
|
||||
|
||||
The potential solution:
|
||||
|
||||
@@ -122,8 +122,8 @@ The potential solution:
|
||||
|
||||
```
|
||||
|
||||
- With Foam repo, just use edit-time link reference definitions with '.md' extension - this makes the links work in the Github UI
|
||||
- Have publish target defined for Github pages, that doesn't use '.md' extension, but still has the link reference definitions. Generate the output into gh-pages branch (or separate repo) with automation.
|
||||
- With Foam repo, just use edit-time link reference definitions with '.md' extension - this makes the links work in the GitHub UI
|
||||
- Have publish target defined for GitHub pages, that doesn't use '.md' extension, but still has the link reference definitions. Generate the output into gh-pages branch (or separate repo) with automation.
|
||||
- This naturally requires first removing the existing link reference definitions during the build
|
||||
- Other
|
||||
- To clean up the search results, remove link reference definition section guards (assuming that these are not defined by the markdown spec). Use unifiedjs parse trees to identify if there's missing (or surplus) definitions (check if they are identified properly by the library), and just add the needed definitions to the bottom of the file (without guards) AND remove them if they are not needed (anywhere from the file).
|
||||
@@ -137,7 +137,7 @@ UI-wise, the publish targets could be picked in some similar fashion as the run/
|
||||
- [tracking issue on GitHub](https://github.com/foambubble/foam/issues/16)
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[wiki-links]: ../wiki-links.md "Wiki Links"
|
||||
[wikilinks]: ../wikilinks.md "Wikilinks"
|
||||
[link-reference-definitions]: ../features/link-reference-definitions.md "Link Reference Definitions"
|
||||
[backlinking]: ../features/backlinking.md "Backlinking"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
@@ -25,7 +25,7 @@ If a roadmap item is a stub, **consider** opening a [GitHub issue](https://githu
|
||||
- [[improve-default-workspace-settings]]
|
||||
- Discussion: [foam#270](https://github.com/foambubble/foam/issues/270)
|
||||
- Improve [[git-integration]]
|
||||
- Fix [[wiki-links]] compatibility issues
|
||||
- Fix [[wikilinks]] compatibility issues
|
||||
- Simplify [[foam-file-format]]
|
||||
|
||||
### Core features
|
||||
@@ -87,7 +87,7 @@ The community is working on a number of automated scripts to help you migrate to
|
||||
[recipes]: ../recipes/recipes.md "Recipes"
|
||||
[contribution-guide]: ../contribution-guide.md "Contribution Guide"
|
||||
[git-integration]: ../features/git-integration.md "Git Integration"
|
||||
[wiki-links]: ../wiki-links.md "Wiki Links"
|
||||
[wikilinks]: ../wikilinks.md "Wikilinks"
|
||||
[foam-file-format]: foam-file-format.md "Foam File Format"
|
||||
[unlinked-references]: unlinked-references.md "Unlinked references (stub)"
|
||||
[make-backlinks-more-prominent]: ../recipes/make-backlinks-more-prominent.md "Make Backlinks More Prominent"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Backlinking
|
||||
|
||||
When using [[wiki-links]], you can find all notes that link to a specific note in the [VS Code Markdown Notes](https://marketplace.visualstudio.com/items?itemName=kortina.vscode-markdown-notes) **Backlinks Explorer**
|
||||
When using [[wikilinks]], you can find all notes that link to a specific note in the [VS Code Markdown Notes](https://marketplace.visualstudio.com/items?itemName=kortina.vscode-markdown-notes) **Backlinks Explorer**
|
||||
|
||||
- Run `Cmd` + `Shift` + `P` (`Ctrl` + `Shift` + `P` for Windows), type "backlinks" and run the **Explorer: Focus on Backlinks** view.
|
||||
- Keep this pane always visible to discover relationships between your thoughts
|
||||
@@ -8,7 +8,7 @@ When using [[wiki-links]], you can find all notes that link to a specific note i
|
||||
- Finding backlinks in published Foam workspaces via [[materialized-backlinks]] is on the [[roadmap]] but not yet implemented.
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[wiki-links]: ../wiki-links.md "Wiki Links"
|
||||
[wikilinks]: ../wikilinks.md "Wikilinks"
|
||||
[make-backlinks-more-prominent]: ../recipes/make-backlinks-more-prominent.md "Make Backlinks More Prominent"
|
||||
[materialized-backlinks]: ../dev/materialized-backlinks.md "Materialized Backlinks (stub)"
|
||||
[roadmap]: ../dev/roadmap.md "Roadmap"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Creating New Notes
|
||||
|
||||
- Write out a new `[[wiki-link]]` and `Cmd` + `Click` to create a new file and enter it.
|
||||
- Write out a new `[[wikilink]]` and `Cmd` + `Click` to create a new file and enter it.
|
||||
- For keyboard navigation, use the 'Follow Definition' key `F12` (or [remap key binding](https://code.visualstudio.com/docs/getstarted/keybindings) to something more ergonomic)
|
||||
- `Cmd` + `Shift` + `P` (`Ctrl` + `Shift` + `P` for Windows), execute `Foam: Create New Note` and enter a **Title Case Name** to create `Title Case Name.md`
|
||||
- Add a keyboard binding to make creating new notes easier.
|
||||
|
||||
@@ -25,9 +25,9 @@ The above configuration would create a file `journal/note-2020-07-25.mdx`, with
|
||||
|
||||
## Daily Note Templates
|
||||
|
||||
In the future, Foam may provide a functionality for specifying a template for new Daily Notes and other types of documents.
|
||||
Daily notes can also make use of [templates](note-templates.md), by defining a special `.foam/templates/daily-note.md` template.
|
||||
|
||||
In the meantime, you can use [VS Code Snippets](https://code.visualstudio.com/docs/editor/userdefinedsnippets) for defining your own Daily Note template.
|
||||
See [Note Templates](note-templates.md) for details of the features available in templates.
|
||||
|
||||
## Roam-style Automatic Daily Notes
|
||||
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
# Foam Local Plugins
|
||||
|
||||
Foam can use workspace plugins to provide customization for users.
|
||||
|
||||
## ATTENTION
|
||||
|
||||
This feature is experimental and its API subject to change.
|
||||
**Local plugins can execute arbitrary code on your machine** - ensure you trust the content of the repo.
|
||||
|
||||
## Goal
|
||||
|
||||
Here are some of the things that we could enable with local plugins in Foam:
|
||||
|
||||
- extend the document syntax to support roam style attributes (e.g. `stage:: seedling`)
|
||||
- automatically add tags to my notes based on the location in the repo (e.g. notes in `/areas/finance` will automatically get the `#finance` tag)
|
||||
- add a new CLI command to support some internal use case or automate import/export
|
||||
- extend the VSCode experience to support one's own workflow, e.g. weekly note, templates, extra panels, foam model derived TOC, ... all without having to write/deploy a VSCode extension
|
||||
|
||||
## How to enable local plugins
|
||||
|
||||
Plugins can execute arbitrary code on the client's machine.
|
||||
For this reason this feature is disabled by default, and needs to be explicitly enabled.
|
||||
|
||||
To enable the feature:
|
||||
|
||||
- create a `~/.foam/config.json` file
|
||||
- add the following content to the file
|
||||
|
||||
```
|
||||
{
|
||||
"experimental": {
|
||||
"localPlugins": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For security reasons this setting can only be defined in the user settings file.
|
||||
(otherwise a malicious repo could set it via its `./foam/config.json`)
|
||||
|
||||
- [[todo]] an additional security mechanism would involve having an explicit list of whitelisted repo paths where plugins are allowed. This would provide finer grain control over when to enable or disable the feature.
|
||||
|
||||
## Technical approach
|
||||
|
||||
When Foam is loaded it will check whether the experimental local plugin feature is enabled, and in such case it will:
|
||||
|
||||
- check `.foam/plugins` directory.
|
||||
- each directory in there is considered a plugin
|
||||
- the layout of each directory is
|
||||
- `index.js` contains the main info about the plugin, specifically it exports:
|
||||
- `name: string` the name of the plugin
|
||||
- `description?: string` the description of the plugin
|
||||
- `parser?: ParserPlugin` an object that interacts with the markdown parsing phase
|
||||
|
||||
Currently for simplicity we keep everything in one file. We might in the future split the plugin by domain (e.g. vscode, cli, core, ...)
|
||||
|
||||
[//begin]: # 'Autogenerated link references for markdown compatibility'
|
||||
[todo]: ../dev/todo.md 'Todo'
|
||||
[//end]: # 'Autogenerated link references'
|
||||
@@ -2,22 +2,22 @@
|
||||
|
||||
## Introduction
|
||||
|
||||
When you use `[[wiki-links]]`, the [foam-vscode](https://github.com/foambubble/foam/tree/master/packages/foam-vscode) extension will automatically generate [Markdown Link Reference Definitions](https://spec.commonmark.org/0.29/#link-reference-definitions) at the bottom of the file. This is done to make the content of the file compatible with various Markdown tools (e.g. parsers, static site generators, VS code plugins etc), which don't support `[[wiki-links]]`.
|
||||
When you use `[[wikilinks]]`, the [foam-vscode](https://github.com/foambubble/foam/tree/master/packages/foam-vscode) extension will automatically generate [Markdown Link Reference Definitions](https://spec.commonmark.org/0.29/#link-reference-definitions) at the bottom of the file. This is done to make the content of the file compatible with various Markdown tools (e.g. parsers, static site generators, VS code plugins etc), which don't support `[[wikilinks]]`.
|
||||
|
||||
## Example
|
||||
|
||||
The following example:
|
||||
|
||||
```md
|
||||
- [[wiki-links]]
|
||||
- [[wikilinks]]
|
||||
- [[github-pages]]
|
||||
```
|
||||
|
||||
...generates the following link reference definitions to the bottom of the file:
|
||||
|
||||
```md
|
||||
[wiki-links]: wiki-links "Wiki Links"
|
||||
[github-pages]: github-pages "Github Pages"
|
||||
[wikilinks]: wikilinks "Wikilinks"
|
||||
[github-pages]: github-pages "GitHub Pages"
|
||||
```
|
||||
|
||||
You can open the [raw markdown](https://foambubble.github.io/foam/features/link-reference-definitions.md) to see them at the bottom of this file
|
||||
@@ -26,7 +26,7 @@ You can open the [raw markdown](https://foambubble.github.io/foam/features/link-
|
||||
|
||||
The three components of a link reference definition are `[link-label]: link-target "Link Title"`
|
||||
|
||||
- **link label:** The link text to match in the surrounding markdown document. This matches the inner bracket of the double-bracketed `[[wiki-link]]` notation
|
||||
- **link label:** The link text to match in the surrounding markdown document. This matches the inner bracket of the double-bracketed `[[wikilink]]` notation
|
||||
- **link destination** The target of the matched link
|
||||
- By default we generate links without extension. This can be overridden, see [Configuration](#configuration) below
|
||||
- **"Link Title"** Optional title for link (The Foam template has a snippet of JavaScript to replace this on the website at runtime)
|
||||
|
||||
@@ -24,20 +24,27 @@ To create a note from a template:
|
||||
|
||||
_Theme: Ayu Light_
|
||||
|
||||
## Default template
|
||||
## Special templates
|
||||
### Default template
|
||||
|
||||
The `.foam/templates/new-note.md` template is special in that it is the template that will be used by the `Foam: Create New Note` command.
|
||||
Customize this template to contain content that you want included every time you create a note.
|
||||
|
||||
### Default daily note template
|
||||
|
||||
The `.foam/templates/daily-note.md` template is special in that it is the template that will be used when creating daily notes (e.g. by using `Foam: Open Daily Note`).
|
||||
Customize this template to contain content that you want included every time you create a daily note.
|
||||
|
||||
## Variables
|
||||
|
||||
Templates can use all the variables available in [VS Code Snippets](https://code.visualstudio.com/docs/editor/userdefinedsnippets#_variables).
|
||||
|
||||
In addition, you can also use variables provided by Foam:
|
||||
|
||||
| Name | Description |
|
||||
| ------------ | ----------------------------------------------------------------------------------- |
|
||||
| `FOAM_TITLE` | The title of the note. If used, Foam will prompt you to enter a title for the note. |
|
||||
| Name | Description |
|
||||
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `FOAM_SELECTED_TEXT` | Foam will fill it with selected text when creating a new note, if any text is selected. Selected text will be replaced with a wikilink to the new note. |
|
||||
| `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.
|
||||
|
||||
@@ -58,6 +65,8 @@ Foam-specific variables (e.g. `$FOAM_TITLE`) can be used within template metadat
|
||||
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.
|
||||
|
||||
**Note:** While you can make use of the `filepath` attribute in [daily note](daily-notes.md) templates (`.foam/templates/daily-note.md`), there is currently no way to have `filepath` vary based on the date. This will be improved in the future. For now, you can customize the location of daily notes using the [`foam.openDailyNote` settings](daily-notes.md).
|
||||
|
||||
#### 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:
|
||||
|
||||
@@ -4,15 +4,19 @@
|
||||
|
||||
- [Frequently Asked Questions](#frequently-asked-questions)
|
||||
- [Links/Graphs/BackLinks don't work. How do I enable them?](#linksgraphsbacklinks-dont-work-how-do-i-enable-them)
|
||||
- [I don't want Foam enabled for all my workspaces](#i-dont-want-foam-enabled-for-all-my-workspaces)
|
||||
|
||||
## Links/Graphs/BackLinks don't work. How do I enable them?
|
||||
|
||||
- Ensure that you have all the [[recommended-extensions]] installed in Visual Studio Code
|
||||
- Reload Visual Studio Code by running `Cmd` + `Shift` + `P` (`Ctrl` + `Shift` + `P` for Windows), type "reload" and run the **Developer: Reload Window** command to for the updated extensions take effect
|
||||
- Check the formatting rules for links on [[foam-file-format]], [[wiki-links]] and [[link-formatting-and-autocompletion]]
|
||||
- Check the formatting rules for links on [[foam-file-format]], [[wikilinks]] and [[link-formatting-and-autocompletion]]
|
||||
|
||||
## I don't want Foam enabled for all my workspaces
|
||||
Any extension you install in Visual Studio Code is enabled by default. Give the philosophy of Foam it works out of the box without doing any configuration upfront. In case you want to disable Foam for a specific workspace, or disable Foam by default and enable it for specific workspaces, it is advised to follow the best practices as [documented by Visual Studio Code](https://code.visualstudio.com/docs/editor/extension-marketplace#_manage-extensions)
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[recommended-extensions]: recommended-extensions.md "Recommended Extensions"
|
||||
[foam-file-format]: dev/foam-file-format.md "Foam File Format"
|
||||
[wiki-links]: wiki-links.md "Wiki Links"
|
||||
[wikilinks]: wikilinks.md "Wikilinks"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
@@ -19,7 +19,7 @@ Uncategorised thoughts, to be added
|
||||
- <https://code.visualstudio.com/api/extension-guides/notebook>
|
||||
- Future architecture
|
||||
- Could we do publish-related settings as a pre-push git hook, e.g. generating footnote labels
|
||||
- Running them on Github Actions to edit stuff as it comes in
|
||||
- Running them on GitHub Actions to edit stuff as it comes in
|
||||
- Ideally, we shouldn't have to touch files, should be just markdown
|
||||
- Looking at the errors/warnings/output panes makes me think, what kind of automated quality tools could we write.
|
||||
- Deduplication, finding similarities...
|
||||
|
||||
@@ -37,7 +37,7 @@ Whether you want to build a [Second Brain](https://www.buildingasecondbrain.com/
|
||||
|
||||
1. Create a single **Foam** workspace for all your knowledge and research following the [Getting started](#getting-started) guide.
|
||||
2. Write your thoughts in markdown documents (I like to call them **Bubbles**, but that might be more than a little twee). These documents should be atomic: Put things that belong together into a single document, and limit its content to that single topic. ([source](https://zettelkasten.de/posts/overview/#principles))
|
||||
3. Use Foam's shortcuts and autocompletions to link your thoughts together with `[[wiki-links]]`, and navigate between them to explore your knowledge graph.
|
||||
3. Use Foam's shortcuts and autocompletions to link your thoughts together with `[[wikilinks]]`, and navigate between them to explore your knowledge graph.
|
||||
4. Get an overview of your **Foam** workspace using a [[graph-visualisation]] (⚠️ WIP), and discover relationships between your thoughts with the use of [[backlinking]].
|
||||
|
||||
Foam is a like a bathtub: _What you get out of it depends on what you put into it._
|
||||
@@ -202,6 +202,8 @@ 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/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>
|
||||
<td align="center"><a href="https://github.com/bronson"><img src="https://avatars.githubusercontent.com/u/1776?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Scott Bronson</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=bronson" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://rafaelriedel.de"><img src="https://avatars.githubusercontent.com/u/41793?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Rafael Riedel</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=rafo" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
## Using foam-gatsby-template
|
||||
|
||||
You can use [foam-gatsby-template](https://github.com/mathieudutour/foam-gatsby-template) to generate a static site to host it online on Github or [Vercel](https://vercel.com).
|
||||
You can use [foam-gatsby-template](https://github.com/mathieudutour/foam-gatsby-template) to generate a static site to host it online on GitHub or [Vercel](https://vercel.com).
|
||||
|
||||
### Publishing your foam to GitHub pages
|
||||
|
||||
It comes configured with Github actions to auto deploy to Github pages when changes are pushed to your main branch.
|
||||
It comes configured with GitHub actions to auto deploy to GitHub pages when changes are pushed to your main branch.
|
||||
|
||||
### Publishing your foam to Vercel
|
||||
|
||||
@@ -17,7 +17,7 @@ cd _layouts
|
||||
npm run build
|
||||
```
|
||||
|
||||
Remove `public` from your .gitignore file then commit and push your public folder in `_layouts` to Github.
|
||||
Remove `public` from your .gitignore file then commit and push your public folder in `_layouts` to GitHub.
|
||||
|
||||
Log into your Vercel account. (Create one if you don't have it already.)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Github Pages
|
||||
# GitHub Pages
|
||||
|
||||
- In VSCode workspace settings set `"foam.edit.linkReferenceDefinitions": "withoutExtensions"`
|
||||
- Execute the “Foam: Run Janitor” command from the command palette.
|
||||
|
||||
@@ -82,6 +82,6 @@ Finally, if all is successful, Vercel will show the detected framework: Jekyll.
|
||||
And now, Vercel will take care of building and rendering our foam workspace each time on push. Vercel will publish our site to `xxx.vercel.app`, we can also define a custom domain name for our Vercel website.
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[publish-to-github-pages]: publish-to-github-pages.md "Github Pages"
|
||||
[publish-to-github-pages]: publish-to-github-pages.md "GitHub Pages"
|
||||
[math-support-with-katex]: math-support-with-katex.md "Katex Math Rendering"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
@@ -30,7 +30,7 @@ With this #recipe you can create notes on your iOS device, which will automatica
|
||||
2. the repository name of your Foam repo
|
||||
3. the GitHub access token from step 7
|
||||
4. An author name
|
||||
11. Check your Github repo for a commit
|
||||
11. Check your GitHub repo for a commit
|
||||
12. If you are publishing your Foam to the web you may want to edit your publishing configuration to exclude inbox files - as publishing (and method) is a user choice that is beyond the scope of this recipe
|
||||
|
||||
## Code for Drafts Action
|
||||
|
||||
@@ -27,5 +27,5 @@ You can use [Mermaid](https://marketplace.visualstudio.com/items?itemName=bierne
|
||||
4. Embed the diagram file as you embedding the image file, for example: ``
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[publish-to-github-pages]: ../publishing/publish-to-github-pages.md "Github Pages"
|
||||
[publish-to-github-pages]: ../publishing/publish-to-github-pages.md "GitHub Pages"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
@@ -37,7 +37,7 @@ A #recipe is a guide, tip or strategy for getting the most out of your Foam work
|
||||
|
||||
## Write
|
||||
|
||||
- Link documents with [[wiki-links]].
|
||||
- Link documents with [[wikilinks]].
|
||||
- Use shortcuts for [[creating-new-notes]]
|
||||
- Instantly create and access your [[daily-notes]]
|
||||
- Add and explore [[tags]]
|
||||
@@ -112,7 +112,7 @@ _See [[contribution-guide]] and [[how-to-write-recipes]]._
|
||||
[graph-visualisation]: ../features/graph-visualisation.md "Graph Visualisation"
|
||||
[backlinking]: ../features/backlinking.md "Backlinking"
|
||||
[unlinked-references]: ../dev/unlinked-references.md "Unlinked references (stub)"
|
||||
[wiki-links]: ../wiki-links.md "Wiki Links"
|
||||
[wikilinks]: ../wikilinks.md "Wikilinks"
|
||||
[creating-new-notes]: ../features/creating-new-notes.md "Creating New Notes"
|
||||
[daily-notes]: ../features/daily-notes.md "Daily notes"
|
||||
[tags]: ../features/tags.md "Tags"
|
||||
@@ -127,7 +127,7 @@ _See [[contribution-guide]] and [[how-to-write-recipes]]._
|
||||
[good-first-task]: ../dev/good-first-task.md "Good First Task"
|
||||
[git-integration]: ../features/git-integration.md "Git Integration"
|
||||
[write-your-notes-in-github-gist]: write-your-notes-in-github-gist.md "Write your notes in GitHub Gist"
|
||||
[publish-to-github-pages]: ../publishing/publish-to-github-pages.md "Github Pages"
|
||||
[publish-to-github-pages]: ../publishing/publish-to-github-pages.md "GitHub Pages"
|
||||
[publish-to-gitlab-pages]: ../publishing/publish-to-gitlab-pages.md "GitLab Pages"
|
||||
[publish-to-azure-devops-wiki]: ../publishing/publish-to-azure-devops-wiki.md "Publish to Azure DevOps Wiki"
|
||||
[publish-to-vercel]: ../publishing/publish-to-vercel.md "Publish to Vercel"
|
||||
|
||||
@@ -15,7 +15,7 @@ Pros
|
||||
- Provides functionality to edit, create, and browser markdown files.
|
||||
- Support journal mode, todo lists, and free writing
|
||||
- Syncs to GitHub repo
|
||||
- Supports Wiki Links
|
||||
- Supports Wikilinks
|
||||
- Supports Backlinks
|
||||
- Developer is happy to prioritize Foam compatibility
|
||||
|
||||
|
||||
@@ -15,10 +15,10 @@ These extensions are not (yet?) defined in `.vscode/extensions.json`, but have b
|
||||
|
||||
- [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)
|
||||
- [Mermaid Support for Preview](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid)
|
||||
- [Markdown Preview Mermaid Support](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid)
|
||||
- [Mermaid Markdown Syntax Highlighting](https://marketplace.visualstudio.com/items?itemName=bpruitt-goddard.mermaid-markdown-syntax-highlighting)
|
||||
- [VSCode PDF Viewing](https://marketplace.visualstudio.com/items?itemName=tomoki1207.pdf)
|
||||
- [Git Lens](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens)
|
||||
- [Markdown Extended](https://marketplace.visualstudio.com/items?itemName=jebbs.markdown-extended) (with `kbd` option disabled, `kbd` turns wiki-links into non-clickable buttons)
|
||||
- [Markdown Extended](https://marketplace.visualstudio.com/items?itemName=jebbs.markdown-extended) (with `kbd` option disabled, `kbd` turns wikilinks into non-clickable buttons)
|
||||
- [GitDoc](https://marketplace.visualstudio.com/items?itemName=vsls-contrib.gitdoc) (easy version management via git auto commits)
|
||||
- [Markdown Footnotes](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-footnotes) (Adds [^footnote] syntax support to VS Code's built-in markdown preview)
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
# Wiki Links
|
||||
# Wikilinks
|
||||
|
||||
Foam enables you to Link pages together using `[[file-name]]` annotations (i.e. `[[media-wiki]]` links).
|
||||
Foam enables you to Link pages together using `[[file-name]]` annotations (i.e. `[[MediaWiki]]` links).
|
||||
|
||||
- Type `[[` and start typing a file name for autocompletion.
|
||||
- Note that your file names should be in `lower-dash-case.md`, and your wiki links should reference file names exactly: `[[lower-dash-case]]`, not `[[Lower Dash Case]]`.
|
||||
- Note that your file names should be in `lower-dash-case.md`, and your wikilinks should reference file names exactly: `[[lower-dash-case]]`, not `[[Lower Dash Case]]`.
|
||||
- See [[link-formatting-and-autocompletion]] for more information, and how to setup your link autocompletions to make this easier.
|
||||
- `Cmd` + `Click` ( `Ctrl` + `Click` on Windows ) on file name to navigate to file (`F12` also works while your cursor is on the file name)
|
||||
- `Cmd` + `Click` ( `Ctrl` + `Click` on Windows ) on non-existent file to create that file in the workspace.
|
||||
- The note creation makes use of the special [`new-note.md` note template](features/note-templates)
|
||||
|
||||
> If the `F12` shortcut feels unnatural you can rebind it at File > Preferences > Keyboard Shortcuts by searching for `editor.action.revealDefinition`.
|
||||
|
||||
## Markdown compatibility
|
||||
|
||||
The [Foam for VSCode](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode) extension automatically generates [[link-reference-definitions]] at the bottom of the file to make wiki-links compatible with Markdown tools and parsers.
|
||||
The [Foam for VSCode](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode) extension automatically generates [[link-reference-definitions]] at the bottom of the file to make wikilinks compatible with Markdown tools and parsers.
|
||||
|
||||
## Read more
|
||||
|
||||
- [[foam-file-format]]
|
||||
- [[note-templates]]
|
||||
- [[link-formatting-and-autocompletion]]
|
||||
- See [[link-reference-definition-improvements]] for further discussion on current problems and potential solutions.
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[link-reference-definitions]: features/link-reference-definitions.md "Link Reference Definitions"
|
||||
[foam-file-format]: dev/foam-file-format.md "Foam File Format"
|
||||
[note-templates]: features/note-templates.md "Note Templates"
|
||||
[link-reference-definition-improvements]: dev/link-reference-definition-improvements.md "Link Reference Definition Improvements"
|
||||
[//end]: # "Autogenerated link references"
|
||||
@@ -4,5 +4,5 @@
|
||||
],
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.13.5"
|
||||
"version": "0.14.2"
|
||||
}
|
||||
|
||||
0
link-formatting-and-autocompletion.md
Normal file
0
link-formatting-and-autocompletion.md
Normal file
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "foam-core",
|
||||
"repository": "https://github.com/foambubble/foam",
|
||||
"version": "0.13.4",
|
||||
"version": "0.14.2",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"dist"
|
||||
@@ -38,6 +38,7 @@
|
||||
"remark-frontmatter": "^2.0.0",
|
||||
"remark-parse": "^8.0.2",
|
||||
"remark-wiki-link": "^0.0.4",
|
||||
"replace-ext": "^2.0.0",
|
||||
"title-case": "^3.0.2",
|
||||
"unified": "^9.0.0",
|
||||
"unist-util-visit": "^2.0.2",
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import { createMarkdownParser } from './markdown-provider';
|
||||
import { FoamConfig, Foam, IDataStore, FoamGraph } from './index';
|
||||
import { FoamWorkspace } from './model/workspace';
|
||||
import { Matcher } from './services/datastore';
|
||||
import { ResourceProvider } from 'model/provider';
|
||||
|
||||
export const bootstrap = async (
|
||||
config: FoamConfig,
|
||||
dataStore: IDataStore,
|
||||
initialProviders: ResourceProvider[]
|
||||
) => {
|
||||
const parser = createMarkdownParser([]);
|
||||
const matcher = new Matcher(
|
||||
config.workspaceFolders,
|
||||
config.includeGlobs,
|
||||
config.ignoreGlobs
|
||||
);
|
||||
const workspace = new FoamWorkspace();
|
||||
await Promise.all(initialProviders.map(p => workspace.registerProvider(p)));
|
||||
|
||||
const graph = FoamGraph.fromWorkspace(workspace, true);
|
||||
|
||||
const foam: Foam = {
|
||||
workspace,
|
||||
graph,
|
||||
config,
|
||||
services: {
|
||||
dataStore,
|
||||
parser,
|
||||
matcher,
|
||||
},
|
||||
dispose: () => {
|
||||
workspace.dispose();
|
||||
graph.dispose();
|
||||
},
|
||||
};
|
||||
|
||||
return foam;
|
||||
};
|
||||
@@ -16,6 +16,7 @@ import { IDisposable, isDisposable } from './common/lifecycle';
|
||||
import { FoamWorkspace } from './model/workspace';
|
||||
import { FoamGraph } from '../src/model/graph';
|
||||
import { URI } from './model/uri';
|
||||
import { FoamTags } from '../src/model/tags';
|
||||
|
||||
export { Position } from './model/position';
|
||||
export { Range } from './model/range';
|
||||
@@ -47,7 +48,7 @@ export { applyTextEdit } from './janitor/apply-text-edit';
|
||||
|
||||
export { createConfigFromFolders } from './config';
|
||||
|
||||
export { bootstrap } from './bootstrap';
|
||||
export { Foam, Services, bootstrap } from './model/foam';
|
||||
|
||||
export {
|
||||
Resource,
|
||||
@@ -55,19 +56,7 @@ export {
|
||||
URI,
|
||||
FoamWorkspace,
|
||||
FoamGraph,
|
||||
FoamTags,
|
||||
NoteLinkDefinition,
|
||||
ResourceParser,
|
||||
};
|
||||
|
||||
export interface Services {
|
||||
dataStore: IDataStore;
|
||||
parser: ResourceParser;
|
||||
matcher: IMatcher;
|
||||
}
|
||||
|
||||
export interface Foam extends IDisposable {
|
||||
services: Services;
|
||||
workspace: FoamWorkspace;
|
||||
graph: FoamGraph;
|
||||
config: FoamConfig;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
isNone,
|
||||
isSome,
|
||||
} from './utils';
|
||||
import { ParserPlugin } from './plugins';
|
||||
import { Logger } from './utils/log';
|
||||
import { URI } from './model/uri';
|
||||
import { FoamWorkspace } from './model/workspace';
|
||||
@@ -32,6 +31,18 @@ import { ResourceProvider } from 'model/provider';
|
||||
import { IDataStore, FileDataStore, IMatcher } from './services/datastore';
|
||||
import { IDisposable } from 'common/lifecycle';
|
||||
|
||||
const ALIAS_DIVIDER_CHAR = '|';
|
||||
|
||||
export interface ParserPlugin {
|
||||
name?: string;
|
||||
visit?: (node: Node, note: Resource, noteSource: string) => void;
|
||||
onDidInitializeParser?: (parser: unified.Processor) => void;
|
||||
onWillParseMarkdown?: (markdown: string) => string;
|
||||
onWillVisitTree?: (tree: Node, note: Resource) => void;
|
||||
onDidVisitTree?: (tree: Node, note: Resource) => void;
|
||||
onDidFindProperties?: (properties: any, note: Resource) => void;
|
||||
}
|
||||
|
||||
export class MarkdownResourceProvider implements ResourceProvider {
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
@@ -48,7 +59,9 @@ export class MarkdownResourceProvider implements ResourceProvider {
|
||||
|
||||
async init(workspace: FoamWorkspace) {
|
||||
const filesByFolder = await Promise.all(
|
||||
this.matcher.include.map(glob => this.dataStore.list(glob))
|
||||
this.matcher.include.map(glob =>
|
||||
this.dataStore.list(glob, this.matcher.exclude)
|
||||
)
|
||||
);
|
||||
const files = this.matcher
|
||||
.match(filesByFolder.flat())
|
||||
@@ -112,7 +125,7 @@ export class MarkdownResourceProvider implements ResourceProvider {
|
||||
switch (link.type) {
|
||||
case 'wikilink':
|
||||
const definitionUri = resource.definitions.find(
|
||||
def => def.label === link.slug
|
||||
def => def.label === link.target
|
||||
)?.url;
|
||||
if (isSome(definitionUri)) {
|
||||
const definedUri = URI.resolve(definitionUri, resource.uri);
|
||||
@@ -121,8 +134,8 @@ export class MarkdownResourceProvider implements ResourceProvider {
|
||||
URI.placeholder(definedUri.path);
|
||||
} else {
|
||||
targetUri =
|
||||
workspace.find(link.slug, resource.uri)?.uri ??
|
||||
URI.placeholder(link.slug);
|
||||
workspace.find(link.target, resource.uri)?.uri ??
|
||||
URI.placeholder(link.target);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -188,12 +201,29 @@ const titlePlugin: ParserPlugin = {
|
||||
|
||||
const wikilinkPlugin: ParserPlugin = {
|
||||
name: 'wikilink',
|
||||
visit: (node, note) => {
|
||||
visit: (node, note, noteSource) => {
|
||||
if (node.type === 'wikiLink') {
|
||||
const text = node.value as string;
|
||||
const alias = node.data?.alias as string;
|
||||
const literalContent = noteSource.substring(
|
||||
node.position!.start.offset!,
|
||||
node.position!.end.offset!
|
||||
);
|
||||
|
||||
const hasAlias =
|
||||
literalContent !== text && literalContent.includes(ALIAS_DIVIDER_CHAR);
|
||||
note.links.push({
|
||||
type: 'wikilink',
|
||||
slug: node.value as string,
|
||||
target: node.value as string,
|
||||
rawText: literalContent,
|
||||
label: hasAlias
|
||||
? alias.trim()
|
||||
: literalContent.substring(2, literalContent.length - 2),
|
||||
target: hasAlias
|
||||
? literalContent
|
||||
.substring(2, literalContent.indexOf(ALIAS_DIVIDER_CHAR))
|
||||
.replace(/\\/g, '')
|
||||
.trim()
|
||||
: text.trim(),
|
||||
range: astPositionToFoamRange(node.position!),
|
||||
});
|
||||
}
|
||||
@@ -250,7 +280,7 @@ export function createMarkdownParser(
|
||||
const parser = unified()
|
||||
.use(markdownParse, { gfm: true })
|
||||
.use(frontmatterPlugin, ['yaml'])
|
||||
.use(wikiLinkPlugin);
|
||||
.use(wikiLinkPlugin, { aliasDivider: ALIAS_DIVIDER_CHAR });
|
||||
|
||||
const plugins = [
|
||||
titlePlugin,
|
||||
@@ -333,7 +363,7 @@ export function createMarkdownParser(
|
||||
|
||||
for (let i = 0, len = plugins.length; i < len; i++) {
|
||||
try {
|
||||
plugins[i].visit?.(node, note);
|
||||
plugins[i].visit?.(node, note, markdown);
|
||||
} catch (e) {
|
||||
handleError(plugins[i], 'visit', uri, e);
|
||||
}
|
||||
@@ -421,8 +451,15 @@ export function createMarkdownReferences(
|
||||
? relativePath
|
||||
: dropExtension(relativePath);
|
||||
|
||||
// [wiki-link-text]: path/to/file.md "Page title"
|
||||
return { label: link.slug, url: pathToNote, title: target.title };
|
||||
// [wikilink-text]: path/to/file.md "Page title"
|
||||
return {
|
||||
label:
|
||||
link.rawText.indexOf('[[') > -1
|
||||
? link.rawText.substring(2, link.rawText.length - 2)
|
||||
: link.rawText || link.label,
|
||||
url: pathToNote,
|
||||
title: target.title,
|
||||
};
|
||||
})
|
||||
.filter(isSome)
|
||||
.sort();
|
||||
|
||||
59
packages/foam-core/src/model/foam.ts
Normal file
59
packages/foam-core/src/model/foam.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { IDisposable } from '../common/lifecycle';
|
||||
import { IDataStore, IMatcher, Matcher } from '../services/datastore';
|
||||
import { FoamConfig } from '../config';
|
||||
import { FoamWorkspace } from './workspace';
|
||||
import { FoamGraph } from './graph';
|
||||
import { ResourceParser } from './note';
|
||||
import { ResourceProvider } from './provider';
|
||||
import { createMarkdownParser } from '../markdown-provider';
|
||||
import { FoamTags } from './tags';
|
||||
|
||||
export interface Services {
|
||||
dataStore: IDataStore;
|
||||
parser: ResourceParser;
|
||||
matcher: IMatcher;
|
||||
}
|
||||
|
||||
export interface Foam extends IDisposable {
|
||||
services: Services;
|
||||
workspace: FoamWorkspace;
|
||||
graph: FoamGraph;
|
||||
config: FoamConfig;
|
||||
tags: FoamTags;
|
||||
}
|
||||
|
||||
export const bootstrap = async (
|
||||
config: FoamConfig,
|
||||
dataStore: IDataStore,
|
||||
initialProviders: ResourceProvider[]
|
||||
) => {
|
||||
const parser = createMarkdownParser([]);
|
||||
const matcher = new Matcher(
|
||||
config.workspaceFolders,
|
||||
config.includeGlobs,
|
||||
config.ignoreGlobs
|
||||
);
|
||||
const workspace = new FoamWorkspace();
|
||||
await Promise.all(initialProviders.map(p => workspace.registerProvider(p)));
|
||||
|
||||
const graph = FoamGraph.fromWorkspace(workspace, true);
|
||||
const tags = FoamTags.fromWorkspace(workspace, true);
|
||||
|
||||
const foam: Foam = {
|
||||
workspace,
|
||||
graph,
|
||||
tags,
|
||||
config,
|
||||
services: {
|
||||
dataStore,
|
||||
parser,
|
||||
matcher,
|
||||
},
|
||||
dispose: () => {
|
||||
workspace.dispose();
|
||||
graph.dispose();
|
||||
},
|
||||
};
|
||||
|
||||
return foam;
|
||||
};
|
||||
@@ -11,8 +11,9 @@ export interface NoteSource {
|
||||
|
||||
export interface WikiLink {
|
||||
type: 'wikilink';
|
||||
slug: string;
|
||||
target: string;
|
||||
label: string;
|
||||
rawText: string;
|
||||
range: Range;
|
||||
}
|
||||
|
||||
|
||||
83
packages/foam-core/src/model/tags.ts
Normal file
83
packages/foam-core/src/model/tags.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { FoamWorkspace } from './workspace';
|
||||
import { URI } from './uri';
|
||||
import { IDisposable } from '../index';
|
||||
import { Resource } from './note';
|
||||
|
||||
export type TagMetadata = { uri: URI };
|
||||
|
||||
export class FoamTags implements IDisposable {
|
||||
public readonly tags: Map<string, TagMetadata[]> = new Map();
|
||||
|
||||
constructor(private readonly workspace: FoamWorkspace) {}
|
||||
|
||||
/**
|
||||
* List of disposables to destroy with the tags
|
||||
*/
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
/**
|
||||
* Computes all tags in the workspace and keep them up-to-date
|
||||
*
|
||||
* @param workspace the target workspace
|
||||
* @param keepMonitoring whether to recompute the links when the workspace changes
|
||||
* @returns the FoamTags
|
||||
*/
|
||||
public static fromWorkspace(
|
||||
workspace: FoamWorkspace,
|
||||
keepMonitoring: boolean = false
|
||||
): FoamTags {
|
||||
let tags = new FoamTags(workspace);
|
||||
|
||||
Object.values(workspace.list()).forEach(resource =>
|
||||
tags.addResourceFromTagIndex(resource)
|
||||
);
|
||||
|
||||
if (keepMonitoring) {
|
||||
tags.disposables.push(
|
||||
workspace.onDidAdd(resource => {
|
||||
tags.addResourceFromTagIndex(resource);
|
||||
}),
|
||||
workspace.onDidUpdate(change => {
|
||||
tags.updateResourceWithinTagIndex(change.old, change.new);
|
||||
}),
|
||||
workspace.onDidDelete(resource => {
|
||||
tags.removeResourceFromTagIndex(resource);
|
||||
})
|
||||
);
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables.forEach(d => d.dispose());
|
||||
this.disposables = [];
|
||||
}
|
||||
|
||||
updateResourceWithinTagIndex(oldResource: Resource, newResource: Resource) {
|
||||
this.removeResourceFromTagIndex(oldResource);
|
||||
this.addResourceFromTagIndex(newResource);
|
||||
}
|
||||
|
||||
addResourceFromTagIndex(resource: Resource) {
|
||||
resource.tags.forEach(tag => {
|
||||
this.tags.set(tag, this.tags.get(tag) ?? []);
|
||||
this.tags.get(tag)?.push({ uri: resource.uri });
|
||||
});
|
||||
}
|
||||
|
||||
removeResourceFromTagIndex(resource: Resource) {
|
||||
resource.tags.forEach(tag => {
|
||||
if (this.tags.has(tag)) {
|
||||
const remainingLocations = this.tags
|
||||
.get(tag)
|
||||
?.filter(meta => !URI.isEqual(meta.uri, resource.uri));
|
||||
|
||||
if (remainingLocations && remainingLocations.length > 0) {
|
||||
this.tags.set(tag, remainingLocations);
|
||||
} else {
|
||||
this.tags.delete(tag);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
// Some code in this file coming from https://github.com/microsoft/vscode/
|
||||
// `URI` is mostly compatible with VSCode's `Uri`.
|
||||
// Having a Foam-specific URI object allows for easier maintenance of the API.
|
||||
// See https://github.com/foambubble/foam/pull/537 for more context.
|
||||
// Some code in this file comes from https://github.com/microsoft/vscode/main/src/vs/base/common/uri.ts
|
||||
// See LICENSE for details
|
||||
|
||||
import * as paths from 'path';
|
||||
@@ -148,6 +151,10 @@ export abstract class URI {
|
||||
return URI.file(posix.dirname(uri.path));
|
||||
}
|
||||
|
||||
static getFileNameWithoutExtension(uri: URI) {
|
||||
return URI.getBasename(uri).replace(/\.[^.]+$/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses a placeholder URI, and a reference directory, to generate
|
||||
* the URI of the corresponding resource
|
||||
|
||||
@@ -26,7 +26,8 @@ const pathToResourceId = (pathValue: string) => {
|
||||
};
|
||||
const uriToResourceId = (uri: URI) => pathToResourceId(uri.path);
|
||||
|
||||
const pathToResourceName = (pathValue: string) => path.parse(pathValue).name;
|
||||
const pathToResourceName = (pathValue: string) =>
|
||||
path.parse(pathValue).name.toLowerCase();
|
||||
export const uriToResourceName = (uri: URI) => pathToResourceName(uri.path);
|
||||
|
||||
export class FoamWorkspace implements IDisposable {
|
||||
@@ -110,7 +111,12 @@ export class FoamWorkspace implements IDisposable {
|
||||
|
||||
case 'key':
|
||||
const name = pathToResourceName(resourceId as string);
|
||||
const paths = this.resourcesByName[name];
|
||||
let paths = this.resourcesByName[name];
|
||||
|
||||
if (isNone(paths) || paths.length === 0) {
|
||||
paths = this.resourcesByName[resourceId as string];
|
||||
}
|
||||
|
||||
if (isNone(paths) || paths.length === 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -118,6 +124,7 @@ export class FoamWorkspace implements IDisposable {
|
||||
const sortedPaths = paths.length === 1
|
||||
? paths
|
||||
: paths.sort((a, b) => a.localeCompare(b));
|
||||
|
||||
return this.resources[sortedPaths[0]];
|
||||
|
||||
case 'absolute-path':
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
import * as fs from 'fs';
|
||||
import path from 'path';
|
||||
import { Node } from 'unist';
|
||||
import { isNotNull } from '../utils';
|
||||
import { Resource } from '../model/note';
|
||||
import unified from 'unified';
|
||||
import { FoamConfig } from '../config';
|
||||
import { Logger } from '../utils/log';
|
||||
import { URI } from '../model/uri';
|
||||
|
||||
export interface FoamPlugin {
|
||||
name: string;
|
||||
description?: string;
|
||||
parser?: ParserPlugin;
|
||||
}
|
||||
|
||||
export interface ParserPlugin {
|
||||
name?: string;
|
||||
visit?: (node: Node, note: Resource) => void;
|
||||
onDidInitializeParser?: (parser: unified.Processor) => void;
|
||||
onWillParseMarkdown?: (markdown: string) => string;
|
||||
onWillVisitTree?: (tree: Node, note: Resource) => void;
|
||||
onDidVisitTree?: (tree: Node, note: Resource) => void;
|
||||
onDidFindProperties?: (properties: any, note: Resource) => void;
|
||||
}
|
||||
|
||||
export interface PluginConfig {
|
||||
enabled?: boolean;
|
||||
pluginFolders?: string[];
|
||||
}
|
||||
|
||||
export const SETTINGS_PATH = 'experimental.localPlugins';
|
||||
|
||||
export async function loadPlugins(config: FoamConfig): Promise<FoamPlugin[]> {
|
||||
const pluginConfig = config.get<PluginConfig>(SETTINGS_PATH, {});
|
||||
const isFeatureEnabled = pluginConfig.enabled ?? false;
|
||||
if (!isFeatureEnabled) {
|
||||
return [];
|
||||
}
|
||||
const pluginDirs: URI[] =
|
||||
pluginConfig.pluginFolders?.map(URI.file) ??
|
||||
findPluginDirs(config.workspaceFolders);
|
||||
|
||||
const plugins = await Promise.all(
|
||||
pluginDirs
|
||||
.filter(dir => fs.statSync(URI.toFsPath(dir)).isDirectory)
|
||||
.map(async dir => {
|
||||
try {
|
||||
const pluginFile = path.join(URI.toFsPath(dir), 'index.js');
|
||||
fs.accessSync(pluginFile);
|
||||
Logger.info(`Found plugin at [${pluginFile}]. Loading..`);
|
||||
const plugin = validate(await import(pluginFile));
|
||||
return plugin;
|
||||
} catch (e) {
|
||||
Logger.error(`Error while loading plugin at [${dir}] - skipping`, e);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
);
|
||||
return plugins.filter(isNotNull);
|
||||
}
|
||||
|
||||
function findPluginDirs(workspaceFolders: URI[]) {
|
||||
return workspaceFolders
|
||||
.map(root => URI.joinPath(root, '.foam', 'plugins'))
|
||||
.reduce((acc, pluginDir) => {
|
||||
try {
|
||||
const content = fs
|
||||
.readdirSync(URI.toFsPath(pluginDir))
|
||||
.map(dir => URI.joinPath(pluginDir, dir));
|
||||
return [
|
||||
...acc,
|
||||
...content.filter(c => fs.statSync(URI.toFsPath(c)).isDirectory()),
|
||||
];
|
||||
} catch {
|
||||
return acc;
|
||||
}
|
||||
}, [] as URI[]);
|
||||
}
|
||||
|
||||
function validate(plugin: any): FoamPlugin {
|
||||
if (!plugin.name) {
|
||||
throw new Error('Plugin must export `name` string property');
|
||||
}
|
||||
return plugin;
|
||||
}
|
||||
@@ -100,7 +100,7 @@ export interface IDataStore {
|
||||
* List the files matching the given glob from the
|
||||
* store
|
||||
*/
|
||||
list: (glob: string) => Promise<URI[]>;
|
||||
list: (glob: string, ignoreGlob?: string | string[]) => Promise<URI[]>;
|
||||
|
||||
/**
|
||||
* Read the content of the file from the store
|
||||
@@ -114,8 +114,10 @@ export interface IDataStore {
|
||||
* File system based data store
|
||||
*/
|
||||
export class FileDataStore implements IDataStore {
|
||||
async list(glob: string): Promise<URI[]> {
|
||||
const res = await findAllFiles(glob);
|
||||
async list(glob: string, ignoreGlob?: string | string[]): Promise<URI[]> {
|
||||
const res = await findAllFiles(glob, {
|
||||
ignore: ignoreGlob,
|
||||
});
|
||||
return res.map(URI.file);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ export const createTestNote = (params: {
|
||||
links?: Array<{ slug: string } | { to: string }>;
|
||||
text?: string;
|
||||
root?: URI;
|
||||
tags?: string[];
|
||||
}): Resource => {
|
||||
const root = params.root ?? URI.file('/');
|
||||
return {
|
||||
@@ -55,7 +56,7 @@ export const createTestNote = (params: {
|
||||
properties: {},
|
||||
title: params.title ?? path.parse(strToUri(params.uri).path).base,
|
||||
definitions: params.definitions ?? [],
|
||||
tags: new Set(),
|
||||
tags: new Set(params.tags) ?? new Set(),
|
||||
links: params.links
|
||||
? params.links.map((link, index) => {
|
||||
const range = Range.create(
|
||||
@@ -67,10 +68,10 @@ export const createTestNote = (params: {
|
||||
return 'slug' in link
|
||||
? {
|
||||
type: 'wikilink',
|
||||
slug: link.slug,
|
||||
target: link.slug,
|
||||
label: link.slug,
|
||||
range: range,
|
||||
text: 'link text',
|
||||
rawText: 'link text',
|
||||
}
|
||||
: {
|
||||
type: 'link',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as path from 'path';
|
||||
import { generateHeading } from '../../src/janitor';
|
||||
import { bootstrap } from '../../src/bootstrap';
|
||||
import { createConfigFromFolders } from '../../src/config';
|
||||
import { Resource } from '../../src/model/note';
|
||||
import { FileDataStore, Matcher } from '../../src/services/datastore';
|
||||
@@ -8,7 +7,7 @@ import { Logger } from '../../src/utils/log';
|
||||
import { FoamWorkspace } from '../../src/model/workspace';
|
||||
import { URI } from '../../src/model/uri';
|
||||
import { Range } from '../../src/model/range';
|
||||
import { MarkdownResourceProvider } from '../../src';
|
||||
import { MarkdownResourceProvider, bootstrap } from '../../src';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as path from 'path';
|
||||
import { generateLinkReferences } from '../../src/janitor';
|
||||
import { bootstrap } from '../../src/bootstrap';
|
||||
import { createConfigFromFolders } from '../../src/config';
|
||||
import { FileDataStore, Matcher } from '../../src/services/datastore';
|
||||
import { Logger } from '../../src/utils/log';
|
||||
@@ -8,7 +7,7 @@ import { FoamWorkspace } from '../../src/model/workspace';
|
||||
import { URI } from '../../src/model/uri';
|
||||
import { Resource } from '../../src/model/note';
|
||||
import { Range } from '../../src/model/range';
|
||||
import { MarkdownResourceProvider } from '../../src';
|
||||
import { MarkdownResourceProvider, bootstrap } from '../../src';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
createMarkdownParser,
|
||||
createMarkdownReferences,
|
||||
ParserPlugin,
|
||||
} from '../src/markdown-provider';
|
||||
import { DirectLink } from '../src/model/note';
|
||||
import { ParserPlugin } from '../src/plugins';
|
||||
import { DirectLink, WikiLink } from '../src/model/note';
|
||||
import { Logger } from '../src/utils/log';
|
||||
import { uriToSlug } from '../src/utils/slug';
|
||||
import { URI } from '../src/model/uri';
|
||||
@@ -130,6 +130,24 @@ this is a [link to intro](#introduction)
|
||||
noteE.uri,
|
||||
]);
|
||||
});
|
||||
|
||||
it('Parses backlinks with an alias', () => {
|
||||
const note = createNoteFromMarkdown(
|
||||
'/path/to/page-a.md',
|
||||
'this is [[link|link alias]]. A link with spaces [[other link | spaced]]'
|
||||
);
|
||||
expect(note.links.length).toEqual(2);
|
||||
let link = note.links[0] as WikiLink;
|
||||
expect(link.type).toEqual('wikilink');
|
||||
expect(link.rawText).toEqual('[[link|link alias]]');
|
||||
expect(link.label).toEqual('link alias');
|
||||
expect(link.target).toEqual('link');
|
||||
link = note.links[1] as WikiLink;
|
||||
expect(link.type).toEqual('wikilink');
|
||||
expect(link.rawText).toEqual('[[other link | spaced]]');
|
||||
expect(link.label).toEqual('spaced');
|
||||
expect(link.target).toEqual('other link');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Note Title', () => {
|
||||
@@ -336,19 +354,6 @@ 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', () => {
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
import path from 'path';
|
||||
import { loadPlugins } from '../src/plugins';
|
||||
import { createMarkdownParser } from '../src/markdown-provider';
|
||||
import { FoamConfig, createConfigFromObject } from '../src/config';
|
||||
import { URI } from '../src/model/uri';
|
||||
import { Logger } from '../src/utils/log';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
const config: FoamConfig = createConfigFromObject([], [], [], {
|
||||
experimental: {
|
||||
localPlugins: {
|
||||
enabled: true,
|
||||
pluginFolders: [path.join(__dirname, 'test-plugin')],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
describe('Foam plugins', () => {
|
||||
it('will not load if feature is not explicitly enabled', async () => {
|
||||
let plugins = await loadPlugins(createConfigFromObject([], [], [], {}));
|
||||
expect(plugins.length).toEqual(0);
|
||||
plugins = await loadPlugins(
|
||||
createConfigFromObject([], [], [], {
|
||||
experimental: {
|
||||
localPlugins: {},
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(plugins.length).toEqual(0);
|
||||
plugins = await loadPlugins(
|
||||
createConfigFromObject([], [], [], {
|
||||
experimental: {
|
||||
localPlugins: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(plugins.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('can load', async () => {
|
||||
const plugins = await loadPlugins(config);
|
||||
expect(plugins.length).toEqual(1);
|
||||
expect(plugins[0].name).toEqual('Test Plugin');
|
||||
});
|
||||
|
||||
it('supports parser extension', async () => {
|
||||
const plugins = await loadPlugins(config);
|
||||
const parserPlugin = plugins[0].parser;
|
||||
expect(parserPlugin).not.toBeUndefined();
|
||||
const parser = createMarkdownParser([parserPlugin!]);
|
||||
|
||||
const note = parser.parse(
|
||||
URI.file('/path/to/a'),
|
||||
`
|
||||
# This is a note with header
|
||||
and some content`
|
||||
);
|
||||
expect(note.properties.hasHeading).toBeTruthy();
|
||||
});
|
||||
});
|
||||
142
packages/foam-core/test/tags.test.ts
Normal file
142
packages/foam-core/test/tags.test.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import { FoamTags } from '../src/model/tags';
|
||||
import { createTestNote, createTestWorkspace } from './core.test';
|
||||
|
||||
describe('FoamTags', () => {
|
||||
it('Collects tags from a list of resources', () => {
|
||||
const ws = createTestWorkspace();
|
||||
|
||||
const pageA = createTestNote({
|
||||
uri: '/page-a.md',
|
||||
title: 'Page A',
|
||||
links: [{ slug: 'placeholder-link' }],
|
||||
tags: ['primary', 'secondary'],
|
||||
});
|
||||
|
||||
const pageB = createTestNote({
|
||||
uri: '/page-b.md',
|
||||
title: 'Page B',
|
||||
links: [{ slug: 'placeholder-link' }],
|
||||
tags: ['primary', 'third'],
|
||||
});
|
||||
|
||||
ws.set(pageA);
|
||||
ws.set(pageB);
|
||||
|
||||
const tags = FoamTags.fromWorkspace(ws);
|
||||
|
||||
expect(tags.tags).toEqual(
|
||||
new Map([
|
||||
['primary', [{ uri: pageA.uri }, { uri: pageB.uri }]],
|
||||
['secondary', [{ uri: pageA.uri }]],
|
||||
['third', [{ uri: pageB.uri }]],
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('Updates an existing tag when a note is tagged with an existing tag', () => {
|
||||
const ws = createTestWorkspace();
|
||||
|
||||
const page = createTestNote({
|
||||
uri: '/page-a.md',
|
||||
title: 'Page A',
|
||||
links: [{ slug: 'placeholder-link' }],
|
||||
tags: ['primary'],
|
||||
});
|
||||
const taglessPage = createTestNote({
|
||||
uri: '/page-b.md',
|
||||
title: 'Page B',
|
||||
});
|
||||
|
||||
ws.set(page);
|
||||
ws.set(taglessPage);
|
||||
|
||||
const tags = FoamTags.fromWorkspace(ws);
|
||||
expect(tags.tags).toEqual(new Map([['primary', [{ uri: page.uri }]]]));
|
||||
|
||||
const newPage = createTestNote({
|
||||
uri: '/page-b.md',
|
||||
title: 'Page B',
|
||||
tags: ['primary'],
|
||||
});
|
||||
|
||||
tags.updateResourceWithinTagIndex(taglessPage, newPage);
|
||||
|
||||
expect(tags.tags).toEqual(
|
||||
new Map([['primary', [{ uri: page.uri }, { uri: newPage.uri }]]])
|
||||
);
|
||||
});
|
||||
|
||||
it('Replaces the tag when a note is updated with an altered tag', () => {
|
||||
const ws = createTestWorkspace();
|
||||
|
||||
const page = createTestNote({
|
||||
uri: '/page-a.md',
|
||||
title: 'Page A',
|
||||
links: [{ slug: 'placeholder-link' }],
|
||||
tags: ['primary'],
|
||||
});
|
||||
|
||||
ws.set(page);
|
||||
|
||||
const tags = FoamTags.fromWorkspace(ws);
|
||||
expect(tags.tags).toEqual(new Map([['primary', [{ uri: page.uri }]]]));
|
||||
|
||||
const pageEdited = createTestNote({
|
||||
uri: '/page-a.md',
|
||||
title: 'Page A',
|
||||
links: [{ slug: 'placeholder-link' }],
|
||||
tags: ['new'],
|
||||
});
|
||||
|
||||
tags.updateResourceWithinTagIndex(page, pageEdited);
|
||||
|
||||
expect(tags.tags).toEqual(new Map([['new', [{ uri: page.uri }]]]));
|
||||
});
|
||||
|
||||
it('Updates the metadata of a tag when the note is moved', () => {
|
||||
const ws = createTestWorkspace();
|
||||
|
||||
const page = createTestNote({
|
||||
uri: '/page-a.md',
|
||||
title: 'Page A',
|
||||
links: [{ slug: 'placeholder-link' }],
|
||||
tags: ['primary'],
|
||||
});
|
||||
ws.set(page);
|
||||
|
||||
const tags = FoamTags.fromWorkspace(ws);
|
||||
expect(tags.tags).toEqual(new Map([['primary', [{ uri: page.uri }]]]));
|
||||
|
||||
const pageEdited = createTestNote({
|
||||
uri: '/new-place/page-a.md',
|
||||
title: 'Page A',
|
||||
links: [{ slug: 'placeholder-link' }],
|
||||
tags: ['primary'],
|
||||
});
|
||||
|
||||
tags.updateResourceWithinTagIndex(page, pageEdited);
|
||||
|
||||
expect(tags.tags).toEqual(
|
||||
new Map([['primary', [{ uri: pageEdited.uri }]]])
|
||||
);
|
||||
});
|
||||
|
||||
it('Updates the metadata of a tag when a note is delete', () => {
|
||||
const ws = createTestWorkspace();
|
||||
|
||||
const page = createTestNote({
|
||||
uri: '/page-a.md',
|
||||
title: 'Page A',
|
||||
links: [{ slug: 'placeholder-link' }],
|
||||
tags: ['primary'],
|
||||
});
|
||||
ws.set(page);
|
||||
|
||||
const tags = FoamTags.fromWorkspace(ws);
|
||||
expect(tags.tags).toEqual(new Map([['primary', [{ uri: page.uri }]]]));
|
||||
|
||||
tags.removeResourceFromTagIndex(page);
|
||||
|
||||
expect(tags.tags).toEqual(new Map());
|
||||
});
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
const middleware = next => ({
|
||||
setNote: note => {
|
||||
note.properties['injectedByMiddleware'] = true;
|
||||
return next.setNote(note);
|
||||
},
|
||||
});
|
||||
|
||||
const parser = {
|
||||
visit: (node, note) => {
|
||||
if (node.type === 'heading') {
|
||||
note.properties.hasHeading = true;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
name: 'Test Plugin',
|
||||
graphMiddleware: middleware,
|
||||
parser: parser,
|
||||
};
|
||||
@@ -232,7 +232,7 @@ describe('Wikilinks', () => {
|
||||
expect(graph.getAllConnections()[0]).toEqual({
|
||||
source: noteA.uri,
|
||||
target: noteB.uri,
|
||||
link: expect.objectContaining({ type: 'wikilink', slug: 'page-b' }),
|
||||
link: expect.objectContaining({ type: 'wikilink', label: 'page-b' }),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -354,6 +354,51 @@ describe('Wikilinks', () => {
|
||||
attachmentABis.uri,
|
||||
]);
|
||||
});
|
||||
|
||||
it('Allows for dendron-style wikilinks, including a dot', () => {
|
||||
const noteA = createTestNote({
|
||||
uri: '/path/to/page-a.md',
|
||||
links: [{ slug: 'dendron.style' }],
|
||||
});
|
||||
const noteB1 = createTestNote({ uri: '/path/to/another/dendron.style.md' });
|
||||
|
||||
const ws = createTestWorkspace();
|
||||
ws.set(noteA).set(noteB1);
|
||||
const graph = FoamGraph.fromWorkspace(ws);
|
||||
|
||||
expect(graph.getLinks(noteA.uri).map(l => l.target)).toEqual([noteB1.uri]);
|
||||
});
|
||||
|
||||
it('Handles capatalization of files and wikilinks correctly', () => {
|
||||
const noteA = createTestNote({
|
||||
uri: '/path/to/page-a.md',
|
||||
links: [
|
||||
// uppercased filename, lowercased slug
|
||||
{ slug: 'page-b' },
|
||||
// lowercased filename, camelcased wikilink
|
||||
{ slug: 'Page-C' },
|
||||
// lowercased filename, lowercased wikilink
|
||||
{ slug: 'page-d' },
|
||||
],
|
||||
});
|
||||
const ws = createTestWorkspace()
|
||||
.set(noteA)
|
||||
.set(createTestNote({ uri: '/somewhere/PAGE-B.md' }))
|
||||
.set(createTestNote({ uri: '/path/another/page-c.md' }))
|
||||
.set(createTestNote({ uri: '/path/another/page-d.md' }));
|
||||
const graph = FoamGraph.fromWorkspace(ws);
|
||||
|
||||
expect(
|
||||
graph
|
||||
.getLinks(noteA.uri)
|
||||
.map(link => link.target.path)
|
||||
.sort()
|
||||
).toEqual([
|
||||
'/path/another/page-c.md',
|
||||
'/path/another/page-d.md',
|
||||
'/somewhere/PAGE-B.md',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('markdown direct links', () => {
|
||||
|
||||
@@ -3741,9 +3741,9 @@ github-slugger@^1.3.0:
|
||||
emoji-regex ">=6.0.0 <=6.1.1"
|
||||
|
||||
glob-parent@^5.0.0:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
|
||||
integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
||||
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
|
||||
dependencies:
|
||||
is-glob "^4.0.1"
|
||||
|
||||
@@ -5924,6 +5924,11 @@ replace-ext@1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb"
|
||||
integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=
|
||||
|
||||
replace-ext@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-2.0.0.tgz#9471c213d22e1bcc26717cd6e50881d88f812b06"
|
||||
integrity sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==
|
||||
|
||||
request-promise-core@1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9"
|
||||
@@ -7220,9 +7225,9 @@ write@1.0.3:
|
||||
mkdirp "^0.5.1"
|
||||
|
||||
ws@^5.2.0:
|
||||
version "5.2.2"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f"
|
||||
integrity sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==
|
||||
version "5.2.3"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.3.tgz#05541053414921bc29c63bee14b8b0dd50b07b3d"
|
||||
integrity sha512-jZArVERrMsKUatIdnLzqvcfydI85dvd/Fp1u/VOpfdDWQ4c9qWXe+VIeAbQ5FrDwciAkr+lzofXLz3Kuf26AOA==
|
||||
dependencies:
|
||||
async-limiter "~1.0.0"
|
||||
|
||||
|
||||
@@ -4,6 +4,64 @@ 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.14.2] - 2021-07-24
|
||||
|
||||
Features:
|
||||
|
||||
- Autocompletion for tags (#708 - thanks @pderaaij)
|
||||
- Use templates for new note created from wikilink (#712 - thanks @movermeyer)
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Improved performance of initial file loading (#730 - thanks @pderaaij)
|
||||
|
||||
## [0.14.1] - 2021-07-14
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Fixed NPE that would cause markdown preview to render incorrectly (#718 - thanks @pderaaij)
|
||||
|
||||
## [0.14.0] - 2021-07-13
|
||||
|
||||
Features:
|
||||
|
||||
- Create new note from selection (#666 - thanks @pderaaij)
|
||||
- Use templates for daily notes (#700 - thanks @movermeyer)
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Fixed for wikilink aliases in tables (#697 - thanks @pderaaij)
|
||||
- Fixed link definition generation in presence of aliased wikilinks (#698 - thanks @pderaaij)
|
||||
- Fixed template insertion of selected text (#701 - thanks @movermeyer)
|
||||
- Fixed preview navigation (#710 - thanks @pderaaij)
|
||||
|
||||
## [0.13.8] - 2021-07-02
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Improved handling of capitalization in wikilinks (#688 - thanks @pderaaij)
|
||||
- This update will make wikilinks with different capitalization, such as `[[wikilink]]` and `[[WikiLink]]` point to the same file. Please note that means that files that only differ in capitalization across the workspace would now be treated as having the same name
|
||||
- Allow dots in wikilinks (#689 - thanks @pderaaij)
|
||||
- Fixed a bug in the expansion of date snippets (thanks @syndenham-chorea)
|
||||
- Added support for wikilink alias syntax, like `[[wikilink|label]]` (#689 - thanks @pderaaij)
|
||||
|
||||
## [0.13.7] - 2021-06-05
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Fixed #667, incorrect resolution of foam-core library
|
||||
|
||||
Internal:
|
||||
|
||||
- BREAKING CHANGE: Removed Foam local plugins
|
||||
If you were previously using the alpha feature of Foam local plugins you will soon be able to migrate the functionality to the V1 API
|
||||
|
||||
## [0.13.6] - 2021-06-05
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Fixed #667, incorrect resolution of foam-core library
|
||||
|
||||
## [0.13.5] - 2021-06-05
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
@@ -28,8 +28,8 @@ You really, _really_, **really** should read [Foam documentation](https://foambu
|
||||
|
||||
## Features
|
||||
|
||||
- Connect your notes using [`[[wiki-links]]`](https://foambubble.github.io/foam/features/backlinking)
|
||||
- Create markdown [references](https://foambubble.github.io/foam/features/link-reference-definitions) for `[[wiki-links]]`, to use your notes in a non-foam workspace
|
||||
- Connect your notes using [`[[wikilinks]]`](https://foambubble.github.io/foam/features/backlinking)
|
||||
- Create markdown [references](https://foambubble.github.io/foam/features/link-reference-definitions) for `[[wikilinks]]`, to use your notes in a non-foam workspace
|
||||
- See how your notes are connected via a [graph](https://foambubble.github.io/foam/features/graph-visualisation) with the `Foam: Show Graph` command
|
||||
- Tag your notes and navigate them with the [Tag Explorer](https://foambubble.github.io/foam/features/tags)
|
||||
- Make your notes navigable both in GitHub UI as well as GitHub Pages
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"type": "git"
|
||||
},
|
||||
"homepage": "https://github.com/foambubble/foam",
|
||||
"version": "0.13.5",
|
||||
"version": "0.14.2",
|
||||
"license": "MIT",
|
||||
"publisher": "foam",
|
||||
"engines": {
|
||||
@@ -395,7 +395,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"dateformat": "^3.0.3",
|
||||
"foam-core": "^0.13.4",
|
||||
"foam-core": "^0.14.2",
|
||||
"gray-matter": "^4.0.2",
|
||||
"markdown-it-regex": "^0.2.0",
|
||||
"micromatch": "^4.0.2",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Uri, workspace } from 'vscode';
|
||||
import { workspace } from 'vscode';
|
||||
import { getDailyNotePath } from './dated-notes';
|
||||
import { URI } from 'foam-core';
|
||||
import { isWindows } from './utils';
|
||||
@@ -13,7 +13,7 @@ describe('getDailyNotePath', () => {
|
||||
test('Adds the root directory to relative directories', async () => {
|
||||
const config = 'journal';
|
||||
|
||||
const expectedPath = Uri.joinPath(
|
||||
const expectedPath = URI.joinPath(
|
||||
workspace.workspaceFolders[0].uri,
|
||||
config,
|
||||
`${isoDate}.md`
|
||||
@@ -25,7 +25,7 @@ describe('getDailyNotePath', () => {
|
||||
const foamConfiguration = workspace.getConfiguration('foam');
|
||||
|
||||
expect(URI.toFsPath(getDailyNotePath(foamConfiguration, date))).toEqual(
|
||||
expectedPath.fsPath
|
||||
URI.toFsPath(expectedPath)
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import { workspace, WorkspaceConfiguration, Uri } from 'vscode';
|
||||
import { workspace, WorkspaceConfiguration } from 'vscode';
|
||||
import dateFormat from 'dateformat';
|
||||
import * as fs from 'fs';
|
||||
import { isAbsolute } from 'path';
|
||||
import { docConfig, focusNote, pathExists } from './utils';
|
||||
import { focusNote, pathExists } from './utils';
|
||||
import { URI } from 'foam-core';
|
||||
import { createNoteFromDailyNoteTemplate } from './features/create-from-template';
|
||||
|
||||
/**
|
||||
* Open the daily note file.
|
||||
*
|
||||
* In the case that the daily note file does not exist,
|
||||
* it gets created along with any folders in its path.
|
||||
*
|
||||
* @param date A given date to be formatted as filename.
|
||||
*/
|
||||
async function openDailyNoteFor(date?: Date) {
|
||||
const foamConfiguration = workspace.getConfiguration('foam');
|
||||
const currentDate = date !== undefined ? date : new Date();
|
||||
@@ -19,6 +27,19 @@ async function openDailyNoteFor(date?: Date) {
|
||||
await focusNote(dailyNotePath, isNew);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the daily note file path.
|
||||
*
|
||||
* This function first checks the `foam.openDailyNote.directory` configuration string,
|
||||
* defaulting to the current directory.
|
||||
*
|
||||
* In the case that the directory path is not absolute,
|
||||
* the resulting path will start on the current workspace top-level.
|
||||
*
|
||||
* @param configuration The current workspace configuration.
|
||||
* @param date A given date to be formatted as filename.
|
||||
* @returns The path to the daily note file.
|
||||
*/
|
||||
function getDailyNotePath(
|
||||
configuration: WorkspaceConfiguration,
|
||||
date: Date
|
||||
@@ -28,7 +49,7 @@ function getDailyNotePath(
|
||||
const dailyNoteFilename = getDailyNoteFileName(configuration, date);
|
||||
|
||||
if (isAbsolute(dailyNoteDirectory)) {
|
||||
return URI.joinPath(Uri.file(dailyNoteDirectory), dailyNoteFilename);
|
||||
return URI.joinPath(URI.file(dailyNoteDirectory), dailyNoteFilename);
|
||||
} else {
|
||||
return URI.joinPath(
|
||||
workspace.workspaceFolders[0].uri,
|
||||
@@ -38,6 +59,17 @@ function getDailyNotePath(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the daily note filename (basename) to use.
|
||||
*
|
||||
* Fetch the filename format and extension from
|
||||
* `foam.openDailyNote.filenameFormat` and
|
||||
* `foam.openDailyNote.fileExtension`, respectively.
|
||||
*
|
||||
* @param configuration The current workspace configuration.
|
||||
* @param date A given date to be formatted as filename.
|
||||
* @returns The daily note's filename.
|
||||
*/
|
||||
function getDailyNoteFileName(
|
||||
configuration: WorkspaceConfiguration,
|
||||
date: Date
|
||||
@@ -52,6 +84,17 @@ function getDailyNoteFileName(
|
||||
return `${dateFormat(date, filenameFormat, false)}.${fileExtension}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a daily note if it does not exist.
|
||||
*
|
||||
* In the case that the folders referenced in the file path also do not exist,
|
||||
* this function will create all folders in the path.
|
||||
*
|
||||
* @param configuration The current workspace configuration.
|
||||
* @param dailyNotePath The path to daily note file.
|
||||
* @param currentDate The current date, to be used as a title.
|
||||
* @returns Wether the file was created.
|
||||
*/
|
||||
async function createDailyNoteIfNotExists(
|
||||
configuration: WorkspaceConfiguration,
|
||||
dailyNotePath: URI,
|
||||
@@ -61,32 +104,23 @@ async function createDailyNoteIfNotExists(
|
||||
return false;
|
||||
}
|
||||
|
||||
await createDailyNoteDirectoryIfNotExists(dailyNotePath);
|
||||
|
||||
const titleFormat: string =
|
||||
configuration.get('openDailyNote.titleFormat') ??
|
||||
configuration.get('openDailyNote.filenameFormat');
|
||||
|
||||
await fs.promises.writeFile(
|
||||
URI.toFsPath(dailyNotePath),
|
||||
`# ${dateFormat(currentDate, titleFormat, false)}${docConfig.eol}${
|
||||
docConfig.eol
|
||||
}`
|
||||
);
|
||||
const templateFallbackText: string = `---
|
||||
foam_template:
|
||||
name: New Daily Note
|
||||
description: Foam's default daily note template
|
||||
---
|
||||
# ${dateFormat(currentDate, titleFormat, false)}
|
||||
`;
|
||||
|
||||
await createNoteFromDailyNoteTemplate(dailyNotePath, templateFallbackText);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async function createDailyNoteDirectoryIfNotExists(dailyNotePath: URI) {
|
||||
const dailyNoteDirectory = URI.getDir(dailyNotePath);
|
||||
|
||||
if (!(await pathExists(dailyNoteDirectory))) {
|
||||
await fs.promises.mkdir(URI.toFsPath(dailyNoteDirectory), {
|
||||
recursive: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
openDailyNoteFor,
|
||||
getDailyNoteFileName,
|
||||
|
||||
@@ -125,10 +125,7 @@ export class BacklinkTreeItem extends vscode.TreeItem {
|
||||
public readonly resource: Resource,
|
||||
public readonly link: ResourceLink
|
||||
) {
|
||||
super(
|
||||
link.type === 'wikilink' ? link.slug : link.label,
|
||||
vscode.TreeItemCollapsibleState.None
|
||||
);
|
||||
super(link.label, vscode.TreeItemCollapsibleState.None);
|
||||
this.label = `${link.range.start.line}: ${this.label}`;
|
||||
this.command = {
|
||||
command: 'vscode.open',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { window, Uri, workspace, commands } from 'vscode';
|
||||
import { URI } from 'foam-core';
|
||||
import path from 'path';
|
||||
|
||||
import { toVsCodeUri } from '../utils/vsc-utils';
|
||||
import { commands, window, workspace } from 'vscode';
|
||||
describe('createFromTemplate', () => {
|
||||
describe('create-note-from-template', () => {
|
||||
afterEach(() => {
|
||||
@@ -66,7 +67,7 @@ describe('createFromTemplate', () => {
|
||||
|
||||
await commands.executeCommand('foam-vscode.create-new-template');
|
||||
|
||||
const file = await workspace.fs.readFile(Uri.file(template));
|
||||
const file = await workspace.fs.readFile(toVsCodeUri(URI.file(template)));
|
||||
expect(window.showInputBox).toHaveBeenCalled();
|
||||
expect(file).toBeDefined();
|
||||
});
|
||||
@@ -86,7 +87,9 @@ describe('createFromTemplate', () => {
|
||||
await commands.executeCommand('foam-vscode.create-new-template');
|
||||
|
||||
expect(window.showInputBox).toHaveBeenCalled();
|
||||
await expect(workspace.fs.readFile(Uri.file(template))).rejects.toThrow();
|
||||
await expect(
|
||||
workspace.fs.readFile(toVsCodeUri(URI.file(template)))
|
||||
).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -149,6 +149,118 @@ describe('resolveFoamTemplateVariables', () => {
|
||||
await resolveFoamTemplateVariables(input, new Set(['FOAM_TITLE']))
|
||||
).toEqual(expected);
|
||||
});
|
||||
|
||||
test('Appends FOAM_SELECTED_TEXT with a newline to the template if there is selected text but FOAM_SELECTED_TEXT is not referenced and the template ends in a newline', async () => {
|
||||
const foamTitle = 'My note title';
|
||||
|
||||
jest
|
||||
.spyOn(window, 'showInputBox')
|
||||
.mockImplementationOnce(jest.fn(() => Promise.resolve(foamTitle)));
|
||||
|
||||
const input = `# \${FOAM_TITLE}\n`;
|
||||
|
||||
const expectedOutput = `# My note title\nSelected text\n`;
|
||||
|
||||
const expectedMap = new Map<string, string>();
|
||||
expectedMap.set('FOAM_TITLE', foamTitle);
|
||||
expectedMap.set('FOAM_SELECTED_TEXT', 'Selected text');
|
||||
|
||||
const expected = [expectedMap, expectedOutput];
|
||||
const givenValues = new Map<string, string>();
|
||||
givenValues.set('FOAM_SELECTED_TEXT', 'Selected text');
|
||||
expect(
|
||||
await resolveFoamTemplateVariables(
|
||||
input,
|
||||
new Set(['FOAM_TITLE', 'FOAM_SELECTED_TEXT']),
|
||||
givenValues
|
||||
)
|
||||
).toEqual(expected);
|
||||
});
|
||||
|
||||
test('Appends FOAM_SELECTED_TEXT with a newline to the template if there is selected text but FOAM_SELECTED_TEXT is not referenced and the template ends in multiple newlines', async () => {
|
||||
const foamTitle = 'My note title';
|
||||
|
||||
jest
|
||||
.spyOn(window, 'showInputBox')
|
||||
.mockImplementationOnce(jest.fn(() => Promise.resolve(foamTitle)));
|
||||
|
||||
const input = `# \${FOAM_TITLE}\n\n`;
|
||||
|
||||
const expectedOutput = `# My note title\n\nSelected text\n`;
|
||||
|
||||
const expectedMap = new Map<string, string>();
|
||||
expectedMap.set('FOAM_TITLE', foamTitle);
|
||||
expectedMap.set('FOAM_SELECTED_TEXT', 'Selected text');
|
||||
|
||||
const expected = [expectedMap, expectedOutput];
|
||||
const givenValues = new Map<string, string>();
|
||||
givenValues.set('FOAM_SELECTED_TEXT', 'Selected text');
|
||||
expect(
|
||||
await resolveFoamTemplateVariables(
|
||||
input,
|
||||
new Set(['FOAM_TITLE', 'FOAM_SELECTED_TEXT']),
|
||||
givenValues
|
||||
)
|
||||
).toEqual(expected);
|
||||
});
|
||||
|
||||
test('Appends FOAM_SELECTED_TEXT without a newline to the template if there is selected text but FOAM_SELECTED_TEXT is not referenced and the template does not end in a newline', 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\nSelected text';
|
||||
|
||||
const expectedMap = new Map<string, string>();
|
||||
expectedMap.set('FOAM_TITLE', foamTitle);
|
||||
expectedMap.set('FOAM_SELECTED_TEXT', 'Selected text');
|
||||
|
||||
const expected = [expectedMap, expectedOutput];
|
||||
const givenValues = new Map<string, string>();
|
||||
givenValues.set('FOAM_SELECTED_TEXT', 'Selected text');
|
||||
expect(
|
||||
await resolveFoamTemplateVariables(
|
||||
input,
|
||||
new Set(['FOAM_TITLE', 'FOAM_SELECTED_TEXT']),
|
||||
givenValues
|
||||
)
|
||||
).toEqual(expected);
|
||||
});
|
||||
|
||||
test('Does not append FOAM_SELECTED_TEXT to a template if there is no selected text and is not referenced', 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);
|
||||
expectedMap.set('FOAM_SELECTED_TEXT', '');
|
||||
|
||||
const expected = [expectedMap, expectedOutput];
|
||||
const givenValues = new Map<string, string>();
|
||||
givenValues.set('FOAM_SELECTED_TEXT', '');
|
||||
expect(
|
||||
await resolveFoamTemplateVariables(
|
||||
input,
|
||||
new Set(['FOAM_TITLE', 'FOAM_SELECTED_TEXT']),
|
||||
givenValues
|
||||
)
|
||||
).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('determineDefaultFilepath', () => {
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
import { URI } from 'foam-core';
|
||||
import { existsSync } from 'fs';
|
||||
import * as path from 'path';
|
||||
import { isAbsolute } from 'path';
|
||||
import { TextEncoder } from 'util';
|
||||
import {
|
||||
commands,
|
||||
ExtensionContext,
|
||||
QuickPickItem,
|
||||
Selection,
|
||||
SnippetString,
|
||||
Uri,
|
||||
TextDocument,
|
||||
ViewColumn,
|
||||
window,
|
||||
workspace,
|
||||
WorkspaceEdit,
|
||||
} 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 { toVsCodeUri } from '../utils/vsc-utils';
|
||||
import { extractFoamTemplateFrontmatterMetadata } from '../utils/template-frontmatter-parser';
|
||||
|
||||
const templatesDir = Uri.joinPath(
|
||||
const templatesDir = URI.joinPath(
|
||||
workspace.workspaceFolders[0].uri,
|
||||
'.foam',
|
||||
'templates'
|
||||
@@ -30,16 +35,26 @@ export class UserCancelledOperation extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
const knownFoamVariables = new Set(['FOAM_TITLE']);
|
||||
interface FoamSelectionContent {
|
||||
document: TextDocument;
|
||||
selection: Selection;
|
||||
content: string;
|
||||
}
|
||||
|
||||
const knownFoamVariables = new Set(['FOAM_TITLE', 'FOAM_SELECTED_TEXT']);
|
||||
|
||||
const wikilinkDefaultTemplateText = `# $\{1:\$FOAM_TITLE}\n\n$0`;
|
||||
const defaultTemplateDefaultText: string = `---
|
||||
foam_template:
|
||||
name: New Note
|
||||
description: Foam's default new note template
|
||||
---
|
||||
# \${FOAM_TITLE}
|
||||
|
||||
\${FOAM_SELECTED_TEXT}
|
||||
`;
|
||||
const defaultTemplateUri = Uri.joinPath(templatesDir, 'new-note.md');
|
||||
const defaultTemplateUri = URI.joinPath(templatesDir, 'new-note.md');
|
||||
const dailyNoteTemplateUri = URI.joinPath(templatesDir, 'daily-note.md');
|
||||
|
||||
const templateContent = `# \${1:$TM_FILENAME_BASE}
|
||||
|
||||
@@ -61,16 +76,16 @@ For a full list of features see [the VS Code snippets page](https://code.visuals
|
||||
`;
|
||||
|
||||
async function templateMetadata(
|
||||
templateUri: Uri
|
||||
templateUri: URI
|
||||
): Promise<Map<string, string>> {
|
||||
const contents = await workspace.fs
|
||||
.readFile(templateUri)
|
||||
.readFile(toVsCodeUri(templateUri))
|
||||
.then(bytes => bytes.toString());
|
||||
const [templateMetadata] = extractFoamTemplateFrontmatterMetadata(contents);
|
||||
return templateMetadata;
|
||||
}
|
||||
|
||||
async function getTemplates(): Promise<Uri[]> {
|
||||
async function getTemplates(): Promise<URI[]> {
|
||||
const templates = await workspace.findFiles('.foam/templates/**.md', null);
|
||||
return templates;
|
||||
}
|
||||
@@ -110,6 +125,11 @@ async function resolveFoamTitle() {
|
||||
}
|
||||
return title;
|
||||
}
|
||||
|
||||
function resolveFoamSelectedText() {
|
||||
return findSelectionContent()?.content ?? '';
|
||||
}
|
||||
|
||||
class Resolver {
|
||||
promises = new Map<string, Thenable<string>>();
|
||||
|
||||
@@ -121,6 +141,9 @@ class Resolver {
|
||||
case 'FOAM_TITLE':
|
||||
this.promises.set(name, resolveFoamTitle());
|
||||
break;
|
||||
case 'FOAM_SELECTED_TEXT':
|
||||
this.promises.set(name, Promise.resolve(resolveFoamSelectedText()));
|
||||
break;
|
||||
default:
|
||||
this.promises.set(name, Promise.resolve(name));
|
||||
break;
|
||||
@@ -242,16 +265,14 @@ async function askUserForTemplate() {
|
||||
}
|
||||
|
||||
async function askUserForFilepathConfirmation(
|
||||
defaultFilepath: Uri,
|
||||
defaultFilepath: URI,
|
||||
defaultFilename: string
|
||||
) {
|
||||
const fsPath = URI.toFsPath(defaultFilepath);
|
||||
return await window.showInputBox({
|
||||
prompt: `Enter the filename for the new note`,
|
||||
value: defaultFilepath.fsPath,
|
||||
valueSelection: [
|
||||
defaultFilepath.fsPath.length - defaultFilename.length,
|
||||
defaultFilepath.fsPath.length - 3,
|
||||
],
|
||||
value: fsPath,
|
||||
valueSelection: [fsPath.length - defaultFilename.length, fsPath.length - 3],
|
||||
validateInput: value =>
|
||||
value.trim().length === 0
|
||||
? 'Please enter a value'
|
||||
@@ -261,27 +282,56 @@ async function askUserForFilepathConfirmation(
|
||||
});
|
||||
}
|
||||
|
||||
function appendSnippetVariableUsage(templateText: string, variable: string) {
|
||||
if (templateText.endsWith('\n')) {
|
||||
return `${templateText}\${${variable}}\n`;
|
||||
} else {
|
||||
return `${templateText}\n\${${variable}}`;
|
||||
}
|
||||
}
|
||||
|
||||
export async function resolveFoamTemplateVariables(
|
||||
templateText: string,
|
||||
extraVariablesToResolve: Set<string> = new Set()
|
||||
extraVariablesToResolve: Set<string> = new Set(),
|
||||
givenValues: Map<string, string> = new Map()
|
||||
): Promise<[Map<string, string>, string]> {
|
||||
const givenValues = new Map<string, string>();
|
||||
const variables = findFoamVariables(templateText.toString()).concat(
|
||||
...extraVariablesToResolve
|
||||
);
|
||||
const variablesInTemplate = findFoamVariables(templateText.toString());
|
||||
const variables = variablesInTemplate.concat(...extraVariablesToResolve);
|
||||
const uniqVariables = [...new Set(variables)];
|
||||
|
||||
const resolvedValues = await resolveFoamVariables(uniqVariables, givenValues);
|
||||
|
||||
if (
|
||||
resolvedValues.get('FOAM_SELECTED_TEXT') &&
|
||||
!variablesInTemplate.includes('FOAM_SELECTED_TEXT')
|
||||
) {
|
||||
templateText = appendSnippetVariableUsage(
|
||||
templateText,
|
||||
'FOAM_SELECTED_TEXT'
|
||||
);
|
||||
variablesInTemplate.push('FOAM_SELECTED_TEXT');
|
||||
variables.push('FOAM_SELECTED_TEXT');
|
||||
uniqVariables.push('FOAM_SELECTED_TEXT');
|
||||
}
|
||||
|
||||
const subbedText = substituteFoamVariables(
|
||||
templateText.toString(),
|
||||
resolvedValues
|
||||
);
|
||||
|
||||
return [resolvedValues, subbedText];
|
||||
}
|
||||
|
||||
async function writeTemplate(templateSnippet: SnippetString, filepath: Uri) {
|
||||
await workspace.fs.writeFile(filepath, new TextEncoder().encode(''));
|
||||
await focusNote(filepath, true);
|
||||
async function writeTemplate(
|
||||
templateSnippet: SnippetString,
|
||||
filepath: URI,
|
||||
viewColumn: ViewColumn = ViewColumn.Active
|
||||
) {
|
||||
await workspace.fs.writeFile(
|
||||
toVsCodeUri(filepath),
|
||||
new TextEncoder().encode('')
|
||||
);
|
||||
await focusNote(filepath, true, viewColumn);
|
||||
await window.activeTextEditor.insertSnippet(templateSnippet);
|
||||
}
|
||||
|
||||
@@ -289,27 +339,63 @@ function currentDirectoryFilepath(filename: string) {
|
||||
const activeFile = window.activeTextEditor?.document?.uri.path;
|
||||
const currentDir =
|
||||
activeFile !== undefined
|
||||
? Uri.parse(path.dirname(activeFile))
|
||||
? URI.parse(path.dirname(activeFile))
|
||||
: workspace.workspaceFolders[0].uri;
|
||||
|
||||
return Uri.joinPath(currentDir, filename);
|
||||
return URI.joinPath(currentDir, filename);
|
||||
}
|
||||
|
||||
function findSelectionContent(): FoamSelectionContent | undefined {
|
||||
const editor = window.activeTextEditor;
|
||||
if (editor === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const document = editor.document;
|
||||
const selection = editor.selection;
|
||||
|
||||
if (!document || selection.isEmpty) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
document,
|
||||
selection,
|
||||
content: document.getText(selection),
|
||||
};
|
||||
}
|
||||
|
||||
async function replaceSelectionWithWikiLink(
|
||||
document: TextDocument,
|
||||
newNoteFile: URI,
|
||||
selection: Selection
|
||||
) {
|
||||
const newNoteTitle = URI.getFileNameWithoutExtension(newNoteFile);
|
||||
|
||||
const originatingFileEdit = new WorkspaceEdit();
|
||||
originatingFileEdit.replace(document.uri, selection, `[[${newNoteTitle}]]`);
|
||||
|
||||
await workspace.applyEdit(originatingFileEdit);
|
||||
}
|
||||
|
||||
function resolveFilepathAttribute(filepath) {
|
||||
return isAbsolute(filepath)
|
||||
? URI.file(filepath)
|
||||
: URI.joinPath(workspace.workspaceFolders[0].uri, filepath);
|
||||
}
|
||||
|
||||
export function determineDefaultFilepath(
|
||||
resolvedValues: Map<string, string>,
|
||||
templateMetadata: Map<string, string>
|
||||
templateMetadata: Map<string, string>,
|
||||
fallbackURI: URI = undefined
|
||||
) {
|
||||
let defaultFilepath: Uri;
|
||||
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
|
||||
);
|
||||
}
|
||||
defaultFilepath = resolveFilepathAttribute(
|
||||
templateMetadata.get('filepath')
|
||||
);
|
||||
} else if (fallbackURI) {
|
||||
return fallbackURI;
|
||||
} else {
|
||||
const defaultSlug = resolvedValues.get('FOAM_TITLE') || 'New Note';
|
||||
defaultFilepath = currentDirectoryFilepath(`${defaultSlug}.md`);
|
||||
@@ -317,11 +403,67 @@ export function determineDefaultFilepath(
|
||||
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;
|
||||
/**
|
||||
* Creates a daily note from the daily note template.
|
||||
* @param filepathFallbackURI the URI to use if the template does not specify the `filepath` metadata attribute. This is configurable by the caller for backwards compatibility purposes.
|
||||
* @param templateFallbackText the template text to use if daily-note.md template does not exist. This is configurable by the caller for backwards compatibility purposes.
|
||||
*/
|
||||
export async function createNoteFromDailyNoteTemplate(
|
||||
filepathFallbackURI: URI,
|
||||
templateFallbackText: string
|
||||
): Promise<void> {
|
||||
return await createNoteFromDefaultTemplate(
|
||||
new Map(),
|
||||
new Set(['FOAM_SELECTED_TEXT']),
|
||||
dailyNoteTemplateUri,
|
||||
filepathFallbackURI,
|
||||
templateFallbackText
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new note when following a placeholder wikilink using the default template.
|
||||
* @param wikilinkPlaceholder the placeholder value from the wikilink. (eg. `[[Hello Joe]]` -> `Hello Joe`)
|
||||
* @param filepathFallbackURI the URI to use if the template does not specify the `filepath` metadata attribute. This is configurable by the caller for backwards compatibility purposes.
|
||||
*/
|
||||
export async function createNoteForPlaceholderWikilink(
|
||||
wikilinkPlaceholder: string,
|
||||
filepathFallbackURI: URI
|
||||
): Promise<void> {
|
||||
return await createNoteFromDefaultTemplate(
|
||||
new Map().set('FOAM_TITLE', wikilinkPlaceholder),
|
||||
new Set(['FOAM_TITLE', 'FOAM_SELECTED_TEXT']),
|
||||
defaultTemplateUri,
|
||||
filepathFallbackURI,
|
||||
wikilinkDefaultTemplateText
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new note using the default note template.
|
||||
* @param givenValues already resolved values of Foam template variables. These are used instead of resolving the Foam template variables.
|
||||
* @param extraVariablesToResolve Foam template variables to resolve, in addition to those mentioned in the template.
|
||||
* @param templateUri the URI of the template to use.
|
||||
* @param filepathFallbackURI the URI to use if the template does not specify the `filepath` metadata attribute. This is configurable by the caller for backwards compatibility purposes.
|
||||
* @param templateFallbackText the template text to use the default note template does not exist. This is configurable by the caller for backwards compatibility purposes.
|
||||
*/
|
||||
async function createNoteFromDefaultTemplate(
|
||||
givenValues: Map<string, string> = new Map(),
|
||||
extraVariablesToResolve: Set<string> = new Set([
|
||||
'FOAM_TITLE',
|
||||
'FOAM_SELECTED_TEXT',
|
||||
]),
|
||||
templateUri: URI = defaultTemplateUri,
|
||||
filepathFallbackURI: URI = undefined,
|
||||
templateFallbackText: string = defaultTemplateDefaultText
|
||||
): Promise<void> {
|
||||
const templateText = existsSync(URI.toFsPath(templateUri))
|
||||
? await workspace.fs
|
||||
.readFile(toVsCodeUri(templateUri))
|
||||
.then(bytes => bytes.toString())
|
||||
: templateFallbackText;
|
||||
|
||||
const selectedContent = findSelectionContent();
|
||||
|
||||
let resolvedValues: Map<string, string>,
|
||||
templateWithResolvedVariables: string;
|
||||
@@ -331,7 +473,8 @@ async function createNoteFromDefaultTemplate(): Promise<void> {
|
||||
templateWithResolvedVariables,
|
||||
] = await resolveFoamTemplateVariables(
|
||||
templateText,
|
||||
new Set(['FOAM_TITLE'])
|
||||
extraVariablesToResolve,
|
||||
givenValues.set('FOAM_SELECTED_TEXT', selectedContent?.content ?? '')
|
||||
);
|
||||
} catch (err) {
|
||||
if (err instanceof UserCancelledOperation) {
|
||||
@@ -349,12 +492,13 @@ async function createNoteFromDefaultTemplate(): Promise<void> {
|
||||
|
||||
const defaultFilepath = determineDefaultFilepath(
|
||||
resolvedValues,
|
||||
templateMetadata
|
||||
templateMetadata,
|
||||
filepathFallbackURI
|
||||
);
|
||||
const defaultFilename = path.basename(defaultFilepath.path);
|
||||
|
||||
let filepath = defaultFilepath;
|
||||
if (existsSync(filepath.fsPath)) {
|
||||
if (existsSync(URI.toFsPath(filepath))) {
|
||||
const newFilepath = await askUserForFilepathConfirmation(
|
||||
defaultFilepath,
|
||||
defaultFilename
|
||||
@@ -363,9 +507,22 @@ async function createNoteFromDefaultTemplate(): Promise<void> {
|
||||
if (newFilepath === undefined) {
|
||||
return;
|
||||
}
|
||||
filepath = Uri.file(newFilepath);
|
||||
filepath = URI.file(newFilepath);
|
||||
}
|
||||
|
||||
await writeTemplate(
|
||||
templateSnippet,
|
||||
filepath,
|
||||
selectedContent ? ViewColumn.Beside : ViewColumn.Active
|
||||
);
|
||||
|
||||
if (selectedContent !== undefined) {
|
||||
await replaceSelectionWithWikiLink(
|
||||
selectedContent.document,
|
||||
filepath,
|
||||
selectedContent.selection
|
||||
);
|
||||
}
|
||||
await writeTemplate(templateSnippet, filepath);
|
||||
}
|
||||
|
||||
async function createNoteFromTemplate(
|
||||
@@ -378,17 +535,23 @@ async function createNoteFromTemplate(
|
||||
templateFilename =
|
||||
(selectedTemplate as QuickPickItem).description ||
|
||||
(selectedTemplate as QuickPickItem).label;
|
||||
const templateUri = Uri.joinPath(templatesDir, templateFilename);
|
||||
const templateUri = URI.joinPath(templatesDir, templateFilename);
|
||||
const templateText = await workspace.fs
|
||||
.readFile(templateUri)
|
||||
.readFile(toVsCodeUri(templateUri))
|
||||
.then(bytes => bytes.toString());
|
||||
|
||||
const selectedContent = findSelectionContent();
|
||||
|
||||
let resolvedValues, templateWithResolvedVariables;
|
||||
try {
|
||||
[
|
||||
resolvedValues,
|
||||
templateWithResolvedVariables,
|
||||
] = await resolveFoamTemplateVariables(templateText);
|
||||
] = await resolveFoamTemplateVariables(
|
||||
templateText,
|
||||
new Set(['FOAM_SELECTED_TEXT']),
|
||||
new Map().set('FOAM_SELECTED_TEXT', selectedContent?.content ?? '')
|
||||
);
|
||||
} catch (err) {
|
||||
if (err instanceof UserCancelledOperation) {
|
||||
return;
|
||||
@@ -417,20 +580,31 @@ async function createNoteFromTemplate(
|
||||
if (filepath === undefined) {
|
||||
return;
|
||||
}
|
||||
const filepathURI = Uri.file(filepath);
|
||||
await writeTemplate(templateSnippet, filepathURI);
|
||||
const filepathURI = URI.file(filepath);
|
||||
|
||||
await writeTemplate(
|
||||
templateSnippet,
|
||||
filepathURI,
|
||||
selectedContent ? ViewColumn.Beside : ViewColumn.Active
|
||||
);
|
||||
|
||||
if (selectedContent !== undefined) {
|
||||
await replaceSelectionWithWikiLink(
|
||||
selectedContent.document,
|
||||
filepathURI,
|
||||
selectedContent.selection
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function createNewTemplate(): Promise<void> {
|
||||
const defaultFilename = 'new-template.md';
|
||||
const defaultTemplate = Uri.joinPath(templatesDir, defaultFilename);
|
||||
const defaultTemplate = URI.joinPath(templatesDir, defaultFilename);
|
||||
const fsPath = URI.toFsPath(defaultTemplate);
|
||||
const filename = await window.showInputBox({
|
||||
prompt: `Enter the filename for the new template`,
|
||||
value: defaultTemplate.fsPath,
|
||||
valueSelection: [
|
||||
defaultTemplate.fsPath.length - defaultFilename.length,
|
||||
defaultTemplate.fsPath.length - 3,
|
||||
],
|
||||
value: fsPath,
|
||||
valueSelection: [fsPath.length - defaultFilename.length, fsPath.length - 3],
|
||||
validateInput: value =>
|
||||
value.trim().length === 0
|
||||
? 'Please enter a value'
|
||||
@@ -442,9 +616,9 @@ async function createNewTemplate(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
const filenameURI = Uri.file(filename);
|
||||
const filenameURI = URI.file(filename);
|
||||
await workspace.fs.writeFile(
|
||||
filenameURI,
|
||||
toVsCodeUri(filenameURI),
|
||||
new TextEncoder().encode(templateContent)
|
||||
);
|
||||
await focusNote(filenameURI, false);
|
||||
|
||||
@@ -100,4 +100,46 @@ describe('Document links provider', () => {
|
||||
);
|
||||
expect(links[0].range).toEqual(new vscode.Range(0, 18, 0, 35));
|
||||
});
|
||||
|
||||
it('should support wikilinks that have an alias', async () => {
|
||||
const fileB = await createFile("# File B that's aliased");
|
||||
const fileA = await createFile(
|
||||
`this is a link to [[${fileB.name}|alias]].`
|
||||
);
|
||||
|
||||
const noteA = parser.parse(fileA.uri, fileA.content);
|
||||
const noteB = parser.parse(fileB.uri, fileB.content);
|
||||
const ws = createTestWorkspace()
|
||||
.set(noteA)
|
||||
.set(noteB);
|
||||
|
||||
const { doc } = await showInEditor(noteA.uri);
|
||||
const provider = new LinkProvider(ws, parser);
|
||||
const links = provider.provideDocumentLinks(doc);
|
||||
|
||||
expect(links.length).toEqual(1);
|
||||
expect(links[0].target).toEqual(OPEN_COMMAND.asURI(noteB.uri));
|
||||
expect(links[0].range).toEqual(new vscode.Range(0, 18, 0, 33));
|
||||
});
|
||||
|
||||
it('should support wikilink aliases in tables using escape character', async () => {
|
||||
const fileB = await createFile('# File that has to be aliased');
|
||||
const fileA = await createFile(`
|
||||
| Col A | ColB |
|
||||
| --- | --- |
|
||||
| [[${fileB.name}\\|alias]] | test |
|
||||
`);
|
||||
const noteA = parser.parse(fileA.uri, fileA.content);
|
||||
const noteB = parser.parse(fileB.uri, fileB.content);
|
||||
const ws = createTestWorkspace()
|
||||
.set(noteA)
|
||||
.set(noteB);
|
||||
|
||||
const { doc } = await showInEditor(noteA.uri);
|
||||
const provider = new LinkProvider(ws, parser);
|
||||
const links = provider.provideDocumentLinks(doc);
|
||||
|
||||
expect(links.length).toEqual(1);
|
||||
expect(links[0].target).toEqual(OPEN_COMMAND.asURI(noteB.uri));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@ import utilityCommands from './utility-commands';
|
||||
import documentLinkProvider from './document-link-provider';
|
||||
import previewNavigation from './preview-navigation';
|
||||
import completionProvider from './link-completion';
|
||||
import tagCompletionProvider from './tag-completion';
|
||||
import linkDecorations from './document-decorator';
|
||||
import { FoamFeature } from '../types';
|
||||
|
||||
@@ -35,4 +36,5 @@ export const features: FoamFeature[] = [
|
||||
linkDecorations,
|
||||
previewNavigation,
|
||||
completionProvider,
|
||||
tagCompletionProvider,
|
||||
];
|
||||
|
||||
@@ -73,7 +73,7 @@ describe('Link Completion', () => {
|
||||
expect(links.items.length).toEqual(4);
|
||||
});
|
||||
|
||||
it('should not return link outside the wiki-link brackets', async () => {
|
||||
it('should not return link outside the wikilink brackets', async () => {
|
||||
const { uri } = await createFile('[[file]] then');
|
||||
const { doc } = await showInEditor(uri);
|
||||
const provider = new CompletionProvider(ws, graph);
|
||||
|
||||
@@ -80,7 +80,7 @@ const getDailyNoteLink = (date: Date) => {
|
||||
return `[[${name.replace(`.${foamExtension}`, '')}]]`;
|
||||
};
|
||||
|
||||
const snippets: (() => DateSnippet)[] = [
|
||||
const snippetFactories: (() => DateSnippet)[] = [
|
||||
() => ({
|
||||
detail: "Insert a link to today's daily note",
|
||||
snippet: '/day',
|
||||
@@ -169,24 +169,28 @@ const computedSnippets: ((number: number) => DateSnippet)[] = [
|
||||
];
|
||||
|
||||
const completions: CompletionItemProvider = {
|
||||
provideCompletionItems: (_document, _position, _token, _context) => {
|
||||
provideCompletionItems: (document, position, _token, _context) => {
|
||||
if (_context.triggerKind === CompletionTriggerKind.Invoke) {
|
||||
// if completion was triggered without trigger character then we return [] to fallback
|
||||
// to vscode word-based suggestions (see https://github.com/foambubble/foam/pull/417)
|
||||
return [];
|
||||
}
|
||||
|
||||
const range = document.getWordRangeAtPosition(position, /\S+/);
|
||||
const completionItems = [
|
||||
...snippets.map(item => createCompletionItem(item())),
|
||||
...generateDayOfWeekSnippets().map(item => createCompletionItem(item)),
|
||||
];
|
||||
...snippetFactories.map(snippetFactory => snippetFactory()),
|
||||
...generateDayOfWeekSnippets(),
|
||||
].map(snippet => {
|
||||
const completionItem = createCompletionItem(snippet);
|
||||
completionItem.range = range;
|
||||
return completionItem;
|
||||
});
|
||||
return completionItems;
|
||||
},
|
||||
};
|
||||
|
||||
const computedCompletions: CompletionItemProvider = {
|
||||
provideCompletionItems: (document, position, _token, _context) => {
|
||||
if (_context.triggerKind === CompletionTriggerKind.Invoke) {
|
||||
export const datesCompletionProvider: CompletionItemProvider = {
|
||||
provideCompletionItems: (document, position, _token, context) => {
|
||||
if (context.triggerKind === CompletionTriggerKind.Invoke) {
|
||||
// if completion was triggered without trigger character then we return [] to fallback
|
||||
// to vscode word-based suggestions (see https://github.com/foambubble/foam/pull/417)
|
||||
return [];
|
||||
@@ -229,7 +233,7 @@ const feature: FoamFeature = {
|
||||
languages.registerCompletionItemProvider('markdown', completions, '/');
|
||||
languages.registerCompletionItemProvider(
|
||||
'markdown',
|
||||
computedCompletions,
|
||||
datesCompletionProvider,
|
||||
'/',
|
||||
'+'
|
||||
);
|
||||
|
||||
@@ -19,7 +19,7 @@ describe('Link generation in preview', () => {
|
||||
expect(md.render(`[[note-a]]`)).toEqual(
|
||||
`<p><a class='foam-note-link' title='${noteA.title}' href='${URI.toFsPath(
|
||||
noteA.uri
|
||||
)}'>note-a</a></p>\n`
|
||||
)}' data-href='${URI.toFsPath(noteA.uri)}'>note-a</a></p>\n`
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import * as vscode from 'vscode';
|
||||
import markdownItRegex from 'markdown-it-regex';
|
||||
import { Foam, FoamWorkspace, Logger, URI } from 'foam-core';
|
||||
import markdownItRegex from 'markdown-it-regex';
|
||||
import * as vscode from 'vscode';
|
||||
import { FoamFeature } from '../types';
|
||||
import { isNone } from '../utils';
|
||||
|
||||
const ALIAS_DIVIDER_CHAR = '|';
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: async (
|
||||
@@ -11,11 +14,13 @@ const feature: FoamFeature = {
|
||||
const foam = await foamPromise;
|
||||
|
||||
return {
|
||||
extendMarkdownIt: (md: markdownit) =>
|
||||
[markdownItWithFoamTags, markdownItWithFoamLinks].reduce(
|
||||
(acc, extension) => extension(acc, foam.workspace),
|
||||
md
|
||||
),
|
||||
extendMarkdownIt: (md: markdownit) => {
|
||||
return [
|
||||
markdownItWithFoamTags,
|
||||
markdownItWithFoamLinks,
|
||||
markdownItWithRemoveLinkReferences,
|
||||
].reduce((acc, extension) => extension(acc, foam.workspace), md);
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -29,13 +34,25 @@ export const markdownItWithFoamLinks = (
|
||||
regex: /\[\[([^[\]]+?)\]\]/,
|
||||
replace: (wikilink: string) => {
|
||||
try {
|
||||
const resource = workspace.find(wikilink);
|
||||
if (resource == null) {
|
||||
return getPlaceholderLink(wikilink);
|
||||
const linkHasAlias = wikilink.includes(ALIAS_DIVIDER_CHAR);
|
||||
const resourceLink = linkHasAlias
|
||||
? wikilink.substring(0, wikilink.indexOf('|'))
|
||||
: wikilink;
|
||||
|
||||
const resource = workspace.find(resourceLink);
|
||||
if (isNone(resource)) {
|
||||
return getPlaceholderLink(resourceLink);
|
||||
}
|
||||
|
||||
const linkLabel = linkHasAlias
|
||||
? wikilink.substr(wikilink.indexOf('|') + 1)
|
||||
: wikilink;
|
||||
|
||||
return `<a class='foam-note-link' title='${
|
||||
resource.title
|
||||
}' href='${URI.toFsPath(resource.uri)}'>${wikilink}</a>`;
|
||||
}' href='${URI.toFsPath(resource.uri)}' data-href='${URI.toFsPath(
|
||||
resource.uri
|
||||
)}'>${linkLabel}</a>`;
|
||||
} catch (e) {
|
||||
Logger.error(
|
||||
`Error while creating link for [[${wikilink}]] in Preview panel`,
|
||||
@@ -60,7 +77,7 @@ export const markdownItWithFoamTags = (
|
||||
replace: (tag: string) => {
|
||||
try {
|
||||
const resource = workspace.find(tag);
|
||||
if (resource == null) {
|
||||
if (isNone(resource)) {
|
||||
return getFoamTag(tag);
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -77,4 +94,22 @@ export const markdownItWithFoamTags = (
|
||||
const getFoamTag = (content: string) =>
|
||||
`<span class='foam-tag'>${content}</span>`;
|
||||
|
||||
export const markdownItWithRemoveLinkReferences = (
|
||||
md: markdownit,
|
||||
workspace: FoamWorkspace
|
||||
) => {
|
||||
// Forget about reference links that contain an alias divider
|
||||
md.inline.ruler.before('link', 'clear-references', state => {
|
||||
if (state.env.references) {
|
||||
Object.keys(state.env.references).forEach(refKey => {
|
||||
if (refKey.includes(ALIAS_DIVIDER_CHAR)) {
|
||||
delete state.env.references[refKey];
|
||||
}
|
||||
});
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return md;
|
||||
};
|
||||
|
||||
export default feature;
|
||||
|
||||
80
packages/foam-vscode/src/features/tag-completion.spec.ts
Normal file
80
packages/foam-vscode/src/features/tag-completion.spec.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { FoamTags, FoamWorkspace } from 'foam-core';
|
||||
import {
|
||||
cleanWorkspace,
|
||||
closeEditors,
|
||||
createFile,
|
||||
createTestNote,
|
||||
showInEditor,
|
||||
} from '../test/test-utils';
|
||||
import { TagCompletionProvider } from './tag-completion';
|
||||
|
||||
describe('Tag Completion', () => {
|
||||
const root = vscode.workspace.workspaceFolders[0].uri;
|
||||
const ws = new FoamWorkspace();
|
||||
ws.set(
|
||||
createTestNote({
|
||||
root,
|
||||
uri: 'file-name.md',
|
||||
tags: new Set(['primary']),
|
||||
})
|
||||
)
|
||||
.set(
|
||||
createTestNote({
|
||||
root,
|
||||
uri: 'File name with spaces.md',
|
||||
tags: new Set(['secondary']),
|
||||
})
|
||||
)
|
||||
.set(
|
||||
createTestNote({
|
||||
root,
|
||||
uri: 'path/to/file.md',
|
||||
links: [{ slug: 'placeholder text' }],
|
||||
tags: new Set(['primary', 'third']),
|
||||
})
|
||||
);
|
||||
const foamTags = FoamTags.fromWorkspace(ws);
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanWorkspace();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
ws.dispose();
|
||||
foamTags.dispose();
|
||||
await cleanWorkspace();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await closeEditors();
|
||||
});
|
||||
|
||||
it('should not return any tags for empty documents', async () => {
|
||||
const { uri } = await createFile('');
|
||||
const { doc } = await showInEditor(uri);
|
||||
const provider = new TagCompletionProvider(foamTags);
|
||||
|
||||
const tags = await provider.provideCompletionItems(
|
||||
doc,
|
||||
new vscode.Position(0, 0)
|
||||
);
|
||||
|
||||
expect(foamTags.tags.get('primary')).toBeTruthy();
|
||||
expect(tags).toBeNull();
|
||||
});
|
||||
|
||||
it('should provide a suggestion when typing #prim', async () => {
|
||||
const { uri } = await createFile('#prim');
|
||||
const { doc } = await showInEditor(uri);
|
||||
const provider = new TagCompletionProvider(foamTags);
|
||||
|
||||
const tags = await provider.provideCompletionItems(
|
||||
doc,
|
||||
new vscode.Position(0, 5)
|
||||
);
|
||||
|
||||
expect(foamTags.tags.get('primary')).toBeTruthy();
|
||||
expect(tags.items.length).toEqual(3);
|
||||
});
|
||||
});
|
||||
58
packages/foam-vscode/src/features/tag-completion.ts
Normal file
58
packages/foam-vscode/src/features/tag-completion.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Foam } from 'foam-core';
|
||||
import { FoamTags } from 'packages/foam-core/src/model/tags';
|
||||
import * as vscode from 'vscode';
|
||||
import { FoamFeature } from '../types';
|
||||
import { mdDocSelector } from '../utils';
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: async (
|
||||
context: vscode.ExtensionContext,
|
||||
foamPromise: Promise<Foam>
|
||||
) => {
|
||||
const foam = await foamPromise;
|
||||
context.subscriptions.push(
|
||||
vscode.languages.registerCompletionItemProvider(
|
||||
mdDocSelector,
|
||||
new TagCompletionProvider(foam.tags),
|
||||
'#'
|
||||
)
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export class TagCompletionProvider
|
||||
implements vscode.CompletionItemProvider<vscode.CompletionItem> {
|
||||
constructor(private foamTags: FoamTags) {}
|
||||
|
||||
provideCompletionItems(
|
||||
document: vscode.TextDocument,
|
||||
position: vscode.Position
|
||||
): vscode.ProviderResult<vscode.CompletionList<vscode.CompletionItem>> {
|
||||
const cursorPrefix = document
|
||||
.lineAt(position)
|
||||
.text.substr(0, position.character);
|
||||
|
||||
const requiresAutocomplete = cursorPrefix.match(/#(.*)/);
|
||||
|
||||
if (!requiresAutocomplete) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const completionTags = [];
|
||||
[...this.foamTags.tags].forEach(([tag]) => {
|
||||
const item = new vscode.CompletionItem(
|
||||
tag,
|
||||
vscode.CompletionItemKind.Text
|
||||
);
|
||||
|
||||
item.insertText = `${tag}`;
|
||||
item.documentation = tag;
|
||||
|
||||
completionTags.push(item);
|
||||
});
|
||||
|
||||
return new vscode.CompletionList(completionTags);
|
||||
}
|
||||
}
|
||||
|
||||
export default feature;
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Foam, FoamWorkspace, Resource } from 'foam-core';
|
||||
import { TagMetadata } from 'packages/foam-core/src/model/tags';
|
||||
import * as vscode from 'vscode';
|
||||
import { Foam, Resource, URI, FoamWorkspace } from 'foam-core';
|
||||
import { FoamFeature } from '../../types';
|
||||
import { getNoteTooltip, getContainsTooltip, isSome } from '../../utils';
|
||||
import { getNoteTooltip, isSome } from '../../utils';
|
||||
|
||||
const TAG_SEPARATOR = '/';
|
||||
const feature: FoamFeature = {
|
||||
@@ -46,18 +47,7 @@ export class TagsProvider implements vscode.TreeDataProvider<TagTreeItem> {
|
||||
}
|
||||
|
||||
private computeTags() {
|
||||
const rawTags: {
|
||||
[key: string]: TagMetadata[];
|
||||
} = this.foam.workspace
|
||||
.list()
|
||||
.reduce((acc: { [key: string]: TagMetadata[] }, note) => {
|
||||
note.tags.forEach(tag => {
|
||||
acc[tag] = acc[tag] ?? [];
|
||||
acc[tag].push({ title: note.title, uri: note.uri });
|
||||
});
|
||||
return acc;
|
||||
}, {});
|
||||
this.tags = Object.entries(rawTags)
|
||||
this.tags = [...this.foam.tags.tags]
|
||||
.map(([tag, notes]) => ({ tag, notes }))
|
||||
.sort((a, b) => a.tag.localeCompare(b.tag));
|
||||
}
|
||||
@@ -124,8 +114,6 @@ export class TagsProvider implements vscode.TreeDataProvider<TagTreeItem> {
|
||||
|
||||
type TagTreeItem = Tag | TagReference | TagSearch;
|
||||
|
||||
type TagMetadata = { title: string; uri: URI };
|
||||
|
||||
export class Tag extends vscode.TreeItem {
|
||||
constructor(
|
||||
public readonly tag: string,
|
||||
@@ -136,7 +124,6 @@ export class Tag extends vscode.TreeItem {
|
||||
this.description = `${this.notes.length} reference${
|
||||
this.notes.length !== 1 ? 's' : ''
|
||||
}`;
|
||||
this.tooltip = getContainsTooltip(this.notes.map(n => n.title));
|
||||
}
|
||||
|
||||
iconPath = new vscode.ThemeIcon('symbol-number');
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { FoamFeature } from '../types';
|
||||
import { commands } from 'vscode';
|
||||
import { createNoteFromPlaceholder, focusNote, isSome } from '../utils';
|
||||
import { URI } from 'foam-core';
|
||||
import { toVsCodeUri } from '../utils/vsc-utils';
|
||||
import { createNoteForPlaceholderWikilink } from './create-from-template';
|
||||
|
||||
export const OPEN_COMMAND = {
|
||||
command: 'foam-vscode.open-resource',
|
||||
@@ -16,16 +16,24 @@ export const OPEN_COMMAND = {
|
||||
return vscode.commands.executeCommand('vscode.open', toVsCodeUri(uri));
|
||||
|
||||
case 'placeholder':
|
||||
const newNote = await createNoteFromPlaceholder(uri);
|
||||
const title = uri.path.split('/').slice(-1)[0];
|
||||
|
||||
if (isSome(newNote)) {
|
||||
const title = uri.path.split('/').slice(-1);
|
||||
const snippet = new vscode.SnippetString(
|
||||
'# ${1:' + title + '}\n\n$0'
|
||||
);
|
||||
await focusNote(newNote, true);
|
||||
await vscode.window.activeTextEditor.insertSnippet(snippet);
|
||||
const basedir =
|
||||
vscode.workspace.workspaceFolders.length > 0
|
||||
? vscode.workspace.workspaceFolders[0].uri
|
||||
: vscode.window.activeTextEditor?.document.uri
|
||||
? URI.getDir(vscode.window.activeTextEditor!.document.uri)
|
||||
: undefined;
|
||||
|
||||
if (basedir === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = toVsCodeUri(
|
||||
URI.createResourceUriFromPlaceholder(basedir, uri)
|
||||
);
|
||||
|
||||
await createNoteForPlaceholderWikilink(title, target);
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -13,6 +13,10 @@ class ExtendedVscodeEnvironment extends VscodeEnvironment {
|
||||
// And also https://github.com/microsoft/vscode-test/issues/37#issuecomment-700167820
|
||||
this.global.RegExp = RegExp;
|
||||
this.global.vscode = vscode;
|
||||
|
||||
vscode.workspace
|
||||
.getConfiguration()
|
||||
.update('foam.edit.linkReferenceDefinitions', 'off');
|
||||
}
|
||||
async teardown() {
|
||||
this.global.vscode = initialVscode;
|
||||
|
||||
@@ -70,10 +70,10 @@ export const createTestNote = (params: {
|
||||
return 'slug' in link
|
||||
? {
|
||||
type: 'wikilink',
|
||||
slug: link.slug,
|
||||
target: link.slug,
|
||||
label: link.slug,
|
||||
range: range,
|
||||
text: 'link text',
|
||||
rawText: 'link text',
|
||||
}
|
||||
: {
|
||||
type: 'link',
|
||||
@@ -139,7 +139,7 @@ export const createNote = (r: Resource) => {
|
||||
|
||||
some content and ${r.links
|
||||
.map(l =>
|
||||
l.type === 'wikilink' ? `[[${l.slug}]]` : `[${l.label}](${l.target})`
|
||||
l.type === 'wikilink' ? `[[${l.label}]]` : `[${l.label}](${l.target})`
|
||||
)
|
||||
.join(' some content between links.\n')}
|
||||
last line.
|
||||
|
||||
@@ -9,13 +9,12 @@ import {
|
||||
Selection,
|
||||
MarkdownString,
|
||||
version,
|
||||
Uri,
|
||||
ViewColumn,
|
||||
} from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
import { Logger, URI } from 'foam-core';
|
||||
import matter from 'gray-matter';
|
||||
import removeMarkdown from 'remove-markdown';
|
||||
import { TextEncoder } from 'util';
|
||||
import os from 'os';
|
||||
import { toVsCodeUri } from './utils/vsc-utils';
|
||||
|
||||
@@ -169,9 +168,13 @@ export function isNone<T>(
|
||||
return value == null; // eslint-disable-line
|
||||
}
|
||||
|
||||
export async function focusNote(notePath: URI, moveCursorToEnd: boolean) {
|
||||
export async function focusNote(
|
||||
notePath: URI,
|
||||
moveCursorToEnd: boolean,
|
||||
viewColumn: ViewColumn = ViewColumn.Active
|
||||
) {
|
||||
const document = await workspace.openTextDocument(toVsCodeUri(notePath));
|
||||
const editor = await window.showTextDocument(document);
|
||||
const editor = await window.showTextDocument(document, viewColumn);
|
||||
|
||||
// Move the cursor to end of the file
|
||||
if (moveCursorToEnd) {
|
||||
@@ -253,30 +256,3 @@ export function stripImages(markdown: string): string {
|
||||
'$1'.length ? '[Image: $1]' : ''
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a note from the given placeholder Uri.
|
||||
*
|
||||
* @param placeholder the placeholder Uri
|
||||
* @returns the Uri of the created note, or `null`
|
||||
* if the Uri was not a placeholder or no reference directory could be found
|
||||
*/
|
||||
export const createNoteFromPlaceholder = async (
|
||||
placeholder: URI
|
||||
): Promise<Uri | null> => {
|
||||
const basedir =
|
||||
workspace.workspaceFolders.length > 0
|
||||
? workspace.workspaceFolders[0].uri
|
||||
: window.activeTextEditor?.document.uri
|
||||
? URI.getDir(window.activeTextEditor!.document.uri)
|
||||
: null;
|
||||
|
||||
if (isSome(basedir)) {
|
||||
const target = toVsCodeUri(
|
||||
URI.createResourceUriFromPlaceholder(basedir, placeholder)
|
||||
);
|
||||
await workspace.fs.writeFile(target, new TextEncoder().encode(''));
|
||||
return target;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -4,14 +4,16 @@
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"outDir": "out",
|
||||
"lib": ["es6"],
|
||||
"lib": ["ES2019"],
|
||||
"sourceMap": true,
|
||||
"strict": false,
|
||||
"downlevelIteration": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", ".vscode-test"],
|
||||
"references": [{
|
||||
"path": "../foam-core"
|
||||
}]
|
||||
"references": [
|
||||
{
|
||||
"path": "../foam-core"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
@@ -22,7 +22,7 @@ Whether you want to build a [Second Brain](https://www.buildingasecondbrain.com/
|
||||
|
||||
1. Create a single **Foam** workspace for all your knowledge and research following the [[Getting started]] guide.
|
||||
2. Write your thoughts in markdown documents (I like to call them **Bubbles**, but that might be more than a little twee). These documents should be atomic: Put things that belong together into a single document, and limit its content to that single topic. ([source](https://zettelkasten.de/posts/overview/#principles))
|
||||
3. Use Foam's shortcuts and autocompletions to link your thoughts together with `[[wiki-links]]`, and navigate between them to explore your knowledge graph.
|
||||
3. Use Foam's shortcuts and autocompletions to link your thoughts together with `[[wikilinks]]`, and navigate between them to explore your knowledge graph.
|
||||
4. Get an overview of your **Foam** workspace using the [[Graph Visualisation]], and discover relationships between your thoughts with the use of [[Backlinking]].
|
||||
|
||||

|
||||
@@ -48,7 +48,7 @@ You can also browse the [docs folder](https://github.com/foambubble/foam/tree/ma
|
||||
Foam is licensed under the [MIT license](LICENSE).
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[wiki-links]: docs/wiki-links.md "Wiki Links"
|
||||
[wikilinks]: docs/wikilinks.md "Wikilinks"
|
||||
[Getting started]: docs/index.md "Getting started"
|
||||
[Graph Visualisation]: docs/features/graph-visualisation.md "Graph Visualisation"
|
||||
[Backlinking]: docs/features/backlinking.md "Backlinking"
|
||||
@@ -158,6 +158,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<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>
|
||||
<td align="center"><a href="https://github.com/bronson"><img src="https://avatars.githubusercontent.com/u/1776?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Scott Bronson</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=bronson" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://rafaelriedel.de"><img src="https://avatars.githubusercontent.com/u/41793?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Rafael Riedel</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=rafo" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
11
yarn.lock
11
yarn.lock
@@ -5422,9 +5422,9 @@ has@^1.0.3:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
hosted-git-info@^2.1.4, hosted-git-info@^2.7.1:
|
||||
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==
|
||||
|
||||
hosted-git-info@^4.0.0:
|
||||
version "4.0.0"
|
||||
@@ -9128,6 +9128,11 @@ repeating@^2.0.0:
|
||||
dependencies:
|
||||
is-finite "^1.0.0"
|
||||
|
||||
replace-ext@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-2.0.0.tgz#9471c213d22e1bcc26717cd6e50881d88f812b06"
|
||||
integrity sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==
|
||||
|
||||
request-promise-core@1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f"
|
||||
|
||||
Reference in New Issue
Block a user