Compare commits
114 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
587466a210 | ||
|
|
52bc1ba13d | ||
|
|
8f045a3ff4 | ||
|
|
b2be5a7311 | ||
|
|
87e2400070 | ||
|
|
78e946c177 | ||
|
|
80e46f7898 | ||
|
|
5f89a59b07 | ||
|
|
f921c095aa | ||
|
|
a51e0613ea | ||
|
|
9df71adb64 | ||
|
|
17c216736b | ||
|
|
66a8c3bd49 | ||
|
|
5f7b3b7c02 | ||
|
|
9ed0d6e18e | ||
|
|
0140748550 | ||
|
|
356dcc5579 | ||
|
|
265afdee19 | ||
|
|
de7c686f75 | ||
|
|
8dfc5bd2ff | ||
|
|
b3c5e75aa2 | ||
|
|
000da4bd1c | ||
|
|
86749940c2 | ||
|
|
27f9a08870 | ||
|
|
e791726692 | ||
|
|
a3c00744ca | ||
|
|
00220b1f6c | ||
|
|
759f4f1963 | ||
|
|
d86fc7f433 | ||
|
|
bd9c6806fa | ||
|
|
4c9a9cec56 | ||
|
|
8a91a6ab36 | ||
|
|
667037bc14 | ||
|
|
30cc9fc9f0 | ||
|
|
abed7be3ec | ||
|
|
d31e094358 | ||
|
|
f320af05c5 | ||
|
|
ee229dac84 | ||
|
|
877d843f60 | ||
|
|
af65e4d5f7 | ||
|
|
dd9fa0af79 | ||
|
|
14f68aea30 | ||
|
|
f68c2ab6db | ||
|
|
bae99a6184 | ||
|
|
861f7dbba7 | ||
|
|
6a4b90d6d7 | ||
|
|
f73ddb88d4 | ||
|
|
41ca70f23c | ||
|
|
7cf7811b85 | ||
|
|
eef0aa7f0b | ||
|
|
d222cfbbec | ||
|
|
ee7a891976 | ||
|
|
680e317dbf | ||
|
|
dbb46369a2 | ||
|
|
562da95f90 | ||
|
|
fc03e07eeb | ||
|
|
ccea5666bf | ||
|
|
2a7a909ac5 | ||
|
|
efac46174a | ||
|
|
4c5d996586 | ||
|
|
6de0024b0b | ||
|
|
70b0fae078 | ||
|
|
fd9bf8f04f | ||
|
|
630a77782d | ||
|
|
6fdce389e9 | ||
|
|
9ce5a9528f | ||
|
|
ca821d3928 | ||
|
|
9dacadedfc | ||
|
|
0c07e7535e | ||
|
|
dddc379f2b | ||
|
|
e83740d65a | ||
|
|
eddd9de665 | ||
|
|
7f6004dd6d | ||
|
|
1258cef04c | ||
|
|
7adbffcfc2 | ||
|
|
8395a408f2 | ||
|
|
721a6cc935 | ||
|
|
1659fe37d9 | ||
|
|
32f9120864 | ||
|
|
cec0aecd06 | ||
|
|
c59584d342 | ||
|
|
ea930d3d17 | ||
|
|
130b839ee9 | ||
|
|
1b4dc8a8b1 | ||
|
|
7fcbf1acf1 | ||
|
|
011706904b | ||
|
|
c14e1e6349 | ||
|
|
97629a2ce6 | ||
|
|
6b3eef43a8 | ||
|
|
d4adce6730 | ||
|
|
ae65f53540 | ||
|
|
8998204298 | ||
|
|
92f6e001e5 | ||
|
|
889f93a7e1 | ||
|
|
4db8c55969 | ||
|
|
7e740fec0f | ||
|
|
beae852c21 | ||
|
|
85e857d973 | ||
|
|
667eee0e10 | ||
|
|
b6e68b3605 | ||
|
|
b9b0f9b515 | ||
|
|
95399977ec | ||
|
|
f759e7cd6e | ||
|
|
5839455535 | ||
|
|
7e4ae82fe1 | ||
|
|
e47155424f | ||
|
|
903a191394 | ||
|
|
5c6212dc96 | ||
|
|
bca9756e2b | ||
|
|
2e435a3828 | ||
|
|
a0f83e7510 | ||
|
|
641024b01b | ||
|
|
c5a7d02656 | ||
|
|
b76196cd23 |
@@ -697,6 +697,97 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "bronson",
|
||||
"name": "Scott Bronson",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1776?v=4",
|
||||
"profile": "https://github.com/bronson",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "rafo",
|
||||
"name": "Rafael Riedel",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/41793?v=4",
|
||||
"profile": "http://rafaelriedel.de",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Pearcekieser",
|
||||
"name": "Pearcekieser",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/5055971?v=4",
|
||||
"profile": "https://github.com/Pearcekieser",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "theowenyoung",
|
||||
"name": "Owen Young",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/62473795?v=4",
|
||||
"profile": "https://github.com/theowenyoung",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"content"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ksprashu",
|
||||
"name": "Prashanth Subrahmanyam",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/476729?v=4",
|
||||
"profile": "http://www.prashu.com",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "JonasSprenger",
|
||||
"name": "Jonas SPRENGER",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/25108895?v=4",
|
||||
"profile": "https://github.com/JonasSprenger",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Laptop765",
|
||||
"name": "Paul",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1468359?v=4",
|
||||
"profile": "https://github.com/Laptop765",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "eltociear",
|
||||
"name": "Ikko Ashimine",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/22633385?v=4",
|
||||
"profile": "https://bandism.net/",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "memeplex",
|
||||
"name": "memeplex",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/2845433?v=4",
|
||||
"profile": "https://github.com/memeplex",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "AndreiD049",
|
||||
"name": "AndreiD049",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/52671223?v=4",
|
||||
"profile": "https://github.com/AndreiD049",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/class-name-casing": "warn",
|
||||
"@typescript-eslint/semi": "warn",
|
||||
"curly": "warn",
|
||||
"eqeqeq": "warn",
|
||||
"no-throw-literal": "warn",
|
||||
"semi": "off",
|
||||
"require-await": "warn"
|
||||
}
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["@typescript-eslint", "import"],
|
||||
"rules": {
|
||||
"@typescript-eslint/class-name-casing": "warn",
|
||||
"@typescript-eslint/semi": "warn",
|
||||
"curly": "warn",
|
||||
"eqeqeq": "warn",
|
||||
"no-throw-literal": "warn",
|
||||
"semi": "off",
|
||||
"require-await": "warn"
|
||||
}
|
||||
}
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/bug.md
vendored
@@ -20,4 +20,4 @@ labels: bug
|
||||
Feel free to attach any of the following that might help with debugging the issue:
|
||||
- screenshots
|
||||
- a zip with a minimal repo to reproduce the issue
|
||||
- the Foam log in VsCode (see [instructions](https://github.com/foambubble/foam/blob/master/docs/foam-logging-in-vscode.md))
|
||||
- the Foam log in VsCode (see [instructions](https://github.com/foambubble/foam/blob/master/docs/features/foam-logging-in-vscode.md))
|
||||
|
||||
15
.github/workflows/ci.yml
vendored
@@ -3,17 +3,15 @@ name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
# The following will also make the workflow run on all PRs, internal and external.
|
||||
# This would create duplicate runs, that we are skipping by adding the "if" to the jobs below.
|
||||
# See https://github.community/t/duplicate-checks-on-push-and-pull-request-simultaneous-event/18012
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-18.04
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 'foambubble/foam'
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Setup Node
|
||||
@@ -27,7 +25,7 @@ jobs:
|
||||
node_modules
|
||||
*/*/node_modules
|
||||
packages/foam-vscode/.vscode-test
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('packages/foam-vscode/src/test/run-tests.ts') }}
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock', 'packages/foam-vscode/src/test/run-tests.ts') }}-${{ secrets.CACHE_VERSION }}
|
||||
- name: Install Dependencies
|
||||
run: yarn
|
||||
- name: Check Lint Rules
|
||||
@@ -39,7 +37,6 @@ jobs:
|
||||
matrix:
|
||||
os: [macos-10.15, ubuntu-18.04, windows-2019]
|
||||
runs-on: ${{ matrix.os }}
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 'foambubble/foam'
|
||||
env:
|
||||
OS: ${{ matrix.os }}
|
||||
steps:
|
||||
@@ -55,12 +52,12 @@ jobs:
|
||||
node_modules
|
||||
*/*/node_modules
|
||||
packages/foam-vscode/.vscode-test
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('packages/foam-vscode/src/test/run-tests.ts') }}
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock', 'packages/foam-vscode/src/test/run-tests.ts') }}-${{ secrets.CACHE_VERSION }}
|
||||
- name: Install Dependencies
|
||||
run: yarn
|
||||
- name: Build Packages
|
||||
run: yarn build
|
||||
- name: Run Tests
|
||||
uses: GabrielBB/xvfb-action@v1.0
|
||||
uses: GabrielBB/xvfb-action@v1.4
|
||||
with:
|
||||
run: yarn test
|
||||
|
||||
100
.vscode/launch.json
vendored
@@ -3,83 +3,29 @@
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"inputs": [
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"id": "packageName",
|
||||
"type": "pickString",
|
||||
"description": "Select the package in which this test is located",
|
||||
"options": ["foam-core", "foam-vscode"],
|
||||
"default": "foam-core"
|
||||
"type": "node",
|
||||
"name": "Debug Jest Tests",
|
||||
"request": "launch",
|
||||
"runtimeArgs": ["workspace", "foam-vscode", "run", "test"], // ${yarnWorkspaceName} is what we're missing
|
||||
"args": ["--runInBand"],
|
||||
"runtimeExecutable": "yarn",
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
"disableOptimisticBPs": true
|
||||
},
|
||||
{
|
||||
"name": "Run VSCode Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/packages/foam-vscode"
|
||||
],
|
||||
"outFiles": ["${workspaceFolder}/packages/foam-vscode/out/**/*.js"],
|
||||
"preLaunchTask": "${defaultBuildTask}"
|
||||
}
|
||||
],
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"name": "vscode-jest-tests",
|
||||
"request": "launch",
|
||||
"runtimeArgs": ["workspace", "${input:packageName}", "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": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/packages/foam-vscode"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/packages/foam-vscode/out/**/*.js"
|
||||
],
|
||||
"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!
|
||||
{
|
||||
"name": "Test VSCode Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/packages/foam-vscode",
|
||||
"--extensionTestsPath=${workspaceFolder}/packages/foam-vscode/out/test/suite/index"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/packages/foam-vscode/out/test/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "${defaultBuildTask}"
|
||||
},
|
||||
{
|
||||
"name": "Test Core",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/node_modules/tsdx/dist/index.js",
|
||||
"args": ["test"],
|
||||
"cwd": "${workspaceFolder}/packages/foam-core",
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"preLaunchTask": "${defaultBuildTask}"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
1
.vscode/settings.json
vendored
@@ -12,6 +12,7 @@
|
||||
},
|
||||
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
|
||||
"typescript.tsc.autoDetect": "off",
|
||||
"foam.edit.linkReferenceDefinitions": "withExtensions",
|
||||
"foam.files.ignore": [
|
||||
"**/.vscode/**/*",
|
||||
"**/_layouts/**/*",
|
||||
|
||||
54
.vscode/tasks.json
vendored
@@ -1,31 +1,31 @@
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "watch: foam-vscode",
|
||||
"type": "npm",
|
||||
"script": "start:vscode",
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"isBackground": true,
|
||||
"presentation": {
|
||||
"reveal": "always"
|
||||
},
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "test: all packages",
|
||||
"type": "npm",
|
||||
"script": "test",
|
||||
"problemMatcher": [],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
},
|
||||
}
|
||||
]
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "watch: foam-vscode",
|
||||
"type": "npm",
|
||||
"script": "watch",
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"isBackground": true,
|
||||
"presentation": {
|
||||
"reveal": "always"
|
||||
},
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "test: all packages",
|
||||
"type": "npm",
|
||||
"script": "test",
|
||||
"problemMatcher": [],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
3
docs/.vscode/extensions.json
vendored
@@ -14,9 +14,6 @@
|
||||
// Tons of markdown goodies (lists, tables of content, so much more)
|
||||
"yzhang.markdown-all-in-one",
|
||||
|
||||
// [[wiki-links]], backlinking etc
|
||||
"kortina.vscode-markdown-notes",
|
||||
|
||||
// Graph visualizer
|
||||
"tchayen.markdown-links",
|
||||
|
||||
|
||||
@@ -19,21 +19,21 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
document
|
||||
.querySelectorAll(".markdown-body a[title]:not([href^=http])")
|
||||
.forEach((a) => {
|
||||
// filter to only wiki-links
|
||||
// filter to only wikilinks
|
||||
var prev = a.previousSibling;
|
||||
var next = a.nextSibling;
|
||||
if (
|
||||
prev instanceof Text && prev.textContent.endsWith('[') &&
|
||||
prev instanceof Text && prev.textContent.endsWith('[') &&
|
||||
next instanceof Text && next.textContent.startsWith(']')
|
||||
) {
|
||||
|
||||
|
||||
// remove surrounding brackets
|
||||
prev.textContent = prev.textContent.slice(0, -1);
|
||||
next.textContent = next.textContent.slice(1);
|
||||
|
||||
// add CSS list for styling
|
||||
a.classList.add('wikilink');
|
||||
|
||||
|
||||
// replace page-link with "Page Title"...
|
||||
a.innerText = a.title;
|
||||
|
||||
|
||||
BIN
docs/assets/images/feature-backlinks-panel.gif
Normal file
|
After Width: | Height: | Size: 821 KiB |
BIN
docs/assets/images/feature-daily-note.gif
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
docs/assets/images/feature-definition-references.gif
Normal file
|
After Width: | Height: | Size: 859 KiB |
BIN
docs/assets/images/feature-definitions-generation.gif
Normal file
|
After Width: | Height: | Size: 621 KiB |
BIN
docs/assets/images/feature-link-autocompletion.gif
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
docs/assets/images/feature-navigation.gif
Normal file
|
After Width: | Height: | Size: 935 KiB |
BIN
docs/assets/images/feature-note-embed.gif
Normal file
|
After Width: | Height: | Size: 298 KiB |
BIN
docs/assets/images/feature-placeholder-orphan-panel.gif
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
docs/assets/images/feature-preview-navigation.gif
Normal file
|
After Width: | Height: | Size: 369 KiB |
BIN
docs/assets/images/feature-show-graph.gif
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
docs/assets/images/feature-syntax-highlight.png
Normal file
|
After Width: | Height: | Size: 394 KiB |
BIN
docs/assets/images/feature-tags-panel.gif
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
docs/assets/images/feature-templates.gif
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
@@ -18,8 +18,6 @@ Before you start contributing we recommend that you read the following links:
|
||||
We understand that diving in an unfamiliar codebase may seem scary,
|
||||
to make it easier for new contributors we provide some resources:
|
||||
|
||||
- [[architecture]] - This document describes the architecture of Foam and how the repository is structured.
|
||||
|
||||
You can also see [existing issues](https://github.com/foambubble/foam/issues) and help out!
|
||||
Finally, the easiest way to help, is to use it and provide feedback by [submitting issues](https://github.com/foambubble/foam/issues/new/choose) or participating in the [Foam Community Discord](https://foambubble.github.io/join-discord/g)!
|
||||
|
||||
@@ -35,37 +33,59 @@ If you're interested in contributing, this short guide will help you get things
|
||||
|
||||
`yarn install`
|
||||
|
||||
3. This project uses [Yarn workspaces](https://classic.yarnpkg.com/en/docs/workspaces/). `foam-vscode` relies on `foam-core`. This means we need to compile it before we do any extension development. From the root, run the command:
|
||||
3. From the root, run the command:
|
||||
|
||||
`yarn build`
|
||||
|
||||
You should now be ready to start working!
|
||||
|
||||
### Structure of the project
|
||||
|
||||
Foam code and documentation live in the monorepo at [foambubble/foam](https://github.com/foambubble/foam/).
|
||||
|
||||
- [/docs](https://github.com/foambubble/foam/tree/master/docs): documentation and [[recipes]].
|
||||
|
||||
Exceptions to the monorepo are:
|
||||
|
||||
- The starter template at [foambubble/foam-template](https://github.com/foambubble/)
|
||||
- All other [[recommended-extensions]] live in their respective GitHub repos
|
||||
|
||||
This project uses [Yarn workspaces](https://classic.yarnpkg.com/en/docs/workspaces/).
|
||||
|
||||
Originally Foam had:
|
||||
|
||||
- [/packages/foam-core](https://github.com/foambubble/foam/tree/master/packages/foam-core) - Powers the core functionality in Foam across all platforms.
|
||||
- [/packages/foam-vscode](https://github.com/foambubble/foam/tree/master/packages/foam-vscode) - The core VS Code plugin.
|
||||
|
||||
To improve DX we have moved the `foam-core` module into `packages/foam-vscode/src/core`, but from a development point of view it's useful to think of the `foam-vscode/src/core` "submodule" as something that might be extracted in the future.
|
||||
|
||||
For all intents and purposes this means two things:
|
||||
|
||||
1. nothing in `foam-vscode/src/core` should depend on files outside of this directory
|
||||
2. code in `foam-vscode/src/core` should NOT depend on `vscode` library
|
||||
|
||||
We have kept the yarn workspace for the time being as we might use it to pull out `foam-core` in the future, or we might need it for other packages that the VS Code plugin could depend upon (e.g. currently the graph visualization is inside the module, but it might be pulled out if its complexity increases).
|
||||
|
||||
### Testing
|
||||
|
||||
Code needs to come with tests.
|
||||
We use the following convention in Foam:
|
||||
|
||||
- *.test.ts are unit tests
|
||||
- *.spec.ts are integration tests
|
||||
- `*.test.ts` are unit tests
|
||||
- `*.spec.ts` are integration tests
|
||||
|
||||
Also, note that tests in `foam-core` live in the `test` directory.
|
||||
Tests in `foam-vscode` live alongside the code in `src`.
|
||||
Tests live alongside the code in `src`.
|
||||
|
||||
### The VS Code Extension
|
||||
|
||||
This guide assumes you read the previous instructions and you're set up to work on Foam.
|
||||
|
||||
1. Now we'll use the launch configuration defined at [`.vscode/launch.json`](https://github.com/foambubble/foam/blob/master/.vscode/launch.json) to start a new extension host of VS Code. From the root, or the `foam-vscode` workspace, press f5.
|
||||
1. Now we'll use the launch configuration defined at [`.vscode/launch.json`](https://github.com/foambubble/foam/blob/master/.vscode/launch.json) to start a new extension host of VS Code. Open the "Run and Debug" Activity (the icon with the bug on the far left) and select "Run VSCode Extension" in the pop-up menu. Now hit F5 or click the green arrow "play" button to fire up a new copy of VS Code with your extension installed.
|
||||
|
||||
2. In the new extension host of VS Code that launched, open a Foam workspace (e.g. your personal one, or a test-specific one created from [foam-template](https://github.com/foambubble/foam-template)). This is strictly not necessary, but the extension won't auto-run unless it's in a workspace with a `.vscode/foam.json` file.
|
||||
|
||||
3. Test a command to make sure it's working as expected. Open the Command Palette (Ctrl/Cmd + Shift + P) and select "Foam: Update Markdown Reference List". If you see no errors, it's good to go!
|
||||
|
||||
For more resources related to the VS Code Extension, check out the links below:
|
||||
|
||||
- [[tutorial-adding-a-new-command-to-the-vs-code-extension]]
|
||||
|
||||
---
|
||||
|
||||
Feel free to modify and submit a PR if this guide is out-of-date or contains errors!
|
||||
@@ -75,5 +95,6 @@ Feel free to modify and submit a PR if this guide is out-of-date or contains err
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[principles]: principles.md "Principles"
|
||||
[code-of-conduct]: code-of-conduct.md "Code of Conduct"
|
||||
[architecture]: dev/architecture.md "Architecture"
|
||||
[recipes]: recipes/recipes.md "Recipes"
|
||||
[recommended-extensions]: recommended-extensions.md "Recommended Extensions"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
tags: architecture
|
||||
---
|
||||
# Architecture
|
||||
|
||||
This document aims to provide a quick overview of the Foam architecture!
|
||||
|
||||
Foam code and documentation live in the monorepo at [foambubble/foam](https://github.com/foambubble/foam/).
|
||||
|
||||
- [/docs](https://github.com/foambubble/foam/tree/master/docs): documentation and [[recipes]].
|
||||
- [/packages/foam-core](https://github.com/foambubble/foam/tree/master/packages/foam-core) - Powers the core functionality in Foam across all platforms.
|
||||
- [/packages/foam-vscode](https://github.com/foambubble/foam/tree/master/packages/foam-vscode) - The core VSCode plugin.
|
||||
|
||||
Exceptions to the monorepo are:
|
||||
|
||||
- The starter template at [foambubble/foam-template](https://github.com/foambubble/)
|
||||
- All other [[recommended-extensions]] live in their respective GitHub repos.
|
||||
- [foam-cli](https://github.com/foambubble/foam-cli) - The Foam CLI tool.
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[recipes]: ../recipes/recipes.md "Recipes"
|
||||
[recommended-extensions]: ../recommended-extensions.md "Recommended Extensions"
|
||||
[//end]: # "Autogenerated link references"
|
||||
@@ -17,7 +17,7 @@
|
||||
`foam-core`'s primary responsibility is to build an API on top of a workspace of markdown files, which allows us to:
|
||||
|
||||
- Treat files as a graph, based on links
|
||||
- Can be either [[wiki-links]] or relative `[markdown](links.md)` style
|
||||
- Can be either [[wikilinks]] or relative `[markdown](links.md)` style
|
||||
- We need to know about the edges (connections) as well as nodes
|
||||
- What link points to what other file, etc.
|
||||
- Needs to have the exact link text, e.g. even if `[[some-page]]` or `[[some-page.md]]` or `[[Some Page]]` point to the same document (`./some-page.md`), we need to know which format was used, so [[link-reference-definitions]] can be generated correctly
|
||||
@@ -61,7 +61,7 @@ Here are some example use cases that the core should support. They don't need to
|
||||
|
||||
- Adding and editing page content
|
||||
- [[materialized-backlinks]]
|
||||
- [[link-reference-definitions]] for [[wiki-links]]
|
||||
- [[link-reference-definitions]] for [[wikilinks]]
|
||||
- [Frontmatter](https://jekyllrb.com/docs/front-matter/)
|
||||
- Finding all documents with `#tag`
|
||||
- Finding all documents with instances of `[[link]]`
|
||||
@@ -99,7 +99,7 @@ Useful for knowing what needs to be supported. See [[feature-comparison]].
|
||||
[workspace-janitor]: ../features/workspace-janitor.md "Janitor"
|
||||
[cli]: ../features/cli.md "Command Line Interface"
|
||||
[build-vs-assemble]: build-vs-assemble.md "Build vs Assemble"
|
||||
[wiki-links]: ../wiki-links.md "Wiki Links"
|
||||
[wikilinks]: ../wikilinks.md "Wikilinks"
|
||||
[link-reference-definitions]: ../features/link-reference-definitions.md "Link Reference Definitions"
|
||||
[materialized-backlinks]: materialized-backlinks.md "Materialized Backlinks (stub)"
|
||||
[todo]: todo.md "Todo"
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
# Foam File Format
|
||||
|
||||
This file is an example of a valid Foam file. Essentially it's just a markdown file with a bit of additional support for mediawiki-style `[[wiki-links]]`.
|
||||
This file is an example of a valid Foam file. Essentially it's just a markdown file with a bit of additional support for MediaWiki-style `[[wikilinks]]`.
|
||||
|
||||
Here are a few specific constraints, mainly because our tooling is a bit fragmented. Most of these should be eventually lifted, and our requirement should just be "Markdown with `[[wiki-links]]`:
|
||||
Here are a few specific constraints, mainly because our tooling is a bit fragmented. Most of these should be eventually lifted, and our requirement should just be "Markdown with `[[wikilinks]]`:
|
||||
|
||||
- **The first top level `# Heading` will be used as title for the note.**
|
||||
- If not available, we will use the file name
|
||||
- **File name should have extension `.md`**
|
||||
- This is a temporary limitation and will be lifted in future versions.
|
||||
- At least `.mdx` will be supported, but ideally we'll support any file that you can map to `Markdown` language mode in VS Code
|
||||
- **In addition to normal Markdown Links syntax you can use `[[media-wiki]]` links.** See [[wiki-links]] for more details.
|
||||
- **In addition to normal Markdown Links syntax you can use `[[MediaWiki]]` links.** See [[wikilinks]] for more details.
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[wiki-links]: ../wiki-links.md "Wiki Links"
|
||||
[wikilinks]: ../wikilinks.md "Wikilinks"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
|
||||
### File-by-file Insertion
|
||||
|
||||
For the time being, if you want to get [[wiki-links]] into all files within the workspace, you'll need to generate the link reference definitions yourself file-by-file (with the assistance of Foam).
|
||||
For the time being, if you want to get [[wikilinks]] into all files within the workspace, you'll need to generate the link reference definitions yourself file-by-file (with the assistance of Foam).
|
||||
|
||||
### Wikilinks don't work on GitHub
|
||||
|
||||
> **TL;DR;** [workaround](#workaround) in the end of the chapter.
|
||||
|
||||
If you click any of the wiki-links on GitHub web UI (such as the `README.md` of a project), you'll notice that the links break with a 404 error.
|
||||
If you click any of the wikilinks on GitHub web UI (such as the `README.md` of a project), you'll notice that the links break with a 404 error.
|
||||
|
||||
At the time of writing (June 28 2020) this is a known, but unsolved error. To understand why this is the case, we need to understand what we are trading off.
|
||||
|
||||
@@ -59,7 +59,7 @@ Problem space in essence:
|
||||
- may clutter the search results
|
||||
- During build-time (when converting markdown to html for publishing purposes)
|
||||
- link reference definitions are needed, if the files are published via such tools (or to such platforms) that don't understand wikilinks
|
||||
- link reference definitions might have to be in different formats depending on the publish target (e.g. Github pages vs Github UI)
|
||||
- link reference definitions might have to be in different formats depending on the publish target (e.g. GitHub pages vs GitHub UI)
|
||||
|
||||
The potential solution:
|
||||
|
||||
@@ -122,8 +122,8 @@ The potential solution:
|
||||
|
||||
```
|
||||
|
||||
- With Foam repo, just use edit-time link reference definitions with '.md' extension - this makes the links work in the Github UI
|
||||
- Have publish target defined for Github pages, that doesn't use '.md' extension, but still has the link reference definitions. Generate the output into gh-pages branch (or separate repo) with automation.
|
||||
- With Foam repo, just use edit-time link reference definitions with '.md' extension - this makes the links work in the GitHub UI
|
||||
- Have publish target defined for GitHub pages, that doesn't use '.md' extension, but still has the link reference definitions. Generate the output into gh-pages branch (or separate repo) with automation.
|
||||
- This naturally requires first removing the existing link reference definitions during the build
|
||||
- Other
|
||||
- To clean up the search results, remove link reference definition section guards (assuming that these are not defined by the markdown spec). Use unifiedjs parse trees to identify if there's missing (or surplus) definitions (check if they are identified properly by the library), and just add the needed definitions to the bottom of the file (without guards) AND remove them if they are not needed (anywhere from the file).
|
||||
@@ -137,7 +137,7 @@ UI-wise, the publish targets could be picked in some similar fashion as the run/
|
||||
- [tracking issue on GitHub](https://github.com/foambubble/foam/issues/16)
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[wiki-links]: ../wiki-links.md "Wiki Links"
|
||||
[wikilinks]: ../wikilinks.md "Wikilinks"
|
||||
[link-reference-definitions]: ../features/link-reference-definitions.md "Link Reference Definitions"
|
||||
[backlinking]: ../features/backlinking.md "Backlinking"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
@@ -25,7 +25,7 @@ If a roadmap item is a stub, **consider** opening a [GitHub issue](https://githu
|
||||
- [[improve-default-workspace-settings]]
|
||||
- Discussion: [foam#270](https://github.com/foambubble/foam/issues/270)
|
||||
- Improve [[git-integration]]
|
||||
- Fix [[wiki-links]] compatibility issues
|
||||
- Fix [[wikilinks]] compatibility issues
|
||||
- Simplify [[foam-file-format]]
|
||||
|
||||
### Core features
|
||||
@@ -87,7 +87,7 @@ The community is working on a number of automated scripts to help you migrate to
|
||||
[recipes]: ../recipes/recipes.md "Recipes"
|
||||
[contribution-guide]: ../contribution-guide.md "Contribution Guide"
|
||||
[git-integration]: ../features/git-integration.md "Git Integration"
|
||||
[wiki-links]: ../wiki-links.md "Wiki Links"
|
||||
[wikilinks]: ../wikilinks.md "Wikilinks"
|
||||
[foam-file-format]: foam-file-format.md "Foam File Format"
|
||||
[unlinked-references]: unlinked-references.md "Unlinked references (stub)"
|
||||
[make-backlinks-more-prominent]: ../recipes/make-backlinks-more-prominent.md "Make Backlinks More Prominent"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Backlinking
|
||||
|
||||
When using [[wiki-links]], you can find all notes that link to a specific note in the [VS Code Markdown Notes](https://marketplace.visualstudio.com/items?itemName=kortina.vscode-markdown-notes) **Backlinks Explorer**
|
||||
When using [[wikilinks]], you can find all notes that link to a specific note in the [VS Code Markdown Notes](https://marketplace.visualstudio.com/items?itemName=kortina.vscode-markdown-notes) **Backlinks Explorer**
|
||||
|
||||
- Run `Cmd` + `Shift` + `P` (`Ctrl` + `Shift` + `P` for Windows), type "backlinks" and run the **Explorer: Focus on Backlinks** view.
|
||||
- Keep this pane always visible to discover relationships between your thoughts
|
||||
@@ -8,7 +8,7 @@ When using [[wiki-links]], you can find all notes that link to a specific note i
|
||||
- Finding backlinks in published Foam workspaces via [[materialized-backlinks]] is on the [[roadmap]] but not yet implemented.
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[wiki-links]: ../wiki-links.md "Wiki Links"
|
||||
[wikilinks]: ../wikilinks.md "Wikilinks"
|
||||
[make-backlinks-more-prominent]: ../recipes/make-backlinks-more-prominent.md "Make Backlinks More Prominent"
|
||||
[materialized-backlinks]: ../dev/materialized-backlinks.md "Materialized Backlinks (stub)"
|
||||
[roadmap]: ../dev/roadmap.md "Roadmap"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Creating New Notes
|
||||
|
||||
- Write out a new `[[wiki-link]]` and `Cmd` + `Click` to create a new file and enter it.
|
||||
- Write out a new `[[wikilink]]` and `Cmd` + `Click` to create a new file and enter it.
|
||||
- For keyboard navigation, use the 'Follow Definition' key `F12` (or [remap key binding](https://code.visualstudio.com/docs/getstarted/keybindings) to something more ergonomic)
|
||||
- `Cmd` + `Shift` + `P` (`Ctrl` + `Shift` + `P` for Windows), execute `Foam: Create New Note` and enter a **Title Case Name** to create `Title Case Name.md`
|
||||
- Add a keyboard binding to make creating new notes easier.
|
||||
|
||||
@@ -13,3 +13,19 @@ For example, to load a stylesheet called `Style.css`, we can update `settings.js
|
||||
"markdown.styles": ["Style.css"]
|
||||
}
|
||||
```
|
||||
|
||||
## Foam elements
|
||||
|
||||
### Foam note & placeholder links
|
||||
|
||||
It is possible to custom style the links to a note or placeholder. The links are an `<a>` tag. For notes use the class `foam-note-link`, for placeholders use `foam-placeholder-link`.
|
||||
|
||||
### Cyclic inclusion warnings
|
||||
|
||||
Foams offers the functionality to include other notes in your note. This will be displayed in the preview tab. Foam recognises a cyclic inclusion of notes and will display a warning when detected. The following html is used and can be custom styled using the class `foam-cyclic-link-warning`.
|
||||
|
||||
```html
|
||||
<div class="foam-cyclic-link-warning">
|
||||
Cyclic link detected for wikilink: ${wikilink}
|
||||
</div>
|
||||
```
|
||||
@@ -25,9 +25,9 @@ The above configuration would create a file `journal/note-2020-07-25.mdx`, with
|
||||
|
||||
## Daily Note Templates
|
||||
|
||||
In the future, Foam may provide a functionality for specifying a template for new Daily Notes and other types of documents.
|
||||
Daily notes can also make use of [templates](note-templates.md), by defining a special `.foam/templates/daily-note.md` template.
|
||||
|
||||
In the meantime, you can use [VS Code Snippets](https://code.visualstudio.com/docs/editor/userdefinedsnippets) for defining your own Daily Note template.
|
||||
See [Note Templates](note-templates.md) for details of the features available in templates.
|
||||
|
||||
## Roam-style Automatic Daily Notes
|
||||
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
# Foam Local Plugins
|
||||
|
||||
Foam can use workspace plugins to provide customization for users.
|
||||
|
||||
## ATTENTION
|
||||
|
||||
This feature is experimental and its API subject to change.
|
||||
**Local plugins can execute arbitrary code on your machine** - ensure you trust the content of the repo.
|
||||
|
||||
## Goal
|
||||
|
||||
Here are some of the things that we could enable with local plugins in Foam:
|
||||
|
||||
- extend the document syntax to support roam style attributes (e.g. `stage:: seedling`)
|
||||
- automatically add tags to my notes based on the location in the repo (e.g. notes in `/areas/finance` will automatically get the `#finance` tag)
|
||||
- add a new CLI command to support some internal use case or automate import/export
|
||||
- extend the VSCode experience to support one's own workflow, e.g. weekly note, templates, extra panels, foam model derived TOC, ... all without having to write/deploy a VSCode extension
|
||||
|
||||
## How to enable local plugins
|
||||
|
||||
Plugins can execute arbitrary code on the client's machine.
|
||||
For this reason this feature is disabled by default, and needs to be explicitly enabled.
|
||||
|
||||
To enable the feature:
|
||||
|
||||
- create a `~/.foam/config.json` file
|
||||
- add the following content to the file
|
||||
|
||||
```
|
||||
{
|
||||
"experimental": {
|
||||
"localPlugins": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For security reasons this setting can only be defined in the user settings file.
|
||||
(otherwise a malicious repo could set it via its `./foam/config.json`)
|
||||
|
||||
- [[todo]] an additional security mechanism would involve having an explicit list of whitelisted repo paths where plugins are allowed. This would provide finer grain control over when to enable or disable the feature.
|
||||
|
||||
## Technical approach
|
||||
|
||||
When Foam is loaded it will check whether the experimental local plugin feature is enabled, and in such case it will:
|
||||
|
||||
- check `.foam/plugins` directory.
|
||||
- each directory in there is considered a plugin
|
||||
- the layout of each directory is
|
||||
- `index.js` contains the main info about the plugin, specifically it exports:
|
||||
- `name: string` the name of the plugin
|
||||
- `description?: string` the description of the plugin
|
||||
- `parser?: ParserPlugin` an object that interacts with the markdown parsing phase
|
||||
|
||||
Currently for simplicity we keep everything in one file. We might in the future split the plugin by domain (e.g. vscode, cli, core, ...)
|
||||
|
||||
[//begin]: # 'Autogenerated link references for markdown compatibility'
|
||||
[todo]: ../dev/todo.md 'Todo'
|
||||
[//end]: # 'Autogenerated link references'
|
||||
20
docs/features/including-notes.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Including notes in a note
|
||||
|
||||
In some situations it might be useful to include the content of another note in your current note. Foam supports this displaying within the vscode environment. Note, this does not work out-of-the-box for your publishing solutions.
|
||||
|
||||
## Including a note
|
||||
|
||||
Including a note can be done by adding an `!` before a wikilink defintion. For example `![[wikilink]]`.
|
||||
|
||||
## Custom styling
|
||||
|
||||
Displaying the inclusion of notes allows for some custom styling, see [[custom-markdown-preview-styles]]
|
||||
|
||||
## Future possibilities
|
||||
|
||||
Work on this feature is evolving and progressing. See the [[inclusion-of-notes]] proposal for the current discussion.
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[custom-markdown-preview-styles]: custom-markdown-preview-styles.md "Custom Markdown Preview Styles"
|
||||
[inclusion-of-notes]: ../proposals/inclusion-of-notes.md "Inclusion of notes Proposal "
|
||||
[//end]: # "Autogenerated link references"
|
||||
@@ -2,22 +2,22 @@
|
||||
|
||||
## Introduction
|
||||
|
||||
When you use `[[wiki-links]]`, the [foam-vscode](https://github.com/foambubble/foam/tree/master/packages/foam-vscode) extension will automatically generate [Markdown Link Reference Definitions](https://spec.commonmark.org/0.29/#link-reference-definitions) at the bottom of the file. This is done to make the content of the file compatible with various Markdown tools (e.g. parsers, static site generators, VS code plugins etc), which don't support `[[wiki-links]]`.
|
||||
When you use `[[wikilinks]]`, the [foam-vscode](https://github.com/foambubble/foam/tree/master/packages/foam-vscode) extension will automatically generate [Markdown Link Reference Definitions](https://spec.commonmark.org/0.29/#link-reference-definitions) at the bottom of the file. This is done to make the content of the file compatible with various Markdown tools (e.g. parsers, static site generators, VS code plugins etc), which don't support `[[wikilinks]]`.
|
||||
|
||||
## Example
|
||||
|
||||
The following example:
|
||||
|
||||
```md
|
||||
- [[wiki-links]]
|
||||
- [[wikilinks]]
|
||||
- [[github-pages]]
|
||||
```
|
||||
|
||||
...generates the following link reference definitions to the bottom of the file:
|
||||
|
||||
```md
|
||||
[wiki-links]: wiki-links "Wiki Links"
|
||||
[github-pages]: github-pages "Github Pages"
|
||||
[wikilinks]: wikilinks "Wikilinks"
|
||||
[github-pages]: github-pages "GitHub Pages"
|
||||
```
|
||||
|
||||
You can open the [raw markdown](https://foambubble.github.io/foam/features/link-reference-definitions.md) to see them at the bottom of this file
|
||||
@@ -26,7 +26,7 @@ You can open the [raw markdown](https://foambubble.github.io/foam/features/link-
|
||||
|
||||
The three components of a link reference definition are `[link-label]: link-target "Link Title"`
|
||||
|
||||
- **link label:** The link text to match in the surrounding markdown document. This matches the inner bracket of the double-bracketed `[[wiki-link]]` notation
|
||||
- **link label:** The link text to match in the surrounding markdown document. This matches the inner bracket of the double-bracketed `[[wikilink]]` notation
|
||||
- **link destination** The target of the matched link
|
||||
- By default we generate links without extension. This can be overridden, see [Configuration](#configuration) below
|
||||
- **"Link Title"** Optional title for link (The Foam template has a snippet of JavaScript to replace this on the website at runtime)
|
||||
|
||||
@@ -24,22 +24,59 @@ To create a note from a template:
|
||||
|
||||
_Theme: Ayu Light_
|
||||
|
||||
## Default template
|
||||
## Special templates
|
||||
### Default template
|
||||
|
||||
The `.foam/templates/new-note.md` template is special in that it is the template that will be used by the `Foam: Create New Note` command.
|
||||
Customize this template to contain content that you want included every time you create a note.
|
||||
|
||||
### Default daily note template
|
||||
|
||||
The `.foam/templates/daily-note.md` template is special in that it is the template that will be used when creating daily notes (e.g. by using `Foam: Open Daily Note`).
|
||||
Customize this template to contain content that you want included every time you create a daily note.
|
||||
|
||||
## Variables
|
||||
|
||||
Templates can use all the variables available in [VS Code Snippets](https://code.visualstudio.com/docs/editor/userdefinedsnippets#_variables).
|
||||
|
||||
In addition, you can also use variables provided by Foam:
|
||||
|
||||
| Name | Description |
|
||||
| ------------ | ----------------------------------------------------------------------------------- |
|
||||
| `FOAM_TITLE` | The title of the note. If used, Foam will prompt you to enter a title for the note. |
|
||||
| Name | Description |
|
||||
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `FOAM_SELECTED_TEXT` | Foam will fill it with selected text when creating a new note, if any text is selected. Selected text will be replaced with a wikilink to the new note. |
|
||||
| `FOAM_TITLE` | The title of the note. If used, Foam will prompt you to enter a title for the note. |
|
||||
| `FOAM_DATE_*` | `FOAM_DATE_YEAR`, `FOAM_DATE_MONTH`, etc. Foam-specific versions of [VS Code's datetime snippet variables](https://code.visualstudio.com/docs/editor/userdefinedsnippets#_variables). Prefer these versions over VS Code's. |
|
||||
|
||||
**Note:** neither the defaulting feature (eg. `${variable:default}`) nor the format feature (eg. `${variable/(.*)/${1:/upcase}/}`) (available to other variables) are available for these Foam-provided variables.
|
||||
**Note:** neither the defaulting feature (eg. `${variable:default}`) nor the format feature (eg. `${variable/(.*)/${1:/upcase}/}`) (available to other variables) are available for these Foam-provided variables. See [#693](https://github.com/foambubble/foam/issues/693).
|
||||
|
||||
### `FOAM_DATE_*` variables
|
||||
|
||||
Foam defines its own set of datetime variables that have a similar behaviour as [VS Code's datetime snippet variables](https://code.visualstudio.com/docs/editor/userdefinedsnippets#_variables).
|
||||
|
||||
For example, `FOAM_DATE_YEAR` has the same behaviour as VS Code's `CURRENT_YEAR`, `FOAM_DATE_SECONDS_UNIX` has the same behaviour as `CURRENT_SECONDS_UNIX`, etc.
|
||||
|
||||
By default, prefer using the `FOAM_DATE_` versions. The datetime used to compute the values will be the same for both `FOAM_DATE_` and VS Code's variables, with the exception of the creation notes using the daily note template.
|
||||
|
||||
#### Relative daily notes
|
||||
|
||||
When referring to daily notes, you can use the relative snippets (`/+1d`, `/tomorrow`, etc.). In these cases, the new notes will be created with the daily note template, but the datetime used should be the relative datetime, not the current datetime.
|
||||
By using the `FOAM_DATE_` versions of the variables, the correct relative date will populate the variables, instead of the current datetime.
|
||||
|
||||
For example, given this daily note template (`.foam/templates/daily-note.md`):
|
||||
|
||||
```markdown
|
||||
# $FOAM_DATE_YEAR-$FOAM_DATE_MONTH-$FOAM_DATE_DATE
|
||||
|
||||
## Here's what I'm going to do today
|
||||
|
||||
* Thing 1
|
||||
* Thing 2
|
||||
```
|
||||
|
||||
When the `/tomorrow` snippet is used, `FOAM_DATE_` variables will be populated with tomorrow's date, as expected.
|
||||
If instead you were to use the VS Code versions of these variables, they would be populated with today's date, not tomorrow's, causing unexpected behaviour.
|
||||
|
||||
When creating notes in any other scenario, the `FOAM_DATE_` values are computed using the same datetime as the VS Code ones, so the `FOAM_DATE_` versions can be used in all scenarios by default.
|
||||
|
||||
## Metadata
|
||||
|
||||
@@ -58,6 +95,8 @@ Foam-specific variables (e.g. `$FOAM_TITLE`) can be used within template metadat
|
||||
The `filepath` metadata attribute allows you to define a relative or absolute filepath to use when creating a note using the template.
|
||||
If the filepath is a relative filepath, it is relative to the current workspace.
|
||||
|
||||
**Note:** While you can make use of the `filepath` attribute in [daily note](daily-notes.md) templates (`.foam/templates/daily-note.md`), there is currently no way to have `filepath` vary based on the date. This will be improved in the future. For now, you can customize the location of daily notes using the [`foam.openDailyNote` settings](daily-notes.md).
|
||||
|
||||
#### Example of relative `filepath`
|
||||
|
||||
For example, `filepath` can be used to customize `.foam/templates/new-note.md`, overriding the default `Foam: Create New Note` behaviour of opening the file in the same directory as the active file:
|
||||
|
||||
@@ -4,15 +4,19 @@
|
||||
|
||||
- [Frequently Asked Questions](#frequently-asked-questions)
|
||||
- [Links/Graphs/BackLinks don't work. How do I enable them?](#linksgraphsbacklinks-dont-work-how-do-i-enable-them)
|
||||
- [I don't want Foam enabled for all my workspaces](#i-dont-want-foam-enabled-for-all-my-workspaces)
|
||||
|
||||
## Links/Graphs/BackLinks don't work. How do I enable them?
|
||||
|
||||
- Ensure that you have all the [[recommended-extensions]] installed in Visual Studio Code
|
||||
- Reload Visual Studio Code by running `Cmd` + `Shift` + `P` (`Ctrl` + `Shift` + `P` for Windows), type "reload" and run the **Developer: Reload Window** command to for the updated extensions take effect
|
||||
- Check the formatting rules for links on [[foam-file-format]], [[wiki-links]] and [[link-formatting-and-autocompletion]]
|
||||
- Check the formatting rules for links on [[foam-file-format]], [[wikilinks]] and [[link-formatting-and-autocompletion]]
|
||||
|
||||
## I don't want Foam enabled for all my workspaces
|
||||
Any extension you install in Visual Studio Code is enabled by default. Give the philosophy of Foam it works out of the box without doing any configuration upfront. In case you want to disable Foam for a specific workspace, or disable Foam by default and enable it for specific workspaces, it is advised to follow the best practices as [documented by Visual Studio Code](https://code.visualstudio.com/docs/editor/extension-marketplace#_manage-extensions)
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[recommended-extensions]: recommended-extensions.md "Recommended Extensions"
|
||||
[foam-file-format]: dev/foam-file-format.md "Foam File Format"
|
||||
[wiki-links]: wiki-links.md "Wiki Links"
|
||||
[wikilinks]: wikilinks.md "Wikilinks"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
@@ -19,7 +19,7 @@ Uncategorised thoughts, to be added
|
||||
- <https://code.visualstudio.com/api/extension-guides/notebook>
|
||||
- Future architecture
|
||||
- Could we do publish-related settings as a pre-push git hook, e.g. generating footnote labels
|
||||
- Running them on Github Actions to edit stuff as it comes in
|
||||
- Running them on GitHub Actions to edit stuff as it comes in
|
||||
- Ideally, we shouldn't have to touch files, should be just markdown
|
||||
- Looking at the errors/warnings/output panes makes me think, what kind of automated quality tools could we write.
|
||||
- Deduplication, finding similarities...
|
||||
|
||||
@@ -37,7 +37,7 @@ Whether you want to build a [Second Brain](https://www.buildingasecondbrain.com/
|
||||
|
||||
1. Create a single **Foam** workspace for all your knowledge and research following the [Getting started](#getting-started) guide.
|
||||
2. Write your thoughts in markdown documents (I like to call them **Bubbles**, but that might be more than a little twee). These documents should be atomic: Put things that belong together into a single document, and limit its content to that single topic. ([source](https://zettelkasten.de/posts/overview/#principles))
|
||||
3. Use Foam's shortcuts and autocompletions to link your thoughts together with `[[wiki-links]]`, and navigate between them to explore your knowledge graph.
|
||||
3. Use Foam's shortcuts and autocompletions to link your thoughts together with `[[wikilinks]]`, and navigate between them to explore your knowledge graph.
|
||||
4. Get an overview of your **Foam** workspace using a [[graph-visualisation]] (⚠️ WIP), and discover relationships between your thoughts with the use of [[backlinking]].
|
||||
|
||||
Foam is a like a bathtub: _What you get out of it depends on what you put into it._
|
||||
@@ -72,6 +72,8 @@ These instructions assume you have a GitHub account, and you have Visual Studio
|
||||
|
||||
After setting up the repository, open `.vscode/settings.json` and edit, add or remove any settings you'd like for your Foam workspace.
|
||||
|
||||
* *If using a [multi-root workspace](https://code.visualstudio.com/docs/editor/multi-root-workspaces) as noted above, make sure that your **Foam** directory is first in the list. There are some settings that will need to be migrated from `.vscode/settings.json` to your `.code-workspace` file.*
|
||||
|
||||
To learn more about how to use **Foam**, read the [[recipes]].
|
||||
|
||||
Getting stuck in the setup? Read the [[frequently-asked-questions]].
|
||||
@@ -202,6 +204,20 @@ If that sounds like something you're interested in, I'd love to have you along o
|
||||
<td align="center"><a href="https://github.com/Barabazs"><img src="https://avatars.githubusercontent.com/u/31799121?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Barabas</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Barabazs" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://enginveske@gmail.com"><img src="https://avatars.githubusercontent.com/u/43685404?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Engincan VESKE</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=EngincanV" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.paulderaaij.nl"><img src="https://avatars.githubusercontent.com/u/495374?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Paul de Raaij</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=pderaaij" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/bronson"><img src="https://avatars.githubusercontent.com/u/1776?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Scott Bronson</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=bronson" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://rafaelriedel.de"><img src="https://avatars.githubusercontent.com/u/41793?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Rafael Riedel</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=rafo" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/Pearcekieser"><img src="https://avatars.githubusercontent.com/u/5055971?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Pearcekieser</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Pearcekieser" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/theowenyoung"><img src="https://avatars.githubusercontent.com/u/62473795?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Owen Young</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=theowenyoung" title="Documentation">📖</a> <a href="#content-theowenyoung" title="Content">🖋</a></td>
|
||||
<td align="center"><a href="http://www.prashu.com"><img src="https://avatars.githubusercontent.com/u/476729?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Prashanth Subrahmanyam</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ksprashu" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/JonasSprenger"><img src="https://avatars.githubusercontent.com/u/25108895?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Jonas SPRENGER</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=JonasSprenger" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Laptop765"><img src="https://avatars.githubusercontent.com/u/1468359?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Paul</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Laptop765" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://bandism.net/"><img src="https://avatars.githubusercontent.com/u/22633385?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Ikko Ashimine</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=eltociear" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/memeplex"><img src="https://avatars.githubusercontent.com/u/2845433?v=4?s=60" width="60px;" alt=""/><br /><sub><b>memeplex</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=memeplex" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/AndreiD049"><img src="https://avatars.githubusercontent.com/u/52671223?v=4?s=60" width="60px;" alt=""/><br /><sub><b>AndreiD049</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=AndreiD049" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
48
docs/proposals/inclusion-of-notes.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Inclusion of notes Proposal <!-- omit in TOC -->
|
||||
|
||||
Currently it is not possible within Foam to include other notes into a note. Next to including a full note it could be interesting to add functionalities that allow for greater flexibility. This proposal discusses some functionalities around inclusion of notes.
|
||||
|
||||
**IMPORTANT: This design is merely a proposal of a design that could be implemented. It DOES NOT represent a commitment by `Foam` developers to implement the features outlined in this document. This document is merely a mechanism to facilitate discussion of a possible future direction for `Foam`.**
|
||||
|
||||
- [Introduction](#introduction)
|
||||
- [New features](#new-features)
|
||||
- [Including a note](#including-a-note)
|
||||
- [Include a section of a note](#include-a-section-of-a-note)
|
||||
- [Include an attribute of a file (note property or frontmatter)](#include-an-attribute-of-a-file-note-property-or-frontmatter)
|
||||
|
||||
## Introduction
|
||||
|
||||
Initial work and thought on including a note was ignited by issue [#652](https://github.com/foambubble/foam/issues/652). Requested by a user was a likewise functionality as offered in Obsidian. This was simply the ability to include a note.
|
||||
|
||||
Whilst researching digital gardening for my own setup, I came across an in-depth overview by [Maggie Appleton](https://maggieappleton.com/roam-garden). Showing examples of her personal Roam Research I see valuable possibilites to connect more information, if we would add additional functionalities to the possibility of including a note. This proposal displays these possible functionalities and markup.
|
||||
|
||||
## New features
|
||||
|
||||
### Including a note
|
||||
|
||||
The minimal functionality is the ability to fully include a note. Markup used in Obsidian for this is `![[wikilink]]`. For Foam I would suggest to follow this syntax. Benefits being:
|
||||
|
||||
- Adds minimal amount of knowledge required as syntax is based on the syntax of creating a wikilink.
|
||||
- Makes the auto-complete work ouf-of-the-box, without any additional code and listeners required.
|
||||
|
||||
**Important**. A risk exists that a loop of including the same notes arises. E.g. Note A includes note B which includes note A. This needs to be prevented by the implementation and made visible to the user.
|
||||
|
||||
### Include a section of a note
|
||||
|
||||
It could be interesting to only include a section of a note instead of the entire note. In order to do so thse user should be able to use the following syntax:
|
||||
|
||||
`![[wikilink#section-b]]`
|
||||
|
||||
As a result it will include the section title + section content until the next section *or* end of file.
|
||||
|
||||
### Include an attribute of a file (note property or frontmatter)
|
||||
|
||||
As a user I could be interested in collecting the value of any given proeprty for a note. For example, I might want to include the tags as defined in the frontmatter of note A. This should be possible via the syntax:
|
||||
|
||||
`![[wikilink:<property>]]`
|
||||
|
||||
The property value should be lookedup by foam defined properties, e.g. title, **or** any property defined in the frontmatter of a note.
|
||||
|
||||
So, the example of including the tags of a note should be:
|
||||
|
||||
`![[wikilink:tags]]`
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
## Using foam-gatsby-template
|
||||
|
||||
You can use [foam-gatsby-template](https://github.com/mathieudutour/foam-gatsby-template) to generate a static site to host it online on Github or [Vercel](https://vercel.com).
|
||||
You can use [foam-gatsby-template](https://github.com/mathieudutour/foam-gatsby-template) to generate a static site to host it online on GitHub or [Vercel](https://vercel.com).
|
||||
|
||||
### Publishing your foam to GitHub pages
|
||||
|
||||
It comes configured with Github actions to auto deploy to Github pages when changes are pushed to your main branch.
|
||||
It comes configured with GitHub actions to auto deploy to GitHub pages when changes are pushed to your main branch.
|
||||
|
||||
### Publishing your foam to Vercel
|
||||
|
||||
@@ -17,7 +17,7 @@ cd _layouts
|
||||
npm run build
|
||||
```
|
||||
|
||||
Remove `public` from your .gitignore file then commit and push your public folder in `_layouts` to Github.
|
||||
Remove `public` from your .gitignore file then commit and push your public folder in `_layouts` to GitHub.
|
||||
|
||||
Log into your Vercel account. (Create one if you don't have it already.)
|
||||
|
||||
@@ -28,3 +28,7 @@ That's it!
|
||||
## Using foam-template-gatsby-kb
|
||||
|
||||
You can use another template [foam-template-gatsby-kb](https://github.com/hikerpig/foam-template-gatsby-kb), and host it on [Vercel](https://vercel.com) or [Netlify](https://www.netlify.com/).
|
||||
|
||||
## Using foam-template-gatsby-theme-primer-wiki
|
||||
|
||||
You can use another template [foam-template-gatsby-theme-primer-wiki](https://github.com/theowenyoung/foam-template-gatsby-theme-primer-wiki), ([Demo](https://demo-wiki.owenyoung.com/)), and host it on Github Pages, [Vercel](https://vercel.com) or [Netlify](https://www.netlify.com/).
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Github Pages
|
||||
# GitHub Pages
|
||||
|
||||
- In VSCode workspace settings set `"foam.edit.linkReferenceDefinitions": "withoutExtensions"`
|
||||
- Execute the “Foam: Run Janitor” command from the command palette.
|
||||
|
||||
@@ -82,6 +82,6 @@ Finally, if all is successful, Vercel will show the detected framework: Jekyll.
|
||||
And now, Vercel will take care of building and rendering our foam workspace each time on push. Vercel will publish our site to `xxx.vercel.app`, we can also define a custom domain name for our Vercel website.
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[publish-to-github-pages]: publish-to-github-pages.md "Github Pages"
|
||||
[publish-to-github-pages]: publish-to-github-pages.md "GitHub Pages"
|
||||
[math-support-with-katex]: math-support-with-katex.md "Katex Math Rendering"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
@@ -30,7 +30,7 @@ With this #recipe you can create notes on your iOS device, which will automatica
|
||||
2. the repository name of your Foam repo
|
||||
3. the GitHub access token from step 7
|
||||
4. An author name
|
||||
11. Check your Github repo for a commit
|
||||
11. Check your GitHub repo for a commit
|
||||
12. If you are publishing your Foam to the web you may want to edit your publishing configuration to exclude inbox files - as publishing (and method) is a user choice that is beyond the scope of this recipe
|
||||
|
||||
## Code for Drafts Action
|
||||
|
||||
@@ -27,5 +27,5 @@ You can use [Mermaid](https://marketplace.visualstudio.com/items?itemName=bierne
|
||||
4. Embed the diagram file as you embedding the image file, for example: ``
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[publish-to-github-pages]: ../publishing/publish-to-github-pages.md "Github Pages"
|
||||
[publish-to-github-pages]: ../publishing/publish-to-github-pages.md "GitHub Pages"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
@@ -37,7 +37,7 @@ A #recipe is a guide, tip or strategy for getting the most out of your Foam work
|
||||
|
||||
## Write
|
||||
|
||||
- Link documents with [[wiki-links]].
|
||||
- Link documents with [[wikilinks]].
|
||||
- Use shortcuts for [[creating-new-notes]]
|
||||
- Instantly create and access your [[daily-notes]]
|
||||
- Add and explore [[tags]]
|
||||
@@ -56,6 +56,7 @@ A #recipe is a guide, tip or strategy for getting the most out of your Foam work
|
||||
- _More..._
|
||||
- VS Code Advanced Features [[todo]] [[good-first-task]]
|
||||
- Focus with Zen Mode
|
||||
- Display content of other notes in the preview tab by [[including-notes]]
|
||||
|
||||
## Version control
|
||||
|
||||
@@ -112,7 +113,7 @@ _See [[contribution-guide]] and [[how-to-write-recipes]]._
|
||||
[graph-visualisation]: ../features/graph-visualisation.md "Graph Visualisation"
|
||||
[backlinking]: ../features/backlinking.md "Backlinking"
|
||||
[unlinked-references]: ../dev/unlinked-references.md "Unlinked references (stub)"
|
||||
[wiki-links]: ../wiki-links.md "Wiki Links"
|
||||
[wikilinks]: ../wikilinks.md "Wikilinks"
|
||||
[creating-new-notes]: ../features/creating-new-notes.md "Creating New Notes"
|
||||
[daily-notes]: ../features/daily-notes.md "Daily notes"
|
||||
[tags]: ../features/tags.md "Tags"
|
||||
@@ -125,9 +126,10 @@ _See [[contribution-guide]] and [[how-to-write-recipes]]._
|
||||
[add-images-to-notes]: add-images-to-notes.md "Add images to your notes"
|
||||
[shows-image-preview-on-hover]: shows-image-preview-on-hover.md "Shows Image Preview on Hover"
|
||||
[good-first-task]: ../dev/good-first-task.md "Good First Task"
|
||||
[including-notes]: ../features/including-notes.md "Including notes in a note"
|
||||
[git-integration]: ../features/git-integration.md "Git Integration"
|
||||
[write-your-notes-in-github-gist]: write-your-notes-in-github-gist.md "Write your notes in GitHub Gist"
|
||||
[publish-to-github-pages]: ../publishing/publish-to-github-pages.md "Github Pages"
|
||||
[publish-to-github-pages]: ../publishing/publish-to-github-pages.md "GitHub Pages"
|
||||
[publish-to-gitlab-pages]: ../publishing/publish-to-gitlab-pages.md "GitLab Pages"
|
||||
[publish-to-azure-devops-wiki]: ../publishing/publish-to-azure-devops-wiki.md "Publish to Azure DevOps Wiki"
|
||||
[publish-to-vercel]: ../publishing/publish-to-vercel.md "Publish to Vercel"
|
||||
|
||||
@@ -15,7 +15,7 @@ Pros
|
||||
- Provides functionality to edit, create, and browser markdown files.
|
||||
- Support journal mode, todo lists, and free writing
|
||||
- Syncs to GitHub repo
|
||||
- Supports Wiki Links
|
||||
- Supports Wikilinks
|
||||
- Supports Backlinks
|
||||
- Developer is happy to prioritize Foam compatibility
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ Once you've opened/created the Foam repository, it will appear in the `Repositor
|
||||
|
||||
## Editing your workspace
|
||||
|
||||
When you create or open a page, you can edit the markdown content as usual, as well as [paste images](https://github.com/vsls-contrib/gistpad#pasting-images-1), and create [`[[links]]` to other pages](https://github.com/vsls-contrib/gistpad#links). When you type `[[`, you'll recieve auto-completion for the existing pages in your workspace, and you can also automatically create new pages by simply creating a link to it.
|
||||
When you create or open a page, you can edit the markdown content as usual, as well as [paste images](https://github.com/vsls-contrib/gistpad#pasting-images-1), and create [`[[links]]` to other pages](https://github.com/vsls-contrib/gistpad#links). When you type `[[`, you'll receive auto-completion for the existing pages in your workspace, and you can also automatically create new pages by simply creating a link to it.
|
||||
|
||||
Since you're using the Visual Studio Code markdown editor, you can benefit from all of the rich language services (e.g. syntax highlighting, header collapsing), as well as the extension ecosystem (e.g. [Emojisense](https://marketplace.visualstudio.com/items?itemName=bierner.emojisense)).
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ These extensions defined in `.vscode/extensions.json` are automatically installe
|
||||
This list is subject to change. Especially the Git ones.
|
||||
|
||||
- [Foam for VSCode](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode) (alpha)
|
||||
- [Markdown Notes](https://marketplace.visualstudio.com/items?itemName=kortina.vscode-markdown-notes)
|
||||
- [Markdown Links](https://marketplace.visualstudio.com/items?itemName=tchayen.markdown-links)
|
||||
- [Markdown All In One](https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one)
|
||||
- [Paste Image](https://marketplace.visualstudio.com/items?itemName=mushan.vscode-paste-image)
|
||||
- [Spell Right](https://marketplace.visualstudio.com/items?itemName=ban.spellright)
|
||||
|
||||
## Extensions For Additional Features
|
||||
|
||||
@@ -15,10 +15,11 @@ These extensions are not (yet?) defined in `.vscode/extensions.json`, but have b
|
||||
|
||||
- [Emojisense](https://marketplace.visualstudio.com/items?itemName=bierner.emojisense)
|
||||
- [Markdown Emoji](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-emoji) (adds `:smile:` syntax, works with emojisense to provide autocomplete for this syntax)
|
||||
- [Mermaid Support for Preview](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid)
|
||||
- [Markdown Preview Mermaid Support](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid)
|
||||
- [Mermaid Markdown Syntax Highlighting](https://marketplace.visualstudio.com/items?itemName=bpruitt-goddard.mermaid-markdown-syntax-highlighting)
|
||||
- [VSCode PDF Viewing](https://marketplace.visualstudio.com/items?itemName=tomoki1207.pdf)
|
||||
- [Git Lens](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens)
|
||||
- [Markdown Extended](https://marketplace.visualstudio.com/items?itemName=jebbs.markdown-extended) (with `kbd` option disabled, `kbd` turns wiki-links into non-clickable buttons)
|
||||
- [Project Manager](https://marketplace.visualstudio.com/items?itemName=alefragnani.project-manager) (to quickly switch between projects)
|
||||
- [Markdown Extended](https://marketplace.visualstudio.com/items?itemName=jebbs.markdown-extended) (with `kbd` option disabled, `kbd` turns wikilinks into non-clickable buttons)
|
||||
- [GitDoc](https://marketplace.visualstudio.com/items?itemName=vsls-contrib.gitdoc) (easy version management via git auto commits)
|
||||
- [Markdown Footnotes](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-footnotes) (Adds [^footnote] syntax support to VS Code's built-in markdown preview)
|
||||
- [Todo Tree](https://marketplace.visualstudio.com/items?itemName=Gruntfuggly.todo-tree) (Searches workspace for TODO and related comments and summarizes those lines in vs-code gutter)
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
# Wiki Links
|
||||
# Wikilinks
|
||||
|
||||
Foam enables you to Link pages together using `[[file-name]]` annotations (i.e. `[[media-wiki]]` links).
|
||||
Foam enables you to Link pages together using `[[file-name]]` annotations (i.e. `[[MediaWiki]]` links).
|
||||
|
||||
- Type `[[` and start typing a file name for autocompletion.
|
||||
- Note that your file names should be in `lower-dash-case.md`, and your wiki links should reference file names exactly: `[[lower-dash-case]]`, not `[[Lower Dash Case]]`.
|
||||
- See [[link-formatting-and-autocompletion]] for more information, and how to setup your link autocompletions to make this easier.
|
||||
- `Cmd` + `Click` ( `Ctrl` + `Click` on Windows ) on file name to navigate to file (`F12` also works while your cursor is on the file name)
|
||||
- `Cmd` + `Click` ( `Ctrl` + `Click` on Windows ) on non-existent file to create that file in the workspace.
|
||||
- The note creation makes use of the special [`new-note.md` note template](features/note-templates)
|
||||
|
||||
> If the `F12` shortcut feels unnatural you can rebind it at File > Preferences > Keyboard Shortcuts by searching for `editor.action.revealDefinition`.
|
||||
|
||||
## Markdown compatibility
|
||||
|
||||
The [Foam for VSCode](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode) extension automatically generates [[link-reference-definitions]] at the bottom of the file to make wiki-links compatible with Markdown tools and parsers.
|
||||
The [Foam for VSCode](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode) extension automatically generates [[link-reference-definitions]] at the bottom of the file to make wikilinks compatible with Markdown tools and parsers.
|
||||
|
||||
## Read more
|
||||
|
||||
- [[foam-file-format]]
|
||||
- [[note-templates]]
|
||||
- [[link-formatting-and-autocompletion]]
|
||||
- See [[link-reference-definition-improvements]] for further discussion on current problems and potential solutions.
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[link-reference-definitions]: features/link-reference-definitions.md "Link Reference Definitions"
|
||||
[foam-file-format]: dev/foam-file-format.md "Foam File Format"
|
||||
[note-templates]: features/note-templates.md "Note Templates"
|
||||
[link-reference-definition-improvements]: dev/link-reference-definition-improvements.md "Link Reference Definition Improvements"
|
||||
[//end]: # "Autogenerated link references"
|
||||
@@ -4,5 +4,5 @@
|
||||
],
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.13.6"
|
||||
"version": "0.15.6"
|
||||
}
|
||||
|
||||
@@ -9,10 +9,6 @@
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"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",
|
||||
"vscode:package-extension": "yarn workspace foam-vscode package-extension",
|
||||
"vscode:install-extension": "yarn workspace foam-vscode install-extension",
|
||||
"vscode:publish-extension": "yarn workspace foam-vscode publish-extension",
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: [['@babel/plugin-transform-runtime', { helpers: false }]],
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
# Foam Core
|
||||
|
||||
This module contains the core functions, model, and API of Foam.
|
||||
It is used by its clients to integrate Foam in various use cases, from VsCode extension, to CLI, to CI integrations.
|
||||
|
||||
## Local Development
|
||||
|
||||
Below is a list of commands you will probably find useful.
|
||||
|
||||
### `yarn watch`
|
||||
|
||||
Runs the project in development/watch mode. Your project will be rebuilt upon changes.
|
||||
|
||||
### `yarn build`
|
||||
|
||||
Bundles the package to the `dist` folder. The package is optimized and bundled with Rollup into multiple formats (CommonJS, UMD, and ES Module).
|
||||
|
||||
### `yarn test`
|
||||
|
||||
Runs the test watcher (Jest) in an interactive mode.
|
||||
By default, runs tests related to files changed since the last commit.
|
||||
@@ -1,48 +0,0 @@
|
||||
{
|
||||
"name": "foam-core",
|
||||
"repository": "https://github.com/foambubble/foam",
|
||||
"version": "0.13.6",
|
||||
"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": "tsdx build --tsconfig ./tsconfig.build.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.10.4",
|
||||
"@babel/plugin-transform-runtime": "^7.10.4",
|
||||
"@babel/preset-env": "^7.10.4",
|
||||
"@babel/preset-typescript": "^7.10.4",
|
||||
"@types/github-slugger": "^1.3.0",
|
||||
"@types/lodash": "^4.14.157",
|
||||
"@types/micromatch": "^4.0.1",
|
||||
"@types/picomatch": "^2.2.1",
|
||||
"husky": "^4.2.5",
|
||||
"tsdx": "^0.13.2",
|
||||
"tslib": "^2.0.0",
|
||||
"typescript": "^3.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"detect-newline": "^3.1.0",
|
||||
"fast-array-diff": "^1.0.0",
|
||||
"github-slugger": "^1.3.0",
|
||||
"glob": "^7.1.6",
|
||||
"lodash": "^4.17.21",
|
||||
"micromatch": "^4.0.2",
|
||||
"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",
|
||||
"yaml": "^1.10.0"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts"
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import { createMarkdownParser } from './markdown-provider';
|
||||
import { FoamConfig, Foam, IDataStore, FoamGraph } from './index';
|
||||
import { FoamWorkspace } from './model/workspace';
|
||||
import { Matcher } from './services/datastore';
|
||||
import { ResourceProvider } from 'model/provider';
|
||||
|
||||
export const bootstrap = async (
|
||||
config: FoamConfig,
|
||||
dataStore: IDataStore,
|
||||
initialProviders: ResourceProvider[]
|
||||
) => {
|
||||
const parser = createMarkdownParser([]);
|
||||
const matcher = new Matcher(
|
||||
config.workspaceFolders,
|
||||
config.includeGlobs,
|
||||
config.ignoreGlobs
|
||||
);
|
||||
const workspace = new FoamWorkspace();
|
||||
await Promise.all(initialProviders.map(p => workspace.registerProvider(p)));
|
||||
|
||||
const graph = FoamGraph.fromWorkspace(workspace, true);
|
||||
|
||||
const foam: Foam = {
|
||||
workspace,
|
||||
graph,
|
||||
config,
|
||||
services: {
|
||||
dataStore,
|
||||
parser,
|
||||
matcher,
|
||||
},
|
||||
dispose: () => {
|
||||
workspace.dispose();
|
||||
graph.dispose();
|
||||
},
|
||||
};
|
||||
|
||||
return foam;
|
||||
};
|
||||
@@ -1,75 +0,0 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { merge } from 'lodash';
|
||||
import { Logger } from './utils/log';
|
||||
import { URI } from './model/uri';
|
||||
|
||||
export interface FoamConfig {
|
||||
workspaceFolders: URI[];
|
||||
includeGlobs: string[];
|
||||
ignoreGlobs: string[];
|
||||
get<T>(path: string): T | undefined;
|
||||
get<T>(path: string, defaultValue: T): T;
|
||||
}
|
||||
|
||||
const DEFAULT_INCLUDES = ['**/*'];
|
||||
|
||||
const DEFAULT_IGNORES = ['**/node_modules/**'];
|
||||
|
||||
export const createConfigFromObject = (
|
||||
workspaceFolders: URI[],
|
||||
include: string[],
|
||||
ignore: string[],
|
||||
settings: any
|
||||
) => {
|
||||
const config: FoamConfig = {
|
||||
workspaceFolders: workspaceFolders,
|
||||
includeGlobs: include,
|
||||
ignoreGlobs: ignore,
|
||||
get: <T>(path: string, defaultValue?: T) => {
|
||||
const tokens = path.split('.');
|
||||
const value = tokens.reduce((acc, t) => acc?.[t], settings);
|
||||
return value ?? defaultValue;
|
||||
},
|
||||
};
|
||||
return config;
|
||||
};
|
||||
|
||||
export const createConfigFromFolders = (
|
||||
workspaceFolders: URI[] | URI,
|
||||
options: {
|
||||
include?: string[];
|
||||
ignore?: string[];
|
||||
} = {}
|
||||
): FoamConfig => {
|
||||
if (!Array.isArray(workspaceFolders)) {
|
||||
workspaceFolders = [workspaceFolders];
|
||||
}
|
||||
const workspaceConfig: any = workspaceFolders.reduce(
|
||||
(acc, f) => merge(acc, parseConfig(URI.joinPath(f, 'config.json'))),
|
||||
{}
|
||||
);
|
||||
// For security reasons local plugins can only be
|
||||
// activated via user config
|
||||
if ('experimental' in workspaceConfig) {
|
||||
delete workspaceConfig['experimental']['localPlugins'];
|
||||
}
|
||||
|
||||
const userConfig = parseConfig(URI.file(`~/.foam/config.json`));
|
||||
|
||||
const settings = merge(workspaceConfig, userConfig);
|
||||
|
||||
return createConfigFromObject(
|
||||
workspaceFolders,
|
||||
options.include ?? DEFAULT_INCLUDES,
|
||||
options.ignore ?? DEFAULT_IGNORES,
|
||||
settings
|
||||
);
|
||||
};
|
||||
|
||||
const parseConfig = (path: URI) => {
|
||||
try {
|
||||
return JSON.parse(readFileSync(URI.toFsPath(path), 'utf8'));
|
||||
} catch {
|
||||
Logger.debug('Could not read configuration from ' + URI.toString(path));
|
||||
}
|
||||
};
|
||||
@@ -1,73 +0,0 @@
|
||||
import {
|
||||
Resource,
|
||||
ResourceLink,
|
||||
NoteLinkDefinition,
|
||||
ResourceParser,
|
||||
} from './model/note';
|
||||
import { FoamConfig } from './config';
|
||||
import {
|
||||
IDataStore,
|
||||
FileDataStore,
|
||||
Matcher,
|
||||
IMatcher,
|
||||
} from './services/datastore';
|
||||
import { ILogger } from './utils/log';
|
||||
import { IDisposable, isDisposable } from './common/lifecycle';
|
||||
import { FoamWorkspace } from './model/workspace';
|
||||
import { FoamGraph } from '../src/model/graph';
|
||||
import { URI } from './model/uri';
|
||||
|
||||
export { Position } from './model/position';
|
||||
export { Range } from './model/range';
|
||||
export { IDataStore, FileDataStore, Matcher, IMatcher };
|
||||
export { ILogger };
|
||||
export { LogLevel, LogLevelThreshold, Logger, BaseLogger } from './utils/log';
|
||||
export { Event, Emitter } from './common/event';
|
||||
export { FoamConfig };
|
||||
export { ResourceProvider } from './model/provider';
|
||||
export { IDisposable, isDisposable };
|
||||
|
||||
export {
|
||||
createMarkdownReferences,
|
||||
stringifyMarkdownLinkReferenceDefinition,
|
||||
createMarkdownParser,
|
||||
MarkdownResourceProvider,
|
||||
} from './markdown-provider';
|
||||
|
||||
export {
|
||||
TextEdit,
|
||||
generateHeading,
|
||||
generateLinkReferences,
|
||||
getKebabCaseFileName,
|
||||
LINK_REFERENCE_DEFINITION_HEADER,
|
||||
LINK_REFERENCE_DEFINITION_FOOTER,
|
||||
} from './janitor';
|
||||
|
||||
export { applyTextEdit } from './janitor/apply-text-edit';
|
||||
|
||||
export { createConfigFromFolders } from './config';
|
||||
|
||||
export { bootstrap } from './bootstrap';
|
||||
|
||||
export {
|
||||
Resource,
|
||||
ResourceLink,
|
||||
URI,
|
||||
FoamWorkspace,
|
||||
FoamGraph,
|
||||
NoteLinkDefinition,
|
||||
ResourceParser,
|
||||
};
|
||||
|
||||
export interface Services {
|
||||
dataStore: IDataStore;
|
||||
parser: ResourceParser;
|
||||
matcher: IMatcher;
|
||||
}
|
||||
|
||||
export interface Foam extends IDisposable {
|
||||
services: Services;
|
||||
workspace: FoamWorkspace;
|
||||
graph: FoamGraph;
|
||||
config: FoamConfig;
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
import * as fs from 'fs';
|
||||
import path from 'path';
|
||||
import { Node } from 'unist';
|
||||
import { isNotNull } from '../utils';
|
||||
import { Resource } from '../model/note';
|
||||
import unified from 'unified';
|
||||
import { FoamConfig } from '../config';
|
||||
import { Logger } from '../utils/log';
|
||||
import { URI } from '../model/uri';
|
||||
|
||||
export interface FoamPlugin {
|
||||
name: string;
|
||||
description?: string;
|
||||
parser?: ParserPlugin;
|
||||
}
|
||||
|
||||
export interface ParserPlugin {
|
||||
name?: string;
|
||||
visit?: (node: Node, note: Resource) => void;
|
||||
onDidInitializeParser?: (parser: unified.Processor) => void;
|
||||
onWillParseMarkdown?: (markdown: string) => string;
|
||||
onWillVisitTree?: (tree: Node, note: Resource) => void;
|
||||
onDidVisitTree?: (tree: Node, note: Resource) => void;
|
||||
onDidFindProperties?: (properties: any, note: Resource) => void;
|
||||
}
|
||||
|
||||
export interface PluginConfig {
|
||||
enabled?: boolean;
|
||||
pluginFolders?: string[];
|
||||
}
|
||||
|
||||
export const SETTINGS_PATH = 'experimental.localPlugins';
|
||||
|
||||
export async function loadPlugins(config: FoamConfig): Promise<FoamPlugin[]> {
|
||||
const pluginConfig = config.get<PluginConfig>(SETTINGS_PATH, {});
|
||||
const isFeatureEnabled = pluginConfig.enabled ?? false;
|
||||
if (!isFeatureEnabled) {
|
||||
return [];
|
||||
}
|
||||
const pluginDirs: URI[] =
|
||||
pluginConfig.pluginFolders?.map(URI.file) ??
|
||||
findPluginDirs(config.workspaceFolders);
|
||||
|
||||
const plugins = await Promise.all(
|
||||
pluginDirs
|
||||
.filter(dir => fs.statSync(URI.toFsPath(dir)).isDirectory)
|
||||
.map(async dir => {
|
||||
try {
|
||||
const pluginFile = path.join(URI.toFsPath(dir), 'index.js');
|
||||
fs.accessSync(pluginFile);
|
||||
Logger.info(`Found plugin at [${pluginFile}]. Loading..`);
|
||||
const plugin = validate(await import(pluginFile));
|
||||
return plugin;
|
||||
} catch (e) {
|
||||
Logger.error(`Error while loading plugin at [${dir}] - skipping`, e);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
);
|
||||
return plugins.filter(isNotNull);
|
||||
}
|
||||
|
||||
function findPluginDirs(workspaceFolders: URI[]) {
|
||||
return workspaceFolders
|
||||
.map(root => URI.joinPath(root, '.foam', 'plugins'))
|
||||
.reduce((acc, pluginDir) => {
|
||||
try {
|
||||
const content = fs
|
||||
.readdirSync(URI.toFsPath(pluginDir))
|
||||
.map(dir => URI.joinPath(pluginDir, dir));
|
||||
return [
|
||||
...acc,
|
||||
...content.filter(c => fs.statSync(URI.toFsPath(c)).isDirectory()),
|
||||
];
|
||||
} catch {
|
||||
return acc;
|
||||
}
|
||||
}, [] as URI[]);
|
||||
}
|
||||
|
||||
function validate(plugin: any): FoamPlugin {
|
||||
if (!plugin.name) {
|
||||
throw new Error('Plugin must export `name` string property');
|
||||
}
|
||||
return plugin;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { isSome } from './core';
|
||||
const HASHTAG_REGEX = /(^|\s)#([0-9]*[\p{L}/_-][\p{L}\p{N}/_-]*)/gmu;
|
||||
const WORD_REGEX = /(^|\s)([0-9]*[\p{L}/_-][\p{L}\p{N}/_-]*)/gmu;
|
||||
|
||||
export const extractHashtags = (text: string): Set<string> => {
|
||||
return isSome(text)
|
||||
? new Set(Array.from(text.matchAll(HASHTAG_REGEX), m => m[2].trim()))
|
||||
: new Set();
|
||||
};
|
||||
|
||||
export const extractTagsFromProp = (prop: string | string[]): Set<string> => {
|
||||
const text = Array.isArray(prop) ? prop.join(' ') : prop;
|
||||
return isSome(text)
|
||||
? new Set(Array.from(text.matchAll(WORD_REGEX)).map(m => m[2].trim()))
|
||||
: new Set();
|
||||
};
|
||||
@@ -1,44 +0,0 @@
|
||||
import { createConfigFromFolders } from '../src/config';
|
||||
import { Logger } from '../src/utils/log';
|
||||
import { URI } from '../src/model/uri';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
const testFolder = URI.joinPath(URI.file(__dirname), 'test-config');
|
||||
|
||||
describe('Foam configuration', () => {
|
||||
it('can read settings from config.json', () => {
|
||||
const config = createConfigFromFolders([
|
||||
URI.joinPath(testFolder, 'folder1'),
|
||||
]);
|
||||
expect(config.get('feature1.setting1.value')).toBeTruthy();
|
||||
expect(config.get('feature2.value')).toEqual(12);
|
||||
|
||||
const section = config.get<{ value: boolean }>('feature1.setting1');
|
||||
expect(section!.value).toBeTruthy();
|
||||
});
|
||||
|
||||
it('can merge settings from multiple foam folders', () => {
|
||||
const config = createConfigFromFolders([
|
||||
URI.joinPath(testFolder, 'folder1'),
|
||||
URI.joinPath(testFolder, 'folder2'),
|
||||
]);
|
||||
|
||||
// override value
|
||||
expect(config.get('feature1.setting1.value')).toBe(false);
|
||||
// this was not overridden
|
||||
expect(config.get('feature1.setting1.extraValue')).toEqual('go foam');
|
||||
// new value from second config file
|
||||
expect(config.get('feature1.setting1.value2')).toBe('hello');
|
||||
|
||||
// this whole section doesn't exist in second file
|
||||
expect(config.get('feature2.value')).toEqual(12);
|
||||
});
|
||||
|
||||
it('cannot activate local plugins from workspace config', () => {
|
||||
const config = createConfigFromFolders([
|
||||
URI.joinPath(testFolder, 'enable-plugins'),
|
||||
]);
|
||||
expect(config.get('experimental.localPlugins.enabled')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -1,94 +0,0 @@
|
||||
import path from 'path';
|
||||
import { FoamWorkspace } from '../src';
|
||||
import { NoteLinkDefinition, Resource } from '../src/model/note';
|
||||
import { IDataStore, Matcher } from '../src/services/datastore';
|
||||
import { MarkdownResourceProvider } from '../src/markdown-provider';
|
||||
import { Range } from '../src/model/range';
|
||||
import { URI } from '../src/model/uri';
|
||||
import { Logger } from '../src/utils/log';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
const position = Range.create(0, 0, 0, 100);
|
||||
|
||||
const documentStart = position.start;
|
||||
const documentEnd = position.end;
|
||||
const eol = '\n';
|
||||
|
||||
/**
|
||||
* Turns a string into a URI
|
||||
* The goal of this function is to make sure we are consistent in the
|
||||
* way we generate URIs (and therefore IDs) across the tests
|
||||
*/
|
||||
export const strToUri = URI.file;
|
||||
|
||||
export const noOpDataStore = (): IDataStore => ({
|
||||
read: _ => Promise.resolve(''),
|
||||
list: _ => Promise.resolve([]),
|
||||
});
|
||||
|
||||
export const createTestWorkspace = () => {
|
||||
const workspace = new FoamWorkspace();
|
||||
const matcher = new Matcher([URI.file('/')], ['**/*']);
|
||||
const provider = new MarkdownResourceProvider(
|
||||
matcher,
|
||||
undefined,
|
||||
undefined,
|
||||
noOpDataStore()
|
||||
);
|
||||
workspace.registerProvider(provider);
|
||||
return workspace;
|
||||
};
|
||||
|
||||
export const createTestNote = (params: {
|
||||
uri: string;
|
||||
title?: string;
|
||||
definitions?: NoteLinkDefinition[];
|
||||
links?: Array<{ slug: string } | { to: string }>;
|
||||
text?: string;
|
||||
root?: URI;
|
||||
}): Resource => {
|
||||
const root = params.root ?? URI.file('/');
|
||||
return {
|
||||
uri: URI.resolve(params.uri, root),
|
||||
type: 'note',
|
||||
properties: {},
|
||||
title: params.title ?? path.parse(strToUri(params.uri).path).base,
|
||||
definitions: params.definitions ?? [],
|
||||
tags: new Set(),
|
||||
links: params.links
|
||||
? params.links.map((link, index) => {
|
||||
const range = Range.create(
|
||||
position.start.line + index,
|
||||
position.start.character,
|
||||
position.start.line + index,
|
||||
position.end.character
|
||||
);
|
||||
return 'slug' in link
|
||||
? {
|
||||
type: 'wikilink',
|
||||
slug: link.slug,
|
||||
target: link.slug,
|
||||
range: range,
|
||||
text: 'link text',
|
||||
}
|
||||
: {
|
||||
type: 'link',
|
||||
target: link.to,
|
||||
label: 'link text',
|
||||
range: range,
|
||||
};
|
||||
})
|
||||
: [],
|
||||
source: {
|
||||
eol: eol,
|
||||
end: documentEnd,
|
||||
contentStart: documentStart,
|
||||
text: params.text ?? '',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
describe('Test utils', () => {
|
||||
it('are happy', () => {});
|
||||
});
|
||||
@@ -1,63 +0,0 @@
|
||||
import path from 'path';
|
||||
import { loadPlugins } from '../src/plugins';
|
||||
import { createMarkdownParser } from '../src/markdown-provider';
|
||||
import { FoamConfig, createConfigFromObject } from '../src/config';
|
||||
import { URI } from '../src/model/uri';
|
||||
import { Logger } from '../src/utils/log';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
const config: FoamConfig = createConfigFromObject([], [], [], {
|
||||
experimental: {
|
||||
localPlugins: {
|
||||
enabled: true,
|
||||
pluginFolders: [path.join(__dirname, 'test-plugin')],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
describe('Foam plugins', () => {
|
||||
it('will not load if feature is not explicitly enabled', async () => {
|
||||
let plugins = await loadPlugins(createConfigFromObject([], [], [], {}));
|
||||
expect(plugins.length).toEqual(0);
|
||||
plugins = await loadPlugins(
|
||||
createConfigFromObject([], [], [], {
|
||||
experimental: {
|
||||
localPlugins: {},
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(plugins.length).toEqual(0);
|
||||
plugins = await loadPlugins(
|
||||
createConfigFromObject([], [], [], {
|
||||
experimental: {
|
||||
localPlugins: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(plugins.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('can load', async () => {
|
||||
const plugins = await loadPlugins(config);
|
||||
expect(plugins.length).toEqual(1);
|
||||
expect(plugins[0].name).toEqual('Test Plugin');
|
||||
});
|
||||
|
||||
it('supports parser extension', async () => {
|
||||
const plugins = await loadPlugins(config);
|
||||
const parserPlugin = plugins[0].parser;
|
||||
expect(parserPlugin).not.toBeUndefined();
|
||||
const parser = createMarkdownParser([parserPlugin!]);
|
||||
|
||||
const note = parser.parse(
|
||||
URI.file('/path/to/a'),
|
||||
`
|
||||
# This is a note with header
|
||||
and some content`
|
||||
);
|
||||
expect(note.properties.hasHeading).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
const middleware = next => ({
|
||||
setNote: note => {
|
||||
note.properties['injectedByMiddleware'] = true;
|
||||
return next.setNote(note);
|
||||
},
|
||||
});
|
||||
|
||||
const parser = {
|
||||
visit: (node, note) => {
|
||||
if (node.type === 'heading') {
|
||||
note.properties.hasHeading = true;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
name: 'Test Plugin',
|
||||
graphMiddleware: middleware,
|
||||
parser: parser,
|
||||
};
|
||||
@@ -1,62 +0,0 @@
|
||||
import { extractHashtags } from '../src/utils';
|
||||
import { Logger } from '../src/utils/log';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
describe('hashtag extraction', () => {
|
||||
it('works with simple strings', () => {
|
||||
expect(extractHashtags('hello #world on #this planet')).toEqual(
|
||||
new Set(['world', 'this'])
|
||||
);
|
||||
});
|
||||
it('works with tags at beginning or end of text', () => {
|
||||
expect(extractHashtags('#hello world on this #planet')).toEqual(
|
||||
new Set(['hello', 'planet'])
|
||||
);
|
||||
});
|
||||
it('supports _ and -', () => {
|
||||
expect(extractHashtags('#hello-world on #this_planet')).toEqual(
|
||||
new Set(['hello-world', 'this_planet'])
|
||||
);
|
||||
});
|
||||
it('supports nested tags', () => {
|
||||
expect(extractHashtags('#parent/child on #planet')).toEqual(
|
||||
new Set(['parent/child', 'planet'])
|
||||
);
|
||||
});
|
||||
it('ignores tags that only have numbers in text', () => {
|
||||
expect(
|
||||
extractHashtags('this #123 tag should be ignore, but not #123four')
|
||||
).toEqual(new Set(['123four']));
|
||||
});
|
||||
it('supports unicode letters like Chinese charaters', () => {
|
||||
expect(
|
||||
extractHashtags(`
|
||||
this #tag_with_unicode_letters_汉字, pure Chinese tag like #纯中文标签 and
|
||||
other mixed tags like #标签1 #123四 should work
|
||||
`)
|
||||
).toEqual(
|
||||
new Set(['tag_with_unicode_letters_汉字', '纯中文标签', '标签1', '123四'])
|
||||
);
|
||||
});
|
||||
|
||||
it('ignores hashes in plain text urls and links', () => {
|
||||
expect(
|
||||
extractHashtags(`
|
||||
test text with url https://site.com/#section1 https://site.com/home#section2 and
|
||||
https://site.com/home/#section3a
|
||||
[link](https://site.com/#section4) with [link2](https://site.com/home#section5) #control
|
||||
hello world
|
||||
`)
|
||||
).toEqual(new Set(['control']));
|
||||
});
|
||||
|
||||
it('ignores hashes in links to sections', () => {
|
||||
expect(
|
||||
extractHashtags(`
|
||||
this is a wikilink to [[#section1]] in the file and a [[link#section2]] in another
|
||||
this is a [link](#section3) to a section
|
||||
`)
|
||||
).toEqual(new Set());
|
||||
});
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "ESNext"
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src",
|
||||
"composite": true,
|
||||
"esModuleInterop": true,
|
||||
"importHelpers": true,
|
||||
"downlevelIteration": 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": [
|
||||
"ES2019", "es2020.string"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
"types"
|
||||
]
|
||||
}
|
||||
@@ -4,6 +4,116 @@ All notable changes to the "foam-vscode" extension will be documented in this fi
|
||||
|
||||
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
|
||||
|
||||
## [0.15.5] - 2021-11-15
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Major improvement in navigation. Use link definitions and link references (#821)
|
||||
- Fixed bug showing in hover reference the same more than once when it had multiple links to another (#822)
|
||||
|
||||
Internal:
|
||||
|
||||
- Foam URI refactoring (#820)
|
||||
- Template service refactoring (#825)
|
||||
|
||||
## [0.15.4] - 2021-11-09
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Detached Foam URI from VS Code URI. This should improve several path related issues in Windows. Given how core this change is, the release is just about this refactoring to easily detect possible side effects.
|
||||
|
||||
## [0.15.3] - 2021-11-08
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Avoid delaying decorations on editor switch (#811 - thanks @memeplex)
|
||||
- Fix preview issue when embedding a note and using reference definitions (#808 - thanks @pderaaij)
|
||||
|
||||
## [0.15.2] - 2021-10-27
|
||||
|
||||
Features:
|
||||
|
||||
- Added `FOAM_DATE_*` template variables (#781)
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Dataviz: apply note type color to filter item label
|
||||
- Dataviz: optimized rendering of graph to reduce load on CPU (#795)
|
||||
- Preview: improved tag highlight in preview (#785 - thanks @pderaaij)
|
||||
- Better handling of link reference definition (#786 - thanks @pderaaij)
|
||||
- Link decorations are now enabled by default (can be turned off in settings)
|
||||
|
||||
## [0.15.1] - 2021-10-21
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Improved filtering controls for graph (#782)
|
||||
- Link Hover: Include other connected notes to link target
|
||||
|
||||
## [0.15.0] - 2021-10-04
|
||||
|
||||
Features:
|
||||
|
||||
- Preview on hover for wikilinks (#728 - thanks @JonasSprenger)
|
||||
- Added tags and controls to graph dataviz (#737 - thanks @dannysemi)
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Improved tags parsing (#708 - thanks @pderaaij)
|
||||
- Fixed support for resources named like JS Object methods (#729 - thanks @JonasSprenger)
|
||||
|
||||
## [0.14.2] - 2021-07-24
|
||||
|
||||
Features:
|
||||
|
||||
- Autocompletion for tags (#708 - thanks @pderaaij)
|
||||
- Use templates for new note created from wikilink (#712 - thanks @movermeyer)
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Improved performance of initial file loading (#730 - thanks @pderaaij)
|
||||
|
||||
## [0.14.1] - 2021-07-14
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Fixed NPE that would cause markdown preview to render incorrectly (#718 - thanks @pderaaij)
|
||||
|
||||
## [0.14.0] - 2021-07-13
|
||||
|
||||
Features:
|
||||
|
||||
- Create new note from selection (#666 - thanks @pderaaij)
|
||||
- Use templates for daily notes (#700 - thanks @movermeyer)
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Fixed for wikilink aliases in tables (#697 - thanks @pderaaij)
|
||||
- Fixed link definition generation in presence of aliased wikilinks (#698 - thanks @pderaaij)
|
||||
- Fixed template insertion of selected text (#701 - thanks @movermeyer)
|
||||
- Fixed preview navigation (#710 - thanks @pderaaij)
|
||||
|
||||
## [0.13.8] - 2021-07-02
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Improved handling of capitalization in wikilinks (#688 - thanks @pderaaij)
|
||||
- This update will make wikilinks with different capitalization, such as `[[wikilink]]` and `[[WikiLink]]` point to the same file. Please note that means that files that only differ in capitalization across the workspace would now be treated as having the same name
|
||||
- Allow dots in wikilinks (#689 - thanks @pderaaij)
|
||||
- Fixed a bug in the expansion of date snippets (thanks @syndenham-chorea)
|
||||
- Added support for wikilink alias syntax, like `[[wikilink|label]]` (#689 - thanks @pderaaij)
|
||||
|
||||
## [0.13.7] - 2021-06-05
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Fixed #667, incorrect resolution of foam-core library
|
||||
|
||||
Internal:
|
||||
|
||||
- BREAKING CHANGE: Removed Foam local plugins
|
||||
If you were previously using the alpha feature of Foam local plugins you will soon be able to migrate the functionality to the V1 API
|
||||
|
||||
## [0.13.6] - 2021-06-05
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
@@ -5,15 +5,114 @@
|
||||
[](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode)
|
||||
[](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode)
|
||||
|
||||
> ⚠️ This is an early stage software. Use at your own peril.
|
||||
> You can join the Foam Community on the [Foam Discord](https://foambubble.github.io/join-discord/e)
|
||||
|
||||
[Foam](https://foambubble.github.io/foam) is a note-taking tool that lives within VsCode, which means you can pair it with your favorite extensions for a great editing experience.
|
||||
[Foam](https://foambubble.github.io/foam) is a note-taking tool that lives within VS Code, which means you can pair it with your favorite extensions for a great editing experience.
|
||||
|
||||
Foam is open source, and allows you to create a local first, markdown based, personal knowledge base. You can also use it to publish your notes.
|
||||
|
||||
Foam is also meant to be extensible, so you can integrate with its internals to customize your knowledge base.
|
||||
|
||||
## Features
|
||||
|
||||
### Graph Visualization
|
||||
|
||||
See how your notes are connected via a [graph](https://foambubble.github.io/foam/features/graph-visualisation) with the `Foam: Show Graph` command.
|
||||
|
||||

|
||||
|
||||
### Link Autocompletion
|
||||
|
||||
Foam helps you create the connections between your notes, and your placeholders as well.
|
||||
|
||||

|
||||
|
||||
### Link Preview and Navigation
|
||||
|
||||
|
||||

|
||||
|
||||
### Go to definition, Peek References
|
||||
|
||||
See where a note is being referenced in your knowledge base.
|
||||
|
||||

|
||||
|
||||
### Navigation in Preview
|
||||
|
||||
Navigate your rendered notes in the VS Code preview panel.
|
||||
|
||||

|
||||
|
||||
### Note embed
|
||||
|
||||
Embed the content from other notes.
|
||||
|
||||

|
||||
|
||||
### Link Alias
|
||||
|
||||
Foam supports link aliasing, so you can have a `[[wikilink]]`, or a `[[wikilink|alias]]`.
|
||||
|
||||
### Templates
|
||||
|
||||
Use [custom templates](https://foambubble.github.io/foam/features/note-templates) to have avoid repetitve work on your notes.
|
||||
|
||||

|
||||
|
||||
### Backlinks Panel
|
||||
|
||||
Quickly check which notes are referencing the currently active note.
|
||||
See for each occurrence the context in which it lives, as well as a preview of the note.
|
||||
|
||||

|
||||
|
||||
### Tag Explorer Panel
|
||||
|
||||
Tag your notes and navigate them with the [Tag Explorer](https://foambubble.github.io/foam/features/tags).
|
||||
Foam also supports hierarchical tags.
|
||||
|
||||

|
||||
|
||||
### Orphans and Placeholder Panels
|
||||
|
||||
Orphans are note that have no inbound nor outbound links.
|
||||
Placeholders are dangling links, or notes without content.
|
||||
Keep them under control, and your knowledge base in better state, by using this panel.
|
||||
|
||||

|
||||
|
||||
### Syntax highlight
|
||||
|
||||
Foam highlights wikilinks and placeholder differently, to help you visualize your knowledge base.
|
||||
|
||||

|
||||
|
||||
### Daily note
|
||||
|
||||
Create a journal with [daily notes](https://foambubble.github.io/foam/features/daily-notes).
|
||||
|
||||

|
||||
|
||||
### Generate references for your wikilinks
|
||||
|
||||
Create markdown [references](https://foambubble.github.io/foam/features/link-reference-definitions) for `[[wikilinks]]`, to use your notes in a non-Foam workspace.
|
||||
With references you can also make your notes navigable both in GitHub UI as well as GitHub Pages.
|
||||
|
||||

|
||||
|
||||
### Commands
|
||||
|
||||
- Explore your knowledge base with the `Foam: Open Random Note` command
|
||||
- Access your daily note with the `Foam: Open Daily Note` command
|
||||
- Create a new note with the `Foam: Create New Note` command
|
||||
- This becomes very powerful when combined with [note templates](https://foambubble.github.io/foam/features/note-templates) and the `Foam: Create New Note from Template` command
|
||||
- See your workspace as a connected graph with the `Foam: Show Graph` command
|
||||
|
||||
## Recipes
|
||||
|
||||
People use Foam in different ways for different use cases, check out the [recipes](https://foambubble.github.io/foam/recipes/recipes) page for inspiration!
|
||||
|
||||
## Getting started
|
||||
|
||||
You really, _really_, **really** should read [Foam documentation](https://foambubble.github.io/foam), but if you can't be bothered, this is how to get started:
|
||||
@@ -22,24 +121,13 @@ You really, _really_, **really** should read [Foam documentation](https://foambu
|
||||
2. Clone the repository and open it in VS Code.
|
||||
3. When prompted to install recommended extensions, click **Install all** (or **Show Recommendations** if you want to review and install them one by one).
|
||||
|
||||
This will also install `Foam for VSCode`, but if you already have it installed, that's ok, just make sure you're up to date on the latest version.
|
||||
|
||||
You really, _really_, **really** should read [Foam documentation](https://foambubble.github.io/foam), but if you can't be bothered, this is how to get started:
|
||||
|
||||
## Features
|
||||
|
||||
- Connect your notes using [`[[wiki-links]]`](https://foambubble.github.io/foam/features/backlinking)
|
||||
- Create markdown [references](https://foambubble.github.io/foam/features/link-reference-definitions) for `[[wiki-links]]`, to use your notes in a non-foam workspace
|
||||
- See how your notes are connected via a [graph](https://foambubble.github.io/foam/features/graph-visualisation) with the `Foam: Show Graph` command
|
||||
- Tag your notes and navigate them with the [Tag Explorer](https://foambubble.github.io/foam/features/tags)
|
||||
- Make your notes navigable both in GitHub UI as well as GitHub Pages
|
||||
- Use [custom templates](https://foambubble.github.io/foam/features/note-templates) for your notes
|
||||
- Create a journal with [daily notes](https://foambubble.github.io/foam/features/daily-notes)
|
||||
- Explore your knowledge base with the `Foam: Open Random Note` command
|
||||
This will also install `Foam`, but if you already have it installed, that's ok, just make sure you're up to date on the latest version.
|
||||
|
||||
## Requirements
|
||||
|
||||
High tolerance for alpha-grade software.
|
||||
Foam is still a Work in Progress.
|
||||
Rest assured it will never lock you in, nor compromise your files, but sometimes some features might break ;)
|
||||
|
||||
## Known Issues
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "foam-vscode",
|
||||
"displayName": "Foam for VSCode (Wikilinks to Markdown)",
|
||||
"description": "Generate markdown reference lists from wikilinks in a workspace",
|
||||
"displayName": "Foam",
|
||||
"description": "VS Code + Markdown + Wikilinks for your note taking and knowledge base",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"url": "https://github.com/foambubble/foam",
|
||||
"type": "git"
|
||||
},
|
||||
"homepage": "https://github.com/foambubble/foam",
|
||||
"version": "0.13.6",
|
||||
"version": "0.15.6",
|
||||
"license": "MIT",
|
||||
"publisher": "foam",
|
||||
"engines": {
|
||||
@@ -223,7 +223,7 @@
|
||||
},
|
||||
"foam.edit.linkReferenceDefinitions": {
|
||||
"type": "string",
|
||||
"default": "withoutExtensions",
|
||||
"default": "off",
|
||||
"enum": [
|
||||
"withExtensions",
|
||||
"withoutExtensions",
|
||||
@@ -235,15 +235,15 @@
|
||||
"Disable wikilink definitions generation"
|
||||
]
|
||||
},
|
||||
"foam.links.navigation.enable": {
|
||||
"description": "Enable navigation through links",
|
||||
"foam.links.hover.enable": {
|
||||
"description": "Enable displaying note content on hover links",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"foam.decorations.links.enable": {
|
||||
"description": "Enable decorations for links",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
"default": true
|
||||
},
|
||||
"foam.openDailyNote.onStartup": {
|
||||
"type": "boolean",
|
||||
@@ -356,9 +356,11 @@
|
||||
"build": "tsc -p ./",
|
||||
"pretest": "yarn build",
|
||||
"test": "node ./out/test/run-tests.js",
|
||||
"lint": "tsdx lint",
|
||||
"test:unit": "node ./out/test/run-tests.js --unit",
|
||||
"test:e2e": "node ./out/test/run-tests.js --e2e",
|
||||
"lint": "tsdx lint src",
|
||||
"clean": "rimraf out",
|
||||
"watch": "tsc --build ./tsconfig.json ../foam-core/tsconfig.json --watch",
|
||||
"watch": "tsc --build ./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",
|
||||
@@ -371,34 +373,54 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.11.0",
|
||||
"@babel/plugin-transform-runtime": "^7.10.4",
|
||||
"@babel/preset-env": "^7.11.0",
|
||||
"@babel/preset-typescript": "^7.10.4",
|
||||
"@types/dateformat": "^3.0.1",
|
||||
"@types/github-slugger": "^1.3.0",
|
||||
"@types/glob": "^7.1.1",
|
||||
"@types/lodash": "^4.14.157",
|
||||
"@types/markdown-it": "^12.0.1",
|
||||
"@types/micromatch": "^4.0.1",
|
||||
"@types/node": "^13.11.0",
|
||||
"@types/picomatch": "^2.2.1",
|
||||
"@types/remove-markdown": "^0.1.1",
|
||||
"@types/vscode": "^1.47.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",
|
||||
"eslint-plugin-import": "^2.24.2",
|
||||
"husky": "^4.2.5",
|
||||
"jest": "^26.2.2",
|
||||
"jest-environment-vscode": "^1.0.0",
|
||||
"jest-extended": "^0.11.5",
|
||||
"markdown-it": "^12.0.4",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-jest": "^26.4.4",
|
||||
"typescript": "^3.8.3",
|
||||
"tsdx": "^0.13.2",
|
||||
"tslib": "^2.0.0",
|
||||
"typescript": "^3.9.5",
|
||||
"vscode-test": "^1.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"dateformat": "^3.0.3",
|
||||
"foam-core": "^0.13.6",
|
||||
"detect-newline": "^3.1.0",
|
||||
"fast-array-diff": "^1.0.1",
|
||||
"github-slugger": "^1.3.0",
|
||||
"glob": "^7.1.6",
|
||||
"gray-matter": "^4.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"markdown-it-regex": "^0.2.0",
|
||||
"micromatch": "^4.0.2",
|
||||
"remove-markdown": "^0.3.0"
|
||||
"remark-frontmatter": "^2.0.0",
|
||||
"remark-parse": "^8.0.2",
|
||||
"remark-wiki-link": "^0.0.4",
|
||||
"remove-markdown": "^0.3.0",
|
||||
"replace-ext": "^2.0.0",
|
||||
"title-case": "^3.0.2",
|
||||
"unified": "^9.0.0",
|
||||
"unist-util-visit": "^2.0.2",
|
||||
"yaml": "^1.10.0"
|
||||
}
|
||||
}
|
||||
|
||||
36
packages/foam-vscode/src/core/.eslintrc.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"rules": {
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"name": "vscode",
|
||||
"message": "Core submodule must not depend on VS Code."
|
||||
}
|
||||
]
|
||||
// Ideally we would also prevent the core module from depending on other modules
|
||||
// but I have been struggling to get it to work.
|
||||
// For future reference, below are some configurations I think should achieve this
|
||||
// (but I couldn't manage to get working).
|
||||
//
|
||||
// "import/no-internal-modules": [
|
||||
// "error",
|
||||
// {
|
||||
// "allow": ["./src/core"]
|
||||
// }
|
||||
// ]
|
||||
// "import/no-restricted-paths": [
|
||||
// "error",
|
||||
// {
|
||||
// "zones": [
|
||||
// {
|
||||
// "target": "./src/core",
|
||||
// "from": "./src/(!core)",
|
||||
// "message": "Core module can't have outside dependencies."
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// ]
|
||||
// "import/no-relative-parent-imports": "error"
|
||||
// note: https://github.com/import-js/eslint-plugin-import/issues/1610
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { applyTextEdit } from '../../src/janitor/apply-text-edit';
|
||||
import { Range } from '../../src/model/range';
|
||||
import { Logger } from '../../src/utils/log';
|
||||
import { Range } from '../model/range';
|
||||
import { Logger } from '../utils/log';
|
||||
import { applyTextEdit } from './apply-text-edit';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import os from 'os';
|
||||
import detectNewline from 'detect-newline';
|
||||
import { Position } from '../model/position';
|
||||
import { TextEdit } from '../index';
|
||||
import { TextEdit } from '.';
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -1,14 +1,13 @@
|
||||
import * as path from 'path';
|
||||
import { generateHeading } from '../../src/janitor';
|
||||
import { bootstrap } from '../../src/bootstrap';
|
||||
import { createConfigFromFolders } from '../../src/config';
|
||||
import { Resource } from '../../src/model/note';
|
||||
import { FileDataStore, Matcher } from '../../src/services/datastore';
|
||||
import { Logger } from '../../src/utils/log';
|
||||
import { FoamWorkspace } from '../../src/model/workspace';
|
||||
import { URI } from '../../src/model/uri';
|
||||
import { Range } from '../../src/model/range';
|
||||
import { MarkdownResourceProvider } from '../../src';
|
||||
import { generateHeading } from '.';
|
||||
import { TEST_DATA_DIR } from '../../test/test-utils';
|
||||
import { MarkdownResourceProvider } from '../markdown-provider';
|
||||
import { bootstrap } from '../model/foam';
|
||||
import { Resource } from '../model/note';
|
||||
import { Range } from '../model/range';
|
||||
import { URI } from '../model/uri';
|
||||
import { FoamWorkspace } from '../model/workspace';
|
||||
import { FileDataStore, Matcher } from '../services/datastore';
|
||||
import { Logger } from '../utils/log';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
@@ -21,17 +20,9 @@ describe('generateHeadings', () => {
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
const config = createConfigFromFolders([
|
||||
URI.file(path.join(__dirname, '..', '__scaffold__')),
|
||||
]);
|
||||
const mdProvider = new MarkdownResourceProvider(
|
||||
new Matcher(
|
||||
config.workspaceFolders,
|
||||
config.includeGlobs,
|
||||
config.ignoreGlobs
|
||||
)
|
||||
);
|
||||
const foam = await bootstrap(config, new FileDataStore(), [mdProvider]);
|
||||
const matcher = new Matcher([URI.joinPath(TEST_DATA_DIR, '__scaffold__')]);
|
||||
const mdProvider = new MarkdownResourceProvider(matcher);
|
||||
const foam = await bootstrap(matcher, new FileDataStore(), [mdProvider]);
|
||||
_workspace = foam.workspace;
|
||||
});
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import * as path from 'path';
|
||||
import { generateLinkReferences } from '../../src/janitor';
|
||||
import { bootstrap } from '../../src/bootstrap';
|
||||
import { createConfigFromFolders } from '../../src/config';
|
||||
import { FileDataStore, Matcher } from '../../src/services/datastore';
|
||||
import { Logger } from '../../src/utils/log';
|
||||
import { FoamWorkspace } from '../../src/model/workspace';
|
||||
import { URI } from '../../src/model/uri';
|
||||
import { Resource } from '../../src/model/note';
|
||||
import { Range } from '../../src/model/range';
|
||||
import { MarkdownResourceProvider } from '../../src';
|
||||
import { generateLinkReferences } from '.';
|
||||
import { TEST_DATA_DIR } from '../../test/test-utils';
|
||||
import { MarkdownResourceProvider } from '../markdown-provider';
|
||||
import { bootstrap } from '../model/foam';
|
||||
import { Resource } from '../model/note';
|
||||
import { Range } from '../model/range';
|
||||
import { URI } from '../model/uri';
|
||||
import { FoamWorkspace } from '../model/workspace';
|
||||
import { FileDataStore, Matcher } from '../services/datastore';
|
||||
import { Logger } from '../utils/log';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
@@ -21,22 +20,14 @@ describe('generateLinkReferences', () => {
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
const config = createConfigFromFolders([
|
||||
URI.file(path.join(__dirname, '..', '__scaffold__')),
|
||||
]);
|
||||
const mdProvider = new MarkdownResourceProvider(
|
||||
new Matcher(
|
||||
config.workspaceFolders,
|
||||
config.includeGlobs,
|
||||
config.ignoreGlobs
|
||||
)
|
||||
);
|
||||
const foam = await bootstrap(config, new FileDataStore(), [mdProvider]);
|
||||
const matcher = new Matcher([URI.joinPath(TEST_DATA_DIR, '__scaffold__')]);
|
||||
const mdProvider = new MarkdownResourceProvider(matcher);
|
||||
const foam = await bootstrap(matcher, new FileDataStore(), [mdProvider]);
|
||||
_workspace = foam.workspace;
|
||||
});
|
||||
|
||||
it('initialised test graph correctly', () => {
|
||||
expect(_workspace.list().length).toEqual(6);
|
||||
expect(_workspace.list().length).toEqual(10);
|
||||
});
|
||||
|
||||
it('should add link references to a file that does not have them', () => {
|
||||
@@ -105,6 +96,54 @@ describe('generateLinkReferences', () => {
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should put links with spaces in angel brackets', () => {
|
||||
const note = findBySlug('angel-reference');
|
||||
const expected = {
|
||||
newText: textForNote(
|
||||
note,
|
||||
`
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[Note being refered as angel]: <Note being refered as angel> "Note being refered as angel"
|
||||
[//end]: # "Autogenerated link references"`
|
||||
),
|
||||
range: Range.create(3, 0, 3, 0),
|
||||
};
|
||||
|
||||
const actual = generateLinkReferences(note, _workspace, false);
|
||||
|
||||
expect(actual!.range.start).toEqual(expected.range.start);
|
||||
expect(actual!.range.end).toEqual(expected.range.end);
|
||||
expect(actual!.newText).toEqual(expected.newText);
|
||||
});
|
||||
|
||||
it('should not remove explicitly entered link references', () => {
|
||||
const note = findBySlug('file-with-explicit-link-references');
|
||||
const expected = null;
|
||||
|
||||
const actual = generateLinkReferences(note, _workspace, false);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should not remove explicitly entered link references and have an implicit link', () => {
|
||||
const note = findBySlug('file-with-explicit-and-implicit-link-references');
|
||||
const expected = {
|
||||
newText: textForNote(
|
||||
note,
|
||||
`[^footerlink]: https://foambubble.github.io/
|
||||
[linkrefenrece]: https://foambubble.github.io/
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[first-document]: first-document "First Document"
|
||||
[//end]: # "Autogenerated link references"`
|
||||
),
|
||||
range: Range.create(5, 0, 10, 42),
|
||||
};
|
||||
|
||||
const actual = generateLinkReferences(note, _workspace, false);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -59,17 +59,78 @@ export const generateLinkReferences = (
|
||||
} else {
|
||||
const first = note.definitions[0];
|
||||
const last = note.definitions[note.definitions.length - 1];
|
||||
|
||||
var nonGeneratedReferenceDefinitions = note.definitions;
|
||||
|
||||
// if we have more definitions then referenced pages AND the page refers to a page
|
||||
// we expect non-generated link definitions to be present
|
||||
// Collect all non-generated definitions, by removing the generated ones
|
||||
if (
|
||||
note.definitions.length > markdownReferences.length &&
|
||||
markdownReferences.length > 0
|
||||
) {
|
||||
// remove all autogenerated definitions
|
||||
const beginIndex = note.definitions.findIndex(
|
||||
({ label }) => label === '//begin'
|
||||
);
|
||||
const endIndex = note.definitions.findIndex(
|
||||
({ label }) => label === '//end'
|
||||
);
|
||||
|
||||
const generatedDefinitions = [...note.definitions].splice(
|
||||
beginIndex,
|
||||
endIndex - beginIndex + 1
|
||||
);
|
||||
|
||||
nonGeneratedReferenceDefinitions = note.definitions.filter(
|
||||
x => !generatedDefinitions.includes(x)
|
||||
);
|
||||
}
|
||||
|
||||
// When we only have explicitly defined link definitions &&
|
||||
// no indication of previously defined generated links &&
|
||||
// there is no reference to another page, return null
|
||||
if (
|
||||
nonGeneratedReferenceDefinitions.length > 0 &&
|
||||
note.definitions.findIndex(({ label }) => label === '//begin') < 0 &&
|
||||
markdownReferences.length === 0
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Format link definitions for non-generated links
|
||||
const nonGeneratedReferences = nonGeneratedReferenceDefinitions
|
||||
.map(stringifyMarkdownLinkReferenceDefinition)
|
||||
.join(note.source.eol);
|
||||
|
||||
const oldReferences = note.definitions
|
||||
.map(stringifyMarkdownLinkReferenceDefinition)
|
||||
.join(note.source.eol);
|
||||
|
||||
if (oldReferences === newReferences) {
|
||||
// When the newly formatted references match the old ones, OR
|
||||
// when non-generated references are present, but no new ones are generated
|
||||
// return null
|
||||
if (
|
||||
oldReferences === newReferences ||
|
||||
(nonGeneratedReferenceDefinitions.length > 0 &&
|
||||
newReferences === '' &&
|
||||
markdownReferences.length > 0)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var fullReferences = `${newReferences}`;
|
||||
// If there are any non-generated definitions, add those to the output as well
|
||||
if (
|
||||
nonGeneratedReferenceDefinitions.length > 0 &&
|
||||
markdownReferences.length > 0
|
||||
) {
|
||||
fullReferences = `${nonGeneratedReferences}${note.source.eol}${newReferences}`;
|
||||
}
|
||||
|
||||
return {
|
||||
// @todo: do we need to ensure new lines?
|
||||
newText: `${newReferences}`,
|
||||
newText: `${fullReferences}`,
|
||||
range: Range.createFromPosition(first.range!.start, last.range!.end),
|
||||
};
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
import {
|
||||
createMarkdownParser,
|
||||
createMarkdownReferences,
|
||||
} from '../src/markdown-provider';
|
||||
import { DirectLink } from '../src/model/note';
|
||||
import { ParserPlugin } from '../src/plugins';
|
||||
import { Logger } from '../src/utils/log';
|
||||
import { uriToSlug } from '../src/utils/slug';
|
||||
import { URI } from '../src/model/uri';
|
||||
import { FoamGraph } from '../src/model/graph';
|
||||
import { createTestWorkspace } from './core.test';
|
||||
ParserPlugin,
|
||||
} from './markdown-provider';
|
||||
import { DirectLink, WikiLink } from './model/note';
|
||||
import { Logger } from './utils/log';
|
||||
import { uriToSlug } from './utils/slug';
|
||||
import { URI } from './model/uri';
|
||||
import { FoamGraph } from './model/graph';
|
||||
import { Range } from './model/range';
|
||||
import { createTestWorkspace } from '../test/test-utils';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
@@ -130,6 +131,60 @@ this is a [link to intro](#introduction)
|
||||
noteE.uri,
|
||||
]);
|
||||
});
|
||||
|
||||
it('Parses backlinks with an alias', () => {
|
||||
const note = createNoteFromMarkdown(
|
||||
'/path/to/page-a.md',
|
||||
'this is [[link|link alias]]. A link with spaces [[other link | spaced]]'
|
||||
);
|
||||
expect(note.links.length).toEqual(2);
|
||||
let link = note.links[0] as WikiLink;
|
||||
expect(link.type).toEqual('wikilink');
|
||||
expect(link.rawText).toEqual('[[link|link alias]]');
|
||||
expect(link.label).toEqual('link alias');
|
||||
expect(link.target).toEqual('link');
|
||||
link = note.links[1] as WikiLink;
|
||||
expect(link.type).toEqual('wikilink');
|
||||
expect(link.rawText).toEqual('[[other link | spaced]]');
|
||||
expect(link.label).toEqual('spaced');
|
||||
expect(link.target).toEqual('other link');
|
||||
});
|
||||
|
||||
it('Skips wikilinks in codeblocks', () => {
|
||||
const noteA = createNoteFromMarkdown(
|
||||
'/dir1/page-a.md',
|
||||
`
|
||||
this is some text with our [[first-wikilink]].
|
||||
|
||||
\`\`\`
|
||||
this is inside a [[codeblock]]
|
||||
\`\`\`
|
||||
|
||||
this is some text with our [[second-wikilink]].
|
||||
`
|
||||
);
|
||||
expect(noteA.links.map(l => l.label)).toEqual([
|
||||
'first-wikilink',
|
||||
'second-wikilink',
|
||||
]);
|
||||
});
|
||||
|
||||
it('Skips wikilinks in inlined codeblocks', () => {
|
||||
const noteA = createNoteFromMarkdown(
|
||||
'/dir1/page-a.md',
|
||||
`
|
||||
this is some text with our [[first-wikilink]].
|
||||
|
||||
this is \`inside a [[codeblock]]\`
|
||||
|
||||
this is some text with our [[second-wikilink]].
|
||||
`
|
||||
);
|
||||
expect(noteA.links.map(l => l.label)).toEqual([
|
||||
'first-wikilink',
|
||||
'second-wikilink',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Note Title', () => {
|
||||
@@ -298,13 +353,51 @@ describe('tags plugin', () => {
|
||||
const noteA = createNoteFromMarkdown(
|
||||
'/dir1/page-a.md',
|
||||
`
|
||||
# this is a heading
|
||||
this is some #text that includes #tags we #care-about.
|
||||
# this is a #heading
|
||||
#this is some #text that includes #tags we #care-about.
|
||||
`
|
||||
);
|
||||
expect(noteA.tags).toEqual(new Set(['text', 'tags', 'care-about']));
|
||||
expect(noteA.tags).toEqual([
|
||||
{ label: 'heading', range: Range.create(1, 12, 1, 20) },
|
||||
{ label: 'this', range: Range.create(2, 0, 2, 5) },
|
||||
{ label: 'text', range: Range.create(2, 14, 2, 19) },
|
||||
{ label: 'tags', range: Range.create(2, 34, 2, 39) },
|
||||
{ label: 'care-about', range: Range.create(2, 43, 2, 54) },
|
||||
]);
|
||||
});
|
||||
|
||||
it('will skip tags in codeblocks', () => {
|
||||
const noteA = createNoteFromMarkdown(
|
||||
'/dir1/page-a.md',
|
||||
`
|
||||
this is some #text that includes #tags we #care-about.
|
||||
|
||||
\`\`\`
|
||||
this is a #codeblock
|
||||
\`\`\`
|
||||
`
|
||||
);
|
||||
expect(noteA.tags.map(t => t.label)).toEqual([
|
||||
'text',
|
||||
'tags',
|
||||
'care-about',
|
||||
]);
|
||||
});
|
||||
|
||||
it('will skip tags in inlined codeblocks', () => {
|
||||
const noteA = createNoteFromMarkdown(
|
||||
'/dir1/page-a.md',
|
||||
`
|
||||
this is some #text that includes #tags we #care-about.
|
||||
this is a \`inlined #codeblock\`
|
||||
`
|
||||
);
|
||||
expect(noteA.tags.map(t => t.label)).toEqual([
|
||||
'text',
|
||||
'tags',
|
||||
'care-about',
|
||||
]);
|
||||
});
|
||||
it('can find tags as text in yaml', () => {
|
||||
const noteA = createNoteFromMarkdown(
|
||||
'/dir1/page-a.md',
|
||||
@@ -316,9 +409,14 @@ tags: hello, world this_is_good
|
||||
this is some #text that includes #tags we #care-about.
|
||||
`
|
||||
);
|
||||
expect(noteA.tags).toEqual(
|
||||
new Set(['text', 'tags', 'care-about', 'hello', 'world', 'this_is_good'])
|
||||
);
|
||||
expect(noteA.tags.map(t => t.label)).toEqual([
|
||||
'hello',
|
||||
'world',
|
||||
'this_is_good',
|
||||
'text',
|
||||
'tags',
|
||||
'care-about',
|
||||
]);
|
||||
});
|
||||
|
||||
it('can find tags as array in yaml', () => {
|
||||
@@ -332,22 +430,34 @@ tags: [hello, world, this_is_good]
|
||||
this is some #text that includes #tags we #care-about.
|
||||
`
|
||||
);
|
||||
expect(noteA.tags).toEqual(
|
||||
new Set(['text', 'tags', 'care-about', 'hello', 'world', 'this_is_good'])
|
||||
);
|
||||
expect(noteA.tags.map(t => t.label)).toEqual([
|
||||
'hello',
|
||||
'world',
|
||||
'this_is_good',
|
||||
'text',
|
||||
'tags',
|
||||
'care-about',
|
||||
]);
|
||||
});
|
||||
|
||||
it('can find nested tags as array in yaml', () => {
|
||||
it('provides rough range for tags in yaml', () => {
|
||||
// For now it's enough to just get the YAML block range
|
||||
// in the future we might want to be more specific
|
||||
|
||||
const noteA = createNoteFromMarkdown(
|
||||
'/dir1/page-a.md',
|
||||
`
|
||||
---
|
||||
tags: [hello, world, parent/child]
|
||||
tags: [hello, world, this_is_good]
|
||||
---
|
||||
# this is a heading
|
||||
this is some text
|
||||
`
|
||||
);
|
||||
expect(noteA.tags).toEqual(new Set(['hello', 'world', 'parent/child']));
|
||||
expect(noteA.tags[0]).toEqual({
|
||||
label: 'hello',
|
||||
range: Range.create(1, 0, 3, 3),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,13 +24,24 @@ import {
|
||||
isNone,
|
||||
isSome,
|
||||
} from './utils';
|
||||
import { ParserPlugin } from './plugins';
|
||||
import { Logger } from './utils/log';
|
||||
import { URI } from './model/uri';
|
||||
import { FoamWorkspace } from './model/workspace';
|
||||
import { ResourceProvider } from 'model/provider';
|
||||
import { IDataStore, FileDataStore, IMatcher } from './services/datastore';
|
||||
import { IDisposable } from 'common/lifecycle';
|
||||
import { IDisposable } from './common/lifecycle';
|
||||
import { ResourceProvider } from './model/provider';
|
||||
|
||||
const ALIAS_DIVIDER_CHAR = '|';
|
||||
|
||||
export interface ParserPlugin {
|
||||
name?: string;
|
||||
visit?: (node: Node, note: Resource, noteSource: string) => void;
|
||||
onDidInitializeParser?: (parser: unified.Processor) => void;
|
||||
onWillParseMarkdown?: (markdown: string) => string;
|
||||
onWillVisitTree?: (tree: Node, note: Resource) => void;
|
||||
onDidVisitTree?: (tree: Node, note: Resource) => void;
|
||||
onDidFindProperties?: (properties: any, note: Resource, node: Node) => void;
|
||||
}
|
||||
|
||||
export class MarkdownResourceProvider implements ResourceProvider {
|
||||
private disposables: IDisposable[] = [];
|
||||
@@ -48,7 +59,9 @@ export class MarkdownResourceProvider implements ResourceProvider {
|
||||
|
||||
async init(workspace: FoamWorkspace) {
|
||||
const filesByFolder = await Promise.all(
|
||||
this.matcher.include.map(glob => this.dataStore.list(glob))
|
||||
this.matcher.include.map(glob =>
|
||||
this.dataStore.list(glob, this.matcher.exclude)
|
||||
)
|
||||
);
|
||||
const files = this.matcher
|
||||
.match(filesByFolder.flat())
|
||||
@@ -112,7 +125,7 @@ export class MarkdownResourceProvider implements ResourceProvider {
|
||||
switch (link.type) {
|
||||
case 'wikilink':
|
||||
const definitionUri = resource.definitions.find(
|
||||
def => def.label === link.slug
|
||||
def => def.label === link.target
|
||||
)?.url;
|
||||
if (isSome(definitionUri)) {
|
||||
const definedUri = URI.resolve(definitionUri, resource.uri);
|
||||
@@ -121,8 +134,8 @@ export class MarkdownResourceProvider implements ResourceProvider {
|
||||
URI.placeholder(definedUri.path);
|
||||
} else {
|
||||
targetUri =
|
||||
workspace.find(link.slug, resource.uri)?.uri ??
|
||||
URI.placeholder(link.slug);
|
||||
workspace.find(link.target, resource.uri)?.uri ??
|
||||
URI.placeholder(link.target);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -150,7 +163,7 @@ const getTextFromChildren = (root: Node): string => {
|
||||
let text = '';
|
||||
visit(root, 'text', node => {
|
||||
if (node.type === 'text') {
|
||||
text = text + node.value;
|
||||
text = text + (node as any).value;
|
||||
}
|
||||
});
|
||||
return text;
|
||||
@@ -158,21 +171,46 @@ const getTextFromChildren = (root: Node): string => {
|
||||
|
||||
const tagsPlugin: ParserPlugin = {
|
||||
name: 'tags',
|
||||
onWillVisitTree: (tree, note) => {
|
||||
note.tags = extractHashtags(note.source.text);
|
||||
onDidFindProperties: (props, note, node) => {
|
||||
if (isSome(props.tags)) {
|
||||
const yamlTags = extractTagsFromProp(props.tags);
|
||||
yamlTags.forEach(t => {
|
||||
note.tags.push({
|
||||
label: t,
|
||||
range: astPositionToFoamRange(node.position!),
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
onDidFindProperties: (props, note) => {
|
||||
const yamlTags = extractTagsFromProp(props.tags);
|
||||
yamlTags.forEach(tag => note.tags.add(tag));
|
||||
visit: (node, note) => {
|
||||
if (node.type === 'text') {
|
||||
const tags = extractHashtags((node as any).value);
|
||||
tags.forEach(tag => {
|
||||
let start = astPointToFoamPosition(node.position!.start);
|
||||
start.character = start.character + tag.offset;
|
||||
const end: Position = {
|
||||
line: start.line,
|
||||
character: start.character + tag.label.length + 1,
|
||||
};
|
||||
note.tags.push({
|
||||
label: tag.label,
|
||||
range: Range.createFromPosition(start, end),
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const titlePlugin: ParserPlugin = {
|
||||
name: 'title',
|
||||
visit: (node, note) => {
|
||||
if (note.title === '' && node.type === 'heading' && node.depth === 1) {
|
||||
if (
|
||||
note.title === '' &&
|
||||
node.type === 'heading' &&
|
||||
(node as any).depth === 1
|
||||
) {
|
||||
note.title =
|
||||
((node as Parent)!.children?.[0]?.value as string) || note.title;
|
||||
((node as Parent)!.children?.[0] as any)?.value || note.title;
|
||||
}
|
||||
},
|
||||
onDidFindProperties: (props, note) => {
|
||||
@@ -188,12 +226,29 @@ const titlePlugin: ParserPlugin = {
|
||||
|
||||
const wikilinkPlugin: ParserPlugin = {
|
||||
name: 'wikilink',
|
||||
visit: (node, note) => {
|
||||
visit: (node, note, noteSource) => {
|
||||
if (node.type === 'wikiLink') {
|
||||
const text = (node as any).value;
|
||||
const alias = node.data?.alias as string;
|
||||
const literalContent = noteSource.substring(
|
||||
node.position!.start.offset!,
|
||||
node.position!.end.offset!
|
||||
);
|
||||
|
||||
const hasAlias =
|
||||
literalContent !== text && literalContent.includes(ALIAS_DIVIDER_CHAR);
|
||||
note.links.push({
|
||||
type: 'wikilink',
|
||||
slug: node.value as string,
|
||||
target: node.value as string,
|
||||
rawText: literalContent,
|
||||
label: hasAlias
|
||||
? alias.trim()
|
||||
: literalContent.substring(2, literalContent.length - 2),
|
||||
target: hasAlias
|
||||
? literalContent
|
||||
.substring(2, literalContent.indexOf(ALIAS_DIVIDER_CHAR))
|
||||
.replace(/\\/g, '')
|
||||
.trim()
|
||||
: text.trim(),
|
||||
range: astPositionToFoamRange(node.position!),
|
||||
});
|
||||
}
|
||||
@@ -219,9 +274,9 @@ const definitionsPlugin: ParserPlugin = {
|
||||
visit: (node, note) => {
|
||||
if (node.type === 'definition') {
|
||||
note.definitions.push({
|
||||
label: node.label as string,
|
||||
url: node.url as string,
|
||||
title: node.title as string,
|
||||
label: (node as any).label,
|
||||
url: (node as any).url,
|
||||
title: (node as any).title,
|
||||
range: astPositionToFoamRange(node.position!),
|
||||
});
|
||||
}
|
||||
@@ -239,7 +294,9 @@ const handleError = (
|
||||
): void => {
|
||||
const name = plugin.name || '';
|
||||
Logger.warn(
|
||||
`Error while executing [${fnName}] in plugin [${name}] for file [${uri?.path}]`,
|
||||
`Error while executing [${fnName}] in plugin [${name}]. ${
|
||||
uri ? 'for file [' + URI.toString(uri) : ']'
|
||||
}.`,
|
||||
e
|
||||
);
|
||||
};
|
||||
@@ -250,7 +307,7 @@ export function createMarkdownParser(
|
||||
const parser = unified()
|
||||
.use(markdownParse, { gfm: true })
|
||||
.use(frontmatterPlugin, ['yaml'])
|
||||
.use(wikiLinkPlugin);
|
||||
.use(wikiLinkPlugin, { aliasDivider: ALIAS_DIVIDER_CHAR });
|
||||
|
||||
const plugins = [
|
||||
titlePlugin,
|
||||
@@ -270,7 +327,7 @@ export function createMarkdownParser(
|
||||
|
||||
const foamParser: ResourceParser = {
|
||||
parse: (uri: URI, markdown: string): Resource => {
|
||||
Logger.debug('Parsing:', uri);
|
||||
Logger.debug('Parsing:', URI.toString(uri));
|
||||
markdown = plugins.reduce((acc, plugin) => {
|
||||
try {
|
||||
return plugin.onWillParseMarkdown?.(acc) || acc;
|
||||
@@ -287,7 +344,7 @@ export function createMarkdownParser(
|
||||
type: 'note',
|
||||
properties: {},
|
||||
title: '',
|
||||
tags: new Set(),
|
||||
tags: [],
|
||||
links: [],
|
||||
definitions: [],
|
||||
source: {
|
||||
@@ -308,7 +365,7 @@ export function createMarkdownParser(
|
||||
visit(tree, node => {
|
||||
if (node.type === 'yaml') {
|
||||
try {
|
||||
const yamlProperties = parseYAML(node.value as string) ?? {};
|
||||
const yamlProperties = parseYAML((node as any).value) ?? {};
|
||||
note.properties = {
|
||||
...note.properties,
|
||||
...yamlProperties,
|
||||
@@ -321,19 +378,22 @@ export function createMarkdownParser(
|
||||
|
||||
for (let i = 0, len = plugins.length; i < len; i++) {
|
||||
try {
|
||||
plugins[i].onDidFindProperties?.(yamlProperties, note);
|
||||
plugins[i].onDidFindProperties?.(yamlProperties, note, node);
|
||||
} catch (e) {
|
||||
handleError(plugins[i], 'onDidFindProperties', uri, e);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.warn(`Error while parsing YAML for [${uri}]`, e);
|
||||
Logger.warn(
|
||||
`Error while parsing YAML for [${URI.toString(uri)}]`,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0, len = plugins.length; i < len; i++) {
|
||||
try {
|
||||
plugins[i].visit?.(node, note);
|
||||
plugins[i].visit?.(node, note, markdown);
|
||||
} catch (e) {
|
||||
handleError(plugins[i], 'visit', uri, e);
|
||||
}
|
||||
@@ -381,7 +441,9 @@ function getFoamDefinitions(
|
||||
export function stringifyMarkdownLinkReferenceDefinition(
|
||||
definition: NoteLinkDefinition
|
||||
) {
|
||||
let text = `[${definition.label}]: ${definition.url}`;
|
||||
let url =
|
||||
definition.url.indexOf(' ') > 0 ? `<${definition.url}>` : definition.url;
|
||||
let text = `[${definition.label}]: ${url}`;
|
||||
if (definition.title) {
|
||||
text = `${text} "${definition.title}"`;
|
||||
}
|
||||
@@ -397,7 +459,9 @@ export function createMarkdownReferences(
|
||||
// Should never occur since we're already in a file,
|
||||
if (source?.type !== 'note') {
|
||||
console.warn(
|
||||
`Note ${noteUri} note found in workspace when attempting to generate markdown reference list`
|
||||
`Note ${URI.toString(
|
||||
noteUri
|
||||
)} note found in workspace when attempting to generate markdown reference list`
|
||||
);
|
||||
return [];
|
||||
}
|
||||
@@ -408,7 +472,11 @@ export function createMarkdownReferences(
|
||||
const targetUri = workspace.resolveLink(source, link);
|
||||
const target = workspace.find(targetUri);
|
||||
if (isNone(target)) {
|
||||
Logger.warn(`Link ${targetUri} in ${noteUri} is not valid.`);
|
||||
Logger.warn(
|
||||
`Link ${URI.toString(targetUri)} in ${URI.toString(
|
||||
noteUri
|
||||
)} is not valid.`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
if (target.type === 'placeholder') {
|
||||
@@ -421,8 +489,15 @@ export function createMarkdownReferences(
|
||||
? relativePath
|
||||
: dropExtension(relativePath);
|
||||
|
||||
// [wiki-link-text]: path/to/file.md "Page title"
|
||||
return { label: link.slug, url: pathToNote, title: target.title };
|
||||
// [wikilink-text]: path/to/file.md "Page title"
|
||||
return {
|
||||
label:
|
||||
link.rawText.indexOf('[[') > -1
|
||||
? link.rawText.substring(2, link.rawText.length - 2)
|
||||
: link.rawText || link.label,
|
||||
url: pathToNote,
|
||||
title: target.title,
|
||||
};
|
||||
})
|
||||
.filter(isSome)
|
||||
.sort();
|
||||
51
packages/foam-vscode/src/core/model/foam.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { IDisposable } from '../common/lifecycle';
|
||||
import { IDataStore, IMatcher } from '../services/datastore';
|
||||
import { FoamWorkspace } from './workspace';
|
||||
import { FoamGraph } from './graph';
|
||||
import { ResourceParser } from './note';
|
||||
import { ResourceProvider } from './provider';
|
||||
import { createMarkdownParser } from '../markdown-provider';
|
||||
import { FoamTags } from './tags';
|
||||
|
||||
export interface Services {
|
||||
dataStore: IDataStore;
|
||||
parser: ResourceParser;
|
||||
matcher: IMatcher;
|
||||
}
|
||||
|
||||
export interface Foam extends IDisposable {
|
||||
services: Services;
|
||||
workspace: FoamWorkspace;
|
||||
graph: FoamGraph;
|
||||
tags: FoamTags;
|
||||
}
|
||||
|
||||
export const bootstrap = async (
|
||||
matcher: IMatcher,
|
||||
dataStore: IDataStore,
|
||||
initialProviders: ResourceProvider[]
|
||||
) => {
|
||||
const parser = createMarkdownParser([]);
|
||||
const workspace = new FoamWorkspace();
|
||||
await Promise.all(initialProviders.map(p => workspace.registerProvider(p)));
|
||||
|
||||
const graph = FoamGraph.fromWorkspace(workspace, true);
|
||||
const tags = FoamTags.fromWorkspace(workspace, true);
|
||||
|
||||
const foam: Foam = {
|
||||
workspace,
|
||||
graph,
|
||||
tags,
|
||||
services: {
|
||||
dataStore,
|
||||
parser,
|
||||
matcher,
|
||||
},
|
||||
dispose: () => {
|
||||
workspace.dispose();
|
||||
graph.dispose();
|
||||
},
|
||||
};
|
||||
|
||||
return foam;
|
||||
};
|
||||
@@ -2,9 +2,9 @@ import { diff } from 'fast-array-diff';
|
||||
import { isEqual } from 'lodash';
|
||||
import { Resource, ResourceLink } from './note';
|
||||
import { URI } from './uri';
|
||||
import { IDisposable } from '../index';
|
||||
import { FoamWorkspace, uriToResourceName } from './workspace';
|
||||
import { Range } from './range';
|
||||
import { IDisposable } from '../common/lifecycle';
|
||||
|
||||
export type Connection = {
|
||||
source: URI;
|
||||
@@ -19,15 +19,15 @@ export class FoamGraph implements IDisposable {
|
||||
/**
|
||||
* Placehoders by key / slug / value
|
||||
*/
|
||||
public readonly placeholders: { [key: string]: URI } = {};
|
||||
public readonly placeholders: Map<string, URI> = new Map();
|
||||
/**
|
||||
* Maps the connections starting from a URI
|
||||
*/
|
||||
public readonly links: { [key: string]: Connection[] } = {};
|
||||
public readonly links: Map<string, Connection[]> = new Map();
|
||||
/**
|
||||
* Maps the connections arriving to a URI
|
||||
*/
|
||||
public readonly backlinks: { [key: string]: Connection[] } = {};
|
||||
public readonly backlinks: Map<string, Connection[]> = new Map();
|
||||
|
||||
/**
|
||||
* List of disposables to destroy with the workspace
|
||||
@@ -42,28 +42,28 @@ export class FoamGraph implements IDisposable {
|
||||
|
||||
public getAllNodes(): URI[] {
|
||||
return [
|
||||
...Object.values(this.placeholders),
|
||||
...Array.from(this.placeholders.values()),
|
||||
...this.workspace.list().map(r => r.uri),
|
||||
];
|
||||
}
|
||||
|
||||
public getAllConnections(): Connection[] {
|
||||
return Object.values(this.links).flat();
|
||||
return Array.from(this.links.values()).flat();
|
||||
}
|
||||
|
||||
public getConnections(uri: URI): Connection[] {
|
||||
return [
|
||||
...(this.links[uri.path] || []),
|
||||
...(this.backlinks[uri.path] || []),
|
||||
...(this.links.get(uri.path) || []),
|
||||
...(this.backlinks.get(uri.path) || []),
|
||||
];
|
||||
}
|
||||
|
||||
public getLinks(uri: URI): Connection[] {
|
||||
return this.links[uri.path] ?? [];
|
||||
return this.links.get(uri.path) ?? [];
|
||||
}
|
||||
|
||||
public getBacklinks(uri: URI): Connection[] {
|
||||
return this.backlinks[uri.path] ?? [];
|
||||
return this.backlinks.get(uri.path) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,9 +80,7 @@ export class FoamGraph implements IDisposable {
|
||||
): FoamGraph {
|
||||
let graph = new FoamGraph(workspace);
|
||||
|
||||
Object.values(workspace.list()).forEach(resource =>
|
||||
graph.resolveResource(resource)
|
||||
);
|
||||
workspace.list().forEach(resource => graph.resolveResource(resource));
|
||||
if (keepMonitoring) {
|
||||
graph.disposables.push(
|
||||
workspace.onDidAdd(resource => {
|
||||
@@ -102,10 +100,10 @@ export class FoamGraph implements IDisposable {
|
||||
private updateLinksRelatedToAddedResource(resource: Resource) {
|
||||
// check if any existing connection can be filled by new resource
|
||||
const name = uriToResourceName(resource.uri);
|
||||
if (name in this.placeholders) {
|
||||
const placeholder = this.placeholders[name];
|
||||
delete this.placeholders[name];
|
||||
const resourcesToUpdate = this.backlinks[placeholder.path] ?? [];
|
||||
const placeholder = this.placeholders.get(name);
|
||||
if (placeholder) {
|
||||
this.placeholders.delete(name);
|
||||
const resourcesToUpdate = this.backlinks.get(placeholder.path) ?? [];
|
||||
resourcesToUpdate.forEach(res =>
|
||||
this.resolveResource(this.workspace.get(res.source))
|
||||
);
|
||||
@@ -143,15 +141,15 @@ export class FoamGraph implements IDisposable {
|
||||
const uri = resource.uri;
|
||||
|
||||
// remove forward links from old resource
|
||||
const resourcesPointedByDeletedNote = this.links[uri.path] ?? [];
|
||||
delete this.links[uri.path];
|
||||
const resourcesPointedByDeletedNote = this.links.get(uri.path) ?? [];
|
||||
this.links.delete(uri.path);
|
||||
resourcesPointedByDeletedNote.forEach(connection =>
|
||||
this.disconnect(uri, connection.target, connection.link)
|
||||
);
|
||||
|
||||
// recompute previous links to old resource
|
||||
const notesPointingToDeletedResource = this.backlinks[uri.path] ?? [];
|
||||
delete this.backlinks[uri.path];
|
||||
const notesPointingToDeletedResource = this.backlinks.get(uri.path) ?? [];
|
||||
this.backlinks.delete(uri.path);
|
||||
notesPointingToDeletedResource.forEach(link =>
|
||||
this.resolveResource(this.workspace.get(link.source))
|
||||
);
|
||||
@@ -161,13 +159,19 @@ export class FoamGraph implements IDisposable {
|
||||
private connect(source: URI, target: URI, link: ResourceLink) {
|
||||
const connection = { source, target, link };
|
||||
|
||||
this.links[source.path] = this.links[source.path] ?? [];
|
||||
this.links[source.path].push(connection);
|
||||
this.backlinks[target.path] = this.backlinks[target.path] ?? [];
|
||||
this.backlinks[target.path].push(connection);
|
||||
if (!this.links.has(source.path)) {
|
||||
this.links.set(source.path, []);
|
||||
}
|
||||
this.links.get(source.path)?.push(connection);
|
||||
|
||||
if (!this.backlinks.get(target.path)) {
|
||||
this.backlinks.set(target.path, []);
|
||||
}
|
||||
|
||||
this.backlinks.get(target.path)?.push(connection);
|
||||
|
||||
if (URI.isPlaceholder(target)) {
|
||||
this.placeholders[uriToPlaceholderId(target)] = target;
|
||||
this.placeholders.set(uriToPlaceholderId(target), target);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -189,24 +193,28 @@ export class FoamGraph implements IDisposable {
|
||||
!URI.isEqual(source, c.source) || !URI.isEqual(target, c.target)
|
||||
: (c: Connection) => !isSameConnection({ source, target, link }, c);
|
||||
|
||||
this.links[source.path] =
|
||||
this.links[source.path]?.filter(connectionsToKeep) ?? [];
|
||||
if (this.links[source.path].length === 0) {
|
||||
delete this.links[source.path];
|
||||
this.links.set(
|
||||
source.path,
|
||||
this.links.get(source.path)?.filter(connectionsToKeep) ?? []
|
||||
);
|
||||
if (this.links.get(source.path)?.length === 0) {
|
||||
this.links.delete(source.path);
|
||||
}
|
||||
this.backlinks[target.path] =
|
||||
this.backlinks[target.path]?.filter(connectionsToKeep) ?? [];
|
||||
if (this.backlinks[target.path].length === 0) {
|
||||
delete this.backlinks[target.path];
|
||||
this.backlinks.set(
|
||||
target.path,
|
||||
this.backlinks.get(target.path)?.filter(connectionsToKeep) ?? []
|
||||
);
|
||||
if (this.backlinks.get(target.path)?.length === 0) {
|
||||
this.backlinks.delete(target.path);
|
||||
if (URI.isPlaceholder(target)) {
|
||||
delete this.placeholders[uriToPlaceholderId(target)];
|
||||
this.placeholders.delete(uriToPlaceholderId(target));
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public resolveResource(resource: Resource) {
|
||||
delete this.links[resource.uri.path];
|
||||
this.links.delete(resource.uri.path);
|
||||
// prettier-ignore
|
||||
resource.links.forEach(link => {
|
||||
const targetUri = this.workspace.resolveLink(resource, link);
|
||||
@@ -11,8 +11,9 @@ export interface NoteSource {
|
||||
|
||||
export interface WikiLink {
|
||||
type: 'wikilink';
|
||||
slug: string;
|
||||
target: string;
|
||||
label: string;
|
||||
rawText: string;
|
||||
range: Range;
|
||||
}
|
||||
|
||||
@@ -32,13 +33,18 @@ export interface NoteLinkDefinition {
|
||||
range?: Range;
|
||||
}
|
||||
|
||||
export interface Tag {
|
||||
label: string;
|
||||
range: Range;
|
||||
}
|
||||
|
||||
export interface Resource {
|
||||
uri: URI;
|
||||
type: string;
|
||||
title: string;
|
||||
properties: any;
|
||||
// sections: NoteSection[]
|
||||
tags: Set<string>;
|
||||
tags: Tag[];
|
||||
links: ResourceLink[];
|
||||
|
||||
// TODO to remove
|
||||
@@ -1,6 +1,6 @@
|
||||
import { IDisposable } from 'common/lifecycle';
|
||||
import { ResourceLink, URI } from 'index';
|
||||
import { Resource } from './note';
|
||||
import { IDisposable } from '../common/lifecycle';
|
||||
import { Resource, ResourceLink } from './note';
|
||||
import { URI } from './uri';
|
||||
import { FoamWorkspace } from './workspace';
|
||||
|
||||
export interface ResourceProvider extends IDisposable {
|
||||
138
packages/foam-vscode/src/core/model/tags.test.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
import { createTestNote, createTestWorkspace } from '../../test/test-utils';
|
||||
import { FoamTags } from './tags';
|
||||
|
||||
describe('FoamTags', () => {
|
||||
it('Collects tags from a list of resources', () => {
|
||||
const ws = createTestWorkspace();
|
||||
|
||||
const pageA = createTestNote({
|
||||
uri: '/page-a.md',
|
||||
title: 'Page A',
|
||||
links: [{ slug: 'placeholder-link' }],
|
||||
tags: ['primary', 'secondary'],
|
||||
});
|
||||
|
||||
const pageB = createTestNote({
|
||||
uri: '/page-b.md',
|
||||
title: 'Page B',
|
||||
links: [{ slug: 'placeholder-link' }],
|
||||
tags: ['primary', 'third'],
|
||||
});
|
||||
|
||||
ws.set(pageA);
|
||||
ws.set(pageB);
|
||||
|
||||
const tags = FoamTags.fromWorkspace(ws);
|
||||
|
||||
expect(tags.tags).toEqual(
|
||||
new Map([
|
||||
['primary', [pageA.uri, pageB.uri]],
|
||||
['secondary', [pageA.uri]],
|
||||
['third', [pageB.uri]],
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('Updates an existing tag when a note is tagged with an existing tag', () => {
|
||||
const ws = createTestWorkspace();
|
||||
|
||||
const page = createTestNote({
|
||||
uri: '/page-a.md',
|
||||
title: 'Page A',
|
||||
links: [{ slug: 'placeholder-link' }],
|
||||
tags: ['primary'],
|
||||
});
|
||||
const taglessPage = createTestNote({
|
||||
uri: '/page-b.md',
|
||||
title: 'Page B',
|
||||
});
|
||||
|
||||
ws.set(page);
|
||||
ws.set(taglessPage);
|
||||
|
||||
const tags = FoamTags.fromWorkspace(ws);
|
||||
expect(tags.tags).toEqual(new Map([['primary', [page.uri]]]));
|
||||
|
||||
const newPage = createTestNote({
|
||||
uri: '/page-b.md',
|
||||
title: 'Page B',
|
||||
tags: ['primary'],
|
||||
});
|
||||
|
||||
tags.updateResourceWithinTagIndex(taglessPage, newPage);
|
||||
|
||||
expect(tags.tags).toEqual(new Map([['primary', [page.uri, newPage.uri]]]));
|
||||
});
|
||||
|
||||
it('Replaces the tag when a note is updated with an altered tag', () => {
|
||||
const ws = createTestWorkspace();
|
||||
|
||||
const page = createTestNote({
|
||||
uri: '/page-a.md',
|
||||
title: 'Page A',
|
||||
links: [{ slug: 'placeholder-link' }],
|
||||
tags: ['primary'],
|
||||
});
|
||||
|
||||
ws.set(page);
|
||||
|
||||
const tags = FoamTags.fromWorkspace(ws);
|
||||
expect(tags.tags).toEqual(new Map([['primary', [page.uri]]]));
|
||||
|
||||
const pageEdited = createTestNote({
|
||||
uri: '/page-a.md',
|
||||
title: 'Page A',
|
||||
links: [{ slug: 'placeholder-link' }],
|
||||
tags: ['new'],
|
||||
});
|
||||
|
||||
tags.updateResourceWithinTagIndex(page, pageEdited);
|
||||
|
||||
expect(tags.tags).toEqual(new Map([['new', [page.uri]]]));
|
||||
});
|
||||
|
||||
it('Updates the metadata of a tag when the note is moved', () => {
|
||||
const ws = createTestWorkspace();
|
||||
|
||||
const page = createTestNote({
|
||||
uri: '/page-a.md',
|
||||
title: 'Page A',
|
||||
links: [{ slug: 'placeholder-link' }],
|
||||
tags: ['primary'],
|
||||
});
|
||||
ws.set(page);
|
||||
|
||||
const tags = FoamTags.fromWorkspace(ws);
|
||||
expect(tags.tags).toEqual(new Map([['primary', [page.uri]]]));
|
||||
|
||||
const pageEdited = createTestNote({
|
||||
uri: '/new-place/page-a.md',
|
||||
title: 'Page A',
|
||||
links: [{ slug: 'placeholder-link' }],
|
||||
tags: ['primary'],
|
||||
});
|
||||
|
||||
tags.updateResourceWithinTagIndex(page, pageEdited);
|
||||
|
||||
expect(tags.tags).toEqual(new Map([['primary', [pageEdited.uri]]]));
|
||||
});
|
||||
|
||||
it('Updates the metadata of a tag when a note is delete', () => {
|
||||
const ws = createTestWorkspace();
|
||||
|
||||
const page = createTestNote({
|
||||
uri: '/page-a.md',
|
||||
title: 'Page A',
|
||||
links: [{ slug: 'placeholder-link' }],
|
||||
tags: ['primary'],
|
||||
});
|
||||
ws.set(page);
|
||||
|
||||
const tags = FoamTags.fromWorkspace(ws);
|
||||
expect(tags.tags).toEqual(new Map([['primary', [page.uri]]]));
|
||||
|
||||
tags.removeResourceFromTagIndex(page);
|
||||
|
||||
expect(tags.tags).toEqual(new Map());
|
||||
});
|
||||
});
|
||||
81
packages/foam-vscode/src/core/model/tags.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { FoamWorkspace } from './workspace';
|
||||
import { URI } from './uri';
|
||||
import { Resource } from './note';
|
||||
import { IDisposable } from '../common/lifecycle';
|
||||
|
||||
export class FoamTags implements IDisposable {
|
||||
public readonly tags: Map<string, URI[]> = new Map();
|
||||
|
||||
/**
|
||||
* List of disposables to destroy with the tags
|
||||
*/
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
/**
|
||||
* Computes all tags in the workspace and keep them up-to-date
|
||||
*
|
||||
* @param workspace the target workspace
|
||||
* @param keepMonitoring whether to recompute the links when the workspace changes
|
||||
* @returns the FoamTags
|
||||
*/
|
||||
public static fromWorkspace(
|
||||
workspace: FoamWorkspace,
|
||||
keepMonitoring: boolean = false
|
||||
): FoamTags {
|
||||
let tags = new FoamTags();
|
||||
|
||||
workspace
|
||||
.list()
|
||||
.forEach(resource => tags.addResourceFromTagIndex(resource));
|
||||
|
||||
if (keepMonitoring) {
|
||||
tags.disposables.push(
|
||||
workspace.onDidAdd(resource => {
|
||||
tags.addResourceFromTagIndex(resource);
|
||||
}),
|
||||
workspace.onDidUpdate(change => {
|
||||
tags.updateResourceWithinTagIndex(change.old, change.new);
|
||||
}),
|
||||
workspace.onDidDelete(resource => {
|
||||
tags.removeResourceFromTagIndex(resource);
|
||||
})
|
||||
);
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables.forEach(d => d.dispose());
|
||||
this.disposables = [];
|
||||
}
|
||||
|
||||
updateResourceWithinTagIndex(oldResource: Resource, newResource: Resource) {
|
||||
this.removeResourceFromTagIndex(oldResource);
|
||||
this.addResourceFromTagIndex(newResource);
|
||||
}
|
||||
|
||||
addResourceFromTagIndex(resource: Resource) {
|
||||
new Set(resource.tags.map(t => t.label)).forEach(tag => {
|
||||
const tagMeta = this.tags.get(tag) ?? [];
|
||||
tagMeta.push(resource.uri);
|
||||
this.tags.set(tag, tagMeta);
|
||||
});
|
||||
}
|
||||
|
||||
removeResourceFromTagIndex(resource: Resource) {
|
||||
resource.tags.forEach(t => {
|
||||
const tag = t.label;
|
||||
if (this.tags.has(tag)) {
|
||||
const remainingLocations = this.tags
|
||||
.get(tag)
|
||||
?.filter(uri => !URI.isEqual(uri, resource.uri));
|
||||
|
||||
if (remainingLocations && remainingLocations.length > 0) {
|
||||
this.tags.set(tag, remainingLocations);
|
||||
} else {
|
||||
this.tags.delete(tag);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Logger } from '../src';
|
||||
import { URI } from '../src/model/uri';
|
||||
import { uriToSlug } from '../src/utils/slug';
|
||||
import { Logger } from '../utils/log';
|
||||
import { uriToSlug } from '../utils/slug';
|
||||
import { URI } from './uri';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
describe('Foam URIs', () => {
|
||||
describe('Foam URI', () => {
|
||||
describe('URI parsing', () => {
|
||||
const base = URI.file('/path/to/file.md');
|
||||
test.each([
|
||||
@@ -16,7 +16,7 @@ describe('Foam URIs', () => {
|
||||
'../relative/file.md#section',
|
||||
URI.parse('file:/path/relative/file.md#section'),
|
||||
],
|
||||
])('URI Parsing (%s) - %s', (input, exp) => {
|
||||
])('URI Parsing (%s)', (input, exp) => {
|
||||
const result = URI.resolve(input, base);
|
||||
expect(result.scheme).toEqual(exp.scheme);
|
||||
expect(result.authority).toEqual(exp.authority);
|
||||
@@ -24,20 +24,45 @@ describe('Foam URIs', () => {
|
||||
expect(result.query).toEqual(exp.query);
|
||||
expect(result.fragment).toEqual(exp.fragment);
|
||||
});
|
||||
});
|
||||
it('supports various cases', () => {
|
||||
expect(uriToSlug(URI.file('/this/is/a/path.md'))).toEqual('path');
|
||||
expect(uriToSlug(URI.file('../a/relative/path.md'))).toEqual('path');
|
||||
expect(uriToSlug(URI.file('another/relative/path.md'))).toEqual('path');
|
||||
expect(uriToSlug(URI.file('no-directory.markdown'))).toEqual(
|
||||
'no-directory'
|
||||
);
|
||||
expect(uriToSlug(URI.file('many.dots.name.markdown'))).toEqual(
|
||||
'manydotsname'
|
||||
);
|
||||
|
||||
it('normalizes the Windows drive letter to upper case', () => {
|
||||
const upperCase = URI.parse('file:///C:/this/is/a/Path');
|
||||
const lowerCase = URI.parse('file:///c:/this/is/a/Path');
|
||||
expect(upperCase.path).toEqual('/C:/this/is/a/Path');
|
||||
expect(lowerCase.path).toEqual('/C:/this/is/a/Path');
|
||||
expect(URI.toFsPath(upperCase)).toEqual('C:\\this\\is\\a\\Path');
|
||||
expect(URI.toFsPath(lowerCase)).toEqual('C:\\this\\is\\a\\Path');
|
||||
});
|
||||
|
||||
it('consistently parses file paths', () => {
|
||||
const win1 = URI.file('c:\\this\\is\\a\\path');
|
||||
const win2 = URI.parse('c:\\this\\is\\a\\path');
|
||||
expect(win1).toEqual(win2);
|
||||
|
||||
const unix1 = URI.file('/this/is/a/path');
|
||||
const unix2 = URI.parse('/this/is/a/path');
|
||||
expect(unix1).toEqual(unix2);
|
||||
});
|
||||
|
||||
it('correctly parses file paths', () => {
|
||||
const winUri = URI.file('c:\\this\\is\\a\\path');
|
||||
const unixUri = URI.file('/this/is/a/path');
|
||||
expect(winUri).toEqual(
|
||||
URI.create({
|
||||
scheme: 'file',
|
||||
path: '/C:/this/is/a/path',
|
||||
})
|
||||
);
|
||||
expect(unixUri).toEqual(
|
||||
URI.create({
|
||||
scheme: 'file',
|
||||
path: '/this/is/a/path',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('computes a relative uri using a slug', () => {
|
||||
it('supports computing relative paths', () => {
|
||||
expect(
|
||||
URI.computeRelativeURI(URI.file('/my/file.md'), '../hello.md')
|
||||
).toEqual(URI.file('/hello.md'));
|
||||
@@ -48,4 +73,16 @@ describe('Foam URIs', () => {
|
||||
URI.computeRelativeURI(URI.file('/my/file.markdown'), '../hello')
|
||||
).toEqual(URI.file('/hello.markdown'));
|
||||
});
|
||||
|
||||
it('can be slugified', () => {
|
||||
expect(uriToSlug(URI.file('/this/is/a/path.md'))).toEqual('path');
|
||||
expect(uriToSlug(URI.file('../a/relative/path.md'))).toEqual('path');
|
||||
expect(uriToSlug(URI.file('another/relative/path.md'))).toEqual('path');
|
||||
expect(uriToSlug(URI.file('no-directory.markdown'))).toEqual(
|
||||
'no-directory'
|
||||
);
|
||||
expect(uriToSlug(URI.file('many.dots.name.markdown'))).toEqual(
|
||||
'manydotsname'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,11 @@
|
||||
// Some code in this file coming from https://github.com/microsoft/vscode/
|
||||
// `URI` is mostly compatible with VSCode's `Uri`.
|
||||
// Having a Foam-specific URI object allows for easier maintenance of the API.
|
||||
// See https://github.com/foambubble/foam/pull/537 for more context.
|
||||
// Some code in this file comes from https://github.com/microsoft/vscode/main/src/vs/base/common/uri.ts
|
||||
// See LICENSE for details
|
||||
|
||||
import * as paths from 'path';
|
||||
import { CharCode } from '../common/charCode';
|
||||
import { isWindows } from '../common/platform';
|
||||
|
||||
/**
|
||||
* Uniform Resource Identifier (URI) http://tools.ietf.org/html/rfc3986.
|
||||
@@ -36,6 +38,9 @@ const _regexp = /^(([^:/?#]{2,}?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
|
||||
|
||||
export abstract class URI {
|
||||
static create(from: Partial<URI>): URI {
|
||||
// When using this method we assume the path is already posix
|
||||
// so we don't check whether it's a Windows path, nor we do any
|
||||
// conversion
|
||||
return {
|
||||
scheme: from.scheme ?? _empty,
|
||||
authority: from.authority ?? _empty,
|
||||
@@ -50,10 +55,14 @@ export abstract class URI {
|
||||
if (!match) {
|
||||
return URI.create({});
|
||||
}
|
||||
let path = percentDecode(match[5] ?? _empty);
|
||||
if (URI.isWindowsPath(path)) {
|
||||
path = windowsPathToUriPath(path);
|
||||
}
|
||||
return URI.create({
|
||||
scheme: match[2] || 'file',
|
||||
authority: percentDecode(match[4] ?? _empty),
|
||||
path: percentDecode(match[5] ?? _empty),
|
||||
path: path,
|
||||
query: percentDecode(match[7] ?? _empty),
|
||||
fragment: percentDecode(match[9] ?? _empty),
|
||||
});
|
||||
@@ -101,12 +110,8 @@ export abstract class URI {
|
||||
// normalize to fwd-slashes on windows,
|
||||
// on other systems bwd-slashes are valid
|
||||
// filename character, eg /f\oo/ba\r.txt
|
||||
if (isWindows) {
|
||||
if (path.startsWith(_slash)) {
|
||||
path = `${path.replace(/\\/g, _slash)}`;
|
||||
} else {
|
||||
path = `/${path.replace(/\\/g, _slash)}`;
|
||||
}
|
||||
if (URI.isWindowsPath(path)) {
|
||||
path = windowsPathToUriPath(path);
|
||||
}
|
||||
|
||||
// check for authority as used in UNC shares
|
||||
@@ -148,6 +153,10 @@ export abstract class URI {
|
||||
return URI.file(posix.dirname(uri.path));
|
||||
}
|
||||
|
||||
static getFileNameWithoutExtension(uri: URI) {
|
||||
return URI.getBasename(uri).replace(/\.[^.]+$/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses a placeholder URI, and a reference directory, to generate
|
||||
* the URI of the corresponding resource
|
||||
@@ -178,7 +187,7 @@ export abstract class URI {
|
||||
throw new Error(`[UriError]: cannot call joinPath on URI without path`);
|
||||
}
|
||||
let newPath: string;
|
||||
if (isWindows && uri.scheme === 'file') {
|
||||
if (URI.isWindowsPath(uri.path) && uri.scheme === 'file') {
|
||||
newPath = URI.file(paths.win32.join(URI.toFsPath(uri), ...pathFragment))
|
||||
.path;
|
||||
} else {
|
||||
@@ -187,7 +196,7 @@ export abstract class URI {
|
||||
return URI.create({ ...uri, path: newPath });
|
||||
}
|
||||
|
||||
static toFsPath(uri: URI, keepDriveLetterCasing = true): string {
|
||||
static toFsPath(uri: URI): string {
|
||||
let value: string;
|
||||
if (uri.authority && uri.path.length > 1 && uri.scheme === 'file') {
|
||||
// unc path: file://shares/c$/far/boo
|
||||
@@ -200,17 +209,13 @@ export abstract class URI {
|
||||
uri.path.charCodeAt(1) <= CharCode.z)) &&
|
||||
uri.path.charCodeAt(2) === CharCode.Colon
|
||||
) {
|
||||
if (!keepDriveLetterCasing) {
|
||||
// windows drive letter: file:///c:/far/boo
|
||||
value = uri.path[1].toLowerCase() + uri.path.substr(2);
|
||||
} else {
|
||||
value = uri.path.substr(1);
|
||||
}
|
||||
// windows drive letter: file:///C:/far/boo
|
||||
value = uri.path[1].toUpperCase() + uri.path.substr(2);
|
||||
} else {
|
||||
// other path
|
||||
value = uri.path;
|
||||
}
|
||||
if (isWindows) {
|
||||
if (URI.isWindowsPath(value)) {
|
||||
value = value.replace(/\//g, '\\');
|
||||
}
|
||||
return value;
|
||||
@@ -222,6 +227,15 @@ export abstract class URI {
|
||||
|
||||
// --- utility
|
||||
|
||||
static isWindowsPath(path: string) {
|
||||
return (
|
||||
(path.length >= 2 && path.charCodeAt(1) === CharCode.Colon) ||
|
||||
(path.length >= 3 &&
|
||||
path.charCodeAt(0) === CharCode.Slash &&
|
||||
path.charCodeAt(2) === CharCode.Colon)
|
||||
);
|
||||
}
|
||||
|
||||
static isUri(thing: any): thing is URI {
|
||||
if (!thing) {
|
||||
return false;
|
||||
@@ -278,6 +292,33 @@ function percentDecode(str: string): string {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a windows-like path to standard URI path
|
||||
* - Normalize the Windows drive letter to upper case
|
||||
* - replace \ with /
|
||||
* - always start with /
|
||||
*
|
||||
* see https://github.com/foambubble/foam/issues/813
|
||||
* see https://github.com/microsoft/vscode/issues/43959
|
||||
* see https://github.com/microsoft/vscode/issues/116298
|
||||
*
|
||||
* @param path the path to convert
|
||||
* @returns the URI compatible path
|
||||
*/
|
||||
function windowsPathToUriPath(path: string): string {
|
||||
path = path.charCodeAt(0) === CharCode.Slash ? path : `/${path}`;
|
||||
path = path.replace(/\\/g, _slash);
|
||||
const code = path.charCodeAt(1);
|
||||
if (
|
||||
path.charCodeAt(2) === CharCode.Colon &&
|
||||
code >= CharCode.a &&
|
||||
code <= CharCode.z
|
||||
) {
|
||||
path = `/${String.fromCharCode(code - 32)}:${path.substr(3)}`; // "/C:".length === 3
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the external version of a uri
|
||||
*/
|
||||
@@ -324,20 +365,20 @@ function encode(uri: URI, skipEncoding: boolean): string {
|
||||
}
|
||||
}
|
||||
if (path) {
|
||||
// lower-case windows drive letters in /C:/fff or C:/fff
|
||||
// upper-case windows drive letters in /c:/fff or c:/fff
|
||||
if (
|
||||
path.length >= 3 &&
|
||||
path.charCodeAt(0) === CharCode.Slash &&
|
||||
path.charCodeAt(2) === CharCode.Colon
|
||||
) {
|
||||
const code = path.charCodeAt(1);
|
||||
if (code >= CharCode.A && code <= CharCode.Z) {
|
||||
path = `/${String.fromCharCode(code + 32)}:${path.substr(3)}`; // "/c:".length === 3
|
||||
if (code >= CharCode.a && code <= CharCode.z) {
|
||||
path = `/${String.fromCharCode(code - 32)}:${path.substr(3)}`; // "/C:".length === 3
|
||||
}
|
||||
} else if (path.length >= 2 && path.charCodeAt(1) === CharCode.Colon) {
|
||||
const code = path.charCodeAt(0);
|
||||
if (code >= CharCode.A && code <= CharCode.Z) {
|
||||
path = `${String.fromCharCode(code + 32)}:${path.substr(2)}`; // "/c:".length === 3
|
||||
if (code >= CharCode.a && code <= CharCode.z) {
|
||||
path = `${String.fromCharCode(code - 32)}:${path.substr(2)}`; // "/C:".length === 3
|
||||
}
|
||||
}
|
||||
// encode the rest of the path
|
||||
@@ -1,8 +1,8 @@
|
||||
import { getReferenceType } from '../src/model/workspace';
|
||||
import { FoamGraph } from '../src/model/graph';
|
||||
import { Logger } from '../src/utils/log';
|
||||
import { createTestNote, createTestWorkspace } from './core.test';
|
||||
import { URI } from '../src/model/uri';
|
||||
import { getReferenceType } from './workspace';
|
||||
import { FoamGraph } from './graph';
|
||||
import { Logger } from '../utils/log';
|
||||
import { URI } from './uri';
|
||||
import { createTestNote, createTestWorkspace } from '../../test/test-utils';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
@@ -64,6 +64,13 @@ describe('Workspace resources', () => {
|
||||
expect(ws.find(uri)).toBeNull();
|
||||
expect(() => ws.get(uri)).toThrow();
|
||||
});
|
||||
|
||||
it('Should work with a resource named like a JS prototype property', () => {
|
||||
const ws = createTestWorkspace();
|
||||
const noteA = createTestNote({ uri: '/somewhere/constructor.md' });
|
||||
ws.set(noteA);
|
||||
expect(ws.list()).toEqual([noteA]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Graph', () => {
|
||||
@@ -232,7 +239,7 @@ describe('Wikilinks', () => {
|
||||
expect(graph.getAllConnections()[0]).toEqual({
|
||||
source: noteA.uri,
|
||||
target: noteB.uri,
|
||||
link: expect.objectContaining({ type: 'wikilink', slug: 'page-b' }),
|
||||
link: expect.objectContaining({ type: 'wikilink', label: 'page-b' }),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -354,6 +361,51 @@ describe('Wikilinks', () => {
|
||||
attachmentABis.uri,
|
||||
]);
|
||||
});
|
||||
|
||||
it('Allows for dendron-style wikilinks, including a dot', () => {
|
||||
const noteA = createTestNote({
|
||||
uri: '/path/to/page-a.md',
|
||||
links: [{ slug: 'dendron.style' }],
|
||||
});
|
||||
const noteB1 = createTestNote({ uri: '/path/to/another/dendron.style.md' });
|
||||
|
||||
const ws = createTestWorkspace();
|
||||
ws.set(noteA).set(noteB1);
|
||||
const graph = FoamGraph.fromWorkspace(ws);
|
||||
|
||||
expect(graph.getLinks(noteA.uri).map(l => l.target)).toEqual([noteB1.uri]);
|
||||
});
|
||||
|
||||
it('Handles capatalization of files and wikilinks correctly', () => {
|
||||
const noteA = createTestNote({
|
||||
uri: '/path/to/page-a.md',
|
||||
links: [
|
||||
// uppercased filename, lowercased slug
|
||||
{ slug: 'page-b' },
|
||||
// lowercased filename, camelcased wikilink
|
||||
{ slug: 'Page-C' },
|
||||
// lowercased filename, lowercased wikilink
|
||||
{ slug: 'page-d' },
|
||||
],
|
||||
});
|
||||
const ws = createTestWorkspace()
|
||||
.set(noteA)
|
||||
.set(createTestNote({ uri: '/somewhere/PAGE-B.md' }))
|
||||
.set(createTestNote({ uri: '/path/another/page-c.md' }))
|
||||
.set(createTestNote({ uri: '/path/another/page-d.md' }));
|
||||
const graph = FoamGraph.fromWorkspace(ws);
|
||||
|
||||
expect(
|
||||
graph
|
||||
.getLinks(noteA.uri)
|
||||
.map(link => link.target.path)
|
||||
.sort()
|
||||
).toEqual([
|
||||
'/path/another/page-c.md',
|
||||
'/path/another/page-d.md',
|
||||
'/somewhere/PAGE-B.md',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('markdown direct links', () => {
|
||||
@@ -473,6 +525,23 @@ describe('Placeholders', () => {
|
||||
link: expect.objectContaining({ type: 'wikilink' }),
|
||||
});
|
||||
});
|
||||
|
||||
it('Should work with a placeholder named like a JS prototype property', () => {
|
||||
const ws = createTestWorkspace();
|
||||
const noteA = createTestNote({
|
||||
uri: '/page-a.md',
|
||||
links: [{ slug: 'constructor' }],
|
||||
});
|
||||
ws.set(noteA);
|
||||
const graph = FoamGraph.fromWorkspace(ws);
|
||||
|
||||
expect(
|
||||
graph
|
||||
.getAllNodes()
|
||||
.map(uri => uri.path)
|
||||
.sort()
|
||||
).toEqual(['/page-a.md', 'constructor']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Updating workspace happy path', () => {
|
||||
@@ -3,8 +3,8 @@ import { Resource, ResourceLink } from './note';
|
||||
import { URI } from './uri';
|
||||
import { isSome, isNone } from '../utils';
|
||||
import { Emitter } from '../common/event';
|
||||
import { IDisposable } from '../index';
|
||||
import { ResourceProvider } from './provider';
|
||||
import { IDisposable } from '../common/lifecycle';
|
||||
|
||||
export function getReferenceType(
|
||||
reference: URI | string
|
||||
@@ -26,7 +26,8 @@ const pathToResourceId = (pathValue: string) => {
|
||||
};
|
||||
const uriToResourceId = (uri: URI) => pathToResourceId(uri.path);
|
||||
|
||||
const pathToResourceName = (pathValue: string) => path.parse(pathValue).name;
|
||||
const pathToResourceName = (pathValue: string) =>
|
||||
path.parse(pathValue).name.toLowerCase();
|
||||
export const uriToResourceName = (uri: URI) => pathToResourceName(uri.path);
|
||||
|
||||
export class FoamWorkspace implements IDisposable {
|
||||
@@ -42,11 +43,11 @@ export class FoamWorkspace implements IDisposable {
|
||||
/**
|
||||
* Resources by key / slug
|
||||
*/
|
||||
private resourcesByName: { [key: string]: string[] } = {};
|
||||
private resourcesByName: Map<string, string[]> = new Map();
|
||||
/**
|
||||
* Resources by URI
|
||||
*/
|
||||
private resources: { [key: string]: Resource } = {};
|
||||
private resources: Map<string, Resource> = new Map();
|
||||
|
||||
registerProvider(provider: ResourceProvider) {
|
||||
this.providers.push(provider);
|
||||
@@ -57,9 +58,11 @@ export class FoamWorkspace implements IDisposable {
|
||||
const id = uriToResourceId(resource.uri);
|
||||
const old = this.find(resource.uri);
|
||||
const name = uriToResourceName(resource.uri);
|
||||
this.resources[id] = resource;
|
||||
this.resourcesByName[name] = this.resourcesByName[name] ?? [];
|
||||
this.resourcesByName[name].push(id);
|
||||
this.resources.set(id, resource);
|
||||
if (!this.resourcesByName.has(name)) {
|
||||
this.resourcesByName.set(name, []);
|
||||
}
|
||||
this.resourcesByName.get(name)?.push(id);
|
||||
isSome(old)
|
||||
? this.onDidUpdateEmitter.fire({ old: old, new: resource })
|
||||
: this.onDidAddEmitter.fire(resource);
|
||||
@@ -68,14 +71,16 @@ export class FoamWorkspace implements IDisposable {
|
||||
|
||||
delete(uri: URI) {
|
||||
const id = uriToResourceId(uri);
|
||||
const deleted = this.resources[id];
|
||||
delete this.resources[id];
|
||||
const deleted = this.resources.get(id);
|
||||
this.resources.delete(id);
|
||||
|
||||
const name = uriToResourceName(uri);
|
||||
this.resourcesByName[name] =
|
||||
this.resourcesByName[name]?.filter(resId => resId !== id) ?? [];
|
||||
if (this.resourcesByName[name].length === 0) {
|
||||
delete this.resourcesByName[name];
|
||||
this.resourcesByName.set(
|
||||
name,
|
||||
this.resourcesByName.get(name)?.filter(resId => resId !== id) ?? []
|
||||
);
|
||||
if (this.resourcesByName.get(name)?.length === 0) {
|
||||
this.resourcesByName.delete(name);
|
||||
}
|
||||
|
||||
isSome(deleted) && this.onDidDeleteEmitter.fire(deleted);
|
||||
@@ -84,12 +89,13 @@ export class FoamWorkspace implements IDisposable {
|
||||
|
||||
public exists(uri: URI): boolean {
|
||||
return (
|
||||
!URI.isPlaceholder(uri) && isSome(this.resources[uriToResourceId(uri)])
|
||||
!URI.isPlaceholder(uri) &&
|
||||
isSome(this.resources.get(uriToResourceId(uri)))
|
||||
);
|
||||
}
|
||||
|
||||
public list(): Resource[] {
|
||||
return Object.values(this.resources);
|
||||
return Array.from(this.resources.values());
|
||||
}
|
||||
|
||||
public get(uri: URI): Resource {
|
||||
@@ -106,11 +112,18 @@ export class FoamWorkspace implements IDisposable {
|
||||
switch (refType) {
|
||||
case 'uri':
|
||||
const uri = resourceId as URI;
|
||||
return this.exists(uri) ? this.resources[uriToResourceId(uri)] : null;
|
||||
return this.exists(uri)
|
||||
? this.resources.get(uriToResourceId(uri)) ?? null
|
||||
: null;
|
||||
|
||||
case 'key':
|
||||
const name = pathToResourceName(resourceId as string);
|
||||
const paths = this.resourcesByName[name];
|
||||
let paths = this.resourcesByName.get(name);
|
||||
|
||||
if (isNone(paths) || paths.length === 0) {
|
||||
paths = this.resourcesByName.get(resourceId as string);
|
||||
}
|
||||
|
||||
if (isNone(paths) || paths.length === 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -118,11 +131,12 @@ export class FoamWorkspace implements IDisposable {
|
||||
const sortedPaths = paths.length === 1
|
||||
? paths
|
||||
: paths.sort((a, b) => a.localeCompare(b));
|
||||
return this.resources[sortedPaths[0]];
|
||||
|
||||
return this.resources.get(sortedPaths[0]) ?? null;
|
||||
|
||||
case 'absolute-path':
|
||||
const resourceUri = URI.file(resourceId as string);
|
||||
return this.resources[uriToResourceId(resourceUri)] ?? null;
|
||||
return this.resources.get(uriToResourceId(resourceUri)) ?? null;
|
||||
|
||||
case 'relative-path':
|
||||
if (isNone(reference)) {
|
||||
@@ -130,7 +144,7 @@ export class FoamWorkspace implements IDisposable {
|
||||
}
|
||||
const relativePath = resourceId as string;
|
||||
const targetUri = URI.computeRelativeURI(reference, relativePath);
|
||||
return this.resources[uriToResourceId(targetUri)] ?? null;
|
||||
return this.resources.get(uriToResourceId(targetUri)) ?? null;
|
||||
|
||||
default:
|
||||
throw new Error('Unexpected reference type: ' + refType);
|
||||