mirror of
https://github.com/foambubble/foam.git
synced 2026-01-10 22:48:09 -05:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a798b520f | ||
|
|
8db4d2897f | ||
|
|
e0bcb6bd92 | ||
|
|
cd92468311 | ||
|
|
5a44fbc26f | ||
|
|
4412f860dd | ||
|
|
3e3d36b954 | ||
|
|
eee6b7bd3a | ||
|
|
792e66b061 | ||
|
|
e5589f0555 | ||
|
|
32f40fa0de | ||
|
|
97e3b20112 | ||
|
|
0060ea2a3a | ||
|
|
a11dee89ba | ||
|
|
62e46e87b0 | ||
|
|
ef7ea8d23b | ||
|
|
36a632f218 | ||
|
|
f9331ad327 | ||
|
|
a944d993fc | ||
|
|
adf2dfa779 | ||
|
|
c04bc347ed | ||
|
|
5f8a064af2 | ||
|
|
eaa522fe1b | ||
|
|
3868cc5a17 | ||
|
|
76d70d40f8 | ||
|
|
2e373c4624 | ||
|
|
71948062b1 | ||
|
|
94711cf11b | ||
|
|
7d0858246d | ||
|
|
d998c8d482 | ||
|
|
85e0784c54 | ||
|
|
035fb7b634 |
@@ -526,6 +526,51 @@
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "MCluck90",
|
||||
"name": "Mike Cluck",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/1753801?v=4",
|
||||
"profile": "https://mcluck.tech",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "bpugh",
|
||||
"name": "Brandon Pugh",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/684781?v=4",
|
||||
"profile": "http://brandonpugh.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "themaxdavitt",
|
||||
"name": "Max Davitt",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/27709025?v=4",
|
||||
"profile": "https://max.davitt.me",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "anglinb",
|
||||
"name": "Brian Anglin",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/2637602?v=4",
|
||||
"profile": "http://briananglin.me",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "elswork",
|
||||
"name": "elswork",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/1455507?v=4",
|
||||
"profile": "http://deft.work",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -59,4 +59,6 @@ jobs:
|
||||
- name: Build Packages
|
||||
run: yarn build
|
||||
- name: Run Tests
|
||||
run: yarn test
|
||||
uses: GabrielBB/xvfb-action@v1.0
|
||||
with:
|
||||
run: yarn test
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,7 @@ node_modules
|
||||
*.tsbuildinfo
|
||||
*.vsix
|
||||
*.log
|
||||
out
|
||||
dist
|
||||
docs/_site
|
||||
docs/.sass-cache
|
||||
|
||||
42
.vscode/settings.json
vendored
42
.vscode/settings.json
vendored
@@ -1,21 +1,25 @@
|
||||
// Place your settings in this file to overwrite default and user settings.
|
||||
{
|
||||
"files.exclude": {
|
||||
// set these to true to hide folders with the compiled JS files,
|
||||
"packages/**/out": false,
|
||||
"packages/**/dist": false
|
||||
},
|
||||
"search.exclude": {
|
||||
// set this to false to include compiled JS folders in search results
|
||||
"packages/**/out": true,
|
||||
"packages/**/dist": true
|
||||
},
|
||||
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
|
||||
"typescript.tsc.autoDetect": "off",
|
||||
"foam.files.ignore": [
|
||||
"**/.vscode/**/*",
|
||||
"**/_layouts/**/*",
|
||||
"**/_site/**/*",
|
||||
"**/node_modules/**/*"
|
||||
]
|
||||
}
|
||||
"files.exclude": {
|
||||
// set these to true to hide folders with the compiled JS files,
|
||||
"packages/**/out": false,
|
||||
"packages/**/dist": false
|
||||
},
|
||||
"search.exclude": {
|
||||
// set this to false to include compiled JS folders in search results
|
||||
"packages/**/out": true,
|
||||
"packages/**/dist": true
|
||||
},
|
||||
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
|
||||
"typescript.tsc.autoDetect": "off",
|
||||
"foam.files.ignore": [
|
||||
"**/.vscode/**/*",
|
||||
"**/_layouts/**/*",
|
||||
"**/_site/**/*",
|
||||
"**/node_modules/**/*"
|
||||
],
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"prettier.requireConfig": true,
|
||||
"editor.formatOnSave": true,
|
||||
"jest.debugCodeLens.showWhenTestStateIn": ["fail", "unknown", "pass"]
|
||||
}
|
||||
|
||||
BIN
docs/assets/images/graph-style.gif
Normal file
BIN
docs/assets/images/graph-style.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.9 MiB |
BIN
docs/assets/images/style-node-by-type.png
Normal file
BIN
docs/assets/images/style-node-by-type.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 73 KiB |
@@ -15,4 +15,4 @@
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[todo]: dev/todo.md "Todo"
|
||||
[//end]: # "Autogenerated link references"
|
||||
[//end]: # "Autogenerated link references"
|
||||
@@ -37,6 +37,16 @@ If you're interested in contributing, this short guide will help you get things
|
||||
|
||||
You should now be ready to start working!
|
||||
|
||||
### Testing
|
||||
|
||||
Code needs to come with tests.
|
||||
We use the following convention in Foam:
|
||||
- *.test.ts are unit tests
|
||||
- *.spec.ts are integration tests
|
||||
|
||||
Also, note that tests in `foam-core` and `foam-cli` live in the `test` directory.
|
||||
Tests in `foam-vscode` live alongside the code in `src`.
|
||||
|
||||
### The VS Code Extension
|
||||
|
||||
This guide assumes you read the previous instructions and you're set up to work on Foam.
|
||||
|
||||
@@ -16,6 +16,6 @@ Exceptions to the monorepo are:
|
||||
- All other [[recommended-extensions]] live in their respective GitHub repos.
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[recipes]: ../recipes/recipes "Recipes"
|
||||
[recommended-extensions]: ../recommended-extensions "Recommended Extensions"
|
||||
[recipes]: ../recipes/recipes.md "Recipes"
|
||||
[recommended-extensions]: ../recommended-extensions.md "Recommended Extensions"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
@@ -14,4 +14,5 @@ The idea would be to automatically generate lists of backlinks (and optionally,
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[todo]: todo.md "Todo"
|
||||
[roadmap]: roadmap.md "Roadmap"
|
||||
[//end]: # "Autogenerated link references"
|
||||
@@ -6,4 +6,5 @@ If you're interested in working on it, please start a conversation in [GitHub is
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[todo]: todo.md "Todo"
|
||||
[roadmap]: roadmap.md "Roadmap"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
@@ -11,4 +11,5 @@ If you're interested in working on it, please start a conversation in [GitHub is
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[todo]: todo.md "Todo"
|
||||
[roadmap]: roadmap.md "Roadmap"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
@@ -87,7 +87,7 @@ The community is working on a number of automated scripts to help you migrate to
|
||||
[build-vs-assemble]: build-vs-assemble.md "Build vs Assemble"
|
||||
[recipes]: ../recipes/recipes.md "Recipes"
|
||||
[contribution-guide]: ../contribution-guide.md "Contribution Guide"
|
||||
[git-integration]: ../features/git-integration.md "Git integration"
|
||||
[git-integration]: ../features/git-integration.md "Git Integration"
|
||||
[wiki-links]: ../wiki-links.md "Wiki Links"
|
||||
[foam-file-format]: foam-file-format.md "Foam File Format"
|
||||
[unlinked-references]: unlinked-references.md "Unlinked references (stub)"
|
||||
|
||||
@@ -14,5 +14,6 @@ Features belong on the [[roadmap]].
|
||||
For more things to do, check backlinks for Pages that annotate [[todo]].
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[roadmap]: roadmap.md "Roadmap"
|
||||
[todo]: todo.md "Todo"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
@@ -6,7 +6,7 @@ If you're interested in working on it, please start a conversation in [GitHub is
|
||||
|
||||
## Notes
|
||||
|
||||
One of Roam's big features is the ability to find all instances of a reference, create a page for it and update all the references to link to the new page.
|
||||
One of Foam's big features is the ability to find all instances of a reference, create a page for it and update all the references to link to the new page.
|
||||
|
||||
Implementing this is on the [[roadmap]], but for the time being you can achieve similar things by:
|
||||
|
||||
@@ -16,4 +16,5 @@ Implementing this is on the [[roadmap]], but for the time being you can achieve
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[todo]: todo.md "Todo"
|
||||
[roadmap]: roadmap.md "Roadmap"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[search-for-notes]: ../recipes/search-for-notes.md "Search for Notes"
|
||||
[graph-visualisation]: graph-visualisation.md "Graph visualisation"
|
||||
[graph-visualisation]: graph-visualisation.md "Graph Visualisation"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
@@ -7,6 +7,51 @@ The graph will:
|
||||
- to navigate to a note by clicking on it while pressing `CTRL` or `CMD`
|
||||
- automatically center the graph on the currently edited note, to immediately see it's connections
|
||||
|
||||
## Custom Graph Styles
|
||||
|
||||
Currently, custom graph styles are supported through the `foam.graph.style` setting.
|
||||
|
||||

|
||||
|
||||
A sample configuration object is provided below:
|
||||
|
||||
```json
|
||||
"background": "#202020",
|
||||
"fontSize": 12,
|
||||
"highlightedForeground": "#f9c74f",
|
||||
"node": {
|
||||
"note": "#277da1",
|
||||
"placeholder": "#545454",
|
||||
}
|
||||
```
|
||||
|
||||
### Style nodes by type
|
||||
It is possible to customize the style of a node based on the `type` property in the YAML frontmatter of the corresponding document.
|
||||
|
||||
For example the following `backlinking.md` note:
|
||||
|
||||
```
|
||||
---
|
||||
type: feature
|
||||
---
|
||||
# Backlinking
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
And the following `settings.json`:
|
||||
```json
|
||||
"foam.graph.style": {
|
||||
"node": {
|
||||
"feature": "red",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Will result in the following graph:
|
||||
|
||||

|
||||
|
||||
### Markdown Links
|
||||
Another extension that provides a great graph visualisation is [Markdown Links](https://marketplace.visualstudio.com/items?itemName=tchayen.markdown-links).
|
||||
The extension doesn't use the Foam model, so discrepancies might arise, but it's a great visualisation extension nonetheless!
|
||||
@@ -14,3 +59,4 @@ The extension doesn't use the Foam model, so discrepancies might arise, but it's
|
||||
- Use the `Markdown Links: Show Graph` command to see the graph
|
||||

|
||||
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ The following example:
|
||||
[wiki-links]: wiki-links "Wiki Links"
|
||||
[github-pages]: github-pages "Github Pages"
|
||||
```
|
||||
You can open the [raw markdown](https://foambubble.github.io/foam/foam-file-format.md) to see them at the bottom of this file
|
||||
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
|
||||
|
||||
## Specification
|
||||
|
||||
|
||||
126
docs/index.md
126
docs/index.md
@@ -76,7 +76,7 @@ To learn more about how to use **Foam**, read the [[recipes]].
|
||||
|
||||
Getting stuck in the setup? Read the [[frequently-asked-questions]].
|
||||
|
||||
There are [[known-issues]], and I'm sure, many unknown issues! Please [report them on GitHub](http://github.com/foambubble/foam/issues)!
|
||||
Check our [issues on GitHub](http://github.com/foambubble/foam/issues) if you get stuck on something, and create a new one if something doesn't seem right!
|
||||
|
||||
## Features
|
||||
|
||||
@@ -107,81 +107,89 @@ If that sounds like something you're interested in, I'd love to have you along o
|
||||
<!-- markdownlint-disable -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><a href="https://jevakallio.dev/"><img src="https://avatars1.githubusercontent.com/u/1203949?v=4" width="60px;" alt=""/><br /><sub><b>Jani Eväkallio</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jevakallio" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jevakallio" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://joeprevite.com/"><img src="https://avatars3.githubusercontent.com/u/3806031?v=4" width="60px;" alt=""/><br /><sub><b>Joe Previte</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jsjoeio" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jsjoeio" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/riccardoferretti"><img src="https://avatars3.githubusercontent.com/u/457005?v=4" width="60px;" alt=""/><br /><sub><b>Riccardo</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=riccardoferretti" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=riccardoferretti" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://ojanaho.com/"><img src="https://avatars0.githubusercontent.com/u/2180090?v=4" width="60px;" alt=""/><br /><sub><b>Janne Ojanaho</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jojanaho" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jojanaho" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://bypaulshen.com/"><img src="https://avatars3.githubusercontent.com/u/2266187?v=4" width="60px;" alt=""/><br /><sub><b>Paul Shen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=paulshen" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/coffenbacher"><img src="https://avatars0.githubusercontent.com/u/245867?v=4" width="60px;" alt=""/><br /><sub><b>coffenbacher</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=coffenbacher" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://mathieu.dutour.me/"><img src="https://avatars2.githubusercontent.com/u/3254314?v=4" width="60px;" alt=""/><br /><sub><b>Mathieu Dutour</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=mathieudutour" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://jevakallio.dev/"><img src="https://avatars1.githubusercontent.com/u/1203949?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Jani Eväkallio</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jevakallio" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jevakallio" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://joeprevite.com/"><img src="https://avatars3.githubusercontent.com/u/3806031?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Joe Previte</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jsjoeio" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jsjoeio" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/riccardoferretti"><img src="https://avatars3.githubusercontent.com/u/457005?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Riccardo</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=riccardoferretti" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=riccardoferretti" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://ojanaho.com/"><img src="https://avatars0.githubusercontent.com/u/2180090?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Janne Ojanaho</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jojanaho" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jojanaho" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://bypaulshen.com/"><img src="https://avatars3.githubusercontent.com/u/2266187?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Paul Shen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=paulshen" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/coffenbacher"><img src="https://avatars0.githubusercontent.com/u/245867?v=4?s=60" width="60px;" alt=""/><br /><sub><b>coffenbacher</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=coffenbacher" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://mathieu.dutour.me/"><img src="https://avatars2.githubusercontent.com/u/3254314?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Mathieu Dutour</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=mathieudutour" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/presidentelect"><img src="https://avatars2.githubusercontent.com/u/1242300?v=4" width="60px;" alt=""/><br /><sub><b>Michael Hansen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=presidentelect" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://klickverbot.at/"><img src="https://avatars1.githubusercontent.com/u/19335?v=4" width="60px;" alt=""/><br /><sub><b>David Nadlinger</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dnadlinger" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://pluckd.co/"><img src="https://avatars2.githubusercontent.com/u/20598571?v=4" width="60px;" alt=""/><br /><sub><b>Fernando</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MrCordeiro" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/jfgonzalez7"><img src="https://avatars3.githubusercontent.com/u/58857736?v=4" width="60px;" alt=""/><br /><sub><b>Juan Gonzalez</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jfgonzalez7" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.louiechristie.com/"><img src="https://avatars1.githubusercontent.com/u/6807448?v=4" width="60px;" alt=""/><br /><sub><b>Louie Christie</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=louiechristie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://supersandro.de/"><img src="https://avatars2.githubusercontent.com/u/7258858?v=4" width="60px;" alt=""/><br /><sub><b>Sandro</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=SuperSandro2000" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Skn0tt"><img src="https://avatars1.githubusercontent.com/u/14912729?v=4" width="60px;" alt=""/><br /><sub><b>Simon Knott</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Skn0tt" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/presidentelect"><img src="https://avatars2.githubusercontent.com/u/1242300?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Michael Hansen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=presidentelect" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://klickverbot.at/"><img src="https://avatars1.githubusercontent.com/u/19335?v=4?s=60" width="60px;" alt=""/><br /><sub><b>David Nadlinger</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dnadlinger" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://pluckd.co/"><img src="https://avatars2.githubusercontent.com/u/20598571?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Fernando</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MrCordeiro" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/jfgonzalez7"><img src="https://avatars3.githubusercontent.com/u/58857736?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Juan Gonzalez</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jfgonzalez7" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.louiechristie.com/"><img src="https://avatars1.githubusercontent.com/u/6807448?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Louie Christie</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=louiechristie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://supersandro.de/"><img src="https://avatars2.githubusercontent.com/u/7258858?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Sandro</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=SuperSandro2000" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Skn0tt"><img src="https://avatars1.githubusercontent.com/u/14912729?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Simon Knott</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Skn0tt" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://styfle.dev/"><img src="https://avatars1.githubusercontent.com/u/229881?v=4" width="60px;" alt=""/><br /><sub><b>Steven</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=styfle" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Georift"><img src="https://avatars2.githubusercontent.com/u/859430?v=4" width="60px;" alt=""/><br /><sub><b>Tim</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Georift" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/sauravkhdoolia"><img src="https://avatars1.githubusercontent.com/u/34188267?v=4" width="60px;" alt=""/><br /><sub><b>Saurav Khdoolia</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=sauravkhdoolia" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://anku.netlify.com/"><img src="https://avatars1.githubusercontent.com/u/22813027?v=4" width="60px;" alt=""/><br /><sub><b>Ankit Tiwari</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=anku255" title="Documentation">📖</a> <a href="https://github.com/foambubble/foam/commits?author=anku255" title="Tests">⚠️</a> <a href="https://github.com/foambubble/foam/commits?author=anku255" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ayushbaweja"><img src="https://avatars1.githubusercontent.com/u/44344063?v=4" width="60px;" alt=""/><br /><sub><b>Ayush Baweja</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ayushbaweja" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/TaiChi-IO"><img src="https://avatars3.githubusercontent.com/u/65092992?v=4" width="60px;" alt=""/><br /><sub><b>TaiChi-IO</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=TaiChi-IO" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/juanfrank77"><img src="https://avatars1.githubusercontent.com/u/12146882?v=4" width="60px;" alt=""/><br /><sub><b>Juan F Gonzalez </b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=juanfrank77" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://styfle.dev/"><img src="https://avatars1.githubusercontent.com/u/229881?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Steven</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=styfle" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Georift"><img src="https://avatars2.githubusercontent.com/u/859430?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Tim</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Georift" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/sauravkhdoolia"><img src="https://avatars1.githubusercontent.com/u/34188267?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Saurav Khdoolia</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=sauravkhdoolia" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://anku.netlify.com/"><img src="https://avatars1.githubusercontent.com/u/22813027?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Ankit Tiwari</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=anku255" title="Documentation">📖</a> <a href="https://github.com/foambubble/foam/commits?author=anku255" title="Tests">⚠️</a> <a href="https://github.com/foambubble/foam/commits?author=anku255" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ayushbaweja"><img src="https://avatars1.githubusercontent.com/u/44344063?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Ayush Baweja</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ayushbaweja" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/TaiChi-IO"><img src="https://avatars3.githubusercontent.com/u/65092992?v=4?s=60" width="60px;" alt=""/><br /><sub><b>TaiChi-IO</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=TaiChi-IO" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/juanfrank77"><img src="https://avatars1.githubusercontent.com/u/12146882?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Juan F Gonzalez </b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=juanfrank77" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://sanketdg.github.io"><img src="https://avatars3.githubusercontent.com/u/8980971?v=4" width="60px;" alt=""/><br /><sub><b>Sanket Dasgupta</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=SanketDG" title="Documentation">📖</a> <a href="https://github.com/foambubble/foam/commits?author=SanketDG" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/nstafie"><img src="https://avatars1.githubusercontent.com/u/10801854?v=4" width="60px;" alt=""/><br /><sub><b>Nicholas Stafie</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=nstafie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/francishamel"><img src="https://avatars3.githubusercontent.com/u/36383308?v=4" width="60px;" alt=""/><br /><sub><b>Francis Hamel</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=francishamel" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://digiguru.co.uk"><img src="https://avatars1.githubusercontent.com/u/619436?v=4" width="60px;" alt=""/><br /><sub><b>digiguru</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=digiguru" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=digiguru" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/chirag-singhal"><img src="https://avatars3.githubusercontent.com/u/42653703?v=4" width="60px;" alt=""/><br /><sub><b>CHIRAG SINGHAL</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=chirag-singhal" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/lostintangent"><img src="https://avatars3.githubusercontent.com/u/116461?v=4" width="60px;" alt=""/><br /><sub><b>Jonathan Carter</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=lostintangent" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.synesthesia.co.uk"><img src="https://avatars3.githubusercontent.com/u/181399?v=4" width="60px;" alt=""/><br /><sub><b>Julian Elve</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=synesthesia" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://sanketdg.github.io"><img src="https://avatars3.githubusercontent.com/u/8980971?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Sanket Dasgupta</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=SanketDG" title="Documentation">📖</a> <a href="https://github.com/foambubble/foam/commits?author=SanketDG" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/nstafie"><img src="https://avatars1.githubusercontent.com/u/10801854?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Nicholas Stafie</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=nstafie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/francishamel"><img src="https://avatars3.githubusercontent.com/u/36383308?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Francis Hamel</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=francishamel" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://digiguru.co.uk"><img src="https://avatars1.githubusercontent.com/u/619436?v=4?s=60" width="60px;" alt=""/><br /><sub><b>digiguru</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=digiguru" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=digiguru" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/chirag-singhal"><img src="https://avatars3.githubusercontent.com/u/42653703?v=4?s=60" width="60px;" alt=""/><br /><sub><b>CHIRAG SINGHAL</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=chirag-singhal" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/lostintangent"><img src="https://avatars3.githubusercontent.com/u/116461?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Jonathan Carter</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=lostintangent" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.synesthesia.co.uk"><img src="https://avatars3.githubusercontent.com/u/181399?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Julian Elve</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=synesthesia" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/thomaskoppelaar"><img src="https://avatars3.githubusercontent.com/u/36331365?v=4" width="60px;" alt=""/><br /><sub><b>Thomas Koppelaar</b></sub></a><br /><a href="#question-thomaskoppelaar" title="Answering Questions">💬</a> <a href="https://github.com/foambubble/foam/commits?author=thomaskoppelaar" title="Code">💻</a> <a href="#userTesting-thomaskoppelaar" title="User Testing">📓</a></td>
|
||||
<td align="center"><a href="http://www.akshaymehra.com"><img src="https://avatars1.githubusercontent.com/u/8671497?v=4" width="60px;" alt=""/><br /><sub><b>Akshay</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MehraAkshay" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://johnlindquist.com"><img src="https://avatars0.githubusercontent.com/u/36073?v=4" width="60px;" alt=""/><br /><sub><b>John Lindquist</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=johnlindquist" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ashwin.run/"><img src="https://avatars2.githubusercontent.com/u/1689183?v=4" width="60px;" alt=""/><br /><sub><b>Ashwin Ramaswami</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=epicfaace" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Klaudioz"><img src="https://avatars1.githubusercontent.com/u/632625?v=4" width="60px;" alt=""/><br /><sub><b>Claudio Canales</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Klaudioz" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/vitaly-pevgonen"><img src="https://avatars0.githubusercontent.com/u/6272738?v=4" width="60px;" alt=""/><br /><sub><b>vitaly-pevgonen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=vitaly-pevgonen" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://dshemetov.github.io"><img src="https://avatars0.githubusercontent.com/u/1810426?v=4" width="60px;" alt=""/><br /><sub><b>Dmitry Shemetov</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dshemetov" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/thomaskoppelaar"><img src="https://avatars3.githubusercontent.com/u/36331365?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Thomas Koppelaar</b></sub></a><br /><a href="#question-thomaskoppelaar" title="Answering Questions">💬</a> <a href="https://github.com/foambubble/foam/commits?author=thomaskoppelaar" title="Code">💻</a> <a href="#userTesting-thomaskoppelaar" title="User Testing">📓</a></td>
|
||||
<td align="center"><a href="http://www.akshaymehra.com"><img src="https://avatars1.githubusercontent.com/u/8671497?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Akshay</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MehraAkshay" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://johnlindquist.com"><img src="https://avatars0.githubusercontent.com/u/36073?v=4?s=60" width="60px;" alt=""/><br /><sub><b>John Lindquist</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=johnlindquist" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ashwin.run/"><img src="https://avatars2.githubusercontent.com/u/1689183?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Ashwin Ramaswami</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=epicfaace" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Klaudioz"><img src="https://avatars1.githubusercontent.com/u/632625?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Claudio Canales</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Klaudioz" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/vitaly-pevgonen"><img src="https://avatars0.githubusercontent.com/u/6272738?v=4?s=60" width="60px;" alt=""/><br /><sub><b>vitaly-pevgonen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=vitaly-pevgonen" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://dshemetov.github.io"><img src="https://avatars0.githubusercontent.com/u/1810426?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Dmitry Shemetov</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dshemetov" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/hooncp"><img src="https://avatars1.githubusercontent.com/u/48883554?v=4" width="60px;" alt=""/><br /><sub><b>hooncp</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=hooncp" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://rt-canada.ca"><img src="https://avatars1.githubusercontent.com/u/13721239?v=4" width="60px;" alt=""/><br /><sub><b>Martin Laws</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=martinlaws" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://seanksmith.me"><img src="https://avatars3.githubusercontent.com/u/2085441?v=4" width="60px;" alt=""/><br /><sub><b>Sean K Smith</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=sksmith" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/kevin-neely/"><img src="https://avatars1.githubusercontent.com/u/37545028?v=4" width="60px;" alt=""/><br /><sub><b>Kevin Neely</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=kneely" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ariefrahmansyah.dev"><img src="https://avatars3.githubusercontent.com/u/8122852?v=4" width="60px;" alt=""/><br /><sub><b>Arief Rahmansyah</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ariefrahmansyah" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://vhanda.in"><img src="https://avatars2.githubusercontent.com/u/426467?v=4" width="60px;" alt=""/><br /><sub><b>Vishesh Handa</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=vHanda" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.linkedin.com/in/heroichitesh"><img src="https://avatars3.githubusercontent.com/u/37622734?v=4" width="60px;" alt=""/><br /><sub><b>Hitesh Kumar</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=HeroicHitesh" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/hooncp"><img src="https://avatars1.githubusercontent.com/u/48883554?v=4?s=60" width="60px;" alt=""/><br /><sub><b>hooncp</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=hooncp" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://rt-canada.ca"><img src="https://avatars1.githubusercontent.com/u/13721239?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Martin Laws</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=martinlaws" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://seanksmith.me"><img src="https://avatars3.githubusercontent.com/u/2085441?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Sean K Smith</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=sksmith" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/kevin-neely/"><img src="https://avatars1.githubusercontent.com/u/37545028?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Kevin Neely</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=kneely" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ariefrahmansyah.dev"><img src="https://avatars3.githubusercontent.com/u/8122852?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Arief Rahmansyah</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ariefrahmansyah" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://vhanda.in"><img src="https://avatars2.githubusercontent.com/u/426467?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Vishesh Handa</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=vHanda" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.linkedin.com/in/heroichitesh"><img src="https://avatars3.githubusercontent.com/u/37622734?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Hitesh Kumar</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=HeroicHitesh" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://spencerwoo.com"><img src="https://avatars2.githubusercontent.com/u/32114380?v=4" width="60px;" alt=""/><br /><sub><b>Spencer Woo</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=spencerwooo" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ingalless.com"><img src="https://avatars3.githubusercontent.com/u/22981941?v=4" width="60px;" alt=""/><br /><sub><b>ingalless</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ingalless" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=ingalless" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://jmg-duarte.github.io"><img src="https://avatars2.githubusercontent.com/u/15343819?v=4" width="60px;" alt=""/><br /><sub><b>José Duarte</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jmg-duarte" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jmg-duarte" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.yenly.wtf"><img src="https://avatars1.githubusercontent.com/u/6759658?v=4" width="60px;" alt=""/><br /><sub><b>Yenly</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=yenly" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.hikerpig.cn"><img src="https://avatars1.githubusercontent.com/u/2259688?v=4" width="60px;" alt=""/><br /><sub><b>hikerpig</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=hikerpig" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://sigfried.org"><img src="https://avatars1.githubusercontent.com/u/1586931?v=4" width="60px;" alt=""/><br /><sub><b>Sigfried Gold</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Sigfried" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.tristansokol.com"><img src="https://avatars3.githubusercontent.com/u/867661?v=4" width="60px;" alt=""/><br /><sub><b>Tristan Sokol</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=tristansokol" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://spencerwoo.com"><img src="https://avatars2.githubusercontent.com/u/32114380?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Spencer Woo</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=spencerwooo" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ingalless.com"><img src="https://avatars3.githubusercontent.com/u/22981941?v=4?s=60" width="60px;" alt=""/><br /><sub><b>ingalless</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ingalless" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=ingalless" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://jmg-duarte.github.io"><img src="https://avatars2.githubusercontent.com/u/15343819?v=4?s=60" width="60px;" alt=""/><br /><sub><b>José Duarte</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jmg-duarte" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jmg-duarte" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.yenly.wtf"><img src="https://avatars1.githubusercontent.com/u/6759658?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Yenly</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=yenly" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.hikerpig.cn"><img src="https://avatars1.githubusercontent.com/u/2259688?v=4?s=60" width="60px;" alt=""/><br /><sub><b>hikerpig</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=hikerpig" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://sigfried.org"><img src="https://avatars1.githubusercontent.com/u/1586931?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Sigfried Gold</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Sigfried" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.tristansokol.com"><img src="https://avatars3.githubusercontent.com/u/867661?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Tristan Sokol</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=tristansokol" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://umbrellait.com"><img src="https://avatars0.githubusercontent.com/u/49779373?v=4" width="60px;" alt=""/><br /><sub><b>Danil Rodin</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=umbrellait-danil-rodin" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/scottjoewilliams/"><img src="https://avatars1.githubusercontent.com/u/2026866?v=4" width="60px;" alt=""/><br /><sub><b>Scott Williams</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=scott-joe" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://jackiexiao.github.io/blog"><img src="https://avatars2.githubusercontent.com/u/18050469?v=4" width="60px;" alt=""/><br /><sub><b>jackiexiao</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Jackiexiao" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://generativist.substack.com/"><img src="https://avatars3.githubusercontent.com/u/78835?v=4" width="60px;" alt=""/><br /><sub><b>John B Nelson</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jbn" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/asifm"><img src="https://avatars2.githubusercontent.com/u/3958387?v=4" width="60px;" alt=""/><br /><sub><b>Asif Mehedi</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=asifm" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/litanlitudan"><img src="https://avatars2.githubusercontent.com/u/4970420?v=4" width="60px;" alt=""/><br /><sub><b>Tan Li</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=litanlitudan" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://shaunagordon.com"><img src="https://avatars1.githubusercontent.com/u/579361?v=4" width="60px;" alt=""/><br /><sub><b>Shauna Gordon</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ShaunaGordon" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://umbrellait.com"><img src="https://avatars0.githubusercontent.com/u/49779373?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Danil Rodin</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=umbrellait-danil-rodin" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/scottjoewilliams/"><img src="https://avatars1.githubusercontent.com/u/2026866?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Scott Williams</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=scott-joe" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://jackiexiao.github.io/blog"><img src="https://avatars2.githubusercontent.com/u/18050469?v=4?s=60" width="60px;" alt=""/><br /><sub><b>jackiexiao</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Jackiexiao" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://generativist.substack.com/"><img src="https://avatars3.githubusercontent.com/u/78835?v=4?s=60" width="60px;" alt=""/><br /><sub><b>John B Nelson</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jbn" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/asifm"><img src="https://avatars2.githubusercontent.com/u/3958387?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Asif Mehedi</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=asifm" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/litanlitudan"><img src="https://avatars2.githubusercontent.com/u/4970420?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Tan Li</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=litanlitudan" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://shaunagordon.com"><img src="https://avatars1.githubusercontent.com/u/579361?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Shauna Gordon</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ShaunaGordon" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://mcluck.tech"><img src="https://avatars1.githubusercontent.com/u/1753801?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Mike Cluck</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MCluck90" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://brandonpugh.com"><img src="https://avatars1.githubusercontent.com/u/684781?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Brandon Pugh</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=bpugh" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://max.davitt.me"><img src="https://avatars1.githubusercontent.com/u/27709025?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Max Davitt</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=themaxdavitt" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://briananglin.me"><img src="https://avatars3.githubusercontent.com/u/2637602?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Brian Anglin</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=anglinb" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://deft.work"><img src="https://avatars1.githubusercontent.com/u/1455507?v=4?s=60" width="60px;" alt=""/><br /><sub><b>elswork</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=elswork" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-enable -->
|
||||
<!-- markdownlint-restore -->
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
**Foam** was inspired by [Roam Research](https://roamresearch.com/) and the [Zettelkasten methodology](https://zettelkasten.de/posts/overview)
|
||||
@@ -193,7 +201,7 @@ If that sounds like something you're interested in, I'd love to have you along o
|
||||
Foam is licensed under the [MIT license](license).
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[graph-visualisation]: features/graph-visualisation.md "Graph visualisation"
|
||||
[graph-visualisation]: features/graph-visualisation.md "Graph Visualisation"
|
||||
[backlinking]: features/backlinking.md "Backlinking"
|
||||
[recommended-extensions]: recommended-extensions.md "Recommended Extensions"
|
||||
[recipes]: recipes/recipes.md "Recipes"
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# Capture Notes With Shortcuts and GitHub Actions
|
||||
|
||||
With this #recipe you can create notes on your iOS device, which will automatically be imported into Foam.
|
||||
|
||||
## Context
|
||||
|
||||
* You use [Foam for VSCode](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode) to manage your notes
|
||||
* You wish to adopt a practice such as [A writing inbox for transient and incomplete notes](https://notes.andymatuschak.org/A%20writing%20inbox%20for%20transient%20and%20incomplete%20notes)
|
||||
* You wish to use [Shorcuts](https://support.apple.com/guide/shortcuts/welcome/ios) to capture quick notes into your Foam notes from your iOS device
|
||||
|
||||
## Other tools
|
||||
|
||||
* We assume you are familiar with how to use GitHub (if you are using Foam this is implicit)
|
||||
* You have an iOS device.
|
||||
|
||||
## Instructions
|
||||
|
||||
|
||||
1. Setup the [`foam-capture-action`]() in your GitHub Repository, to be triggered by "Workflow dispatch" events.
|
||||
|
||||
```
|
||||
name: Manually triggered workflow
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
data:
|
||||
description: 'What information to put in the knowledge base.'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
store_data:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: anglinb/foam-capture-action@main
|
||||
with:
|
||||
capture: ${{ github.event.inputs.data }}
|
||||
- run: |
|
||||
git config --local user.email "example@gmail.com"
|
||||
git config --local user.name "Your name"
|
||||
git commit -m "Captured from workflow trigger" -a
|
||||
git push -u origin master
|
||||
```
|
||||
|
||||
2. In GitHub [create a Personal Access Token](https://github.com/settings/tokens) and give it `repo` scope - make a note of the token
|
||||
3. Run this command to find your `workflow-id` to be used in the Shortcut.
|
||||
```bash
|
||||
curl \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-H "Authorization: Bearer <GITHUB_TOKEN>" \
|
||||
https://api.github.com/repos/<owner>/<repository>/actions/workflows
|
||||
```
|
||||
4. Copy this [Shortcut](https://www.icloud.com/shortcuts/57d2ed90c40e43a5badcc174ebfaaf1d) to your iOS devices and edit the contents of the last step, `GetContentsOfURL`
|
||||
- Make sure you update the URL of the shortcut step with the `owner`, `repository`, `workflow-id` (from the previous step)
|
||||
- Make sure you update the headers of the shortcut step, replaceing `[GITHUB_TOKEN]` with your Personal Access Token (from step 2)
|
||||
|
||||
5. Run the shortcut & celebrate! ✨ (You should see a GitHub Action run start and the text you entered show up in `inbox.md` in your repository.)
|
||||
@@ -82,6 +82,7 @@ A #recipe is a guide, tip or strategy for getting the most out of your Foam work
|
||||
## Workflow
|
||||
|
||||
- Capture notes from Drafts app on iOS [[capture-notes-with-drafts-pro]]
|
||||
- Capture notes from iOS Shortcuts [[capture-notes-with-shortcuts-and-github-actions]]
|
||||
|
||||
## Creative ideas
|
||||
|
||||
@@ -102,7 +103,7 @@ _See [[contribution-guide]] and [[how-to-write-recipes]]._
|
||||
[how-to-write-recipes]: how-to-write-recipes.md "How to Write Recipes"
|
||||
[todo]: ../dev/todo.md "Todo"
|
||||
[web-clipper]: web-clipper.md "Web Clipper"
|
||||
[graph-visualisation]: ../features/graph-visualisation.md "Graph visualisation"
|
||||
[graph-visualisation]: ../features/graph-visualisation.md "Graph Visualisation"
|
||||
[backlinking]: ../features/backlinking.md "Backlinking"
|
||||
[unlinked-references]: ../dev/unlinked-references.md "Unlinked references (stub)"
|
||||
[wiki-links]: ../wiki-links.md "Wiki Links"
|
||||
@@ -115,7 +116,7 @@ _See [[contribution-guide]] and [[how-to-write-recipes]]._
|
||||
[add-images-to-notes]: add-images-to-notes.md "Add images to your notes"
|
||||
[shows-image-preview-on-hover]: shows-image-preview-on-hover.md "Shows Image Preview on Hover"
|
||||
[good-first-task]: ../dev/good-first-task.md "Good First Task"
|
||||
[git-integration]: ../features/git-integration.md "Git integration"
|
||||
[git-integration]: ../features/git-integration.md "Git Integration"
|
||||
[write-your-notes-in-github-gist]: write-your-notes-in-github-gist.md "Write your notes in GitHub Gist"
|
||||
[publish-to-github-pages]: ../publishing/publish-to-github-pages.md "Github Pages"
|
||||
[publish-to-gitlab-pages]: ../publishing/publish-to-gitlab-pages.md "GitLab Pages"
|
||||
@@ -127,4 +128,5 @@ _See [[contribution-guide]] and [[how-to-write-recipes]]._
|
||||
[math-support-with-mathjax]: ../publishing/math-support-with-mathjax.md "Math Support"
|
||||
[math-support-with-katex]: ../publishing/math-support-with-katex.md "Katex Math Rendering"
|
||||
[capture-notes-with-drafts-pro]: capture-notes-with-drafts-pro.md "Capture Notes With Drafts Pro"
|
||||
[capture-notes-with-shortcuts-and-github-actions]: capture-notes-with-shortcuts-and-github-actions.md "Capture Notes With Shortcuts and GitHub Actions"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
],
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.7.4"
|
||||
"version": "0.8.0"
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"version": "0.2.0",
|
||||
"description": "Foam",
|
||||
"repository": "git@github.com:foambubble/foam.git",
|
||||
"author": "Jani Eväkallio <jani.evakallio@gmail.com>",
|
||||
"license": "MIT",
|
||||
"private": "true",
|
||||
"workspaces": [
|
||||
@@ -33,7 +32,7 @@
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "tsdx lint"
|
||||
"pre-commit": "yarn lint"
|
||||
}
|
||||
},
|
||||
"prettier": {
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
@@ -19,7 +19,7 @@ $ npm install -g foam-cli
|
||||
$ foam COMMAND
|
||||
running command...
|
||||
$ foam (-v|--version|version)
|
||||
foam-cli/0.7.4 darwin-x64 node-v12.18.2
|
||||
foam-cli/0.8.0 darwin-x64 node-v12.18.2
|
||||
$ foam --help [COMMAND]
|
||||
USAGE
|
||||
$ foam COMMAND
|
||||
@@ -65,7 +65,7 @@ EXAMPLE
|
||||
$ foam-cli janitor path-to-foam-workspace
|
||||
```
|
||||
|
||||
_See code: [src/commands/janitor.ts](https://github.com/foambubble/foam/blob/v0.7.4/src/commands/janitor.ts)_
|
||||
_See code: [src/commands/janitor.ts](https://github.com/foambubble/foam/blob/v0.8.0/src/commands/janitor.ts)_
|
||||
|
||||
## `foam migrate [WORKSPACEPATH]`
|
||||
|
||||
@@ -84,7 +84,7 @@ EXAMPLE
|
||||
Successfully generated link references and heading!
|
||||
```
|
||||
|
||||
_See code: [src/commands/migrate.ts](https://github.com/foambubble/foam/blob/v0.7.4/src/commands/migrate.ts)_
|
||||
_See code: [src/commands/migrate.ts](https://github.com/foambubble/foam/blob/v0.8.0/src/commands/migrate.ts)_
|
||||
<!-- commandsstop -->
|
||||
|
||||
## Development
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"name": "foam-cli",
|
||||
"description": "Foam CLI",
|
||||
"version": "0.7.4",
|
||||
"author": "Jani Eväkallio @jevakallio",
|
||||
"version": "0.8.0",
|
||||
"bin": {
|
||||
"foam": "./bin/run"
|
||||
},
|
||||
@@ -11,7 +10,7 @@
|
||||
"@oclif/command": "^1",
|
||||
"@oclif/config": "^1",
|
||||
"@oclif/plugin-help": "^3",
|
||||
"foam-core": "^0.7.4",
|
||||
"foam-core": "^0.8.0",
|
||||
"ora": "^4.0.4",
|
||||
"tslib": "^1"
|
||||
},
|
||||
|
||||
@@ -75,7 +75,7 @@ export default class Janitor extends Command {
|
||||
file = definitions ? applyTextEdit(file, definitions) : file;
|
||||
|
||||
if (heading || definitions) {
|
||||
return writeFileToDisk(note.source.uri, file);
|
||||
return writeFileToDisk(note.uri, file);
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
|
||||
@@ -66,7 +66,7 @@ Successfully generated link references and heading!
|
||||
if (note.title != null) {
|
||||
const kebabCasedFileName = getKebabCaseFileName(note.title);
|
||||
if (kebabCasedFileName) {
|
||||
return renameFile(note.source.uri, kebabCasedFileName);
|
||||
return renameFile(note.uri, kebabCasedFileName);
|
||||
}
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
@@ -100,7 +100,7 @@ Successfully generated link references and heading!
|
||||
file = definitions ? applyTextEdit(file, definitions) : file;
|
||||
|
||||
if (heading || definitions) {
|
||||
return writeFileToDisk(note.source.uri, file);
|
||||
return writeFileToDisk(note.uri, file);
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as fs from 'fs';
|
||||
import mockFS from 'mock-fs';
|
||||
import { URI } from 'foam-core';
|
||||
|
||||
const doesFileExist = path =>
|
||||
const doesFileExist = (path: string) =>
|
||||
fs.promises
|
||||
.access(path)
|
||||
.then(() => true)
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
# Foam Core
|
||||
|
||||
Repository for tooling used by the other modules
|
||||
This module contains the core functions, model, and API of Foam.
|
||||
It is used by its clients to integrate Foam in various use cases, from VsCode extension, to CLI, to CI integrations.
|
||||
|
||||
## Local Development
|
||||
|
||||
Below is a list of commands you will probably find useful.
|
||||
|
||||
### `npm start` or `yarn start`
|
||||
### `yarn watch`
|
||||
|
||||
Runs the project in development/watch mode. Your project will be rebuilt upon changes.
|
||||
|
||||
### `npm run build` or `yarn build`
|
||||
### `yarn build`
|
||||
|
||||
Bundles the package to the `dist` folder. The package is optimized and bundled with Rollup into multiple formats (CommonJS, UMD, and ES Module).
|
||||
|
||||
### `npm test` or `yarn test`
|
||||
### `yarn test`
|
||||
|
||||
Runs the test watcher (Jest) in an interactive mode.
|
||||
By default, runs tests related to files changed since the last commit.
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"name": "foam-core",
|
||||
"author": "Jani Eväkallio",
|
||||
"repository": "https://github.com/foambubble/foam",
|
||||
"version": "0.7.4",
|
||||
"version": "0.8.0",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createGraph } from './note-graph';
|
||||
import { createGraph } from './model/note-graph';
|
||||
import { createMarkdownParser } from './markdown-provider';
|
||||
import { FoamConfig, Foam, Services } from './index';
|
||||
import { loadPlugins } from './plugins';
|
||||
@@ -36,9 +36,8 @@ export const bootstrap = async (config: FoamConfig, services: Services) => {
|
||||
const content = await services.dataStore.read(uri);
|
||||
graph.setNote(await parser.parse(uri, content));
|
||||
});
|
||||
services.dataStore.onDidDelete(async uri => {
|
||||
const note = graph.getNoteByURI(uri);
|
||||
note && graph.deleteNote(note.id);
|
||||
services.dataStore.onDidDelete(uri => {
|
||||
graph.deleteNote(uri);
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export const LINK_REFERENCE_DEFINITION_HEADER = `[//begin]: # "Autogenerated link references for markdown compatibility"`;
|
||||
export const LINK_REFERENCE_DEFINITION_FOOTER = `[//end]: # "Autogenerated link references"`;
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Note, NoteLink } from './types';
|
||||
import { Note, NoteLink } from './model/note';
|
||||
import { URI } from './common/uri';
|
||||
import { NoteGraph, NoteGraphAPI } from './note-graph';
|
||||
import { NoteGraph, NoteGraphAPI } from './model/note-graph';
|
||||
import { FoamConfig } from './config';
|
||||
import { IDataStore, FileDataStore } from './services/datastore';
|
||||
import { ILogger } from './utils/log';
|
||||
@@ -24,6 +24,8 @@ export {
|
||||
generateHeading,
|
||||
generateLinkReferences,
|
||||
getKebabCaseFileName,
|
||||
LINK_REFERENCE_DEFINITION_HEADER,
|
||||
LINK_REFERENCE_DEFINITION_FOOTER,
|
||||
} from './janitor';
|
||||
|
||||
export { applyTextEdit } from './janitor/apply-text-edit';
|
||||
@@ -34,11 +36,6 @@ export { bootstrap } from './bootstrap';
|
||||
|
||||
export { NoteGraph, NoteGraphAPI, Note, NoteLink, URI };
|
||||
|
||||
export {
|
||||
LINK_REFERENCE_DEFINITION_HEADER,
|
||||
LINK_REFERENCE_DEFINITION_FOOTER,
|
||||
} from './definitions';
|
||||
|
||||
export interface Services {
|
||||
dataStore: IDataStore;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { Position } from 'unist';
|
||||
import GithubSlugger from 'github-slugger';
|
||||
import { GraphNote, NoteGraphAPI } from '../note-graph';
|
||||
import { Note } from '../types';
|
||||
import {
|
||||
LINK_REFERENCE_DEFINITION_HEADER,
|
||||
LINK_REFERENCE_DEFINITION_FOOTER,
|
||||
} from '../definitions';
|
||||
import { NoteGraphAPI } from '../model/note-graph';
|
||||
import { Note } from '../model/note';
|
||||
import {
|
||||
createMarkdownReferences,
|
||||
stringifyMarkdownLinkReferenceDefinition,
|
||||
} from '../markdown-provider';
|
||||
import { getHeadingFromFileName } from '../utils';
|
||||
import { getHeadingFromFileName, uriToSlug } from '../utils';
|
||||
|
||||
export const LINK_REFERENCE_DEFINITION_HEADER = `[//begin]: # "Autogenerated link references for markdown compatibility"`;
|
||||
export const LINK_REFERENCE_DEFINITION_FOOTER = `[//end]: # "Autogenerated link references"`;
|
||||
|
||||
const slugger = new GithubSlugger();
|
||||
|
||||
@@ -20,7 +19,7 @@ export interface TextEdit {
|
||||
}
|
||||
|
||||
export const generateLinkReferences = (
|
||||
note: GraphNote,
|
||||
note: Note,
|
||||
ng: NoteGraphAPI,
|
||||
includeExtensions: boolean
|
||||
): TextEdit | null => {
|
||||
@@ -30,7 +29,7 @@ export const generateLinkReferences = (
|
||||
|
||||
const markdownReferences = createMarkdownReferences(
|
||||
ng,
|
||||
note.id,
|
||||
note.uri,
|
||||
includeExtensions
|
||||
);
|
||||
|
||||
@@ -113,7 +112,7 @@ export const generateHeading = (note: Note): TextEdit | null => {
|
||||
|
||||
return {
|
||||
newText: `${paddingStart}# ${getHeadingFromFileName(
|
||||
note.slug
|
||||
uriToSlug(note.uri)
|
||||
)}${paddingEnd}`,
|
||||
range: {
|
||||
start: note.source.contentStart,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Node } from 'unist';
|
||||
import unified from 'unified';
|
||||
import markdownParse from 'remark-parse';
|
||||
import wikiLinkPlugin from 'remark-wiki-link';
|
||||
@@ -7,15 +8,35 @@ import visit from 'unist-util-visit';
|
||||
import { Parent, Point } from 'unist';
|
||||
import detectNewline from 'detect-newline';
|
||||
import os from 'os';
|
||||
import { NoteGraphAPI } from './note-graph';
|
||||
import { NoteLinkDefinition, Note, NoteParser } from './types';
|
||||
import { NoteGraphAPI } from './model/note-graph';
|
||||
import { NoteLinkDefinition, Note, NoteParser } from './model/note';
|
||||
import { dropExtension, extractHashtags, extractTagsFromProp } from './utils';
|
||||
import { uriToSlug, computeRelativePath, getBasename } from './utils/uri';
|
||||
import { ID } from './types';
|
||||
import {
|
||||
uriToSlug,
|
||||
computeRelativePath,
|
||||
getBasename,
|
||||
parseUri,
|
||||
} from './utils/uri';
|
||||
import { ParserPlugin } from './plugins';
|
||||
import { Logger } from './utils/log';
|
||||
import { URI } from './common/uri';
|
||||
|
||||
/**
|
||||
* Traverses all the children of the given node, extracts
|
||||
* the text from them, and returns it concatenated.
|
||||
*
|
||||
* @param root the node from which to start collecting text
|
||||
*/
|
||||
const getTextFromChildren = (root: Node): string => {
|
||||
let text = '';
|
||||
visit(root, 'text', node => {
|
||||
if (node.type === 'text') {
|
||||
text = text + node.value;
|
||||
}
|
||||
});
|
||||
return text;
|
||||
};
|
||||
|
||||
const tagsPlugin: ParserPlugin = {
|
||||
name: 'tags',
|
||||
onWillVisitTree: (tree, note) => {
|
||||
@@ -41,7 +62,7 @@ const titlePlugin: ParserPlugin = {
|
||||
},
|
||||
onDidVisitTree: (tree, note) => {
|
||||
if (note.title == null) {
|
||||
note.title = getBasename(note.source.uri);
|
||||
note.title = getBasename(note.uri);
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -56,6 +77,19 @@ const wikilinkPlugin: ParserPlugin = {
|
||||
position: node.position!,
|
||||
});
|
||||
}
|
||||
if (node.type === 'link') {
|
||||
const targetUri = (node as any).url;
|
||||
const uri = parseUri(note.uri, targetUri);
|
||||
if (uri.scheme !== 'file' || uri.path === note.uri.path) {
|
||||
return;
|
||||
}
|
||||
const label = getTextFromChildren(node);
|
||||
note.links.push({
|
||||
type: 'link',
|
||||
target: targetUri,
|
||||
label: label,
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -126,14 +160,13 @@ export function createMarkdownParser(extraPlugins: ParserPlugin[]): NoteParser {
|
||||
const eol = detectNewline(markdown) || os.EOL;
|
||||
|
||||
var note: Note = {
|
||||
slug: uriToSlug(uri),
|
||||
uri: uri,
|
||||
properties: {},
|
||||
title: null,
|
||||
tags: new Set(),
|
||||
links: [],
|
||||
definitions: [],
|
||||
source: {
|
||||
uri: uri,
|
||||
text: markdown,
|
||||
contentStart: tree.position!.start,
|
||||
end: tree.position!.end,
|
||||
@@ -236,23 +269,26 @@ export function stringifyMarkdownLinkReferenceDefinition(
|
||||
}
|
||||
export function createMarkdownReferences(
|
||||
graph: NoteGraphAPI,
|
||||
noteId: ID,
|
||||
noteUri: URI,
|
||||
includeExtension: boolean
|
||||
): NoteLinkDefinition[] {
|
||||
const source = graph.getNote(noteId);
|
||||
const source = graph.getNote(noteUri);
|
||||
|
||||
// Should never occur since we're already in a file,
|
||||
// but better safe than sorry.
|
||||
if (!source) {
|
||||
console.warn(
|
||||
`Note ${noteId} was not added to NoteGraph before attempting to generate markdown reference list`
|
||||
`Note ${noteUri} was not added to NoteGraph before attempting to generate markdown reference list`
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
return graph
|
||||
.getForwardLinks(noteId)
|
||||
.getForwardLinks(noteUri)
|
||||
.map(link => {
|
||||
if (link.link.type !== 'wikilink') {
|
||||
return null;
|
||||
}
|
||||
let target = graph.getNote(link.to);
|
||||
// if we don't find the target by ID we search the graph by slug
|
||||
if (!target) {
|
||||
@@ -268,15 +304,12 @@ export function createMarkdownReferences(
|
||||
// but int the future we may want to surface these too
|
||||
if (!target) {
|
||||
Logger.info(
|
||||
`Warning: Link '${link.to}' in '${noteId}' points to a non-existing note.`
|
||||
`Warning: Link '${link.to}' in '${noteUri}' points to a non-existing note.`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const relativePath = computeRelativePath(
|
||||
source.source.uri,
|
||||
target.source.uri
|
||||
);
|
||||
const relativePath = computeRelativePath(source.uri, target.uri);
|
||||
|
||||
const pathToNote = includeExtension
|
||||
? relativePath
|
||||
@@ -286,7 +319,7 @@ export function createMarkdownReferences(
|
||||
return {
|
||||
label: link.link.slug,
|
||||
url: pathToNote,
|
||||
title: target.title || target.slug,
|
||||
title: target.title || uriToSlug(target.uri),
|
||||
};
|
||||
})
|
||||
.filter(Boolean)
|
||||
|
||||
171
packages/foam-core/src/model/note-graph.ts
Normal file
171
packages/foam-core/src/model/note-graph.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import { Graph } from 'graphlib';
|
||||
import { URI } from '../common/uri';
|
||||
import { Note, NoteLink } from '../model/note';
|
||||
import {
|
||||
computeRelativeURI,
|
||||
nameToSlug,
|
||||
isSome,
|
||||
uriToSlug,
|
||||
parseUri,
|
||||
} from '../utils';
|
||||
import { Event, Emitter } from '../common/event';
|
||||
|
||||
export interface GraphConnection {
|
||||
from: URI;
|
||||
to: URI;
|
||||
link: NoteLink;
|
||||
}
|
||||
|
||||
export type NoteGraphEventHandler = (e: { note: Note }) => void;
|
||||
|
||||
export type NotesQuery = { slug: string } | { title: string };
|
||||
|
||||
export interface NoteGraphAPI {
|
||||
setNote(note: Note): Note;
|
||||
deleteNote(noteUri: URI): Note | null;
|
||||
getNotes(query?: NotesQuery): Note[];
|
||||
getNote(noteUri: URI): Note | null;
|
||||
getAllLinks(noteUri: URI): GraphConnection[];
|
||||
getForwardLinks(noteUri: URI): GraphConnection[];
|
||||
getBacklinks(noteUri: URI): GraphConnection[];
|
||||
onDidAddNote: Event<Note>;
|
||||
onDidUpdateNote: Event<Note>;
|
||||
onDidDeleteNote: Event<Note>;
|
||||
}
|
||||
|
||||
export type Middleware = (next: NoteGraphAPI) => Partial<NoteGraphAPI>;
|
||||
|
||||
export const createGraph = (middlewares: Middleware[]): NoteGraphAPI => {
|
||||
const graph: NoteGraphAPI = new NoteGraph();
|
||||
return middlewares.reduce((acc, m) => backfill(acc, m), graph);
|
||||
};
|
||||
|
||||
const uriToId = (uri: URI) => uri.path;
|
||||
|
||||
export class NoteGraph implements NoteGraphAPI {
|
||||
onDidAddNote: Event<Note>;
|
||||
onDidUpdateNote: Event<Note>;
|
||||
onDidDeleteNote: Event<Note>;
|
||||
|
||||
private graph: Graph;
|
||||
private onDidAddNoteEmitter = new Emitter<Note>();
|
||||
private onDidUpdateNoteEmitter = new Emitter<Note>();
|
||||
private onDidDeleteEmitter = new Emitter<Note>();
|
||||
|
||||
constructor() {
|
||||
this.graph = new Graph();
|
||||
this.onDidAddNote = this.onDidAddNoteEmitter.event;
|
||||
this.onDidUpdateNote = this.onDidUpdateNoteEmitter.event;
|
||||
this.onDidDeleteNote = this.onDidDeleteEmitter.event;
|
||||
}
|
||||
|
||||
public setNote(note: Note): Note {
|
||||
const oldNote = this.getNote(note.uri);
|
||||
if (isSome(oldNote)) {
|
||||
this.removeForwardLinks(note.uri);
|
||||
}
|
||||
this.graph.setNode(uriToId(note.uri), note);
|
||||
note.links.forEach(link => {
|
||||
let targetUri = null;
|
||||
if (link.type === 'wikilink') {
|
||||
const definitionUri = note.definitions.find(
|
||||
def => def.label === link.slug
|
||||
)?.url;
|
||||
targetUri = computeRelativeURI(note.uri, definitionUri ?? link.slug);
|
||||
} else {
|
||||
targetUri = parseUri(note.uri, link.target);
|
||||
}
|
||||
const connection: GraphConnection = {
|
||||
from: note.uri,
|
||||
to: targetUri,
|
||||
link: link,
|
||||
};
|
||||
this.graph.setEdge(uriToId(note.uri), uriToId(targetUri), connection);
|
||||
});
|
||||
isSome(oldNote)
|
||||
? this.onDidUpdateNoteEmitter.fire(note)
|
||||
: this.onDidAddNoteEmitter.fire(note);
|
||||
return note;
|
||||
}
|
||||
|
||||
public deleteNote(noteUri: URI): Note | null {
|
||||
return this.doDelete(noteUri, true);
|
||||
}
|
||||
|
||||
private doDelete(noteUri: URI, fireEvent: boolean): Note | null {
|
||||
const note = this.getNote(noteUri);
|
||||
if (isSome(note)) {
|
||||
if (this.getBacklinks(noteUri).length >= 1) {
|
||||
this.graph.setNode(uriToId(noteUri), null); // Changes node to the "no file" style
|
||||
} else {
|
||||
this.graph.removeNode(uriToId(noteUri));
|
||||
}
|
||||
fireEvent && this.onDidDeleteEmitter.fire(note);
|
||||
}
|
||||
return note;
|
||||
}
|
||||
|
||||
public getNotes(query?: NotesQuery): Note[] {
|
||||
// prettier-ignore
|
||||
const filterFn =
|
||||
query == null ? (note: Note | null) => note != null
|
||||
: 'slug' in query ? (note: Note | null) => note && [nameToSlug(query.slug), query.slug].includes(uriToSlug(note.uri))
|
||||
: 'title' in query ? (note: Note | null) => note?.title === query.title
|
||||
: (note: Note | null) => note != null;
|
||||
|
||||
return this.graph
|
||||
.nodes()
|
||||
.map(id => this.graph.node(id))
|
||||
.filter(filterFn);
|
||||
}
|
||||
|
||||
public getNote(noteUri: URI): Note | null {
|
||||
return this.graph.node(uriToId(noteUri)) ?? null;
|
||||
}
|
||||
|
||||
public getAllLinks(noteUri: URI): GraphConnection[] {
|
||||
return (this.graph.nodeEdges(uriToId(noteUri)) || []).map(edge =>
|
||||
this.graph.edge(edge.v, edge.w)
|
||||
);
|
||||
}
|
||||
|
||||
public getForwardLinks(noteUri: URI): GraphConnection[] {
|
||||
return (this.graph.outEdges(uriToId(noteUri)) || []).map(edge =>
|
||||
this.graph.edge(edge.v, edge.w)
|
||||
);
|
||||
}
|
||||
|
||||
public removeForwardLinks(noteUri: URI) {
|
||||
(this.graph.outEdges(uriToId(noteUri)) || []).forEach(edge => {
|
||||
this.graph.removeEdge(edge);
|
||||
});
|
||||
}
|
||||
|
||||
public getBacklinks(noteUri: URI): GraphConnection[] {
|
||||
return (this.graph.inEdges(uriToId(noteUri)) || []).map(edge =>
|
||||
this.graph.edge(edge.v, edge.w)
|
||||
);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.onDidAddNoteEmitter.dispose();
|
||||
this.onDidUpdateNoteEmitter.dispose();
|
||||
this.onDidDeleteEmitter.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
const backfill = (next: NoteGraphAPI, middleware: Middleware): NoteGraphAPI => {
|
||||
const m = middleware(next);
|
||||
return {
|
||||
setNote: m.setNote || next.setNote,
|
||||
deleteNote: m.deleteNote || next.deleteNote,
|
||||
getNotes: m.getNotes || next.getNotes,
|
||||
getNote: m.getNote || next.getNote,
|
||||
getAllLinks: m.getAllLinks || next.getAllLinks,
|
||||
getForwardLinks: m.getForwardLinks || next.getForwardLinks,
|
||||
getBacklinks: m.getBacklinks || next.getBacklinks,
|
||||
onDidAddNote: next.onDidAddNote,
|
||||
onDidUpdateNote: next.onDidUpdateNote,
|
||||
onDidDeleteNote: next.onDidDeleteNote,
|
||||
};
|
||||
};
|
||||
@@ -1,13 +1,8 @@
|
||||
// this file can't simply be .d.ts because the TS compiler wouldn't copy it to the dist directory
|
||||
// see https://stackoverflow.com/questions/56018167/typescript-does-not-copy-d-ts-files-to-build
|
||||
import { Position, Point } from 'unist';
|
||||
import { URI } from './common/uri';
|
||||
import { URI } from '../common/uri';
|
||||
export { Position, Point };
|
||||
|
||||
export type ID = string;
|
||||
|
||||
export interface NoteSource {
|
||||
uri: URI;
|
||||
text: string;
|
||||
contentStart: Point;
|
||||
end: Point;
|
||||
@@ -20,8 +15,13 @@ export interface WikiLink {
|
||||
position: Position;
|
||||
}
|
||||
|
||||
// at the moment we only model wikilink
|
||||
export type NoteLink = WikiLink;
|
||||
export interface DirectLink {
|
||||
type: 'link';
|
||||
label: string;
|
||||
target: string;
|
||||
}
|
||||
|
||||
export type NoteLink = WikiLink | DirectLink;
|
||||
|
||||
export interface NoteLinkDefinition {
|
||||
label: string;
|
||||
@@ -31,8 +31,8 @@ export interface NoteLinkDefinition {
|
||||
}
|
||||
|
||||
export interface Note {
|
||||
uri: URI;
|
||||
title: string | null;
|
||||
slug: string; // note: this slug is not necessarily unique
|
||||
properties: any;
|
||||
// sections: NoteSection[]
|
||||
tags: Set<string>;
|
||||
@@ -1,175 +0,0 @@
|
||||
import { Graph } from 'graphlib';
|
||||
import { URI } from './common/uri';
|
||||
import { ID, Note, NoteLink } from './types';
|
||||
import { computeRelativeURI, nameToSlug, isSome } from './utils';
|
||||
import { Event, Emitter } from './common/event';
|
||||
|
||||
export type GraphNote = Note & {
|
||||
id: ID;
|
||||
};
|
||||
|
||||
export interface GraphConnection {
|
||||
from: ID;
|
||||
to: ID;
|
||||
link: NoteLink;
|
||||
}
|
||||
|
||||
export type NoteGraphEventHandler = (e: { note: GraphNote }) => void;
|
||||
|
||||
export type NotesQuery = { slug: string } | { title: string };
|
||||
|
||||
export interface NoteGraphAPI {
|
||||
setNote(note: Note): GraphNote;
|
||||
deleteNote(noteId: ID): GraphNote | null;
|
||||
getNotes(query?: NotesQuery): GraphNote[];
|
||||
getNote(noteId: ID): GraphNote | null;
|
||||
getNoteByURI(uri: URI): GraphNote | null;
|
||||
getAllLinks(noteId: ID): GraphConnection[];
|
||||
getForwardLinks(noteId: ID): GraphConnection[];
|
||||
getBacklinks(noteId: ID): GraphConnection[];
|
||||
onDidAddNote: Event<GraphNote>;
|
||||
onDidUpdateNote: Event<GraphNote>;
|
||||
onDidDeleteNote: Event<GraphNote>;
|
||||
}
|
||||
|
||||
export type Middleware = (next: NoteGraphAPI) => Partial<NoteGraphAPI>;
|
||||
|
||||
export const createGraph = (middlewares: Middleware[]): NoteGraphAPI => {
|
||||
const graph: NoteGraphAPI = new NoteGraph();
|
||||
return middlewares.reduce((acc, m) => backfill(acc, m), graph);
|
||||
};
|
||||
|
||||
export class NoteGraph implements NoteGraphAPI {
|
||||
onDidAddNote: Event<GraphNote>;
|
||||
onDidUpdateNote: Event<GraphNote>;
|
||||
onDidDeleteNote: Event<GraphNote>;
|
||||
|
||||
private graph: Graph;
|
||||
private createIdFromURI: (uri: URI) => ID;
|
||||
private onDidAddNoteEmitter = new Emitter<GraphNote>();
|
||||
private onDidUpdateNoteEmitter = new Emitter<GraphNote>();
|
||||
private onDidDeleteEmitter = new Emitter<GraphNote>();
|
||||
|
||||
constructor() {
|
||||
this.graph = new Graph();
|
||||
this.onDidAddNote = this.onDidAddNoteEmitter.event;
|
||||
this.onDidUpdateNote = this.onDidUpdateNoteEmitter.event;
|
||||
this.onDidDeleteNote = this.onDidDeleteEmitter.event;
|
||||
this.createIdFromURI = uri => uri.path;
|
||||
}
|
||||
|
||||
public setNote(note: Note): GraphNote {
|
||||
const id = this.createIdFromURI(note.source.uri);
|
||||
const oldNote = this.getNote(id);
|
||||
if (isSome(oldNote)) {
|
||||
this.removeForwardLinks(id);
|
||||
}
|
||||
const graphNote: GraphNote = {
|
||||
...note,
|
||||
id: id,
|
||||
};
|
||||
this.graph.setNode(id, graphNote);
|
||||
note.links.forEach(link => {
|
||||
const relativePath =
|
||||
note.definitions.find(def => def.label === link.slug)?.url ?? link.slug;
|
||||
const targetPath = computeRelativeURI(note.source.uri, relativePath);
|
||||
const targetId = this.createIdFromURI(targetPath);
|
||||
const connection: GraphConnection = {
|
||||
from: graphNote.id,
|
||||
to: targetId,
|
||||
link: link,
|
||||
};
|
||||
this.graph.setEdge(graphNote.id, targetId, connection);
|
||||
});
|
||||
isSome(oldNote)
|
||||
? this.onDidUpdateNoteEmitter.fire(graphNote)
|
||||
: this.onDidAddNoteEmitter.fire(graphNote);
|
||||
return graphNote;
|
||||
}
|
||||
|
||||
public deleteNote(noteId: ID): GraphNote | null {
|
||||
return this.doDelete(noteId, true);
|
||||
}
|
||||
|
||||
private doDelete(noteId: ID, fireEvent: boolean): GraphNote | null {
|
||||
const note = this.getNote(noteId);
|
||||
if (isSome(note)) {
|
||||
if (this.getBacklinks(noteId).length >= 1) {
|
||||
this.graph.setNode(noteId, null); // Changes node to the "no file" style
|
||||
} else {
|
||||
this.graph.removeNode(noteId);
|
||||
}
|
||||
fireEvent && this.onDidDeleteEmitter.fire(note);
|
||||
}
|
||||
return note;
|
||||
}
|
||||
|
||||
public getNotes(query?: NotesQuery): GraphNote[] {
|
||||
// prettier-ignore
|
||||
const filterFn =
|
||||
query == null ? (note: Note | null) => note != null
|
||||
: 'slug' in query ? (note: Note | null) => [nameToSlug(query.slug), query.slug].includes(note?.slug as string)
|
||||
: 'title' in query ? (note: Note | null) => note?.title === query.title
|
||||
: (note: Note | null) => note != null;
|
||||
|
||||
return this.graph
|
||||
.nodes()
|
||||
.map(id => this.graph.node(id))
|
||||
.filter(filterFn);
|
||||
}
|
||||
|
||||
public getNote(noteId: ID): GraphNote | null {
|
||||
return this.graph.node(noteId) ?? null;
|
||||
}
|
||||
|
||||
public getNoteByURI(uri: URI): GraphNote | null {
|
||||
return this.getNote(this.createIdFromURI(uri));
|
||||
}
|
||||
|
||||
public getAllLinks(noteId: ID): GraphConnection[] {
|
||||
return (this.graph.nodeEdges(noteId) || []).map(edge =>
|
||||
this.graph.edge(edge.v, edge.w)
|
||||
);
|
||||
}
|
||||
|
||||
public getForwardLinks(noteId: ID): GraphConnection[] {
|
||||
return (this.graph.outEdges(noteId) || []).map(edge =>
|
||||
this.graph.edge(edge.v, edge.w)
|
||||
);
|
||||
}
|
||||
|
||||
public removeForwardLinks(noteId: ID) {
|
||||
(this.graph.outEdges(noteId) || []).forEach(edge => {
|
||||
this.graph.removeEdge(edge);
|
||||
});
|
||||
}
|
||||
|
||||
public getBacklinks(noteId: ID): GraphConnection[] {
|
||||
return (this.graph.inEdges(noteId) || []).map(edge =>
|
||||
this.graph.edge(edge.v, edge.w)
|
||||
);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.onDidAddNoteEmitter.dispose();
|
||||
this.onDidUpdateNoteEmitter.dispose();
|
||||
this.onDidDeleteEmitter.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
const backfill = (next: NoteGraphAPI, middleware: Middleware): NoteGraphAPI => {
|
||||
const m = middleware(next);
|
||||
return {
|
||||
setNote: m.setNote || next.setNote,
|
||||
deleteNote: m.deleteNote || next.deleteNote,
|
||||
getNotes: m.getNotes || next.getNotes,
|
||||
getNote: m.getNote || next.getNote,
|
||||
getNoteByURI: m.getNoteByURI || next.getNoteByURI,
|
||||
getAllLinks: m.getAllLinks || next.getAllLinks,
|
||||
getForwardLinks: m.getForwardLinks || next.getForwardLinks,
|
||||
getBacklinks: m.getBacklinks || next.getBacklinks,
|
||||
onDidAddNote: next.onDidAddNote,
|
||||
onDidUpdateNote: next.onDidUpdateNote,
|
||||
onDidDeleteNote: next.onDidDeleteNote,
|
||||
};
|
||||
};
|
||||
@@ -2,8 +2,8 @@ import * as fs from 'fs';
|
||||
import path from 'path';
|
||||
import { Node } from 'unist';
|
||||
import { isNotNull } from '../utils';
|
||||
import { Middleware } from '../note-graph';
|
||||
import { Note } from '../types';
|
||||
import { Middleware } from '../model/note-graph';
|
||||
import { Note } from '../model/note';
|
||||
import unified from 'unified';
|
||||
import { FoamConfig } from '../config';
|
||||
import { Logger } from '../utils/log';
|
||||
|
||||
@@ -105,7 +105,7 @@ export class FileDataStore implements IDataStore, IDisposable {
|
||||
|
||||
if (isSome(watcher)) {
|
||||
this._disposables.push(
|
||||
watcher.onDidCreate(async uri => {
|
||||
watcher.onDidCreate(uri => {
|
||||
if (this.isMatch(uri)) {
|
||||
Logger.info(`Created: ${uri.path}`);
|
||||
this.onDidCreateEmitter.fire(uri);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { posix } from 'path';
|
||||
import GithubSlugger from 'github-slugger';
|
||||
import { ID } from '../types';
|
||||
import { hash } from './core';
|
||||
import { URI } from '../common/uri';
|
||||
|
||||
@@ -12,7 +11,7 @@ export const nameToSlug = (noteName: string): string => {
|
||||
return GithubSlugger.slug(noteName);
|
||||
};
|
||||
|
||||
export const hashURI = (uri: URI): ID => {
|
||||
export const hashURI = (uri: URI): string => {
|
||||
return hash(posix.normalize(uri.path));
|
||||
};
|
||||
|
||||
@@ -36,3 +35,25 @@ export const computeRelativeURI = (
|
||||
path: posix.join(posix.dirname(reference.path), slug),
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses a URI from value, taking into consideration possible relative paths.
|
||||
*
|
||||
* @param reference the URI to use as reference in case value is a relative path
|
||||
* @param value the value to parse for a URI
|
||||
* @returns the URI from the given value. In case of a relative path, the URI will take into account
|
||||
* the reference from which it is computed
|
||||
*/
|
||||
export const parseUri = (reference: URI, value: string): URI => {
|
||||
let uri = URI.parse(value);
|
||||
if (uri.scheme === 'file' && !value.startsWith('/')) {
|
||||
const [path, fragment] = value.split('#');
|
||||
uri = path.length > 0 ? computeRelativeURI(reference, path) : reference;
|
||||
if (fragment) {
|
||||
uri = uri.with({
|
||||
fragment: fragment,
|
||||
});
|
||||
}
|
||||
}
|
||||
return uri;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NoteGraph, createGraph } from '../src/note-graph';
|
||||
import { NoteLinkDefinition, Note } from '../src/types';
|
||||
import { NoteGraph, createGraph } from '../src/model/note-graph';
|
||||
import { NoteLinkDefinition, Note } from '../src/model/note';
|
||||
import { uriToSlug } from '../src/utils';
|
||||
import { URI } from '../src/common/uri';
|
||||
import { Logger } from '../src/utils/log';
|
||||
@@ -15,32 +15,46 @@ const documentStart = position.start;
|
||||
const documentEnd = position.end;
|
||||
const eol = '\n';
|
||||
|
||||
/**
|
||||
* Turns a string into a URI
|
||||
* The goal of this function is to make sure we are consistent in the
|
||||
* way we generate URIs (and therefore IDs) across the tests
|
||||
*/
|
||||
export const strToUri = URI.file;
|
||||
|
||||
export const createTestNote = (params: {
|
||||
uri: string;
|
||||
title?: string;
|
||||
definitions?: NoteLinkDefinition[];
|
||||
links?: { slug: string }[];
|
||||
links?: Array<{ slug: string } | { to: string }>;
|
||||
text?: string;
|
||||
}): Note => {
|
||||
return {
|
||||
uri: strToUri(params.uri),
|
||||
properties: {},
|
||||
title: params.title ?? null,
|
||||
slug: uriToSlug(URI.file(params.uri)),
|
||||
definitions: params.definitions ?? [],
|
||||
tags: new Set(),
|
||||
links: params.links
|
||||
? params.links.map(link => ({
|
||||
type: 'wikilink',
|
||||
slug: link.slug,
|
||||
position: position,
|
||||
text: 'link text',
|
||||
}))
|
||||
? params.links.map(link =>
|
||||
'slug' in link
|
||||
? {
|
||||
type: 'wikilink',
|
||||
slug: link.slug,
|
||||
position: position,
|
||||
text: 'link text',
|
||||
}
|
||||
: {
|
||||
type: 'link',
|
||||
target: link.to,
|
||||
label: 'link text',
|
||||
}
|
||||
)
|
||||
: [],
|
||||
source: {
|
||||
eol: eol,
|
||||
end: documentEnd,
|
||||
contentStart: documentStart,
|
||||
uri: URI.file(params.uri),
|
||||
text: params.text ?? '',
|
||||
},
|
||||
};
|
||||
@@ -56,7 +70,7 @@ describe('Note graph', () => {
|
||||
expect(
|
||||
graph
|
||||
.getNotes()
|
||||
.map(n => n.slug)
|
||||
.map(n => uriToSlug(n.uri))
|
||||
.sort()
|
||||
).toEqual(['page-a', 'page-b', 'page-c']);
|
||||
});
|
||||
@@ -73,7 +87,10 @@ describe('Note graph', () => {
|
||||
graph.setNote(createTestNote({ uri: '/page-c.md' }));
|
||||
|
||||
expect(
|
||||
graph.getForwardLinks(noteB.id).map(link => graph.getNote(link.to)!.slug)
|
||||
graph
|
||||
.getForwardLinks(noteB.uri)
|
||||
.map(link => graph.getNote(link.to)!.uri)
|
||||
.map(uriToSlug)
|
||||
).toEqual(['page-a']);
|
||||
});
|
||||
|
||||
@@ -89,14 +106,50 @@ describe('Note graph', () => {
|
||||
graph.setNote(createTestNote({ uri: '/page-c.md' }));
|
||||
|
||||
expect(
|
||||
graph.getBacklinks(noteA.id).map(link => graph.getNote(link.from)!.slug)
|
||||
graph
|
||||
.getBacklinks(noteA.uri)
|
||||
.map(link => graph.getNote(link.from)!.uri)
|
||||
.map(uriToSlug)
|
||||
).toEqual(['page-b']);
|
||||
});
|
||||
|
||||
it('Detects backlinks of direct links', () => {
|
||||
const graph = new NoteGraph();
|
||||
|
||||
const noteA = createTestNote({
|
||||
uri: '/path/to/page-a.md',
|
||||
});
|
||||
// connected via absolute path
|
||||
const noteB = createTestNote({
|
||||
uri: '/page-b.md',
|
||||
links: [{ to: noteA.uri.path }],
|
||||
});
|
||||
// connected via relative path
|
||||
const noteC = createTestNote({
|
||||
uri: '/path/docs/page-c.md',
|
||||
links: [{ to: '../to/page-a.md' }],
|
||||
});
|
||||
// not connected - wrong path
|
||||
const noteD = createTestNote({
|
||||
uri: '/path/docs/page-d.md',
|
||||
links: [{ to: '../to/another/page-a.md' }],
|
||||
});
|
||||
graph.setNote(noteA);
|
||||
graph.setNote(noteB);
|
||||
graph.setNote(noteC);
|
||||
graph.setNote(noteD);
|
||||
|
||||
expect(
|
||||
graph
|
||||
.getBacklinks(noteA.uri)
|
||||
.map(link => graph.getNote(link.from)!.uri)
|
||||
.map(uriToSlug)
|
||||
).toEqual(['page-b', 'page-c']);
|
||||
});
|
||||
it('Returns null when accessing non-existing node', () => {
|
||||
const graph = new NoteGraph();
|
||||
graph.setNote(createTestNote({ uri: 'page-a' }));
|
||||
expect(graph.getNote('non-existing')).toBeNull();
|
||||
expect(graph.getNote(strToUri('non-existing'))).toBeNull();
|
||||
});
|
||||
|
||||
it('Allows adding edges to non-existing documents', () => {
|
||||
@@ -108,7 +161,7 @@ describe('Note graph', () => {
|
||||
})
|
||||
);
|
||||
|
||||
expect(graph.getNote('non-existing')).toBeNull();
|
||||
expect(graph.getNote(strToUri('non-existing'))).toBeNull();
|
||||
});
|
||||
|
||||
it('Updates links when modifying note', () => {
|
||||
@@ -123,13 +176,22 @@ describe('Note graph', () => {
|
||||
const noteC = graph.setNote(createTestNote({ uri: '/page-c.md' }));
|
||||
|
||||
expect(
|
||||
graph.getForwardLinks(noteB.id).map(link => graph.getNote(link.to)?.slug)
|
||||
graph
|
||||
.getForwardLinks(noteB.uri)
|
||||
.map(link => graph.getNote(link.to)!.uri)
|
||||
.map(uriToSlug)
|
||||
).toEqual(['page-a']);
|
||||
expect(
|
||||
graph.getBacklinks(noteA.id).map(link => graph.getNote(link.from)?.slug)
|
||||
graph
|
||||
.getBacklinks(noteA.uri)
|
||||
.map(link => graph.getNote(link.from)!.uri)
|
||||
.map(uriToSlug)
|
||||
).toEqual(['page-b']);
|
||||
expect(
|
||||
graph.getBacklinks(noteC.id).map(link => graph.getNote(link.from)?.slug)
|
||||
graph
|
||||
.getBacklinks(noteC.uri)
|
||||
.map(link => graph.getNote(link.from)!.uri)
|
||||
.map(uriToSlug)
|
||||
).toEqual([]);
|
||||
|
||||
graph.setNote(
|
||||
@@ -140,19 +202,31 @@ describe('Note graph', () => {
|
||||
);
|
||||
|
||||
expect(
|
||||
graph.getForwardLinks(noteB.id).map(link => graph.getNote(link.to)?.slug)
|
||||
graph
|
||||
.getForwardLinks(noteB.uri)
|
||||
.map(link => graph.getNote(link.to)!.uri)
|
||||
.map(uriToSlug)
|
||||
).toEqual(['page-c']);
|
||||
expect(
|
||||
graph.getBacklinks(noteA.id).map(link => graph.getNote(link.from)?.slug)
|
||||
graph
|
||||
.getBacklinks(noteA.uri)
|
||||
.map(link => graph.getNote(link.from)!.uri)
|
||||
.map(uriToSlug)
|
||||
).toEqual([]);
|
||||
expect(
|
||||
graph.getBacklinks(noteC.id).map(link => graph.getNote(link.from)?.slug)
|
||||
graph
|
||||
.getBacklinks(noteC.uri)
|
||||
.map(link => graph.getNote(link.from)!.uri)
|
||||
.map(uriToSlug)
|
||||
).toEqual(['page-b']);
|
||||
|
||||
// Tests #393: page-a should not lose its links when updated
|
||||
graph.setNote(createTestNote({ title: 'Test-C', uri: '/page-c.md' }));
|
||||
expect(
|
||||
graph.getBacklinks(noteC.id).map(link => graph.getNote(link.from)?.slug)
|
||||
graph
|
||||
.getBacklinks(noteC.uri)
|
||||
.map(link => graph.getNote(link.from)!.uri)
|
||||
.map(uriToSlug)
|
||||
).toEqual(['page-b']);
|
||||
});
|
||||
|
||||
@@ -176,17 +250,22 @@ describe('Note graph', () => {
|
||||
})
|
||||
);
|
||||
|
||||
graph.deleteNote(noteA.id);
|
||||
graph.deleteNote(noteA.uri);
|
||||
expect(
|
||||
graph.getForwardLinks(noteB.id).map(link => link?.link?.slug)
|
||||
graph.getForwardLinks(noteB.uri).map(link => (link as any)?.link?.slug)
|
||||
).toEqual(['page-a']);
|
||||
expect(graph.getNote(noteA.id)).toBeNull();
|
||||
expect(graph.getNote(noteA.uri)).toBeNull();
|
||||
|
||||
graph.deleteNote(noteC.id);
|
||||
graph.deleteNote(noteC.uri);
|
||||
expect(
|
||||
graph.getForwardLinks(noteC.id).map(link => link?.link?.slug)
|
||||
graph.getForwardLinks(noteC.uri).map(link => (link as any)?.link?.slug)
|
||||
).toEqual([]);
|
||||
expect(graph.getNotes().map(note => note.slug)).toEqual(['page-b']);
|
||||
expect(
|
||||
graph
|
||||
.getNotes()
|
||||
.map(note => note.uri)
|
||||
.map(uriToSlug)
|
||||
).toEqual(['page-b']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -201,7 +280,7 @@ describe('Graph querying', () => {
|
||||
it('finds the note by slug', () => {
|
||||
const graph = new NoteGraph();
|
||||
const note = graph.setNote(createTestNote({ uri: '/page-a.md' }));
|
||||
expect(graph.getNotes({ slug: note.slug }).length).toEqual(1);
|
||||
expect(graph.getNotes({ slug: uriToSlug(note.uri) }).length).toEqual(1);
|
||||
});
|
||||
|
||||
it('finds a note by slug when there is more than one', () => {
|
||||
@@ -262,7 +341,7 @@ describe('graph events', () => {
|
||||
const note = graph.setNote(
|
||||
createTestNote({ uri: '/dir1/page-a.md', title: 'My Title' })
|
||||
);
|
||||
graph.deleteNote(note.id);
|
||||
graph.deleteNote(note.uri);
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
listener.dispose();
|
||||
});
|
||||
@@ -270,10 +349,10 @@ describe('graph events', () => {
|
||||
const graph = new NoteGraph();
|
||||
const callback = jest.fn();
|
||||
const listener = graph.onDidDeleteNote(callback);
|
||||
const note = graph.setNote(
|
||||
graph.setNote(
|
||||
createTestNote({ uri: '/dir1/page-a.md', title: 'My Title' })
|
||||
);
|
||||
graph.deleteNote('non-existing-note');
|
||||
graph.deleteNote(strToUri('non-existing-note'));
|
||||
expect(callback).toHaveBeenCalledTimes(0);
|
||||
listener.dispose();
|
||||
});
|
||||
@@ -309,7 +388,7 @@ describe('graph events', () => {
|
||||
expect(updateCallback).toHaveBeenCalledTimes(2);
|
||||
expect(deleteCallback).toHaveBeenCalledTimes(0);
|
||||
|
||||
graph.deleteNote(note.id);
|
||||
graph.deleteNote(note.uri);
|
||||
expect(addCallback).toHaveBeenCalledTimes(1);
|
||||
expect(updateCallback).toHaveBeenCalledTimes(2);
|
||||
expect(deleteCallback).toHaveBeenCalledTimes(1);
|
||||
@@ -319,7 +398,7 @@ describe('graph events', () => {
|
||||
});
|
||||
|
||||
describe('graph middleware', () => {
|
||||
it('can intercept calls to the graph', async () => {
|
||||
it('can intercept calls to the graph', () => {
|
||||
const graph = createGraph([
|
||||
next => ({
|
||||
setNote: note => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as path from 'path';
|
||||
import { NoteGraphAPI } from '../../src/note-graph';
|
||||
import { NoteGraphAPI } from '../../src/model/note-graph';
|
||||
import { generateHeading } from '../../src/janitor';
|
||||
import { bootstrap } from '../../src/bootstrap';
|
||||
import { createConfigFromFolders } from '../../src/config';
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import * as path from 'path';
|
||||
import { NoteGraphAPI, GraphNote } from '../../src/note-graph';
|
||||
import { generateLinkReferences } from '../../src/janitor';
|
||||
import { bootstrap } from '../../src/bootstrap';
|
||||
import { createConfigFromFolders } from '../../src/config';
|
||||
import { Services } from '../../src';
|
||||
import { Services, Note, NoteGraphAPI } from '../../src';
|
||||
import { FileDataStore } from '../../src/services/datastore';
|
||||
import { Logger } from '../../src/utils/log';
|
||||
import { URI } from '../../src/common/uri';
|
||||
@@ -136,7 +135,7 @@ describe('generateLinkReferences', () => {
|
||||
* @param note the note we are adjusting for
|
||||
* @param text starting text, using a \n line separator
|
||||
*/
|
||||
function textForNote(note: GraphNote, text: string): string {
|
||||
function textForNote(note: Note, text: string): string {
|
||||
return text.split('\n').join(note.source.eol);
|
||||
}
|
||||
|
||||
@@ -149,7 +148,7 @@ function textForNote(note: GraphNote, text: string): string {
|
||||
* @param pos starting position
|
||||
*/
|
||||
function pointForNote(
|
||||
note: GraphNote,
|
||||
note: Note,
|
||||
pos: { line: number; column: number; offset: number }
|
||||
) {
|
||||
const rows = pos.line - 1;
|
||||
|
||||
@@ -2,10 +2,12 @@ import {
|
||||
createMarkdownParser,
|
||||
createMarkdownReferences,
|
||||
} from '../src/markdown-provider';
|
||||
import { NoteGraph } from '../src/note-graph';
|
||||
import { DirectLink } from '../src/model/note';
|
||||
import { NoteGraph } from '../src/model/note-graph';
|
||||
import { ParserPlugin } from '../src/plugins';
|
||||
import { URI } from '../src/common/uri';
|
||||
import { Logger } from '../src/utils/log';
|
||||
import { uriToSlug } from '../src/utils';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
@@ -51,11 +53,56 @@ describe('Markdown loader', () => {
|
||||
expect(
|
||||
graph
|
||||
.getNotes()
|
||||
.map(n => n.slug)
|
||||
.map(n => n.uri)
|
||||
.map(uriToSlug)
|
||||
.sort()
|
||||
).toEqual(['page-a', 'page-b', 'page-c', 'page-d', 'page-e']);
|
||||
});
|
||||
|
||||
it('Ingores external links', () => {
|
||||
const note = createNoteFromMarkdown(
|
||||
'/path/to/page-a.md',
|
||||
`
|
||||
this is a [link to google](https://www.google.com)
|
||||
`
|
||||
);
|
||||
expect(note.links.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('Ignores references to sections in the same file', () => {
|
||||
const note = createNoteFromMarkdown(
|
||||
'/path/to/page-a.md',
|
||||
`
|
||||
this is a [link to intro](#introduction)
|
||||
`
|
||||
);
|
||||
expect(note.links.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('Parses internal links correctly', () => {
|
||||
const note = createNoteFromMarkdown(
|
||||
'/path/to/page-a.md',
|
||||
'this is a [link to page b](../doc/page-b.md)'
|
||||
);
|
||||
expect(note.links.length).toEqual(1);
|
||||
const link = note.links[0] as DirectLink;
|
||||
expect(link.type).toEqual('link');
|
||||
expect(link.label).toEqual('link to page b');
|
||||
expect(link.target).toEqual('../doc/page-b.md');
|
||||
});
|
||||
|
||||
it('Parses links that have formatting in label', () => {
|
||||
const note = createNoteFromMarkdown(
|
||||
'/path/to/page-a.md',
|
||||
'this is [**link** with __formatting__](../doc/page-b.md)'
|
||||
);
|
||||
expect(note.links.length).toEqual(1);
|
||||
const link = note.links[0] as DirectLink;
|
||||
expect(link.type).toEqual('link');
|
||||
expect(link.label).toEqual('link with formatting');
|
||||
expect(link.target).toEqual('../doc/page-b.md');
|
||||
});
|
||||
|
||||
it('Parses wikilinks correctly', () => {
|
||||
const graph = new NoteGraph();
|
||||
const noteA = graph.setNote(createNoteFromMarkdown('/page-a.md', pageA));
|
||||
@@ -65,10 +112,16 @@ describe('Markdown loader', () => {
|
||||
graph.setNote(createNoteFromMarkdown('/page e.md', pageE));
|
||||
|
||||
expect(
|
||||
graph.getBacklinks(noteB.id).map(link => graph.getNote(link.from)!.slug)
|
||||
graph
|
||||
.getBacklinks(noteB.uri)
|
||||
.map(link => graph.getNote(link.from)!.uri)
|
||||
.map(uriToSlug)
|
||||
).toEqual(['page-a']);
|
||||
expect(
|
||||
graph.getForwardLinks(noteA.id).map(link => graph.getNote(link.to)!.slug)
|
||||
graph
|
||||
.getForwardLinks(noteA.uri)
|
||||
.map(link => graph.getNote(link.to)!.uri)
|
||||
.map(uriToSlug)
|
||||
).toEqual(['page-b', 'page-c', 'page-d', 'page-e']);
|
||||
});
|
||||
});
|
||||
@@ -78,7 +131,7 @@ describe('Note Title', () => {
|
||||
const graph = new NoteGraph();
|
||||
const note = graph.setNote(createNoteFromMarkdown('/page-a.md', pageA));
|
||||
|
||||
const pageANoteTitle = graph.getNote(note.id)!.title;
|
||||
const pageANoteTitle = graph.getNote(note.uri)!.title;
|
||||
expect(pageANoteTitle).toBe('Page A');
|
||||
});
|
||||
|
||||
@@ -93,7 +146,7 @@ This file has no heading.
|
||||
)
|
||||
);
|
||||
|
||||
const pageANoteTitle = graph.getNote(note.id)!.title;
|
||||
const pageANoteTitle = graph.getNote(note.uri)!.title;
|
||||
expect(pageANoteTitle).toEqual('page-d');
|
||||
});
|
||||
|
||||
@@ -113,7 +166,7 @@ date: 20-12-12
|
||||
)
|
||||
);
|
||||
|
||||
const pageENoteTitle = graph.getNote(note.id)!.title;
|
||||
const pageENoteTitle = graph.getNote(note.uri)!.title;
|
||||
expect(pageENoteTitle).toBe('Note Title');
|
||||
});
|
||||
|
||||
@@ -151,7 +204,7 @@ date: 20-12-12
|
||||
date: '20-12-12',
|
||||
};
|
||||
|
||||
const actual: any = graph.getNote(note.id)!.properties;
|
||||
const actual: any = graph.getNote(note.uri)!.properties;
|
||||
|
||||
expect(actual.title).toBe(expected.title);
|
||||
expect(actual.date).toBe(expected.date);
|
||||
@@ -173,7 +226,7 @@ date: 20-12-12
|
||||
|
||||
const expected = {};
|
||||
|
||||
const actual = graph.getNote(note.id)!.properties;
|
||||
const actual = graph.getNote(note.uri)!.properties;
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
@@ -196,7 +249,7 @@ title: - one
|
||||
|
||||
const expected = {};
|
||||
|
||||
const actual = graph.getNote(note.id)!.properties;
|
||||
const actual = graph.getNote(note.uri)!.properties;
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
@@ -211,7 +264,7 @@ describe('wikilinks definitions', () => {
|
||||
graph.setNote(createNoteFromMarkdown('/dir1/page-b.md', pageB));
|
||||
graph.setNote(createNoteFromMarkdown('/dir1/page-c.md', pageC));
|
||||
|
||||
const noExtRefs = createMarkdownReferences(graph, noteA.id, false);
|
||||
const noExtRefs = createMarkdownReferences(graph, noteA.uri, false);
|
||||
expect(noExtRefs.map(r => r.url)).toEqual(['page-b', 'page-c']);
|
||||
});
|
||||
|
||||
@@ -223,7 +276,7 @@ describe('wikilinks definitions', () => {
|
||||
graph.setNote(createNoteFromMarkdown('/dir1/page-b.md', pageB));
|
||||
graph.setNote(createNoteFromMarkdown('/dir1/page-c.md', pageC));
|
||||
|
||||
const extRefs = createMarkdownReferences(graph, noteA.id, true);
|
||||
const extRefs = createMarkdownReferences(graph, noteA.uri, true);
|
||||
expect(extRefs.map(r => r.url)).toEqual(['page-b.md', 'page-c.md']);
|
||||
});
|
||||
|
||||
@@ -235,7 +288,7 @@ describe('wikilinks definitions', () => {
|
||||
graph.setNote(createNoteFromMarkdown('/dir2/page-b.md', pageB));
|
||||
graph.setNote(createNoteFromMarkdown('/dir3/page-c.md', pageC));
|
||||
|
||||
const extRefs = createMarkdownReferences(graph, noteA.id, true);
|
||||
const extRefs = createMarkdownReferences(graph, noteA.uri, true);
|
||||
expect(extRefs.map(r => r.url)).toEqual([
|
||||
'../dir2/page-b.md',
|
||||
'../dir3/page-c.md',
|
||||
@@ -298,7 +351,7 @@ describe('parser plugins', () => {
|
||||
};
|
||||
const parser = createMarkdownParser([testPlugin]);
|
||||
|
||||
it('can augment the parsing of the file', async () => {
|
||||
it('can augment the parsing of the file', () => {
|
||||
const note1 = parser.parse(
|
||||
URI.file('/path/to/a'),
|
||||
`
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import path from 'path';
|
||||
import { loadPlugins } from '../src/plugins';
|
||||
import { createMarkdownParser } from '../src/markdown-provider';
|
||||
import { createGraph } from '../src/note-graph';
|
||||
import { createGraph } from '../src/model/note-graph';
|
||||
import { createTestNote } from './core.test';
|
||||
import { FoamConfig, createConfigFromObject } from '../src/config';
|
||||
import { URI } from '../src/common/uri';
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
hashURI,
|
||||
computeRelativeURI,
|
||||
extractHashtags,
|
||||
parseUri,
|
||||
} from '../src/utils';
|
||||
import { URI } from '../src/common/uri';
|
||||
import { Logger } from '../src/utils/log';
|
||||
@@ -50,6 +51,27 @@ describe('URI utils', () => {
|
||||
computeRelativeURI(URI.file('/my/file.markdown'), '../hello')
|
||||
).toEqual(URI.file('/hello.markdown'));
|
||||
});
|
||||
|
||||
describe('URI parsing', () => {
|
||||
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.with({ fragment: 'section' })],
|
||||
[
|
||||
'../relative/file.md#section',
|
||||
URI.parse('file:/path/relative/file.md#section'),
|
||||
],
|
||||
])('URI Parsing (%s) - %s', (input, exp) => {
|
||||
const result = parseUri(base, input);
|
||||
expect(result.scheme).toEqual(exp.scheme);
|
||||
expect(result.authority).toEqual(exp.authority);
|
||||
expect(result.path).toEqual(exp.path);
|
||||
expect(result.query).toEqual(exp.query);
|
||||
expect(result.fragment).toEqual(exp.fragment);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('hashtag extraction', () => {
|
||||
|
||||
3
packages/foam-vscode/.gitignore
vendored
3
packages/foam-vscode/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
out
|
||||
.vscode-test/
|
||||
*.vsix
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"tabWidth": 2
|
||||
}
|
||||
@@ -4,9 +4,36 @@ 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.8.0] - 2021-01-15
|
||||
|
||||
Features:
|
||||
- Model: Now direct links are included in the Foam model (#433)
|
||||
- Commaands: Added `Open random note` command (#440 - thanks @MCluck90)
|
||||
- Dataviz: Added graph style override from VsCode theme (#438 - thanks @jmg-duarte)
|
||||
- Dataviz: Added graph style customization based on note type (#449)
|
||||
|
||||
Fixes and Improvements:
|
||||
- Various improvements and fixes in documentation (thanks @anglinb, @themaxdavitt, @elswork)
|
||||
|
||||
## [0.7.7] - 2020-12-31
|
||||
|
||||
Fixes and Improvements:
|
||||
- Fixed word-based-suggestions (#415 #417 - thanks @bpugh!)
|
||||
- Date snippets use standard wikilink syntax (#416 - thanks @MCluck90!)
|
||||
|
||||
## [0.7.6] - 2020-12-20
|
||||
|
||||
Fixes and Improvements:
|
||||
- Fixed "Janitor" command issue in Windows (#410)
|
||||
|
||||
## [0.7.5] - 2020-12-17
|
||||
|
||||
Fixes and Improvements:
|
||||
- Fixed "Open Daily Note" command issue in Windows (#407)
|
||||
|
||||
## [0.7.4] - 2020-12-16
|
||||
|
||||
Fixes and Improvement:
|
||||
Fixes and Improvements:
|
||||
- Fixed a bug that was causing Foam to not work correctly in Windows (#391)
|
||||
|
||||
## [0.7.3] - 2020-12-13
|
||||
|
||||
@@ -6,15 +6,17 @@
|
||||
[](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode)
|
||||
|
||||
|
||||
This is the VS Code extension for [Foam](https://foambubble.github.io/foam).
|
||||
|
||||
> ℹ️ the extension doesn't do much on it's own. To learn how to use it, read [Foam documentation](https://foambubble.github.io/foam) and the [Getting started](https://foambubble.github.io/foam/#getting-started) guide.
|
||||
|
||||
> ⚠️ This is an early stage software. Use at your own peril.
|
||||
|
||||
> You can join the Foam Community on the [Foam Discord](https://discord.gg/EKFyMrVNZ9)
|
||||
|
||||
## Quick getting started
|
||||
[Foam](https://foambubble.github.io/foam) is a note-taking tool that lives within VsCode, which means you can pair it with your favorite extensions for a great editing experience.
|
||||
|
||||
Foam is open source, and allows you to create a local first, markdown based, personal knowledge base. You can also use it to publish your notes.
|
||||
|
||||
Foam is also meant to be extensible, so you can integrate with its internals to customize your knowledge base.
|
||||
|
||||
## Getting started
|
||||
|
||||
You really, _really_, **really** should read [Foam documentation](https://foambubble.github.io/foam), but if you can't be bothered, this is how to get started:
|
||||
|
||||
@@ -24,13 +26,18 @@ You really, _really_, **really** should read [Foam documentation](https://foambu
|
||||
|
||||
This will also install `Foam for VSCode`, but if you already have it installed, that's ok, just make sure you're up to date on the latest version.
|
||||
|
||||
You really, _really_, **really** should read [Foam documentation](https://foambubble.github.io/foam), but if you can't be bothered, this is how to get started:
|
||||
|
||||
## Features
|
||||
|
||||
- Connect your notes using `[[wiki-links]]`
|
||||
- Create markdown references for `[[wiki-links]]` with the `Foam: Update Markdown Reference List` command, to use your notes in a non-foam workspace
|
||||
- See how your notes are connected via a graph with the `Foam: Show Graph` command
|
||||
- Tag your notes and navigate them with the Tag Explorer
|
||||
- Connect your notes using [`[[wiki-links]]`](https://foambubble.github.io/foam/features/backlinking)
|
||||
- Create markdown [references](https://foambubble.github.io/foam/features/link-reference-definitions) for `[[wiki-links]]`, to use your notes in a non-foam workspace
|
||||
- See how your notes are connected via a [graph](https://foambubble.github.io/foam/features/graph-visualisation) with the `Foam: Show Graph` command
|
||||
- Tag your notes and navigate them with the [Tag Explorer](https://foambubble.github.io/foam/features/tags)
|
||||
- Make your notes navigable both in GitHub UI as well as GitHub Pages
|
||||
- Use [custom templates](https://foambubble.github.io/foam/features/note-templates) for your notes
|
||||
- Create a journal with [daily notes](https://foambubble.github.io/foam/features/daily-notes)
|
||||
- Explore your knowledge base with the `Foam: Open Random Note` command
|
||||
|
||||
## Requirements
|
||||
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
"name": "foam-vscode",
|
||||
"displayName": "Foam for VSCode (Wikilinks to Markdown)",
|
||||
"description": "Generate markdown reference lists from wikilinks in a workspace",
|
||||
"author": "Jani Eväkallio",
|
||||
"repository": {
|
||||
"url": "https://github.com/foambubble/foam",
|
||||
"type": "git"
|
||||
},
|
||||
"homepage": "https://github.com/foambubble/foam",
|
||||
"version": "0.7.4",
|
||||
"version": "0.8.0",
|
||||
"license": "MIT",
|
||||
"publisher": "foam",
|
||||
"engines": {
|
||||
@@ -23,6 +22,7 @@
|
||||
"onView:foam-vscode.tags-explorer",
|
||||
"onCommand:foam-vscode.update-wikilinks",
|
||||
"onCommand:foam-vscode.open-daily-note",
|
||||
"onCommand:foam-vscode.open-random-note",
|
||||
"onCommand:foam-vscode.janitor",
|
||||
"onCommand:foam-vscode.copy-without-brackets",
|
||||
"onCommand:foam-vscode.show-graph"
|
||||
@@ -56,6 +56,10 @@
|
||||
"command": "foam-vscode.open-daily-note",
|
||||
"title": "Foam: Open Daily Note"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.open-random-note",
|
||||
"title": "Foam: Open Random Note"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.janitor",
|
||||
"title": "Foam: Run Janitor (Experimental)"
|
||||
@@ -156,6 +160,11 @@
|
||||
"type": "number",
|
||||
"default": 24,
|
||||
"description": "The maximum title length before being abbreviated. Set to 0 or less to disable."
|
||||
},
|
||||
"foam.graph.style": {
|
||||
"type": "object",
|
||||
"description": "Custom graph styling settings. An example is present in the documentation.",
|
||||
"default": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -168,8 +177,9 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc -p ./",
|
||||
"test": "jest",
|
||||
"lint": "eslint src --ext ts",
|
||||
"pretest": "yarn build",
|
||||
"test": "node ./out/test/run-tests.js",
|
||||
"lint": "tsdx lint",
|
||||
"clean": "rimraf out",
|
||||
"watch": "tsc --build ./tsconfig.json ../foam-core/tsconfig.json --watch",
|
||||
"vscode:start-debugging": "yarn clean && yarn watch",
|
||||
@@ -196,12 +206,15 @@
|
||||
"eslint": "^6.8.0",
|
||||
"glob": "^7.1.6",
|
||||
"jest": "^26.2.2",
|
||||
"jest-environment-vscode": "^1.0.0",
|
||||
"jest-extended": "^0.11.5",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-jest": "^26.4.4",
|
||||
"typescript": "^3.8.3",
|
||||
"vscode-test": "^1.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"dateformat": "^3.0.3",
|
||||
"foam-core": "^0.7.4"
|
||||
"foam-core": "^0.8.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
import {
|
||||
Selection,
|
||||
Uri,
|
||||
window,
|
||||
workspace,
|
||||
WorkspaceConfiguration
|
||||
} from "vscode";
|
||||
import { dirname, join } from "path";
|
||||
import dateFormat from "dateformat";
|
||||
import * as fs from "fs";
|
||||
import { docConfig, focusNote, pathExists } from "./utils";
|
||||
import { workspace, WorkspaceConfiguration } from 'vscode';
|
||||
import { dirname, join } from 'path';
|
||||
import dateFormat from 'dateformat';
|
||||
import * as fs from 'fs';
|
||||
import { docConfig, focusNote, pathExists } from './utils';
|
||||
|
||||
async function openDailyNoteFor(date?: Date) {
|
||||
const foamConfiguration = workspace.getConfiguration("foam");
|
||||
const foamConfiguration = workspace.getConfiguration('foam');
|
||||
const currentDate = date !== undefined ? date : new Date();
|
||||
|
||||
const dailyNotePath = getDailyNotePath(foamConfiguration, currentDate);
|
||||
@@ -24,9 +18,9 @@ async function openDailyNoteFor(date?: Date) {
|
||||
await focusNote(dailyNotePath, isNew);
|
||||
}
|
||||
function getDailyNotePath(configuration: WorkspaceConfiguration, date: Date) {
|
||||
const rootDirectory = workspace.workspaceFolders[0].uri.path;
|
||||
const rootDirectory = workspace.workspaceFolders[0].uri.fsPath;
|
||||
const dailyNoteDirectory: string =
|
||||
configuration.get("openDailyNote.directory") ?? ".";
|
||||
configuration.get('openDailyNote.directory') ?? '.';
|
||||
const dailyNoteFilename = getDailyNoteFileName(configuration, date);
|
||||
|
||||
return join(rootDirectory, dailyNoteDirectory, dailyNoteFilename);
|
||||
@@ -37,10 +31,10 @@ function getDailyNoteFileName(
|
||||
date: Date
|
||||
): string {
|
||||
const filenameFormat: string = configuration.get(
|
||||
"openDailyNote.filenameFormat"
|
||||
'openDailyNote.filenameFormat'
|
||||
);
|
||||
const fileExtension: string = configuration.get(
|
||||
"openDailyNote.fileExtension"
|
||||
'openDailyNote.fileExtension'
|
||||
);
|
||||
|
||||
return `${dateFormat(date, filenameFormat, false)}.${fileExtension}`;
|
||||
@@ -58,8 +52,8 @@ async function createDailyNoteIfNotExists(
|
||||
await createDailyNoteDirectoryIfNotExists(dailyNotePath);
|
||||
|
||||
const titleFormat: string =
|
||||
configuration.get("openDailyNote.titleFormat") ??
|
||||
configuration.get("openDailyNote.filenameFormat");
|
||||
configuration.get('openDailyNote.titleFormat') ??
|
||||
configuration.get('openDailyNote.filenameFormat');
|
||||
|
||||
await fs.promises.writeFile(
|
||||
dailyNotePath,
|
||||
@@ -83,5 +77,5 @@ export {
|
||||
openDailyNoteFor,
|
||||
getDailyNoteFileName,
|
||||
createDailyNoteIfNotExists,
|
||||
getDailyNotePath
|
||||
getDailyNotePath,
|
||||
};
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
"use strict";
|
||||
|
||||
import { workspace, ExtensionContext, window, Uri } from "vscode";
|
||||
import { workspace, ExtensionContext, window } from 'vscode';
|
||||
import {
|
||||
bootstrap,
|
||||
FoamConfig,
|
||||
Foam,
|
||||
Services,
|
||||
Logger,
|
||||
FileDataStore
|
||||
} from "foam-core";
|
||||
FileDataStore,
|
||||
} from 'foam-core';
|
||||
|
||||
import { features } from "./features";
|
||||
import { getConfigFromVscode } from "./services/config";
|
||||
import { VsCodeOutputLogger, exposeLogger } from "./services/logging";
|
||||
import { VsCodeDataStore } from "./services/datastore";
|
||||
import { features } from './features';
|
||||
import { getConfigFromVscode } from './services/config';
|
||||
import { VsCodeOutputLogger, exposeLogger } from './services/logging';
|
||||
|
||||
export async function activate(context: ExtensionContext) {
|
||||
const logger = new VsCodeOutputLogger();
|
||||
@@ -21,14 +18,14 @@ export async function activate(context: ExtensionContext) {
|
||||
exposeLogger(context, logger);
|
||||
|
||||
try {
|
||||
Logger.info("Starting Foam");
|
||||
Logger.info('Starting Foam');
|
||||
|
||||
const config: FoamConfig = getConfigFromVscode();
|
||||
const watcher = workspace.createFileSystemWatcher("**/*");
|
||||
const watcher = workspace.createFileSystemWatcher('**/*');
|
||||
const dataStore = new FileDataStore(config, watcher);
|
||||
|
||||
const services: Services = {
|
||||
dataStore: dataStore
|
||||
dataStore: dataStore,
|
||||
};
|
||||
const foamPromise: Promise<Foam> = bootstrap(config, services);
|
||||
|
||||
@@ -41,7 +38,7 @@ export async function activate(context: ExtensionContext) {
|
||||
|
||||
context.subscriptions.push(dataStore, foam, watcher);
|
||||
} catch (e) {
|
||||
Logger.error("An error occurred while bootstrapping Foam", e);
|
||||
Logger.error('An error occurred while bootstrapping Foam', e);
|
||||
window.showErrorMessage(
|
||||
`An error occurred while bootstrapping Foam. ${e.stack}`
|
||||
);
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { env, window, Uri, Position, Selection, commands } from 'vscode';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
describe('copyWithoutBrackets', () => {
|
||||
it('should get the input from the active editor selection', async () => {
|
||||
const doc = await vscode.workspace.openTextDocument(
|
||||
Uri.parse('untitled:/hello.md')
|
||||
);
|
||||
const editor = await window.showTextDocument(doc);
|
||||
editor.edit(builder => {
|
||||
builder.insert(new Position(0, 0), 'This is my [[test-content]].');
|
||||
});
|
||||
editor.selection = new Selection(new Position(0, 0), new Position(1, 0));
|
||||
await commands.executeCommand('foam-vscode.copy-without-brackets');
|
||||
const value = await env.clipboard.readText();
|
||||
expect(value).toEqual('This is my Test Content.');
|
||||
});
|
||||
});
|
||||
@@ -1,21 +1,19 @@
|
||||
import {
|
||||
window,
|
||||
env,
|
||||
ExtensionContext,
|
||||
commands,
|
||||
} from "vscode";
|
||||
import { FoamFeature } from "../types";
|
||||
import { removeBrackets } from "../utils";
|
||||
import { window, env, ExtensionContext, commands } from 'vscode';
|
||||
import { FoamFeature } from '../types';
|
||||
import { removeBrackets } from '../utils';
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: (context: ExtensionContext) => {
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand("foam-vscode.copy-without-brackets", copyWithoutBrackets)
|
||||
commands.registerCommand(
|
||||
'foam-vscode.copy-without-brackets',
|
||||
copyWithoutBrackets
|
||||
)
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
async function copyWithoutBrackets () {
|
||||
async function copyWithoutBrackets() {
|
||||
// Get the active text editor
|
||||
const editor = window.activeTextEditor;
|
||||
|
||||
|
||||
@@ -4,17 +4,17 @@ import {
|
||||
ExtensionContext,
|
||||
workspace,
|
||||
Uri,
|
||||
SnippetString
|
||||
} from "vscode";
|
||||
import * as path from "path";
|
||||
import { FoamFeature } from "../types";
|
||||
import { TextEncoder } from "util";
|
||||
import { focusNote } from "../utils";
|
||||
SnippetString,
|
||||
} from 'vscode';
|
||||
import * as path from 'path';
|
||||
import { FoamFeature } from '../types';
|
||||
import { TextEncoder } from 'util';
|
||||
import { focusNote } from '../utils';
|
||||
|
||||
const templatesDir = `${workspace.workspaceFolders[0].uri.path}/.foam/templates`;
|
||||
|
||||
async function getTemplates(): Promise<string[]> {
|
||||
const templates = await workspace.findFiles(".foam/templates/**.md");
|
||||
const templates = await workspace.findFiles('.foam/templates/**.md');
|
||||
// parse title, not whole file!
|
||||
return templates.map(template => path.basename(template.path));
|
||||
}
|
||||
@@ -23,7 +23,7 @@ const feature: FoamFeature = {
|
||||
activate: (context: ExtensionContext) => {
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand(
|
||||
"foam-vscode.create-note-from-template",
|
||||
'foam-vscode.create-note-from-template',
|
||||
async () => {
|
||||
const templates = await getTemplates();
|
||||
const activeFile = window.activeTextEditor?.document?.fileName;
|
||||
@@ -34,14 +34,14 @@ const feature: FoamFeature = {
|
||||
const selectedTemplate = await window.showQuickPick(templates);
|
||||
const folder = await window.showInputBox({
|
||||
prompt: `Where should the template be created?`,
|
||||
value: currentDir
|
||||
value: currentDir,
|
||||
});
|
||||
|
||||
let filename = await window.showInputBox({
|
||||
prompt: `Enter the filename for the new note`,
|
||||
value: ``,
|
||||
validateInput: value =>
|
||||
value.length ? undefined : "Please enter a value!"
|
||||
value.length ? undefined : 'Please enter a value!',
|
||||
});
|
||||
filename = path.extname(filename).length
|
||||
? filename
|
||||
@@ -54,14 +54,14 @@ const feature: FoamFeature = {
|
||||
const snippet = new SnippetString(templateText.toString());
|
||||
await workspace.fs.writeFile(
|
||||
Uri.file(targetFile),
|
||||
new TextEncoder().encode("")
|
||||
new TextEncoder().encode('')
|
||||
);
|
||||
await focusNote(targetFile, true);
|
||||
await window.activeTextEditor.insertSnippet(snippet);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default feature;
|
||||
|
||||
@@ -1,104 +1,125 @@
|
||||
import * as vscode from "vscode";
|
||||
import * as path from "path";
|
||||
import { FoamFeature } from "../types";
|
||||
import { Foam, Logger } from "foam-core";
|
||||
import { TextDecoder } from "util";
|
||||
import { getTitleMaxLength } from "../settings";
|
||||
import { isSome } from "../utils";
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import { FoamFeature } from '../types';
|
||||
import { Foam, Logger } from 'foam-core';
|
||||
import { TextDecoder } from 'util';
|
||||
import { getGraphStyle, getTitleMaxLength } from '../settings';
|
||||
import { isSome } from '../utils';
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: (context: vscode.ExtensionContext, foamPromise: Promise<Foam>) => {
|
||||
vscode.commands.registerCommand("foam-vscode.show-graph", async () => {
|
||||
const foam = await foamPromise;
|
||||
const panel = await createGraphPanel(foam, context);
|
||||
|
||||
const onFoamChanged = _ => {
|
||||
updateGraph(panel, foam);
|
||||
};
|
||||
|
||||
const noteAddedListener = foam.notes.onDidAddNote(onFoamChanged);
|
||||
const noteUpdatedListener = foam.notes.onDidUpdateNote(onFoamChanged);
|
||||
const noteDeletedListener = foam.notes.onDidDeleteNote(onFoamChanged);
|
||||
panel.onDidDispose(() => {
|
||||
noteAddedListener.dispose();
|
||||
noteUpdatedListener.dispose();
|
||||
noteDeletedListener.dispose();
|
||||
});
|
||||
|
||||
vscode.window.onDidChangeActiveTextEditor(e => {
|
||||
if (e.document.uri.scheme === "file") {
|
||||
const note = foam.notes.getNoteByURI(e.document.uri);
|
||||
if (isSome(note)) {
|
||||
panel.webview.postMessage({
|
||||
type: "didSelectNote",
|
||||
payload: note.id
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
let panel: vscode.WebviewPanel | undefined = undefined;
|
||||
vscode.workspace.onDidChangeConfiguration(event => {
|
||||
if (event.affectsConfiguration('foam.graph.style')) {
|
||||
const style = getGraphStyle();
|
||||
panel.webview.postMessage({
|
||||
type: 'didUpdateStyle',
|
||||
payload: style,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
vscode.commands.registerCommand('foam-vscode.show-graph', async () => {
|
||||
if (panel) {
|
||||
const columnToShowIn = vscode.window.activeTextEditor
|
||||
? vscode.window.activeTextEditor.viewColumn
|
||||
: undefined;
|
||||
panel.reveal(columnToShowIn);
|
||||
} else {
|
||||
const foam = await foamPromise;
|
||||
panel = await createGraphPanel(foam, context);
|
||||
const onFoamChanged = _ => {
|
||||
updateGraph(panel, foam);
|
||||
};
|
||||
|
||||
const noteAddedListener = foam.notes.onDidAddNote(onFoamChanged);
|
||||
const noteUpdatedListener = foam.notes.onDidUpdateNote(onFoamChanged);
|
||||
const noteDeletedListener = foam.notes.onDidDeleteNote(onFoamChanged);
|
||||
panel.onDidDispose(() => {
|
||||
noteAddedListener.dispose();
|
||||
noteUpdatedListener.dispose();
|
||||
noteDeletedListener.dispose();
|
||||
panel = undefined;
|
||||
});
|
||||
|
||||
vscode.window.onDidChangeActiveTextEditor(e => {
|
||||
if (e.document.uri.scheme === 'file') {
|
||||
const note = foam.notes.getNote(e.document.uri);
|
||||
if (isSome(note)) {
|
||||
panel.webview.postMessage({
|
||||
type: 'didSelectNote',
|
||||
payload: note.uri.path,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
function updateGraph(panel: vscode.WebviewPanel, foam: Foam) {
|
||||
const graph = generateGraphData(foam);
|
||||
panel.webview.postMessage({
|
||||
type: "didUpdateGraphData",
|
||||
payload: graph
|
||||
type: 'didUpdateGraphData',
|
||||
payload: graph,
|
||||
});
|
||||
}
|
||||
|
||||
function generateGraphData(foam: Foam) {
|
||||
const graph = {
|
||||
nodes: {},
|
||||
edges: new Set()
|
||||
edges: new Set(),
|
||||
};
|
||||
|
||||
foam.notes.getNotes().forEach(n => {
|
||||
const links = foam.notes.getForwardLinks(n.id);
|
||||
graph.nodes[n.id] = {
|
||||
id: n.id,
|
||||
type: "note",
|
||||
uri: n.source.uri,
|
||||
title: cutTitle(n.title)
|
||||
const links = foam.notes.getForwardLinks(n.uri);
|
||||
graph.nodes[n.uri.path] = {
|
||||
id: n.uri.path,
|
||||
type: n.properties.type ?? 'note',
|
||||
uri: n.uri,
|
||||
title: cutTitle(n.title),
|
||||
};
|
||||
links.forEach(link => {
|
||||
if (!(link.to in graph.nodes)) {
|
||||
graph.nodes[link.to] = {
|
||||
if (!(link.to.path in graph.nodes)) {
|
||||
graph.nodes[link.to.path] = {
|
||||
id: link.to,
|
||||
type: "nonExistingNote",
|
||||
type: 'placeholder',
|
||||
uri: `virtual:${link.to}`,
|
||||
title: cutTitle(link.link.slug)
|
||||
title:
|
||||
'slug' in link.link
|
||||
? cutTitle(link.link.slug)
|
||||
: cutTitle(link.link.label),
|
||||
};
|
||||
}
|
||||
graph.edges.add({
|
||||
source: link.from,
|
||||
target: link.to
|
||||
source: link.from.path,
|
||||
target: link.to.path,
|
||||
});
|
||||
});
|
||||
});
|
||||
return {
|
||||
nodes: graph.nodes,
|
||||
links: Array.from(graph.edges)
|
||||
links: Array.from(graph.edges),
|
||||
};
|
||||
}
|
||||
|
||||
function cutTitle(title: string): string {
|
||||
const maxLen = getTitleMaxLength();
|
||||
if (maxLen > 0 && title.length > maxLen) {
|
||||
return title.substring(0, maxLen).concat("...");
|
||||
return title.substring(0, maxLen).concat('...');
|
||||
}
|
||||
return title;
|
||||
}
|
||||
|
||||
async function createGraphPanel(foam: Foam, context: vscode.ExtensionContext) {
|
||||
const panel = vscode.window.createWebviewPanel(
|
||||
"foam-graph",
|
||||
"Foam Graph",
|
||||
'foam-graph',
|
||||
'Foam Graph',
|
||||
vscode.ViewColumn.Two,
|
||||
{
|
||||
enableScripts: true,
|
||||
retainContextWhenHidden: true
|
||||
retainContextWhenHidden: true,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -107,22 +128,29 @@ async function createGraphPanel(foam: Foam, context: vscode.ExtensionContext) {
|
||||
panel.webview.onDidReceiveMessage(
|
||||
async message => {
|
||||
switch (message.type) {
|
||||
case "webviewDidLoad":
|
||||
case 'webviewDidLoad':
|
||||
const styles = getGraphStyle();
|
||||
panel.webview.postMessage({
|
||||
type: 'didUpdateStyle',
|
||||
payload: styles,
|
||||
});
|
||||
updateGraph(panel, foam);
|
||||
break;
|
||||
|
||||
case "webviewDidSelectNode":
|
||||
const noteId = message.payload;
|
||||
const selectedNote = foam.notes.getNote(noteId);
|
||||
case 'webviewDidSelectNode':
|
||||
const noteUri = vscode.Uri.parse(message.payload);
|
||||
const selectedNote = foam.notes.getNote(noteUri);
|
||||
|
||||
const doc = await vscode.workspace.openTextDocument(
|
||||
selectedNote.source.uri.path // vscode doesn't recognize the URI directly
|
||||
);
|
||||
vscode.window.showTextDocument(doc, vscode.ViewColumn.One);
|
||||
if (isSome(selectedNote)) {
|
||||
const doc = await vscode.workspace.openTextDocument(
|
||||
selectedNote.uri.path // vscode doesn't recognize the URI directly
|
||||
);
|
||||
vscode.window.showTextDocument(doc, vscode.ViewColumn.One);
|
||||
}
|
||||
break;
|
||||
|
||||
case "error":
|
||||
Logger.error("An error occurred in the graph view", message.payload);
|
||||
case 'error':
|
||||
Logger.error('An error occurred in the graph view', message.payload);
|
||||
break;
|
||||
}
|
||||
},
|
||||
@@ -138,27 +166,27 @@ async function getWebviewContent(
|
||||
panel: vscode.WebviewPanel
|
||||
) {
|
||||
const webviewPath = vscode.Uri.file(
|
||||
path.join(context.extensionPath, "static", "dataviz.html")
|
||||
path.join(context.extensionPath, 'static', 'dataviz.html')
|
||||
);
|
||||
const file = await vscode.workspace.fs.readFile(webviewPath);
|
||||
const text = new TextDecoder("utf-8").decode(file);
|
||||
const text = new TextDecoder('utf-8').decode(file);
|
||||
|
||||
const webviewUri = (fileName: string) =>
|
||||
panel.webview
|
||||
.asWebviewUri(
|
||||
vscode.Uri.file(path.join(context.extensionPath, "static", fileName))
|
||||
vscode.Uri.file(path.join(context.extensionPath, 'static', fileName))
|
||||
)
|
||||
.toString();
|
||||
|
||||
const graphDirectory = path.join("graphs", "default");
|
||||
const graphDirectory = path.join('graphs', 'default');
|
||||
const textWithVariables = text
|
||||
.replace(
|
||||
"${graphPath}",
|
||||
"{{" + path.join(graphDirectory, "graph.js") + "}}"
|
||||
'${graphPath}', // eslint-disable-line
|
||||
'{{' + path.join(graphDirectory, 'graph.js') + '}}'
|
||||
)
|
||||
.replace(
|
||||
"${graphStylesPath}",
|
||||
"{{" + path.join(graphDirectory, "graph.css") + "}}"
|
||||
'${graphStylesPath}', // eslint-disable-line
|
||||
'{{' + path.join(graphDirectory, 'graph.css') + '}}'
|
||||
);
|
||||
|
||||
// Basic templating. Will replace the script paths with the
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
import createReferences from "./wikilink-reference-generation";
|
||||
import openDailyNote from "./open-daily-note";
|
||||
import janitor from "./janitor";
|
||||
import dataviz from "./dataviz";
|
||||
import copyWithoutBrackets from "./copy-without-brackets";
|
||||
import openDatedNote from "./open-dated-note";
|
||||
import tagsExplorer from "./tags-tree-view";
|
||||
import createFromTemplate from "./create-from-template";
|
||||
import { FoamFeature } from "../types";
|
||||
import createReferences from './wikilink-reference-generation';
|
||||
import openDailyNote from './open-daily-note';
|
||||
import janitor from './janitor';
|
||||
import dataviz from './dataviz';
|
||||
import copyWithoutBrackets from './copy-without-brackets';
|
||||
import openDatedNote from './open-dated-note';
|
||||
import tagsExplorer from './tags-tree-view';
|
||||
import createFromTemplate from './create-from-template';
|
||||
import openRandomNote from './open-random-note';
|
||||
import { FoamFeature } from '../types';
|
||||
|
||||
export const features: FoamFeature[] = [
|
||||
tagsExplorer,
|
||||
createReferences,
|
||||
openDailyNote,
|
||||
openRandomNote,
|
||||
janitor,
|
||||
dataviz,
|
||||
copyWithoutBrackets,
|
||||
openDatedNote,
|
||||
createFromTemplate
|
||||
createFromTemplate,
|
||||
];
|
||||
|
||||
@@ -4,31 +4,31 @@ import {
|
||||
ExtensionContext,
|
||||
commands,
|
||||
Range,
|
||||
ProgressLocation
|
||||
} from "vscode";
|
||||
import * as fs from "fs";
|
||||
import { FoamFeature } from "../types";
|
||||
ProgressLocation,
|
||||
} from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
import { FoamFeature } from '../types';
|
||||
import {
|
||||
applyTextEdit,
|
||||
generateLinkReferences,
|
||||
generateHeading,
|
||||
Foam
|
||||
} from "foam-core";
|
||||
Foam,
|
||||
} from 'foam-core';
|
||||
|
||||
import {
|
||||
getWikilinkDefinitionSetting,
|
||||
LinkReferenceDefinitionsSetting
|
||||
} from "../settings";
|
||||
import { astPositionToVsCodePosition } from "../utils";
|
||||
LinkReferenceDefinitionsSetting,
|
||||
} from '../settings';
|
||||
import { astPositionToVsCodePosition } from '../utils';
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: (context: ExtensionContext, foamPromise: Promise<Foam>) => {
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand("foam-vscode.janitor", async () =>
|
||||
commands.registerCommand('foam-vscode.janitor', async () =>
|
||||
janitor(await foamPromise)
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
async function janitor(foam: Foam) {
|
||||
@@ -44,7 +44,7 @@ async function janitor(foam: Foam) {
|
||||
const outcome = await window.withProgress(
|
||||
{
|
||||
location: ProgressLocation.Notification,
|
||||
title: `Running Foam Janitor across ${noOfFiles} files!`
|
||||
title: `Running Foam Janitor across ${noOfFiles} files!`,
|
||||
},
|
||||
() => runJanitor(foam)
|
||||
);
|
||||
@@ -75,21 +75,21 @@ async function runJanitor(foam: Foam) {
|
||||
|
||||
const dirtyTextDocuments = workspace.textDocuments.filter(
|
||||
textDocument =>
|
||||
(textDocument.languageId === "markdown" ||
|
||||
textDocument.languageId === "mdx") &&
|
||||
(textDocument.languageId === 'markdown' ||
|
||||
textDocument.languageId === 'mdx') &&
|
||||
textDocument.isDirty
|
||||
);
|
||||
|
||||
const dirtyEditorsFileName = dirtyTextDocuments.map(
|
||||
dirtyTextDocument => dirtyTextDocument.uri.path
|
||||
dirtyTextDocument => dirtyTextDocument.uri.fsPath
|
||||
);
|
||||
|
||||
const dirtyNotes = notes.filter(note =>
|
||||
dirtyEditorsFileName.includes(note.source.uri.path)
|
||||
dirtyEditorsFileName.includes(note.uri.fsPath)
|
||||
);
|
||||
|
||||
const nonDirtyNotes = notes.filter(
|
||||
note => !dirtyEditorsFileName.includes(note.source.uri.path)
|
||||
note => !dirtyEditorsFileName.includes(note.uri.fsPath)
|
||||
);
|
||||
|
||||
const wikilinkSetting = getWikilinkDefinitionSetting();
|
||||
@@ -125,7 +125,7 @@ async function runJanitor(foam: Foam) {
|
||||
text = definitions ? applyTextEdit(text, definitions) : text;
|
||||
text = heading ? applyTextEdit(text, heading) : text;
|
||||
|
||||
return fs.promises.writeFile(note.source.uri.path, text);
|
||||
return fs.promises.writeFile(note.uri.fsPath, text);
|
||||
});
|
||||
|
||||
await Promise.all(fileWritePromises);
|
||||
@@ -135,7 +135,7 @@ async function runJanitor(foam: Foam) {
|
||||
for (const doc of dirtyTextDocuments) {
|
||||
const editor = await window.showTextDocument(doc);
|
||||
const note = dirtyNotes.find(
|
||||
n => n.source.uri.path === editor.document.uri.path
|
||||
n => n.uri.fsPath === editor.document.uri.fsPath
|
||||
)!;
|
||||
|
||||
// Get edits
|
||||
@@ -151,6 +151,7 @@ async function runJanitor(foam: Foam) {
|
||||
|
||||
if (heading || definitions) {
|
||||
// Apply Edits
|
||||
/* eslint-disable */
|
||||
await editor.edit(editBuilder => {
|
||||
// Note: The ordering matters. Definitions need to be inserted
|
||||
// before heading, since inserting a heading changes line numbers below
|
||||
@@ -169,13 +170,14 @@ async function runJanitor(foam: Foam) {
|
||||
editBuilder.replace(start, heading.newText);
|
||||
}
|
||||
});
|
||||
/* eslint-enable */
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
updatedHeadingCount,
|
||||
updatedDefinitionListCount,
|
||||
changedAnyFiles: updatedHeadingCount + updatedDefinitionListCount
|
||||
changedAnyFiles: updatedHeadingCount + updatedDefinitionListCount,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { ExtensionContext, commands } from "vscode";
|
||||
import { FoamFeature } from "../types";
|
||||
import { openDailyNoteFor } from "../dated-notes";
|
||||
import { ExtensionContext, commands } from 'vscode';
|
||||
import { FoamFeature } from '../types';
|
||||
import { openDailyNoteFor } from '../dated-notes';
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: (context: ExtensionContext) => {
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand("foam-vscode.open-daily-note", openDailyNoteFor)
|
||||
commands.registerCommand('foam-vscode.open-daily-note', openDailyNoteFor)
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default feature;
|
||||
|
||||
@@ -6,16 +6,16 @@ import {
|
||||
CompletionItemProvider,
|
||||
CompletionItem,
|
||||
CompletionItemKind,
|
||||
CompletionList
|
||||
} from "vscode";
|
||||
CompletionList,
|
||||
CompletionTriggerKind,
|
||||
} from 'vscode';
|
||||
import {
|
||||
createDailyNoteIfNotExists,
|
||||
getDailyNoteFileName,
|
||||
openDailyNoteFor,
|
||||
getDailyNotePath
|
||||
} from "../dated-notes";
|
||||
import { LinkReferenceDefinitionsSetting } from "../settings";
|
||||
import { FoamFeature } from "../types";
|
||||
getDailyNotePath,
|
||||
} from '../dated-notes';
|
||||
import { FoamFeature } from '../types';
|
||||
|
||||
interface DateSnippet {
|
||||
snippet: string;
|
||||
@@ -24,22 +24,18 @@ interface DateSnippet {
|
||||
}
|
||||
|
||||
const daysOfWeek = [
|
||||
{ day: "sunday", index: 0 },
|
||||
{ day: "monday", index: 1 },
|
||||
{ day: "tuesday", index: 2 },
|
||||
{ day: "wednesday", index: 3 },
|
||||
{ day: "thursday", index: 4 },
|
||||
{ day: "friday", index: 5 },
|
||||
{ day: "saturday", index: 6 }
|
||||
{ day: 'sunday', index: 0 },
|
||||
{ day: 'monday', index: 1 },
|
||||
{ day: 'tuesday', index: 2 },
|
||||
{ day: 'wednesday', index: 3 },
|
||||
{ day: 'thursday', index: 4 },
|
||||
{ day: 'friday', index: 5 },
|
||||
{ day: 'saturday', index: 6 },
|
||||
];
|
||||
type AfterCompletionOptions = "noop" | "createNote" | "navigateToNote";
|
||||
const foamConfig = workspace.getConfiguration("foam");
|
||||
const foamExtension = foamConfig.get("openDailyNote.fileExtension");
|
||||
const foamLinkReferenceDefinitions = foamConfig.get(
|
||||
"edit.linkReferenceDefinitions"
|
||||
);
|
||||
type AfterCompletionOptions = 'noop' | 'createNote' | 'navigateToNote';
|
||||
const foamConfig = workspace.getConfiguration('foam');
|
||||
const foamNavigateOnSelect: AfterCompletionOptions = foamConfig.get(
|
||||
"dateSnippets.afterCompletion"
|
||||
'dateSnippets.afterCompletion'
|
||||
);
|
||||
|
||||
const generateDayOfWeekSnippets = (): DateSnippet[] => {
|
||||
@@ -55,7 +51,7 @@ const generateDayOfWeekSnippets = (): DateSnippet[] => {
|
||||
return {
|
||||
date: target,
|
||||
detail: `Get a daily note link for ${day}`,
|
||||
snippet: `/${day}`
|
||||
snippet: `/${day}`,
|
||||
};
|
||||
});
|
||||
return snippets;
|
||||
@@ -68,54 +64,57 @@ const createCompletionItem = ({ snippet, date, detail }: DateSnippet) => {
|
||||
);
|
||||
completionItem.insertText = getDailyNoteLink(date);
|
||||
completionItem.detail = `${completionItem.insertText} - ${detail}`;
|
||||
if (foamNavigateOnSelect !== "noop") {
|
||||
if (foamNavigateOnSelect !== 'noop') {
|
||||
completionItem.command = {
|
||||
command: "foam-vscode.open-dated-note",
|
||||
title: "Open a note for the given date",
|
||||
arguments: [date]
|
||||
command: 'foam-vscode.open-dated-note',
|
||||
title: 'Open a note for the given date',
|
||||
arguments: [date],
|
||||
};
|
||||
}
|
||||
return completionItem;
|
||||
};
|
||||
|
||||
const getDailyNoteLink = (date: Date) => {
|
||||
let name = getDailyNoteFileName(foamConfig, date);
|
||||
if (
|
||||
foamLinkReferenceDefinitions ===
|
||||
LinkReferenceDefinitionsSetting.withoutExtensions
|
||||
) {
|
||||
name = name.replace(`.${foamExtension}`, "");
|
||||
}
|
||||
return `[[${name}]]`;
|
||||
const foamExtension = foamConfig.get('openDailyNote.fileExtension');
|
||||
const name = getDailyNoteFileName(foamConfig, date);
|
||||
return `[[${name.replace(`.${foamExtension}`, '')}]]`;
|
||||
};
|
||||
|
||||
const snippets: (() => DateSnippet)[] = [
|
||||
() => ({
|
||||
detail: "Insert a link to today's daily note",
|
||||
snippet: "/day",
|
||||
date: new Date()
|
||||
snippet: '/day',
|
||||
date: new Date(),
|
||||
}),
|
||||
() => ({
|
||||
detail: "Insert a link to today's daily note",
|
||||
snippet: "/today",
|
||||
date: new Date()
|
||||
snippet: '/today',
|
||||
date: new Date(),
|
||||
}),
|
||||
() => {
|
||||
const today = new Date();
|
||||
return {
|
||||
detail: "Insert a link to tomorrow's daily note",
|
||||
snippet: "/tomorrow",
|
||||
date: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1)
|
||||
snippet: '/tomorrow',
|
||||
date: new Date(
|
||||
today.getFullYear(),
|
||||
today.getMonth(),
|
||||
today.getDate() + 1
|
||||
),
|
||||
};
|
||||
},
|
||||
() => {
|
||||
const today = new Date();
|
||||
return {
|
||||
detail: "Insert a link to yesterday's daily note",
|
||||
snippet: "/yesterday",
|
||||
date: new Date(today.getFullYear(), today.getMonth(), today.getDate() - 1)
|
||||
snippet: '/yesterday',
|
||||
date: new Date(
|
||||
today.getFullYear(),
|
||||
today.getMonth(),
|
||||
today.getDate() - 1
|
||||
),
|
||||
};
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
const computedSnippets: ((number: number) => DateSnippet)[] = [
|
||||
@@ -128,7 +127,7 @@ const computedSnippets: ((number: number) => DateSnippet)[] = [
|
||||
today.getFullYear(),
|
||||
today.getMonth(),
|
||||
today.getDate() + days
|
||||
)
|
||||
),
|
||||
};
|
||||
},
|
||||
(weeks: number) => {
|
||||
@@ -140,7 +139,7 @@ const computedSnippets: ((number: number) => DateSnippet)[] = [
|
||||
today.getFullYear(),
|
||||
today.getMonth(),
|
||||
today.getDate() + 7 * weeks
|
||||
)
|
||||
),
|
||||
};
|
||||
},
|
||||
(months: number) => {
|
||||
@@ -152,7 +151,7 @@ const computedSnippets: ((number: number) => DateSnippet)[] = [
|
||||
today.getFullYear(),
|
||||
today.getMonth() + months,
|
||||
today.getDate()
|
||||
)
|
||||
),
|
||||
};
|
||||
},
|
||||
(years: number) => {
|
||||
@@ -164,27 +163,39 @@ const computedSnippets: ((number: number) => DateSnippet)[] = [
|
||||
today.getFullYear() + years,
|
||||
today.getMonth(),
|
||||
today.getDate()
|
||||
)
|
||||
),
|
||||
};
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
const completions: CompletionItemProvider = {
|
||||
provideCompletionItems: (_document, _position, _token, _context) => {
|
||||
if (_context.triggerKind === CompletionTriggerKind.Invoke) {
|
||||
// if completion was triggered without trigger character then we return [] to fallback
|
||||
// to vscode word-based suggestions (see https://github.com/foambubble/foam/pull/417)
|
||||
return [];
|
||||
}
|
||||
|
||||
const completionItems = [
|
||||
...snippets.map(item => createCompletionItem(item())),
|
||||
...generateDayOfWeekSnippets().map(item => createCompletionItem(item))
|
||||
...generateDayOfWeekSnippets().map(item => createCompletionItem(item)),
|
||||
];
|
||||
return completionItems;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const computedCompletions: CompletionItemProvider = {
|
||||
provideCompletionItems: (document, position, _token, _context) => {
|
||||
if (_context.triggerKind === CompletionTriggerKind.Invoke) {
|
||||
// if completion was triggered without trigger character then we return [] to fallback
|
||||
// to vscode word-based suggestions (see https://github.com/foambubble/foam/pull/417)
|
||||
return [];
|
||||
}
|
||||
|
||||
const range = document.getWordRangeAtPosition(position, /\S+/);
|
||||
const snippetString = document.getText(range);
|
||||
const matches = snippetString.match(/(\d+)/);
|
||||
const number: string = matches ? matches[0] : "1";
|
||||
const number: string = matches ? matches[0] : '1';
|
||||
const completionItems = computedSnippets.map(item => {
|
||||
const completionItem = createCompletionItem(item(parseInt(number)));
|
||||
completionItem.range = range;
|
||||
@@ -192,14 +203,14 @@ const computedCompletions: CompletionItemProvider = {
|
||||
});
|
||||
// We still want the list to be treated as "incomplete", because the user may add another number
|
||||
return new CompletionList(completionItems, true);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const datedNoteCommand = (date: Date) => {
|
||||
if (foamNavigateOnSelect === "navigateToNote") {
|
||||
if (foamNavigateOnSelect === 'navigateToNote') {
|
||||
return openDailyNoteFor(date);
|
||||
}
|
||||
if (foamNavigateOnSelect === "createNote") {
|
||||
if (foamNavigateOnSelect === 'createNote') {
|
||||
return createDailyNoteIfNotExists(
|
||||
foamConfig,
|
||||
getDailyNotePath(foamConfig, date),
|
||||
@@ -211,18 +222,18 @@ const datedNoteCommand = (date: Date) => {
|
||||
const feature: FoamFeature = {
|
||||
activate: (context: ExtensionContext) => {
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand("foam-vscode.open-dated-note", date =>
|
||||
commands.registerCommand('foam-vscode.open-dated-note', date =>
|
||||
datedNoteCommand(date)
|
||||
)
|
||||
);
|
||||
languages.registerCompletionItemProvider("markdown", completions, "/");
|
||||
languages.registerCompletionItemProvider('markdown', completions, '/');
|
||||
languages.registerCompletionItemProvider(
|
||||
"markdown",
|
||||
'markdown',
|
||||
computedCompletions,
|
||||
"/",
|
||||
"+"
|
||||
'/',
|
||||
'+'
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default feature;
|
||||
|
||||
31
packages/foam-vscode/src/features/open-random-note.ts
Normal file
31
packages/foam-vscode/src/features/open-random-note.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Foam } from 'foam-core';
|
||||
import { ExtensionContext, commands, window } from 'vscode';
|
||||
import { FoamFeature } from '../types';
|
||||
import { focusNote } from '../utils';
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: (context: ExtensionContext, foamPromise: Promise<Foam>) => {
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand('foam-vscode.open-random-note', async () => {
|
||||
const foam = await foamPromise;
|
||||
const currentFile = window.activeTextEditor?.document.uri.path;
|
||||
const notes = foam.notes.getNotes();
|
||||
if (notes.length <= 1) {
|
||||
window.showInformationMessage(
|
||||
'Could not find another note to open. If you believe this is a bug, please file an issue.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let randomNoteIndex = Math.floor(Math.random() * notes.length);
|
||||
if (notes[randomNoteIndex].uri.path === currentFile) {
|
||||
randomNoteIndex = (randomNoteIndex + 1) % notes.length;
|
||||
}
|
||||
|
||||
focusNote(notes[randomNoteIndex].uri.path, false);
|
||||
})
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default feature;
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as vscode from "vscode";
|
||||
import { FoamFeature } from "../../types";
|
||||
import { Foam, Note } from "foam-core";
|
||||
import * as vscode from 'vscode';
|
||||
import { FoamFeature } from '../../types';
|
||||
import { Foam, Note } from 'foam-core';
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: async (
|
||||
@@ -11,12 +11,12 @@ const feature: FoamFeature = {
|
||||
const provider = new TagsProvider(foam);
|
||||
context.subscriptions.push(
|
||||
vscode.window.registerTreeDataProvider(
|
||||
"foam-vscode.tags-explorer",
|
||||
'foam-vscode.tags-explorer',
|
||||
provider
|
||||
)
|
||||
);
|
||||
foam.notes.onDidUpdateNote(() => provider.refresh());
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default feature;
|
||||
@@ -29,7 +29,7 @@ export class TagsProvider implements vscode.TreeDataProvider<TagTreeItem> {
|
||||
|
||||
private tags: {
|
||||
tag: string;
|
||||
noteIds: string[];
|
||||
noteUris: vscode.Uri[];
|
||||
}[];
|
||||
|
||||
constructor(private foam: Foam) {
|
||||
@@ -43,16 +43,16 @@ export class TagsProvider implements vscode.TreeDataProvider<TagTreeItem> {
|
||||
|
||||
private computeTags() {
|
||||
const rawTags: {
|
||||
[key: string]: string[];
|
||||
[key: string]: vscode.Uri[];
|
||||
} = this.foam.notes.getNotes().reduce((acc, note) => {
|
||||
note.tags.forEach(tag => {
|
||||
acc[tag] = acc[tag] ?? [];
|
||||
acc[tag].push(note.id);
|
||||
acc[tag].push(note.uri);
|
||||
});
|
||||
return acc;
|
||||
}, {});
|
||||
this.tags = Object.entries(rawTags)
|
||||
.map(([tag, noteIds]) => ({ tag, noteIds }))
|
||||
.map(([tag, noteUris]) => ({ tag, noteUris }))
|
||||
.sort((a, b) => a.tag.localeCompare(b.tag));
|
||||
}
|
||||
|
||||
@@ -62,18 +62,18 @@ export class TagsProvider implements vscode.TreeDataProvider<TagTreeItem> {
|
||||
|
||||
getChildren(element?: Tag): Thenable<TagTreeItem[]> {
|
||||
if (element) {
|
||||
const references: TagReference[] = element.noteIds.map(id => {
|
||||
const references: TagReference[] = element.noteUris.map(id => {
|
||||
const note = this.foam.notes.getNote(id);
|
||||
return new TagReference(element.tag, note);
|
||||
});
|
||||
return Promise.resolve([
|
||||
new TagSearch(element.tag),
|
||||
...references.sort((a, b) => a.title.localeCompare(b.title))
|
||||
...references.sort((a, b) => a.title.localeCompare(b.title)),
|
||||
]);
|
||||
}
|
||||
if (!element) {
|
||||
const tags: Tag[] = this.tags.map(
|
||||
({ tag, noteIds }) => new Tag(tag, noteIds)
|
||||
({ tag, noteUris }) => new Tag(tag, noteUris)
|
||||
);
|
||||
return Promise.resolve(tags.sort((a, b) => a.tag.localeCompare(b.tag)));
|
||||
}
|
||||
@@ -83,16 +83,19 @@ export class TagsProvider implements vscode.TreeDataProvider<TagTreeItem> {
|
||||
type TagTreeItem = Tag | TagReference | TagSearch;
|
||||
|
||||
export class Tag extends vscode.TreeItem {
|
||||
constructor(public readonly tag: string, public readonly noteIds: string[]) {
|
||||
constructor(
|
||||
public readonly tag: string,
|
||||
public readonly noteUris: vscode.Uri[]
|
||||
) {
|
||||
super(tag, vscode.TreeItemCollapsibleState.Collapsed);
|
||||
this.description = `${this.noteIds.length} reference${
|
||||
this.noteIds.length !== 1 ? "s" : ""
|
||||
this.description = `${this.noteUris.length} reference${
|
||||
this.noteUris.length !== 1 ? 's' : ''
|
||||
}`;
|
||||
this.tooltip = this.description;
|
||||
}
|
||||
|
||||
iconPath = new vscode.ThemeIcon("symbol-number");
|
||||
contextValue = "tag";
|
||||
iconPath = new vscode.ThemeIcon('symbol-number');
|
||||
contextValue = 'tag';
|
||||
}
|
||||
|
||||
export class TagSearch extends vscode.TreeItem {
|
||||
@@ -101,21 +104,21 @@ export class TagSearch extends vscode.TreeItem {
|
||||
const searchString = `#${tag}`;
|
||||
this.tooltip = `Search ${searchString} in workspace`;
|
||||
this.command = {
|
||||
command: "workbench.action.findInFiles",
|
||||
command: 'workbench.action.findInFiles',
|
||||
arguments: [
|
||||
{
|
||||
query: searchString,
|
||||
triggerSearch: true,
|
||||
matchWholeWord: true,
|
||||
isCaseSensitive: true
|
||||
}
|
||||
isCaseSensitive: true,
|
||||
},
|
||||
],
|
||||
title: "Search"
|
||||
title: 'Search',
|
||||
};
|
||||
}
|
||||
|
||||
iconPath = new vscode.ThemeIcon("search");
|
||||
contextValue = "tag-search";
|
||||
iconPath = new vscode.ThemeIcon('search');
|
||||
contextValue = 'tag-search';
|
||||
}
|
||||
|
||||
export class TagReference extends vscode.TreeItem {
|
||||
@@ -123,9 +126,9 @@ export class TagReference extends vscode.TreeItem {
|
||||
constructor(tag: string, note: Note) {
|
||||
super(note.title, vscode.TreeItemCollapsibleState.None);
|
||||
this.title = note.title;
|
||||
this.description = note.source.uri.path;
|
||||
this.description = note.uri.path;
|
||||
this.tooltip = this.description;
|
||||
const resourceUri = note.source.uri;
|
||||
const resourceUri = note.uri;
|
||||
let selection: vscode.Range | null = null;
|
||||
// TODO move search fn to core
|
||||
const lines = note.source.text.split(/\r?\n/);
|
||||
@@ -139,18 +142,18 @@ export class TagReference extends vscode.TreeItem {
|
||||
// TODO I like about this showing the git state of the note, but I don't like the md icon
|
||||
this.resourceUri = resourceUri;
|
||||
this.command = {
|
||||
command: "vscode.open",
|
||||
command: 'vscode.open',
|
||||
arguments: [
|
||||
resourceUri,
|
||||
{
|
||||
preview: true,
|
||||
selection: selection
|
||||
}
|
||||
selection: selection,
|
||||
},
|
||||
],
|
||||
title: "Open File"
|
||||
title: 'Open File',
|
||||
};
|
||||
}
|
||||
|
||||
iconPath = new vscode.ThemeIcon("note");
|
||||
contextValue = "reference";
|
||||
iconPath = new vscode.ThemeIcon('note');
|
||||
contextValue = 'reference';
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { uniq } from "lodash";
|
||||
import { uniq } from 'lodash';
|
||||
import {
|
||||
CancellationToken,
|
||||
CodeLens,
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
TextDocument,
|
||||
window,
|
||||
workspace,
|
||||
Position
|
||||
} from "vscode";
|
||||
Position,
|
||||
} from 'vscode';
|
||||
|
||||
import {
|
||||
createMarkdownReferences,
|
||||
@@ -19,33 +19,33 @@ import {
|
||||
NoteGraphAPI,
|
||||
Foam,
|
||||
LINK_REFERENCE_DEFINITION_HEADER,
|
||||
LINK_REFERENCE_DEFINITION_FOOTER
|
||||
} from "foam-core";
|
||||
LINK_REFERENCE_DEFINITION_FOOTER,
|
||||
} from 'foam-core';
|
||||
import {
|
||||
hasEmptyTrailing,
|
||||
docConfig,
|
||||
loadDocConfig,
|
||||
isMdEditor,
|
||||
mdDocSelector,
|
||||
getText
|
||||
} from "../utils";
|
||||
import { FoamFeature } from "../types";
|
||||
getText,
|
||||
} from '../utils';
|
||||
import { FoamFeature } from '../types';
|
||||
import {
|
||||
getWikilinkDefinitionSetting,
|
||||
LinkReferenceDefinitionsSetting
|
||||
} from "../settings";
|
||||
LinkReferenceDefinitionsSetting,
|
||||
} from '../settings';
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: async (context: ExtensionContext, foamPromise: Promise<Foam>) => {
|
||||
const foam = await foamPromise;
|
||||
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand("foam-vscode.update-wikilinks", () =>
|
||||
commands.registerCommand('foam-vscode.update-wikilinks', () =>
|
||||
updateReferenceList(foam.notes)
|
||||
),
|
||||
|
||||
workspace.onWillSaveTextDocument(e => {
|
||||
if (e.document.languageId === "markdown") {
|
||||
if (e.document.languageId === 'markdown') {
|
||||
updateDocumentInNoteGraph(foam, e.document);
|
||||
e.waitUntil(updateReferenceList(foam.notes));
|
||||
}
|
||||
@@ -67,7 +67,7 @@ const feature: FoamFeature = {
|
||||
updateDocumentInNoteGraph(foam, editor.document);
|
||||
updateReferenceList(foam.notes);
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function updateDocumentInNoteGraph(foam: Foam, document: TextDocument) {
|
||||
@@ -119,7 +119,7 @@ async function updateReferenceList(foam: NoteGraphAPI) {
|
||||
|
||||
// references must always be preceded by an empty line
|
||||
const spacing = doc.lineAt(range.start.line - 1).isEmptyOrWhitespace
|
||||
? ""
|
||||
? ''
|
||||
: docConfig.eol;
|
||||
|
||||
await editor.edit(editBuilder => {
|
||||
@@ -138,7 +138,7 @@ function generateReferenceList(
|
||||
return [];
|
||||
}
|
||||
|
||||
const note = foam.getNoteByURI(doc.uri);
|
||||
const note = foam.getNote(doc.uri);
|
||||
|
||||
// Should never happen as `doc` is usually given by `editor.document`, which
|
||||
// binds to an opened note.
|
||||
@@ -152,7 +152,7 @@ function generateReferenceList(
|
||||
const references = uniq(
|
||||
createMarkdownReferences(
|
||||
foam,
|
||||
note.id,
|
||||
note.uri,
|
||||
wikilinkSetting === LinkReferenceDefinitionsSetting.withExtensions
|
||||
).map(stringifyMarkdownLinkReferenceDefinition)
|
||||
);
|
||||
@@ -161,7 +161,7 @@ function generateReferenceList(
|
||||
return [
|
||||
LINK_REFERENCE_DEFINITION_HEADER,
|
||||
...references,
|
||||
LINK_REFERENCE_DEFINITION_FOOTER
|
||||
LINK_REFERENCE_DEFINITION_FOOTER,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -220,14 +220,14 @@ class WikilinkReferenceCodeLensProvider implements CodeLensProvider {
|
||||
const oldRefs = getText(range).replace(/\r?\n|\r/g, docConfig.eol);
|
||||
const newRefs = refs.join(docConfig.eol);
|
||||
|
||||
let status = oldRefs === newRefs ? "up to date" : "out of date";
|
||||
let status = oldRefs === newRefs ? 'up to date' : 'out of date';
|
||||
|
||||
return [
|
||||
new CodeLens(range, {
|
||||
arguments: [],
|
||||
title: `Link references (${status})`,
|
||||
command: ""
|
||||
})
|
||||
command: '',
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { workspace } from "vscode";
|
||||
import { FoamConfig, createConfigFromFolders } from "foam-core";
|
||||
import { getIgnoredFilesSetting } from "../settings";
|
||||
import { workspace } from 'vscode';
|
||||
import { FoamConfig, createConfigFromFolders } from 'foam-core';
|
||||
import { getIgnoredFilesSetting } from '../settings';
|
||||
|
||||
// TODO this is still to be improved - foam config should
|
||||
// not be dependent on vscode but at the moment it's convenient
|
||||
@@ -10,6 +10,6 @@ export const getConfigFromVscode = (): FoamConfig => {
|
||||
const excludeGlobs = getIgnoredFilesSetting();
|
||||
|
||||
return createConfigFromFolders(workspaceFolders, {
|
||||
ignore: excludeGlobs.map(g => g.toString())
|
||||
ignore: excludeGlobs.map(g => g.toString()),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -4,11 +4,11 @@ import {
|
||||
URI,
|
||||
FoamConfig,
|
||||
IDisposable,
|
||||
Logger
|
||||
} from "foam-core";
|
||||
import { workspace, FileSystemWatcher, EventEmitter } from "vscode";
|
||||
import { TextDecoder } from "util";
|
||||
import { isSome } from "../utils";
|
||||
Logger,
|
||||
} from 'foam-core';
|
||||
import { workspace, FileSystemWatcher, EventEmitter } from 'vscode';
|
||||
import { TextDecoder } from 'util';
|
||||
import { isSome } from '../utils';
|
||||
|
||||
export class VsCodeDataStore implements IDataStore, IDisposable {
|
||||
onDidCreateEmitter = new EventEmitter<URI>();
|
||||
@@ -22,23 +22,23 @@ export class VsCodeDataStore implements IDataStore, IDisposable {
|
||||
files: URI[];
|
||||
|
||||
constructor(private config: FoamConfig) {
|
||||
this.watcher = workspace.createFileSystemWatcher("**/*");
|
||||
this.watcher = workspace.createFileSystemWatcher('**/*');
|
||||
this.watcher.onDidCreate(async uri => {
|
||||
await this.listFiles();
|
||||
if (this.isMatch(uri)) {
|
||||
Logger.info("Created: ", uri);
|
||||
Logger.info('Created: ', uri);
|
||||
this.onDidCreateEmitter.fire(uri);
|
||||
}
|
||||
});
|
||||
this.watcher.onDidChange(uri => {
|
||||
if (this.isMatch(uri)) {
|
||||
Logger.info("Updated: ", uri);
|
||||
Logger.info('Updated: ', uri);
|
||||
this.onDidChangeEmitter.fire(uri);
|
||||
}
|
||||
});
|
||||
this.watcher.onDidDelete(uri => {
|
||||
if (this.isMatch(uri)) {
|
||||
Logger.info("Deleted: ", uri);
|
||||
Logger.info('Deleted: ', uri);
|
||||
this.files = this.files.filter(f => f.path !== uri.path);
|
||||
this.onDidDeleteEmitter.fire(uri);
|
||||
}
|
||||
@@ -47,8 +47,8 @@ export class VsCodeDataStore implements IDataStore, IDisposable {
|
||||
|
||||
async listFiles(): Promise<URI[]> {
|
||||
this.files = await workspace.findFiles(
|
||||
`{${this.config.includeGlobs.join(",")}}`,
|
||||
`{${this.config.ignoreGlobs.join(",")}}`
|
||||
`{${this.config.includeGlobs.join(',')}}`,
|
||||
`{${this.config.ignoreGlobs.join(',')}}`
|
||||
);
|
||||
|
||||
return this.files;
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { window, commands, ExtensionContext } from "vscode";
|
||||
import { ILogger, IDisposable, LogLevel, BaseLogger } from "foam-core";
|
||||
import { getFoamLoggerLevel } from "../settings";
|
||||
import { window, commands, ExtensionContext } from 'vscode';
|
||||
import { ILogger, IDisposable, LogLevel, BaseLogger } from 'foam-core';
|
||||
import { getFoamLoggerLevel } from '../settings';
|
||||
|
||||
export interface VsCodeLogger extends ILogger, IDisposable {
|
||||
show();
|
||||
}
|
||||
|
||||
export class VsCodeOutputLogger extends BaseLogger implements VsCodeLogger {
|
||||
private channel = window.createOutputChannel("Foam");
|
||||
private channel = window.createOutputChannel('Foam');
|
||||
|
||||
constructor() {
|
||||
super(getFoamLoggerLevel());
|
||||
this.channel.appendLine("Foam Logging: " + getFoamLoggerLevel());
|
||||
this.channel.appendLine('Foam Logging: ' + getFoamLoggerLevel());
|
||||
}
|
||||
|
||||
log(lvl: LogLevel, msg?: any, ...extra: any[]): void {
|
||||
@@ -42,12 +42,12 @@ export const exposeLogger = (
|
||||
logger: VsCodeLogger
|
||||
): void => {
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand("foam-vscode.set-log-level", async () => {
|
||||
const items: LogLevel[] = ["debug", "info", "warn", "error"];
|
||||
commands.registerCommand('foam-vscode.set-log-level', async () => {
|
||||
const items: LogLevel[] = ['debug', 'info', 'warn', 'error'];
|
||||
const level = await window.showQuickPick(
|
||||
items.map(item => ({
|
||||
label: item,
|
||||
description: item === logger.getLevel() && "Current"
|
||||
description: item === logger.getLevel() && 'Current',
|
||||
}))
|
||||
);
|
||||
logger.setLevel(level.label);
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { workspace, GlobPattern } from "vscode";
|
||||
import { LogLevel } from "foam-core";
|
||||
import { workspace, GlobPattern } from 'vscode';
|
||||
import { LogLevel } from 'foam-core';
|
||||
|
||||
export enum LinkReferenceDefinitionsSetting {
|
||||
withExtensions = "withExtensions",
|
||||
withoutExtensions = "withoutExtensions",
|
||||
off = "off"
|
||||
withExtensions = 'withExtensions',
|
||||
withoutExtensions = 'withoutExtensions',
|
||||
off = 'off',
|
||||
}
|
||||
|
||||
export function getWikilinkDefinitionSetting(): LinkReferenceDefinitionsSetting {
|
||||
return workspace
|
||||
.getConfiguration("foam.edit")
|
||||
.getConfiguration('foam.edit')
|
||||
.get<LinkReferenceDefinitionsSetting>(
|
||||
"linkReferenceDefinitions",
|
||||
'linkReferenceDefinitions',
|
||||
LinkReferenceDefinitionsSetting.withoutExtensions
|
||||
);
|
||||
}
|
||||
@@ -19,16 +19,21 @@ export function getWikilinkDefinitionSetting(): LinkReferenceDefinitionsSetting
|
||||
/** Retrieve the list of file ignoring globs. */
|
||||
export function getIgnoredFilesSetting(): GlobPattern[] {
|
||||
return [
|
||||
...workspace.getConfiguration().get("foam.files.ignore", []),
|
||||
...Object.keys(workspace.getConfiguration().get("files.exclude", {}))
|
||||
...workspace.getConfiguration().get('foam.files.ignore', []),
|
||||
...Object.keys(workspace.getConfiguration().get('files.exclude', {})),
|
||||
];
|
||||
}
|
||||
|
||||
/** Retrieves the maximum length for a Graph node title. */
|
||||
export function getTitleMaxLength(): number {
|
||||
return workspace.getConfiguration("foam.graph").get("titleMaxLength");
|
||||
return workspace.getConfiguration('foam.graph').get('titleMaxLength');
|
||||
}
|
||||
|
||||
/** Retrive the graph's style object */
|
||||
export function getGraphStyle(): object {
|
||||
return workspace.getConfiguration('foam.graph').get('style');
|
||||
}
|
||||
|
||||
export function getFoamLoggerLevel(): LogLevel {
|
||||
return workspace.getConfiguration("foam.logging").get("level") ?? "info";
|
||||
return workspace.getConfiguration('foam.logging').get('level') ?? 'info';
|
||||
}
|
||||
|
||||
30
packages/foam-vscode/src/test/run-tests.ts
Normal file
30
packages/foam-vscode/src/test/run-tests.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import * as path from 'path';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import { runTests } from 'vscode-test';
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
// The folder containing the Extension Manifest package.json
|
||||
// Passed to `--extensionDevelopmentPath`
|
||||
const extensionDevelopmentPath = path.resolve(__dirname, '../../');
|
||||
|
||||
// The path to the extension test script
|
||||
// Passed to --extensionTestsPath
|
||||
const extensionTestsPath = path.resolve(__dirname, './suite');
|
||||
|
||||
const tmpWorkspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'foam-'));
|
||||
|
||||
// Download VS Code, unzip it and run the integration test
|
||||
await runTests({
|
||||
extensionDevelopmentPath,
|
||||
extensionTestsPath,
|
||||
launchArgs: [tmpWorkspaceDir, '--disable-extensions'],
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Failed to run tests');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
66
packages/foam-vscode/src/test/suite.ts
Normal file
66
packages/foam-vscode/src/test/suite.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
// Based on https://github.com/svsool/vscode-memo/blob/master/src/test/testRunner.ts
|
||||
/**
|
||||
* We use the following convention in Foam:
|
||||
* - *.test.ts are unit tests
|
||||
* they might still rely on vscode API and hence will be run in this environment, but
|
||||
* are fundamentally about testing functions in isolations
|
||||
* - *.spec.ts are integration tests
|
||||
* they will make direct use of the vscode API to be invoked as commands, create editors,
|
||||
* and so on..
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import { runCLI } from '@jest/core';
|
||||
|
||||
const rootDir = path.resolve(__dirname, '../..');
|
||||
|
||||
export function run(): Promise<void> {
|
||||
process.stdout.write = (buffer: string) => {
|
||||
console.log(buffer);
|
||||
return true;
|
||||
};
|
||||
process.stderr.write = (buffer: string) => {
|
||||
console.error(buffer);
|
||||
return true;
|
||||
};
|
||||
process.env.FORCE_COLOR = '1';
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const { results } = await runCLI(
|
||||
{
|
||||
rootDir,
|
||||
roots: ['<rootDir>/src'],
|
||||
transform: JSON.stringify({ '^.+\\.ts$': 'ts-jest' }),
|
||||
runInBand: true,
|
||||
testRegex: '\\.(test|spec)\\.ts$',
|
||||
testEnvironment:
|
||||
'<rootDir>/src/test/support/extended-vscode-environment.js',
|
||||
setupFiles: ['<rootDir>/src/test/support/jest-setup.ts'],
|
||||
setupFilesAfterEnv: ['jest-extended'],
|
||||
globals: JSON.stringify({
|
||||
'ts-jest': {
|
||||
tsconfig: path.resolve(rootDir, './tsconfig.json'),
|
||||
},
|
||||
}),
|
||||
testTimeout: 20000,
|
||||
verbose: true,
|
||||
colors: true,
|
||||
} as any,
|
||||
[rootDir]
|
||||
);
|
||||
|
||||
const failures = results.testResults.reduce(
|
||||
(acc, res) => (res.failureMessage ? acc + 1 : acc),
|
||||
0
|
||||
);
|
||||
|
||||
return failures === 0
|
||||
? resolve()
|
||||
: reject(`${failures} tests have failed!`);
|
||||
} catch (error) {
|
||||
return reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Based on https://github.com/svsool/vscode-memo/blob/master/src/test/env/ExtendedVscodeEnvironment.js
|
||||
const VscodeEnvironment = require('jest-environment-vscode');
|
||||
|
||||
class ExtendedVscodeEnvironment extends VscodeEnvironment {
|
||||
async setup() {
|
||||
await super.setup();
|
||||
// Expose RegExp otherwise document.getWordRangeAtPosition won't work as supposed.
|
||||
// Implementation of getWordRangeAtPosition uses "instanceof RegExp" which returns false
|
||||
// due to Jest running tests in the different vm context.
|
||||
// See https://github.com/nodejs/node-v0.x-archive/issues/1277.
|
||||
this.global.RegExp = RegExp;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ExtendedVscodeEnvironment;
|
||||
2
packages/foam-vscode/src/test/support/jest-setup.ts
Normal file
2
packages/foam-vscode/src/test/support/jest-setup.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
// Based on https://github.com/svsool/vscode-memo/blob/master/src/test/config/jestSetup.ts
|
||||
jest.mock('vscode', () => (global as any).vscode, { virtual: true });
|
||||
6
packages/foam-vscode/src/types.d.ts
vendored
6
packages/foam-vscode/src/types.d.ts
vendored
@@ -1,6 +1,6 @@
|
||||
import { ExtensionContext } from "vscode";
|
||||
import { Foam } from "foam-core";
|
||||
import { ExtensionContext } from 'vscode';
|
||||
import { Foam } from 'foam-core';
|
||||
|
||||
export interface FoamFeature {
|
||||
activate: (context: ExtensionContext, foamPromise: Promise<Foam>) => void
|
||||
activate: (context: ExtensionContext, foamPromise: Promise<Foam>) => void;
|
||||
}
|
||||
|
||||
65
packages/foam-vscode/src/utils.test.ts
Normal file
65
packages/foam-vscode/src/utils.test.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { dropExtension, removeBrackets, toTitleCase } from './utils';
|
||||
|
||||
describe('dropExtension', () => {
|
||||
test('returns file name without extension', () => {
|
||||
expect(dropExtension('file.md')).toEqual('file');
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeBrackets', () => {
|
||||
it('removes the brackets', () => {
|
||||
const input = 'hello world [[this-is-it]]';
|
||||
const actual = removeBrackets(input);
|
||||
const expected = 'hello world This Is It';
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
it('removes the brackets and the md file extension', () => {
|
||||
const input = 'hello world [[this-is-it.md]]';
|
||||
const actual = removeBrackets(input);
|
||||
const expected = 'hello world This Is It';
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
it('removes the brackets and the mdx file extension', () => {
|
||||
const input = 'hello world [[this-is-it.mdx]]';
|
||||
const actual = removeBrackets(input);
|
||||
const expected = 'hello world This Is It';
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
it('removes the brackets and the markdown file extension', () => {
|
||||
const input = 'hello world [[this-is-it.markdown]]';
|
||||
const actual = removeBrackets(input);
|
||||
const expected = 'hello world This Is It';
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
it('removes the brackets even with numbers', () => {
|
||||
const input = 'hello world [[2020-07-21.markdown]]';
|
||||
const actual = removeBrackets(input);
|
||||
const expected = 'hello world 2020 07 21';
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
it('removes brackets for more than one word', () => {
|
||||
const input =
|
||||
'I am reading this as part of the [[book-club]] put on by [[egghead]] folks (Lauro).';
|
||||
const actual = removeBrackets(input);
|
||||
const expected =
|
||||
'I am reading this as part of the Book Club put on by Egghead folks (Lauro).';
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toTitleCase', () => {
|
||||
it('title cases a word', () => {
|
||||
const input =
|
||||
'look at this really long sentence but I am calling it a word';
|
||||
const actual = toTitleCase(input);
|
||||
const expected =
|
||||
'Look At This Really Long Sentence But I Am Calling It A Word';
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
it('works on one word', () => {
|
||||
const input = 'word';
|
||||
const actual = toTitleCase(input);
|
||||
const expected = 'Word';
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
@@ -7,10 +7,10 @@ import {
|
||||
TextEditor,
|
||||
workspace,
|
||||
Uri,
|
||||
Selection
|
||||
} from "vscode";
|
||||
import * as fs from "fs";
|
||||
import { Logger } from "foam-core";
|
||||
Selection,
|
||||
} from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
import { Logger } from 'foam-core';
|
||||
|
||||
interface Point {
|
||||
line: number;
|
||||
@@ -18,34 +18,34 @@ interface Point {
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
export const docConfig = { tab: " ", eol: "\r\n" };
|
||||
export const docConfig = { tab: ' ', eol: '\r\n' };
|
||||
|
||||
export const mdDocSelector = [
|
||||
{ language: "markdown", scheme: "file" },
|
||||
{ language: "markdown", scheme: "untitled" }
|
||||
{ language: 'markdown', scheme: 'file' },
|
||||
{ language: 'markdown', scheme: 'untitled' },
|
||||
];
|
||||
|
||||
export function loadDocConfig() {
|
||||
// Load workspace config
|
||||
let activeEditor = window.activeTextEditor;
|
||||
if (!activeEditor) {
|
||||
Logger.debug("Failed to load config, no active editor");
|
||||
Logger.debug('Failed to load config, no active editor');
|
||||
return;
|
||||
}
|
||||
|
||||
docConfig.eol = activeEditor.document.eol === EndOfLine.CRLF ? "\r\n" : "\n";
|
||||
docConfig.eol = activeEditor.document.eol === EndOfLine.CRLF ? '\r\n' : '\n';
|
||||
|
||||
let tabSize = Number(activeEditor.options.tabSize);
|
||||
let insertSpaces = activeEditor.options.insertSpaces;
|
||||
if (insertSpaces) {
|
||||
docConfig.tab = " ".repeat(tabSize);
|
||||
docConfig.tab = ' '.repeat(tabSize);
|
||||
} else {
|
||||
docConfig.tab = "\t";
|
||||
docConfig.tab = '\t';
|
||||
}
|
||||
}
|
||||
|
||||
export function isMdEditor(editor: TextEditor) {
|
||||
return editor && editor.document && editor.document.languageId === "markdown";
|
||||
return editor && editor.document && editor.document.languageId === 'markdown';
|
||||
}
|
||||
|
||||
export function detectGeneratedCode(
|
||||
@@ -61,7 +61,7 @@ export function detectGeneratedCode(
|
||||
if (headerLine < 0 || headerLine >= footerLine) {
|
||||
return {
|
||||
range: null,
|
||||
lines: []
|
||||
lines: [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ export function detectGeneratedCode(
|
||||
new Position(headerLine, 0),
|
||||
new Position(footerLine, lines[footerLine].length + 1)
|
||||
),
|
||||
lines: lines.slice(headerLine + 1, footerLine + 1)
|
||||
lines: lines.slice(headerLine + 1, footerLine + 1),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -83,9 +83,9 @@ export function getText(range: Range): string {
|
||||
}
|
||||
|
||||
export function dropExtension(path: string): string {
|
||||
const parts = path.split(".");
|
||||
const parts = path.split('.');
|
||||
parts.pop();
|
||||
return parts.join(".");
|
||||
return parts.join('.');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,17 +103,17 @@ export const astPositionToVsCodePosition = (point: Point): Position => {
|
||||
*/
|
||||
export function removeBrackets(s: string): string {
|
||||
// take in the string, split on space
|
||||
const stringSplitBySpace = s.split(" ");
|
||||
const stringSplitBySpace = s.split(' ');
|
||||
|
||||
// loop through words
|
||||
const modifiedWords = stringSplitBySpace.map(currentWord => {
|
||||
if (currentWord.includes("[[")) {
|
||||
if (currentWord.includes('[[')) {
|
||||
// all of these transformations will turn this "[[you-are-awesome]]"
|
||||
// to this "you are awesome"
|
||||
let word = currentWord.replace(/(\[\[)/g, "");
|
||||
word = word.replace(/(\]\])/g, "");
|
||||
word = word.replace(/(.mdx|.md|.markdown)/g, "");
|
||||
word = word.replace(/[-]/g, " ");
|
||||
let word = currentWord.replace(/(\[\[)/g, '');
|
||||
word = word.replace(/(\]\])/g, '');
|
||||
word = word.replace(/(.mdx|.md|.markdown)/g, '');
|
||||
word = word.replace(/[-]/g, ' ');
|
||||
|
||||
// then we titlecase the word so "you are awesome"
|
||||
// becomes "You Are Awesome"
|
||||
@@ -125,7 +125,7 @@ export function removeBrackets(s: string): string {
|
||||
return currentWord;
|
||||
});
|
||||
|
||||
return modifiedWords.join(" ");
|
||||
return modifiedWords.join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,9 +135,9 @@ export function removeBrackets(s: string): string {
|
||||
*/
|
||||
export function toTitleCase(word: string): string {
|
||||
return word
|
||||
.split(" ")
|
||||
.split(' ')
|
||||
.map(word => word[0].toUpperCase() + word.substring(1))
|
||||
.join(" ");
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,10 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="graph" style="position: absolute; top: 0; left: 0; right: 0; bottom: 0;"></div>
|
||||
<div
|
||||
id="graph"
|
||||
style="position: absolute; top: 0; left: 0; right: 0; bottom: 0;"
|
||||
></div>
|
||||
<!-- To test the graph locally in a broswer:
|
||||
1. copy the json data object received in the message payload
|
||||
2. paste the content in test-data.js
|
||||
|
||||
@@ -1,29 +1,20 @@
|
||||
const CONTAINER_ID = "graph";
|
||||
const CONTAINER_ID = 'graph';
|
||||
|
||||
function getStyle(name, fallback) {
|
||||
return (
|
||||
getComputedStyle(document.documentElement).getPropertyValue(name) ||
|
||||
fallback
|
||||
);
|
||||
}
|
||||
|
||||
const style = {
|
||||
background: getStyle(`--vscode-panel-background`, "#202020"),
|
||||
fontSize: parseInt(getStyle(`--vscode-font-size`, 12)) - 2,
|
||||
highlightedForeground: getStyle(
|
||||
"--vscode-list-highlightForeground",
|
||||
"#f9c74f"
|
||||
),
|
||||
/** The style fallback. This values should only be set when all else failed. */
|
||||
const styleFallback = {
|
||||
background: '#202020',
|
||||
fontSize: 12,
|
||||
highlightedForeground: '#f9c74f',
|
||||
node: {
|
||||
note: getStyle("--vscode-editor-foreground", "#277da1"),
|
||||
nonExistingNote: getStyle(
|
||||
"--vscode-list-deemphasizedForeground",
|
||||
"#545454"
|
||||
),
|
||||
unknown: getStyle("--vscode-editor-foreground", "#f94144")
|
||||
}
|
||||
note: '#277da1',
|
||||
placeholder: '#545454',
|
||||
},
|
||||
};
|
||||
|
||||
function getStyle(name) {
|
||||
return getComputedStyle(document.documentElement).getPropertyValue(name);
|
||||
}
|
||||
|
||||
const sizeScale = d3
|
||||
.scaleLinear()
|
||||
.domain([0, 30])
|
||||
@@ -36,6 +27,21 @@ const labelAlpha = d3
|
||||
.range([0, 1])
|
||||
.clamp(true);
|
||||
|
||||
const defaultStyle = {
|
||||
background: getStyle(`--vscode-panel-background`) ?? styleFallback.background,
|
||||
fontSize:
|
||||
parseInt(getStyle(`--vscode-font-size`) ?? styleFallback.fontSize) - 2,
|
||||
highlightedForeground:
|
||||
getStyle('--vscode-list-highlightForeground') ??
|
||||
styleFallback.highlightedForeground,
|
||||
node: {
|
||||
note: getStyle('--vscode-editor-foreground') ?? styleFallback.node.note,
|
||||
placeholder:
|
||||
getStyle('--vscode-list-deemphasizedForeground') ??
|
||||
styleFallback.node.placeholder,
|
||||
},
|
||||
};
|
||||
|
||||
let model = {
|
||||
selectedNodes: new Set(),
|
||||
hoverNode: null,
|
||||
@@ -44,8 +50,13 @@ let model = {
|
||||
nodeInfo: {},
|
||||
data: {
|
||||
nodes: [],
|
||||
links: []
|
||||
}
|
||||
links: [],
|
||||
},
|
||||
/** The style property.
|
||||
* It tries to be set using VSCode values,
|
||||
* in the case it fails, use the fallback style values.
|
||||
*/
|
||||
style: defaultStyle,
|
||||
};
|
||||
const graph = ForceGraph();
|
||||
|
||||
@@ -92,7 +103,7 @@ const Actions = {
|
||||
});
|
||||
remaining.forEach(nodeId => {
|
||||
m.data.nodes.push({
|
||||
id: nodeId
|
||||
id: nodeId,
|
||||
});
|
||||
});
|
||||
m.data.links = links; // links can be swapped out without problem
|
||||
@@ -118,22 +129,41 @@ const Actions = {
|
||||
highlightNode: nodeId =>
|
||||
update(m => {
|
||||
m.hoverNode = nodeId;
|
||||
})
|
||||
}),
|
||||
/** Applies a new style to the graph,
|
||||
* missing elements are set to their existing values.
|
||||
*
|
||||
* @param {*} newStyle the style to be applied
|
||||
*/
|
||||
updateStyle: newStyle => {
|
||||
if (!newStyle) {
|
||||
return;
|
||||
}
|
||||
model.style = {
|
||||
...defaultStyle,
|
||||
...newStyle,
|
||||
node: {
|
||||
...defaultStyle.node,
|
||||
...newStyle.node,
|
||||
},
|
||||
};
|
||||
graph.backgroundColor(model.style.background);
|
||||
},
|
||||
};
|
||||
|
||||
function initDataviz(channel) {
|
||||
const elem = document.getElementById(CONTAINER_ID);
|
||||
graph(elem)
|
||||
.graphData(model.data)
|
||||
.backgroundColor(style.background)
|
||||
.backgroundColor(model.style.background)
|
||||
.linkHoverPrecision(8)
|
||||
.d3Force("x", d3.forceX())
|
||||
.d3Force("y", d3.forceY())
|
||||
.d3Force("collide", d3.forceCollide(graph.nodeRelSize()))
|
||||
.d3Force('x', d3.forceX())
|
||||
.d3Force('y', d3.forceY())
|
||||
.d3Force('collide', d3.forceCollide(graph.nodeRelSize()))
|
||||
.linkWidth(0.2)
|
||||
.linkDirectionalParticles(1)
|
||||
.linkDirectionalParticleWidth(link =>
|
||||
getLinkState(link, model) === "highlighted" ? 1 : 0
|
||||
getLinkState(link, model) === 'highlighted' ? 1 : 0
|
||||
)
|
||||
.nodeCanvasObject((node, ctx, globalScale) => {
|
||||
const info = model.nodeInfo[node.id];
|
||||
@@ -143,10 +173,10 @@ function initDataviz(channel) {
|
||||
}
|
||||
const size = sizeScale(info.neighbors.length);
|
||||
const { fill, border } = getNodeColor(node.id, model);
|
||||
const fontSize = style.fontSize / globalScale;
|
||||
const fontSize = model.style.fontSize / globalScale;
|
||||
let textColor = d3.rgb(fill);
|
||||
textColor.opacity =
|
||||
getNodeState(node.id, model) === "highlighted"
|
||||
getNodeState(node.id, model) === 'highlighted'
|
||||
? 1
|
||||
: labelAlpha(globalScale);
|
||||
const label = info.title;
|
||||
@@ -161,16 +191,16 @@ function initDataviz(channel) {
|
||||
Actions.highlightNode(node?.id);
|
||||
})
|
||||
.onNodeClick((node, event) => {
|
||||
if (event.getModifierState("Control") || event.getModifierState("Meta")) {
|
||||
if (event.getModifierState('Control') || event.getModifierState('Meta')) {
|
||||
channel.postMessage({
|
||||
type: "webviewDidSelectNode",
|
||||
payload: node.id
|
||||
type: 'webviewDidSelectNode',
|
||||
payload: node.id,
|
||||
});
|
||||
}
|
||||
Actions.selectNode(node.id, event.getModifierState("Shift"));
|
||||
Actions.selectNode(node.id, event.getModifierState('Shift'));
|
||||
})
|
||||
.onBackgroundClick(event => {
|
||||
Actions.selectNode(null, event.getModifierState("Shift"));
|
||||
Actions.selectNode(null, event.getModifierState('Shift'));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -192,52 +222,54 @@ function augmentGraphInfo(data) {
|
||||
|
||||
function getNodeColor(nodeId, model) {
|
||||
const info = model.nodeInfo[nodeId];
|
||||
const typeFill = style.node[info.type || "unknown"];
|
||||
const style = model.style;
|
||||
const typeFill = style.node[info.type ?? 'note'] ?? style.node['note'];
|
||||
switch (getNodeState(nodeId, model)) {
|
||||
case "regular":
|
||||
case 'regular':
|
||||
return { fill: typeFill, border: typeFill };
|
||||
case "lessened":
|
||||
case 'lessened':
|
||||
const darker = d3.hsl(typeFill).darker(3);
|
||||
return { fill: darker, border: darker };
|
||||
case "highlighted":
|
||||
case 'highlighted':
|
||||
return {
|
||||
fill: typeFill,
|
||||
border: style.highlightedForeground
|
||||
border: style.highlightedForeground,
|
||||
};
|
||||
default:
|
||||
throw new Error("Unknown type for node", nodeId);
|
||||
throw new Error('Unknown type for node', nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
function getLinkColor(link, model) {
|
||||
const style = model.style;
|
||||
switch (getLinkState(link, model)) {
|
||||
case "regular":
|
||||
case 'regular':
|
||||
return d3.hsl(style.node.note).darker(2);
|
||||
case "highlighted":
|
||||
case 'highlighted':
|
||||
return style.highlightedForeground;
|
||||
case "lessened":
|
||||
case 'lessened':
|
||||
return d3.hsl(style.node.note).darker(4);
|
||||
default:
|
||||
throw new Error("Unknown type for link", link);
|
||||
throw new Error('Unknown type for link', link);
|
||||
}
|
||||
}
|
||||
|
||||
function getNodeState(nodeId, model) {
|
||||
return model.selectedNodes.has(nodeId) || model.hoverNode === nodeId
|
||||
? "highlighted"
|
||||
? 'highlighted'
|
||||
: model.focusNodes.size === 0
|
||||
? "regular"
|
||||
? 'regular'
|
||||
: model.focusNodes.has(nodeId)
|
||||
? "regular"
|
||||
: "lessened";
|
||||
? 'regular'
|
||||
: 'lessened';
|
||||
}
|
||||
|
||||
function getLinkState(link, model) {
|
||||
return model.focusNodes.size === 0
|
||||
? "regular"
|
||||
? 'regular'
|
||||
: model.focusLinks.has(link)
|
||||
? "highlighted"
|
||||
: "lessened";
|
||||
? 'highlighted'
|
||||
: 'lessened';
|
||||
}
|
||||
|
||||
const Draw = ctx => ({
|
||||
@@ -251,12 +283,12 @@ const Draw = ctx => ({
|
||||
},
|
||||
text: function(text, x, y, size, color) {
|
||||
ctx.font = `${size}px Sans-Serif`;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "top";
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillStyle = color;
|
||||
ctx.fillText(text, x, y);
|
||||
return this;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// init the app
|
||||
@@ -265,34 +297,34 @@ try {
|
||||
|
||||
window.onload = () => {
|
||||
initDataviz(vscode);
|
||||
console.log("ready");
|
||||
console.log('ready');
|
||||
vscode.postMessage({
|
||||
type: "webviewDidLoad"
|
||||
type: 'webviewDidLoad',
|
||||
});
|
||||
};
|
||||
window.addEventListener("error", error => {
|
||||
|
||||
window.addEventListener('error', error => {
|
||||
vscode.postMessage({
|
||||
type: "error",
|
||||
type: 'error',
|
||||
payload: {
|
||||
message: error.message,
|
||||
filename: error.filename,
|
||||
lineno: error.lineno,
|
||||
colno: error.colno,
|
||||
error: error.error
|
||||
}
|
||||
error: error.error,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener("message", event => {
|
||||
window.addEventListener('message', event => {
|
||||
const message = event.data;
|
||||
|
||||
switch (message.type) {
|
||||
case "didUpdateGraphData":
|
||||
case 'didUpdateGraphData':
|
||||
const graphData = augmentGraphInfo(message.payload);
|
||||
console.log("didUpdateGraphData", graphData);
|
||||
console.log('didUpdateGraphData', graphData);
|
||||
Actions.refresh(graphData);
|
||||
break;
|
||||
case "didSelectNote":
|
||||
case 'didSelectNote':
|
||||
const noteId = message.payload;
|
||||
const node = graph.graphData().nodes.find(node => node.id === noteId);
|
||||
if (node) {
|
||||
@@ -300,24 +332,28 @@ try {
|
||||
Actions.selectNode(noteId);
|
||||
}
|
||||
break;
|
||||
case 'didUpdateStyle':
|
||||
const style = message.payload;
|
||||
Actions.updateStyle(style);
|
||||
break;
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
console.log("VsCode not detected");
|
||||
console.log('VsCode not detected');
|
||||
}
|
||||
|
||||
window.addEventListener("resize", () => {
|
||||
window.addEventListener('resize', () => {
|
||||
graph.width(window.innerWidth).height(window.innerHeight);
|
||||
});
|
||||
|
||||
// For testing
|
||||
if (window.data) {
|
||||
console.log("Test mode");
|
||||
console.log('Test mode');
|
||||
window.model = model;
|
||||
window.graph = graph;
|
||||
window.onload = () => {
|
||||
initDataviz({
|
||||
postMessage: message => console.log("message", message)
|
||||
postMessage: message => console.log('message', message),
|
||||
});
|
||||
const graphData = augmentGraphInfo(window.data);
|
||||
Actions.refresh(graphData);
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
// @note: This will fail due to utils importing 'vscode'
|
||||
// which needs to be mocked in the jest test environment.
|
||||
// See: https://github.com/microsoft/vscode-test/issues/37
|
||||
import { dropExtension, removeBrackets, toTitleCase } from '../src/utils';
|
||||
|
||||
describe("dropExtension", () => {
|
||||
test("returns file name without extension", () => {
|
||||
expect(dropExtension('file.md')).toEqual('file');
|
||||
});
|
||||
});
|
||||
|
||||
describe("removeBrackets", () => {
|
||||
it("removes the brackets", () => {
|
||||
const input = "hello world [[this-is-it]]";
|
||||
const actual = removeBrackets(input);
|
||||
const expected = "hello world This Is It";
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
it("removes the brackets and the md file extension", () => {
|
||||
const input = "hello world [[this-is-it.md]]";
|
||||
const actual = removeBrackets(input);
|
||||
const expected = "hello world This Is It";
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
it("removes the brackets and the mdx file extension", () => {
|
||||
const input = "hello world [[this-is-it.mdx]]";
|
||||
const actual = removeBrackets(input);
|
||||
const expected = "hello world This Is It";
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
it("removes the brackets and the markdown file extension", () => {
|
||||
const input = "hello world [[this-is-it.markdown]]";
|
||||
const actual = removeBrackets(input);
|
||||
const expected = "hello world This Is It";
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
it("removes the brackets even with numbers", () => {
|
||||
const input = "hello world [[2020-07-21.markdown]]";
|
||||
const actual = removeBrackets(input);
|
||||
const expected = "hello world 2020 07 21";
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
it("removes brackets for more than one word", () => {
|
||||
const input = "I am reading this as part of the [[book-club]] put on by [[egghead]] folks (Lauro).";
|
||||
const actual = removeBrackets(input);
|
||||
const expected = "I am reading this as part of the Book Club put on by Egghead folks (Lauro).";
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("toTitleCase", () => {
|
||||
it("title cases a word", () => {
|
||||
const input = "look at this really long sentence but I am calling it a word";
|
||||
const actual = toTitleCase(input);
|
||||
const expected = "Look At This Really Long Sentence But I Am Calling It A Word";
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
it("works on one word", () => {
|
||||
const input = "word";
|
||||
const actual = toTitleCase(input);
|
||||
const expected = "Word";
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
trailingComma: 'es5',
|
||||
tabWidth: 2,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
};
|
||||
126
readme.md
126
readme.md
@@ -4,7 +4,7 @@
|
||||
# Foam
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
[](#contributors-)
|
||||
[](#contributors-)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
**Foam** is a personal knowledge management and sharing system inspired by [Roam Research](https://roamresearch.com/), built on [Visual Studio Code](https://code.visualstudio.com/) and [GitHub](https://github.com/).
|
||||
@@ -38,6 +38,8 @@ 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).
|
||||
|
||||
## License
|
||||
|
||||
Foam is licensed under the [MIT license](LICENSE).
|
||||
@@ -58,81 +60,89 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<!-- markdownlint-disable -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><a href="https://jevakallio.dev/"><img src="https://avatars1.githubusercontent.com/u/1203949?v=4" width="60px;" alt=""/><br /><sub><b>Jani Eväkallio</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jevakallio" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jevakallio" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://joeprevite.com/"><img src="https://avatars3.githubusercontent.com/u/3806031?v=4" width="60px;" alt=""/><br /><sub><b>Joe Previte</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jsjoeio" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jsjoeio" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/riccardoferretti"><img src="https://avatars3.githubusercontent.com/u/457005?v=4" width="60px;" alt=""/><br /><sub><b>Riccardo</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=riccardoferretti" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=riccardoferretti" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://ojanaho.com/"><img src="https://avatars0.githubusercontent.com/u/2180090?v=4" width="60px;" alt=""/><br /><sub><b>Janne Ojanaho</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jojanaho" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jojanaho" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://bypaulshen.com/"><img src="https://avatars3.githubusercontent.com/u/2266187?v=4" width="60px;" alt=""/><br /><sub><b>Paul Shen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=paulshen" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/coffenbacher"><img src="https://avatars0.githubusercontent.com/u/245867?v=4" width="60px;" alt=""/><br /><sub><b>coffenbacher</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=coffenbacher" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://mathieu.dutour.me/"><img src="https://avatars2.githubusercontent.com/u/3254314?v=4" width="60px;" alt=""/><br /><sub><b>Mathieu Dutour</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=mathieudutour" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://jevakallio.dev/"><img src="https://avatars1.githubusercontent.com/u/1203949?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Jani Eväkallio</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jevakallio" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jevakallio" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://joeprevite.com/"><img src="https://avatars3.githubusercontent.com/u/3806031?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Joe Previte</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jsjoeio" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jsjoeio" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/riccardoferretti"><img src="https://avatars3.githubusercontent.com/u/457005?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Riccardo</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=riccardoferretti" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=riccardoferretti" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://ojanaho.com/"><img src="https://avatars0.githubusercontent.com/u/2180090?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Janne Ojanaho</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jojanaho" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jojanaho" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://bypaulshen.com/"><img src="https://avatars3.githubusercontent.com/u/2266187?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Paul Shen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=paulshen" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/coffenbacher"><img src="https://avatars0.githubusercontent.com/u/245867?v=4?s=60" width="60px;" alt=""/><br /><sub><b>coffenbacher</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=coffenbacher" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://mathieu.dutour.me/"><img src="https://avatars2.githubusercontent.com/u/3254314?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Mathieu Dutour</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=mathieudutour" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/presidentelect"><img src="https://avatars2.githubusercontent.com/u/1242300?v=4" width="60px;" alt=""/><br /><sub><b>Michael Hansen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=presidentelect" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://klickverbot.at/"><img src="https://avatars1.githubusercontent.com/u/19335?v=4" width="60px;" alt=""/><br /><sub><b>David Nadlinger</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dnadlinger" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://pluckd.co/"><img src="https://avatars2.githubusercontent.com/u/20598571?v=4" width="60px;" alt=""/><br /><sub><b>Fernando</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MrCordeiro" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/jfgonzalez7"><img src="https://avatars3.githubusercontent.com/u/58857736?v=4" width="60px;" alt=""/><br /><sub><b>Juan Gonzalez</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jfgonzalez7" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.louiechristie.com/"><img src="https://avatars1.githubusercontent.com/u/6807448?v=4" width="60px;" alt=""/><br /><sub><b>Louie Christie</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=louiechristie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://supersandro.de/"><img src="https://avatars2.githubusercontent.com/u/7258858?v=4" width="60px;" alt=""/><br /><sub><b>Sandro</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=SuperSandro2000" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Skn0tt"><img src="https://avatars1.githubusercontent.com/u/14912729?v=4" width="60px;" alt=""/><br /><sub><b>Simon Knott</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Skn0tt" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/presidentelect"><img src="https://avatars2.githubusercontent.com/u/1242300?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Michael Hansen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=presidentelect" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://klickverbot.at/"><img src="https://avatars1.githubusercontent.com/u/19335?v=4?s=60" width="60px;" alt=""/><br /><sub><b>David Nadlinger</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dnadlinger" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://pluckd.co/"><img src="https://avatars2.githubusercontent.com/u/20598571?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Fernando</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MrCordeiro" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/jfgonzalez7"><img src="https://avatars3.githubusercontent.com/u/58857736?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Juan Gonzalez</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jfgonzalez7" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.louiechristie.com/"><img src="https://avatars1.githubusercontent.com/u/6807448?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Louie Christie</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=louiechristie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://supersandro.de/"><img src="https://avatars2.githubusercontent.com/u/7258858?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Sandro</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=SuperSandro2000" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Skn0tt"><img src="https://avatars1.githubusercontent.com/u/14912729?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Simon Knott</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Skn0tt" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://styfle.dev/"><img src="https://avatars1.githubusercontent.com/u/229881?v=4" width="60px;" alt=""/><br /><sub><b>Steven</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=styfle" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Georift"><img src="https://avatars2.githubusercontent.com/u/859430?v=4" width="60px;" alt=""/><br /><sub><b>Tim</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Georift" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/sauravkhdoolia"><img src="https://avatars1.githubusercontent.com/u/34188267?v=4" width="60px;" alt=""/><br /><sub><b>Saurav Khdoolia</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=sauravkhdoolia" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://anku.netlify.com/"><img src="https://avatars1.githubusercontent.com/u/22813027?v=4" width="60px;" alt=""/><br /><sub><b>Ankit Tiwari</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=anku255" title="Documentation">📖</a> <a href="https://github.com/foambubble/foam/commits?author=anku255" title="Tests">⚠️</a> <a href="https://github.com/foambubble/foam/commits?author=anku255" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ayushbaweja"><img src="https://avatars1.githubusercontent.com/u/44344063?v=4" width="60px;" alt=""/><br /><sub><b>Ayush Baweja</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ayushbaweja" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/TaiChi-IO"><img src="https://avatars3.githubusercontent.com/u/65092992?v=4" width="60px;" alt=""/><br /><sub><b>TaiChi-IO</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=TaiChi-IO" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/juanfrank77"><img src="https://avatars1.githubusercontent.com/u/12146882?v=4" width="60px;" alt=""/><br /><sub><b>Juan F Gonzalez </b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=juanfrank77" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://styfle.dev/"><img src="https://avatars1.githubusercontent.com/u/229881?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Steven</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=styfle" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Georift"><img src="https://avatars2.githubusercontent.com/u/859430?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Tim</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Georift" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/sauravkhdoolia"><img src="https://avatars1.githubusercontent.com/u/34188267?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Saurav Khdoolia</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=sauravkhdoolia" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://anku.netlify.com/"><img src="https://avatars1.githubusercontent.com/u/22813027?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Ankit Tiwari</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=anku255" title="Documentation">📖</a> <a href="https://github.com/foambubble/foam/commits?author=anku255" title="Tests">⚠️</a> <a href="https://github.com/foambubble/foam/commits?author=anku255" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ayushbaweja"><img src="https://avatars1.githubusercontent.com/u/44344063?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Ayush Baweja</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ayushbaweja" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/TaiChi-IO"><img src="https://avatars3.githubusercontent.com/u/65092992?v=4?s=60" width="60px;" alt=""/><br /><sub><b>TaiChi-IO</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=TaiChi-IO" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/juanfrank77"><img src="https://avatars1.githubusercontent.com/u/12146882?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Juan F Gonzalez </b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=juanfrank77" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://sanketdg.github.io"><img src="https://avatars3.githubusercontent.com/u/8980971?v=4" width="60px;" alt=""/><br /><sub><b>Sanket Dasgupta</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=SanketDG" title="Documentation">📖</a> <a href="https://github.com/foambubble/foam/commits?author=SanketDG" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/nstafie"><img src="https://avatars1.githubusercontent.com/u/10801854?v=4" width="60px;" alt=""/><br /><sub><b>Nicholas Stafie</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=nstafie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/francishamel"><img src="https://avatars3.githubusercontent.com/u/36383308?v=4" width="60px;" alt=""/><br /><sub><b>Francis Hamel</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=francishamel" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://digiguru.co.uk"><img src="https://avatars1.githubusercontent.com/u/619436?v=4" width="60px;" alt=""/><br /><sub><b>digiguru</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=digiguru" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=digiguru" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/chirag-singhal"><img src="https://avatars3.githubusercontent.com/u/42653703?v=4" width="60px;" alt=""/><br /><sub><b>CHIRAG SINGHAL</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=chirag-singhal" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/lostintangent"><img src="https://avatars3.githubusercontent.com/u/116461?v=4" width="60px;" alt=""/><br /><sub><b>Jonathan Carter</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=lostintangent" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.synesthesia.co.uk"><img src="https://avatars3.githubusercontent.com/u/181399?v=4" width="60px;" alt=""/><br /><sub><b>Julian Elve</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=synesthesia" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://sanketdg.github.io"><img src="https://avatars3.githubusercontent.com/u/8980971?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Sanket Dasgupta</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=SanketDG" title="Documentation">📖</a> <a href="https://github.com/foambubble/foam/commits?author=SanketDG" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/nstafie"><img src="https://avatars1.githubusercontent.com/u/10801854?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Nicholas Stafie</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=nstafie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/francishamel"><img src="https://avatars3.githubusercontent.com/u/36383308?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Francis Hamel</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=francishamel" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://digiguru.co.uk"><img src="https://avatars1.githubusercontent.com/u/619436?v=4?s=60" width="60px;" alt=""/><br /><sub><b>digiguru</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=digiguru" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=digiguru" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/chirag-singhal"><img src="https://avatars3.githubusercontent.com/u/42653703?v=4?s=60" width="60px;" alt=""/><br /><sub><b>CHIRAG SINGHAL</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=chirag-singhal" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/lostintangent"><img src="https://avatars3.githubusercontent.com/u/116461?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Jonathan Carter</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=lostintangent" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.synesthesia.co.uk"><img src="https://avatars3.githubusercontent.com/u/181399?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Julian Elve</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=synesthesia" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/thomaskoppelaar"><img src="https://avatars3.githubusercontent.com/u/36331365?v=4" width="60px;" alt=""/><br /><sub><b>Thomas Koppelaar</b></sub></a><br /><a href="#question-thomaskoppelaar" title="Answering Questions">💬</a> <a href="https://github.com/foambubble/foam/commits?author=thomaskoppelaar" title="Code">💻</a> <a href="#userTesting-thomaskoppelaar" title="User Testing">📓</a></td>
|
||||
<td align="center"><a href="http://www.akshaymehra.com"><img src="https://avatars1.githubusercontent.com/u/8671497?v=4" width="60px;" alt=""/><br /><sub><b>Akshay</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MehraAkshay" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://johnlindquist.com"><img src="https://avatars0.githubusercontent.com/u/36073?v=4" width="60px;" alt=""/><br /><sub><b>John Lindquist</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=johnlindquist" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ashwin.run/"><img src="https://avatars2.githubusercontent.com/u/1689183?v=4" width="60px;" alt=""/><br /><sub><b>Ashwin Ramaswami</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=epicfaace" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Klaudioz"><img src="https://avatars1.githubusercontent.com/u/632625?v=4" width="60px;" alt=""/><br /><sub><b>Claudio Canales</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Klaudioz" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/vitaly-pevgonen"><img src="https://avatars0.githubusercontent.com/u/6272738?v=4" width="60px;" alt=""/><br /><sub><b>vitaly-pevgonen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=vitaly-pevgonen" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://dshemetov.github.io"><img src="https://avatars0.githubusercontent.com/u/1810426?v=4" width="60px;" alt=""/><br /><sub><b>Dmitry Shemetov</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dshemetov" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/thomaskoppelaar"><img src="https://avatars3.githubusercontent.com/u/36331365?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Thomas Koppelaar</b></sub></a><br /><a href="#question-thomaskoppelaar" title="Answering Questions">💬</a> <a href="https://github.com/foambubble/foam/commits?author=thomaskoppelaar" title="Code">💻</a> <a href="#userTesting-thomaskoppelaar" title="User Testing">📓</a></td>
|
||||
<td align="center"><a href="http://www.akshaymehra.com"><img src="https://avatars1.githubusercontent.com/u/8671497?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Akshay</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MehraAkshay" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://johnlindquist.com"><img src="https://avatars0.githubusercontent.com/u/36073?v=4?s=60" width="60px;" alt=""/><br /><sub><b>John Lindquist</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=johnlindquist" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ashwin.run/"><img src="https://avatars2.githubusercontent.com/u/1689183?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Ashwin Ramaswami</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=epicfaace" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Klaudioz"><img src="https://avatars1.githubusercontent.com/u/632625?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Claudio Canales</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Klaudioz" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/vitaly-pevgonen"><img src="https://avatars0.githubusercontent.com/u/6272738?v=4?s=60" width="60px;" alt=""/><br /><sub><b>vitaly-pevgonen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=vitaly-pevgonen" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://dshemetov.github.io"><img src="https://avatars0.githubusercontent.com/u/1810426?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Dmitry Shemetov</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dshemetov" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/hooncp"><img src="https://avatars1.githubusercontent.com/u/48883554?v=4" width="60px;" alt=""/><br /><sub><b>hooncp</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=hooncp" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://rt-canada.ca"><img src="https://avatars1.githubusercontent.com/u/13721239?v=4" width="60px;" alt=""/><br /><sub><b>Martin Laws</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=martinlaws" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://seanksmith.me"><img src="https://avatars3.githubusercontent.com/u/2085441?v=4" width="60px;" alt=""/><br /><sub><b>Sean K Smith</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=sksmith" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/kevin-neely/"><img src="https://avatars1.githubusercontent.com/u/37545028?v=4" width="60px;" alt=""/><br /><sub><b>Kevin Neely</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=kneely" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ariefrahmansyah.dev"><img src="https://avatars3.githubusercontent.com/u/8122852?v=4" width="60px;" alt=""/><br /><sub><b>Arief Rahmansyah</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ariefrahmansyah" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://vhanda.in"><img src="https://avatars2.githubusercontent.com/u/426467?v=4" width="60px;" alt=""/><br /><sub><b>Vishesh Handa</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=vHanda" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.linkedin.com/in/heroichitesh"><img src="https://avatars3.githubusercontent.com/u/37622734?v=4" width="60px;" alt=""/><br /><sub><b>Hitesh Kumar</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=HeroicHitesh" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/hooncp"><img src="https://avatars1.githubusercontent.com/u/48883554?v=4?s=60" width="60px;" alt=""/><br /><sub><b>hooncp</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=hooncp" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://rt-canada.ca"><img src="https://avatars1.githubusercontent.com/u/13721239?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Martin Laws</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=martinlaws" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://seanksmith.me"><img src="https://avatars3.githubusercontent.com/u/2085441?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Sean K Smith</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=sksmith" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/kevin-neely/"><img src="https://avatars1.githubusercontent.com/u/37545028?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Kevin Neely</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=kneely" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ariefrahmansyah.dev"><img src="https://avatars3.githubusercontent.com/u/8122852?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Arief Rahmansyah</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ariefrahmansyah" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://vhanda.in"><img src="https://avatars2.githubusercontent.com/u/426467?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Vishesh Handa</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=vHanda" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.linkedin.com/in/heroichitesh"><img src="https://avatars3.githubusercontent.com/u/37622734?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Hitesh Kumar</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=HeroicHitesh" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://spencerwoo.com"><img src="https://avatars2.githubusercontent.com/u/32114380?v=4" width="60px;" alt=""/><br /><sub><b>Spencer Woo</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=spencerwooo" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ingalless.com"><img src="https://avatars3.githubusercontent.com/u/22981941?v=4" width="60px;" alt=""/><br /><sub><b>ingalless</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ingalless" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=ingalless" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://jmg-duarte.github.io"><img src="https://avatars2.githubusercontent.com/u/15343819?v=4" width="60px;" alt=""/><br /><sub><b>José Duarte</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jmg-duarte" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jmg-duarte" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.yenly.wtf"><img src="https://avatars1.githubusercontent.com/u/6759658?v=4" width="60px;" alt=""/><br /><sub><b>Yenly</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=yenly" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.hikerpig.cn"><img src="https://avatars1.githubusercontent.com/u/2259688?v=4" width="60px;" alt=""/><br /><sub><b>hikerpig</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=hikerpig" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://sigfried.org"><img src="https://avatars1.githubusercontent.com/u/1586931?v=4" width="60px;" alt=""/><br /><sub><b>Sigfried Gold</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Sigfried" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.tristansokol.com"><img src="https://avatars3.githubusercontent.com/u/867661?v=4" width="60px;" alt=""/><br /><sub><b>Tristan Sokol</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=tristansokol" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://spencerwoo.com"><img src="https://avatars2.githubusercontent.com/u/32114380?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Spencer Woo</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=spencerwooo" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ingalless.com"><img src="https://avatars3.githubusercontent.com/u/22981941?v=4?s=60" width="60px;" alt=""/><br /><sub><b>ingalless</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ingalless" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=ingalless" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://jmg-duarte.github.io"><img src="https://avatars2.githubusercontent.com/u/15343819?v=4?s=60" width="60px;" alt=""/><br /><sub><b>José Duarte</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jmg-duarte" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jmg-duarte" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.yenly.wtf"><img src="https://avatars1.githubusercontent.com/u/6759658?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Yenly</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=yenly" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.hikerpig.cn"><img src="https://avatars1.githubusercontent.com/u/2259688?v=4?s=60" width="60px;" alt=""/><br /><sub><b>hikerpig</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=hikerpig" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://sigfried.org"><img src="https://avatars1.githubusercontent.com/u/1586931?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Sigfried Gold</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Sigfried" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.tristansokol.com"><img src="https://avatars3.githubusercontent.com/u/867661?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Tristan Sokol</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=tristansokol" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://umbrellait.com"><img src="https://avatars0.githubusercontent.com/u/49779373?v=4" width="60px;" alt=""/><br /><sub><b>Danil Rodin</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=umbrellait-danil-rodin" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/scottjoewilliams/"><img src="https://avatars1.githubusercontent.com/u/2026866?v=4" width="60px;" alt=""/><br /><sub><b>Scott Williams</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=scott-joe" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://jackiexiao.github.io/blog"><img src="https://avatars2.githubusercontent.com/u/18050469?v=4" width="60px;" alt=""/><br /><sub><b>jackiexiao</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Jackiexiao" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://generativist.substack.com/"><img src="https://avatars3.githubusercontent.com/u/78835?v=4" width="60px;" alt=""/><br /><sub><b>John B Nelson</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jbn" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/asifm"><img src="https://avatars2.githubusercontent.com/u/3958387?v=4" width="60px;" alt=""/><br /><sub><b>Asif Mehedi</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=asifm" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/litanlitudan"><img src="https://avatars2.githubusercontent.com/u/4970420?v=4" width="60px;" alt=""/><br /><sub><b>Tan Li</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=litanlitudan" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://shaunagordon.com"><img src="https://avatars1.githubusercontent.com/u/579361?v=4" width="60px;" alt=""/><br /><sub><b>Shauna Gordon</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ShaunaGordon" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://umbrellait.com"><img src="https://avatars0.githubusercontent.com/u/49779373?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Danil Rodin</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=umbrellait-danil-rodin" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/scottjoewilliams/"><img src="https://avatars1.githubusercontent.com/u/2026866?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Scott Williams</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=scott-joe" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://jackiexiao.github.io/blog"><img src="https://avatars2.githubusercontent.com/u/18050469?v=4?s=60" width="60px;" alt=""/><br /><sub><b>jackiexiao</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Jackiexiao" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://generativist.substack.com/"><img src="https://avatars3.githubusercontent.com/u/78835?v=4?s=60" width="60px;" alt=""/><br /><sub><b>John B Nelson</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jbn" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/asifm"><img src="https://avatars2.githubusercontent.com/u/3958387?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Asif Mehedi</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=asifm" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/litanlitudan"><img src="https://avatars2.githubusercontent.com/u/4970420?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Tan Li</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=litanlitudan" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://shaunagordon.com"><img src="https://avatars1.githubusercontent.com/u/579361?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Shauna Gordon</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ShaunaGordon" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://mcluck.tech"><img src="https://avatars1.githubusercontent.com/u/1753801?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Mike Cluck</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MCluck90" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://brandonpugh.com"><img src="https://avatars1.githubusercontent.com/u/684781?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Brandon Pugh</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=bpugh" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://max.davitt.me"><img src="https://avatars1.githubusercontent.com/u/27709025?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Max Davitt</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=themaxdavitt" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://briananglin.me"><img src="https://avatars3.githubusercontent.com/u/2637602?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Brian Anglin</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=anglinb" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://deft.work"><img src="https://avatars1.githubusercontent.com/u/1455507?v=4?s=60" width="60px;" alt=""/><br /><sub><b>elswork</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=elswork" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-enable -->
|
||||
<!-- markdownlint-restore -->
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
||||
142
yarn.lock
142
yarn.lock
@@ -1656,6 +1656,17 @@
|
||||
"@types/yargs" "^15.0.0"
|
||||
chalk "^4.0.0"
|
||||
|
||||
"@jest/types@^26.6.2":
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e"
|
||||
integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==
|
||||
dependencies:
|
||||
"@types/istanbul-lib-coverage" "^2.0.0"
|
||||
"@types/istanbul-reports" "^3.0.0"
|
||||
"@types/node" "*"
|
||||
"@types/yargs" "^15.0.0"
|
||||
chalk "^4.0.0"
|
||||
|
||||
"@lerna/add@3.21.0":
|
||||
version "3.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.21.0.tgz#27007bde71cc7b0a2969ab3c2f0ae41578b4577b"
|
||||
@@ -2756,6 +2767,21 @@
|
||||
"@types/istanbul-lib-coverage" "*"
|
||||
"@types/istanbul-lib-report" "*"
|
||||
|
||||
"@types/istanbul-reports@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz#508b13aa344fa4976234e75dddcc34925737d821"
|
||||
integrity sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==
|
||||
dependencies:
|
||||
"@types/istanbul-lib-report" "*"
|
||||
|
||||
"@types/jest@26.x":
|
||||
version "26.0.19"
|
||||
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.19.tgz#e6fa1e3def5842ec85045bd5210e9bb8289de790"
|
||||
integrity sha512-jqHoirTG61fee6v6rwbnEuKhpSKih0tuhqeFbCmMmErhtu3BYlOZaXWjffgOstMM4S/3iQD31lI5bGLTrs97yQ==
|
||||
dependencies:
|
||||
jest-diff "^26.0.0"
|
||||
pretty-format "^26.0.0"
|
||||
|
||||
"@types/jest@^24.0.15":
|
||||
version "24.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-24.9.1.tgz#02baf9573c78f1b9974a5f36778b366aa77bd534"
|
||||
@@ -4679,6 +4705,11 @@ diff-sequences@^26.0.0:
|
||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.0.0.tgz#0760059a5c287637b842bd7085311db7060e88a6"
|
||||
integrity sha512-JC/eHYEC3aSS0vZGjuoc4vHA0yAQTzhQQldXMeMF+JlxLGJlCO38Gma82NV9gk1jGFz8mDzUMeaKXvjRRdJ2dg==
|
||||
|
||||
diff-sequences@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1"
|
||||
integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==
|
||||
|
||||
diff@^4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
|
||||
@@ -5367,7 +5398,7 @@ expand-brackets@^2.1.4:
|
||||
snapdragon "^0.8.1"
|
||||
to-regex "^3.0.1"
|
||||
|
||||
expect@^24.9.0:
|
||||
expect@^24.1.0, expect@^24.9.0:
|
||||
version "24.9.0"
|
||||
resolved "https://registry.yarnpkg.com/expect/-/expect-24.9.0.tgz#b75165b4817074fa4a157794f46fe9f1ba15b6ca"
|
||||
integrity sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q==
|
||||
@@ -7039,6 +7070,16 @@ jest-diff@^24.3.0, jest-diff@^24.9.0:
|
||||
jest-get-type "^24.9.0"
|
||||
pretty-format "^24.9.0"
|
||||
|
||||
jest-diff@^26.0.0:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394"
|
||||
integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==
|
||||
dependencies:
|
||||
chalk "^4.0.0"
|
||||
diff-sequences "^26.6.2"
|
||||
jest-get-type "^26.3.0"
|
||||
pretty-format "^26.6.2"
|
||||
|
||||
jest-diff@^26.1.0:
|
||||
version "26.1.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.1.0.tgz#00a549bdc936c9691eb4dc25d1fbd78bf456abb2"
|
||||
@@ -7177,6 +7218,25 @@ jest-environment-node@^26.2.0:
|
||||
jest-mock "^26.2.0"
|
||||
jest-util "^26.2.0"
|
||||
|
||||
jest-environment-vscode@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-environment-vscode/-/jest-environment-vscode-1.0.0.tgz#96367fe8531047e64359e0682deafc973bfaea91"
|
||||
integrity sha512-VKlj5j5pNurFEwWPaDiX1kBgmhWqcJTAZsvEX1x5lh0/+5myjk+qipEs/dPJVRbBPb3XFxiR48XzGn+wOU7SSQ==
|
||||
|
||||
jest-extended@^0.11.5:
|
||||
version "0.11.5"
|
||||
resolved "https://registry.yarnpkg.com/jest-extended/-/jest-extended-0.11.5.tgz#f063b3f1eaadad8d7c13a01f0dfe0f538d498ccf"
|
||||
integrity sha512-3RsdFpLWKScpsLD6hJuyr/tV5iFOrw7v6YjA3tPdda9sJwoHwcMROws5gwiIZfcwhHlJRwFJB2OUvGmF3evV/Q==
|
||||
dependencies:
|
||||
expect "^24.1.0"
|
||||
jest-get-type "^22.4.3"
|
||||
jest-matcher-utils "^22.0.0"
|
||||
|
||||
jest-get-type@^22.4.3:
|
||||
version "22.4.3"
|
||||
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-22.4.3.tgz#e3a8504d8479342dd4420236b322869f18900ce4"
|
||||
integrity sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==
|
||||
|
||||
jest-get-type@^24.9.0:
|
||||
version "24.9.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e"
|
||||
@@ -7187,6 +7247,11 @@ jest-get-type@^26.0.0:
|
||||
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.0.0.tgz#381e986a718998dbfafcd5ec05934be538db4039"
|
||||
integrity sha512-zRc1OAPnnws1EVfykXOj19zo2EMw5Hi6HLbFCSjpuJiXtOWAYIjNsHVSbpQ8bDX7L5BGYGI8m+HmKdjHYFF0kg==
|
||||
|
||||
jest-get-type@^26.3.0:
|
||||
version "26.3.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0"
|
||||
integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==
|
||||
|
||||
jest-haste-map@^24.9.0:
|
||||
version "24.9.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d"
|
||||
@@ -7340,6 +7405,15 @@ jest-leak-detector@^26.2.0:
|
||||
jest-get-type "^26.0.0"
|
||||
pretty-format "^26.2.0"
|
||||
|
||||
jest-matcher-utils@^22.0.0:
|
||||
version "22.4.3"
|
||||
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-22.4.3.tgz#4632fe428ebc73ebc194d3c7b65d37b161f710ff"
|
||||
integrity sha512-lsEHVaTnKzdAPR5t4B6OcxXo9Vy4K+kRRbG5gtddY8lBEC+Mlpvm1CJcsMESRjzUhzkz568exMV1hTB76nAKbA==
|
||||
dependencies:
|
||||
chalk "^2.0.1"
|
||||
jest-get-type "^22.4.3"
|
||||
pretty-format "^22.4.3"
|
||||
|
||||
jest-matcher-utils@^24.9.0:
|
||||
version "24.9.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz#f5b3661d5e628dffe6dd65251dfdae0e87c3a073"
|
||||
@@ -8443,6 +8517,13 @@ lru-cache@^5.1.1:
|
||||
dependencies:
|
||||
yallist "^3.0.2"
|
||||
|
||||
lru-cache@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
|
||||
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
macos-release@^2.2.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.4.0.tgz#837b39fc01785c3584f103c5599e0f0c8068b49e"
|
||||
@@ -8732,7 +8813,7 @@ mkdirp-promise@^5.0.1:
|
||||
dependencies:
|
||||
mkdirp "*"
|
||||
|
||||
mkdirp@*:
|
||||
mkdirp@*, mkdirp@1.x:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||
@@ -9640,6 +9721,14 @@ prettier@^1.19.1:
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
|
||||
integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
|
||||
|
||||
pretty-format@^22.4.3:
|
||||
version "22.4.3"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-22.4.3.tgz#f873d780839a9c02e9664c8a082e9ee79eaac16f"
|
||||
integrity sha512-S4oT9/sT6MN7/3COoOy+ZJeA92VmOnveLHgrwBE3Z1W5N9S2A1QGNYiE1z75DAENbJrXXUb+OWXhpJcg05QKQQ==
|
||||
dependencies:
|
||||
ansi-regex "^3.0.0"
|
||||
ansi-styles "^3.2.0"
|
||||
|
||||
pretty-format@^24.9.0:
|
||||
version "24.9.0"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9"
|
||||
@@ -9650,6 +9739,16 @@ pretty-format@^24.9.0:
|
||||
ansi-styles "^3.2.0"
|
||||
react-is "^16.8.4"
|
||||
|
||||
pretty-format@^26.0.0, pretty-format@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93"
|
||||
integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==
|
||||
dependencies:
|
||||
"@jest/types" "^26.6.2"
|
||||
ansi-regex "^5.0.0"
|
||||
ansi-styles "^4.0.0"
|
||||
react-is "^17.0.1"
|
||||
|
||||
pretty-format@^26.1.0:
|
||||
version "26.1.0"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.1.0.tgz#272b9cd1f1a924ab5d443dc224899d7a65cb96ec"
|
||||
@@ -9828,6 +9927,11 @@ react-is@^16.12.0, react-is@^16.8.1, react-is@^16.8.4:
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
react-is@^17.0.1:
|
||||
version "17.0.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
|
||||
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==
|
||||
|
||||
read-cmd-shim@^1.0.1:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-1.0.5.tgz#87e43eba50098ba5a32d0ceb583ab8e43b961c16"
|
||||
@@ -10477,6 +10581,13 @@ semver@7.0.0:
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
|
||||
integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
|
||||
|
||||
semver@7.x:
|
||||
version "7.3.4"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97"
|
||||
integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||
@@ -11371,6 +11482,23 @@ ts-jest@^24.0.2:
|
||||
semver "^5.5"
|
||||
yargs-parser "10.x"
|
||||
|
||||
ts-jest@^26.4.4:
|
||||
version "26.4.4"
|
||||
resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.4.4.tgz#61f13fb21ab400853c532270e52cc0ed7e502c49"
|
||||
integrity sha512-3lFWKbLxJm34QxyVNNCgXX1u4o/RV0myvA2y2Bxm46iGIjKlaY0own9gIckbjZJPn+WaJEnfPPJ20HHGpoq4yg==
|
||||
dependencies:
|
||||
"@types/jest" "26.x"
|
||||
bs-logger "0.x"
|
||||
buffer-from "1.x"
|
||||
fast-json-stable-stringify "2.x"
|
||||
jest-util "^26.1.0"
|
||||
json5 "2.x"
|
||||
lodash.memoize "4.x"
|
||||
make-error "1.x"
|
||||
mkdirp "1.x"
|
||||
semver "7.x"
|
||||
yargs-parser "20.x"
|
||||
|
||||
ts-node@^8:
|
||||
version "8.10.2"
|
||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d"
|
||||
@@ -12143,6 +12271,11 @@ yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3:
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
|
||||
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
|
||||
|
||||
yallist@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||
|
||||
yaml@^1.10.0, yaml@^1.7.2:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e"
|
||||
@@ -12155,6 +12288,11 @@ yargs-parser@10.x:
|
||||
dependencies:
|
||||
camelcase "^4.1.0"
|
||||
|
||||
yargs-parser@20.x:
|
||||
version "20.2.4"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54"
|
||||
integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==
|
||||
|
||||
yargs-parser@^13.1.2:
|
||||
version "13.1.2"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"
|
||||
|
||||
Reference in New Issue
Block a user