Compare commits

...

112 Commits

Author SHA1 Message Date
Riccardo Ferretti
a004e61b2a v0.26.11 2025-04-19 19:23:40 +02:00
Riccardo Ferretti
1a7e633edc Prepare for next release 2025-04-19 19:22:44 +02:00
Riccardo Ferretti
fb78e2baff Updated minimum VS Code version 2025-04-19 19:11:27 +02:00
Riccardo Ferretti
9d143394dc Updated test version and added flag to remove console warnings 2025-04-19 19:11:27 +02:00
Riccardo Ferretti
249e3dd924 Added return type to function 2025-04-19 19:11:27 +02:00
allcontributors[bot]
3398ab08ac add Tenormis as a contributor for code (#1458)
* update docs/index.md [skip ci]

* update readme.md [skip ci]

* update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2025-04-19 19:10:59 +02:00
Tenormis
2a197cfee5 Support custom fonts (#1457)
Co-authored-by: tenormis <tenormis@mars.com>
2025-04-19 19:09:39 +02:00
Riccardo Ferretti
e95aa05059 removing new force-graph library from spell check 2025-03-29 19:01:49 +01:00
Riccardo Ferretti
dc8df6fd1e v0.26.10 2025-03-29 17:05:42 +01:00
Riccardo Ferretti
babcbb1ec1 Preparation for next release 2025-03-29 17:05:18 +01:00
Riccardo Ferretti
82e46b22ff Improved rendering of embedded notes. Fixes #1443 2025-03-29 17:03:29 +01:00
Riccardo Ferretti
1999b04ea2 v0.26.9 2025-03-29 11:24:53 +01:00
Riccardo Ferretti
ebc9c5761e Preparing release 2025-03-29 11:24:27 +01:00
Riccardo Ferretti
f37f2e20a2 Added link to "foam wiki" in contribution guide 2025-03-29 11:22:43 +01:00
Riccardo Ferretti
f11a779132 Bumped force-graph 2025-03-29 11:22:13 +01:00
Riccardo Ferretti
20694278a6 Added getLinkNodeId function (see #1438)
Given that the link object is at times a string and at times an object (couldn't replicate, so exact cause unknown, but in line with the types of `force-graph` API), adding a function to properly extract the ID from the object
2025-03-29 11:22:02 +01:00
Riccardo Ferretti
6a849d220b v0.26.8 2025-03-14 18:37:21 +01:00
Riccardo Ferretti
6001dc0214 Preparing for next release 2025-03-14 18:37:02 +01:00
Riccardo Ferretti
93cedcc490 Tweaks to note navigator to improve hierarchy layout 2025-03-14 17:11:25 +01:00
Riccardo Ferretti
cad0c38965 Showing tag relationships in foam graph (#1436) 2025-03-14 11:22:14 +01:00
Riccardo Ferretti
bdb95a0832 v0.26.7 2025-03-09 22:59:12 +01:00
Riccardo Ferretti
e0580d39bf Prepare for next release 2025-03-09 22:58:45 +01:00
Riccardo Ferretti
279b3b48f1 Improved parsing of tags (fixes #1434) 2025-03-09 22:57:10 +01:00
Riccardo Ferretti
e3c63fca89 v0.26.6 2025-03-08 22:32:09 +01:00
Riccardo
948b7db5ef Preparation for next release 2025-03-08 22:31:54 +01:00
Riccardo
503a8f5f18 Added navigation for tags in editor (#1433) 2025-03-08 22:26:06 +01:00
allcontributors[bot]
8ab4f13543 add emmanuel-ferdman as a contributor for doc (#1432)
* update docs/index.md [skip ci]

* update readme.md [skip ci]

* update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2025-03-08 22:21:39 +01:00
Emmanuel Ferdman
894bf12899 Update foam-logging-in-vscode.md reference (#1431)
Signed-off-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>
2025-03-08 22:20:29 +01:00
Riccardo
a3b375e248 Fix wikilink embed (#1430)
* wikilink embed: fixed bug in resolution, and extracted getNoteContent function

Bug was due to missing base note when resolving links in `withLinksRelativeToWorkspaceRoot`.
Also updated link to be rendered in HTML as absolute

* Improved embed reporting when running in web mode

* Fixed test (by not preloading document selectors)
2025-03-08 17:01:45 +01:00
Riccardo Ferretti
c840070a3a renamed references to branch master into main 2025-03-07 17:11:11 +01:00
Riccardo Ferretti
8a6551f281 updating graph when file change detected also for web extension 2025-03-07 16:56:37 +01:00
Riccardo Ferretti
88ae96cf25 v0.26.5 2025-02-21 13:09:18 +01:00
Riccardo Ferretti
acfd2e1fc1 Preparation for next release 2025-02-21 13:08:32 +01:00
Riccardo
6b02a87538 Web extension support for daily note (#1426)
* Using nodemon for watch task

* Added documentation and generator pattern to support getting some data from multiple sources

* asAbsoluteUrl can now take URI or string

* Tweaked daily note computation

* Replacing URI.withFragment with generic URI.with

* Removed URI.file from non-testing code

* fixed asAbsoluteUri

* Various tweaks and fixes

* Fixed create-note command
2025-02-21 13:07:00 +01:00
allcontributors[bot]
1a99e693df add n8layman as a contributor for code (#1425)
* update docs/index.md [skip ci]

* update readme.md [skip ci]

* update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2025-02-21 09:28:47 +01:00
Nathan Layman
dd467ce86f Add config argument to specify valid markdown flavors (#1424)
* Try and add in support for quarto wikilink autocomplete as in https://github.com/MilesMcBain/foam

* Try and add in support for quarto wikilink autocomplete as in https://github.com/MilesMcBain/foam but make it general based on a new config in settings.json, "foam.supportedLanguages". That should allow for rmarkdown files as well.

* remove package-lock.json in favor of yarn.lock
2025-02-21 09:27:46 +01:00
Riccardo
7d7446ef7e added reference to alias in documentation 2025-01-26 14:54:56 +01:00
allcontributors[bot]
6be4f002b8 add markschaver as a contributor for doc (#1416)
* update docs/index.md [skip ci]

* update readme.md [skip ci]

* update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-11-23 18:48:42 +01:00
Mark Schaver
bb6faee06d Edited command menu titles to make case consistent (#1415) 2024-11-23 18:48:07 +01:00
Riccardo Ferretti
31cfeb3034 v0.26.4 2024-11-12 22:53:21 +01:00
Riccardo Ferretti
5d11818ffc Prepare for next release 2024-11-12 22:53:11 +01:00
Riccardo
e7ee143544 Improved URI handling for virtual FS (#1409)
* Improved URI handling for virtual FS

* Ensure virtual filesystem is accepted as mdSelector

---------

Co-authored-by: Paul de Raaij <paul@paulderaaij.nl>
2024-11-12 22:51:59 +01:00
Riccardo Ferretti
aa311b2688 v0.26.3 2024-11-12 22:48:36 +01:00
Riccardo Ferretti
0fca141a7b Preparing for next release 2024-11-12 22:48:15 +01:00
Paul de Raaij
6a4bd341ab Stop iterating over all resources for finding matching identifiers (#1411) 2024-11-12 22:40:52 +01:00
Riccardo Ferretti
764750f591 v0.26.2 2024-11-06 15:29:44 +01:00
Riccardo Ferretti
2686b9a365 Preparation for next release 2024-11-06 15:29:28 +01:00
Paul de Raaij
5a6ef644bd Improve performance via Triemap in workspace (#1406) 2024-10-15 22:52:13 +02:00
Riccardo Ferretti
d7c92f8284 v0.26.1 2024-10-09 14:01:54 -07:00
Riccardo Ferretti
c2e5e4bf2a Preparation for next release 2024-10-09 14:01:27 -07:00
Paul de Raaij
9d0ba879d2 Add polyfills to web bundle (#1401)
* Add missing dev dependency on vscode-test-web

* Package polyfills to make web extension fully work
2024-10-09 22:18:32 +02:00
Riccardo Ferretti
9606dcc64c v0.26.0 2024-10-01 13:32:58 -07:00
Riccardo Ferretti
d70e441790 Preparation for next release 2024-10-01 13:32:35 -07:00
Riccardo
dde11f8c6f Foam as Web Extension (#1395)
See https://github.com/foambubble/foam/pull/1290 for context.
Major thanks to @pderaaij that did all the hard work here.

* using js-sha1 instead of node's crypto to compute sha1
* Using esbuild to bundle native and web extension (WIP)
* Added message regarding unsupported embeds in web extension
* support for graph webview in web extension
2024-09-17 09:57:38 +02:00
allcontributors[bot]
cd9ee4d556 add PiotrAleksander as a contributor for doc (#1394)
* update docs/index.md [skip ci]

* update readme.md [skip ci]

* update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-09-12 22:05:38 +02:00
Piotr Mrzygłosz
a8296c2c88 403 potential error-related comment (#1393) 2024-09-12 22:04:46 +02:00
Paul de Raaij
13a340eb1d Exclude workspace when linking to a file (#1372)
* Exclude workspace when linking to a file
2024-08-22 19:22:35 +02:00
allcontributors[bot]
d2dd979e70 add Hegghammer as a contributor for doc (#1384)
* update docs/index.md [skip ci]

* update readme.md [skip ci]

* update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-08-15 09:42:06 +02:00
Declan Millar
4989796cb0 docs: fix spelling and grammar (#1382) 2024-08-15 09:41:28 +02:00
Thomas Hegghammer
d24814d065 Add pdf export recipe (#1383) 2024-08-15 09:40:41 +02:00
Riccardo Ferretti
4a410d1f5c v0.25.12 2024-07-13 14:14:16 +02:00
Riccardo Ferretti
ccb92ad5ee Preparation for next release 2024-07-13 14:13:04 +02:00
Riccardo Ferretti
e6512cffa8 Fixed imports 2024-07-13 14:09:32 +02:00
Riccardo Ferretti
1fa4f37d96 Moved around settings functions 2024-07-13 13:51:23 +02:00
Riccardo Ferretti
27b9b451ad Refactored utils.ts 2024-07-13 13:51:23 +02:00
allcontributors[bot]
362d6f8e09 add hereistheusername as a contributor for code (#1367)
* update docs/index.md [skip ci]

* update readme.md [skip ci]

* update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-07-10 20:39:31 +02:00
Xinglan Liu
cef8d2a532 generate copy without wikilinks (#1365)
* add generate standalone note command

* fix embeded wikilinks

* refactor convertLinksFormat function & add 4 user command interfaces

* change user interface

* modify createUpdateLinkEdit to accomplish convert

* only images can be embedded

* keey filename when using in page anchor

* give a default value to alias in link format combination branch

* add tests to createUpdateLinkEdit about changint links' type and isEmbed

* get target from getIdentifier

---------

Co-authored-by: Riccardo <code@riccardoferretti.com>
2024-07-10 20:38:51 +02:00
Riccardo Ferretti
22b837f252 Adding redirect to code-of-conduct 2024-06-03 23:43:54 +02:00
allcontributors[bot]
07e02c2d69 add Walshkev as a contributor for doc (#1359)
* update docs/index.md [skip ci]

* update readme.md [skip ci]

* update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-05-31 13:15:28 +02:00
Kevin Walsh
931ad7a5b6 [doc] documentation additions (#1358)
* Update readme.md to include code of conduct 

lack of code of conduct that was in the previous version of foam _1

* added the Contribution Guide 

this has a link to their Contribution Guide that is already on the foams website.

* Update readme.md presentation 

added See the before the contribution guide and code of conduct links to make it more professional

* Update readme.md

---------

Co-authored-by: Riccardo <code@riccardoferretti.com>
2024-05-31 13:15:13 +02:00
Riccardo Ferretti
db7eb9775f Improved YAML regex delimiter
Fixes #1347
2024-03-22 09:23:57 +01:00
Riccardo Ferretti
b25152d115 v0.25.11 2024-03-18 13:17:27 +01:00
Riccardo Ferretti
1545079c62 Prepare for release 2024-03-18 13:17:04 +01:00
Riccardo Ferretti
4835164902 Fire onDidUpdate even only after full graph recomputed 2024-03-18 13:16:26 +01:00
Riccardo Ferretti
06efdc2865 v0.25.10 2024-03-18 10:09:31 +01:00
Riccardo Ferretti
b68fd7e138 Prepare for release 2024-03-18 10:09:04 +01:00
Riccardo Ferretti
d8baa2fd36 Fixed graph computation issue 2024-03-18 10:06:18 +01:00
Riccardo Ferretti
7f587095e8 v0.25.9 2024-03-17 20:51:24 +01:00
Riccardo Ferretti
77ad245319 Prepare next release 2024-03-17 20:51:16 +01:00
Riccardo
b892c783da Rename placeholder on note creation so it can update it if necessary (#1344)
* Introduced Location
* Passing a reference to the source link to the create-note command

Also
* Added withTiming fn for performance logging
* Added extra test to check incoming wikilink with sections
* Tweaked creation of vscode URI to also support raw objects
2024-03-17 20:49:11 +01:00
Andrew Thiesen
e4f6259104 Update recipes.md (#1341)
REM foamy-js. https://github.com/yenly/foamy-nextjs has been Archived and is no longer being maintained.
2024-03-10 15:18:37 +01:00
Riccardo Ferretti
aa197239fc v0.25.8 2024-02-21 09:53:40 +01:00
Riccardo Ferretti
8f3c23dd60 Prepare for release 2024-02-21 09:47:23 +01:00
Richard C Yeh
9a027c08ba Bump dateformat from 3.0.3 to 4.5.1. Closes #1324. (#1326) 2024-02-19 10:15:34 +01:00
dependabot[bot]
959d0f1ea1 Bump axios from 1.3.2 to 1.6.1 (#1306)
Bumps [axios](https://github.com/axios/axios) from 1.3.2 to 1.6.1.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.3.2...v1.6.1)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-23 19:48:34 +01:00
dependabot[bot]
57e32c4349 Bump @babel/traverse from 7.20.13 to 7.23.2 (#1297)
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.20.13 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-23 19:48:10 +01:00
dependabot[bot]
f168f66368 Bump follow-redirects from 1.15.2 to 1.15.4 (#1318)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.2 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.2...v1.15.4)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-23 19:47:46 +01:00
Riccardo Ferretti
103ff12b2d v0.25.7 2024-01-16 14:47:01 +01:00
Riccardo Ferretti
96a3afa132 Prepare for release 2024-01-16 14:46:11 +01:00
Riccardo Ferretti
d586e63104 Removed "use" from quick fix list elements 2024-01-16 14:29:54 +01:00
Miguel Angel Bruni Montero
2fba6e9008 Modifies url encoding to target only the filename and skip spaces (#1322) 2024-01-15 20:22:04 +01:00
Riccardo
cdbb965661 Update commands.md 2023-12-15 22:31:54 +01:00
Riccardo Ferretti
0c958e31f6 v0.25.6 2023-12-13 20:01:32 +01:00
Riccardo Ferretti
fd84fcfd74 Preparing next release 2023-12-13 20:01:00 +01:00
allcontributors[bot]
becf495edc add MABruni as a contributor for code (#1313)
* update docs/index.md [skip ci]

* update readme.md [skip ci]

* update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2023-12-13 19:59:11 +01:00
Miguel Angel Bruni Montero
4d99883c03 Fix #1298. Wikilink definitions links not working. (#1311)
* Added encoding to handle special characters when wikilink definitions are generated

* Added tests for wikilinks encoding and updated previous tests to support encoded links
2023-12-13 19:58:36 +01:00
Riccardo Ferretti
8bd679c751 v0.25.5 2023-11-30 19:19:26 +01:00
Riccardo Ferretti
8986ee286c Preparation for next release 2023-11-30 19:19:03 +01:00
Riccardo
51b1af1981 Using note title in preview panel (#1309)
See conversation in https://github.com/foambubble/foam/pull/1150
2023-11-30 19:17:50 +01:00
Daniel Carosone
4276e8043f extension descriptions now reflect .vscode dir (#1302)
* extension descriptions now reflect .vscode dir

including that image pasting is now supported natively

* Markdown AiO is still recommended

---------

Co-authored-by: Daniel Carosone <dan@geek.com.au>
2023-10-26 10:24:51 +02:00
Riccardo Ferretti
8d1e9b15ce Readding markdown-all-in-one in extension list and fixing script 2023-10-26 10:23:45 +02:00
jonathan berger
bfcfad32e8 Add more detail to Support for sections. (#1296)
* Add more detail to Support for `sections`.

* Removed extra quote

---------

Co-authored-by: Riccardo <code@riccardoferretti.com>
2023-10-25 12:53:00 +02:00
allcontributors[bot]
abe18cc961 add dcarosone as a contributor for doc (#1301)
* update docs/index.md [skip ci]

* update readme.md [skip ci]

* update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2023-10-25 11:43:29 +02:00
Riccardo Ferretti
9490aa2dad Fixed typo 2023-10-25 11:36:34 +02:00
Riccardo Ferretti
6c3f4588b3 Explicitly create .vscode folder in sync script 2023-10-25 11:26:07 +02:00
Riccardo Ferretti
21b8c5b827 Updated default extensions and settings for foam docs and template
Also monitoring the folder to trigger the appropriate sync job
2023-10-25 11:22:46 +02:00
Daniel Carosone
c66ed74aea replicate .vscode dir to template (#1300)
fixes #1299

Co-authored-by: Daniel Carosone <dan@geek.com.au>
2023-10-25 11:17:43 +02:00
Riccardo Ferretti
3876811fb6 v0.25.4 2023-10-19 17:41:13 +02:00
Riccardo Ferretti
d9299ee9d4 Prepare for next release 2023-10-19 17:40:29 +02:00
Joe Taber
23e21a62f3 Copy assets to foam-template repo (#1238) 2023-10-12 17:22:38 +02:00
Riccardo
e7c8d5a4eb Added support for linking to sections in current file (#1289)
And improved support for links to sections that need to be slugified
2023-10-12 17:01:13 +02:00
Daniel Wang
3ef1b69b2e Fix embedded notes not generating proper reference links (#1286) 2023-09-25 10:09:13 +02:00
117 changed files with 6208 additions and 3590 deletions

View File

@@ -1058,6 +1058,96 @@
"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"
]
},
{
"login": "Tenormis",
"name": "Tenormis",
"avatar_url": "https://avatars.githubusercontent.com/u/61572102?v=4",
"profile": "https://github.com/Tenormis",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,

View File

@@ -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

View File

@@ -3,10 +3,10 @@ name: CI
on:
push:
branches:
- master
- main
pull_request:
branches:
- master
- main
jobs:
typos-check:

View File

@@ -3,9 +3,11 @@ name: Update Docs
on:
push:
branches:
- master
- main
paths:
- docs/user/**/*
- docs/.vscode/**/*
- docs/assets/**/*
workflow_dispatch:
jobs:
@@ -20,11 +22,15 @@ jobs:
- uses: actions/checkout@v3
with:
path: foam
- name: Copy and fixup user docs files
- 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.

1
.gitignore vendored
View File

@@ -1,6 +1,7 @@
node_modules
.DS_Store
.vscode-test/
.vscode-test-web/
*.tsbuildinfo
*.vsix
*.log

23
.vscode/tasks.json vendored
View File

@@ -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"

View File

@@ -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"

View File

@@ -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"
}

View File

@@ -3,6 +3,6 @@
[
{
"key": "cmd+shift+n",
"command": "vscodeMarkdownNotes.newNote"
"command": "foam-vscode.create-note"
}
]

View File

@@ -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"]
}

View File

@@ -1 +0,0 @@
Backlinking

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

View File

@@ -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.

View File

@@ -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.
@@ -13,6 +14,8 @@ Before you start contributing we recommend that you read the following links:
- [[principles]] - This document describes the guiding principles behind Foam.
- [[code-of-conduct]] - Rules we hope every contributor aims to follow, allowing everyone to participate in our community!
To get yourself familiar with the codebase you can also browse [this repo](https://app.komment.ai/wiki/github/foambubble/foam)
## Diving In
We understand that diving in an unfamiliar codebase may seem scary,
@@ -44,19 +47,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 +84,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.

View File

@@ -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"

View File

@@ -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
@@ -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).

View 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.**

View File

@@ -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

View File

@@ -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

View File

@@ -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]].
@@ -255,6 +255,18 @@ 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="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>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Tenormis"><img src="https://avatars.githubusercontent.com/u/61572102?v=4?s=60" width="60px;" alt="Tenormis"/><br /><sub><b>Tenormis</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Tenormis" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -10,13 +10,13 @@ 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.
- 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
- `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:

View File

@@ -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)

View File

@@ -38,6 +38,7 @@ A sample configuration object is provided below, you can provide as many or as l
"foam.graph.style": {
"background": "#202020",
"fontSize": 12,
"fontFamily": "Sans-Serif",
"lineColor": "#277da1",
"lineWidth": 0.2,
"particleWidth": 1.0,
@@ -50,6 +51,7 @@ A sample configuration object is provided below, you can provide as many or as l
- `background` background color of the graph, adjust to increase contrast
- `fontSize` size of the title font for each node
- `fontFamily` font of the title font for each node
- `lineColor` color of the edges between nodes in the graph
- `lineWidth` thickness of the edges between nodes
- `particleWidth` size of the particle animation showing link direction when highlighting a node

View File

@@ -1,22 +1,22 @@
# 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
@@ -53,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.

View File

@@ -32,6 +32,7 @@ Some properties have special meaning for Foam:
| `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
---
```

View File

@@ -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
![Create new template GIF](../../assets/images/create-new-template.gif)
@@ -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)
![Create new note from template GIF](../../assets/images/create-new-note-from-template.gif)
@@ -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,12 +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_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. |
| 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
@@ -70,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
@@ -84,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.
@@ -97,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.
@@ -146,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
```
@@ -166,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:
@@ -210,7 +211,7 @@ foam_template:
---
---
existing_frontmatter: "Existing Frontmatter block"
existing_frontmatter: 'Existing Frontmatter block'
---
This is the rest of the template
```

View File

@@ -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

View File

@@ -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)

View File

@@ -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).

View File

@@ -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.

View File

@@ -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

View 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:
![Sample PDF output](../../assets/images/pdf_output.png)
## 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`.

View File

@@ -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"

View File

@@ -4,5 +4,5 @@
],
"npmClient": "yarn",
"useWorkspaces": true,
"version": "0.25.3"
"version": "0.26.11"
}

View File

@@ -6,6 +6,7 @@ out/**/*.spec.*
test-data/**
src/**
jest.config.js
esbuild.js
.test-workspace
.gitignore
vsc-extension-quickstart.md

View File

@@ -4,6 +4,147 @@ 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.11] - 2025-04-19
Fixes and Improvements:
- Support for custom fonts in graph view (#1457 - thanks @Tenormis)
## [0.26.10] - 2025-03-29
Fixes and Improvements:
- General improvment of wiki embeds (#1443)
## [0.26.9] - 2025-03-29
Fixes and Improvements:
- Defensive get of link object ID in graph (#1438)
Internal:
- Updated `force-graph` library
## [0.26.8] - 2025-03-14
Fixes and Improvements:
- Tag hierarchy now visible in graph (#1436)
- Improved Notes Explorer layout
## [0.26.7] - 2025-03-09
Fixes 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:

View 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);
});

View File

@@ -8,11 +8,11 @@
"type": "git"
},
"homepage": "https://github.com/foambubble/foam",
"version": "0.25.3",
"version": "0.26.11",
"license": "MIT",
"publisher": "foam",
"engines": {
"vscode": "^1.70.0"
"vscode": "^1.96.0"
},
"icon": "assets/icon/FOAM_ICON_256.png",
"categories": [
@@ -21,7 +21,8 @@
"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",
@@ -300,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-wikilink-definitions",
"title": "Foam: Update wikilink definitions"
"title": "Foam: Update Wikilink Definitions"
},
{
"command": "foam-vscode.open-daily-note",
@@ -346,6 +347,14 @@
"command": "foam-vscode.open-resource",
"title": "Foam: Open Resource"
},
{
"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",
@@ -396,6 +405,11 @@
"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",
@@ -448,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",
@@ -649,21 +670,23 @@
]
},
"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",
"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"
@@ -680,8 +703,10 @@
"@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",
@@ -691,6 +716,7 @@
"jest-extended": "^3.2.3",
"markdown-it": "^12.0.4",
"micromatch": "^4.0.2",
"nodemon": "^3.1.7",
"rimraf": "^3.0.2",
"ts-jest": "^29.1.1",
"tslib": "^2.0.0",
@@ -699,13 +725,16 @@
"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.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",

View File

@@ -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 {

View File

@@ -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/

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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> {

View File

@@ -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);

View File

@@ -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);

View File

@@ -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';

View File

@@ -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]);
});
});
});

View File

@@ -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`
);
}

View File

@@ -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 () => {

View File

@@ -1,2 +1,3 @@
export { generateLinkReferences } from './generate-link-references';
export { generateHeading } from './generate-headings';
export { convertLinkFormat } from './convert-links-format';

View File

@@ -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;
@@ -28,24 +28,25 @@ export const bootstrap = async (
initialProviders: ResourceProvider[],
defaultExtension: string = '.md'
) => {
const tsStart = Date.now();
const workspace = await FoamWorkspace.fromProviders(
initialProviders,
dataStore,
defaultExtension
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)) {

View File

@@ -139,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',

View 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);
}
}

View File

@@ -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'),

View File

@@ -58,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 });
@@ -71,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)
@@ -119,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,
});
}
/**
@@ -195,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;
@@ -381,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);
}

View File

@@ -126,9 +126,9 @@ describe('Identifier computation', () => {
});
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';

View File

@@ -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,7 @@ 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`)
@@ -33,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);
@@ -41,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;
@@ -57,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 {
@@ -70,17 +78,21 @@ export class FoamWorkspace implements IDisposable {
}
public listByIdentifier(identifier: string): Resource[] {
const needle = normalize('/' + identifier);
let needle = this.getTrieIdentifier(identifier);
const mdNeedle =
getExtension(needle) !== this.defaultExtension
? needle + this.defaultExtension
getExtension(normalize(identifier)) !== this.defaultExtension
? this.getTrieIdentifier(identifier + this.defaultExtension)
: undefined;
const resources: Resource[] = [];
for (const key of this._resources.keys()) {
if (key.endsWith(mdNeedle) || key.endsWith(needle)) {
resources.push(this._resources.get(normalize(key)));
}
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(Resource.sortByPath);
}
@@ -92,21 +104,19 @@ 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,
@@ -119,9 +129,32 @@ export class FoamWorkspace implements IDisposable {
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('#');
@@ -135,14 +168,17 @@ export class FoamWorkspace implements IDisposable {
: isSome(baseUri)
? baseUri.resolve(candidate).path
: null;
resource = this._resources.get(normalize(searchKey));
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;
}

View File

@@ -254,4 +254,185 @@ describe('MarkdownLink', () => {
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(), `![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);
});
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(), `![link](to/path.md)`).links[0];
const linkAddSection = MarkdownLink.createUpdateLinkEdit(link, {
section: 'one section',
});
expect(linkAddSection.newText).toEqual(
`![link](<to/path.md#one section>)`
);
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);
});
});
});

View File

@@ -1,4 +1,5 @@
import { ResourceLink } from '../model/note';
import { TextEdit } from './text-edit';
export abstract class MarkdownLink {
private static wikilinkRegex = new RegExp(
@@ -38,29 +39,41 @@ 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;
}
): TextEdit {
const { target, section, alias } = MarkdownLink.analyzeLink(link);
const newTarget = delta.target ?? target;
const newSection = delta.section ?? section ?? '';
const newAlias = delta.alias ?? alias ?? '';
const sectionDivider = newSection ? '#' : '';
const aliasDivider = newAlias ? '|' : '';
const embed = link.isEmbed ? '!' : '';
if (link.type === 'wikilink') {
const embed = delta.isEmbed ?? link.isEmbed ? '!' : '';
const type = delta.type ?? link.type;
if (type === 'wikilink') {
return {
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: `${embed}[${newAlias}](${newTarget}${sectionDivider}${newSection})`,
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`);
}
}

View File

@@ -320,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),
});
});
});

View File

@@ -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)
),
});
}
}
@@ -459,9 +495,9 @@ export const getBlockFor = (
}
});
let nLines = startLine == -1 ? 1 : endLine - startLine;
let nLines = startLine === -1 ? 1 : endLine - startLine;
let block =
startLine == -1
startLine === -1
? lines[searchLine] ?? ''
: lines.slice(startLine, endLine).join('\n');

View File

@@ -148,7 +148,7 @@ describe('Link resolution', () => {
const ws = createTestWorkspace().set(noteA).set(noteB);
expect(ws.resolveLink(noteA, noteA.links[0])).toEqual(
noteB.uri.withFragment('section')
noteB.uri.with({ fragment: 'section' })
);
});
@@ -163,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', () => {
@@ -311,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',
]);
});
});

View File

@@ -76,7 +76,7 @@ export class MarkdownResourceProvider implements ResourceProvider {
URI.placeholder(target);
if (section) {
targetUri = targetUri.withFragment(section);
targetUri = targetUri.with({ fragment: section });
}
}
break;
@@ -93,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;
}
@@ -137,13 +137,21 @@ export function createMarkdownReferences(
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,
};
})

View File

@@ -1,24 +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();
}
}

View File

@@ -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;
};

View File

@@ -1,67 +1,4 @@
import {
isInFrontMatter,
isOnYAMLKeywordLine,
removeBrackets,
toTitleCase,
} from './utils';
describe('removeBrackets', () => {
it('removes the brackets', () => {
const input = 'hello world [[this-is-it]]';
const actual = removeBrackets(input);
const expected = 'hello world This Is It';
expect(actual).toEqual(expected);
});
it('removes the brackets and the md file extension', () => {
const input = 'hello world [[this-is-it.md]]';
const actual = removeBrackets(input);
const expected = 'hello world This Is It';
expect(actual).toEqual(expected);
});
it('removes the brackets and the mdx file extension', () => {
const input = 'hello world [[this-is-it.mdx]]';
const actual = removeBrackets(input);
const expected = 'hello world This Is It';
expect(actual).toEqual(expected);
});
it('removes the brackets and the markdown file extension', () => {
const input = 'hello world [[this-is-it.markdown]]';
const actual = removeBrackets(input);
const expected = 'hello world This Is It';
expect(actual).toEqual(expected);
});
it('removes the brackets even with numbers', () => {
const input = 'hello world [[2020-07-21.markdown]]';
const actual = removeBrackets(input);
const expected = 'hello world 2020 07 21';
expect(actual).toEqual(expected);
});
it('removes brackets for more than one word', () => {
const input =
'I am reading this as part of the [[book-club]] put on by [[egghead]] folks (Lauro).';
const actual = removeBrackets(input);
const expected =
'I am reading this as part of the Book Club put on by Egghead folks (Lauro).';
expect(actual).toEqual(expected);
});
});
describe('toTitleCase', () => {
it('title cases a word', () => {
const input =
'look at this really long sentence but I am calling it a word';
const actual = toTitleCase(input);
const expected =
'Look At This Really Long Sentence But I Am Calling It A Word';
expect(actual).toEqual(expected);
});
it('works on one word', () => {
const input = 'word';
const actual = toTitleCase(input);
const expected = 'Word';
expect(actual).toEqual(expected);
});
});
import { isInFrontMatter, isOnYAMLKeywordLine } from './md';
describe('isInFrontMatter', () => {
it('is true for started front matter', () => {
@@ -81,6 +18,12 @@ describe('isInFrontMatter', () => {
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);

View 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;
}

View File

@@ -1,5 +1,5 @@
import { workspace } from 'vscode';
import { createDailyNoteIfNotExists, getDailyNotePath } from './dated-notes';
import { createDailyNoteIfNotExists, getDailyNoteUri } from './dated-notes';
import { isWindows } from './core/common/platform';
import {
cleanWorkspace,
@@ -10,8 +10,9 @@ import {
withModifiedFoamConfiguration,
} from './test/test-utils-vscode';
import { fromVsCodeUri } from './utils/vsc-utils';
import { URI } from './core/model/uri';
describe('getDailyNotePath', () => {
describe('getDailyNoteUri', () => {
const date = new Date('2021-02-07T00:00:00Z');
const year = date.getFullYear();
const month = date.getMonth() + 1;
@@ -21,12 +22,12 @@ describe('getDailyNotePath', () => {
test('Adds the root directory to relative directories', async () => {
const config = 'journal';
const expectedPath = fromVsCodeUri(
const expectedUri = fromVsCodeUri(
workspace.workspaceFolders[0].uri
).joinPath(config, `${isoDate}.md`);
await withModifiedFoamConfiguration('openDailyNote.directory', config, () =>
expect(getDailyNotePath(date).toFsPath()).toEqual(expectedPath.toFsPath())
expect(getDailyNoteUri(date)).toEqual(expectedUri)
);
});
@@ -39,7 +40,7 @@ describe('getDailyNotePath', () => {
: `${config}/${isoDate}.md`;
await withModifiedFoamConfiguration('openDailyNote.directory', config, () =>
expect(getDailyNotePath(date).toFsPath()).toMatch(expectedPath)
expect(getDailyNoteUri(date).toFsPath()).toMatch(expectedPath)
);
});
});
@@ -54,7 +55,7 @@ describe('Daily note template', () => {
['.foam', 'templates', 'daily-note.md']
);
const uri = getDailyNotePath(targetDate);
const uri = getDailyNoteUri(targetDate);
await createDailyNoteIfNotExists(targetDate);

View File

@@ -1,9 +1,9 @@
import { joinPath } from './core/utils/path';
import dateFormat from 'dateformat';
import { focusNote } from './utils';
import { URI } from './core/model/uri';
import { NoteFactory } from './services/templates';
import { getFoamVsCodeConfig } from './services/config';
import { asAbsoluteWorkspaceUri } from './services/editor';
import { asAbsoluteWorkspaceUri, focusNote } from './services/editor';
/**
* Open the daily note file.
@@ -33,17 +33,13 @@ export async function openDailyNoteFor(date?: Date) {
* This function first checks the `foam.openDailyNote.directory` configuration string,
* defaulting to the current directory.
*
* In the case that the directory path is not absolute,
* the resulting path will start on the current workspace top-level.
*
* @param date A given date to be formatted as filename.
* @returns The path to the daily note file.
* @returns The URI to the daily note file.
*/
export function getDailyNotePath(date: Date): URI {
export function getDailyNoteUri(date: Date): URI {
const folder = getFoamVsCodeConfig<string>('openDailyNote.directory') ?? '.';
const dailyNoteDirectory = asAbsoluteWorkspaceUri(URI.file(folder));
const dailyNoteFilename = getDailyNoteFileName(date);
return dailyNoteDirectory.joinPath(dailyNoteFilename);
return asAbsoluteWorkspaceUri(joinPath(folder, dailyNoteFilename));
}
/**
@@ -77,20 +73,20 @@ export function getDailyNoteFileName(date: Date): string {
* @returns Whether the file was created.
*/
export async function createDailyNoteIfNotExists(targetDate: Date) {
const pathFromLegacyConfiguration = getDailyNotePath(targetDate);
const uriFromLegacyConfiguration = getDailyNoteUri(targetDate);
const pathFromLegacyConfiguration = uriFromLegacyConfiguration.toFsPath();
const titleFormat: string =
getFoamVsCodeConfig('openDailyNote.titleFormat') ??
getFoamVsCodeConfig('openDailyNote.filenameFormat');
const templateFallbackText = `---
foam_template:
filepath: "${pathFromLegacyConfiguration.toFsPath().replace(/\\/g, '\\\\')}"
---
# ${dateFormat(targetDate, titleFormat, false)}
`;
const templateFallbackText = `# ${dateFormat(
targetDate,
titleFormat,
false
)}\n`;
return await NoteFactory.createFromDailyNoteTemplate(
pathFromLegacyConfiguration,
uriFromLegacyConfiguration,
templateFallbackText,
targetDate
);

View File

@@ -17,7 +17,6 @@ import { VsCodeWatcher } from './services/watcher';
import { createMarkdownParser } from './core/services/markdown-parser';
import VsCodeBasedParserCache from './services/cache';
import { createMatcherAndDataStore } from './services/editor';
import { getFoamVsCodeConfig } from './services/config';
export async function activate(context: ExtensionContext) {
const logger = new VsCodeOutputLogger();
@@ -73,7 +72,9 @@ export async function activate(context: ExtensionContext) {
);
// Load the features
const resPromises = features.map(feature => feature(context, foamPromise));
const featuresPromises = features.map(feature =>
feature(context, foamPromise)
);
const foam = await foamPromise;
Logger.info(`Loaded ${foam.workspace.list().length} resources`);
@@ -102,14 +103,15 @@ export async function activate(context: ExtensionContext) {
})
);
const res = (await Promise.all(resPromises)).filter(r => r != null);
const feats = (await Promise.all(featuresPromises)).filter(r => r != null);
return {
extendMarkdownIt: (md: markdownit) => {
return res.reduce((acc: markdownit, r: any) => {
return feats.reduce((acc: markdownit, r: any) => {
return r.extendMarkdownIt ? r.extendMarkdownIt(acc) : acc;
}, md);
},
foam,
};
} catch (e) {
Logger.error('An error occurred while bootstrapping Foam', e);

View File

@@ -0,0 +1,188 @@
import { commands, ExtensionContext, window, workspace, Uri } from 'vscode';
import { Foam } from '../../core/model/foam';
import { FoamWorkspace } from '../../core/model/workspace';
import { fromVsCodeUri, toVsCodeRange } from '../../utils/vsc-utils';
import { ResourceParser } from '../../core/model/note';
import { IMatcher } from '../../core/services/datastore';
import { convertLinkFormat } from '../../core/janitor';
import { isMdEditor } from '../../services/editor';
type LinkFormat = 'wikilink' | 'link';
enum ConvertOption {
Wikilink2MDlink,
MDlink2Wikilink,
}
interface IConfig {
from: string;
to: string;
}
const Config: { [key in ConvertOption]: IConfig } = {
[ConvertOption.Wikilink2MDlink]: {
from: 'wikilink',
to: 'link',
},
[ConvertOption.MDlink2Wikilink]: {
from: 'link',
to: 'wikilink',
},
};
export default async function activate(
context: ExtensionContext,
foamPromise: Promise<Foam>
) {
const foam = await foamPromise;
/*
commands:
foam-vscode.convert-link-style-inplace
foam-vscode.convert-link-style-incopy
*/
context.subscriptions.push(
commands.registerCommand('foam-vscode.convert-link-style-inplace', () => {
return convertLinkAdapter(
foam.workspace,
foam.services.parser,
foam.services.matcher,
true
);
}),
commands.registerCommand('foam-vscode.convert-link-style-incopy', () => {
return convertLinkAdapter(
foam.workspace,
foam.services.parser,
foam.services.matcher,
false
);
})
);
}
async function convertLinkAdapter(
fWorkspace: FoamWorkspace,
fParser: ResourceParser,
fMatcher: IMatcher,
isInPlace: boolean
) {
const convertOption = await pickConvertStrategy();
if (!convertOption) {
window.showInformationMessage('Convert canceled');
return;
}
if (isInPlace) {
await convertLinkInPlace(fWorkspace, fParser, fMatcher, convertOption);
} else {
await convertLinkInCopy(fWorkspace, fParser, fMatcher, convertOption);
}
}
async function pickConvertStrategy(): Promise<IConfig | undefined> {
const options = {
'to wikilink': ConvertOption.MDlink2Wikilink,
'to markdown link': ConvertOption.Wikilink2MDlink,
};
return window.showQuickPick(Object.keys(options)).then(name => {
if (name) {
return Config[options[name]];
} else {
return undefined;
}
});
}
/**
* convert links based on its workspace and the note containing it.
* Changes happen in-place
* @param fWorkspace
* @param fParser
* @param fMatcher
* @param convertOption
* @returns void
*/
async function convertLinkInPlace(
fWorkspace: FoamWorkspace,
fParser: ResourceParser,
fMatcher: IMatcher,
convertOption: IConfig
) {
const editor = window.activeTextEditor;
const doc = editor.document;
if (!isMdEditor(editor) || !fMatcher.isMatch(fromVsCodeUri(doc.uri))) {
return;
}
// const eol = getEditorEOL();
let text = doc.getText();
const resource = fParser.parse(fromVsCodeUri(doc.uri), text);
const textReplaceArr = resource.links
.filter(link => link.type === convertOption.from)
.map(link =>
convertLinkFormat(
link,
convertOption.to as LinkFormat,
fWorkspace,
resource
)
)
/* transform .range property into vscode range */
.map(linkReplace => ({
...linkReplace,
range: toVsCodeRange(linkReplace.range),
}));
/* reorder the array such that the later range comes first */
textReplaceArr.sort((a, b) => b.range.start.compareTo(a.range.start));
await editor.edit(editorBuilder => {
textReplaceArr.forEach(edit => {
editorBuilder.replace(edit.range, edit.newText);
});
});
}
/**
* convert links based on its workspace and the note containing it.
* Changes happen in a copy
* 1. prepare a copy file, and makt it the activeTextEditor
* 2. call to convertLinkInPlace
* @param fWorkspace
* @param fParser
* @param fMatcher
* @param convertOption
* @returns void
*/
async function convertLinkInCopy(
fWorkspace: FoamWorkspace,
fParser: ResourceParser,
fMatcher: IMatcher,
convertOption: IConfig
) {
const editor = window.activeTextEditor;
const doc = editor.document;
if (!isMdEditor(editor) || !fMatcher.isMatch(fromVsCodeUri(doc.uri))) {
return;
}
// const eol = getEditorEOL();
let text = doc.getText();
const resource = fParser.parse(fromVsCodeUri(doc.uri), text);
const basePath = doc.uri.path.split('/').slice(0, -1).join('/');
const fileUri = doc.uri.with({
path: `${
basePath ? basePath + '/' : ''
}${resource.uri.getName()}.copy${resource.uri.getExtension()}`,
});
const encoder = new TextEncoder();
await workspace.fs.writeFile(fileUri, encoder.encode(text));
await window.showTextDocument(fileUri);
await convertLinkInPlace(fWorkspace, fParser, fMatcher, convertOption);
}

View File

@@ -1,9 +1,13 @@
import { env, Position, Selection, commands } from 'vscode';
import { createFile, showInEditor } from '../../test/test-utils-vscode';
import { removeBrackets, toTitleCase } from './copy-without-brackets';
describe('copy-without-brackets command', () => {
it('should get the input from the active editor selection', async () => {
const { uri } = await createFile('This is my [[test-content]].');
const { uri } = await createFile('This is my [[test-content]].', [
'copy-without-brackets',
'file.md',
]);
const { editor } = await showInEditor(uri);
editor.selection = new Selection(new Position(0, 0), new Position(1, 0));
await commands.executeCommand('foam-vscode.copy-without-brackets');
@@ -11,3 +15,61 @@ describe('copy-without-brackets command', () => {
expect(value).toEqual('This is my Test Content.');
});
});
describe('removeBrackets', () => {
it('removes the brackets', () => {
const input = 'hello world [[this-is-it]]';
const actual = removeBrackets(input);
const expected = 'hello world This Is It';
expect(actual).toEqual(expected);
});
it('removes the brackets and the md file extension', () => {
const input = 'hello world [[this-is-it.md]]';
const actual = removeBrackets(input);
const expected = 'hello world This Is It';
expect(actual).toEqual(expected);
});
it('removes the brackets and the mdx file extension', () => {
const input = 'hello world [[this-is-it.mdx]]';
const actual = removeBrackets(input);
const expected = 'hello world This Is It';
expect(actual).toEqual(expected);
});
it('removes the brackets and the markdown file extension', () => {
const input = 'hello world [[this-is-it.markdown]]';
const actual = removeBrackets(input);
const expected = 'hello world This Is It';
expect(actual).toEqual(expected);
});
it('removes the brackets even with numbers', () => {
const input = 'hello world [[2020-07-21.markdown]]';
const actual = removeBrackets(input);
const expected = 'hello world 2020 07 21';
expect(actual).toEqual(expected);
});
it('removes brackets for more than one word', () => {
const input =
'I am reading this as part of the [[book-club]] put on by [[egghead]] folks (Lauro).';
const actual = removeBrackets(input);
const expected =
'I am reading this as part of the Book Club put on by Egghead folks (Lauro).';
expect(actual).toEqual(expected);
});
});
describe('toTitleCase', () => {
it('title cases a word', () => {
const input =
'look at this really long sentence but I am calling it a word';
const actual = toTitleCase(input);
const expected =
'Look At This Really Long Sentence But I Am Calling It A Word';
expect(actual).toEqual(expected);
});
it('works on one word', () => {
const input = 'word';
const actual = toTitleCase(input);
const expected = 'Word';
expect(actual).toEqual(expected);
});
});

View File

@@ -1,5 +1,4 @@
import { window, env, ExtensionContext, commands } from 'vscode';
import { removeBrackets } from '../../utils';
export default async function activate(context: ExtensionContext) {
context.subscriptions.push(
@@ -31,3 +30,46 @@ async function copyWithoutBrackets() {
window.showInformationMessage('Successfully copied to clipboard!');
}
}
/**
* Used for the "Copy to Clipboard Without Brackets" command
*
*/
export function removeBrackets(s: string): string {
// take in the string, split on space
const stringSplitBySpace = s.split(' ');
// loop through words
const modifiedWords = stringSplitBySpace.map(currentWord => {
if (currentWord.includes('[[')) {
// all of these transformations will turn this "[[you-are-awesome]]"
// to this "you are awesome"
let word = currentWord.replace(/(\[\[)/g, '');
word = word.replace(/(\]\])/g, '');
word = word.replace(/(.mdx|.md|.markdown)/g, '');
word = word.replace(/[-]/g, ' ');
// then we titlecase the word so "you are awesome"
// becomes "You Are Awesome"
const titleCasedWord = toTitleCase(word);
return titleCasedWord;
}
return currentWord;
});
return modifiedWords.join(' ');
}
/**
* Takes in a string and returns it titlecased
*
* @example toTitleCase("hello world") -> "Hello World"
*/
export function toTitleCase(word: string): string {
return word
.split(' ')
.map(word => word[0].toUpperCase() + word.substring(1))
.join(' ');
}

View File

@@ -1,4 +1,4 @@
import { commands, window } from 'vscode';
import { commands, window, workspace } from 'vscode';
import { URI } from '../../core/model/uri';
import { asAbsoluteWorkspaceUri, readFile } from '../../services/editor';
import {
@@ -10,7 +10,11 @@ import {
showInEditor,
} from '../../test/test-utils-vscode';
import { fromVsCodeUri } from '../../utils/vsc-utils';
import { CREATE_NOTE_COMMAND } from './create-note';
import { CREATE_NOTE_COMMAND, createNote } from './create-note';
import { Location } from '../../core/model/location';
import { Range } from '../../core/model/range';
import { ResourceLink } from '../../core/model/note';
import { createMarkdownParser } from '../../core/services/markdown-parser';
describe('create-note command', () => {
afterEach(() => {
@@ -131,44 +135,53 @@ describe('create-note command', () => {
});
it('supports various options to deal with relative paths', async () => {
const TEST_FOLDER = 'create-note-tests';
const base = await createFile('relative path tests base file', [
'create-note-tests',
TEST_FOLDER,
'base-file.md',
]);
await closeEditors();
await showInEditor(base.uri);
const target = getUriInWorkspace();
expectSameUri(window.activeTextEditor.document.uri, base.uri);
await commands.executeCommand('foam-vscode.create-note', {
notePath: target.getBasename(),
notePath: 'note-resolved-from-root.md',
text: 'test resolving from root',
onRelativeNotePath: 'resolve-from-root',
});
expectSameUri(
window.activeTextEditor.document.uri,
fromVsCodeUri(workspace.workspaceFolders?.[0].uri).joinPath(
'note-resolved-from-root.md'
)
);
expect(window.activeTextEditor.document.getText()).toEqual(
'test resolving from root'
);
expectSameUri(window.activeTextEditor.document.uri, target);
await closeEditors();
await showInEditor(base.uri);
expectSameUri(window.activeTextEditor.document.uri, base.uri);
await commands.executeCommand('foam-vscode.create-note', {
notePath: target.getBasename(),
notePath: 'note-resolved-from-current-dir.md',
text: 'test resolving from current dir',
onRelativeNotePath: 'resolve-from-current-dir',
});
expectSameUri(
window.activeTextEditor.document.uri,
fromVsCodeUri(workspace.workspaceFolders?.[0].uri).joinPath(
TEST_FOLDER,
'note-resolved-from-current-dir.md'
)
);
expect(window.activeTextEditor.document.getText()).toEqual(
'test resolving from current dir'
);
expect(fromVsCodeUri(window.activeTextEditor.document.uri).path).toEqual(
fromVsCodeUri(window.activeTextEditor.document.uri)
.getDirectory()
.joinPath(target.getBasename()).path
);
await closeEditors();
await showInEditor(base.uri);
await commands.executeCommand('foam-vscode.create-note', {
notePath: target.getBasename(),
notePath: 'note-that-should-not-be-created.md',
text: 'test cancelling',
onRelativeNotePath: 'cancel',
});
@@ -180,13 +193,13 @@ describe('create-note command', () => {
.spyOn(window, 'showInputBox')
.mockImplementationOnce(jest.fn(() => Promise.resolve(undefined)));
await commands.executeCommand('foam-vscode.create-note', {
notePath: target.getBasename(),
notePath: 'ask-me-about-it.md',
text: 'test asking',
onRelativeNotePath: 'ask',
});
expect(spy).toHaveBeenCalled();
await deleteFile(base);
// await deleteFile(base);
});
});
@@ -194,8 +207,14 @@ describe('factories', () => {
describe('forPlaceholder', () => {
it('adds the .md extension to notes created for placeholders', async () => {
await closeEditors();
const link: ResourceLink = {
type: 'wikilink',
rawText: '[[my-placeholder]]',
range: Range.create(0, 0, 0, 0),
isEmbed: false,
};
const command = CREATE_NOTE_COMMAND.forPlaceholder(
'my-placeholder',
Location.forObjectWithRange(URI.file(''), link),
'.md'
);
await commands.executeCommand(command.name, command.params);
@@ -204,5 +223,41 @@ describe('factories', () => {
expect(doc.uri.path).toMatch(/my-placeholder.md$/);
expect(doc.getText()).toMatch(/^# my-placeholder/);
});
it('replaces the original placeholder based on the new note identifier (#1327)', async () => {
await closeEditors();
const templateA = await createFile(
`---
foam_template:
name: 'Example Template'
description: 'An example for reproducing a bug'
filepath: '$FOAM_SLUG-world.md'
---`,
['.foam', 'templates', 'template-a.md']
);
const noteA = await createFile(`this is my [[hello]]`);
const parser = createMarkdownParser();
const res = parser.parse(noteA.uri, noteA.content);
const command = CREATE_NOTE_COMMAND.forPlaceholder(
Location.forObjectWithRange(noteA.uri, res.links[0]),
'.md',
{
templatePath: templateA.uri.path,
}
);
const results: Awaited<ReturnType<typeof createNote>> =
await commands.executeCommand(command.name, command.params);
expect(results.didCreateFile).toBeTruthy();
expect(results.uri.path.endsWith('hello-world.md')).toBeTruthy();
const newNoteDoc = window.activeTextEditor.document;
expect(newNoteDoc.uri.path).toMatch(/hello-world.md$/);
const { doc } = await showInEditor(noteA.uri);
expect(doc.getText()).toEqual(`this is my [[hello-world]]`);
});
});
});

View File

@@ -10,10 +10,21 @@ import { Resolver } from '../../services/variable-resolver';
import { asAbsoluteWorkspaceUri, fileExists } from '../../services/editor';
import { isSome } from '../../core/utils';
import { CommandDescriptor } from '../../utils/commands';
import { Foam } from '../../core/model/foam';
import { Location } from '../../core/model/location';
import { MarkdownLink } from '../../core/services/markdown-link';
import { ResourceLink } from '../../core/model/note';
import { toVsCodeRange, toVsCodeUri } from '../../utils/vsc-utils';
export default async function activate(context: vscode.ExtensionContext) {
export default async function activate(
context: vscode.ExtensionContext,
foamPromise: Promise<Foam>
) {
const foam = await foamPromise;
context.subscriptions.push(
vscode.commands.registerCommand(CREATE_NOTE_COMMAND.command, createNote)
vscode.commands.registerCommand(CREATE_NOTE_COMMAND.command, args =>
createNote(args, foam)
)
);
}
@@ -48,6 +59,11 @@ interface CreateNoteArgs {
* The title of the note (translates into the FOAM_TITLE variable)
*/
title?: string;
/**
* The source link that triggered the creation of the note.
* It will be updated with the appropriate identifier to the note, if necessary.
*/
sourceLink?: Location<ResourceLink>;
/**
* What to do in case the target file already exists
*/
@@ -66,7 +82,7 @@ const DEFAULT_NEW_NOTE_TEXT = `# \${FOAM_TITLE}
\${FOAM_SELECTED_TEXT}`;
async function createNote(args: CreateNoteArgs) {
export async function createNote(args: CreateNoteArgs, foam: Foam) {
args = args ?? {};
const date = isSome(args.date) ? new Date(Date.parse(args.date)) : new Date();
const resolver = new Resolver(
@@ -77,7 +93,13 @@ async function createNote(args: CreateNoteArgs) {
resolver.define('FOAM_TITLE', args.title);
}
const text = args.text ?? DEFAULT_NEW_NOTE_TEXT;
const noteUri = args.notePath && URI.file(args.notePath);
const schemaSource = vscode.workspace.workspaceFolders[0].uri;
const noteUri =
args.notePath &&
new URI({
scheme: schemaSource.scheme,
path: args.notePath,
});
let templateUri: URI;
if (args.askForTemplate) {
const selectedTemplate = await askUserForTemplate();
@@ -88,27 +110,43 @@ async function createNote(args: CreateNoteArgs) {
}
} else {
templateUri = args.templatePath
? asAbsoluteWorkspaceUri(URI.file(args.templatePath))
? asAbsoluteWorkspaceUri(args.templatePath)
: getDefaultTemplateUri();
}
if (await fileExists(templateUri)) {
return NoteFactory.createFromTemplate(
templateUri,
resolver,
noteUri,
text,
args.onFileExists
);
} else {
return NoteFactory.createNote(
noteUri ?? (await getPathFromTitle(resolver)),
text,
resolver,
args.onFileExists,
args.onRelativeNotePath
);
const createdNote = (await fileExists(templateUri))
? await NoteFactory.createFromTemplate(
templateUri,
resolver,
noteUri,
text,
args.onFileExists
)
: await NoteFactory.createNote(
noteUri ?? (await getPathFromTitle(templateUri.scheme, resolver)),
text,
resolver,
args.onFileExists,
args.onRelativeNotePath
);
if (args.sourceLink) {
const identifier = foam.workspace.getIdentifier(createdNote.uri);
const edit = MarkdownLink.createUpdateLinkEdit(args.sourceLink.data, {
target: identifier,
});
if (edit.newText !== args.sourceLink.data.rawText) {
const updateLink = new vscode.WorkspaceEdit();
const uri = toVsCodeUri(args.sourceLink.uri);
updateLink.replace(
uri,
toVsCodeRange(args.sourceLink.range),
edit.newText
);
await vscode.workspace.applyEdit(updateLink);
}
}
return createdNote;
}
export const CREATE_NOTE_COMMAND = {
@@ -123,12 +161,12 @@ export const CREATE_NOTE_COMMAND = {
* @returns the command descriptor
*/
forPlaceholder: (
placeholder: string,
sourceLink: Location<ResourceLink>,
defaultExtension: string,
extra: Partial<CreateNoteArgs> = {}
): CommandDescriptor<CreateNoteArgs> => {
const endsWithDefaultExtension = new RegExp(defaultExtension + '$');
const { target: placeholder } = MarkdownLink.analyzeLink(sourceLink.data);
const title = placeholder.endsWith(defaultExtension)
? placeholder.replace(endsWithDefaultExtension, '')
: placeholder;
@@ -140,6 +178,7 @@ export const CREATE_NOTE_COMMAND = {
params: {
title,
notePath,
sourceLink,
...extra,
},
};

View File

@@ -10,3 +10,4 @@ export { default as openResource } from './open-resource';
export { default as updateGraphCommand } from './update-graph';
export { default as updateWikilinksCommand } from './update-wikilinks';
export { default as createNote } from './create-note';
export { default as generateStandaloneNote } from './convert-links-format-in-note';

View File

@@ -5,18 +5,18 @@ import {
commands,
ProgressLocation,
} from 'vscode';
import { getWikilinkDefinitionSetting } from '../../settings';
import detectNewline from 'detect-newline';
import { Foam } from '../../core/model/foam';
import { Resource } from '../../core/model/note';
import { generateHeading, generateLinkReferences } from '../../core/janitor';
import { Range } from '../../core/model/range';
import { TextEdit } from '../../core/services/text-edit';
import {
toVsCodePosition,
toVsCodeRange,
toVsCodeUri,
} from '../../utils/vsc-utils';
import { Foam } from '../../core/model/foam';
import { Resource } from '../../core/model/note';
import { generateHeading, generateLinkReferences } from '../../core/janitor';
import { Range } from '../../core/model/range';
import detectNewline from 'detect-newline';
import { TextEdit } from '../../core/services/text-edit';
import { getWikilinkDefinitionSetting } from './update-wikilinks';
export default async function activate(
context: ExtensionContext,

View File

@@ -1,6 +1,6 @@
import { ExtensionContext, commands, window } from 'vscode';
import { focusNote } from '../../utils';
import { Foam } from '../../core/model/foam';
import { focusNote } from '../../services/editor';
export default async function activate(
context: ExtensionContext,

View File

@@ -11,7 +11,7 @@ import {
workspace,
Position,
} from 'vscode';
import { isMdEditor, mdDocSelector } from '../../utils';
import { isMdEditor, getFoamDocSelectors } from '../../services/editor';
import { Foam } from '../../core/model/foam';
import { FoamWorkspace } from '../../core/model/workspace';
import {
@@ -22,7 +22,6 @@ import {
import { fromVsCodeUri, toVsCodeRange } from '../../utils/vsc-utils';
import { getEditorEOL } from '../../services/editor';
import { ResourceParser } from '../../core/model/note';
import { getWikilinkDefinitionSetting } from '../../settings';
import { IMatcher } from '../../core/services/datastore';
export default async function activate(
@@ -49,7 +48,7 @@ export default async function activate(
);
}),
languages.registerCodeLensProvider(
mdDocSelector,
getFoamDocSelectors(),
new WikilinkReferenceCodeLensProvider(
foam.workspace,
foam.services.parser
@@ -58,6 +57,15 @@ export default async function activate(
);
}
export function getWikilinkDefinitionSetting():
| 'withExtensions'
| 'withoutExtensions'
| 'off' {
return workspace
.getConfiguration('foam.edit')
.get('linkReferenceDefinitions', 'withoutExtensions');
}
async function updateWikilinkDefinitions(
fWorkspace: FoamWorkspace,
fParser: ResourceParser,

View File

@@ -1,6 +1,5 @@
import { uniqWith } from 'lodash';
import * as vscode from 'vscode';
import { getNoteTooltip, mdDocSelector, isSome } from '../utils';
import { fromVsCodeUri, toVsCodeRange } from '../utils/vsc-utils';
import {
ConfigurationMonitor,
@@ -14,6 +13,9 @@ import { FoamGraph } from '../core/model/graph';
import { OPEN_COMMAND } from './commands/open-resource';
import { CREATE_NOTE_COMMAND } from './commands/create-note';
import { commandAsURI } from '../utils/commands';
import { Location } from '../core/model/location';
import { getNoteTooltip, getFoamDocSelectors } from '../services/editor';
import { isSome } from '../core/utils';
export const CONFIG_KEY = 'links.hover.enable';
@@ -29,7 +31,7 @@ export default async function activate(
context.subscriptions.push(
isHoverEnabled,
vscode.languages.registerHoverProvider(
mdDocSelector,
getFoamDocSelectors(),
new HoverProvider(
isHoverEnabled,
foam.workspace,
@@ -107,7 +109,7 @@ export class HoverProvider implements vscode.HoverProvider {
}
const command = CREATE_NOTE_COMMAND.forPlaceholder(
targetUri.path,
Location.forObjectWithRange(documentUri, targetLink),
this.workspace.defaultExtension,
{
askForTemplate: true,

View File

@@ -5,8 +5,8 @@ import { Resource } from '../core/model/note';
import { URI } from '../core/model/uri';
import { FoamWorkspace } from '../core/model/workspace';
import { getFoamVsCodeConfig } from '../services/config';
import { getNoteTooltip, mdDocSelector } from '../utils';
import { fromVsCodeUri, toVsCodeUri } from '../utils/vsc-utils';
import { getNoteTooltip, getFoamDocSelectors } from '../services/editor';
export const aliasCommitCharacters = ['#'];
export const linkCommitCharacters = ['#', '|'];
@@ -27,12 +27,12 @@ export default async function activate(
const foam = await foamPromise;
context.subscriptions.push(
vscode.languages.registerCompletionItemProvider(
mdDocSelector,
getFoamDocSelectors(),
new WikilinkCompletionProvider(foam.workspace, foam.graph),
'['
),
vscode.languages.registerCompletionItemProvider(
mdDocSelector,
getFoamDocSelectors(),
new SectionCompletionProvider(foam.workspace),
'#'
),
@@ -123,7 +123,7 @@ export class SectionCompletionProvider
const item = new ResourceCompletionItem(
b.label,
vscode.CompletionItemKind.Text,
resource.uri.withFragment(b.label)
resource.uri.with({ fragment: b.label })
);
item.sortText = String(b.range.start.line).padStart(5, '0');
item.range = replacementRange;

View File

@@ -12,6 +12,7 @@ import { createMarkdownParser } from '../core/services/markdown-parser';
import { FoamGraph } from '../core/model/graph';
import { commandAsURI } from '../utils/commands';
import { CREATE_NOTE_COMMAND } from './commands/create-note';
import { Location } from '../core/model/location';
describe('Document navigation', () => {
const parser = createMarkdownParser([]);
@@ -71,9 +72,8 @@ describe('Document navigation', () => {
it('should create links for placeholders', async () => {
const fileA = await createFile(`this is a link to [[a placeholder]].`);
const ws = createTestWorkspace().set(
parser.parse(fileA.uri, fileA.content)
);
const noteA = parser.parse(fileA.uri, fileA.content);
const ws = createTestWorkspace().set(noteA);
const graph = FoamGraph.fromWorkspace(ws);
const { doc } = await showInEditor(fileA.uri);
@@ -83,9 +83,13 @@ describe('Document navigation', () => {
expect(links.length).toEqual(1);
expect(links[0].target).toEqual(
commandAsURI(
CREATE_NOTE_COMMAND.forPlaceholder('a placeholder', '.md', {
onFileExists: 'open',
})
CREATE_NOTE_COMMAND.forPlaceholder(
Location.forObjectWithRange(noteA.uri, noteA.links[0]),
'.md',
{
onFileExists: 'open',
}
)
)
);
expect(links[0].range).toEqual(new vscode.Range(0, 20, 0, 33));
@@ -227,6 +231,10 @@ describe('Document navigation', () => {
doc,
new vscode.Position(0, 26)
);
// Make sure the references are sorted by position, so we match the right expectation
refs.sort((a, b) => a.range.start.character - b.range.start.character);
expect(refs.length).toEqual(2);
expect(refs[0]).toEqual({
uri: toVsCodeUri(fileB.uri),

View File

@@ -1,5 +1,4 @@
import * as vscode from 'vscode';
import { mdDocSelector } from '../utils';
import { toVsCodeRange, toVsCodeUri, fromVsCodeUri } from '../utils/vsc-utils';
import { Foam } from '../core/model/foam';
import { FoamWorkspace } from '../core/model/workspace';
@@ -10,6 +9,8 @@ import { FoamGraph } from '../core/model/graph';
import { Position } from '../core/model/position';
import { CREATE_NOTE_COMMAND } from './commands/create-note';
import { commandAsURI } from '../utils/commands';
import { Location } from '../core/model/location';
import { getFoamDocSelectors } from '../services/editor';
export default async function activate(
context: vscode.ExtensionContext,
@@ -25,15 +26,15 @@ export default async function activate(
context.subscriptions.push(
vscode.languages.registerDefinitionProvider(
mdDocSelector,
getFoamDocSelectors(),
navigationProvider
),
vscode.languages.registerDocumentLinkProvider(
mdDocSelector,
getFoamDocSelectors(),
navigationProvider
),
vscode.languages.registerReferenceProvider(
mdDocSelector,
getFoamDocSelectors(),
navigationProvider
)
);
@@ -146,10 +147,8 @@ export class NavigationProvider
public provideDocumentLinks(
document: vscode.TextDocument
): vscode.DocumentLink[] {
const resource = this.parser.parse(
fromVsCodeUri(document.uri),
document.getText()
);
const documentUri = fromVsCodeUri(document.uri);
const resource = this.parser.parse(documentUri, document.getText());
const targets: { link: ResourceLink; target: URI }[] = resource.links.map(
link => ({
@@ -158,11 +157,11 @@ export class NavigationProvider
})
);
return targets
const links: vscode.DocumentLink[] = targets
.filter(o => o.target.isPlaceholder()) // links to resources are managed by the definition provider
.map(o => {
const command = CREATE_NOTE_COMMAND.forPlaceholder(
o.target.path,
Location.forObjectWithRange(documentUri, o.link),
this.workspace.defaultExtension,
{
onFileExists: 'open',
@@ -181,5 +180,26 @@ export class NavigationProvider
documentLink.tooltip = `Create note for '${o.target.path}'`;
return documentLink;
});
const tags: vscode.DocumentLink[] = resource.tags.map(tag => {
const command = {
name: 'foam-vscode.views.tags-explorer.focus',
params: [tag.label, documentUri],
};
const documentLink = new vscode.DocumentLink(
new vscode.Range(
tag.range.start.line,
tag.range.start.character,
tag.range.end.line,
tag.range.end.character
),
commandAsURI(command)
);
documentLink.tooltip = `Explore tag '${tag.label}'`;
return documentLink;
});
return links.concat(tags);
}
}

View File

@@ -1,6 +1,5 @@
import * as vscode from 'vscode';
import { URI } from '../../core/model/uri';
import { isNone } from '../../utils';
import { Foam } from '../../core/model/foam';
import { FoamWorkspace } from '../../core/model/workspace';
import { Connection, FoamGraph } from '../../core/model/graph';
@@ -14,6 +13,7 @@ import {
createConnectionItemsForResource,
} from './utils/tree-view-utils';
import { BaseTreeProvider } from './utils/base-tree-provider';
import { isNone } from '../../core/utils';
export default async function activate(
context: vscode.ExtensionContext,
@@ -30,7 +30,6 @@ export default async function activate(
treeDataProvider: provider,
showCollapseAll: true,
});
const baseTitle = treeView.title;
const updateTreeView = async () => {
provider.target = vscode.window.activeTextEditor
@@ -53,12 +52,7 @@ export default async function activate(
}
export class ConnectionsTreeDataProvider extends BaseTreeProvider<vscode.TreeItem> {
public show = new ContextMemento<'all links' | 'backlinks' | 'forward links'>(
this.state,
`foam-vscode.views.connections.show`,
'all links',
true
);
public show: ContextMemento<'all links' | 'backlinks' | 'forward links'>;
public target?: URI = undefined;
public nValues = 0;
private connectionItems: ResourceRangeTreeItem[] = [];
@@ -70,6 +64,12 @@ export class ConnectionsTreeDataProvider extends BaseTreeProvider<vscode.TreeIte
registerCommands = true // for testing. don't love it, but will do for now
) {
super();
this.show = new ContextMemento<'all links' | 'backlinks' | 'forward links'>(
this.state,
`foam-vscode.views.connections.show`,
'all links',
true
);
if (!registerCommands) {
return;
}

View File

@@ -1,10 +1,8 @@
import * as vscode from 'vscode';
import { TextDecoder } from 'util';
import { getGraphStyle, getTitleMaxLength } from '../../settings';
import { isSome } from '../../utils';
import { Foam } from '../../core/model/foam';
import { Logger } from '../../core/utils/log';
import { fromVsCodeUri } from '../../utils/vsc-utils';
import { isSome } from '../../core/utils';
export default async function activate(
context: vscode.ExtensionContext,
@@ -41,7 +39,7 @@ export default async function activate(
});
vscode.window.onDidChangeActiveTextEditor(e => {
if (e?.document?.uri?.scheme === 'file') {
if (e?.document?.uri?.scheme !== 'untitled') {
const note = foam.workspace.get(fromVsCodeUri(e.document.uri));
if (isSome(note)) {
panel.webview.postMessage({
@@ -104,7 +102,9 @@ function generateGraphData(foam: Foam) {
}
function cutTitle(title: string): string {
const maxLen = getTitleMaxLength();
const maxLen = vscode.workspace
.getConfiguration('foam.graph')
.get('titleMaxLength', 24);
if (maxLen > 0 && title.length > maxLen) {
return title.substring(0, maxLen).concat('...');
}
@@ -166,28 +166,37 @@ async function getWebviewContent(
context: vscode.ExtensionContext,
panel: vscode.WebviewPanel
) {
const datavizPath = vscode.Uri.joinPath(
vscode.Uri.file(context.extensionPath),
const datavizUri = vscode.Uri.joinPath(
context.extensionUri,
'static',
'dataviz'
);
const getWebviewUri = (fileName: string) =>
panel.webview.asWebviewUri(vscode.Uri.joinPath(datavizPath, fileName));
panel.webview.asWebviewUri(vscode.Uri.joinPath(datavizUri, fileName));
const indexHtml = await vscode.workspace.fs.readFile(
vscode.Uri.joinPath(datavizPath, 'index.html')
);
const indexHtml =
vscode.env.uiKind === vscode.UIKind.Desktop
? new TextDecoder('utf-8').decode(
await vscode.workspace.fs.readFile(
vscode.Uri.joinPath(datavizUri, 'index.html')
)
)
: await fetch(getWebviewUri('index.html').toString()).then(r => r.text());
// Replace the script paths with the appropriate webview URI.
const filled = new TextDecoder('utf-8')
.decode(indexHtml)
.replace(/data-replace (src|href)="[^"]+"/g, match => {
const filled = indexHtml.replace(
/data-replace (src|href)="[^"]+"/g,
match => {
const i = match.indexOf(' ');
const j = match.indexOf('=');
const uri = getWebviewUri(match.slice(j + 2, -1).trim());
return match.slice(i + 1, j) + '="' + uri.toString() + '"';
});
}
);
return filled;
}
function getGraphStyle(): object {
return vscode.workspace.getConfiguration('foam.graph').get('style');
}

View File

@@ -91,11 +91,7 @@ export class NotesProvider extends FolderTreeProvider<
NotesTreeItems,
Resource
> {
public show = new ContextMemento<'all' | 'notes-only'>(
this.state,
`foam-vscode.views.notes-explorer.show`,
'all'
);
public show: ContextMemento<'all' | 'notes-only'>;
constructor(
private workspace: FoamWorkspace,
@@ -103,6 +99,12 @@ export class NotesProvider extends FolderTreeProvider<
private state: vscode.Memento
) {
super();
this.show = new ContextMemento<'all' | 'notes-only'>(
this.state,
`foam-vscode.views.notes-explorer.show`,
'all'
);
this.disposables.push(
vscode.commands.registerCommand(
`foam-vscode.views.notes-explorer.show:all`,

View File

@@ -1,8 +1,11 @@
import * as vscode from 'vscode';
import { Foam } from '../../core/model/foam';
import { createMatcherAndDataStore } from '../../services/editor';
import { getAttachmentsExtensions, getOrphansConfig } from '../../settings';
import { GroupedResourcesTreeDataProvider } from './utils/grouped-resources-tree-data-provider';
import { getAttachmentsExtensions } from '../../settings';
import {
GroupedResourcesConfig,
GroupedResourcesTreeDataProvider,
} from './utils/grouped-resources-tree-data-provider';
import { ResourceTreeItem, UriTreeItem } from './utils/tree-view-utils';
import { IMatcher } from '../../core/services/datastore';
import { FoamWorkspace } from '../../core/model/workspace';
@@ -46,6 +49,13 @@ export default async function activate(
);
}
/** Retrieve the orphans configuration */
export function getOrphansConfig(): GroupedResourcesConfig {
const orphansConfig = vscode.workspace.getConfiguration('foam.orphans');
const exclude: string[] = orphansConfig.get('exclude');
return { exclude };
}
export class OrphanTreeView extends GroupedResourcesTreeDataProvider {
constructor(
state: vscode.Memento,

View File

@@ -1,8 +1,10 @@
import * as vscode from 'vscode';
import { Foam } from '../../core/model/foam';
import { createMatcherAndDataStore } from '../../services/editor';
import { getPlaceholdersConfig } from '../../settings';
import { GroupedResourcesTreeDataProvider } from './utils/grouped-resources-tree-data-provider';
import {
GroupedResourcesConfig,
GroupedResourcesTreeDataProvider,
} from './utils/grouped-resources-tree-data-provider';
import {
UriTreeItem,
createBacklinkItemsForResource,
@@ -16,6 +18,13 @@ import { URI } from '../../core/model/uri';
import { FoamWorkspace } from '../../core/model/workspace';
import { FolderTreeItem } from './utils/folder-tree-provider';
/** Retrieve the placeholders configuration */
export function getPlaceholdersConfig(): GroupedResourcesConfig {
const placeholderCfg = vscode.workspace.getConfiguration('foam.placeholders');
const exclude: string[] = placeholderCfg.get('exclude');
return { exclude };
}
export default async function activate(
context: vscode.ExtensionContext,
foamPromise: Promise<Foam>

View File

@@ -54,6 +54,51 @@ export default async function activate(
provider,
node => node.contextValue === 'tag' || node.contextValue === 'folder'
)
),
vscode.commands.registerCommand(
`foam-vscode.views.${provider.providerId}.focus`,
async (tag?: string, source?: object) => {
if (tag == null) {
tag = await vscode.window.showQuickPick(
Array.from(foam.tags.tags.keys()),
{
title: 'Select a tag to focus',
}
);
}
if (tag == null) {
return;
}
const tagItem = (await provider.findTreeItemByPath(
provider.valueToPath(tag)
)) as TagItem;
if (tagItem == null) {
return;
}
await treeView.reveal(tagItem, {
select: true,
focus: true,
expand: true,
});
const children = await provider.getChildren(tagItem);
const sourceUri = source ? new URI(source) : undefined;
const resourceItem = sourceUri
? children.find(
t =>
t instanceof ResourceTreeItem &&
sourceUri.isEqual(t.resource?.uri)
)
: undefined;
// doing it as a two reveal process as revealing just the resource
// was only working when the tag item was already expanded
if (resourceItem) {
treeView.reveal(resourceItem, {
select: true,
focus: true,
expand: false,
});
}
}
)
);
}

View File

@@ -19,7 +19,6 @@ export interface Folder<T> {
export class FolderTreeItem<T> extends vscode.TreeItem {
collapsibleState = vscode.TreeItemCollapsibleState.Collapsed;
contextValue = 'folder';
iconPath = new vscode.ThemeIcon('folder');
constructor(
public node: Folder<T>,

View File

@@ -10,6 +10,10 @@ import {
Folder,
} from './folder-tree-provider';
export interface GroupedResourcesConfig {
exclude: string[];
}
type GroupedResourceTreeItem = UriTreeItem | FolderTreeItem<URI>;
/**
@@ -31,11 +35,7 @@ export abstract class GroupedResourcesTreeDataProvider extends FolderTreeProvide
GroupedResourceTreeItem,
URI
> {
public groupBy = new ContextMemento<'off' | 'folder'>(
this.state,
`foam-vscode.views.${this.providerId}.group-by`,
'folder'
);
public groupBy: ContextMemento<'off' | 'folder'>;
/**
* Creates an instance of GroupedResourcesTreeDataProvider.
@@ -57,6 +57,12 @@ export abstract class GroupedResourcesTreeDataProvider extends FolderTreeProvide
private matcher: IMatcher
) {
super();
this.groupBy = new ContextMemento<'off' | 'folder'>(
this.state,
`foam-vscode.views.${this.providerId}.group-by`,
'folder'
);
this.disposables.push(
vscode.commands.registerCommand(
`foam-vscode.views.${this.providerId}.group-by:folder`,

View File

@@ -5,11 +5,11 @@ import { toVsCodeUri } from '../../../utils/vsc-utils';
import { Range } from '../../../core/model/range';
import { URI } from '../../../core/model/uri';
import { FoamWorkspace } from '../../../core/model/workspace';
import { getNoteTooltip } from '../../../utils';
import { isSome } from '../../../core/utils';
import { getBlockFor } from '../../../core/services/markdown-parser';
import { Connection, FoamGraph } from '../../../core/model/graph';
import { Logger } from '../../../core/utils/log';
import { getNoteTooltip } from '../../../services/editor';
export class BaseTreeItem extends vscode.TreeItem {
resolveTreeItem(): Promise<vscode.TreeItem> {
@@ -43,6 +43,9 @@ export class UriTreeItem extends BaseTreeItem {
}
export class ResourceTreeItem extends UriTreeItem {
iconPath = vscode.ThemeIcon.File;
contextValue = 'foam.resource';
constructor(
public readonly resource: Resource,
private readonly workspace: FoamWorkspace,
@@ -62,8 +65,6 @@ export class ResourceTreeItem extends UriTreeItem {
title: 'Go to location',
};
this.resourceUri = toVsCodeUri(resource.uri);
this.iconPath = vscode.ThemeIcon.File;
this.contextValue = 'foam.resource';
}
async resolveTreeItem(): Promise<ResourceTreeItem> {

View File

@@ -1,9 +1,10 @@
/*global markdownit:readonly*/
import markdownItRegex from 'markdown-it-regex';
import { isNone } from '../../utils';
import { FoamWorkspace } from '../../core/model/workspace';
import { Logger } from '../../core/utils/log';
import { isNone } from '../../core/utils';
import { commandAsURI } from '../../utils/commands';
export const markdownItFoamTags = (
md: markdownit,
@@ -14,10 +15,7 @@ export const markdownItFoamTags = (
regex: /(?<=^|\s)(#[0-9]*[\p{L}/_-][\p{L}\p{N}/_-]*)/u,
replace: (tag: string) => {
try {
const resource = workspace.find(tag);
if (isNone(resource)) {
return getFoamTag(tag);
}
return getFoamTag(tag);
} catch (e) {
Logger.error(
`Error while creating link for ${tag} in Preview panel`,
@@ -29,6 +27,8 @@ export const markdownItFoamTags = (
});
};
// Commands can't be run in the preview (see https://github.com/microsoft/vscode/issues/102532)
// for we just return the tag as a span
const getFoamTag = (content: string) =>
`<span class='foam-tag'>${content}</span>`;

View File

@@ -0,0 +1,28 @@
/*global markdownit:readonly*/
import markdownItRegex from 'markdown-it-regex';
import { ResourceParser } from '../../core/model/note';
import { FoamWorkspace } from '../../core/model/workspace';
export const WIKILINK_EMBED_REGEX =
/((?:(?:full|content)-(?:inline|card)|full|content|inline|card)?!\[\[[^[\]]+?\]\])/;
export const markdownItWikilinkEmbed = (
md: markdownit,
workspace: FoamWorkspace,
parser: ResourceParser
) => {
return md.use(markdownItRegex, {
name: 'embed-wikilinks',
regex: WIKILINK_EMBED_REGEX,
replace: (wikilinkItem: string) => {
return `
<div class="foam-embed-not-supported-warning">
Embed not supported in web mode: ${wikilinkItem}
</div>
`;
},
});
};
export default markdownItWikilinkEmbed;

View File

@@ -4,15 +4,20 @@
import { readFileSync } from 'fs';
import { workspace as vsWorkspace } from 'vscode';
import markdownItRegex from 'markdown-it-regex';
import { isSome, isNone } from '../../utils';
import { FoamWorkspace } from '../../core/model/workspace';
import { Logger } from '../../core/utils/log';
import { Resource, ResourceParser } from '../../core/model/note';
import { getFoamVsCodeConfig } from '../../services/config';
import { fromVsCodeUri, toVsCodeUri } from '../../utils/vsc-utils';
import { MarkdownLink } from '../../core/services/markdown-link';
import { URI } from '../../core/model/uri';
import { Position } from '../../core/model/position';
import { TextEdit } from '../../core/services/text-edit';
import { isNone, isSome } from '../../core/utils';
import {
asAbsoluteWorkspaceUri,
isVirtualWorkspace,
} from '../../services/editor';
export const WIKILINK_EMBED_REGEX =
/((?:(?:full|content)-(?:inline|card)|full|content|inline|card)?!\[\[[^[\]]+?\]\])/;
@@ -22,7 +27,7 @@ export const WIKILINK_EMBED_REGEX =
export const WIKILINK_EMBED_REGEX_GROUPS =
/((?:\w+)|(?:(?:\w+)-(?:\w+)))?!\[\[([^[\]]+?)\]\]/;
export const CONFIG_EMBED_NOTE_TYPE = 'preview.embedNoteType';
const refsStack: string[] = [];
let refsStack: string[] = [];
export const markdownItWikilinkEmbed = (
md: markdownit,
@@ -34,10 +39,18 @@ export const markdownItWikilinkEmbed = (
regex: WIKILINK_EMBED_REGEX,
replace: (wikilinkItem: string) => {
try {
const [_, noteEmbedModifier, wikilink] = wikilinkItem.match(
const [, noteEmbedModifier, wikilink] = wikilinkItem.match(
WIKILINK_EMBED_REGEX_GROUPS
);
if (isVirtualWorkspace()) {
return `
<div class="foam-embed-not-supported-warning">
Embed not supported in virtual workspace: ![[${wikilink}]]
</div>
`;
}
const includedNote = workspace.find(wikilink);
if (!includedNote) {
@@ -48,56 +61,31 @@ export const markdownItWikilinkEmbed = (
includedNote.uri.path.toLocaleLowerCase()
);
if (!cyclicLinkDetected) {
refsStack.push(includedNote.uri.path.toLocaleLowerCase());
}
if (cyclicLinkDetected) {
return `<div class="foam-cyclic-link-warning">Cyclic link detected for wikilink: ${wikilink}</div>`;
return `
<div class="foam-cyclic-link-warning">
Cyclic link detected for wikilink: ${wikilink}
<div class="foam-cyclic-link-warning__stack">
Link sequence:
<ul>
${refsStack.map(ref => `<li>${ref}</li>`).join('')}
</ul>
</div>
</div>
`;
}
let content = `Embed for [[${wikilink}]]`;
let html: string;
switch (includedNote.type) {
case 'note': {
const { noteScope, noteStyle } =
retrieveNoteConfig(noteEmbedModifier);
refsStack.push(includedNote.uri.path.toLocaleLowerCase());
const extractor: EmbedNoteExtractor =
noteScope === 'full'
? fullExtractor
: noteScope === 'content'
? contentExtractor
: fullExtractor;
const formatter: EmbedNoteFormatter =
noteStyle === 'card'
? cardFormatter
: noteStyle === 'inline'
? inlineFormatter
: cardFormatter;
content = extractor(includedNote, parser, workspace);
html = formatter(content, md);
break;
}
case 'attachment':
content = `
<div class="embed-container-attachment">
${md.renderInline('[[' + wikilink + ']]')}<br/>
Embed for attachments is not supported
</div>`;
html = md.render(content);
break;
case 'image':
content = `<div class="embed-container-image">${md.render(
`![](${md.normalizeLink(includedNote.uri.path)})`
)}</div>`;
html = md.render(content);
break;
}
const content = getNoteContent(
includedNote,
noteEmbedModifier,
parser,
workspace,
md
);
refsStack.pop();
return html;
return refsStack.length === 0 ? md.render(content) : content;
} catch (e) {
Logger.error(
`Error while including ${wikilinkItem} into the current document of the Preview panel`,
@@ -109,11 +97,65 @@ Embed for attachments is not supported
});
};
function getNoteContent(
includedNote: Resource,
noteEmbedModifier: string | undefined,
parser: ResourceParser,
workspace: FoamWorkspace,
md: markdownit
): string {
let content = `Embed for [[${includedNote.uri.path}]]`;
let toRender: string;
switch (includedNote.type) {
case 'note': {
const { noteScope, noteStyle } = retrieveNoteConfig(noteEmbedModifier);
const extractor: EmbedNoteExtractor =
noteScope === 'full'
? fullExtractor
: noteScope === 'content'
? contentExtractor
: fullExtractor;
const formatter: EmbedNoteFormatter =
noteStyle === 'card'
? cardFormatter
: noteStyle === 'inline'
? inlineFormatter
: cardFormatter;
content = extractor(includedNote, parser, workspace);
toRender = formatter(content, md);
break;
}
case 'attachment':
content = `
<div class="embed-container-attachment">
${md.renderInline('[[' + includedNote.uri.path + ']]')}<br/>
Embed for attachments is not supported
</div>`;
toRender = md.render(content);
break;
case 'image':
content = `<div class="embed-container-image">${md.render(
`![](${md.normalizeLink(includedNote.uri.path)})`
)}</div>`;
toRender = md.render(content);
break;
default:
toRender = content;
}
return toRender;
}
function withLinksRelativeToWorkspaceRoot(
noteUri: URI,
noteText: string,
parser: ResourceParser,
workspace: FoamWorkspace
) {
): string {
const note = parser.parse(
fromVsCodeUri(vsWorkspace.workspaceFolders[0].uri),
noteText
@@ -121,15 +163,13 @@ function withLinksRelativeToWorkspaceRoot(
const edits = note.links
.map(link => {
const info = MarkdownLink.analyzeLink(link);
const resource = workspace.find(info.target);
const resource = workspace.find(info.target, noteUri);
// embedded notes that aren't created are still collected
// return null so it can be filtered in the next step
if (isNone(resource)) {
return null;
}
const pathFromRoot = vsWorkspace.asRelativePath(
toVsCodeUri(resource.uri)
);
const pathFromRoot = asAbsoluteWorkspaceUri(resource.uri).path;
return MarkdownLink.createUpdateLinkEdit(link, {
target: pathFromRoot,
});
@@ -185,7 +225,12 @@ function fullExtractor(
.slice(section.range.start.line, section.range.end.line)
.join('\n');
}
noteText = withLinksRelativeToWorkspaceRoot(noteText, parser, workspace);
noteText = withLinksRelativeToWorkspaceRoot(
note.uri,
noteText,
parser,
workspace
);
return noteText;
}
@@ -211,7 +256,12 @@ function contentExtractor(
}
rows.shift();
noteText = rows.join('\n');
noteText = withLinksRelativeToWorkspaceRoot(noteText, parser, workspace);
noteText = withLinksRelativeToWorkspaceRoot(
note.uri,
noteText,
parser,
workspace
);
return noteText;
}
@@ -221,13 +271,11 @@ function contentExtractor(
export type EmbedNoteFormatter = (content: string, md: markdownit) => string;
function cardFormatter(content: string, md: markdownit): string {
return md.render(
`<div class="embed-container-note">${md.render(content)}</div>`
);
return `<div class="embed-container-note">\n\n${content}\n\n</div>`;
}
function inlineFormatter(content: string, md: markdownit): string {
return md.render(content);
return content;
}
export default markdownItWikilinkEmbed;

View File

@@ -26,9 +26,9 @@ describe('Link generation in preview', () => {
markdownItRemoveLinkReferences,
].reduce((acc, extension) => extension(acc, ws), MarkdownIt());
it('generates a link to a note', () => {
it('generates a link to a note using the note title as link', () => {
expect(md.render(`[[note-a]]`)).toEqual(
`<p><a class='foam-note-link' title='${noteA.title}' href='/path/to/note-a.md' data-href='/path/to/note-a.md'>note-a</a></p>\n`
`<p><a class='foam-note-link' title='${noteA.title}' href='/path/to/note-a.md' data-href='/path/to/note-a.md'>${noteA.title}</a></p>\n`
);
});
@@ -48,13 +48,22 @@ describe('Link generation in preview', () => {
const note = `[[note-a]]
[note-a]: <note-a.md> "Note A"`;
expect(md.render(note)).toEqual(
`<p><a class='foam-note-link' title='${noteA.title}' href='/path/to/note-a.md' data-href='/path/to/note-a.md'>note-a</a>\n[note-a]: &lt;note-a.md&gt; &quot;Note A&quot;</p>\n`
`<p><a class='foam-note-link' title='${noteA.title}' href='/path/to/note-a.md' data-href='/path/to/note-a.md'>${noteA.title}</a>\n[note-a]: &lt;note-a.md&gt; &quot;Note A&quot;</p>\n`
);
});
it('generates a link to a section within the note', () => {
expect(md.render(`[[#sec]]`)).toEqual(
`<p><a class='foam-note-link' title='sec' href='#sec' data-href='#sec'>#sec</a></p>\n`
);
expect(md.render(`[[#Section Name]]`)).toEqual(
`<p><a class='foam-note-link' title='Section Name' href='#section-name' data-href='#section-name'>#Section Name</a></p>\n`
);
});
it('generates a link to a note with a specific section', () => {
expect(md.render(`[[note-b#sec2]]`)).toEqual(
`<p><a class='foam-note-link' title='My second note#sec2' href='/path2/to/note-b.md#sec2' data-href='/path2/to/note-b.md#sec2'>note-b#sec2</a></p>\n`
`<p><a class='foam-note-link' title='My second note#sec2' href='/path2/to/note-b.md#sec2' data-href='/path2/to/note-b.md#sec2'>${noteB.title}#sec2</a></p>\n`
);
});
@@ -66,7 +75,7 @@ describe('Link generation in preview', () => {
it('generates a link to a note if the note exists, but the section does not exist', () => {
expect(md.render(`[[note-b#nonexistentsec]]`)).toEqual(
`<p><a class='foam-note-link' title='My second note#nonexistentsec' href='/path2/to/note-b.md#nonexistentsec' data-href='/path2/to/note-b.md#nonexistentsec'>note-b#nonexistentsec</a></p>\n`
`<p><a class='foam-note-link' title='My second note#nonexistentsec' href='/path2/to/note-b.md#nonexistentsec' data-href='/path2/to/note-b.md#nonexistentsec'>${noteB.title}#nonexistentsec</a></p>\n`
);
});

View File

@@ -2,13 +2,14 @@
import markdownItRegex from 'markdown-it-regex';
import * as vscode from 'vscode';
import { isNone } from '../../utils';
import { FoamWorkspace } from '../../core/model/workspace';
import { Logger } from '../../core/utils/log';
import { toVsCodeUri } from '../../utils/vsc-utils';
import { MarkdownLink } from '../../core/services/markdown-link';
import { Range } from '../../core/model/range';
import { isEmpty } from 'lodash';
import { toSlug } from '../../utils/slug';
import { isNone } from '../../core/utils';
export const markdownItWikilinkNavigation = (
md: markdownit,
@@ -26,18 +27,33 @@ export const markdownItWikilinkNavigation = (
isEmbed: false,
});
const formattedSection = section ? `#${section}` : '';
const linkSection = section ? `#${toSlug(section)}` : '';
const label = isEmpty(alias) ? `${target}${formattedSection}` : alias;
// [[#section]] links
if (target.length === 0) {
// we don't have a good way to check if the section exists within the
// open file, so we just create a regular link for it
return getResourceLink(section, linkSection, label);
}
const resource = workspace.find(target);
if (isNone(resource)) {
return getPlaceholderLink(label);
}
const link = `${vscode.workspace.asRelativePath(
toVsCodeUri(resource.uri)
)}${formattedSection}`;
const title = `${resource.title}${formattedSection}`;
return `<a class='foam-note-link' title='${title}' href='/${link}' data-href='/${link}'>${label}</a>`;
const resourceLabel = isEmpty(alias)
? `${resource.title}${formattedSection}`
: alias;
const resourceLink = `/${vscode.workspace.asRelativePath(
toVsCodeUri(resource.uri),
false
)}`;
return getResourceLink(
`${resource.title}${formattedSection}`,
`${resourceLink}${linkSection}`,
resourceLabel
);
} catch (e) {
Logger.error(
`Error while creating link for [[${wikilink}]] in Preview panel`,
@@ -52,4 +68,7 @@ export const markdownItWikilinkNavigation = (
const getPlaceholderLink = (content: string) =>
`<a class='foam-placeholder-link' title="Link to non-existing resource" href="javascript:void(0);">${content}</a>`;
const getResourceLink = (title: string, link: string, label: string) =>
`<a class='foam-note-link' title='${title}' href='${link}' data-href='${link}'>${label}</a>`;
export default markdownItWikilinkNavigation;

View File

@@ -1,7 +1,8 @@
import * as vscode from 'vscode';
import { Foam } from '../core/model/foam';
import { FoamTags } from '../core/model/tags';
import { isInFrontMatter, isOnYAMLKeywordLine, mdDocSelector } from '../utils';
import { isInFrontMatter, isOnYAMLKeywordLine } from '../core/utils/md';
import { getFoamDocSelectors } from '../services/editor';
// this regex is different from HASHTAG_REGEX in that it does not look for a
// #+character. It uses a negative look-ahead for `# `
@@ -16,7 +17,7 @@ export default async function activate(
const foam = await foamPromise;
context.subscriptions.push(
vscode.languages.registerCompletionItemProvider(
mdDocSelector,
getFoamDocSelectors(),
new TagCompletionProvider(foam.tags),
'#'
)

View File

@@ -5,13 +5,13 @@ import { Resource, ResourceParser } from '../core/model/note';
import { Range } from '../core/model/range';
import { FoamWorkspace } from '../core/model/workspace';
import { MarkdownLink } from '../core/services/markdown-link';
import { isNone } from '../utils';
import {
fromVsCodeUri,
toVsCodePosition,
toVsCodeRange,
toVsCodeUri,
} from '../utils/vsc-utils';
import { isNone } from '../core/utils';
const AMBIGUOUS_IDENTIFIER_CODE = 'ambiguous-identifier';
const UNKNOWN_SECTION_CODE = 'unknown-section';
@@ -240,12 +240,12 @@ const createReplaceSectionCommand = (
section: string
): vscode.CodeAction => {
const action = new vscode.CodeAction(
`Use ${section}`,
`${section}`,
vscode.CodeActionKind.QuickFix
);
action.command = {
command: REPLACE_TEXT_COMMAND.name,
title: `Use section ${section}`,
title: `Use section "${section}"`,
arguments: [
{
value: section,
@@ -269,7 +269,7 @@ const createFindIdentifierCommand = (
possibleTargets: vscode.Uri[]
): vscode.CodeAction => {
const action = new vscode.CodeAction(
`Use ${vscode.workspace.asRelativePath(target)}`,
`${vscode.workspace.asRelativePath(target)}`,
vscode.CodeActionKind.QuickFix
);
action.command = {

Some files were not shown because too many files have changed in this diff Show More