mirror of
https://github.com/foambubble/foam.git
synced 2026-01-11 06:58:11 -05:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a849d220b | ||
|
|
6001dc0214 | ||
|
|
93cedcc490 | ||
|
|
cad0c38965 | ||
|
|
bdb95a0832 | ||
|
|
e0580d39bf | ||
|
|
279b3b48f1 | ||
|
|
e3c63fca89 | ||
|
|
948b7db5ef | ||
|
|
503a8f5f18 | ||
|
|
8ab4f13543 | ||
|
|
894bf12899 | ||
|
|
a3b375e248 | ||
|
|
c840070a3a | ||
|
|
8a6551f281 | ||
|
|
88ae96cf25 | ||
|
|
acfd2e1fc1 | ||
|
|
6b02a87538 | ||
|
|
1a99e693df | ||
|
|
dd467ce86f | ||
|
|
7d7446ef7e | ||
|
|
6be4f002b8 | ||
|
|
bb6faee06d | ||
|
|
31cfeb3034 | ||
|
|
5d11818ffc | ||
|
|
e7ee143544 | ||
|
|
aa311b2688 | ||
|
|
0fca141a7b | ||
|
|
6a4bd341ab | ||
|
|
764750f591 | ||
|
|
2686b9a365 | ||
|
|
5a6ef644bd |
@@ -1112,6 +1112,33 @@
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "markschaver",
|
||||
"name": "Mark Schaver",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7584?v=4",
|
||||
"profile": "http://schaver.com/",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "n8layman",
|
||||
"name": "Nathan Layman",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/25353944?v=4",
|
||||
"profile": "https://github.com/n8layman",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "emmanuel-ferdman",
|
||||
"name": "Emmanuel Ferdman",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/35470921?v=4",
|
||||
"profile": "https://github.com/emmanuel-ferdman",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
8
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
8
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -74,13 +74,13 @@ body:
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating System Version
|
||||
description: What opearting system are you using?
|
||||
description: What operating system are you using?
|
||||
placeholder: |
|
||||
- OS: [e.g. macOS, Windows, Linux]
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: vscode_version
|
||||
id: vscode_version
|
||||
attributes:
|
||||
label: Visual Studio Code Version
|
||||
description: |
|
||||
@@ -92,6 +92,6 @@ body:
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: |
|
||||
description: |
|
||||
Add any other context about the problem here.
|
||||
The Foam log output for VSCode can be found here: https://github.com/foambubble/foam/blob/master/docs/features/foam-logging-in-vscode.md
|
||||
The Foam log output for VSCode can be found here: https://github.com/foambubble/foam/blob/main/docs/user/tools/foam-logging-in-vscode.md
|
||||
|
||||
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -3,10 +3,10 @@ name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
|
||||
jobs:
|
||||
typos-check:
|
||||
|
||||
2
.github/workflows/update-docs.yml
vendored
2
.github/workflows/update-docs.yml
vendored
@@ -3,7 +3,7 @@ name: Update Docs
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
paths:
|
||||
- docs/user/**/*
|
||||
- docs/.vscode/**/*
|
||||
|
||||
23
.vscode/tasks.json
vendored
23
.vscode/tasks.json
vendored
@@ -7,7 +7,28 @@
|
||||
"label": "watch: foam-vscode",
|
||||
"type": "npm",
|
||||
"script": "watch",
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"problemMatcher": {
|
||||
"owner": "typescript",
|
||||
"fileLocation": ["relative", "${workspaceFolder}"],
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.*?)\\((\\d+),(\\d+)\\):\\s+(.*)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3,
|
||||
"message": 4
|
||||
}
|
||||
],
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": {
|
||||
"regexp": ".*"
|
||||
},
|
||||
"endsPattern": {
|
||||
"regexp": ".*"
|
||||
}
|
||||
}
|
||||
},
|
||||
"isBackground": true,
|
||||
"presentation": {
|
||||
"reveal": "always"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
tags: todo, good-first-task
|
||||
---
|
||||
|
||||
# Contribution Guide
|
||||
|
||||
Foam is open to contributions of any kind, including but not limited to code, documentation, ideas, and feedback.
|
||||
@@ -44,19 +45,19 @@ You should now be ready to start working!
|
||||
|
||||
Foam code and documentation live in the monorepo at [foambubble/foam](https://github.com/foambubble/foam/).
|
||||
|
||||
- [/docs](https://github.com/foambubble/foam/tree/master/docs): documentation and [[recipes]].
|
||||
- [/docs](https://github.com/foambubble/foam/tree/main/docs): documentation and [[recipes]].
|
||||
|
||||
Exceptions to the monorepo are:
|
||||
|
||||
- The starter template at [foambubble/foam-template](https://github.com/foambubble/)
|
||||
- All other [[recommended-extensions]] live in their respective GitHub repos
|
||||
|
||||
This project uses [Yarn workspaces](https://classic.yarnpkg.com/en/docs/workspaces/).
|
||||
This project uses [Yarn workspaces](https://classic.yarnpkg.com/en/docs/workspaces/).
|
||||
|
||||
Originally Foam had:
|
||||
|
||||
- [/packages/foam-core](https://github.com/foambubble/foam/tree/ee7a8919761f168d3931079adf21c5ad4d63db59/packages/foam-core) - Powers the core functionality in Foam across all platforms.
|
||||
- [/packages/foam-vscode](https://github.com/foambubble/foam/tree/master/packages/foam-vscode) - The core VS Code plugin.
|
||||
- [/packages/foam-vscode](https://github.com/foambubble/foam/tree/main/packages/foam-vscode) - The core VS Code plugin.
|
||||
|
||||
To improve DX we have moved the `foam-core` module into `packages/foam-vscode/src/core`, but from a development point of view it's useful to think of the `foam-vscode/src/core` "submodule" as something that might be extracted in the future.
|
||||
|
||||
@@ -81,7 +82,7 @@ Tests 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. 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.
|
||||
1. Now we'll use the launch configuration defined at [`.vscode/launch.json`](https://github.com/foambubble/foam/blob/main/.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.
|
||||
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
# Foam Core 2020-07-11
|
||||
|
||||
Present: @jevakallio, @riccardoferretti
|
||||
|
||||
### Tests
|
||||
|
||||
- How do we know this approach works?
|
||||
- Supports renaming
|
||||
- Supports searching with (attribute-x)
|
||||
- Find dead links
|
||||
|
||||
### Getting started
|
||||
|
||||
- Land work to master
|
||||
- Create a foam-core package
|
||||
|
||||
-
|
||||
|
||||
### Open questions
|
||||
|
||||
- How should writing to files work
|
||||
- What if affected notes have unsaved changes
|
||||
|
||||
### Graph methods
|
||||
|
||||
- get all
|
||||
- search by
|
||||
- tag
|
||||
- free text
|
||||
- [[todo]]: how do vs code search editors work? are they pluggable? what do they need?
|
||||
- find dead links
|
||||
- for linters
|
||||
- serialize/toJSON (for visualizers)
|
||||
- subscribe to changes
|
||||
- find if a link exists (and which link) in a given row / column position + return it's start and end position - this would probably be needed e.g. to CTRL-hovering to work properly
|
||||
|
||||
### Node methods
|
||||
|
||||
- rename node and all links to that node
|
||||
- get links
|
||||
- forward links (for link lists)
|
||||
- backlinks (with surrounding context)
|
||||
|
||||
### Node definition
|
||||
|
||||
What do we need the node (and edge metadata) to contain:
|
||||
|
||||
- `id`: tbd
|
||||
- should be unique, needs some kind of unique gen function
|
||||
- should be reconstructable even if links are not updated every time
|
||||
- what happens during rename? is reparenting the graph going to be hard?
|
||||
- do id's need to be persistent, or can we create them per in-memory session, keep them stable despite renames, and then next session generate a new id?
|
||||
- Ideally should be a path to file, so it's easy to look up from the graph by id for renaming
|
||||
- `type`: Note | Image | etc
|
||||
- `title`: can be read from markdown title or frontmatter metadata
|
||||
- `path`: full path to file, relative to workspace (graph) root
|
||||
- `links`:
|
||||
- `id`: File to link to
|
||||
- `text`: The link label
|
||||
- `type` markdown | mediawiki | image | http
|
||||
- `section`: : Anchor link to a heading in target note, if we want to add support for linking to sections
|
||||
- `block` (ref)
|
||||
- Positional data from AST?
|
||||
- `tags`
|
||||
|
||||
### Markdown layer
|
||||
|
||||
- `source`: raw markdown (rename?)
|
||||
- `ast`: raw markdown ast
|
||||
- `checksum`: if we do caching
|
||||
|
||||
### Link text
|
||||
|
||||
// some-file.md
|
||||
// # Some File
|
||||
|
||||
Write -> Store on disk
|
||||
[[Some File]] -> [Some File](some-file.md)
|
||||
|
||||
Editing
|
||||
[Some File](some-file.md)
|
||||
|
||||
On disk (could be solved by migration)
|
||||
[[some-file]]
|
||||
[[Some File]]
|
||||
|
||||
- docs/index.md -> Index
|
||||
- notes/index.md -> Index
|
||||
|
||||
[[Index]]
|
||||
[[Index | notes/index.md]]
|
||||
|
||||
[Index] docs/index.md
|
||||
[Index | notes/index.md]: notes/index.md
|
||||
|
||||
[[Some File | path/to/some-file.md]]
|
||||
|
||||
Do we apply any constraints:
|
||||
|
||||
- `[[file-name-without-extension]]`
|
||||
- `[[file-name-with-extension.md]]`
|
||||
- `[[Title Cased File Name]]`
|
||||
|
||||
Not supported by Markdown Notes:
|
||||
|
||||
- `[[path/to/file-name.md]]` - Just use markdown links
|
||||
- `[[Target Note Title]]`
|
||||
|
||||
Issues:
|
||||
|
||||
- Name clashes in directories
|
||||
- Name clashes between extensions
|
||||
- Renaming
|
||||
- Change filename/title needs to reflect everywhere
|
||||
- Orphaning
|
||||
|
||||
- If we can't rely on in-memory process to rename things correctly while changes happen (e.g. file is renamed, moved, deleted, or titled) <ref id="1" />
|
||||
|
||||
Solving this issue is necessarily heuristic. We could try to write smart solutions, plus a linter for orphans
|
||||
|
||||
How others solve this:
|
||||
|
||||
- Unique ids -- could support optionally as part of file name or front matter metadata. Should not be required.
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[todo]: ../todo.md "Todo"
|
||||
[Index]: ../../index.md "Foam"
|
||||
[//end]: # "Autogenerated link references"
|
||||
@@ -66,8 +66,9 @@ The potential solution:
|
||||
- For edit-time
|
||||
- Make edit-time link reference definition generation optional via user settings. They should be on by default, and generating valid markdown links with a relative path to a `.md` file.
|
||||
- Make format of the link reference definition configurable (whether to include '.md' or not)
|
||||
- Out of recommended extensions, currently only "markdown links" doesn't support them (?). However even its [code](https://github.com/tchayen/markdown-links/blob/master/src/parsing.ts#L25) seems to include wikilink parser, so it might just be a bug?
|
||||
- Out of recommended extensions, currently only "markdown links" doesn't support them (?). However even its [code](https://github.com/tchayen/markdown-links/blob/main/src/parsing.ts#L25) seems to include wikilink parser, so it might just be a bug?
|
||||
- For build-time
|
||||
|
||||
- To satisfy mutually incompatible constraints between GitHub UI, VSCode UI, and GitHub Pages, we should add a pre-processing/build step for pushing to GitHub Pages.
|
||||
- This would be a GitHub action (or a local script, ran via foam-cli) that outputs publish-friendly markdown format for static site generators and other publishing tools
|
||||
- This build step should be pluggable, so that other transformations could be ran during it
|
||||
@@ -125,6 +126,7 @@ 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.
|
||||
- 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).
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ If you want to pick up work in this category, you should have a plan on how long
|
||||
|
||||
Everything else, categorised into themes. Just because something is on this list doesn't mean it'll get done. If you're interested in working on items in this category, check the [[contribution-guide]] for how to get started.
|
||||
|
||||
If a roadmap item is a stub, **consider** opening a [GitHub issue](https://github.com/foambubble/foam/issues) to start a conversation to avoid situations where the implementation does not fit long term vision and roadmap. _Note that this is optional. The only centralised governance in Foam is to decide what ends up in the official [template](https://github.com/foambubble/foam-template), [documentation](https://github.com/foambubble/foam) and [extension](https://github.com/foambubble/foam/tree/master/packages/foam-vscode). You are free to build whatever you want for yourself, and we'd love if you shared it with us, but you are by no means obligated to do so!_
|
||||
If a roadmap item is a stub, **consider** opening a [GitHub issue](https://github.com/foambubble/foam/issues) to start a conversation to avoid situations where the implementation does not fit long term vision and roadmap. _Note that this is optional. The only centralised governance in Foam is to decide what ends up in the official [template](https://github.com/foambubble/foam-template), [documentation](https://github.com/foambubble/foam) and [extension](https://github.com/foambubble/foam/tree/main/packages/foam-vscode). You are free to build whatever you want for yourself, and we'd love if you shared it with us, but you are by no means obligated to do so!_
|
||||
|
||||
**When creating GitHub issues to discuss roadmap items, link them here.**
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
# Releasing Foam
|
||||
|
||||
1. Get to the latest code
|
||||
- `git checkout master && git fetch && git rebase`
|
||||
- `git checkout main && git fetch && git rebase`
|
||||
2. Sanity checks
|
||||
- `yarn reset`
|
||||
- `yarn test`
|
||||
3. Update change log
|
||||
3. Update change log
|
||||
- `./packages/foam-vscode/CHANGELOG.md`
|
||||
- `git add *`
|
||||
- `git commit -m"Preparation for next release"`
|
||||
4. Update version
|
||||
- `$ yarn version-extension <version>` (where `version` is `patch/minor/major`)
|
||||
- `$ yarn version-extension <version>` (where `version` is `patch/minor/major`)
|
||||
5. Package extension
|
||||
- `$ yarn package-extension`
|
||||
6. Publish extension
|
||||
|
||||
@@ -10,7 +10,6 @@ Uncategorised thoughts, to be added
|
||||
- Investigate other similar extensions:
|
||||
- [Unotes](https://marketplace.visualstudio.com/items?itemName=ryanmcalister.Unotes)
|
||||
- [vscode-memo](https://github.com/svsool/vscode-memo)
|
||||
- [gistpad wiki](https://github.com/jevakallio/gistpad/tree/master/src/repos/wiki)
|
||||
- Open in Foam
|
||||
- When you want to open a Foam published website in your own VS Code, we could have a "Open in Foam" link that opens the link in VS Code via a url binding (if possible), downloads the github repo locally, and opens it as a Foam workspace.
|
||||
- Every Foam could have a different theme even in the editor, so you'll see it like they see it
|
||||
|
||||
@@ -60,17 +60,17 @@ These instructions assume you have a GitHub account, and you have Visual Studio
|
||||
|
||||
<a class="github-button" href="https://github.com/foambubble/foam-template/generate" data-icon="octicon-repo-template" data-size="large" aria-label="Use this template foambubble/foam-template on GitHub">Use this template</a>
|
||||
|
||||
*If you want to keep your thoughts to yourself, remember to set the repository private, or if you don't want to use GitHub to host your workspace at all, choose [**Download as ZIP**](https://github.com/foambubble/foam-template/archive/master.zip) instead of **Use this template**.*
|
||||
_If you want to keep your thoughts to yourself, remember to set the repository private, or if you don't want to use GitHub to host your workspace at all, choose [**Download as ZIP**](https://github.com/foambubble/foam-template/archive/main.zip) instead of **Use this template**._
|
||||
|
||||
2. [Clone the repository locally](https://help.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository) and open it in VS Code.
|
||||
|
||||
*Open the repository as a folder using the `File > Open...` menu item. In VS Code, "open workspace" refers to [multi-root workspaces](https://code.visualstudio.com/docs/editor/multi-root-workspaces).*
|
||||
_Open the repository as a folder using the `File > Open...` menu item. In VS Code, "open workspace" refers to [multi-root workspaces](https://code.visualstudio.com/docs/editor/multi-root-workspaces)._
|
||||
|
||||
3. When prompted to install recommended extensions, click **Install all** (or **Show Recommendations** if you want to review and install them one by one)
|
||||
|
||||
After setting up the repository, open `.vscode/settings.json` and edit, add or remove any settings you'd like for your Foam workspace.
|
||||
|
||||
* *If using a [multi-root workspace](https://code.visualstudio.com/docs/editor/multi-root-workspaces) as noted above, make sure that your **Foam** directory is first in the list. There are some settings that will need to be migrated from `.vscode/settings.json` to your `.code-workspace` file.*
|
||||
- _If using a [multi-root workspace](https://code.visualstudio.com/docs/editor/multi-root-workspaces) as noted above, make sure that your **Foam** directory is first in the list. There are some settings that will need to be migrated from `.vscode/settings.json` to your `.code-workspace` file._
|
||||
|
||||
To learn more about how to use **Foam**, read the [[recipes]].
|
||||
|
||||
@@ -263,6 +263,9 @@ If that sounds like something you're interested in, I'd love to have you along o
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.hegghammer.com"><img src="https://avatars.githubusercontent.com/u/64712218?v=4?s=60" width="60px;" alt="Thomas Hegghammer"/><br /><sub><b>Thomas Hegghammer</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Hegghammer" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PiotrAleksander"><img src="https://avatars.githubusercontent.com/u/6314591?v=4?s=60" width="60px;" alt="Piotr Mrzygłosz"/><br /><sub><b>Piotr Mrzygłosz</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=PiotrAleksander" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://schaver.com/"><img src="https://avatars.githubusercontent.com/u/7584?v=4?s=60" width="60px;" alt="Mark Schaver"/><br /><sub><b>Mark Schaver</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=markschaver" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/n8layman"><img src="https://avatars.githubusercontent.com/u/25353944?v=4?s=60" width="60px;" alt="Nathan Layman"/><br /><sub><b>Nathan Layman</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=n8layman" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/emmanuel-ferdman"><img src="https://avatars.githubusercontent.com/u/35470921?v=4?s=60" width="60px;" alt="Emmanuel Ferdman"/><br /><sub><b>Emmanuel Ferdman</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=emmanuel-ferdman" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
# Link Reference Definitions
|
||||
|
||||
When you use `[[wikilinks]]`, the [foam-vscode](https://github.com/foambubble/foam/tree/master/packages/foam-vscode) extension can automatically generate [Markdown Link Reference Definitions](https://spec.commonmark.org/0.29/#link-reference-definitions) at the bottom of the file. This is not needed to navigate your workspace with foam-vscode, but is useful for files to remain compatible with various Markdown tools (e.g. parsers, static site generators, VS code plugins etc), which don't support `[[wikilinks]]`.
|
||||
When you use `[[wikilinks]]`, the [foam-vscode](https://github.com/foambubble/foam/tree/main/packages/foam-vscode) extension can automatically generate [Markdown Link Reference Definitions](https://spec.commonmark.org/0.29/#link-reference-definitions) at the bottom of the file. This is not needed to navigate your workspace with foam-vscode, but is useful for files to remain 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
|
||||
- [[wikilinks]]
|
||||
- [[github-pages]]
|
||||
```
|
||||
```md
|
||||
- [[wikilinks]]
|
||||
- [[github-pages]]
|
||||
```
|
||||
|
||||
...generates the following link reference definitions to the bottom of the file:
|
||||
|
||||
```md
|
||||
[wikilinks]: wikilinks "Wikilinks"
|
||||
[github-pages]: github-pages "GitHub Pages"
|
||||
```
|
||||
```md
|
||||
[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
|
||||
You can open the [raw markdown](https://foambubble.github.io/foam/user/features/link-reference-definitions.md) to see them at the bottom of this file
|
||||
@@ -53,15 +53,15 @@ There are three options for excluding files from your Foam project:
|
||||
|
||||
1. `files.exclude` (from VSCode) will prevent the folder from showing in the file explorer.
|
||||
|
||||
> "Configure glob patterns for excluding files and folders. For example, the file explorer decides which files and folders to show or hide based on this setting. Refer to the Search: Exclude setting to define search-specific excludes."
|
||||
> "Configure glob patterns for excluding files and folders. For example, the file explorer decides which files and folders to show or hide based on this setting. Refer to the Search: Exclude setting to define search-specific excludes."
|
||||
|
||||
2. `files.watcherExclude` (from VSCode) prevents VSCode from constantly monitoring files for changes.
|
||||
|
||||
> "Configure paths or glob patterns to exclude from file watching. Paths or basic glob patterns that are relative (for example `build/output` or `*.js`) will be resolved to an absolute path using the currently opened workspace. Complex glob patterns must match on absolute paths (i.e. prefix with `**/` or the full path and suffix with `/**` to match files within a path) to match properly (for example `**/build/output/**` or `/Users/name/workspaces/project/build/output/**`). When you experience the file watcher process consuming a lot of CPU, make sure to exclude large folders that are of less interest (such as build output folders)."
|
||||
> "Configure paths or glob patterns to exclude from file watching. Paths or basic glob patterns that are relative (for example `build/output` or `*.js`) will be resolved to an absolute path using the currently opened workspace. Complex glob patterns must match on absolute paths (i.e. prefix with `**/` or the full path and suffix with `/**` to match files within a path) to match properly (for example `**/build/output/**` or `/Users/name/workspaces/project/build/output/**`). When you experience the file watcher process consuming a lot of CPU, make sure to exclude large folders that are of less interest (such as build output folders)."
|
||||
|
||||
3. `foam.files.ignore` (from Foam) ignores files from being added to the Foam graph.
|
||||
|
||||
> "Specifies the list of globs that will be ignored by Foam (e.g. they will not be considered when creating the graph). To ignore the all the content of a given folder, use `<folderName>/**/*`" (requires reloading VSCode to take effect).
|
||||
> "Specifies the list of globs that will be ignored by Foam (e.g. they will not be considered when creating the graph). To ignore the all the content of a given folder, use `<folderName>/**/*`" (requires reloading VSCode to take effect).
|
||||
|
||||
For instance, if you're using a local instance of [Jekyll](https://jekyllrb.com/), you may find that it writes copies of each `.md` file into a `_site` directory, which may lead to Foam generating references to them instead of the original source notes.
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ Some properties have special meaning for Foam:
|
||||
| `title` | will assign the name to the note that you will see in the graph, regardless of the filename or the first heading (also see how to [[write-notes-in-foam]]) |
|
||||
| `type` | can be used to style notes differently in the graph (also see [[graph-visualization]]). The default type for a document is `note` unless otherwise specified with this property. |
|
||||
| `tags` | can be used to add tags to a note (see [[tags]]) |
|
||||
| `alias` | can be used to add aliases to the note. an alias will show up in the link autocompletion |
|
||||
|
||||
For example:
|
||||
|
||||
@@ -40,7 +41,7 @@ For example:
|
||||
title: "Note Title"
|
||||
type: "daily-note"
|
||||
tags: daily, funny, planning
|
||||
|
||||
alias: alias1, alias2
|
||||
---
|
||||
```
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ Note templates are `.md` files located in the special `.foam/templates` director
|
||||
|
||||
Create a template:
|
||||
|
||||
* Run the `Foam: Create New Template` command from the command palette
|
||||
* OR manually create a regular `.md` file in the `.foam/templates` directory
|
||||
- Run the `Foam: Create New Template` command from the command palette
|
||||
- OR manually create a regular `.md` file in the `.foam/templates` directory
|
||||
|
||||

|
||||
|
||||
@@ -17,8 +17,8 @@ _Theme: Ayu Light_
|
||||
|
||||
To create a note from a template:
|
||||
|
||||
* Run the `Foam: Create New Note From Template` command and follow the instructions. Don't worry if you've not created a template yet! You'll be prompted to create a new template if none exist.
|
||||
* OR run the `Foam: Create New Note` command, which uses the special default template (`.foam/templates/new-note.md`, if it exists)
|
||||
- Run the `Foam: Create New Note From Template` command and follow the instructions. Don't worry if you've not created a template yet! You'll be prompted to create a new template if none exist.
|
||||
- OR run the `Foam: Create New Note` command, which uses the special default template (`.foam/templates/new-note.md`, if it exists)
|
||||
|
||||

|
||||
|
||||
@@ -29,7 +29,7 @@ _Theme: Ayu Light_
|
||||
### 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. To begin it is *recommended* to define the YAML Front-Matter of the template similar to the following:
|
||||
Customize this template to contain content that you want included every time you create a note. To begin it is _recommended_ to define the YAML Front-Matter of the template similar to the following:
|
||||
|
||||
```markdown
|
||||
---
|
||||
@@ -40,7 +40,7 @@ type: basic-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. To begin it is *recommended* to define the YAML Front-Matter of the template similar to the following:
|
||||
Customize this template to contain content that you want included every time you create a daily note. To begin it is _recommended_ to define the YAML Front-Matter of the template similar to the following:
|
||||
|
||||
```markdown
|
||||
---
|
||||
@@ -54,12 +54,12 @@ Templates can use all the variables available in [VS Code Snippets](https://code
|
||||
|
||||
In addition, you can also use variables provided by Foam:
|
||||
|
||||
| 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 |
|
||||
| `FOAM_TITLE` | The title of the note. If used, Foam will prompt you to enter a title for the note. |
|
||||
| `FOAM_TITLE_SAFE` | The title of the note in a file system safe format. If used, Foam will prompt you to enter a title for the note unless `FOAM_TITLE` has already caused the prompt. |
|
||||
| `FOAM_SLUG` | The sluggified title of the note (using the default github slug method). If used, Foam will prompt you to enter a title for the note unless `FOAM_TITLE` has already caused the prompt. |
|
||||
| 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 |
|
||||
| `FOAM_TITLE` | The title of the note. If used, Foam will prompt you to enter a title for the note. |
|
||||
| `FOAM_TITLE_SAFE` | The title of the note in a file system safe format. If used, Foam will prompt you to enter a title for the note unless `FOAM_TITLE` has already caused the prompt. |
|
||||
| `FOAM_SLUG` | The sluggified title of the note (using the default github slug method). If used, Foam will prompt you to enter a title for the note unless `FOAM_TITLE` has already caused the prompt. |
|
||||
| `FOAM_DATE_*` | `FOAM_DATE_YEAR`, `FOAM_DATE_MONTH`, `FOAM_DATE_WEEK` etc. Foam-specific versions of [VS Code's datetime snippet variables](https://code.visualstudio.com/docs/editor/userdefinedsnippets#_variables). Prefer these versions over VS Code's. |
|
||||
|
||||
### `FOAM_DATE_*` variables
|
||||
@@ -70,7 +70,7 @@ For example, `FOAM_DATE_YEAR` has the same behaviour as VS Code's `CURRENT_YEAR`
|
||||
|
||||
By default, prefer using the `FOAM_DATE_` versions. The datetime used to compute the values will be the same for both `FOAM_DATE_` and VS Code's variables, with the exception of the creation notes using the daily note template.
|
||||
|
||||
For more nitty-gritty details about the supported date formats, [see here](https://github.com/foambubble/foam/blob/master/packages/foam-vscode/src/services/variable-resolver.ts).
|
||||
For more nitty-gritty details about the supported date formats, [see here](https://github.com/foambubble/foam/blob/main/packages/foam-vscode/src/services/variable-resolver.ts).
|
||||
|
||||
#### Relative daily notes
|
||||
|
||||
@@ -84,8 +84,8 @@ For example, given this daily note template (`.foam/templates/daily-note.md`):
|
||||
|
||||
## Here's what I'm going to do today
|
||||
|
||||
* Thing 1
|
||||
* Thing 2
|
||||
- Thing 1
|
||||
- Thing 2
|
||||
```
|
||||
|
||||
When the `/tomorrow` snippet is used, `FOAM_DATE_` variables will be populated with tomorrow's date, as expected.
|
||||
@@ -97,11 +97,11 @@ When creating notes in any other scenario, the `FOAM_DATE_` values are computed
|
||||
|
||||
Templates can also contain metadata about the templates themselves. The metadata is defined in YAML "Frontmatter" blocks within the templates.
|
||||
|
||||
| Name | Description |
|
||||
| ------------- | ---------------------- |
|
||||
| Name | Description |
|
||||
| ------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `filepath` | The filepath to use when creating the new note. If the filepath is a relative filepath, it is relative to the current workspace. |
|
||||
| `name` | A human readable name to show in the template picker. |
|
||||
| `description` | A human readable description to show in the template picker. |
|
||||
| `name` | A human readable name to show in the template picker. |
|
||||
| `description` | A human readable description to show in the template picker. |
|
||||
|
||||
Foam-specific variables (e.g. `$FOAM_TITLE`) can be used within template metadata. However, VS Code snippet variables are ([currently](https://github.com/foambubble/foam/pull/655)) not supported.
|
||||
|
||||
@@ -146,9 +146,10 @@ It is possible to vary the `filepath` value based on the current date using the
|
||||
---
|
||||
type: daily-note
|
||||
foam_template:
|
||||
description: Daily Note for $FOAM_TITLE
|
||||
filepath: "C:\\Users\\foam_user\\foam_notes\\journal\\$FOAM_DATE_YEAR\\$FOAM_DATE_MONTH-$FOAM_DATE_MONTH_NAME_SHORT\\$FOAM_DATE_YEAR-$FOAM_DATE_MONTH-$FOAM_DATE_DATE-daily-note.md"
|
||||
description: Daily Note for $FOAM_TITLE
|
||||
filepath: "C:\\Users\\foam_user\\foam_notes\\journal\\$FOAM_DATE_YEAR\\$FOAM_DATE_MONTH-$FOAM_DATE_MONTH_NAME_SHORT\\$FOAM_DATE_YEAR-$FOAM_DATE_MONTH-$FOAM_DATE_DATE-daily-note.md"
|
||||
---
|
||||
|
||||
# $FOAM_DATE_YEAR-$FOAM_DATE_MONTH-$FOAM_DATE_DATE Daily Notes
|
||||
```
|
||||
|
||||
@@ -166,7 +167,7 @@ If your template already has a YAML Frontmatter block, you can add the Foam temp
|
||||
|
||||
#### Limitations
|
||||
|
||||
Foam only supports adding the template metadata to *YAML* Frontmatter blocks. If the existing Frontmatter block uses some other format (e.g. JSON), you will have to add the template metadata to its own YAML Frontmatter block.
|
||||
Foam only supports adding the template metadata to _YAML_ Frontmatter blocks. If the existing Frontmatter block uses some other format (e.g. JSON), you will have to add the template metadata to its own YAML Frontmatter block.
|
||||
|
||||
Further, the template metadata must be provided as a [YAML block mapping](https://yaml.org/spec/1.2/spec.html#id2798057), with the attributes placed on the lines immediately following the `foam_template` line:
|
||||
|
||||
@@ -210,7 +211,7 @@ foam_template:
|
||||
---
|
||||
|
||||
---
|
||||
existing_frontmatter: "Existing Frontmatter block"
|
||||
existing_frontmatter: 'Existing Frontmatter block'
|
||||
---
|
||||
This is the rest of the template
|
||||
```
|
||||
|
||||
@@ -36,31 +36,31 @@ _Note that first entry in `.order` file defines wiki's home page._
|
||||
|
||||
While you are pushing changes to GitHub, you won't see the wiki updated if you don't add Azure as a remote. You can push to multiple repositories simultaneously.
|
||||
|
||||
1. First open a terminal and check if Azure is added running: `git remote show origin`. If you don't see Azure add it in the output then follow these steps.
|
||||
2. Rename your current remote (most likely named origin) to a different name by running: `git remote rename origin main`
|
||||
3. You can then add the remote for your second remote repository, in this case, Azure. e.g `git remote add azure https://<YOUR_ID>@dev.azure.com/<YOUR_ID>/foam-notes/_git/foam-notes`. You can get it from: Repos->Files->Clone and copy the URL.
|
||||
4. Now, you need to set up your origin remote to push to both of these. So run: `git config -e` and edit it.
|
||||
5. Add the `remote origin` section to the bottom of the file with the URLs from each remote repository you'd like to push to. You'll see something like that:
|
||||
1. First open a terminal and check if Azure is added running: `git remote show origin`. If you don't see Azure add it in the output then follow these steps.
|
||||
2. Rename your current remote (most likely named origin) to a different name by running: `git remote rename origin main`
|
||||
3. You can then add the remote for your second remote repository, in this case, Azure. e.g `git remote add azure https://<YOUR_ID>@dev.azure.com/<YOUR_ID>/foam-notes/_git/foam-notes`. You can get it from: Repos->Files->Clone and copy the URL.
|
||||
4. Now, you need to set up your origin remote to push to both of these. So run: `git config -e` and edit it.
|
||||
5. Add the `remote origin` section to the bottom of the file with the URLs from each remote repository you'd like to push to. You'll see something like that:
|
||||
|
||||
```bash
|
||||
[core]
|
||||
```bash
|
||||
[core]
|
||||
...
|
||||
(ignore this part)
|
||||
...
|
||||
(ignore this part)
|
||||
...
|
||||
[branch "master"]
|
||||
remote = github
|
||||
merge = refs/heads/master
|
||||
[branch "main"]
|
||||
remote = github
|
||||
merge = refs/heads/main
|
||||
[remote "github"]
|
||||
url = git@github.com:username/repo.git
|
||||
fetch = +refs/heads/*:refs/remotes/github/*
|
||||
url = git@github.com:username/repo.git
|
||||
fetch = +refs/heads/*:refs/remotes/github/*
|
||||
[remote "azure"]
|
||||
url = https://<YOUR_ID>@dev.azure.com/<YOUR_ID>/foam-notes/_git/foam-notes
|
||||
fetch = +refs/heads/*:refs/remotes/azure/*
|
||||
url = https://<YOUR_ID>@dev.azure.com/<YOUR_ID>/foam-notes/_git/foam-notes
|
||||
fetch = +refs/heads/*:refs/remotes/azure/*
|
||||
[remote "origin"]
|
||||
url = git@github.com:username/repo.git
|
||||
url = https://<YOUR_ID>@dev.azure.com/<YOUR_ID>/foam-notes/_git/foam-notes
|
||||
```
|
||||
url = git@github.com:username/repo.git
|
||||
url = https://<YOUR_ID>@dev.azure.com/<YOUR_ID>/foam-notes/_git/foam-notes
|
||||
```
|
||||
|
||||
6. You can then push to both repositories by: `git push origin master` or a single one using: `git push github master` or `git push azure master`
|
||||
6. You can then push to both repositories by: `git push origin main` or a single one using: `git push github main` or `git push azure main`
|
||||
|
||||
For more information, read the [Azure DevOps documentation](https://docs.microsoft.com/en-us/azure/devops/project/wiki/publish-repo-to-wiki).
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
],
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.26.1"
|
||||
"version": "0.26.8"
|
||||
}
|
||||
|
||||
31802
package-lock.json
generated
31802
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,55 @@ 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.26.8] - 2025-03-14
|
||||
|
||||
Fixese and Improvements:
|
||||
|
||||
- Tag hierarchy now visible in graph (#1436)
|
||||
- Improved Notes Explorer layout
|
||||
|
||||
## [0.26.7] - 2025-03-09
|
||||
|
||||
Fixese and Improvements:
|
||||
|
||||
- Improved parsing of tags (fixes #1434)
|
||||
|
||||
## [0.26.6] - 2025-03-08
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Improved graph based navigation when running in virtual workspace
|
||||
- Improved wikilink embeds and fixed cycle detection issue (#1430)
|
||||
- Added links in tags to navigate to corresponding tag explorer item (#1432)
|
||||
|
||||
Internal:
|
||||
|
||||
- Renamed branch from `master` to `main`
|
||||
|
||||
## [0.26.5] - 2025-02-21
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Improved handling of virtual FS URIs (#1426)
|
||||
|
||||
## [0.26.4] - 2024-11-12
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Improved handling of virtual FS URIs (#1409)
|
||||
|
||||
## [0.26.3] - 2024-11-12
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Finetuned use of triemap (#1411 - thanks @pderaaij)
|
||||
|
||||
## [0.26.2] - 2024-11-06
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Performance improvements (#1406 - thanks @pderaaij)
|
||||
|
||||
## [0.26.1] - 2024-10-09
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"type": "git"
|
||||
},
|
||||
"homepage": "https://github.com/foambubble/foam",
|
||||
"version": "0.26.1",
|
||||
"version": "0.26.8",
|
||||
"license": "MIT",
|
||||
"publisher": "foam",
|
||||
"engines": {
|
||||
@@ -301,19 +301,19 @@
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.update-graph",
|
||||
"title": "Foam: Update graph"
|
||||
"title": "Foam: Update Graph"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.set-log-level",
|
||||
"title": "Foam: Set log level"
|
||||
"title": "Foam: Set Log Level"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.show-graph",
|
||||
"title": "Foam: Show graph"
|
||||
"title": "Foam: Show Graph"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.update-wikilink-definitions",
|
||||
"title": "Foam: Update wikilink definitions"
|
||||
"title": "Foam: Update Wikilink Definitions"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.open-daily-note",
|
||||
@@ -349,11 +349,11 @@
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.convert-link-style-inplace",
|
||||
"title": "Foam: convert link style in place"
|
||||
"title": "Foam: Convert Link Style in Place"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.convert-link-style-incopy",
|
||||
"title": "Foam: convert link format in copy"
|
||||
"title": "Foam: Convert Link Format in Copy"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.orphans.group-by:folder",
|
||||
@@ -405,6 +405,11 @@
|
||||
"title": "Expand all",
|
||||
"icon": "$(expand-all)"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.tags-explorer.focus",
|
||||
"title": "Focus on tag",
|
||||
"icon": "$(symbol-number)"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.placeholders.show:for-current-file",
|
||||
"title": "Show placeholders in current file",
|
||||
@@ -457,6 +462,13 @@
|
||||
"configuration": {
|
||||
"title": "Foam",
|
||||
"properties": {
|
||||
"foam.supportedLanguages": {
|
||||
"type": "array",
|
||||
"default": [
|
||||
"markdown"
|
||||
],
|
||||
"description": "List of languages to treat as Markdown-like documents."
|
||||
},
|
||||
"foam.completion.label": {
|
||||
"type": "string",
|
||||
"default": "path",
|
||||
@@ -670,7 +682,7 @@
|
||||
"test:e2e": "yarn test-setup && node ./out/test/run-tests.js --e2e",
|
||||
"lint": "dts lint src",
|
||||
"clean": "rimraf out",
|
||||
"watch": "tsc --build ./tsconfig.json --watch",
|
||||
"watch": "nodemon --watch 'src/**/*.ts' --exec 'yarn build' --ext ts",
|
||||
"vscode:start-debugging": "yarn clean && yarn watch",
|
||||
"package-extension": "npx vsce package --yarn",
|
||||
"install-extension": "code --install-extension ./foam-vscode-$npm_package_version.vsix",
|
||||
@@ -704,6 +716,7 @@
|
||||
"jest-extended": "^3.2.3",
|
||||
"markdown-it": "^12.0.4",
|
||||
"micromatch": "^4.0.2",
|
||||
"nodemon": "^3.1.7",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-jest": "^29.1.1",
|
||||
"tslib": "^2.0.0",
|
||||
@@ -720,6 +733,7 @@
|
||||
"lodash": "^4.17.21",
|
||||
"lru-cache": "^7.14.1",
|
||||
"markdown-it-regex": "^0.2.0",
|
||||
"mnemonist": "^0.39.8",
|
||||
"path-browserify": "^1.0.1",
|
||||
"remark-frontmatter": "^2.0.0",
|
||||
"remark-parse": "^8.0.2",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// taken from https://github.com/microsoft/vscode/tree/master/src/vs/base/common
|
||||
// taken from https://github.com/microsoft/vscode/tree/main/src/vs/base/common
|
||||
|
||||
import { Emitter, Event } from './event';
|
||||
import { IDisposable } from './lifecycle';
|
||||
@@ -29,7 +29,7 @@ export interface CancellationToken {
|
||||
) => IDisposable;
|
||||
}
|
||||
|
||||
const shortcutEvent: Event<any> = Object.freeze(function(
|
||||
const shortcutEvent: Event<any> = Object.freeze(function (
|
||||
callback,
|
||||
context?
|
||||
): IDisposable {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// taken from https://github.com/microsoft/vscode/tree/master/src/vs/base/common
|
||||
// taken from https://github.com/microsoft/vscode/tree/main/src/vs/base/common
|
||||
|
||||
// Names from https://blog.codinghorror.com/ascii-pronunciation-rules-for-programmers/
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// taken from https://github.com/microsoft/vscode/tree/master/src/vs/base/common
|
||||
// taken from https://github.com/microsoft/vscode/tree/main/src/vs/base/common
|
||||
|
||||
export interface ErrorListenerCallback {
|
||||
(error: any): void;
|
||||
@@ -21,7 +21,7 @@ export class ErrorHandler {
|
||||
constructor() {
|
||||
this.listeners = [];
|
||||
|
||||
this.unexpectedErrorHandler = function(e: any) {
|
||||
this.unexpectedErrorHandler = function (e: any) {
|
||||
setTimeout(() => {
|
||||
if (e.stack) {
|
||||
throw new Error(e.message + '\n\n' + e.stack);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// taken from https://github.com/microsoft/vscode/tree/master/src/vs/base/common
|
||||
// taken from https://github.com/microsoft/vscode/tree/main/src/vs/base/common
|
||||
|
||||
import { onUnexpectedError } from './errors';
|
||||
import { once as onceFn } from './functional';
|
||||
@@ -115,7 +115,7 @@ export namespace Event {
|
||||
* Given an event, returns the same event but typed as `Event<void>`.
|
||||
*/
|
||||
export function signal<T>(event: Event<T>): Event<void> {
|
||||
return (event as Event<any>) as Event<void>;
|
||||
return event as Event<any> as Event<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -525,9 +525,7 @@ class LeakageMonitor {
|
||||
|
||||
constructor(
|
||||
readonly customThreshold?: number,
|
||||
readonly name: string = Math.random()
|
||||
.toString(18)
|
||||
.slice(2, 5)
|
||||
readonly name: string = Math.random().toString(18).slice(2, 5)
|
||||
) {}
|
||||
|
||||
dispose(): void {
|
||||
@@ -549,10 +547,7 @@ class LeakageMonitor {
|
||||
if (!this._stacks) {
|
||||
this._stacks = new Map();
|
||||
}
|
||||
const stack = new Error()
|
||||
.stack!.split('\n')
|
||||
.slice(3)
|
||||
.join('\n');
|
||||
const stack = new Error().stack!.split('\n').slice(3).join('\n');
|
||||
const count = this._stacks.get(stack) || 0;
|
||||
this._stacks.set(stack, count + 1);
|
||||
this._warnCountdown -= 1;
|
||||
@@ -607,7 +602,7 @@ class LeakageMonitor {
|
||||
}
|
||||
*/
|
||||
export class Emitter<T> {
|
||||
private static readonly _noop = function() {};
|
||||
private static readonly _noop = function () {};
|
||||
|
||||
private readonly _options?: EmitterOptions;
|
||||
private readonly _leakageMon?: LeakageMonitor;
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// taken from https://github.com/microsoft/vscode/tree/master/src/vs/base/common
|
||||
// taken from https://github.com/microsoft/vscode/tree/main/src/vs/base/common
|
||||
|
||||
export function once<T extends Function>(this: unknown, fn: T): T {
|
||||
const _this = this;
|
||||
let didCall = false;
|
||||
let result: unknown;
|
||||
|
||||
return (function() {
|
||||
return function () {
|
||||
if (didCall) {
|
||||
return result;
|
||||
}
|
||||
@@ -19,5 +19,5 @@ export function once<T extends Function>(this: unknown, fn: T): T {
|
||||
result = fn.apply(_this, arguments);
|
||||
|
||||
return result;
|
||||
} as unknown) as T;
|
||||
} as unknown as T;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// taken from https://github.com/microsoft/vscode/tree/master/src/vs/base/common
|
||||
// taken from https://github.com/microsoft/vscode/tree/main/src/vs/base/common
|
||||
|
||||
export namespace Iterable {
|
||||
export function is<T = any>(thing: any): thing is IterableIterator<T> {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// taken from https://github.com/microsoft/vscode/tree/master/src/vs/base/common
|
||||
// taken from https://github.com/microsoft/vscode/tree/main/src/vs/base/common
|
||||
|
||||
import { once } from './functional';
|
||||
import { Iterable } from './iterator';
|
||||
@@ -164,7 +164,7 @@ export class DisposableStore implements IDisposable {
|
||||
if (!t) {
|
||||
return t;
|
||||
}
|
||||
if (((t as unknown) as DisposableStore) === this) {
|
||||
if ((t as unknown as DisposableStore) === this) {
|
||||
throw new Error('Cannot register a disposable on itself!');
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ export abstract class Disposable implements IDisposable {
|
||||
}
|
||||
|
||||
protected _register<T extends IDisposable>(t: T): T {
|
||||
if (((t as unknown) as Disposable) === this) {
|
||||
if ((t as unknown as Disposable) === this) {
|
||||
throw new Error('Cannot register a disposable on itself!');
|
||||
}
|
||||
return this._store.add(t);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// taken from https://github.com/microsoft/vscode/tree/master/src/vs/base/common
|
||||
// taken from https://github.com/microsoft/vscode/tree/main/src/vs/base/common
|
||||
|
||||
class Node<E> {
|
||||
static readonly Undefined = new Node<any>(undefined);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// taken from https://github.com/microsoft/vscode/tree/master/src/vs/base/common
|
||||
// taken from https://github.com/microsoft/vscode/tree/main/src/vs/base/common
|
||||
|
||||
const LANGUAGE_DEFAULT = 'en';
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ describe('Foam URI', () => {
|
||||
const base = URI.file('/path/to/file.md');
|
||||
test.each([
|
||||
['https://www.google.com', URI.parse('https://www.google.com')],
|
||||
['/path/to/a/file.md', URI.file('/path/to/a/file.md')],
|
||||
['../relative/file.md', URI.file('/path/relative/file.md')],
|
||||
['#section', base.withFragment('section')],
|
||||
['/path/to/a/file.md', URI.parse('file:///path/to/a/file.md')],
|
||||
['../relative/file.md', URI.parse('file:///path/relative/file.md')],
|
||||
['#section', base.with({ fragment: 'section' })],
|
||||
[
|
||||
'../relative/file.md#section',
|
||||
URI.parse('file:/path/relative/file.md#section'),
|
||||
|
||||
@@ -58,6 +58,11 @@ export class URI {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Will not work with web extension. Use only for testing.
|
||||
* @param value the path to turn into a URI
|
||||
* @returns the file URI
|
||||
*/
|
||||
static file(value: string): URI {
|
||||
const [path, authority] = pathUtils.fromFsPath(value);
|
||||
return new URI({ scheme: 'file', authority, path });
|
||||
@@ -71,7 +76,7 @@ export class URI {
|
||||
const uri = value instanceof URI ? value : URI.parse(value);
|
||||
if (!uri.isAbsolute()) {
|
||||
if (uri.scheme === 'file' || uri.scheme === 'placeholder') {
|
||||
let newUri = this.withFragment(uri.fragment);
|
||||
let newUri = this.with({ fragment: uri.fragment });
|
||||
if (uri.path) {
|
||||
newUri = (isDirectory ? newUri : newUri.getDirectory())
|
||||
.joinPath(uri.path)
|
||||
@@ -119,8 +124,20 @@ export class URI {
|
||||
return new URI({ ...this, path });
|
||||
}
|
||||
|
||||
withFragment(fragment: string): URI {
|
||||
return new URI({ ...this, fragment });
|
||||
with(change: {
|
||||
scheme?: string;
|
||||
authority?: string;
|
||||
path?: string;
|
||||
query?: string;
|
||||
fragment?: string;
|
||||
}): URI {
|
||||
return new URI({
|
||||
scheme: change.scheme ?? this.scheme,
|
||||
authority: change.authority ?? this.authority,
|
||||
path: change.path ?? this.path,
|
||||
query: change.query ?? this.query,
|
||||
fragment: change.fragment ?? this.fragment,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -380,11 +397,30 @@ function encodeURIComponentMinimal(path: string): string {
|
||||
*
|
||||
* TODO this probably needs to be moved to the workspace service
|
||||
*/
|
||||
export function asAbsoluteUri(uri: URI, baseFolders: URI[]): URI {
|
||||
return URI.file(
|
||||
pathUtils.asAbsolutePaths(
|
||||
uri.path,
|
||||
baseFolders.map(f => f.path)
|
||||
)[0]
|
||||
);
|
||||
export function asAbsoluteUri(
|
||||
uriOrPath: URI | string,
|
||||
baseFolders: URI[]
|
||||
): URI {
|
||||
if (baseFolders.length === 0) {
|
||||
throw new Error('At least one base folder needed to compute URI');
|
||||
}
|
||||
const path = uriOrPath instanceof URI ? uriOrPath.path : uriOrPath;
|
||||
if (path.startsWith('/')) {
|
||||
return uriOrPath instanceof URI ? uriOrPath : baseFolders[0].with({ path });
|
||||
}
|
||||
let tokens = path.split('/');
|
||||
while (tokens[0].trim() === '') {
|
||||
tokens.shift();
|
||||
}
|
||||
const firstDir = tokens[0];
|
||||
if (baseFolders.length > 1) {
|
||||
for (const folder of baseFolders) {
|
||||
const lastDir = folder.path.split('/').pop();
|
||||
if (lastDir === firstDir) {
|
||||
tokens = tokens.slice(1);
|
||||
return folder.joinPath(...tokens);
|
||||
}
|
||||
}
|
||||
}
|
||||
return baseFolders[0].joinPath(...tokens);
|
||||
}
|
||||
|
||||
@@ -126,9 +126,9 @@ describe('Identifier computation', () => {
|
||||
});
|
||||
const ws = new FoamWorkspace('.md').set(first).set(second).set(third);
|
||||
|
||||
expect(ws.getIdentifier(first.uri.withFragment('section name'))).toEqual(
|
||||
'to/page-a#section name'
|
||||
);
|
||||
expect(
|
||||
ws.getIdentifier(first.uri.with({ fragment: 'section name' }))
|
||||
).toEqual('to/page-a#section name');
|
||||
});
|
||||
|
||||
const needle = '/project/car/todo';
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Emitter } from '../common/event';
|
||||
import { ResourceProvider } from './provider';
|
||||
import { IDisposable } from '../common/lifecycle';
|
||||
import { IDataStore } from '../services/datastore';
|
||||
import TrieMap from 'mnemonist/trie-map';
|
||||
|
||||
export class FoamWorkspace implements IDisposable {
|
||||
private onDidAddEmitter = new Emitter<Resource>();
|
||||
@@ -20,7 +21,7 @@ export class FoamWorkspace implements IDisposable {
|
||||
/**
|
||||
* Resources by path
|
||||
*/
|
||||
private _resources: Map<string, Resource> = new Map();
|
||||
private _resources: TrieMap<string, Resource> = new TrieMap();
|
||||
|
||||
/**
|
||||
* @param defaultExtension: The default extension for notes in this workspace (e.g. `.md`)
|
||||
@@ -33,7 +34,10 @@ export class FoamWorkspace implements IDisposable {
|
||||
|
||||
set(resource: Resource) {
|
||||
const old = this.find(resource.uri);
|
||||
this._resources.set(normalize(resource.uri.path), resource);
|
||||
|
||||
// store resource
|
||||
this._resources.set(this.getTrieIdentifier(resource.uri.path), resource);
|
||||
|
||||
isSome(old)
|
||||
? this.onDidUpdateEmitter.fire({ old: old, new: resource })
|
||||
: this.onDidAddEmitter.fire(resource);
|
||||
@@ -41,8 +45,8 @@ export class FoamWorkspace implements IDisposable {
|
||||
}
|
||||
|
||||
delete(uri: URI) {
|
||||
const deleted = this._resources.get(normalize(uri.path));
|
||||
this._resources.delete(normalize(uri.path));
|
||||
const deleted = this._resources.get(this.getTrieIdentifier(uri));
|
||||
this._resources.delete(this.getTrieIdentifier(uri));
|
||||
|
||||
isSome(deleted) && this.onDidDeleteEmitter.fire(deleted);
|
||||
return deleted ?? null;
|
||||
@@ -57,7 +61,11 @@ export class FoamWorkspace implements IDisposable {
|
||||
}
|
||||
|
||||
public resources(): IterableIterator<Resource> {
|
||||
return this._resources.values();
|
||||
const resources: Array<Resource> = Array.from(
|
||||
this._resources.values()
|
||||
).sort(Resource.sortByPath);
|
||||
|
||||
return resources.values();
|
||||
}
|
||||
|
||||
public get(uri: URI): Resource {
|
||||
@@ -70,17 +78,21 @@ export class FoamWorkspace implements IDisposable {
|
||||
}
|
||||
|
||||
public listByIdentifier(identifier: string): Resource[] {
|
||||
const needle = normalize('/' + identifier);
|
||||
let needle = this.getTrieIdentifier(identifier);
|
||||
|
||||
const mdNeedle =
|
||||
getExtension(needle) !== this.defaultExtension
|
||||
? needle + this.defaultExtension
|
||||
getExtension(normalize(identifier)) !== this.defaultExtension
|
||||
? this.getTrieIdentifier(identifier + this.defaultExtension)
|
||||
: undefined;
|
||||
|
||||
const resources: Resource[] = [];
|
||||
for (const key of this._resources.keys()) {
|
||||
if (key.endsWith(mdNeedle) || key.endsWith(needle)) {
|
||||
resources.push(this._resources.get(normalize(key)));
|
||||
}
|
||||
|
||||
this._resources.find(needle).forEach(elm => resources.push(elm[1]));
|
||||
|
||||
if (mdNeedle) {
|
||||
this._resources.find(mdNeedle).forEach(elm => resources.push(elm[1]));
|
||||
}
|
||||
|
||||
return resources.sort(Resource.sortByPath);
|
||||
}
|
||||
|
||||
@@ -92,21 +104,19 @@ export class FoamWorkspace implements IDisposable {
|
||||
public getIdentifier(forResource: URI, exclude?: URI[]): string {
|
||||
const amongst = [];
|
||||
const basename = forResource.getBasename();
|
||||
for (const res of this._resources.values()) {
|
||||
// skip elements that cannot possibly match
|
||||
if (!res.uri.path.endsWith(basename)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.listByIdentifier(basename).map(res => {
|
||||
// skip self
|
||||
if (res.uri.isEqual(forResource)) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
// skip exclude list
|
||||
if (exclude && exclude.find(ex => ex.isEqual(res.uri))) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
amongst.push(res.uri);
|
||||
}
|
||||
});
|
||||
|
||||
let identifier = FoamWorkspace.getShortestIdentifier(
|
||||
forResource.path,
|
||||
@@ -119,9 +129,32 @@ export class FoamWorkspace implements IDisposable {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a note identifier in reversed order. Used to optimise the storage of notes in
|
||||
* the workspace to optimise retrieval of notes.
|
||||
*
|
||||
* @param reference the URI path to reverse
|
||||
*/
|
||||
private getTrieIdentifier(reference: URI | string): string {
|
||||
let path: string;
|
||||
if (reference instanceof URI) {
|
||||
path = (reference as URI).path;
|
||||
} else {
|
||||
path = reference as string;
|
||||
}
|
||||
|
||||
let reversedPath = normalize(path).split('/').reverse().join('/');
|
||||
|
||||
if (reversedPath.indexOf('/') < 0) {
|
||||
reversedPath = reversedPath + '/';
|
||||
}
|
||||
|
||||
return reversedPath;
|
||||
}
|
||||
|
||||
public find(reference: URI | string, baseUri?: URI): Resource | null {
|
||||
if (reference instanceof URI) {
|
||||
return this._resources.get(normalize((reference as URI).path)) ?? null;
|
||||
return this._resources.get(this.getTrieIdentifier(reference)) ?? null;
|
||||
}
|
||||
let resource: Resource | null = null;
|
||||
const [path, fragment] = (reference as string).split('#');
|
||||
@@ -135,14 +168,17 @@ export class FoamWorkspace implements IDisposable {
|
||||
: isSome(baseUri)
|
||||
? baseUri.resolve(candidate).path
|
||||
: null;
|
||||
resource = this._resources.get(normalize(searchKey));
|
||||
resource = this._resources.get(this.getTrieIdentifier(searchKey));
|
||||
if (resource) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resource && fragment) {
|
||||
resource = { ...resource, uri: resource.uri.withFragment(fragment) };
|
||||
resource = {
|
||||
...resource,
|
||||
uri: resource.uri.with({ fragment: fragment }),
|
||||
};
|
||||
}
|
||||
return resource ?? null;
|
||||
}
|
||||
|
||||
@@ -320,20 +320,55 @@ this is some #text that includes #tags we #care-about.
|
||||
]);
|
||||
});
|
||||
|
||||
it('provides rough range for tags in yaml', () => {
|
||||
it('provides a specific range for tags in yaml', () => {
|
||||
// For now it's enough to just get the YAML block range
|
||||
// in the future we might want to be more specific
|
||||
|
||||
const noteA = createNoteFromMarkdown(`
|
||||
---
|
||||
prop: hello world
|
||||
tags: [hello, world, this_is_good]
|
||||
another: i love the world
|
||||
---
|
||||
# this is a heading
|
||||
this is some text
|
||||
`);
|
||||
expect(noteA.tags[0]).toEqual({
|
||||
label: 'hello',
|
||||
range: Range.create(1, 0, 3, 3),
|
||||
range: Range.create(3, 7, 3, 12),
|
||||
});
|
||||
expect(noteA.tags[1]).toEqual({
|
||||
label: 'world',
|
||||
range: Range.create(3, 14, 3, 19),
|
||||
});
|
||||
expect(noteA.tags[2]).toEqual({
|
||||
label: 'this_is_good',
|
||||
range: Range.create(3, 21, 3, 33),
|
||||
});
|
||||
|
||||
const noteB = createNoteFromMarkdown(`
|
||||
---
|
||||
prop: hello world
|
||||
tags:
|
||||
- hello
|
||||
- world
|
||||
- this_is_good
|
||||
another: i love the world
|
||||
---
|
||||
# this is a heading
|
||||
this is some text
|
||||
`);
|
||||
expect(noteB.tags[0]).toEqual({
|
||||
label: 'hello',
|
||||
range: Range.create(4, 2, 4, 7),
|
||||
});
|
||||
expect(noteB.tags[1]).toEqual({
|
||||
label: 'world',
|
||||
range: Range.create(5, 2, 5, 7),
|
||||
});
|
||||
expect(noteB.tags[2]).toEqual({
|
||||
label: 'this_is_good',
|
||||
range: Range.create(6, 2, 6, 14),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -173,15 +173,51 @@ const getTextFromChildren = (root: Node): string => {
|
||||
return text;
|
||||
};
|
||||
|
||||
function getPropertiesInfoFromYAML(yamlText: string): {
|
||||
[key: string]: { key: string; value: string; text: string; line: number };
|
||||
} {
|
||||
const yamlProps = yamlText
|
||||
.split(/(\w+:)/g)
|
||||
.filter(item => item.trim() !== '');
|
||||
const lines = yamlText.split('\n');
|
||||
let result: { line: number; key: string; text: string; value: string }[] = [];
|
||||
for (let i = 0; i < yamlProps.length / 2; i++) {
|
||||
const key = yamlProps[i * 2].replace(':', '');
|
||||
const value = yamlProps[i * 2 + 1].trim();
|
||||
const text = yamlProps[i * 2] + yamlProps[i * 2 + 1];
|
||||
result.push({ key, value, text, line: -1 });
|
||||
}
|
||||
result = result.map(p => {
|
||||
const line = lines.findIndex(l => l.startsWith(p.key + ':'));
|
||||
return { ...p, line };
|
||||
});
|
||||
return result.reduce((acc, curr) => {
|
||||
acc[curr.key] = curr;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
const tagsPlugin: ParserPlugin = {
|
||||
name: 'tags',
|
||||
onDidFindProperties: (props, note, node) => {
|
||||
if (isSome(props.tags)) {
|
||||
const tagPropertyInfo = getPropertiesInfoFromYAML((node as any).value)[
|
||||
'tags'
|
||||
];
|
||||
const tagPropertyStartLine =
|
||||
node.position!.start.line + tagPropertyInfo.line;
|
||||
const tagPropertyLines = tagPropertyInfo.text.split('\n');
|
||||
const yamlTags = extractTagsFromProp(props.tags);
|
||||
for (const tag of yamlTags) {
|
||||
const tagLine = tagPropertyLines.findIndex(l => l.includes(tag));
|
||||
const line = tagPropertyStartLine + tagLine;
|
||||
const charStart = tagPropertyLines[tagLine].indexOf(tag);
|
||||
note.tags.push({
|
||||
label: tag,
|
||||
range: astPositionToFoamRange(node.position!),
|
||||
range: Range.createFromPosition(
|
||||
Position.create(line, charStart),
|
||||
Position.create(line, charStart + tag.length)
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ describe('Link resolution', () => {
|
||||
const ws = createTestWorkspace().set(noteA).set(noteB);
|
||||
|
||||
expect(ws.resolveLink(noteA, noteA.links[0])).toEqual(
|
||||
noteB.uri.withFragment('section')
|
||||
noteB.uri.with({ fragment: 'section' })
|
||||
);
|
||||
});
|
||||
|
||||
@@ -163,7 +163,7 @@ describe('Link resolution', () => {
|
||||
const ws = createTestWorkspace().set(noteA);
|
||||
|
||||
expect(ws.resolveLink(noteA, noteA.links[0])).toEqual(
|
||||
noteA.uri.withFragment('section')
|
||||
noteA.uri.with({ fragment: 'section' })
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ export class MarkdownResourceProvider implements ResourceProvider {
|
||||
URI.placeholder(target);
|
||||
|
||||
if (section) {
|
||||
targetUri = targetUri.withFragment(section);
|
||||
targetUri = targetUri.with({ fragment: section });
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -93,7 +93,7 @@ export class MarkdownResourceProvider implements ResourceProvider {
|
||||
workspace.find(path, resource.uri)?.uri ??
|
||||
URI.placeholder(resource.uri.resolve(path).path);
|
||||
if (section && !targetUri.isPlaceholder()) {
|
||||
targetUri = targetUri.withFragment(section);
|
||||
targetUri = targetUri.with({ fragment: section });
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,23 +1,85 @@
|
||||
import sha1 from 'js-sha1';
|
||||
|
||||
/**
|
||||
* Checks if a value is not null.
|
||||
*
|
||||
* @param value - The value to check.
|
||||
* @returns True if the value is not null, otherwise false.
|
||||
*/
|
||||
export function isNotNull<T>(value: T | null): value is T {
|
||||
return value != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a value is not null, undefined, or void.
|
||||
*
|
||||
* @param value - The value to check.
|
||||
* @returns True if the value is not null, undefined, or void, otherwise false.
|
||||
*/
|
||||
export function isSome<T>(
|
||||
value: T | null | undefined | void
|
||||
): value is NonNullable<T> {
|
||||
return value != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a value is null, undefined, or void.
|
||||
*
|
||||
* @param value - The value to check.
|
||||
* @returns True if the value is null, undefined, or void, otherwise false.
|
||||
*/
|
||||
export function isNone<T>(
|
||||
value: T | null | undefined | void
|
||||
): value is null | undefined | void {
|
||||
return value == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a string is numeric.
|
||||
*
|
||||
* @param value - The string to check.
|
||||
* @returns True if the string is numeric, otherwise false.
|
||||
*/
|
||||
export function isNumeric(value: string): boolean {
|
||||
return /-?\d+$/.test(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a SHA-1 hash of the given text.
|
||||
*
|
||||
* @param text - The text to hash.
|
||||
* @returns The SHA-1 hash of the text.
|
||||
*/
|
||||
export const hash = (text: string) => sha1.sha1(text);
|
||||
|
||||
/**
|
||||
* Executes an array of functions and returns the first result that satisfies the predicate.
|
||||
*
|
||||
* @param functions - The array of functions to execute.
|
||||
* @param predicate - The predicate to test the results. Defaults to checking if the result is not null.
|
||||
* @returns The first result that satisfies the predicate, or undefined if no result satisfies the predicate.
|
||||
*/
|
||||
export async function firstFrom<T>(
|
||||
functions: Array<() => T | Promise<T>>,
|
||||
predicate: (result: T) => boolean = result => result != null
|
||||
): Promise<T | undefined> {
|
||||
for (const fn of functions) {
|
||||
const result = await fn();
|
||||
if (predicate(result)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazily executes an array of functions and yields their results.
|
||||
*
|
||||
* @param functions - The array of functions to execute.
|
||||
* @returns A generator yielding the results of the functions.
|
||||
*/
|
||||
function* lazyExecutor<T>(functions: Array<() => T>): Generator<T> {
|
||||
for (const fn of functions) {
|
||||
yield fn();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { workspace } from 'vscode';
|
||||
import { createDailyNoteIfNotExists, getDailyNotePath } from './dated-notes';
|
||||
import { createDailyNoteIfNotExists, getDailyNoteUri } from './dated-notes';
|
||||
import { isWindows } from './core/common/platform';
|
||||
import {
|
||||
cleanWorkspace,
|
||||
@@ -10,8 +10,9 @@ import {
|
||||
withModifiedFoamConfiguration,
|
||||
} from './test/test-utils-vscode';
|
||||
import { fromVsCodeUri } from './utils/vsc-utils';
|
||||
import { URI } from './core/model/uri';
|
||||
|
||||
describe('getDailyNotePath', () => {
|
||||
describe('getDailyNoteUri', () => {
|
||||
const date = new Date('2021-02-07T00:00:00Z');
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth() + 1;
|
||||
@@ -21,12 +22,12 @@ describe('getDailyNotePath', () => {
|
||||
test('Adds the root directory to relative directories', async () => {
|
||||
const config = 'journal';
|
||||
|
||||
const expectedPath = fromVsCodeUri(
|
||||
const expectedUri = fromVsCodeUri(
|
||||
workspace.workspaceFolders[0].uri
|
||||
).joinPath(config, `${isoDate}.md`);
|
||||
|
||||
await withModifiedFoamConfiguration('openDailyNote.directory', config, () =>
|
||||
expect(getDailyNotePath(date).toFsPath()).toEqual(expectedPath.toFsPath())
|
||||
expect(getDailyNoteUri(date)).toEqual(expectedUri)
|
||||
);
|
||||
});
|
||||
|
||||
@@ -39,7 +40,7 @@ describe('getDailyNotePath', () => {
|
||||
: `${config}/${isoDate}.md`;
|
||||
|
||||
await withModifiedFoamConfiguration('openDailyNote.directory', config, () =>
|
||||
expect(getDailyNotePath(date).toFsPath()).toMatch(expectedPath)
|
||||
expect(getDailyNoteUri(date).toFsPath()).toMatch(expectedPath)
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -54,7 +55,7 @@ describe('Daily note template', () => {
|
||||
['.foam', 'templates', 'daily-note.md']
|
||||
);
|
||||
|
||||
const uri = getDailyNotePath(targetDate);
|
||||
const uri = getDailyNoteUri(targetDate);
|
||||
|
||||
await createDailyNoteIfNotExists(targetDate);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { joinPath } from './core/utils/path';
|
||||
import dateFormat from 'dateformat';
|
||||
import { URI } from './core/model/uri';
|
||||
import { NoteFactory } from './services/templates';
|
||||
@@ -32,17 +33,13 @@ export async function openDailyNoteFor(date?: Date) {
|
||||
* 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 date A given date to be formatted as filename.
|
||||
* @returns The path to the daily note file.
|
||||
* @returns The URI to the daily note file.
|
||||
*/
|
||||
export function getDailyNotePath(date: Date): URI {
|
||||
export function getDailyNoteUri(date: Date): URI {
|
||||
const folder = getFoamVsCodeConfig<string>('openDailyNote.directory') ?? '.';
|
||||
const dailyNoteDirectory = asAbsoluteWorkspaceUri(URI.file(folder));
|
||||
const dailyNoteFilename = getDailyNoteFileName(date);
|
||||
return dailyNoteDirectory.joinPath(dailyNoteFilename);
|
||||
return asAbsoluteWorkspaceUri(joinPath(folder, dailyNoteFilename));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,20 +73,20 @@ export function getDailyNoteFileName(date: Date): string {
|
||||
* @returns Whether the file was created.
|
||||
*/
|
||||
export async function createDailyNoteIfNotExists(targetDate: Date) {
|
||||
const pathFromLegacyConfiguration = getDailyNotePath(targetDate);
|
||||
const uriFromLegacyConfiguration = getDailyNoteUri(targetDate);
|
||||
const pathFromLegacyConfiguration = uriFromLegacyConfiguration.toFsPath();
|
||||
const titleFormat: string =
|
||||
getFoamVsCodeConfig('openDailyNote.titleFormat') ??
|
||||
getFoamVsCodeConfig('openDailyNote.filenameFormat');
|
||||
|
||||
const templateFallbackText = `---
|
||||
foam_template:
|
||||
filepath: "${pathFromLegacyConfiguration.toFsPath().replace(/\\/g, '\\\\')}"
|
||||
---
|
||||
# ${dateFormat(targetDate, titleFormat, false)}
|
||||
`;
|
||||
const templateFallbackText = `# ${dateFormat(
|
||||
targetDate,
|
||||
titleFormat,
|
||||
false
|
||||
)}\n`;
|
||||
|
||||
return await NoteFactory.createFromDailyNoteTemplate(
|
||||
pathFromLegacyConfiguration,
|
||||
uriFromLegacyConfiguration,
|
||||
templateFallbackText,
|
||||
targetDate
|
||||
);
|
||||
|
||||
@@ -175,11 +175,11 @@ async function convertLinkInCopy(
|
||||
const resource = fParser.parse(fromVsCodeUri(doc.uri), text);
|
||||
const basePath = doc.uri.path.split('/').slice(0, -1).join('/');
|
||||
|
||||
const fileUri = Uri.file(
|
||||
`${
|
||||
const fileUri = doc.uri.with({
|
||||
path: `${
|
||||
basePath ? basePath + '/' : ''
|
||||
}${resource.uri.getName()}.copy${resource.uri.getExtension()}`
|
||||
);
|
||||
}${resource.uri.getName()}.copy${resource.uri.getExtension()}`,
|
||||
});
|
||||
const encoder = new TextEncoder();
|
||||
await workspace.fs.writeFile(fileUri, encoder.encode(text));
|
||||
await window.showTextDocument(fileUri);
|
||||
|
||||
@@ -4,7 +4,10 @@ import { removeBrackets, toTitleCase } from './copy-without-brackets';
|
||||
|
||||
describe('copy-without-brackets command', () => {
|
||||
it('should get the input from the active editor selection', async () => {
|
||||
const { uri } = await createFile('This is my [[test-content]].');
|
||||
const { uri } = await createFile('This is my [[test-content]].', [
|
||||
'copy-without-brackets',
|
||||
'file.md',
|
||||
]);
|
||||
const { editor } = await showInEditor(uri);
|
||||
editor.selection = new Selection(new Position(0, 0), new Position(1, 0));
|
||||
await commands.executeCommand('foam-vscode.copy-without-brackets');
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { commands, window } from 'vscode';
|
||||
import { commands, window, workspace } from 'vscode';
|
||||
import { URI } from '../../core/model/uri';
|
||||
import { asAbsoluteWorkspaceUri, readFile } from '../../services/editor';
|
||||
import {
|
||||
@@ -135,44 +135,53 @@ describe('create-note command', () => {
|
||||
});
|
||||
|
||||
it('supports various options to deal with relative paths', async () => {
|
||||
const TEST_FOLDER = 'create-note-tests';
|
||||
const base = await createFile('relative path tests base file', [
|
||||
'create-note-tests',
|
||||
TEST_FOLDER,
|
||||
'base-file.md',
|
||||
]);
|
||||
|
||||
await closeEditors();
|
||||
await showInEditor(base.uri);
|
||||
|
||||
const target = getUriInWorkspace();
|
||||
expectSameUri(window.activeTextEditor.document.uri, base.uri);
|
||||
await commands.executeCommand('foam-vscode.create-note', {
|
||||
notePath: target.getBasename(),
|
||||
notePath: 'note-resolved-from-root.md',
|
||||
text: 'test resolving from root',
|
||||
onRelativeNotePath: 'resolve-from-root',
|
||||
});
|
||||
expectSameUri(
|
||||
window.activeTextEditor.document.uri,
|
||||
fromVsCodeUri(workspace.workspaceFolders?.[0].uri).joinPath(
|
||||
'note-resolved-from-root.md'
|
||||
)
|
||||
);
|
||||
expect(window.activeTextEditor.document.getText()).toEqual(
|
||||
'test resolving from root'
|
||||
);
|
||||
expectSameUri(window.activeTextEditor.document.uri, target);
|
||||
|
||||
await closeEditors();
|
||||
await showInEditor(base.uri);
|
||||
expectSameUri(window.activeTextEditor.document.uri, base.uri);
|
||||
await commands.executeCommand('foam-vscode.create-note', {
|
||||
notePath: target.getBasename(),
|
||||
notePath: 'note-resolved-from-current-dir.md',
|
||||
text: 'test resolving from current dir',
|
||||
onRelativeNotePath: 'resolve-from-current-dir',
|
||||
});
|
||||
expectSameUri(
|
||||
window.activeTextEditor.document.uri,
|
||||
fromVsCodeUri(workspace.workspaceFolders?.[0].uri).joinPath(
|
||||
TEST_FOLDER,
|
||||
'note-resolved-from-current-dir.md'
|
||||
)
|
||||
);
|
||||
expect(window.activeTextEditor.document.getText()).toEqual(
|
||||
'test resolving from current dir'
|
||||
);
|
||||
expect(fromVsCodeUri(window.activeTextEditor.document.uri).path).toEqual(
|
||||
fromVsCodeUri(window.activeTextEditor.document.uri)
|
||||
.getDirectory()
|
||||
.joinPath(target.getBasename()).path
|
||||
);
|
||||
|
||||
await closeEditors();
|
||||
await showInEditor(base.uri);
|
||||
await commands.executeCommand('foam-vscode.create-note', {
|
||||
notePath: target.getBasename(),
|
||||
notePath: 'note-that-should-not-be-created.md',
|
||||
text: 'test cancelling',
|
||||
onRelativeNotePath: 'cancel',
|
||||
});
|
||||
@@ -184,13 +193,13 @@ describe('create-note command', () => {
|
||||
.spyOn(window, 'showInputBox')
|
||||
.mockImplementationOnce(jest.fn(() => Promise.resolve(undefined)));
|
||||
await commands.executeCommand('foam-vscode.create-note', {
|
||||
notePath: target.getBasename(),
|
||||
notePath: 'ask-me-about-it.md',
|
||||
text: 'test asking',
|
||||
onRelativeNotePath: 'ask',
|
||||
});
|
||||
expect(spy).toHaveBeenCalled();
|
||||
|
||||
await deleteFile(base);
|
||||
// await deleteFile(base);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -93,7 +93,13 @@ export async function createNote(args: CreateNoteArgs, foam: Foam) {
|
||||
resolver.define('FOAM_TITLE', args.title);
|
||||
}
|
||||
const text = args.text ?? DEFAULT_NEW_NOTE_TEXT;
|
||||
const noteUri = args.notePath && URI.file(args.notePath);
|
||||
const schemaSource = vscode.workspace.workspaceFolders[0].uri;
|
||||
const noteUri =
|
||||
args.notePath &&
|
||||
new URI({
|
||||
scheme: schemaSource.scheme,
|
||||
path: args.notePath,
|
||||
});
|
||||
let templateUri: URI;
|
||||
if (args.askForTemplate) {
|
||||
const selectedTemplate = await askUserForTemplate();
|
||||
@@ -104,7 +110,7 @@ export async function createNote(args: CreateNoteArgs, foam: Foam) {
|
||||
}
|
||||
} else {
|
||||
templateUri = args.templatePath
|
||||
? asAbsoluteWorkspaceUri(URI.file(args.templatePath))
|
||||
? asAbsoluteWorkspaceUri(args.templatePath)
|
||||
: getDefaultTemplateUri();
|
||||
}
|
||||
|
||||
@@ -117,7 +123,7 @@ export async function createNote(args: CreateNoteArgs, foam: Foam) {
|
||||
args.onFileExists
|
||||
)
|
||||
: await NoteFactory.createNote(
|
||||
noteUri ?? (await getPathFromTitle(resolver)),
|
||||
noteUri ?? (await getPathFromTitle(templateUri.scheme, resolver)),
|
||||
text,
|
||||
resolver,
|
||||
args.onFileExists,
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
workspace,
|
||||
Position,
|
||||
} from 'vscode';
|
||||
import { isMdEditor, mdDocSelector } from '../../services/editor';
|
||||
import { isMdEditor, getFoamDocSelectors } from '../../services/editor';
|
||||
import { Foam } from '../../core/model/foam';
|
||||
import { FoamWorkspace } from '../../core/model/workspace';
|
||||
import {
|
||||
@@ -48,7 +48,7 @@ export default async function activate(
|
||||
);
|
||||
}),
|
||||
languages.registerCodeLensProvider(
|
||||
mdDocSelector,
|
||||
getFoamDocSelectors(),
|
||||
new WikilinkReferenceCodeLensProvider(
|
||||
foam.workspace,
|
||||
foam.services.parser
|
||||
|
||||
@@ -14,7 +14,7 @@ import { OPEN_COMMAND } from './commands/open-resource';
|
||||
import { CREATE_NOTE_COMMAND } from './commands/create-note';
|
||||
import { commandAsURI } from '../utils/commands';
|
||||
import { Location } from '../core/model/location';
|
||||
import { getNoteTooltip, mdDocSelector } from '../services/editor';
|
||||
import { getNoteTooltip, getFoamDocSelectors } from '../services/editor';
|
||||
import { isSome } from '../core/utils';
|
||||
|
||||
export const CONFIG_KEY = 'links.hover.enable';
|
||||
@@ -31,7 +31,7 @@ export default async function activate(
|
||||
context.subscriptions.push(
|
||||
isHoverEnabled,
|
||||
vscode.languages.registerHoverProvider(
|
||||
mdDocSelector,
|
||||
getFoamDocSelectors(),
|
||||
new HoverProvider(
|
||||
isHoverEnabled,
|
||||
foam.workspace,
|
||||
|
||||
@@ -6,7 +6,7 @@ import { URI } from '../core/model/uri';
|
||||
import { FoamWorkspace } from '../core/model/workspace';
|
||||
import { getFoamVsCodeConfig } from '../services/config';
|
||||
import { fromVsCodeUri, toVsCodeUri } from '../utils/vsc-utils';
|
||||
import { getNoteTooltip, mdDocSelector } from '../services/editor';
|
||||
import { getNoteTooltip, getFoamDocSelectors } from '../services/editor';
|
||||
|
||||
export const aliasCommitCharacters = ['#'];
|
||||
export const linkCommitCharacters = ['#', '|'];
|
||||
@@ -27,12 +27,12 @@ export default async function activate(
|
||||
const foam = await foamPromise;
|
||||
context.subscriptions.push(
|
||||
vscode.languages.registerCompletionItemProvider(
|
||||
mdDocSelector,
|
||||
getFoamDocSelectors(),
|
||||
new WikilinkCompletionProvider(foam.workspace, foam.graph),
|
||||
'['
|
||||
),
|
||||
vscode.languages.registerCompletionItemProvider(
|
||||
mdDocSelector,
|
||||
getFoamDocSelectors(),
|
||||
new SectionCompletionProvider(foam.workspace),
|
||||
'#'
|
||||
),
|
||||
@@ -123,7 +123,7 @@ export class SectionCompletionProvider
|
||||
const item = new ResourceCompletionItem(
|
||||
b.label,
|
||||
vscode.CompletionItemKind.Text,
|
||||
resource.uri.withFragment(b.label)
|
||||
resource.uri.with({ fragment: b.label })
|
||||
);
|
||||
item.sortText = String(b.range.start.line).padStart(5, '0');
|
||||
item.range = replacementRange;
|
||||
|
||||
@@ -231,6 +231,10 @@ describe('Document navigation', () => {
|
||||
doc,
|
||||
new vscode.Position(0, 26)
|
||||
);
|
||||
|
||||
// Make sure the references are sorted by position, so we match the right expectation
|
||||
refs.sort((a, b) => a.range.start.character - b.range.start.character);
|
||||
|
||||
expect(refs.length).toEqual(2);
|
||||
expect(refs[0]).toEqual({
|
||||
uri: toVsCodeUri(fileB.uri),
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Position } from '../core/model/position';
|
||||
import { CREATE_NOTE_COMMAND } from './commands/create-note';
|
||||
import { commandAsURI } from '../utils/commands';
|
||||
import { Location } from '../core/model/location';
|
||||
import { mdDocSelector } from '../services/editor';
|
||||
import { getFoamDocSelectors } from '../services/editor';
|
||||
|
||||
export default async function activate(
|
||||
context: vscode.ExtensionContext,
|
||||
@@ -26,15 +26,15 @@ export default async function activate(
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.languages.registerDefinitionProvider(
|
||||
mdDocSelector,
|
||||
getFoamDocSelectors(),
|
||||
navigationProvider
|
||||
),
|
||||
vscode.languages.registerDocumentLinkProvider(
|
||||
mdDocSelector,
|
||||
getFoamDocSelectors(),
|
||||
navigationProvider
|
||||
),
|
||||
vscode.languages.registerReferenceProvider(
|
||||
mdDocSelector,
|
||||
getFoamDocSelectors(),
|
||||
navigationProvider
|
||||
)
|
||||
);
|
||||
@@ -157,7 +157,7 @@ export class NavigationProvider
|
||||
})
|
||||
);
|
||||
|
||||
return targets
|
||||
const links: vscode.DocumentLink[] = targets
|
||||
.filter(o => o.target.isPlaceholder()) // links to resources are managed by the definition provider
|
||||
.map(o => {
|
||||
const command = CREATE_NOTE_COMMAND.forPlaceholder(
|
||||
@@ -180,5 +180,26 @@ export class NavigationProvider
|
||||
documentLink.tooltip = `Create note for '${o.target.path}'`;
|
||||
return documentLink;
|
||||
});
|
||||
|
||||
const tags: vscode.DocumentLink[] = resource.tags.map(tag => {
|
||||
const command = {
|
||||
name: 'foam-vscode.views.tags-explorer.focus',
|
||||
params: [tag.label, documentUri],
|
||||
};
|
||||
|
||||
const documentLink = new vscode.DocumentLink(
|
||||
new vscode.Range(
|
||||
tag.range.start.line,
|
||||
tag.range.start.character,
|
||||
tag.range.end.line,
|
||||
tag.range.end.character
|
||||
),
|
||||
commandAsURI(command)
|
||||
);
|
||||
documentLink.tooltip = `Explore tag '${tag.label}'`;
|
||||
return documentLink;
|
||||
});
|
||||
|
||||
return links.concat(tags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export default async function activate(
|
||||
});
|
||||
|
||||
vscode.window.onDidChangeActiveTextEditor(e => {
|
||||
if (e?.document?.uri?.scheme === 'file') {
|
||||
if (e?.document?.uri?.scheme !== 'untitled') {
|
||||
const note = foam.workspace.get(fromVsCodeUri(e.document.uri));
|
||||
if (isSome(note)) {
|
||||
panel.webview.postMessage({
|
||||
|
||||
@@ -54,6 +54,51 @@ export default async function activate(
|
||||
provider,
|
||||
node => node.contextValue === 'tag' || node.contextValue === 'folder'
|
||||
)
|
||||
),
|
||||
vscode.commands.registerCommand(
|
||||
`foam-vscode.views.${provider.providerId}.focus`,
|
||||
async (tag?: string, source?: object) => {
|
||||
if (tag == null) {
|
||||
tag = await vscode.window.showQuickPick(
|
||||
Array.from(foam.tags.tags.keys()),
|
||||
{
|
||||
title: 'Select a tag to focus',
|
||||
}
|
||||
);
|
||||
}
|
||||
if (tag == null) {
|
||||
return;
|
||||
}
|
||||
const tagItem = (await provider.findTreeItemByPath(
|
||||
provider.valueToPath(tag)
|
||||
)) as TagItem;
|
||||
if (tagItem == null) {
|
||||
return;
|
||||
}
|
||||
await treeView.reveal(tagItem, {
|
||||
select: true,
|
||||
focus: true,
|
||||
expand: true,
|
||||
});
|
||||
const children = await provider.getChildren(tagItem);
|
||||
const sourceUri = source ? new URI(source) : undefined;
|
||||
const resourceItem = sourceUri
|
||||
? children.find(
|
||||
t =>
|
||||
t instanceof ResourceTreeItem &&
|
||||
sourceUri.isEqual(t.resource?.uri)
|
||||
)
|
||||
: undefined;
|
||||
// doing it as a two reveal process as revealing just the resource
|
||||
// was only working when the tag item was already expanded
|
||||
if (resourceItem) {
|
||||
treeView.reveal(resourceItem, {
|
||||
select: true,
|
||||
focus: true,
|
||||
expand: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ export interface Folder<T> {
|
||||
export class FolderTreeItem<T> extends vscode.TreeItem {
|
||||
collapsibleState = vscode.TreeItemCollapsibleState.Collapsed;
|
||||
contextValue = 'folder';
|
||||
iconPath = new vscode.ThemeIcon('folder');
|
||||
|
||||
constructor(
|
||||
public node: Folder<T>,
|
||||
|
||||
@@ -43,6 +43,9 @@ export class UriTreeItem extends BaseTreeItem {
|
||||
}
|
||||
|
||||
export class ResourceTreeItem extends UriTreeItem {
|
||||
iconPath = vscode.ThemeIcon.File;
|
||||
contextValue = 'foam.resource';
|
||||
|
||||
constructor(
|
||||
public readonly resource: Resource,
|
||||
private readonly workspace: FoamWorkspace,
|
||||
@@ -62,8 +65,6 @@ export class ResourceTreeItem extends UriTreeItem {
|
||||
title: 'Go to location',
|
||||
};
|
||||
this.resourceUri = toVsCodeUri(resource.uri);
|
||||
this.iconPath = vscode.ThemeIcon.File;
|
||||
this.contextValue = 'foam.resource';
|
||||
}
|
||||
|
||||
async resolveTreeItem(): Promise<ResourceTreeItem> {
|
||||
|
||||
@@ -4,6 +4,7 @@ import markdownItRegex from 'markdown-it-regex';
|
||||
import { FoamWorkspace } from '../../core/model/workspace';
|
||||
import { Logger } from '../../core/utils/log';
|
||||
import { isNone } from '../../core/utils';
|
||||
import { commandAsURI } from '../../utils/commands';
|
||||
|
||||
export const markdownItFoamTags = (
|
||||
md: markdownit,
|
||||
@@ -14,10 +15,7 @@ export const markdownItFoamTags = (
|
||||
regex: /(?<=^|\s)(#[0-9]*[\p{L}/_-][\p{L}\p{N}/_-]*)/u,
|
||||
replace: (tag: string) => {
|
||||
try {
|
||||
const resource = workspace.find(tag);
|
||||
if (isNone(resource)) {
|
||||
return getFoamTag(tag);
|
||||
}
|
||||
return getFoamTag(tag);
|
||||
} catch (e) {
|
||||
Logger.error(
|
||||
`Error while creating link for ${tag} in Preview panel`,
|
||||
@@ -29,6 +27,8 @@ export const markdownItFoamTags = (
|
||||
});
|
||||
};
|
||||
|
||||
// Commands can't be run in the preview (see https://github.com/microsoft/vscode/issues/102532)
|
||||
// for we just return the tag as a span
|
||||
const getFoamTag = (content: string) =>
|
||||
`<span class='foam-tag'>${content}</span>`;
|
||||
|
||||
|
||||
@@ -17,9 +17,10 @@ export const markdownItWikilinkEmbed = (
|
||||
regex: WIKILINK_EMBED_REGEX,
|
||||
replace: (wikilinkItem: string) => {
|
||||
return `
|
||||
<div style="padding: 0.25em; margin: 1.5em 0; text-align: center; border: 1px solid var(--vscode-editorLineNumber-foreground);">
|
||||
Embeds are not supported in web extension: <br/> ${wikilinkItem}
|
||||
</div>`;
|
||||
<div class="foam-embed-not-supported-warning">
|
||||
Embed not supported in web mode: ${wikilinkItem}
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -10,9 +10,14 @@ import { Resource, ResourceParser } from '../../core/model/note';
|
||||
import { getFoamVsCodeConfig } from '../../services/config';
|
||||
import { fromVsCodeUri, toVsCodeUri } from '../../utils/vsc-utils';
|
||||
import { MarkdownLink } from '../../core/services/markdown-link';
|
||||
import { URI } from '../../core/model/uri';
|
||||
import { Position } from '../../core/model/position';
|
||||
import { TextEdit } from '../../core/services/text-edit';
|
||||
import { isNone, isSome } from '../../core/utils';
|
||||
import {
|
||||
asAbsoluteWorkspaceUri,
|
||||
isVirtualWorkspace,
|
||||
} from '../../services/editor';
|
||||
|
||||
export const WIKILINK_EMBED_REGEX =
|
||||
/((?:(?:full|content)-(?:inline|card)|full|content|inline|card)?!\[\[[^[\]]+?\]\])/;
|
||||
@@ -22,7 +27,7 @@ export const WIKILINK_EMBED_REGEX =
|
||||
export const WIKILINK_EMBED_REGEX_GROUPS =
|
||||
/((?:\w+)|(?:(?:\w+)-(?:\w+)))?!\[\[([^[\]]+?)\]\]/;
|
||||
export const CONFIG_EMBED_NOTE_TYPE = 'preview.embedNoteType';
|
||||
const refsStack: string[] = [];
|
||||
let refsStack: string[] = [];
|
||||
|
||||
export const markdownItWikilinkEmbed = (
|
||||
md: markdownit,
|
||||
@@ -38,6 +43,14 @@ export const markdownItWikilinkEmbed = (
|
||||
WIKILINK_EMBED_REGEX_GROUPS
|
||||
);
|
||||
|
||||
if (isVirtualWorkspace()) {
|
||||
return `
|
||||
<div class="foam-embed-not-supported-warning">
|
||||
Embed not supported in virtual workspace: ![[${wikilink}]]
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
const includedNote = workspace.find(wikilink);
|
||||
|
||||
if (!includedNote) {
|
||||
@@ -48,56 +61,31 @@ export const markdownItWikilinkEmbed = (
|
||||
includedNote.uri.path.toLocaleLowerCase()
|
||||
);
|
||||
|
||||
if (!cyclicLinkDetected) {
|
||||
refsStack.push(includedNote.uri.path.toLocaleLowerCase());
|
||||
}
|
||||
|
||||
if (cyclicLinkDetected) {
|
||||
return `<div class="foam-cyclic-link-warning">Cyclic link detected for wikilink: ${wikilink}</div>`;
|
||||
return `
|
||||
<div class="foam-cyclic-link-warning">
|
||||
Cyclic link detected for wikilink: ${wikilink}
|
||||
<div class="foam-cyclic-link-warning__stack">
|
||||
Link sequence:
|
||||
<ul>
|
||||
${refsStack.map(ref => `<li>${ref}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
let content = `Embed for [[${wikilink}]]`;
|
||||
let html: string;
|
||||
|
||||
switch (includedNote.type) {
|
||||
case 'note': {
|
||||
const { noteScope, noteStyle } =
|
||||
retrieveNoteConfig(noteEmbedModifier);
|
||||
refsStack.push(includedNote.uri.path.toLocaleLowerCase());
|
||||
|
||||
const extractor: EmbedNoteExtractor =
|
||||
noteScope === 'full'
|
||||
? fullExtractor
|
||||
: noteScope === 'content'
|
||||
? contentExtractor
|
||||
: fullExtractor;
|
||||
|
||||
const formatter: EmbedNoteFormatter =
|
||||
noteStyle === 'card'
|
||||
? cardFormatter
|
||||
: noteStyle === 'inline'
|
||||
? inlineFormatter
|
||||
: cardFormatter;
|
||||
|
||||
content = extractor(includedNote, parser, workspace);
|
||||
html = formatter(content, md);
|
||||
break;
|
||||
}
|
||||
case 'attachment':
|
||||
content = `
|
||||
<div class="embed-container-attachment">
|
||||
${md.renderInline('[[' + wikilink + ']]')}<br/>
|
||||
Embed for attachments is not supported
|
||||
</div>`;
|
||||
html = md.render(content);
|
||||
break;
|
||||
case 'image':
|
||||
content = `<div class="embed-container-image">${md.render(
|
||||
`})`
|
||||
)}</div>`;
|
||||
html = md.render(content);
|
||||
break;
|
||||
}
|
||||
const content = getNoteContent(
|
||||
includedNote,
|
||||
noteEmbedModifier,
|
||||
parser,
|
||||
workspace,
|
||||
md
|
||||
);
|
||||
refsStack.pop();
|
||||
return html;
|
||||
return content;
|
||||
} catch (e) {
|
||||
Logger.error(
|
||||
`Error while including ${wikilinkItem} into the current document of the Preview panel`,
|
||||
@@ -109,11 +97,65 @@ Embed for attachments is not supported
|
||||
});
|
||||
};
|
||||
|
||||
function getNoteContent(
|
||||
includedNote: Resource,
|
||||
noteEmbedModifier: string | undefined,
|
||||
parser: ResourceParser,
|
||||
workspace: FoamWorkspace,
|
||||
md: markdownit
|
||||
): string {
|
||||
let content = `Embed for [[${includedNote.uri.path}]]`;
|
||||
let html: string;
|
||||
|
||||
switch (includedNote.type) {
|
||||
case 'note': {
|
||||
const { noteScope, noteStyle } = retrieveNoteConfig(noteEmbedModifier);
|
||||
|
||||
const extractor: EmbedNoteExtractor =
|
||||
noteScope === 'full'
|
||||
? fullExtractor
|
||||
: noteScope === 'content'
|
||||
? contentExtractor
|
||||
: fullExtractor;
|
||||
|
||||
const formatter: EmbedNoteFormatter =
|
||||
noteStyle === 'card'
|
||||
? cardFormatter
|
||||
: noteStyle === 'inline'
|
||||
? inlineFormatter
|
||||
: cardFormatter;
|
||||
|
||||
content = extractor(includedNote, parser, workspace);
|
||||
html = formatter(content, md);
|
||||
break;
|
||||
}
|
||||
case 'attachment':
|
||||
content = `
|
||||
<div class="embed-container-attachment">
|
||||
${md.renderInline('[[' + includedNote.uri.path + ']]')}<br/>
|
||||
Embed for attachments is not supported
|
||||
</div>`;
|
||||
html = md.render(content);
|
||||
break;
|
||||
case 'image':
|
||||
content = `<div class="embed-container-image">${md.render(
|
||||
`})`
|
||||
)}</div>`;
|
||||
html = md.render(content);
|
||||
break;
|
||||
default:
|
||||
html = content;
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function withLinksRelativeToWorkspaceRoot(
|
||||
noteUri: URI,
|
||||
noteText: string,
|
||||
parser: ResourceParser,
|
||||
workspace: FoamWorkspace
|
||||
) {
|
||||
): string {
|
||||
const note = parser.parse(
|
||||
fromVsCodeUri(vsWorkspace.workspaceFolders[0].uri),
|
||||
noteText
|
||||
@@ -121,15 +163,13 @@ function withLinksRelativeToWorkspaceRoot(
|
||||
const edits = note.links
|
||||
.map(link => {
|
||||
const info = MarkdownLink.analyzeLink(link);
|
||||
const resource = workspace.find(info.target);
|
||||
const resource = workspace.find(info.target, noteUri);
|
||||
// embedded notes that aren't created are still collected
|
||||
// return null so it can be filtered in the next step
|
||||
if (isNone(resource)) {
|
||||
return null;
|
||||
}
|
||||
const pathFromRoot = vsWorkspace.asRelativePath(
|
||||
toVsCodeUri(resource.uri)
|
||||
);
|
||||
const pathFromRoot = asAbsoluteWorkspaceUri(resource.uri).path;
|
||||
return MarkdownLink.createUpdateLinkEdit(link, {
|
||||
target: pathFromRoot,
|
||||
});
|
||||
@@ -185,7 +225,12 @@ function fullExtractor(
|
||||
.slice(section.range.start.line, section.range.end.line)
|
||||
.join('\n');
|
||||
}
|
||||
noteText = withLinksRelativeToWorkspaceRoot(noteText, parser, workspace);
|
||||
noteText = withLinksRelativeToWorkspaceRoot(
|
||||
note.uri,
|
||||
noteText,
|
||||
parser,
|
||||
workspace
|
||||
);
|
||||
return noteText;
|
||||
}
|
||||
|
||||
@@ -211,7 +256,12 @@ function contentExtractor(
|
||||
}
|
||||
rows.shift();
|
||||
noteText = rows.join('\n');
|
||||
noteText = withLinksRelativeToWorkspaceRoot(noteText, parser, workspace);
|
||||
noteText = withLinksRelativeToWorkspaceRoot(
|
||||
note.uri,
|
||||
noteText,
|
||||
parser,
|
||||
workspace
|
||||
);
|
||||
return noteText;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as vscode from 'vscode';
|
||||
import { Foam } from '../core/model/foam';
|
||||
import { FoamTags } from '../core/model/tags';
|
||||
import { isInFrontMatter, isOnYAMLKeywordLine } from '../core/utils/md';
|
||||
import { mdDocSelector } from '../services/editor';
|
||||
import { getFoamDocSelectors } from '../services/editor';
|
||||
|
||||
// this regex is different from HASHTAG_REGEX in that it does not look for a
|
||||
// #+character. It uses a negative look-ahead for `# `
|
||||
@@ -17,7 +17,7 @@ export default async function activate(
|
||||
const foam = await foamPromise;
|
||||
context.subscriptions.push(
|
||||
vscode.languages.registerCompletionItemProvider(
|
||||
mdDocSelector,
|
||||
getFoamDocSelectors(),
|
||||
new TagCompletionProvider(foam.tags),
|
||||
'#'
|
||||
)
|
||||
|
||||
@@ -18,23 +18,27 @@ describe('Editor utils', () => {
|
||||
|
||||
describe('getCurrentEditorDirectory', () => {
|
||||
it('should return the directory of the active text editor', async () => {
|
||||
const file = await createFile('this is the file content.');
|
||||
const file = await createFile('this is the file content.', [
|
||||
'editor-utils',
|
||||
'file.md',
|
||||
]);
|
||||
await showInEditor(file.uri);
|
||||
|
||||
expect(getCurrentEditorDirectory()).toEqual(file.uri.getDirectory());
|
||||
});
|
||||
|
||||
it('should return the directory of the workspace folder if no editor is open', async () => {
|
||||
it('should throw if no editor is open', async () => {
|
||||
await closeEditors();
|
||||
expect(getCurrentEditorDirectory()).toEqual(
|
||||
fromVsCodeUri(workspace.workspaceFolders[0].uri)
|
||||
);
|
||||
expect(() => getCurrentEditorDirectory()).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('replaceSelection', () => {
|
||||
it('should replace the selection in the active editor', async () => {
|
||||
const fileA = await createFile('This is the file A');
|
||||
const fileA = await createFile('This is the file A', [
|
||||
'replace-selection',
|
||||
'file.md',
|
||||
]);
|
||||
const doc = await showInEditor(fileA.uri);
|
||||
const selection = new Selection(0, 5, 0, 7); // 'is'
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import { getExcerpt, stripFrontMatter, stripImages } from '../core/utils/md';
|
||||
import { isSome } from '../core/utils/core';
|
||||
import { fromVsCodeUri, toVsCodeUri } from '../utils/vsc-utils';
|
||||
import { asAbsoluteUri, URI } from '../core/model/uri';
|
||||
import { getFoamVsCodeConfig } from './config';
|
||||
import {
|
||||
AlwaysIncludeMatcher,
|
||||
FileListBasedMatcher,
|
||||
@@ -52,13 +53,38 @@ export function formatMarkdownTooltip(content: string): MarkdownString {
|
||||
return md;
|
||||
}
|
||||
|
||||
export const mdDocSelector = [
|
||||
{ language: 'markdown', scheme: 'file' },
|
||||
{ language: 'markdown', scheme: 'untitled' },
|
||||
];
|
||||
// Generate the document selector dynamically
|
||||
export const getFoamDocSelectors = () =>
|
||||
getFoamVsCodeConfig<string[]>('supportedLanguages', ['markdown']).flatMap(
|
||||
lang => [
|
||||
{ language: lang, scheme: 'file' }, // Local files
|
||||
{ language: lang, scheme: 'vscode-vfs' }, // Remote files
|
||||
{ language: lang, scheme: 'untitled' }, // Untitled files
|
||||
]
|
||||
);
|
||||
|
||||
export function isMdEditor(editor: TextEditor) {
|
||||
return editor && editor.document && editor.document.languageId === 'markdown';
|
||||
// Check if the editor's document is a supported language
|
||||
export function isMdEditor(editor: TextEditor): boolean {
|
||||
const supportedLanguages = getFoamVsCodeConfig<string[]>(
|
||||
'supportedLanguages',
|
||||
['markdown']
|
||||
);
|
||||
return (
|
||||
editor &&
|
||||
editor.document &&
|
||||
supportedLanguages.includes(editor.document.languageId)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the workspace contains remote or virtual file system folders.
|
||||
* @returns True if the workspace contains remote or virtual file system folders, false otherwise.
|
||||
*/
|
||||
export function isVirtualWorkspace(): boolean {
|
||||
return workspace.workspaceFolders.some(folder => {
|
||||
const scheme = folder.uri.scheme;
|
||||
return scheme === 'vscode-remote' || scheme === 'vscode-vfs';
|
||||
});
|
||||
}
|
||||
|
||||
export function findSelectionContent(): SelectionInfo | undefined {
|
||||
@@ -134,12 +160,10 @@ export function getEditorEOL(): string {
|
||||
|
||||
/**
|
||||
* Returns the directory of the file currently open in the editor.
|
||||
* If no file is open in the editor it will return the first folder
|
||||
* in the workspace.
|
||||
* If both aren't available it will throw.
|
||||
* If no file is open in the editor it will throw.
|
||||
*
|
||||
* @returns URI
|
||||
* @throws Error if no file is open in editor AND no workspace folder defined
|
||||
* @throws Error if no file is open in editor
|
||||
*/
|
||||
export function getCurrentEditorDirectory(): URI {
|
||||
const uri = window.activeTextEditor?.document?.uri;
|
||||
@@ -148,11 +172,7 @@ export function getCurrentEditorDirectory(): URI {
|
||||
return fromVsCodeUri(uri).getDirectory();
|
||||
}
|
||||
|
||||
if (workspace.workspaceFolders.length > 0) {
|
||||
return fromVsCodeUri(workspace.workspaceFolders[0].uri);
|
||||
}
|
||||
|
||||
throw new Error('A file must be open in editor, or workspace folder needed');
|
||||
throw new Error('No editor open');
|
||||
}
|
||||
|
||||
export async function fileExists(uri: URI): Promise<boolean> {
|
||||
@@ -179,17 +199,17 @@ export function deleteFile(uri: URI) {
|
||||
|
||||
/**
|
||||
* Turns a relative URI into an absolute URI for the given workspace.
|
||||
* @param uri the uri to evaluate
|
||||
* @param uriOrPath the uri or path to evaluate
|
||||
* @returns an absolute uri
|
||||
*/
|
||||
export function asAbsoluteWorkspaceUri(uri: URI): URI {
|
||||
export function asAbsoluteWorkspaceUri(uriOrPath: URI | string): URI {
|
||||
if (workspace.workspaceFolders === undefined) {
|
||||
throw new Error('An open folder or workspace is required');
|
||||
}
|
||||
const folders = workspace.workspaceFolders.map(folder =>
|
||||
fromVsCodeUri(folder.uri)
|
||||
);
|
||||
const res = asAbsoluteUri(uri, folders);
|
||||
const res = asAbsoluteUri(uriOrPath, folders);
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -219,9 +239,9 @@ export async function createMatcherAndDataStore(excludes: string[]): Promise<{
|
||||
let files: Uri[] = [];
|
||||
for (const folder of workspace.workspaceFolders) {
|
||||
const uris = await workspace.findFiles(
|
||||
new RelativePattern(folder.uri.path, '**/*'),
|
||||
new RelativePattern(folder.uri, '**/*'),
|
||||
new RelativePattern(
|
||||
folder.uri.path,
|
||||
folder.uri,
|
||||
`{${excludePatterns.get(folder.name).join(',')}}`
|
||||
)
|
||||
);
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
import { Resolver } from './variable-resolver';
|
||||
import dateFormat from 'dateformat';
|
||||
import { getFoamVsCodeConfig } from './config';
|
||||
import { isNone } from '../core/utils';
|
||||
import { firstFrom, isNone } from '../core/utils';
|
||||
|
||||
/**
|
||||
* The templates directory
|
||||
@@ -233,7 +233,7 @@ const createFnForOnRelativePathStrategy =
|
||||
const newProposedPath = await askUserForFilepathConfirmation(
|
||||
existingFile
|
||||
);
|
||||
return newProposedPath && URI.file(newProposedPath);
|
||||
return newProposedPath && existingFile.with({ path: newProposedPath });
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -257,7 +257,7 @@ const createFnForOnFileExistsStrategy =
|
||||
const newProposedPath = await askUserForFilepathConfirmation(
|
||||
existingFile
|
||||
);
|
||||
return newProposedPath && URI.file(newProposedPath);
|
||||
return newProposedPath && existingFile.with({ path: newProposedPath });
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -348,15 +348,16 @@ export const NoteFactory = {
|
||||
resolver
|
||||
);
|
||||
|
||||
let newFilePath = template.metadata.has('filepath')
|
||||
? URI.file(template.metadata.get('filepath'))
|
||||
: filepathFallbackURI;
|
||||
const pathSources = [
|
||||
() =>
|
||||
template.metadata.has('filepath')
|
||||
? asAbsoluteWorkspaceUri(template.metadata.get('filepath'))
|
||||
: null,
|
||||
() => filepathFallbackURI,
|
||||
() => getPathFromTitle(templateUri.scheme, resolver),
|
||||
];
|
||||
|
||||
if (isNone(newFilePath)) {
|
||||
newFilePath = await getPathFromTitle(resolver);
|
||||
} else if (!newFilePath.path.startsWith('./')) {
|
||||
newFilePath = asAbsoluteWorkspaceUri(newFilePath);
|
||||
}
|
||||
const newFilePath = await firstFrom(pathSources);
|
||||
|
||||
return NoteFactory.createNote(
|
||||
newFilePath,
|
||||
@@ -443,7 +444,7 @@ export const createTemplate = async (): Promise<void> => {
|
||||
return;
|
||||
}
|
||||
|
||||
const filenameURI = URI.file(filename);
|
||||
const filenameURI = defaultTemplate.with({ path: filename });
|
||||
await workspace.fs.writeFile(
|
||||
toVsCodeUri(filenameURI),
|
||||
new TextEncoder().encode(TEMPLATE_CONTENT)
|
||||
@@ -475,7 +476,7 @@ async function askUserForFilepathConfirmation(
|
||||
});
|
||||
}
|
||||
|
||||
export const getPathFromTitle = async (resolver: Resolver) => {
|
||||
export const getPathFromTitle = async (scheme: string, resolver: Resolver) => {
|
||||
const defaultName = await resolver.resolveFromName('FOAM_TITLE_SAFE');
|
||||
return URI.file(`${defaultName}.md`);
|
||||
return new URI({ scheme, path: `${defaultName}.md` });
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ const initGUI = () => {
|
||||
if (!nodeTypeFilterControllers.has(type)) {
|
||||
const ctrl = nodeTypeFilterFolder
|
||||
.add(m.showNodesOfType, type)
|
||||
.onFinishChange(function() {
|
||||
.onFinishChange(function () {
|
||||
Actions.updateFilters();
|
||||
});
|
||||
ctrl.domElement.previousSibling.style.color = getNodeTypeColor(
|
||||
@@ -254,17 +254,28 @@ function augmentGraphInfo(graph) {
|
||||
node.links = [];
|
||||
if (node.tags && node.tags.length > 0) {
|
||||
node.tags.forEach(tag => {
|
||||
const tagNode = {
|
||||
id: tag.label,
|
||||
title: tag.label,
|
||||
type: 'tag',
|
||||
properties: {},
|
||||
neighbors: [],
|
||||
links: [],
|
||||
};
|
||||
graph.nodeInfo[tag.label] = tagNode;
|
||||
subtags = tag.label.split('/');
|
||||
for (let i = 0; i < subtags.length; i++) {
|
||||
const label = subtags.slice(0, i + 1).join('/');
|
||||
const tagNode = {
|
||||
id: label,
|
||||
title: label,
|
||||
type: 'tag',
|
||||
properties: {},
|
||||
neighbors: [],
|
||||
links: [],
|
||||
};
|
||||
graph.nodeInfo[tagNode.id] = tagNode;
|
||||
if (i > 0) {
|
||||
const parent = subtags.slice(0, i).join('/');
|
||||
graph.links.push({
|
||||
source: parent,
|
||||
target: label,
|
||||
});
|
||||
}
|
||||
}
|
||||
graph.links.push({
|
||||
source: tagNode.id,
|
||||
source: tag.label,
|
||||
target: node.id,
|
||||
});
|
||||
});
|
||||
@@ -374,6 +385,12 @@ function getLinkColor(link, model) {
|
||||
const style = model.style;
|
||||
switch (getLinkState(link, model)) {
|
||||
case 'regular':
|
||||
if (
|
||||
model.graph.nodeInfo[link.source.id].type === 'tag' &&
|
||||
model.graph.nodeInfo[link.target.id].type === 'tag'
|
||||
) {
|
||||
return getNodeTypeColor('tag', model);
|
||||
}
|
||||
return style.lineColor;
|
||||
case 'highlighted':
|
||||
return style.highlightedForeground;
|
||||
|
||||
@@ -13,8 +13,21 @@
|
||||
}
|
||||
|
||||
.foam-cyclic-link-warning {
|
||||
border: 1px solid var(--vscode-editorWarning-foreground);
|
||||
background-color: var(--vscode-editorError-background);
|
||||
color: var(--vscode-editorError-foreground);
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.foam-cyclic-link-warning__stack {
|
||||
color: var(--vscode-editorWarning-foreground);
|
||||
}
|
||||
|
||||
.foam-embed-not-supported-warning {
|
||||
border: 1px solid var(--vscode-editorWarning-foreground);
|
||||
background-color: var(--vscode-editorError-background);
|
||||
color: var(--vscode-editorWarning-foreground);
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.embed-container-note {
|
||||
|
||||
20
readme.md
20
readme.md
@@ -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://marketplace.visualstudio.com/items?itemName=foam.foam-vscode)
|
||||
@@ -180,15 +180,15 @@ Quick links to next documentation sections
|
||||
- [Call To Adventure](https://foambubble.github.io/foam#call-to-adventure)
|
||||
- [Thanks and attribution](https://foambubble.github.io/foam#thanks-and-attribution)
|
||||
|
||||
You can also browse the [docs folder](https://github.com/foambubble/foam/tree/master/docs).
|
||||
You can also browse the [docs folder](https://github.com/foambubble/foam/tree/main/docs).
|
||||
|
||||
## License
|
||||
|
||||
Foam is licensed under the [MIT license](LICENSE).
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[Backlinking]: docs/user/features/backlinking.md "Backlinking"
|
||||
[//end]: # "Autogenerated link references"
|
||||
[//begin]: # 'Autogenerated link references for markdown compatibility'
|
||||
[Backlinking]: docs/user/features/backlinking.md 'Backlinking'
|
||||
[//end]: # 'Autogenerated link references'
|
||||
|
||||
## Contribution Guide
|
||||
|
||||
@@ -198,8 +198,6 @@ See the [Contribution Guide](https://foambubble.github.io/foam/dev/contribution-
|
||||
|
||||
See the [Code of Conduct](https://foambubble.github.io/foam/dev/code-of-conduct)
|
||||
|
||||
|
||||
|
||||
## Contributors ✨
|
||||
|
||||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||
@@ -365,6 +363,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.hegghammer.com"><img src="https://avatars.githubusercontent.com/u/64712218?v=4?s=60" width="60px;" alt="Thomas Hegghammer"/><br /><sub><b>Thomas Hegghammer</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Hegghammer" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PiotrAleksander"><img src="https://avatars.githubusercontent.com/u/6314591?v=4?s=60" width="60px;" alt="Piotr Mrzygłosz"/><br /><sub><b>Piotr Mrzygłosz</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=PiotrAleksander" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://schaver.com/"><img src="https://avatars.githubusercontent.com/u/7584?v=4?s=60" width="60px;" alt="Mark Schaver"/><br /><sub><b>Mark Schaver</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=markschaver" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/n8layman"><img src="https://avatars.githubusercontent.com/u/25353944?v=4?s=60" width="60px;" alt="Nathan Layman"/><br /><sub><b>Nathan Layman</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=n8layman" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/emmanuel-ferdman"><img src="https://avatars.githubusercontent.com/u/35470921?v=4?s=60" width="60px;" alt="Emmanuel Ferdman"/><br /><sub><b>Emmanuel Ferdman</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=emmanuel-ferdman" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -375,3 +376,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[Backlinking]: docs/user/features/backlinking.md "Backlinking"
|
||||
[//end]: # "Autogenerated link references"
|
||||
Reference in New Issue
Block a user