mirror of
https://github.com/foambubble/foam.git
synced 2026-01-11 15:08:01 -05:00
Compare commits
105 Commits
sort-link-
...
cli/fake-i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80a799dff9 | ||
|
|
20ca92f451 | ||
|
|
4557150378 | ||
|
|
deb382af2d | ||
|
|
34c775f185 | ||
|
|
22876cd5f0 | ||
|
|
2d51ce5893 | ||
|
|
adfef15404 | ||
|
|
e92dd7a121 | ||
|
|
950016dcc7 | ||
|
|
6b99a8bd23 | ||
|
|
cfc94ab693 | ||
|
|
5b471b32e6 | ||
|
|
847f5ccf56 | ||
|
|
0342285a7b | ||
|
|
9c1350f64b | ||
|
|
808a453641 | ||
|
|
63521c91e7 | ||
|
|
c652385a97 | ||
|
|
ee7fa79e63 | ||
|
|
ea0a659119 | ||
|
|
b0e587ddb8 | ||
|
|
04d86bc45a | ||
|
|
76b421e800 | ||
|
|
d8c42bd8c4 | ||
|
|
093275ac0c | ||
|
|
c2e998927d | ||
|
|
0ace092c34 | ||
|
|
dd6569b264 | ||
|
|
37359a021d | ||
|
|
6b97e5a066 | ||
|
|
cb6ad7809f | ||
|
|
896e894aff | ||
|
|
c0f84f66f1 | ||
|
|
d7de46274d | ||
|
|
10af405da5 | ||
|
|
4ebec70d50 | ||
|
|
5bd331edab | ||
|
|
c9bba037b0 | ||
|
|
942d9b480f | ||
|
|
655ea856ce | ||
|
|
e977963e1a | ||
|
|
a5f8050d9c | ||
|
|
4a1a825e12 | ||
|
|
c5bd48d86e | ||
|
|
9e07b0f19a | ||
|
|
bb8d0dabba | ||
|
|
b113cafeba | ||
|
|
7c041e0fc8 | ||
|
|
e35f29cee2 | ||
|
|
4d33ad485b | ||
|
|
f6c3ecf369 | ||
|
|
9e452aa9c9 | ||
|
|
52f1dc45a6 | ||
|
|
92e4510c2d | ||
|
|
78586be4a3 | ||
|
|
79a5621f31 | ||
|
|
9367e8e495 | ||
|
|
b611b1bb07 | ||
|
|
b987ae7a3f | ||
|
|
6fa858f8d4 | ||
|
|
e57db48f0e | ||
|
|
3e20dc3356 | ||
|
|
d65f724b56 | ||
|
|
6bd9aaa949 | ||
|
|
d905972f61 | ||
|
|
3511ce30e3 | ||
|
|
215dea151f | ||
|
|
d7b930ff1e | ||
|
|
3c5a81e7d6 | ||
|
|
9a3e2a0b10 | ||
|
|
49c6da07f0 | ||
|
|
75431c89ba | ||
|
|
9e064be3f0 | ||
|
|
2b3f351330 | ||
|
|
1c01c266d9 | ||
|
|
2501638a55 | ||
|
|
20ae1f2ad9 | ||
|
|
a94110898b | ||
|
|
3191d7ddf3 | ||
|
|
ab097e10d7 | ||
|
|
d09ccdbd29 | ||
|
|
df3b657d58 | ||
|
|
f1424c3a73 | ||
|
|
086884ff5c | ||
|
|
05cbdc1c04 | ||
|
|
5b423852d6 | ||
|
|
e1ac54c7e2 | ||
|
|
8bbf9727f6 | ||
|
|
debe94882f | ||
|
|
639edafe52 | ||
|
|
d7425f88e4 | ||
|
|
99d49a0120 | ||
|
|
e9150fb8ea | ||
|
|
57c34db6df | ||
|
|
3bdf90c8b4 | ||
|
|
566d85ec62 | ||
|
|
be704d7494 | ||
|
|
3c3da97190 | ||
|
|
0cdb875221 | ||
|
|
f2efd14186 | ||
|
|
b86edc4660 | ||
|
|
652dc7d845 | ||
|
|
d5fd5410f0 | ||
|
|
efb913028f |
248
.all-contributorsrc
Normal file
248
.all-contributorsrc
Normal file
@@ -0,0 +1,248 @@
|
||||
{
|
||||
"projectName": "foam",
|
||||
"projectOwner": "foambubble",
|
||||
"repoType": "github",
|
||||
"repoHost": "https://github.com",
|
||||
"files": [
|
||||
"docs/index.md",
|
||||
"readme.md"
|
||||
],
|
||||
"imageSize": 60,
|
||||
"commit": false,
|
||||
"commitConvention": "none",
|
||||
"contributors": [
|
||||
{
|
||||
"login": "jevakallio",
|
||||
"name": "Jani Eväkallio",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/1203949?v=4",
|
||||
"profile": "https://jevakallio.dev/",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "jsjoeio",
|
||||
"name": "Joe Previte",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/3806031?v=4",
|
||||
"profile": "https://joeprevite.com/",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "riccardoferretti",
|
||||
"name": "Riccardo",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/457005?v=4",
|
||||
"profile": "https://github.com/riccardoferretti",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "jojanaho",
|
||||
"name": "Janne Ojanaho",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/2180090?v=4",
|
||||
"profile": "http://ojanaho.com/",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "paulshen",
|
||||
"name": "Paul Shen",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/2266187?v=4",
|
||||
"profile": "http://bypaulshen.com/",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "coffenbacher",
|
||||
"name": "coffenbacher",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/245867?v=4",
|
||||
"profile": "https://github.com/coffenbacher",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "mathieudutour",
|
||||
"name": "Mathieu Dutour",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/3254314?v=4",
|
||||
"profile": "https://mathieu.dutour.me/",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "presidentelect",
|
||||
"name": "Michael Hansen",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/1242300?v=4",
|
||||
"profile": "https://github.com/presidentelect",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "dnadlinger",
|
||||
"name": "David Nadlinger",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/19335?v=4",
|
||||
"profile": "http://klickverbot.at/",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "MrCordeiro",
|
||||
"name": "Fernando",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/20598571?v=4",
|
||||
"profile": "https://pluckd.co/",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "jfgonzalez7",
|
||||
"name": "Juan Gonzalez",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/58857736?v=4",
|
||||
"profile": "https://github.com/jfgonzalez7",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "louiechristie",
|
||||
"name": "Louie Christie",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/6807448?v=4",
|
||||
"profile": "http://www.louiechristie.com/",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "SuperSandro2000",
|
||||
"name": "Sandro",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/7258858?v=4",
|
||||
"profile": "https://supersandro.de/",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Skn0tt",
|
||||
"name": "Simon Knott",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/14912729?v=4",
|
||||
"profile": "https://github.com/Skn0tt",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "styfle",
|
||||
"name": "Steven",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/229881?v=4",
|
||||
"profile": "https://styfle.dev/",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Georift",
|
||||
"name": "Tim",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/859430?v=4",
|
||||
"profile": "https://github.com/Georift",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sauravkhdoolia",
|
||||
"name": "Saurav Khdoolia",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/34188267?v=4",
|
||||
"profile": "https://github.com/sauravkhdoolia",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "anku255",
|
||||
"name": "Ankit Tiwari",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/22813027?v=4",
|
||||
"profile": "https://anku.netlify.com/",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"test"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ayushbaweja",
|
||||
"name": "Ayush Baweja",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/44344063?v=4",
|
||||
"profile": "https://github.com/ayushbaweja",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "TaiChi-IO",
|
||||
"name": "TaiChi-IO",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/65092992?v=4",
|
||||
"profile": "https://github.com/TaiChi-IO",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "juanfrank77",
|
||||
"name": "Juan F Gonzalez ",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/12146882?v=4",
|
||||
"profile": "https://github.com/juanfrank77",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "SanketDG",
|
||||
"name": "Sanket Dasgupta",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/8980971?v=4",
|
||||
"profile": "https://sanketdg.github.io",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "nstafie",
|
||||
"name": "Nicholas Stafie",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/10801854?v=4",
|
||||
"profile": "https://github.com/nstafie",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "francishamel",
|
||||
"name": "Francis Hamel",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/36383308?v=4",
|
||||
"profile": "https://github.com/francishamel",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "digiguru",
|
||||
"name": "digiguru",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/619436?v=4",
|
||||
"profile": "http://digiguru.co.uk",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
"skipCi": true
|
||||
}
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,4 +1,7 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
.vscode-test/
|
||||
*.tsbuildinfo
|
||||
*.vsix
|
||||
*.log
|
||||
dist
|
||||
|
||||
9
.vscode/launch.json
vendored
9
.vscode/launch.json
vendored
@@ -35,6 +35,15 @@
|
||||
"${workspaceFolder}/packages/foam-vscode/out/test/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "Build foam-vscode"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Workspace Manager tests",
|
||||
"program": "${workspaceFolder}/node_modules/tsdx/dist/index.js",
|
||||
"args": ["test"],
|
||||
"cwd": "${workspaceFolder}/packages/foam-core",
|
||||
"internalConsoleOptions": "openOnSessionStart"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -8,9 +8,11 @@ layout: default
|
||||
|
||||
<script type="text/javascript">
|
||||
// Hack: Replace page-link with "Page Title"
|
||||
document.querySelectorAll(".markdown-body a[title]").forEach((a) => {
|
||||
a.innerText = a.title;
|
||||
});
|
||||
document
|
||||
.querySelectorAll(".markdown-body a[title]:not([href^=http])")
|
||||
.forEach((a) => {
|
||||
a.innerText = a.title;
|
||||
});
|
||||
|
||||
document.querySelectorAll(".github-only").forEach((el) => {
|
||||
el.remove();
|
||||
|
||||
28
docs/_layouts/mathjax.html
Normal file
28
docs/_layouts/mathjax.html
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
{{ content }}
|
||||
|
||||
<script type="text/javascript">
|
||||
// Hack: Replace page-link with "Page Title"
|
||||
document
|
||||
.querySelectorAll(".markdown-body a[title]:not([href^=http])")
|
||||
.forEach((a) => {
|
||||
a.innerText = a.title;
|
||||
});
|
||||
|
||||
document.querySelectorAll(".github-only").forEach((el) => {
|
||||
el.remove();
|
||||
});
|
||||
</script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/mathjax@2/MathJax.js?config=TeX-AMS-MML_HTMLorMML" type="text/javascript"></script>
|
||||
<script type="text/x-mathjax-config">
|
||||
MathJax.Hub.Config({
|
||||
tex2jax: {
|
||||
skipTags: ['script', 'noscript', 'style', 'textarea', 'pre'],
|
||||
inlineMath: [['$','$']]
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -6,9 +6,11 @@ layout: default
|
||||
|
||||
<script type="text/javascript">
|
||||
// Hack: Replace page-link with "Page Title"
|
||||
document.querySelectorAll(".markdown-body a[title]").forEach((a) => {
|
||||
a.innerText = a.title;
|
||||
});
|
||||
document
|
||||
.querySelectorAll(".markdown-body a[title]:not([href^=http])")
|
||||
.forEach((a) => {
|
||||
a.innerText = a.title;
|
||||
});
|
||||
|
||||
document.querySelectorAll(".github-only").forEach((el) => {
|
||||
el.remove();
|
||||
|
||||
BIN
docs/assets/images/demo-backlinks-explorer.gif
Normal file
BIN
docs/assets/images/demo-backlinks-explorer.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 MiB |
BIN
docs/assets/images/prettify-links-demo.gif
Normal file
BIN
docs/assets/images/prettify-links-demo.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 430 KiB |
25
docs/automatically-expand-urls-to-well-titled-links.md
Normal file
25
docs/automatically-expand-urls-to-well-titled-links.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Automatically Expand URLs to Well-Titled Links
|
||||
|
||||
Convert a link to a fully-formed Markdown link, using the page's title as a display name. Useful for citations and creating link collections.
|
||||
|
||||
## Required Extensions
|
||||
|
||||
- [Markdown Link Expander](https://marketplace.visualstudio.com/items?itemName=skn0tt.markdown-link-expander) (not included in template)
|
||||
|
||||
Markdown Link Expander will scrape your URL's `<title>` tag to create a nice Markdown-style link.
|
||||
|
||||
## Instructions
|
||||
|
||||

|
||||
|
||||
1. Highlight desired URL
|
||||
2. `Cmd` + `Shift` + `P`
|
||||
3. `Expand URL to Markdown`
|
||||
4. Profit
|
||||
|
||||
Tip: If you paste a lot of links, give the action a custom [key binding](https://code.visualstudio.com/docs/getstarted/keybindings)
|
||||
|
||||
## Feedback and issues
|
||||
|
||||
Have an idea for the extension? [Feel free to share! 🎉](https://github.com/Skn0tt/markdown-link-expander/issues)
|
||||
|
||||
@@ -4,11 +4,11 @@ When using [[wiki-links]], you can find all notes that link to a specific note i
|
||||
|
||||
- Run `Cmd` + `Shift` + `P` (`Ctrl` + `Shift` + `P` for Windows), type "backlinks" and run the **Explorer: Focus on Backlinks** view.
|
||||
- Keep this pane always visible to discover relationships between your thoughts
|
||||
- You can drag the backlinks pane to a different section in VS Code if you prefer.
|
||||
- You can drag the backlinks pane to a different section in VS Code if you prefer. See: [[make-backlinks-more-prominent]]
|
||||
- Finding backlinks in published Foam workspaces via [[materialized-backlinks]] is on the [[roadmap]] but not yet implemented.
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[wiki-links]: wiki-links "Wiki Links"
|
||||
[materialized-backlinks]: materialized-backlinks "Materialized Backlinks"
|
||||
[materialized-backlinks]: materialized-backlinks "Materialized Backlinks (stub)"
|
||||
[roadmap]: roadmap "Roadmap"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
- What use cases are we working towards?
|
||||
-[[todo]] User round table
|
||||
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[todo]: todo "Todo"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
7
docs/blog.md
Normal file
7
docs/blog.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Foam Blog
|
||||
|
||||
- [[2020-07-11-three-weeks-in]]
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[2020-07-11-three-weeks-in]: blog/2020-07-11-three-weeks-in "Three Weeks In"
|
||||
[//end]: # "Autogenerated link references"
|
||||
3
docs/blog/2020-07-11-three-weeks-in.md
Normal file
3
docs/blog/2020-07-11-three-weeks-in.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Three Weeks In
|
||||
|
||||
Todo
|
||||
@@ -12,7 +12,7 @@ Foam is open to contributions of any kind, including but not limited to code, do
|
||||
- Foam code and documentation live in the monorepo at [foambubble/foam](https://github.com/foambubble/foam/)
|
||||
- [/docs](https://github.com/foambubble/foam/docs): documentation and [[recipes]]
|
||||
- [/packages/foam-vscode](https://github.com/foambubble/foam/tree/master/packages/foam-vscode): the core VSCode plugin
|
||||
- [/packages/foam-workspace-manager](https://github.com/foambubble/foam/tree/master/packages/foam-workspace-manager): Foam workspace automations
|
||||
- [/packages/foam-core](https://github.com/foambubble/foam/tree/master/packages/foam-core): powers the core functionality in Foam across all platforms
|
||||
- Exceptions to the monorepo are:
|
||||
- The starter template at [foambubble/foam-template](https://github.com/foambubble/)
|
||||
- All other [[recommended-extensions]] live in their respective GitHub repos.
|
||||
@@ -29,9 +29,9 @@ If you're interested in contributing to the VS Code extension (aka `foam-vscode`
|
||||
|
||||
`yarn install`
|
||||
|
||||
3. This project uses [Yarn workspaces](https://classic.yarnpkg.com/en/docs/workspaces/).`foam-vscode` relies on `foam-workspace-manager`. This means we need to compile it before we do any extension development. From the root, run the command:
|
||||
3. This project uses [Yarn workspaces](https://classic.yarnpkg.com/en/docs/workspaces/).`foam-vscode` relies on `foam-core`. This means we need to compile it before we do any extension development. From the root, run the command:
|
||||
|
||||
`yarn workspace foam-workspace-manager build`
|
||||
`yarn workspace foam-core build`
|
||||
|
||||
4. Now we'll use the launch configuration defined at [`.vscode/launch.json`](https://github.com/foambubble/foam/blob/master/.vscode/launch.json) to start a new extension host of VS Code. From the root, or the `foam-vscode` workspace, press f5.
|
||||
5. In the new extension host of VS Code that launched, open a Foam workspace (e.g. your personal one, or a test-specific one created from foam-template). This is strictly not necessary, but the extension won't auto-run unless it's in a workspace with a `.vscode/foam.json` file.
|
||||
|
||||
15
docs/custom-markdown-preview-styles.md
Normal file
15
docs/custom-markdown-preview-styles.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Custom Markdown Preview Styles
|
||||
|
||||
Visual Studio Code allows you to use your own CSS in the Markdown preview tab.
|
||||
|
||||
## Instructions
|
||||
|
||||
Custom CSS for the Markdown preview can be implemented by using the `"markdown.styles": []` setting in `settings.json`. The stylesheets can either be https URLs or relative paths to local files in the current workspace.
|
||||
|
||||
For example, to load a stylesheet called `Style.css`, we can update `settings.json` with the following line:
|
||||
|
||||
```
|
||||
{
|
||||
"markdown.styles": ["Style.css"]
|
||||
}
|
||||
```
|
||||
19
docs/eleventy-and-netlify.md
Normal file
19
docs/eleventy-and-netlify.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Eleventy and Netlify
|
||||
|
||||
You can use [foam-eleventy-template](https://github.com/juanfrank77/foam-eleventy-template) to generate a static site with [Eleventy](https://www.11ty.dev/), and host it online on [Netlify](https://www.netlify.com/).
|
||||
|
||||
With this template you can
|
||||
- Have control over what to publish and what to keep private
|
||||
- Customize the styling of the site to your own liking
|
||||
|
||||
## Publishing your foam
|
||||
|
||||
When you're ready to publish, import the GitHub repository you created with **foam-eleventy-template** into your Netlify account. (Create one if you don't have it already.)
|
||||
|
||||
Once that's done, all you have to do is make changes to your workspace in VS COde and push them to the main branch on GitHub. Netlify will recognize the changes, deploy them automatically and give you a link where your Foam is published.
|
||||
|
||||
|
||||
That's it!
|
||||
|
||||
You can now see it online and use that link to share it with your friends, so that they can see it too.
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
- Can be either [[wiki-links]] or relative `[markdown](links.md)` style
|
||||
- We need to know about the edges (connections) as well as nodes
|
||||
- What link points to what other file, etc.
|
||||
- Needs to have the exact link text, e.g. even if `[[some-page]]` or `[[some-page.md]]` or `[[Some Page]]` point to the same document (`./some-page.md`), we need to know which format was used, so link reference definitions can be generated correctly
|
||||
- Needs to have the exact link text, e.g. even if `[[some-page]]` or `[[some-page.md]]` or `[[Some Page]]` point to the same document (`./some-page.md`), we need to know which format was used, so [[link-reference-definitions]] can be generated correctly
|
||||
- Treat each file as semi-structured data
|
||||
- Title, headings, lists, paragraphs, images, links, data, code
|
||||
- Also, possible Foam-specific meta stuff, like "backlinks" or "block references".
|
||||
@@ -44,7 +44,7 @@ Ideally, `foam-core` will be generic enough that in can be used by third parties
|
||||
- Aliasing, call the same thing by multiple names
|
||||
- Doesn't exist yet, should we support?
|
||||
- The graph should be observable (EventEmitter?) so changes can be applied
|
||||
- EventEmitted w/ cross platform dependency
|
||||
- EventEmitter w/ cross platform dependency
|
||||
- Wonka (staltz's callbag)
|
||||
- Can be a long term goal
|
||||
- Short term fix: Just run the build fully on every change
|
||||
@@ -61,12 +61,13 @@ Here are some example use cases that the core should support. They don't need to
|
||||
|
||||
- Adding and editing page content
|
||||
- [[materialized-backlinks]]
|
||||
- [[wiki-links]] reference definitions
|
||||
- [[link-reference-definitions]] for [[wiki-links]]
|
||||
- [Frontmatter](https://jekyllrb.com/docs/front-matter/)
|
||||
- Finding all documents with `#tag`
|
||||
- Finding all documents with instances of `[[link]]`
|
||||
- Visualisations
|
||||
- Full text search
|
||||
|
||||
- Or, if search is too expensive/complex, when given a list of file names and line/column positions from VS Code search API, can return the document context (e.g. full paragraph, preceding/following line etc)
|
||||
|
||||
## Collaboration
|
||||
@@ -90,12 +91,17 @@ Here are some example use cases that the core should support. They don't need to
|
||||
|
||||
Useful for knowing what needs to be supported. See [[feature-comparison]].
|
||||
|
||||
## Meeting notes
|
||||
|
||||
- [[foam-core-2020-07-11]]
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[workspace-janitor]: workspace-janitor "Workspace Janitor"
|
||||
[cli]: cli "Command Line Interface"
|
||||
[build-vs-assemble]: build-vs-assemble "Build vs Assemble"
|
||||
[wiki-links]: wiki-links "Wiki Links"
|
||||
[materialized-backlinks]: materialized-backlinks "Materialized Backlinks (stub)"
|
||||
[build-vs-assemble]: build-vs-assemble "Build vs Assemble"
|
||||
[feature-comparison]: feature-comparison "Feature comparison"
|
||||
[todo]: todo "Todo"
|
||||
[feature-comparison]: feature-comparison "Feature comparison"
|
||||
[foam-core-2020-07-11]: meeting-notes/foam-core-2020-07-11 "Foam Core 2020-07-11"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
@@ -14,25 +14,7 @@ Here are a few specific constraints, mainly because our tooling is a bit fragmen
|
||||
- **File name should have extension `.md` or `.markdown`**
|
||||
- This is a temporary limitation and will be lifted in future versions.
|
||||
- At least `.mdx` will be supported, but ideally we'll support any file that you can map to `Markdown` language mode in VS Code
|
||||
- **In addition to normal Markdown Links syntax you can use `[[media-wiki]]` links.**
|
||||
- When you do, the [foam-vscode](https://github.com/foambubble/foam/tree/master/packages/foam-vscode) extension will automatically generate [Markdown Link Reference Definitions](https://spec.commonmark.org/0.29/#link-reference-definitions) at the bottom of the file.
|
||||
- Here's an example:
|
||||
- [[wiki-links]]
|
||||
- [[github-pages]]
|
||||
- This will generate the following references:
|
||||
```md
|
||||
[wiki-links]: wiki-links "Wiki Links"
|
||||
[github-pages]: github-pages "Github Pages"
|
||||
```
|
||||
- The three components are `[link-label]: link-target "Link Title"
|
||||
- **link label:** The link text to match in the surrounding markdown document. This matches the inner bracket of the double-bracketed `[[wiki-link]]` notation
|
||||
- **link destination** The target of the matched link
|
||||
- Right now we generate link destinations without file extension. This is a choice, see [discussion here](https://foambubble.github.io/foam/wiki-links#why-dont-wiki-links-work-on-github).
|
||||
- **"Link Title"** Optional title for link (The Foam template has a snippet of JavaScript to replace this on the website at runtime)
|
||||
- Open the [raw markdown](https://raw.githubusercontent.com/foambubble/foam/master/foam-file-format.md) to see them at the bottom of this file
|
||||
- In the near future, these can be batch generated for all workspace files (WIP)
|
||||
- For the time being, if you want to get [[wiki-links]] support across all files, you'll need to generate the link reference definitions yourself.
|
||||
- If you end up writing a batch job for this, please share your solution, as this is something we'll need to implement soon!
|
||||
- **In addition to normal Markdown Links syntax you can use `[[media-wiki]]` links.** See [[wiki-links]] for more details.
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[wiki-links]: wiki-links "Wiki Links"
|
||||
|
||||
51
docs/gitlab-pages.md
Normal file
51
docs/gitlab-pages.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# GitLab Pages
|
||||
|
||||
You don't have to use GitHub to serve Foam pages. You can also use GitLab.
|
||||
|
||||
## Setup a project
|
||||
|
||||
### Generate the directory from GitHub
|
||||
|
||||
Generate a solution using the [Foam template].
|
||||
|
||||
Change the remote to GitLab, or copy all the files into a new GitLab repo.
|
||||
|
||||
### Add a _config.yaml
|
||||
Add another file to the root directory (the one with `readme.md` in it) called `_config.yaml` (no extension)
|
||||
|
||||
```yaml
|
||||
title: My Awesome Foam Project
|
||||
baseurl: "" # the subpath of your site, e.g. /blog
|
||||
url: "/" # the base hostname & protocol for your site
|
||||
theme: jekyll-theme-minimal
|
||||
```
|
||||
|
||||
You can choose a theme if you want from places like [Jekyll Themes](https://jekyllthemes.io/)
|
||||
|
||||
### Add a Gemlock file
|
||||
|
||||
Add another file to the root directory (the one with `readme.md` in it) called `Gemfile` (no extension)
|
||||
|
||||
```ruby
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "jekyll"
|
||||
gem "jekyll-theme-minimal"
|
||||
gem "jekyll-optional-front-matter"
|
||||
```
|
||||
|
||||
Commit the file and push it to gitlab.
|
||||
|
||||
## Setup CI/CD
|
||||
|
||||
1. From the project home in GitLab click `Set up CI/CD`
|
||||
2. Choose `Jekyll` as your template from the template dropdown
|
||||
3. Click `commit`
|
||||
4. Now when you go to CI / CD > Pipelines, you should see the code running
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- *Could not locate Gemfile* - You didn't follow the steps above to [#Add a Gemlock file]
|
||||
- *Conversion error: Jekyll::Converters::Scss encountered an error while converting* You need to reference a theme.
|
||||
- *Pages are running in CI/CD, but I only ever see `test`, and never deploy* - Perhaps you've renamed the main branch (from master) - check the settings in `.gitlab-ci.yml` and ensure the deploy command is running to the branch you expect it to.
|
||||
- *I deployed, but my .msd files don't seem to be being converted into .html files* - You need a gem that GitHub installs by default - check `gem "jekyll-optional-front-matter"` appears in the `Gemfile`
|
||||
5
docs/images-from-your-clipboard.md
Normal file
5
docs/images-from-your-clipboard.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Images from your Clipboard
|
||||
|
||||
You can directly link and paste images that are copied to the clipboard using the [Paste
|
||||
Image](https://marketplace.visualstudio.com/items?itemName=mushan.vscode-paste-image)
|
||||
extension.
|
||||
@@ -1,16 +1,23 @@
|
||||
# Inbox
|
||||
|
||||
Uncategorised thoughts
|
||||
Uncategorised thoughts, to be added
|
||||
|
||||
- Release notes
|
||||
- Automatic updates
|
||||
- Markdown Preview
|
||||
- It's possible to customise the markdown preview styling. **Maybe make it use local foam workspace styles for live preview of the site??**
|
||||
- See: https://marketplace.visualstudio.com/items?itemName=bierner.markdown-preview-github-styles
|
||||
- Use VS Code [CodeTour](https://marketplace.visualstudio.com/items?itemName=vsls-contrib.codetour) for onboarding
|
||||
- Investigate other similar extensions:
|
||||
- [Unotes](https://marketplace.visualstudio.com/items?itemName=ryanmcalister.Unotes)
|
||||
- [vscode-memo](https://github.com/svsool/vscode-memo)
|
||||
- [gistpad wiki](https://github.com/jevakallio/gistpad/tree/master/src/repos/wiki)
|
||||
- Developer documentation
|
||||
- GistPad has a good vs code contrib primer: https://github.com/jevakallio/gistpad/blob/master/CONTRIBUTING.md
|
||||
- VS Code Notebooks API
|
||||
- https://code.visualstudio.com/api/extension-guides/notebook
|
||||
- Snippets in template
|
||||
- Foam as a (VS Code) language
|
||||
- Syntax highlighting
|
||||
- Autocompletion
|
||||
- Get rid of mediawiki links in favor of write time tooling
|
||||
- Snippets
|
||||
- Future architecture
|
||||
- Could we do publish-related settings as a pre-push git hook, e.g. generating footnote labels
|
||||
|
||||
@@ -62,10 +62,12 @@ These instructions assume you have a GitHub account, and you have Visual Studio
|
||||
|
||||
<a class="github-button" href="https://github.com/foambubble/foam-template/generate" data-icon="octicon-repo-template" data-size="large" aria-label="Use this template foambubble/foam-template on GitHub">Use this template</a>
|
||||
|
||||
(If you want to keep your thoughts to yourself, remember to set the repository private, or if you don't want to use GitHub to host your workspace at all, choose **Download as ZIP** instead of **Use this template**.)
|
||||
*If you want to keep your thoughts to yourself, remember to set the repository private, or if you don't want to use GitHub to host your workspace at all, choose **Download as ZIP** instead of **Use this template**.*
|
||||
|
||||
2. [Clone the repository locally](https://help.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository) and open it in VS Code.
|
||||
|
||||
*Open the repository as a folder using the `File > Open...` menu item. In VS Code, "open workspace" refers to [multi-root workspaces](https://code.visualstudio.com/docs/editor/multi-root-workspaces).*
|
||||
|
||||
3. When prompted to install recommended extensions, click **Install all** (or **Show Recommendations** if you want to review and install them one by one)
|
||||
|
||||
After setting up the repository, open `.vscode/settings.json` and edit, add or remove any settings you'd like for your Foam workspace.
|
||||
@@ -99,11 +101,54 @@ If that sounds like something you're interested in, I'd love to have you along o
|
||||
|
||||
## Thanks and attribution
|
||||
|
||||
**Foam** is built by [Jani Eväkallio](https://github.com/jevakallio) ([@jevakallio](https://twitter.com/jevakallio)).
|
||||
**Foam** is built by [Jani Eväkallio](https://github.com/jevakallio) ([@jevakallio](https://twitter.com/jevakallio)), and all our contributors:
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||
<!-- prettier-ignore-start -->
|
||||
<!-- 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>
|
||||
</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>
|
||||
</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></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>
|
||||
</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></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>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-enable -->
|
||||
<!-- 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)
|
||||
|
||||
**Foam** wouldn't be possible without [Visual Studio Code](https://code.visualstudio.com/) and [GitHub](https://github.com/), and relies heavily on our fantastic open source [[recommended-extensions]] and all their contributors:
|
||||
**Foam** wouldn't be possible without [Visual Studio Code](https://code.visualstudio.com/) and [GitHub](https://github.com/), and relies heavily on our fantastic open source [[recommended-extensions]] and all their contributors!
|
||||
|
||||
## License
|
||||
|
||||
|
||||
142
docs/link-reference-definition-improvements.md
Normal file
142
docs/link-reference-definition-improvements.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# Link Reference Definition Improvements
|
||||
|
||||
## Current Problems
|
||||
|
||||
### File-by-file Insertion
|
||||
|
||||
For the time being, if you want to get [[wiki-links]] into all files within the workspace, you'll need to generate the link reference definitions yourself file-by-file (with the assistance of Foam).
|
||||
|
||||
### Wikilinks don't work on GitHub
|
||||
|
||||
> **TL;DR;** [workaround](#workaround) in the end of the chapter.
|
||||
|
||||
If you click any of the wiki-links on GitHub web UI (such as the `README.md` of a project), you'll notice that the links break with a 404 error.
|
||||
|
||||
At the time of writing (June 28 2020) this is a known, but unsolved error. To understand why this is the case, we need to understand what we are trading off.
|
||||
|
||||
So, why don't they work on GitHub?
|
||||
|
||||
The three components of [[link-reference-definitions]] are link label, link destination and Link Title.
|
||||
|
||||
The issue is the middle **link destination** component. It's configured to point to the file name **without file extension**, i.e. "file-name" instead of "file-name.md". This is to make the GitHub Pages rendering work, because if we generated the links to `file-name.md`, the links would point to the raw markdown files instead of their generated HTML versions.
|
||||
|
||||
| Environment | `file-name` | `file-name.md` |
|
||||
| ---------------- | ----------- | -------------- |
|
||||
| **VS Code** | Works | Works |
|
||||
| **GitHub pages** | Works | Breaks |
|
||||
| **GitHub UI** | Breaks | Works |
|
||||
|
||||
So as you can see, we've prioritised GitHub Pages over GitHub Web UI for the time being.
|
||||
|
||||
Ideally, we'd like a solution that works with both, but it's not defined yet (see [[link-reference-definitions]] for more details)
|
||||
|
||||
#### Workaround
|
||||
|
||||
For the time being, you can use relative `[markdown links](markdown-link.md)` syntax.
|
||||
|
||||
**Pros:**
|
||||
|
||||
- This will work on all platforms.
|
||||
|
||||
**Cons:**
|
||||
|
||||
- It will break the Markdown Notes [[backlinking]] support
|
||||
- Less convenient to write
|
||||
|
||||
### Finding certain words clutter the VS Code search results
|
||||
|
||||
Since link reference definitions have `[//begin]` and `[//end]` guards with explanatory text that use certain words, these words (like "generate") appear in VS Code search results if you happen to search matching strings from the workspace.
|
||||
|
||||
## Improvement Proposal
|
||||
|
||||
Problem space in essence:
|
||||
|
||||
- During edit-time (when modifying the markdown files in an editor)
|
||||
- link reference definitions are needed if user uses editor extensions that don't understand wikilinks
|
||||
- link reference definitions may be annoying since they
|
||||
- add content to files that the user hasn't typed in by themselves
|
||||
- get out of date if user uses a tool that doesn't autogenerate them
|
||||
- may clutter the search results
|
||||
- During build-time (when converting markdown to html for publishing purposes)
|
||||
- link reference definitions are needed, if the files are published via such tools (or to such platforms) that don't understand wikilinks
|
||||
- link reference definitions might have to be in different formats depending on the publish target (e.g. Github pages vs Github UI)
|
||||
|
||||
The potential solution:
|
||||
|
||||
- For edit-time
|
||||
- Make edit-time link reference definition generation optional via user settings. They should be on by default, and generating valid markdown links with a relative path to a `.md` file.
|
||||
- Make format of the link reference definition configurable (whether to include '.md' or not)
|
||||
- Out of recommended extensions, currently only "markdown links" doesn't support them (?). However even its [code](https://github.com/tchayen/markdown-links/blob/master/src/parsing.ts#L25) seems to include wikilink parser, so it might just be a bug?
|
||||
- For build-time
|
||||
- To satisfy mutually incompatible constraints between GitHub UI, VSCode UI, and GitHub Pages, we should add a pre-processing/build step for pushing to GitHub Pages.
|
||||
- This would be a GitHub action (or a local script, ran via foam-cli) that outputs publish-friendly markdown format for static site generators and other publishing tools
|
||||
- This build step should be pluggable, so that other transformations could be ran during it
|
||||
- Have publish targets defined in settings, that support both turning the link reference definitions on/off and defining their format (.md or not). Example draft (including also edit-time aspect):
|
||||
```typescript
|
||||
// settings json
|
||||
// see enumerations below for explanations on values
|
||||
{
|
||||
"foam": {
|
||||
"publish": [
|
||||
{
|
||||
"name": "Gitlab Mirror", // name of the publish target
|
||||
"linkTranspilation": "Off",
|
||||
"linkReferenceDefinitions": "withExtensions"
|
||||
},
|
||||
{
|
||||
"name": "GitHub Pages",
|
||||
"linkTranspilation": "Off",
|
||||
"linkReferenceDefinitions": "withoutExtensions"
|
||||
},
|
||||
{
|
||||
"name": "Blog",
|
||||
"linkTranspilation": "Off",
|
||||
"linkReferenceDefinitions": "Off"
|
||||
},
|
||||
{
|
||||
"name": "My Amazing PDF book",
|
||||
"linkTranspilation": "WikiLinksToMarkdown"
|
||||
}
|
||||
],
|
||||
"edit": {
|
||||
"linkReferenceDefinitions": "Off"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Defines if and how links in markdown files are somehow converted (in-place) during build time
|
||||
// Note that this enumeration is not valid edit-time, since we (probably) don't want to change text like this while user is editing it
|
||||
enum LinkTranspilation {
|
||||
Off, // links are not transpiled
|
||||
WikiLinksToMarkdown, // links using wiki-format [[link]] are converted to normal md links: [link](./some/file.md)
|
||||
// if this is set, not link reference definitions are generated (not needed)
|
||||
}
|
||||
|
||||
// Defines if and how link reference definition section is generated
|
||||
enum LinkReferenceDefinitions {
|
||||
Off, // link reference definitions are not generated
|
||||
WithExtensions, // link reference definitions contain .md (or similar) file extensions
|
||||
WithoutExtensions // link reference definitions do not contain file extenions
|
||||
}
|
||||
|
||||
```
|
||||
- With Foam repo, just use edit-time link reference definitions with '.md' extension - this makes the links work in the Github UI
|
||||
- Have publish target defined for Github pages, that doesn't use '.md' extension, but still has the link reference definitions. Generate the output into gh-pages branch (or separate repo) with automation.
|
||||
- This naturally requires first removing the existing link reference definitions during the build
|
||||
- Other
|
||||
- To clean up the search results, remove link reference definition section guards (assuming that these are not defined by the markdown spec). Use unifiedjs parse trees to identify if there's missing (or surplus) definitions (check if they are identified properly by the library), and just add the needed definitions to the bottom of the file (without guards) AND remove them if they are not needed (anywhere from the file).
|
||||
|
||||
Note that the proposal above supports both (build-time) inline transpilation of wikilinks as well as creation reference definitions. Depending on the direction of Foam, also only one of them could be selected. In that case the other could be implemented at later point of time.
|
||||
|
||||
UI-wise, the publish targets could be picked in some similar fashion as the run/debug targets in vscode by implementing a separate panel, or maybe through command execution (CTRL+SHIFT+P) - not yet defined at this point.
|
||||
|
||||
## Links
|
||||
|
||||
- [tracking issue on GitHub](https://github.com/foambubble/foam/issues/16)
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[wiki-links]: wiki-links "Wiki Links"
|
||||
[roadmap]: roadmap "Roadmap"
|
||||
[link-reference-definitions]: link-reference-definitions "Link Reference Definitions"
|
||||
[backlinking]: backlinking "Backlinking"
|
||||
[//end]: # "Autogenerated link references"
|
||||
37
docs/link-reference-definitions.md
Normal file
37
docs/link-reference-definitions.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Link Reference Definitions
|
||||
|
||||
## Introduction
|
||||
|
||||
When you use `[[wiki-links]]`, the [foam-vscode](https://github.com/foambubble/foam/tree/master/packages/foam-vscode) extension will automatically generate [Markdown Link Reference Definitions](https://spec.commonmark.org/0.29/#link-reference-definitions) at the bottom of the file. This is done to make the content of the file compatible with various Markdown tools (e.g. parsers, static site generators, VS code plugins etc), which don't support `[[wiki-links]]`.
|
||||
|
||||
## Example
|
||||
|
||||
The following example:
|
||||
```md
|
||||
- [[wiki-links]]
|
||||
- [[github-pages]]
|
||||
```
|
||||
...generates the following link reference definitions to the bottom of the file:
|
||||
```md
|
||||
[wiki-links]: wiki-links "Wiki Links"
|
||||
[github-pages]: github-pages "Github Pages"
|
||||
```
|
||||
You can open the [raw markdown](https://raw.githubusercontent.com/foambubble/foam/master/foam-file-format.md) to see them at the bottom of this file
|
||||
|
||||
## Specification
|
||||
|
||||
The three components of a link reference definition are `[link-label]: link-target "Link Title"`
|
||||
|
||||
- **link label:** The link text to match in the surrounding markdown document. This matches the inner bracket of the double-bracketed `[[wiki-link]]` notation
|
||||
- **link destination** The target of the matched link
|
||||
- Right now we generate link destinations without file extension. This is a choice, see [discussion here](https://foambubble.github.io/foam/wiki-links#why-dont-wiki-links-work-on-github).
|
||||
- **"Link Title"** Optional title for link (The Foam template has a snippet of JavaScript to replace this on the website at runtime)
|
||||
|
||||
See [[link-reference-definition-improvements]] for further discussion on current problems and potential solutions.
|
||||
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[wiki-links]: wiki-links "Wiki Links"
|
||||
[roadmap]: roadmap "Roadmap"
|
||||
[link-reference-definition-improvements]: link-reference-definition-improvements "Link Reference Definition Improvements"
|
||||
[//end]: # "Autogenerated link references"
|
||||
24
docs/make-backlinks-more-prominent.md
Normal file
24
docs/make-backlinks-more-prominent.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Make Backlinks More Prominent
|
||||
|
||||
One of the most most common early feature requests in Foam is to make the Markdown Notes Backlinks Explorer more prominent.
|
||||
|
||||
At the moment, you can drag the explorer pane to your bottom pane, and either show it side by side with another pane, or have take the full width of the editor:
|
||||
|
||||

|
||||
|
||||
In the future we'll want to improve this feature by
|
||||
|
||||
- [[materialized-backlinks]]
|
||||
- Providing more context around back link reference
|
||||
- Could be done by tweaking Markdown Notes slightly. Maybe a user setting?
|
||||
- Make back links editable using [VS Code Search Editors](https://code.visualstudio.com/updates/v1_43#_search-editors)
|
||||
- [Suggested by @Jash on Discord](https://discordapp.com/channels/729975036148056075/729978910363746315/730999992419876956)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[materialized-backlinks]: materialized-backlinks "Materialized Backlinks (stub)"
|
||||
[//end]: # "Autogenerated link references"
|
||||
36
docs/math-support.md
Normal file
36
docs/math-support.md
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
layout: mathjax
|
||||
---
|
||||
|
||||
# Math Support
|
||||
|
||||
The published pages don't support math formulas by default. To enable this feature, you can add the following code snippet to the end of `_layouts/page.html`:
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/mathjax@2/MathJax.js?config=TeX-AMS-MML_HTMLorMML" type="text/javascript"></script>
|
||||
<script type="text/x-mathjax-config">
|
||||
MathJax.Hub.Config({
|
||||
tex2jax: {
|
||||
skipTags: ['script', 'noscript', 'style', 'textarea', 'pre'],
|
||||
inlineMath: [['$','$']]
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
Example of inline math: $e^{i \pi}+1=0$
|
||||
Example of displayed math:
|
||||
|
||||
$$ f_{\mathbf{X}}\left(x_{1}, \ldots, x_{k}\right)=\frac{\exp \left(-\frac{1}{2}(\mathbf{x}-\boldsymbol{\mu})^{\mathrm{T}} \mathbf{\Sigma}^{-1}(\mathbf{x}-\boldsymbol{\mu})\right)}{\sqrt{(2 \pi)^{k}|\mathbf{\Sigma}|}} $$
|
||||
|
||||
If you want the index page of your Foam site to render maths, you'll need to add that to `_layouts/home.html` as well, or change the layout of the index page to be "page" instead of "home" by putting this Front Matter on the top of your `readme.md/index.md`:
|
||||
|
||||
```
|
||||
---
|
||||
layout: page
|
||||
---
|
||||
|
||||
# Your normal title here
|
||||
```
|
||||
|
||||
Reference: [How to support latex in github-pages](https://stackoverflow.com/questions/26275645/how-to-support-latex-in-github-pages)
|
||||
126
docs/meeting-notes/foam-core-2020-07-11.md
Normal file
126
docs/meeting-notes/foam-core-2020-07-11.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# Foam Core 2020-07-11
|
||||
|
||||
Present: @jevakallio, @riccardoferretti
|
||||
|
||||
### Tests
|
||||
|
||||
- How do we know this approach works?
|
||||
- Supports renaming
|
||||
- Supports searching with (attribute-x)
|
||||
- Find dead links
|
||||
|
||||
### Getting started
|
||||
|
||||
- Land work to master
|
||||
- Create a foam-core package
|
||||
-
|
||||
|
||||
### Open questions
|
||||
|
||||
- How should writing to files work
|
||||
- What if affected notes have unsaved changes
|
||||
|
||||
### Graph methods
|
||||
|
||||
- get all
|
||||
- search by
|
||||
- tag
|
||||
- free text
|
||||
- [[todo]]: how do vs code search editors work? are they pluggable? what do they need?
|
||||
- find dead links
|
||||
- for linters
|
||||
- serialize/toJSON (for visualizers)
|
||||
- subscribe to changes
|
||||
- find if a link exists (and which link) in a given row / column position + return it's start and end position - this would probably be needed e.g. to CTRL-hovering to work properly
|
||||
|
||||
### Node methods
|
||||
|
||||
- rename node and all links to that node
|
||||
- get links
|
||||
- forward links (for link lists)
|
||||
- backlinks (with surrounding context)
|
||||
|
||||
### Node definition
|
||||
|
||||
What do we need the node (and edge metadata) to contain:
|
||||
|
||||
- `id`: tbd
|
||||
- should be unique, needs some kind of unique gen function
|
||||
- should be reconstructable even if links are not updated every time
|
||||
- what happens during rename? is reparenting the graph going to be hard?
|
||||
- do id's need to be persistent, or can we create them per in-memory session, keep them stable despite renames, and then next session generate a new id?
|
||||
- Ideally should be a path to file, so it's easy to look up from the graph by id for renaming
|
||||
- `type`: Note | Image | etc
|
||||
- `title`: can be read from markdown title or frontmatter metadata
|
||||
- `path`: full path to file, relative to workspace (graph) root
|
||||
- `links`:
|
||||
- `id`: File to link to
|
||||
- `text`: The link label
|
||||
- `type` markdown | mediawiki | image | http
|
||||
- `section`: : Anchor link to a heading in target note, if we want to add support for linking to sections
|
||||
- `block` (ref)
|
||||
- Positional data from AST?
|
||||
- `tags`
|
||||
|
||||
### Markdown layer
|
||||
|
||||
- `source`: raw markdown (rename?)
|
||||
- `ast`: raw markdown ast
|
||||
- `checksum`: if we do caching
|
||||
|
||||
### Link text
|
||||
|
||||
// some-file.md
|
||||
// # Some File
|
||||
|
||||
Write -> Store on disk
|
||||
[[Some File]] -> [Some File](some-file.md)
|
||||
|
||||
Editing
|
||||
[Some File](some-file.md)
|
||||
|
||||
On disk (could be solved by migration)
|
||||
[[some-file]]
|
||||
[[Some File]]
|
||||
|
||||
- docs/index.md -> Index
|
||||
- notes/index.md -> Index
|
||||
|
||||
[[Index]]
|
||||
[[Index | notes/index.md]]
|
||||
|
||||
[Index] docs/index.md
|
||||
[Index | notes/index.md]: notes/index.md
|
||||
|
||||
[[Some File | path/to/some-file.md]]
|
||||
|
||||
Do we apply any constraints:
|
||||
|
||||
- `[[file-name-without-extension]]`
|
||||
- `[[file-name-with-extension.md]]`
|
||||
- `[[Title Cased File Name]]`
|
||||
|
||||
Not supported by Markdown Notes:
|
||||
|
||||
- `[[path/to/file-name.md]]` - Just use markdown links
|
||||
- `[[Target Note Title]]`
|
||||
|
||||
Issues:
|
||||
|
||||
- Name clashes in directories
|
||||
- Name clashes between extensions
|
||||
- Renaming
|
||||
- Change filename/title needs to reflect everywhere
|
||||
- Orphaning
|
||||
|
||||
- If we can't rely on in-memory process to rename things correctly while changes happen (e.g. file is renamed, moved, deleted, or titled) <ref id="1" />
|
||||
|
||||
Solving this issue is necessarily heuristic. We could try to write smart solutions, plus a linter for orphans
|
||||
|
||||
How others solve this:
|
||||
|
||||
- Unique ids -- could support optionally as part of file name or front matter metadata. Should not be required.
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[todo]: ../todo "Todo"
|
||||
[//end]: # "Autogenerated link references"
|
||||
@@ -1,48 +0,0 @@
|
||||
<p class="github-only">
|
||||
👋 <b>Hello friend! Looks like you're reading this page on GitHub. Please go to the 👉<a href="https://foambubble.github.io/foam">rendered Foam Workspace</a> for an improved experience! </b>
|
||||
</p>
|
||||
|
||||
<p class="github-only">
|
||||
👀<i>This is an early stage project under rapid development. For updates follow <a href="https://twitter.com/jevakallio" target="_blank">@jevakallio</a> on Twitter, or join the <a href="https://discord.gg/rtdZKgj" target="_blank">Foam community Discord</a>! 💬</i>
|
||||
</p>
|
||||
|
||||
# Foam
|
||||
|
||||
**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/).
|
||||
|
||||
You can use **Foam** for organising your research, keeping re-discoverable notes, writing long-form content and, optionally, publishing it to the web.
|
||||
|
||||
**Foam** is free, open source, and extremely extensible to suit your personal workflow. You own the information you create with Foam, and you're free to share it, and collaborate on it with anyone you want.
|
||||
|
||||
## How do I use Foam?
|
||||
|
||||
Whether you want to build a [Second Brain](https://www.buildingasecondbrain.com/) or a [Zettelkasten](https://zettelkasten.de/posts/overview/), write a book, or just get better at long-term learning, **Foam** can help you organise your thoughts if you follow these simple rules:
|
||||
|
||||
1. Create a single **Foam** workspace for all your knowledge and research following the [Getting started](https://foambubble.github.io/foam#getting-started) guide .
|
||||
2. Write your thoughts in markdown documents (I like to call them **Bubbles**, but that might be more than a little twee). These documents should be atomic: Put things that belong together into a single document, and limit its content to that single topic. ([source](https://zettelkasten.de/posts/overview/#principles))
|
||||
3. Use Foam's shortcuts and autocompletions to link your thoughts together with `[[wiki-links]]`, and navigate between them to explore your knowledge graph.
|
||||
4. Get an overview of your **Foam** workspace using the [[Graph Visualisation](https://foambubble.github.io/foam/graph-visualisation)], and discover relationships between your thoughts with the use of [[Backlinking](https://foambubble.github.io/foam/backlinking)].
|
||||
|
||||

|
||||
|
||||
Foam is a like a bathtub: _What you get out of it depends on what you put into it._
|
||||
|
||||
## Learn more
|
||||
|
||||
**Head over to the 👉[Published version of this Foam workspace](https://foambubble.github.io/foam#whats-in-a-foam)** to see Foam in action and read the rest of the documentation!
|
||||
|
||||
Quick links to next documentation sections
|
||||
|
||||
- [What's in a Foam?](https://foambubble.github.io/foam#whats-in-a-foam)
|
||||
- [Getting started](https://foambubble.github.io/foam#getting-started)
|
||||
- [Features](https://foambubble.github.io/foam#features)
|
||||
- [Call To Adventure](https://foambubble.github.io/foam#call-to-adventure)
|
||||
- [Thanks and attribution](https://foambubble.github.io/foam#thanks-and-attribution)
|
||||
|
||||
## License
|
||||
|
||||
Foam is licensed under the [MIT license](license).
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[wiki-links]: wiki-links "Wiki Links"
|
||||
[//end]: # "Autogenerated link references"
|
||||
@@ -14,7 +14,7 @@ Guides, tips and strategies for getting the most out of your Foam workspace!
|
||||
- [Workflow](#workflow)
|
||||
- [Creative ideas](#creative-ideas)
|
||||
- [Other](#other)
|
||||
|
||||
|
||||
## Contribute
|
||||
|
||||
- Start by reading [[contribution-guide]]
|
||||
@@ -23,7 +23,7 @@ Guides, tips and strategies for getting the most out of your Foam workspace!
|
||||
## Take smart notes
|
||||
|
||||
- Introduction to Zettelkasten [[todo]]
|
||||
|
||||
|
||||
## Discover
|
||||
- Explore your notes using [[graph-visualisation]]
|
||||
- Discover relationships with [[backlinking]]
|
||||
@@ -31,13 +31,16 @@ Guides, tips and strategies for getting the most out of your Foam workspace!
|
||||
|
||||
## Organise
|
||||
- Using [[backlinking]] for [[reference-lists]].
|
||||
|
||||
|
||||
## Write
|
||||
- Link documents with [[wiki-links]]
|
||||
- Use shortcuts for [[creating-new-notes]]
|
||||
- Draw [[diagrams-in-markdown]]
|
||||
- Prettify your links, [[automatically-expand-urls-to-well-titled-links]]
|
||||
- Style your environment with [[custom-markdown-preview-styles]]
|
||||
- Paste and link [[images-from-your-clipboard]]
|
||||
- [Markdown All-in-One](https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one) features [[todo]] [[good-first-task]]
|
||||
- Manage checklists
|
||||
- Manage checklists
|
||||
- Automatic Table of Contents
|
||||
- Live preview markdown
|
||||
- _More..._
|
||||
@@ -52,15 +55,17 @@ Guides, tips and strategies for getting the most out of your Foam workspace!
|
||||
## Publish
|
||||
|
||||
- Publish to [[github-pages]]
|
||||
- Publish to [[gitlab-pages]]
|
||||
- Publish your site with [[eleventy-and-netlify]]
|
||||
- Make the site your own by [[customising-styles]].
|
||||
- Host your own website [[todo]]
|
||||
- Math support [[math-support]]
|
||||
|
||||
## Collaborate
|
||||
|
||||
- Give your team push access to your GitHub repo [[todo]]
|
||||
- Real-time collaboration via VS Code Live Share [[todo]]
|
||||
- Accept patches via GitHub PRs [[todo]]
|
||||
|
||||
|
||||
## Workflow
|
||||
|
||||
Workflow recipes wanted!
|
||||
@@ -69,7 +74,7 @@ _See [[contribution-guide]] and [[how-to-write-recipes]]._
|
||||
|
||||
## Creative ideas
|
||||
|
||||
Creative ideas welcome!
|
||||
Creative ideas welcome!
|
||||
|
||||
- Support [Anki](https://apps.ankiweb.net/) cards from notes like [Remnote](https://www.remnote.io/) [[todo]]
|
||||
|
||||
@@ -77,7 +82,7 @@ _See [[contribution-guide]] and [[how-to-write-recipes]]._
|
||||
|
||||
## Other
|
||||
|
||||
Thought of a recipe but don't see a category for them? Add them here and we'll organise them once we detect a theme.
|
||||
Thought of a recipe but don't see a category for them? Add them here and we'll organise them once we detect a theme.
|
||||
|
||||
_See [[contribution-guide]] and [[how-to-write-recipes]]._
|
||||
|
||||
@@ -92,9 +97,13 @@ _See [[contribution-guide]] and [[how-to-write-recipes]]._
|
||||
[wiki-links]: wiki-links "Wiki Links"
|
||||
[creating-new-notes]: creating-new-notes "Creating New Notes"
|
||||
[diagrams-in-markdown]: diagrams-in-markdown "Diagrams in Markdown"
|
||||
[good-first-task]: good-first-task "Good First Task"
|
||||
[automatically-expand-urls-to-well-titled-links]: automatically-expand-urls-to-well-titled-links "Automatically Expand URLs to Well-Titled Links"
|
||||
[custom-markdown-preview-styles]: custom-markdown-preview-styles "Custom Markdown Preview Styles"
|
||||
[images-from-your-clipboard]: images-from-your-clipboard "Images from your Clipboard"
|
||||
[good-first-task]: good-first-task "Good First Task"
|
||||
[git-integration]: git-integration "Git integration"
|
||||
[github-pages]: github-pages "Github Pages"
|
||||
[eleventy-and-netlify]: eleventy-and-netlify "Eleventy and Netlify"
|
||||
[customising-styles]: customising-styles "Customising Styles"
|
||||
[math-support]: math-support "Math Support"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
@@ -38,9 +38,12 @@ If a roadmap item is a stub, **consider** opening a [GitHub issue](https://githu
|
||||
- [[daily-notes]]
|
||||
- [[block-references]]
|
||||
- [[improved-backlinking]]
|
||||
- UX: [[make-backlinks-more-prominent]]
|
||||
- [[materialized-backlinks]]
|
||||
- [[automatic-git-syncing]]
|
||||
- [[git-flows-for-teams]]
|
||||
- [[user-settings]]
|
||||
- [[link-reference-definitions]]
|
||||
|
||||
### Publishing
|
||||
|
||||
@@ -81,7 +84,7 @@ If a roadmap item is a stub, **consider** opening a [GitHub issue](https://githu
|
||||
[build-vs-assemble]: build-vs-assemble "Build vs Assemble"
|
||||
[recipes]: recipes "Recipes"
|
||||
[cli]: cli "Command Line Interface"
|
||||
[workspace-janitor]: workspace-janitor "Workspace Janitor (stub)"
|
||||
[workspace-janitor]: workspace-janitor "Workspace Janitor"
|
||||
[contribution-guide]: contribution-guide "Contribution Guide"
|
||||
[improve-default-workspace-settings]: improve-default-workspace-settings "Improve Default Workspace Settings (stub)"
|
||||
[git-integration]: git-integration "Git integration"
|
||||
@@ -109,4 +112,5 @@ If a roadmap item is a stub, **consider** opening a [GitHub issue](https://githu
|
||||
[migrating-from-obsidian]: migrating-from-obsidian "Migrating from Obsidian (stub)"
|
||||
[foam-linter]: foam-linter "Foam Linter (stub)"
|
||||
[refactoring-via-language-server-protocol]: refactoring-via-language-server-protocol "Refactoring via Language Server Protocol (stub)"
|
||||
[//end]: # "Autogenerated link references"
|
||||
[make-backlinks-more-prominent]: make-backlinks-more-prominent "Make Backlinks More Prominent"
|
||||
[//end]: # "Autogenerated link references"
|
||||
@@ -4,20 +4,17 @@ It would be good to have some shared terminology to talk about Foam concepts. So
|
||||
|
||||
Here's some ideas, these are open for discussion.
|
||||
|
||||
## Foam (project)
|
||||
## Foam, the software project
|
||||
|
||||
The set of tools and ideas collected in this organisation.
|
||||
|
||||
## Foam workspace
|
||||
## (Your) Foam
|
||||
|
||||
The directory/repository where you keep all your documents.
|
||||
The directory/repository where you keep all your notes.
|
||||
|
||||
Also happens to sound quite a lot like Home. Funny, that.
|
||||
|
||||
## Bubble
|
||||
|
||||
Individual Foam document, written in Markdown.
|
||||
Individual Foam note, written in Markdown.
|
||||
|
||||
## Foam blog
|
||||
|
||||
When you use Foam to publish content to an audience.
|
||||
|
||||
_Better ideas welcome._
|
||||
|
||||
9
docs/user-settings.md
Normal file
9
docs/user-settings.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# User Settings (stub)
|
||||
|
||||
**[[todo]] This [[roadmap]] item needs more specification work.**
|
||||
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[todo]: todo "Todo"
|
||||
[roadmap]: roadmap "Roadmap"
|
||||
[//end]: # "Autogenerated link references"
|
||||
@@ -1,6 +1,6 @@
|
||||
# Wiki Links
|
||||
|
||||
Foam enables you to Link pages together using `[[file-name]]` annotations.
|
||||
Foam enables you to Link pages together using `[[file-name]]` annotations (i.e. `[[media-wiki]]` links).
|
||||
|
||||
- Both `[[file-name]]` and `[[file-name.md]]` work
|
||||
- Type `[[` and start typing a file name for autocompletion.
|
||||
@@ -16,72 +16,15 @@ Foam enables you to Link pages together using `[[file-name]]` annotations.
|
||||
|
||||
## Markdown compatibility
|
||||
|
||||
The [Foam for VSCode](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode) extension automatically generates [markdown link reference definitions](https://spec.commonmark.org/0.29/#link-reference-definitions) at the bottom of the file to make wiki-links compatible with Markdown tools and parsers.
|
||||
|
||||
If you look at link references the bottom of any Foam workspace file that uses wiki-links, you should see an automatically generated list of references that look as follows:
|
||||
|
||||
```markdown
|
||||
[wiki-links]: wiki-links "Wiki Links"
|
||||
[other-page]: other-page "Other Page"
|
||||
```
|
||||
|
||||
These exist to make `[[wiki-links]]` compatible with Markdown-consuming tools such as static site generators, VS Code plugins etc.
|
||||
|
||||
## Why don't `[[wiki-links]]` work on GitHub
|
||||
|
||||
> **TL;DR;** [workaround](#workaround) in the end.
|
||||
|
||||
If you click any of the wiki-links on GitHub web UI (such as the `README.md` of a project), you'll notice that the links break with a 404 error.
|
||||
|
||||
At the time of writing (June 28 2020) this is a known, but unsolved error. To understand why this is the case, we need to understand what we are trading off.
|
||||
|
||||
So, why don't they work on GitHub?
|
||||
|
||||
The three components of a [link reference definitions](https://spec.commonmark.org/0.29/#link-reference-definitions) are:
|
||||
|
||||
- **link label:** The link text to match in the surrounding markdown document. This matches the inner bracket of the double-bracketed `[[wiki-link]]` notation
|
||||
- **link destination** The target of the matched link
|
||||
- **"Link Title"** Optional title for link (The Foam template has a snippet of JavaScript to replace this on the website at runtime)
|
||||
|
||||
The issue is the middle **link destination** component. It's configured to point to the file name **without file extension**, i.e. "file-name" instead of "file-name.md". This is to make the GitHub Pages rendering work, because if we generated the links to `file-name.md`, the links would point to the raw markdown files instead of their generated HTML versions.
|
||||
|
||||
| Environment | `file-name` | `file-name.md` |
|
||||
| ---------------- | ----------- | -------------- |
|
||||
| **VS Code** | Works | Works |
|
||||
| **GitHub pages** | Works | Breaks |
|
||||
| **GitHub UI** | Breaks | Works |
|
||||
|
||||
So as you can see, we've prioritised GitHub Pages over GitHub Web U for the time being.
|
||||
|
||||
Ideally, we'd like a solution that works with both, but I haven't thought of it yet. Ideas include:
|
||||
|
||||
- **Writing a better static side generator that works with `file-name.md` link targets.** This is on the [[roadmap]], but for the time being GitHub Pages support is as must-have.
|
||||
- **Adding a configuration setting to generate `file-name.md` link targets.** This is fine and I would accept this contribution to [foam-vscode](https://github.com/foambubble/foam/tree/master/packages/foam-vscode), but it doesn't solve the core problem.
|
||||
|
||||
An acceptable solution may include one where we don't generate link reference definitions at all, but if we do, ideally, we'd like to generate `file-name.md` links since those are more standards compatible for different markdown tools.
|
||||
|
||||
I'm sure there's an elegant-ish solution out there. Ideas and suggestions welcome that the [tracking issue on GitHub](https://github.com/foambubble/foam/issues/16)
|
||||
|
||||
### Workaround
|
||||
|
||||
For the time being, you can use relative `[markdown links](markdown-link.md)` syntax.
|
||||
|
||||
**Pros:**
|
||||
|
||||
- This will work on all platforms.
|
||||
|
||||
**Cons:**
|
||||
|
||||
- It will break the Markdown Notes [[backlinking]] support
|
||||
- Less convenient to write
|
||||
The [Foam for VSCode](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode) extension automatically generates [[link-reference-definitions]] at the bottom of the file to make wiki-links compatible with Markdown tools and parsers.
|
||||
|
||||
## Read more
|
||||
|
||||
- [[foam-file-format]]
|
||||
- See [[link-reference-definition-improvements]] for further discussion on current problems and potential solutions.
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[wiki-links]: wiki-links "Wiki Links"
|
||||
[roadmap]: roadmap "Roadmap"
|
||||
[backlinking]: backlinking "Backlinking"
|
||||
[link-reference-definitions]: link-reference-definitions "Link Reference Definitions"
|
||||
[foam-file-format]: foam-file-format "Foam File Format"
|
||||
[link-reference-definition-improvements]: link-reference-definition-improvements "Link Reference Definition Improvements"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
8
lerna.json
Normal file
8
lerna.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.2.0"
|
||||
}
|
||||
24
package.json
24
package.json
@@ -1,13 +1,33 @@
|
||||
{
|
||||
"name": "foam",
|
||||
"version": "0.0.1",
|
||||
"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": ["packages/*"],
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"watch": "yarn workspace foam-vscode watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"all-contributors-cli": "^6.16.1",
|
||||
"lerna": "^3.22.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "tsdx lint"
|
||||
}
|
||||
},
|
||||
"prettier": {
|
||||
"printWidth": 80,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ $ npm install -g foam-cli
|
||||
$ foam COMMAND
|
||||
running command...
|
||||
$ foam (-v|--version|version)
|
||||
foam-cli/0.1.0 darwin-x64 node-v12.18.0
|
||||
foam-cli/0.2.0 darwin-x64 node-v12.18.0
|
||||
$ foam --help [COMMAND]
|
||||
USAGE
|
||||
$ foam COMMAND
|
||||
@@ -49,7 +49,7 @@ EXAMPLE
|
||||
hello world from ./src/hello.ts!
|
||||
```
|
||||
|
||||
_See code: [src/commands/hello.ts](https://github.com/foambubble/foam/blob/v0.1.0/src/commands/hello.ts)_
|
||||
_See code: [src/commands/hello.ts](https://github.com/foambubble/foam/blob/v0.2.0/src/commands/hello.ts)_
|
||||
|
||||
## `foam help [COMMAND]`
|
||||
|
||||
@@ -73,5 +73,5 @@ _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v3.1.0
|
||||
|
||||
- Run `yarn` somewhere in workspace (ideally root, see [yarn workspace docs](https://classic.yarnpkg.com/en/docs/workspaces/)
|
||||
- This will automatically symlink all package directories so you're using the local copy
|
||||
- In `packages/foam-workspace-manager`, run `yarn start` to rebuild the library on every change
|
||||
- In `packages/foam-core`, run `yarn start` to rebuild the library on every change
|
||||
- In `packages/foam-cli`, make changes and run with `yarn run cli`. This should use latest workspace manager changes.
|
||||
|
||||
6
packages/foam-cli/babel.config.js
Normal file
6
packages/foam-cli/babel.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
['@babel/preset-env', {targets: {node: 'current'}}],
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
};
|
||||
188
packages/foam-cli/jest.config.js
Normal file
188
packages/foam-cli/jest.config.js
Normal file
@@ -0,0 +1,188 @@
|
||||
// For a detailed explanation regarding each configuration property, visit:
|
||||
// https://jestjs.io/docs/en/configuration.html
|
||||
|
||||
module.exports = {
|
||||
// All imported modules in your tests should be mocked automatically
|
||||
// automock: false,
|
||||
|
||||
// Stop running tests after `n` failures
|
||||
// bail: 0,
|
||||
|
||||
// The directory where Jest should store its cached dependency information
|
||||
// cacheDirectory: "/private/var/folders/p6/5y5l8tbs1d32pq9b596lk48h0000gn/T/jest_dx",
|
||||
|
||||
// Automatically clear mock calls and instances between every test
|
||||
clearMocks: true,
|
||||
|
||||
// Indicates whether the coverage information should be collected while executing the test
|
||||
// collectCoverage: false,
|
||||
|
||||
// An array of glob patterns indicating a set of files for which coverage information should be collected
|
||||
// collectCoverageFrom: undefined,
|
||||
|
||||
// The directory where Jest should output its coverage files
|
||||
// coverageDirectory: undefined,
|
||||
|
||||
// An array of regexp pattern strings used to skip coverage collection
|
||||
// coveragePathIgnorePatterns: [
|
||||
// "/node_modules/"
|
||||
// ],
|
||||
|
||||
// Indicates which provider should be used to instrument code for coverage
|
||||
// coverageProvider: "babel",
|
||||
|
||||
// A list of reporter names that Jest uses when writing coverage reports
|
||||
// coverageReporters: [
|
||||
// "json",
|
||||
// "text",
|
||||
// "lcov",
|
||||
// "clover"
|
||||
// ],
|
||||
|
||||
// An object that configures minimum threshold enforcement for coverage results
|
||||
// coverageThreshold: undefined,
|
||||
|
||||
// A path to a custom dependency extractor
|
||||
// dependencyExtractor: undefined,
|
||||
|
||||
// Make calling deprecated APIs throw helpful error messages
|
||||
// errorOnDeprecated: false,
|
||||
|
||||
// Force coverage collection from ignored files using an array of glob patterns
|
||||
// forceCoverageMatch: [],
|
||||
|
||||
// A path to a module which exports an async function that is triggered once before all test suites
|
||||
// globalSetup: undefined,
|
||||
|
||||
// A path to a module which exports an async function that is triggered once after all test suites
|
||||
// globalTeardown: undefined,
|
||||
|
||||
// A set of global variables that need to be available in all test environments
|
||||
// globals: {},
|
||||
|
||||
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
|
||||
// maxWorkers: "50%",
|
||||
|
||||
// An array of directory names to be searched recursively up from the requiring module's location
|
||||
// moduleDirectories: [
|
||||
// "node_modules"
|
||||
// ],
|
||||
|
||||
// An array of file extensions your modules use
|
||||
// moduleFileExtensions: [
|
||||
// "js",
|
||||
// "json",
|
||||
// "jsx",
|
||||
// "ts",
|
||||
// "tsx",
|
||||
// "node"
|
||||
// ],
|
||||
|
||||
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
|
||||
// moduleNameMapper: {},
|
||||
|
||||
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
||||
// modulePathIgnorePatterns: [],
|
||||
|
||||
// Activates notifications for test results
|
||||
// notify: false,
|
||||
|
||||
// An enum that specifies notification mode. Requires { notify: true }
|
||||
// notifyMode: "failure-change",
|
||||
|
||||
// A preset that is used as a base for Jest's configuration
|
||||
// preset: undefined,
|
||||
|
||||
// Run tests from one or more projects
|
||||
// projects: undefined,
|
||||
|
||||
// Use this configuration option to add custom reporters to Jest
|
||||
// reporters: undefined,
|
||||
|
||||
// Automatically reset mock state between every test
|
||||
// resetMocks: false,
|
||||
|
||||
// Reset the module registry before running each individual test
|
||||
// resetModules: false,
|
||||
|
||||
// A path to a custom resolver
|
||||
// resolver: undefined,
|
||||
|
||||
// Automatically restore mock state between every test
|
||||
// restoreMocks: false,
|
||||
|
||||
// The root directory that Jest should scan for tests and modules within
|
||||
// rootDir: undefined,
|
||||
|
||||
// A list of paths to directories that Jest should use to search for files in
|
||||
// roots: [
|
||||
// "<rootDir>"
|
||||
// ],
|
||||
|
||||
// Allows you to use a custom runner instead of Jest's default test runner
|
||||
// runner: "jest-runner",
|
||||
|
||||
// The paths to modules that run some code to configure or set up the testing environment before each test
|
||||
// setupFiles: [],
|
||||
|
||||
// A list of paths to modules that run some code to configure or set up the testing framework before each test
|
||||
// setupFilesAfterEnv: [],
|
||||
|
||||
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
|
||||
// snapshotSerializers: [],
|
||||
|
||||
// The test environment that will be used for testing
|
||||
testEnvironment: "node",
|
||||
|
||||
// Options that will be passed to the testEnvironment
|
||||
// testEnvironmentOptions: {},
|
||||
|
||||
// Adds a location field to test results
|
||||
// testLocationInResults: false,
|
||||
|
||||
// The glob patterns Jest uses to detect test files
|
||||
// testMatch: [
|
||||
// "**/__tests__/**/*.[jt]s?(x)",
|
||||
// "**/?(*.)+(spec|test).[tj]s?(x)"
|
||||
// ],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
|
||||
// testPathIgnorePatterns: [
|
||||
// "/node_modules/"
|
||||
// ],
|
||||
|
||||
// The regexp pattern or array of patterns that Jest uses to detect test files
|
||||
// testRegex: [],
|
||||
|
||||
// This option allows the use of a custom results processor
|
||||
// testResultsProcessor: undefined,
|
||||
|
||||
// This option allows use of a custom test runner
|
||||
// testRunner: "jasmine2",
|
||||
|
||||
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
|
||||
// testURL: "http://localhost",
|
||||
|
||||
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
|
||||
// timers: "real",
|
||||
|
||||
// A map from regular expressions to paths to transformers
|
||||
// transform: undefined,
|
||||
|
||||
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
||||
// transformIgnorePatterns: [
|
||||
// "/node_modules/"
|
||||
// ],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
||||
// unmockedModulePathPatterns: undefined,
|
||||
|
||||
// Indicates whether each individual test should be reported during the run
|
||||
// verbose: undefined,
|
||||
|
||||
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
|
||||
// watchPathIgnorePatterns: [],
|
||||
|
||||
// Whether to use watchman for file crawling
|
||||
// watchman: true,
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "foam-cli",
|
||||
"description": "Foam CLI",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"author": "Jani Eväkallio @jevakallio",
|
||||
"bin": {
|
||||
"foam": "./bin/run"
|
||||
@@ -11,20 +11,33 @@
|
||||
"@oclif/command": "^1",
|
||||
"@oclif/config": "^1",
|
||||
"@oclif/plugin-help": "^3",
|
||||
"foam-workspace-manager": "^0.1.1",
|
||||
"@types/inquirer": "^6.5.0",
|
||||
"foam-core": "^0.2.0",
|
||||
"inquirer": "^7.3.2",
|
||||
"ora": "^4.0.4",
|
||||
"tslib": "^1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.10.4",
|
||||
"@babel/preset-env": "^7.10.4",
|
||||
"@babel/preset-typescript": "^7.10.4",
|
||||
"@oclif/dev-cli": "^1",
|
||||
"@types/node": "^10",
|
||||
"babel-jest": "^26.1.0",
|
||||
"chai": "^4",
|
||||
"eslint": "^5.13",
|
||||
"eslint-config-oclif": "^3.1",
|
||||
"eslint-config-oclif-typescript": "^0.1",
|
||||
"foam-core": "^0.2.0",
|
||||
"globby": "^10",
|
||||
"jest": "^26.1.0",
|
||||
"mock-fs": "^4.12.0",
|
||||
"ts-node": "^8",
|
||||
"typescript": "^3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"foam-core": "^0.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
},
|
||||
@@ -53,7 +66,7 @@
|
||||
"postpack": "rm -f oclif.manifest.json",
|
||||
"posttest": "eslint . --ext .ts --config .eslintrc",
|
||||
"prepack": "rm -rf lib && tsc -b && oclif-dev manifest && oclif-dev readme",
|
||||
"test": "nyc --extension .ts mocha --forbid-only \"test/**/*.test.ts\"",
|
||||
"test": "jest",
|
||||
"version": "oclif-dev readme && git add README.md"
|
||||
},
|
||||
"types": "lib/index.d.ts"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {Command, flags} from '@oclif/command'
|
||||
import { WorkspaceManager } from 'foam-workspace-manager';
|
||||
import { NoteGraph } from 'foam-core';
|
||||
export default class Hello extends Command {
|
||||
static description = 'describe the command here'
|
||||
|
||||
@@ -23,19 +23,19 @@ hello world from ./src/hello.ts!
|
||||
const {args, flags} = this.parse(Hello)
|
||||
const name = flags.name ?? 'world'
|
||||
|
||||
const wm = new WorkspaceManager('./foo');
|
||||
wm.addNoteFromMarkdown('page-a.md', `
|
||||
# Page A
|
||||
## Section
|
||||
- [[page-b]]
|
||||
- [[page-c]];
|
||||
`);
|
||||
// const wm = new NoteGraph();
|
||||
// wm.addNoteFromMarkdown('page-a.md', `
|
||||
// # Page A
|
||||
// ## Section
|
||||
// - [[page-b]]
|
||||
// - [[page-c]];
|
||||
// `);
|
||||
|
||||
wm.addNoteFromMarkdown('page-a.md', `
|
||||
# Page B
|
||||
This references [[page-a]]`);
|
||||
// wm.addNoteFromMarkdown('page-a.md', `
|
||||
// # Page B
|
||||
// This references [[page-a]]`);
|
||||
|
||||
console.log(wm.getNoteWithLinks('page-a'));
|
||||
// console.log(wm.getNoteWithLinks('page-a'));
|
||||
|
||||
|
||||
|
||||
|
||||
168
packages/foam-cli/src/commands/init.ts
Normal file
168
packages/foam-cli/src/commands/init.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
/*eslint-disable no-unused-vars*/
|
||||
|
||||
import { Command, flags } from '@oclif/command';
|
||||
import * as inquirer from 'inquirer';
|
||||
import * as ora from 'ora';
|
||||
|
||||
// @todo implement this class, currently it does nothing but collect inputs
|
||||
export default class Init extends Command {
|
||||
static description = 'Initialize a new Foam workspace from template';
|
||||
|
||||
// @todo better examples
|
||||
static examples = [`$ foam init`];
|
||||
|
||||
// @todo validate inputs
|
||||
static flags = {
|
||||
help: flags.help({ char: 'h' }),
|
||||
name: flags.string({
|
||||
char: 'n',
|
||||
description: 'workspace name',
|
||||
}),
|
||||
|
||||
scm: flags.string({
|
||||
char: 's',
|
||||
description: 'source control (github, git, local)'
|
||||
}),
|
||||
|
||||
template: flags.string({
|
||||
char: 't',
|
||||
description: 'template'
|
||||
}),
|
||||
|
||||
gitHubUser: flags.string({
|
||||
char: 'u',
|
||||
description: 'github username'
|
||||
}),
|
||||
|
||||
gitHubPassword: flags.string({
|
||||
description: 'github password'
|
||||
}),
|
||||
|
||||
// @todo make flag
|
||||
githubPages: flags.string({
|
||||
char: 'p',
|
||||
description: 'enable github pages'
|
||||
}),
|
||||
|
||||
repoOwner: flags.string({
|
||||
char: 'p',
|
||||
description: 'github repo owner'
|
||||
}),
|
||||
|
||||
visibility: flags.string({
|
||||
char: 'v',
|
||||
description: 'github repo visibility (public/private)'
|
||||
}),
|
||||
};
|
||||
|
||||
async run() {
|
||||
const { flags } = this.parse(Init);
|
||||
|
||||
const name =
|
||||
flags.name ||
|
||||
(await inquirer.prompt({
|
||||
name: 'name',
|
||||
message: 'Give your workspace a name',
|
||||
type: 'input',
|
||||
default: 'foam',
|
||||
})).name;
|
||||
|
||||
const template =
|
||||
flags.template ||
|
||||
(await inquirer.prompt({
|
||||
name: 'template',
|
||||
message: 'Choose from one of the available templates',
|
||||
type: 'list',
|
||||
choices: [
|
||||
{ name: 'Default (foam-template)' },
|
||||
{ name: 'Gatsby + GitHub Actions (foam-template-gatsby)' },
|
||||
{ name: '11ty + Netlify (foam-template-eleventy)' },
|
||||
{ name: 'MLH Fellowship Workspace (foam-template-mlh)' },
|
||||
],
|
||||
})).template;
|
||||
|
||||
const scm = (await inquirer.prompt([
|
||||
{
|
||||
name: 'scm',
|
||||
message: 'How do you want to store your workspace?',
|
||||
type: 'list',
|
||||
default: 'GitHub',
|
||||
choices: [
|
||||
{ name: 'GitHub' },
|
||||
{ name: 'Local git repository' },
|
||||
{ name: 'Local directory (no source control)' },
|
||||
],
|
||||
},
|
||||
])).scm;
|
||||
|
||||
if (scm === 'GitHub') {
|
||||
const userName =
|
||||
flags.gitHubUser ||
|
||||
(await inquirer.prompt({
|
||||
name: 'username',
|
||||
message: 'GitHub username',
|
||||
type: 'input'
|
||||
})).username;
|
||||
|
||||
const password =
|
||||
flags.gitHubPassword ||
|
||||
(await inquirer.prompt({
|
||||
name: 'password',
|
||||
message: 'GitHub password',
|
||||
type: 'password'
|
||||
})).password;
|
||||
|
||||
const owner =
|
||||
flags.repoOwner ||
|
||||
(await inquirer.prompt({
|
||||
name: 'owner',
|
||||
message: 'GitHub repository owner',
|
||||
type: 'input',
|
||||
default: userName
|
||||
})).owner;
|
||||
|
||||
const visibility =
|
||||
flags.visibility ||
|
||||
(await inquirer.prompt({
|
||||
name: 'visibility',
|
||||
message: 'Should the repository be public or private?',
|
||||
type: 'list',
|
||||
choices: [
|
||||
{ name: 'Public' },
|
||||
{ name: 'Private' }
|
||||
],
|
||||
})).visibility.toLowerCase();
|
||||
|
||||
const pages =
|
||||
flags.githubPages ||
|
||||
((await inquirer.prompt({
|
||||
name: 'pages',
|
||||
message: 'Publish automatically to GitHub pages?',
|
||||
type: 'list',
|
||||
choices: [
|
||||
{ name: 'Yes' },
|
||||
{ name: 'No' }
|
||||
],
|
||||
})).pages === 'Yes');
|
||||
|
||||
|
||||
const sure = (await inquirer.prompt({
|
||||
name: 'sure',
|
||||
type: 'confirm',
|
||||
message: `Create a new ${visibility} Foam in https://github.com/${owner}/${name}?`
|
||||
})).sure;
|
||||
|
||||
if (sure) {
|
||||
const spinner = ora().start();
|
||||
await new Promise(resolve => {
|
||||
setTimeout(() => resolve(), 1000);
|
||||
});
|
||||
spinner.succeed();
|
||||
spinner.succeed('Foam workspace created!');
|
||||
spinner.succeed('Run "code foam" to open your new workspace');
|
||||
}
|
||||
} else {
|
||||
console.log(`Created a private Foam workspace in ./${name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
74
packages/foam-cli/src/commands/janitor.ts
Normal file
74
packages/foam-cli/src/commands/janitor.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { Command, flags } from '@oclif/command';
|
||||
import * as ora from 'ora';
|
||||
import { initializeNoteGraph, generateLinkReferences, generateHeading, getKebabCaseFileName } from 'foam-core';
|
||||
import { applyTextEdit } from '../utils/apply-text-edit';
|
||||
import { writeFileToDisk } from '../utils/write-file-to-disk';
|
||||
import { isValidDirectory } from '../utils';
|
||||
|
||||
export default class Janitor extends Command {
|
||||
static description = 'Updates link references and heading across all the markdown files in the given workspaces';
|
||||
|
||||
static examples = [
|
||||
`$ foam-cli janitor path-to-foam-workspace
|
||||
Successfully generated link references and heading!
|
||||
`,
|
||||
]
|
||||
|
||||
static flags = {
|
||||
help: flags.help({ char: 'h' }),
|
||||
}
|
||||
|
||||
static args = [{ name: 'workspacePath' }]
|
||||
|
||||
async run() {
|
||||
const spinner = ora('Reading Files').start();
|
||||
|
||||
const { args, flags } = this.parse(Janitor)
|
||||
|
||||
const { workspacePath = './' } = args;
|
||||
|
||||
if (isValidDirectory(workspacePath)) {
|
||||
const graph = await initializeNoteGraph(workspacePath);
|
||||
|
||||
const notes = graph.getNotes().filter(Boolean); // removes undefined notes
|
||||
|
||||
spinner.succeed();
|
||||
spinner.text = `${notes.length} files found`;
|
||||
spinner.succeed();
|
||||
|
||||
// exit early if no files found.
|
||||
if (notes.length === 0) {
|
||||
this.exit();
|
||||
}
|
||||
|
||||
spinner.text = 'Generating link definitions';
|
||||
|
||||
const fileWritePromises = notes.map(note => {
|
||||
// Get edits
|
||||
const heading = generateHeading(note);
|
||||
const definitions = generateLinkReferences(note, graph);
|
||||
|
||||
|
||||
// apply Edits
|
||||
let file = note.source;
|
||||
file = heading ? applyTextEdit(file, heading) : file;
|
||||
file = definitions ? applyTextEdit(file, definitions) : file;
|
||||
|
||||
|
||||
if (heading || definitions) {
|
||||
return writeFileToDisk(note.path, file);
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
})
|
||||
|
||||
await Promise.all(fileWritePromises);
|
||||
|
||||
spinner.succeed();
|
||||
spinner.succeed('Done!');
|
||||
}
|
||||
else {
|
||||
spinner.fail('Directory does not exist!');
|
||||
}
|
||||
}
|
||||
}
|
||||
95
packages/foam-cli/src/commands/migrate.ts
Normal file
95
packages/foam-cli/src/commands/migrate.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { Command, flags } from '@oclif/command';
|
||||
import * as ora from 'ora';
|
||||
import { initializeNoteGraph, generateLinkReferences, generateHeading, getKebabCaseFileName } from 'foam-core';
|
||||
import { applyTextEdit } from '../utils/apply-text-edit';
|
||||
import { writeFileToDisk } from '../utils/write-file-to-disk';
|
||||
import { renameFile } from '../utils/rename-file';
|
||||
import { isValidDirectory } from '../utils';
|
||||
|
||||
// @todo: Refactor 'migrate' and 'janitor' commands and avoid repeatition
|
||||
export default class Migrate extends Command {
|
||||
static description = 'Updates file names, link references and heading across all the markdown files in the given workspaces';
|
||||
|
||||
static examples = [
|
||||
`$ foam-cli migrate path-to-foam-workspace
|
||||
Successfully generated link references and heading!
|
||||
`,
|
||||
]
|
||||
|
||||
static flags = {
|
||||
help: flags.help({ char: 'h' }),
|
||||
}
|
||||
|
||||
static args = [{ name: 'workspacePath' }]
|
||||
|
||||
async run() {
|
||||
const spinner = ora('Reading Files').start();
|
||||
|
||||
const { args, flags } = this.parse(Migrate)
|
||||
|
||||
const { workspacePath = './' } = args;
|
||||
|
||||
if (isValidDirectory(workspacePath)) {
|
||||
let graph = await initializeNoteGraph(workspacePath);
|
||||
|
||||
let notes = graph.getNotes().filter(Boolean); // removes undefined notes
|
||||
|
||||
spinner.succeed();
|
||||
spinner.text = `${notes.length} files found`;
|
||||
spinner.succeed();
|
||||
|
||||
// exit early if no files found.
|
||||
if (notes.length === 0) {
|
||||
this.exit();
|
||||
}
|
||||
|
||||
// Kebab case file names
|
||||
const fileRename = notes.map(note => {
|
||||
const kebabCasedFileName = getKebabCaseFileName(note.title);
|
||||
if (kebabCasedFileName) {
|
||||
return renameFile(note.path, kebabCasedFileName);
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
})
|
||||
|
||||
await Promise.all(fileRename);
|
||||
|
||||
spinner.text = 'Renaming files';
|
||||
|
||||
// Reinitialize the graph after renaming files
|
||||
graph = await initializeNoteGraph(workspacePath);
|
||||
|
||||
notes = graph.getNotes().filter(Boolean); // remove undefined notes
|
||||
|
||||
spinner.succeed();
|
||||
spinner.text = 'Generating link definitions'
|
||||
|
||||
const fileWritePromises = await Promise.all(notes.map(note => {
|
||||
// Get edits
|
||||
const heading = generateHeading(note);
|
||||
const definitions = generateLinkReferences(note, graph);
|
||||
|
||||
|
||||
// apply Edits
|
||||
let file = note.source;
|
||||
file = heading ? applyTextEdit(file, heading) : file;
|
||||
file = definitions ? applyTextEdit(file, definitions) : file;
|
||||
|
||||
|
||||
if (heading || definitions) {
|
||||
return writeFileToDisk(note.path, file);
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
}))
|
||||
|
||||
await Promise.all(fileWritePromises);
|
||||
|
||||
spinner.succeed();
|
||||
spinner.succeed('Done!');
|
||||
}
|
||||
else {
|
||||
spinner.fail('Directory does not exist!');
|
||||
}
|
||||
}
|
||||
}
|
||||
18
packages/foam-cli/src/utils/apply-text-edit.ts
Normal file
18
packages/foam-cli/src/utils/apply-text-edit.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { TextEdit } from 'foam-core';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param text text on which the textEdit will be applied
|
||||
* @param textEdit
|
||||
* @returns {string} text with the applied textEdit
|
||||
*/
|
||||
export const applyTextEdit = (text: string, textEdit: TextEdit): string => {
|
||||
const characters = text.split('');
|
||||
const startOffset = textEdit.range.start.offset || 0;
|
||||
const endOffset = textEdit.range.end.offset || 0;
|
||||
const deleteCount = endOffset - startOffset;
|
||||
|
||||
const textToAppend = `${textEdit.newText}`;
|
||||
characters.splice(startOffset, deleteCount, textToAppend);
|
||||
return characters.join('');
|
||||
}
|
||||
4
packages/foam-cli/src/utils/index.ts
Normal file
4
packages/foam-cli/src/utils/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import * as fs from 'fs';
|
||||
|
||||
|
||||
export const isValidDirectory = (path: string) => fs.existsSync(path) && fs.lstatSync(path).isDirectory();
|
||||
14
packages/foam-cli/src/utils/rename-file.ts
Normal file
14
packages/foam-cli/src/utils/rename-file.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import * as fs from 'fs'
|
||||
|
||||
/**
|
||||
*
|
||||
* @param fileUri absolute path for the file that needs to renamed
|
||||
* @param newFileName "new file name" without the extension
|
||||
*/
|
||||
export const renameFile = async (fileUri: string, newFileName: string) => {
|
||||
const fileName = fileUri.split('/').pop();
|
||||
const extension = fileName?.split('.').pop();
|
||||
const newFileUri = fileUri.replace(`${fileName}`, `${newFileName}.${extension}`);
|
||||
|
||||
return fs.promises.rename(fileUri, newFileUri);
|
||||
}
|
||||
8
packages/foam-cli/src/utils/write-file-to-disk.ts
Normal file
8
packages/foam-cli/src/utils/write-file-to-disk.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import * as fs from 'fs';
|
||||
|
||||
export const writeFileToDisk = async (fileUri: string, data: string): Promise<Boolean> => {
|
||||
return fs.promises.writeFile(fileUri, data).then(() => true).catch(err => {
|
||||
console.log('error while writing to file: ', err)
|
||||
return false;
|
||||
})
|
||||
}
|
||||
82
packages/foam-cli/test/apply-text-edit.test.ts
Normal file
82
packages/foam-cli/test/apply-text-edit.test.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
|
||||
import { applyTextEdit } from '../src/utils/apply-text-edit';
|
||||
|
||||
describe('applyTextEdit', () => {
|
||||
it('should return text with applied TextEdit in the end of the string', () => {
|
||||
const textEdit = {
|
||||
newText: `\n 4. this is fourth line`,
|
||||
range: {
|
||||
start: { line: 3, column: 1, offset: 79 }, end: { line: 3, column: 1, offset: 79 },
|
||||
},
|
||||
};
|
||||
|
||||
const text = `
|
||||
1. this is first line
|
||||
2. this is second line
|
||||
3. this is third line
|
||||
`;
|
||||
|
||||
const expected = `
|
||||
1. this is first line
|
||||
2. this is second line
|
||||
3. this is third line
|
||||
4. this is fourth line
|
||||
`;
|
||||
|
||||
|
||||
const actual = applyTextEdit(text, textEdit);
|
||||
|
||||
expect(actual).toBe(expected)
|
||||
})
|
||||
|
||||
it('should return text with applied TextEdit at the top of the string', () => {
|
||||
const textEdit = {
|
||||
newText: `\n 1. this is first line`,
|
||||
range: {
|
||||
start: { line: 0, column: 0, offset: 0 }, end: { line: 0, column: 0, offset: 0 },
|
||||
},
|
||||
};
|
||||
|
||||
const text = `
|
||||
2. this is second line
|
||||
3. this is third line
|
||||
`;
|
||||
|
||||
const expected = `
|
||||
1. this is first line
|
||||
2. this is second line
|
||||
3. this is third line
|
||||
`;
|
||||
|
||||
|
||||
const actual = applyTextEdit(text, textEdit);
|
||||
|
||||
expect(actual).toBe(expected)
|
||||
});
|
||||
|
||||
it('should return text with applied TextEdit in the middle of the string', () => {
|
||||
const textEdit = {
|
||||
newText: `\n 2. this is the updated second line`,
|
||||
range: {
|
||||
start: { line: 0, column: 0, offset: 26 }, end: { line: 0, column: 0, offset: 53 },
|
||||
},
|
||||
};
|
||||
|
||||
const text = `
|
||||
1. this is first line
|
||||
2. this is second line
|
||||
3. this is third line
|
||||
`;
|
||||
|
||||
const expected = `
|
||||
1. this is first line
|
||||
2. this is the updated second line
|
||||
3. this is third line
|
||||
`;
|
||||
|
||||
|
||||
const actual = applyTextEdit(text, textEdit);
|
||||
|
||||
expect(actual).toBe(expected)
|
||||
});
|
||||
})
|
||||
28
packages/foam-cli/test/rename-file.test.ts
Normal file
28
packages/foam-cli/test/rename-file.test.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
import { renameFile } from '../src/utils/rename-file'
|
||||
import * as fs from 'fs';
|
||||
import mockFS from 'mock-fs';
|
||||
|
||||
const doesFileExist = (path) => fs.promises.access(path).then(() => true).catch(() => false);
|
||||
|
||||
describe('renameFile', () => {
|
||||
|
||||
const fileUri = './test/oldFileName.md';
|
||||
|
||||
beforeAll(() => {
|
||||
mockFS({ [fileUri]: '' })
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
mockFS.restore();
|
||||
});
|
||||
|
||||
it('should rename existing file', async () => {
|
||||
expect(await doesFileExist(fileUri)).toBe(true);
|
||||
|
||||
renameFile(fileUri, 'new-file-name');
|
||||
|
||||
expect(await doesFileExist(fileUri)).toBe(false);
|
||||
expect(await doesFileExist('./test/new-file-name.md')).toBe(true);
|
||||
});
|
||||
});
|
||||
24
packages/foam-cli/test/write-file-to-disk.test.ts
Normal file
24
packages/foam-cli/test/write-file-to-disk.test.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
|
||||
import { writeFileToDisk } from '../src/utils/write-file-to-disk'
|
||||
import * as fs from 'fs';
|
||||
import mockFS from 'mock-fs';
|
||||
|
||||
describe('writeFileToDisk', () => {
|
||||
const fileUri = './test-file.md';
|
||||
|
||||
beforeAll(() => {
|
||||
mockFS({ [fileUri]: 'content in the existing file' });
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
fs.unlinkSync(fileUri);
|
||||
mockFS.restore();
|
||||
})
|
||||
|
||||
it('should overrwrite existing file in the disk with the new data', async () => {
|
||||
const expected = `content in the new file`;
|
||||
await writeFileToDisk(fileUri, expected);
|
||||
const actual = await fs.promises.readFile(fileUri, { encoding: 'utf8' });
|
||||
expect(actual).toBe(expected);
|
||||
});
|
||||
})
|
||||
@@ -10,5 +10,6 @@
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
],
|
||||
"references": [{ "path": "../foam-core" }]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Foam Workspace Manager
|
||||
# Foam Core
|
||||
|
||||
Repository for tooling user for managing Foam workspaces
|
||||
Repository for tooling used by the other modules
|
||||
|
||||
## Local Development
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
{
|
||||
"version": "0.1.1",
|
||||
"name": "foam-core",
|
||||
"author": "Jani Eväkallio",
|
||||
"repository": "https://github.com/foambubble/foam",
|
||||
"version": "0.2.0",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
"dist"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "tsdx watch",
|
||||
"build": "tsdx build",
|
||||
@@ -17,33 +14,27 @@
|
||||
"lint": "tsdx lint",
|
||||
"prepare": "tsdx build"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "tsdx lint"
|
||||
}
|
||||
},
|
||||
"prettier": {
|
||||
"printWidth": 80,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5"
|
||||
},
|
||||
"name": "foam-workspace-manager",
|
||||
"author": "Jani Eväkallio",
|
||||
"repository": "https://github.com/foambubble/foam",
|
||||
"module": "dist/foam-workspace-manager.esm.js",
|
||||
"devDependencies": {
|
||||
"@types/github-slugger": "^1.3.0",
|
||||
"@types/graphlib": "^2.1.6",
|
||||
"@types/lodash": "^4.14.157",
|
||||
"husky": "^4.2.5",
|
||||
"tsdx": "^0.13.2",
|
||||
"tslib": "^2.0.0",
|
||||
"typescript": "^3.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/lodash": "^4.14.157",
|
||||
"lodash": "^4.17.15",
|
||||
"detect-newline": "^3.1.0",
|
||||
"github-slugger": "^1.3.0",
|
||||
"glob": "^7.1.6",
|
||||
"graphlib": "^2.1.8",
|
||||
"lodash": "^4.17.19",
|
||||
"remark-parse": "^8.0.2",
|
||||
"remark-wiki-link": "^0.0.4",
|
||||
"unified": "^9.0.0"
|
||||
}
|
||||
"title-case": "^3.0.2",
|
||||
"unified": "^9.0.0",
|
||||
"unist-util-visit": "^2.0.2"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts"
|
||||
}
|
||||
27
packages/foam-core/src/index.ts
Normal file
27
packages/foam-core/src/index.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { NoteGraph, Note, NoteLink } from './note-graph';
|
||||
|
||||
export {
|
||||
createNoteFromMarkdown,
|
||||
createMarkdownReferences,
|
||||
stringifyMarkdownLinkReferenceDefinition,
|
||||
} from './markdown-provider';
|
||||
|
||||
export { TextEdit, generateHeading, generateLinkReferences, getKebabCaseFileName } from './janitor'
|
||||
|
||||
export { initializeNoteGraph } from './initialize-note-graph'
|
||||
|
||||
export { NoteGraph, Note, NoteLink };
|
||||
|
||||
export interface FoamConfig {
|
||||
// TODO
|
||||
}
|
||||
|
||||
export interface Foam {
|
||||
notes: NoteGraph;
|
||||
// config: FoamConfig
|
||||
}
|
||||
|
||||
export const createFoam = (config: FoamConfig) => ({
|
||||
notes: new NoteGraph(),
|
||||
config: config,
|
||||
});
|
||||
30
packages/foam-core/src/initialize-note-graph.ts
Normal file
30
packages/foam-core/src/initialize-note-graph.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
|
||||
import glob from 'glob';
|
||||
import { promisify } from 'util';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import detectNewline from 'detect-newline';
|
||||
import { NoteGraph } from './note-graph';
|
||||
import { createNoteFromMarkdown } from './markdown-provider';
|
||||
|
||||
const findAllFiles = promisify(glob);
|
||||
|
||||
export const initializeNoteGraph = async (workspacePath: string) => {
|
||||
// remove trailing slash from workspacePath if exists
|
||||
if (workspacePath.substr(-1) == '/') workspacePath = workspacePath.slice(0, -1);
|
||||
|
||||
const files = await findAllFiles(`${workspacePath}/**/*.md`, {});
|
||||
|
||||
const graph = new NoteGraph();
|
||||
await Promise.all(
|
||||
(await files).map(f => {
|
||||
return fs.promises.readFile(f).then(data => {
|
||||
const markdown = (data || '').toString();
|
||||
const eol = detectNewline(markdown) || os.EOL;
|
||||
graph.setNote(createNoteFromMarkdown(f, markdown, eol));
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
return graph;
|
||||
}
|
||||
87
packages/foam-core/src/janitor/index.ts
Normal file
87
packages/foam-core/src/janitor/index.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { Position } from 'unist';
|
||||
import GithubSlugger from 'github-slugger';
|
||||
import { Note, NoteGraph } from '../index';
|
||||
import {
|
||||
createMarkdownReferences,
|
||||
stringifyMarkdownLinkReferenceDefinition,
|
||||
} from '../markdown-provider';
|
||||
import { getHeadingFromFileName } from '../utils'
|
||||
|
||||
const slugger = new GithubSlugger()
|
||||
|
||||
export interface TextEdit {
|
||||
range: Position;
|
||||
newText: string;
|
||||
}
|
||||
|
||||
export const generateLinkReferences = (note: Note, ng: NoteGraph): TextEdit | null => {
|
||||
if (!note) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const newReferences = createMarkdownReferences(ng, note.id).map(
|
||||
stringifyMarkdownLinkReferenceDefinition
|
||||
).join('\n');
|
||||
|
||||
if (note.definitions.length === 0) {
|
||||
if (newReferences.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const padding = note.end.column === 1 ? note.eol : `${note.eol}${note.eol}`;
|
||||
return {
|
||||
newText: `${padding}${newReferences}`,
|
||||
range: {
|
||||
start: note.end,
|
||||
end: note.end,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const first = note.definitions[0];
|
||||
const last = note.definitions[note.definitions.length - 1];
|
||||
|
||||
const oldRefrences = note.definitions.map(stringifyMarkdownLinkReferenceDefinition).join(note.eol);
|
||||
|
||||
if (oldRefrences === newReferences) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
// @todo: do we need to ensure new lines?
|
||||
newText: `${newReferences}`,
|
||||
range: {
|
||||
start: first.position!.start,
|
||||
end: last.position!.end,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const generateHeading = (note: Note): TextEdit | null => {
|
||||
if (!note) {
|
||||
return null;
|
||||
}
|
||||
// Note: This may not work if the heading is same as the file name
|
||||
if (note.title !== note.id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
newText: `# ${getHeadingFromFileName(note.id)}${note.eol}${note.eol}`,
|
||||
range: {
|
||||
start: { line: 0, column: 0, offset: 0 },
|
||||
end: { line: 0, column: 0, offset: 0 }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param fileName
|
||||
* @returns null if file name is already in kebab case otherise returns
|
||||
* the kebab cased file name
|
||||
*/
|
||||
export const getKebabCaseFileName = (fileName: string) => {
|
||||
const kebabCasedFileName = slugger.slug(fileName);
|
||||
return kebabCasedFileName === fileName ? null : kebabCasedFileName;
|
||||
}
|
||||
112
packages/foam-core/src/markdown-provider.ts
Normal file
112
packages/foam-core/src/markdown-provider.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import unified from 'unified';
|
||||
import markdownParse from 'remark-parse';
|
||||
import wikiLinkPlugin from 'remark-wiki-link';
|
||||
import visit, { CONTINUE, EXIT } from 'unist-util-visit';
|
||||
import { Node, Parent } from 'unist';
|
||||
import * as path from 'path';
|
||||
import { Note, NoteLink, NoteLinkDefinition, NoteGraph } from './note-graph';
|
||||
import { dropExtension } from './utils';
|
||||
|
||||
let processor: unified.Processor | null = null;
|
||||
|
||||
function parse(markdown: string): Node {
|
||||
processor =
|
||||
processor ||
|
||||
unified()
|
||||
.use(markdownParse, { gfm: true })
|
||||
.use(wikiLinkPlugin);
|
||||
return processor.parse(markdown);
|
||||
}
|
||||
|
||||
export function createNoteFromMarkdown(uri: string, markdown: string, eol: string): Note {
|
||||
const filename = path.basename(uri);
|
||||
const id = path.parse(filename).name;
|
||||
const tree = parse(markdown);
|
||||
let title = id;
|
||||
visit(tree, node => {
|
||||
if (node.type === 'heading' && node.depth === 1) {
|
||||
title = ((node as Parent)!.children[0].value as string) || title;
|
||||
}
|
||||
return title === id ? CONTINUE : EXIT;
|
||||
});
|
||||
const links: NoteLink[] = [];
|
||||
const definitions: NoteLinkDefinition[] = [];
|
||||
visit(tree, node => {
|
||||
if (node.type === 'wikiLink') {
|
||||
links.push({
|
||||
to: node.value as string,
|
||||
text: node.value as string,
|
||||
position: node.position!,
|
||||
});
|
||||
}
|
||||
|
||||
if (node.type === 'definition') {
|
||||
definitions.push({
|
||||
label: node.label as string,
|
||||
url: node.url as string,
|
||||
title: node.title as string,
|
||||
position: node.position,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const end = tree.position!.end;
|
||||
|
||||
return new Note(id, title, links, definitions, end, uri, markdown, eol);
|
||||
}
|
||||
|
||||
export function stringifyMarkdownLinkReferenceDefinition(
|
||||
definition: NoteLinkDefinition
|
||||
) {
|
||||
let text = `[${definition.label}]: ${definition.url}`;
|
||||
if (definition.title) {
|
||||
text = `${text} "${definition.title}"`;
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
export function createMarkdownReferences(
|
||||
graph: NoteGraph,
|
||||
noteId: string
|
||||
): NoteLinkDefinition[] {
|
||||
const source = graph.getNote(noteId);
|
||||
|
||||
// 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`
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
return graph
|
||||
.getForwardLinks(noteId)
|
||||
.map(link => {
|
||||
const target = graph.getNote(link.to);
|
||||
|
||||
// We are dropping links to non-existent notes here,
|
||||
// but int the future we may want to surface these too
|
||||
if (!target) {
|
||||
console.log(
|
||||
`Warning: Link '${link.to}' in '${noteId}' points to a non-existing note.`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const relativePath = path.relative(
|
||||
path.dirname(source.path),
|
||||
target.path
|
||||
);
|
||||
const relativePathWithoutExtension = dropExtension(relativePath);
|
||||
|
||||
// [wiki-link-text]: wiki-link "Page title"
|
||||
return {
|
||||
label: link.text,
|
||||
url: relativePathWithoutExtension,
|
||||
title: target.title,
|
||||
};
|
||||
})
|
||||
.filter(Boolean)
|
||||
.sort() as NoteLinkDefinition[];
|
||||
}
|
||||
111
packages/foam-core/src/note-graph.ts
Normal file
111
packages/foam-core/src/note-graph.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { Graph, Edge } from 'graphlib';
|
||||
import { Position, Point } from 'unist';
|
||||
import GithubSlugger from 'github-slugger';
|
||||
|
||||
type ID = string;
|
||||
|
||||
export interface Link {
|
||||
from: ID;
|
||||
to: ID;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface NoteLink {
|
||||
to: ID;
|
||||
text: string;
|
||||
position: Position;
|
||||
}
|
||||
|
||||
export interface NoteLinkDefinition {
|
||||
label: string;
|
||||
url: string;
|
||||
title?: string;
|
||||
position?: Position;
|
||||
}
|
||||
|
||||
export class Note {
|
||||
public id: ID;
|
||||
public title: string;
|
||||
public source: string;
|
||||
public path: string;
|
||||
public end: Point;
|
||||
public eol: string;
|
||||
public links: NoteLink[];
|
||||
public definitions: NoteLinkDefinition[];
|
||||
|
||||
constructor(
|
||||
id: ID,
|
||||
title: string,
|
||||
links: NoteLink[],
|
||||
definitions: NoteLinkDefinition[],
|
||||
end: Point,
|
||||
path: string,
|
||||
source: string,
|
||||
eol: string
|
||||
) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.source = source;
|
||||
this.path = path;
|
||||
this.links = links;
|
||||
this.definitions = definitions;
|
||||
this.end = end;
|
||||
this.eol = eol;
|
||||
}
|
||||
}
|
||||
|
||||
export class NoteGraph {
|
||||
private graph: Graph;
|
||||
|
||||
constructor() {
|
||||
this.graph = new Graph();
|
||||
}
|
||||
|
||||
public setNote(note: Note) {
|
||||
if (this.graph.hasNode(note.id)) {
|
||||
(this.graph.outEdges(note.id) || []).forEach(edge => {
|
||||
this.graph.removeEdge(edge);
|
||||
});
|
||||
}
|
||||
this.graph.setNode(note.id, note);
|
||||
note.links.forEach(link => {
|
||||
const slugger = new GithubSlugger();
|
||||
this.graph.setEdge(note.id, slugger.slug(link.to), link.text);
|
||||
});
|
||||
}
|
||||
|
||||
public getNotes(): Note[] {
|
||||
return this.graph.nodes().map(id => this.graph.node(id));
|
||||
}
|
||||
|
||||
public getNote(noteId: ID): Note | void {
|
||||
if (this.graph.hasNode(noteId)) {
|
||||
return this.graph.node(noteId);
|
||||
}
|
||||
throw new Error(`Note with ID [${noteId}] not found`);
|
||||
}
|
||||
|
||||
public getAllLinks(noteId: ID): Link[] {
|
||||
return (this.graph.nodeEdges(noteId) || []).map(edge =>
|
||||
convertEdgeToLink(edge, this.graph)
|
||||
);
|
||||
}
|
||||
|
||||
public getForwardLinks(noteId: ID): Link[] {
|
||||
return (this.graph.outEdges(noteId) || []).map(edge =>
|
||||
convertEdgeToLink(edge, this.graph)
|
||||
);
|
||||
}
|
||||
|
||||
public getBacklinks(noteId: ID): Link[] {
|
||||
return (this.graph.inEdges(noteId) || []).map(edge =>
|
||||
convertEdgeToLink(edge, this.graph)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const convertEdgeToLink = (edge: Edge, graph: Graph): Link => ({
|
||||
from: edge.v,
|
||||
to: edge.w,
|
||||
text: graph.edge(edge.v, edge.w),
|
||||
});
|
||||
16
packages/foam-core/src/utils.ts
Normal file
16
packages/foam-core/src/utils.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { titleCase } from 'title-case';
|
||||
|
||||
export function dropExtension(path: string): string {
|
||||
const parts = path.split('.');
|
||||
parts.pop();
|
||||
return parts.join('.');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param filename
|
||||
* @returns title cased heading after removing special characters
|
||||
*/
|
||||
export const getHeadingFromFileName = (filename: string): string => {
|
||||
return titleCase(filename.replace(/[^\w\s]/gi, ' '));
|
||||
}
|
||||
3
packages/foam-core/test/__migration__/Roam Document.md
Normal file
3
packages/foam-core/test/__migration__/Roam Document.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Roam Document
|
||||
|
||||
[[Second Roam Document]]
|
||||
@@ -0,0 +1 @@
|
||||
# Second Roam Document
|
||||
@@ -0,0 +1 @@
|
||||
This file is missing a title
|
||||
7
packages/foam-core/test/__scaffold__/first-document.md
Normal file
7
packages/foam-core/test/__scaffold__/first-document.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# First Document
|
||||
|
||||
[[file-without-title]]
|
||||
|
||||
[//begin]: # 'Autogenerated link references for markdown compatibility'
|
||||
[second-document]: second-document 'Second Document'
|
||||
[//end]: # 'Autogenerated link references'
|
||||
9
packages/foam-core/test/__scaffold__/index.md
Normal file
9
packages/foam-core/test/__scaffold__/index.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Index
|
||||
|
||||
This file is intentionally missing the link reference definitions
|
||||
|
||||
[[first-document]]
|
||||
|
||||
[[second-document]]
|
||||
|
||||
[[file-without-title]]
|
||||
9
packages/foam-core/test/__scaffold__/second-document.md
Normal file
9
packages/foam-core/test/__scaffold__/second-document.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Second Document
|
||||
|
||||
This is just a link target for now.
|
||||
|
||||
We can use it for other things later if needed.
|
||||
|
||||
[//begin]: # 'Autogenerated link references for markdown compatibility'
|
||||
[first-document]: first-document 'First Document'
|
||||
[//end]: # 'Autogenerated link references'
|
||||
11
packages/foam-core/test/__scaffold__/third-document.md
Normal file
11
packages/foam-core/test/__scaffold__/third-document.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Third Document
|
||||
|
||||
All the link references are correct in this file.
|
||||
|
||||
[[first-document]]
|
||||
|
||||
[[second-document]]
|
||||
|
||||
|
||||
[first-document]: first-document "First Document"
|
||||
[second-document]: second-document "Second Document"
|
||||
189
packages/foam-core/test/core.test.ts
Normal file
189
packages/foam-core/test/core.test.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import { NoteGraph, Note } from '../src/note-graph';
|
||||
|
||||
const position = {
|
||||
start: { line: 0, column: 0 },
|
||||
end: { line: 0, column: 0 },
|
||||
};
|
||||
|
||||
const documentEnd = position.end;
|
||||
const eol = '\n';
|
||||
|
||||
describe('Note graph', () => {
|
||||
it('Adds notes to graph', () => {
|
||||
const graph = new NoteGraph();
|
||||
graph.setNote(
|
||||
new Note('page-a', 'page-a', [], [], documentEnd, eol, '/page-a.md', '')
|
||||
);
|
||||
graph.setNote(
|
||||
new Note('page-b', 'page-b', [], [], documentEnd, eol, '/page-b.md', '')
|
||||
);
|
||||
graph.setNote(
|
||||
new Note('page-c', 'page-c', [], [], documentEnd, eol, '/page-c.md', '')
|
||||
);
|
||||
|
||||
expect(
|
||||
graph
|
||||
.getNotes()
|
||||
.map(n => n.id)
|
||||
.sort()
|
||||
).toEqual(['page-a', 'page-b', 'page-c']);
|
||||
});
|
||||
|
||||
it('Detects forward links', () => {
|
||||
const graph = new NoteGraph();
|
||||
graph.setNote(
|
||||
new Note('page-a', 'page-a', [], [], documentEnd, eol, '/page-a.md', '')
|
||||
);
|
||||
graph.setNote(
|
||||
new Note(
|
||||
'page-b',
|
||||
'page-b',
|
||||
[{ to: 'page-a', text: 'go', position }],
|
||||
[],
|
||||
documentEnd,
|
||||
eol,
|
||||
'/page-b.md',
|
||||
''
|
||||
)
|
||||
);
|
||||
graph.setNote(
|
||||
new Note('page-c', 'page-c', [], [], documentEnd, eol, '/page-c.md', '')
|
||||
);
|
||||
|
||||
expect(
|
||||
graph
|
||||
.getForwardLinks('page-b')
|
||||
.map(link => link.to)
|
||||
.sort()
|
||||
).toEqual(['page-a']);
|
||||
});
|
||||
|
||||
it('Detects backlinks', () => {
|
||||
const graph = new NoteGraph();
|
||||
graph.setNote(
|
||||
new Note('page-a', 'page-a', [], [], documentEnd, eol, '/page-a.md', '')
|
||||
);
|
||||
graph.setNote(
|
||||
new Note(
|
||||
'page-b',
|
||||
'page-b',
|
||||
[{ to: 'page-a', text: 'go', position }],
|
||||
[],
|
||||
documentEnd,
|
||||
eol,
|
||||
'/page-b.md',
|
||||
''
|
||||
)
|
||||
);
|
||||
graph.setNote(
|
||||
new Note('page-c', 'page-c', [], [], documentEnd, eol, '/page-c.md', '')
|
||||
);
|
||||
|
||||
expect(
|
||||
graph
|
||||
.getBacklinks('page-a')
|
||||
.map(link => link.from)
|
||||
.sort()
|
||||
).toEqual(['page-b']);
|
||||
});
|
||||
|
||||
it('Fails when accessing non-existing node', () => {
|
||||
expect(() => {
|
||||
const graph = new NoteGraph();
|
||||
graph.setNote(
|
||||
new Note('page-a', 'page-a', [], [], documentEnd, eol, '/path-b.md', '')
|
||||
);
|
||||
graph.getNote('non-existing');
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('Allows adding edges to non-existing documents', () => {
|
||||
const graph = new NoteGraph();
|
||||
graph.setNote(
|
||||
new Note(
|
||||
'page-a',
|
||||
'page-a',
|
||||
[{ to: 'non-existing', text: 'does not exist', position }],
|
||||
[],
|
||||
documentEnd,
|
||||
eol,
|
||||
'/path-b.md',
|
||||
''
|
||||
)
|
||||
);
|
||||
expect(graph.getNote('non-existing')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('Updates links when modifying note', () => {
|
||||
const graph = new NoteGraph();
|
||||
graph.setNote(
|
||||
new Note('page-a', 'page-a', [], [], documentEnd, eol, '/page-a.md', '')
|
||||
);
|
||||
graph.setNote(
|
||||
new Note(
|
||||
'page-b',
|
||||
'page-b',
|
||||
[{ to: 'page-a', text: 'go', position }],
|
||||
[],
|
||||
documentEnd,
|
||||
eol,
|
||||
'/page-b.md',
|
||||
''
|
||||
)
|
||||
);
|
||||
graph.setNote(
|
||||
new Note('page-c', 'page-c', [], [], documentEnd, eol, '/page-c.md', '')
|
||||
);
|
||||
|
||||
expect(
|
||||
graph
|
||||
.getForwardLinks('page-b')
|
||||
.map(link => link.to)
|
||||
.sort()
|
||||
).toEqual(['page-a']);
|
||||
expect(
|
||||
graph
|
||||
.getBacklinks('page-a')
|
||||
.map(link => link.from)
|
||||
.sort()
|
||||
).toEqual(['page-b']);
|
||||
expect(
|
||||
graph
|
||||
.getBacklinks('page-c')
|
||||
.map(link => link.from)
|
||||
.sort()
|
||||
).toEqual([]);
|
||||
|
||||
graph.setNote(
|
||||
new Note(
|
||||
'page-b',
|
||||
'page-b',
|
||||
[{ to: 'page-c', text: 'go', position }],
|
||||
[],
|
||||
documentEnd,
|
||||
eol,
|
||||
'/path-2b.md',
|
||||
''
|
||||
)
|
||||
);
|
||||
|
||||
expect(
|
||||
graph
|
||||
.getForwardLinks('page-b')
|
||||
.map(link => link.to)
|
||||
.sort()
|
||||
).toEqual(['page-c']);
|
||||
expect(
|
||||
graph
|
||||
.getBacklinks('page-a')
|
||||
.map(link => link.from)
|
||||
.sort()
|
||||
).toEqual([]);
|
||||
expect(
|
||||
graph
|
||||
.getBacklinks('page-c')
|
||||
.map(link => link.from)
|
||||
.sort()
|
||||
).toEqual(['page-b']);
|
||||
});
|
||||
});
|
||||
53
packages/foam-core/test/janitor/generateHeadings.test.ts
Normal file
53
packages/foam-core/test/janitor/generateHeadings.test.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import * as path from 'path';
|
||||
import { NoteGraph, Note } from '../../src/note-graph';
|
||||
import { generateHeading } from '../../src/janitor';
|
||||
import { initializeNoteGraph } from '../../src/initialize-note-graph';
|
||||
|
||||
describe('generateHeadings', () => {
|
||||
let _graph: NoteGraph;
|
||||
|
||||
beforeAll(async () => {
|
||||
_graph = await initializeNoteGraph(path.join(__dirname, '../__scaffold__'));
|
||||
});
|
||||
|
||||
it('should add heading to a file that does not have them', () => {
|
||||
const note = _graph.getNote('file-without-title') as Note;
|
||||
|
||||
const expected = {
|
||||
newText: `# File without Title
|
||||
|
||||
`,
|
||||
range: {
|
||||
start: {
|
||||
line: 0,
|
||||
column: 0,
|
||||
offset: 0,
|
||||
},
|
||||
end: {
|
||||
line: 0,
|
||||
column: 0,
|
||||
offset: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const actual = generateHeading(note!);
|
||||
|
||||
expect(actual!.range.start).toEqual(expected.range.start);
|
||||
expect(actual!.range.end).toEqual(expected.range.end);
|
||||
expect(actual!.newText).toEqual(expected.newText);
|
||||
|
||||
});
|
||||
|
||||
it('should not cause any changes to a file that does heading', () => {
|
||||
const note = _graph.getNote('index') as Note;
|
||||
|
||||
const expected = null;
|
||||
|
||||
const actual = generateHeading(note!);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
|
||||
})
|
||||
|
||||
});
|
||||
106
packages/foam-core/test/janitor/generateLinkReferences.test.ts
Normal file
106
packages/foam-core/test/janitor/generateLinkReferences.test.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import * as path from 'path';
|
||||
import { NoteGraph, Note } from '../../src/note-graph';
|
||||
import { generateLinkReferences } from '../../src/janitor';
|
||||
import { initializeNoteGraph } from '../../src/initialize-note-graph';
|
||||
|
||||
describe('generateLinkReferences', () => {
|
||||
let _graph: NoteGraph;
|
||||
|
||||
beforeAll(async () => {
|
||||
_graph = await initializeNoteGraph(path.join(__dirname, '../__scaffold__'));
|
||||
});
|
||||
|
||||
it('initialised test graph correctly', () => {
|
||||
expect(_graph.getNotes().length).toEqual(5);
|
||||
});
|
||||
|
||||
it('should add link references to a file that does not have them', () => {
|
||||
const note = _graph.getNote('index') as Note;
|
||||
const expected = {
|
||||
newText: `
|
||||
[first-document]: first-document "First Document"
|
||||
[second-document]: second-document "Second Document"
|
||||
[file-without-title]: file-without-title "file-without-title"`,
|
||||
range: {
|
||||
start: {
|
||||
line: 10,
|
||||
column: 1,
|
||||
offset: 140,
|
||||
},
|
||||
end: {
|
||||
line: 10,
|
||||
column: 1,
|
||||
offset: 140,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const actual = generateLinkReferences(note!, _graph);
|
||||
|
||||
expect(actual!.range.start).toEqual(expected.range.start);
|
||||
expect(actual!.range.end).toEqual(expected.range.end);
|
||||
expect(actual!.newText).toEqual(expected.newText);
|
||||
});
|
||||
|
||||
it('should remove link definitions from a file that has them, if no links are present', () => {
|
||||
const note = _graph.getNote('second-document') as Note;
|
||||
|
||||
const expected = {
|
||||
newText: "",
|
||||
range: {
|
||||
start: {
|
||||
line: 7,
|
||||
column: 1,
|
||||
offset: 105,
|
||||
},
|
||||
end: {
|
||||
line: 9,
|
||||
column: 43,
|
||||
offset: 269,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const actual = generateLinkReferences(note!, _graph);
|
||||
|
||||
expect(actual!.range.start).toEqual(expected.range.start);
|
||||
expect(actual!.range.end).toEqual(expected.range.end);
|
||||
expect(actual!.newText).toEqual(expected.newText);
|
||||
});
|
||||
|
||||
it('should update link definitions if they are present but changed', () => {
|
||||
const note = _graph.getNote('first-document') as Note;
|
||||
|
||||
const expected = {
|
||||
newText: `[file-without-title]: file-without-title "file-without-title"`,
|
||||
range: {
|
||||
start: {
|
||||
line: 5,
|
||||
column: 1,
|
||||
offset: 42,
|
||||
},
|
||||
end: {
|
||||
line: 7,
|
||||
column: 43,
|
||||
offset: 209,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const actual = generateLinkReferences(note!, _graph);
|
||||
|
||||
expect(actual!.range.start).toEqual(expected.range.start);
|
||||
expect(actual!.range.end).toEqual(expected.range.end);
|
||||
expect(actual!.newText).toEqual(expected.newText);
|
||||
});
|
||||
|
||||
it('should not cause any changes if link reference definitions were up to date', () => {
|
||||
const note = _graph.getNote('third-document') as Note;
|
||||
|
||||
const expected = null;
|
||||
|
||||
const actual = generateLinkReferences(note!, _graph);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
51
packages/foam-core/test/markdown-provider.test.ts
Normal file
51
packages/foam-core/test/markdown-provider.test.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { createNoteFromMarkdown } from '../src/markdown-provider';
|
||||
import { NoteGraph } from '../src/note-graph';
|
||||
|
||||
const pageA = `
|
||||
# Page A
|
||||
|
||||
## Section
|
||||
- [[page-b]]
|
||||
- [[page-c]]
|
||||
`;
|
||||
|
||||
const pageB = `
|
||||
# Page B
|
||||
|
||||
This references [[page-a]]`;
|
||||
|
||||
const pageC = `
|
||||
# Page C
|
||||
`;
|
||||
|
||||
// @todo: Add tests for definitions
|
||||
describe('Markdown loader', () => {
|
||||
it('Converts markdown to notes', () => {
|
||||
const graph = new NoteGraph();
|
||||
graph.setNote(createNoteFromMarkdown('page-a', pageA, '\n'));
|
||||
graph.setNote(createNoteFromMarkdown('page-b', pageB, '\n'));
|
||||
graph.setNote(createNoteFromMarkdown('page-c', pageC, '\n'));
|
||||
|
||||
expect(
|
||||
graph
|
||||
.getNotes()
|
||||
.map(n => n.id)
|
||||
.sort()
|
||||
).toEqual(['page-a', 'page-b', 'page-c']);
|
||||
});
|
||||
|
||||
it('Parses wikilinks correctly', () => {
|
||||
const graph = new NoteGraph();
|
||||
graph.setNote(createNoteFromMarkdown('page-a', pageA, '\n'));
|
||||
graph.setNote(createNoteFromMarkdown('page-b', pageB, '\n'));
|
||||
graph.setNote(createNoteFromMarkdown('page-c', pageC, '\n'));
|
||||
|
||||
expect(graph.getBacklinks('page-b').map(link => link.from)).toEqual([
|
||||
'page-a',
|
||||
]);
|
||||
expect(graph.getForwardLinks('page-a').map(link => link.to)).toEqual([
|
||||
'page-b',
|
||||
'page-c',
|
||||
]);
|
||||
});
|
||||
});
|
||||
34
packages/foam-core/tsconfig.json
Normal file
34
packages/foam-core/tsconfig.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": [
|
||||
"src",
|
||||
"types"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
// to override config from tsconfig.base.json
|
||||
"outDir": "dist",
|
||||
"rootDir": "./src",
|
||||
// for references
|
||||
"baseUrl": "src",
|
||||
"lib": [
|
||||
"esnext"
|
||||
],
|
||||
"module": "esnext",
|
||||
"importHelpers": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
// "noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true
|
||||
// "paths": {
|
||||
// "*": ["src/*", "node_modules/*"]
|
||||
// },
|
||||
// "jsx": "react",
|
||||
},
|
||||
}
|
||||
1
packages/foam-core/types/utils.d.ts
vendored
Normal file
1
packages/foam-core/types/utils.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
declare module 'remark-wiki-link';
|
||||
@@ -1077,6 +1077,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.44.tgz#980cc5a29a3ef3bea6ff1f7d021047d7ea575e21"
|
||||
integrity sha512-iaIVzr+w2ZJ5HkidlZ3EJM8VTZb2MJLCjw3V+505yVts0gRC4UMvjw0d1HPtGqI/HQC/KdsYtayfzl+AXY2R8g==
|
||||
|
||||
"@types/graphlib@^2.1.6":
|
||||
version "2.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/graphlib/-/graphlib-2.1.6.tgz#5c7b515bfadc08d737f2e84fadbd151117c73207"
|
||||
integrity sha512-os2Xj+pV/iwLkLX17LWuXdPooA4Jf4xg8WSdKPUi0tCSseP95oikcA1irOgVl3K2QYnoXrjJT3qVZeQ1uskB7g==
|
||||
|
||||
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
|
||||
@@ -2843,6 +2848,13 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
|
||||
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
|
||||
|
||||
graphlib@^2.1.8:
|
||||
version "2.1.8"
|
||||
resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da"
|
||||
integrity sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==
|
||||
dependencies:
|
||||
lodash "^4.17.15"
|
||||
|
||||
growly@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
|
||||
@@ -3986,10 +3998,10 @@ lodash.sortby@^4.7.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
|
||||
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
|
||||
|
||||
lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4:
|
||||
version "4.17.15"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
||||
lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.4:
|
||||
version "4.17.19"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
|
||||
integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
|
||||
|
||||
log-symbols@^2.2.0:
|
||||
version "2.2.0"
|
||||
@@ -5975,6 +5987,15 @@ unist-util-visit@^2.0.0:
|
||||
unist-util-is "^4.0.0"
|
||||
unist-util-visit-parents "^3.0.0"
|
||||
|
||||
unist-util-visit@^2.0.2:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c"
|
||||
integrity sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==
|
||||
dependencies:
|
||||
"@types/unist" "^2.0.0"
|
||||
unist-util-is "^4.0.0"
|
||||
unist-util-visit-parents "^3.0.0"
|
||||
|
||||
universalify@^0.1.0:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||
1
packages/foam-vscode/.gitignore
vendored
1
packages/foam-vscode/.gitignore
vendored
@@ -1,4 +1,3 @@
|
||||
out
|
||||
node_modules
|
||||
.vscode-test/
|
||||
*.vsix
|
||||
|
||||
@@ -4,6 +4,22 @@ 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.2.0] - 2020-07-12
|
||||
|
||||
Improvements:
|
||||
|
||||
- Order link references alphabetically to cause smaller diffs
|
||||
- Remove link references when links are removed
|
||||
- Documentation improvements
|
||||
|
||||
Underneath, everything has changed:
|
||||
|
||||
- Published from [Foam monorepo](https://github.com/foambubble/foam)
|
||||
- Rewrote markdown parsing to use unifiedjs AST
|
||||
- Rewrote workspace index to user graphlib graph data structures
|
||||
|
||||
These changes will enable to make more robust and ambitious releases more frequently 🎉
|
||||
|
||||
## [0.1.7] - 2020-07-04
|
||||
|
||||
- Support paths to files in subdirectories
|
||||
@@ -19,7 +35,7 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
|
||||
## [0.1.4] - 2020-06-25
|
||||
|
||||
- Fix flaky reference block replacement logic that would occasionally leave
|
||||
trailing fragments in the end of the document ([#3](https://github.com/foambubble/foam-vscode/issues/3))
|
||||
trailing fragments in the end of the document ([#3](https://github.com/foambubble/foam-vscode/issues/3))
|
||||
|
||||
## 0.1.3 - 2020-06-25
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# foam-vscode
|
||||
|
||||
[](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode)
|
||||
[](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode)
|
||||
[](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode)
|
||||
[](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode)
|
||||
|
||||
|
||||
**foam-vscode** is the VS Code extension for [Foam](https://foambubble.github.io/foam).
|
||||
|
||||
> ℹ️ foam-vscode 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.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "Generate markdown reference lists from wikilinks in a workspace",
|
||||
"author": "Jani Eväkallio",
|
||||
"repository": "https://github.com/foambubble/foam",
|
||||
"version": "0.1.7",
|
||||
"version": "0.2.0",
|
||||
"license": "MIT",
|
||||
"publisher": "foam",
|
||||
"engines": {
|
||||
@@ -15,7 +15,8 @@
|
||||
],
|
||||
"activationEvents": [
|
||||
"workspaceContains:.vscode/foam.json",
|
||||
"onCommand:foam-vscode.update-wikilinks"
|
||||
"onCommand:foam-vscode.update-wikilinks",
|
||||
"onCommand:foam-vscode.open-daily-note"
|
||||
],
|
||||
"main": "./out/extension.js",
|
||||
"contributes": {
|
||||
@@ -23,6 +24,49 @@
|
||||
{
|
||||
"command": "foam-vscode.update-wikilinks",
|
||||
"title": "Foam: Update Markdown Reference List"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.open-daily-note",
|
||||
"title": "Foam: Open Daily Note"
|
||||
}
|
||||
],
|
||||
"configuration": {
|
||||
"title": "Foam",
|
||||
"properties": {
|
||||
"foam.openDailyNote.fileExtension": {
|
||||
"type": "string",
|
||||
"scope": "resource",
|
||||
"default": "md"
|
||||
},
|
||||
"foam.openDailyNote.filenameFormat": {
|
||||
"type": "string",
|
||||
"default": "isoDate",
|
||||
"markdownDescription": "Specifies how the daily note filename is formatted. See the [dateformat docs](https://www.npmjs.com/package/dateformat) for valid formats",
|
||||
"scope": "resource"
|
||||
},
|
||||
"foam.openDailyNote.titleFormat": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"default": null,
|
||||
"markdownDescription": "Specifies how the daily note title is formatted. Will default to the filename format if set to null. See the [dateformat docs](https://www.npmjs.com/package/dateformat) for valid formats",
|
||||
"scope": "resource"
|
||||
},
|
||||
"foam.openDailyNote.directory": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"default": null,
|
||||
"description": "The directory into which daily notes should be created. Defaults to the workspace root."
|
||||
}
|
||||
}
|
||||
},
|
||||
"keybindings": [
|
||||
{
|
||||
"command": "foam-vscode.open-daily-note",
|
||||
"key": "alt+d"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -37,6 +81,7 @@
|
||||
"publish-extension": "npx vsce publish && yarn npm-cleanup"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/dateformat": "^3.0.1",
|
||||
"@types/glob": "^7.1.1",
|
||||
"@types/node": "^13.11.0",
|
||||
"@types/vscode": "^1.45.1",
|
||||
@@ -49,6 +94,7 @@
|
||||
"vscode-test": "^1.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"foam-workspace-manager": "^0.1.1"
|
||||
"dateformat": "^3.0.3",
|
||||
"foam-core": "^0.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,246 +4,36 @@
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
import {
|
||||
CancellationToken,
|
||||
CodeLens,
|
||||
CodeLensProvider,
|
||||
commands,
|
||||
EndOfLine,
|
||||
ExtensionContext,
|
||||
languages,
|
||||
Range,
|
||||
TextEditor,
|
||||
TextDocument,
|
||||
TextDocumentWillSaveEvent,
|
||||
window,
|
||||
workspace,
|
||||
Position,
|
||||
} from "vscode";
|
||||
import * as fs from "fs";
|
||||
import { workspace, ExtensionContext, window, EndOfLine } from "vscode";
|
||||
|
||||
import { basename, dirname, relative } from "path";
|
||||
import * as ws from "./workspace";
|
||||
|
||||
/**
|
||||
* Workspace config
|
||||
*/
|
||||
const docConfig = { tab: " ", eol: "\r\n" };
|
||||
const mdDocSelector = [
|
||||
{ language: "markdown", scheme: "file" },
|
||||
{ language: "markdown", scheme: "untitled" },
|
||||
];
|
||||
|
||||
function loadDocConfig() {
|
||||
// Load workspace config
|
||||
let activeEditor = window.activeTextEditor;
|
||||
if (!activeEditor) {
|
||||
console.log("Failed to load config, no active editor");
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
} else {
|
||||
docConfig.tab = "\t";
|
||||
}
|
||||
}
|
||||
|
||||
const REFERENCE_HEADER = `[//begin]: # "Autogenerated link references for markdown compatibility"`;
|
||||
const REFERENCE_FOOTER = `[//end]: # "Autogenerated link references"`;
|
||||
import { createNoteFromMarkdown, createFoam, FoamConfig } from "foam-core";
|
||||
import { features } from "./features";
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand(
|
||||
"foam-vscode.update-wikilinks",
|
||||
updateReferenceList
|
||||
),
|
||||
workspace.onWillSaveTextDocument(onWillSave),
|
||||
languages.registerCodeLensProvider(
|
||||
mdDocSelector,
|
||||
new WikilinkReferenceCodeLensProvider()
|
||||
)
|
||||
const foamPromise = bootstrap(getConfig());
|
||||
features.forEach(f => {
|
||||
f.activate(context, foamPromise);
|
||||
});
|
||||
}
|
||||
|
||||
const bootstrap = async (config: FoamConfig) => {
|
||||
const files = await workspace.findFiles("**/*");
|
||||
const foam = createFoam(config);
|
||||
await Promise.all(
|
||||
files
|
||||
.filter(f => f.scheme === "file" && f.path.match(/\.(md|mdx|markdown)/i))
|
||||
.map(f => {
|
||||
return fs.promises.readFile(f.fsPath).then(data => {
|
||||
const markdown = (data || "").toString();
|
||||
const eol = window.activeTextEditor?.document?.eol === EndOfLine.CRLF ? "\r\n" : "\n";
|
||||
foam.notes.setNote(createNoteFromMarkdown(f.fsPath, markdown, eol));
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
return foam;
|
||||
};
|
||||
|
||||
async function createReferenceList() {
|
||||
let editor = window.activeTextEditor;
|
||||
if (!editor || !isMdEditor(editor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let refs = await generateReferenceList(editor.document);
|
||||
if (refs && refs.length) {
|
||||
await editor.edit(function (editBuilder) {
|
||||
if (editor) {
|
||||
const spacing = hasEmptyTrailing
|
||||
? docConfig.eol
|
||||
: docConfig.eol + docConfig.eol;
|
||||
|
||||
editBuilder.insert(
|
||||
new Position(editor.document.lineCount, 0),
|
||||
spacing + refs.join(docConfig.eol)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function updateReferenceList() {
|
||||
const editor = window.activeTextEditor;
|
||||
|
||||
if (!editor || !isMdEditor(editor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loadDocConfig();
|
||||
|
||||
const doc = editor.document;
|
||||
const range = detectReferenceListRange(doc);
|
||||
|
||||
if (!range) {
|
||||
await createReferenceList();
|
||||
} else {
|
||||
const refs = await generateReferenceList(doc);
|
||||
|
||||
// references must always be preceded by an empty line
|
||||
const spacing = doc.lineAt(range.start.line - 1).isEmptyOrWhitespace
|
||||
? ""
|
||||
: docConfig.eol;
|
||||
|
||||
await editor.edit((editBuilder) => {
|
||||
editBuilder.replace(range, spacing + refs.join(docConfig.eol));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function generateReferenceList(doc: TextDocument): Promise<string[]> {
|
||||
const filePath = doc.fileName;
|
||||
|
||||
const id = dropExtension(basename(filePath));
|
||||
|
||||
// @todo fix hack
|
||||
await ws.ready;
|
||||
|
||||
// update file in index for future reference
|
||||
// @todo should probably be an update method instead
|
||||
// so we can prune existing references
|
||||
ws.manager.addNoteFromMarkdown(filePath, doc.getText());
|
||||
|
||||
// find note by id
|
||||
const note = ws.manager.getNoteWithLinks(id);
|
||||
|
||||
if (note.linkedNotes.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const references = [];
|
||||
|
||||
for (const link of note.linkedNotes) {
|
||||
const relativePath = relative(dirname(filePath), link.absolutePath);
|
||||
if (relativePath) {
|
||||
const relativePathWithoutExtension = dropExtension(relativePath);
|
||||
|
||||
// [wiki-link-text]: wiki-link "Page title"
|
||||
references.push(
|
||||
`[${link.id}]: ${relativePathWithoutExtension} "${link.title}"`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
references.sort()
|
||||
|
||||
// for (const backlink of note.backlinks) {
|
||||
// references.push(
|
||||
// `[backlink:${backlink.id}]: ${backlink.filename} "${backlink.title}"`
|
||||
// );
|
||||
// }
|
||||
|
||||
return [REFERENCE_HEADER, ...references, REFERENCE_FOOTER];
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the range of existing reference list
|
||||
* @param doc
|
||||
*/
|
||||
function detectReferenceListRange(doc: TextDocument): Range {
|
||||
const fullText = doc.getText();
|
||||
|
||||
const headerIndex = fullText.indexOf(REFERENCE_HEADER);
|
||||
const footerIndex = fullText.lastIndexOf(REFERENCE_FOOTER);
|
||||
|
||||
if (headerIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const headerLine =
|
||||
fullText.substring(0, headerIndex).split(docConfig.eol).length - 1;
|
||||
|
||||
const footerLine =
|
||||
fullText.substring(0, footerIndex).split(docConfig.eol).length - 1;
|
||||
|
||||
if (headerLine >= footerLine) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Range(
|
||||
new Position(headerLine, 0),
|
||||
new Position(footerLine, REFERENCE_FOOTER.length)
|
||||
);
|
||||
}
|
||||
|
||||
function onWillSave(e: TextDocumentWillSaveEvent) {
|
||||
if (e.document.languageId === "markdown") {
|
||||
e.waitUntil(updateReferenceList());
|
||||
}
|
||||
}
|
||||
|
||||
function hasEmptyTrailing(doc: TextDocument): boolean {
|
||||
return doc.lineAt(doc.lineCount - 1).isEmptyOrWhitespace;
|
||||
}
|
||||
|
||||
function getText(range: Range): string {
|
||||
return window.activeTextEditor.document.getText(range);
|
||||
}
|
||||
|
||||
function isMdEditor(editor: TextEditor) {
|
||||
return editor && editor.document && editor.document.languageId === "markdown";
|
||||
}
|
||||
|
||||
function dropExtension(path: string): string {
|
||||
const parts = path.split(".");
|
||||
parts.pop();
|
||||
return parts.join(".");
|
||||
}
|
||||
|
||||
class WikilinkReferenceCodeLensProvider implements CodeLensProvider {
|
||||
public provideCodeLenses(
|
||||
document: TextDocument,
|
||||
_: CancellationToken
|
||||
): CodeLens[] | Thenable<CodeLens[]> {
|
||||
loadDocConfig();
|
||||
|
||||
let range = detectReferenceListRange(document);
|
||||
if (!range) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return generateReferenceList(document).then((refs) => {
|
||||
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";
|
||||
|
||||
return [
|
||||
new CodeLens(range, {
|
||||
arguments: [],
|
||||
title: `Link references (${status})`,
|
||||
command: "",
|
||||
}),
|
||||
];
|
||||
});
|
||||
}
|
||||
}
|
||||
const getConfig = () => {
|
||||
return {};
|
||||
};
|
||||
|
||||
5
packages/foam-vscode/src/features/index.ts
Normal file
5
packages/foam-vscode/src/features/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import createReferences from "./wikilink-reference-generation";
|
||||
import openDailyNote from "./open-daily-note";
|
||||
import { FoamFeature } from "../types";
|
||||
|
||||
export const features: FoamFeature[] = [createReferences, openDailyNote];
|
||||
96
packages/foam-vscode/src/features/open-daily-note.ts
Normal file
96
packages/foam-vscode/src/features/open-daily-note.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import {
|
||||
window,
|
||||
workspace,
|
||||
Uri,
|
||||
WorkspaceConfiguration,
|
||||
ExtensionContext,
|
||||
commands,
|
||||
} from "vscode";
|
||||
import { dirname, join } from "path";
|
||||
import dateFormat = require("dateformat");
|
||||
import fs = require("fs");
|
||||
import { FoamFeature } from "../types";
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: async (context: ExtensionContext) => {
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand("foam-vscode.open-daily-note", openDailyNote)
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
async function openDailyNote() {
|
||||
const foamConfiguration = workspace.getConfiguration("foam");
|
||||
const currentDate = new Date();
|
||||
|
||||
const dailyNotePath = getDailyNotePath(foamConfiguration, currentDate);
|
||||
|
||||
createDailyNoteIfNotExists(foamConfiguration, dailyNotePath, currentDate);
|
||||
await focusDailyNote(dailyNotePath);
|
||||
}
|
||||
|
||||
function getDailyNotePath(configuration: WorkspaceConfiguration, date: Date) {
|
||||
const rootDirectory = workspace.workspaceFolders[0].uri.fsPath;
|
||||
const dailyNoteDirectory: string =
|
||||
configuration.get("openDailyNote.directory") ?? ".";
|
||||
const dailyNoteFilename = getDailyNoteFileName(configuration, date);
|
||||
|
||||
return join(rootDirectory, dailyNoteDirectory, dailyNoteFilename);
|
||||
}
|
||||
|
||||
function getDailyNoteFileName(
|
||||
configuration: WorkspaceConfiguration,
|
||||
date: Date
|
||||
): string {
|
||||
const filenameFormat: string = configuration.get(
|
||||
"openDailyNote.filenameFormat"
|
||||
);
|
||||
const fileExtension: string = configuration.get(
|
||||
"openDailyNote.fileExtension"
|
||||
);
|
||||
|
||||
return `${dateFormat(date, filenameFormat, false)}.${fileExtension}`;
|
||||
}
|
||||
|
||||
async function createDailyNoteIfNotExists(
|
||||
configuration: WorkspaceConfiguration,
|
||||
dailyNotePath: string,
|
||||
currentDate: Date
|
||||
) {
|
||||
if (await pathExists(dailyNotePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
createDailyNoteDirectoryIfNotExists(dailyNotePath);
|
||||
|
||||
const titleFormat: string =
|
||||
configuration.get("openDailyNote.titleFormat") ??
|
||||
configuration.get("openDailyNote.filenameFormat");
|
||||
|
||||
await fs.promises.writeFile(
|
||||
dailyNotePath,
|
||||
`# ${dateFormat(currentDate, titleFormat, false)}\r\n`
|
||||
);
|
||||
}
|
||||
|
||||
async function createDailyNoteDirectoryIfNotExists(dailyNotePath: string) {
|
||||
const dailyNoteDirectory = dirname(dailyNotePath);
|
||||
|
||||
if (!(await pathExists(dailyNoteDirectory))) {
|
||||
await fs.promises.mkdir(dailyNoteDirectory, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function focusDailyNote(dailyNotePath: string) {
|
||||
const document = await workspace.openTextDocument(Uri.parse(dailyNotePath));
|
||||
window.showTextDocument(document);
|
||||
}
|
||||
|
||||
async function pathExists(path: string) {
|
||||
return fs.promises
|
||||
.access(path, fs.constants.F_OK)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
}
|
||||
|
||||
export default feature;
|
||||
@@ -0,0 +1,198 @@
|
||||
import { uniq } from "lodash";
|
||||
import {
|
||||
CancellationToken,
|
||||
CodeLens,
|
||||
CodeLensProvider,
|
||||
commands,
|
||||
ExtensionContext,
|
||||
languages,
|
||||
Range,
|
||||
TextDocument,
|
||||
window,
|
||||
workspace,
|
||||
Position
|
||||
} from "vscode";
|
||||
|
||||
import {
|
||||
createMarkdownReferences,
|
||||
stringifyMarkdownLinkReferenceDefinition,
|
||||
createNoteFromMarkdown,
|
||||
NoteGraph,
|
||||
Foam
|
||||
} from "foam-core";
|
||||
import { basename } from "path";
|
||||
import {
|
||||
hasEmptyTrailing,
|
||||
docConfig,
|
||||
loadDocConfig,
|
||||
isMdEditor,
|
||||
mdDocSelector,
|
||||
getText,
|
||||
dropExtension
|
||||
} from "../utils";
|
||||
import { FoamFeature } from "../types";
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: async (context: ExtensionContext, foamPromise: Promise<Foam>) => {
|
||||
const foam = await foamPromise;
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand("foam-vscode.update-wikilinks", () =>
|
||||
updateReferenceList(foam.notes)
|
||||
),
|
||||
workspace.onWillSaveTextDocument(e => {
|
||||
if (e.document.languageId === "markdown") {
|
||||
foam.notes.setNote(
|
||||
createNoteFromMarkdown(e.document.fileName, e.document.getText(), docConfig.eol)
|
||||
);
|
||||
e.waitUntil(updateReferenceList(foam.notes));
|
||||
}
|
||||
}),
|
||||
languages.registerCodeLensProvider(
|
||||
mdDocSelector,
|
||||
new WikilinkReferenceCodeLensProvider(foam.notes)
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const REFERENCE_HEADER = `[//begin]: # "Autogenerated link references for markdown compatibility"`;
|
||||
const REFERENCE_FOOTER = `[//end]: # "Autogenerated link references"`;
|
||||
|
||||
async function createReferenceList(foam: NoteGraph) {
|
||||
let editor = window.activeTextEditor;
|
||||
if (!editor || !isMdEditor(editor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let refs = await generateReferenceList(foam, editor.document);
|
||||
if (refs && refs.length) {
|
||||
await editor.edit(function (editBuilder) {
|
||||
if (editor) {
|
||||
const spacing = hasEmptyTrailing
|
||||
? docConfig.eol
|
||||
: docConfig.eol + docConfig.eol;
|
||||
|
||||
editBuilder.insert(
|
||||
new Position(editor.document.lineCount, 0),
|
||||
spacing + refs.join(docConfig.eol)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function updateReferenceList(foam: NoteGraph) {
|
||||
const editor = window.activeTextEditor;
|
||||
|
||||
if (!editor || !isMdEditor(editor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loadDocConfig();
|
||||
|
||||
const doc = editor.document;
|
||||
const range = detectReferenceListRange(doc);
|
||||
|
||||
if (!range) {
|
||||
await createReferenceList(foam);
|
||||
} else {
|
||||
const refs = await generateReferenceList(foam, doc);
|
||||
|
||||
// references must always be preceded by an empty line
|
||||
const spacing = doc.lineAt(range.start.line - 1).isEmptyOrWhitespace
|
||||
? ""
|
||||
: docConfig.eol;
|
||||
|
||||
await editor.edit(editBuilder => {
|
||||
editBuilder.replace(range, spacing + refs.join(docConfig.eol));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function generateReferenceList(
|
||||
foam: NoteGraph,
|
||||
doc: TextDocument
|
||||
): Promise<string[]> {
|
||||
const filePath = doc.fileName;
|
||||
|
||||
const id = dropExtension(basename(filePath));
|
||||
|
||||
const references = uniq(
|
||||
createMarkdownReferences(foam, id).map(
|
||||
stringifyMarkdownLinkReferenceDefinition
|
||||
)
|
||||
);
|
||||
|
||||
if (references.length) {
|
||||
return [REFERENCE_HEADER, ...references, REFERENCE_FOOTER];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the range of existing reference list
|
||||
* @param doc
|
||||
*/
|
||||
function detectReferenceListRange(doc: TextDocument): Range {
|
||||
const fullText = doc.getText();
|
||||
|
||||
const headerIndex = fullText.indexOf(REFERENCE_HEADER);
|
||||
const footerIndex = fullText.lastIndexOf(REFERENCE_FOOTER);
|
||||
|
||||
if (headerIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const headerLine =
|
||||
fullText.substring(0, headerIndex).split(docConfig.eol).length - 1;
|
||||
|
||||
const footerLine =
|
||||
fullText.substring(0, footerIndex).split(docConfig.eol).length - 1;
|
||||
|
||||
if (headerLine >= footerLine) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Range(
|
||||
new Position(headerLine, 0),
|
||||
new Position(footerLine, REFERENCE_FOOTER.length)
|
||||
);
|
||||
}
|
||||
|
||||
class WikilinkReferenceCodeLensProvider implements CodeLensProvider {
|
||||
private foam: NoteGraph;
|
||||
|
||||
constructor(foam: NoteGraph) {
|
||||
this.foam = foam;
|
||||
}
|
||||
|
||||
public provideCodeLenses(
|
||||
document: TextDocument,
|
||||
_: CancellationToken
|
||||
): CodeLens[] | Thenable<CodeLens[]> {
|
||||
loadDocConfig();
|
||||
|
||||
let range = detectReferenceListRange(document);
|
||||
if (!range) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return generateReferenceList(this.foam, document).then(refs => {
|
||||
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";
|
||||
|
||||
return [
|
||||
new CodeLens(range, {
|
||||
arguments: [],
|
||||
title: `Link references (${status})`,
|
||||
command: ""
|
||||
})
|
||||
];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default feature;
|
||||
6
packages/foam-vscode/src/types.d.ts
vendored
Normal file
6
packages/foam-vscode/src/types.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
import { ExtensionContext } from "vscode";
|
||||
import { Foam } from "foam-core";
|
||||
|
||||
export interface FoamFeature {
|
||||
activate: (context: ExtensionContext, foamPromise: Promise<Foam>) => void
|
||||
}
|
||||
71
packages/foam-vscode/src/utils.ts
Normal file
71
packages/foam-vscode/src/utils.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { EndOfLine, Range, TextDocument, window, Position, TextEditor } from "vscode";
|
||||
|
||||
export const docConfig = { tab: " ", eol: "\r\n" };
|
||||
|
||||
export const mdDocSelector = [
|
||||
{ language: "markdown", scheme: "file" },
|
||||
{ language: "markdown", scheme: "untitled" }
|
||||
];
|
||||
|
||||
export function loadDocConfig() {
|
||||
// Load workspace config
|
||||
let activeEditor = window.activeTextEditor;
|
||||
if (!activeEditor) {
|
||||
console.log("Failed to load config, no active editor");
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
} else {
|
||||
docConfig.tab = "\t";
|
||||
}
|
||||
}
|
||||
|
||||
export function isMdEditor(editor: TextEditor) {
|
||||
return editor && editor.document && editor.document.languageId === "markdown";
|
||||
}
|
||||
|
||||
export function detectGeneratedCode(
|
||||
fullText: string,
|
||||
header: string,
|
||||
footer: string
|
||||
): { range: Range | null; lines: string[] } {
|
||||
const lines = fullText.split(docConfig.eol);
|
||||
|
||||
const headerLine = lines.findIndex(line => line === header);
|
||||
const footerLine = lines.findIndex(line => line === footer);
|
||||
|
||||
if (headerLine < 0 || headerLine >= footerLine) {
|
||||
return {
|
||||
range: null,
|
||||
lines: []
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
range: new Range(
|
||||
new Position(headerLine, 0),
|
||||
new Position(footerLine, lines[footerLine].length + 1)
|
||||
),
|
||||
lines: lines.slice(headerLine + 1, footerLine + 1)
|
||||
};
|
||||
}
|
||||
|
||||
export function hasEmptyTrailing(doc: TextDocument): boolean {
|
||||
return doc.lineAt(doc.lineCount - 1).isEmptyOrWhitespace;
|
||||
}
|
||||
|
||||
export function getText(range: Range): string {
|
||||
return window.activeTextEditor.document.getText(range);
|
||||
}
|
||||
|
||||
export function dropExtension(path: string): string {
|
||||
const parts = path.split(".");
|
||||
parts.pop();
|
||||
return parts.join(".");
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import * as fs from "fs";
|
||||
import { basename } from "path";
|
||||
import { workspace } from "vscode";
|
||||
import { WorkspaceManager } from "foam-workspace-manager";
|
||||
|
||||
// build initial index
|
||||
export const manager = new WorkspaceManager(workspace.rootPath);
|
||||
export const ready = (async () => {
|
||||
const files = await workspace.findFiles("**/*");
|
||||
await Promise.all(
|
||||
files
|
||||
.filter(
|
||||
(f) => f.scheme === "file" && f.path.match(/\.(md|mdx|markdown)/i)
|
||||
)
|
||||
.map((f) => {
|
||||
return fs.promises.readFile(f.fsPath).then((data) => {
|
||||
let markdown = (data || "").toString();
|
||||
manager.addNoteFromMarkdown(f.fsPath, markdown);
|
||||
});
|
||||
})
|
||||
);
|
||||
})();
|
||||
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"target": "es6",
|
||||
"outDir": "out",
|
||||
"lib": ["es6"],
|
||||
@@ -12,5 +14,9 @@
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
},
|
||||
"exclude": ["node_modules", ".vscode-test"]
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", ".vscode-test"],
|
||||
"references": [{
|
||||
"path": "../foam-core/tsconfig.json"
|
||||
}]
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
name: CI
|
||||
on: [push]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Begin CI...
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node 12
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
|
||||
- name: Use cached node_modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: node_modules
|
||||
key: nodeModules-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
nodeModules-
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Lint
|
||||
run: yarn lint
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Test
|
||||
run: yarn test --ci --coverage --maxWorkers=2
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Build
|
||||
run: yarn build
|
||||
env:
|
||||
CI: true
|
||||
4
packages/foam-workspace-manager/.gitignore
vendored
4
packages/foam-workspace-manager/.gitignore
vendored
@@ -1,4 +0,0 @@
|
||||
*.log
|
||||
.DS_Store
|
||||
node_modules
|
||||
dist
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Jani Eväkallio
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,179 +0,0 @@
|
||||
// for readability
|
||||
import { basename } from 'path';
|
||||
import {
|
||||
readWorkspaceFile,
|
||||
parseNoteTitleFromMarkdown,
|
||||
parseNoteLinksFromMarkdown,
|
||||
} from './utils/utils';
|
||||
|
||||
type ID = string;
|
||||
type Index = Map<ID, Set<ID>>;
|
||||
|
||||
export interface Note {
|
||||
/**
|
||||
* Base name of the file without extension, e.g. wiki-link
|
||||
*/
|
||||
id: ID;
|
||||
title: string;
|
||||
filename: string;
|
||||
extension: string;
|
||||
absolutePath: string;
|
||||
markdown: string; // do we need this?
|
||||
}
|
||||
|
||||
export interface NoteWithLinks extends Note {
|
||||
/**
|
||||
* Notes referenced from this note (wikilinks)
|
||||
*/
|
||||
linkedNotes: Note[];
|
||||
|
||||
/**
|
||||
* Notes that reference this note (backlinks) */
|
||||
backlinks: Note[];
|
||||
}
|
||||
|
||||
function getOrInitializeIndexForId(index: Index, id: ID): Set<ID> {
|
||||
let links: Set<ID>;
|
||||
if (index.has(id)) {
|
||||
links = index.get(id)!;
|
||||
} else {
|
||||
index.set(id, (links = new Set<ID>()));
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
export class WorkspaceManager {
|
||||
/**
|
||||
* Workspace base path
|
||||
*/
|
||||
path: string;
|
||||
/**
|
||||
* Note metadata for files in this workspace, keyed by id
|
||||
*/
|
||||
notes: Map<ID, Note> = new Map();
|
||||
|
||||
/**
|
||||
* Link index A->B
|
||||
*/
|
||||
linksFromNoteById: Index = new Map();
|
||||
|
||||
/**
|
||||
* Reverse backlinks B->A
|
||||
*/
|
||||
linksBackToNoteById: Index = new Map();
|
||||
|
||||
constructor(path: string, notes: Note[] = []) {
|
||||
this.path = path;
|
||||
this.notes = new Map<ID, Note>(notes.map(note => [note.id, note]));
|
||||
}
|
||||
|
||||
public getNoteWithLinks(id: ID): NoteWithLinks | null {
|
||||
const note = this.notes.get(id);
|
||||
if (!note) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const linkedNotes = Array.from(
|
||||
getOrInitializeIndexForId(this.linksFromNoteById, id)
|
||||
)
|
||||
.map(id => this.notes.get(id))
|
||||
.filter(Boolean) as Note[];
|
||||
|
||||
const backlinks = Array.from(
|
||||
getOrInitializeIndexForId(this.linksBackToNoteById, id)
|
||||
)
|
||||
.map(id => this.notes.get(id))
|
||||
.filter(Boolean) as Note[];
|
||||
|
||||
return {
|
||||
...note,
|
||||
linkedNotes,
|
||||
backlinks,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param filename File name relative to workspace path
|
||||
*/
|
||||
public async addNoteByFilePath(filePath: string): Promise<Note> {
|
||||
return await this.addNoteFromMarkdown(
|
||||
this.path,
|
||||
await readWorkspaceFile(filePath)
|
||||
);
|
||||
}
|
||||
|
||||
public addNoteFromMarkdown(absolutePath: string, markdown: string): Note {
|
||||
// parse markdown
|
||||
const filename = basename(absolutePath);
|
||||
const parts = filename.split('.');
|
||||
const extension = parts.pop()!;
|
||||
const id = parts.join('.');
|
||||
const title = parseNoteTitleFromMarkdown(markdown);
|
||||
const note: Note = {
|
||||
id,
|
||||
title: title || id,
|
||||
filename,
|
||||
absolutePath,
|
||||
extension,
|
||||
markdown,
|
||||
};
|
||||
|
||||
// extract linksTo
|
||||
return this.addNote(note);
|
||||
}
|
||||
|
||||
public addNote(note: Note): Note {
|
||||
const linkIds = parseNoteLinksFromMarkdown(note.markdown);
|
||||
|
||||
this.notes.set(note.id, note);
|
||||
|
||||
if (linkIds.length > 0) {
|
||||
let linksFromNote = getOrInitializeIndexForId(
|
||||
this.linksFromNoteById,
|
||||
note.id
|
||||
);
|
||||
|
||||
for (const id of linkIds) {
|
||||
linksFromNote.add(id);
|
||||
getOrInitializeIndexForId(this.linksBackToNoteById, id).add(note.id);
|
||||
}
|
||||
}
|
||||
|
||||
return note;
|
||||
}
|
||||
|
||||
/*
|
||||
// Clearly I'm too tired to do this right now
|
||||
public removeNote(a: ID): Note | null {
|
||||
let note = this.notes.get(a);
|
||||
if (!note) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// find references from this note to others
|
||||
let linksFromNote = getOrInitializeIndexForId(this.linksFromNoteById, a);
|
||||
|
||||
// remove the index
|
||||
this.linksFromNoteById.delete(a);
|
||||
|
||||
// find all notes that reference the note we are deleting
|
||||
for (const b in linksFromNote) {
|
||||
const backlinks = getOrInitializeIndexForId(this.linksBackToNoteById, b);
|
||||
if (backlinks.has(a)) {
|
||||
// @todo, trigger event?
|
||||
backlinks.delete(a);
|
||||
}
|
||||
}
|
||||
|
||||
return note;
|
||||
}
|
||||
|
||||
|
||||
// @ts-expect-error
|
||||
public renameNote(note: Note, newFilename: string) {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { WorkspaceManager, Note, NoteWithLinks } from './WorkspaceManager';
|
||||
@@ -1,63 +0,0 @@
|
||||
/**
|
||||
* Adapted from vscode-markdown/src/util.ts
|
||||
* https://github.com/yzhang-gh/vscode-markdown/blob/master/src/util.ts
|
||||
*/
|
||||
|
||||
export const REGEX_FENCED_CODE_BLOCK = /^( {0,3}|\t)```[^`\r\n]*$[\w\W]+?^( {0,3}|\t)``` *$/gm;
|
||||
|
||||
export function markdownHeadingToPlainText(text: string) {
|
||||
// Remove Markdown syntax (bold, italic, links etc.) in a heading
|
||||
// For example: `_italic_` -> `italic`
|
||||
return text.replace(/\[([^\]]*)\]\[[^\]]*\]/, (_, g1) => g1);
|
||||
}
|
||||
|
||||
export function rxWikiLink(): RegExp {
|
||||
const pattern = '\\[\\[([^\\]]+)\\]\\]'; // [[wiki-link-regex]]
|
||||
return new RegExp(pattern, 'ig');
|
||||
}
|
||||
|
||||
export function rxMarkdownHeading(level: number): RegExp {
|
||||
const pattern = `^#{${level}}\\s+(.+)$`;
|
||||
return new RegExp(pattern, 'im');
|
||||
}
|
||||
|
||||
export const mdDocSelector = [
|
||||
{ language: 'markdown', scheme: 'file' },
|
||||
{ language: 'markdown', scheme: 'untitled' },
|
||||
];
|
||||
|
||||
export function findTopLevelHeading(md: string): string | null {
|
||||
const regex = rxMarkdownHeading(1);
|
||||
const match = regex.exec(md);
|
||||
if (match) {
|
||||
return markdownHeadingToPlainText(match[1]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function cleanupMarkdown(markdown: string) {
|
||||
const replacer = (foundStr: string) => foundStr.replace(/[^\r\n]/g, '');
|
||||
return markdown
|
||||
.replace(REGEX_FENCED_CODE_BLOCK, replacer) //// Remove fenced code blocks (and #603, #675)
|
||||
.replace(/<!-- omit in (toc|TOC) -->/g, '< omit in toc >') //// Escape magic comment
|
||||
.replace(/<!--[\W\w]+?-->/g, replacer) //// Remove comments
|
||||
.replace(/^---[\W\w]+?(\r?\n)---/, replacer); //// Remove YAML front matter
|
||||
}
|
||||
|
||||
export function findWikilinksInMarkdown(markdown: string): string[] {
|
||||
const md = cleanupMarkdown(markdown);
|
||||
const regex = rxWikiLink();
|
||||
const unique = new Set<string>();
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(md))) {
|
||||
// can be file-name or file.name.ext
|
||||
const [, name] = match;
|
||||
if (name) {
|
||||
unique.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(unique);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// @todo convert this to use ast parsing
|
||||
|
||||
// import unified from 'unified';
|
||||
// import markdown from 'remark-parse';
|
||||
// import wikiLinkPlugin from 'remark-wiki-link';
|
||||
// let processor = unified()
|
||||
// .use(markdown, { gfm: true })
|
||||
// .use(wikiLinkPlugin);
|
||||
|
||||
import { findTopLevelHeading, findWikilinksInMarkdown } from './markdown-utils';
|
||||
|
||||
// @ts-expect-error
|
||||
export function readWorkspaceFile(filename: string): string {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
export function parseNoteTitleFromMarkdown(markdown: string): string | null {
|
||||
return findTopLevelHeading(markdown);
|
||||
}
|
||||
|
||||
export function parseNoteLinksFromMarkdown(markdown: string): string[] {
|
||||
return findWikilinksInMarkdown(markdown);
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
import { WorkspaceManager } from '../src/WorkspaceManager';
|
||||
|
||||
const pageA = `
|
||||
# Page A
|
||||
|
||||
## Section
|
||||
- [[page-b]]
|
||||
- [[page-c]];
|
||||
`;
|
||||
|
||||
const pageB = `
|
||||
# Page B
|
||||
|
||||
This references [[page-a]]`;
|
||||
|
||||
const pageC = `
|
||||
# Page C
|
||||
`;
|
||||
|
||||
describe('WorkspaceManager', () => {
|
||||
it('links things correctly when added in order', () => {
|
||||
const ws = new WorkspaceManager('dir/');
|
||||
|
||||
ws.addNoteFromMarkdown('page-a.md', pageA);
|
||||
ws.addNoteFromMarkdown('page-b.md', pageB);
|
||||
ws.addNoteFromMarkdown('page-c.md', pageC);
|
||||
|
||||
const note = ws.getNoteWithLinks('page-a');
|
||||
expect(note).not.toBeNull();
|
||||
expect(note!.linkedNotes.map(n => n.id)).toEqual(['page-b', 'page-c']);
|
||||
expect(note!.backlinks.map(n => n.id)).toEqual(['page-b']);
|
||||
});
|
||||
|
||||
it('links things correctly when added out of order', () => {
|
||||
const ws = new WorkspaceManager('dir/');
|
||||
|
||||
ws.addNoteFromMarkdown('page-b.md', pageB);
|
||||
ws.addNoteFromMarkdown('page-a.md', pageA);
|
||||
ws.addNoteFromMarkdown('page-c.md', pageC);
|
||||
|
||||
const note = ws.getNoteWithLinks('page-a');
|
||||
|
||||
expect(note).not.toBeNull();
|
||||
expect(note!.linkedNotes.map(n => n.id)).toEqual(['page-b', 'page-c']);
|
||||
expect(note!.backlinks.map(n => n.id)).toEqual(['page-b']);
|
||||
});
|
||||
|
||||
it('updates links when adding a changed document', () => {
|
||||
const ws = new WorkspaceManager('dir/');
|
||||
|
||||
ws.addNoteFromMarkdown('page-b.md', pageB);
|
||||
ws.addNoteFromMarkdown('page-a.md', pageA);
|
||||
ws.addNoteFromMarkdown('page-c.md', pageC);
|
||||
|
||||
const before = ws.getNoteWithLinks('page-a');
|
||||
|
||||
// change document
|
||||
ws.addNoteFromMarkdown(
|
||||
'page-c.md',
|
||||
`
|
||||
# Page C
|
||||
[[page-a]]
|
||||
[[page-b]]
|
||||
`
|
||||
);
|
||||
|
||||
const after = ws.getNoteWithLinks('page-a');
|
||||
|
||||
expect(before).not.toEqual(after);
|
||||
expect(before!.linkedNotes.map(n => n.id)).toEqual(['page-b', 'page-c']);
|
||||
expect(before!.backlinks.map(n => n.id)).toEqual(['page-b']);
|
||||
|
||||
expect(after!.linkedNotes.map(n => n.id)).toEqual(['page-b', 'page-c']);
|
||||
expect(after!.backlinks.map(n => n.id)).toEqual(['page-b', 'page-c']);
|
||||
});
|
||||
|
||||
/*
|
||||
it('updates links correctly when page is removed', () => {
|
||||
const ws = new WorkspaceManager('dir/');
|
||||
|
||||
ws.addNoteFromMarkdown('page-a.md', pageA);
|
||||
ws.addNoteFromMarkdown('page-b.md', pageB);
|
||||
ws.addNoteFromMarkdown('page-c.md', pageC);
|
||||
|
||||
|
||||
ws.removeNote('page-c');
|
||||
|
||||
const note = ws.getNoteWithLinks('page-a');
|
||||
|
||||
console.log(note);
|
||||
expect(note).not.toBeNull();
|
||||
expect(note!.linkedNotes.map(n => n.id)).toEqual(['page-b']);
|
||||
expect(note!.backlinks.map(n => n.id)).toEqual(['page-b']);
|
||||
});
|
||||
*/
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"include": ["src", "types"],
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"lib": ["dom", "esnext"],
|
||||
"importHelpers": true,
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"moduleResolution": "node",
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"*": ["src/*", "node_modules/*"]
|
||||
},
|
||||
"jsx": "react",
|
||||
"esModuleInterop": true
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
declare module 'remark-wiki-link';
|
||||
63
readme.md
63
readme.md
@@ -1,13 +1,15 @@
|
||||
<p class="github-only">
|
||||
👋 <b>Hello friend! Looks like you're reading this page on GitHub. Please go to the 👉<a href="https://foambubble.github.io/foam">rendered Foam Workspace</a> for an improved experience! </b>
|
||||
</p>
|
||||
👋 **Hello friend! Looks like you're reading this page on GitHub. Please go to the 👉[rendered Foam Workspace](https://foambubble.github.io/foam) for an improved experience!**
|
||||
|
||||
|
||||
👀*This is an early stage project under rapid development. For updates follow [@jevakallio](https://twitter.com/jevakallio) on Twitter, or join the [Foam community Discord](https://discord.gg/rtdZKgj)! 💬*
|
||||
|
||||
<p class="github-only">
|
||||
👀<i>This is an early stage project under rapid development. For updates follow <a href="https://twitter.com/jevakallio" target="_blank">@jevakallio</a> on Twitter, or join the <a href="https://discord.gg/rtdZKgj" target="_blank">Foam community Discord</a>! 💬</i>
|
||||
</p>
|
||||
|
||||
# Foam
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
[](#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/).
|
||||
|
||||
You can use **Foam** for organising your research, keeping re-discoverable notes, writing long-form content and, optionally, publishing it to the web.
|
||||
@@ -46,3 +48,52 @@ Foam is licensed under the [MIT license](license).
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[wiki-links]: wiki-links "Wiki Links"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
## Contributors ✨
|
||||
|
||||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||
<!-- prettier-ignore-start -->
|
||||
<!-- 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>
|
||||
</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>
|
||||
</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></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>
|
||||
</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></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>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-enable -->
|
||||
<!-- 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!
|
||||
13
tsconfig.base.json
Normal file
13
tsconfig.base.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"include": ["./packages/*/src"],
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"foam-core": ["./packages/foam-core/src"],
|
||||
"foam-cli": ["./packages/foam-cli/src"],
|
||||
"foam-vscode": ["./packages/foam-vscode/src"]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user