mirror of
https://github.com/foambubble/foam.git
synced 2026-01-11 15:08:01 -05:00
Compare commits
41 Commits
build-and-
...
spike/lsp-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2f46af479 | ||
|
|
b0051b4da3 | ||
|
|
b2449df390 | ||
|
|
12b98e633d | ||
|
|
35d361e119 | ||
|
|
9becf8e88c | ||
|
|
a65549b38a | ||
|
|
54c2635aa6 | ||
|
|
7f9783a7ea | ||
|
|
8b8d6fcc57 | ||
|
|
88d1856026 | ||
|
|
d58e343fa2 | ||
|
|
bc405dc515 | ||
|
|
3fb3bb04b3 | ||
|
|
604564a643 | ||
|
|
0e5ce819b7 | ||
|
|
3b45e0e92a | ||
|
|
ab6d6ed569 | ||
|
|
3e084fd944 | ||
|
|
b4e9c76a4b | ||
|
|
76188bde79 | ||
|
|
346cc46f77 | ||
|
|
f63e78026f | ||
|
|
19140488ae | ||
|
|
3297d8bd67 | ||
|
|
5fabe08afa | ||
|
|
87f172d217 | ||
|
|
3bebbcc45a | ||
|
|
b4224659b7 | ||
|
|
7f3ba7853e | ||
|
|
bcab41917a | ||
|
|
f32b432e26 | ||
|
|
5383865d88 | ||
|
|
c2c639b402 | ||
|
|
f4a864ee7d | ||
|
|
bf2aa599ef | ||
|
|
24738e4390 | ||
|
|
0e0355ae9f | ||
|
|
49ddc41d41 | ||
|
|
5226917e35 | ||
|
|
ba6448ee89 |
@@ -175,7 +175,8 @@
|
||||
"profile": "https://anku.netlify.com/",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"test"
|
||||
"test",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -315,6 +316,96 @@
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "vitaly-pevgonen",
|
||||
"name": "vitaly-pevgonen",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/6272738?v=4",
|
||||
"profile": "https://github.com/vitaly-pevgonen",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "dshemetov",
|
||||
"name": "Dmitry Shemetov",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/1810426?v=4",
|
||||
"profile": "https://dshemetov.github.io",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "hooncp",
|
||||
"name": "hooncp",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/48883554?v=4",
|
||||
"profile": "https://github.com/hooncp",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "martinlaws",
|
||||
"name": "Martin Laws",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/13721239?v=4",
|
||||
"profile": "http://rt-canada.ca",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sksmith",
|
||||
"name": "Sean K Smith",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/2085441?v=4",
|
||||
"profile": "http://seanksmith.me",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "kneely",
|
||||
"name": "Kevin Neely",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/37545028?v=4",
|
||||
"profile": "https://www.linkedin.com/in/kevin-neely/",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ariefrahmansyah",
|
||||
"name": "Arief Rahmansyah",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/8122852?v=4",
|
||||
"profile": "https://ariefrahmansyah.dev",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "vHanda",
|
||||
"name": "Vishesh Handa",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/426467?v=4",
|
||||
"profile": "http://vhanda.in",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "HeroicHitesh",
|
||||
"name": "Hitesh Kumar",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/37622734?v=4",
|
||||
"profile": "http://www.linkedin.com/in/heroichitesh",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "spencerwooo",
|
||||
"name": "Spencer Woo",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/32114380?v=4",
|
||||
"profile": "https://spencerwoo.com",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
4
.github/workflows/foam-vscode.yml
vendored
4
.github/workflows/foam-vscode.yml
vendored
@@ -20,8 +20,8 @@ jobs:
|
||||
|
||||
- name: Lint foam-vscode
|
||||
run: yarn workspace foam-vscode lint
|
||||
# - name: Test foam-vscode
|
||||
# run: yarn workspace foam-vscode test
|
||||
- name: Test foam-vscode
|
||||
run: yarn workspace foam-vscode test
|
||||
# - name: Publish foam-vscode
|
||||
# if: github.ref == 'refs/heads/master'
|
||||
# run: yarn workspace foam-vscode publish-extension
|
||||
|
||||
68
.vscode/launch.json
vendored
68
.vscode/launch.json
vendored
@@ -6,10 +6,34 @@
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
// This task is also defined in packages/foam-vscode/.vscode/launch.json
|
||||
// for when running separately outside of the monorepo environment
|
||||
"name": "Run Extension",
|
||||
"type": "extensionHost",
|
||||
"type": "node",
|
||||
"name": "vscode-jest-tests",
|
||||
"request": "launch",
|
||||
"runtimeArgs": ["workspace", "foam-core", "run", "test"], // ${yarnWorkspaceName} is what we're missing
|
||||
"args": [
|
||||
"--runInBand"
|
||||
],
|
||||
"runtimeExecutable": "yarn",
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
"disableOptimisticBPs": true,
|
||||
},
|
||||
{
|
||||
"name": "Debug Jest Tests",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"runtimeArgs": [
|
||||
"--inspect-brk",
|
||||
"${workspaceRoot}/node_modules/.bin/tsdx",
|
||||
"test",
|
||||
],
|
||||
"console": "integratedTerminal",
|
||||
"cwd": "${workspaceFolder}/packages/foam-core",
|
||||
"internalConsoleOptions": "neverOpen"
|
||||
},
|
||||
{
|
||||
"name": "Run VSCode Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
@@ -18,12 +42,14 @@
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/packages/foam-vscode/out/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "Build foam-vscode"
|
||||
"preLaunchTask": "${defaultBuildTask}"
|
||||
},
|
||||
|
||||
// @NOTE: This task is broken. VSCode e2e tests are currently disabled
|
||||
// due to incompability of jest and mocha inside a typescript monorepo
|
||||
// Contributions to fix this are welcome!
|
||||
{
|
||||
// This task is also defined in packages/foam-vscode/.vscode/launch.json
|
||||
// for when running separately outside of the monorepo environment
|
||||
"name": "Extension Tests",
|
||||
"name": "Test VSCode Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
@@ -34,16 +60,36 @@
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/packages/foam-vscode/out/test/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "Build foam-vscode"
|
||||
"preLaunchTask": "${defaultBuildTask}"
|
||||
},
|
||||
{
|
||||
"name": "Test Core",
|
||||
"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"
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"preLaunchTask": "${defaultBuildTask}"
|
||||
},
|
||||
{
|
||||
"name": "Debug Language Server",
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"port": 6009,
|
||||
// large timeout to account for typescript watch startup time
|
||||
"timeout": 30000,
|
||||
"restart": true,
|
||||
"trace": true,
|
||||
"localRoot": "${workspaceFolder}/packages/foam-language-server/src/",
|
||||
"remoteRoot": "${workspaceFolder}/packages/foam-language-server/dist/",
|
||||
"outFiles": ["${workspaceFolder}/packages/foam-language-server/dist/**/*.js"]
|
||||
},
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Run Extension & Debug LSP",
|
||||
"configurations": ["Run VSCode Extension", "Debug Language Server"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
29
.vscode/tasks.json
vendored
29
.vscode/tasks.json
vendored
@@ -4,19 +4,28 @@
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
// This task is also defined in packages/foam-vscode/.vscode/tasks.json
|
||||
// for when running separately outside of the monorepo environment
|
||||
"type": "npm",
|
||||
"script": "watch",
|
||||
"label": "Build foam-vscode",
|
||||
"path": "packages/foam-vscode",
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"label": "watch: foam-vscode",
|
||||
"type": "npm",
|
||||
"script": "start:vscode",
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"isBackground": true,
|
||||
"presentation": {
|
||||
"reveal": "silent",
|
||||
"revealProblems": "onProblem",
|
||||
"focus": true
|
||||
"reveal": "always"
|
||||
},
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "test: all packages",
|
||||
"type": "npm",
|
||||
"script": "test",
|
||||
"problemMatcher": [],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -19,10 +19,27 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
document
|
||||
.querySelectorAll(".markdown-body a[title]:not([href^=http])")
|
||||
.forEach((a) => {
|
||||
// Hack: Replace page-link with "Page Title"...
|
||||
a.innerText = a.title;
|
||||
// ...and normalize the links to allow html pages navigation
|
||||
a.href = normalizeMdLink(a.href);
|
||||
// filter to only wiki-links
|
||||
var prev = a.previousSibling;
|
||||
var next = a.nextSibling;
|
||||
if (
|
||||
prev instanceof Text && prev.textContent.endsWith('[') &&
|
||||
next instanceof Text && next.textContent.startsWith(']')
|
||||
) {
|
||||
|
||||
// remove surrounding brackets
|
||||
prev.textContent = prev.textContent.slice(0, -1);
|
||||
next.textContent = next.textContent.slice(1);
|
||||
|
||||
// add CSS list for styling
|
||||
a.classList.add('wikilink');
|
||||
|
||||
// replace page-link with "Page Title"...
|
||||
a.innerText = a.title;
|
||||
|
||||
// ...and normalize the links to allow html pages navigation
|
||||
a.href = normalizeMdLink(a.href);
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll(".github-only").forEach((el) => {
|
||||
|
||||
@@ -52,6 +52,16 @@ blockquote {
|
||||
"DejaVu Serif", "Bitstream Vera Serif", "Liberation Serif", Georgia, serif;
|
||||
}
|
||||
|
||||
.wikilink:before {
|
||||
content: "[[";
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.wikilink:after {
|
||||
content: "]]";
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.github-only {
|
||||
display: none;
|
||||
}
|
||||
|
||||
BIN
docs/assets/images/custom-snippet.gif
Normal file
BIN
docs/assets/images/custom-snippet.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 521 KiB |
68
docs/assets/images/diagram-drawio-demo.drawio.svg
Normal file
68
docs/assets/images/diagram-drawio-demo.drawio.svg
Normal file
@@ -0,0 +1,68 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="281px" height="181px" viewBox="-0.5 -0.5 281 181" content="<mxfile host="dd26700e-36c4-4a92-a6df-21c7be0d8350" modified="2020-08-31T10:44:14.509Z" agent="5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.48.2 Chrome/78.0.3904.130 Electron/7.3.2 Safari/537.36" etag="ygaBEVmOlivkHZFzXaxy" version="13.1.3"><diagram id="6hGFLwfOUW9BJ-s0fimq" name="Page-1">1ZVNT4NAEIZ/DVdT2NrWq7XqQU89aI+bMsKahSHboYC/3qXMAhvUmJi08QTzzMcy72yGQKyz+sHIIn3GGHQQzeI6EHdBFIXLcGUfLWmYhMvrjiRGxcwGsFUfwHDGtFQxHLxAQtSkCh/uMc9hTx6TxmDlh72h9k8tZAITsN1LPaUvKqa0o6toOfBHUEnqTg4XN50nky6YOzmkMsZqhMQmEGuDSN1bVq9Bt+o5Xbq8+2+8/YcZyOk3Caz7gRrXG8S2VTbRUIoJ5lJvBnprsMxjaAvMrDXEPCEWFoYWvgNRw3OTJaFFKWWavVArem3Tr+z5nbkbue5qLn0yGmfkZJou69qZu7FvSDtZLq9rsO3qW4kYHbA0e46K+FZJkwBHiX489mIDZmBPsSEGtCR19KtLvmBJHzfMwL7wGL4eyeKyI1n+o5HMzzQSPvoodekXHQ3JH0GVKoJtIU8fX9k16MvN9cAQ1D9rMO2OE6I5rxC3RN1KqYaN1LN0tI0Ws78LIiaCRBcXJFz4gvQCnUOQ+UQQcXFBRHQ+Qaw5/LROvtG/X2w+AQ==</diagram></mxfile>">
|
||||
<defs/>
|
||||
<g>
|
||||
<path d="M 110 60 L 110 90 L 60 90 L 60 113.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 60 118.88 L 56.5 111.88 L 60 113.63 L 63.5 111.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<path d="M 170 60 L 170 90 L 220 90 L 220 113.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 220 118.88 L 216.5 111.88 L 220 113.63 L 223.5 111.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<rect x="80" y="0" width="120" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 30px; margin-left: 81px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||
1
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="140" y="34" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
1
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="0" y="120" width="120" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 150px; margin-left: 1px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||
2
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="60" y="154" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
2
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="160" y="120" width="120" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 150px; margin-left: 161px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||
3
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="220" y="154" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
3
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
</g>
|
||||
<switch>
|
||||
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
|
||||
<a transform="translate(0,-5)" xlink:href="https://desk.draw.io/support/solutions/articles/16000042487" target="_blank">
|
||||
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
|
||||
Viewer does not support full SVG 1.1
|
||||
</text>
|
||||
</a>
|
||||
</switch>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.7 KiB |
BIN
docs/assets/images/markdown-snippets.gif
Normal file
BIN
docs/assets/images/markdown-snippets.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 407 KiB |
BIN
docs/assets/images/migrating-one-note.png
Normal file
BIN
docs/assets/images/migrating-one-note.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 104 KiB |
BIN
docs/assets/images/snippets.gif
Normal file
BIN
docs/assets/images/snippets.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 389 KiB |
BIN
docs/assets/images/vercel-detect-preset.png
Normal file
BIN
docs/assets/images/vercel-detect-preset.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
@@ -9,7 +9,7 @@ The following recipe is written with the assumption that you already have an [Az
|
||||
## Setup a Foam workspace
|
||||
|
||||
1. Generate a Foam workspace using the [foam-template project](https://github.com/foambubble/foam-template).
|
||||
2. Change the remote to a git repository in Azure DevOps, or copy all the files into a new Azure DevOps git repository.
|
||||
2. Change the remote to a git repository in Azure DevOps (Repos -> Import a Repository -> Add Clone URL with Authentication), or copy all the files into a new Azure DevOps git repository.
|
||||
3. Define which document will be the wiki home page. To do that, create a file called `.order` in the Foam workspace root folder, with first line being the document filename without `.md` extension. For a project created from the Foam template, the file would look like this:
|
||||
```
|
||||
readme
|
||||
@@ -31,4 +31,33 @@ There is default table of contents pane to the left of the wiki content. Here, y
|
||||
|
||||
_Note that first entry in `.order` file defines wiki's home page._
|
||||
|
||||
## Update wiki
|
||||
|
||||
While you are pushing changes to GitHub, you won't see the wiki updated if you don't add Azure as a remote. You can push to multiple repositories simultaneously.
|
||||
|
||||
1. First open a terminal and check if Azure is added running: `git remote show origin`. If you don't see Azure add it in the output then follow these steps.
|
||||
2. Rename your current remote (most likely named origin) to a different name by running: `git remote rename origin main`
|
||||
3. You can then add the remote for your second remote repository, in this case, Azure. e.g `git remote add azure https://<YOUR_ID>@dev.azure.com/<YOUR_ID>/foam-notes/_git/foam-notes`. You can get it from: Repos->Files->Clone and copy the URL.
|
||||
4. Now, you need to set up your origin remote to push to both of these. So run: `git config -e` and edit it.
|
||||
5. Add the `remote origin` section to the bottom of the file with the URLs from each remote repository you'd like to push to. You'll see something like that:
|
||||
```bash
|
||||
[core]
|
||||
...
|
||||
(ignore this part)
|
||||
...
|
||||
[branch "master"]
|
||||
remote = github
|
||||
merge = refs/heads/master
|
||||
[remote "github"]
|
||||
url = git@github.com:username/repo.git
|
||||
fetch = +refs/heads/*:refs/remotes/github/*
|
||||
[remote "azure"]
|
||||
url = https://<YOUR_ID>@dev.azure.com/<YOUR_ID>/foam-notes/_git/foam-notes
|
||||
fetch = +refs/heads/*:refs/remotes/azure/*
|
||||
[remote "origin"]
|
||||
url = git@github.com:username/repo.git
|
||||
url = https://<YOUR_ID>@dev.azure.com/<YOUR_ID>/foam-notes/_git/foam-notes
|
||||
```
|
||||
6. You can then push to both repositories by: `git push origin master` or a single one using: `git push github master` or `git push azure master`
|
||||
|
||||
For more information, read the [Azure DevOps documentation](https://docs.microsoft.com/en-us/azure/devops/project/wiki/publish-repo-to-wiki).
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# Creating New Notes
|
||||
|
||||
- Write out a new `[[wiki-link]]` and `Cmd` + `Click` to create a new file.
|
||||
- `Cmd` + `Shift` + `P` (`Ctrl` + `Shift` + `P` for Windows), execute `New Note` from [VS Code Markdown Notes](<(https://marketplace.visualstudio.com/items?itemName=kortina.vscode-markdown-notes)>) and enter a **Title Case Name** to create `title-case-name.md`
|
||||
- Write out a new `[[wiki-link]]` and `Cmd` + `Click` to create a new file and enter it.
|
||||
- For keyboard navigation, use the 'Follow Definition' key `F12` (or [remap key binding](https://code.visualstudio.com/docs/getstarted/keybindings) to something more ergonomic)
|
||||
- `Cmd` + `Shift` + `P` (`Ctrl` + `Shift` + `P` for Windows), execute `New Note` from [VS Code Markdown Notes](https://marketplace.visualstudio.com/items?itemName=kortina.vscode-markdown-notes) and enter a **Title Case Name** to create `title-case-name.md`
|
||||
- Add a keyboard binding to make creating new notes easier.
|
||||
- You shouldn't worry too much about categorising your notes. You can always [[search-for-notes]], and explore them using the [[graph-visualisation]].
|
||||
|
||||
|
||||
10
docs/custom-snippets.md
Normal file
10
docs/custom-snippets.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Adding Custom Snippets
|
||||
|
||||
You can add custom snippets whilst the default set of snippets are decided by following the below steps:
|
||||
|
||||
1. `Cmd` + `Shift` + `P` (`Ctrl` + `Shift` + `P` for Windows), type `snippets` and select `Preferences: Configure User Snippets`.
|
||||
2. The command palette will remain in focus. Search for `markdown` and select the entry entitled `markdown.json (Markdown)`.
|
||||
3. A JSON file will open. You can author your own snippets using the [documentation](https://code.visualstudio.com/docs/editor/userdefinedsnippets#_create-your-own-snippets) to help you, or if you're using a snippet shared by another Foam user then you can copy and paste it in, as the below GIF demonstrates:
|
||||

|
||||
|
||||
To get started, you might consider replacing the entire contents of the `markdown.json` file opened by the steps above with the JSON in [this comment](https://github.com/foambubble/foam/pull/192#issuecomment-666736270).
|
||||
@@ -10,7 +10,7 @@ The default keyboard shortcut for "Open Daily Note" is `alt`+`d`. This can be ov
|
||||
|
||||
## Configuration
|
||||
|
||||
By default, Daily Notes will be created in a file called `yyyy-mm-dd.md` in the workspace root, with a heading `yyyy-mm-dd`.
|
||||
By default, Daily Notes will be created in a file called `yyyy-mm-dd.md` in the workspace root, with a heading `yyyy-mm-dd`.
|
||||
|
||||
These settings can be overridden in your workspace or global `.vscode/settings.json` file, using the [**dateformat** date masking syntax](https://github.com/felixge/node-dateformat#mask-options):
|
||||
|
||||
@@ -33,7 +33,7 @@ In the meantime, you can use [VS Code Snippets](https://code.visualstudio.com/do
|
||||
|
||||
In the future, Foam may provide an option for automatically opening your Daily Note when you open your Foam workspace.
|
||||
|
||||
If you want this behaviour now, you can use the excellent [Auto Run Command](https://marketplace.visualstudio.com/items?itemName=gabrielgrinberg.auto-run-command#review-details) extension to run the "Open Daily Note" command upon entering a Foam workspace by specifying the following configuration in your `.vscode/settings.json`:
|
||||
If you want this behavior now, you can use the excellent [Auto Run Command](https://marketplace.visualstudio.com/items?itemName=gabrielgrinberg.auto-run-command#review-details) extension to run the "Open Daily Note" command upon entering a Foam workspace by specifying the following configuration in your `.vscode/settings.json`:
|
||||
|
||||
```json
|
||||
"auto-run-command.rules": [
|
||||
@@ -44,3 +44,11 @@ If you want this behaviour now, you can use the excellent [Auto Run Command](htt
|
||||
}
|
||||
],
|
||||
```
|
||||
|
||||
## Extend Functionality (Weekly, Monthly, Quarterly Notes)
|
||||
|
||||
Please see [[note-macros]]
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[note-macros]: note-macros "Custom Note Macros"
|
||||
[//end]: # "Autogenerated link references"
|
||||
@@ -1,17 +1,31 @@
|
||||
# Diagrams in Markdown
|
||||
|
||||
You can use VS Code plugins such as [Mermaid](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid) to draw and preview diagrams in your content.
|
||||
We have two alternative recipes for displaying diagrams in markdown:
|
||||
|
||||
- [Mermaid](#mermaid)
|
||||
- [Draw.io](#drawio)
|
||||
|
||||
|
||||
## Mermaid
|
||||
|
||||
You can use [Mermaid](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid) plugin to draw and preview diagrams in your content.
|
||||
|
||||
⚠️ Be aware that Mermaid diagrams don't automatically get rendered in published Foams in [[github-pages]], and would require you to eject to another static site generation approach that supports Mermaid plugins.
|
||||
|
||||
---
|
||||
## Draw.io
|
||||
|
||||
[[todo]] [[good-first-task]] **Help improve this recipe!**
|
||||
[Draw.io](https://marketplace.visualstudio.com/items?itemName=hediet.vscode-drawio) extension allows you to create, edit, and display your diagrams without leaving Visual Studio Code. The `.drawio.svg` or `.drawio.png` files can be automatically embedded and displayed in published Foams, no export needed. FYI, the diagram below was made using Draw.io! You can check the diagram [here](./assets/images/diagram-drawio-demo.drawio.svg).
|
||||
|
||||

|
||||
|
||||
### Using Draw.io
|
||||
|
||||
1. Install [Draw.io](https://marketplace.visualstudio.com/items?itemName=hediet.vscode-drawio) VS Code extension.
|
||||
2. Create a new `*.drawio.svg` or `*.drawio.png` file.
|
||||
3. Start drawing your diagram. Once you done, save it.
|
||||
4. Embed the diagram file as you embedding the image file, for example: ``
|
||||
|
||||
[[todo]] [[good-first-task]] Suggestions for alternative diagramming approaches welcome
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[github-pages]: github-pages "Github Pages"
|
||||
[todo]: todo "Todo"
|
||||
[good-first-task]: good-first-task "Good First Task"
|
||||
[//end]: # "Autogenerated link references"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
19
docs/frequently-asked-questions.md
Normal file
19
docs/frequently-asked-questions.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Frequently Asked Questions
|
||||
|
||||
> ⚠️ Foam is still in preview. Expect the experience to be a little rough.
|
||||
|
||||
- [Frequently Asked Questions](#frequently-asked-questions)
|
||||
- [Links/Graphs/BackLinks don't work. How do I enable them?](#linksgraphsbacklinks-dont-work-how-do-i-enable-them)
|
||||
|
||||
## Links/Graphs/BackLinks don't work. How do I enable them?
|
||||
|
||||
- Ensure that you have all the [[recommended-extensions]] installed in Visual Studio Code
|
||||
- Reload Visual Studio Code by running `Cmd` + `Shift` + `P` (`Ctrl` + `Shift` + `P` for Windows), type "reload" and run the **Developer: Reload Window** command to for the updated extensions take effect
|
||||
- Check the formatting rules for links on [[foam-file-format]], [[wiki-links]] and [[link-formatting-and-autocompletion]]
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[recommended-extensions]: recommended-extensions "Recommended Extensions"
|
||||
[foam-file-format]: foam-file-format "Foam File Format"
|
||||
[wiki-links]: wiki-links "Wiki Links"
|
||||
[link-formatting-and-autocompletion]: link-formatting-and-autocompletion "Link Formatting and Autocompletion"
|
||||
[//end]: # "Autogenerated link references"
|
||||
@@ -74,6 +74,8 @@ After setting up the repository, open `.vscode/settings.json` and edit, add or r
|
||||
|
||||
To learn more about how to use **Foam**, read the [[recipes]].
|
||||
|
||||
Getting stuck in the setup? Read the [[frequently-asked-questions]].
|
||||
|
||||
There are [[known-issues]], and I'm sure, many unknown issues! Please [report them on GitHub](http://github.com/foambubble/foam/issues)!
|
||||
|
||||
## Features
|
||||
@@ -129,7 +131,7 @@ If that sounds like something you're interested in, I'd love to have you along o
|
||||
<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://anku.netlify.com/"><img src="https://avatars1.githubusercontent.com/u/22813027?v=4" width="60px;" alt=""/><br /><sub><b>Ankit Tiwari</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=anku255" title="Documentation">📖</a> <a href="https://github.com/foambubble/foam/commits?author=anku255" title="Tests">⚠️</a> <a href="https://github.com/foambubble/foam/commits?author=anku255" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ayushbaweja"><img src="https://avatars1.githubusercontent.com/u/44344063?v=4" width="60px;" alt=""/><br /><sub><b>Ayush Baweja</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ayushbaweja" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/TaiChi-IO"><img src="https://avatars3.githubusercontent.com/u/65092992?v=4" width="60px;" alt=""/><br /><sub><b>TaiChi-IO</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=TaiChi-IO" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/juanfrank77"><img src="https://avatars1.githubusercontent.com/u/12146882?v=4" width="60px;" alt=""/><br /><sub><b>Juan F Gonzalez </b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=juanfrank77" title="Documentation">📖</a></td>
|
||||
@@ -149,6 +151,20 @@ If that sounds like something you're interested in, I'd love to have you along o
|
||||
<td align="center"><a href="http://johnlindquist.com"><img src="https://avatars0.githubusercontent.com/u/36073?v=4" width="60px;" alt=""/><br /><sub><b>John Lindquist</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=johnlindquist" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ashwin.run/"><img src="https://avatars2.githubusercontent.com/u/1689183?v=4" width="60px;" alt=""/><br /><sub><b>Ashwin Ramaswami</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=epicfaace" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Klaudioz"><img src="https://avatars1.githubusercontent.com/u/632625?v=4" width="60px;" alt=""/><br /><sub><b>Claudio Canales</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Klaudioz" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/vitaly-pevgonen"><img src="https://avatars0.githubusercontent.com/u/6272738?v=4" width="60px;" alt=""/><br /><sub><b>vitaly-pevgonen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=vitaly-pevgonen" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://dshemetov.github.io"><img src="https://avatars0.githubusercontent.com/u/1810426?v=4" width="60px;" alt=""/><br /><sub><b>Dmitry Shemetov</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dshemetov" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/hooncp"><img src="https://avatars1.githubusercontent.com/u/48883554?v=4" width="60px;" alt=""/><br /><sub><b>hooncp</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=hooncp" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://rt-canada.ca"><img src="https://avatars1.githubusercontent.com/u/13721239?v=4" width="60px;" alt=""/><br /><sub><b>Martin Laws</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=martinlaws" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://seanksmith.me"><img src="https://avatars3.githubusercontent.com/u/2085441?v=4" width="60px;" alt=""/><br /><sub><b>Sean K Smith</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=sksmith" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/kevin-neely/"><img src="https://avatars1.githubusercontent.com/u/37545028?v=4" width="60px;" alt=""/><br /><sub><b>Kevin Neely</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=kneely" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ariefrahmansyah.dev"><img src="https://avatars3.githubusercontent.com/u/8122852?v=4" width="60px;" alt=""/><br /><sub><b>Arief Rahmansyah</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ariefrahmansyah" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://vhanda.in"><img src="https://avatars2.githubusercontent.com/u/426467?v=4" width="60px;" alt=""/><br /><sub><b>Vishesh Handa</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=vHanda" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.linkedin.com/in/heroichitesh"><img src="https://avatars3.githubusercontent.com/u/37622734?v=4" width="60px;" alt=""/><br /><sub><b>Hitesh Kumar</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=HeroicHitesh" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://spencerwoo.com"><img src="https://avatars2.githubusercontent.com/u/32114380?v=4" width="60px;" alt=""/><br /><sub><b>Spencer Woo</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=spencerwooo" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -169,6 +185,7 @@ Foam is licensed under the [MIT license](license).
|
||||
[backlinking]: backlinking "Backlinking"
|
||||
[recommended-extensions]: recommended-extensions "Recommended Extensions"
|
||||
[recipes]: recipes "Recipes"
|
||||
[frequently-asked-questions]: frequently-asked-questions "Frequently Asked Questions"
|
||||
[known-issues]: known-issues "Known Issues"
|
||||
[roadmap]: roadmap "Roadmap"
|
||||
[principles]: principles "Principles"
|
||||
|
||||
58
docs/katex-math-rendering.md
Normal file
58
docs/katex-math-rendering.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Katex Math Rendering
|
||||
|
||||
Apart from using the method mentioned in [[math-support]], we can also use KaTeX to render our math equations in Foam. The caveat is: we can't rely on GitHub Pages to host and deploy our website anymore, because the plugin we'll be using to let Jekyll support KaTeX doesn't play well together with GitHub Pages.
|
||||
|
||||
The alternative solution is to using [[vercel]] for building and publishing our website, so before you start integrating KaTeX into your Foam project, please follow the instructions to host your Foam workspace on [[vercel]] first.
|
||||
|
||||
## Adding required plugins
|
||||
|
||||
Add the plugin `jekyll-katex` to your Foam workspace's `_config.yml` and `Gemfile` if you haven't done so already. For detailed instructions, please refer to the `#Adding a _config.yml` and `#Adding a Gemfile` in [[vercel]].
|
||||
|
||||
## Loading KaTeX JS and CSS
|
||||
|
||||
Because we are using KaTeX to render math, we will also need to import KaTeX's JS and CSS files from CDN. The official method to load these files is documented at: [KaTeX/KaTeX#starter-template](https://github.com/KaTeX/KaTeX#starter-template). In our case, we will need to add the following code snippet to our `_layouts/page.html`:
|
||||
|
||||
```html
|
||||
<!-- _layouts/page.html -->
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css" integrity="sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X" crossorigin="anonymous">
|
||||
|
||||
<!-- The loading of KaTeX is deferred to speed up page rendering -->
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js" integrity="sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4" crossorigin="anonymous"></script>
|
||||
|
||||
<!-- To automatically render math in text elements, include the auto-render extension: -->
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js" integrity="sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa" crossorigin="anonymous" onload="renderMathInElement(document.body);"></script>
|
||||
|
||||
<!-- ... -->
|
||||
```
|
||||
|
||||
## Adding liquid tags to wrap page content
|
||||
|
||||
The plugin `jekyll-katex` focuses on rendering:
|
||||
|
||||
- Single math equations wrapped inside `katex` liquid tags like {% raw %}`{% katex %} ... {% endkatex %}`{% endraw %}.
|
||||
- Or multiple math equations in paragraphs wrapped inside {% raw %}`{% katexmm %} ... {% endkatexmm %}`{% endraw %}.
|
||||
|
||||
In our case, we'll be using the latter tag to wrap our {% raw %}`{{ content }}`{% endraw %}. Wrap {% raw %}`{{ content }}`{% endraw %} in the liquid tags inside `_layouts/page.html` like so:
|
||||
|
||||
```html
|
||||
<!-- _layouts/page.html -->
|
||||
|
||||
<!-- ... -->
|
||||
{% raw %}{% katexmm %} {{ content }} {% endkatexmm %}{% endraw %}
|
||||
<!-- ... -->
|
||||
```
|
||||
|
||||
## Render equations in Foam's homepage as well
|
||||
|
||||
You may have noticed that we only made modifications to the template `_layouts/page.html`, which means that `_layouts/home.html` won't have KaTeX support. If you wan't to render math in Foam's home page, you'll need to make the same modifications to `_layouts/home.html` as well.
|
||||
|
||||
Finally, if all goes well, then our site hosted on Vercel will support rendering math equations with KaTeX after commiting these changes to GitHub. Here's a demo of the default template with KaTeX support: [Foam Template with KaTeX support](https://foam-template.vercel.app/).
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[math-support]: math-support "Math Support"
|
||||
[vercel]: vercel "Vercel"
|
||||
[//end]: # "Autogenerated link references"
|
||||
42
docs/lsp-architecture.md
Normal file
42
docs/lsp-architecture.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# LSP Architecture
|
||||
|
||||
## Information requirements
|
||||
|
||||
### Document Completion
|
||||
|
||||
- List of all files in the workspace
|
||||
- Name
|
||||
- Title
|
||||
- Short preview of content
|
||||
|
||||
### Anchor completion
|
||||
|
||||
- List of all headings in the workspace
|
||||
- List of all paragraphs in the workspace
|
||||
- With full content
|
||||
- With context of where they are in the document
|
||||
- With hash ids
|
||||
- With document positions
|
||||
|
||||
## Link highlight
|
||||
|
||||
- All links
|
||||
- With positions
|
||||
|
||||
## Go to / peek link definition
|
||||
|
||||
- Get link at position
|
||||
- Get link target document
|
||||
- Get link target range
|
||||
|
||||
## Go to / peek references
|
||||
|
||||
- Get all links that point to file
|
||||
- Get all links that point to section/id
|
||||
- Get all instances of a hashtag
|
||||
|
||||
## Symbols
|
||||
|
||||
- Get all links in a document
|
||||
- Get all hashtags in a document
|
||||
- Get all block ids in a document
|
||||
@@ -4,7 +4,7 @@ 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`:
|
||||
Published Foam 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>
|
||||
@@ -18,11 +18,26 @@ The published pages don't support math formulas by default. To enable this featu
|
||||
</script>
|
||||
```
|
||||
|
||||
Example of inline math: $e^{i \pi}+1=0$
|
||||
Example of displayed math:
|
||||
This approach uses the [MathJax](https://www.mathjax.org/) library to render anything delimited by ```$``` (customizable in the snippet above) pairs to inline math and ```$$``` to blocks of math (like a html div tag) using with the AMS-LaTeX dialect embedded within MathJax.
|
||||
|
||||
Example of inline math using `$...$`:
|
||||
|
||||
`$e^{i \pi}+1=0$`, becomes $e^{i \pi}+1=0$
|
||||
|
||||
Example of a math block using `$$...$$`:
|
||||
|
||||
`$$ 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}|}} $$`
|
||||
|
||||
Becomes:
|
||||
|
||||
$$ 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}|}} $$
|
||||
|
||||
## Alternative approaches
|
||||
|
||||
There are other dialects of LaTeX (instead of AMS), and other JavaScript rendering libraries you may want to use. In a future version of Foam, we may support KaTeX syntax out of the box, but at this time, these integrations are left as an exercise to the user.
|
||||
|
||||
## Why don't my Math expressions work on my Foam's home page?
|
||||
|
||||
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`:
|
||||
|
||||
```
|
||||
@@ -33,4 +48,4 @@ 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)
|
||||
Reference: [How to support latex in github-pages](https://stackoverflow.com/questions/26275645/how-to-support-latex-in-github-pages)
|
||||
|
||||
36
docs/migrating-from-onenote.md
Normal file
36
docs/migrating-from-onenote.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Migrating from OneNote
|
||||
|
||||
This guide mostly duplicates the instructions at the repo for the PowerShell [script](https://github.com/nixsee/ConvertOneNote2MarkDown).
|
||||
|
||||
## Summary
|
||||
|
||||
The powershell script 'ConvertOneNote2MarkDown-v2.ps1' will utilize the OneNote Object Model on your workstation to convert all OneNote pages to Word documents and then utilizes PanDoc to convert the Word documents to Markdown (.md) format. It will also:
|
||||
|
||||
* Create a folder structure for your Notebooks and Sections.
|
||||
* Process pages that are in sections at the Notebook, Section Group and 1st Nested Section Group levels.
|
||||
* Allow you you choose between putting all Images in a central '/media' folder for each notebook, or in a separate '/media' folder in each folder of the hierarchy.
|
||||
* Fix image references in the resulting .md files, generating relative references to the image files within the markdown document.
|
||||
* A title, description, and date header will be added to each file as well.
|
||||
* And more (see details at repo)!
|
||||
|
||||
## Usage
|
||||
|
||||
1. Start the OneNote application. All notebooks currently loaded in [OneNote](https://getonetastic.com/download) will be converted.
|
||||
2. It is advised that you install [Onetastic](https://getonetastic.com/download) and the attached macro, which will automatically expand any collapsed paragraphs in the notebook. They won't be exported otherwise.
|
||||
* To install the macro, click the New Macro Button within the Onetastic Toolbar and then select File -> Import and select the .xml macro included in the release.
|
||||
* Run the macro for each Notebook that is open
|
||||
3. For the next sections, it is highly recommended that you use VS Code, and its embedded PowerShell terminal, as this allows you to edit and run the script, as well as check the results of the .md output all in one window.
|
||||
4. Whatever you choose, you will need to do the following:
|
||||
1. Clone the script to your computer (see [here](https://git-scm.com/book/en/v2/Git-Basics-Getting-a-Git-Repository), if you're unfamiliar with git).
|
||||
2. Once cloned, navigate to the repo folder. In VS Code, use File -> Add Folder to Workspace, right click on the folder in the left side bar and click [Open In Integrated Terminal](assets/images/migrating-one-note.png).
|
||||
3. Run the script by executing
|
||||
```.\ConvertOnenote2Markdown-v2```
|
||||
* if you receive an error, try running this line to bypass security:
|
||||
```Set-ExecutionPolicy Bypass -Scope Process```
|
||||
* if you still have trouble, try running both Onenote and Powershell as an administrator.
|
||||
5. It will ask you for the path to store the markdown folder structure. Please use an empty folder. If using VS Code, you might not be able to paste the filepath - right click on the blinking cursor and it will paste from clipboard. **Attention:** use a full absolute path for the destination.
|
||||
6. Read the prompts carefully to select your desired options. If you aren't actively editing your pages in Onenote, it is HIGHLY recommended that you don't delete the intermediate word docs, as they take 80+% of the time to generate. They are stored in their own folder, out of the way. You can then quickly re-run the script with different parameters until you find what you like.
|
||||
7. Sit back and wait until the process completes.
|
||||
8. To stop the process at any time, press Ctrl+C.
|
||||
9. If you like, you can inspect some of the .md files prior to completion. If you're not happy with the results, stop the process, delete the .md and re-run with different parameters.
|
||||
10. At this point, you should be ready to load the new directory into Foam!
|
||||
@@ -14,9 +14,11 @@ This Roadmap item discusses existing assembled solutions, and some thoughts on f
|
||||
- Provides functionality to edit, create, and browser markdown files.
|
||||
- Support journal mode, todo lists, and free writing
|
||||
- Syncs to GitHub repo
|
||||
- Supports Wiki Links
|
||||
- Supports Backlinks
|
||||
- Developer is happy to prioritize Foam compatibility
|
||||
|
||||
#### Cons
|
||||
- Doesn't support [[wiki-links]] **(deal breaker, but support [coming soon](https://twitter.com/GitJournal_io/status/1280194357296062466))**
|
||||
- Doesn't generate link reference lists (but this is ok, since [[workspace-janitor]] as a GitHub action can solve this)
|
||||
- Not as sleek as Apple/Google notes, some keyboard state glitching on Android, etc.
|
||||
- Lack of control over roadmap. Established product with a paid plan, so may not be open to Foam-supportive changes and additions that don't benefit most users.
|
||||
@@ -44,4 +46,4 @@ Given the effort vs reward ratio, it's a low priority for core team, but if some
|
||||
[build-vs-assemble]: build-vs-assemble "Build vs Assemble"
|
||||
[wiki-links]: wiki-links "Wiki Links"
|
||||
[workspace-janitor]: workspace-janitor "Janitor"
|
||||
[//end]: # "Autogenerated link references"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
63
docs/note-macros.md
Normal file
63
docs/note-macros.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Custom Note Macros
|
||||
|
||||
This extension gives you the ability to create custom note macros. It was heavily inspired by Foam's [[daily-notes]] functionality.
|
||||
|
||||
## Installation
|
||||
|
||||
**This extension is not included in the template**
|
||||
|
||||
To install search note-macros in vscode or head to [note-macros - Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=NeelyInnovations.note-macros)
|
||||
|
||||
## Instructions
|
||||
|
||||
### Run macro From command pallette
|
||||
|
||||
Simply use `Ctrl+P` or `Alt+P` depend on your os, and type `Note Macros: Run A Macro` then chose the macro you want to execute.
|
||||
|
||||
### Create Custom Note Macros
|
||||
|
||||
Create your own custom macros by adding them to your `settings.json` (Code|File > Preferences > User Settings). A full example can be found at [settings.json](https://github.com/kneely/note-macros/blob/master/settings.json)
|
||||
|
||||
For example:
|
||||
|
||||
This macro creates a Weekly note in the Weekly note Directory.
|
||||
|
||||
```json
|
||||
{
|
||||
"note-macros": {
|
||||
"Weekly": [
|
||||
{
|
||||
"type": "note",
|
||||
"directory": "Weekly",
|
||||
"extension": ".md",
|
||||
"name": "weekly-note",
|
||||
"date": "yyyy-W"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For an explanation of the fields please go to [note-macros - Explanation of Fields](https://github.com/kneely/note-macros#explanation-of-fields)
|
||||
|
||||
### Add Keybindings to Run your Macros
|
||||
|
||||
in `keybindings.json` (Code|File > Preferences > Keyboard Shortcuts) add bindings to your macros:
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "ctrl+cmd+/",
|
||||
"command": "note-macros.Weekly"
|
||||
}
|
||||
```
|
||||
|
||||
## Issues and Feedback
|
||||
|
||||
If you have any issues or questions please look at the [README.md](https://github.com/kneely/note-macros#note-macros) on the [note-macros](https://github.com/kneely/note-macros) GitHub.
|
||||
|
||||
If you run into any issues that are not fixed by referring to the README or feature requests please open an [issue](https://github.com/kneely/note-macros/issues).
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[daily-notes]: daily-notes "Daily notes"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
59
docs/predefined-user-snippets.md
Normal file
59
docs/predefined-user-snippets.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Pre-defined User Snippets
|
||||
|
||||
Having pre-defined user snippets would enable us to introduce Roam style commands to Foam. Consider the below snippets:
|
||||
|
||||
```json
|
||||
{
|
||||
"Zettelkasten Id": {
|
||||
"scope": "markdown",
|
||||
"prefix": "/id",
|
||||
"description": "Zettelkasten Id",
|
||||
"body": [
|
||||
"${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE} ${CURRENT_HOUR}:${CURRENT_MINUTE}:${CURRENT_SECOND}"
|
||||
]
|
||||
},
|
||||
"Current date": {
|
||||
"scope": "markdown",
|
||||
"prefix": "/date",
|
||||
"description": "Current date",
|
||||
"body": [
|
||||
"${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE} ${CURRENT_HOUR}:${CURRENT_MINUTE}:${CURRENT_SECOND}"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Which would look like:
|
||||

|
||||
|
||||
Using snippets enables Foam users to add [[custom-snippets]] themselves so they live alongside the first-class `/commands`.
|
||||
|
||||
## Notes & Considerations
|
||||
|
||||
- VS Code supplies "commands" already via the command palette
|
||||
- Consider the UX around this. Users less familiar with VS Code are more likely to be familiar with `/` to trigger a command menu. Experienced VS Code users may be more likely to favour the command palette.
|
||||
- We can use `TabCompletionProvider` and `snippets` and mix them (maybe) via the following VS Code setting: `"editor.snippetSuggestions": "inline" | "top" | "bottom" | "none"`
|
||||
- For more discussion, consult the PR [here](https://github.com/foambubble/foam/pull/192).
|
||||
|
||||
## Simplifying Markdown Syntax
|
||||
|
||||
Some markdown syntax is difficult for users who have never authored markdown before. Take for example a checkbox/todo. The following syntax is required:
|
||||
|
||||
```
|
||||
- [ ] Something todo...
|
||||
```
|
||||
|
||||
We could provide snippets that expand out into the associated markdown syntax, like in the below GIF:
|
||||

|
||||
|
||||
The JSON for these snippets can be found [here](https://github.com/foambubble/foam/pull/192#issuecomment-666736270).
|
||||
|
||||
[//begin]: # 'Autogenerated link references for markdown compatibility'
|
||||
[custom-snippets]: custom-snippets 'Adding Custom Snippets'
|
||||
[//end]: # 'Autogenerated link references'
|
||||
[//begin]: # 'Autogenerated link references for markdown compatibility'
|
||||
[custom-snippets]: custom-snippets 'Adding Custom Snippets'
|
||||
[//end]: # 'Autogenerated link references'
|
||||
[//begin]: # 'Autogenerated link references for markdown compatibility'
|
||||
[custom-snippets]: custom-snippets 'Adding Custom Snippets'
|
||||
[//end]: # 'Autogenerated link references'
|
||||
@@ -36,7 +36,7 @@ This principle may seem like it contradicts [Foam wants you to own your thoughts
|
||||
## Foam allows people to collaborate in discovering better ways to work, together.
|
||||
|
||||
- **Foam is a collection of ideas.** Foam was released to the public not to share the few good ideas in it, but to learn many good ideas from others. As you improve your own workflow, share your work on your own Foam blog.
|
||||
- **Foam is open for contributions.** If you use a tool or workflow that you like that fits these principles, please contribute them back to the Foam template as [[recipes]], [[recommended-extensions]] or documentation in [this workspace](httpsL//github,com/foambubble/foam). See also: [[roadmap]] and [[contribution-guide]].
|
||||
- **Foam is open for contributions.** If you use a tool or workflow that you like that fits these principles, please contribute them back to the Foam template as [[recipes]], [[recommended-extensions]] or documentation in [this workspace](https://github.com/foambubble/foam). See also: [[roadmap]] and [[contribution-guide]].
|
||||
- **Foam is open source.** Feel free to fork it, improve it and remix it. Just don't sell it, as per our [license](license).
|
||||
- **Foam is not Roam.** This project was inspired by Roam Research, but we're not limited by what Roam does. No idea is too big (though if it doesn't fit with Foam's core workflow, we might make it a [[recipes]] page instead).
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ Guides, tips and strategies for getting the most out of your Foam workspace!
|
||||
- Link documents with [[wiki-links]], using Foam's [[link-formatting-and-autocompletion]].
|
||||
- Use shortcuts for [[creating-new-notes]]
|
||||
- Instantly create and access your [[daily-notes]]
|
||||
- Use custom [[note-macros]] to create weekly, monthly etc. notes
|
||||
- Draw [[diagrams-in-markdown]]
|
||||
- Prettify your links, [[automatically-expand-urls-to-well-titled-links]]
|
||||
- Style your environment with [[custom-markdown-preview-styles]]
|
||||
@@ -60,8 +61,11 @@ Guides, tips and strategies for getting the most out of your Foam workspace!
|
||||
- Publish to [[gitlab-pages]]
|
||||
- Publish your site with [[eleventy-and-netlify]]
|
||||
- Publish to [[azure-devops-wiki]]
|
||||
- Publish to [[vercel]]
|
||||
- Make the site your own by [[customising-styles]].
|
||||
- Math support [[math-support]]
|
||||
- Render math symbols, by either
|
||||
- adding client-side [[math-support]] to the default [[github-pages]] site
|
||||
- adding a custom Jekyll plugin to support [[katex-math-rendering]]
|
||||
|
||||
## Collaborate
|
||||
|
||||
@@ -104,13 +108,16 @@ _See [[contribution-guide]] and [[how-to-write-recipes]]._
|
||||
[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"
|
||||
[note-macros]: note-macros "Custom Note Macros"
|
||||
[git-integration]: git-integration "Git integration"
|
||||
[gistpad]: gistpad "GistPad"
|
||||
[github-pages]: github-pages "Github Pages"
|
||||
[gitlab-pages]: gitlab-pages "GitLab Pages"
|
||||
[eleventy-and-netlify]: eleventy-and-netlify "Eleventy and Netlify"
|
||||
[azure-devops-wiki]: azure-devops-wiki "Azure DevOps Wiki"
|
||||
[vercel]: vercel "Vercel"
|
||||
[customising-styles]: customising-styles "Customising Styles"
|
||||
[math-support]: math-support "Math Support"
|
||||
[katex-math-rendering]: katex-math-rendering "Katex Math Rendering"
|
||||
[capture-notes-with-drafts-pro]: capture-notes-with-drafts-pro "Capture Notes With Drafts Pro"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
@@ -43,6 +43,7 @@ If a roadmap item is a stub, **consider** opening a [GitHub issue](https://githu
|
||||
- [[git-flows-for-teams]]
|
||||
- [[user-settings]]
|
||||
- [[link-reference-definitions]]
|
||||
- [[predefined-user-snippets]]
|
||||
|
||||
### Publishing
|
||||
|
||||
@@ -65,14 +66,18 @@ If a roadmap item is a stub, **consider** opening a [GitHub issue](https://githu
|
||||
|
||||
### Migration
|
||||
|
||||
The community is working on a number of automated scripts to help you migrate to Foam. The main work of developing such a method involves exporting your notes, converting them to the Markdown format, and then making sure that the links between notes (if you had those) still work.
|
||||
|
||||
- [[migrating-from-roam]]
|
||||
- Discussion: [foam#55](https://github.com/foambubble/foam/issues/55)
|
||||
- [[migrating-from-obsidian]]
|
||||
- Discussion: [foam#46](https://github.com/foambubble/foam/issues/46)
|
||||
- [[Migrating from OneNote (stub)]]
|
||||
- [[migrating-from-onenote]]
|
||||
- Discussion: [foam#151](https://github.com/foambubble/foam/issues/151)
|
||||
- _Migration from other tools..._
|
||||
### integration
|
||||
|
||||
### Integration
|
||||
|
||||
- _Integrations to third party tools_...
|
||||
|
||||
### Wild ideas
|
||||
@@ -100,6 +105,7 @@ If a roadmap item is a stub, **consider** opening a [GitHub issue](https://githu
|
||||
[git-flows-for-teams]: git-flows-for-teams "Git Flows for Teams (stub)"
|
||||
[user-settings]: user-settings "User Settings (stub)"
|
||||
[link-reference-definitions]: link-reference-definitions "Link Reference Definitions"
|
||||
[predefined-user-snippets]: predefined-user-snippets "Pre-defined User Snippets"
|
||||
[officially-support-alternative-templates]: officially-support-alternative-templates "Officially Support Alternative Templates (stub)"
|
||||
[improved-static-site-generation]: improved-static-site-generation "Improved Static Site Generation (stub)"
|
||||
[mdx-by-default]: mdx-by-default "MDX by Default(stub)"
|
||||
@@ -112,6 +118,7 @@ If a roadmap item is a stub, **consider** opening a [GitHub issue](https://githu
|
||||
[web-editor]: web-editor "Web Editor (stub)"
|
||||
[migrating-from-roam]: migrating-from-roam "Migrating from Roam (stub)"
|
||||
[migrating-from-obsidian]: migrating-from-obsidian "Migrating from Obsidian (stub)"
|
||||
[migrating-from-onenote]: migrating-from-onenote "Migrating from OneNote"
|
||||
[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"
|
||||
|
||||
85
docs/vercel.md
Normal file
85
docs/vercel.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# Vercel
|
||||
|
||||
[Vercel](https://vercel.com/) is a static website hosting solution similar to [[github-pages]]. This recipe shows you how to deploy the default Foam website template to Vercel.
|
||||
|
||||
## Setting up the project
|
||||
|
||||
### Using Foam's template
|
||||
|
||||
Generate a GitHub repository using the default [Foam template](https://github.com/foambubble/foam-template), this will be the workspace that we will be deploying with Vercel. This workspace is a barebone Jekyll source website, which means we can customize and install plugins just like any other Jekyll websites.
|
||||
|
||||
As we won't be using GitHub Pages, we will be adding a few configuration files in order to help Vercel pick up on how to build our site.
|
||||
|
||||
### Adding a `_config.yml`
|
||||
|
||||
First, we'll need to add a `_config.yml` at the root directory. This is the Jekyll configuration file. In here, we will set the site's title, theme, repository and permalink options, and also tell Jekyll what plugins to use:
|
||||
|
||||
```yaml
|
||||
# _config.yml
|
||||
title: Foam
|
||||
# All the plugins we will be installing now that we won't be using GitHub Pages
|
||||
plugins:
|
||||
- jekyll-katex # optional
|
||||
- jekyll-default-layout
|
||||
- jekyll-relative-links
|
||||
- jekyll-readme-index
|
||||
- jekyll-titles-from-headings
|
||||
- jekyll-optional-front-matter
|
||||
# The default Jekyll theme we will be using
|
||||
theme: jekyll-theme-primer
|
||||
# The GitHub repository that we are hosting our foam workspace from
|
||||
repository: user/repo
|
||||
# Generate permalinks in format specified in: https://jekyllrb.com/docs/permalinks/#built-in-formats
|
||||
permalink: pretty
|
||||
```
|
||||
|
||||
The `theme` specifies a theme for our deployed Jekyll website. The default GitHub Pages template is called [Primer](https://github.com/pages-themes/primer). See Primer docs for how to customise html layouts and templates. We can also choose a theme if you want from places like [Jekyll Themes](https://jekyllthemes.io/).
|
||||
|
||||
The `plugins` specifies a list of Jekyll plugins that we will be installing in the next section. As we won't be using GitHub Pages, we'll need to install these plugins that GitHub Pages installs for us under the hood.
|
||||
|
||||
_If you want to use LaTeX rendered with KaTeX (which is what the plugin `jekyll-katex` does), you can specify it here. And yes, one of the benefits of deploying with Vercel is that we can use KaTeX to render LaTeX! More on: [[katex-math-rendering]]_
|
||||
|
||||
### Adding a `Gemfile`
|
||||
|
||||
Next up, we'll create another new file called `Gemfile` in the root directory. This is where we will let Vercel know what plugins to install when building our website.
|
||||
|
||||
In our `Gemfile`, we need to specify our Ruby packages:
|
||||
|
||||
```ruby
|
||||
# Gemfile
|
||||
source "https://rubygems.org"
|
||||
gem "jekyll"
|
||||
gem "kramdown-parser-gfm"
|
||||
gem "jekyll-theme-primer"
|
||||
gem "jekyll-optional-front-matter"
|
||||
gem "jekyll-default-layout"
|
||||
gem "jekyll-relative-links"
|
||||
gem "jekyll-readme-index"
|
||||
gem "jekyll-titles-from-headings"
|
||||
gem "jekyll-katex" # Optional, the package that enables KaTeX math rendering
|
||||
```
|
||||
|
||||
### Enable math rendering with KaTeX (optional)
|
||||
|
||||
Besides adding the plugin `jekyll-katex` in `_config.yml` and `Gemfile`, we'll also have to follow the guides in [[katex-math-rendering]] to let our site fully support using KaTeX to render math equations.
|
||||
|
||||
### Commiting changes to GitHub repo
|
||||
|
||||
Finally, commit the newly created files to GitHub.
|
||||
|
||||
## Importing project to Vercel
|
||||
|
||||
First, import our foam workspace (GitHub repository) to Vercel with [Vercel's _Import Git Repository_](https://vercel.com/import/git). Paste our GitHub repo's url and Vercel will automatically pull and analyze the tool we use to deploy our website. (In our case: Jekyll.)
|
||||
|
||||
Next, select the folder to deploy from if prompted. If we are using the default template, then Vercel will default to the root directory of our Foam workspace.
|
||||
|
||||
Finally, if all is successful, Vercel will show the detected framework: Jekyll. Press `Deploy` to proceed on publishing our project.
|
||||
|
||||

|
||||
|
||||
And now, Vercel will take care of building and rendering our foam workspace each time on push. Vercel will publish our site to `xxx.vercel.app`, we can also define a custom domain name for our Vercel website.
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[github-pages]: github-pages "GitHub Pages"
|
||||
[katex-math-rendering]: katex-math-rendering "Katex Math Rendering"
|
||||
[//end]: # "Autogenerated link references"
|
||||
@@ -10,7 +10,7 @@
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"watch:vscode": "yarn workspace foam-vscode watch",
|
||||
"start:vscode": "yarn workspace foam-vscode vscode:start-debugging",
|
||||
"build:core": "yarn workspace foam-core build",
|
||||
"watch:core": "yarn workspace foam-core start",
|
||||
"test:core": "yarn workspace foam-core test",
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
"build": "tsc -b",
|
||||
"test": "jest",
|
||||
"lint": "echo Missing lint task in CLI package",
|
||||
"cli": "./bin/run",
|
||||
"cli": "yarn build && ./bin/run",
|
||||
"postpack": "rm -f oclif.manifest.json",
|
||||
"prepack": "rm -rf lib && tsc -b && oclif-dev manifest && oclif-dev readme",
|
||||
"version": "oclif-dev readme && git add README.md"
|
||||
|
||||
@@ -57,12 +57,12 @@ export default class Janitor extends Command {
|
||||
const definitions = generateLinkReferences(note, graph, !flags['without-extensions']);
|
||||
|
||||
// apply Edits
|
||||
let file = note.source;
|
||||
let file = note.source.text;
|
||||
file = heading ? applyTextEdit(file, heading) : file;
|
||||
file = definitions ? applyTextEdit(file, definitions) : file;
|
||||
|
||||
if (heading || definitions) {
|
||||
return writeFileToDisk(note.path, file);
|
||||
return writeFileToDisk(note.source.uri, file);
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
|
||||
@@ -58,7 +58,7 @@ Successfully generated link references and heading!
|
||||
if (note.title != null) {
|
||||
const kebabCasedFileName = getKebabCaseFileName(note.title);
|
||||
if (kebabCasedFileName) {
|
||||
return renameFile(note.path, kebabCasedFileName);
|
||||
return renameFile(note.source.uri, kebabCasedFileName);
|
||||
}
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
@@ -83,12 +83,12 @@ Successfully generated link references and heading!
|
||||
const definitions = generateLinkReferences(note, graph, !flags['without-extensions']);
|
||||
|
||||
// apply Edits
|
||||
let file = note.source;
|
||||
let file = note.source.text;
|
||||
file = heading ? applyTextEdit(file, heading) : file;
|
||||
file = definitions ? applyTextEdit(file, definitions) : file;
|
||||
|
||||
if (heading || definitions) {
|
||||
return writeFileToDisk(note.path, file);
|
||||
return writeFileToDisk(note.source.uri, file);
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "rimraf dist",
|
||||
"build": "tsdx build",
|
||||
"build": "tsdx build --tsconfig ./tsconfig.build.json",
|
||||
"test": "tsdx test",
|
||||
"lint": "tsdx lint",
|
||||
"lint": "tsdx lint src test",
|
||||
"watch": "tsdx watch",
|
||||
"prepare": "tsdx build"
|
||||
},
|
||||
@@ -30,11 +30,13 @@
|
||||
"glob": "^7.1.6",
|
||||
"graphlib": "^2.1.8",
|
||||
"lodash": "^4.17.19",
|
||||
"remark-frontmatter": "^2.0.0",
|
||||
"remark-parse": "^8.0.2",
|
||||
"remark-wiki-link": "^0.0.4",
|
||||
"title-case": "^3.0.2",
|
||||
"unified": "^9.0.0",
|
||||
"unist-util-visit": "^2.0.2"
|
||||
"unist-util-visit": "^2.0.2",
|
||||
"yaml": "^1.10.0"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts"
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { Position } from 'unist';
|
||||
import GithubSlugger from 'github-slugger';
|
||||
import { Note, GraphNote, NoteGraph } from '../note-graph';
|
||||
import {
|
||||
Note,
|
||||
NoteGraph,
|
||||
LINK_REFERENCE_DEFINITION_HEADER,
|
||||
LINK_REFERENCE_DEFINITION_FOOTER,
|
||||
} from '../index';
|
||||
} from '../definitions';
|
||||
import {
|
||||
createMarkdownReferences,
|
||||
stringifyMarkdownLinkReferenceDefinition,
|
||||
@@ -20,7 +19,7 @@ export interface TextEdit {
|
||||
}
|
||||
|
||||
export const generateLinkReferences = (
|
||||
note: Note,
|
||||
note: GraphNote,
|
||||
ng: NoteGraph,
|
||||
includeExtensions: boolean
|
||||
): TextEdit | null => {
|
||||
@@ -41,28 +40,30 @@ export const generateLinkReferences = (
|
||||
LINK_REFERENCE_DEFINITION_HEADER,
|
||||
...markdownReferences.map(stringifyMarkdownLinkReferenceDefinition),
|
||||
LINK_REFERENCE_DEFINITION_FOOTER,
|
||||
].join(note.eol);
|
||||
].join(note.source.eol);
|
||||
|
||||
if (note.definitions.length === 0) {
|
||||
if (newReferences.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const padding = note.end.column === 1 ? note.eol : `${note.eol}${note.eol}`;
|
||||
const padding =
|
||||
note.source.end.column === 1
|
||||
? note.source.eol
|
||||
: `${note.source.eol}${note.source.eol}`;
|
||||
return {
|
||||
newText: `${padding}${newReferences}`,
|
||||
range: {
|
||||
start: note.end,
|
||||
end: note.end,
|
||||
start: note.source.end,
|
||||
end: note.source.end,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const first = note.definitions[0];
|
||||
const last = note.definitions[note.definitions.length - 1];
|
||||
|
||||
const oldReferences = note.definitions
|
||||
.map(stringifyMarkdownLinkReferenceDefinition)
|
||||
.join(note.eol);
|
||||
.join(note.source.eol);
|
||||
|
||||
if (oldReferences === newReferences) {
|
||||
return null;
|
||||
@@ -88,11 +89,28 @@ export const generateHeading = (note: Note): TextEdit | null => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const frontmatterExists = note.source.contentStart.line !== 1;
|
||||
|
||||
let newLineExistsAfterFrontmatter = false;
|
||||
if (frontmatterExists) {
|
||||
const lines = note.source.text.split(note.source.eol);
|
||||
const index = note.source.contentStart.line - 1;
|
||||
const line = lines[index];
|
||||
newLineExistsAfterFrontmatter = line === '';
|
||||
}
|
||||
|
||||
const paddingStart = frontmatterExists ? note.source.eol : '';
|
||||
const paddingEnd = newLineExistsAfterFrontmatter
|
||||
? note.source.eol
|
||||
: `${note.source.eol}${note.source.eol}`;
|
||||
|
||||
return {
|
||||
newText: `# ${getHeadingFromFileName(note.id)}${note.eol}${note.eol}`,
|
||||
newText: `${paddingStart}# ${getHeadingFromFileName(
|
||||
note.slug
|
||||
)}${paddingEnd}`,
|
||||
range: {
|
||||
start: { line: 0, column: 0, offset: 0 },
|
||||
end: { line: 0, column: 0, offset: 0 },
|
||||
start: note.source.contentStart,
|
||||
end: note.source.contentStart,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import unified from 'unified';
|
||||
import markdownParse from 'remark-parse';
|
||||
import wikiLinkPlugin from 'remark-wiki-link';
|
||||
import frontmatterPlugin from 'remark-frontmatter';
|
||||
import { parse as parseYAML } from 'yaml';
|
||||
import visit, { CONTINUE, EXIT } from 'unist-util-visit';
|
||||
import { Node, Parent, Point } from 'unist';
|
||||
import * as path from 'path';
|
||||
import { Note, NoteLink, NoteLinkDefinition, NoteGraph } from './note-graph';
|
||||
import { dropExtension } from './utils';
|
||||
import { NoteLink, NoteLinkDefinition, NoteGraph, Note } from './note-graph';
|
||||
import { dropExtension, uriToSlug } from './utils';
|
||||
import { ID } from './types';
|
||||
|
||||
let processor: unified.Processor | null = null;
|
||||
|
||||
@@ -14,6 +17,7 @@ function parse(markdown: string): Node {
|
||||
processor ||
|
||||
unified()
|
||||
.use(markdownParse, { gfm: true })
|
||||
.use(frontmatterPlugin, ['yaml'])
|
||||
.use(wikiLinkPlugin);
|
||||
return processor.parse(markdown);
|
||||
}
|
||||
@@ -23,23 +27,35 @@ export function createNoteFromMarkdown(
|
||||
markdown: string,
|
||||
eol: string
|
||||
): Note {
|
||||
const filename = path.basename(uri);
|
||||
const id = path.parse(filename).name;
|
||||
const tree = parse(markdown);
|
||||
let title: string | null = null;
|
||||
|
||||
visit(tree, node => {
|
||||
if (node.type === 'heading' && node.depth === 1) {
|
||||
title = ((node as Parent)!.children[0].value as string) || title;
|
||||
}
|
||||
return title === null ? CONTINUE : EXIT;
|
||||
});
|
||||
|
||||
const links: NoteLink[] = [];
|
||||
const linkDefinitions: NoteLinkDefinition[] = [];
|
||||
let frontmatter: any = {};
|
||||
let start: Point = { line: 1, column: 1, offset: 0 }; // start position of the note
|
||||
visit(tree, node => {
|
||||
if (node.type === 'yaml') {
|
||||
frontmatter = parseYAML(node.value as string) ?? {}; // parseYAML returns null if the frontmatter is empty
|
||||
// Update the start position of the note by exluding the metadata
|
||||
start = {
|
||||
line: node.position!.end.line! + 1,
|
||||
column: 1,
|
||||
offset: node.position!.end.offset! + 1,
|
||||
};
|
||||
}
|
||||
|
||||
if (node.type === 'wikiLink') {
|
||||
links.push({
|
||||
to: node.value as string,
|
||||
text: node.value as string,
|
||||
type: 'wikilink',
|
||||
slug: node.value as string,
|
||||
position: node.position!,
|
||||
});
|
||||
}
|
||||
@@ -54,10 +70,26 @@ export function createNoteFromMarkdown(
|
||||
}
|
||||
});
|
||||
|
||||
// Give precendence to the title from the frontmatter if it exists
|
||||
title = frontmatter.title ?? title;
|
||||
|
||||
const end = tree.position!.end;
|
||||
const definitions = getFoamDefinitions(linkDefinitions, end);
|
||||
|
||||
return new Note(id, title, links, definitions, end, uri, markdown, eol);
|
||||
return {
|
||||
properties: frontmatter,
|
||||
slug: uriToSlug(uri),
|
||||
title: title,
|
||||
links: links,
|
||||
definitions: definitions,
|
||||
source: {
|
||||
uri: uri,
|
||||
text: markdown,
|
||||
contentStart: start,
|
||||
end: end,
|
||||
eol: eol,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getFoamDefinitions(
|
||||
@@ -97,7 +129,7 @@ export function stringifyMarkdownLinkReferenceDefinition(
|
||||
}
|
||||
export function createMarkdownReferences(
|
||||
graph: NoteGraph,
|
||||
noteId: string,
|
||||
noteId: ID,
|
||||
includeExtension: boolean
|
||||
): NoteLinkDefinition[] {
|
||||
const source = graph.getNote(noteId);
|
||||
@@ -114,8 +146,17 @@ export function createMarkdownReferences(
|
||||
return graph
|
||||
.getForwardLinks(noteId)
|
||||
.map(link => {
|
||||
const target = graph.getNote(link.to);
|
||||
|
||||
let target = graph.getNote(link.to);
|
||||
// if we don't find the target by ID we search the graph by slug
|
||||
if (!target) {
|
||||
const candidates = graph.getNotes({ slug: link.link.slug });
|
||||
if (candidates.length > 1) {
|
||||
console.log(
|
||||
`Warning: Slug ${link.link.slug} matches ${candidates.length} documents. Picking one.`
|
||||
);
|
||||
}
|
||||
target = candidates.length > 0 ? candidates[0] : null;
|
||||
}
|
||||
// We are dropping links to non-existent notes here,
|
||||
// but int the future we may want to surface these too
|
||||
if (!target) {
|
||||
@@ -126,8 +167,8 @@ export function createMarkdownReferences(
|
||||
}
|
||||
|
||||
const relativePath = path.relative(
|
||||
path.dirname(source.path),
|
||||
target.path
|
||||
path.dirname(source.source.uri),
|
||||
target.source.uri
|
||||
);
|
||||
|
||||
const pathToNote = includeExtension
|
||||
@@ -136,9 +177,9 @@ export function createMarkdownReferences(
|
||||
|
||||
// [wiki-link-text]: path/to/file.md "Page title"
|
||||
return {
|
||||
label: link.text,
|
||||
label: link.link.slug,
|
||||
url: pathToNote,
|
||||
title: target.title || target.id,
|
||||
title: target.title || target.slug,
|
||||
};
|
||||
})
|
||||
.filter(Boolean)
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
import { Graph, Edge } from 'graphlib';
|
||||
import { Position, Point } from 'unist';
|
||||
import GithubSlugger from 'github-slugger';
|
||||
import { Graph } from 'graphlib';
|
||||
import { EventEmitter } from 'events';
|
||||
import { Position, Point, URI, ID } from './types';
|
||||
import { hashURI, computeRelativeURI } from './utils';
|
||||
|
||||
type ID = string;
|
||||
|
||||
export interface Link {
|
||||
from: ID;
|
||||
to: ID;
|
||||
export interface NoteSource {
|
||||
uri: URI;
|
||||
text: string;
|
||||
contentStart: Point;
|
||||
end: Point;
|
||||
eol: string;
|
||||
}
|
||||
|
||||
export interface NoteLink {
|
||||
to: ID;
|
||||
text: string;
|
||||
export interface WikiLink {
|
||||
type: 'wikilink';
|
||||
slug: string;
|
||||
position: Position;
|
||||
}
|
||||
|
||||
// at the moment we only model wikilink
|
||||
export type NoteLink = WikiLink;
|
||||
|
||||
export interface NoteLinkDefinition {
|
||||
label: string;
|
||||
url: string;
|
||||
@@ -24,91 +27,108 @@ export interface NoteLinkDefinition {
|
||||
position?: Position;
|
||||
}
|
||||
|
||||
export class Note {
|
||||
public id: ID;
|
||||
public title: string | null;
|
||||
public source: string;
|
||||
public path: string;
|
||||
public end: Point;
|
||||
public eol: string;
|
||||
public links: NoteLink[];
|
||||
public definitions: NoteLinkDefinition[];
|
||||
|
||||
constructor(
|
||||
id: ID,
|
||||
title: string | null,
|
||||
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 interface Note {
|
||||
title: string | null;
|
||||
slug: string; // note: this slug is not necessarily unique
|
||||
properties: object;
|
||||
// sections: NoteSection[]
|
||||
// tags: NoteTag[]
|
||||
links: NoteLink[];
|
||||
definitions: NoteLinkDefinition[];
|
||||
source: NoteSource;
|
||||
}
|
||||
|
||||
export type NoteGraphEventHandler = (e: { note: Note }) => void;
|
||||
export type GraphNote = Note & {
|
||||
id: ID;
|
||||
};
|
||||
|
||||
export interface GraphConnection {
|
||||
from: ID;
|
||||
to: ID;
|
||||
link: NoteLink;
|
||||
}
|
||||
|
||||
export type NoteGraphEventHandler = (e: { note: GraphNote }) => void;
|
||||
|
||||
export type NotesQuery = { slug: string } | { title: string };
|
||||
|
||||
export class NoteGraph {
|
||||
private graph: Graph;
|
||||
private events: EventEmitter;
|
||||
private createIdFromURI: (uri: URI) => ID;
|
||||
|
||||
constructor() {
|
||||
this.graph = new Graph();
|
||||
this.events = new EventEmitter();
|
||||
this.createIdFromURI = hashURI;
|
||||
}
|
||||
|
||||
public setNote(note: Note) {
|
||||
const noteExists = this.graph.hasNode(note.id);
|
||||
public setNote(note: Note): GraphNote {
|
||||
const id = this.createIdFromURI(note.source.uri);
|
||||
const noteExists = this.graph.hasNode(id);
|
||||
if (noteExists) {
|
||||
(this.graph.outEdges(note.id) || []).forEach(edge => {
|
||||
(this.graph.outEdges(id) || []).forEach(edge => {
|
||||
this.graph.removeEdge(edge);
|
||||
});
|
||||
}
|
||||
|
||||
this.graph.setNode(note.id, note);
|
||||
const graphNote: GraphNote = {
|
||||
...note,
|
||||
id: id,
|
||||
};
|
||||
this.graph.setNode(id, graphNote);
|
||||
note.links.forEach(link => {
|
||||
const slugger = new GithubSlugger();
|
||||
this.graph.setEdge(note.id, slugger.slug(link.to), link.text);
|
||||
const relativePath =
|
||||
note.definitions.find(def => def.label === link.slug)?.url ?? link.slug;
|
||||
const targetPath = computeRelativeURI(note.source.uri, relativePath);
|
||||
const targetId = this.createIdFromURI(targetPath);
|
||||
const connection: GraphConnection = {
|
||||
from: graphNote.id,
|
||||
to: targetId,
|
||||
link: link,
|
||||
};
|
||||
this.graph.setEdge(graphNote.id, targetId, connection);
|
||||
});
|
||||
|
||||
this.events.emit(noteExists ? 'update' : 'add', { note });
|
||||
this.events.emit(noteExists ? 'update' : 'add', { note: graphNote });
|
||||
return graphNote;
|
||||
}
|
||||
|
||||
public getNotes(): Note[] {
|
||||
return this.graph.nodes().map(id => this.graph.node(id));
|
||||
public getNotes(query?: NotesQuery): GraphNote[] {
|
||||
// prettier-ignore
|
||||
const filterFn =
|
||||
query == null ? (note: Note | null) => note != null
|
||||
: 'slug' in query ? (note: Note | null) => note?.slug === query.slug
|
||||
: 'title' in query ? (note: Note | null) => note?.title === query.title
|
||||
: (note: Note | null) => note != null;
|
||||
|
||||
return this.graph
|
||||
.nodes()
|
||||
.map(id => this.graph.node(id))
|
||||
.filter(filterFn);
|
||||
}
|
||||
|
||||
public getNote(noteId: ID): Note | void {
|
||||
if (this.graph.hasNode(noteId)) {
|
||||
return this.graph.node(noteId);
|
||||
}
|
||||
throw new Error(`Note with ID [${noteId}] not found`);
|
||||
public getNote(noteId: ID): GraphNote | null {
|
||||
return this.graph.node(noteId) ?? null;
|
||||
}
|
||||
|
||||
public getAllLinks(noteId: ID): Link[] {
|
||||
public getNoteByURI(uri: URI): GraphNote | null {
|
||||
return this.getNote(this.createIdFromURI(uri));
|
||||
}
|
||||
|
||||
public getAllLinks(noteId: ID): GraphConnection[] {
|
||||
return (this.graph.nodeEdges(noteId) || []).map(edge =>
|
||||
convertEdgeToLink(edge, this.graph)
|
||||
this.graph.edge(edge.v, edge.w)
|
||||
);
|
||||
}
|
||||
|
||||
public getForwardLinks(noteId: ID): Link[] {
|
||||
public getForwardLinks(noteId: ID): GraphConnection[] {
|
||||
return (this.graph.outEdges(noteId) || []).map(edge =>
|
||||
convertEdgeToLink(edge, this.graph)
|
||||
this.graph.edge(edge.v, edge.w)
|
||||
);
|
||||
}
|
||||
|
||||
public getBacklinks(noteId: ID): Link[] {
|
||||
public getBacklinks(noteId: ID): GraphConnection[] {
|
||||
return (this.graph.inEdges(noteId) || []).map(edge =>
|
||||
convertEdgeToLink(edge, this.graph)
|
||||
this.graph.edge(edge.v, edge.w)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -129,9 +149,3 @@ export class NoteGraph {
|
||||
this.events.removeAllListeners();
|
||||
}
|
||||
}
|
||||
|
||||
const convertEdgeToLink = (edge: Edge, graph: Graph): Link => ({
|
||||
from: edge.v,
|
||||
to: edge.w,
|
||||
text: graph.edge(edge.v, edge.w),
|
||||
});
|
||||
|
||||
4
packages/foam-core/src/types.ts
Normal file
4
packages/foam-core/src/types.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { Position, Point } from 'unist';
|
||||
|
||||
export type URI = string;
|
||||
export type ID = string;
|
||||
@@ -1,4 +1,34 @@
|
||||
import path from 'path';
|
||||
import crypto from 'crypto';
|
||||
import { titleCase } from 'title-case';
|
||||
import GithubSlugger from 'github-slugger';
|
||||
import { URI, ID } from 'types';
|
||||
|
||||
export const hash = (text: string) =>
|
||||
crypto
|
||||
.createHash('sha1')
|
||||
.update(text)
|
||||
.digest('hex');
|
||||
|
||||
export const uriToSlug = (noteUri: URI): string => {
|
||||
return GithubSlugger.slug(path.parse(noteUri).name);
|
||||
};
|
||||
|
||||
export const hashURI = (uri: URI): ID => {
|
||||
return hash(path.normalize(uri));
|
||||
};
|
||||
|
||||
export const computeRelativeURI = (
|
||||
reference: URI,
|
||||
relativeSlug: string
|
||||
): URI => {
|
||||
// if no extension is provided, use the same extension as the source file
|
||||
const slug =
|
||||
path.extname(relativeSlug) !== ''
|
||||
? relativeSlug
|
||||
: `${relativeSlug}${path.extname(reference)}`;
|
||||
return path.normalize(path.join(path.dirname(reference), slug));
|
||||
};
|
||||
|
||||
export function dropExtension(path: string): string {
|
||||
const parts = path.split('.');
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
noTitle: This frontmatter doesn't contain any title
|
||||
---
|
||||
@@ -1,189 +1,187 @@
|
||||
import { NoteGraph, Note } from '../src/note-graph';
|
||||
import { NoteGraph, NoteLinkDefinition, Note } from '../src/note-graph';
|
||||
import { uriToSlug } from '../src/utils';
|
||||
|
||||
const position = {
|
||||
start: { line: 0, column: 0 },
|
||||
end: { line: 0, column: 0 },
|
||||
start: { line: 1, column: 1 },
|
||||
end: { line: 1, column: 1 },
|
||||
};
|
||||
|
||||
const documentStart = position.start;
|
||||
const documentEnd = position.end;
|
||||
const eol = '\n';
|
||||
|
||||
const createTestNote = (params: {
|
||||
uri: string;
|
||||
title?: string;
|
||||
definitions?: NoteLinkDefinition[];
|
||||
links?: { slug: string }[];
|
||||
text?: string;
|
||||
}): Note => {
|
||||
return {
|
||||
properties: {},
|
||||
title: params.title ?? null,
|
||||
slug: uriToSlug(params.uri),
|
||||
definitions: params.definitions ?? [],
|
||||
links: params.links
|
||||
? params.links.map(link => ({
|
||||
type: 'wikilink',
|
||||
slug: link.slug,
|
||||
position: position,
|
||||
text: 'link text',
|
||||
}))
|
||||
: [],
|
||||
source: {
|
||||
eol: eol,
|
||||
end: documentEnd,
|
||||
contentStart: documentStart,
|
||||
uri: params.uri,
|
||||
text: params.text ?? '',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
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', '')
|
||||
);
|
||||
graph.setNote(createTestNote({ uri: '/page-a.md' }));
|
||||
graph.setNote(createTestNote({ uri: '/page-b.md' }));
|
||||
graph.setNote(createTestNote({ uri: '/page-c.md' }));
|
||||
|
||||
expect(
|
||||
graph
|
||||
.getNotes()
|
||||
.map(n => n.id)
|
||||
.map(n => n.slug)
|
||||
.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', '')
|
||||
graph.setNote(createTestNote({ uri: '/page-a.md' }));
|
||||
const noteB = graph.setNote(
|
||||
createTestNote({
|
||||
uri: '/page-b.md',
|
||||
links: [{ slug: 'page-a' }],
|
||||
})
|
||||
);
|
||||
graph.setNote(createTestNote({ uri: '/page-c.md' }));
|
||||
|
||||
expect(
|
||||
graph
|
||||
.getForwardLinks('page-b')
|
||||
.map(link => link.to)
|
||||
.sort()
|
||||
graph.getForwardLinks(noteB.id).map(link => graph.getNote(link.to)!.slug)
|
||||
).toEqual(['page-a']);
|
||||
});
|
||||
|
||||
it('Detects backlinks', () => {
|
||||
const graph = new NoteGraph();
|
||||
const noteA = graph.setNote(createTestNote({ uri: '/page-a.md' }));
|
||||
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', '')
|
||||
createTestNote({
|
||||
uri: '/page-b.md',
|
||||
links: [{ slug: 'page-a' }],
|
||||
})
|
||||
);
|
||||
graph.setNote(createTestNote({ uri: '/page-c.md' }));
|
||||
|
||||
expect(
|
||||
graph
|
||||
.getBacklinks('page-a')
|
||||
.map(link => link.from)
|
||||
.sort()
|
||||
graph.getBacklinks(noteA.id).map(link => graph.getNote(link.from)!.slug)
|
||||
).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('Returns null when accessing non-existing node', () => {
|
||||
const graph = new NoteGraph();
|
||||
graph.setNote(createTestNote({ uri: 'page-a' }));
|
||||
expect(graph.getNote('non-existing')).toBeNull();
|
||||
});
|
||||
|
||||
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',
|
||||
''
|
||||
)
|
||||
createTestNote({
|
||||
uri: '/page-a.md',
|
||||
links: [{ slug: 'non-existing' }],
|
||||
})
|
||||
);
|
||||
expect(graph.getNote('non-existing')).toBeUndefined();
|
||||
|
||||
expect(graph.getNote('non-existing')).toBeNull();
|
||||
});
|
||||
|
||||
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', '')
|
||||
const noteA = graph.setNote(createTestNote({ uri: '/page-a.md' }));
|
||||
const noteB = graph.setNote(
|
||||
createTestNote({
|
||||
uri: '/page-b.md',
|
||||
links: [{ slug: 'page-a' }],
|
||||
})
|
||||
);
|
||||
const noteC = graph.setNote(createTestNote({ uri: '/page-c.md' }));
|
||||
|
||||
expect(
|
||||
graph
|
||||
.getForwardLinks('page-b')
|
||||
.map(link => link.to)
|
||||
.sort()
|
||||
graph.getForwardLinks(noteB.id).map(link => graph.getNote(link.to)?.slug)
|
||||
).toEqual(['page-a']);
|
||||
expect(
|
||||
graph
|
||||
.getBacklinks('page-a')
|
||||
.map(link => link.from)
|
||||
.sort()
|
||||
graph.getBacklinks(noteA.id).map(link => graph.getNote(link.from)?.slug)
|
||||
).toEqual(['page-b']);
|
||||
expect(
|
||||
graph
|
||||
.getBacklinks('page-c')
|
||||
.map(link => link.from)
|
||||
.sort()
|
||||
graph.getBacklinks(noteC.id).map(link => graph.getNote(link.from)?.slug)
|
||||
).toEqual([]);
|
||||
|
||||
graph.setNote(
|
||||
new Note(
|
||||
'page-b',
|
||||
'page-b',
|
||||
[{ to: 'page-c', text: 'go', position }],
|
||||
[],
|
||||
documentEnd,
|
||||
eol,
|
||||
'/path-2b.md',
|
||||
''
|
||||
)
|
||||
createTestNote({
|
||||
uri: '/page-b.md',
|
||||
links: [{ slug: 'page-c' }],
|
||||
})
|
||||
);
|
||||
|
||||
expect(
|
||||
graph
|
||||
.getForwardLinks('page-b')
|
||||
.map(link => link.to)
|
||||
.sort()
|
||||
graph.getForwardLinks(noteB.id).map(link => graph.getNote(link.to)?.slug)
|
||||
).toEqual(['page-c']);
|
||||
expect(
|
||||
graph
|
||||
.getBacklinks('page-a')
|
||||
.map(link => link.from)
|
||||
.sort()
|
||||
graph.getBacklinks(noteA.id).map(link => graph.getNote(link.from)?.slug)
|
||||
).toEqual([]);
|
||||
expect(
|
||||
graph
|
||||
.getBacklinks('page-c')
|
||||
.map(link => link.from)
|
||||
.sort()
|
||||
graph.getBacklinks(noteC.id).map(link => graph.getNote(link.from)?.slug)
|
||||
).toEqual(['page-b']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Graph querying', () => {
|
||||
it('returns empty set if no note is found', () => {
|
||||
const graph = new NoteGraph();
|
||||
graph.setNote(createTestNote({ uri: '/page-a.md' }));
|
||||
expect(graph.getNotes({ slug: 'non-existing' })).toEqual([]);
|
||||
expect(graph.getNotes({ title: 'non-existing' })).toEqual([]);
|
||||
});
|
||||
|
||||
it('finds the note by slug', () => {
|
||||
const graph = new NoteGraph();
|
||||
const note = graph.setNote(createTestNote({ uri: '/page-a.md' }));
|
||||
expect(graph.getNotes({ slug: note.slug }).length).toEqual(1);
|
||||
});
|
||||
|
||||
it('finds a note by slug when there is more than one', () => {
|
||||
const graph = new NoteGraph();
|
||||
graph.setNote(createTestNote({ uri: '/dir1/page-a.md' }));
|
||||
graph.setNote(createTestNote({ uri: '/dir2/page-a.md' }));
|
||||
expect(graph.getNotes({ slug: 'page-a' }).length).toEqual(2);
|
||||
});
|
||||
|
||||
it('finds a note by title', () => {
|
||||
const graph = new NoteGraph();
|
||||
const note = graph.setNote(
|
||||
createTestNote({ uri: '/dir1/page-a.md', title: 'My Title' })
|
||||
);
|
||||
expect(graph.getNotes({ title: 'My Title' }).length).toEqual(1);
|
||||
});
|
||||
|
||||
it('finds a note by title when there are several', () => {
|
||||
const graph = new NoteGraph();
|
||||
graph.setNote(
|
||||
createTestNote({ uri: '/dir1/page-a.md', title: 'My Title' })
|
||||
);
|
||||
graph.setNote(
|
||||
createTestNote({ uri: '/dir3/page-b.md', title: 'My Title' })
|
||||
);
|
||||
expect(graph.getNotes({ title: 'My Title' }).length).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as path from 'path';
|
||||
import { NoteGraph, Note } from '../../src/note-graph';
|
||||
import { NoteGraph } from '../../src/note-graph';
|
||||
import { generateHeading } from '../../src/janitor';
|
||||
import { initializeNoteGraph } from '../../src/initialize-note-graph';
|
||||
|
||||
@@ -11,26 +11,26 @@ describe('generateHeadings', () => {
|
||||
});
|
||||
|
||||
it('should add heading to a file that does not have them', () => {
|
||||
const note = _graph.getNote('file-without-title') as Note;
|
||||
const note = _graph.getNotes({ slug: 'file-without-title' })[0];
|
||||
const expected = {
|
||||
newText: `# File without Title
|
||||
|
||||
`,
|
||||
range: {
|
||||
start: {
|
||||
line: 0,
|
||||
column: 0,
|
||||
line: 1,
|
||||
column: 1,
|
||||
offset: 0,
|
||||
},
|
||||
end: {
|
||||
line: 0,
|
||||
column: 0,
|
||||
line: 1,
|
||||
column: 1,
|
||||
offset: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const actual = generateHeading(note!);
|
||||
const actual = generateHeading(note);
|
||||
|
||||
expect(actual!.range.start).toEqual(expected.range.start);
|
||||
expect(actual!.range.end).toEqual(expected.range.end);
|
||||
@@ -38,12 +38,25 @@ describe('generateHeadings', () => {
|
||||
});
|
||||
|
||||
it('should not cause any changes to a file that has a heading', () => {
|
||||
const note = _graph.getNote('index') as Note;
|
||||
const note = _graph.getNotes({ slug: 'index' })[0];
|
||||
expect(generateHeading(note)).toBeNull();
|
||||
});
|
||||
|
||||
const expected = null;
|
||||
it('should generate heading when the file only contains frontmatter', () => {
|
||||
const note = _graph.getNotes({ slug: 'file-with-only-frontmatter' })[0];
|
||||
|
||||
const actual = generateHeading(note!);
|
||||
const expected = {
|
||||
newText: '\n# File with only Frontmatter\n\n',
|
||||
range: {
|
||||
start: { line: 4, column: 1, offset: 60 },
|
||||
end: { line: 4, column: 1, offset: 60 },
|
||||
},
|
||||
};
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as path from 'path';
|
||||
import { NoteGraph, Note } from '../../src/note-graph';
|
||||
import { NoteGraph } from '../../src/note-graph';
|
||||
import { generateLinkReferences } from '../../src/janitor';
|
||||
import { initializeNoteGraph } from '../../src/initialize-note-graph';
|
||||
|
||||
@@ -11,11 +11,11 @@ describe('generateLinkReferences', () => {
|
||||
});
|
||||
|
||||
it('initialised test graph correctly', () => {
|
||||
expect(_graph.getNotes().length).toEqual(5);
|
||||
expect(_graph.getNotes().length).toEqual(6);
|
||||
});
|
||||
|
||||
it('should add link references to a file that does not have them', () => {
|
||||
const note = _graph.getNote('index') as Note;
|
||||
const note = _graph.getNotes({ slug: 'index' })[0];
|
||||
const expected = {
|
||||
newText: `
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
@@ -37,7 +37,7 @@ describe('generateLinkReferences', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const actual = generateLinkReferences(note!, _graph, false);
|
||||
const actual = generateLinkReferences(note, _graph, false);
|
||||
|
||||
expect(actual!.range.start).toEqual(expected.range.start);
|
||||
expect(actual!.range.end).toEqual(expected.range.end);
|
||||
@@ -45,7 +45,7 @@ describe('generateLinkReferences', () => {
|
||||
});
|
||||
|
||||
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 note = _graph.getNotes({ slug: 'second-document' })[0];
|
||||
|
||||
const expected = {
|
||||
newText: '',
|
||||
@@ -63,7 +63,7 @@ describe('generateLinkReferences', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const actual = generateLinkReferences(note!, _graph, false);
|
||||
const actual = generateLinkReferences(note, _graph, false);
|
||||
|
||||
expect(actual!.range.start).toEqual(expected.range.start);
|
||||
expect(actual!.range.end).toEqual(expected.range.end);
|
||||
@@ -71,7 +71,7 @@ describe('generateLinkReferences', () => {
|
||||
});
|
||||
|
||||
it('should update link definitions if they are present but changed', () => {
|
||||
const note = _graph.getNote('first-document') as Note;
|
||||
const note = _graph.getNotes({ slug: 'first-document' })[0];
|
||||
|
||||
const expected = {
|
||||
newText: `[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
@@ -91,7 +91,7 @@ describe('generateLinkReferences', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const actual = generateLinkReferences(note!, _graph, false);
|
||||
const actual = generateLinkReferences(note, _graph, false);
|
||||
|
||||
expect(actual!.range.start).toEqual(expected.range.start);
|
||||
expect(actual!.range.end).toEqual(expected.range.end);
|
||||
@@ -99,11 +99,11 @@ describe('generateLinkReferences', () => {
|
||||
});
|
||||
|
||||
it('should not cause any changes if link reference definitions were up to date', () => {
|
||||
const note = _graph.getNote('third-document') as Note;
|
||||
const note = _graph.getNotes({ slug: 'third-document' })[0];
|
||||
|
||||
const expected = null;
|
||||
|
||||
const actual = generateLinkReferences(note!, _graph, false);
|
||||
const actual = generateLinkReferences(note, _graph, false);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
createNoteFromMarkdown,
|
||||
createMarkdownReferences,
|
||||
} from '../src/markdown-provider';
|
||||
import { NoteGraph, Note } from '../src/note-graph';
|
||||
import { NoteGraph } from '../src/note-graph';
|
||||
|
||||
const pageA = `
|
||||
# Page A
|
||||
@@ -25,64 +25,128 @@ const pageD = `
|
||||
This file has no heading.
|
||||
`;
|
||||
|
||||
const pageE = `
|
||||
---
|
||||
title: Note Title
|
||||
date: 20-12-12
|
||||
---
|
||||
|
||||
# Other Note Title
|
||||
`;
|
||||
|
||||
const pageF = `
|
||||
---
|
||||
---
|
||||
|
||||
# Empty Frontmatter
|
||||
`;
|
||||
|
||||
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'));
|
||||
graph.setNote(createNoteFromMarkdown('/page-a.md', pageA, '\n'));
|
||||
graph.setNote(createNoteFromMarkdown('/page-b.md', pageB, '\n'));
|
||||
graph.setNote(createNoteFromMarkdown('/page-c.md', pageC, '\n'));
|
||||
|
||||
expect(
|
||||
graph
|
||||
.getNotes()
|
||||
.map(n => n.id)
|
||||
.map(n => n.slug)
|
||||
.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'));
|
||||
const noteA = graph.setNote(
|
||||
createNoteFromMarkdown('/page-a.md', pageA, '\n')
|
||||
);
|
||||
const noteB = graph.setNote(
|
||||
createNoteFromMarkdown('/page-b.md', pageB, '\n')
|
||||
);
|
||||
graph.setNote(createNoteFromMarkdown('/page-c.md', 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',
|
||||
]);
|
||||
expect(
|
||||
graph.getBacklinks(noteB.id).map(link => graph.getNote(link.from)!.slug)
|
||||
).toEqual(['page-a']);
|
||||
expect(
|
||||
graph.getForwardLinks(noteA.id).map(link => graph.getNote(link.to)!.slug)
|
||||
).toEqual(['page-b', 'page-c']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Note Title', () => {
|
||||
it('should initialize note title if heading exists', () => {
|
||||
const graph = new NoteGraph();
|
||||
graph.setNote(createNoteFromMarkdown('page-a', pageA, '\n'));
|
||||
const note = graph.setNote(
|
||||
createNoteFromMarkdown('/page-a.md', pageA, '\n')
|
||||
);
|
||||
|
||||
const pageANoteTitle = (graph.getNote('page-a') as Note).title;
|
||||
const pageANoteTitle = graph.getNote(note.id)!.title;
|
||||
expect(pageANoteTitle).toBe('Page A');
|
||||
});
|
||||
|
||||
it('should not initialize note title if heading does not exist', () => {
|
||||
const graph = new NoteGraph();
|
||||
graph.setNote(createNoteFromMarkdown('page-d', pageD, '\n'));
|
||||
const note = graph.setNote(
|
||||
createNoteFromMarkdown('/page-d.md', pageD, '\n')
|
||||
);
|
||||
|
||||
const pageANoteTitle = (graph.getNote('page-d') as Note).title;
|
||||
const pageANoteTitle = graph.getNote(note.id)!.title;
|
||||
expect(pageANoteTitle).toBe(null);
|
||||
});
|
||||
|
||||
it('should give precedence to frontmatter title over other headings', () => {
|
||||
const graph = new NoteGraph();
|
||||
const note = graph.setNote(
|
||||
createNoteFromMarkdown('/page-e.md', pageE, '\n')
|
||||
);
|
||||
|
||||
const pageENoteTitle = graph.getNote(note.id)!.title;
|
||||
expect(pageENoteTitle).toBe('Note Title');
|
||||
});
|
||||
});
|
||||
|
||||
describe('frontmatter', () => {
|
||||
it('should parse yaml frontmatter', () => {
|
||||
const graph = new NoteGraph();
|
||||
const note = graph.setNote(
|
||||
createNoteFromMarkdown('/page-e.md', pageE, '\n')
|
||||
);
|
||||
|
||||
const expected = {
|
||||
title: 'Note Title',
|
||||
date: '20-12-12',
|
||||
};
|
||||
|
||||
const actual: any = graph.getNote(note.id)!.properties;
|
||||
|
||||
expect(actual.title).toBe(expected.title);
|
||||
expect(actual.date).toBe(expected.date);
|
||||
});
|
||||
|
||||
it('should parse empty frontmatter', () => {
|
||||
const graph = new NoteGraph();
|
||||
const note = graph.setNote(
|
||||
createNoteFromMarkdown('/page-f.md', pageF, '\n')
|
||||
);
|
||||
|
||||
const expected = {};
|
||||
|
||||
const actual = graph.getNote(note.id)!.properties;
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('wikilinks definitions', () => {
|
||||
it('can generate links without file extension when includeExtension = false', () => {
|
||||
const graph = new NoteGraph();
|
||||
const noteA = createNoteFromMarkdown('dir1/page-a.md', pageA, '\n');
|
||||
const noteB = createNoteFromMarkdown('dir1/page-b.md', pageB, '\n');
|
||||
const noteC = createNoteFromMarkdown('dir1/page-c.md', pageC, '\n');
|
||||
graph.setNote(noteA);
|
||||
graph.setNote(noteB);
|
||||
graph.setNote(noteC);
|
||||
const noteA = graph.setNote(
|
||||
createNoteFromMarkdown('/dir1/page-a.md', pageA, '\n')
|
||||
);
|
||||
graph.setNote(createNoteFromMarkdown('/dir1/page-b.md', pageB, '\n'));
|
||||
graph.setNote(createNoteFromMarkdown('/dir1/page-c.md', pageC, '\n'));
|
||||
|
||||
const noExtRefs = createMarkdownReferences(graph, noteA.id, false);
|
||||
expect(noExtRefs.map(r => r.url)).toEqual(['page-b', 'page-c']);
|
||||
@@ -90,12 +154,11 @@ describe('wikilinks definitions', () => {
|
||||
|
||||
it('can generate links with file extension when includeExtension = true', () => {
|
||||
const graph = new NoteGraph();
|
||||
const noteA = createNoteFromMarkdown('dir1/page-a.md', pageA, '\n');
|
||||
const noteB = createNoteFromMarkdown('dir1/page-b.md', pageB, '\n');
|
||||
const noteC = createNoteFromMarkdown('dir1/page-c.md', pageC, '\n');
|
||||
graph.setNote(noteA);
|
||||
graph.setNote(noteB);
|
||||
graph.setNote(noteC);
|
||||
const noteA = graph.setNote(
|
||||
createNoteFromMarkdown('/dir1/page-a.md', pageA, '\n')
|
||||
);
|
||||
graph.setNote(createNoteFromMarkdown('/dir1/page-b.md', pageB, '\n'));
|
||||
graph.setNote(createNoteFromMarkdown('/dir1/page-c.md', pageC, '\n'));
|
||||
|
||||
const extRefs = createMarkdownReferences(graph, noteA.id, true);
|
||||
expect(extRefs.map(r => r.url)).toEqual(['page-b.md', 'page-c.md']);
|
||||
@@ -103,12 +166,11 @@ describe('wikilinks definitions', () => {
|
||||
|
||||
it('use relative paths', () => {
|
||||
const graph = new NoteGraph();
|
||||
const noteA = createNoteFromMarkdown('dir1/page-a.md', pageA, '\n');
|
||||
const noteB = createNoteFromMarkdown('dir2/page-b.md', pageB, '\n');
|
||||
const noteC = createNoteFromMarkdown('dir3/page-c.md', pageC, '\n');
|
||||
graph.setNote(noteA);
|
||||
graph.setNote(noteB);
|
||||
graph.setNote(noteC);
|
||||
const noteA = graph.setNote(
|
||||
createNoteFromMarkdown('/dir1/page-a.md', pageA, '\n')
|
||||
);
|
||||
graph.setNote(createNoteFromMarkdown('/dir2/page-b.md', pageB, '\n'));
|
||||
graph.setNote(createNoteFromMarkdown('/dir3/page-c.md', pageC, '\n'));
|
||||
|
||||
const extRefs = createMarkdownReferences(graph, noteA.id, true);
|
||||
expect(extRefs.map(r => r.url)).toEqual([
|
||||
|
||||
30
packages/foam-core/test/utils.test.ts
Normal file
30
packages/foam-core/test/utils.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { uriToSlug, hashURI, computeRelativeURI } from '../src/utils';
|
||||
|
||||
describe('URI utils', () => {
|
||||
it('supports various cases', () => {
|
||||
expect(uriToSlug('/this/is/a/path.md')).toEqual('path');
|
||||
expect(uriToSlug('../a/relative/path.md')).toEqual('path');
|
||||
expect(uriToSlug('another/relative/path.md')).toEqual('path');
|
||||
expect(uriToSlug('no-directory.markdown')).toEqual('no-directory');
|
||||
expect(uriToSlug('many.dots.name.markdown')).toEqual('manydotsname');
|
||||
});
|
||||
|
||||
it('normalizes URI before hashing', () => {
|
||||
expect(hashURI('/this/is/a/path.md')).toEqual(
|
||||
hashURI('/this/has/../is/a/path.md')
|
||||
);
|
||||
expect(hashURI('this/is/a/path.md')).toEqual(
|
||||
hashURI('this/has/../is/a/path.md')
|
||||
);
|
||||
});
|
||||
|
||||
it('computes a relative uri using a slug', () => {
|
||||
expect(computeRelativeURI('/my/file.md', '../hello.md')).toEqual(
|
||||
'/hello.md'
|
||||
);
|
||||
expect(computeRelativeURI('/my/file.md', '../hello')).toEqual('/hello.md');
|
||||
expect(computeRelativeURI('/my/file.markdown', '../hello')).toEqual(
|
||||
'/hello.markdown'
|
||||
);
|
||||
});
|
||||
});
|
||||
6
packages/foam-core/tsconfig.build.json
Normal file
6
packages/foam-core/tsconfig.build.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "es6"
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,29 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src",
|
||||
"composite": true,
|
||||
"esModuleInterop": true,
|
||||
"importHelpers": true,
|
||||
|
||||
// commonjs module format is used so that the incremental
|
||||
// tsc build-mode ran during development can replace individual
|
||||
// files (as opposed to generate the .cjs.development.js bundle.
|
||||
//
|
||||
// this is overridden in tsconfig.build.json for distribution
|
||||
"module": "commonjs",
|
||||
|
||||
"moduleResolution": "node",
|
||||
"outDir": "dist",
|
||||
"rootDir": "./src",
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"lib": [
|
||||
"esnext"
|
||||
]
|
||||
},
|
||||
"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",
|
||||
},
|
||||
]
|
||||
}
|
||||
33
packages/foam-language-server/package.json
Normal file
33
packages/foam-language-server/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "foam-language-server",
|
||||
"author": "Jani Eväkallio",
|
||||
"repository": "https://github.com/foambubble/foam",
|
||||
"version": "0.3.0",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "rimraf dist",
|
||||
"build": "tsdx build --tsconfig ./tsconfig.build.json",
|
||||
"test": "tsdx test",
|
||||
"lint": "tsdx lint src test",
|
||||
"watch": "tsdx watch",
|
||||
"prepare": "npm run build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsdx": "^0.13.2",
|
||||
"tslib": "^2.0.0",
|
||||
"typescript": "^3.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"file-uri-to-path": "^2.0.0",
|
||||
"foam-core": "^0.3.0",
|
||||
"remark-stringify": "^8.1.1",
|
||||
"short-hash": "^1.0.0",
|
||||
"vscode-languageserver": "^6.1.1",
|
||||
"vscode-languageserver-textdocument": "^1.0.1"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts"
|
||||
}
|
||||
100
packages/foam-language-server/src/file.ts
Normal file
100
packages/foam-language-server/src/file.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import unified from 'unified';
|
||||
import markdownParse from 'remark-parse';
|
||||
import markdownStringify from 'remark-stringify';
|
||||
import wikiLinkPlugin from 'remark-wiki-link';
|
||||
import frontmatterPlugin from 'remark-frontmatter';
|
||||
import shortHash from 'short-hash';
|
||||
import visit from 'unist-util-visit';
|
||||
import { Node, Parent, Point, Position } from 'unist';
|
||||
import GitHubSlugger from 'github-slugger';
|
||||
let processor: unified.Processor | null = null;
|
||||
|
||||
let stringifier = unified()
|
||||
.use(markdownStringify)
|
||||
.use(wikiLinkPlugin);
|
||||
|
||||
function parse(markdown: string): Node {
|
||||
processor =
|
||||
processor ||
|
||||
unified()
|
||||
.use(markdownParse, { gfm: true })
|
||||
.use(frontmatterPlugin, ['yaml'])
|
||||
.use(wikiLinkPlugin);
|
||||
return processor.parse(markdown);
|
||||
}
|
||||
|
||||
export interface FileBlock {
|
||||
text: string;
|
||||
type: string;
|
||||
hash?: string;
|
||||
sort: string;
|
||||
key: string;
|
||||
position: Position;
|
||||
}
|
||||
|
||||
export function getFileBlocks(markdown: string): FileBlock[] {
|
||||
let tree = parse(markdown);
|
||||
let blocks: FileBlock[] = [];
|
||||
let order = 0;
|
||||
visit(tree, node => {
|
||||
const block = getFileBlockFromNode(node, ++order);
|
||||
if (block) {
|
||||
blocks.push(block);
|
||||
}
|
||||
});
|
||||
|
||||
return blocks;
|
||||
}
|
||||
|
||||
let previouslySeenListItemLine = -1;
|
||||
function getFileBlockFromNode(node: Node, order: number): FileBlock | null {
|
||||
switch (node.type) {
|
||||
case 'heading': {
|
||||
const text = (node.children as any[])[0].value as string;
|
||||
return {
|
||||
text,
|
||||
key: new GitHubSlugger().slug(text),
|
||||
type: node.type,
|
||||
sort: order.toString().padStart(6, '0'),
|
||||
position: node.position!,
|
||||
};
|
||||
}
|
||||
|
||||
case 'listItem': {
|
||||
// @todo unhack
|
||||
previouslySeenListItemLine = node.position!.start.line;
|
||||
return null;
|
||||
}
|
||||
|
||||
case 'paragraph': {
|
||||
let text;
|
||||
let existingHash;
|
||||
let hashRegex = /\^(\w+)\s{0,}$/;
|
||||
|
||||
try {
|
||||
text = stringifier!.stringify(node);
|
||||
let matches = hashRegex.exec(text);
|
||||
if (matches) {
|
||||
existingHash = matches[1];
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Stringify failed', e.message, e, node);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const type = node.position!.start.line === previouslySeenListItemLine ? 'listItem' : 'paragraph';
|
||||
return {
|
||||
text,
|
||||
hash: existingHash,
|
||||
key: existingHash || shortHash(text),
|
||||
type,
|
||||
sort: order.toString().padStart(6, '0'),
|
||||
position: node.position!,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
423
packages/foam-language-server/src/index.ts
Normal file
423
packages/foam-language-server/src/index.ts
Normal file
@@ -0,0 +1,423 @@
|
||||
import {
|
||||
createConnection,
|
||||
ProposedFeatures,
|
||||
InitializeParams,
|
||||
DidChangeConfigurationNotification,
|
||||
CompletionItem,
|
||||
CompletionItemKind,
|
||||
TextDocuments,
|
||||
TextDocumentPositionParams,
|
||||
TextDocumentSyncKind,
|
||||
InsertTextFormat,
|
||||
InitializeResult,
|
||||
Position,
|
||||
TextDocumentContentChangeEvent,
|
||||
Range,
|
||||
} from 'vscode-languageserver';
|
||||
|
||||
import { TextDocument } from 'vscode-languageserver-textdocument';
|
||||
import { getWorkspaceFiles } from './workspace';
|
||||
import { getFileBlocks, FileBlock } from './file';
|
||||
|
||||
const innerUpdate = TextDocument.update;
|
||||
TextDocument.update = (document: TextDocument, changes: TextDocumentContentChangeEvent[], version: number) => {
|
||||
const updated = innerUpdate(document, changes, version);
|
||||
siphonOnDidChangeTextDocumentEvent(document, changes);
|
||||
return updated;
|
||||
}
|
||||
|
||||
const textDocumentManager = new TextDocuments(TextDocument);
|
||||
|
||||
let workspaceFiles: {
|
||||
title: string;
|
||||
fileName: string;
|
||||
filePath: string;
|
||||
target: string;
|
||||
preview: string;
|
||||
}[] = [];
|
||||
|
||||
// Create a connection for the server, using Node's IPC as a transport.
|
||||
// Also include all preview / proposed LSP features.
|
||||
let connection = createConnection(ProposedFeatures.all);
|
||||
|
||||
let hasConfigurationCapability: boolean = false;
|
||||
let hasWorkspaceFolderCapability: boolean = false;
|
||||
|
||||
connection.onInitialize((params: InitializeParams) => {
|
||||
let capabilities = params.capabilities;
|
||||
|
||||
// Does the client support the `workspace/configuration` request?
|
||||
// If not, we fall back using global settings.
|
||||
hasConfigurationCapability = !!(
|
||||
capabilities.workspace && !!capabilities.workspace.configuration
|
||||
);
|
||||
|
||||
hasWorkspaceFolderCapability = !!(
|
||||
capabilities.workspace && !!capabilities.workspace.workspaceFolders
|
||||
);
|
||||
|
||||
const result: InitializeResult = {
|
||||
capabilities: {
|
||||
textDocumentSync: TextDocumentSyncKind.Incremental,
|
||||
// Tell the client that this server supports code completion.
|
||||
executeCommandProvider: {
|
||||
commands: ['foam/insertHash'],
|
||||
},
|
||||
completionProvider: {
|
||||
triggerCharacters: ['[', '#'],
|
||||
resolveProvider: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (hasWorkspaceFolderCapability) {
|
||||
result.capabilities.workspace = {
|
||||
workspaceFolders: {
|
||||
supported: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
connection.onInitialized(async () => {
|
||||
if (hasConfigurationCapability) {
|
||||
// Register for all configuration changes.
|
||||
connection.client.register(
|
||||
DidChangeConfigurationNotification.type,
|
||||
undefined
|
||||
);
|
||||
}
|
||||
|
||||
if (hasWorkspaceFolderCapability) {
|
||||
const folders = await connection.workspace.getWorkspaceFolders();
|
||||
if (!folders || folders.length === 0) {
|
||||
console.error('No workspace folders found');
|
||||
} else {
|
||||
if (folders.length > 1) {
|
||||
console.log('Multi-root workspaces not yet supported');
|
||||
}
|
||||
|
||||
let folder = folders[0];
|
||||
console.log('Initializing workspace at ' + folder.uri);
|
||||
workspaceFiles = await getWorkspaceFiles(folder.uri);
|
||||
}
|
||||
}
|
||||
|
||||
textDocumentManager.onDidOpen(e => {
|
||||
console.log('onDidOpen', e.document.uri);
|
||||
});
|
||||
|
||||
textDocumentManager.onDidClose(e => {
|
||||
// console.log('onDidClose', e.document);
|
||||
});
|
||||
|
||||
textDocumentManager.onDidChangeContent(e => {
|
||||
console.log('onDidChange', e.document.uri);
|
||||
});
|
||||
|
||||
textDocumentManager.onWillSave(e => {
|
||||
// console.log('onWillSave', e.document, e.reason);
|
||||
});
|
||||
|
||||
textDocumentManager.onWillSaveWaitUntil((e, cancellationToken) => {
|
||||
// console.log('onWillSaveTextDocumentWaitUntil', e.document, e.reason);
|
||||
// Could return text edits here;
|
||||
return [];
|
||||
});
|
||||
|
||||
textDocumentManager.onDidSave(e => {
|
||||
// console.log('onDidSave', e.document);
|
||||
});
|
||||
});
|
||||
|
||||
connection.onDidChangeConfiguration(change => {
|
||||
if (hasConfigurationCapability) {
|
||||
// todo
|
||||
}
|
||||
});
|
||||
|
||||
connection.onDidChangeWatchedFiles(_change => {
|
||||
// Monitored files have change in VSCode
|
||||
connection.console.log('We received an file change event');
|
||||
});
|
||||
|
||||
connection.onExecuteCommand(async e => {
|
||||
if (e.command === 'foam/insertHash') {
|
||||
const {
|
||||
sourceUri,
|
||||
sourcePosition,
|
||||
targetUri,
|
||||
targetBlock,
|
||||
}: {
|
||||
sourceUri: string;
|
||||
sourcePosition: Position;
|
||||
targetUri: string;
|
||||
targetBlock: FileBlock;
|
||||
} = e.arguments![0];
|
||||
|
||||
const astPosition = targetBlock.position;
|
||||
const docPosition = {
|
||||
line: astPosition.end.line - 1,
|
||||
character: astPosition.end.column - 1,
|
||||
};
|
||||
|
||||
const result = await connection.workspace.applyEdit({
|
||||
label: 'Insert generated hash',
|
||||
edit: {
|
||||
changes: {
|
||||
[targetUri]: [
|
||||
{
|
||||
newText: ` ^${targetBlock.key}`,
|
||||
range: {
|
||||
start: docPosition,
|
||||
end: docPosition,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (result.applied) {
|
||||
activeHashEdit = {
|
||||
sourceUri: sourceUri,
|
||||
sourceRange: {
|
||||
start: sourcePosition,
|
||||
end: {
|
||||
...sourcePosition,
|
||||
character: sourcePosition.character + targetBlock.key.length,
|
||||
},
|
||||
},
|
||||
targetUri: targetUri,
|
||||
targetRange: {
|
||||
start: { ...docPosition, character: docPosition.character },
|
||||
end: {
|
||||
...docPosition,
|
||||
character: docPosition.character + targetBlock.key.length + 2, // +" ^"
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
console.log(
|
||||
'foam/insertHash',
|
||||
sourceUri,
|
||||
sourcePosition,
|
||||
targetUri,
|
||||
targetBlock
|
||||
);
|
||||
} else {
|
||||
console.log('unrecognized command', e.command, e.arguments);
|
||||
}
|
||||
});
|
||||
|
||||
function getAvailableSections(
|
||||
uri: string,
|
||||
position: Position
|
||||
): CompletionItem[] {
|
||||
const targetUri =
|
||||
'file:///Users/swanson/dev/foam-link-completion-demo/Principles.md';
|
||||
const document = textDocumentManager.get(targetUri);
|
||||
const blocks = getFileBlocks(document!.getText());
|
||||
|
||||
// try to find the end brackets, so we can remove them and add them
|
||||
// back on the other side of our cursor, so when the insertion is
|
||||
// completed, the text cursor will remain on the outside of the brackets
|
||||
|
||||
// const sections = getMockSections();
|
||||
// const document = textDocumentManager.get(uri);
|
||||
// if (document) {
|
||||
// const endBracketRange = {
|
||||
// start: position,
|
||||
// end: { ...position, character: position.character + 2 },
|
||||
// };
|
||||
|
||||
// if (document.getText(endBracketRange) == ']]') {
|
||||
// return sections.map((section) => ({
|
||||
// label: `#${section}`,
|
||||
// kind: CompletionItemKind.Text,
|
||||
// insertTextFormat: InsertTextFormat.Snippet,
|
||||
// textEdit: {
|
||||
// newText: '${1:' + shortHash(section) + '}]] $0',
|
||||
// range: endBracketRange
|
||||
// },
|
||||
// }));
|
||||
// }
|
||||
// }
|
||||
|
||||
const kindsByType: { [x: string]: CompletionItemKind } = {
|
||||
heading: CompletionItemKind.Folder,
|
||||
paragraph: CompletionItemKind.Text,
|
||||
listItem: CompletionItemKind.Keyword,
|
||||
};
|
||||
|
||||
// no luck locating end brocket range, just inject the value
|
||||
// and let user handle in manually
|
||||
return blocks.map(block => {
|
||||
const labelPrefix = block.type === 'heading' ? '# ' : '';
|
||||
const insertNewHash = block.type !== 'heading' && !block.hash;
|
||||
return {
|
||||
label: `${labelPrefix}${block.text}`,
|
||||
detail: block.key,
|
||||
documentation: {
|
||||
kind: 'markdown',
|
||||
value: block.text,
|
||||
},
|
||||
kind: kindsByType[block.type],
|
||||
sortText: block.sort.toString(),
|
||||
filterText: block.text,
|
||||
insertText: insertNewHash ? '${1:' + block.key + '}$0' : `${block.key}$0`,
|
||||
insertTextFormat: InsertTextFormat.Snippet,
|
||||
command: insertNewHash
|
||||
? {
|
||||
title: 'Insert Hash',
|
||||
command: 'foam/insertHash',
|
||||
arguments: [
|
||||
{
|
||||
sourceUri: uri,
|
||||
sourcePosition: position,
|
||||
targetUri: targetUri,
|
||||
targetBlock: block,
|
||||
},
|
||||
],
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function isRequestAtWikiLinkStartBlock(
|
||||
uri: string,
|
||||
position: Position
|
||||
): boolean {
|
||||
|
||||
// find the currently open document
|
||||
const document = textDocumentManager.get(uri);
|
||||
if (!document) {
|
||||
console.log('document not found by uri', uri);
|
||||
return false;
|
||||
}
|
||||
|
||||
// get the preceding three character range
|
||||
const text = document.getText({
|
||||
start: { line: position.line, character: position.character - 3 },
|
||||
end: position,
|
||||
});
|
||||
|
||||
// we get three characters, so that we can trigger autocompletion in following cases:
|
||||
// 1. when user types the second "[", the range is " [[", and we show all files
|
||||
// 2. when user has cleared existing link content and types the first character
|
||||
// of the new content, e.g. "[[S".
|
||||
return text.includes('[[');
|
||||
}
|
||||
|
||||
// This handler provides the initial list of the completion items.
|
||||
connection.onCompletion(
|
||||
(_textDocumentPosition: TextDocumentPositionParams): CompletionItem[] => {
|
||||
console.log('onCompletion.textDocumentPosition', _textDocumentPosition);
|
||||
|
||||
const documentContext = (_textDocumentPosition as any).context;
|
||||
if (documentContext && documentContext.triggerCharacter === '#') {
|
||||
return getAvailableSections(
|
||||
_textDocumentPosition.textDocument.uri,
|
||||
_textDocumentPosition.position
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
isRequestAtWikiLinkStartBlock(
|
||||
_textDocumentPosition.textDocument.uri,
|
||||
_textDocumentPosition.position
|
||||
)
|
||||
) {
|
||||
return workspaceFiles.map(document => ({
|
||||
label: document.title,
|
||||
detail: document.fileName,
|
||||
documentation: document.preview,
|
||||
commitCharacters: ['#', '|'],
|
||||
kind: CompletionItemKind.Text,
|
||||
insertTextFormat: InsertTextFormat.Snippet,
|
||||
textEdit: {
|
||||
newText: document.target + '$1]] $0',
|
||||
range: {
|
||||
start: _textDocumentPosition.position,
|
||||
end: {
|
||||
..._textDocumentPosition.position,
|
||||
character: _textDocumentPosition.position.character + 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
);
|
||||
|
||||
connection.onCompletionResolve(item => {
|
||||
return item;
|
||||
});
|
||||
|
||||
textDocumentManager.listen(connection);
|
||||
|
||||
// Listen on the connection
|
||||
connection.listen();
|
||||
|
||||
|
||||
let activeHashEdit: {
|
||||
sourceUri: string;
|
||||
sourceRange: Range;
|
||||
targetUri: string;
|
||||
targetRange: Range;
|
||||
} | null = null;
|
||||
|
||||
|
||||
function siphonOnDidChangeTextDocumentEvent(document: TextDocument, changes: TextDocumentContentChangeEvent[]) {
|
||||
if (!activeHashEdit) return;
|
||||
if (document.uri !== activeHashEdit.sourceUri) return;
|
||||
if (changes.length !== 1) return;
|
||||
|
||||
const edit = activeHashEdit;
|
||||
const text = changes[0].text;
|
||||
const range = (changes[0] as any).range as Range;
|
||||
|
||||
if (!range) return;
|
||||
|
||||
if (
|
||||
range.start.line === range.end.line &&
|
||||
range.start.line === edit.sourceRange.start.line &&
|
||||
range.end.line === edit.sourceRange.end.line &&
|
||||
range.start.character >= edit.sourceRange.start.character &&
|
||||
range.end.character <= edit.sourceRange.end.character + 1
|
||||
) {
|
||||
const val = document.getText(edit.sourceRange);
|
||||
const hash = val.split(']')[0];
|
||||
|
||||
console.log('Text at edit point', hash);
|
||||
|
||||
if (range.end.character == edit.sourceRange.end.character + 1) {
|
||||
edit.sourceRange.end.character += 1;
|
||||
edit.targetRange.end.character += 1;
|
||||
activeHashEdit = edit;
|
||||
}
|
||||
|
||||
connection.workspace.applyEdit({
|
||||
label: 'Update generated hash',
|
||||
edit: {
|
||||
changes: {
|
||||
[edit.targetUri]: [
|
||||
{
|
||||
newText: hash.length === 0 ? '' : ` ^${hash}`,
|
||||
range: edit.targetRange,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
} else {
|
||||
activeHashEdit = null;
|
||||
}
|
||||
};
|
||||
23
packages/foam-language-server/src/workspace.ts
Normal file
23
packages/foam-language-server/src/workspace.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import glob from 'glob';
|
||||
import uriToPath from 'file-uri-to-path';
|
||||
import { promisify } from 'util';
|
||||
import { extname, basename, relative } from 'path';
|
||||
|
||||
const findAllFiles = promisify(glob);
|
||||
|
||||
export async function getWorkspaceFiles(workspaceUri: string) {
|
||||
let path = uriToPath(workspaceUri);
|
||||
if (path.substr(-1) === '/') {
|
||||
path = path.slice(0, -1);
|
||||
}
|
||||
|
||||
const files = await findAllFiles(`${path}/**/*.md`, {});
|
||||
|
||||
return files.map(filePath => {
|
||||
const title = basename(filePath, extname(filePath));
|
||||
const fileName = relative(path, filePath);
|
||||
const ext = extname(filePath);
|
||||
const target = fileName.slice(0, -ext.length);
|
||||
return { title, filePath, fileName, target, preview: ''};
|
||||
})
|
||||
}
|
||||
8
packages/foam-language-server/tsconfig.build.json
Normal file
8
packages/foam-language-server/tsconfig.build.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "es6",
|
||||
"sourceMap": true,
|
||||
"inlineSourceMap": false
|
||||
}
|
||||
}
|
||||
31
packages/foam-language-server/tsconfig.json
Normal file
31
packages/foam-language-server/tsconfig.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src",
|
||||
"composite": true,
|
||||
"esModuleInterop": true,
|
||||
"importHelpers": true,
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"lib": [
|
||||
"esnext"
|
||||
],
|
||||
|
||||
// @NOTE regarding inlineSourceMap:
|
||||
//
|
||||
// Preferrably we would use "sourceMap": true to generate
|
||||
// *.map.js files instead of inline base64 encoded source maps,
|
||||
// however for some reason the VSCode Node debugger looks
|
||||
// for source maps in the src/ directory instead of dist/ directory,
|
||||
// causing the VSCode debugging experience to break.
|
||||
"inlineSourceMap": true,
|
||||
},
|
||||
"include": ["src", "types"],
|
||||
"exclude": ["node_modules"],
|
||||
"references": [{
|
||||
"path": "../foam-core"
|
||||
}]
|
||||
}
|
||||
1
packages/foam-language-server/types/remark-wiki-link.d.ts
vendored
Normal file
1
packages/foam-language-server/types/remark-wiki-link.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
declare module 'remark-wiki-link';
|
||||
4
packages/foam-language-server/types/short-hash.d.ts
vendored
Normal file
4
packages/foam-language-server/types/short-hash.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
declare module 'short-hash' {
|
||||
function shortHash(input: string): string;
|
||||
export = shortHash;
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
"curly": "warn",
|
||||
"eqeqeq": "warn",
|
||||
"no-throw-literal": "warn",
|
||||
"semi": "off"
|
||||
"semi": "off",
|
||||
"require-await": "warn"
|
||||
}
|
||||
}
|
||||
|
||||
7
packages/foam-vscode/.vscode/extensions.json
vendored
7
packages/foam-vscode/.vscode/extensions.json
vendored
@@ -1,7 +0,0 @@
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint"
|
||||
]
|
||||
}
|
||||
40
packages/foam-vscode/.vscode/launch.json
vendored
40
packages/foam-vscode/.vscode/launch.json
vendored
@@ -1,40 +0,0 @@
|
||||
// A launch configuration that compiles the extension and then opens it inside a new window
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
// This task is also defined in ${workspaceFolder}/.vscode/launch.json
|
||||
// for when running in a monorepo environment
|
||||
"name": "Run Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "${defaultBuildTask}"
|
||||
},
|
||||
{
|
||||
// This task is also defined in ${workspaceFolder}/.vscode/launch.json
|
||||
// for when running in a monorepo environment
|
||||
"name": "Extension Tests",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}",
|
||||
"--extensionTestsPath=${workspaceFolder}/out/test/suite/index"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/test/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "${defaultBuildTask}"
|
||||
}
|
||||
]
|
||||
}
|
||||
11
packages/foam-vscode/.vscode/settings.json
vendored
11
packages/foam-vscode/.vscode/settings.json
vendored
@@ -1,11 +0,0 @@
|
||||
// Place your settings in this file to overwrite default and user settings.
|
||||
{
|
||||
"files.exclude": {
|
||||
"out": false // set this to true to hide the "out" folder with the compiled JS files
|
||||
},
|
||||
"search.exclude": {
|
||||
"out": true // set this to false to include "out" folder in search results
|
||||
},
|
||||
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
|
||||
"typescript.tsc.autoDetect": "off"
|
||||
}
|
||||
22
packages/foam-vscode/.vscode/tasks.json
vendored
22
packages/foam-vscode/.vscode/tasks.json
vendored
@@ -1,22 +0,0 @@
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
// This task is also defined in ${workspaceFolder}/.vscode/tasks.json
|
||||
// for when running in a monorepo environment
|
||||
"type": "npm",
|
||||
"script": "watch",
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"isBackground": true,
|
||||
"presentation": {
|
||||
"reveal": "never"
|
||||
},
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
16
packages/foam-vscode/__mocks__/vscode.ts
Normal file
16
packages/foam-vscode/__mocks__/vscode.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
Note: this is needed in order to test certain parts
|
||||
of functionality of `foam-vscode`
|
||||
|
||||
Following the advice from this article:
|
||||
https://www.richardkotze.com/coding/unit-test-mock-vs-code-extension-api-jest
|
||||
|
||||
combined with advice from this GitHub issue comment:
|
||||
https://github.com/microsoft/vscode-test/issues/37#issuecomment-584744386
|
||||
*/
|
||||
|
||||
const vscode = {
|
||||
// Add values and methods as needed for tests
|
||||
};
|
||||
|
||||
module.exports = vscode;
|
||||
6
packages/foam-vscode/babel.config.js
Normal file
6
packages/foam-vscode/babel.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
["@babel/preset-env", { targets: { node: "current" } }],
|
||||
"@babel/preset-typescript"
|
||||
]
|
||||
};
|
||||
188
packages/foam-vscode/jest.config.js
Normal file
188
packages/foam-vscode/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,
|
||||
};
|
||||
@@ -88,11 +88,12 @@
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf out",
|
||||
"build": "tsc -p ./",
|
||||
"test": "echo No tests in VSCode extensions yet",
|
||||
"test": "jest",
|
||||
"lint": "eslint src --ext ts",
|
||||
"watch": "tsc -watch -p ./",
|
||||
"clean": "tsc --build ./tsconfig.json ../foam-core/tsconfig.json ../foam-language-server/tsconfig.json --clean",
|
||||
"watch": "tsc --build ./tsconfig.json ../foam-core/tsconfig.json ../foam-language-server/tsconfig.json --watch",
|
||||
"vscode:start-debugging": "yarn clean && yarn watch",
|
||||
"vscode:prepublish": "yarn npm-install && yarn run build",
|
||||
"npm-install": "rimraf node_modules && npm i",
|
||||
"npm-cleanup": "rimraf package-lock.json node_modules && yarn",
|
||||
@@ -100,20 +101,26 @@
|
||||
"publish-extension": "npx vsce publish && yarn npm-cleanup"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.11.0",
|
||||
"@babel/preset-env": "^7.11.0",
|
||||
"@babel/preset-typescript": "^7.10.4",
|
||||
"@types/dateformat": "^3.0.1",
|
||||
"@types/glob": "^7.1.1",
|
||||
"@types/node": "^13.11.0",
|
||||
"@types/vscode": "^1.45.1",
|
||||
"@typescript-eslint/eslint-plugin": "^2.30.0",
|
||||
"@typescript-eslint/parser": "^2.30.0",
|
||||
"babel-jest": "^26.2.2",
|
||||
"eslint": "^6.8.0",
|
||||
"glob": "^7.1.6",
|
||||
"jest": "^26.2.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^3.8.3",
|
||||
"vscode-test": "^1.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"dateformat": "^3.0.3",
|
||||
"foam-core": "0.3.0"
|
||||
"foam-core": "0.3.0",
|
||||
"vscode-languageclient": "^6.1.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,14 @@ export function activate(context: ExtensionContext) {
|
||||
});
|
||||
}
|
||||
|
||||
export function deactivate() {
|
||||
export async function deactivate(): Promise<void> {
|
||||
workspaceWatcher?.dispose();
|
||||
|
||||
// call all deactivate functions and if any of them
|
||||
// return promises, wait for their completion
|
||||
await Promise.all(features.map(
|
||||
f => (f.deactivate && f.deactivate()) || Promise.resolve()
|
||||
));
|
||||
}
|
||||
|
||||
function isLocalMarkdownFile(uri: Uri) {
|
||||
@@ -69,7 +75,7 @@ const bootstrap = async (config: FoamConfig) => {
|
||||
true,
|
||||
true
|
||||
);
|
||||
|
||||
|
||||
workspaceWatcher.onDidCreate(uri => {
|
||||
if (isLocalMarkdownFile(uri)) {
|
||||
addFile(uri).then(() => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import createReferences from "./wikilink-reference-generation";
|
||||
import openDailyNote from "./open-daily-note";
|
||||
import janitor from "./janitor";
|
||||
import languageServerClient from "./language-server-client";
|
||||
import { FoamFeature } from "../types";
|
||||
|
||||
export const features: FoamFeature[] = [createReferences, openDailyNote, janitor];
|
||||
export const features: FoamFeature[] = [/*createReferences, */languageServerClient, openDailyNote, janitor];
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
workspace,
|
||||
ExtensionContext,
|
||||
commands,
|
||||
Position,
|
||||
Range
|
||||
Range,
|
||||
ProgressLocation
|
||||
} from "vscode";
|
||||
import fs = require("fs");
|
||||
import { FoamFeature } from "../types";
|
||||
@@ -12,14 +12,14 @@ import {
|
||||
applyTextEdit,
|
||||
generateLinkReferences,
|
||||
generateHeading,
|
||||
Foam,
|
||||
Note
|
||||
Foam
|
||||
} from "foam-core";
|
||||
|
||||
import { includeExtensions } from "../settings";
|
||||
import { astPositionToVsCodePosition } from "../utils";
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: async (context: ExtensionContext, foamPromise: Promise<Foam>) => {
|
||||
activate: (context: ExtensionContext, foamPromise: Promise<Foam>) => {
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand("foam-vscode.janitor", async () =>
|
||||
janitor(await foamPromise)
|
||||
@@ -30,31 +30,41 @@ const feature: FoamFeature = {
|
||||
|
||||
async function janitor(foam: Foam) {
|
||||
try {
|
||||
const outcome = await runJanitor(foam);
|
||||
if (outcome.processedFileCount === 0) {
|
||||
window.showInformationMessage(
|
||||
"Foam Janitor didn't file any notes to clean up"
|
||||
const noOfFiles = foam.notes.getNotes().filter(Boolean).length;
|
||||
|
||||
if (noOfFiles === 0) {
|
||||
return window.showInformationMessage(
|
||||
"Foam Janitor didn't find any notes to clean up."
|
||||
);
|
||||
} else if (!outcome.changedAnyFiles) {
|
||||
}
|
||||
|
||||
const outcome = await window.withProgress(
|
||||
{
|
||||
location: ProgressLocation.Notification,
|
||||
title: `Running Foam Janitor across ${noOfFiles} files!`
|
||||
},
|
||||
() => runJanitor(foam)
|
||||
);
|
||||
|
||||
if (!outcome.changedAnyFiles) {
|
||||
window.showInformationMessage(
|
||||
`Foam Janitor checked ${outcome.processedFileCount} files, and found nothing to clean up!`
|
||||
`Foam Janitor checked ${noOfFiles} files, and found nothing to clean up!`
|
||||
);
|
||||
} else {
|
||||
window.showInformationMessage(
|
||||
`Foam Janitor checked ${outcome.processedFileCount} files and updated ${outcome.updatedDefinitionListCount} out-of-date definition lists and added ${outcome.updatedHeadingCount} missing headings. Please check the changes before committing them into version control!`
|
||||
`Foam Janitor checked ${noOfFiles} files and updated ${outcome.updatedDefinitionListCount} out-of-date definition lists and added ${outcome.updatedHeadingCount} missing headings. Please check the changes before committing them into version control!`
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
window.showErrorMessage(`Foam Janitor attempted to clean your workspace but ran into an error. Please check that we didn't break anything before committing any changes to version control, and pass the following error message to the Foam team on GitHub issues:
|
||||
${e.message}
|
||||
${e.stackTrace}`);
|
||||
${e.stack}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function runJanitor(foam: Foam) {
|
||||
const notes = foam.notes.getNotes().filter(Boolean);
|
||||
|
||||
let processedFileCount = 0;
|
||||
let updatedHeadingCount = 0;
|
||||
let updatedDefinitionListCount = 0;
|
||||
|
||||
@@ -69,22 +79,19 @@ async function runJanitor(foam: Foam) {
|
||||
dirtyTextDocument => dirtyTextDocument.fileName
|
||||
);
|
||||
|
||||
const dirtyNotes: Note[] = notes.filter(note =>
|
||||
dirtyEditorsFileName.includes(note.path)
|
||||
const dirtyNotes = notes.filter(note =>
|
||||
dirtyEditorsFileName.includes(note.source.uri)
|
||||
);
|
||||
|
||||
const nonDirtyNotes: Note[] = notes.filter(
|
||||
note => !dirtyEditorsFileName.includes(note.path)
|
||||
const nonDirtyNotes = notes.filter(
|
||||
note => !dirtyEditorsFileName.includes(note.source.uri)
|
||||
);
|
||||
|
||||
// Apply Text Edits to Non Dirty Notes using fs module just like CLI
|
||||
|
||||
const fileWritePromises = nonDirtyNotes.map(note => {
|
||||
processedFileCount += 1;
|
||||
|
||||
let heading = generateHeading(note);
|
||||
if (heading) {
|
||||
console.log("fs.write heading " + note.path + " " + note.title);
|
||||
updatedHeadingCount += 1;
|
||||
}
|
||||
|
||||
@@ -104,11 +111,11 @@ async function runJanitor(foam: Foam) {
|
||||
// Apply Edits
|
||||
// Note: The ordering matters. Definitions need to be inserted
|
||||
// before heading, since inserting a heading changes line numbers below
|
||||
let text = note.source;
|
||||
let text = note.source.text;
|
||||
text = definitions ? applyTextEdit(text, definitions) : text;
|
||||
text = heading ? applyTextEdit(text, heading) : text;
|
||||
|
||||
return fs.promises.writeFile(note.path, text);
|
||||
return fs.promises.writeFile(note.source.uri, text);
|
||||
});
|
||||
|
||||
await Promise.all(fileWritePromises);
|
||||
@@ -116,10 +123,10 @@ async function runJanitor(foam: Foam) {
|
||||
// Handle dirty editors in serial, as VSCode only allows
|
||||
// edits to be applied to active text editors
|
||||
for (const doc of dirtyTextDocuments) {
|
||||
processedFileCount += 1;
|
||||
|
||||
const editor = await window.showTextDocument(doc);
|
||||
const note = dirtyNotes.find(n => n.path === editor.document.fileName);
|
||||
const note = dirtyNotes.find(
|
||||
n => n.source.uri === editor.document.fileName
|
||||
);
|
||||
|
||||
// Get edits
|
||||
const heading = generateHeading(note);
|
||||
@@ -136,27 +143,17 @@ async function runJanitor(foam: Foam) {
|
||||
// before heading, since inserting a heading changes line numbers below
|
||||
if (definitions) {
|
||||
updatedDefinitionListCount += 1;
|
||||
const start = new Position(
|
||||
definitions.range.start.line - 1,
|
||||
definitions.range.start.column
|
||||
);
|
||||
const end = new Position(
|
||||
definitions.range.end.line - 1,
|
||||
definitions.range.end.column
|
||||
);
|
||||
const start = astPositionToVsCodePosition(definitions.range.start);
|
||||
const end = astPositionToVsCodePosition(definitions.range.end);
|
||||
|
||||
const range = new Range(start, end);
|
||||
editBuilder.replace(range, definitions!.newText);
|
||||
}
|
||||
|
||||
if (heading) {
|
||||
console.log("editor.write heading " + note.title);
|
||||
|
||||
updatedHeadingCount += 1;
|
||||
const start = new Position(
|
||||
heading.range.start.line,
|
||||
heading.range.start.column
|
||||
);
|
||||
editBuilder.insert(start, heading.newText);
|
||||
const start = astPositionToVsCodePosition(heading.range.start);
|
||||
editBuilder.replace(start, heading.newText);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -165,7 +162,6 @@ async function runJanitor(foam: Foam) {
|
||||
return {
|
||||
updatedHeadingCount,
|
||||
updatedDefinitionListCount,
|
||||
processedFileCount,
|
||||
changedAnyFiles: updatedHeadingCount + updatedDefinitionListCount
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import * as path from "path";
|
||||
import { workspace, ExtensionContext } from "vscode";
|
||||
import { FoamFeature } from "../../types";
|
||||
|
||||
import {
|
||||
LanguageClient,
|
||||
LanguageClientOptions,
|
||||
ServerOptions,
|
||||
TransportKind
|
||||
} from "vscode-languageclient";
|
||||
|
||||
let client: LanguageClient;
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: (context: ExtensionContext) => {
|
||||
// @TODO figure out path for production deploys
|
||||
let serverModule = context.asAbsolutePath(
|
||||
path.join("..", "foam-language-server", "dist", "index.js")
|
||||
);
|
||||
|
||||
// The debug options for the server
|
||||
// --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging
|
||||
let debugOptions = { execArgv: ["--nolazy", "--inspect=6009"] };
|
||||
|
||||
// If the extension is launched in debug mode then the debug server options are used
|
||||
// Otherwise the run options are used
|
||||
let serverOptions: ServerOptions = {
|
||||
run: { module: serverModule, transport: TransportKind.ipc },
|
||||
debug: {
|
||||
module: serverModule,
|
||||
transport: TransportKind.ipc,
|
||||
options: debugOptions
|
||||
}
|
||||
};
|
||||
|
||||
// Options to control the language client
|
||||
let clientOptions: LanguageClientOptions = {
|
||||
// Register the server for plain markdown documents
|
||||
documentSelector: [
|
||||
{ scheme: "file", language: "markdown" },
|
||||
{ scheme: "file", language: "mdx" }
|
||||
],
|
||||
synchronize: {
|
||||
fileEvents: workspace.createFileSystemWatcher(
|
||||
"**/.vscode/settings.json"
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// Create the language client and start the client.
|
||||
client = new LanguageClient(
|
||||
"foam-language-server",
|
||||
"Foam Language Server",
|
||||
serverOptions,
|
||||
clientOptions
|
||||
);
|
||||
|
||||
console.log(
|
||||
"Starting foam-language-server with options\n",
|
||||
JSON.stringify(serverOptions, null, 2)
|
||||
);
|
||||
|
||||
// Start the client. This will also launch the server
|
||||
client.start();
|
||||
},
|
||||
deactivate() {
|
||||
if (client) {
|
||||
console.log("Stopping foam-language-server");
|
||||
return client.stop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default feature;
|
||||
@@ -14,7 +14,7 @@ import { FoamFeature } from "../types";
|
||||
import { docConfig } from '../utils';
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: async (context: ExtensionContext) => {
|
||||
activate: (context: ExtensionContext) => {
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand("foam-vscode.open-daily-note", openDailyNote)
|
||||
);
|
||||
@@ -63,7 +63,7 @@ async function createDailyNoteIfNotExists(
|
||||
return false;
|
||||
}
|
||||
|
||||
createDailyNoteDirectoryIfNotExists(dailyNotePath);
|
||||
await createDailyNoteDirectoryIfNotExists(dailyNotePath);
|
||||
|
||||
const titleFormat: string =
|
||||
configuration.get("openDailyNote.titleFormat") ??
|
||||
@@ -97,7 +97,7 @@ async function focusDailyNote(dailyNotePath: string, isNewNote: boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
async function pathExists(path: string) {
|
||||
function pathExists(path: string) {
|
||||
return fs.promises
|
||||
.access(path, fs.constants.F_OK)
|
||||
.then(() => true)
|
||||
|
||||
@@ -85,9 +85,9 @@ async function createReferenceList(foam: NoteGraph) {
|
||||
|
||||
let refs = await generateReferenceList(foam, editor.document);
|
||||
if (refs && refs.length) {
|
||||
await editor.edit(function (editBuilder) {
|
||||
await editor.edit(function(editBuilder) {
|
||||
if (editor) {
|
||||
const spacing = hasEmptyTrailing
|
||||
const spacing = hasEmptyTrailing(editor.document)
|
||||
? docConfig.eol
|
||||
: docConfig.eol + docConfig.eol;
|
||||
|
||||
@@ -115,7 +115,7 @@ async function updateReferenceList(foam: NoteGraph) {
|
||||
if (!range) {
|
||||
await createReferenceList(foam);
|
||||
} else {
|
||||
const refs = await generateReferenceList(foam, doc);
|
||||
const refs = generateReferenceList(foam, doc);
|
||||
|
||||
// references must always be preceded by an empty line
|
||||
const spacing = doc.lineAt(range.start.line - 1).isEmptyOrWhitespace
|
||||
@@ -128,17 +128,22 @@ async function updateReferenceList(foam: NoteGraph) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function generateReferenceList(
|
||||
foam: NoteGraph,
|
||||
doc: TextDocument
|
||||
): Promise<string[]> {
|
||||
function generateReferenceList(foam: NoteGraph, doc: TextDocument): string[] {
|
||||
const filePath = doc.fileName;
|
||||
|
||||
const id = dropExtension(basename(filePath));
|
||||
const note = foam.getNoteByURI(filePath);
|
||||
|
||||
// Should never happen as `doc` is usually given by `editor.document`, which
|
||||
// binds to an opened note.
|
||||
if (!note) {
|
||||
console.warn(
|
||||
`Can't find note for URI ${filePath} before attempting to generate its markdown reference list`
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
const references = uniq(
|
||||
createMarkdownReferences(foam, id, includeExtensions()).map(
|
||||
createMarkdownReferences(foam, note.id, includeExtensions()).map(
|
||||
stringifyMarkdownLinkReferenceDefinition
|
||||
)
|
||||
);
|
||||
@@ -202,20 +207,19 @@ class WikilinkReferenceCodeLensProvider implements CodeLensProvider {
|
||||
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);
|
||||
const refs = generateReferenceList(this.foam, document);
|
||||
const oldRefs = getText(range).replace(/\r?\n|\r/g, docConfig.eol);
|
||||
const newRefs = refs.join(docConfig.eol);
|
||||
|
||||
let status = oldRefs === newRefs ? "up to date" : "out of date";
|
||||
let status = oldRefs === newRefs ? "up to date" : "out of date";
|
||||
|
||||
return [
|
||||
new CodeLens(range, {
|
||||
arguments: [],
|
||||
title: `Link references (${status})`,
|
||||
command: ""
|
||||
})
|
||||
];
|
||||
});
|
||||
return [
|
||||
new CodeLens(range, {
|
||||
arguments: [],
|
||||
title: `Link references (${status})`,
|
||||
command: ""
|
||||
})
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
3
packages/foam-vscode/src/types.d.ts
vendored
3
packages/foam-vscode/src/types.d.ts
vendored
@@ -2,5 +2,6 @@ import { ExtensionContext } from "vscode";
|
||||
import { Foam } from "foam-core";
|
||||
|
||||
export interface FoamFeature {
|
||||
activate: (context: ExtensionContext, foamPromise: Promise<Foam>) => void
|
||||
activate: (context: ExtensionContext, foamPromise: Promise<Foam>) => void,
|
||||
deactivate?: () => Promise<void>
|
||||
}
|
||||
|
||||
@@ -1,4 +1,17 @@
|
||||
import { EndOfLine, Range, TextDocument, window, Position, TextEditor } from "vscode";
|
||||
import {
|
||||
EndOfLine,
|
||||
Range,
|
||||
TextDocument,
|
||||
window,
|
||||
Position,
|
||||
TextEditor
|
||||
} from "vscode";
|
||||
|
||||
interface Point {
|
||||
line: number;
|
||||
column: number;
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
export const docConfig = { tab: " ", eol: "\r\n" };
|
||||
|
||||
@@ -69,3 +82,12 @@ export function dropExtension(path: string): string {
|
||||
parts.pop();
|
||||
return parts.join(".");
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param point ast position (1-indexed)
|
||||
* @returns VSCode position (0-indexed)
|
||||
*/
|
||||
export const astPositionToVsCodePosition = (point: Point): Position => {
|
||||
return new Position(point.line - 1, point.column - 1);
|
||||
};
|
||||
|
||||
10
packages/foam-vscode/test/utils.test.ts
Normal file
10
packages/foam-vscode/test/utils.test.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
// @note: This will fail due to utils importing 'vscode'
|
||||
// which needs to be mocked in the jest test environment.
|
||||
// See: https://github.com/microsoft/vscode-test/issues/37
|
||||
import { dropExtension } from '../src/utils';
|
||||
|
||||
describe("dropExtension", () => {
|
||||
test("returns file name without extension", () => {
|
||||
expect(dropExtension('file.md')).toEqual('file');
|
||||
});
|
||||
});
|
||||
@@ -1,22 +1,18 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"target": "es6",
|
||||
"esModuleInterop": true,
|
||||
"outDir": "out",
|
||||
"lib": ["es6"],
|
||||
"sourceMap": true,
|
||||
"rootDir": "src",
|
||||
"strict": false /* enable all strict type-checking options */
|
||||
/* Additional Checks */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
"strict": false
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", ".vscode-test"],
|
||||
"references": [{
|
||||
"path": "../foam-core/tsconfig.json"
|
||||
"path": "../foam-core"
|
||||
}, {
|
||||
"path": "../foam-language-server"
|
||||
}]
|
||||
}
|
||||
|
||||
20
readme.md
20
readme.md
@@ -7,7 +7,7 @@
|
||||
# Foam
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
[](#contributors-)
|
||||
[](#contributors-)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
**Foam** is a personal knowledge management and sharing system inspired by [Roam Research](https://roamresearch.com/), built on [Visual Studio Code](https://code.visualstudio.com/) and [GitHub](https://github.com/).
|
||||
@@ -43,7 +43,7 @@ Quick links to next documentation sections
|
||||
|
||||
## License
|
||||
|
||||
Foam is licensed under the [MIT license](license).
|
||||
Foam is licensed under the [MIT license](LICENSE).
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[wiki-links]: wiki-links "Wiki Links"
|
||||
@@ -79,7 +79,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<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://anku.netlify.com/"><img src="https://avatars1.githubusercontent.com/u/22813027?v=4" width="60px;" alt=""/><br /><sub><b>Ankit Tiwari</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=anku255" title="Documentation">📖</a> <a href="https://github.com/foambubble/foam/commits?author=anku255" title="Tests">⚠️</a> <a href="https://github.com/foambubble/foam/commits?author=anku255" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ayushbaweja"><img src="https://avatars1.githubusercontent.com/u/44344063?v=4" width="60px;" alt=""/><br /><sub><b>Ayush Baweja</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ayushbaweja" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/TaiChi-IO"><img src="https://avatars3.githubusercontent.com/u/65092992?v=4" width="60px;" alt=""/><br /><sub><b>TaiChi-IO</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=TaiChi-IO" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/juanfrank77"><img src="https://avatars1.githubusercontent.com/u/12146882?v=4" width="60px;" alt=""/><br /><sub><b>Juan F Gonzalez </b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=juanfrank77" title="Documentation">📖</a></td>
|
||||
@@ -99,6 +99,20 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<td align="center"><a href="http://johnlindquist.com"><img src="https://avatars0.githubusercontent.com/u/36073?v=4" width="60px;" alt=""/><br /><sub><b>John Lindquist</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=johnlindquist" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ashwin.run/"><img src="https://avatars2.githubusercontent.com/u/1689183?v=4" width="60px;" alt=""/><br /><sub><b>Ashwin Ramaswami</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=epicfaace" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Klaudioz"><img src="https://avatars1.githubusercontent.com/u/632625?v=4" width="60px;" alt=""/><br /><sub><b>Claudio Canales</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Klaudioz" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/vitaly-pevgonen"><img src="https://avatars0.githubusercontent.com/u/6272738?v=4" width="60px;" alt=""/><br /><sub><b>vitaly-pevgonen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=vitaly-pevgonen" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://dshemetov.github.io"><img src="https://avatars0.githubusercontent.com/u/1810426?v=4" width="60px;" alt=""/><br /><sub><b>Dmitry Shemetov</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dshemetov" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/hooncp"><img src="https://avatars1.githubusercontent.com/u/48883554?v=4" width="60px;" alt=""/><br /><sub><b>hooncp</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=hooncp" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://rt-canada.ca"><img src="https://avatars1.githubusercontent.com/u/13721239?v=4" width="60px;" alt=""/><br /><sub><b>Martin Laws</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=martinlaws" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://seanksmith.me"><img src="https://avatars3.githubusercontent.com/u/2085441?v=4" width="60px;" alt=""/><br /><sub><b>Sean K Smith</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=sksmith" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/kevin-neely/"><img src="https://avatars1.githubusercontent.com/u/37545028?v=4" width="60px;" alt=""/><br /><sub><b>Kevin Neely</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=kneely" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ariefrahmansyah.dev"><img src="https://avatars3.githubusercontent.com/u/8122852?v=4" width="60px;" alt=""/><br /><sub><b>Arief Rahmansyah</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ariefrahmansyah" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://vhanda.in"><img src="https://avatars2.githubusercontent.com/u/426467?v=4" width="60px;" alt=""/><br /><sub><b>Vishesh Handa</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=vHanda" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.linkedin.com/in/heroichitesh"><img src="https://avatars3.githubusercontent.com/u/37622734?v=4" width="60px;" alt=""/><br /><sub><b>Hitesh Kumar</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=HeroicHitesh" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://spencerwoo.com"><img src="https://avatars2.githubusercontent.com/u/32114380?v=4" width="60px;" alt=""/><br /><sub><b>Spencer Woo</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=spencerwooo" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user