Compare commits

...

29 Commits

Author SHA1 Message Date
Riccardo Ferretti
8b7400c20f v0.20.4 2023-01-04 13:07:17 +01:00
Riccardo Ferretti
d2feee8c75 Preparation for next release 2023-01-04 13:07:02 +01:00
allcontributors[bot]
2113705fb6 add badsketch as a contributor for code (#1130)
* 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-01-03 11:16:46 +01:00
Daniel Wang
4758923188 Feat #903: Added support for emoji tags (#1125)
* Added support for emoji tags
2023-01-03 11:13:12 +01:00
Riccardo Ferretti
275028ffa5 v0.20.3 2022-12-19 19:59:31 +01:00
Riccardo Ferretti
8b425ff420 Preparation for next release 2022-12-19 19:59:04 +01:00
Numan Zaheer Ahmed
64638868c0 Fix minor typo in frequently-asked-questions.md (#1121) 2022-12-11 16:32:30 +01:00
Riccardo Ferretti
7a2756eb1d Adding number of entries in title for orphan, placeholder, tag treeviews 2022-12-04 18:14:01 +01:00
Riccardo Ferretti
540371bf96 Setting prettier as default formatter for typescript 2022-12-04 18:12:03 +01:00
allcontributors[bot]
42b32103f5 add jonathanpberger as a contributor for doc (#1114)
* 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>
2022-11-28 17:55:37 +01:00
jonathan berger
1d264dcb22 Add mention of FOAM_DATE_WEEK to documentation. (#1113)
Because it was added in code, but hard to search.

Co-authored-by: JPB <jonathan@ltmp.co>
2022-11-28 17:54:08 +01:00
allcontributors[bot]
7f8c58084b add sn3akiwhizper as a contributor for doc (#1109)
* 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>
2022-11-22 22:09:42 +01:00
sn3akiwhizper
6b2dda4a71 Documentation updates and cleaning (#1108)
* fixing some unmatched links, daily-note path note, start graphviz/tags/properties clarification

fixed links that didn't actually link to their target, add discussion about creating daily-notes in path based off date, then clarifying notes about styling graph viz and tags while enhancing the note properties descriptions by describing how properties are described and which properties are custom, which are foam-specific, and which are foam-template-specific

* add filter view, default variables to graphviz, and viewing tags in graphviz

graphviz: discuss filter view, add all changeable variables to graph style example, tags: describe viewing tags in the graphviz

* add small note about learning yaml

* last push fixing up some todos

* making recommended changes from PR request
2022-11-22 22:09:04 +01:00
Riccardo
a6cb50f3d6 Fix #907: use definition provider for resource links, link provider for placeholder links (#1106) 2022-11-14 00:34:52 +01:00
Riccardo Ferretti
09c2198928 fixed readme and docs 2022-11-12 17:19:00 +01:00
Riccardo Ferretti
431da1b385 docs: add Lloyd-Jackman-UKPL as a contributor for doc 2022-11-12 17:10:36 +01:00
Lloyd Jackman
57e0621c24 Added open source doc (#1102)
* Added open source doc
2022-11-11 15:22:05 +01:00
Riccardo Ferretti
ebd1008215 v0.20.2 2022-10-26 22:59:12 +02:00
Riccardo Ferretti
0df958c3a4 Preparation for next release 2022-10-26 22:58:31 +02:00
Riccardo Ferretti
14709313ae fix #1094: use default template when none is provided 2022-10-26 22:55:12 +02:00
Riccardo Ferretti
ff2dd23918 Refactored grouped resource tree data provider to use new matcher 2022-10-26 19:18:13 +02:00
Riccardo Ferretti
66e74966ee removed unused asset 2022-10-25 22:06:29 +02:00
Riccardo Ferretti
9b022d0c61 renamed Create Note command 2022-10-25 21:59:12 +02:00
Riccardo Ferretti
e1b301814e removed unused deps 2022-10-25 21:58:31 +02:00
Riccardo Ferretti
2010d8d51e removed unused document 2022-10-25 21:52:47 +02:00
Riccardo Ferretti
22fa977c9b removing unnecessary files from extension bundle 2022-10-25 21:51:46 +02:00
allcontributors[bot]
285293ec23 add elgirafo as a contributor for doc (#1093)
* 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>
2022-10-25 09:09:54 +02:00
luca
6497f527ca Updated FAQ (#1092)
Added an additional answer regarding publishing foam notes with graph view.
2022-10-25 09:09:17 +02:00
Riccardo
689c167384 Bootstrap improvements and glob dependency removal (#1084)
* refactored relationship between workspace and providers, plus using VS Code for globbing
* glob dependency is now only for testing
2022-10-19 23:30:50 +02:00
58 changed files with 999 additions and 586 deletions

View File

@@ -950,6 +950,51 @@
"contributions": [
"code"
]
},
{
"login": "elgirafo",
"name": "luca",
"avatar_url": "https://avatars.githubusercontent.com/u/80516439?v=4",
"profile": "http://elgirafo.xyz",
"contributions": [
"doc"
]
},
{
"login": "Lloyd-Jackman-UKPL",
"name": "Lloyd Jackman",
"avatar_url": "https://avatars.githubusercontent.com/u/55206370?v=4",
"profile": "https://github.com/Lloyd-Jackman-UKPL",
"contributions": [
"doc"
]
},
{
"login": "sn3akiwhizper",
"name": "sn3akiwhizper",
"avatar_url": "https://avatars.githubusercontent.com/u/102705294?v=4",
"profile": "http://sn3akiwhizper.github.io",
"contributions": [
"doc"
]
},
{
"login": "jonathanpberger",
"name": "jonathan berger",
"avatar_url": "https://avatars.githubusercontent.com/u/41085?v=4",
"profile": "http://jonathanpberger.com/",
"contributions": [
"doc"
]
},
{
"login": "badsketch",
"name": "Daniel Wang",
"avatar_url": "https://avatars.githubusercontent.com/u/8953212?v=4",
"profile": "https://github.com/badsketch",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,

View File

@@ -28,5 +28,8 @@
"jest.rootPath": "packages/foam-vscode",
"jest.jestCommandLine": "yarn jest",
"gitdoc.enabled": false,
"search.mode": "reuseEditor"
"search.mode": "reuseEditor",
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}

4
docs/.vscode/custom-tag-style.css vendored Normal file
View File

@@ -0,0 +1,4 @@
.foam-tag{
color:#ffffff;
background-color: #000000;
}

View File

@@ -26,5 +26,8 @@
"files.exclude": {
"_site/**": true
},
"files.insertFinalNewline": true
"files.insertFinalNewline": true,
"markdown.styles": [
".vscode/custom-tag-style.css"
]
}

View File

@@ -0,0 +1,29 @@
# Achieving Greater Privacy and Security
Foam, at its heart and committed to in its [Principles](https://foambubble.github.io/foam/principles), allows the user to control their content in a flexible and non-prescriptive manner. This extends to user preferences, or requirements depending on application and context, around both privacy and security. One way that these use cases can be met is through the use of open-source and not-for-profit mechanisms in the user's workflow to provide a functional equivalence.
Here are a few suggestions on increasing privacy and security when using Foam.
## VS Codium: The Open Source build of VS Code
Foam is built upon VS Code, itself a Microsoft product built on top of an open source project.
As can be found [here](https://github.com/Microsoft/vscode/issues/60#issuecomment-161792005) the **VS Code product itself is not fully open source**. This means that its inner workings are not fully transparent, facilitating the collection and distribution of your data, as specified in its [Privacy Statement](https://devblogs.microsoft.com/visualstudio/privacy/).
If you prefer a fully open source editor based on the same core of VS Code (and for most intents and purposes equivalent to it), you can try [VSCodium](https://github.com/VSCodium).
In its own introduction it is described as, "Binary releases of VS Code without MS branding/telemetry/licensing". Installation packages are easily available across Windows, Unix and Linux (or you can build it from source!).
Access to the VS Code marketplace of add-ons remains in place, including the Foam extension.
The change you will notice in using VS Code versus VS Codium - simply speaking, none. It is, in just about every way you will think of, the same IDE, just without the Microsoft proprietary licence and telemetry. Your Foam experience will remain as smooth and productive as before the change.
## Version Control and Replication
In Foam's [Getting Started](https://foambubble.github.io/foam/#getting-started) section, the set up describes how to set up your notes with a GitHub repository in using the template provided. Doing so provides the user with the ability to see commits made and therefore versions of their notes, allows the user to work across devices or collaborate effectively with other users, and makes publishing to GitHub pages easy.
It's important at the same time to point out the closed-source nature of GitHub, being owned by Microsoft.
One alternative approach could be to use [GitLab](https://gitlab.com/), an open source alternative to GitHub. Whilst it improves on the aspect of transparency, it does also collect usage details and sends your content across the internet.
And of course data is still stored in clear in the cloud, making it susceptible to hacks of the service.
A more private approach would manage replication between devices and users with a serverless mechanism like [Syncthing](https://syncthing.net). Its continuous synchronisation means that changes in files are seen almost instantly and offers the choice of using only local network connections or securely using public relays when a local network connection is unavailable. This means that having two connected devices online will have them synchronised, but it is worth noting that the continuous synchronisation could result in corruption if two users worked on the same file simultaneously and it doesn't offer the same kind of version control that git does (though versioning support can be found and is described [here](https://docs.syncthing.net/users/versioning.html)). It is also not advisable to attempt to use a continuous synchronisation tool to sync local git repositories as the risk of corruption on the git files is high (see [here](https://forum.syncthing.net/t/can-syncthing-reliably-sync-local-git-repos-not-github/8404/18)).
If you need the version control and collaboration, but do not want to compromise on your privacy, the best course of action is to host the open source GitLab server software yourself. The steps (well described [here](https://www.techrepublic.com/article/how-to-set-up-a-gitlab-server-and-host-your-own-git-repositories/)) are not especially complex by any means and can be used exclusively on the local network, if required, offering a rich experience of "built-in version control, issue tracking, code review, CI/CD, and more", according to its website, [GitLab / GitLab Community Edition · GitLab](https://gitlab.com/rluna-gitlab/gitlab-ce).

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 MiB

View File

@@ -1,7 +1,7 @@
# Developing documentation
The best way to develop docs for the Foam repo is to directly open the `$foam-repo/docs/` as the root folder in a new vscode window.
This automatically configures vscode with the necessary settings enabled (like [[link-reference-definitions]]) to effiniently write this documentation.
This automatically configures vscode with the necessary settings enabled (like [[link-reference-definitions]]) to efficiently write this documentation.
## Organization

View File

@@ -4,7 +4,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.
This guide aims to help guide new and seasoned contributors getting around the Foam codebase.
This guide aims to help guide new and seasoned contributors getting around the Foam codebase. For a comprehensive guide about contributing to open-source projects in general, [see here](https://sqldbawithabeard.com/2019/11/29/how-to-fork-a-github-repository-and-contribute-to-an-open-source-project/).
## Getting Up To Speed
@@ -23,17 +23,18 @@ Finally, the easiest way to help, is to use it and provide feedback by [submitti
## Contributing
If you're interested in contributing, this short guide will help you get things set up locally.
If you're interested in contributing, this short guide will help you get things set up locally (assuming [node.js](https://nodejs.org/) and [yarn](https://yarnpkg.com/) are already installed on your system).
1. Clone the repo locally:
1. Fork the project to your Github account by clicking the "Fork" button on the top right hand corner of the project's [home repository page](https://github.com/foambubble/foam).
2. Clone your newly forked repo locally:
`git clone https://github.com/foambubble/foam.git`
`git clone https://github.com/your_username/foam.git`
2. Install the necessary dependencies by running this command from the root:
3. Install the necessary dependencies by running this command from the root of the cloned repository:
`yarn install`
3. From the root, run the command:
4. From the repository root, run the command:
`yarn build`
@@ -86,6 +87,18 @@ This guide assumes you read the previous instructions and you're set up to work
3. Test a command to make sure it's working as expected. Open the Command Palette (Ctrl/Cmd + Shift + P) and select "Foam: Update Markdown Reference List". If you see no errors, it's good to go!
### Submitting a Pull Request (PR)
After you have made your changes to your copy of the project, it is time to try and merge those changes into the public community project.
1. Return to the project's [home repository page](https://github.com/foambubble/foam).
2. Github should show you an button called "Compare & pull request" linking your forked repository to the community repository.
3. Click that button and confirm that your repository is going to be merged into the community repository. See [this guide](https://sqldbawithabeard.com/2019/11/29/how-to-fork-a-github-repository-and-contribute-to-an-open-source-project/) for more specifics.
4. Add as many relevant details to the PR message to make it clear to the project maintainers and other members of the community what you have accomplished with your new changes. Link to any issues the changes are related to.
5. Your PR will then need to be reviewed and accepted by the other members of the community. Any discussion about the changes will occur in your PR thread.
6. Once reviewed and accept you can complete the merge request!
7. Finally rest and watch the sun rise on a grateful universe... Or start tackling the other open issues ;)
---
Feel free to modify and submit a PR if this guide is out-of-date or contains errors!

View File

@@ -239,6 +239,13 @@ If that sounds like something you're interested in, I'd love to have you along o
<td align="center"><a href="https://www.readingsnail.pe.kr"><img src="https://avatars.githubusercontent.com/u/1904967?v=4?s=60" width="60px;" alt="Woosuk Park"/><br /><sub><b>Woosuk Park</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=readingsnail" title="Documentation">📖</a></td>
<td align="center"><a href="http://www.dmurph.com"><img src="https://avatars.githubusercontent.com/u/294026?v=4?s=60" width="60px;" alt="Daniel Murphy"/><br /><sub><b>Daniel Murphy</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dmurph" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/Dominic-DallOsto"><img src="https://avatars.githubusercontent.com/u/26859884?v=4?s=60" width="60px;" alt="Dominic D"/><br /><sub><b>Dominic D</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Dominic-DallOsto" title="Code">💻</a></td>
<td align="center"><a href="http://elgirafo.xyz"><img src="https://avatars.githubusercontent.com/u/80516439?v=4?s=60" width="60px;" alt="luca"/><br /><sub><b>luca</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=elgirafo" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/Lloyd-Jackman-UKPL"><img src="https://avatars.githubusercontent.com/u/55206370?v=4?s=60" width="60px;" alt="Lloyd Jackman"/><br /><sub><b>Lloyd Jackman</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Lloyd-Jackman-UKPL" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center"><a href="http://sn3akiwhizper.github.io"><img src="https://avatars.githubusercontent.com/u/102705294?v=4?s=60" width="60px;" alt="sn3akiwhizper"/><br /><sub><b>sn3akiwhizper</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=sn3akiwhizper" title="Documentation">📖</a></td>
<td align="center"><a href="http://jonathanpberger.com/"><img src="https://avatars.githubusercontent.com/u/41085?v=4?s=60" width="60px;" alt="jonathan berger"/><br /><sub><b>jonathan berger</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jonathanpberger" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/badsketch"><img src="https://avatars.githubusercontent.com/u/8953212?v=4?s=60" width="60px;" alt="Daniel Wang"/><br /><sub><b>Daniel Wang</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=badsketch" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -45,11 +45,15 @@ 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`.
## Extend Functionality (Weekly, Monthly, Quarterly Notes)
Please see [[note-macros]]
[//begin]: # "Autogenerated link references for markdown compatibility"
[Note Templates]: note-templates.md "Note Templates"
[note-properties]: note-properties.md "Note Properties"
[note-templates#Example of date-based|Example of date-based filepath]: note-templates.md "Note Templates"
[note-macros]: ../recipes/note-macros.md "Custom Note Macros"
[//end]: # "Autogenerated link references"

View File

@@ -3,15 +3,29 @@
Foam comes with a graph visualization of your notes.
To see the graph execute the `Foam: Show Graph` command.
Your files, such as notes and documents, are shown as the nodes of the graph along with the tags defined in your notes. The edges of the graph represent either a link between two files or a file that contains a certain tag. A node in the graph will grow in size with the number of connections it has, representing stronger or more defined concepts and topics.
## Graph Navigation
With the graph you can:
With the Foam graph visualization you can:
- highlight a node by hovering on it, to quickly see how it's connected to the rest of your notes
- select one or more (by keeping `shift` pressed while selecting) nodes by clicking on them, to better understand the structure of your notes
- navigate to a note by clicking on it while pressing `ctrl` or `cmd`
- navigate to a note by clicking on it's node while pressing `ctrl` or `cmd`
- automatically center the graph on the currently edited note, to immediately see its connections
## Filter View
If you only wish to view certain types of notes or tags, or want to hide linked attachment nodes then you can apply filters to the graph.
- Open the graph view using the `Foam: Show Graph` command
- Click the button in the top right corner of the graph view that says "Open Controls"
- Expand the "Filter By Type" dropdown to view the selection of types that you can filter by
- Uncheck the checkbox for any type you want to hide
- The types displayed in this dropdown are defined by [[note-properties]] which includes Foam-standard types as well as custom types defined by you!
![Graph filtering demo](../../assets/images/graph-filter.gif)
## Custom Graph Styles
The Foam graph will use the current VS Code theme by default, but it's possible to customize it with the `foam.graph.style` setting.
@@ -24,28 +38,41 @@ A sample configuration object is provided below, you can provide as many or as l
"foam.graph.style": {
"background": "#202020",
"fontSize": 12,
"lineColor": "#277da1",
"lineWidth": 0.2,
"particleWidth": 1.0,
"highlightedForeground": "#f9c74f",
"node": {
"note": "#277da1",
"placeholder": "#545454",
"feature": "green",
}
}
```
- `note` defines the color for regular nodes
- `placeholder` defines the color for links that don't match any existing note. This is a [[placeholder]] because no file with such name exists (see [[wikilinks]] for more info).
- `feature` shows an example of how you can use note types to customize the graph. It defines the color for the notes of type `feature`
- see [[note-properties]] for details
- you can have as many types as you want
- `background` background color of the graph, adjust to increase contrast
- `fontSize` size 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
- `highlightedForeground` color of highlighted nodes and edges when hovering over a node
- to style individual types of nodes jump to the next section: [Style Nodes By Type](#style-nodes-by-type)
### Style nodes by type
### Style Nodes by Type
It is possible to customize the style of a node based on the `type` property in the YAML frontmatter of the corresponding document.
There are a few default node types defined by Foam that are displayed in the graph:
- `note` defines the color for regular nodes whose documents have not overriden the `type` property.
- `placeholder` defines the color for links that don't match any existing note. This is a [[placeholder]] because no file with such name exists.
- see [[wikilinks]] for more info <!--NOTE: this placeholder link should NOT have an associated file. This is to demonstrate the custom coloring-->
- `tag` defines the color for nodes representing #tags, allowing tags to be used as graph nodes similar to backlinks.
- see [[tags]] for more info
- `feature` shows an example of how you can use note types to customize the graph. It defines the color for the notes of type `feature`
- see [[note-properties]] for details
For example the following `backlinking.md` note:
```
```markdown
---
type: feature
---
@@ -58,7 +85,11 @@ And the following `settings.json`:
```json
"foam.graph.style": {
"background": "#202020",
"node": {
"note": "#277da1",
"placeholder": "#545454",
"tag": "#f9c74f",
"feature": "red",
}
}
@@ -69,6 +100,7 @@ Will result in the following graph:
![Style node by type](../../assets/images/style-node-by-type.png)
[//begin]: # "Autogenerated link references for markdown compatibility"
[wikilinks]: wikilinks.md "Wikilinks"
[note-properties]: note-properties.md "Note Properties"
[wikilinks]: wikilinks.md "Wikilinks"
[tags]: tags.md "Tags"
[//end]: # "Autogenerated link references"

View File

@@ -48,9 +48,23 @@ You can override this setting in your Foam workspace's `settings.json`:
Sometimes, you may want to ignore certain files or folders, so that Foam doesn't generate link reference definitions to them.
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."
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)."
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).
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.
You can ignore the `_site` directory by adding the following to your `.vscode/settings.json`:
You can ignore the `_site` directory by adding any of the following settings to your `.vscode/settings.json` file:
```json
"files.exclude": {
@@ -59,18 +73,16 @@ You can ignore the `_site` directory by adding the following to your `.vscode/se
"files.watcherExclude": {
"**/_site": true
},
"foam.files.ignore": [
"_site/**/*"
]
```
After changing the setting in your workspace, you can run the [[workspace-janitor]] command to convert all existing definitions.
## Future improvements
Implement `foam.exclude`. [[todo]]
See [[link-reference-definition-improvements]] for further discussion on current problems and potential solutions.
[//begin]: # "Autogenerated link references for markdown compatibility"
[workspace-janitor]: ../tools/workspace-janitor.md "Janitor"
[todo]: ../../dev/todo.md "Todo"
[link-reference-definition-improvements]: ../../dev/proposals/link-reference-definition-improvements.md "Link Reference Definition Improvements"
[//end]: # "Autogenerated link references"

View File

@@ -1,35 +1,56 @@
---
type: feature
keywords: hello world
keywords: hello world, bonjour
tags: [hello, bonjour]
---
# Note Properties
At the top of the file you can have a section where you define your properties.
At the top of the file you can have a section where you define your properties. This section is known as the [Front-Matter](https://learn.cloudcannon.com/jekyll/introduction-to-jekyll-front-matter/) of the document and uses [YAML formatting](https://www.codeproject.com/Articles/1214409/Learn-YAML-in-five-minutes).
> Be aware that this section needs to be at the very top of the file to be valid
> Be aware that this YAML section needs to be at the very top of the file to be valid.
For example, for this file, we have:
```text
```markdown
---
type: feature
keywords: hello world
keywords: hello world, bonjour
---
```
Those are properties.
Properties can be used to organize your notes.
This sets the `type` of this document to `feature` and sets **three** keywords for the document: `hello`, `world`, and `bonjour`. The YAML parser will treat both spaces and commas as the seperators for these YAML properties. If you want to use multi-word values for these properties, you will need to combine the words with dashes or underscores (i.e. instead of `hello world`, use `hello_world` or `hello-world`).
> You can set as many custom properties for a document as you like, but there are a few [special properties](#special-properties) defined by Foam.
## Special Properties
Some properties have special meaning for Foam:
- the `title` property 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]])
- the `type` property can be used to style notes differently in the graph (also see [[graph-visualization]])
- the `tags` property can be used to add tags to a note (see [[tags-and-tag-explorer]])
| Name | Description |
| -------------------- | ------------------- |
| `title` | will assign the name to the note that you will see in the graph, regardless of the filename or the first heading (also see how to [[write-notes-in-foam]]) |
| `type` | can be used to style notes differently in the graph (also see [[graph-visualization]]). The default type for a document is `note` unless otherwise specified with this property. |
| `tags` | can be used to add tags to a note (see [[tags]]) |
For example:
```markdown
---
title: "Note Title"
type: "daily-note"
tags: daily, funny, planning
---
```
## Foam Template Properties
There also exists properties that are even more specific to Foam templates, see [[note-templates#Metadata]] for more info.
[//begin]: # "Autogenerated link references for markdown compatibility"
[write-notes-in-foam]: ../getting-started/write-notes-in-foam.md "Writing Notes"
[graph-visualization]: graph-visualization.md "Graph Visualization"
[tags]: tags.md "Tags"
[note-templates#Metadata]: note-templates.md "Note Templates"
[//end]: # "Autogenerated link references"

View File

@@ -25,15 +25,28 @@ To create a note from a template:
_Theme: Ayu Light_
## Special templates
### Default template
The `.foam/templates/new-note.md` template is special in that it is the template that will be used by the `Foam: Create New Note` command.
Customize this template to contain content that you want included every time you create a note.
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
---
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.
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
---
type: daily-note
---
```
## Variables
@@ -41,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 note. |
| `FOAM_TITLE` | The title of the note. If used, Foam will prompt you to enter a title for the note. |
| `FOAM_SLUG` | The sluggified title of the note (using the default github slug method). If used, Foam will prompt you to enter a title for the note unless `FOAM_TITLE` has already caused the prompt. |
| `FOAM_DATE_*` | `FOAM_DATE_YEAR`, `FOAM_DATE_MONTH`, etc. Foam-specific versions of [VS Code's datetime snippet variables](https://code.visualstudio.com/docs/editor/userdefinedsnippets#_variables). Prefer these versions over VS Code's. |
| Name | Description |
| -------------------- | ------------ |
| `FOAM_SELECTED_TEXT` | Foam will fill it with selected text when creating a new note, if any text is selected. Selected text will be replaced with a wikilink to the new |
| `FOAM_TITLE` | The title of the note. If used, Foam will prompt you to enter a title for the note. |
| `FOAM_SLUG` | The sluggified title of the note (using the default github slug method). If used, Foam will prompt you to enter a title for the note unless `FOAM_TITLE` has already caused the prompt. |
| `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
@@ -56,6 +69,8 @@ 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).
#### Relative daily notes
When referring to daily notes, you can use the relative snippets (`/+1d`, `/tomorrow`, etc.). In these cases, the new notes will be created with the daily note template, but the datetime used should be the relative datetime, not the current datetime.
@@ -81,22 +96,19 @@ 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.
### `filepath` attribute
The `filepath` metadata attribute allows you to define a relative or absolute filepath to use when creating a note using the template.
If the filepath is a relative filepath, it is relative to the current workspace.
The `filepath` metadata attribute allows you to define a relative or absolute filepath to use when creating a note using the template. If the filepath is a relative filepath, it is relative to the current workspace.
**Note:** While you can make use of the `filepath` attribute in [daily note](daily-notes.md) templates (`.foam/templates/daily-note.md`), there is currently no way to have `filepath` vary based on the date. This will be improved in the future. For now, you can customize the location of daily notes using the [`foam.openDailyNote` settings](daily-notes.md).
#### Example of relative `filepath`
#### Example of **relative** `filepath`
For example, `filepath` can be used to customize `.foam/templates/new-note.md`, overriding the default `Foam: Create New Note` behaviour of opening the file in the same directory as the active file:
@@ -109,7 +121,7 @@ foam_template:
---
```
#### Example of absolute `filepath`
#### Example of **absolute** `filepath`
`filepath` can be an absolute filepath, so that the notes get created in the same location, regardless of which file or workspace the editor currently has open.
The format of an absolute filepath may vary depending on the filesystem used.
@@ -125,6 +137,22 @@ foam_template:
---
```
#### Example of **date-based** `filepath`
It is possible to vary the `filepath` value based on the current date using the `FOAM_DATE_*` variables. This is especially useful for the [[daily-notes]] template if you wish to organize by years, months, etc. Below is an example of a daily-note template metadata section that will create new daily notes under the `journal/YEAR/MONTH-MONTH_NAME/` filepath. For example, when a note is created on November 15, 2022, a new file will be created at `C:\Users\foam_user\foam_notes\journal\2022\11-Nov\2022-11-15-daily-note.md`. This method also respects the creation of daily notes relative to the current date (i.e. `/+1d`).
```markdown
---
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"
---
# $FOAM_DATE_YEAR-$FOAM_DATE_MONTH-$FOAM_DATE_DATE Daily Notes
```
> Note: this method **requires** the use of absolute file paths, and in this example is using Windows path conventions. This method will also override any filename formatting defined in `.vscode/settings.json`
### `name` and `description` attributes
These attributes provide a human readable name and description to be shown in the template picker (e.g. When a user uses the `Foam: Create New Note From Template` command):
@@ -185,3 +213,7 @@ existing_frontmatter: "Existing Frontmatter block"
---
This is the rest of the template
```
[//begin]: # "Autogenerated link references for markdown compatibility"
[daily-notes]: daily-notes.md "Daily Notes"
[//end]: # "Autogenerated link references"

View File

@@ -1,5 +1,5 @@
---
tags: my-tag1 my-tag2
tags: my-tag1 my-tag2 my-tag3/notes
---
# Tags
@@ -11,20 +11,45 @@ You can add tags to your notes to categorize or link notes together.
There are two ways of creating a tag:
- Adding a `#tag` anywhere in the text of the note, for example: #my-tag1
- Using the `tags: tag1, tag2` yaml frontmatter [[note property|note-properties]]. Notice `my-tag1` and `my-tag2` tags which are added to this document this way.
- Using the `tags: tag1, tag2` yaml frontmatter [[note-properties|note property]]. Notice `my-tag1` and `my-tag2` tags which are added to this document this way.
Tags can also be hierarchical, so you can have `#parent/child`.
Tags can also be hierarchical, so you can have `#parent/child` such as #my-tag3/info.
## Using *Tag Explorer*
It's possible to navigate tags via the Tag Explorer panel.
In the future it will be possible to explore tags via the graph as well.
It's possible to navigate tags via the Tag Explorer panel. Expand the Tag Explorer view in the left side bar which will list all the tags found in current Foam environment. Then, each level of tags can be expanded until the options to search by tag and a list of all files containing a particular tag are shown.
Tags can also be visualized in the Foam Graph Explorer. See [[graph-visualization]] for more info including how to change the color of nodes representing tags.
## Styling tags
Inline tags can be styled using custom CSS with the selector `.foam-tag`.
It is possible to customize the way that tags look in the Markdown Preview panel that renders your Foam notes. This requires some knowledge of the CSS language, which is used to customize the styles of web technologies such as VSCode. A cursory introduction to CSS can be [found here](https://www.freecodecamp.org/news/get-started-with-css-in-5-minutes-e0804813fc3e/).
1. Create a CSS file within your Foam project, for example in `.foam/css/custom-tag-style.css` or [.vscode/custom-tag-style.css](../../.vscode/custom-tag-style.css)
2. Add CSS code that targets the `.foam-tag` class
3. Add a rule for each [CSS property](https://www.w3schools.com/cssref/index.php) you would like applied to your tags.
4. Open the `.vscode/settings.json` file (or the Settings browser with `ctrl+,`)
5. Add the path to your new stylesheet to the `markdown.styles` setting.
> Note: the file path for the stylesheet will be relative to the currently open folder in the workspace when changing this setting for the current workspace. If changing this setting for the user, then the file path will be relative to your global [VSCode settings](https://code.visualstudio.com/docs/getstarted/settings).
The end result will be a CSS file that looks similiar to the content below. Now you can make your tags standout in your note previews.
```css
.foam-tag{
color:#ffffff;
background-color: #000000;
}
```
![custom tag style demo](../../assets/images/custom-tag-style.png)
## Using backlinks in place of tags
Given the power of backlinks, some people prefer to use them as tags.
For example you can tag your notes about books with [[book]].
[//begin]: # "Autogenerated link references for markdown compatibility"
[note-properties|note property]: note-properties.md "Note Properties"
[graph-visualization]: graph-visualization.md "Graph Visualization"
[//end]: # "Autogenerated link references"

View File

@@ -6,11 +6,11 @@ Wikilinks are the internal links that connect the files in your knowledge base.
To create a wikilink, type `[[` and then start typing the name of another note in your repo. Once the desired note is selected press the `tab` key to autocomplete it. For example: [[graph-visualization]].
`Cmd` + `Click` ( `Ctrl` + `Click` on Windows ) on wikilink to navigate to that note (`F12` also works while your cursor is on the wikilink). If the file doesn't exist it will be created in your workspace based on your default [[note-template]] settings.
`Cmd` + `Click` ( `Ctrl` + `Click` on Windows ) on wikilink to navigate to that note (`F12` also works while your cursor is on the wikilink). If the file doesn't exist it will be created in your workspace based on your default [[note-templates]] settings.
## Placeholders
You can also create a [[placeholder]].
You can also create a [[placeholder]]. <!--NOTE: this placeholder link should NOT have an associated file. This is to demonstrate the concept-->
A placeholder is a wikilink that doesn't have a target file and a link to a placeholder is styled differently so you can easily tell them apart.
They can still be helpful to highlight connections.
@@ -34,8 +34,8 @@ The [Foam for VSCode](https://marketplace.visualstudio.com/items?itemName=foam.f
[//begin]: # "Autogenerated link references for markdown compatibility"
[graph-visualization]: graph-visualization.md "Graph Visualization"
[note-templates]: note-templates.md "Note Templates"
[link-reference-definitions]: link-reference-definitions.md "Link Reference Definitions"
[foam-file-format]: ../../dev/foam-file-format.md "Foam File Format"
[note-templates]: note-templates.md "Note Templates"
[link-reference-definition-improvements]: ../../dev/proposals/link-reference-definition-improvements.md "Link Reference Definition Improvements"
[//end]: # "Autogenerated link references"

View File

@@ -13,7 +13,12 @@
- Check the formatting rules for links on [[foam-file-format]] and [[wikilinks]]
## I don't want Foam enabled for all my workspaces
Any extension you install in Visual Studio Code is enabled by default. Give the philosophy of Foam it works out of the box without doing any configuration upfront. In case you want to disable Foam for a specific workspace, or disable Foam by default and enable it for specific workspaces, it is advised to follow the best practices as [documented by Visual Studio Code](https://code.visualstudio.com/docs/editor/extension-marketplace#_manage-extensions)
Any extension you install in Visual Studio Code is enabled by default. Given the philosophy of Foam, it works out of the box without doing any configuration upfront. In case you want to disable Foam for a specific workspace, or disable Foam by default and enable it for specific workspaces, it is advised to follow the best practices as [documented by Visual Studio Code](https://code.visualstudio.com/docs/editor/extension-marketplace#_manage-extensions)
## I want to publish the graph view to GitHub pages or Vercel
If you want a different front-end look to your published foam and a way to see your graph view, we'd recommend checking out these templates:
- [foam-gatsby](https://github.com/mathieudutour/foam-gatsby-template) by [Mathieu Dutour](https://github.com/mathieudutour)
- [foam-gatsby-kb](https://github.com/hikerpig/foam-template-gatsby-kb) by [hikerpig](https://github.com/hikerpig)
[//begin]: # "Autogenerated link references for markdown compatibility"
[recommended-extensions]: getting-started/recommended-extensions.md "Recommended Extensions"

View File

@@ -36,8 +36,12 @@ If you want to learn more about VS Code, check out their [website](https://code.
You can see a few panels on the left, including:
- `Outline`: this panel shows you the structure of the file based on the headings
- `Tag Explorer`: This shows you the tags in your workspace, see [[tags-and-tag-explorer]] for more information on tags
- `Tag Explorer`: This shows you the tags in your workspace, see [[tags]] for more information on tags
## Settings
To view or change the settings in VS Code, press `cmd+,`
To view or change the settings in VS Code, press `cmd+,` on Mac and `ctrl+,` on Windows/Linux.
[//begin]: # "Autogenerated link references for markdown compatibility"
[tags]: ../features/tags.md "Tags"
[//end]: # "Autogenerated link references"

View File

@@ -218,7 +218,7 @@ Commit the file and push it to gitlab.
### Troubleshooting
- *Could not locate Gemfile* - You didn't follow the steps above to [#Add a Gemlock file]
- *Could not locate Gemfile* - You didn't follow the steps above to [Add a Gemlock file](#add-a-gemlock-file)
- *Conversion error: Jekyll::Converters::Scss encountered an error while converting* You need to reference a theme.
- *Pages are running in CI/CD, but I only ever see `test`, and never deploy* - Perhaps you've renamed the main branch (from master) - check the settings in `.gitlab-ci.yml` and ensure the deploy command is running to the branch you expect it to.
- *I deployed, but my .msd files don't seem to be being converted into .html files* - You need a gem that GitHub installs by default - check `gem "jekyll-optional-front-matter"` appears in the `Gemfile`

View File

@@ -62,7 +62,7 @@ A #recipe is a guide, tip or strategy for getting the most out of your Foam work
- Quick commits with VS Code's built in [[git-integration]]
- Store your workspace in an auto-synced GitHub repo with [[write-your-notes-in-github-gist]]
- Sync your GitHub repo automatically using the [GitDoc VSCode Plugin](https://marketplace.visualstudio.com/items?itemName=vsls-contrib.gitdoc).
- Sync your GitHub repo automatically using the [GitDoc VSCode Plugin](https://marketplace.visualstudio.com/items?itemName=vsls-contrib.gitdoc) [[automatic-git-syncing]].
## Publish
@@ -82,8 +82,8 @@ A #recipe is a guide, tip or strategy for getting the most out of your Foam work
## Collaborate
- Give your team push access to your GitHub repo [[todo]]
- Real-time collaboration via VS Code Live Share [[todo]]
- Give your team push access to your [GitHub repo](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-personal-account-on-github/managing-access-to-your-personal-repositories/inviting-collaborators-to-a-personal-repository)
- Real-time collaboration via VS Code Live Share [[real-time-collaboration]]
- Accept patches via GitHub PRs [[todo]]
## Workflow
@@ -128,6 +128,7 @@ _See [[contribution-guide]] and [[how-to-write-recipes]]._
[good-first-task]: ../../dev/good-first-task.md "Good First Task"
[including-notes]: ../features/including-notes.md "Including notes in a note"
[write-your-notes-in-github-gist]: write-your-notes-in-github-gist.md "Write your notes in GitHub Gist"
[automatic-git-syncing]: automatic-git-syncing.md "Automatically Sync with Git"
[publish-to-github-pages]: ../publishing/publish-to-github-pages.md "GitHub Pages"
[publish-to-gitlab-pages]: ../publishing/publish-to-gitlab-pages.md "GitLab Pages"
[publish-to-azure-devops-wiki]: ../publishing/publish-to-azure-devops-wiki.md "Publish to Azure DevOps Wiki"
@@ -137,6 +138,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"
[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"
[//end]: # "Autogenerated link references"

View File

@@ -4,5 +4,5 @@
],
"npmClient": "yarn",
"useWorkspaces": true,
"version": "0.20.1"
"version": "0.20.4"
}

View File

@@ -1,7 +1,12 @@
.vscode/**
.vscode-test/**
out/test/**
out/**/*.test.*
out/**/*.spec.*
test-data/**
src/**
jest.config.js
.test-workspace
.gitignore
vsc-extension-quickstart.md
**/tsconfig.json

View File

@@ -4,9 +4,33 @@ 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.20.4] - 2023-01-04
Fixes and Improvements:
- Added support for emoji tags (#1125 - thanks @badsketch)
## [0.20.3] - 2022-12-19
Fixes and Improvements:
- Show number of entries in title for orphan, placeholder, tag treeviews
## [0.20.2] - 2022-10-26
Fixes and Improvements:
- Creating new note uses default template when none is provided (#1094)
Internal:
- Changed matcher implementation to remove dependency on micromatch/glob
- Removed unnecessary dependencies and assets from extension
## [0.20.1] - 2022-10-13
Fixes and Improvements:
- Improved support for daily notes in multi root workspace (#1073)
- Create note from placeholder using template (#1061 - thanks @Dominic-DallOsto)
- Improved support for globs in multi root workspace (#1083)
@@ -14,9 +38,11 @@ Fixes and Improvements:
## [0.20.0] - 2022-09-30
New Features:
- Added `foam-vscode.create-note` command, which can be very customized for several use cases (#1076)
Fixes and Improvements:
- Removed `+` as a trigger char for date snippets
- Improved attachment support (#915)
- Improved error handling when starting Foam without an open workspace (#908)
@@ -27,6 +53,7 @@ Fixes and Improvements:
## [0.19.5] - 2022-09-01
Fixes and Improvements:
- Added `FOAM_DATE_WEEK` variable (#1053 - Thanks @dmurph)
- Fixed extension inclusion when generating references for attachments
- Link completion label can be note title as well as path (#1059)
@@ -35,20 +62,24 @@ Fixes and Improvements:
## [0.19.4] - 2022-08-07
Fixes and Improvements:
- Fixed note embed in preview (#1052)
## [0.19.3] - 2022-08-04
Fixes and Improvements:
- Image embeds fixed in preview (#1036)
## [0.19.2] - 2022-08-04
Fixes and Improvements:
- Added support for angle markdown links (#1044)
- Filter out invalid file name chars when creating note (#1042)
Internal:
- Reorganized docs (#1031, thanks @infogulch)
- Fixed documentation links (#1046)
- Preview code refactoring
@@ -56,39 +87,46 @@ Internal:
## [0.19.1] - 2022-07-11
Internal:
- Introduced cache for markdown parser (#1030)
- Various code refactorings
## [0.19.0] - 2022-07-07
New Features:
- Support for attachments (PDF) and images (#1027)
- Support for opening day notes for other days as well (#1026, thanks @alper)
## [0.18.5] - 2022-06-29
Fixes and Improvements:
- Support for `alias` YAML property to define note alias (#1014 - thanks @lingyv-li)
Internal:
- Improved extension bundling (#1015 - thanks @lingyv-li)
- Use `vscode.workspace.fs` instead of `fs` (#1005 - thanks @joshdover)
## [0.18.4] - 2022-06-03
Fixes and Improvements:
- move past `]]` when writing wikilinks (#998 - thanks @Lauviah0622)
- highlight improvements (#890 - thanks @memeplex)
## [0.18.3] - 2022-04-17
Fixes and Improvements:
- Better reporting when links fail to resolve
- Failing link resolution during graph computation no longer fatal
## [0.18.2] - 2022-04-14
Fixes and Improvements:
- Fixed parsing error on empty direct links (#980 - thanks @chrisUsick)
- Improved rendering in preview of wikilinks that have link definitions (#979 - thanks @josephdecock)
- Restored handling of section-only wikilinks (#981)
@@ -96,6 +134,7 @@ Fixes and Improvements:
## [0.18.1] - 2022-04-13
Fixes and Improvements:
- Fixed parsing error for direct links with square brackets in them (#977)
- Improved markdown direct link resolution (#972)
- Improved templates support for custom paths (#970)
@@ -103,14 +142,17 @@ Fixes and Improvements:
## [0.18.0] - 2022-04-11
Features:
- Link synchronization on file rename
Internal:
- Changed graph computation on workspace change to simplify code
## [0.17.8] - 2022-04-01
Fixes and Improvements:
- Do not add ignored files to Foam upon change (#480)
- Restore full use of editor.action.openLink (#693)
- Minor performance improvements
@@ -118,49 +160,58 @@ Fixes and Improvements:
## [0.17.7] - 2022-03-29
Fixes and Improvements:
- Include links with sections in backlinks (#895)
- Improved navigation when document editor is already open
## [0.17.6] - 2022-03-03
Fixes and Improvements:
- Don't fail on error when scannig workspace (#943 - thanks @develmusa)
## [0.17.5] - 2022-02-22
Fixes and Improvements:
- Added FOAM_SLUG template variable (#865 - Thanks @techCarpenter)
## [0.17.4] - 2022-02-13
Fixes and Improvements:
- Improvements to Foam variables in templates (#882 - thanks @movermeyer)
- Foam variables can now be used just any other VS Code variables, including in combination with placeholders and transformers
## [0.17.3] - 2022-01-14
Fixes and Improvements:
- Fixed autocompletion with tags (#885 - thanks @memeplex)
- Improved "Open Daily Note" to be usabled in tasks (#897 - thanks @MCluck90)
## [0.17.2] - 2021-12-22
Fixes and Improvements:
- Improved support for wikilinks in titles (#878)
- Use syntax injection for wikilinks (#876 - thanks @memeplex)
- Fix when applying text edits in last line
- Fix when applying text edits in last line
Internal:
- DX: Clean up of testing setup (#881 - thanks @memeplex)
## [0.17.1] - 2021-12-16
Fixes and Improvements:
- Decorate markdown files only (#857)
- Fix template placeholders issue (#859)
- Improved replacement range for link completion
Internal:
- Major URI/path handling refactoring (#858 - thanks @memeplex)
## [0.17.0] - 2021-12-08
@@ -216,7 +267,6 @@ Fixes and Improvements:
- Link Reference Generation is now OFF by default
- Fixed preview navigation (#830)
## [0.15.5] - 2021-11-15
Fixes and Improvements:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 604 KiB

View File

@@ -8,7 +8,7 @@
"type": "git"
},
"homepage": "https://github.com/foambubble/foam",
"version": "0.20.1",
"version": "0.20.4",
"license": "MIT",
"publisher": "foam",
"engines": {
@@ -166,7 +166,7 @@
"commands": [
{
"command": "foam-vscode.create-note",
"title": "Foam: Create Note"
"title": "Foam: Create New Note"
},
{
"command": "foam-vscode.clear-cache",
@@ -210,7 +210,7 @@
},
{
"command": "foam-vscode.create-note-from-template",
"title": "Foam: Create Note From Template"
"title": "Foam: Create New Note From Template"
},
{
"command": "foam-vscode.create-note-from-default-template",
@@ -485,10 +485,12 @@
"eslint-import-resolver-typescript": "^2.5.0",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-jest": "^25.3.0",
"glob": "^7.1.6",
"husky": "^4.2.5",
"jest": "^26.2.2",
"jest-extended": "^0.11.5",
"markdown-it": "^12.0.4",
"micromatch": "^4.0.2",
"rimraf": "^3.0.2",
"ts-jest": "^26.4.4",
"tsdx": "^0.13.2",
@@ -500,19 +502,14 @@
"dependencies": {
"dateformat": "^3.0.3",
"detect-newline": "^3.1.0",
"fast-array-diff": "^1.0.1",
"github-slugger": "^1.4.0",
"glob": "^7.1.6",
"gray-matter": "^4.0.2",
"lodash": "^4.17.21",
"lru-cache": "^7.12.0",
"markdown-it-regex": "^0.2.0",
"micromatch": "^4.0.2",
"remark-frontmatter": "^2.0.0",
"remark-parse": "^8.0.2",
"remark-wiki-link": "^0.0.4",
"remove-markdown": "^0.3.0",
"replace-ext": "^2.0.0",
"title-case": "^3.0.2",
"unified": "^9.0.0",
"unist-util-visit": "^2.0.2",

View File

@@ -4,10 +4,10 @@ import { MarkdownResourceProvider } from '../services/markdown-provider';
import { Resource } from '../model/note';
import { Range } from '../model/range';
import { FoamWorkspace } from '../model/workspace';
import { FileDataStore, Matcher } from '../services/datastore';
import { Logger } from '../utils/log';
import detectNewline from 'detect-newline';
import { createMarkdownParser } from '../services/markdown-parser';
import { FileDataStore } from '../../test/test-datastore';
Logger.setLevel('error');
@@ -20,11 +20,13 @@ describe('generateHeadings', () => {
};
beforeAll(async () => {
const matcher = new Matcher([TEST_DATA_DIR.joinPath('__scaffold__')]);
const dataStore = new FileDataStore(readFileFromFs);
const dataStore = new FileDataStore(
readFileFromFs,
TEST_DATA_DIR.joinPath('__scaffold__').toFsPath()
);
const parser = createMarkdownParser();
const mdProvider = new MarkdownResourceProvider(matcher, dataStore, parser);
_workspace = await FoamWorkspace.fromProviders([mdProvider]);
const mdProvider = new MarkdownResourceProvider(dataStore, parser);
_workspace = await FoamWorkspace.fromProviders([mdProvider], dataStore);
});
it.skip('should add heading to a file that does not have them', async () => {

View File

@@ -4,12 +4,12 @@ import { MarkdownResourceProvider } from '../services/markdown-provider';
import { Resource } from '../model/note';
import { Range } from '../model/range';
import { FoamWorkspace } from '../model/workspace';
import { FileDataStore, Matcher } from '../services/datastore';
import { Logger } from '../utils/log';
import fs from 'fs';
import { URI } from '../model/uri';
import { EOL } from 'os';
import { createMarkdownParser } from '../services/markdown-parser';
import { FileDataStore } from '../../test/test-datastore';
Logger.setLevel('error');
@@ -23,14 +23,16 @@ describe('generateLinkReferences', () => {
};
beforeAll(async () => {
const matcher = new Matcher([TEST_DATA_DIR.joinPath('__scaffold__')]);
/** 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);
const dataStore = new FileDataStore(
readFile,
TEST_DATA_DIR.joinPath('__scaffold__').toFsPath()
);
const parser = createMarkdownParser();
const mdProvider = new MarkdownResourceProvider(matcher, dataStore, parser);
_workspace = await FoamWorkspace.fromProviders([mdProvider]);
const mdProvider = new MarkdownResourceProvider(dataStore, parser);
_workspace = await FoamWorkspace.fromProviders([mdProvider], dataStore);
});
it('initialised test graph correctly', () => {

View File

@@ -1,5 +1,5 @@
import { IDisposable } from '../common/lifecycle';
import { IDataStore, IMatcher } from '../services/datastore';
import { IDataStore, IMatcher, IWatcher } from '../services/datastore';
import { FoamWorkspace } from './workspace';
import { FoamGraph } from './graph';
import { ResourceParser } from './note';
@@ -22,14 +22,18 @@ export interface Foam extends IDisposable {
export const bootstrap = async (
matcher: IMatcher,
watcher: IWatcher | undefined,
dataStore: IDataStore,
parser: ResourceParser,
initialProviders: ResourceProvider[]
) => {
const workspace = new FoamWorkspace();
const tsStart = Date.now();
await Promise.all(initialProviders.map(p => workspace.registerProvider(p)));
const workspace = await FoamWorkspace.fromProviders(
initialProviders,
dataStore
);
const tsWsDone = Date.now();
Logger.info(`Workspace loaded in ${tsWsDone - tsStart}ms`);
@@ -41,13 +45,28 @@ export const bootstrap = async (
const tsTagsEnd = Date.now();
Logger.info(`Tags loaded in ${tsTagsEnd - tsGraphDone}ms`);
watcher?.onDidChange(async uri => {
if (matcher.isMatch(uri)) {
await workspace.fetchAndSet(uri);
}
});
watcher?.onDidCreate(async uri => {
await matcher.refresh();
if (matcher.isMatch(uri)) {
await workspace.fetchAndSet(uri);
}
});
watcher?.onDidDelete(uri => {
workspace.delete(uri);
});
const foam: Foam = {
workspace,
graph,
tags,
services: {
dataStore,
parser,
dataStore,
matcher,
},
dispose: () => {

View File

@@ -4,7 +4,6 @@ import { URI } from './uri';
import { FoamWorkspace } from './workspace';
export interface ResourceProvider extends IDisposable {
init: (workspace: FoamWorkspace) => Promise<void>;
supports: (uri: URI) => boolean;
readAsMarkdown: (uri: URI) => Promise<string | null>;
fetch: (uri: URI) => Promise<Resource | null>;

View File

@@ -5,6 +5,7 @@ import { isSome } from '../utils';
import { Emitter } from '../common/event';
import { ResourceProvider } from './provider';
import { IDisposable } from '../common/lifecycle';
import { IDataStore } from '../services/datastore';
export class FoamWorkspace implements IDisposable {
private onDidAddEmitter = new Emitter<Resource>();
@@ -23,7 +24,6 @@ export class FoamWorkspace implements IDisposable {
registerProvider(provider: ResourceProvider) {
this.providers.push(provider);
return provider.init(this);
}
set(resource: Resource) {
@@ -159,6 +159,20 @@ export class FoamWorkspace implements IDisposable {
return Promise.resolve(null);
}
/**
* Takes a resource URI, and adds it to the workspace as a resource.
* If the URI is not supported by any provider or is not found, it will not
* add anything to the workspace, and return null.
*
* @param uri the URI where the resource is located
* @returns A promise to the Resource, or null if none was found
*/
public async fetchAndSet(uri: URI): Promise<Resource | null> {
const resource = await this.fetch(uri);
resource && this.set(resource);
return resource;
}
public readAsMarkdown(uri: URI): Promise<string | null> {
for (const provider of this.providers) {
if (provider.supports(uri)) {
@@ -220,12 +234,13 @@ export class FoamWorkspace implements IDisposable {
}
static async fromProviders(
providers: ResourceProvider[]
providers: ResourceProvider[],
dataStore: IDataStore
): Promise<FoamWorkspace> {
const workspace = new FoamWorkspace();
for (const provider of providers) {
await workspace.registerProvider(provider);
}
await Promise.all(providers.map(p => workspace.registerProvider(p)));
const files = await dataStore.list();
await Promise.all(files.map(f => workspace.fetchAndSet(f)));
return workspace;
}
}

View File

@@ -1,8 +1,6 @@
import { Resource, ResourceLink } from '../model/note';
import { Logger } from '../utils/log';
import { URI } from '../model/uri';
import { FoamWorkspace } from '../model/workspace';
import { IDataStore, IMatcher, IWatcher } from '../services/datastore';
import { IDisposable } from '../common/lifecycle';
import { ResourceProvider } from '../model/provider';
import { getFoamVsCodeConfig } from '../../services/config';
@@ -37,51 +35,6 @@ const asResource = (uri: URI): Resource => {
export class AttachmentResourceProvider implements ResourceProvider {
private disposables: IDisposable[] = [];
constructor(
private readonly matcher: IMatcher,
private readonly dataStore: IDataStore,
private readonly watcher?: IWatcher
) {}
async init(workspace: FoamWorkspace) {
const filesByFolder = await Promise.all(
this.matcher.include.map(glob =>
this.dataStore.list(glob, this.matcher.exclude)
)
);
const files = this.matcher
.match(filesByFolder.flat())
.filter(this.supports);
Logger.info(
`Found ${
files.length
} attachments, with extensions: ${attachmentExtensions.join(', ')}`
);
for (const uri of files) {
Logger.debug('Found: ' + uri.toString());
workspace.set(asResource(uri));
}
if (this.watcher != null) {
this.disposables = [
this.watcher.onDidChange(async uri => {
if (this.matcher.isMatch(uri) && this.supports(uri)) {
workspace.set(asResource(uri));
}
}),
this.watcher.onDidCreate(async uri => {
if (this.matcher.isMatch(uri) && this.supports(uri)) {
workspace.set(asResource(uri));
}
}),
this.watcher.onDidDelete(uri => {
this.supports(uri) && workspace.delete(uri);
}),
];
}
}
supports(uri: URI) {
return attachmentExtensions.includes(
uri.getExtension().toLocaleLowerCase()

View File

@@ -1,7 +1,7 @@
import { readFileFromFs, TEST_DATA_DIR } from '../../test/test-utils';
import { Matcher, toMatcherPathFormat } from '../../test/test-datastore';
import { TEST_DATA_DIR } from '../../test/test-utils';
import { URI } from '../model/uri';
import { Logger } from '../utils/log';
import { FileDataStore, Matcher, toMatcherPathFormat } from './datastore';
Logger.setLevel('error');
@@ -83,11 +83,3 @@ describe('Matcher', () => {
expect(matcher.isMatch(files[3])).toEqual(false);
});
});
describe('Datastore', () => {
it('uses the matcher to get the file list', async () => {
const matcher = new Matcher([testFolder], ['**/*.md'], []);
const ds = new FileDataStore(readFileFromFs);
expect((await ds.list(matcher.include[0])).length).toEqual(4);
});
});

View File

@@ -1,13 +1,30 @@
import micromatch from 'micromatch';
import { URI } from '../model/uri';
import { Logger } from '../utils/log';
import { glob } from 'glob';
import { promisify } from 'util';
import { isWindows } from '../common/platform';
import { Event } from '../common/event';
import { asAbsolutePaths } from '../utils/path';
const findAllFiles = promisify(glob);
/**
* Represents a source of files and content
*/
export interface IDataStore {
/**
* List the files matching the given glob from the
* store
*/
list: () => Promise<URI[]>;
/**
* Read the content of the file from the store
*
* Returns `null` in case of errors while reading
*/
read: (uri: URI) => Promise<string | null>;
}
export interface IWatcher {
onDidChange: Event<URI>;
onDidCreate: Event<URI>;
onDidDelete: Event<URI>;
}
export interface IMatcher {
/**
@@ -25,6 +42,14 @@ export interface IMatcher {
*/
isMatch(uri: URI): boolean;
/**
* Refreshes the list of files that this matcher matches
* To be used when new files are added to the workspace,
* it can be a more or less expensive operation depending on the
* implementation of the matcher
*/
refresh(): Promise<void>;
/**
* The include globs
*/
@@ -36,98 +61,14 @@ export interface IMatcher {
exclude: string[];
}
/**
* The matcher requires the path to be in unix format, so if we are in windows
* we convert the fs path on the way in and out
*/
export const toMatcherPathFormat = isWindows
? (uri: URI) => uri.toFsPath().replace(/\\/g, '/')
: (uri: URI) => uri.toFsPath();
export const toFsPath = isWindows
? (path: string): string => path.replace(/\//g, '\\')
: (path: string): string => path;
export class Matcher implements IMatcher {
public readonly folders: string[];
public readonly include: string[] = [];
public readonly exclude: string[] = [];
export class GenericDataStore implements IDataStore {
constructor(
baseFolders: URI[],
includeGlobs: string[] = ['**/*'],
excludeGlobs: string[] = []
) {
this.folders = baseFolders.map(toMatcherPathFormat);
Logger.info('Workspace folders: ', this.folders);
private readonly listFiles: () => Promise<URI[]>,
private readFile: (uri: URI) => Promise<string>
) {}
this.include = includeGlobs.flatMap(glob =>
asAbsolutePaths(glob, this.folders)
);
this.exclude = excludeGlobs.flatMap(glob =>
asAbsolutePaths(glob, this.folders)
);
Logger.info('Glob patterns', {
includeGlobs: this.include,
ignoreGlobs: this.exclude,
});
}
match(files: URI[]) {
const matches = micromatch(
files.map(f => f.toFsPath()),
this.include,
{
ignore: this.exclude,
nocase: true,
format: toFsPath,
}
);
return matches.map(URI.file);
}
isMatch(uri: URI) {
return this.match([uri]).length > 0;
}
}
export interface IWatcher {
onDidChange: Event<URI>;
onDidCreate: Event<URI>;
onDidDelete: Event<URI>;
}
/**
* Represents a source of files and content
*/
export interface IDataStore {
/**
* List the files matching the given glob from the
* store
*/
list: (glob: string, ignoreGlob?: string | string[]) => Promise<URI[]>;
/**
* Read the content of the file from the store
*
* Returns `null` in case of errors while reading
*/
read: (uri: URI) => Promise<string | null>;
}
/**
* File system based data store
*/
export class FileDataStore implements IDataStore {
constructor(private readFile: (uri: URI) => Promise<string>) {}
async list(glob: string, ignoreGlob?: string | string[]): Promise<URI[]> {
const res = await findAllFiles(glob, {
ignore: ignoreGlob,
strict: false,
});
return res.map(URI.file);
async list(): Promise<URI[]> {
return this.listFiles();
}
async read(uri: URI) {
@@ -141,3 +82,75 @@ export class FileDataStore implements IDataStore {
}
}
}
/**
* A matcher that instead of using globs uses a list of files to
* check the matches.
* The {@link refresh} function has been added to the interface to accomodate
* this matcher, far from ideal but to be refactored later
*/
export class FileListBasedMatcher implements IMatcher {
private files: string[] = [];
include: string[];
exclude: string[];
constructor(files: URI[], private readonly listFiles: () => Promise<URI[]>) {
this.files = files.map(f => f.path);
}
match(files: URI[]): URI[] {
return files.filter(f => this.files.includes(f.path));
}
isMatch(uri: URI): boolean {
return this.files.includes(uri.path);
}
async refresh() {
this.files = (await this.listFiles()).map(f => f.path);
}
static async createFromListFn(listFiles: () => Promise<URI[]>) {
const files = await listFiles();
return new FileListBasedMatcher(files, listFiles);
}
}
/**
* A matcher that includes all URIs passed to it
*/
export class AlwaysIncludeMatcher implements IMatcher {
include: string[] = ['**/*'];
exclude: string[] = [];
match(files: URI[]): URI[] {
return files;
}
isMatch(uri: URI): boolean {
return true;
}
refresh(): Promise<void> {
return;
}
}
export class SubstringExcludeMatcher implements IMatcher {
include: string[] = ['**/*'];
exclude: string[] = [];
constructor(exclude: string) {
this.exclude = [exclude];
}
match(files: URI[]): URI[] {
return files.filter(f => this.isMatch(f));
}
isMatch(uri: URI): boolean {
return !uri.path.includes(this.exclude[0]);
}
refresh(): Promise<void> {
return;
}
}

View File

@@ -8,64 +8,19 @@ import { isNone, isSome } from '../utils';
import { Logger } from '../utils/log';
import { URI } from '../model/uri';
import { FoamWorkspace } from '../model/workspace';
import { IDataStore, IMatcher, IWatcher } from '../services/datastore';
import { IDisposable } from '../common/lifecycle';
import { ResourceProvider } from '../model/provider';
import { MarkdownLink } from './markdown-link';
import { IDataStore } from './datastore';
export class MarkdownResourceProvider implements ResourceProvider {
private disposables: IDisposable[] = [];
constructor(
private readonly matcher: IMatcher,
private readonly dataStore: IDataStore,
private readonly parser: ResourceParser,
private readonly watcher?: IWatcher
private readonly parser: ResourceParser
) {}
async init(workspace: FoamWorkspace) {
const filesByFolder = await Promise.all(
this.matcher.include.map(glob =>
this.dataStore.list(glob, this.matcher.exclude)
)
);
const files = this.matcher
.match(filesByFolder.flat())
.filter(this.supports);
await Promise.all(
files.map(async uri => {
Logger.debug('Found: ' + uri.toString());
const content = await this.dataStore.read(uri);
if (isSome(content)) {
workspace.set(this.parser.parse(uri, content));
}
})
);
if (this.watcher != null) {
this.disposables = [
this.watcher.onDidChange(async uri => {
if (this.matcher.isMatch(uri) && this.supports(uri)) {
const content = await this.dataStore.read(uri);
isSome(content) &&
workspace.set(await this.parser.parse(uri, content));
}
}),
this.watcher.onDidCreate(async uri => {
if (this.matcher.isMatch(uri) && this.supports(uri)) {
const content = await this.dataStore.read(uri);
isSome(content) &&
workspace.set(await this.parser.parse(uri, content));
}
}),
this.watcher.onDidDelete(uri => {
this.supports(uri) && workspace.delete(uri);
}),
];
}
}
supports(uri: URI) {
return uri.isMarkdown();
}

View File

@@ -1,6 +1,6 @@
import { isSome } from './core';
const HASHTAG_REGEX = /(?<=^|\s)#([0-9]*[\p{L}/_-][\p{L}\p{N}/_-]*)/gmu;
const WORD_REGEX = /(?<=^|\s)([0-9]*[\p{L}/_-][\p{L}\p{N}/_-]*)/gmu;
const HASHTAG_REGEX = /(?<=^|\s)#([0-9]*[\p{L}\p{Emoji_Presentation}/_-][\p{L}\p{Emoji_Presentation}\p{N}/_-]*)/gmu;
const WORD_REGEX = /(?<=^|\s)([0-9]*[\p{L}\p{Emoji_Presentation}/_-][\p{L}\p{Emoji_Presentation}\p{N}/_-]*)/gmu;
export const extractHashtags = (
text: string

View File

@@ -7,11 +7,13 @@ describe('hashtag extraction', () => {
it('returns empty list if no tags are present', () => {
expect(extractHashtags('hello world')).toEqual([]);
});
it('works with simple strings', () => {
expect(
extractHashtags('hello #world on #this planet').map(t => t.label)
).toEqual(['world', 'this']);
});
it('detects the offset of the tag', () => {
expect(extractHashtags('#hello')).toEqual([{ label: 'hello', offset: 0 }]);
expect(extractHashtags(' #hello')).toEqual([{ label: 'hello', offset: 1 }]);
@@ -19,21 +21,25 @@ describe('hashtag extraction', () => {
{ label: 'hello', offset: 3 },
]);
});
it('works with tags at beginning or end of text', () => {
expect(
extractHashtags('#hello world on this #planet').map(t => t.label)
).toEqual(['hello', 'planet']);
});
it('supports _ and -', () => {
expect(
extractHashtags('#hello-world on #this_planet').map(t => t.label)
).toEqual(['hello-world', 'this_planet']);
});
it('supports nested tags', () => {
expect(
extractHashtags('#parent/child on #planet').map(t => t.label)
).toEqual(['parent/child', 'planet']);
});
it('ignores tags that only have numbers in text', () => {
expect(
extractHashtags('this #123 tag should be ignore, but not #123four').map(
@@ -41,7 +47,8 @@ describe('hashtag extraction', () => {
)
).toEqual(['123four']);
});
it('supports unicode letters like Chinese charaters', () => {
it('supports unicode letters like Chinese characters', () => {
expect(
extractHashtags(`
this #tag_with_unicode_letters_汉字, pure Chinese tag like #纯中文标签 and
@@ -55,6 +62,24 @@ describe('hashtag extraction', () => {
]);
});
it('supports emoji tags', () => {
expect(
extractHashtags(`this is a pure emoji #⭐, #⭐⭐, #👍👍🏽👍🏿 some mixed emoji #π🥧, #✅todo
#urgent❗ or #❗❗urgent, and some nested emoji #📥/🟥 or #📥/🟢
`).map(t => t.label)
).toEqual([
'⭐',
'⭐⭐',
'👍👍🏽👍🏿',
'π🥧',
'✅todo',
'urgent❗',
'❗❗urgent',
'📥/🟥',
'📥/🟢',
]);
});
it('ignores hashes in plain text urls and links', () => {
expect(
extractHashtags(`

View File

@@ -1,18 +1,16 @@
import { workspace, ExtensionContext, window, commands } from 'vscode';
import { MarkdownResourceProvider } from './core/services/markdown-provider';
import { bootstrap } from './core/model/foam';
import { URI } from './core/model/uri';
import { FileDataStore, Matcher } from './core/services/datastore';
import { Logger } from './core/utils/log';
import { features } from './features';
import { VsCodeOutputLogger, exposeLogger } from './services/logging';
import { getIgnoredFilesSetting } from './settings';
import { fromVsCodeUri, toVsCodeUri } from './utils/vsc-utils';
import { AttachmentResourceProvider } from './core/services/attachment-provider';
import { VsCodeWatcher } from './services/watcher';
import { createMarkdownParser } from './core/services/markdown-parser';
import VsCodeBasedParserCache from './services/cache';
import { createMatcherAndDataStore } from './services/editor';
export async function activate(context: ExtensionContext) {
const logger = new VsCodeOutputLogger();
@@ -28,33 +26,30 @@ export async function activate(context: ExtensionContext) {
}
// Prepare Foam
const readFile = async (uri: URI) =>
(await workspace.fs.readFile(toVsCodeUri(uri))).toString();
const dataStore = new FileDataStore(readFile);
const matcher = new Matcher(
workspace.workspaceFolders.map(dir => fromVsCodeUri(dir.uri)),
['**/*'],
getIgnoredFilesSetting().map(g => g.toString())
);
const excludes = getIgnoredFilesSetting().map(g => g.toString());
const {
matcher,
dataStore,
excludePatterns,
} = await createMatcherAndDataStore(excludes);
Logger.info('Loading from directories:');
for (const folder of workspace.workspaceFolders) {
Logger.info('- ' + folder.uri.fsPath);
Logger.info(' Include: **/*');
Logger.info(' Exclude: ' + excludePatterns.get(folder.name).join(','));
}
const watcher = new VsCodeWatcher(
workspace.createFileSystemWatcher('**/*')
);
const parserCache = new VsCodeBasedParserCache(context);
const parser = createMarkdownParser([], parserCache);
const markdownProvider = new MarkdownResourceProvider(
matcher,
dataStore,
parser,
watcher
);
const attachmentProvider = new AttachmentResourceProvider(
matcher,
dataStore,
watcher
);
const markdownProvider = new MarkdownResourceProvider(dataStore, parser);
const attachmentProvider = new AttachmentResourceProvider();
const foamPromise = bootstrap(matcher, dataStore, parser, [
const foamPromise = bootstrap(matcher, watcher, dataStore, parser, [
markdownProvider,
attachmentProvider,
]);
@@ -64,6 +59,7 @@ export async function activate(context: ExtensionContext) {
const foam = await foamPromise;
Logger.info(`Loaded ${foam.workspace.list().length} resources`);
context.subscriptions.push(
foam,
watcher,

View File

@@ -3,6 +3,7 @@ import { FoamFeature } from '../../types';
import { URI } from '../../core/model/uri';
import {
askUserForTemplate,
getDefaultTemplateUri,
getPathFromTitle,
NoteFactory,
} from '../../services/templates';
@@ -67,8 +68,9 @@ async function createNote(args: CreateNoteArgs) {
return;
}
} else {
templateUri =
args.templatePath && asAbsoluteWorkspaceUri(URI.file(args.templatePath));
templateUri = args.templatePath
? asAbsoluteWorkspaceUri(URI.file(args.templatePath))
: getDefaultTemplateUri();
}
if (await fileExists(templateUri)) {

View File

@@ -3,30 +3,26 @@ import { createMarkdownParser } from '../core/services/markdown-parser';
import { MarkdownResourceProvider } from '../core/services/markdown-provider';
import { FoamGraph } from '../core/model/graph';
import { FoamWorkspace } from '../core/model/workspace';
import { FileDataStore, Matcher } from '../core/services/datastore';
import {
cleanWorkspace,
closeEditors,
createFile,
showInEditor,
} from '../test/test-utils-vscode';
import { fromVsCodeUri, toVsCodeUri } from '../utils/vsc-utils';
import { toVsCodeUri } from '../utils/vsc-utils';
import { HoverProvider } from './hover-provider';
import { readFileFromFs } from '../test/test-utils';
import { FileDataStore } from '../test/test-datastore';
// We can't use createTestWorkspace from /packages/foam-vscode/src/test/test-utils.ts
// because we need a MarkdownResourceProvider with a real instance of FileDataStore.
const createWorkspace = () => {
const matcher = new Matcher(
vscode.workspace.workspaceFolders.map(f => fromVsCodeUri(f.uri))
const dataStore = new FileDataStore(
readFileFromFs,
vscode.workspace.workspaceFolders[0].uri.fsPath
);
const dataStore = new FileDataStore(readFileFromFs);
const parser = createMarkdownParser();
const resourceProvider = new MarkdownResourceProvider(
matcher,
dataStore,
parser
);
const resourceProvider = new MarkdownResourceProvider(dataStore, parser);
const workspace = new FoamWorkspace();
workspace.registerProvider(resourceProvider);
return workspace;

View File

@@ -54,7 +54,7 @@ describe('Document navigation', () => {
expect(links.length).toEqual(0);
});
it('should create links for wikilinks', async () => {
it('should not create links for wikilinks, as this is managed by the definition provider', async () => {
const fileA = await createFile('# File A', ['file-a.md']);
const fileB = await createFile(`this is a link to [[${fileA.name}]].`);
const ws = createTestWorkspace()
@@ -66,9 +66,7 @@ describe('Document navigation', () => {
const provider = new NavigationProvider(ws, graph, parser);
const links = provider.provideDocumentLinks(doc);
expect(links.length).toEqual(1);
expect(links[0].target).toEqual(OPEN_COMMAND.asURI(fileA.uri));
expect(links[0].range).toEqual(new vscode.Range(0, 20, 0, 26));
expect(links.length).toEqual(0);
});
it('should create links for placeholders', async () => {

View File

@@ -159,22 +159,23 @@ export class NavigationProvider
})
);
return targets.map(o => {
const command = OPEN_COMMAND.asURI(o.target);
const documentLink = new vscode.DocumentLink(
new vscode.Range(
o.link.range.start.line,
o.link.range.start.character + 2,
o.link.range.end.line,
o.link.range.end.character - 2
),
command
);
documentLink.tooltip = o.target.isPlaceholder()
? `Create note for '${o.target.path}'`
: `Go to ${o.target.toFsPath()}`;
return documentLink;
});
return targets
.filter(o => o.target.isPlaceholder()) // links to resources are managed by the definition provider
.map(o => {
const command = OPEN_COMMAND.asURI(o.target);
const documentLink = new vscode.DocumentLink(
new vscode.Range(
o.link.range.start.line,
o.link.range.start.character + 2,
o.link.range.end.line,
o.link.range.end.character - 2
),
command
);
documentLink.tooltip = `Create note for '${o.target.path}'`;
return documentLink;
});
}
}

View File

@@ -1,32 +0,0 @@
import { FoamGraph } from '../../core/model/graph';
import { createTestNote, createTestWorkspace } from '../../test/test-utils';
import { isOrphan } from './orphans';
const orphanA = createTestNote({
uri: '/path/orphan-a.md',
title: 'Orphan A',
});
const nonOrphan1 = createTestNote({
uri: '/path/non-orphan-1.md',
});
const nonOrphan2 = createTestNote({
uri: '/path/non-orphan-2.md',
links: [{ slug: 'non-orphan-1' }],
});
const workspace = createTestWorkspace()
.set(orphanA)
.set(nonOrphan1)
.set(nonOrphan2);
const graph = FoamGraph.fromWorkspace(workspace);
describe('isOrphan', () => {
it('should return true when a note with no connections is provided', () => {
expect(isOrphan(orphanA.uri, graph)).toBeTruthy();
});
it('should return false when a note with connections is provided', () => {
expect(isOrphan(nonOrphan1.uri, graph)).toBeFalsy();
});
});

View File

@@ -1,7 +1,6 @@
import * as vscode from 'vscode';
import { Foam } from '../../core/model/foam';
import { FoamGraph } from '../../core/model/graph';
import { URI } from '../../core/model/uri';
import { createMatcherAndDataStore } from '../../services/editor';
import { getOrphansConfig } from '../../settings';
import { FoamFeature } from '../../types';
import {
@@ -9,8 +8,8 @@ import {
ResourceTreeItem,
UriTreeItem,
} from '../../utils/grouped-resources-tree-data-provider';
import { fromVsCodeUri } from '../../utils/vsc-utils';
const EXCLUDE_TYPES = ['image', 'attachment'];
const feature: FoamFeature = {
activate: async (
context: vscode.ExtensionContext,
@@ -18,34 +17,45 @@ const feature: FoamFeature = {
) => {
const foam = await foamPromise;
const workspacesURIs = vscode.workspace.workspaceFolders.map(dir =>
fromVsCodeUri(dir.uri)
const { matcher } = await createMatcherAndDataStore(
getOrphansConfig().exclude
);
const provider = new GroupedResourcesTreeDataProvider(
'orphans',
'orphan',
getOrphansConfig(),
workspacesURIs,
() => foam.graph.getAllNodes().filter(uri => isOrphan(uri, foam.graph)),
() =>
foam.graph
.getAllNodes()
.filter(
uri =>
!EXCLUDE_TYPES.includes(foam.workspace.find(uri)?.type) &&
foam.graph.getConnections(uri).length === 0
),
uri => {
if (uri.isPlaceholder()) {
return new UriTreeItem(uri);
}
const resource = foam.workspace.find(uri);
return new ResourceTreeItem(resource, foam.workspace);
}
return uri.isPlaceholder()
? new UriTreeItem(uri)
: new ResourceTreeItem(foam.workspace.find(uri), foam.workspace);
},
matcher
);
provider.setGroupBy(getOrphansConfig().groupBy);
const treeView = vscode.window.createTreeView('foam-vscode.orphans', {
treeDataProvider: provider,
showCollapseAll: true,
});
const baseTitle = treeView.title;
treeView.title = baseTitle + ` (${provider.numElements})`;
context.subscriptions.push(
vscode.window.registerTreeDataProvider('foam-vscode.orphans', provider),
...provider.commands,
foam.graph.onDidUpdate(() => provider.refresh())
foam.graph.onDidUpdate(() => {
treeView.title = baseTitle + ` (${provider.numElements})`;
provider.refresh();
})
);
},
};
export const isOrphan = (uri: URI, graph: FoamGraph) =>
graph.getConnections(uri).length === 0;
export default feature;

View File

@@ -1,12 +1,12 @@
import * as vscode from 'vscode';
import { Foam } from '../../core/model/foam';
import { createMatcherAndDataStore } from '../../services/editor';
import { getPlaceholdersConfig } from '../../settings';
import { FoamFeature } from '../../types';
import {
GroupedResourcesTreeDataProvider,
UriTreeItem,
} from '../../utils/grouped-resources-tree-data-provider';
import { fromVsCodeUri } from '../../utils/vsc-utils';
const feature: FoamFeature = {
activate: async (
@@ -14,27 +14,34 @@ const feature: FoamFeature = {
foamPromise: Promise<Foam>
) => {
const foam = await foamPromise;
const workspacesURIs = vscode.workspace.workspaceFolders.map(dir =>
fromVsCodeUri(dir.uri)
const { matcher } = await createMatcherAndDataStore(
getPlaceholdersConfig().exclude
);
const provider = new GroupedResourcesTreeDataProvider(
'placeholders',
'placeholder',
getPlaceholdersConfig(),
workspacesURIs,
() => foam.graph.getAllNodes().filter(uri => uri.isPlaceholder()),
uri => {
return new UriTreeItem(uri);
}
},
matcher
);
provider.setGroupBy(getPlaceholdersConfig().groupBy);
const treeView = vscode.window.createTreeView('foam-vscode.placeholders', {
treeDataProvider: provider,
showCollapseAll: true,
});
const baseTitle = treeView.title;
treeView.title = baseTitle + ` (${provider.numElements})`;
context.subscriptions.push(
vscode.window.registerTreeDataProvider(
'foam-vscode.placeholders',
provider
),
treeView,
...provider.commands,
foam.graph.onDidUpdate(() => provider.refresh())
foam.graph.onDidUpdate(() => {
treeView.title = baseTitle + ` (${provider.numElements})`;
provider.refresh();
})
);
},
};

View File

@@ -1,20 +1,24 @@
import { createTestNote, readFileFromFs } from '../../test/test-utils';
import {
createTestNote,
readFileFromFs,
TEST_DATA_DIR,
} from '../../test/test-utils';
import { cleanWorkspace, closeEditors } from '../../test/test-utils-vscode';
import { TagItem, TagReference, TagsProvider } from './tags-explorer';
import { bootstrap, Foam } from '../../core/model/foam';
import { MarkdownResourceProvider } from '../../core/services/markdown-provider';
import { FileDataStore, Matcher } from '../../core/services/datastore';
import { createMarkdownParser } from '../../core/services/markdown-parser';
import { URI } from '../../core/model/uri';
import { FileDataStore, Matcher } from '../../test/test-datastore';
describe('Tags tree panel', () => {
let _foam: Foam;
let provider: TagsProvider;
const dataStore = new FileDataStore(readFileFromFs);
const matcher = new Matcher([URI.file('/root')]);
const dataStore = new FileDataStore(readFileFromFs, TEST_DATA_DIR.toFsPath());
const matcher = new Matcher([URI.file(TEST_DATA_DIR.toFsPath())]);
const parser = createMarkdownParser();
const mdProvider = new MarkdownResourceProvider(matcher, dataStore, parser);
const mdProvider = new MarkdownResourceProvider(dataStore, parser);
beforeAll(async () => {
await cleanWorkspace();
@@ -26,7 +30,9 @@ describe('Tags tree panel', () => {
});
beforeEach(async () => {
_foam = await bootstrap(matcher, dataStore, parser, [mdProvider]);
_foam = await bootstrap(matcher, undefined, dataStore, parser, [
mdProvider,
]);
provider = new TagsProvider(_foam, _foam.workspace);
await closeEditors();
});

View File

@@ -15,11 +15,18 @@ const feature: FoamFeature = {
) => {
const foam = await foamPromise;
const provider = new TagsProvider(foam, foam.workspace);
const treeView = vscode.window.createTreeView('foam-vscode.tags-explorer', {
treeDataProvider: provider,
showCollapseAll: true,
});
const baseTitle = treeView.title;
treeView.title = baseTitle + ` (${foam.tags.tags.size})`;
context.subscriptions.push(
vscode.window.registerTreeDataProvider(
'foam-vscode.tags-explorer',
provider
)
treeView,
foam.tags.onDidUpdate(() => {
treeView.title = baseTitle + ` (${foam.tags.tags.size})`;
})
);
foam.tags.onDidUpdate(() => provider.refresh());
},
@@ -29,9 +36,12 @@ export default feature;
export class TagsProvider implements vscode.TreeDataProvider<TagTreeItem> {
// prettier-ignore
private _onDidChangeTreeData: vscode.EventEmitter<TagTreeItem | undefined | void> = new vscode.EventEmitter<TagTreeItem | undefined | void>();
private _onDidChangeTreeData: vscode.EventEmitter<
TagTreeItem | undefined | void
> = new vscode.EventEmitter<TagTreeItem | undefined | void>();
// prettier-ignore
readonly onDidChangeTreeData: vscode.Event<TagTreeItem | undefined | void> = this._onDidChangeTreeData.event;
readonly onDidChangeTreeData: vscode.Event<TagTreeItem | undefined | void> =
this._onDidChangeTreeData.event;
private tags: {
tag: string;

View File

@@ -1,10 +1,13 @@
import { isEmpty } from 'lodash';
import { asAbsoluteUri, URI } from '../core/model/uri';
import { TextEncoder } from 'util';
import {
FileType,
RelativePattern,
Selection,
SnippetString,
TextDocument,
Uri,
ViewColumn,
window,
workspace,
@@ -13,6 +16,13 @@ import {
import { focusNote } from '../utils';
import { fromVsCodeUri, toVsCodeUri } from '../utils/vsc-utils';
import { isSome } from '../core/utils';
import {
AlwaysIncludeMatcher,
FileListBasedMatcher,
GenericDataStore,
IDataStore,
IMatcher,
} from '../core/services/datastore';
interface SelectionInfo {
document: TextDocument;
@@ -124,3 +134,54 @@ export function asAbsoluteWorkspaceUri(uri: URI): URI {
const res = asAbsoluteUri(uri, folders);
return res;
}
export const createMatcherAndDataStore = async (
excludes: string[]
): Promise<{
matcher: IMatcher;
dataStore: IDataStore;
excludePatterns: Map<string, string[]>;
}> => {
const excludePatterns = new Map<string, string[]>();
workspace.workspaceFolders.forEach(f => excludePatterns.set(f.name, []));
for (const exclude of excludes) {
const tokens = exclude.split('/');
const matchesFolder = workspace.workspaceFolders.find(
f => f.name === tokens[0]
);
if (matchesFolder) {
excludePatterns.get(tokens[0]).push(tokens.slice(1).join('/'));
} else {
for (const [, value] of excludePatterns.entries()) {
value.push(exclude);
}
}
}
const listFiles = async () => {
let files: Uri[] = [];
for (const folder of workspace.workspaceFolders) {
const uris = await workspace.findFiles(
new RelativePattern(folder.uri.path, '**/*'),
new RelativePattern(
folder.uri.path,
`{${excludePatterns.get(folder.name).join(',')}}`
)
);
files = [...files, ...uris];
}
return files.map(fromVsCodeUri);
};
const readFile = async (uri: URI) =>
(await workspace.fs.readFile(toVsCodeUri(uri))).toString();
const dataStore = new GenericDataStore(listFiles, readFile);
const matcher = isEmpty(excludes)
? new AlwaysIncludeMatcher()
: await FileListBasedMatcher.createFromListFn(listFiles);
return { matcher, dataStore, excludePatterns };
};

View File

@@ -0,0 +1,85 @@
import { URI } from '../core/model/uri';
import { Logger } from '../core/utils/log';
import { Matcher, toMatcherPathFormat } from './test-datastore';
import { TEST_DATA_DIR } from './test-utils';
Logger.setLevel('error');
const testFolder = TEST_DATA_DIR.joinPath('test-datastore');
describe('Matcher', () => {
it('generates globs with the base dir provided', () => {
const matcher = new Matcher([testFolder], ['*'], []);
expect(matcher.folders).toEqual([toMatcherPathFormat(testFolder)]);
expect(matcher.include).toEqual([
toMatcherPathFormat(testFolder.joinPath('*')),
]);
});
it('defaults to including everything and excluding nothing', () => {
const matcher = new Matcher([testFolder]);
expect(matcher.exclude).toEqual([]);
expect(matcher.include).toEqual([
toMatcherPathFormat(testFolder.joinPath('**', '*')),
]);
});
it('supports multiple includes', () => {
const matcher = new Matcher([testFolder], ['g1', 'g2'], []);
expect(matcher.exclude).toEqual([]);
expect(matcher.include).toEqual([
toMatcherPathFormat(testFolder.joinPath('g1')),
toMatcherPathFormat(testFolder.joinPath('g2')),
]);
});
it('has a match method to filter strings', () => {
const matcher = new Matcher([testFolder], ['*.md'], []);
const files = [
testFolder.joinPath('file1.md'),
testFolder.joinPath('file2.md'),
testFolder.joinPath('file3.mdx'),
testFolder.joinPath('sub', 'file4.md'),
];
expect(matcher.match(files)).toEqual([
testFolder.joinPath('file1.md'),
testFolder.joinPath('file2.md'),
]);
});
it('has a isMatch method to see whether a file is matched or not', () => {
const matcher = new Matcher([testFolder], ['*.md'], []);
const files = [
testFolder.joinPath('file1.md'),
testFolder.joinPath('file2.md'),
testFolder.joinPath('file3.mdx'),
testFolder.joinPath('sub', 'file4.md'),
];
expect(matcher.isMatch(files[0])).toEqual(true);
expect(matcher.isMatch(files[1])).toEqual(true);
expect(matcher.isMatch(files[2])).toEqual(false);
expect(matcher.isMatch(files[3])).toEqual(false);
});
it('happy path', () => {
const matcher = new Matcher([URI.file('/root/')], ['**/*'], ['**/*.pdf']);
expect(matcher.isMatch(URI.file('/root/file.md'))).toBeTruthy();
expect(matcher.isMatch(URI.file('/root/file.pdf'))).toBeFalsy();
expect(matcher.isMatch(URI.file('/root/dir/file.md'))).toBeTruthy();
expect(matcher.isMatch(URI.file('/root/dir/file.pdf'))).toBeFalsy();
});
it('ignores files in the exclude list', () => {
const matcher = new Matcher([testFolder], ['*.md'], ['file1.*']);
const files = [
testFolder.joinPath('file1.md'),
testFolder.joinPath('file2.md'),
testFolder.joinPath('file3.mdx'),
testFolder.joinPath('sub', 'file4.md'),
];
expect(matcher.isMatch(files[0])).toEqual(false);
expect(matcher.isMatch(files[1])).toEqual(true);
expect(matcher.isMatch(files[2])).toEqual(false);
expect(matcher.isMatch(files[3])).toEqual(false);
});
});

View File

@@ -0,0 +1,96 @@
import micromatch from 'micromatch';
import { promisify } from 'util';
import { glob } from 'glob';
import { Logger } from '../core/utils/log';
import { IDataStore, IMatcher } from '../core/services/datastore';
import { URI } from '../core/model/uri';
import { isWindows } from '../core/common/platform';
import { asAbsolutePaths } from '../core/utils/path';
const findAllFiles = promisify(glob);
/**
* File system based data store
*/
export class FileDataStore implements IDataStore {
constructor(
private readFile: (uri: URI) => Promise<string>,
private readonly basedir: string
) {}
async list(): Promise<URI[]> {
const res = await findAllFiles([this.basedir, '**/*'].join('/'));
return res.map(URI.file);
}
async read(uri: URI) {
try {
return await this.readFile(uri);
} catch (e) {
Logger.error(
`FileDataStore: error while reading uri: ${uri.path} - ${e}`
);
return null;
}
}
}
/**
* The matcher requires the path to be in unix format, so if we are in windows
* we convert the fs path on the way in and out
*/
export const toMatcherPathFormat = isWindows
? (uri: URI) => uri.toFsPath().replace(/\\/g, '/')
: (uri: URI) => uri.toFsPath();
export const toFsPath = isWindows
? (path: string): string => path.replace(/\//g, '\\')
: (path: string): string => path;
export class Matcher implements IMatcher {
public readonly folders: string[];
public readonly include: string[] = [];
public readonly exclude: string[] = [];
constructor(
baseFolders: URI[],
includeGlobs: string[] = ['**/*'],
excludeGlobs: string[] = []
) {
this.folders = baseFolders.map(toMatcherPathFormat);
Logger.info('Workspace folders: ', this.folders);
this.include = includeGlobs.flatMap(glob =>
asAbsolutePaths(glob, this.folders)
);
this.exclude = excludeGlobs.flatMap(glob =>
asAbsolutePaths(glob, this.folders)
);
Logger.info('Glob patterns', {
includeGlobs: this.include,
ignoreGlobs: this.exclude,
});
}
match(files: URI[]) {
const matches = micromatch(
files.map(f => f.toFsPath()),
this.include,
{
ignore: this.exclude,
nocase: true,
format: toFsPath,
}
);
return matches.map(URI.file);
}
isMatch(uri: URI) {
return this.match([uri]).length > 0;
}
refresh(): Promise<void> {
return Promise.resolve();
}
}

View File

@@ -14,7 +14,7 @@ Logger.setLevel('error');
export const cleanWorkspace = async () => {
const files = await vscode.workspace.findFiles('**', '{.vscode,.keep}');
await Promise.all(files.map(f => vscode.workspace.fs.delete(f)));
await Promise.all(files.map(f => deleteFile(fromVsCodeUri(f))));
};
export const showInEditor = async (uri: URI) => {
@@ -28,9 +28,15 @@ export const closeEditors = async () => {
await wait(100);
};
export const deleteFile = (file: URI | { uri: URI }) => {
export const deleteFile = async (file: URI | { uri: URI }) => {
const uri = 'uri' in file ? file.uri : file;
return vscode.workspace.fs.delete(toVsCodeUri(uri), { recursive: true });
try {
await vscode.workspace.fs.delete(toVsCodeUri(uri), {
recursive: true,
});
} catch (e) {
// ignore
}
};
/**

View File

@@ -6,7 +6,6 @@ import { Logger } from '../core/utils/log';
import { Range } from '../core/model/range';
import { URI } from '../core/model/uri';
import { FoamWorkspace } from '../core/model/workspace';
import { Matcher } from '../core/services/datastore';
import { MarkdownResourceProvider } from '../core/services/markdown-provider';
import { NoteLinkDefinition, Resource } from '../core/model/note';
import { createMarkdownParser } from '../core/services/markdown-parser';
@@ -32,13 +31,11 @@ export const strToUri = URI.file;
export const createTestWorkspace = () => {
const workspace = new FoamWorkspace();
const matcher = new Matcher([URI.file('/')], ['**/*']);
const parser = createMarkdownParser();
const provider = new MarkdownResourceProvider(
matcher,
{
read: _ => Promise.resolve(''),
list: _ => Promise.resolve([]),
list: () => Promise.resolve([]),
},
parser
);

View File

@@ -8,11 +8,9 @@ import {
workspace,
Selection,
MarkdownString,
version,
ViewColumn,
} from 'vscode';
import matter from 'gray-matter';
import removeMarkdown from 'remove-markdown';
import { toVsCodeUri } from './utils/vsc-utils';
import { Logger } from './core/utils/log';
import { URI } from './core/model/uri';
@@ -179,14 +177,8 @@ export function getContainsTooltip(titles: string[]): string {
* @param note A Foam Note
*/
export function getNoteTooltip(content: string): string {
const STABLE_MARKDOWN_STRING_API_VERSION = '1.52.1';
const strippedContent = stripFrontMatter(stripImages(content));
if (version >= STABLE_MARKDOWN_STRING_API_VERSION) {
return formatMarkdownTooltip(strippedContent) as any;
}
return formatSimpleTooltip(strippedContent);
return formatMarkdownTooltip(strippedContent) as any;
}
export function formatMarkdownTooltip(content: string): MarkdownString {
@@ -200,16 +192,6 @@ export function formatMarkdownTooltip(content: string): MarkdownString {
return md;
}
export function formatSimpleTooltip(content: string): string {
const CHARACTERS_LIMIT = 200;
const flatContent = removeMarkdown(content)
.replace(/\r?\n|\r/g, ' ')
.replace(/\s+/g, ' ');
const extract = flatContent.substr(0, CHARACTERS_LIMIT);
const ellipsis = flatContent.length > CHARACTERS_LIMIT ? '...' : '';
return `${extract}${ellipsis}`;
}
export function getExcerpt(
markdown: string,
maxLines: number

View File

@@ -1,16 +1,19 @@
import { FoamWorkspace } from '../core/model/workspace';
import { OPEN_COMMAND } from '../features/commands/open-resource';
import {
GroupedResoucesConfigGroupBy,
GroupedResourcesConfig,
} from '../settings';
import { createTestNote, strToUri } from '../test/test-utils';
AlwaysIncludeMatcher,
SubstringExcludeMatcher,
} from '../core/services/datastore';
import { OPEN_COMMAND } from '../features/commands/open-resource';
import { GroupedResoucesConfigGroupBy } from '../settings';
import { createTestNote } from '../test/test-utils';
import {
DirectoryTreeItem,
GroupedResourcesTreeDataProvider,
UriTreeItem,
} from './grouped-resources-tree-data-provider';
const testMatcher = new SubstringExcludeMatcher('path-exclude');
describe('GroupedResourcesTreeDataProvider', () => {
const matchingNote1 = createTestNote({ uri: '/path/ABC.md', title: 'ABC' });
const matchingNote2 = createTestNote({
@@ -32,25 +35,19 @@ describe('GroupedResourcesTreeDataProvider', () => {
.set(excludedPathNote)
.set(notMatchingNote);
// Mock config
const config: GroupedResourcesConfig = {
exclude: ['path-exclude/**/*'],
groupBy: GroupedResoucesConfigGroupBy.Folder,
};
it('should return the grouped resources as a folder tree', async () => {
const provider = new GroupedResourcesTreeDataProvider(
'length3',
'note',
config,
[strToUri('')],
() =>
workspace
.list()
.filter(r => r.title.length === 3)
.map(r => r.uri),
uri => new UriTreeItem(uri)
uri => new UriTreeItem(uri),
testMatcher
);
provider.setGroupBy(GroupedResoucesConfigGroupBy.Folder);
const result = await provider.getChildren();
expect(result).toMatchObject([
{
@@ -72,15 +69,16 @@ describe('GroupedResourcesTreeDataProvider', () => {
const provider = new GroupedResourcesTreeDataProvider(
'length3',
'note',
config,
[strToUri('')],
() =>
workspace
.list()
.filter(r => r.title.length === 3)
.map(r => r.uri),
uri => new UriTreeItem(uri)
uri => new UriTreeItem(uri),
testMatcher
);
provider.setGroupBy(GroupedResoucesConfigGroupBy.Folder);
const directory = new DirectoryTreeItem(
'/path',
[new UriTreeItem(matchingNote1.uri)],
@@ -98,22 +96,19 @@ describe('GroupedResourcesTreeDataProvider', () => {
});
it('should return the flattened resources', async () => {
const mockConfig = {
...config,
groupBy: GroupedResoucesConfigGroupBy.Off,
};
const provider = new GroupedResourcesTreeDataProvider(
'length3',
'note',
mockConfig,
[strToUri('')],
() =>
workspace
.list()
.filter(r => r.title.length === 3)
.map(r => r.uri),
uri => new UriTreeItem(uri)
uri => new UriTreeItem(uri),
testMatcher
);
provider.setGroupBy(GroupedResoucesConfigGroupBy.Off);
const result = await provider.getChildren();
expect(result).toMatchObject([
{
@@ -132,19 +127,19 @@ describe('GroupedResourcesTreeDataProvider', () => {
});
it('should return the grouped resources without exclusion', async () => {
const mockConfig = { ...config, exclude: [] };
const provider = new GroupedResourcesTreeDataProvider(
'length3',
'note',
mockConfig,
[strToUri('')],
() =>
workspace
.list()
.filter(r => r.title.length === 3)
.map(r => r.uri),
uri => new UriTreeItem(uri)
uri => new UriTreeItem(uri),
new AlwaysIncludeMatcher()
);
provider.setGroupBy(GroupedResoucesConfigGroupBy.Folder);
const result = await provider.getChildren();
expect(result).toMatchObject([
expect.anything(),
@@ -163,15 +158,15 @@ describe('GroupedResourcesTreeDataProvider', () => {
const provider = new GroupedResourcesTreeDataProvider(
'length3',
description,
config,
[strToUri('')],
() =>
workspace
.list()
.filter(r => r.title.length === 3)
.map(r => r.uri),
uri => new UriTreeItem(uri)
uri => new UriTreeItem(uri),
testMatcher
);
provider.setGroupBy(GroupedResoucesConfigGroupBy.Folder);
const result = await provider.getChildren();
expect(result).toMatchObject([
{

View File

@@ -1,16 +1,13 @@
import * as path from 'path';
import * as vscode from 'vscode';
import micromatch from 'micromatch';
import {
GroupedResourcesConfig,
GroupedResoucesConfigGroupBy,
} from '../settings';
import { GroupedResoucesConfigGroupBy } from '../settings';
import { getContainsTooltip, getNoteTooltip, isSome } from '../utils';
import { OPEN_COMMAND } from '../features/commands/open-resource';
import { toVsCodeUri } from './vsc-utils';
import { URI } from '../core/model/uri';
import { Resource } from '../core/model/note';
import { FoamWorkspace } from '../core/model/workspace';
import { IMatcher } from '../core/services/datastore';
/**
* Provides the ability to expose a TreeDataExplorerView in VSCode. This class will
@@ -82,13 +79,10 @@ export class GroupedResourcesTreeDataProvider
constructor(
private providerId: string,
private resourceName: string,
config: GroupedResourcesConfig,
workspaceUris: URI[],
private computeResources: () => Array<URI>,
private createTreeItem: (item: URI) => GroupedResourceTreeItem
private createTreeItem: (item: URI) => GroupedResourceTreeItem,
private matcher: IMatcher
) {
this.groupBy = config.groupBy;
this.exclude = this.getGlobs(workspaceUris, config.exclude);
this.setContext();
this.doComputeResources();
}
@@ -106,6 +100,10 @@ export class GroupedResourcesTreeDataProvider
];
}
public get numElements() {
return this.flatUris.length;
}
setGroupBy(groupBy: GroupedResoucesConfigGroupBy): void {
this.groupBy = groupBy;
this.setContext();
@@ -163,30 +161,10 @@ export class GroupedResourcesTreeDataProvider
private doComputeResources(): void {
this.flatUris = this.computeResources()
.filter(uri => !this.isMatch(uri))
.filter(uri => this.matcher.isMatch(uri))
.filter(isSome);
}
private isMatch(uri: URI) {
return micromatch.isMatch(uri.toFsPath(), this.exclude);
}
private getGlobs(fsURI: URI[], globs: string[]): string[] {
globs = globs.map(glob => (glob.startsWith('/') ? glob.slice(1) : glob));
const exclude: string[] = [];
for (const fsPath of fsURI) {
let folder = fsPath.path.replace(/\\/g, '/');
if (folder.substr(-1) === '/') {
folder = folder.slice(0, -1);
}
exclude.push(...globs.map(g => `${folder}/${g}`));
}
return exclude;
}
private getUrisByDirectory(): UrisByDirectory {
const resourcesByDirectory: UrisByDirectory = {};
for (const uri of this.flatUris) {

View File

@@ -1,41 +0,0 @@
# Welcome to your VS Code Extension
## What's in the folder
* This folder contains all of the files necessary for your extension.
* `package.json` - this is the manifest file in which you declare your extension and command.
* The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesnt yet need to load the plugin.
* `src/extension.ts` - this is the main file where you will provide the implementation of your command.
* The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`.
* We pass the function containing the implementation of the command as the second parameter to `registerCommand`.
## Get up and running straight away
* Press `F5` to open a new window with your extension loaded.
* Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`.
* Set breakpoints in your code inside `src/extension.ts` to debug your extension.
* Find output from your extension in the debug console.
## Make changes
* You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`.
* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes.
## Explore the API
* You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`.
## Run tests
* Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`.
* Press `F5` to run the tests in a new window with your extension loaded.
* See the output of the test result in the debug console.
* Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder.
* The provided test runner will only consider files matching the name pattern `**.test.ts`.
* You can create folders inside the `test` folder to structure your tests any way you want.
## Go further
* Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension).
* [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace.
* Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration).

View File

@@ -5,7 +5,7 @@
👀*This is an early stage project under rapid development. For updates join the [Foam community Discord](https://foambubble.github.io/join-discord/g)! 💬*
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-103-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-108-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
[![Discord Chat](https://img.shields.io/discord/729975036148056075?color=748AD9&label=discord%20chat&style=flat-square)](https://foambubble.github.io/join-discord/g)
@@ -186,7 +186,7 @@ You can also browse the [docs folder](https://github.com/foambubble/foam/tree/ma
Foam is licensed under the [MIT license](LICENSE).
[//begin]: # "Autogenerated link references for markdown compatibility"
[Backlinking]: docs/features/backlinking.md "Backlinking"
[Backlinking]: docs/user/features/backlinking.md "Backlinking"
[//end]: # "Autogenerated link references"
## Contributors ✨
@@ -330,6 +330,13 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center"><a href="https://www.readingsnail.pe.kr"><img src="https://avatars.githubusercontent.com/u/1904967?v=4?s=60" width="60px;" alt="Woosuk Park"/><br /><sub><b>Woosuk Park</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=readingsnail" title="Documentation">📖</a></td>
<td align="center"><a href="http://www.dmurph.com"><img src="https://avatars.githubusercontent.com/u/294026?v=4?s=60" width="60px;" alt="Daniel Murphy"/><br /><sub><b>Daniel Murphy</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dmurph" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/Dominic-DallOsto"><img src="https://avatars.githubusercontent.com/u/26859884?v=4?s=60" width="60px;" alt="Dominic D"/><br /><sub><b>Dominic D</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Dominic-DallOsto" title="Code">💻</a></td>
<td align="center"><a href="http://elgirafo.xyz"><img src="https://avatars.githubusercontent.com/u/80516439?v=4?s=60" width="60px;" alt="luca"/><br /><sub><b>luca</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=elgirafo" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/Lloyd-Jackman-UKPL"><img src="https://avatars.githubusercontent.com/u/55206370?v=4?s=60" width="60px;" alt="Lloyd Jackman"/><br /><sub><b>Lloyd Jackman</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Lloyd-Jackman-UKPL" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center"><a href="http://sn3akiwhizper.github.io"><img src="https://avatars.githubusercontent.com/u/102705294?v=4?s=60" width="60px;" alt="sn3akiwhizper"/><br /><sub><b>sn3akiwhizper</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=sn3akiwhizper" title="Documentation">📖</a></td>
<td align="center"><a href="http://jonathanpberger.com/"><img src="https://avatars.githubusercontent.com/u/41085?v=4?s=60" width="60px;" alt="jonathan berger"/><br /><sub><b>jonathan berger</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jonathanpberger" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/badsketch"><img src="https://avatars.githubusercontent.com/u/8953212?v=4?s=60" width="60px;" alt="Daniel Wang"/><br /><sub><b>Daniel Wang</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=badsketch" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -2222,9 +2222,9 @@
"@babel/types" "^7.3.0"
"@types/braces@*":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/braces/-/braces-3.0.0.tgz#7da1c0d44ff1c7eb660a36ec078ea61ba7eb42cb"
integrity sha512-TbH79tcyi9FHwbyboOKeRachRq63mSuWYXOflsNO9ZyE5ClQ/JaozNKl+aWUq87qPNsXasXxi2AbgfwIJ+8GQw==
version "3.0.1"
resolved "https://registry.yarnpkg.com/@types/braces/-/braces-3.0.1.tgz#5a284d193cfc61abb2e5a50d36ebbc50d942a32b"
integrity sha512-+euflG6ygo4bn0JHtn4pYqcXwRtLvElQ7/nnjDu7iYG56H0+OhCd7d6Ug0IE3WcFpZozBKW2+80FUbv5QGk5AQ==
"@types/dateformat@^3.0.1":
version "3.0.1"
@@ -2340,9 +2340,9 @@
integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==
"@types/micromatch@^4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@types/micromatch/-/micromatch-4.0.1.tgz#9381449dd659fc3823fd2a4190ceacc985083bc7"
integrity sha512-my6fLBvpY70KattTNzYOK6KU1oR1+UCz9ug/JbcF5UrEmeCt9P7DV2t7L8+t18mMPINqGQCE4O8PLOPbI84gxw==
version "4.0.2"
resolved "https://registry.yarnpkg.com/@types/micromatch/-/micromatch-4.0.2.tgz#ce29c8b166a73bf980a5727b1e4a4d099965151d"
integrity sha512-oqXqVb0ci19GtH0vOA/U2TmHTcRY9kuZl4mqUxe0QmJAlIW13kzhuK5pi1i9+ngav8FjpSb9FVS/GE00GLX1VA==
dependencies:
"@types/braces" "*"
@@ -5087,11 +5087,6 @@ extsprintf@^1.2.0:
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
fast-array-diff@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/fast-array-diff/-/fast-array-diff-1.0.1.tgz#463bacfeddaa3f5d56b79f6847fe322f15581c92"
integrity sha512-pU83E/Y7+c/hRrDlmIiPYHy0Ugt+QypqzHKZI5qFOWMJAspWdmOyIeN/1FbdnGPlROp6FeGLLfhMO075DBqb4A==
fast-deep-equal@^3.1.1:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
@@ -9548,11 +9543,6 @@ remark-wiki-link@^0.0.4:
"@babel/runtime" "^7.4.4"
unist-util-map "^1.0.3"
remove-markdown@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/remove-markdown/-/remove-markdown-0.3.0.tgz#5e4b667493a93579728f3d52ecc1db9ca505dc98"
integrity sha1-XktmdJOpNXlyjz1S7MHbnKUF3Jg=
remove-trailing-separator@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
@@ -9575,11 +9565,6 @@ repeating@^2.0.0:
dependencies:
is-finite "^1.0.0"
replace-ext@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-2.0.0.tgz#9471c213d22e1bcc26717cd6e50881d88f812b06"
integrity sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==
request-promise-core@1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f"