mirror of
https://github.com/foambubble/foam.git
synced 2026-01-10 22:48:09 -05:00
Compare commits
180 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdb95a0832 | ||
|
|
e0580d39bf | ||
|
|
279b3b48f1 | ||
|
|
e3c63fca89 | ||
|
|
948b7db5ef | ||
|
|
503a8f5f18 | ||
|
|
8ab4f13543 | ||
|
|
894bf12899 | ||
|
|
a3b375e248 | ||
|
|
c840070a3a | ||
|
|
8a6551f281 | ||
|
|
88ae96cf25 | ||
|
|
acfd2e1fc1 | ||
|
|
6b02a87538 | ||
|
|
1a99e693df | ||
|
|
dd467ce86f | ||
|
|
7d7446ef7e | ||
|
|
6be4f002b8 | ||
|
|
bb6faee06d | ||
|
|
31cfeb3034 | ||
|
|
5d11818ffc | ||
|
|
e7ee143544 | ||
|
|
aa311b2688 | ||
|
|
0fca141a7b | ||
|
|
6a4bd341ab | ||
|
|
764750f591 | ||
|
|
2686b9a365 | ||
|
|
5a6ef644bd | ||
|
|
d7c92f8284 | ||
|
|
c2e5e4bf2a | ||
|
|
9d0ba879d2 | ||
|
|
9606dcc64c | ||
|
|
d70e441790 | ||
|
|
dde11f8c6f | ||
|
|
cd9ee4d556 | ||
|
|
a8296c2c88 | ||
|
|
13a340eb1d | ||
|
|
d2dd979e70 | ||
|
|
4989796cb0 | ||
|
|
d24814d065 | ||
|
|
4a410d1f5c | ||
|
|
ccb92ad5ee | ||
|
|
e6512cffa8 | ||
|
|
1fa4f37d96 | ||
|
|
27b9b451ad | ||
|
|
362d6f8e09 | ||
|
|
cef8d2a532 | ||
|
|
22b837f252 | ||
|
|
07e02c2d69 | ||
|
|
931ad7a5b6 | ||
|
|
db7eb9775f | ||
|
|
b25152d115 | ||
|
|
1545079c62 | ||
|
|
4835164902 | ||
|
|
06efdc2865 | ||
|
|
b68fd7e138 | ||
|
|
d8baa2fd36 | ||
|
|
7f587095e8 | ||
|
|
77ad245319 | ||
|
|
b892c783da | ||
|
|
e4f6259104 | ||
|
|
aa197239fc | ||
|
|
8f3c23dd60 | ||
|
|
9a027c08ba | ||
|
|
959d0f1ea1 | ||
|
|
57e32c4349 | ||
|
|
f168f66368 | ||
|
|
103ff12b2d | ||
|
|
96a3afa132 | ||
|
|
d586e63104 | ||
|
|
2fba6e9008 | ||
|
|
cdbb965661 | ||
|
|
0c958e31f6 | ||
|
|
fd84fcfd74 | ||
|
|
becf495edc | ||
|
|
4d99883c03 | ||
|
|
8bd679c751 | ||
|
|
8986ee286c | ||
|
|
51b1af1981 | ||
|
|
4276e8043f | ||
|
|
8d1e9b15ce | ||
|
|
bfcfad32e8 | ||
|
|
abe18cc961 | ||
|
|
9490aa2dad | ||
|
|
6c3f4588b3 | ||
|
|
21b8c5b827 | ||
|
|
c66ed74aea | ||
|
|
3876811fb6 | ||
|
|
d9299ee9d4 | ||
|
|
23e21a62f3 | ||
|
|
e7c8d5a4eb | ||
|
|
3ef1b69b2e | ||
|
|
5859b2a9c6 | ||
|
|
54086fdd7e | ||
|
|
a308dfd109 | ||
|
|
e327115673 | ||
|
|
c019767476 | ||
|
|
5e8b817a82 | ||
|
|
e0acc0ba8c | ||
|
|
df4efc5138 | ||
|
|
1e2b3b1bc3 | ||
|
|
eee364de50 | ||
|
|
3ed2bcd37b | ||
|
|
797aa9f29a | ||
|
|
30ab58485c | ||
|
|
ba98f1990c | ||
|
|
6c1d6868f7 | ||
|
|
e773e1ff68 | ||
|
|
86e2bb1ba0 | ||
|
|
fc2fb6a0ab | ||
|
|
956d0119be | ||
|
|
8ddb6a2d12 | ||
|
|
fb9447630f | ||
|
|
06a5988a52 | ||
|
|
fac2247382 | ||
|
|
b089b997bb | ||
|
|
a504054504 | ||
|
|
a00d18cfbb | ||
|
|
5ca7c3eb52 | ||
|
|
571b6a3528 | ||
|
|
b6c9eac86c | ||
|
|
557330413c | ||
|
|
20894a1166 | ||
|
|
d0b3d5ff11 | ||
|
|
34fb62bb0b | ||
|
|
f297139e0c | ||
|
|
09e13f77b0 | ||
|
|
56d8c4c7a0 | ||
|
|
626a323193 | ||
|
|
25d9b5e459 | ||
|
|
c2241f16de | ||
|
|
5dee7cb2c0 | ||
|
|
154ded382b | ||
|
|
5de69ff3c3 | ||
|
|
8aefcfd515 | ||
|
|
e0e08a2a0f | ||
|
|
93c5d2f80c | ||
|
|
1c294d84c5 | ||
|
|
f1b15eceed | ||
|
|
96f410a453 | ||
|
|
95a14d5dd6 | ||
|
|
10905fd703 | ||
|
|
f4eaf5c5ff | ||
|
|
b4830eaf30 | ||
|
|
0cda6aed50 | ||
|
|
89c9bb5a7f | ||
|
|
941e870a65 | ||
|
|
c6655c33ff | ||
|
|
c94fb18f8a | ||
|
|
cbd55bac74 | ||
|
|
83a90177b9 | ||
|
|
37aec28af6 | ||
|
|
447f7fc068 | ||
|
|
ad1243665a | ||
|
|
f07de73bc4 | ||
|
|
c431ccfb62 | ||
|
|
f31ef897cc | ||
|
|
7a5f45c0ce | ||
|
|
df32d9e708 | ||
|
|
b3d4691bfa | ||
|
|
f5260f7d3f | ||
|
|
9b4b7ec84d | ||
|
|
52b7f86a9f | ||
|
|
2db7060124 | ||
|
|
a4f04b3b6b | ||
|
|
b5a8a5d7c7 | ||
|
|
f5a29e431c | ||
|
|
5a7a1ba89f | ||
|
|
b054bafc78 | ||
|
|
8acb60253a | ||
|
|
3c69508dcb | ||
|
|
a368be9b47 | ||
|
|
b1f76bb653 | ||
|
|
d4bc16b9bd | ||
|
|
882b0b6012 | ||
|
|
048623d910 | ||
|
|
f2fbe927ae | ||
|
|
d0ee71be1b | ||
|
|
2a14dc0c57 | ||
|
|
745acbabd3 |
@@ -1004,8 +1004,144 @@
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Skakerman",
|
||||
"name": "Scott Akerman",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/15224439?v=4",
|
||||
"profile": "http://scottakerman.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "jimgraham",
|
||||
"name": "Jim Graham",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/430293?v=4",
|
||||
"profile": "http://www.jim-graham.net/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "hezhizhen",
|
||||
"name": "Zhizhen He",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7611700?v=4",
|
||||
"profile": "https://t.me/littlepoint",
|
||||
"contributions": [
|
||||
"tool"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "tcheneau",
|
||||
"name": "Tony Cheneau",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/952059?v=4",
|
||||
"profile": "https://amnesiak.org/me",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "nicholas-l",
|
||||
"name": "Nicholas Latham",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/12977174?v=4",
|
||||
"profile": "https://github.com/nicholas-l",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "thara",
|
||||
"name": "Tomochika Hara",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1532891?v=4",
|
||||
"profile": "https://thara.dev",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "dcarosone",
|
||||
"name": "Daniel Carosone",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/11495017?v=4",
|
||||
"profile": "https://github.com/dcarosone",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "MABruni",
|
||||
"name": "Miguel Angel Bruni Montero",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/100445384?v=4",
|
||||
"profile": "https://github.com/MABruni",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Walshkev",
|
||||
"name": "Kevin Walsh ",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/77123083?v=4",
|
||||
"profile": "https://github.com/Walshkev",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "hereistheusername",
|
||||
"name": "Xinglan Liu",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/33437051?v=4",
|
||||
"profile": "http://hereistheusername.github.io/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Hegghammer",
|
||||
"name": "Thomas Hegghammer",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/64712218?v=4",
|
||||
"profile": "http://www.hegghammer.com",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "PiotrAleksander",
|
||||
"name": "Piotr Mrzygłosz",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/6314591?v=4",
|
||||
"profile": "https://github.com/PiotrAleksander",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "markschaver",
|
||||
"name": "Mark Schaver",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7584?v=4",
|
||||
"profile": "http://schaver.com/",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "n8layman",
|
||||
"name": "Nathan Layman",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/25353944?v=4",
|
||||
"profile": "https://github.com/n8layman",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "emmanuel-ferdman",
|
||||
"name": "Emmanuel Ferdman",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/35470921?v=4",
|
||||
"profile": "https://github.com/emmanuel-ferdman",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
"skipCi": true
|
||||
"skipCi": true,
|
||||
"commitType": "docs"
|
||||
}
|
||||
|
||||
@@ -6,21 +6,22 @@
|
||||
"sourceType": "module"
|
||||
},
|
||||
"env": { "node": true, "es6": true },
|
||||
"plugins": ["@typescript-eslint", "import", "jest"],
|
||||
"plugins": ["jest"],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:import/recommended",
|
||||
"plugin:import/typescript",
|
||||
"plugin:jest/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"no-redeclare": "off",
|
||||
"no-unused-vars": "off",
|
||||
"no-use-before-define": "off",
|
||||
"@typescript-eslint/no-use-before-define": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/interface-name-prefix": "off",
|
||||
"@typescript-eslint/no-unused-vars": "warn",
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
{
|
||||
|
||||
8
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
8
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -74,13 +74,13 @@ body:
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating System Version
|
||||
description: What opearting system are you using?
|
||||
description: What operating system are you using?
|
||||
placeholder: |
|
||||
- OS: [e.g. macOS, Windows, Linux]
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: vscode_version
|
||||
id: vscode_version
|
||||
attributes:
|
||||
label: Visual Studio Code Version
|
||||
description: |
|
||||
@@ -92,6 +92,6 @@ body:
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: |
|
||||
description: |
|
||||
Add any other context about the problem here.
|
||||
The Foam log output for VSCode can be found here: https://github.com/foambubble/foam/blob/master/docs/features/foam-logging-in-vscode.md
|
||||
The Foam log output for VSCode can be found here: https://github.com/foambubble/foam/blob/main/docs/user/tools/foam-logging-in-vscode.md
|
||||
|
||||
41
.github/workflows/ci.yml
vendored
41
.github/workflows/ci.yml
vendored
@@ -3,24 +3,34 @@ name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
|
||||
jobs:
|
||||
typos-check:
|
||||
name: Spell Check with Typos
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Actions Repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Check spelling with custom config file
|
||||
uses: crate-ci/typos@v1.14.8
|
||||
with:
|
||||
config: ./typos.toml
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '12'
|
||||
node-version: '18'
|
||||
- name: Restore Dependencies and VS Code test instance
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
@@ -34,21 +44,22 @@ jobs:
|
||||
|
||||
test:
|
||||
name: Build and Test
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-10.15, ubuntu-18.04, windows-2019]
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
OS: ${{ matrix.os }}
|
||||
# strategy:
|
||||
# matrix:
|
||||
# os: [macos-12, ubuntu-22.04, windows-2022]
|
||||
# runs-on: ${{ matrix.os }}
|
||||
runs-on: ubuntu-22.04
|
||||
# env:
|
||||
# OS: ${{ matrix.os }}
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '12'
|
||||
node-version: '18'
|
||||
- name: Restore Dependencies and VS Code test instance
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
|
||||
49
.github/workflows/update-docs.yml
vendored
Normal file
49
.github/workflows/update-docs.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
name: Update Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- docs/user/**/*
|
||||
- docs/.vscode/**/*
|
||||
- docs/assets/**/*
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update-docs:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: foambubble/foam-template
|
||||
path: foam-template
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
path: foam
|
||||
- name: Copy and fixup user docs files
|
||||
id: copy
|
||||
run: |
|
||||
rm -r foam-template/docs
|
||||
rm -r foam-template/assets
|
||||
rm -r foam-template/.vscode
|
||||
cp -r foam/docs/user foam-template/docs
|
||||
cp -r foam/docs/assets foam-template/assets
|
||||
cp -r foam/docs/.vscode foam-template/.vscode
|
||||
|
||||
# Strip autogenerated wikileaks references because
|
||||
# they are not an appropriate default user experience.
|
||||
(cd foam-template/docs; sed -i '/\[\/\/begin\]/,/\[\/\/end\]/d' $(find . -type f -name \*.md))
|
||||
|
||||
# Set the commit message format
|
||||
echo "message=Docs sync @ $(cd foam; git log --pretty='format:%h %s')" >> $GITHUB_OUTPUT
|
||||
- uses: peter-evans/create-pull-request@v4
|
||||
with:
|
||||
token: ${{ secrets.FOAM_DOCS_SYNC_TOKEN }}
|
||||
path: foam-template
|
||||
commit-message: ${{ steps.copy.outputs.message }}
|
||||
branch: bot/foam-docs-sync
|
||||
delete-branch: true
|
||||
title: Sync docs from foam
|
||||
body: Copy docs from main foam repo
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
.vscode-test/
|
||||
.vscode-test-web/
|
||||
*.tsbuildinfo
|
||||
*.vsix
|
||||
*.log
|
||||
|
||||
23
.vscode/tasks.json
vendored
23
.vscode/tasks.json
vendored
@@ -7,7 +7,28 @@
|
||||
"label": "watch: foam-vscode",
|
||||
"type": "npm",
|
||||
"script": "watch",
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"problemMatcher": {
|
||||
"owner": "typescript",
|
||||
"fileLocation": ["relative", "${workspaceFolder}"],
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.*?)\\((\\d+),(\\d+)\\):\\s+(.*)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3,
|
||||
"message": 4
|
||||
}
|
||||
],
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": {
|
||||
"regexp": ".*"
|
||||
},
|
||||
"endsPattern": {
|
||||
"regexp": ".*"
|
||||
}
|
||||
}
|
||||
},
|
||||
"isBackground": true,
|
||||
"presentation": {
|
||||
"reveal": "always"
|
||||
|
||||
10
docs/.vscode/extensions.json
vendored
10
docs/.vscode/extensions.json
vendored
@@ -5,17 +5,11 @@
|
||||
// Foam's own extension
|
||||
"foam.foam-vscode",
|
||||
|
||||
// Prettier for auto formatting code
|
||||
"esbenp.prettier-vscode",
|
||||
|
||||
// GitLens for seeing version history inline
|
||||
"eamodio.gitlens",
|
||||
|
||||
// Tons of markdown goodies (lists, tables of content, so much more)
|
||||
"yzhang.markdown-all-in-one",
|
||||
|
||||
// Graph visualizer
|
||||
"tchayen.markdown-links",
|
||||
// Prettier for auto formatting code
|
||||
"esbenp.prettier-vscode",
|
||||
|
||||
// Understated grayscale theme (light and dark variants)
|
||||
"philipbe.theme-gray-matter"
|
||||
|
||||
4
docs/.vscode/foam.json
vendored
4
docs/.vscode/foam.json
vendored
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"purpose": "this file exists to tell the foam-vscode plugin that it's currently in a foam workspace",
|
||||
"future": "we may use this for custom configuration"
|
||||
}
|
||||
2
docs/.vscode/keybindings.json
vendored
2
docs/.vscode/keybindings.json
vendored
@@ -3,6 +3,6 @@
|
||||
[
|
||||
{
|
||||
"key": "cmd+shift+n",
|
||||
"command": "vscodeMarkdownNotes.newNote"
|
||||
"command": "foam-vscode.create-note"
|
||||
}
|
||||
]
|
||||
|
||||
13
docs/.vscode/settings.json
vendored
13
docs/.vscode/settings.json
vendored
@@ -5,7 +5,6 @@
|
||||
"editor.overviewRulerBorder": false,
|
||||
"editor.lineHeight": 24,
|
||||
"foam.edit.linkReferenceDefinitions": "withExtensions",
|
||||
"vscodeMarkdownNotes.noteCompletionConvention": "noExtension",
|
||||
"[markdown]": {
|
||||
"editor.quickSuggestions": {
|
||||
"other": true,
|
||||
@@ -13,21 +12,11 @@
|
||||
"strings": false
|
||||
}
|
||||
},
|
||||
"cSpell.language": "en-GB",
|
||||
"git.enableSmartCommit": true,
|
||||
"git.postCommitCommand": "sync",
|
||||
"spellright.language": [
|
||||
"en"
|
||||
],
|
||||
"spellright.documentTypes": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
],
|
||||
"files.exclude": {
|
||||
"_site/**": true
|
||||
},
|
||||
"files.insertFinalNewline": true,
|
||||
"markdown.styles": [
|
||||
".vscode/custom-tag-style.css"
|
||||
]
|
||||
"markdown.styles": [".vscode/custom-tag-style.css"]
|
||||
}
|
||||
|
||||
1
docs/.vscode/spellright.dict
vendored
1
docs/.vscode/spellright.dict
vendored
@@ -1 +0,0 @@
|
||||
Backlinking
|
||||
BIN
docs/assets/images/note-embed-type-demo.gif
Normal file
BIN
docs/assets/images/note-embed-type-demo.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 568 KiB |
BIN
docs/assets/images/pdf_output.png
Normal file
BIN
docs/assets/images/pdf_output.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 102 KiB |
@@ -1,3 +1,8 @@
|
||||
---
|
||||
redirect_from:
|
||||
- /code-of-conduct
|
||||
---
|
||||
|
||||
# Code of Conduct
|
||||
|
||||
We follow the [Contributor Covenant](https://www.contributor-covenant.org/) code of conduct.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
tags: todo, good-first-task
|
||||
---
|
||||
|
||||
# Contribution Guide
|
||||
|
||||
Foam is open to contributions of any kind, including but not limited to code, documentation, ideas, and feedback.
|
||||
@@ -44,19 +45,19 @@ You should now be ready to start working!
|
||||
|
||||
Foam code and documentation live in the monorepo at [foambubble/foam](https://github.com/foambubble/foam/).
|
||||
|
||||
- [/docs](https://github.com/foambubble/foam/tree/master/docs): documentation and [[recipes]].
|
||||
- [/docs](https://github.com/foambubble/foam/tree/main/docs): documentation and [[recipes]].
|
||||
|
||||
Exceptions to the monorepo are:
|
||||
|
||||
- The starter template at [foambubble/foam-template](https://github.com/foambubble/)
|
||||
- All other [[recommended-extensions]] live in their respective GitHub repos
|
||||
|
||||
This project uses [Yarn workspaces](https://classic.yarnpkg.com/en/docs/workspaces/).
|
||||
This project uses [Yarn workspaces](https://classic.yarnpkg.com/en/docs/workspaces/).
|
||||
|
||||
Originally Foam had:
|
||||
|
||||
- [/packages/foam-core](https://github.com/foambubble/foam/tree/ee7a8919761f168d3931079adf21c5ad4d63db59/packages/foam-core) - Powers the core functionality in Foam across all platforms.
|
||||
- [/packages/foam-vscode](https://github.com/foambubble/foam/tree/master/packages/foam-vscode) - The core VS Code plugin.
|
||||
- [/packages/foam-vscode](https://github.com/foambubble/foam/tree/main/packages/foam-vscode) - The core VS Code plugin.
|
||||
|
||||
To improve DX we have moved the `foam-core` module into `packages/foam-vscode/src/core`, but from a development point of view it's useful to think of the `foam-vscode/src/core` "submodule" as something that might be extracted in the future.
|
||||
|
||||
@@ -81,7 +82,7 @@ Tests live alongside the code in `src`.
|
||||
|
||||
This guide assumes you read the previous instructions and you're set up to work on Foam.
|
||||
|
||||
1. Now we'll use the launch configuration defined at [`.vscode/launch.json`](https://github.com/foambubble/foam/blob/master/.vscode/launch.json) to start a new extension host of VS Code. Open the "Run and Debug" Activity (the icon with the bug on the far left) and select "Run VSCode Extension" in the pop-up menu. Now hit F5 or click the green arrow "play" button to fire up a new copy of VS Code with your extension installed.
|
||||
1. Now we'll use the launch configuration defined at [`.vscode/launch.json`](https://github.com/foambubble/foam/blob/main/.vscode/launch.json) to start a new extension host of VS Code. Open the "Run and Debug" Activity (the icon with the bug on the far left) and select "Run VSCode Extension" in the pop-up menu. Now hit F5 or click the green arrow "play" button to fire up a new copy of VS Code with your extension installed.
|
||||
|
||||
2. In the new extension host of VS Code that launched, open a Foam workspace (e.g. your personal one, or a test-specific one created from [foam-template](https://github.com/foambubble/foam-template)). This is strictly not necessary, but the extension won't auto-run unless it's in a workspace with a `.vscode/foam.json` file.
|
||||
|
||||
@@ -93,7 +94,7 @@ After you have made your changes to your copy of the project, it is time to try
|
||||
|
||||
1. Return to the project's [home repository page](https://github.com/foambubble/foam).
|
||||
2. Github should show you an button called "Compare & pull request" linking your forked repository to the community repository.
|
||||
3. Click that button and confirm that your repository is going to be merged into the community repository. See [this guide](https://sqldbawithabeard.com/2019/11/29/how-to-fork-a-github-repository-and-contribute-to-an-open-source-project/) for more specifics.
|
||||
3. Click that button and confirm that your repository is going to be merged into the community repository. See [this guide](https://blog.robsewell.com/blog/how-to-fork-a-github-repository-and-contribute-to-an-open-source-project/) for more specifics.
|
||||
4. Add as many relevant details to the PR message to make it clear to the project maintainers and other members of the community what you have accomplished with your new changes. Link to any issues the changes are related to.
|
||||
5. Your PR will then need to be reviewed and accepted by the other members of the community. Any discussion about the changes will occur in your PR thread.
|
||||
6. Once reviewed and accept you can complete the merge request!
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
# Foam Core 2020-07-11
|
||||
|
||||
Present: @jevakallio, @riccardoferretti
|
||||
|
||||
### Tests
|
||||
|
||||
- How do we know this approach works?
|
||||
- Supports renaming
|
||||
- Supports searching with (attribute-x)
|
||||
- Find dead links
|
||||
|
||||
### Getting started
|
||||
|
||||
- Land work to master
|
||||
- Create a foam-core package
|
||||
|
||||
-
|
||||
|
||||
### Open questions
|
||||
|
||||
- How should writing to files work
|
||||
- What if affected notes have unsaved changes
|
||||
|
||||
### Graph methods
|
||||
|
||||
- get all
|
||||
- search by
|
||||
- tag
|
||||
- free text
|
||||
- [[todo]]: how do vs code search editors work? are they pluggable? what do they need?
|
||||
- find dead links
|
||||
- for linters
|
||||
- serialize/toJSON (for visualizers)
|
||||
- subscribe to changes
|
||||
- find if a link exists (and which link) in a given row / column position + return it's start and end position - this would probably be needed e.g. to CTRL-hovering to work properly
|
||||
|
||||
### Node methods
|
||||
|
||||
- rename node and all links to that node
|
||||
- get links
|
||||
- forward links (for link lists)
|
||||
- backlinks (with surrounding context)
|
||||
|
||||
### Node definition
|
||||
|
||||
What do we need the node (and edge metadata) to contain:
|
||||
|
||||
- `id`: tbd
|
||||
- should be unique, needs some kind of unique gen function
|
||||
- should be reconstructable even if links are not updated every time
|
||||
- what happens during rename? is reparenting the graph going to be hard?
|
||||
- do id's need to be persistent, or can we create them per in-memory session, keep them stable despite renames, and then next session generate a new id?
|
||||
- Ideally should be a path to file, so it's easy to look up from the graph by id for renaming
|
||||
- `type`: Note | Image | etc
|
||||
- `title`: can be read from markdown title or frontmatter metadata
|
||||
- `path`: full path to file, relative to workspace (graph) root
|
||||
- `links`:
|
||||
- `id`: File to link to
|
||||
- `text`: The link label
|
||||
- `type` markdown | mediawiki | image | http
|
||||
- `section`: : Anchor link to a heading in target note, if we want to add support for linking to sections
|
||||
- `block` (ref)
|
||||
- Positional data from AST?
|
||||
- `tags`
|
||||
|
||||
### Markdown layer
|
||||
|
||||
- `source`: raw markdown (rename?)
|
||||
- `ast`: raw markdown ast
|
||||
- `checksum`: if we do caching
|
||||
|
||||
### Link text
|
||||
|
||||
// some-file.md
|
||||
// # Some File
|
||||
|
||||
Write -> Store on disk
|
||||
[[Some File]] -> [Some File](some-file.md)
|
||||
|
||||
Editing
|
||||
[Some File](some-file.md)
|
||||
|
||||
On disk (could be solved by migration)
|
||||
[[some-file]]
|
||||
[[Some File]]
|
||||
|
||||
- docs/index.md -> Index
|
||||
- notes/index.md -> Index
|
||||
|
||||
[[Index]]
|
||||
[[Index | notes/index.md]]
|
||||
|
||||
[Index] docs/index.md
|
||||
[Index | notes/index.md]: notes/index.md
|
||||
|
||||
[[Some File | path/to/some-file.md]]
|
||||
|
||||
Do we apply any constraints:
|
||||
|
||||
- `[[file-name-without-extension]]`
|
||||
- `[[file-name-with-extension.md]]`
|
||||
- `[[Title Cased File Name]]`
|
||||
|
||||
Not supported by Markdown Notes:
|
||||
|
||||
- `[[path/to/file-name.md]]` - Just use markdown links
|
||||
- `[[Target Note Title]]`
|
||||
|
||||
Issues:
|
||||
|
||||
- Name clashes in directories
|
||||
- Name clashes between extensions
|
||||
- Renaming
|
||||
- Change filename/title needs to reflect everywhere
|
||||
- Orphaning
|
||||
|
||||
- If we can't rely on in-memory process to rename things correctly while changes happen (e.g. file is renamed, moved, deleted, or titled) <ref id="1" />
|
||||
|
||||
Solving this issue is necessarily heuristic. We could try to write smart solutions, plus a linter for orphans
|
||||
|
||||
How others solve this:
|
||||
|
||||
- Unique ids -- could support optionally as part of file name or front matter metadata. Should not be required.
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[todo]: ../todo.md "Todo"
|
||||
[Index]: ../../index.md "Foam"
|
||||
[//end]: # "Autogenerated link references"
|
||||
@@ -14,7 +14,7 @@ Currently it is not possible within Foam to include other notes into a note. Nex
|
||||
|
||||
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.
|
||||
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 possibilities 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
|
||||
|
||||
@@ -29,7 +29,7 @@ The minimal functionality is the ability to fully include a note. Markup used in
|
||||
|
||||
### 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:
|
||||
It could be interesting to only include a section of a note instead of the entire note. In order to do so the user should be able to use the following syntax:
|
||||
|
||||
`![[wikilink#section-b]]`
|
||||
|
||||
@@ -37,11 +37,11 @@ As a result it will include the section title + section content until the next s
|
||||
|
||||
### 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:
|
||||
As a user I could be interested in collecting the value of any given property 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.
|
||||
The property value should be looked up 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:
|
||||
|
||||
|
||||
@@ -66,8 +66,9 @@ The potential solution:
|
||||
- For edit-time
|
||||
- Make edit-time link reference definition generation optional via user settings. They should be on by default, and generating valid markdown links with a relative path to a `.md` file.
|
||||
- Make format of the link reference definition configurable (whether to include '.md' or not)
|
||||
- Out of recommended extensions, currently only "markdown links" doesn't support them (?). However even its [code](https://github.com/tchayen/markdown-links/blob/master/src/parsing.ts#L25) seems to include wikilink parser, so it might just be a bug?
|
||||
- Out of recommended extensions, currently only "markdown links" doesn't support them (?). However even its [code](https://github.com/tchayen/markdown-links/blob/main/src/parsing.ts#L25) seems to include wikilink parser, so it might just be a bug?
|
||||
- For build-time
|
||||
|
||||
- To satisfy mutually incompatible constraints between GitHub UI, VSCode UI, and GitHub Pages, we should add a pre-processing/build step for pushing to GitHub Pages.
|
||||
- This would be a GitHub action (or a local script, ran via foam-cli) that outputs publish-friendly markdown format for static site generators and other publishing tools
|
||||
- This build step should be pluggable, so that other transformations could be ran during it
|
||||
@@ -117,7 +118,7 @@ The potential solution:
|
||||
enum LinkReferenceDefinitions {
|
||||
Off, // link reference definitions are not generated
|
||||
WithExtensions, // link reference definitions contain .md (or similar) file extensions
|
||||
WithoutExtensions // link reference definitions do not contain file extenions
|
||||
WithoutExtensions // link reference definitions do not contain file extensions
|
||||
}
|
||||
|
||||
```
|
||||
@@ -125,6 +126,7 @@ The potential solution:
|
||||
- With Foam repo, just use edit-time link reference definitions with '.md' extension - this makes the links work in the GitHub UI
|
||||
- Have publish target defined for GitHub pages, that doesn't use '.md' extension, but still has the link reference definitions. Generate the output into gh-pages branch (or separate repo) with automation.
|
||||
- This naturally requires first removing the existing link reference definitions during the build
|
||||
|
||||
- Other
|
||||
- To clean up the search results, remove link reference definition section guards (assuming that these are not defined by the markdown spec). Use unifiedjs parse trees to identify if there's missing (or surplus) definitions (check if they are identified properly by the library), and just add the needed definitions to the bottom of the file (without guards) AND remove them if they are not needed (anywhere from the file).
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ If you want to pick up work in this category, you should have a plan on how long
|
||||
|
||||
Everything else, categorised into themes. Just because something is on this list doesn't mean it'll get done. If you're interested in working on items in this category, check the [[contribution-guide]] for how to get started.
|
||||
|
||||
If a roadmap item is a stub, **consider** opening a [GitHub issue](https://github.com/foambubble/foam/issues) to start a conversation to avoid situations where the implementation does not fit long term vision and roadmap. _Note that this is optional. The only centralised governance in Foam is to decide what ends up in the official [template](https://github.com/foambubble/foam-template), [documentation](https://github.com/foambubble/foam) and [extension](https://github.com/foambubble/foam/tree/master/packages/foam-vscode). You are free to build whatever you want for yourself, and we'd love if you shared it with us, but you are by no means obligated to do so!_
|
||||
If a roadmap item is a stub, **consider** opening a [GitHub issue](https://github.com/foambubble/foam/issues) to start a conversation to avoid situations where the implementation does not fit long term vision and roadmap. _Note that this is optional. The only centralised governance in Foam is to decide what ends up in the official [template](https://github.com/foambubble/foam-template), [documentation](https://github.com/foambubble/foam) and [extension](https://github.com/foambubble/foam/tree/main/packages/foam-vscode). You are free to build whatever you want for yourself, and we'd love if you shared it with us, but you are by no means obligated to do so!_
|
||||
|
||||
**When creating GitHub issues to discuss roadmap items, link them here.**
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
# Releasing Foam
|
||||
|
||||
1. Get to the latest code
|
||||
- `git checkout master && git fetch && git rebase`
|
||||
- `git checkout main && git fetch && git rebase`
|
||||
2. Sanity checks
|
||||
- `yarn reset`
|
||||
- `yarn test`
|
||||
3. Update change log
|
||||
3. Update change log
|
||||
- `./packages/foam-vscode/CHANGELOG.md`
|
||||
- `git add *`
|
||||
- `git commit -m"Preparation for next release"`
|
||||
4. Update version
|
||||
- `$ yarn version-extension <version>` (where `version` is `patch/minor/major`)
|
||||
- `$ yarn version-extension <version>` (where `version` is `patch/minor/major`)
|
||||
5. Package extension
|
||||
- `$ yarn package-extension`
|
||||
6. Publish extension
|
||||
@@ -20,6 +20,6 @@
|
||||
- select "tags" in top left
|
||||
- select the tag that was just released, click "edit" and copy release information from changelog
|
||||
- publish (no need to attach artifacts)
|
||||
8. Annouce on Discord
|
||||
8. Announce on Discord
|
||||
|
||||
Steps 1 to 6 should really be replaced by a GitHub action...
|
||||
Steps 1 to 6 should really be replaced by a GitHub action...
|
||||
|
||||
@@ -10,7 +10,6 @@ Uncategorised thoughts, to be added
|
||||
- Investigate other similar extensions:
|
||||
- [Unotes](https://marketplace.visualstudio.com/items?itemName=ryanmcalister.Unotes)
|
||||
- [vscode-memo](https://github.com/svsool/vscode-memo)
|
||||
- [gistpad wiki](https://github.com/jevakallio/gistpad/tree/master/src/repos/wiki)
|
||||
- Open in Foam
|
||||
- When you want to open a Foam published website in your own VS Code, we could have a "Open in Foam" link that opens the link in VS Code via a url binding (if possible), downloads the github repo locally, and opens it as a Foam workspace.
|
||||
- Every Foam could have a different theme even in the editor, so you'll see it like they see it
|
||||
|
||||
@@ -60,17 +60,17 @@ These instructions assume you have a GitHub account, and you have Visual Studio
|
||||
|
||||
<a class="github-button" href="https://github.com/foambubble/foam-template/generate" data-icon="octicon-repo-template" data-size="large" aria-label="Use this template foambubble/foam-template on GitHub">Use this template</a>
|
||||
|
||||
*If you want to keep your thoughts to yourself, remember to set the repository private, or if you don't want to use GitHub to host your workspace at all, choose [**Download as ZIP**](https://github.com/foambubble/foam-template/archive/master.zip) instead of **Use this template**.*
|
||||
_If you want to keep your thoughts to yourself, remember to set the repository private, or if you don't want to use GitHub to host your workspace at all, choose [**Download as ZIP**](https://github.com/foambubble/foam-template/archive/main.zip) instead of **Use this template**._
|
||||
|
||||
2. [Clone the repository locally](https://help.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository) and open it in VS Code.
|
||||
|
||||
*Open the repository as a folder using the `File > Open...` menu item. In VS Code, "open workspace" refers to [multi-root workspaces](https://code.visualstudio.com/docs/editor/multi-root-workspaces).*
|
||||
_Open the repository as a folder using the `File > Open...` menu item. In VS Code, "open workspace" refers to [multi-root workspaces](https://code.visualstudio.com/docs/editor/multi-root-workspaces)._
|
||||
|
||||
3. When prompted to install recommended extensions, click **Install all** (or **Show Recommendations** if you want to review and install them one by one)
|
||||
|
||||
After setting up the repository, open `.vscode/settings.json` and edit, add or remove any settings you'd like for your Foam workspace.
|
||||
|
||||
* *If using a [multi-root workspace](https://code.visualstudio.com/docs/editor/multi-root-workspaces) as noted above, make sure that your **Foam** directory is first in the list. There are some settings that will need to be migrated from `.vscode/settings.json` to your `.code-workspace` file.*
|
||||
- _If using a [multi-root workspace](https://code.visualstudio.com/docs/editor/multi-root-workspaces) as noted above, make sure that your **Foam** directory is first in the list. There are some settings that will need to be migrated from `.vscode/settings.json` to your `.code-workspace` file._
|
||||
|
||||
To learn more about how to use **Foam**, read the [[recipes]].
|
||||
|
||||
@@ -247,6 +247,25 @@ If that sounds like something you're interested in, I'd love to have you along o
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://jonathanpberger.com/"><img src="https://avatars.githubusercontent.com/u/41085?v=4?s=60" width="60px;" alt="jonathan berger"/><br /><sub><b>jonathan berger</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jonathanpberger" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/badsketch"><img src="https://avatars.githubusercontent.com/u/8953212?v=4?s=60" width="60px;" alt="Daniel Wang"/><br /><sub><b>Daniel Wang</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=badsketch" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://yongliangliu.com"><img src="https://avatars.githubusercontent.com/u/41845017?v=4?s=60" width="60px;" alt="Liu YongLiang"/><br /><sub><b>Liu YongLiang</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=tlylt" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://scottakerman.com"><img src="https://avatars.githubusercontent.com/u/15224439?v=4?s=60" width="60px;" alt="Scott Akerman"/><br /><sub><b>Scott Akerman</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Skakerman" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.jim-graham.net/"><img src="https://avatars.githubusercontent.com/u/430293?v=4?s=60" width="60px;" alt="Jim Graham"/><br /><sub><b>Jim Graham</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jimgraham" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://t.me/littlepoint"><img src="https://avatars.githubusercontent.com/u/7611700?v=4?s=60" width="60px;" alt="Zhizhen He"/><br /><sub><b>Zhizhen He</b></sub></a><br /><a href="#tool-hezhizhen" title="Tools">🔧</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://amnesiak.org/me"><img src="https://avatars.githubusercontent.com/u/952059?v=4?s=60" width="60px;" alt="Tony Cheneau"/><br /><sub><b>Tony Cheneau</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=tcheneau" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/nicholas-l"><img src="https://avatars.githubusercontent.com/u/12977174?v=4?s=60" width="60px;" alt="Nicholas Latham"/><br /><sub><b>Nicholas Latham</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=nicholas-l" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://thara.dev"><img src="https://avatars.githubusercontent.com/u/1532891?v=4?s=60" width="60px;" alt="Tomochika Hara"/><br /><sub><b>Tomochika Hara</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=thara" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dcarosone"><img src="https://avatars.githubusercontent.com/u/11495017?v=4?s=60" width="60px;" alt="Daniel Carosone"/><br /><sub><b>Daniel Carosone</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dcarosone" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MABruni"><img src="https://avatars.githubusercontent.com/u/100445384?v=4?s=60" width="60px;" alt="Miguel Angel Bruni Montero"/><br /><sub><b>Miguel Angel Bruni Montero</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MABruni" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Walshkev"><img src="https://avatars.githubusercontent.com/u/77123083?v=4?s=60" width="60px;" alt="Kevin Walsh "/><br /><sub><b>Kevin Walsh </b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Walshkev" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://hereistheusername.github.io/"><img src="https://avatars.githubusercontent.com/u/33437051?v=4?s=60" width="60px;" alt="Xinglan Liu"/><br /><sub><b>Xinglan Liu</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=hereistheusername" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.hegghammer.com"><img src="https://avatars.githubusercontent.com/u/64712218?v=4?s=60" width="60px;" alt="Thomas Hegghammer"/><br /><sub><b>Thomas Hegghammer</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Hegghammer" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PiotrAleksander"><img src="https://avatars.githubusercontent.com/u/6314591?v=4?s=60" width="60px;" alt="Piotr Mrzygłosz"/><br /><sub><b>Piotr Mrzygłosz</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=PiotrAleksander" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://schaver.com/"><img src="https://avatars.githubusercontent.com/u/7584?v=4?s=60" width="60px;" alt="Mark Schaver"/><br /><sub><b>Mark Schaver</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=markschaver" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/n8layman"><img src="https://avatars.githubusercontent.com/u/25353944?v=4?s=60" width="60px;" alt="Nathan Layman"/><br /><sub><b>Nathan Layman</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=n8layman" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/emmanuel-ferdman"><img src="https://avatars.githubusercontent.com/u/35470921?v=4?s=60" width="60px;" alt="Emmanuel Ferdman"/><br /><sub><b>Emmanuel Ferdman</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=emmanuel-ferdman" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# Backlinking
|
||||
|
||||
When using [[wikilinks]], you can find all notes that link to a specific note in the **Backlinks Explorer**
|
||||
When using [[wikilinks]], you can find all notes that link to a specific note in the **Connections Explorer**
|
||||
|
||||
- Run `Cmd` + `Shift` + `P` (`Ctrl` + `Shift` + `P` for Windows), type "backlinks" and run the **Explorer: Focus on Backlinks** view.
|
||||
- Run `Cmd` + `Shift` + `P` (`Ctrl` + `Shift` + `P` for Windows), type "connections" and run the **Explorer: Focus on Connections** view.
|
||||
- Keep this pane always visible to discover relationships between your thoughts
|
||||
- You can drag the backlinks pane to a different section in VS Code if you prefer. See: [[make-backlinks-more-prominent]]
|
||||
- You can drag the connections panel to a different section in VS Code if you prefer. See: [[make-backlinks-more-prominent]]
|
||||
- You can filter the connections to see just backlinks, forward links, or all connections
|
||||
- Finding backlinks in published Foam workspaces via [[materialized-backlinks]] is on the [[roadmap]] but not yet implemented.
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
|
||||
29
docs/user/features/built-in-note-embedding-types.md
Normal file
29
docs/user/features/built-in-note-embedding-types.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Built-In Note Embedding Types
|
||||
|
||||
When embedding a note, there are a few ways to modify the scope of the content as well as its display style. The following are Foam keywords that are used to describe note embedding.
|
||||
|
||||
Note, this only applies to note embedding, not embedding of attachments or images.
|
||||
|
||||

|
||||
|
||||
## Scope
|
||||
|
||||
- `full` - the entire note in the case of `![[note]]` or the entire section in the case of `![[note#section1]]`
|
||||
- `content` - everything excluding the title of the section. So the entire note minus the title for `![[note]]`, or the entire section minus the section header for `![[note#section1]]`
|
||||
|
||||
## Style
|
||||
|
||||
- `card` - outlines the embedded note with a border
|
||||
- `inline` - adds the note continuously as if the text were part of the calling note
|
||||
|
||||
## Default Setting
|
||||
|
||||
Foam expresses note display type as `<scope>-<style>`.
|
||||
|
||||
By default, Foam configures note embedding to be `full-card`. That is, whenever the standard embedding syntax is used, `![[note]]`, the note will have `full` scope and `card` style display. This setting is stored under `foam.preview.embedNoteStyle` and can be modified.
|
||||
|
||||
## Explicit Modifiers
|
||||
|
||||
Prepend the wikilink with one of the scope or style keywords, or a combination of the two to explicitly modify a note embedding if you would like to override the default setting.
|
||||
|
||||
For example, given your `foam.embedNoteStyle` is set to `content-card`, embedding a note with standard syntax `![[note-a]]` would show a bordered note without its title. Say, for a specific `note-b` you would like to display the title. You can simply use one of the above keywords to override your default setting like so: `full![[note-b]]`. In this case, `full` overrides the default `content` scope and because a style is not specified, it falls back to the default style setting, `card`. If you would like it to be inline, override that as well: `full-inline![[note-b]]`.
|
||||
@@ -2,23 +2,26 @@
|
||||
|
||||
Foam has various commands that you can explore by calling the command palette and typing "Foam".
|
||||
|
||||
In particular, some commands can be very customizible and can help with custom workflows and use cases.
|
||||
In particular, some commands can be very customizable and can help with custom workflows and use cases.
|
||||
|
||||
## foam-vscode.create-note command
|
||||
|
||||
This command creates a note.
|
||||
Although it works fine on its own, it can be customized to achieve various use cases.
|
||||
Here are the settings available for the command:
|
||||
- notePath: The path of the note to create. If relative it will be resolved against the workspace root.
|
||||
- templatePath: The path of the template to use. If relative it will be resolved against the workspace root.
|
||||
- text: The text to use for the note. If also a template is provided, the template has precedence
|
||||
- variables: Variables to use in the text or template (e.g. `FOAM_TITLE`)
|
||||
- date: The date used to resolve the FOAM_DATE_* variables. in `YYYY-MM-DD` format
|
||||
- onFileExists?: 'overwrite' | 'open' | 'ask' | 'cancel': What to do in case the target file already exists
|
||||
|
||||
- `notePath`: The path of the note to create. If relative it will be resolved against the workspace root.
|
||||
- `templatePath`: The path of the template to use. If relative it will be resolved against the workspace root.
|
||||
- `title`: The title of the note (that is, the `FOAM_TITLE` variable)
|
||||
- `text`: The text to use for the note. If also a template is provided, the template has precedence
|
||||
- `variables`: Variables to use in the text or template
|
||||
- `date`: The date used to resolve the FOAM*DATE*\* variables. in `YYYY-MM-DD` format
|
||||
- `onFileExists?: 'overwrite' | 'open' | 'ask' | 'cancel'`: What to do in case the target file already exists
|
||||
|
||||
To customize a command and associate a key binding to it, open the key binding settings and add the appropriate configuration, here are some examples:
|
||||
|
||||
- Create a note called `test note.md` with some text. If the note already exists, ask for a new name
|
||||
|
||||
```
|
||||
{
|
||||
"key": "alt+f",
|
||||
@@ -32,6 +35,7 @@ To customize a command and associate a key binding to it, open the key binding s
|
||||
```
|
||||
|
||||
- Create a note following the `weekly-note.md` template. If the note already exists, open it
|
||||
|
||||
```
|
||||
{
|
||||
"key": "alt+g",
|
||||
@@ -43,3 +47,27 @@ To customize a command and associate a key binding to it, open the key binding s
|
||||
}
|
||||
```
|
||||
|
||||
## foam-vscode.open-resource command
|
||||
|
||||
This command opens a resource.
|
||||
|
||||
Normally it receives a `URI`, which identifies the resource to open.
|
||||
|
||||
It is also possible to pass in a filter, which will be run against the workspace resources to find one or more matches.
|
||||
|
||||
- If there is one match, it will be opened
|
||||
- If there is more than one match, a quick pick will show up allowing the user to select the desired resource
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
{
|
||||
"key": "alt+f",
|
||||
"command": "foam-vscode.open-resource",
|
||||
"args": {
|
||||
"filter": {
|
||||
"title": "Weekly Note*"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Daily Notes
|
||||
|
||||
Daily notes allow you to quickly create and access a new notes file for each day. This is a surpisingly effective and increasingly common strategy to organize notes and manage events.
|
||||
Daily notes allow you to quickly create and access a new notes file for each day. This is a surprisingly effective and increasingly common strategy to organize notes and manage events.
|
||||
|
||||
View today's note file by running the `Foam: Open Daily Note` command, by using the shortcut `alt+d` (note: shortcuts can be [overridden](https://code.visualstudio.com/docs/getstarted/keybindings)), or by using [#snippets](#Snippets). The name, location, and title of daily notes files is [#configurable](#Configuration).
|
||||
View today's note file by running the `Foam: Open Daily Note` command, by using the shortcut `alt+d` (note: shortcuts can be [overridden](https://code.visualstudio.com/docs/getstarted/keybindings)), or by using [#snippets](#Snippets). The name, location, and title of daily notes files are [#configurable](#Configuration).
|
||||
|
||||
## Roam-style Automatic Daily Notes
|
||||
|
||||
@@ -29,11 +29,11 @@ Create a link to a recent daily note using [snippets](https://code.visualstudio.
|
||||
|
||||
## Configuration
|
||||
|
||||
By default, Daily Notes will be created in a file called `yyyy-mm-dd.md` in the workspace's `journals` folder, with a heading `yyyy-mm-dd`.
|
||||
By default, Daily Notes will be created in a file called `yyyy-mm-dd.md` in the workspace's `journals` folder, with the heading `yyyy-mm-dd`.
|
||||
|
||||
These settings can be overridden in your workspace or global `.vscode/settings.json` file, using the [**dateformat** date masking syntax](https://github.com/felixge/node-dateformat#mask-options):
|
||||
|
||||
It's possible to customize path and heading of your daily notes, by following the [dateformat masking syntax](https://github.com/felixge/node-dateformat#mask-options).
|
||||
It's possible to customize the path and heading of your daily notes, by following the [dateformat masking syntax](https://github.com/felixge/node-dateformat#mask-options).
|
||||
The following properties can be used:
|
||||
|
||||
```json
|
||||
@@ -45,7 +45,7 @@ The following properties can be used:
|
||||
|
||||
The above configuration would create a file `journal/daily-note-2020-07-25.mdx`, with the heading `Journal Entry, Sunday, July 25`.
|
||||
|
||||
> NOTE: It is possible to set the filepath of a daily note according to the date using the special [[note-properties]] configurable for [[Note Templates]]. Specifically see [[note-templates#Example of date-based|Example of date-based filepath]]. Using the template property will override any setting configured through `.vscode/settings.json`.
|
||||
> NOTE: It is possible to set the filepath of a daily note according to the date using the special [[note-properties]] configurable for [[Note Templates]]. Specifically, see [[note-templates#Example of date-based|Example of date-based filepath]]. Using the template property will override any setting configured through `.vscode/settings.json`.
|
||||
|
||||
## Extend Functionality (Weekly, Monthly, Quarterly Notes)
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ It is possible to customize the style of a node based on the `type` property in
|
||||
|
||||
There are a few default node types defined by Foam that are displayed in the graph:
|
||||
|
||||
- `note` defines the color for regular nodes whose documents have not overriden the `type` property.
|
||||
- `note` defines the color for regular nodes whose documents have not overridden the `type` property.
|
||||
- `placeholder` defines the color for links that don't match any existing note. This is a [[placeholder]] because no file with such name exists.
|
||||
- see [[wikilinks]] for more info <!--NOTE: this placeholder link should NOT have an associated file. This is to demonstrate the custom coloring-->
|
||||
- `tag` defines the color for nodes representing #tags, allowing tags to be used as graph nodes similar to backlinks.
|
||||
|
||||
@@ -4,17 +4,20 @@ In some situations it might be useful to include the content of another note in
|
||||
|
||||
## Including a note
|
||||
|
||||
Including a note can be done by adding an `!` before a wikilink defintion. For example `![[wikilink]]`.
|
||||
Including a note can be done by adding an `!` before a wikilink definition. For example `![[wikilink]]`.
|
||||
|
||||
## Custom styling
|
||||
|
||||
Displaying the inclusion of notes allows for some custom styling, see [[custom-markdown-preview-styles]]
|
||||
To modify how an embedded note looks and the scope of its content, see [[built-in-note-embedding-types]]
|
||||
|
||||
For more fine-grained 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]: ../../dev/proposals/inclusion-of-notes.md "Inclusion of notes Proposal "
|
||||
[//end]: # "Autogenerated link references"
|
||||
[//begin]: # 'Autogenerated link references for markdown compatibility'
|
||||
[built-in-note-embedding-types]: built-in-note-embedding-types.md 'Built-In Note Embedding Types'
|
||||
[custom-markdown-preview-styles]: custom-markdown-preview-styles.md 'Custom Markdown Preview Styles'
|
||||
[inclusion-of-notes]: ../../dev/proposals/inclusion-of-notes.md 'Inclusion of notes Proposal '
|
||||
[//end]: # 'Autogenerated link references'
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
# Link Reference Definitions
|
||||
|
||||
When you use `[[wikilinks]]`, the [foam-vscode](https://github.com/foambubble/foam/tree/master/packages/foam-vscode) extension can automatically generate [Markdown Link Reference Definitions](https://spec.commonmark.org/0.29/#link-reference-definitions) at the bottom of the file. This is not needed to navigate your workspace with foam-vscode, but is useful for files to remain compatible with various Markdown tools (e.g. parsers, static site generators, VS code plugins etc), which don't support `[[wikilinks]]`.
|
||||
When you use `[[wikilinks]]`, the [foam-vscode](https://github.com/foambubble/foam/tree/main/packages/foam-vscode) extension can automatically generate [Markdown Link Reference Definitions](https://spec.commonmark.org/0.29/#link-reference-definitions) at the bottom of the file. This is not needed to navigate your workspace with foam-vscode, but is useful for files to remain compatible with various Markdown tools (e.g. parsers, static site generators, VS code plugins etc), which don't support `[[wikilinks]]`.
|
||||
|
||||
## Example
|
||||
|
||||
The following example:
|
||||
|
||||
```md
|
||||
- [[wikilinks]]
|
||||
- [[github-pages]]
|
||||
```
|
||||
```md
|
||||
- [[wikilinks]]
|
||||
- [[github-pages]]
|
||||
```
|
||||
|
||||
...generates the following link reference definitions to the bottom of the file:
|
||||
|
||||
```md
|
||||
[wikilinks]: wikilinks "Wikilinks"
|
||||
[github-pages]: github-pages "GitHub Pages"
|
||||
```
|
||||
```md
|
||||
[wikilinks]: wikilinks 'Wikilinks'
|
||||
[github-pages]: github-pages 'GitHub Pages'
|
||||
```
|
||||
|
||||
You can open the [raw markdown](https://foambubble.github.io/foam/features/link-reference-definitions.md) to see them at the bottom of this file
|
||||
You can open the [raw markdown](https://foambubble.github.io/foam/user/features/link-reference-definitions.md) to see them at the bottom of this file
|
||||
|
||||
## Specification
|
||||
|
||||
@@ -52,15 +53,15 @@ There are three options for excluding files from your Foam project:
|
||||
|
||||
1. `files.exclude` (from VSCode) will prevent the folder from showing in the file explorer.
|
||||
|
||||
> "Configure glob patterns for excluding files and folders. For example, the file explorer decides which files and folders to show or hide based on this setting. Refer to the Search: Exclude setting to define search-specific excludes."
|
||||
> "Configure glob patterns for excluding files and folders. For example, the file explorer decides which files and folders to show or hide based on this setting. Refer to the Search: Exclude setting to define search-specific excludes."
|
||||
|
||||
2. `files.watcherExclude` (from VSCode) prevents VSCode from constantly monitoring files for changes.
|
||||
|
||||
> "Configure paths or glob patterns to exclude from file watching. Paths or basic glob patterns that are relative (for example `build/output` or `*.js`) will be resolved to an absolute path using the currently opened workspace. Complex glob patterns must match on absolute paths (i.e. prefix with `**/` or the full path and suffix with `/**` to match files within a path) to match properly (for example `**/build/output/**` or `/Users/name/workspaces/project/build/output/**`). When you experience the file watcher process consuming a lot of CPU, make sure to exclude large folders that are of less interest (such as build output folders)."
|
||||
> "Configure paths or glob patterns to exclude from file watching. Paths or basic glob patterns that are relative (for example `build/output` or `*.js`) will be resolved to an absolute path using the currently opened workspace. Complex glob patterns must match on absolute paths (i.e. prefix with `**/` or the full path and suffix with `/**` to match files within a path) to match properly (for example `**/build/output/**` or `/Users/name/workspaces/project/build/output/**`). When you experience the file watcher process consuming a lot of CPU, make sure to exclude large folders that are of less interest (such as build output folders)."
|
||||
|
||||
3. `foam.files.ignore` (from Foam) ignores files from being added to the Foam graph.
|
||||
|
||||
> "Specifies the list of globs that will be ignored by Foam (e.g. they will not be considered when creating the graph). To ignore the all the content of a given folder, use `<folderName>/**/*`" (requires reloading VSCode to take effect).
|
||||
> "Specifies the list of globs that will be ignored by Foam (e.g. they will not be considered when creating the graph). To ignore the all the content of a given folder, use `<folderName>/**/*`" (requires reloading VSCode to take effect).
|
||||
|
||||
For instance, if you're using a local instance of [Jekyll](https://jekyllrb.com/), you may find that it writes copies of each `.md` file into a `_site` directory, which may lead to Foam generating references to them instead of the original source notes.
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ keywords: hello world, bonjour
|
||||
---
|
||||
```
|
||||
|
||||
This sets the `type` of this document to `feature` and sets **three** keywords for the document: `hello`, `world`, and `bonjour`. The YAML parser will treat both spaces and commas as the seperators for these YAML properties. If you want to use multi-word values for these properties, you will need to combine the words with dashes or underscores (i.e. instead of `hello world`, use `hello_world` or `hello-world`).
|
||||
This sets the `type` of this document to `feature` and sets **three** keywords for the document: `hello`, `world`, and `bonjour`. The YAML parser will treat both spaces and commas as the separators for these YAML properties. If you want to use multi-word values for these properties, you will need to combine the words with dashes or underscores (i.e. instead of `hello world`, use `hello_world` or `hello-world`).
|
||||
|
||||
> You can set as many custom properties for a document as you like, but there are a few [special properties](#special-properties) defined by Foam.
|
||||
|
||||
@@ -27,11 +27,12 @@ This sets the `type` of this document to `feature` and sets **three** keywords f
|
||||
|
||||
Some properties have special meaning for Foam:
|
||||
|
||||
| Name | Description |
|
||||
| -------------------- | ------------------- |
|
||||
| `title` | will assign the name to the note that you will see in the graph, regardless of the filename or the first heading (also see how to [[write-notes-in-foam]]) |
|
||||
| `type` | can be used to style notes differently in the graph (also see [[graph-visualization]]). The default type for a document is `note` unless otherwise specified with this property. |
|
||||
| `tags` | can be used to add tags to a note (see [[tags]]) |
|
||||
| Name | Description |
|
||||
| ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `title` | will assign the name to the note that you will see in the graph, regardless of the filename or the first heading (also see how to [[write-notes-in-foam]]) |
|
||||
| `type` | can be used to style notes differently in the graph (also see [[graph-visualization]]). The default type for a document is `note` unless otherwise specified with this property. |
|
||||
| `tags` | can be used to add tags to a note (see [[tags]]) |
|
||||
| `alias` | can be used to add aliases to the note. an alias will show up in the link autocompletion |
|
||||
|
||||
For example:
|
||||
|
||||
@@ -40,7 +41,7 @@ For example:
|
||||
title: "Note Title"
|
||||
type: "daily-note"
|
||||
tags: daily, funny, planning
|
||||
|
||||
alias: alias1, alias2
|
||||
---
|
||||
```
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ Note templates are `.md` files located in the special `.foam/templates` director
|
||||
|
||||
Create a template:
|
||||
|
||||
* Run the `Foam: Create New Template` command from the command palette
|
||||
* OR manually create a regular `.md` file in the `.foam/templates` directory
|
||||
- Run the `Foam: Create New Template` command from the command palette
|
||||
- OR manually create a regular `.md` file in the `.foam/templates` directory
|
||||
|
||||

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

|
||||
|
||||
@@ -29,7 +29,7 @@ _Theme: Ayu Light_
|
||||
### Default template
|
||||
|
||||
The `.foam/templates/new-note.md` template is special in that it is the template that will be used by the `Foam: Create New Note` command.
|
||||
Customize this template to contain content that you want included every time you create a note. To begin it is *recommended* to define the YAML Front-Matter of the template similar to the following:
|
||||
Customize this template to contain content that you want included every time you create a note. To begin it is _recommended_ to define the YAML Front-Matter of the template similar to the following:
|
||||
|
||||
```markdown
|
||||
---
|
||||
@@ -40,7 +40,7 @@ type: basic-note
|
||||
### Default daily note template
|
||||
|
||||
The `.foam/templates/daily-note.md` template is special in that it is the template that will be used when creating daily notes (e.g. by using `Foam: Open Daily Note`).
|
||||
Customize this template to contain content that you want included every time you create a daily note. To begin it is *recommended* to define the YAML Front-Matter of the template similar to the following:
|
||||
Customize this template to contain content that you want included every time you create a daily note. To begin it is _recommended_ to define the YAML Front-Matter of the template similar to the following:
|
||||
|
||||
```markdown
|
||||
---
|
||||
@@ -54,11 +54,12 @@ Templates can use all the variables available in [VS Code Snippets](https://code
|
||||
|
||||
In addition, you can also use variables provided by Foam:
|
||||
|
||||
| Name | Description |
|
||||
| -------------------- | ------------ |
|
||||
| `FOAM_SELECTED_TEXT` | Foam will fill it with selected text when creating a new note, if any text is selected. Selected text will be replaced with a wikilink to the new |
|
||||
| `FOAM_TITLE` | The title of the note. If used, Foam will prompt you to enter a title for the note. |
|
||||
| `FOAM_SLUG` | The sluggified title of the note (using the default github slug method). If used, Foam will prompt you to enter a title for the note unless `FOAM_TITLE` has already caused the prompt. |
|
||||
| Name | Description |
|
||||
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `FOAM_SELECTED_TEXT` | Foam will fill it with selected text when creating a new note, if any text is selected. Selected text will be replaced with a wikilink to the new |
|
||||
| `FOAM_TITLE` | The title of the note. If used, Foam will prompt you to enter a title for the note. |
|
||||
| `FOAM_TITLE_SAFE` | The title of the note in a file system safe format. If used, Foam will prompt you to enter a title for the note unless `FOAM_TITLE` has already caused the prompt. |
|
||||
| `FOAM_SLUG` | The sluggified title of the note (using the default github slug method). If used, Foam will prompt you to enter a title for the note unless `FOAM_TITLE` has already caused the prompt. |
|
||||
| `FOAM_DATE_*` | `FOAM_DATE_YEAR`, `FOAM_DATE_MONTH`, `FOAM_DATE_WEEK` etc. Foam-specific versions of [VS Code's datetime snippet variables](https://code.visualstudio.com/docs/editor/userdefinedsnippets#_variables). Prefer these versions over VS Code's. |
|
||||
|
||||
### `FOAM_DATE_*` variables
|
||||
@@ -69,7 +70,7 @@ For example, `FOAM_DATE_YEAR` has the same behaviour as VS Code's `CURRENT_YEAR`
|
||||
|
||||
By default, prefer using the `FOAM_DATE_` versions. The datetime used to compute the values will be the same for both `FOAM_DATE_` and VS Code's variables, with the exception of the creation notes using the daily note template.
|
||||
|
||||
For more nitty-gritty details about the supported date formats, [see here](https://github.com/foambubble/foam/blob/master/packages/foam-vscode/src/services/variable-resolver.ts).
|
||||
For more nitty-gritty details about the supported date formats, [see here](https://github.com/foambubble/foam/blob/main/packages/foam-vscode/src/services/variable-resolver.ts).
|
||||
|
||||
#### Relative daily notes
|
||||
|
||||
@@ -83,8 +84,8 @@ For example, given this daily note template (`.foam/templates/daily-note.md`):
|
||||
|
||||
## Here's what I'm going to do today
|
||||
|
||||
* Thing 1
|
||||
* Thing 2
|
||||
- Thing 1
|
||||
- Thing 2
|
||||
```
|
||||
|
||||
When the `/tomorrow` snippet is used, `FOAM_DATE_` variables will be populated with tomorrow's date, as expected.
|
||||
@@ -96,11 +97,11 @@ When creating notes in any other scenario, the `FOAM_DATE_` values are computed
|
||||
|
||||
Templates can also contain metadata about the templates themselves. The metadata is defined in YAML "Frontmatter" blocks within the templates.
|
||||
|
||||
| Name | Description |
|
||||
| ------------- | ---------------------- |
|
||||
| Name | Description |
|
||||
| ------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `filepath` | The filepath to use when creating the new note. If the filepath is a relative filepath, it is relative to the current workspace. |
|
||||
| `name` | A human readable name to show in the template picker. |
|
||||
| `description` | A human readable description to show in the template picker. |
|
||||
| `name` | A human readable name to show in the template picker. |
|
||||
| `description` | A human readable description to show in the template picker. |
|
||||
|
||||
Foam-specific variables (e.g. `$FOAM_TITLE`) can be used within template metadata. However, VS Code snippet variables are ([currently](https://github.com/foambubble/foam/pull/655)) not supported.
|
||||
|
||||
@@ -145,9 +146,10 @@ It is possible to vary the `filepath` value based on the current date using the
|
||||
---
|
||||
type: daily-note
|
||||
foam_template:
|
||||
description: Daily Note for $FOAM_TITLE
|
||||
filepath: "C:\\Users\\foam_user\\foam_notes\\journal\\$FOAM_DATE_YEAR\\$FOAM_DATE_MONTH-$FOAM_DATE_MONTH_NAME_SHORT\\$FOAM_DATE_YEAR-$FOAM_DATE_MONTH-$FOAM_DATE_DATE-daily-note.md"
|
||||
description: Daily Note for $FOAM_TITLE
|
||||
filepath: "C:\\Users\\foam_user\\foam_notes\\journal\\$FOAM_DATE_YEAR\\$FOAM_DATE_MONTH-$FOAM_DATE_MONTH_NAME_SHORT\\$FOAM_DATE_YEAR-$FOAM_DATE_MONTH-$FOAM_DATE_DATE-daily-note.md"
|
||||
---
|
||||
|
||||
# $FOAM_DATE_YEAR-$FOAM_DATE_MONTH-$FOAM_DATE_DATE Daily Notes
|
||||
```
|
||||
|
||||
@@ -165,7 +167,7 @@ If your template already has a YAML Frontmatter block, you can add the Foam temp
|
||||
|
||||
#### Limitations
|
||||
|
||||
Foam only supports adding the template metadata to *YAML* Frontmatter blocks. If the existing Frontmatter block uses some other format (e.g. JSON), you will have to add the template metadata to its own YAML Frontmatter block.
|
||||
Foam only supports adding the template metadata to _YAML_ Frontmatter blocks. If the existing Frontmatter block uses some other format (e.g. JSON), you will have to add the template metadata to its own YAML Frontmatter block.
|
||||
|
||||
Further, the template metadata must be provided as a [YAML block mapping](https://yaml.org/spec/1.2/spec.html#id2798057), with the attributes placed on the lines immediately following the `foam_template` line:
|
||||
|
||||
@@ -193,7 +195,7 @@ You can add the template metadata to its own YAML Frontmatter block at the start
|
||||
foam_template:
|
||||
name: My Note Template
|
||||
description: This is my note template
|
||||
filepath: `journal/$FOAM_TITLE.md`
|
||||
filepath: 'journal/$FOAM_TITLE.md'
|
||||
---
|
||||
This is the rest of the template
|
||||
```
|
||||
@@ -205,11 +207,11 @@ If the note already has a Frontmatter block, a Foam-specific Frontmatter block c
|
||||
foam_template:
|
||||
name: My Note Template
|
||||
description: This is my note template
|
||||
filepath: `journal/$FOAM_TITLE.md`
|
||||
filepath: 'journal/$FOAM_TITLE.md'
|
||||
---
|
||||
|
||||
---
|
||||
existing_frontmatter: "Existing Frontmatter block"
|
||||
existing_frontmatter: 'Existing Frontmatter block'
|
||||
---
|
||||
This is the rest of the template
|
||||
```
|
||||
|
||||
42
docs/user/features/resource-filters.md
Normal file
42
docs/user/features/resource-filters.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Resource Filters
|
||||
|
||||
Resource filters can be passed to some Foam commands to limit their scope.
|
||||
|
||||
A filter supports the following parameters:
|
||||
|
||||
- `tag`: include a resource if it has the given tag (e.g. `{"tag": "#research"}`)
|
||||
- `type`: include a resource if it is of the given type (e.g. `{"type": "daily-note"}`)
|
||||
- `path`: include a resource if its path matches the given regex (e.g. `{"path": "/projects/*"}`). **Note that this parameter supports regex and not globs.**
|
||||
- `expression`: include a resource if it makes the given expression `true`, where `resource` represents the resource being evaluated (e.g. `{"expression": "resource.type ==='weekly-note'"}`)
|
||||
- `title`: include a resource if the title matches the given regex (e.g. `{"title": "Team meeting:*"}`)
|
||||
|
||||
A filter also supports some logical operators:
|
||||
|
||||
- `and`: include a resource if it matches all the sub-parameters (e.g `{"and": [{"tag": "#research"}, {"title": "Paper *"}]}`)
|
||||
- `or`: include a resource if it matches any of the sub-parameters (e.g `{"or": [{"tag": "#research"}, {"title": "Paper *"}]}`)
|
||||
- `not`: invert the result of the nested filter (e.g. `{"not": {"type": "daily-note"}}`)
|
||||
|
||||
Here is an example of a complex filter, for example to show the Foam graph only of a subset of the workspace:
|
||||
|
||||
```
|
||||
{
|
||||
"key": "alt+f",
|
||||
"command": "foam-vscode.show-graph",
|
||||
"args": {
|
||||
"filter": {
|
||||
"and": [
|
||||
{
|
||||
"or": [
|
||||
{ "type": 'daily-note' },
|
||||
{ "type": 'weekly-note' },
|
||||
{ "path": '/projects/*' },
|
||||
],
|
||||
"not": {
|
||||
{ "tag": '#b' },
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -15,6 +15,10 @@ There are two ways of creating a tag:
|
||||
|
||||
Tags can also be hierarchical, so you can have `#parent/child` such as #my-tag3/info.
|
||||
|
||||
### Tag completion
|
||||
|
||||
Typing the `#` character will launch VS Code's "Intellisense." This provider will show a list of possible tags that match the character. If you are editing in the frontmatter [[note-properties|note property]], you can invoke tag completion on the `tags:` line by either typing the `#` character, or using the ["trigger suggest"](https://code.visualstudio.com/docs/editor/intellisense) keybinding (usually `ctrl+space`). If the `#` is used in the frontmatter, it will be removed when the tag is inserted.
|
||||
|
||||
## Using *Tag Explorer*
|
||||
|
||||
It's possible to navigate tags via the Tag Explorer panel. Expand the Tag Explorer view in the left side bar which will list all the tags found in current Foam environment. Then, each level of tags can be expanded until the options to search by tag and a list of all files containing a particular tag are shown.
|
||||
@@ -33,7 +37,7 @@ It is possible to customize the way that tags look in the Markdown Preview panel
|
||||
|
||||
> Note: the file path for the stylesheet will be relative to the currently open folder in the workspace when changing this setting for the current workspace. If changing this setting for the user, then the file path will be relative to your global [VSCode settings](https://code.visualstudio.com/docs/getstarted/settings).
|
||||
|
||||
The end result will be a CSS file that looks similiar to the content below. Now you can make your tags standout in your note previews.
|
||||
The end result will be a CSS file that looks similar to the content below. Now you can make your tags standout in your note previews.
|
||||
|
||||
```css
|
||||
.foam-tag{
|
||||
@@ -49,7 +53,10 @@ The end result will be a CSS file that looks similiar to the content below. Now
|
||||
Given the power of backlinks, some people prefer to use them as tags.
|
||||
For example you can tag your notes about books with [[book]].
|
||||
|
||||
[note-properties|note property]: note-properties.md "Note Properties"
|
||||
[graph-visualization]: graph-visualization.md "Graph Visualization"
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[note-properties|note property]: note-properties.md "Note Properties"
|
||||
[graph-visualization]: graph-visualization.md "Graph Visualization"
|
||||
[//end]: # "Autogenerated link references"
|
||||
[//end]: # "Autogenerated link references"
|
||||
@@ -20,7 +20,10 @@ Remember, with `CTRL/CMD+click` on a wikilink you can navigate to the note, or c
|
||||
|
||||
## Support for sections
|
||||
|
||||
Foam supports autocompletion, navigation, embedding and diagnostics for note sections. Just use the standard wiki syntax of `[[resource#Section Title]]`.
|
||||
Foam supports autocompletion, navigation, embedding and diagnostics for note sections. Just use the standard wiki syntax of `[[resource#Section Title]]`.
|
||||
- If it's an external file, `[your link will need the filename](other-file.md#that-section-I-want-to-link-to)`, but
|
||||
- if it's an anchor within the same document, `[you just need an octothorpe and the section name](#that-section-above)`.
|
||||
- Doesn't matter what heading-level the anchor is; whether you're linking to an `H1` like `# MEN WALK ON MOON` or an `H2` like `## Astronauts Land on Plain`, the link syntax uses a single octothorpe: `[Walk!](#men-walk-on-moon)` and `[Land!](#astronauts-land-on-plain-collect-rocks-plant-flag)`. Autocomplete is your friend here.
|
||||
|
||||
## Markdown compatibility
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
- [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)
|
||||
- [I want to publish the graph view to GitHub pages or Vercel](#i-want-to-publish-the-graph-view-to-github-pages-or-vercel)
|
||||
|
||||
## Links/Graphs/BackLinks don't work. How do I enable them?
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@ This list is subject to change.
|
||||
|
||||
- [Foam for VSCode](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode) (alpha)
|
||||
- [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)
|
||||
- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
|
||||
|
||||
## Extensions For Additional Features
|
||||
|
||||
These extensions are not (yet?) defined in `.vscode/extensions.json`, but have been used by others and shown to play nice with Foam.
|
||||
These extensions are not defined in `.vscode/extensions.json`, but have been used by others and shown to play nice with Foam.
|
||||
|
||||
- [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)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# Using Foam
|
||||
|
||||
Foam is a collection VS Code extensions and recipes that power up the editor into a full-blown note taking system.
|
||||
This folder contains user documentation describing how to get started using Foam, what its main features are, and strategies for getting the most out of Foam.
|
||||
The full docs are included in the `foam-template` repo that most users start from.
|
||||
Foam is a collection VS Code extensions and recipes that power up the editor
|
||||
into a full-blown note taking system. This folder contains user documentation
|
||||
describing how to get started using Foam, what its main features are, and
|
||||
strategies for getting the most out of Foam. The full docs are included in the
|
||||
`foam-template` repo that most users start from.
|
||||
|
||||
> See also [[frequently-asked-questions]].
|
||||
|
||||
@@ -12,7 +14,7 @@ The full docs are included in the `foam-template` repo that most users start fro
|
||||
- [[recommended-extensions]]
|
||||
- [[creating-new-notes]]
|
||||
- [[write-notes-in-foam]]
|
||||
- [[sync-notes-with-soruce-control]]
|
||||
- [[sync-notes-with-source-control]]
|
||||
- [[keyboard-shortcuts]]
|
||||
|
||||
## Features
|
||||
@@ -55,7 +57,7 @@ See [[publishing]] for more details.
|
||||
[recommended-extensions]: getting-started/recommended-extensions.md "Recommended Extensions"
|
||||
[creating-new-notes]: getting-started/creating-new-notes.md "Creating New Notes"
|
||||
[write-notes-in-foam]: getting-started/write-notes-in-foam.md "Writing Notes"
|
||||
[sync-notes-with-soruce-control]: getting-started/sync-notes-with-soruce-control.md "Sync notes with source control"
|
||||
[sync-notes-with-source-control]: getting-started/sync-notes-with-source-control.md "Sync notes with source control"
|
||||
[keyboard-shortcuts]: getting-started/keyboard-shortcuts.md "Keyboard Shortcuts"
|
||||
[wikilinks]: features/wikilinks.md "Wikilinks"
|
||||
[tags]: features/tags.md "Tags"
|
||||
|
||||
@@ -50,7 +50,7 @@ In our case, we'll be using the latter tag to wrap our {% raw %}`{{ content }}`{
|
||||
|
||||
You may have noticed that we only made modifications to the template `_layouts/page.html`, which means that `_layouts/home.html` won't have KaTeX support. If you wan't to render math in Foam's home page, you'll need to make the same modifications to `_layouts/home.html` as well.
|
||||
|
||||
Finally, if all goes well, then our site hosted on Vercel will support rendering math equations with KaTeX after commiting these changes to GitHub. Here's a demo of the default template with KaTeX support: [Foam Template with KaTeX support](https://foam-template.vercel.app/).
|
||||
Finally, if all goes well, then our site hosted on Vercel will support rendering math equations with KaTeX after committing these changes to GitHub. Here's a demo of the default template with KaTeX support: [Foam Template with KaTeX support](https://foam-template.vercel.app/).
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[math-support-with-mathjax]: math-support-with-mathjax.md "Math Support"
|
||||
|
||||
@@ -36,31 +36,31 @@ _Note that first entry in `.order` file defines wiki's home page._
|
||||
|
||||
While you are pushing changes to GitHub, you won't see the wiki updated if you don't add Azure as a remote. You can push to multiple repositories simultaneously.
|
||||
|
||||
1. First open a terminal and check if Azure is added running: `git remote show origin`. If you don't see Azure add it in the output then follow these steps.
|
||||
2. Rename your current remote (most likely named origin) to a different name by running: `git remote rename origin main`
|
||||
3. You can then add the remote for your second remote repository, in this case, Azure. e.g `git remote add azure https://<YOUR_ID>@dev.azure.com/<YOUR_ID>/foam-notes/_git/foam-notes`. You can get it from: Repos->Files->Clone and copy the URL.
|
||||
4. Now, you need to set up your origin remote to push to both of these. So run: `git config -e` and edit it.
|
||||
5. Add the `remote origin` section to the bottom of the file with the URLs from each remote repository you'd like to push to. You'll see something like that:
|
||||
1. First open a terminal and check if Azure is added running: `git remote show origin`. If you don't see Azure add it in the output then follow these steps.
|
||||
2. Rename your current remote (most likely named origin) to a different name by running: `git remote rename origin main`
|
||||
3. You can then add the remote for your second remote repository, in this case, Azure. e.g `git remote add azure https://<YOUR_ID>@dev.azure.com/<YOUR_ID>/foam-notes/_git/foam-notes`. You can get it from: Repos->Files->Clone and copy the URL.
|
||||
4. Now, you need to set up your origin remote to push to both of these. So run: `git config -e` and edit it.
|
||||
5. Add the `remote origin` section to the bottom of the file with the URLs from each remote repository you'd like to push to. You'll see something like that:
|
||||
|
||||
```bash
|
||||
[core]
|
||||
```bash
|
||||
[core]
|
||||
...
|
||||
(ignore this part)
|
||||
...
|
||||
(ignore this part)
|
||||
...
|
||||
[branch "master"]
|
||||
remote = github
|
||||
merge = refs/heads/master
|
||||
[branch "main"]
|
||||
remote = github
|
||||
merge = refs/heads/main
|
||||
[remote "github"]
|
||||
url = git@github.com:username/repo.git
|
||||
fetch = +refs/heads/*:refs/remotes/github/*
|
||||
url = git@github.com:username/repo.git
|
||||
fetch = +refs/heads/*:refs/remotes/github/*
|
||||
[remote "azure"]
|
||||
url = https://<YOUR_ID>@dev.azure.com/<YOUR_ID>/foam-notes/_git/foam-notes
|
||||
fetch = +refs/heads/*:refs/remotes/azure/*
|
||||
url = https://<YOUR_ID>@dev.azure.com/<YOUR_ID>/foam-notes/_git/foam-notes
|
||||
fetch = +refs/heads/*:refs/remotes/azure/*
|
||||
[remote "origin"]
|
||||
url = git@github.com:username/repo.git
|
||||
url = https://<YOUR_ID>@dev.azure.com/<YOUR_ID>/foam-notes/_git/foam-notes
|
||||
```
|
||||
url = git@github.com:username/repo.git
|
||||
url = https://<YOUR_ID>@dev.azure.com/<YOUR_ID>/foam-notes/_git/foam-notes
|
||||
```
|
||||
|
||||
6. You can then push to both repositories by: `git push origin master` or a single one using: `git push github master` or `git push azure master`
|
||||
6. You can then push to both repositories by: `git push origin main` or a single one using: `git push github main` or `git push azure main`
|
||||
|
||||
For more information, read the [Azure DevOps documentation](https://docs.microsoft.com/en-us/azure/devops/project/wiki/publish-repo-to-wiki).
|
||||
|
||||
@@ -65,7 +65,7 @@ gem "jekyll-katex" # Optional, the package that enables KaTeX math rendering
|
||||
|
||||
Besides adding the plugin `jekyll-katex` in `_config.yml` and `Gemfile`, we'll also have to follow the guides in [[math-support-with-katex]] to let our site fully support using KaTeX to render math equations.
|
||||
|
||||
### Commiting changes to GitHub repo
|
||||
### Committing changes to GitHub repo
|
||||
|
||||
Finally, commit the newly created files to GitHub.
|
||||
|
||||
|
||||
@@ -2,8 +2,12 @@
|
||||
|
||||
This #recipe allows you to paste images on to your notes.
|
||||
|
||||
You can directly link and paste images that are copied to the clipboard using either the [Paste
|
||||
Image](https://marketplace.visualstudio.com/items?itemName=mushan.vscode-paste-image)
|
||||
extension, or the [Markdown Image](https://marketplace.visualstudio.com/items?itemName=hancel.markdown-image) extension.
|
||||
VScode (since
|
||||
[1.79](https://code.visualstudio.com/updates/v1_79#_copy-external-media-files-into-workspace-on-drop-or-paste-for-markdown))
|
||||
now has the ability to paste images from the clipboard, or drag-and-drop image
|
||||
files, directly into markdown documents. The file will be created in the
|
||||
workspace, and a link generated in Markdown format.
|
||||
|
||||
The former does not have MDX support (yet), the latter does.
|
||||
VSCode settings under `Markdown > Copy Files` and `Markdown > Editor > Drop` can
|
||||
be used to configure where the files get placed in your workspace, how they're
|
||||
named, how conflicts with existing files are handled, and more.
|
||||
|
||||
@@ -6,7 +6,7 @@ With this #recipe you can create notes on your iOS device, which will automatica
|
||||
|
||||
* You use [Foam for VSCode](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode) to manage your notes
|
||||
* You wish to adopt a practice such as [A writing inbox for transient and incomplete notes](https://notes.andymatuschak.org/A%20writing%20inbox%20for%20transient%20and%20incomplete%20notes)
|
||||
* You wish to use [Shorcuts](https://support.apple.com/guide/shortcuts/welcome/ios) to capture quick notes into your Foam notes from your iOS device
|
||||
* You wish to use [Shortcuts](https://support.apple.com/guide/shortcuts/welcome/ios) to capture quick notes into your Foam notes from your iOS device
|
||||
|
||||
## Other tools
|
||||
|
||||
@@ -29,6 +29,9 @@ on:
|
||||
jobs:
|
||||
store_data:
|
||||
runs-on: ubuntu-latest
|
||||
# If you encounter a 403 error from a workflow run, try uncommenting the following 2 lines (taken from: https://stackoverflow.com/questions/75880266/cant-make-push-on-a-repo-with-github-actions accepted answer)
|
||||
# permissions:
|
||||
# contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: anglinb/foam-capture-action@main
|
||||
|
||||
53
docs/user/recipes/export-to-pdf.md
Normal file
53
docs/user/recipes/export-to-pdf.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Export to PDF
|
||||
|
||||
This #recipe shows how to export a note to PDF.
|
||||
|
||||
## Required extensions
|
||||
|
||||
- **[vscode-pandoc](https://marketplace.visualstudio.com/items?itemName=chrischinchilla.vscode-pandoc)**
|
||||
|
||||
## Required third-party tools
|
||||
|
||||
- [Pandoc](https://pandoc.org/installing.html)
|
||||
- A [LaTeX distribution](https://www.latex-project.org/get/) such as TeXLive (Linux), MacTeX (MacOS), or MikTeX (Windows)
|
||||
|
||||
Check that Pandoc is installed by opening a terminal and running `pandoc --version`.
|
||||
|
||||
Check that Pandoc can produce PDFs with LaTeX by running the following in the terminal.
|
||||
|
||||
```
|
||||
echo It is working > test.md
|
||||
pandoc test.md -o test.pdf
|
||||
```
|
||||
|
||||
## Instructions
|
||||
|
||||
1. Create a folder in your workspace named `.pandoc`. Take note of the full path to this directory. The rest of this recipe will refer to this path as `$WORKSPACE/.pandoc`.
|
||||
|
||||
2. Download the template file [`foam.latex`](https://raw.githubusercontent.com/Hegghammer/foam-templates/main/foam.latex) from [Hegghammer/foam-templates](https://github.com/Hegghammer/foam-templates) and place it in `$WORKSPACE/.pandoc`.
|
||||
|
||||
3. In VSCode, open `settings.json` for your user (or just for your workspace if you prefer), and add the following line:
|
||||
|
||||
```
|
||||
"pandoc.pdfOptString": "--from=markdown+wikilinks_title_after_pipe --resource-path $WORKSPACE/.pandoc --template foam --listings",
|
||||
```
|
||||
|
||||
Make sure to replace `$WORKSPACE/.pandoc` with the real full path to the `.pandoc` directory you created earlier.
|
||||
|
||||
4. Open a Foam note in VSCode.
|
||||
|
||||
5. Press `Ctrl` + `k`, `p`. Choose "pdf", and press `Enter`.
|
||||
|
||||
The PDF should look something like this:
|
||||
|
||||

|
||||
|
||||
## Options
|
||||
|
||||
If you include a name in the `author` parameter in the YAML of the Foam note, that name will feature in the PDF header on the top left.
|
||||
|
||||
If you don't want syntax highlighting and frames around the codeblocks, remove `--listings` from the `pandoc.pdfOptString` parameter in `settings.json`.
|
||||
|
||||
## Further customization
|
||||
|
||||
If you know some LaTeX, you can [tweak](https://bookdown.org/yihui/rmarkdown-cookbook/latex-template.html) the `foam.latex` template to your needs. Alternatively, you can supply another ready-made template such as [Eisvogel](https://github.com/Wandmalfarbe/pandoc-latex-template); just place the `TEMPLATE_NAME.latex` file in `$WORKSPACE/.pandoc`. You can also use all of Pandoc's [other functionalities](https://learnbyexample.github.io/customizing-pandoc/) by tweaking the `pandoc.pdfOptString` parameter in `settings.json`.
|
||||
@@ -1,19 +1,21 @@
|
||||
<!-- omit in toc -->
|
||||
|
||||
# Recipes
|
||||
|
||||
A #recipe is a guide, tip or strategy for getting the most out of your Foam workspace!
|
||||
|
||||
- [Contribute](#contribute)
|
||||
- [Take smart notes](#take-smart-notes)
|
||||
- [Discover](#discover)
|
||||
- [Organise](#organise)
|
||||
- [Write](#write)
|
||||
- [Version control](#version-control)
|
||||
- [Publish](#publish)
|
||||
- [Collaborate](#collaborate)
|
||||
- [Workflow](#workflow)
|
||||
- [Creative ideas](#creative-ideas)
|
||||
- [Other](#other)
|
||||
- [Recipes](#recipes)
|
||||
- [Contribute](#contribute)
|
||||
- [Take smart notes](#take-smart-notes)
|
||||
- [Discover](#discover)
|
||||
- [Organise](#organise)
|
||||
- [Write](#write)
|
||||
- [Version control](#version-control)
|
||||
- [Publish](#publish)
|
||||
- [Collaborate](#collaborate)
|
||||
- [Workflow](#workflow)
|
||||
- [Creative ideas](#creative-ideas)
|
||||
- [Other](#other)
|
||||
|
||||
## Contribute
|
||||
|
||||
@@ -75,11 +77,11 @@ A #recipe is a guide, tip or strategy for getting the most out of your Foam work
|
||||
- Publish using community templates
|
||||
- [[publish-to-netlify-with-eleventy]] by [@juanfrank77](https://github.com/juanfrank77)
|
||||
- [[generate-gatsby-site]] by [@mathieudutour](https://github.com/mathieudutour) and [@hikerpig](https://github.com/hikerpig)
|
||||
- [foamy-nextjs](https://github.com/yenly/foamy-nextjs) by [@yenly](https://github.com/yenly)
|
||||
- Make the site your own by [[publish-to-github]].
|
||||
- Render math symbols, by either
|
||||
- adding client-side [[math-support-with-mathjax]] to the default [[publish-to-github-pages]] site
|
||||
- adding a custom Jekyll plugin to support [[math-support-with-katex]]
|
||||
- Export note to PDF [[export-to-pdf]]
|
||||
|
||||
## Collaborate
|
||||
|
||||
@@ -140,6 +142,7 @@ _See [[contribution-guide]] and [[how-to-write-recipes]]._
|
||||
[publish-to-github]: ../publishing/publish-to-github.md "Publish to GitHub"
|
||||
[math-support-with-mathjax]: ../publishing/math-support-with-mathjax.md "Math Support"
|
||||
[math-support-with-katex]: ../publishing/math-support-with-katex.md "Katex Math Rendering"
|
||||
[export-to-pdf]: export-to-pdf.md "Export to PDF"
|
||||
[real-time-collaboration]: real-time-collaboration.md "Real-time Collaboration"
|
||||
[capture-notes-with-drafts-pro]: capture-notes-with-drafts-pro.md "Capture Notes With Drafts Pro"
|
||||
[capture-notes-with-shortcuts-and-github-actions]: capture-notes-with-shortcuts-and-github-actions.md "Capture Notes With Shortcuts and GitHub Actions"
|
||||
|
||||
@@ -53,7 +53,7 @@ If such an app was worth building, it would have to have the following features:
|
||||
|
||||
- Instant loading and syncing for quick notes
|
||||
- Sleek, simple, beautifully designed user experience.
|
||||
- Ability to search and navigate forward links and back links (onlly in paid GitJournal version)
|
||||
- Ability to search and navigate forward links and back links (only in paid GitJournal version)
|
||||
- Killer feature that makes it the best note taking tool for Foam (?)
|
||||
|
||||
Given the effort vs reward ratio, it's a low priority for core team, but if someone wants to work on this, we can provide support! Talk to us on the #mobile-apps channel on [Foam Discord](https://foambubble.github.io/join-discord/w).
|
||||
|
||||
@@ -44,7 +44,7 @@ When editing a file, you can easily navigate `[[links]]` by hovering over them t
|
||||
|
||||
You can view a page's backlinks using either of the following techniques:
|
||||
|
||||
1. Expanding the file's node in the `Repositories` tree, since it's child nodes will represent backlinks. This makes it easy to browse your pages and their backlinks in a single hierachical view.
|
||||
1. Expanding the file's node in the `Repositories` tree, since it's child nodes will represent backlinks. This makes it easy to browse your pages and their backlinks in a single hierarchical view.
|
||||
|
||||
1. Opening a file, and then viewing it's backlinks list at the bottom of the editor view. This makes it easy to read a page and then see its backlinks in a contextually rich way.
|
||||
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
],
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.20.7"
|
||||
"version": "0.26.7"
|
||||
}
|
||||
|
||||
@@ -16,16 +16,16 @@
|
||||
"reset": "yarn && yarn clean && yarn build",
|
||||
"clean": "lerna run clean",
|
||||
"build": "lerna run build",
|
||||
"test": "yarn workspace foam-vscode test",
|
||||
"test": "yarn workspace foam-vscode test --stream",
|
||||
"lint": "lerna run lint",
|
||||
"watch": "lerna run watch --concurrency 20 --stream"
|
||||
},
|
||||
"devDependencies": {
|
||||
"all-contributors-cli": "^6.16.1",
|
||||
"lerna": "^3.22.1"
|
||||
"lerna": "^6.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
"node": ">=18"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
@@ -33,6 +33,7 @@
|
||||
}
|
||||
},
|
||||
"prettier": {
|
||||
"arrowParens": "avoid",
|
||||
"printWidth": 80,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
|
||||
@@ -6,6 +6,7 @@ out/**/*.spec.*
|
||||
test-data/**
|
||||
src/**
|
||||
jest.config.js
|
||||
esbuild.js
|
||||
.test-workspace
|
||||
.gitignore
|
||||
vsc-extension-quickstart.md
|
||||
|
||||
@@ -4,6 +4,248 @@ All notable changes to the "foam-vscode" extension will be documented in this fi
|
||||
|
||||
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
|
||||
|
||||
## [0.26.7] - 2025-03-09
|
||||
|
||||
Fixese and Improvements:
|
||||
|
||||
- Improved parsing of tags (fixes #1434)
|
||||
|
||||
## [0.26.6] - 2025-03-08
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Improved graph based navigation when running in virtual workspace
|
||||
- Improved wikilink embeds and fixed cycle detection issue (#1430)
|
||||
- Added links in tags to navigate to corresponding tag explorer item (#1432)
|
||||
|
||||
Internal:
|
||||
|
||||
- Renamed branch from `master` to `main`
|
||||
|
||||
## [0.26.5] - 2025-02-21
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Improved handling of virtual FS URIs (#1426)
|
||||
|
||||
## [0.26.4] - 2024-11-12
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Improved handling of virtual FS URIs (#1409)
|
||||
|
||||
## [0.26.3] - 2024-11-12
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Finetuned use of triemap (#1411 - thanks @pderaaij)
|
||||
|
||||
## [0.26.2] - 2024-11-06
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Performance improvements (#1406 - thanks @pderaaij)
|
||||
|
||||
## [0.26.1] - 2024-10-09
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Fixed issue with Buffer in web extension (#1401 - thanks @pderaaij)
|
||||
|
||||
## [0.26.0] - 2024-10-01
|
||||
|
||||
Features:
|
||||
|
||||
- Foam is now a web extension! (#1395 - many thanks @pderaaij)
|
||||
|
||||
## [0.25.12] - 2024-07-13
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Improved YAML support (#1367)
|
||||
- Added convesion of wikilinks to markdown links (#1365 - thanks @hereistheusername)
|
||||
- Refactored util and settings code
|
||||
|
||||
## [0.25.11] - 2024-03-18
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Actually fixed bug in graph computation (#1345)
|
||||
|
||||
## [0.25.10] - 2024-03-18
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Fixed bug in graph computation (#1345)
|
||||
|
||||
## [0.25.9] - 2024-03-17
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Improved note creation from placeholder (#1344)
|
||||
|
||||
## [0.25.8] - 2024-02-21
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Upgraded dataformat to improve support for daily note naming (#1326 - thanks @rcyeh)
|
||||
|
||||
## [0.25.7] - 2024-01-16
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Modifies url encoding to target only the filename and skip spaces (#1322 - thanks @MABruni)
|
||||
- Minor tweak to quick action menu with suggestions for section name
|
||||
|
||||
## [0.25.6] - 2023-12-13
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Fixed wikilink definition encoding (#1311 - thanks @MABruni)
|
||||
|
||||
## [0.25.5] - 2023-11-30
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Using note title in preview (#1309)
|
||||
|
||||
## [0.25.4] - 2023-09-19
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Added support for linking sections within same document (#1289)
|
||||
- Fixed note embedding bug (#1286 - thanks @badsketch)
|
||||
|
||||
## [0.25.3] - 2023-09-07
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Fixed incorrect handling of embedding of non-existing notes (#1283 - thanks @badsketch)
|
||||
- Introduced Note Embedding Sytanx (#1281 - thanks @badsketch)
|
||||
- Attachments are not considered when computing orphan notes (#1242)
|
||||
|
||||
## [0.25.2] - 2023-09-02
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Added content-only embed styles (#1279 - thanks @badsketch)
|
||||
- Added expand-all button to tree views (#1276)
|
||||
|
||||
## [0.25.1] - 2023-08-23
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Added support for path parameter in filter (#1250)
|
||||
- Added grouping and filtering to tag explorer (#1275)
|
||||
- Added new setting to control note embedding (#1273 - thanks @badsketch)
|
||||
- Added last week's days to snippets (#1248 - thanks @jimgraham)
|
||||
|
||||
Internal:
|
||||
|
||||
- Updated jest to v29 (#1271 - thanks @nicholas-l)
|
||||
- Improved test cleanup and management (#1274)
|
||||
|
||||
## [0.25.0] - 2023-06-30
|
||||
|
||||
Features:
|
||||
|
||||
- Support for multiple extensions and custom default extension (#1235)
|
||||
- Added `FOAM_TITLE_SAFE` template variable (#1232)
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Connections panel tweaks (#1233)
|
||||
|
||||
## [0.24.0] - 2023-05-19
|
||||
|
||||
Features:
|
||||
|
||||
- Converted backlinks panel into more general connections panel (#1230)
|
||||
|
||||
Internal:
|
||||
|
||||
- Improved janitor code (#1228)
|
||||
- Refactored code related to tree view panels (#1226)
|
||||
- Lint and cleanup (#1224)
|
||||
|
||||
## [0.23.0] - 2023-05-06
|
||||
|
||||
Features:
|
||||
|
||||
- Added notes explorer (#1223)
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Enabled tag completion in front matter (#1191 - thanks @jimgraham)
|
||||
- Various improvements to tree views (#1220)
|
||||
|
||||
## [0.22.2] - 2023-04-20
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Support to show placeholders only for open file in panel (#1201, #988)
|
||||
- Show note block in panels on hover preview (#1201, #800)
|
||||
- Show tag references within tag explorer (#1201)
|
||||
- Improved structure of view related commands (#1201)
|
||||
- Ignore `.foam` directory
|
||||
|
||||
## [0.22.1] - 2023-04-15
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Allow the `#` char to trigger tag autocompletion (#1192, #1189 - thanks @jimgraham)
|
||||
|
||||
## [0.22.0] - 2023-04-15
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Added support for deep tag hierarchy in Tag Explorer panel (#1134, #1194)
|
||||
- Consolidated and improved Backlinks, Placeholders and Orphans panels (#1196)
|
||||
- Fixed note resolution when using template without defined path (#1197)
|
||||
|
||||
## [0.21.4] - 2023-04-14
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Fixed issue with generated daily note template due to path escape (#1188, #1190)
|
||||
|
||||
## [0.21.3] - 2023-04-12
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Fixed relative path from workspace root in templates (#1188)
|
||||
|
||||
## [0.21.2] - 2023-04-11
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Fixed embed with relative paths (#1168, #1170)
|
||||
- Improved multi-root folder support for daily notes (#1126, #1175)
|
||||
- Improved use of tag completion (#1183 - thanks @jimgraham)
|
||||
- Fixed relative path use in note creation when using templates (#1170)
|
||||
|
||||
Internal:
|
||||
|
||||
- Sync user docs with foam-template docs (#1180 - thanks @infogulch)
|
||||
|
||||
## [0.21.1] - 2023-02-24
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Fixed note creation from placeholder (#1172)
|
||||
|
||||
## [0.21.0] - 2023-02-16
|
||||
|
||||
Features:
|
||||
|
||||
- Added support for filters for the `foam-vscode.open-resource` command (#1161)
|
||||
|
||||
## [0.20.8] - 2023-02-10
|
||||
|
||||
Internal:
|
||||
|
||||
- Updated most dependencies (#1160)
|
||||
|
||||
## [0.20.7] - 2023-01-31
|
||||
|
||||
Fixes and Improvements:
|
||||
@@ -58,7 +300,7 @@ Fixes and Improvements:
|
||||
|
||||
## [0.20.0] - 2022-09-30
|
||||
|
||||
New Features:
|
||||
Features:
|
||||
|
||||
- Added `foam-vscode.create-note` command, which can be very customized for several use cases (#1076)
|
||||
|
||||
@@ -114,7 +356,7 @@ Internal:
|
||||
|
||||
## [0.19.0] - 2022-07-07
|
||||
|
||||
New Features:
|
||||
Features:
|
||||
|
||||
- Support for attachments (PDF) and images (#1027)
|
||||
- Support for opening day notes for other days as well (#1026, thanks @alper)
|
||||
@@ -594,7 +836,7 @@ Fixes and Improvements:
|
||||
|
||||
## [0.7.1] - 2020-11-27
|
||||
|
||||
New Feature:
|
||||
Features:
|
||||
|
||||
- Foam logging can now be inspected in VsCode Output panel (#377)
|
||||
|
||||
@@ -606,7 +848,7 @@ Fixes and Improvements:
|
||||
|
||||
## [0.7.0] - 2020-11-25
|
||||
|
||||
New Features:
|
||||
Features:
|
||||
|
||||
- Foam stays in sync with changes in notes
|
||||
- Dataviz: Added multiple selection in graph (shift+click on node)
|
||||
@@ -618,7 +860,7 @@ Fixes and Improvements:
|
||||
|
||||
## [0.6.0] - 2020-11-19
|
||||
|
||||
New features:
|
||||
Features:
|
||||
|
||||
- Added command to create notes from templates (#115 - Thanks @ingalless)
|
||||
|
||||
@@ -633,7 +875,7 @@ Fixes and Improvements:
|
||||
|
||||
## [0.5.0] - 2020-11-09
|
||||
|
||||
New features:
|
||||
Features:
|
||||
|
||||
- Added tags panel (#311)
|
||||
|
||||
@@ -647,7 +889,7 @@ Fixes and Improvements:
|
||||
|
||||
## [0.4.0] - 2020-10-28
|
||||
|
||||
New features:
|
||||
Features:
|
||||
|
||||
- Added `Foam: Show Graph` command
|
||||
- Added date snippets (/+1d, ...) to create wikilinks to dates in daily note format
|
||||
@@ -675,7 +917,7 @@ Fixes and improvements:
|
||||
|
||||
## [0.3.0] - 2020-07-25
|
||||
|
||||
New features:
|
||||
Features:
|
||||
|
||||
- [Daily Notes](https://foambubble.github.io/foam/daily-notes)
|
||||
- [Janitor](https://foambubble.github.io/foam/workspace-janitor) for updating headings and link references across your workspace
|
||||
|
||||
33
packages/foam-vscode/LICENSE
Normal file
33
packages/foam-vscode/LICENSE
Normal file
@@ -0,0 +1,33 @@
|
||||
The MIT Licence (MIT)
|
||||
|
||||
Copyright 2020 - present Jani Eväkallio <jani.evakallio@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
Where noted, some code uses the following license:
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2015 - present Microsoft Corporation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode)
|
||||
[](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode)
|
||||
[](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode)
|
||||
[](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode)
|
||||
[](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode)
|
||||
|
||||
> You can join the Foam Community on the [Foam Discord](https://foambubble.github.io/join-discord/e)
|
||||
|
||||
111
packages/foam-vscode/esbuild.js
Normal file
111
packages/foam-vscode/esbuild.js
Normal file
@@ -0,0 +1,111 @@
|
||||
// also see https://code.visualstudio.com/api/working-with-extensions/bundling-extension
|
||||
const assert = require('assert');
|
||||
const esbuild = require('esbuild');
|
||||
const polyfillPlugin = require('esbuild-plugin-polyfill-node');
|
||||
|
||||
// pass the platform to esbuild as an argument
|
||||
|
||||
function getPlatform() {
|
||||
const args = process.argv.slice(2);
|
||||
const pArg = args.find(arg => arg.startsWith('--platform='));
|
||||
if (pArg) {
|
||||
return pArg.split('=')[1];
|
||||
}
|
||||
throw new Error('No platform specified. Pass --platform <web|node>.');
|
||||
}
|
||||
|
||||
const platform = getPlatform();
|
||||
assert(['web', 'node'].includes(platform), 'Platform must be "web" or "node".');
|
||||
|
||||
const production = process.argv.includes('--production');
|
||||
const watch = process.argv.includes('--watch');
|
||||
|
||||
const config = {
|
||||
web: {
|
||||
platform: 'browser',
|
||||
format: 'cjs',
|
||||
outfile: `out/bundles/extension-web.js`,
|
||||
plugins: [
|
||||
polyfillPlugin.polyfillNode({
|
||||
// Options (optional)
|
||||
}),
|
||||
{
|
||||
name: 'path-browserify',
|
||||
setup(build) {
|
||||
build.onResolve({ filter: /^path$/ }, args => {
|
||||
return { path: require.resolve('path-browserify') };
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'wikilink-embed',
|
||||
setup(build) {
|
||||
build.onResolve({ filter: /wikilink-embed/ }, args => {
|
||||
return {
|
||||
path: require.resolve(
|
||||
args.resolveDir + '/wikilink-embed-web-extension.ts'
|
||||
),
|
||||
};
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
node: {
|
||||
platform: 'node',
|
||||
format: 'cjs',
|
||||
outfile: `out/bundles/extension-node.js`,
|
||||
plugins: [],
|
||||
},
|
||||
};
|
||||
|
||||
async function main() {
|
||||
const ctx = await esbuild.context({
|
||||
...config[platform],
|
||||
entryPoints: ['src/extension.ts'],
|
||||
bundle: true,
|
||||
minify: production,
|
||||
sourcemap: !production,
|
||||
sourcesContent: false,
|
||||
external: ['vscode'],
|
||||
logLevel: 'silent',
|
||||
plugins: [
|
||||
...config[platform].plugins,
|
||||
/* add to the end of plugins array */
|
||||
esbuildProblemMatcherPlugin,
|
||||
],
|
||||
});
|
||||
if (watch) {
|
||||
await ctx.watch();
|
||||
} else {
|
||||
await ctx.rebuild();
|
||||
await ctx.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {import('esbuild').Plugin}
|
||||
*/
|
||||
const esbuildProblemMatcherPlugin = {
|
||||
name: 'esbuild-problem-matcher',
|
||||
|
||||
setup(build) {
|
||||
build.onStart(() => {
|
||||
console.log('[watch] build started');
|
||||
});
|
||||
build.onEnd(result => {
|
||||
result.errors.forEach(({ text, location }) => {
|
||||
console.error(`✘ [ERROR] ${text}`);
|
||||
console.error(
|
||||
` ${location.file}:${location.line}:${location.column}:`
|
||||
);
|
||||
});
|
||||
console.log('[watch] build finished');
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
main().catch(e => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -8,7 +8,7 @@
|
||||
"type": "git"
|
||||
},
|
||||
"homepage": "https://github.com/foambubble/foam",
|
||||
"version": "0.20.7",
|
||||
"version": "0.26.7",
|
||||
"license": "MIT",
|
||||
"publisher": "foam",
|
||||
"engines": {
|
||||
@@ -21,7 +21,14 @@
|
||||
"activationEvents": [
|
||||
"workspaceContains:.vscode/foam.json"
|
||||
],
|
||||
"main": "./out/extension.js",
|
||||
"main": "./out/bundles/extension-node.js",
|
||||
"browser": "./out/bundles/extension-web.js",
|
||||
"capabilities": {
|
||||
"untrustedWorkspaces": {
|
||||
"supported": "limited",
|
||||
"description": "No expressions are allowed in filters."
|
||||
}
|
||||
},
|
||||
"contributes": {
|
||||
"markdown.markdownItPlugins": true,
|
||||
"markdown.previewStyles": [
|
||||
@@ -50,28 +57,34 @@
|
||||
"views": {
|
||||
"explorer": [
|
||||
{
|
||||
"id": "foam-vscode.backlinks",
|
||||
"name": "Backlinks",
|
||||
"id": "foam-vscode.connections",
|
||||
"name": "Connections",
|
||||
"icon": "$(references)",
|
||||
"contextualTitle": "Backlinks"
|
||||
"contextualTitle": "Foam"
|
||||
},
|
||||
{
|
||||
"id": "foam-vscode.tags-explorer",
|
||||
"name": "Tag Explorer",
|
||||
"icon": "$(tag)",
|
||||
"contextualTitle": "Tags Explorer"
|
||||
"contextualTitle": "Foam"
|
||||
},
|
||||
{
|
||||
"id": "foam-vscode.notes-explorer",
|
||||
"name": "Notes",
|
||||
"icon": "$(notebook)",
|
||||
"contextualTitle": "Foam"
|
||||
},
|
||||
{
|
||||
"id": "foam-vscode.orphans",
|
||||
"name": "Orphans",
|
||||
"icon": "$(debug-gripper)",
|
||||
"contextualTitle": "Orphans"
|
||||
"contextualTitle": "Foam"
|
||||
},
|
||||
{
|
||||
"id": "foam-vscode.placeholders",
|
||||
"name": "Placeholders",
|
||||
"icon": "$(debug-disconnect)",
|
||||
"contextualTitle": "Placeholders"
|
||||
"contextualTitle": "Foam"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -81,8 +94,8 @@
|
||||
"contents": "No tags found. Notes that contain tags will show up here. You may add tags to a note with a hashtag (#tag) or by adding a tag list to the front matter (tags: tag1, tag2)."
|
||||
},
|
||||
{
|
||||
"view": "foam-vscode.backlinks",
|
||||
"contents": "No backlinks found for selected resource."
|
||||
"view": "foam-vscode.connections",
|
||||
"contents": "Nothing found for the selected resource and the current filter."
|
||||
},
|
||||
{
|
||||
"view": "foam-vscode.orphans",
|
||||
@@ -90,29 +103,99 @@
|
||||
},
|
||||
{
|
||||
"view": "foam-vscode.placeholders",
|
||||
"contents": "No placeholders found. Pending links and notes without content will show up here."
|
||||
"contents": "No placeholders found for selected resource or workspace."
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"view/title": [
|
||||
{
|
||||
"command": "foam-vscode.group-orphans-by-folder",
|
||||
"when": "view == foam-vscode.orphans && foam-vscode.orphans-grouped-by-folder == false",
|
||||
"command": "foam-vscode.views.connections.show:backlinks",
|
||||
"when": "view == foam-vscode.connections && foam-vscode.views.connections.show == 'all links'",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.group-orphans-off",
|
||||
"when": "view == foam-vscode.orphans && foam-vscode.orphans-grouped-by-folder == true",
|
||||
"command": "foam-vscode.views.connections.show:forward-links",
|
||||
"when": "view == foam-vscode.connections && foam-vscode.views.connections.show == 'backlinks'",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.group-placeholders-by-folder",
|
||||
"when": "view == foam-vscode.placeholders && foam-vscode.placeholders-grouped-by-folder == false",
|
||||
"command": "foam-vscode.views.connections.show:all-links",
|
||||
"when": "view == foam-vscode.connections && foam-vscode.views.connections.show == 'forward links'",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.group-placeholders-off",
|
||||
"when": "view == foam-vscode.placeholders && foam-vscode.placeholders-grouped-by-folder == true",
|
||||
"command": "foam-vscode.views.orphans.group-by:folder",
|
||||
"when": "view == foam-vscode.orphans && foam-vscode.views.orphans.group-by == 'off'",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.orphans.group-by:off",
|
||||
"when": "view == foam-vscode.orphans && foam-vscode.views.orphans.group-by == 'folder'",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.tags-explorer.show:for-current-file",
|
||||
"when": "view == foam-vscode.tags-explorer && foam-vscode.views.tags-explorer.show == 'all'",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.tags-explorer.show:all",
|
||||
"when": "view == foam-vscode.tags-explorer && foam-vscode.views.tags-explorer.show == 'for-current-file'",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.tags-explorer.group-by:folder",
|
||||
"when": "view == foam-vscode.tags-explorer && foam-vscode.views.tags-explorer.group-by == 'off'",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.tags-explorer.group-by:off",
|
||||
"when": "view == foam-vscode.tags-explorer && foam-vscode.views.tags-explorer.group-by == 'folder'",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.tags-explorer.expand-all",
|
||||
"when": "view == foam-vscode.tags-explorer",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.placeholders.show:for-current-file",
|
||||
"when": "view == foam-vscode.placeholders && foam-vscode.views.placeholders.show == 'all'",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.placeholders.show:all",
|
||||
"when": "view == foam-vscode.placeholders && foam-vscode.views.placeholders.show == 'for-current-file'",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.placeholders.group-by:folder",
|
||||
"when": "view == foam-vscode.placeholders && foam-vscode.views.placeholders.group-by == 'off'",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.placeholders.group-by:off",
|
||||
"when": "view == foam-vscode.placeholders && foam-vscode.views.placeholders.group-by == 'folder'",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.placeholders.expand-all",
|
||||
"when": "view == foam-vscode.placeholders",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.notes-explorer.show:notes",
|
||||
"when": "view == foam-vscode.notes-explorer && foam-vscode.views.notes-explorer.show == 'all'",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.notes-explorer.show:all",
|
||||
"when": "view == foam-vscode.notes-explorer && foam-vscode.views.notes-explorer.show == 'notes-only'",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.notes-explorer.expand-all",
|
||||
"when": "view == foam-vscode.notes-explorer",
|
||||
"group": "navigation"
|
||||
}
|
||||
],
|
||||
@@ -126,19 +209,75 @@
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.group-orphans-by-folder",
|
||||
"command": "foam-vscode.views.connections.show:all-links",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.group-orphans-off",
|
||||
"command": "foam-vscode.views.connections.show:backlinks",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.group-placeholders-by-folder",
|
||||
"command": "foam-vscode.views.connections.show:forward-links",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.group-placeholders-off",
|
||||
"command": "foam-vscode.views.orphans.group-by:folder",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.orphans.group-by:off",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.tags-explorer.show:for-current-file",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.tags-explorer.show:all",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.tags-explorer.group-by:folder",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.tags-explorer.group-by:off",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.tags-explorer.expand-all",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.placeholders.show:for-current-file",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.placeholders.show:all",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.placeholders.group-by:folder",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.placeholders.group-by:off",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.placeholders.expand-all",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.notes-explorer.show:all",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.notes-explorer.show:notes",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.notes-explorer.expand-all",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
@@ -162,19 +301,19 @@
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.update-graph",
|
||||
"title": "Foam: Update graph"
|
||||
"title": "Foam: Update Graph"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.set-log-level",
|
||||
"title": "Foam: Set log level"
|
||||
"title": "Foam: Set Log Level"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.show-graph",
|
||||
"title": "Foam: Show graph"
|
||||
"title": "Foam: Show Graph"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.update-wikilinks",
|
||||
"title": "Foam: Update Markdown Reference List"
|
||||
"command": "foam-vscode.update-wikilink-definitions",
|
||||
"title": "Foam: Update Wikilink Definitions"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.open-daily-note",
|
||||
@@ -209,25 +348,108 @@
|
||||
"title": "Foam: Open Resource"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.group-orphans-by-folder",
|
||||
"title": "Foam: Group Orphans By Folder",
|
||||
"command": "foam-vscode.convert-link-style-inplace",
|
||||
"title": "Foam: Convert Link Style in Place"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.convert-link-style-incopy",
|
||||
"title": "Foam: Convert Link Format in Copy"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.orphans.group-by:folder",
|
||||
"title": "Group By Folder",
|
||||
"icon": "$(list-tree)"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.group-orphans-off",
|
||||
"title": "Foam: Don't Group Orphans",
|
||||
"command": "foam-vscode.views.connections.show:backlinks",
|
||||
"title": "Show Backlinks",
|
||||
"icon": "$(arrow-left)"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.connections.show:forward-links",
|
||||
"title": "Show Links",
|
||||
"icon": "$(arrow-right)"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.connections.show:all-links",
|
||||
"title": "Show All",
|
||||
"icon": "$(arrow-swap)"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.orphans.group-by:off",
|
||||
"title": "Flat list",
|
||||
"icon": "$(list-flat)"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.group-placeholders-by-folder",
|
||||
"title": "Foam: Group Placeholders By Folder",
|
||||
"command": "foam-vscode.views.tags-explorer.show:for-current-file",
|
||||
"title": "Show tags in current file",
|
||||
"icon": "$(file)"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.tags-explorer.show:all",
|
||||
"title": "Show tags in workspace",
|
||||
"icon": "$(files)"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.tags-explorer.group-by:folder",
|
||||
"title": "Group By Folder",
|
||||
"icon": "$(list-tree)"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.group-placeholders-off",
|
||||
"title": "Foam: Don't Group Placeholders",
|
||||
"command": "foam-vscode.views.tags-explorer.group-by:off",
|
||||
"title": "Flat list",
|
||||
"icon": "$(list-flat)"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.tags-explorer.expand-all",
|
||||
"title": "Expand all",
|
||||
"icon": "$(expand-all)"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.tags-explorer.focus",
|
||||
"title": "Focus on tag",
|
||||
"icon": "$(symbol-number)"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.placeholders.show:for-current-file",
|
||||
"title": "Show placeholders in current file",
|
||||
"icon": "$(file)"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.placeholders.show:all",
|
||||
"title": "Show placeholders in workspace",
|
||||
"icon": "$(files)"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.placeholders.group-by:folder",
|
||||
"title": "Group By Folder",
|
||||
"icon": "$(list-tree)"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.placeholders.group-by:off",
|
||||
"title": "Flat list",
|
||||
"icon": "$(list-flat)"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.placeholders.expand-all",
|
||||
"title": "Expand all",
|
||||
"icon": "$(expand-all)"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.notes-explorer.show:all",
|
||||
"title": "Show all resources",
|
||||
"icon": "$(files)"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.notes-explorer.expand-all",
|
||||
"title": "Expand all",
|
||||
"icon": "$(expand-all)"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.views.notes-explorer.show:notes",
|
||||
"title": "Show only notes",
|
||||
"icon": "$(file)"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.create-new-template",
|
||||
"title": "Foam: Create New Template"
|
||||
@@ -240,6 +462,13 @@
|
||||
"configuration": {
|
||||
"title": "Foam",
|
||||
"properties": {
|
||||
"foam.supportedLanguages": {
|
||||
"type": "array",
|
||||
"default": [
|
||||
"markdown"
|
||||
],
|
||||
"description": "List of languages to treat as Markdown-like documents."
|
||||
},
|
||||
"foam.completion.label": {
|
||||
"type": "string",
|
||||
"default": "path",
|
||||
@@ -285,6 +514,16 @@
|
||||
"default": "pdf mp3 webm wav m4a mp4 avi mov rtf txt doc docx pages xls xlsx numbers ppt pptm pptx",
|
||||
"description": "Space separated list of file extensions that will be considered attachments"
|
||||
},
|
||||
"foam.files.notesExtensions": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Space separated list of extra file extensions that will be considered text notes (e.g. 'mdx txt markdown')"
|
||||
},
|
||||
"foam.files.defaultNoteExtension": {
|
||||
"type": "string",
|
||||
"default": "md",
|
||||
"description": "The default extension for new notes"
|
||||
},
|
||||
"foam.files.newNotePath": {
|
||||
"type": "string",
|
||||
"default": "root",
|
||||
@@ -369,21 +608,6 @@
|
||||
"default": [],
|
||||
"markdownDescription": "Specifies the list of glob patterns that will be excluded from the orphans report. To ignore the all the content of a given folder, use `**<folderName>/**/*`"
|
||||
},
|
||||
"foam.orphans.groupBy": {
|
||||
"type": [
|
||||
"string"
|
||||
],
|
||||
"enum": [
|
||||
"off",
|
||||
"folder"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"Disable grouping",
|
||||
"Group by folder"
|
||||
],
|
||||
"default": "folder",
|
||||
"markdownDescription": "Group orphans report entries by."
|
||||
},
|
||||
"foam.placeholders.exclude": {
|
||||
"type": [
|
||||
"array"
|
||||
@@ -391,21 +615,6 @@
|
||||
"default": [],
|
||||
"markdownDescription": "Specifies the list of glob patterns that will be excluded from the placeholders report. To ignore the all the content of a given folder, use `**<folderName>/**/*`"
|
||||
},
|
||||
"foam.placeholders.groupBy": {
|
||||
"type": [
|
||||
"string"
|
||||
],
|
||||
"enum": [
|
||||
"off",
|
||||
"folder"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"Disable grouping",
|
||||
"Group by folder"
|
||||
],
|
||||
"default": "folder",
|
||||
"markdownDescription": "Group blank note report entries by."
|
||||
},
|
||||
"foam.dateSnippets.afterCompletion": {
|
||||
"type": "string",
|
||||
"default": "createNote",
|
||||
@@ -421,10 +630,21 @@
|
||||
],
|
||||
"description": "Whether or not to navigate to the target daily note when a daily note snippet is selected."
|
||||
},
|
||||
"foam.preview.embedNoteInContainer": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Wrap embedded notes in a container when displayed in preview panel"
|
||||
"foam.preview.embedNoteType": {
|
||||
"type": "string",
|
||||
"default": "full-card",
|
||||
"enum": [
|
||||
"full-inline",
|
||||
"full-card",
|
||||
"content-inline",
|
||||
"content-card"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"Include the section with title and style inline",
|
||||
"Include the section with title and style it within a container",
|
||||
"Include the section without title and style inline",
|
||||
"Include the section without title and style it within a container"
|
||||
]
|
||||
},
|
||||
"foam.graph.titleMaxLength": {
|
||||
"type": "number",
|
||||
@@ -450,71 +670,78 @@
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc -p ./",
|
||||
"pretest": "yarn build",
|
||||
"test": "node ./out/test/run-tests.js",
|
||||
"pretest:unit": "yarn build",
|
||||
"test:unit": "node ./out/test/run-tests.js --unit",
|
||||
"pretest:e2e": "yarn build",
|
||||
"test:e2e": "node ./out/test/run-tests.js --e2e",
|
||||
"lint": "tsdx lint src",
|
||||
"build:node": "node esbuild.js --platform=node",
|
||||
"build:web": "node esbuild.js --platform=web",
|
||||
"build": "yarn build:node && yarn build:web",
|
||||
"vscode:prepublish": "yarn clean && yarn build:node --production && yarn build:web --production",
|
||||
"compile": "tsc -p ./",
|
||||
"test-reset-workspace": "rm -rf .test-workspace && mkdir .test-workspace && touch .test-workspace/.keep",
|
||||
"test-setup": "yarn compile && yarn build && yarn test-reset-workspace",
|
||||
"test": "yarn test-setup && node ./out/test/run-tests.js",
|
||||
"test:unit": "yarn test-setup && node ./out/test/run-tests.js --unit",
|
||||
"test:e2e": "yarn test-setup && node ./out/test/run-tests.js --e2e",
|
||||
"lint": "dts lint src",
|
||||
"clean": "rimraf out",
|
||||
"watch": "tsc --build ./tsconfig.json --watch",
|
||||
"watch": "nodemon --watch 'src/**/*.ts' --exec 'yarn build' --ext ts",
|
||||
"vscode:start-debugging": "yarn clean && yarn watch",
|
||||
"esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node",
|
||||
"vscode:prepublish": "yarn run esbuild-base -- --minify",
|
||||
"package-extension": "npx vsce package --yarn",
|
||||
"install-extension": "code --install-extension ./foam-vscode-$npm_package_version.vsix",
|
||||
"open-in-browser": "vscode-test-web --quality=stable --browser=chromium --extensionDevelopmentPath=. ",
|
||||
"publish-extension-openvsx": "npx ovsx publish foam-vscode-$npm_package_version.vsix -p $OPENVSX_TOKEN",
|
||||
"publish-extension-vscode": "npx vsce publish --packagePath foam-vscode-$npm_package_version.vsix",
|
||||
"publish-extension": "yarn publish-extension-vscode && yarn publish-extension-openvsx"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/dateformat": "^3.0.1",
|
||||
"@types/glob": "^7.1.1",
|
||||
"@types/jest": "^29.5.3",
|
||||
"@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",
|
||||
"esbuild": "^0.14.45",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-import-resolver-typescript": "^2.5.0",
|
||||
"eslint-plugin-import": "^2.24.2",
|
||||
"eslint-plugin-jest": "^25.3.0",
|
||||
"glob": "^7.1.6",
|
||||
"@types/vscode": "^1.70.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.51.0",
|
||||
"@typescript-eslint/parser": "^5.51.0",
|
||||
"@vscode/test-web": "^0.0.62",
|
||||
"dts-cli": "^1.6.3",
|
||||
"esbuild": "^0.17.7",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"eslint": "^8.33.0",
|
||||
"eslint-import-resolver-typescript": "^3.5.3",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-jest": "^27.2.1",
|
||||
"husky": "^4.2.5",
|
||||
"jest": "^26.2.2",
|
||||
"jest-extended": "^0.11.5",
|
||||
"jest": "^29.6.2",
|
||||
"jest-extended": "^3.2.3",
|
||||
"markdown-it": "^12.0.4",
|
||||
"micromatch": "^4.0.2",
|
||||
"nodemon": "^3.1.7",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-jest": "^26.4.4",
|
||||
"tsdx": "^0.13.2",
|
||||
"ts-jest": "^29.1.1",
|
||||
"tslib": "^2.0.0",
|
||||
"typescript": "^3.9.5",
|
||||
"typescript": "^4.9.5",
|
||||
"vscode-test": "^1.3.0",
|
||||
"wait-for-expect": "^3.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"dateformat": "^3.0.3",
|
||||
"dateformat": "4.5.1",
|
||||
"detect-newline": "^3.1.0",
|
||||
"github-slugger": "^1.4.0",
|
||||
"gray-matter": "^4.0.2",
|
||||
"js-sha1": "^0.7.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lru-cache": "^7.12.0",
|
||||
"lru-cache": "^7.14.1",
|
||||
"markdown-it-regex": "^0.2.0",
|
||||
"mnemonist": "^0.39.8",
|
||||
"path-browserify": "^1.0.1",
|
||||
"remark-frontmatter": "^2.0.0",
|
||||
"remark-parse": "^8.0.2",
|
||||
"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"
|
||||
"yaml": "^2.2.2"
|
||||
},
|
||||
"__metadata": {
|
||||
"id": "b85c6625-454b-4b61-8a22-c42f3d0f2e1e",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// taken from https://github.com/microsoft/vscode/tree/master/src/vs/base/common
|
||||
// taken from https://github.com/microsoft/vscode/tree/main/src/vs/base/common
|
||||
|
||||
import { Emitter, Event } from './event';
|
||||
import { IDisposable } from './lifecycle';
|
||||
@@ -29,7 +29,7 @@ export interface CancellationToken {
|
||||
) => IDisposable;
|
||||
}
|
||||
|
||||
const shortcutEvent: Event<any> = Object.freeze(function(
|
||||
const shortcutEvent: Event<any> = Object.freeze(function (
|
||||
callback,
|
||||
context?
|
||||
): IDisposable {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// taken from https://github.com/microsoft/vscode/tree/master/src/vs/base/common
|
||||
// taken from https://github.com/microsoft/vscode/tree/main/src/vs/base/common
|
||||
|
||||
// Names from https://blog.codinghorror.com/ascii-pronunciation-rules-for-programmers/
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// taken from https://github.com/microsoft/vscode/tree/master/src/vs/base/common
|
||||
// taken from https://github.com/microsoft/vscode/tree/main/src/vs/base/common
|
||||
|
||||
export interface ErrorListenerCallback {
|
||||
(error: any): void;
|
||||
@@ -21,7 +21,7 @@ export class ErrorHandler {
|
||||
constructor() {
|
||||
this.listeners = [];
|
||||
|
||||
this.unexpectedErrorHandler = function(e: any) {
|
||||
this.unexpectedErrorHandler = function (e: any) {
|
||||
setTimeout(() => {
|
||||
if (e.stack) {
|
||||
throw new Error(e.message + '\n\n' + e.stack);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// taken from https://github.com/microsoft/vscode/tree/master/src/vs/base/common
|
||||
// taken from https://github.com/microsoft/vscode/tree/main/src/vs/base/common
|
||||
|
||||
import { onUnexpectedError } from './errors';
|
||||
import { once as onceFn } from './functional';
|
||||
@@ -115,7 +115,7 @@ export namespace Event {
|
||||
* Given an event, returns the same event but typed as `Event<void>`.
|
||||
*/
|
||||
export function signal<T>(event: Event<T>): Event<void> {
|
||||
return (event as Event<any>) as Event<void>;
|
||||
return event as Event<any> as Event<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -525,9 +525,7 @@ class LeakageMonitor {
|
||||
|
||||
constructor(
|
||||
readonly customThreshold?: number,
|
||||
readonly name: string = Math.random()
|
||||
.toString(18)
|
||||
.slice(2, 5)
|
||||
readonly name: string = Math.random().toString(18).slice(2, 5)
|
||||
) {}
|
||||
|
||||
dispose(): void {
|
||||
@@ -549,10 +547,7 @@ class LeakageMonitor {
|
||||
if (!this._stacks) {
|
||||
this._stacks = new Map();
|
||||
}
|
||||
const stack = new Error()
|
||||
.stack!.split('\n')
|
||||
.slice(3)
|
||||
.join('\n');
|
||||
const stack = new Error().stack!.split('\n').slice(3).join('\n');
|
||||
const count = this._stacks.get(stack) || 0;
|
||||
this._stacks.set(stack, count + 1);
|
||||
this._warnCountdown -= 1;
|
||||
@@ -607,7 +602,7 @@ class LeakageMonitor {
|
||||
}
|
||||
*/
|
||||
export class Emitter<T> {
|
||||
private static readonly _noop = function() {};
|
||||
private static readonly _noop = function () {};
|
||||
|
||||
private readonly _options?: EmitterOptions;
|
||||
private readonly _leakageMon?: LeakageMonitor;
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// taken from https://github.com/microsoft/vscode/tree/master/src/vs/base/common
|
||||
// taken from https://github.com/microsoft/vscode/tree/main/src/vs/base/common
|
||||
|
||||
export function once<T extends Function>(this: unknown, fn: T): T {
|
||||
const _this = this;
|
||||
let didCall = false;
|
||||
let result: unknown;
|
||||
|
||||
return (function() {
|
||||
return function () {
|
||||
if (didCall) {
|
||||
return result;
|
||||
}
|
||||
@@ -19,5 +19,5 @@ export function once<T extends Function>(this: unknown, fn: T): T {
|
||||
result = fn.apply(_this, arguments);
|
||||
|
||||
return result;
|
||||
} as unknown) as T;
|
||||
} as unknown as T;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// taken from https://github.com/microsoft/vscode/tree/master/src/vs/base/common
|
||||
// taken from https://github.com/microsoft/vscode/tree/main/src/vs/base/common
|
||||
|
||||
export namespace Iterable {
|
||||
export function is<T = any>(thing: any): thing is IterableIterator<T> {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// taken from https://github.com/microsoft/vscode/tree/master/src/vs/base/common
|
||||
// taken from https://github.com/microsoft/vscode/tree/main/src/vs/base/common
|
||||
|
||||
import { once } from './functional';
|
||||
import { Iterable } from './iterator';
|
||||
@@ -164,7 +164,7 @@ export class DisposableStore implements IDisposable {
|
||||
if (!t) {
|
||||
return t;
|
||||
}
|
||||
if (((t as unknown) as DisposableStore) === this) {
|
||||
if ((t as unknown as DisposableStore) === this) {
|
||||
throw new Error('Cannot register a disposable on itself!');
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ export abstract class Disposable implements IDisposable {
|
||||
}
|
||||
|
||||
protected _register<T extends IDisposable>(t: T): T {
|
||||
if (((t as unknown) as Disposable) === this) {
|
||||
if ((t as unknown as Disposable) === this) {
|
||||
throw new Error('Cannot register a disposable on itself!');
|
||||
}
|
||||
return this._store.add(t);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// taken from https://github.com/microsoft/vscode/tree/master/src/vs/base/common
|
||||
// taken from https://github.com/microsoft/vscode/tree/main/src/vs/base/common
|
||||
|
||||
class Node<E> {
|
||||
static readonly Undefined = new Node<any>(undefined);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// taken from https://github.com/microsoft/vscode/tree/master/src/vs/base/common
|
||||
// taken from https://github.com/microsoft/vscode/tree/main/src/vs/base/common
|
||||
|
||||
const LANGUAGE_DEFAULT = 'en';
|
||||
|
||||
|
||||
@@ -361,7 +361,7 @@ describe('SnippetParser', () => {
|
||||
assertIdent('this ${1:is ${2:nested with $var}} and repeating $1');
|
||||
});
|
||||
|
||||
test('Parser, choise marker', () => {
|
||||
test('Parser, choice marker', () => {
|
||||
const { placeholders } = new SnippetParser().parse('${1|one,two,three|}');
|
||||
|
||||
assert.strictEqual(placeholders.length, 1);
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import os from 'os';
|
||||
import detectNewline from 'detect-newline';
|
||||
import { Position } from '../model/position';
|
||||
import { Range } from '../model/range';
|
||||
|
||||
export interface TextEdit {
|
||||
range: Range;
|
||||
newText: string;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param text text on which the textEdit will be applied
|
||||
* @param textEdit
|
||||
* @returns {string} text with the applied textEdit
|
||||
*/
|
||||
export const applyTextEdit = (text: string, textEdit: TextEdit): string => {
|
||||
const eol = detectNewline(text) || os.EOL;
|
||||
const lines = text.split(eol);
|
||||
const characters = text.split('');
|
||||
const startOffset = getOffset(lines, textEdit.range.start, eol);
|
||||
const endOffset = getOffset(lines, textEdit.range.end, eol);
|
||||
const deleteCount = endOffset - startOffset;
|
||||
|
||||
const textToAppend = `${textEdit.newText}`;
|
||||
characters.splice(startOffset, deleteCount, textToAppend);
|
||||
return characters.join('');
|
||||
};
|
||||
|
||||
const getOffset = (
|
||||
lines: string[],
|
||||
position: Position,
|
||||
eol: string
|
||||
): number => {
|
||||
const eolLen = eol.length;
|
||||
let offset = 0;
|
||||
let i = 0;
|
||||
while (i < position.line && i < lines.length) {
|
||||
offset = offset + lines[i].length + eolLen;
|
||||
i++;
|
||||
}
|
||||
return offset + Math.min(position.character, lines[i]?.length ?? 0);
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
import { convertLinkFormat } from '.';
|
||||
import { TEST_DATA_DIR } from '../../test/test-utils';
|
||||
import { MarkdownResourceProvider } from '../services/markdown-provider';
|
||||
import { Resource } from '../model/note';
|
||||
import { FoamWorkspace } from '../model/workspace';
|
||||
import { Logger } from '../utils/log';
|
||||
import fs from 'fs';
|
||||
import { URI } from '../model/uri';
|
||||
import { createMarkdownParser } from '../services/markdown-parser';
|
||||
import { FileDataStore } from '../../test/test-datastore';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
describe('generateStdMdLink', () => {
|
||||
let _workspace: FoamWorkspace;
|
||||
// TODO slug must be reserved for actual slugs, not file names
|
||||
const findBySlug = (slug: string): Resource => {
|
||||
return _workspace
|
||||
.list()
|
||||
.find(res => res.uri.getName() === slug) as Resource;
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
/** Use fs for reading files in units where vscode.workspace is unavailable */
|
||||
const readFile = async (uri: URI) =>
|
||||
(await fs.promises.readFile(uri.toFsPath())).toString();
|
||||
const dataStore = new FileDataStore(
|
||||
readFile,
|
||||
TEST_DATA_DIR.joinPath('__scaffold__').toFsPath()
|
||||
);
|
||||
const parser = createMarkdownParser();
|
||||
const mdProvider = new MarkdownResourceProvider(dataStore, parser);
|
||||
_workspace = await FoamWorkspace.fromProviders([mdProvider], dataStore);
|
||||
});
|
||||
|
||||
it('initialised test graph correctly', () => {
|
||||
expect(_workspace.list().length).toEqual(11);
|
||||
});
|
||||
|
||||
it('can generate markdown links correctly', async () => {
|
||||
const note = findBySlug('file-with-different-link-formats');
|
||||
const actual = note.links
|
||||
.filter(link => link.type === 'wikilink')
|
||||
.map(link => convertLinkFormat(link, 'link', _workspace, note));
|
||||
const expected: string[] = [
|
||||
'[first-document](first-document.md)',
|
||||
'[second-document](second-document.md)',
|
||||
'[[non-exist-file]]',
|
||||
'[#one section](<file-with-different-link-formats.md#one section>)',
|
||||
'[another name](<file-with-different-link-formats.md#one section>)',
|
||||
'[an alias](first-document.md)',
|
||||
'[first-document](first-document.md)',
|
||||
];
|
||||
expect(actual.length).toEqual(expected.length);
|
||||
actual.forEach((LinkReplace, index) => {
|
||||
expect(LinkReplace.newText).toEqual(expected[index]);
|
||||
});
|
||||
});
|
||||
|
||||
it('can generate wikilinks correctly', async () => {
|
||||
const note = findBySlug('file-with-different-link-formats');
|
||||
const actual = note.links
|
||||
.filter(link => link.type === 'link')
|
||||
.map(link => convertLinkFormat(link, 'wikilink', _workspace, note));
|
||||
const expected: string[] = ['[[first-document|file]]'];
|
||||
expect(actual.length).toEqual(expected.length);
|
||||
actual.forEach((LinkReplace, index) => {
|
||||
expect(LinkReplace.newText).toEqual(expected[index]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,83 @@
|
||||
import { Resource, ResourceLink } from '../model/note';
|
||||
import { URI } from '../model/uri';
|
||||
import { Range } from '../model/range';
|
||||
import { FoamWorkspace } from '../model/workspace';
|
||||
import { isNone } from '../utils';
|
||||
import { MarkdownLink } from '../services/markdown-link';
|
||||
|
||||
export interface LinkReplace {
|
||||
newText: string;
|
||||
range: Range /* old range */;
|
||||
}
|
||||
|
||||
/**
|
||||
* convert a link based on its workspace and the note containing it.
|
||||
* According to targetFormat parameter to decide output format. If link.type === targetFormat, then it simply copy
|
||||
* the rawText into LinkReplace. Therefore, it's recommended to filter before conversion.
|
||||
* If targetFormat isn't supported, or the target resource pointed by link cannot be found, the function will throw
|
||||
* exception.
|
||||
* @param link
|
||||
* @param targetFormat 'wikilink' | 'link'
|
||||
* @param workspace
|
||||
* @param note
|
||||
* @returns LinkReplace { newText: string; range: Range; }
|
||||
*/
|
||||
export function convertLinkFormat(
|
||||
link: ResourceLink,
|
||||
targetFormat: 'wikilink' | 'link',
|
||||
workspace: FoamWorkspace,
|
||||
note: Resource | URI
|
||||
): LinkReplace {
|
||||
const resource = note instanceof URI ? workspace.find(note) : note;
|
||||
const targetUri = workspace.resolveLink(resource, link);
|
||||
/* If it's already the target format or a placeholder, no transformation happens */
|
||||
if (link.type === targetFormat || targetUri.scheme === 'placeholder') {
|
||||
return {
|
||||
newText: link.rawText,
|
||||
range: link.range,
|
||||
};
|
||||
}
|
||||
|
||||
let { target, section, alias } = MarkdownLink.analyzeLink(link);
|
||||
let sectionDivider = section ? '#' : '';
|
||||
|
||||
if (isNone(targetUri)) {
|
||||
throw new Error(
|
||||
`Unexpected state: link to: "${link.rawText}" is not resolvable`
|
||||
);
|
||||
}
|
||||
|
||||
const targetRes = workspace.find(targetUri);
|
||||
let relativeUri = targetRes.uri.relativeTo(resource.uri.getDirectory());
|
||||
|
||||
if (targetFormat === 'wikilink') {
|
||||
return MarkdownLink.createUpdateLinkEdit(link, {
|
||||
target: workspace.getIdentifier(relativeUri),
|
||||
type: 'wikilink',
|
||||
});
|
||||
}
|
||||
|
||||
if (targetFormat === 'link') {
|
||||
/* if alias is empty, construct one as target#section */
|
||||
if (alias === '') {
|
||||
/* in page anchor have no filename */
|
||||
if (relativeUri.getBasename() === resource.uri.getBasename()) {
|
||||
target = '';
|
||||
}
|
||||
alias = `${target}${sectionDivider}${section}`;
|
||||
}
|
||||
|
||||
/* if it's originally an embedded note, the markdown link shouldn't be embedded */
|
||||
const isEmbed = targetRes.type === 'image' ? link.isEmbed : false;
|
||||
|
||||
return MarkdownLink.createUpdateLinkEdit(link, {
|
||||
alias: alias,
|
||||
target: relativeUri.path,
|
||||
isEmbed: isEmbed,
|
||||
type: 'link',
|
||||
});
|
||||
}
|
||||
throw new Error(
|
||||
`Unexpected state: targetFormat: ${targetFormat} is not supported`
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import matter from 'gray-matter';
|
||||
import { TextEdit } from './apply-text-edit';
|
||||
import { Resource } from '../model/note';
|
||||
import { Range } from '../model/range';
|
||||
import { TextEdit } from '../services/text-edit';
|
||||
import { getHeadingFromFileName } from '../utils';
|
||||
|
||||
export const generateHeading = async (
|
||||
|
||||
@@ -36,7 +36,7 @@ describe('generateLinkReferences', () => {
|
||||
});
|
||||
|
||||
it('initialised test graph correctly', () => {
|
||||
expect(_workspace.list().length).toEqual(10);
|
||||
expect(_workspace.list().length).toEqual(11);
|
||||
});
|
||||
|
||||
it('should add link references to a file that does not have them', async () => {
|
||||
@@ -141,7 +141,7 @@ describe('generateLinkReferences', () => {
|
||||
newText: textForNote(
|
||||
`
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[Note being refered as angel]: <Note being refered as angel> "Note being refered as angel"
|
||||
[Note being referred as angel]: <Note being referred as angel> "Note being referred as angel"
|
||||
[//end]: # "Autogenerated link references"`
|
||||
),
|
||||
range: Range.create(3, 0, 3, 0),
|
||||
@@ -183,13 +183,11 @@ describe('generateLinkReferences', () => {
|
||||
const note = findBySlug('file-with-explicit-and-implicit-link-references');
|
||||
const expected = {
|
||||
newText: textForNote(
|
||||
`[^footerlink]: https://foambubble.github.io/
|
||||
[linkrefenrece]: https://foambubble.github.io/
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
`[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[first-document]: first-document "First Document"
|
||||
[//end]: # "Autogenerated link references"`
|
||||
),
|
||||
range: Range.create(5, 0, 10, 42),
|
||||
range: Range.create(8, 0, 10, 42),
|
||||
};
|
||||
|
||||
const noteText = await _workspace.readAsMarkdown(note.uri);
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import { Resource } from '../model/note';
|
||||
import { NoteLinkDefinition, Resource } from '../model/note';
|
||||
import { Range } from '../model/range';
|
||||
import {
|
||||
createMarkdownReferences,
|
||||
stringifyMarkdownLinkReferenceDefinition,
|
||||
} from '../services/markdown-provider';
|
||||
import { createMarkdownReferences } from '../services/markdown-provider';
|
||||
import { FoamWorkspace } from '../model/workspace';
|
||||
import { TextEdit } from './apply-text-edit';
|
||||
import { TextEdit } from '../services/text-edit';
|
||||
import { Position } from '../model/position';
|
||||
|
||||
export const LINK_REFERENCE_DEFINITION_HEADER = `[//begin]: # "Autogenerated link references for markdown compatibility"`;
|
||||
@@ -22,112 +19,62 @@ export const generateLinkReferences = async (
|
||||
return null;
|
||||
}
|
||||
|
||||
const markdownReferences = createMarkdownReferences(
|
||||
const newWikilinkDefinitions = createMarkdownReferences(
|
||||
workspace,
|
||||
note.uri,
|
||||
note,
|
||||
includeExtensions
|
||||
);
|
||||
|
||||
const beginDelimiterDef = note.definitions.find(
|
||||
({ label }) => label === '//begin'
|
||||
);
|
||||
const endDelimiterDef = note.definitions.find(
|
||||
({ label }) => label === '//end'
|
||||
);
|
||||
|
||||
const lines = text.split(eol);
|
||||
|
||||
const targetRange =
|
||||
beginDelimiterDef && endDelimiterDef
|
||||
? Range.createFromPosition(
|
||||
beginDelimiterDef.range.start,
|
||||
endDelimiterDef.range.end
|
||||
)
|
||||
: Range.create(
|
||||
lines.length - 1,
|
||||
lines[lines.length - 1].length,
|
||||
lines.length - 1,
|
||||
lines[lines.length - 1].length
|
||||
);
|
||||
|
||||
const newReferences =
|
||||
markdownReferences.length === 0
|
||||
newWikilinkDefinitions.length === 0
|
||||
? ''
|
||||
: [
|
||||
LINK_REFERENCE_DEFINITION_HEADER,
|
||||
...markdownReferences.map(stringifyMarkdownLinkReferenceDefinition),
|
||||
...newWikilinkDefinitions.map(NoteLinkDefinition.format),
|
||||
LINK_REFERENCE_DEFINITION_FOOTER,
|
||||
].join(eol);
|
||||
|
||||
if (note.definitions.length === 0) {
|
||||
if (newReferences.length === 0) {
|
||||
return null;
|
||||
}
|
||||
// check if the new references match the existing references
|
||||
const existingReferences = lines
|
||||
.slice(targetRange.start.line, targetRange.end.line + 1)
|
||||
.join(eol);
|
||||
|
||||
const lines = text.split(eol);
|
||||
const end = Position.create(
|
||||
lines.length - 1,
|
||||
lines[lines.length - 1].length
|
||||
);
|
||||
const padding = end.character === 0 ? eol : `${eol}${eol}`;
|
||||
return {
|
||||
newText: `${padding}${newReferences}`,
|
||||
range: Range.createFromPosition(end, end),
|
||||
};
|
||||
} else {
|
||||
const first = note.definitions[0];
|
||||
const last = note.definitions[note.definitions.length - 1];
|
||||
// adjust padding based on whether there are existing definitions
|
||||
// and, if not, whether we are on an empty line at the end of the file
|
||||
const padding =
|
||||
newWikilinkDefinitions.length === 0 || // no definitions
|
||||
!Position.isEqual(targetRange.start, targetRange.end) // replace existing definitions
|
||||
? ''
|
||||
: targetRange.start.character > 0 // not an empty line
|
||||
? `${eol}${eol}`
|
||||
: eol;
|
||||
|
||||
let 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(eol);
|
||||
|
||||
const oldReferences = note.definitions
|
||||
.map(stringifyMarkdownLinkReferenceDefinition)
|
||||
.join(eol);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
let 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}${eol}${newReferences}`;
|
||||
}
|
||||
|
||||
return {
|
||||
// @todo: do we need to ensure new lines?
|
||||
newText: `${fullReferences}`,
|
||||
range: Range.createFromPosition(first.range!.start, last.range!.end),
|
||||
};
|
||||
}
|
||||
return existingReferences === newReferences
|
||||
? null
|
||||
: {
|
||||
newText: `${padding}${newReferences}`,
|
||||
range: targetRange,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export { generateLinkReferences } from './generate-link-references';
|
||||
export { generateHeading } from './generate-headings';
|
||||
export { convertLinkFormat } from './convert-links-format';
|
||||
|
||||
@@ -5,7 +5,7 @@ import { FoamGraph } from './graph';
|
||||
import { ResourceParser } from './note';
|
||||
import { ResourceProvider } from './provider';
|
||||
import { FoamTags } from './tags';
|
||||
import { Logger } from '../utils/log';
|
||||
import { Logger, withTiming, withTimingAsync } from '../utils/log';
|
||||
|
||||
export interface Services {
|
||||
dataStore: IDataStore;
|
||||
@@ -25,25 +25,28 @@ export const bootstrap = async (
|
||||
watcher: IWatcher | undefined,
|
||||
dataStore: IDataStore,
|
||||
parser: ResourceParser,
|
||||
initialProviders: ResourceProvider[]
|
||||
initialProviders: ResourceProvider[],
|
||||
defaultExtension: string = '.md'
|
||||
) => {
|
||||
const tsStart = Date.now();
|
||||
|
||||
const workspace = await FoamWorkspace.fromProviders(
|
||||
initialProviders,
|
||||
dataStore
|
||||
const workspace = await withTimingAsync(
|
||||
() =>
|
||||
FoamWorkspace.fromProviders(
|
||||
initialProviders,
|
||||
dataStore,
|
||||
defaultExtension
|
||||
),
|
||||
ms => Logger.info(`Workspace loaded in ${ms}ms`)
|
||||
);
|
||||
|
||||
const tsWsDone = Date.now();
|
||||
Logger.info(`Workspace loaded in ${tsWsDone - tsStart}ms`);
|
||||
const graph = withTiming(
|
||||
() => FoamGraph.fromWorkspace(workspace, true),
|
||||
ms => Logger.info(`Graph loaded in ${ms}ms`)
|
||||
);
|
||||
|
||||
const graph = FoamGraph.fromWorkspace(workspace, true);
|
||||
const tsGraphDone = Date.now();
|
||||
Logger.info(`Graph loaded in ${tsGraphDone - tsWsDone}ms`);
|
||||
|
||||
const tags = FoamTags.fromWorkspace(workspace, true);
|
||||
const tsTagsEnd = Date.now();
|
||||
Logger.info(`Tags loaded in ${tsTagsEnd - tsGraphDone}ms`);
|
||||
const tags = withTiming(
|
||||
() => FoamTags.fromWorkspace(workspace, true),
|
||||
ms => Logger.info(`Tags loaded in ${ms}ms`)
|
||||
);
|
||||
|
||||
watcher?.onDidChange(async uri => {
|
||||
if (matcher.isMatch(uri)) {
|
||||
|
||||
@@ -22,12 +22,7 @@ describe('Graph', () => {
|
||||
const noteD = createTestNote({ uri: '/Page D.md' });
|
||||
const noteE = createTestNote({ uri: '/page e.md' });
|
||||
|
||||
workspace
|
||||
.set(noteA)
|
||||
.set(noteB)
|
||||
.set(noteC)
|
||||
.set(noteD)
|
||||
.set(noteE);
|
||||
workspace.set(noteA).set(noteB).set(noteC).set(noteD).set(noteE);
|
||||
const graph = FoamGraph.fromWorkspace(workspace);
|
||||
|
||||
expect(graph.getBacklinks(noteB.uri).map(l => l.source)).toEqual([
|
||||
@@ -69,9 +64,7 @@ describe('Graph', () => {
|
||||
uri: '/note-b.md',
|
||||
links: [{ to: noteA.uri.path }, { to: noteA.uri.path }],
|
||||
});
|
||||
const ws = createTestWorkspace()
|
||||
.set(noteA)
|
||||
.set(noteB);
|
||||
const ws = createTestWorkspace().set(noteA).set(noteB);
|
||||
const graph = FoamGraph.fromWorkspace(ws);
|
||||
expect(graph.getBacklinks(noteA.uri)).toEqual([
|
||||
{
|
||||
@@ -95,9 +88,7 @@ describe('Graph', () => {
|
||||
uri: '/note-b.md',
|
||||
links: [{ to: noteA.uri.path }, { to: noteA.uri.path }],
|
||||
});
|
||||
const ws = createTestWorkspace()
|
||||
.set(noteA)
|
||||
.set(noteB);
|
||||
const ws = createTestWorkspace().set(noteA).set(noteB);
|
||||
const graph = FoamGraph.fromWorkspace(ws, true);
|
||||
|
||||
expect(graph.getBacklinks(noteA.uri).length).toEqual(2);
|
||||
@@ -148,6 +139,21 @@ describe('Graph', () => {
|
||||
).toEqual(['/path/another/page-c.md', '/somewhere/page-b.md']);
|
||||
});
|
||||
|
||||
it('should create inbound connections when targeting a section', () => {
|
||||
const noteA = createTestNote({
|
||||
uri: '/path/to/page-a.md',
|
||||
links: [{ slug: 'page-b#section 2' }],
|
||||
});
|
||||
const noteB = createTestNote({
|
||||
uri: '/somewhere/page-b.md',
|
||||
text: '## Section 1\n\n## Section 2',
|
||||
});
|
||||
const ws = createTestWorkspace().set(noteA).set(noteB);
|
||||
const graph = FoamGraph.fromWorkspace(ws);
|
||||
|
||||
expect(graph.getBacklinks(noteB.uri).length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should support attachments', () => {
|
||||
const noteA = createTestNote({
|
||||
uri: '/path/to/page-a.md',
|
||||
@@ -165,9 +171,7 @@ describe('Graph', () => {
|
||||
uri: '/path/to/more/attachment-b.pdf',
|
||||
});
|
||||
const ws = createTestWorkspace();
|
||||
ws.set(noteA)
|
||||
.set(attachmentA)
|
||||
.set(attachmentB);
|
||||
ws.set(noteA).set(attachmentA).set(attachmentB);
|
||||
const graph = FoamGraph.fromWorkspace(ws);
|
||||
|
||||
expect(graph.getBacklinks(attachmentA.uri).map(l => l.source)).toEqual([
|
||||
@@ -189,9 +193,7 @@ describe('Graph', () => {
|
||||
uri: '/path/to/attachment-a.pdf',
|
||||
});
|
||||
const ws = createTestWorkspace();
|
||||
ws.set(noteA)
|
||||
.set(attachmentA)
|
||||
.set(attachmentABis);
|
||||
ws.set(noteA).set(attachmentA).set(attachmentABis);
|
||||
const graph = FoamGraph.fromWorkspace(ws);
|
||||
|
||||
expect(graph.getLinks(noteA.uri).map(l => l.target)).toEqual([
|
||||
@@ -211,9 +213,7 @@ describe('Graph', () => {
|
||||
uri: '/path/to/attachment-a.pdf',
|
||||
});
|
||||
const ws = createTestWorkspace();
|
||||
ws.set(noteA)
|
||||
.set(attachmentABis)
|
||||
.set(attachmentA);
|
||||
ws.set(noteA).set(attachmentABis).set(attachmentA);
|
||||
const graph = FoamGraph.fromWorkspace(ws);
|
||||
|
||||
expect(graph.getLinks(noteA.uri).map(l => l.target)).toEqual([
|
||||
@@ -323,9 +323,7 @@ describe('Regenerating graph after workspace changes', () => {
|
||||
uri: '/path/to/more/page-c.md',
|
||||
});
|
||||
const ws = createTestWorkspace();
|
||||
ws.set(noteA)
|
||||
.set(noteB)
|
||||
.set(noteC);
|
||||
ws.set(noteA).set(noteB).set(noteC);
|
||||
let graph = FoamGraph.fromWorkspace(ws);
|
||||
|
||||
expect(graph.getLinks(noteA.uri).map(l => l.target)).toEqual([noteB.uri]);
|
||||
@@ -512,9 +510,7 @@ describe('Updating graph on workspace state', () => {
|
||||
uri: '/path/to/more/page-c.md',
|
||||
});
|
||||
const ws = createTestWorkspace();
|
||||
ws.set(noteA)
|
||||
.set(noteB)
|
||||
.set(noteC);
|
||||
ws.set(noteA).set(noteB).set(noteC);
|
||||
const graph = FoamGraph.fromWorkspace(ws, true);
|
||||
|
||||
expect(graph.getLinks(noteA.uri).map(l => l.target)).toEqual([noteB.uri]);
|
||||
|
||||
34
packages/foam-vscode/src/core/model/location.ts
Normal file
34
packages/foam-vscode/src/core/model/location.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Range } from './range';
|
||||
import { URI } from './uri';
|
||||
|
||||
/**
|
||||
* Represents a location inside a resource, such as a line
|
||||
* inside a text file.
|
||||
*/
|
||||
export interface Location<T> {
|
||||
/**
|
||||
* The resource identifier of this location.
|
||||
*/
|
||||
uri: URI;
|
||||
/**
|
||||
* The document range of this locations.
|
||||
*/
|
||||
range: Range;
|
||||
/**
|
||||
* The data associated to this location.
|
||||
*/
|
||||
data: T;
|
||||
}
|
||||
|
||||
export abstract class Location<T> {
|
||||
static create<T>(uri: URI, range: Range, data: T): Location<T> {
|
||||
return { uri, range, data };
|
||||
}
|
||||
|
||||
static forObjectWithRange<T extends { range: Range }>(
|
||||
uri: URI,
|
||||
obj: T
|
||||
): Location<T> {
|
||||
return Location.create(uri, obj.range, obj);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ export interface ResourceLink {
|
||||
type: 'wikilink' | 'link';
|
||||
rawText: string;
|
||||
range: Range;
|
||||
isEmbed: boolean;
|
||||
}
|
||||
|
||||
export interface NoteLinkDefinition {
|
||||
@@ -14,6 +15,19 @@ export interface NoteLinkDefinition {
|
||||
range?: Range;
|
||||
}
|
||||
|
||||
export abstract class NoteLinkDefinition {
|
||||
static format(definition: NoteLinkDefinition) {
|
||||
const url =
|
||||
definition.url.indexOf(' ') > 0 ? `<${definition.url}>` : definition.url;
|
||||
let text = `[${definition.label}]: ${url}`;
|
||||
if (definition.title) {
|
||||
text = `${text} "${definition.title}"`;
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
export interface Tag {
|
||||
label: string;
|
||||
range: Range;
|
||||
@@ -52,6 +66,10 @@ export abstract class Resource {
|
||||
return a.title.localeCompare(b.title);
|
||||
}
|
||||
|
||||
public static sortByPath(a: Resource, b: Resource) {
|
||||
return a.uri.path.localeCompare(b.uri.path);
|
||||
}
|
||||
|
||||
public static isResource(thing: any): thing is Resource {
|
||||
if (!thing) {
|
||||
return false;
|
||||
|
||||
@@ -69,4 +69,8 @@ export abstract class Range {
|
||||
static isBefore(a: Range, b: Range): number {
|
||||
return a.start.line - b.start.line || a.start.character - b.start.character;
|
||||
}
|
||||
|
||||
static toString(range: Range): string {
|
||||
return `${range.start.line}:${range.start.character} - ${range.end.line}:${range.end.character}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@ describe('Foam URI', () => {
|
||||
const base = URI.file('/path/to/file.md');
|
||||
test.each([
|
||||
['https://www.google.com', URI.parse('https://www.google.com')],
|
||||
['/path/to/a/file.md', URI.file('/path/to/a/file.md')],
|
||||
['../relative/file.md', URI.file('/path/relative/file.md')],
|
||||
['#section', base.withFragment('section')],
|
||||
['/path/to/a/file.md', URI.parse('file:///path/to/a/file.md')],
|
||||
['../relative/file.md', URI.parse('file:///path/relative/file.md')],
|
||||
['#section', base.with({ fragment: 'section' })],
|
||||
[
|
||||
'../relative/file.md#section',
|
||||
URI.parse('file:/path/relative/file.md#section'),
|
||||
|
||||
@@ -26,7 +26,8 @@ import * as pathUtils from '../utils/path';
|
||||
|
||||
const _empty = '';
|
||||
const _slash = '/';
|
||||
const _regexp = /^(([^:/?#]{2,}?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
|
||||
const _regexp =
|
||||
/^(([^:/?#]{2,}?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
|
||||
|
||||
export class URI {
|
||||
readonly scheme: string;
|
||||
@@ -57,6 +58,11 @@ export class URI {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Will not work with web extension. Use only for testing.
|
||||
* @param value the path to turn into a URI
|
||||
* @returns the file URI
|
||||
*/
|
||||
static file(value: string): URI {
|
||||
const [path, authority] = pathUtils.fromFsPath(value);
|
||||
return new URI({ scheme: 'file', authority, path });
|
||||
@@ -70,7 +76,7 @@ export class URI {
|
||||
const uri = value instanceof URI ? value : URI.parse(value);
|
||||
if (!uri.isAbsolute()) {
|
||||
if (uri.scheme === 'file' || uri.scheme === 'placeholder') {
|
||||
let newUri = this.withFragment(uri.fragment);
|
||||
let newUri = this.with({ fragment: uri.fragment });
|
||||
if (uri.path) {
|
||||
newUri = (isDirectory ? newUri : newUri.getDirectory())
|
||||
.joinPath(uri.path)
|
||||
@@ -118,8 +124,20 @@ export class URI {
|
||||
return new URI({ ...this, path });
|
||||
}
|
||||
|
||||
withFragment(fragment: string): URI {
|
||||
return new URI({ ...this, fragment });
|
||||
with(change: {
|
||||
scheme?: string;
|
||||
authority?: string;
|
||||
path?: string;
|
||||
query?: string;
|
||||
fragment?: string;
|
||||
}): URI {
|
||||
return new URI({
|
||||
scheme: change.scheme ?? this.scheme,
|
||||
authority: change.authority ?? this.authority,
|
||||
path: change.path ?? this.path,
|
||||
query: change.query ?? this.query,
|
||||
fragment: change.fragment ?? this.fragment,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -194,7 +212,6 @@ function encode(uri: URI, skipEncoding: boolean): string {
|
||||
: encodeURIComponentMinimal;
|
||||
|
||||
let res = '';
|
||||
// eslint-disable-next-line prefer-const
|
||||
let { scheme, authority, path, query, fragment } = uri;
|
||||
if (scheme) {
|
||||
res += scheme;
|
||||
@@ -380,11 +397,30 @@ function encodeURIComponentMinimal(path: string): string {
|
||||
*
|
||||
* TODO this probably needs to be moved to the workspace service
|
||||
*/
|
||||
export function asAbsoluteUri(uri: URI, baseFolders: URI[]): URI {
|
||||
return URI.file(
|
||||
pathUtils.asAbsolutePaths(
|
||||
uri.path,
|
||||
baseFolders.map(f => f.path)
|
||||
)[0]
|
||||
);
|
||||
export function asAbsoluteUri(
|
||||
uriOrPath: URI | string,
|
||||
baseFolders: URI[]
|
||||
): URI {
|
||||
if (baseFolders.length === 0) {
|
||||
throw new Error('At least one base folder needed to compute URI');
|
||||
}
|
||||
const path = uriOrPath instanceof URI ? uriOrPath.path : uriOrPath;
|
||||
if (path.startsWith('/')) {
|
||||
return uriOrPath instanceof URI ? uriOrPath : baseFolders[0].with({ path });
|
||||
}
|
||||
let tokens = path.split('/');
|
||||
while (tokens[0].trim() === '') {
|
||||
tokens.shift();
|
||||
}
|
||||
const firstDir = tokens[0];
|
||||
if (baseFolders.length > 1) {
|
||||
for (const folder of baseFolders) {
|
||||
const lastDir = folder.path.split('/').pop();
|
||||
if (lastDir === firstDir) {
|
||||
tokens = tokens.slice(1);
|
||||
return folder.joinPath(...tokens);
|
||||
}
|
||||
}
|
||||
}
|
||||
return baseFolders[0].joinPath(...tokens);
|
||||
}
|
||||
|
||||
@@ -87,6 +87,13 @@ describe('Workspace resources', () => {
|
||||
const res = ws.find('test-file#my-section');
|
||||
expect(res.uri.fragment).toEqual('my-section');
|
||||
});
|
||||
|
||||
it('should find absolute files even when no basedir is provided', () => {
|
||||
const noteA = createTestNote({ uri: '/a/path/to/file.md' });
|
||||
const ws = createTestWorkspace().set(noteA);
|
||||
|
||||
expect(ws.find('/a/path/to/file.md').uri.path).toEqual(noteA.uri.path);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Identifier computation', () => {
|
||||
@@ -100,10 +107,7 @@ describe('Identifier computation', () => {
|
||||
const third = createTestNote({
|
||||
uri: '/another/path/for/page-a.md',
|
||||
});
|
||||
const ws = new FoamWorkspace()
|
||||
.set(first)
|
||||
.set(second)
|
||||
.set(third);
|
||||
const ws = new FoamWorkspace('.md').set(first).set(second).set(third);
|
||||
|
||||
expect(ws.getIdentifier(first.uri)).toEqual('to/page-a');
|
||||
expect(ws.getIdentifier(second.uri)).toEqual('way/for/page-a');
|
||||
@@ -120,14 +124,11 @@ describe('Identifier computation', () => {
|
||||
const third = createTestNote({
|
||||
uri: '/another/path/for/page-a.md',
|
||||
});
|
||||
const ws = new FoamWorkspace()
|
||||
.set(first)
|
||||
.set(second)
|
||||
.set(third);
|
||||
const ws = new FoamWorkspace('.md').set(first).set(second).set(third);
|
||||
|
||||
expect(ws.getIdentifier(first.uri.withFragment('section name'))).toEqual(
|
||||
'to/page-a#section name'
|
||||
);
|
||||
expect(
|
||||
ws.getIdentifier(first.uri.with({ fragment: 'section name' }))
|
||||
).toEqual('to/page-a#section name');
|
||||
});
|
||||
|
||||
const needle = '/project/car/todo';
|
||||
@@ -169,18 +170,14 @@ describe('Identifier computation', () => {
|
||||
});
|
||||
|
||||
it('should ignore elements from the exclude list', () => {
|
||||
const workspace = new FoamWorkspace();
|
||||
const workspace = new FoamWorkspace('.md');
|
||||
const noteA = createTestNote({ uri: '/path/to/note-a.md' });
|
||||
const noteB = createTestNote({ uri: '/path/to/note-b.md' });
|
||||
const noteC = createTestNote({ uri: '/path/to/note-c.md' });
|
||||
const noteD = createTestNote({ uri: '/path/to/note-d.md' });
|
||||
const noteABis = createTestNote({ uri: '/path/to/another/note-a.md' });
|
||||
|
||||
workspace
|
||||
.set(noteA)
|
||||
.set(noteB)
|
||||
.set(noteC)
|
||||
.set(noteD);
|
||||
workspace.set(noteA).set(noteB).set(noteC).set(noteD);
|
||||
expect(workspace.getIdentifier(noteABis.uri)).toEqual('another/note-a');
|
||||
expect(
|
||||
workspace.getIdentifier(noteABis.uri, [noteB.uri, noteA.uri])
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Emitter } from '../common/event';
|
||||
import { ResourceProvider } from './provider';
|
||||
import { IDisposable } from '../common/lifecycle';
|
||||
import { IDataStore } from '../services/datastore';
|
||||
import TrieMap from 'mnemonist/trie-map';
|
||||
|
||||
export class FoamWorkspace implements IDisposable {
|
||||
private onDidAddEmitter = new Emitter<Resource>();
|
||||
@@ -20,7 +21,12 @@ export class FoamWorkspace implements IDisposable {
|
||||
/**
|
||||
* Resources by path
|
||||
*/
|
||||
private _resources: Map<string, Resource> = new Map();
|
||||
private _resources: TrieMap<string, Resource> = new TrieMap();
|
||||
|
||||
/**
|
||||
* @param defaultExtension: The default extension for notes in this workspace (e.g. `.md`)
|
||||
*/
|
||||
constructor(public defaultExtension: string = '.md') {}
|
||||
|
||||
registerProvider(provider: ResourceProvider) {
|
||||
this.providers.push(provider);
|
||||
@@ -28,7 +34,10 @@ export class FoamWorkspace implements IDisposable {
|
||||
|
||||
set(resource: Resource) {
|
||||
const old = this.find(resource.uri);
|
||||
this._resources.set(normalize(resource.uri.path), resource);
|
||||
|
||||
// store resource
|
||||
this._resources.set(this.getTrieIdentifier(resource.uri.path), resource);
|
||||
|
||||
isSome(old)
|
||||
? this.onDidUpdateEmitter.fire({ old: old, new: resource })
|
||||
: this.onDidAddEmitter.fire(resource);
|
||||
@@ -36,8 +45,8 @@ export class FoamWorkspace implements IDisposable {
|
||||
}
|
||||
|
||||
delete(uri: URI) {
|
||||
const deleted = this._resources.get(normalize(uri.path));
|
||||
this._resources.delete(normalize(uri.path));
|
||||
const deleted = this._resources.get(this.getTrieIdentifier(uri));
|
||||
this._resources.delete(this.getTrieIdentifier(uri));
|
||||
|
||||
isSome(deleted) && this.onDidDeleteEmitter.fire(deleted);
|
||||
return deleted ?? null;
|
||||
@@ -52,7 +61,11 @@ export class FoamWorkspace implements IDisposable {
|
||||
}
|
||||
|
||||
public resources(): IterableIterator<Resource> {
|
||||
return this._resources.values();
|
||||
const resources: Array<Resource> = Array.from(
|
||||
this._resources.values()
|
||||
).sort(Resource.sortByPath);
|
||||
|
||||
return resources.values();
|
||||
}
|
||||
|
||||
public get(uri: URI): Resource {
|
||||
@@ -65,16 +78,22 @@ export class FoamWorkspace implements IDisposable {
|
||||
}
|
||||
|
||||
public listByIdentifier(identifier: string): Resource[] {
|
||||
const needle = normalize('/' + identifier);
|
||||
let needle = this.getTrieIdentifier(identifier);
|
||||
|
||||
const mdNeedle =
|
||||
getExtension(needle) !== '.md' ? needle + '.md' : undefined;
|
||||
const resources = [];
|
||||
for (const key of this._resources.keys()) {
|
||||
if ((mdNeedle && key.endsWith(mdNeedle)) || key.endsWith(needle)) {
|
||||
resources.push(this._resources.get(normalize(key)));
|
||||
}
|
||||
getExtension(normalize(identifier)) !== this.defaultExtension
|
||||
? this.getTrieIdentifier(identifier + this.defaultExtension)
|
||||
: undefined;
|
||||
|
||||
const resources: Resource[] = [];
|
||||
|
||||
this._resources.find(needle).forEach(elm => resources.push(elm[1]));
|
||||
|
||||
if (mdNeedle) {
|
||||
this._resources.find(mdNeedle).forEach(elm => resources.push(elm[1]));
|
||||
}
|
||||
return resources.sort((a, b) => a.uri.path.localeCompare(b.uri.path));
|
||||
|
||||
return resources.sort(Resource.sortByPath);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,61 +104,86 @@ export class FoamWorkspace implements IDisposable {
|
||||
public getIdentifier(forResource: URI, exclude?: URI[]): string {
|
||||
const amongst = [];
|
||||
const basename = forResource.getBasename();
|
||||
for (const res of this._resources.values()) {
|
||||
// skip elements that cannot possibly match
|
||||
if (!res.uri.path.endsWith(basename)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.listByIdentifier(basename).map(res => {
|
||||
// skip self
|
||||
if (res.uri.isEqual(forResource)) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
// skip exclude list
|
||||
if (exclude && exclude.find(ex => ex.isEqual(res.uri))) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
amongst.push(res.uri);
|
||||
}
|
||||
});
|
||||
|
||||
let identifier = FoamWorkspace.getShortestIdentifier(
|
||||
forResource.path,
|
||||
amongst.map(uri => uri.path)
|
||||
);
|
||||
identifier = changeExtension(identifier, '.md', '');
|
||||
identifier = changeExtension(identifier, this.defaultExtension, '');
|
||||
if (forResource.fragment) {
|
||||
identifier += `#${forResource.fragment}`;
|
||||
}
|
||||
return identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a note identifier in reversed order. Used to optimise the storage of notes in
|
||||
* the workspace to optimise retrieval of notes.
|
||||
*
|
||||
* @param reference the URI path to reverse
|
||||
*/
|
||||
private getTrieIdentifier(reference: URI | string): string {
|
||||
let path: string;
|
||||
if (reference instanceof URI) {
|
||||
path = (reference as URI).path;
|
||||
} else {
|
||||
path = reference as string;
|
||||
}
|
||||
|
||||
let reversedPath = normalize(path).split('/').reverse().join('/');
|
||||
|
||||
if (reversedPath.indexOf('/') < 0) {
|
||||
reversedPath = reversedPath + '/';
|
||||
}
|
||||
|
||||
return reversedPath;
|
||||
}
|
||||
|
||||
public find(reference: URI | string, baseUri?: URI): Resource | null {
|
||||
if (reference instanceof URI) {
|
||||
return this._resources.get(normalize((reference as URI).path)) ?? null;
|
||||
return this._resources.get(this.getTrieIdentifier(reference)) ?? null;
|
||||
}
|
||||
let resource: Resource | null = null;
|
||||
const [path, fragment] = (reference as string).split('#');
|
||||
if (FoamWorkspace.isIdentifier(path)) {
|
||||
resource = this.listByIdentifier(path)[0];
|
||||
} else {
|
||||
if (isAbsolute(path) || isSome(baseUri)) {
|
||||
if (getExtension(path) !== '.md') {
|
||||
const uri = baseUri.resolve(path + '.md');
|
||||
resource = uri ? this._resources.get(normalize(uri.path)) : null;
|
||||
}
|
||||
if (!resource) {
|
||||
const uri = baseUri.resolve(path);
|
||||
resource = uri ? this._resources.get(normalize(uri.path)) : null;
|
||||
const candidates = [path, path + this.defaultExtension];
|
||||
for (const candidate of candidates) {
|
||||
const searchKey = isAbsolute(candidate)
|
||||
? candidate
|
||||
: isSome(baseUri)
|
||||
? baseUri.resolve(candidate).path
|
||||
: null;
|
||||
resource = this._resources.get(this.getTrieIdentifier(searchKey));
|
||||
if (resource) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resource && fragment) {
|
||||
resource = { ...resource, uri: resource.uri.withFragment(fragment) };
|
||||
resource = {
|
||||
...resource,
|
||||
uri: resource.uri.with({ fragment: fragment }),
|
||||
};
|
||||
}
|
||||
return resource ?? null;
|
||||
}
|
||||
|
||||
public resolveLink(resource: Resource, link: ResourceLink): URI {
|
||||
// TODO add tests
|
||||
for (const provider of this.providers) {
|
||||
if (provider.supports(resource.uri)) {
|
||||
return provider.resolveLink(this, resource, link);
|
||||
@@ -235,9 +279,10 @@ export class FoamWorkspace implements IDisposable {
|
||||
|
||||
static async fromProviders(
|
||||
providers: ResourceProvider[],
|
||||
dataStore: IDataStore
|
||||
dataStore: IDataStore,
|
||||
defaultExtension: string = '.md'
|
||||
): Promise<FoamWorkspace> {
|
||||
const workspace = new FoamWorkspace();
|
||||
const workspace = new FoamWorkspace(defaultExtension);
|
||||
await Promise.all(providers.map(p => workspace.registerProvider(p)));
|
||||
const files = await dataStore.list();
|
||||
await Promise.all(files.map(f => workspace.fetchAndSet(f)));
|
||||
|
||||
@@ -3,17 +3,15 @@ import { URI } from '../model/uri';
|
||||
import { FoamWorkspace } from '../model/workspace';
|
||||
import { IDisposable } from '../common/lifecycle';
|
||||
import { ResourceProvider } from '../model/provider';
|
||||
import { getFoamVsCodeConfig } from '../../services/config';
|
||||
|
||||
const attachmentExtConfig = getFoamVsCodeConfig(
|
||||
'files.attachmentExtensions',
|
||||
''
|
||||
)
|
||||
.split(' ')
|
||||
.map(ext => '.' + ext.trim());
|
||||
|
||||
const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp'];
|
||||
const attachmentExtensions = [...attachmentExtConfig, ...imageExtensions];
|
||||
export const imageExtensions = [
|
||||
'.png',
|
||||
'.jpg',
|
||||
'.jpeg',
|
||||
'.gif',
|
||||
'.svg',
|
||||
'.webp',
|
||||
];
|
||||
|
||||
const asResource = (uri: URI): Resource => {
|
||||
const type = imageExtensions.includes(uri.getExtension())
|
||||
@@ -34,9 +32,14 @@ const asResource = (uri: URI): Resource => {
|
||||
|
||||
export class AttachmentResourceProvider implements ResourceProvider {
|
||||
private disposables: IDisposable[] = [];
|
||||
public readonly attachmentExtensions: string[];
|
||||
|
||||
constructor(attachmentExtensions: string[] = []) {
|
||||
this.attachmentExtensions = [...imageExtensions, ...attachmentExtensions];
|
||||
}
|
||||
|
||||
supports(uri: URI) {
|
||||
return attachmentExtensions.includes(
|
||||
return this.attachmentExtensions.includes(
|
||||
uri.getExtension().toLocaleLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ export class GenericDataStore implements IDataStore {
|
||||
/**
|
||||
* A matcher that instead of using globs uses a list of files to
|
||||
* check the matches.
|
||||
* The {@link refresh} function has been added to the interface to accomodate
|
||||
* The {@link refresh} function has been added to the interface to accommodate
|
||||
* this matcher, far from ideal but to be refactored later
|
||||
*/
|
||||
export class FileListBasedMatcher implements IMatcher {
|
||||
|
||||
@@ -107,6 +107,7 @@ describe('MarkdownLink', () => {
|
||||
type: 'link',
|
||||
rawText: '[link](#section)',
|
||||
range: Range.create(0, 0),
|
||||
isEmbed: false,
|
||||
};
|
||||
const parsed = MarkdownLink.analyzeLink(link);
|
||||
expect(parsed.target).toEqual('');
|
||||
@@ -161,7 +162,7 @@ describe('MarkdownLink', () => {
|
||||
target: 'new-link',
|
||||
});
|
||||
expect(edit.newText).toEqual(`[[new-link#section]]`);
|
||||
expect(edit.selection).toEqual(link.range);
|
||||
expect(edit.range).toEqual(link.range);
|
||||
});
|
||||
it('should rename the section only', () => {
|
||||
const link = parser.parse(
|
||||
@@ -172,7 +173,7 @@ describe('MarkdownLink', () => {
|
||||
section: 'new-section',
|
||||
});
|
||||
expect(edit.newText).toEqual(`[[wikilink#new-section]]`);
|
||||
expect(edit.selection).toEqual(link.range);
|
||||
expect(edit.range).toEqual(link.range);
|
||||
});
|
||||
it('should rename both target and section', () => {
|
||||
const link = parser.parse(
|
||||
@@ -184,7 +185,7 @@ describe('MarkdownLink', () => {
|
||||
section: 'new-section',
|
||||
});
|
||||
expect(edit.newText).toEqual(`[[new-link#new-section]]`);
|
||||
expect(edit.selection).toEqual(link.range);
|
||||
expect(edit.range).toEqual(link.range);
|
||||
});
|
||||
it('should be able to remove the section', () => {
|
||||
const link = parser.parse(
|
||||
@@ -195,7 +196,7 @@ describe('MarkdownLink', () => {
|
||||
section: '',
|
||||
});
|
||||
expect(edit.newText).toEqual(`[[wikilink]]`);
|
||||
expect(edit.selection).toEqual(link.range);
|
||||
expect(edit.range).toEqual(link.range);
|
||||
});
|
||||
it('should be able to rename the alias', () => {
|
||||
const link = parser.parse(getRandomURI(), `this is a [[wikilink|alias]]`)
|
||||
@@ -204,7 +205,7 @@ describe('MarkdownLink', () => {
|
||||
alias: 'new-alias',
|
||||
});
|
||||
expect(edit.newText).toEqual(`[[wikilink|new-alias]]`);
|
||||
expect(edit.selection).toEqual(link.range);
|
||||
expect(edit.range).toEqual(link.range);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -216,7 +217,7 @@ describe('MarkdownLink', () => {
|
||||
target: 'to/another-path.md',
|
||||
});
|
||||
expect(edit.newText).toEqual(`[link](to/another-path.md)`);
|
||||
expect(edit.selection).toEqual(link.range);
|
||||
expect(edit.range).toEqual(link.range);
|
||||
});
|
||||
it('should rename the section only', () => {
|
||||
const link = parser.parse(
|
||||
@@ -227,7 +228,7 @@ describe('MarkdownLink', () => {
|
||||
section: 'section2',
|
||||
});
|
||||
expect(edit.newText).toEqual(`[link](to/path.md#section2)`);
|
||||
expect(edit.selection).toEqual(link.range);
|
||||
expect(edit.range).toEqual(link.range);
|
||||
});
|
||||
it('should rename both target and section', () => {
|
||||
const link = parser.parse(
|
||||
@@ -239,7 +240,7 @@ describe('MarkdownLink', () => {
|
||||
section: 'section2',
|
||||
});
|
||||
expect(edit.newText).toEqual(`[link](to/another-path.md#section2)`);
|
||||
expect(edit.selection).toEqual(link.range);
|
||||
expect(edit.range).toEqual(link.range);
|
||||
});
|
||||
it('should be able to remove the section', () => {
|
||||
const link = parser.parse(
|
||||
@@ -250,7 +251,188 @@ describe('MarkdownLink', () => {
|
||||
section: '',
|
||||
});
|
||||
expect(edit.newText).toEqual(`[link](to/path.md)`);
|
||||
expect(edit.selection).toEqual(link.range);
|
||||
expect(edit.range).toEqual(link.range);
|
||||
});
|
||||
});
|
||||
|
||||
describe('convert wikilink to link', () => {
|
||||
it('should generate default alias if no one', () => {
|
||||
const wikilink = parser.parse(getRandomURI(), `[[wikilink]]`).links[0];
|
||||
const wikilinkEdit = MarkdownLink.createUpdateLinkEdit(wikilink, {
|
||||
type: 'link',
|
||||
});
|
||||
expect(wikilinkEdit.newText).toEqual(`[wikilink](wikilink)`);
|
||||
expect(wikilinkEdit.range).toEqual(wikilink.range);
|
||||
|
||||
const wikilinkWithSection = parser.parse(
|
||||
getRandomURI(),
|
||||
`[[wikilink#section]]`
|
||||
).links[0];
|
||||
const wikilinkWithSectionEdit = MarkdownLink.createUpdateLinkEdit(
|
||||
wikilinkWithSection,
|
||||
{
|
||||
type: 'link',
|
||||
}
|
||||
);
|
||||
expect(wikilinkWithSectionEdit.newText).toEqual(
|
||||
`[wikilink#section](wikilink#section)`
|
||||
);
|
||||
expect(wikilinkWithSectionEdit.range).toEqual(wikilinkWithSection.range);
|
||||
});
|
||||
|
||||
it('should use alias in the wikilik the if there has one', () => {
|
||||
const wikilink = parser.parse(
|
||||
getRandomURI(),
|
||||
`[[wikilink#section|alias]]`
|
||||
).links[0];
|
||||
const wikilinkEdit = MarkdownLink.createUpdateLinkEdit(wikilink, {
|
||||
type: 'link',
|
||||
});
|
||||
expect(wikilinkEdit.newText).toEqual(`[alias](wikilink#section)`);
|
||||
expect(wikilinkEdit.range).toEqual(wikilink.range);
|
||||
});
|
||||
});
|
||||
|
||||
describe('convert link to wikilink', () => {
|
||||
it('should reorganize target, section, and alias in wikilink manner', () => {
|
||||
const link = parser.parse(getRandomURI(), `[link](to/path.md)`).links[0];
|
||||
const linkEdit = MarkdownLink.createUpdateLinkEdit(link, {
|
||||
type: 'wikilink',
|
||||
});
|
||||
expect(linkEdit.newText).toEqual(`[[to/path.md|link]]`);
|
||||
expect(linkEdit.range).toEqual(link.range);
|
||||
|
||||
const linkWithSection = parser.parse(
|
||||
getRandomURI(),
|
||||
`[link](to/path.md#section)`
|
||||
).links[0];
|
||||
const linkWithSectionEdit = MarkdownLink.createUpdateLinkEdit(
|
||||
linkWithSection,
|
||||
{
|
||||
type: 'wikilink',
|
||||
}
|
||||
);
|
||||
expect(linkWithSectionEdit.newText).toEqual(
|
||||
`[[to/path.md#section|link]]`
|
||||
);
|
||||
expect(linkWithSectionEdit.range).toEqual(linkWithSection.range);
|
||||
});
|
||||
|
||||
it('should use alias in the wikilik the if there has one', () => {
|
||||
const wikilink = parser.parse(
|
||||
getRandomURI(),
|
||||
`[[wikilink#section|alias]]`
|
||||
).links[0];
|
||||
const wikilinkEdit = MarkdownLink.createUpdateLinkEdit(wikilink, {
|
||||
type: 'link',
|
||||
});
|
||||
expect(wikilinkEdit.newText).toEqual(`[alias](wikilink#section)`);
|
||||
expect(wikilinkEdit.range).toEqual(wikilink.range);
|
||||
});
|
||||
});
|
||||
|
||||
describe('convert to its original type', () => {
|
||||
it('should remain unchanged', () => {
|
||||
const link = parser.parse(getRandomURI(), `[link](to/path.md#section)`)
|
||||
.links[0];
|
||||
const linkEdit = MarkdownLink.createUpdateLinkEdit(link, {
|
||||
type: 'link',
|
||||
});
|
||||
expect(linkEdit.newText).toEqual(`[link](to/path.md#section)`);
|
||||
expect(linkEdit.range).toEqual(link.range);
|
||||
|
||||
const wikilink = parser.parse(
|
||||
getRandomURI(),
|
||||
`[[wikilink#section|alias]]`
|
||||
).links[0];
|
||||
const wikilinkEdit = MarkdownLink.createUpdateLinkEdit(wikilink, {
|
||||
type: 'wikilink',
|
||||
});
|
||||
expect(wikilinkEdit.newText).toEqual(`[[wikilink#section|alias]]`);
|
||||
expect(wikilinkEdit.range).toEqual(wikilink.range);
|
||||
});
|
||||
});
|
||||
|
||||
describe('change isEmbed property', () => {
|
||||
it('should change isEmbed only', () => {
|
||||
const wikilink = parser.parse(getRandomURI(), `[[wikilink]]`).links[0];
|
||||
const wikilinkEdit = MarkdownLink.createUpdateLinkEdit(wikilink, {
|
||||
isEmbed: true,
|
||||
});
|
||||
expect(wikilinkEdit.newText).toEqual(`![[wikilink]]`);
|
||||
expect(wikilinkEdit.range).toEqual(wikilink.range);
|
||||
|
||||
const link = parser.parse(getRandomURI(), ``).links[0];
|
||||
const linkEdit = MarkdownLink.createUpdateLinkEdit(link, {
|
||||
isEmbed: false,
|
||||
});
|
||||
expect(linkEdit.newText).toEqual(`[link](to/path.md)`);
|
||||
expect(linkEdit.range).toEqual(link.range);
|
||||
});
|
||||
|
||||
it('should be unchanged if the update value is the same as the original one', () => {
|
||||
const embeddedWikilink = parser.parse(getRandomURI(), `![[wikilink]]`)
|
||||
.links[0];
|
||||
const embeddedWikilinkEdit = MarkdownLink.createUpdateLinkEdit(
|
||||
embeddedWikilink,
|
||||
{
|
||||
isEmbed: true,
|
||||
}
|
||||
);
|
||||
expect(embeddedWikilinkEdit.newText).toEqual(`![[wikilink]]`);
|
||||
expect(embeddedWikilinkEdit.range).toEqual(embeddedWikilink.range);
|
||||
|
||||
const link = parser.parse(getRandomURI(), `[link](to/path.md)`).links[0];
|
||||
const linkEdit = MarkdownLink.createUpdateLinkEdit(link, {
|
||||
isEmbed: false,
|
||||
});
|
||||
expect(linkEdit.newText).toEqual(`[link](to/path.md)`);
|
||||
expect(linkEdit.range).toEqual(link.range);
|
||||
});
|
||||
});
|
||||
|
||||
describe('insert angles', () => {
|
||||
it('should insert angles when meeting space in links', () => {
|
||||
const link = parser.parse(getRandomURI(), ``).links[0];
|
||||
const linkAddSection = MarkdownLink.createUpdateLinkEdit(link, {
|
||||
section: 'one section',
|
||||
});
|
||||
expect(linkAddSection.newText).toEqual(
|
||||
``
|
||||
);
|
||||
expect(linkAddSection.range).toEqual(link.range);
|
||||
|
||||
const linkChangingTarget = parser.parse(
|
||||
getRandomURI(),
|
||||
`[link](to/path.md#one-section)`
|
||||
).links[0];
|
||||
const linkEdit = MarkdownLink.createUpdateLinkEdit(linkChangingTarget, {
|
||||
target: 'to/another path.md',
|
||||
});
|
||||
expect(linkEdit.newText).toEqual(
|
||||
`[link](<to/another path.md#one-section>)`
|
||||
);
|
||||
expect(linkEdit.range).toEqual(linkChangingTarget.range);
|
||||
|
||||
const wikilink = parser.parse(getRandomURI(), `[[wikilink#one section]]`)
|
||||
.links[0];
|
||||
const wikilinkEdit = MarkdownLink.createUpdateLinkEdit(wikilink, {
|
||||
type: 'link',
|
||||
});
|
||||
expect(wikilinkEdit.newText).toEqual(
|
||||
`[wikilink#one section](<wikilink#one section>)`
|
||||
);
|
||||
expect(wikilinkEdit.range).toEqual(wikilink.range);
|
||||
});
|
||||
|
||||
it('should not insert angles in wikilink', () => {
|
||||
const wikilink = parser.parse(getRandomURI(), `[[wikilink#one section]]`)
|
||||
.links[0];
|
||||
const wikilinkEdit = MarkdownLink.createUpdateLinkEdit(wikilink, {
|
||||
target: 'another wikilink',
|
||||
});
|
||||
expect(wikilinkEdit.newText).toEqual(`[[another wikilink#one section]]`);
|
||||
expect(wikilinkEdit.range).toEqual(wikilink.range);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -38,7 +38,13 @@ export abstract class MarkdownLink {
|
||||
|
||||
public static createUpdateLinkEdit(
|
||||
link: ResourceLink,
|
||||
delta: { target?: string; section?: string; alias?: string }
|
||||
delta: {
|
||||
target?: string;
|
||||
section?: string;
|
||||
alias?: string;
|
||||
type?: 'wikilink' | 'link';
|
||||
isEmbed?: boolean;
|
||||
}
|
||||
) {
|
||||
const { target, section, alias } = MarkdownLink.analyzeLink(link);
|
||||
const newTarget = delta.target ?? target;
|
||||
@@ -46,20 +52,27 @@ export abstract class MarkdownLink {
|
||||
const newAlias = delta.alias ?? alias ?? '';
|
||||
const sectionDivider = newSection ? '#' : '';
|
||||
const aliasDivider = newAlias ? '|' : '';
|
||||
if (link.type === 'wikilink') {
|
||||
const embed = delta.isEmbed ?? link.isEmbed ? '!' : '';
|
||||
const type = delta.type ?? link.type;
|
||||
if (type === 'wikilink') {
|
||||
return {
|
||||
newText: `[[${newTarget}${sectionDivider}${newSection}${aliasDivider}${newAlias}]]`,
|
||||
selection: link.range,
|
||||
newText: `${embed}[[${newTarget}${sectionDivider}${newSection}${aliasDivider}${newAlias}]]`,
|
||||
range: link.range,
|
||||
};
|
||||
}
|
||||
if (link.type === 'link') {
|
||||
if (type === 'link') {
|
||||
const defaultAlias = () => {
|
||||
return `${newTarget}${sectionDivider}${newSection}`;
|
||||
};
|
||||
const useAngles =
|
||||
newTarget.indexOf(' ') > 0 || newSection.indexOf(' ') > 0;
|
||||
return {
|
||||
newText: `[${newAlias}](${newTarget}${sectionDivider}${newSection})`,
|
||||
selection: link.range,
|
||||
newText: `${embed}[${newAlias ? newAlias : defaultAlias()}](${
|
||||
useAngles ? '<' : ''
|
||||
}${newTarget}${sectionDivider}${newSection}${useAngles ? '>' : ''})`,
|
||||
range: link.range,
|
||||
};
|
||||
}
|
||||
throw new Error(
|
||||
`Unexpected state: link of type ${link.type} is not supported`
|
||||
);
|
||||
throw new Error(`Unexpected state: link of type ${type} is not supported`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { createMarkdownParser, ParserPlugin } from './markdown-parser';
|
||||
import {
|
||||
createMarkdownParser,
|
||||
getBlockFor,
|
||||
ParserPlugin,
|
||||
} from './markdown-parser';
|
||||
import { Logger } from '../utils/log';
|
||||
import { URI } from '../model/uri';
|
||||
import { Range } from '../model/range';
|
||||
import { getRandomURI } from '../../test/test-utils';
|
||||
import { Position } from '../model/position';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
@@ -39,6 +44,7 @@ describe('Markdown parsing', () => {
|
||||
const link = note.links[0];
|
||||
expect(link.type).toEqual('link');
|
||||
expect(link.rawText).toEqual('[link to page b](../doc/page-b.md)');
|
||||
expect(link.isEmbed).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should detect links that have formatting in label', () => {
|
||||
@@ -48,6 +54,15 @@ describe('Markdown parsing', () => {
|
||||
expect(note.links.length).toEqual(1);
|
||||
const link = note.links[0];
|
||||
expect(link.type).toEqual('link');
|
||||
expect(link.isEmbed).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should detect embed links', () => {
|
||||
const note = createNoteFromMarkdown('this is ');
|
||||
expect(note.links.length).toEqual(1);
|
||||
const link = note.links[0];
|
||||
expect(link.type).toEqual('link');
|
||||
expect(link.isEmbed).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should detect wikilinks', () => {
|
||||
@@ -61,6 +76,16 @@ describe('Markdown parsing', () => {
|
||||
link = note.links[1];
|
||||
expect(link.type).toEqual('wikilink');
|
||||
expect(link.rawText).toEqual('[[a file]]');
|
||||
expect(link.isEmbed).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should detect wikilink embeds', () => {
|
||||
const note = createNoteFromMarkdown('Some content and ![[an embed]]');
|
||||
expect(note.links.length).toEqual(1);
|
||||
const link = note.links[0];
|
||||
expect(link.type).toEqual('wikilink');
|
||||
expect(link.rawText).toEqual('![[an embed]]');
|
||||
expect(link.isEmbed).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should detect wikilinks that have aliases', () => {
|
||||
@@ -74,6 +99,7 @@ describe('Markdown parsing', () => {
|
||||
link = note.links[1];
|
||||
expect(link.type).toEqual('wikilink');
|
||||
expect(link.rawText).toEqual('[[other link | spaced]]');
|
||||
expect(link.isEmbed).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should skip wikilinks in codeblocks', () => {
|
||||
@@ -294,20 +320,55 @@ this is some #text that includes #tags we #care-about.
|
||||
]);
|
||||
});
|
||||
|
||||
it('provides rough range for tags in yaml', () => {
|
||||
it('provides a specific range for tags in yaml', () => {
|
||||
// For now it's enough to just get the YAML block range
|
||||
// in the future we might want to be more specific
|
||||
|
||||
const noteA = createNoteFromMarkdown(`
|
||||
---
|
||||
prop: hello world
|
||||
tags: [hello, world, this_is_good]
|
||||
another: i love the world
|
||||
---
|
||||
# this is a heading
|
||||
this is some text
|
||||
`);
|
||||
expect(noteA.tags[0]).toEqual({
|
||||
label: 'hello',
|
||||
range: Range.create(1, 0, 3, 3),
|
||||
range: Range.create(3, 7, 3, 12),
|
||||
});
|
||||
expect(noteA.tags[1]).toEqual({
|
||||
label: 'world',
|
||||
range: Range.create(3, 14, 3, 19),
|
||||
});
|
||||
expect(noteA.tags[2]).toEqual({
|
||||
label: 'this_is_good',
|
||||
range: Range.create(3, 21, 3, 33),
|
||||
});
|
||||
|
||||
const noteB = createNoteFromMarkdown(`
|
||||
---
|
||||
prop: hello world
|
||||
tags:
|
||||
- hello
|
||||
- world
|
||||
- this_is_good
|
||||
another: i love the world
|
||||
---
|
||||
# this is a heading
|
||||
this is some text
|
||||
`);
|
||||
expect(noteB.tags[0]).toEqual({
|
||||
label: 'hello',
|
||||
range: Range.create(4, 2, 4, 7),
|
||||
});
|
||||
expect(noteB.tags[1]).toEqual({
|
||||
label: 'world',
|
||||
range: Range.create(5, 2, 5, 7),
|
||||
});
|
||||
expect(noteB.tags[2]).toEqual({
|
||||
label: 'this_is_good',
|
||||
range: Range.create(6, 2, 6, 14),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -438,3 +499,121 @@ But with some content.
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Block detection for lists', () => {
|
||||
const md = `
|
||||
- this is block 1
|
||||
- this is [[block]] 2
|
||||
- this is block 2.1
|
||||
- this is block 3
|
||||
- this is block 3.1
|
||||
- this is block 3.1.1
|
||||
- this is block 3.2
|
||||
- this is block 4
|
||||
this is a simple line
|
||||
this is another simple line
|
||||
`;
|
||||
|
||||
it('can detect block', () => {
|
||||
const { block } = getBlockFor(md, 1);
|
||||
expect(block).toEqual('- this is block 1');
|
||||
});
|
||||
|
||||
it('supports nested blocks 1', () => {
|
||||
const { block } = getBlockFor(md, 2);
|
||||
expect(block).toEqual(`- this is [[block]] 2
|
||||
- this is block 2.1`);
|
||||
});
|
||||
|
||||
it('supports nested blocks 2', () => {
|
||||
const { block } = getBlockFor(md, 5);
|
||||
expect(block).toEqual(` - this is block 3.1
|
||||
- this is block 3.1.1`);
|
||||
});
|
||||
|
||||
it('returns the line if no block is detected', () => {
|
||||
const { block } = getBlockFor(md, 9);
|
||||
expect(block).toEqual(`this is a simple line`);
|
||||
});
|
||||
|
||||
it('is compatible with Range object', () => {
|
||||
const note = parser.parse(URI.file('/path/to/a'), md);
|
||||
const { start } = note.links[0].range;
|
||||
const { block } = getBlockFor(md, start);
|
||||
expect(block).toEqual(`- this is [[block]] 2
|
||||
- this is block 2.1`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('block detection for sections', () => {
|
||||
const markdown = `
|
||||
# Section 1
|
||||
- this is block 1
|
||||
- this is [[block]] 2
|
||||
- this is block 2.1
|
||||
|
||||
# Section 2
|
||||
this is a simple line
|
||||
this is another simple line
|
||||
|
||||
## Section 2.1
|
||||
- this is block 3.1
|
||||
- this is block 3.1.1
|
||||
- this is block 3.2
|
||||
|
||||
# Section 3
|
||||
# Section 4
|
||||
some text
|
||||
some text
|
||||
`;
|
||||
|
||||
it('should return correct block for valid markdown string with line number', () => {
|
||||
const { block, nLines } = getBlockFor(markdown, 1);
|
||||
expect(block).toEqual(`# Section 1
|
||||
- this is block 1
|
||||
- this is [[block]] 2
|
||||
- this is block 2.1
|
||||
`);
|
||||
expect(nLines).toEqual(5);
|
||||
});
|
||||
|
||||
it('should return correct block for valid markdown string with position', () => {
|
||||
const { block, nLines } = getBlockFor(markdown, 6);
|
||||
expect(block).toEqual(`# Section 2
|
||||
this is a simple line
|
||||
this is another simple line
|
||||
|
||||
## Section 2.1
|
||||
- this is block 3.1
|
||||
- this is block 3.1.1
|
||||
- this is block 3.2
|
||||
`);
|
||||
expect(nLines).toEqual(9);
|
||||
});
|
||||
|
||||
it('should return single line for section with no content', () => {
|
||||
const { block, nLines } = getBlockFor(markdown, 15);
|
||||
expect(block).toEqual('# Section 3');
|
||||
expect(nLines).toEqual(1);
|
||||
});
|
||||
|
||||
it('should return till end of file for last section', () => {
|
||||
const { block, nLines } = getBlockFor(markdown, 16);
|
||||
expect(block).toEqual(`# Section 4
|
||||
some text
|
||||
some text`);
|
||||
expect(nLines).toEqual(3);
|
||||
});
|
||||
|
||||
it('should return single line for non-existing line number', () => {
|
||||
const { block, nLines } = getBlockFor(markdown, 100);
|
||||
expect(block).toEqual('');
|
||||
expect(nLines).toEqual(1);
|
||||
});
|
||||
|
||||
it('should return single line for non-existing position', () => {
|
||||
const { block, nLines } = getBlockFor(markdown, Position.create(100, 2));
|
||||
expect(block).toEqual('');
|
||||
expect(nLines).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -173,15 +173,51 @@ const getTextFromChildren = (root: Node): string => {
|
||||
return text;
|
||||
};
|
||||
|
||||
function getPropertiesInfoFromYAML(yamlText: string): {
|
||||
[key: string]: { key: string; value: string; text: string; line: number };
|
||||
} {
|
||||
const yamlProps = yamlText
|
||||
.split(/(\w+:)/g)
|
||||
.filter(item => item.trim() !== '');
|
||||
const lines = yamlText.split('\n');
|
||||
let result: { line: number; key: string; text: string; value: string }[] = [];
|
||||
for (let i = 0; i < yamlProps.length / 2; i++) {
|
||||
const key = yamlProps[i * 2].replace(':', '');
|
||||
const value = yamlProps[i * 2 + 1].trim();
|
||||
const text = yamlProps[i * 2] + yamlProps[i * 2 + 1];
|
||||
result.push({ key, value, text, line: -1 });
|
||||
}
|
||||
result = result.map(p => {
|
||||
const line = lines.findIndex(l => l.startsWith(p.key + ':'));
|
||||
return { ...p, line };
|
||||
});
|
||||
return result.reduce((acc, curr) => {
|
||||
acc[curr.key] = curr;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
const tagsPlugin: ParserPlugin = {
|
||||
name: 'tags',
|
||||
onDidFindProperties: (props, note, node) => {
|
||||
if (isSome(props.tags)) {
|
||||
const tagPropertyInfo = getPropertiesInfoFromYAML((node as any).value)[
|
||||
'tags'
|
||||
];
|
||||
const tagPropertyStartLine =
|
||||
node.position!.start.line + tagPropertyInfo.line;
|
||||
const tagPropertyLines = tagPropertyInfo.text.split('\n');
|
||||
const yamlTags = extractTagsFromProp(props.tags);
|
||||
for (const tag of yamlTags) {
|
||||
const tagLine = tagPropertyLines.findIndex(l => l.includes(tag));
|
||||
const line = tagPropertyStartLine + tagLine;
|
||||
const charStart = tagPropertyLines[tagLine].indexOf(tag);
|
||||
note.tags.push({
|
||||
label: tag,
|
||||
range: astPositionToFoamRange(node.position!),
|
||||
range: Range.createFromPosition(
|
||||
Position.create(line, charStart),
|
||||
Position.create(line, charStart + tag.length)
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -241,7 +277,7 @@ const sectionsPlugin: ParserPlugin = {
|
||||
astPointToFoamPosition(tree.position.end).line + 1,
|
||||
0
|
||||
);
|
||||
// Close all the remainig sections
|
||||
// Close all the remaining sections
|
||||
while (sectionStack.length > 0) {
|
||||
const section = sectionStack.pop();
|
||||
note.sections.push({
|
||||
@@ -268,7 +304,7 @@ const titlePlugin: ParserPlugin = {
|
||||
}
|
||||
},
|
||||
onDidFindProperties: (props, note) => {
|
||||
// Give precendence to the title from the frontmatter if it exists
|
||||
// Give precedence to the title from the frontmatter if it exists
|
||||
note.title = props.title?.toString() ?? note.title;
|
||||
},
|
||||
onDidVisitTree: (tree, note) => {
|
||||
@@ -299,18 +335,33 @@ const wikilinkPlugin: ParserPlugin = {
|
||||
name: 'wikilink',
|
||||
visit: (node, note, noteSource) => {
|
||||
if (node.type === 'wikiLink') {
|
||||
const isEmbed =
|
||||
noteSource.charAt(node.position!.start.offset - 1) === '!';
|
||||
|
||||
const literalContent = noteSource.substring(
|
||||
node.position!.start.offset!,
|
||||
isEmbed
|
||||
? node.position!.start.offset! - 1
|
||||
: node.position!.start.offset!,
|
||||
node.position!.end.offset!
|
||||
);
|
||||
|
||||
const range = isEmbed
|
||||
? Range.create(
|
||||
node.position.start.line - 1,
|
||||
node.position.start.column - 2,
|
||||
node.position.end.line - 1,
|
||||
node.position.end.column - 1
|
||||
)
|
||||
: astPositionToFoamRange(node.position!);
|
||||
|
||||
note.links.push({
|
||||
type: 'wikilink',
|
||||
rawText: literalContent,
|
||||
range: astPositionToFoamRange(node.position!),
|
||||
range,
|
||||
isEmbed,
|
||||
});
|
||||
}
|
||||
if (node.type === 'link') {
|
||||
if (node.type === 'link' || node.type === 'image') {
|
||||
const targetUri = (node as any).url;
|
||||
const uri = note.uri.resolve(targetUri);
|
||||
if (uri.scheme !== 'file' || uri.path === note.uri.path) {
|
||||
@@ -324,6 +375,7 @@ const wikilinkPlugin: ParserPlugin = {
|
||||
type: 'link',
|
||||
rawText: literalContent,
|
||||
range: astPositionToFoamRange(node.position!),
|
||||
isEmbed: literalContent.startsWith('!'),
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -408,3 +460,46 @@ const astPositionToFoamRange = (pos: AstPosition): Range =>
|
||||
pos.end.line - 1,
|
||||
pos.end.column - 1
|
||||
);
|
||||
|
||||
const blockParser = unified().use(markdownParse, { gfm: true });
|
||||
export const getBlockFor = (
|
||||
markdown: string,
|
||||
line: number | Position
|
||||
): { block: string; nLines: number } => {
|
||||
const searchLine = typeof line === 'number' ? line : line.line;
|
||||
const tree = blockParser.parse(markdown);
|
||||
const lines = markdown.split('\n');
|
||||
let startLine = -1;
|
||||
let endLine = -1;
|
||||
|
||||
// For list items, we also include the sub-lists
|
||||
visit(tree, ['listItem'], (node: any) => {
|
||||
if (node.position.start.line === searchLine + 1) {
|
||||
startLine = node.position.start.line - 1;
|
||||
endLine = node.position.end.line;
|
||||
return visit.EXIT;
|
||||
}
|
||||
});
|
||||
|
||||
// For headings, we also include the sub-sections
|
||||
let headingLevel = -1;
|
||||
visit(tree, ['heading'], (node: any) => {
|
||||
if (startLine > -1 && node.depth <= headingLevel) {
|
||||
endLine = node.position.start.line - 1;
|
||||
return visit.EXIT;
|
||||
}
|
||||
if (node.position.start.line === searchLine + 1) {
|
||||
headingLevel = node.depth;
|
||||
startLine = node.position.start.line - 1;
|
||||
endLine = lines.length - 1; // in case it's the last section
|
||||
}
|
||||
});
|
||||
|
||||
let nLines = startLine === -1 ? 1 : endLine - startLine;
|
||||
let block =
|
||||
startLine === -1
|
||||
? lines[searchLine] ?? ''
|
||||
: lines.slice(startLine, endLine).join('\n');
|
||||
|
||||
return { block, nLines };
|
||||
};
|
||||
|
||||
@@ -51,10 +51,7 @@ describe('Link resolution', () => {
|
||||
);
|
||||
const noteB = createNoteFromMarkdown('Page b', '/path/one/page b.md');
|
||||
const noteB2 = createNoteFromMarkdown('Page b 2', '/path/two/page b.md');
|
||||
workspace
|
||||
.set(noteA)
|
||||
.set(noteB)
|
||||
.set(noteB2);
|
||||
workspace.set(noteA).set(noteB).set(noteB2);
|
||||
expect(workspace.resolveLink(noteA, noteA.links[0])).toEqual(noteB2.uri);
|
||||
});
|
||||
|
||||
@@ -63,10 +60,7 @@ describe('Link resolution', () => {
|
||||
const noteA = createNoteFromMarkdown('Link to [[page b]]', '/page-a.md');
|
||||
const noteB = createNoteFromMarkdown('Page b', '/path/one/page b.md');
|
||||
const noteB2 = createNoteFromMarkdown('Page b2', '/path/two/page b.md');
|
||||
workspace
|
||||
.set(noteA)
|
||||
.set(noteB)
|
||||
.set(noteB2);
|
||||
workspace.set(noteA).set(noteB).set(noteB2);
|
||||
expect(workspace.resolveLink(noteA, noteA.links[0])).toEqual(noteB.uri);
|
||||
});
|
||||
|
||||
@@ -80,10 +74,7 @@ describe('Link resolution', () => {
|
||||
const noteB3 = createTestNote({ uri: '/path/to/yet/page-b.md' });
|
||||
|
||||
const ws = createTestWorkspace();
|
||||
ws.set(noteA)
|
||||
.set(noteB1)
|
||||
.set(noteB2)
|
||||
.set(noteB3);
|
||||
ws.set(noteA).set(noteB1).set(noteB2).set(noteB3);
|
||||
|
||||
expect(ws.resolveLink(noteA, noteA.links[0])).toEqual(noteB2.uri);
|
||||
expect(ws.resolveLink(noteA, noteA.links[1])).toEqual(noteB3.uri);
|
||||
@@ -97,10 +88,7 @@ describe('Link resolution', () => {
|
||||
);
|
||||
const noteB = createNoteFromMarkdown('Page b', '/path/one/page b.md');
|
||||
const noteB2 = createNoteFromMarkdown('Page b2', '/path/two/page b.md');
|
||||
workspace
|
||||
.set(noteA)
|
||||
.set(noteB)
|
||||
.set(noteB2);
|
||||
workspace.set(noteA).set(noteB).set(noteB2);
|
||||
expect(workspace.resolveLink(noteA, noteA.links[0])).toEqual(noteB2.uri);
|
||||
expect(workspace.resolveLink(noteA, noteA.links[1])).toEqual(noteB.uri);
|
||||
});
|
||||
@@ -157,12 +145,10 @@ describe('Link resolution', () => {
|
||||
],
|
||||
});
|
||||
const noteB = createTestNote({ uri: '/somewhere/PAGE-B.md' });
|
||||
const ws = createTestWorkspace()
|
||||
.set(noteA)
|
||||
.set(noteB);
|
||||
const ws = createTestWorkspace().set(noteA).set(noteB);
|
||||
|
||||
expect(ws.resolveLink(noteA, noteA.links[0])).toEqual(
|
||||
noteB.uri.withFragment('section')
|
||||
noteB.uri.with({ fragment: 'section' })
|
||||
);
|
||||
});
|
||||
|
||||
@@ -177,9 +163,68 @@ describe('Link resolution', () => {
|
||||
const ws = createTestWorkspace().set(noteA);
|
||||
|
||||
expect(ws.resolveLink(noteA, noteA.links[0])).toEqual(
|
||||
noteA.uri.withFragment('section')
|
||||
noteA.uri.with({ fragment: 'section' })
|
||||
);
|
||||
});
|
||||
|
||||
it('should resolve wikilinks with special characters', () => {
|
||||
const ws = createTestWorkspace();
|
||||
const noteA = createNoteFromMarkdown(
|
||||
`Link to [[page: a]] and [[page %b%]] and [[page? c]] and [[[page] d]] and
|
||||
[[page ^e^]] and [[page \`f\`]] and [[page {g}]] and [[page ~i]] and
|
||||
[[page /j]]`
|
||||
);
|
||||
const noteB = createNoteFromMarkdown(
|
||||
'Note containing :',
|
||||
'/dir1/page: a.md'
|
||||
);
|
||||
const noteC = createNoteFromMarkdown(
|
||||
'Note containing %',
|
||||
'/dir1/page %b%.md'
|
||||
);
|
||||
const noteD = createNoteFromMarkdown(
|
||||
'Note containing ?',
|
||||
'/dir1/page? c.md'
|
||||
);
|
||||
const noteE = createNoteFromMarkdown(
|
||||
'Note containing ]',
|
||||
'/dir1/[page] d.md'
|
||||
);
|
||||
const noteF = createNoteFromMarkdown(
|
||||
'Note containing ^',
|
||||
'/dir1/page ^e^.md'
|
||||
);
|
||||
const noteG = createNoteFromMarkdown(
|
||||
'Note containing `',
|
||||
'/dir1/page `f`.md'
|
||||
);
|
||||
const noteH = createNoteFromMarkdown(
|
||||
'Note containing { and }',
|
||||
'/dir1/page {g}.md'
|
||||
);
|
||||
const noteI = createNoteFromMarkdown(
|
||||
'Note containing ~',
|
||||
'/dir1/page ~i.md'
|
||||
);
|
||||
ws.set(noteA)
|
||||
.set(noteB)
|
||||
.set(noteC)
|
||||
.set(noteD)
|
||||
.set(noteE)
|
||||
.set(noteF)
|
||||
.set(noteG)
|
||||
.set(noteH)
|
||||
.set(noteI);
|
||||
|
||||
expect(ws.resolveLink(noteA, noteA.links[0])).toEqual(noteB.uri);
|
||||
expect(ws.resolveLink(noteA, noteA.links[1])).toEqual(noteC.uri);
|
||||
expect(ws.resolveLink(noteA, noteA.links[2])).toEqual(noteD.uri);
|
||||
expect(ws.resolveLink(noteA, noteA.links[3])).toEqual(noteE.uri);
|
||||
expect(ws.resolveLink(noteA, noteA.links[4])).toEqual(noteF.uri);
|
||||
expect(ws.resolveLink(noteA, noteA.links[5])).toEqual(noteG.uri);
|
||||
expect(ws.resolveLink(noteA, noteA.links[6])).toEqual(noteH.uri);
|
||||
expect(ws.resolveLink(noteA, noteA.links[7])).toEqual(noteI.uri);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Markdown direct links', () => {
|
||||
@@ -258,10 +303,7 @@ describe('Link resolution', () => {
|
||||
);
|
||||
|
||||
const ws = createTestWorkspace();
|
||||
ws.set(noteA)
|
||||
.set(noteB)
|
||||
.set(noteC)
|
||||
.set(noteD);
|
||||
ws.set(noteA).set(noteB).set(noteC).set(noteD);
|
||||
|
||||
expect(ws.resolveLink(noteB, noteB.links[0])).toEqual(noteA.uri);
|
||||
expect(ws.resolveLink(noteC, noteC.links[0])).toEqual(noteA.uri);
|
||||
@@ -328,9 +370,78 @@ describe('Generation of markdown references', () => {
|
||||
.set(createNoteFromMarkdown('Content of note C', '/dir3/page-c.md'));
|
||||
|
||||
const references = createMarkdownReferences(workspace, noteA.uri, true);
|
||||
expect(references.map(r => r.url)).toEqual([
|
||||
expect(references.map(r => decodeURIComponent(r.url))).toEqual([
|
||||
'../dir2/page-b.md',
|
||||
'../dir3/page-c.md',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should generate links for embedded notes that are formatted properly', () => {
|
||||
const workspace = createTestWorkspace();
|
||||
const noteA = createNoteFromMarkdown(
|
||||
'Link to ![[page-b]] and [[page-c]]',
|
||||
'/dir1/page-a.md'
|
||||
);
|
||||
workspace
|
||||
.set(noteA)
|
||||
.set(createNoteFromMarkdown('Content of note B', '/dir2/page-b.md'))
|
||||
.set(createNoteFromMarkdown('Content of note C', '/dir3/page-c.md'));
|
||||
|
||||
const references = createMarkdownReferences(workspace, noteA.uri, true);
|
||||
expect(references.map(r => [decodeURIComponent(r.url), r.label])).toEqual([
|
||||
['../dir2/page-b.md', 'page-b'],
|
||||
['../dir3/page-c.md', 'page-c'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not generate links for placeholders', () => {
|
||||
const workspace = createTestWorkspace();
|
||||
const noteA = createNoteFromMarkdown(
|
||||
'Link to ![[page-b]] and [[page-c]] and [[does-not-exist]] and ![[does-not-exist-either]]',
|
||||
'/dir1/page-a.md'
|
||||
);
|
||||
workspace
|
||||
.set(noteA)
|
||||
.set(createNoteFromMarkdown('Content of note B', '/dir2/page-b.md'))
|
||||
.set(createNoteFromMarkdown('Content of note C', '/dir3/page-c.md'));
|
||||
|
||||
const references = createMarkdownReferences(workspace, noteA.uri, true);
|
||||
expect(references.map(r => decodeURIComponent(r.url))).toEqual([
|
||||
'../dir2/page-b.md',
|
||||
'../dir3/page-c.md',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should encode special characters in links', () => {
|
||||
const workspace = createTestWorkspace();
|
||||
const noteA = createNoteFromMarkdown(
|
||||
`Link to [[page: a]] and [[page %b%]] and [[page? c]] and [[[page] d]] and
|
||||
[[page ^e^]] and [[page \`f\`]] and [[page {g}]] and [[page ~i]] and
|
||||
[[page /j]]`
|
||||
);
|
||||
workspace
|
||||
.set(noteA)
|
||||
.set(createNoteFromMarkdown('Note containing :', '/dir1/page: a.md'))
|
||||
.set(createNoteFromMarkdown('Note containing %', '/dir1/page %b%.md'))
|
||||
.set(createNoteFromMarkdown('Note containing ?', '/dir1/page? c.md'))
|
||||
.set(createNoteFromMarkdown('Note containing ]', '/dir1/[page] d.md'))
|
||||
.set(createNoteFromMarkdown('Note containing ^', '/dir1/page ^e^.md'))
|
||||
.set(createNoteFromMarkdown('Note containing `', '/dir1/page `f`.md'))
|
||||
.set(
|
||||
createNoteFromMarkdown('Note containing { and }', '/dir1/page {g}.md')
|
||||
)
|
||||
.set(createNoteFromMarkdown('Note containing ~', '/dir1/page ~i.md'));
|
||||
|
||||
const references = createMarkdownReferences(workspace, noteA.uri, true);
|
||||
expect(references.map(r => decodeURIComponent(r.url))).toEqual([
|
||||
'../dir1/page: a.md',
|
||||
'../dir1/page %b%.md',
|
||||
'../dir1/page? c.md',
|
||||
'../dir1/[page] d.md',
|
||||
'../dir1/page ^e^.md',
|
||||
'../dir1/page `f`.md',
|
||||
'../dir1/page {g}.md',
|
||||
'../dir1/page ~i.md',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,17 +12,19 @@ import { IDisposable } from '../common/lifecycle';
|
||||
import { ResourceProvider } from '../model/provider';
|
||||
import { MarkdownLink } from './markdown-link';
|
||||
import { IDataStore } from './datastore';
|
||||
import { uniqBy } from 'lodash';
|
||||
|
||||
export class MarkdownResourceProvider implements ResourceProvider {
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
private readonly dataStore: IDataStore,
|
||||
private readonly parser: ResourceParser
|
||||
private readonly parser: ResourceParser,
|
||||
public readonly noteExtensions: string[] = ['.md']
|
||||
) {}
|
||||
|
||||
supports(uri: URI) {
|
||||
return uri.isMarkdown();
|
||||
return this.noteExtensions.includes(uri.getExtension());
|
||||
}
|
||||
|
||||
async readAsMarkdown(uri: URI): Promise<string | null> {
|
||||
@@ -74,7 +76,7 @@ export class MarkdownResourceProvider implements ResourceProvider {
|
||||
URI.placeholder(target);
|
||||
|
||||
if (section) {
|
||||
targetUri = targetUri.withFragment(section);
|
||||
targetUri = targetUri.with({ fragment: section });
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -91,7 +93,7 @@ export class MarkdownResourceProvider implements ResourceProvider {
|
||||
workspace.find(path, resource.uri)?.uri ??
|
||||
URI.placeholder(resource.uri.resolve(path).path);
|
||||
if (section && !targetUri.isPlaceholder()) {
|
||||
targetUri = targetUri.withFragment(section);
|
||||
targetUri = targetUri.with({ fragment: section });
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -106,27 +108,19 @@ export class MarkdownResourceProvider implements ResourceProvider {
|
||||
|
||||
export function createMarkdownReferences(
|
||||
workspace: FoamWorkspace,
|
||||
noteUri: URI,
|
||||
source: Resource | URI,
|
||||
includeExtension: boolean
|
||||
): NoteLinkDefinition[] {
|
||||
const source = workspace.find(noteUri);
|
||||
// Should never occur since we're already in a file,
|
||||
if (source?.type !== 'note') {
|
||||
console.warn(
|
||||
`Note ${noteUri.toString()} note found in workspace when attempting \
|
||||
to generate markdown reference list`
|
||||
);
|
||||
return [];
|
||||
}
|
||||
const resource = source instanceof URI ? workspace.find(source) : source;
|
||||
|
||||
return source.links
|
||||
const definitions = resource.links
|
||||
.filter(link => link.type === 'wikilink')
|
||||
.map(link => {
|
||||
const targetUri = workspace.resolveLink(source, link);
|
||||
const targetUri = workspace.resolveLink(resource, link);
|
||||
const target = workspace.find(targetUri);
|
||||
if (isNone(target)) {
|
||||
Logger.warn(
|
||||
`Link ${targetUri.toString()} in ${noteUri.toString()} is not valid.`
|
||||
`Link ${targetUri.toString()} in ${resource.uri.toString()} is not valid.`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
@@ -135,34 +129,33 @@ to generate markdown reference list`
|
||||
return null;
|
||||
}
|
||||
|
||||
let relativeUri = target.uri.relativeTo(noteUri.getDirectory());
|
||||
if (!includeExtension && relativeUri.path.endsWith('.md')) {
|
||||
let relativeUri = target.uri.relativeTo(resource.uri.getDirectory());
|
||||
if (
|
||||
!includeExtension &&
|
||||
relativeUri.path.endsWith(workspace.defaultExtension)
|
||||
) {
|
||||
relativeUri = relativeUri.changeExtension('*', '');
|
||||
}
|
||||
|
||||
// Extract base path and link name separately.
|
||||
const basePath = relativeUri.path.split('/').slice(0, -1).join('/');
|
||||
const linkName = relativeUri.path.split('/').pop();
|
||||
|
||||
const encodedURL = encodeURIComponent(linkName).replace(/%20/g, ' ');
|
||||
|
||||
// [wikilink-text]: path/to/file.md "Page title"
|
||||
return {
|
||||
label:
|
||||
link.rawText.indexOf('[[') > -1
|
||||
? link.rawText.substring(2, link.rawText.length - 2)
|
||||
: link.rawText,
|
||||
url: relativeUri.path,
|
||||
// embedded looks like ![[note-a]]
|
||||
// regular note looks like [[note-a]]
|
||||
label: link.rawText.substring(
|
||||
link.isEmbed ? 3 : 2,
|
||||
link.rawText.length - 2
|
||||
),
|
||||
url: `${basePath ? basePath + '/' : ''}${encodedURL}`,
|
||||
title: target.title,
|
||||
};
|
||||
})
|
||||
.filter(isSome)
|
||||
.sort();
|
||||
}
|
||||
|
||||
export function stringifyMarkdownLinkReferenceDefinition(
|
||||
definition: NoteLinkDefinition
|
||||
) {
|
||||
const url =
|
||||
definition.url.indexOf(' ') > 0 ? `<${definition.url}>` : definition.url;
|
||||
let text = `[${definition.label}]: ${url}`;
|
||||
if (definition.title) {
|
||||
text = `${text} "${definition.title}"`;
|
||||
}
|
||||
|
||||
return text;
|
||||
return uniqBy(definitions, def => NoteLinkDefinition.format(def));
|
||||
}
|
||||
|
||||
132
packages/foam-vscode/src/core/services/resource-filter.test.ts
Normal file
132
packages/foam-vscode/src/core/services/resource-filter.test.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { Logger } from '../utils/log';
|
||||
import { createTestNote } from '../../test/test-utils';
|
||||
import { createFilter } from './resource-filter';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
describe('Resource Filter', () => {
|
||||
describe('Filter parameters', () => {
|
||||
it('should support the path regex', () => {
|
||||
const noteA = createTestNote({
|
||||
uri: '/path/to/foo.md',
|
||||
type: 'type-1',
|
||||
});
|
||||
const noteB = createTestNote({
|
||||
uri: 'note-b.md',
|
||||
type: '/path/to/bar.md',
|
||||
});
|
||||
|
||||
const filter = createFilter({ path: 'foo' }, false);
|
||||
|
||||
expect(filter(noteA)).toBeTruthy();
|
||||
expect(filter(noteB)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should support expressions when code execution is enabled', () => {
|
||||
const noteA = createTestNote({
|
||||
uri: 'note-a.md',
|
||||
type: 'type-1',
|
||||
});
|
||||
const noteB = createTestNote({
|
||||
uri: 'note-b.md',
|
||||
type: 'type-2',
|
||||
});
|
||||
|
||||
const filter = createFilter(
|
||||
{
|
||||
expression: 'resource.type === "type-1"',
|
||||
},
|
||||
true
|
||||
);
|
||||
expect(filter(noteA)).toBeTruthy();
|
||||
expect(filter(noteB)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should not allow expressions when code execution is not enabled', () => {
|
||||
const noteA = createTestNote({
|
||||
uri: 'note-a.md',
|
||||
type: 'type-1',
|
||||
});
|
||||
const noteB = createTestNote({
|
||||
uri: 'note-b.md',
|
||||
type: 'type-2',
|
||||
});
|
||||
|
||||
const filter = createFilter(
|
||||
{
|
||||
expression: 'resource.type === "type-1"',
|
||||
},
|
||||
false
|
||||
);
|
||||
expect(filter(noteA)).toBeTruthy();
|
||||
expect(filter(noteB)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should support resource type', () => {
|
||||
const noteA = createTestNote({
|
||||
uri: 'note-a.md',
|
||||
type: 'type-1',
|
||||
});
|
||||
const noteB = createTestNote({
|
||||
uri: 'note-b.md',
|
||||
type: 'type-2',
|
||||
});
|
||||
|
||||
const filter = createFilter(
|
||||
{
|
||||
type: 'type-1',
|
||||
},
|
||||
false
|
||||
);
|
||||
expect(filter(noteA)).toBeTruthy();
|
||||
expect(filter(noteB)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should support resource title', () => {
|
||||
const noteA = createTestNote({
|
||||
uri: 'note-a.md',
|
||||
title: 'title-1',
|
||||
});
|
||||
const noteB = createTestNote({
|
||||
uri: 'note-b.md',
|
||||
title: 'title-2',
|
||||
});
|
||||
const noteC = createTestNote({
|
||||
uri: 'note-c.md',
|
||||
title: 'another title',
|
||||
});
|
||||
|
||||
const filter = createFilter(
|
||||
{
|
||||
title: '^title',
|
||||
},
|
||||
false
|
||||
);
|
||||
expect(filter(noteA)).toBeTruthy();
|
||||
expect(filter(noteB)).toBeTruthy();
|
||||
expect(filter(noteC)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Filter operators', () => {
|
||||
it('should support the OR operator', () => {
|
||||
const noteA = createTestNote({
|
||||
uri: 'note-a.md',
|
||||
type: 'type-1',
|
||||
});
|
||||
const noteB = createTestNote({
|
||||
uri: 'note-b.md',
|
||||
type: 'type-2',
|
||||
});
|
||||
|
||||
const filter = createFilter(
|
||||
{
|
||||
or: [{ type: 'type-1' }, { type: 'type-2' }],
|
||||
},
|
||||
false
|
||||
);
|
||||
expect(filter(noteA)).toBeTruthy();
|
||||
expect(filter(noteB)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
80
packages/foam-vscode/src/core/services/resource-filter.ts
Normal file
80
packages/foam-vscode/src/core/services/resource-filter.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { negate } from 'lodash';
|
||||
import { Resource } from '../model/note';
|
||||
|
||||
export interface FilterDescriptor
|
||||
extends FilterDescriptorOp,
|
||||
FilterDescriptorParam {}
|
||||
|
||||
interface FilterDescriptorOp {
|
||||
and?: FilterDescriptor[];
|
||||
or?: FilterDescriptor[];
|
||||
not?: FilterDescriptor;
|
||||
}
|
||||
|
||||
interface FilterDescriptorParam {
|
||||
/**
|
||||
* A regex of the path to include
|
||||
*/
|
||||
path?: string;
|
||||
|
||||
/**
|
||||
* A tag
|
||||
*/
|
||||
tag?: string;
|
||||
|
||||
/**
|
||||
* A note type
|
||||
*/
|
||||
type?: string;
|
||||
|
||||
/**
|
||||
* The title of the note
|
||||
*/
|
||||
title?: string;
|
||||
|
||||
/**
|
||||
* An expression to evaluate to JS, use `resource` to reference the resource object
|
||||
*/
|
||||
expression?: string;
|
||||
}
|
||||
|
||||
type ResourceFilter = (r: Resource) => boolean;
|
||||
|
||||
export function createFilter(
|
||||
filter: FilterDescriptor,
|
||||
enableCode: boolean
|
||||
): ResourceFilter {
|
||||
filter = filter ?? {};
|
||||
const expressionFn =
|
||||
enableCode && filter.expression
|
||||
? resource => eval(filter.expression) // eslint-disable-line no-eval
|
||||
: undefined;
|
||||
return resource => {
|
||||
if (expressionFn && !expressionFn(resource)) {
|
||||
return false;
|
||||
}
|
||||
if (filter.path && !resource.uri.path.match(filter.path)) {
|
||||
return false;
|
||||
}
|
||||
if (filter.type && resource.type !== filter.type) {
|
||||
return false;
|
||||
}
|
||||
if (filter.title && !resource.title.match(filter.title)) {
|
||||
return false;
|
||||
}
|
||||
if (filter.and) {
|
||||
return filter.and
|
||||
.map(pred => createFilter(pred, enableCode))
|
||||
.every(fn => fn(resource));
|
||||
}
|
||||
if (filter.or) {
|
||||
return filter.or
|
||||
.map(pred => createFilter(pred, enableCode))
|
||||
.some(fn => fn(resource));
|
||||
}
|
||||
if (filter.not) {
|
||||
return negate(createFilter(filter.not, enableCode))(resource);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Range } from '../model/range';
|
||||
import { Logger } from '../utils/log';
|
||||
import { applyTextEdit } from './apply-text-edit';
|
||||
import { TextEdit } from './text-edit';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
@@ -23,7 +23,7 @@ describe('applyTextEdit', () => {
|
||||
3. this is third line
|
||||
4. this is fourth line`;
|
||||
|
||||
const actual = applyTextEdit(text, textEdit);
|
||||
const actual = TextEdit.apply(text, textEdit);
|
||||
|
||||
expect(actual).toBe(expected);
|
||||
});
|
||||
@@ -45,7 +45,7 @@ describe('applyTextEdit', () => {
|
||||
3. this is third line
|
||||
`;
|
||||
|
||||
const actual = applyTextEdit(text, textEdit);
|
||||
const actual = TextEdit.apply(text, textEdit);
|
||||
|
||||
expect(actual).toBe(expected);
|
||||
});
|
||||
@@ -68,7 +68,7 @@ describe('applyTextEdit', () => {
|
||||
3. this is third line
|
||||
`;
|
||||
|
||||
const actual = applyTextEdit(text, textEdit);
|
||||
const actual = TextEdit.apply(text, textEdit);
|
||||
|
||||
expect(actual).toBe(expected);
|
||||
});
|
||||
44
packages/foam-vscode/src/core/services/text-edit.ts
Normal file
44
packages/foam-vscode/src/core/services/text-edit.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import detectNewline from 'detect-newline';
|
||||
import { Position } from '../model/position';
|
||||
import { Range } from '../model/range';
|
||||
|
||||
export interface TextEdit {
|
||||
range: Range;
|
||||
newText: string;
|
||||
}
|
||||
|
||||
export abstract class TextEdit {
|
||||
/**
|
||||
*
|
||||
* @param text text on which the textEdit will be applied
|
||||
* @param textEdit
|
||||
* @returns {string} text with the applied textEdit
|
||||
*/
|
||||
public static apply(text: string, textEdit: TextEdit): string {
|
||||
const eol = detectNewline.graceful(text);
|
||||
const lines = text.split(eol);
|
||||
const characters = text.split('');
|
||||
const startOffset = getOffset(lines, textEdit.range.start, eol);
|
||||
const endOffset = getOffset(lines, textEdit.range.end, eol);
|
||||
const deleteCount = endOffset - startOffset;
|
||||
|
||||
const textToAppend = `${textEdit.newText}`;
|
||||
characters.splice(startOffset, deleteCount, textToAppend);
|
||||
return characters.join('');
|
||||
}
|
||||
}
|
||||
|
||||
const getOffset = (
|
||||
lines: string[],
|
||||
position: Position,
|
||||
eol: string
|
||||
): number => {
|
||||
const eolLen = eol.length;
|
||||
let offset = 0;
|
||||
let i = 0;
|
||||
while (i < position.line && i < lines.length) {
|
||||
offset = offset + lines[i].length + eolLen;
|
||||
i++;
|
||||
}
|
||||
return offset + Math.min(position.character, lines[i]?.length ?? 0);
|
||||
};
|
||||
@@ -1,27 +1,85 @@
|
||||
import crypto from 'crypto';
|
||||
import sha1 from 'js-sha1';
|
||||
|
||||
/**
|
||||
* Checks if a value is not null.
|
||||
*
|
||||
* @param value - The value to check.
|
||||
* @returns True if the value is not null, otherwise false.
|
||||
*/
|
||||
export function isNotNull<T>(value: T | null): value is T {
|
||||
return value != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a value is not null, undefined, or void.
|
||||
*
|
||||
* @param value - The value to check.
|
||||
* @returns True if the value is not null, undefined, or void, otherwise false.
|
||||
*/
|
||||
export function isSome<T>(
|
||||
value: T | null | undefined | void
|
||||
): value is NonNullable<T> {
|
||||
return value != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a value is null, undefined, or void.
|
||||
*
|
||||
* @param value - The value to check.
|
||||
* @returns True if the value is null, undefined, or void, otherwise false.
|
||||
*/
|
||||
export function isNone<T>(
|
||||
value: T | null | undefined | void
|
||||
): value is null | undefined | void {
|
||||
return value == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a string is numeric.
|
||||
*
|
||||
* @param value - The string to check.
|
||||
* @returns True if the string is numeric, otherwise false.
|
||||
*/
|
||||
export function isNumeric(value: string): boolean {
|
||||
return /-?\d+$/.test(value);
|
||||
}
|
||||
|
||||
export const hash = (text: string) =>
|
||||
crypto
|
||||
.createHash('sha1')
|
||||
.update(text)
|
||||
.digest('hex');
|
||||
/**
|
||||
* Generates a SHA-1 hash of the given text.
|
||||
*
|
||||
* @param text - The text to hash.
|
||||
* @returns The SHA-1 hash of the text.
|
||||
*/
|
||||
export const hash = (text: string) => sha1.sha1(text);
|
||||
|
||||
/**
|
||||
* Executes an array of functions and returns the first result that satisfies the predicate.
|
||||
*
|
||||
* @param functions - The array of functions to execute.
|
||||
* @param predicate - The predicate to test the results. Defaults to checking if the result is not null.
|
||||
* @returns The first result that satisfies the predicate, or undefined if no result satisfies the predicate.
|
||||
*/
|
||||
export async function firstFrom<T>(
|
||||
functions: Array<() => T | Promise<T>>,
|
||||
predicate: (result: T) => boolean = result => result != null
|
||||
): Promise<T | undefined> {
|
||||
for (const fn of functions) {
|
||||
const result = await fn();
|
||||
if (predicate(result)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazily executes an array of functions and yields their results.
|
||||
*
|
||||
* @param functions - The array of functions to execute.
|
||||
* @returns A generator yielding the results of the functions.
|
||||
*/
|
||||
function* lazyExecutor<T>(functions: Array<() => T>): Generator<T> {
|
||||
for (const fn of functions) {
|
||||
yield fn();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { isSome } from './core';
|
||||
const HASHTAG_REGEX = /(?<=^|\s)#([0-9]*[\p{L}\p{Emoji_Presentation}/_-][\p{L}\p{Emoji_Presentation}\p{N}/_-]*)/gmu;
|
||||
const WORD_REGEX = /(?<=^|\s)([0-9]*[\p{L}\p{Emoji_Presentation}/_-][\p{L}\p{Emoji_Presentation}\p{N}/_-]*)/gmu;
|
||||
export const HASHTAG_REGEX =
|
||||
/(?<=^|\s)#([0-9]*[\p{L}\p{Emoji_Presentation}/_-][\p{L}\p{Emoji_Presentation}\p{N}/_-]*)/gmu;
|
||||
const WORD_REGEX =
|
||||
/(?<=^|\s)([0-9]*[\p{L}\p{Emoji_Presentation}/_-][\p{L}\p{Emoji_Presentation}\p{N}/_-]*)/gmu;
|
||||
|
||||
export const extractHashtags = (
|
||||
text: string
|
||||
|
||||
@@ -89,3 +89,25 @@ export class Logger {
|
||||
Logger.defaultLogger = logger;
|
||||
}
|
||||
}
|
||||
|
||||
export const withTiming = <T>(
|
||||
fn: () => T,
|
||||
onDidComplete: (elapsed: number) => void
|
||||
): T => {
|
||||
const tsStart = Date.now();
|
||||
const res = fn();
|
||||
const tsEnd = Date.now();
|
||||
onDidComplete(tsEnd - tsStart);
|
||||
return res;
|
||||
};
|
||||
|
||||
export const withTimingAsync = async <T>(
|
||||
fn: () => Promise<T>,
|
||||
onDidComplete: (elapsed: number) => void
|
||||
): Promise<T> => {
|
||||
const tsStart = Date.now();
|
||||
const res = await fn();
|
||||
const tsEnd = Date.now();
|
||||
onDidComplete(tsEnd - tsStart);
|
||||
return res;
|
||||
};
|
||||
|
||||
70
packages/foam-vscode/src/core/utils/md.test.ts
Normal file
70
packages/foam-vscode/src/core/utils/md.test.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { isInFrontMatter, isOnYAMLKeywordLine } from './md';
|
||||
|
||||
describe('isInFrontMatter', () => {
|
||||
it('is true for started front matter', () => {
|
||||
const content = `---
|
||||
|
||||
`;
|
||||
const actual = isInFrontMatter(content, 1);
|
||||
expect(actual).toBeTruthy();
|
||||
});
|
||||
it('is true for inside completed front matter', () => {
|
||||
const content = '---\ntitle: A title\n---\n';
|
||||
const actual = isInFrontMatter(content, 1);
|
||||
expect(actual).toBeTruthy();
|
||||
});
|
||||
it('is true for inside completed front matter with "..." end delimiter', () => {
|
||||
const content = '---\ntitle: A title\n...\n';
|
||||
const actual = isInFrontMatter(content, 1);
|
||||
expect(actual).toBeTruthy();
|
||||
});
|
||||
it('is false for non valid front matter delimiter #1347', () => {
|
||||
const content = '---\ntitle: A title\n-..\n\n\n---\ntest\n';
|
||||
expect(isInFrontMatter(content, 1)).toBeTruthy();
|
||||
expect(isInFrontMatter(content, 4)).toBeTruthy();
|
||||
expect(isInFrontMatter(content, 6)).toBeFalsy();
|
||||
});
|
||||
it('is false for outside completed front matter', () => {
|
||||
const content = '---\ntitle: A title\n---\ncontent\nmore content\n';
|
||||
const actual = isInFrontMatter(content, 3);
|
||||
expect(actual).toBeFalsy();
|
||||
});
|
||||
it('is false for outside completed front matter with "..." end delimiter', () => {
|
||||
const content = '---\ntitle: A title\n...\ncontent\nmore content\n';
|
||||
const actual = isInFrontMatter(content, 3);
|
||||
expect(actual).toBeFalsy();
|
||||
});
|
||||
it('is false for position on initial front matter delimiter', () => {
|
||||
const content = '---\ntitle: A title\n---\ncontent\nmore content\n';
|
||||
const actual = isInFrontMatter(content, 0);
|
||||
expect(actual).toBeFalsy();
|
||||
});
|
||||
it('is false for position on final front matter delimiter', () => {
|
||||
const content = '---\ntitle: A title\n---\ncontent\nmore content\n';
|
||||
const actual = isInFrontMatter(content, 2);
|
||||
expect(actual).toBeFalsy();
|
||||
});
|
||||
|
||||
describe('isOnYAMLKeywordLine', () => {
|
||||
it('is true if line starts with keyword', () => {
|
||||
const content = 'tags: foo, bar\n';
|
||||
const actual = isOnYAMLKeywordLine(content, 'tags');
|
||||
expect(actual).toBeTruthy();
|
||||
});
|
||||
it('is true if previous line starts with keyword', () => {
|
||||
const content = 'tags: foo\n - bar\n';
|
||||
const actual = isOnYAMLKeywordLine(content, 'tags');
|
||||
expect(actual).toBeTruthy();
|
||||
});
|
||||
it('is false if line starts with wrong keyword', () => {
|
||||
const content = 'tags: foo, bar\n';
|
||||
const actual = isOnYAMLKeywordLine(content, 'title');
|
||||
expect(actual).toBeFalsy();
|
||||
});
|
||||
it('is false if previous line starts with wrong keyword', () => {
|
||||
const content = 'dates:\n - 2023-01-1\n - 2023-01-02\n';
|
||||
const actual = isOnYAMLKeywordLine(content, 'tags');
|
||||
expect(actual).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
70
packages/foam-vscode/src/core/utils/md.ts
Normal file
70
packages/foam-vscode/src/core/utils/md.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import matter from 'gray-matter';
|
||||
|
||||
export function getExcerpt(
|
||||
markdown: string,
|
||||
maxLines: number
|
||||
): { excerpt: string; lines: number } {
|
||||
const OFFSET_LINES_LIMIT = 5;
|
||||
const paragraphs = markdown.replace(/\r\n/g, '\n').split('\n\n');
|
||||
const excerpt: string[] = [];
|
||||
let lines = 0;
|
||||
for (const paragraph of paragraphs) {
|
||||
const n = paragraph.split('\n').length;
|
||||
if (lines > maxLines || lines + n - maxLines > OFFSET_LINES_LIMIT) {
|
||||
break;
|
||||
}
|
||||
excerpt.push(paragraph);
|
||||
lines = lines + n + 1;
|
||||
}
|
||||
return { excerpt: excerpt.join('\n\n'), lines };
|
||||
}
|
||||
|
||||
export function stripFrontMatter(markdown: string): string {
|
||||
return matter(markdown).content.trim();
|
||||
}
|
||||
|
||||
export function stripImages(markdown: string): string {
|
||||
return markdown.replace(
|
||||
/!\[(.*)\]\([-/\\.A-Za-z]*\)/gi,
|
||||
'$1'.length ? '[Image: $1]' : ''
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the given line is inside a front matter block
|
||||
* @param content the string to check
|
||||
* @param lineNumber the line number within the string, 0-based
|
||||
* @returns true if the line is inside a frontmatter block in content
|
||||
*/
|
||||
export function isInFrontMatter(content: string, lineNumber: number): boolean {
|
||||
const FIRST_DELIMITER_MATCH = /^---\s*?$/m;
|
||||
const LAST_DELIMITER_MATCH = /^(-{3}|\.{3})/;
|
||||
|
||||
// if we're on the first line, we're not _yet_ in the front matter
|
||||
if (lineNumber === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// look for --- at start, and a second --- or ... to end
|
||||
if (content.match(FIRST_DELIMITER_MATCH) === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const lines = content.split('\n');
|
||||
lines.shift();
|
||||
const endLineNumber = lines.findIndex(l => l.match(LAST_DELIMITER_MATCH));
|
||||
|
||||
return endLineNumber === -1 || endLineNumber >= lineNumber;
|
||||
}
|
||||
|
||||
export function isOnYAMLKeywordLine(content: string, keyword: string): boolean {
|
||||
const keywordMatch = /^\s*(\w+):/gm;
|
||||
|
||||
if (content.match(keywordMatch) === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const matches = Array.from(content.matchAll(keywordMatch));
|
||||
const lastMatch = matches[matches.length - 1];
|
||||
return lastMatch[1] === keyword;
|
||||
}
|
||||
@@ -1,72 +1,73 @@
|
||||
import { workspace } from 'vscode';
|
||||
import { createDailyNoteIfNotExists, getDailyNotePath } from './dated-notes';
|
||||
import { isWindows } from './core/common/platform';
|
||||
import {
|
||||
cleanWorkspace,
|
||||
closeEditors,
|
||||
createFile,
|
||||
deleteFile,
|
||||
showInEditor,
|
||||
withModifiedFoamConfiguration,
|
||||
} from './test/test-utils-vscode';
|
||||
import { fromVsCodeUri } from './utils/vsc-utils';
|
||||
|
||||
describe('getDailyNotePath', () => {
|
||||
const date = new Date('2021-02-07T00:00:00Z');
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth() + 1;
|
||||
const day = date.getDate();
|
||||
const isoDate = `${year}-0${month}-0${day}`;
|
||||
|
||||
test('Adds the root directory to relative directories', async () => {
|
||||
const config = 'journal';
|
||||
|
||||
const expectedPath = fromVsCodeUri(
|
||||
workspace.workspaceFolders[0].uri
|
||||
).joinPath(config, `${isoDate}.md`);
|
||||
|
||||
await withModifiedFoamConfiguration('openDailyNote.directory', config, () =>
|
||||
expect(getDailyNotePath(date).toFsPath()).toEqual(expectedPath.toFsPath())
|
||||
);
|
||||
});
|
||||
|
||||
test('Uses absolute directories without modification', async () => {
|
||||
const config = isWindows
|
||||
? 'C:\\absolute_path\\journal'
|
||||
: '/absolute_path/journal';
|
||||
const expectedPath = isWindows
|
||||
? `${config}\\${isoDate}.md`
|
||||
: `${config}/${isoDate}.md`;
|
||||
|
||||
await withModifiedFoamConfiguration('openDailyNote.directory', config, () =>
|
||||
expect(getDailyNotePath(date).toFsPath()).toMatch(expectedPath)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Daily note template', () => {
|
||||
it('Uses the daily note variables in the template', async () => {
|
||||
const targetDate = new Date(2021, 8, 12);
|
||||
|
||||
const template = await createFile(
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
'hello ${FOAM_DATE_MONTH_NAME} ${FOAM_DATE_DATE} hello',
|
||||
['.foam', 'templates', 'daily-note.md']
|
||||
);
|
||||
|
||||
const uri = getDailyNotePath(targetDate);
|
||||
|
||||
await createDailyNoteIfNotExists(targetDate);
|
||||
|
||||
const doc = await showInEditor(uri);
|
||||
const content = doc.editor.document.getText();
|
||||
expect(content).toEqual('hello September 12 hello');
|
||||
|
||||
await deleteFile(template.uri);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanWorkspace();
|
||||
await closeEditors();
|
||||
});
|
||||
});
|
||||
import { workspace } from 'vscode';
|
||||
import { createDailyNoteIfNotExists, getDailyNoteUri } from './dated-notes';
|
||||
import { isWindows } from './core/common/platform';
|
||||
import {
|
||||
cleanWorkspace,
|
||||
closeEditors,
|
||||
createFile,
|
||||
deleteFile,
|
||||
showInEditor,
|
||||
withModifiedFoamConfiguration,
|
||||
} from './test/test-utils-vscode';
|
||||
import { fromVsCodeUri } from './utils/vsc-utils';
|
||||
import { URI } from './core/model/uri';
|
||||
|
||||
describe('getDailyNoteUri', () => {
|
||||
const date = new Date('2021-02-07T00:00:00Z');
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth() + 1;
|
||||
const day = date.getDate();
|
||||
const isoDate = `${year}-0${month}-0${day}`;
|
||||
|
||||
test('Adds the root directory to relative directories', async () => {
|
||||
const config = 'journal';
|
||||
|
||||
const expectedUri = fromVsCodeUri(
|
||||
workspace.workspaceFolders[0].uri
|
||||
).joinPath(config, `${isoDate}.md`);
|
||||
|
||||
await withModifiedFoamConfiguration('openDailyNote.directory', config, () =>
|
||||
expect(getDailyNoteUri(date)).toEqual(expectedUri)
|
||||
);
|
||||
});
|
||||
|
||||
test('Uses absolute directories without modification', async () => {
|
||||
const config = isWindows
|
||||
? 'C:\\absolute_path\\journal'
|
||||
: '/absolute_path/journal';
|
||||
const expectedPath = isWindows
|
||||
? `${config}\\${isoDate}.md`
|
||||
: `${config}/${isoDate}.md`;
|
||||
|
||||
await withModifiedFoamConfiguration('openDailyNote.directory', config, () =>
|
||||
expect(getDailyNoteUri(date).toFsPath()).toMatch(expectedPath)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Daily note template', () => {
|
||||
it('Uses the daily note variables in the template', async () => {
|
||||
const targetDate = new Date(2021, 8, 12);
|
||||
|
||||
const template = await createFile(
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
'hello ${FOAM_DATE_MONTH_NAME} ${FOAM_DATE_DATE} hello',
|
||||
['.foam', 'templates', 'daily-note.md']
|
||||
);
|
||||
|
||||
const uri = getDailyNoteUri(targetDate);
|
||||
|
||||
await createDailyNoteIfNotExists(targetDate);
|
||||
|
||||
const doc = await showInEditor(uri);
|
||||
const content = doc.editor.document.getText();
|
||||
expect(content).toEqual('hello September 12 hello');
|
||||
|
||||
await deleteFile(template.uri);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanWorkspace();
|
||||
await closeEditors();
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user