mirror of
https://github.com/foambubble/foam.git
synced 2026-01-11 15:08:01 -05:00
Compare commits
29 Commits
cli/basic-
...
v0.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5418ea8bb2 | ||
|
|
50dd3685cd | ||
|
|
0ae0ab6c50 | ||
|
|
e1f5fb06c6 | ||
|
|
c04b342a76 | ||
|
|
0fc9397ed8 | ||
|
|
11fa878ed8 | ||
|
|
2e3f02c5bc | ||
|
|
7c6874d528 | ||
|
|
fe9c6461c4 | ||
|
|
1047443677 | ||
|
|
0cf2295c2a | ||
|
|
9e7c3ffed8 | ||
|
|
c69a2bb2b1 | ||
|
|
a3ab4f53a7 | ||
|
|
524ab15b12 | ||
|
|
fd9fe12571 | ||
|
|
8d41d02ef4 | ||
|
|
8b578c1a1c | ||
|
|
1e757aa7c4 | ||
|
|
c88c24f97e | ||
|
|
0c95ea7ca1 | ||
|
|
00d1bd3a23 | ||
|
|
a2e511ec6f | ||
|
|
639a1ea21c | ||
|
|
271017c663 | ||
|
|
c7277383dd | ||
|
|
339274309f | ||
|
|
dc1c237c05 |
@@ -241,6 +241,44 @@
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "chirag-singhal",
|
||||
"name": "CHIRAG SINGHAL",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/42653703?v=4",
|
||||
"profile": "https://github.com/chirag-singhal",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "lostintangent",
|
||||
"name": "Jonathan Carter",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/116461?v=4",
|
||||
"profile": "https://github.com/lostintangent",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "synesthesia",
|
||||
"name": "Julian Elve",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/181399?v=4",
|
||||
"profile": "https://www.synesthesia.co.uk",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "thomaskoppelaar",
|
||||
"name": "Thomas Koppelaar",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/36331365?v=4",
|
||||
"profile": "https://github.com/thomaskoppelaar",
|
||||
"contributions": [
|
||||
"question",
|
||||
"code",
|
||||
"userTesting"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
25
.github/workflows/foam-cli.yml
vendored
Normal file
25
.github/workflows/foam-cli.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Test foam-cli
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'packages/foam-cli/**'
|
||||
push:
|
||||
paths:
|
||||
- 'packages/foam-cli/**'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/setup-node@v1
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
# - name: Lint foam-lint
|
||||
# run: yarn workspace foam-cli lint
|
||||
|
||||
- name: Test foam-cli
|
||||
run: yarn workspace foam-cli test
|
||||
25
.github/workflows/foam-core.yml
vendored
Normal file
25
.github/workflows/foam-core.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Test foam-core
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'packages/foam-core/**'
|
||||
push:
|
||||
paths:
|
||||
- 'packages/foam-core/**'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/setup-node@v1
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
- name: Lint foam-core
|
||||
run: yarn workspace foam-core lint
|
||||
|
||||
- name: Test foam-core
|
||||
run: yarn workspace foam-core test
|
||||
29
.github/workflows/foam-vscode.yml
vendored
Normal file
29
.github/workflows/foam-vscode.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: Test foam-vscode
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'packages/foam-vscode/**'
|
||||
push:
|
||||
paths:
|
||||
- 'packages/foam-vscode/**'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/setup-node@v1
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
- name: Lint foam-vscode
|
||||
run: yarn workspace foam-vscode lint
|
||||
# - name: Test foam-vscode
|
||||
# run: yarn workspace foam-vscode test
|
||||
# - name: Publish foam-vscode
|
||||
# if: github.ref == 'refs/heads/master'
|
||||
# run: yarn workspace foam-vscode publish-extension
|
||||
# with:
|
||||
# vsce_token: ${{ secrets.VSCE_TOKEN }}
|
||||
35
docs/_layouts/foam.html
Normal file
35
docs/_layouts/foam.html
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
<script type="text/javascript">
|
||||
// NOTE: this should be in sync with the settings/usage in the vscode extension
|
||||
// atm it's just a wide superset of md extensions to cover a wide range of cases
|
||||
var MD_EXT = ['.md', '.markdown', '.mdx', '.mdown', '.mkdn', '.mkd', '.mdwn', '.mdtxt', '.mdtext', '.text', '.Rmd'];
|
||||
function normalizeMdLink(link) {
|
||||
var url = new URL(link);
|
||||
var mdFileExt = MD_EXT.find(ext => url.pathname.endsWith(ext));
|
||||
if (mdFileExt) {
|
||||
url.pathname = url.pathname.slice(0, mdFileExt.length * -1);
|
||||
}
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', (event) => {
|
||||
document
|
||||
.querySelectorAll(".markdown-body a[title]:not([href^=http])")
|
||||
.forEach((a) => {
|
||||
// Hack: Replace page-link with "Page Title"...
|
||||
a.innerText = a.title;
|
||||
// ...and normalize the links to allow html pages navigation
|
||||
a.href = normalizeMdLink(a.href);
|
||||
});
|
||||
|
||||
document.querySelectorAll(".github-only").forEach((el) => {
|
||||
el.remove();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
{{ content }}
|
||||
@@ -1,25 +1,16 @@
|
||||
---
|
||||
layout: default
|
||||
layout: foam
|
||||
---
|
||||
|
||||
<script async defer src="https://buttons.github.io/buttons.js"></script>
|
||||
|
||||
{{ content }}
|
||||
|
||||
<script type="text/javascript">
|
||||
// Hack: Replace page-link with "Page Title"
|
||||
document
|
||||
.querySelectorAll(".markdown-body a[title]:not([href^=http])")
|
||||
.forEach((a) => {
|
||||
a.innerText = a.title;
|
||||
});
|
||||
|
||||
document.querySelectorAll(".github-only").forEach((el) => {
|
||||
el.remove();
|
||||
});
|
||||
|
||||
<script>
|
||||
window.addEventListener('DOMContentLoaded', (event) => {
|
||||
var duplicateHeading = document.querySelector("h1:not(#foam)");
|
||||
if (duplicateHeading && duplicateHeading.remove) {
|
||||
duplicateHeading.remove();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,22 +1,9 @@
|
||||
---
|
||||
layout: default
|
||||
layout: foam
|
||||
---
|
||||
|
||||
{{ content }}
|
||||
|
||||
<script type="text/javascript">
|
||||
// Hack: Replace page-link with "Page Title"
|
||||
document
|
||||
.querySelectorAll(".markdown-body a[title]:not([href^=http])")
|
||||
.forEach((a) => {
|
||||
a.innerText = a.title;
|
||||
});
|
||||
|
||||
document.querySelectorAll(".github-only").forEach((el) => {
|
||||
el.remove();
|
||||
});
|
||||
</script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/mathjax@2/MathJax.js?config=TeX-AMS-MML_HTMLorMML" type="text/javascript"></script>
|
||||
<script type="text/x-mathjax-config">
|
||||
MathJax.Hub.Config({
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
---
|
||||
layout: default
|
||||
layout: foam
|
||||
---
|
||||
|
||||
{{ content }}
|
||||
|
||||
<script type="text/javascript">
|
||||
// Hack: Replace page-link with "Page Title"
|
||||
document
|
||||
.querySelectorAll(".markdown-body a[title]:not([href^=http])")
|
||||
.forEach((a) => {
|
||||
a.innerText = a.title;
|
||||
});
|
||||
|
||||
document.querySelectorAll(".github-only").forEach((el) => {
|
||||
el.remove();
|
||||
});
|
||||
</script>
|
||||
|
||||
BIN
docs/assets/images/azure-devops-wiki-demo.png
Normal file
BIN
docs/assets/images/azure-devops-wiki-demo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 82 KiB |
BIN
docs/assets/images/daily-note.gif
Normal file
BIN
docs/assets/images/daily-note.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 141 KiB |
BIN
docs/assets/images/foam-janitor-demo.gif
Normal file
BIN
docs/assets/images/foam-janitor-demo.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
34
docs/azure-devops-wiki.md
Normal file
34
docs/azure-devops-wiki.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Azure DevOps Wiki
|
||||
|
||||
Publish your Foam workspace as an Azure DevOps wiki.
|
||||
|
||||
[Azure DevOps](https://azure.microsoft.com/en-us/services/devops/) is Microsoft's collaboration software for software development teams, formerly known as Team Foundation Server (TFS) and Visual Studio Team Services. It is available as an on-premise or SaaS version. The following recipe was tested with the SaaS version, but should work the same way for the on-premise.
|
||||
|
||||
The following recipe is written with the assumption that you already have an [Azure DevOps](https://azure.microsoft.com/en-us/services/devops/) project.
|
||||
|
||||
## Setup a Foam workspace
|
||||
|
||||
1. Generate a Foam workspace using the [foam-template project](https://github.com/foambubble/foam-template).
|
||||
2. Change the remote to a git repository in Azure DevOps, or copy all the files into a new Azure DevOps git repository.
|
||||
3. Define which document will be the wiki home page. To do that, create a file called `.order` in the Foam workspace root folder, with first line being the document filename without `.md` extension. For a project created from the Foam template, the file would look like this:
|
||||
```
|
||||
readme
|
||||
```
|
||||
4. Push the repository to remote in Azure DevOps.
|
||||
|
||||
## Publish repository to a wiki
|
||||
|
||||
|
||||
1. Navigate to your Azure DevOps project in a web browser.
|
||||
2. Choose **Overview** > **Wiki**. If you don't have wikis for your project, choose **Publish code as a wiki** on welcome page.
|
||||
3. Choose repository with your Foam workspace, branch (usually `master` or `main`), folder (for workspace created from foam-template it is `/`), and wiki name, and press **Publish**.
|
||||
|
||||
A published workspace looks like this:
|
||||
|
||||

|
||||
|
||||
There is default table of contents pane to the left of the wiki content. Here, you'll find a list of all directories that are present in your Foam workspace, and all wiki pages. Page names are derived from files names, and they are listed in alphabetical order. You may reorder pages by adding filenames without `.md` extension to `.order` file.
|
||||
|
||||
_Note that first entry in `.order` file defines wiki's home page._
|
||||
|
||||
For more information, read the [Azure DevOps documentation](https://docs.microsoft.com/en-us/azure/devops/project/wiki/publish-repo-to-wiki).
|
||||
142
docs/capture-notes-with-drafts-pro.md
Normal file
142
docs/capture-notes-with-drafts-pro.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# Capture Notes With Drafts Pro
|
||||
|
||||
## Context
|
||||
|
||||
* You use [Foam for VSCode](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode) to manage your notes
|
||||
* You wish to adopt a practice such as [A writing inbox for transient and incomplete notes](https://notes.andymatuschak.org/A%20writing%20inbox%20for%20transient%20and%20incomplete%20notes)
|
||||
* You wish to use [Drafts Pro](https://docs.getdrafts.com/) to capture quick notes into your Foam notes from your iOS device
|
||||
|
||||
## Required Extensions
|
||||
|
||||
* [Foam for VSCode](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode)
|
||||
|
||||
## Other tools
|
||||
|
||||
* We assume you are familiar with how to use GitHub (if you are using Foam this is implicit)
|
||||
* You have an iOS device with [Drafts](https://getdrafts.com/)
|
||||
* You have upgraded to [Drafts Pro](https://docs.getdrafts.com/draftspro) (needed to edit actions).
|
||||
|
||||
## Instructions
|
||||
|
||||
1. [Create a new action in Drafts](https://docs.getdrafts.com/docs/actions/editing-actions)
|
||||
2. Add a single [step](https://docs.getdrafts.com/actions/steps/) of type Script
|
||||
3. Edit the script adding the code from the block below
|
||||
4. Edit settings at the top of the script to suit your preferences
|
||||
5. Set other Action options in Drafts as you wish
|
||||
6. Save the Action
|
||||
7. In GitHub [create a Personal Access Token](https://github.com/settings/tokens) and give it `repo` scope - make a note of the token
|
||||
8. In Drafts create a note
|
||||
9. Select the action you created in steps 1-6
|
||||
10. On the first run you will need to add the following information:
|
||||
1. your GitHub username
|
||||
2. the repository name of your Foam repo
|
||||
3. the GitHub access token from step 7
|
||||
4. An author name
|
||||
11. Check your Github repo for a commit
|
||||
12. If you are publishing your Foam to the web you may want to edit your publishing configuration to exclude inbox files - as publishing (and method) is a user choice that is beyond the scope of this recipe
|
||||
|
||||
## Code for Drafts Action
|
||||
|
||||
```javascript
|
||||
// adapted from https://forums.getdrafts.com/t/script-step-post-to-github-without-working-copy/3594
|
||||
// post to writing inbox in Foam digital garden
|
||||
|
||||
/*
|
||||
* edit these lines to suit your preferences
|
||||
*/
|
||||
const inboxFolder = "inbox/"; // the folder in your Foam repo where notes are saved. MUST have trailing slash, except for root of repo use ''
|
||||
const requiredTags = ['inbox']; // all documents will have these added in addition to tags from the Drafts app
|
||||
const addLinkToInbox = true; // true = created note will have link to [[index]], false = no link
|
||||
const addTimeStamp = true; // true = add a note of capture date/time at foot of note
|
||||
|
||||
/*
|
||||
* stop editing
|
||||
*/
|
||||
|
||||
const credential = Credential.create("GitHub garden repo", "The repo name, and its credentials, hosting your Foam notes");
|
||||
credential.addTextField("username", "GitHub Username");
|
||||
credential.addTextField('repo', 'Repo name');
|
||||
credential.addPasswordField("key", "GitHub personal access token");
|
||||
credential.addTextField('author', 'Author');
|
||||
credential.authorize();
|
||||
|
||||
const githubKey = credential.getValue('key');
|
||||
const githubUser = credential.getValue('username');
|
||||
const repo = credential.getValue('repo');
|
||||
const author = credential.getValue('author');
|
||||
|
||||
const http = HTTP.create(); // create HTTP object
|
||||
const base = 'https://api.github.com';
|
||||
|
||||
|
||||
const posttime = new Date();
|
||||
const title = draft.title;
|
||||
const txt = draft.processTemplate("[[line|3..]]");
|
||||
const mergedTags = [...draft.tags, ...requiredTags];
|
||||
const slugbase = title.toLowerCase().replace(/\s/g, "-");
|
||||
|
||||
const datestr = `${posttime.getFullYear()}-${pad(posttime.getMonth() + 1)}-${pad(posttime.getDate())}`;
|
||||
const timestr = `${pad(posttime.getHours())}:${pad(posttime.getMinutes())}:00`;
|
||||
const yr = `${posttime.getFullYear()}`;
|
||||
const pdOffset = posttime.getTimezoneOffset();
|
||||
const offsetChar = pdOffset >= 0 ? '-' : '+';
|
||||
var pdHours = Math.floor(pdOffset/60);
|
||||
console.log(pdHours);
|
||||
pdHours = pdHours >= 0 ? pdHours : pdHours * -1;
|
||||
console.log(pdHours);
|
||||
const tzString = `${offsetChar}${pad(pdHours)}:00`;
|
||||
const postdate = `${datestr}T${timestr}${tzString}`;
|
||||
|
||||
|
||||
const slug = `${slugbase}`
|
||||
const fn = `${slug}.md`;
|
||||
let preamble = `# ${title} \n\n`;
|
||||
|
||||
mergedTags.forEach(function(item,index){
|
||||
preamble += `#${item} `;
|
||||
}
|
||||
);
|
||||
|
||||
if (addLinkToInbox) {
|
||||
preamble += "\n\n[[inbox]]\n";
|
||||
}
|
||||
|
||||
preamble += "\n\n";
|
||||
|
||||
var doc = `${preamble}${txt}`;
|
||||
|
||||
if (addTimeStamp){
|
||||
|
||||
doc += `\n\nCaptured: ${postdate}\n`
|
||||
}
|
||||
|
||||
const options = {
|
||||
url: `https://api.github.com/repos/${githubUser}/${repo}/contents/${inboxFolder}${fn}`,
|
||||
method: 'PUT',
|
||||
data: {
|
||||
message: `Inbox from Drafts ${datestr}`,
|
||||
content: Base64.encode(doc)
|
||||
},
|
||||
headers: {
|
||||
'Authorization': `token ${githubKey}`
|
||||
}
|
||||
};
|
||||
|
||||
var response = http.request(options);
|
||||
|
||||
if (response.success) {
|
||||
// yay
|
||||
} else {
|
||||
console.log(response.statusCode);
|
||||
console.log(response.error);
|
||||
}
|
||||
|
||||
function pad(n) {
|
||||
let str = String(n);
|
||||
while (str.length < 2) {
|
||||
str = `0${str}`;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
```
|
||||
@@ -1,36 +1,46 @@
|
||||
# Daily notes
|
||||
|
||||
The idea is to make it easier for people to be able to create Daily notes.
|
||||
Automatically create a Daily Note by executing the "Foam: Open Daily Note" command. If a Daily Note for today's date already exists, the command opens the existing note.
|
||||
|
||||
This feature is open for discussion at [foambubble/foam#54](https://github.com/foambubble/foam/issues/54).
|
||||

|
||||
|
||||
## Must have
|
||||
## Keyboard shortcut
|
||||
|
||||
- Should exist as part of [foam-vscode](https://github.com/foambubble/foam/tree/master/packages/foam-vscode) extension
|
||||
- A new command "Foam: Open Daily Note", which creates new file and focuses into it, or opens the existing file for today if it exists already.
|
||||
- File should have a Level 1 `# Heading`
|
||||
- Extension should define a hotkey for it (TBD? What do other apps do?)
|
||||
The default keyboard shortcut for "Open Daily Note" is `alt`+`d`. This can be overridden using the [VS Code Keybindings editor](https://code.visualstudio.com/docs/getstarted/keybindings).
|
||||
|
||||
## Should have
|
||||
## Configuration
|
||||
|
||||
- Settings to customise:
|
||||
- Format of file name, default to `yyyy-mm-dd`
|
||||
- Format of title
|
||||
- Default to same as file name
|
||||
- Allow override e.g. `MMMMM D, YYYY`, to July 8. 2020 (locale specific)
|
||||
- Extension of file, default to `.md`
|
||||
- Directory into which file should be created, default to `./` (workspace root)
|
||||
- Should create directory if needed
|
||||
By default, Daily Notes will be created in a file called `yyyy-mm-dd.md` in the workspace root, with a heading `yyyy-mm-dd`.
|
||||
|
||||
## Could have
|
||||
- Automatically update a "daily notes" index file, newest first. Format TBD
|
||||
- A custom note template .md file that could be stored in `.vscode/` directory (would supercede having to format the title)
|
||||
- This could be useful for people who e.g. want there to be a format for every day's notes with fixed questions and stuff.
|
||||
- Maybe we could do it with just back links or tags, by putting e.g. `[[daily]]` into the note template
|
||||
- If files were named in alphabetic sortable order, and back links would display in reverse order, newest would always come on top
|
||||
These settings can be overridden in your workspace or global `.vscode/settings.json` file, using the [**dateformat** date masking syntax](https://github.com/felixge/node-dateformat#mask-options):
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[todo]: todo "Todo"
|
||||
[roadmap]: roadmap "Roadmap"
|
||||
[//end]: # "Autogenerated link references"
|
||||
```json
|
||||
"foam.openDailyNote.directory": "journal",
|
||||
"foam.openDailyNote.filenameFormat": "'daily-note'-yyyy-mm-dd",
|
||||
"foam.openDailyNote.fileExtension": "mdx",
|
||||
"foam.openDailyNote.titleFormat": "'Journal Entry, ' dddd, mmmm d",
|
||||
```
|
||||
|
||||
The above configuration would create a file `journal/note-2020-07-25.mdx`, with the heading `Journal Entry, Sunday, July 25`.
|
||||
|
||||
## Daily Note Templates
|
||||
|
||||
In the future, Foam may provide a functionality for specifying a template for new Daily Notes and other types of documents.
|
||||
|
||||
In the meantime, you can use [VS Code Snippets](https://code.visualstudio.com/docs/editor/userdefinedsnippets) for defining your own Daily Note template.
|
||||
|
||||
## Roam-style Automatic Daily Notes
|
||||
|
||||
In the future, Foam may provide an option for automatically opening your Daily Note when you open your Foam workspace.
|
||||
|
||||
If you want this behaviour now, you can use the excellent [Auto Run Command](https://marketplace.visualstudio.com/items?itemName=gabrielgrinberg.auto-run-command#review-details) extension to run the "Open Daily Note" command upon entering a Foam workspace by specifying the following configuration in your `.vscode/settings.json`:
|
||||
|
||||
```json
|
||||
"auto-run-command.rules": [
|
||||
{
|
||||
"condition": "hasFile: .vscode/foam.json",
|
||||
"command": "foam-vscode.open-daily-note",
|
||||
"message": "Have a nice day!"
|
||||
}
|
||||
],
|
||||
```
|
||||
|
||||
@@ -10,7 +10,7 @@ With this template you can
|
||||
|
||||
When you're ready to publish, import the GitHub repository you created with **foam-eleventy-template** into your Netlify account. (Create one if you don't have it already.)
|
||||
|
||||
Once that's done, all you have to do is make changes to your workspace in VS COde and push them to the main branch on GitHub. Netlify will recognize the changes, deploy them automatically and give you a link where your Foam is published.
|
||||
Once that's done, all you have to do is make changes to your workspace in VS Code and push them to the main branch on GitHub. Netlify will recognize the changes, deploy them automatically and give you a link where your Foam is published.
|
||||
|
||||
|
||||
That's it!
|
||||
|
||||
51
docs/gistpad.md
Normal file
51
docs/gistpad.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# GistPad
|
||||
|
||||
[GistPad](https://aka.ms/gistpad) is a Visual Studio Code extension that allows you to edit your GitHub gists and repos, without needing to clone anything locally. It provides support for editing Foam workspaces, complete with `[[link]]` [completion/navigation](https://github.com/vsls-contrib/gistpad#links), [daily pages](https://github.com/vsls-contrib/gistpad#daily-pages), [pasting images](https://github.com/vsls-contrib/gistpad#pasting-images-1) and [backlinks](https://github.com/vsls-contrib/gistpad#backlinks). If you'd like to persist your notes in a GitHub repository, and automatically sync changes without needing to manually commit/push/pull, then GistPad might be an option worth exploring.
|
||||
|
||||
<img width="700px" src="https://user-images.githubusercontent.com/116461/87234714-96ba9400-c388-11ea-92c3-544d9a3bb633.png" />
|
||||
|
||||
## Getting started
|
||||
|
||||
To start using GistPad for your Foam-based knowledge base, simply perform the following steps:
|
||||
|
||||
1. Download the [GistPad extension](https://aka.ms/gistpad) and then re-start Visual Studio Code
|
||||
|
||||
1. Run the `GistPad: Sign In` command, and provide a [GitHub token](https://github.com/settings/tokens/new) that includes the `repo` scope (and optionally `gist` and `delete_repo` scope, if you'd like to use GistPad for managing your GitHub content more holistically)
|
||||
|
||||
1. Run the `GistPad: Manage Repository` command and select the `Create repo from template...` or `Create private repo from template...` depending on your preference
|
||||
|
||||
1. Select the `Foam-style wiki` template, and then specify a name for your Foam workspace (e.g. `my-foam-notes`, `johns-knowledge-base`)
|
||||
|
||||
Your new repo will be created in your GitHub account, and the `Foam` welcome page will be automatically opened. If you already have an existing Foam workspace in GitHub, then when you run step #3 above, simply select or specify the name of the GitHub repository instead.
|
||||
|
||||
> Note: If you have any and all feedback on how GistPad can be improved to support your Foam workflow, please don't hesitate to [let us know](https://github.com/vsls-contrib/gistpad)! 👍
|
||||
|
||||
<img width="700px" src="https://user-images.githubusercontent.com/116461/87863222-c1b76180-c90d-11ea-87d9-04bee1c58a0d.png" />
|
||||
|
||||
## Managing your workspace
|
||||
|
||||
Once you've opened/created the Foam repository, it will appear in the `Repositories` view of the `GistPad` tab (the one with the little notebook icon). From this tree view, you can add/edit/delete/rename new pages, upload local files, as well as view the backlinks for each page (they appear as child notes of a page).
|
||||
|
||||
<img width="250px" src="https://user-images.githubusercontent.com/116461/87234704-83a7c400-c388-11ea-90a8-2a660bef4dc5.png" />
|
||||
|
||||
## Editing your workspace
|
||||
|
||||
When you create or open a page, you can edit the markdown content as usual, as well as [paste images](https://github.com/vsls-contrib/gistpad#pasting-images-1), and create [`[[links]]` to other pages](https://github.com/vsls-contrib/gistpad#links). When you type `[[`, you'll recieve auto-completion for the existing pages in your workspace, and you can also automatically create new pages by simply creating a link to it.
|
||||
|
||||
Since you're using the Visual Studio Code markdown editor, you can benefit from all of the rich language services (e.g. syntax highlighting, header collapsing), as well as the extension ecosystem (e.g. [Emojisense](https://marketplace.visualstudio.com/items?itemName=bierner.emojisense)).
|
||||
|
||||
## Navigating your workspace
|
||||
|
||||
When editing a file, you can easily navigate `[[links]]` by hovering over them to see a preview of their contents and/or `cmd+clicking` on them in order to jump to the respective page. Furthermore, when you add a link to a page, a [backlink](https://github.com/vsls-contrib/gistpad#backlinks) is automatically added to it.
|
||||
|
||||
You can view a page's backlinks using either of the following techniques:
|
||||
|
||||
1. Expanding the file's node in the `Repositories` tree, since it's child nodes will represent backlinks. This makes it easy to browse your pages and their backlinks in a single hierachical view.
|
||||
|
||||
1. Opening a file, and then viewing it's backlinks list at the bottom of the editor view. This makes it easy to read a page and then see its backlinks in a contextually rich way.
|
||||
|
||||
## Daily Pages
|
||||
|
||||
In addition to create arbitrary pages, you can also use GistPad for journaling or capturing [daily notes](https://github.com/vsls-contrib/gistpad#daily-pages). Simply click the calendar icon in the `Repositories` tree, which will open up the page that represents today. If the page doesn't already exist, then it will be created in the workspace before being opened.
|
||||
|
||||
<img width="700px" src="https://user-images.githubusercontent.com/116461/87234721-b356cc00-c388-11ea-946a-e7f9c92258a6.png" />
|
||||
@@ -1,5 +1,5 @@
|
||||
# Images from your Clipboard
|
||||
|
||||
You can directly link and paste images that are copied to the clipboard using the [Paste
|
||||
You can directly link and paste images that are copied to the clipboard using either the [Paste
|
||||
Image](https://marketplace.visualstudio.com/items?itemName=mushan.vscode-paste-image)
|
||||
extension.
|
||||
extension, or the [Markdown Image](https://marketplace.visualstudio.com/items?itemName=hancel.markdown-image) extension. The former does not have MDX support (yet), the latter does.
|
||||
|
||||
@@ -11,6 +11,10 @@ Uncategorised thoughts, to be added
|
||||
- [Unotes](https://marketplace.visualstudio.com/items?itemName=ryanmcalister.Unotes)
|
||||
- [vscode-memo](https://github.com/svsool/vscode-memo)
|
||||
- [gistpad wiki](https://github.com/jevakallio/gistpad/tree/master/src/repos/wiki)
|
||||
- Open in Foam
|
||||
- When you want to open a Foam published website in your own VS Code, we could have a "Open in Foam" link that opens the link in VS Code via a url binding (if possible), downloads the github repo locally, and opens it as a Foam workspace.
|
||||
- Every Foam could have a different theme even in the editor, so you'll see it like they see it
|
||||
- UI and layout design of your workspace can become a thing
|
||||
- Developer documentation
|
||||
- GistPad has a good vs code contrib primer: https://github.com/jevakallio/gistpad/blob/master/CONTRIBUTING.md
|
||||
- VS Code Notebooks API
|
||||
|
||||
@@ -108,37 +108,43 @@ If that sounds like something you're interested in, I'd love to have you along o
|
||||
<!-- markdownlint-disable -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><a href="https://jevakallio.dev/"><img src="https://avatars1.githubusercontent.com/u/1203949?v=4" width="60px;" alt=""/><br /><sub><b>Jani Eväkallio</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jevakallio" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jevakallio" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://joeprevite.com/"><img src="https://avatars3.githubusercontent.com/u/3806031?v=4" width="60px;" alt=""/><br /><sub><b>Joe Previte</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jsjoeio" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jsjoeio" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/riccardoferretti"><img src="https://avatars3.githubusercontent.com/u/457005?v=4" width="60px;" alt=""/><br /><sub><b>Riccardo</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=riccardoferretti" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=riccardoferretti" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://ojanaho.com/"><img src="https://avatars0.githubusercontent.com/u/2180090?v=4" width="60px;" alt=""/><br /><sub><b>Janne Ojanaho</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jojanaho" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jojanaho" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://bypaulshen.com/"><img src="https://avatars3.githubusercontent.com/u/2266187?v=4" width="60px;" alt=""/><br /><sub><b>Paul Shen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=paulshen" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/coffenbacher"><img src="https://avatars0.githubusercontent.com/u/245867?v=4" width="60px;" alt=""/><br /><sub><b>coffenbacher</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=coffenbacher" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://mathieu.dutour.me/"><img src="https://avatars2.githubusercontent.com/u/3254314?v=4" width="60px;" alt=""/><br /><sub><b>Mathieu Dutour</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=mathieudutour" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://jevakallio.dev/"><img src="https://avatars1.githubusercontent.com/u/1203949?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Jani Eväkallio</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jevakallio" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jevakallio" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://joeprevite.com/"><img src="https://avatars3.githubusercontent.com/u/3806031?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Joe Previte</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jsjoeio" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jsjoeio" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/riccardoferretti"><img src="https://avatars3.githubusercontent.com/u/457005?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Riccardo</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=riccardoferretti" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=riccardoferretti" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://ojanaho.com/"><img src="https://avatars0.githubusercontent.com/u/2180090?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Janne Ojanaho</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jojanaho" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jojanaho" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://bypaulshen.com/"><img src="https://avatars3.githubusercontent.com/u/2266187?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Paul Shen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=paulshen" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/coffenbacher"><img src="https://avatars0.githubusercontent.com/u/245867?v=4?s=60" width="60px;" alt=""/><br /><sub><b>coffenbacher</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=coffenbacher" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://mathieu.dutour.me/"><img src="https://avatars2.githubusercontent.com/u/3254314?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Mathieu Dutour</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=mathieudutour" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/presidentelect"><img src="https://avatars2.githubusercontent.com/u/1242300?v=4" width="60px;" alt=""/><br /><sub><b>Michael Hansen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=presidentelect" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://klickverbot.at/"><img src="https://avatars1.githubusercontent.com/u/19335?v=4" width="60px;" alt=""/><br /><sub><b>David Nadlinger</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dnadlinger" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://pluckd.co/"><img src="https://avatars2.githubusercontent.com/u/20598571?v=4" width="60px;" alt=""/><br /><sub><b>Fernando</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MrCordeiro" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/jfgonzalez7"><img src="https://avatars3.githubusercontent.com/u/58857736?v=4" width="60px;" alt=""/><br /><sub><b>Juan Gonzalez</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jfgonzalez7" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.louiechristie.com/"><img src="https://avatars1.githubusercontent.com/u/6807448?v=4" width="60px;" alt=""/><br /><sub><b>Louie Christie</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=louiechristie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://supersandro.de/"><img src="https://avatars2.githubusercontent.com/u/7258858?v=4" width="60px;" alt=""/><br /><sub><b>Sandro</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=SuperSandro2000" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Skn0tt"><img src="https://avatars1.githubusercontent.com/u/14912729?v=4" width="60px;" alt=""/><br /><sub><b>Simon Knott</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Skn0tt" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/presidentelect"><img src="https://avatars2.githubusercontent.com/u/1242300?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Michael Hansen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=presidentelect" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://klickverbot.at/"><img src="https://avatars1.githubusercontent.com/u/19335?v=4?s=60" width="60px;" alt=""/><br /><sub><b>David Nadlinger</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dnadlinger" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://pluckd.co/"><img src="https://avatars2.githubusercontent.com/u/20598571?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Fernando</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MrCordeiro" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/jfgonzalez7"><img src="https://avatars3.githubusercontent.com/u/58857736?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Juan Gonzalez</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jfgonzalez7" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.louiechristie.com/"><img src="https://avatars1.githubusercontent.com/u/6807448?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Louie Christie</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=louiechristie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://supersandro.de/"><img src="https://avatars2.githubusercontent.com/u/7258858?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Sandro</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=SuperSandro2000" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Skn0tt"><img src="https://avatars1.githubusercontent.com/u/14912729?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Simon Knott</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Skn0tt" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://styfle.dev/"><img src="https://avatars1.githubusercontent.com/u/229881?v=4" width="60px;" alt=""/><br /><sub><b>Steven</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=styfle" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Georift"><img src="https://avatars2.githubusercontent.com/u/859430?v=4" width="60px;" alt=""/><br /><sub><b>Tim</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Georift" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/sauravkhdoolia"><img src="https://avatars1.githubusercontent.com/u/34188267?v=4" width="60px;" alt=""/><br /><sub><b>Saurav Khdoolia</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=sauravkhdoolia" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://anku.netlify.com/"><img src="https://avatars1.githubusercontent.com/u/22813027?v=4" width="60px;" alt=""/><br /><sub><b>Ankit Tiwari</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=anku255" title="Documentation">📖</a> <a href="https://github.com/foambubble/foam/commits?author=anku255" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/ayushbaweja"><img src="https://avatars1.githubusercontent.com/u/44344063?v=4" width="60px;" alt=""/><br /><sub><b>Ayush Baweja</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ayushbaweja" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/TaiChi-IO"><img src="https://avatars3.githubusercontent.com/u/65092992?v=4" width="60px;" alt=""/><br /><sub><b>TaiChi-IO</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=TaiChi-IO" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/juanfrank77"><img src="https://avatars1.githubusercontent.com/u/12146882?v=4" width="60px;" alt=""/><br /><sub><b>Juan F Gonzalez </b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=juanfrank77" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://styfle.dev/"><img src="https://avatars1.githubusercontent.com/u/229881?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Steven</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=styfle" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Georift"><img src="https://avatars2.githubusercontent.com/u/859430?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Tim</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Georift" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/sauravkhdoolia"><img src="https://avatars1.githubusercontent.com/u/34188267?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Saurav Khdoolia</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=sauravkhdoolia" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://anku.netlify.com/"><img src="https://avatars1.githubusercontent.com/u/22813027?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Ankit Tiwari</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=anku255" title="Documentation">📖</a> <a href="https://github.com/foambubble/foam/commits?author=anku255" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/ayushbaweja"><img src="https://avatars1.githubusercontent.com/u/44344063?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Ayush Baweja</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ayushbaweja" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/TaiChi-IO"><img src="https://avatars3.githubusercontent.com/u/65092992?v=4?s=60" width="60px;" alt=""/><br /><sub><b>TaiChi-IO</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=TaiChi-IO" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/juanfrank77"><img src="https://avatars1.githubusercontent.com/u/12146882?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Juan F Gonzalez </b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=juanfrank77" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://sanketdg.github.io"><img src="https://avatars3.githubusercontent.com/u/8980971?v=4" width="60px;" alt=""/><br /><sub><b>Sanket Dasgupta</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=SanketDG" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/nstafie"><img src="https://avatars1.githubusercontent.com/u/10801854?v=4" width="60px;" alt=""/><br /><sub><b>Nicholas Stafie</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=nstafie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/francishamel"><img src="https://avatars3.githubusercontent.com/u/36383308?v=4" width="60px;" alt=""/><br /><sub><b>Francis Hamel</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=francishamel" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://digiguru.co.uk"><img src="https://avatars1.githubusercontent.com/u/619436?v=4" width="60px;" alt=""/><br /><sub><b>digiguru</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=digiguru" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=digiguru" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://sanketdg.github.io"><img src="https://avatars3.githubusercontent.com/u/8980971?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Sanket Dasgupta</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=SanketDG" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/nstafie"><img src="https://avatars1.githubusercontent.com/u/10801854?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Nicholas Stafie</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=nstafie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/francishamel"><img src="https://avatars3.githubusercontent.com/u/36383308?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Francis Hamel</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=francishamel" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://digiguru.co.uk"><img src="https://avatars1.githubusercontent.com/u/619436?v=4?s=60" width="60px;" alt=""/><br /><sub><b>digiguru</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=digiguru" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=digiguru" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/chirag-singhal"><img src="https://avatars3.githubusercontent.com/u/42653703?v=4?s=60" width="60px;" alt=""/><br /><sub><b>CHIRAG SINGHAL</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=chirag-singhal" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/lostintangent"><img src="https://avatars3.githubusercontent.com/u/116461?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Jonathan Carter</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=lostintangent" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.synesthesia.co.uk"><img src="https://avatars3.githubusercontent.com/u/181399?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Julian Elve</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=synesthesia" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/thomaskoppelaar"><img src="https://avatars3.githubusercontent.com/u/36331365?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Thomas Koppelaar</b></sub></a><br /><a href="#question-thomaskoppelaar" title="Answering Questions">💬</a> <a href="https://github.com/foambubble/foam/commits?author=thomaskoppelaar" title="Code">💻</a> <a href="#userTesting-thomaskoppelaar" title="User Testing">📓</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -24,14 +24,31 @@ The three components of a link reference definition are `[link-label]: link-targ
|
||||
|
||||
- **link label:** The link text to match in the surrounding markdown document. This matches the inner bracket of the double-bracketed `[[wiki-link]]` notation
|
||||
- **link destination** The target of the matched link
|
||||
- Right now we generate link destinations without file extension. This is a choice, see [discussion here](https://foambubble.github.io/foam/wiki-links#why-dont-wiki-links-work-on-github).
|
||||
- By default we generate links without extension. This can be overridden, see [Configuration](#configuration) below
|
||||
- **"Link Title"** Optional title for link (The Foam template has a snippet of JavaScript to replace this on the website at runtime)
|
||||
|
||||
## Configuration
|
||||
|
||||
You can choose to generate link reference definitions with or without file extensions, depending on the target. As a rule of thumb:
|
||||
|
||||
- Links with file extensions work better with standard markdown-based tools, such as GitHub web UI.
|
||||
- Links without file extensions work better with certain web publishing tools that treat links as literal urls and don't transform them automatically, such as the standard GitHub pages installation.
|
||||
|
||||
By default, Foam generates links without file extensions for legacy reasons, but this may change in future versions.
|
||||
|
||||
You can override this setting in your Foam workspace's `settings.json`:
|
||||
|
||||
- `"foam.edit.linkReferenceDefinitions": "withoutExtensions"` (default)
|
||||
- `"foam.edit.linkReferenceDefinitions": "withExtensions"`
|
||||
|
||||
After changing the setting in your workspace, you can run the [[workspace-janitor]] command to convert all existing definitions.
|
||||
|
||||
## Future improvements
|
||||
|
||||
See [[link-reference-definition-improvements]] for further discussion on current problems and potential solutions.
|
||||
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[wiki-links]: wiki-links "Wiki Links"
|
||||
[roadmap]: roadmap "Roadmap"
|
||||
[workspace-janitor]: workspace-janitor "Workspace Janitor"
|
||||
[link-reference-definition-improvements]: link-reference-definition-improvements "Link Reference Definition Improvements"
|
||||
[//end]: # "Autogenerated link references"
|
||||
@@ -1,4 +0,0 @@
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[unrelated]: unrelated "Unrelated"
|
||||
[//end]: # "Autogenerated link references"
|
||||
@@ -35,6 +35,7 @@ Guides, tips and strategies for getting the most out of your Foam workspace!
|
||||
## Write
|
||||
- Link documents with [[wiki-links]]
|
||||
- Use shortcuts for [[creating-new-notes]]
|
||||
- Instantly create and access your [[daily-notes]]
|
||||
- Draw [[diagrams-in-markdown]]
|
||||
- Prettify your links, [[automatically-expand-urls-to-well-titled-links]]
|
||||
- Style your environment with [[custom-markdown-preview-styles]]
|
||||
@@ -49,7 +50,8 @@ Guides, tips and strategies for getting the most out of your Foam workspace!
|
||||
|
||||
## Version control
|
||||
|
||||
- Quick commits with [[git-integration]]
|
||||
- Quick commits with VS Code's built in [[git-integration]]
|
||||
- Store your workspace in an auto-synced GitHub repo with [[gistpad]]
|
||||
- Sync your GitHub repo automatically [[todo]].
|
||||
|
||||
## Publish
|
||||
@@ -57,6 +59,7 @@ Guides, tips and strategies for getting the most out of your Foam workspace!
|
||||
- Publish to [[github-pages]]
|
||||
- Publish to [[gitlab-pages]]
|
||||
- Publish your site with [[eleventy-and-netlify]]
|
||||
- Publish to [[azure-devops-wiki]]
|
||||
- Make the site your own by [[customising-styles]].
|
||||
- Math support [[math-support]]
|
||||
|
||||
@@ -68,9 +71,7 @@ Guides, tips and strategies for getting the most out of your Foam workspace!
|
||||
|
||||
## Workflow
|
||||
|
||||
Workflow recipes wanted!
|
||||
|
||||
_See [[contribution-guide]] and [[how-to-write-recipes]]._
|
||||
- Capture notes from Drafts app on iOS [[capture-notes-with-drafts-pro]]
|
||||
|
||||
## Creative ideas
|
||||
|
||||
@@ -96,14 +97,19 @@ _See [[contribution-guide]] and [[how-to-write-recipes]]._
|
||||
[reference-lists]: reference-lists "Reference Lists"
|
||||
[wiki-links]: wiki-links "Wiki Links"
|
||||
[creating-new-notes]: creating-new-notes "Creating New Notes"
|
||||
[daily-notes]: daily-notes "Daily Notes"
|
||||
[diagrams-in-markdown]: diagrams-in-markdown "Diagrams in Markdown"
|
||||
[automatically-expand-urls-to-well-titled-links]: automatically-expand-urls-to-well-titled-links "Automatically Expand URLs to Well-Titled Links"
|
||||
[custom-markdown-preview-styles]: custom-markdown-preview-styles "Custom Markdown Preview Styles"
|
||||
[images-from-your-clipboard]: images-from-your-clipboard "Images from your Clipboard"
|
||||
[good-first-task]: good-first-task "Good First Task"
|
||||
[git-integration]: git-integration "Git integration"
|
||||
[gistpad]: gistpad "GistPad"
|
||||
[github-pages]: github-pages "Github Pages"
|
||||
[gitlab-pages]: gitlab-pages "GitLab Pages"
|
||||
[eleventy-and-netlify]: eleventy-and-netlify "Eleventy and Netlify"
|
||||
[azure-devops-wiki]: azure-devops-wiki "Azure DevOps Wiki"
|
||||
[customising-styles]: customising-styles "Customising Styles"
|
||||
[math-support]: math-support "Math Support"
|
||||
[capture-notes-with-drafts-pro]: capture-notes-with-drafts-pro "Capture Notes With Drafts Pro"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
@@ -35,7 +35,6 @@ If a roadmap item is a stub, **consider** opening a [GitHub issue](https://githu
|
||||
|
||||
- [[renaming-files]]
|
||||
- [[unlinked-references]]
|
||||
- [[daily-notes]]
|
||||
- [[block-references]]
|
||||
- [[improved-backlinking]]
|
||||
- UX: [[make-backlinks-more-prominent]]
|
||||
@@ -70,8 +69,9 @@ If a roadmap item is a stub, **consider** opening a [GitHub issue](https://githu
|
||||
- Discussion: [foam#55](https://github.com/foambubble/foam/issues/55)
|
||||
- [[migrating-from-obsidian]]
|
||||
- Discussion: [foam#46](https://github.com/foambubble/foam/issues/46)
|
||||
- [[Migrating from OneNote (stub)]]
|
||||
- Discussion: [foam#151](https://github.com/foambubble/foam/issues/151)
|
||||
- _Migration from other tools..._
|
||||
|
||||
### integration
|
||||
- _Integrations to third party tools_...
|
||||
|
||||
@@ -92,12 +92,14 @@ If a roadmap item is a stub, **consider** opening a [GitHub issue](https://githu
|
||||
[foam-file-format]: foam-file-format "Foam File Format"
|
||||
[renaming-files]: renaming-files "Renaming files (stub)"
|
||||
[unlinked-references]: unlinked-references "Unlinked references (stub)"
|
||||
[daily-notes]: daily-notes "Daily notes"
|
||||
[block-references]: block-references "Block References (stub)"
|
||||
[improved-backlinking]: improved-backlinking "Improved Backlinking (stub)"
|
||||
[make-backlinks-more-prominent]: make-backlinks-more-prominent "Make Backlinks More Prominent"
|
||||
[materialized-backlinks]: materialized-backlinks "Materialized Backlinks (stub)"
|
||||
[automatic-git-syncing]: automatic-git-syncing "Automatic Git Syncing (stub)"
|
||||
[git-flows-for-teams]: git-flows-for-teams "Git Flows for Teams (stub)"
|
||||
[user-settings]: user-settings "User Settings (stub)"
|
||||
[link-reference-definitions]: link-reference-definitions "Link Reference Definitions"
|
||||
[officially-support-alternative-templates]: officially-support-alternative-templates "Officially Support Alternative Templates (stub)"
|
||||
[improved-static-site-generation]: improved-static-site-generation "Improved Static Site Generation (stub)"
|
||||
[mdx-by-default]: mdx-by-default "MDX by Default(stub)"
|
||||
@@ -113,4 +115,4 @@ If a roadmap item is a stub, **consider** opening a [GitHub issue](https://githu
|
||||
[foam-linter]: foam-linter "Foam Linter (stub)"
|
||||
[refactoring-via-language-server-protocol]: refactoring-via-language-server-protocol "Refactoring via Language Server Protocol (stub)"
|
||||
[make-backlinks-more-prominent]: make-backlinks-more-prominent "Make Backlinks More Prominent"
|
||||
[//end]: # "Autogenerated link references"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
@@ -1,69 +1,40 @@
|
||||
# Workspace Janitor
|
||||
# Janitor
|
||||
|
||||
## What is it?
|
||||
To store your personal knowledge graph in markdown files instead of a database, we need some additional tooling to create and maintain relationships with notes.
|
||||
|
||||
To store your personal knowledge graph in markdown files instead of a database, we need some additional tooling to create and maintain:
|
||||
**Foam Janitor** (inspired by Andy Matuschak's [note-link-janitor](https://github.com/andymatuschak/note-link-janitor)) helps you migrate existing notes to Foam, and maintain your Foam's health over time.
|
||||
|
||||
- Relationships between notes
|
||||
- link references for [[wiki-links]]/markdown compatibility
|
||||
- [[materialized-backlinks]]
|
||||
- etc.
|
||||
- Format and structure of each note (e.g. title)
|
||||
- Renaming and refactoring files and directories
|
||||
- Linting related functionality
|
||||
- File names
|
||||
- Note links
|
||||
- Zettelkasten linking
|
||||
- Visibility for orphaned notes
|
||||
Currently, Foam's Janitor helps you to:
|
||||
- Ensure your [[link-reference-definitions]] are up to date
|
||||
- Ensure every document has a well-formatted title (required for Markdown Links, Markdown Notes, and Foam Gatsby Template compatibility)
|
||||
|
||||
This is necessary:
|
||||
In the future, Janitor can help you with
|
||||
- Updating [[materialized-backlinks]]
|
||||
- Lint, format and structure notes
|
||||
- Rename and move notes around while keeping their references up to date.
|
||||
|
||||
- When migrating to Foam
|
||||
- To maintain your workspace health over long period of time
|
||||
## Using Janitor from VS Code (Experimental)
|
||||
|
||||
## Problem
|
||||
Execute the "Foam: Run Janitor" command from the command palette.
|
||||
|
||||
Currently, the VS Code extension is very naive, and only updates the currently active editor. This isn't sufficient, because:
|
||||

|
||||
|
||||
- For e.g. [[materialized-backlinks]] to work, files need to update in the background.
|
||||
- Separation of clean vs generated workspace for publishing?
|
||||
- Support for standard markdown tools
|
||||
- Output to a /build directory
|
||||
- Would have to implement a custom previewer
|
||||
- [[todo]] **Janne Ojanaho! Write a short proposal for this.**
|
||||
- If VS Code extension bugs our or is not ran, the workspace can lead in an inconsistent state
|
||||
- In collaboration scenarios, two people may change the same file, causing incomplete updates
|
||||
- If someone pushes changes from outside VS Code (Obsidian, Git Journal, etc.) links aren't updated
|
||||
## Using Janitor from command line (Experimental)
|
||||
|
||||
## Proposal
|
||||
> ⚠️ Improvements to this documentation are welcome!
|
||||
|
||||
Implement a note janitor (working title, named after Andy Matuschak's [note-link-janitor](https://github.com/andymatuschak/note-link-janitor)), which ensures all files are correctly linked and updated, no matter how changes happen.
|
||||
The Janitor can be installed from [NPM](https://www.npmjs.com/) and executed as a standalone CLI tool:
|
||||
|
||||
Janitor should be runnable:
|
||||
```sh
|
||||
> npm install -g foam-cli
|
||||
> foam janitor path/to/workspace
|
||||
```
|
||||
|
||||
- From VS Code
|
||||
- Replaces the existing logic in extension.ts
|
||||
- In theory, we could do this using the [VS Code Tasks](https://code.visualstudio.com/docs/editor/tasks), exposing it from foam-vscode with a [TaskProvider](https://code.visualstudio.com/api/extension-guides/task-provider)
|
||||
- It's not clear to me whether modifying the file in the active VS Code buffer from a background task is problematic for e.g. focus/selection management. I think this is how e.g. Prettier works, but not sure if there's some special case for the active editor.
|
||||
- VS Code provides its own workspace watching functionality. Not sure it would be beneficial to use this over just chokidar-style file watching in the background
|
||||
- As a pre-push Git Hook (is this a good idea?) to ensure we send fully formed note graphs, even if you had to do some editing outside vs code
|
||||
- As a GitHub Action (for incoming changes via PRs and other apps like GitJournal)
|
||||
- Run the "build" and push back to master/gh-pages
|
||||
- As a NPM script/dependency for any other purpose
|
||||
You can run the Janitor as a git hook on every commit to ensure your workspace links are up to date. This can be especially helpful if you edit your markdown documents from other apps.
|
||||
|
||||
## Software architecture
|
||||
|
||||
- It's not really clear to me whether the workspace janitor should be its own package, or if the Janitor should just be the [[cli]] package, or the [[foam-core]] package.
|
||||
- Ideally the janitor should be lightweight so installing it on CI is quick
|
||||
- It would be cool if it didn't have many weird node-specific dependencies, maybe it could be even ran INSIDE a mobile application?
|
||||
- We don't want to pollute foam-core, but janitor might actually get quite diverse in use cases.
|
||||
|
||||
Decoupling things like the core janitor API from the CLI API would help potentially with situations where we might want to have a separate file watcher strategy for CLI and active VS Code workspaces (see above).
|
||||
You can also run the Janitor from a GitHub action to ensure that all changes coming to your workspace are up to date. This can be useful when editing your Foam notes from mobile (i.e. via [GitJournal](https://gitjournal.io)), or your Foam has multiple contributors and you want to ensure that all changes are correctly integrated.
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[wiki-links]: wiki-links "Wiki Links"
|
||||
[link-reference-definitions]: link-reference-definitions "Link Reference Definitions"
|
||||
[materialized-backlinks]: materialized-backlinks "Materialized Backlinks (stub)"
|
||||
[cli]: cli "Command Line Interface"
|
||||
[foam-core]: foam-core "Foam Core"
|
||||
[todo]: todo "Todo"
|
||||
[//end]: # "Autogenerated link references"
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
],
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.2.0"
|
||||
"version": "0.3.0"
|
||||
}
|
||||
|
||||
10
package.json
10
package.json
@@ -10,7 +10,15 @@
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"watch": "yarn workspace foam-vscode watch"
|
||||
"watch:vscode": "yarn workspace foam-vscode watch",
|
||||
"build:core": "yarn workspace foam-core build",
|
||||
"watch:core": "yarn workspace foam-core start",
|
||||
"test:core": "yarn workspace foam-core test",
|
||||
"clean": "lerna run clean",
|
||||
"build": "lerna run build",
|
||||
"test": "lerna run test",
|
||||
"lint": "lerna run lint",
|
||||
"watch": "lerna run watch --concurrency 20 --stream"
|
||||
},
|
||||
"devDependencies": {
|
||||
"all-contributors-cli": "^6.16.1",
|
||||
|
||||
@@ -19,7 +19,7 @@ $ npm install -g foam-cli
|
||||
$ foam COMMAND
|
||||
running command...
|
||||
$ foam (-v|--version|version)
|
||||
foam-cli/0.2.0 darwin-x64 node-v12.18.0
|
||||
foam-cli/0.3.0 darwin-x64 node-v12.18.0
|
||||
$ foam --help [COMMAND]
|
||||
USAGE
|
||||
$ foam COMMAND
|
||||
@@ -28,28 +28,9 @@ USAGE
|
||||
<!-- usagestop -->
|
||||
# Commands
|
||||
<!-- commands -->
|
||||
* [`foam hello [FILE]`](#foam-hello-file)
|
||||
* [`foam help [COMMAND]`](#foam-help-command)
|
||||
|
||||
## `foam hello [FILE]`
|
||||
|
||||
describe the command here
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ foam hello [FILE]
|
||||
|
||||
OPTIONS
|
||||
-f, --force
|
||||
-h, --help show CLI help
|
||||
-n, --name=name name to print
|
||||
|
||||
EXAMPLE
|
||||
$ foam hello
|
||||
hello world from ./src/hello.ts!
|
||||
```
|
||||
|
||||
_See code: [src/commands/hello.ts](https://github.com/foambubble/foam/blob/v0.2.0/src/commands/hello.ts)_
|
||||
* [`foam janitor [WORKSPACEPATH]`](#foam-janitor-workspacepath)
|
||||
* [`foam migrate [WORKSPACEPATH]`](#foam-migrate-workspacepath)
|
||||
|
||||
## `foam help [COMMAND]`
|
||||
|
||||
@@ -67,6 +48,43 @@ OPTIONS
|
||||
```
|
||||
|
||||
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v3.1.0/src/commands/help.ts)_
|
||||
|
||||
## `foam janitor [WORKSPACEPATH]`
|
||||
|
||||
Updates link references and heading across all the markdown files in the given workspaces
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ foam janitor [WORKSPACEPATH]
|
||||
|
||||
OPTIONS
|
||||
-h, --help show CLI help
|
||||
-w, --without-extensions generate link reference definitions without extensions (for legacy support)
|
||||
|
||||
EXAMPLE
|
||||
$ foam-cli janitor path-to-foam-workspace
|
||||
```
|
||||
|
||||
_See code: [src/commands/janitor.ts](https://github.com/foambubble/foam/blob/v0.3.0/src/commands/janitor.ts)_
|
||||
|
||||
## `foam migrate [WORKSPACEPATH]`
|
||||
|
||||
Updates file names, link references and heading across all the markdown files in the given workspaces
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ foam migrate [WORKSPACEPATH]
|
||||
|
||||
OPTIONS
|
||||
-h, --help show CLI help
|
||||
-w, --without-extensions generate link reference definitions without extensions (for legacy support)
|
||||
|
||||
EXAMPLE
|
||||
$ foam-cli migrate path-to-foam-workspace
|
||||
Successfully generated link references and heading!
|
||||
```
|
||||
|
||||
_See code: [src/commands/migrate.ts](https://github.com/foambubble/foam/blob/v0.3.0/src/commands/migrate.ts)_
|
||||
<!-- commandsstop -->
|
||||
|
||||
## Development
|
||||
|
||||
6
packages/foam-cli/babel.config.js
Normal file
6
packages/foam-cli/babel.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
['@babel/preset-env', {targets: {node: 'current'}}],
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
};
|
||||
188
packages/foam-cli/jest.config.js
Normal file
188
packages/foam-cli/jest.config.js
Normal file
@@ -0,0 +1,188 @@
|
||||
// For a detailed explanation regarding each configuration property, visit:
|
||||
// https://jestjs.io/docs/en/configuration.html
|
||||
|
||||
module.exports = {
|
||||
// All imported modules in your tests should be mocked automatically
|
||||
// automock: false,
|
||||
|
||||
// Stop running tests after `n` failures
|
||||
// bail: 0,
|
||||
|
||||
// The directory where Jest should store its cached dependency information
|
||||
// cacheDirectory: "/private/var/folders/p6/5y5l8tbs1d32pq9b596lk48h0000gn/T/jest_dx",
|
||||
|
||||
// Automatically clear mock calls and instances between every test
|
||||
clearMocks: true,
|
||||
|
||||
// Indicates whether the coverage information should be collected while executing the test
|
||||
// collectCoverage: false,
|
||||
|
||||
// An array of glob patterns indicating a set of files for which coverage information should be collected
|
||||
// collectCoverageFrom: undefined,
|
||||
|
||||
// The directory where Jest should output its coverage files
|
||||
// coverageDirectory: undefined,
|
||||
|
||||
// An array of regexp pattern strings used to skip coverage collection
|
||||
// coveragePathIgnorePatterns: [
|
||||
// "/node_modules/"
|
||||
// ],
|
||||
|
||||
// Indicates which provider should be used to instrument code for coverage
|
||||
// coverageProvider: "babel",
|
||||
|
||||
// A list of reporter names that Jest uses when writing coverage reports
|
||||
// coverageReporters: [
|
||||
// "json",
|
||||
// "text",
|
||||
// "lcov",
|
||||
// "clover"
|
||||
// ],
|
||||
|
||||
// An object that configures minimum threshold enforcement for coverage results
|
||||
// coverageThreshold: undefined,
|
||||
|
||||
// A path to a custom dependency extractor
|
||||
// dependencyExtractor: undefined,
|
||||
|
||||
// Make calling deprecated APIs throw helpful error messages
|
||||
// errorOnDeprecated: false,
|
||||
|
||||
// Force coverage collection from ignored files using an array of glob patterns
|
||||
// forceCoverageMatch: [],
|
||||
|
||||
// A path to a module which exports an async function that is triggered once before all test suites
|
||||
// globalSetup: undefined,
|
||||
|
||||
// A path to a module which exports an async function that is triggered once after all test suites
|
||||
// globalTeardown: undefined,
|
||||
|
||||
// A set of global variables that need to be available in all test environments
|
||||
// globals: {},
|
||||
|
||||
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
|
||||
// maxWorkers: "50%",
|
||||
|
||||
// An array of directory names to be searched recursively up from the requiring module's location
|
||||
// moduleDirectories: [
|
||||
// "node_modules"
|
||||
// ],
|
||||
|
||||
// An array of file extensions your modules use
|
||||
// moduleFileExtensions: [
|
||||
// "js",
|
||||
// "json",
|
||||
// "jsx",
|
||||
// "ts",
|
||||
// "tsx",
|
||||
// "node"
|
||||
// ],
|
||||
|
||||
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
|
||||
// moduleNameMapper: {},
|
||||
|
||||
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
||||
// modulePathIgnorePatterns: [],
|
||||
|
||||
// Activates notifications for test results
|
||||
// notify: false,
|
||||
|
||||
// An enum that specifies notification mode. Requires { notify: true }
|
||||
// notifyMode: "failure-change",
|
||||
|
||||
// A preset that is used as a base for Jest's configuration
|
||||
// preset: undefined,
|
||||
|
||||
// Run tests from one or more projects
|
||||
// projects: undefined,
|
||||
|
||||
// Use this configuration option to add custom reporters to Jest
|
||||
// reporters: undefined,
|
||||
|
||||
// Automatically reset mock state between every test
|
||||
// resetMocks: false,
|
||||
|
||||
// Reset the module registry before running each individual test
|
||||
// resetModules: false,
|
||||
|
||||
// A path to a custom resolver
|
||||
// resolver: undefined,
|
||||
|
||||
// Automatically restore mock state between every test
|
||||
// restoreMocks: false,
|
||||
|
||||
// The root directory that Jest should scan for tests and modules within
|
||||
// rootDir: undefined,
|
||||
|
||||
// A list of paths to directories that Jest should use to search for files in
|
||||
// roots: [
|
||||
// "<rootDir>"
|
||||
// ],
|
||||
|
||||
// Allows you to use a custom runner instead of Jest's default test runner
|
||||
// runner: "jest-runner",
|
||||
|
||||
// The paths to modules that run some code to configure or set up the testing environment before each test
|
||||
// setupFiles: [],
|
||||
|
||||
// A list of paths to modules that run some code to configure or set up the testing framework before each test
|
||||
// setupFilesAfterEnv: [],
|
||||
|
||||
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
|
||||
// snapshotSerializers: [],
|
||||
|
||||
// The test environment that will be used for testing
|
||||
testEnvironment: "node",
|
||||
|
||||
// Options that will be passed to the testEnvironment
|
||||
// testEnvironmentOptions: {},
|
||||
|
||||
// Adds a location field to test results
|
||||
// testLocationInResults: false,
|
||||
|
||||
// The glob patterns Jest uses to detect test files
|
||||
// testMatch: [
|
||||
// "**/__tests__/**/*.[jt]s?(x)",
|
||||
// "**/?(*.)+(spec|test).[tj]s?(x)"
|
||||
// ],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
|
||||
// testPathIgnorePatterns: [
|
||||
// "/node_modules/"
|
||||
// ],
|
||||
|
||||
// The regexp pattern or array of patterns that Jest uses to detect test files
|
||||
// testRegex: [],
|
||||
|
||||
// This option allows the use of a custom results processor
|
||||
// testResultsProcessor: undefined,
|
||||
|
||||
// This option allows use of a custom test runner
|
||||
// testRunner: "jasmine2",
|
||||
|
||||
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
|
||||
// testURL: "http://localhost",
|
||||
|
||||
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
|
||||
// timers: "real",
|
||||
|
||||
// A map from regular expressions to paths to transformers
|
||||
// transform: undefined,
|
||||
|
||||
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
||||
// transformIgnorePatterns: [
|
||||
// "/node_modules/"
|
||||
// ],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
||||
// unmockedModulePathPatterns: undefined,
|
||||
|
||||
// Indicates whether each individual test should be reported during the run
|
||||
// verbose: undefined,
|
||||
|
||||
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
|
||||
// watchPathIgnorePatterns: [],
|
||||
|
||||
// Whether to use watchman for file crawling
|
||||
// watchman: true,
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "foam-cli",
|
||||
"description": "Foam CLI",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"author": "Jani Eväkallio @jevakallio",
|
||||
"bin": {
|
||||
"foam": "./bin/run"
|
||||
@@ -11,17 +11,24 @@
|
||||
"@oclif/command": "^1",
|
||||
"@oclif/config": "^1",
|
||||
"@oclif/plugin-help": "^3",
|
||||
"foam-core": "^0.3.0",
|
||||
"ora": "^4.0.4",
|
||||
"tslib": "^1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.10.4",
|
||||
"@babel/preset-env": "^7.10.4",
|
||||
"@babel/preset-typescript": "^7.10.4",
|
||||
"@oclif/dev-cli": "^1",
|
||||
"@types/node": "^10",
|
||||
"babel-jest": "^26.1.0",
|
||||
"chai": "^4",
|
||||
"eslint": "^5.13",
|
||||
"eslint-config-oclif": "^3.1",
|
||||
"eslint-config-oclif-typescript": "^0.1",
|
||||
"foam-core": "^0.2.0",
|
||||
"globby": "^10",
|
||||
"jest": "^26.1.0",
|
||||
"mock-fs": "^4.12.0",
|
||||
"ts-node": "^8",
|
||||
"typescript": "^3.3"
|
||||
},
|
||||
@@ -52,11 +59,13 @@
|
||||
},
|
||||
"repository": "foambubble/foam",
|
||||
"scripts": {
|
||||
"clean": "rimraf tmp",
|
||||
"build": "tsc -b",
|
||||
"test": "jest",
|
||||
"lint": "echo Missing lint task in CLI package",
|
||||
"cli": "./bin/run",
|
||||
"postpack": "rm -f oclif.manifest.json",
|
||||
"posttest": "eslint . --ext .ts --config .eslintrc",
|
||||
"prepack": "rm -rf lib && tsc -b && oclif-dev manifest && oclif-dev readme",
|
||||
"test": "nyc --extension .ts mocha --forbid-only \"test/**/*.test.ts\"",
|
||||
"version": "oclif-dev readme && git add README.md"
|
||||
},
|
||||
"types": "lib/index.d.ts"
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import {Command, flags} from '@oclif/command'
|
||||
import { NoteGraph } from 'foam-core';
|
||||
export default class Hello extends Command {
|
||||
static description = 'describe the command here'
|
||||
|
||||
static examples = [
|
||||
`$ foam hello
|
||||
hello world from ./src/hello.ts!
|
||||
`,
|
||||
]
|
||||
|
||||
static flags = {
|
||||
help: flags.help({char: 'h'}),
|
||||
// flag with a value (-n, --name=VALUE)
|
||||
name: flags.string({char: 'n', description: 'name to print'}),
|
||||
// flag with no value (-f, --force)
|
||||
force: flags.boolean({char: 'f'}),
|
||||
}
|
||||
|
||||
static args = [{name: 'file'}]
|
||||
|
||||
async run() {
|
||||
const {args, flags} = this.parse(Hello)
|
||||
const name = flags.name ?? 'world'
|
||||
|
||||
// const wm = new NoteGraph();
|
||||
// wm.addNoteFromMarkdown('page-a.md', `
|
||||
// # Page A
|
||||
// ## Section
|
||||
// - [[page-b]]
|
||||
// - [[page-c]];
|
||||
// `);
|
||||
|
||||
// wm.addNoteFromMarkdown('page-a.md', `
|
||||
// # Page B
|
||||
// This references [[page-a]]`);
|
||||
|
||||
// console.log(wm.getNoteWithLinks('page-a'));
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
79
packages/foam-cli/src/commands/janitor.ts
Normal file
79
packages/foam-cli/src/commands/janitor.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { Command, flags } from '@oclif/command';
|
||||
import * as ora from 'ora';
|
||||
import {
|
||||
initializeNoteGraph,
|
||||
generateLinkReferences,
|
||||
generateHeading,
|
||||
applyTextEdit
|
||||
} from 'foam-core';
|
||||
import { writeFileToDisk } from '../utils/write-file-to-disk';
|
||||
import { isValidDirectory } from '../utils';
|
||||
|
||||
export default class Janitor extends Command {
|
||||
static description =
|
||||
'Updates link references and heading across all the markdown files in the given workspaces';
|
||||
|
||||
static examples = [
|
||||
`$ foam-cli janitor path-to-foam-workspace
|
||||
`,
|
||||
];
|
||||
|
||||
static flags = {
|
||||
'without-extensions': flags.boolean({
|
||||
char: 'w',
|
||||
description: 'generate link reference definitions without extensions (for legacy support)'
|
||||
}),
|
||||
help: flags.help({ char: 'h' }),
|
||||
};
|
||||
|
||||
static args = [{ name: 'workspacePath' }];
|
||||
|
||||
async run() {
|
||||
const spinner = ora('Reading Files').start();
|
||||
|
||||
const { args, flags } = this.parse(Janitor);
|
||||
|
||||
const { workspacePath = './' } = args;
|
||||
|
||||
if (isValidDirectory(workspacePath)) {
|
||||
const graph = await initializeNoteGraph(workspacePath);
|
||||
|
||||
const notes = graph.getNotes().filter(Boolean); // removes undefined notes
|
||||
|
||||
spinner.succeed();
|
||||
spinner.text = `${notes.length} files found`;
|
||||
spinner.succeed();
|
||||
|
||||
// exit early if no files found.
|
||||
if (notes.length === 0) {
|
||||
this.exit();
|
||||
}
|
||||
|
||||
spinner.text = 'Generating link definitions';
|
||||
|
||||
const fileWritePromises = notes.map(note => {
|
||||
// Get edits
|
||||
const heading = generateHeading(note);
|
||||
const definitions = generateLinkReferences(note, graph, !flags['without-extensions']);
|
||||
|
||||
// apply Edits
|
||||
let file = note.source;
|
||||
file = heading ? applyTextEdit(file, heading) : file;
|
||||
file = definitions ? applyTextEdit(file, definitions) : file;
|
||||
|
||||
if (heading || definitions) {
|
||||
return writeFileToDisk(note.path, file);
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
|
||||
await Promise.all(fileWritePromises);
|
||||
|
||||
spinner.succeed();
|
||||
spinner.succeed('Done!');
|
||||
} else {
|
||||
spinner.fail('Directory does not exist!');
|
||||
}
|
||||
}
|
||||
}
|
||||
106
packages/foam-cli/src/commands/migrate.ts
Normal file
106
packages/foam-cli/src/commands/migrate.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { Command, flags } from '@oclif/command';
|
||||
import * as ora from 'ora';
|
||||
import {
|
||||
initializeNoteGraph,
|
||||
generateLinkReferences,
|
||||
generateHeading,
|
||||
getKebabCaseFileName,
|
||||
applyTextEdit
|
||||
} from 'foam-core';
|
||||
import { writeFileToDisk } from '../utils/write-file-to-disk';
|
||||
import { renameFile } from '../utils/rename-file';
|
||||
import { isValidDirectory } from '../utils';
|
||||
|
||||
// @todo: Refactor 'migrate' and 'janitor' commands and avoid repeatition
|
||||
export default class Migrate extends Command {
|
||||
static description =
|
||||
'Updates file names, link references and heading across all the markdown files in the given workspaces';
|
||||
|
||||
static examples = [
|
||||
`$ foam-cli migrate path-to-foam-workspace
|
||||
Successfully generated link references and heading!
|
||||
`,
|
||||
];
|
||||
|
||||
static flags = {
|
||||
'without-extensions': flags.boolean({
|
||||
char: 'w',
|
||||
description: 'generate link reference definitions without extensions (for legacy support)'
|
||||
}),
|
||||
help: flags.help({ char: 'h' }),
|
||||
};
|
||||
|
||||
static args = [{ name: 'workspacePath' }];
|
||||
|
||||
async run() {
|
||||
const spinner = ora('Reading Files').start();
|
||||
|
||||
const { args, flags } = this.parse(Migrate);
|
||||
|
||||
const { workspacePath = './' } = args;
|
||||
|
||||
if (isValidDirectory(workspacePath)) {
|
||||
let graph = await initializeNoteGraph(workspacePath);
|
||||
|
||||
let notes = graph.getNotes().filter(Boolean); // removes undefined notes
|
||||
|
||||
spinner.succeed();
|
||||
spinner.text = `${notes.length} files found`;
|
||||
spinner.succeed();
|
||||
|
||||
// exit early if no files found.
|
||||
if (notes.length === 0) {
|
||||
this.exit();
|
||||
}
|
||||
|
||||
// Kebab case file names
|
||||
const fileRename = notes.map(note => {
|
||||
if (note.title != null) {
|
||||
const kebabCasedFileName = getKebabCaseFileName(note.title);
|
||||
if (kebabCasedFileName) {
|
||||
return renameFile(note.path, kebabCasedFileName);
|
||||
}
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
|
||||
await Promise.all(fileRename);
|
||||
|
||||
spinner.text = 'Renaming files';
|
||||
|
||||
// Reinitialize the graph after renaming files
|
||||
graph = await initializeNoteGraph(workspacePath);
|
||||
|
||||
notes = graph.getNotes().filter(Boolean); // remove undefined notes
|
||||
|
||||
spinner.succeed();
|
||||
spinner.text = 'Generating link definitions';
|
||||
|
||||
const fileWritePromises = await Promise.all(
|
||||
notes.map(note => {
|
||||
// Get edits
|
||||
const heading = generateHeading(note);
|
||||
const definitions = generateLinkReferences(note, graph, !flags['without-extensions']);
|
||||
|
||||
// apply Edits
|
||||
let file = note.source;
|
||||
file = heading ? applyTextEdit(file, heading) : file;
|
||||
file = definitions ? applyTextEdit(file, definitions) : file;
|
||||
|
||||
if (heading || definitions) {
|
||||
return writeFileToDisk(note.path, file);
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
})
|
||||
);
|
||||
|
||||
await Promise.all(fileWritePromises);
|
||||
|
||||
spinner.succeed();
|
||||
spinner.succeed('Done!');
|
||||
} else {
|
||||
spinner.fail('Directory does not exist!');
|
||||
}
|
||||
}
|
||||
}
|
||||
4
packages/foam-cli/src/utils/index.ts
Normal file
4
packages/foam-cli/src/utils/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import * as fs from 'fs';
|
||||
|
||||
export const isValidDirectory = (path: string) =>
|
||||
fs.existsSync(path) && fs.lstatSync(path).isDirectory();
|
||||
15
packages/foam-cli/src/utils/rename-file.ts
Normal file
15
packages/foam-cli/src/utils/rename-file.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param fileUri absolute path for the file that needs to renamed
|
||||
* @param newFileName "new file name" without the extension
|
||||
*/
|
||||
export const renameFile = async (fileUri: string, newFileName: string) => {
|
||||
const dirName = path.dirname(fileUri);
|
||||
const extension = path.extname(fileUri);
|
||||
const newFileUri = path.join(dirName, `${newFileName}${extension}`);
|
||||
|
||||
return fs.promises.rename(fileUri, newFileUri);
|
||||
};
|
||||
5
packages/foam-cli/src/utils/write-file-to-disk.ts
Normal file
5
packages/foam-cli/src/utils/write-file-to-disk.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as fs from 'fs';
|
||||
|
||||
export const writeFileToDisk = async (fileUri: string, data: string) => {
|
||||
return fs.promises.writeFile(fileUri, data);
|
||||
};
|
||||
30
packages/foam-cli/test/rename-file.test.ts
Normal file
30
packages/foam-cli/test/rename-file.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { renameFile } from '../src/utils/rename-file';
|
||||
import * as fs from 'fs';
|
||||
import mockFS from 'mock-fs';
|
||||
|
||||
const doesFileExist = path =>
|
||||
fs.promises
|
||||
.access(path)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
|
||||
describe('renameFile', () => {
|
||||
const fileUri = './test/oldFileName.md';
|
||||
|
||||
beforeAll(() => {
|
||||
mockFS({ [fileUri]: '' });
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
mockFS.restore();
|
||||
});
|
||||
|
||||
it('should rename existing file', async () => {
|
||||
expect(await doesFileExist(fileUri)).toBe(true);
|
||||
|
||||
renameFile(fileUri, 'new-file-name');
|
||||
|
||||
expect(await doesFileExist(fileUri)).toBe(false);
|
||||
expect(await doesFileExist('./test/new-file-name.md')).toBe(true);
|
||||
});
|
||||
});
|
||||
23
packages/foam-cli/test/write-file-to-disk.test.ts
Normal file
23
packages/foam-cli/test/write-file-to-disk.test.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { writeFileToDisk } from '../src/utils/write-file-to-disk';
|
||||
import * as fs from 'fs';
|
||||
import mockFS from 'mock-fs';
|
||||
|
||||
describe('writeFileToDisk', () => {
|
||||
const fileUri = './test-file.md';
|
||||
|
||||
beforeAll(() => {
|
||||
mockFS({ [fileUri]: 'content in the existing file' });
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
fs.unlinkSync(fileUri);
|
||||
mockFS.restore();
|
||||
});
|
||||
|
||||
it('should overrwrite existing file in the disk with the new data', async () => {
|
||||
const expected = `content in the new file`;
|
||||
await writeFileToDisk(fileUri, expected);
|
||||
const actual = await fs.promises.readFile(fileUri, { encoding: 'utf8' });
|
||||
expect(actual).toBe(expected);
|
||||
});
|
||||
});
|
||||
@@ -2,19 +2,21 @@
|
||||
"name": "foam-core",
|
||||
"author": "Jani Eväkallio",
|
||||
"repository": "https://github.com/foambubble/foam",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"start": "tsdx watch",
|
||||
"clean": "rimraf dist",
|
||||
"build": "tsdx build",
|
||||
"test": "tsdx test",
|
||||
"lint": "tsdx lint",
|
||||
"watch": "tsdx watch",
|
||||
"prepare": "tsdx build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/github-slugger": "^1.3.0",
|
||||
"@types/graphlib": "^2.1.6",
|
||||
"@types/lodash": "^4.14.157",
|
||||
"husky": "^4.2.5",
|
||||
@@ -23,10 +25,14 @@
|
||||
"typescript": "^3.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"detect-newline": "^3.1.0",
|
||||
"github-slugger": "^1.3.0",
|
||||
"glob": "^7.1.6",
|
||||
"graphlib": "^2.1.8",
|
||||
"lodash": "^4.17.19",
|
||||
"remark-parse": "^8.0.2",
|
||||
"remark-wiki-link": "^0.0.4",
|
||||
"title-case": "^3.0.2",
|
||||
"unified": "^9.0.0",
|
||||
"unist-util-visit": "^2.0.2"
|
||||
},
|
||||
|
||||
2
packages/foam-core/src/definitions.ts
Normal file
2
packages/foam-core/src/definitions.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const LINK_REFERENCE_DEFINITION_HEADER = `[//begin]: # "Autogenerated link references for markdown compatibility"`;
|
||||
export const LINK_REFERENCE_DEFINITION_FOOTER = `[//end]: # "Autogenerated link references"`;
|
||||
@@ -3,20 +3,37 @@ import { NoteGraph, Note, NoteLink } from './note-graph';
|
||||
export {
|
||||
createNoteFromMarkdown,
|
||||
createMarkdownReferences,
|
||||
stringifyMarkdownLinkReferenceDefinition,
|
||||
} from './markdown-provider';
|
||||
|
||||
export { NoteGraph, Note, NoteLink }
|
||||
export {
|
||||
TextEdit,
|
||||
generateHeading,
|
||||
generateLinkReferences,
|
||||
getKebabCaseFileName,
|
||||
} from './janitor';
|
||||
|
||||
export { applyTextEdit } from './janitor/apply-text-edit';
|
||||
|
||||
export { initializeNoteGraph } from './initialize-note-graph';
|
||||
|
||||
export { NoteGraph, Note, NoteLink };
|
||||
|
||||
export {
|
||||
LINK_REFERENCE_DEFINITION_HEADER,
|
||||
LINK_REFERENCE_DEFINITION_FOOTER,
|
||||
} from './definitions';
|
||||
|
||||
export interface FoamConfig {
|
||||
// TODO
|
||||
}
|
||||
|
||||
export interface Foam {
|
||||
notes: NoteGraph
|
||||
notes: NoteGraph;
|
||||
// config: FoamConfig
|
||||
}
|
||||
|
||||
export const createFoam = (config: FoamConfig) => ({
|
||||
notes: new NoteGraph(),
|
||||
config: config,
|
||||
})
|
||||
});
|
||||
|
||||
30
packages/foam-core/src/initialize-note-graph.ts
Normal file
30
packages/foam-core/src/initialize-note-graph.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import glob from 'glob';
|
||||
import { promisify } from 'util';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import detectNewline from 'detect-newline';
|
||||
import { NoteGraph } from './note-graph';
|
||||
import { createNoteFromMarkdown } from './markdown-provider';
|
||||
|
||||
const findAllFiles = promisify(glob);
|
||||
|
||||
export const initializeNoteGraph = async (workspacePath: string) => {
|
||||
// remove trailing slash from workspacePath if exists
|
||||
if (workspacePath.substr(-1) === '/')
|
||||
workspacePath = workspacePath.slice(0, -1);
|
||||
|
||||
const files = await findAllFiles(`${workspacePath}/**/*.md`, {});
|
||||
|
||||
const graph = new NoteGraph();
|
||||
await Promise.all(
|
||||
(await files).map(f => {
|
||||
return fs.promises.readFile(f).then(data => {
|
||||
const markdown = (data || '').toString();
|
||||
const eol = detectNewline(markdown) || os.EOL;
|
||||
graph.setNote(createNoteFromMarkdown(f, markdown, eol));
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
return graph;
|
||||
};
|
||||
18
packages/foam-core/src/janitor/apply-text-edit.ts
Normal file
18
packages/foam-core/src/janitor/apply-text-edit.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { TextEdit } from '../index';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param text text on which the textEdit will be applied
|
||||
* @param textEdit
|
||||
* @returns {string} text with the applied textEdit
|
||||
*/
|
||||
export const applyTextEdit = (text: string, textEdit: TextEdit): string => {
|
||||
const characters = text.split('');
|
||||
const startOffset = textEdit.range.start.offset || 0;
|
||||
const endOffset = textEdit.range.end.offset || 0;
|
||||
const deleteCount = endOffset - startOffset;
|
||||
|
||||
const textToAppend = `${textEdit.newText}`;
|
||||
characters.splice(startOffset, deleteCount, textToAppend);
|
||||
return characters.join('');
|
||||
};
|
||||
109
packages/foam-core/src/janitor/index.ts
Normal file
109
packages/foam-core/src/janitor/index.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { Position } from 'unist';
|
||||
import GithubSlugger from 'github-slugger';
|
||||
import {
|
||||
Note,
|
||||
NoteGraph,
|
||||
LINK_REFERENCE_DEFINITION_HEADER,
|
||||
LINK_REFERENCE_DEFINITION_FOOTER,
|
||||
} from '../index';
|
||||
import {
|
||||
createMarkdownReferences,
|
||||
stringifyMarkdownLinkReferenceDefinition,
|
||||
} from '../markdown-provider';
|
||||
import { getHeadingFromFileName } from '../utils';
|
||||
|
||||
const slugger = new GithubSlugger();
|
||||
|
||||
export interface TextEdit {
|
||||
range: Position;
|
||||
newText: string;
|
||||
}
|
||||
|
||||
export const generateLinkReferences = (
|
||||
note: Note,
|
||||
ng: NoteGraph,
|
||||
includeExtensions: boolean
|
||||
): TextEdit | null => {
|
||||
if (!note) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const markdownReferences = createMarkdownReferences(
|
||||
ng,
|
||||
note.id,
|
||||
includeExtensions
|
||||
);
|
||||
|
||||
const newReferences =
|
||||
markdownReferences.length === 0
|
||||
? ''
|
||||
: [
|
||||
LINK_REFERENCE_DEFINITION_HEADER,
|
||||
...markdownReferences.map(stringifyMarkdownLinkReferenceDefinition),
|
||||
LINK_REFERENCE_DEFINITION_FOOTER,
|
||||
].join(note.eol);
|
||||
|
||||
if (note.definitions.length === 0) {
|
||||
if (newReferences.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const padding = note.end.column === 1 ? note.eol : `${note.eol}${note.eol}`;
|
||||
return {
|
||||
newText: `${padding}${newReferences}`,
|
||||
range: {
|
||||
start: note.end,
|
||||
end: note.end,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const first = note.definitions[0];
|
||||
const last = note.definitions[note.definitions.length - 1];
|
||||
|
||||
const oldReferences = note.definitions
|
||||
.map(stringifyMarkdownLinkReferenceDefinition)
|
||||
.join(note.eol);
|
||||
|
||||
if (oldReferences === newReferences) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
// @todo: do we need to ensure new lines?
|
||||
newText: `${newReferences}`,
|
||||
range: {
|
||||
start: first.position!.start,
|
||||
end: last.position!.end,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const generateHeading = (note: Note): TextEdit | null => {
|
||||
if (!note) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (note.title) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
newText: `# ${getHeadingFromFileName(note.id)}${note.eol}${note.eol}`,
|
||||
range: {
|
||||
start: { line: 0, column: 0, offset: 0 },
|
||||
end: { line: 0, column: 0, offset: 0 },
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param fileName
|
||||
* @returns null if file name is already in kebab case otherise returns
|
||||
* the kebab cased file name
|
||||
*/
|
||||
export const getKebabCaseFileName = (fileName: string) => {
|
||||
const kebabCasedFileName = slugger.slug(fileName);
|
||||
return kebabCasedFileName === fileName ? null : kebabCasedFileName;
|
||||
};
|
||||
@@ -2,9 +2,9 @@ import unified from 'unified';
|
||||
import markdownParse from 'remark-parse';
|
||||
import wikiLinkPlugin from 'remark-wiki-link';
|
||||
import visit, { CONTINUE, EXIT } from 'unist-util-visit';
|
||||
import { Node, Parent } from 'unist';
|
||||
import { Node, Parent, Point } from 'unist';
|
||||
import * as path from 'path';
|
||||
import { Note, NoteLink, NoteGraph } from './note-graph';
|
||||
import { Note, NoteLink, NoteLinkDefinition, NoteGraph } from './note-graph';
|
||||
import { dropExtension } from './utils';
|
||||
|
||||
let processor: unified.Processor | null = null;
|
||||
@@ -18,40 +18,88 @@ function parse(markdown: string): Node {
|
||||
return processor.parse(markdown);
|
||||
}
|
||||
|
||||
export function createNoteFromMarkdown(uri: string, markdown: string): Note {
|
||||
export function createNoteFromMarkdown(
|
||||
uri: string,
|
||||
markdown: string,
|
||||
eol: string
|
||||
): Note {
|
||||
const filename = path.basename(uri);
|
||||
const id = path.parse(filename).name;
|
||||
const tree = parse(markdown);
|
||||
let title = id;
|
||||
let title: string | null = null;
|
||||
visit(tree, node => {
|
||||
if (node.type === 'heading' && node.depth === 1) {
|
||||
title = ((node as Parent)!.children[0].value as string) || title;
|
||||
}
|
||||
return title === id ? CONTINUE : EXIT;
|
||||
return title === null ? CONTINUE : EXIT;
|
||||
});
|
||||
const links: NoteLink[] = [];
|
||||
const linkDefinitions: NoteLinkDefinition[] = [];
|
||||
visit(tree, node => {
|
||||
if (node.type === 'wikiLink') {
|
||||
links.push({
|
||||
to: node.value as string,
|
||||
text: node.value as string,
|
||||
position: node.position!
|
||||
position: node.position!,
|
||||
});
|
||||
}
|
||||
|
||||
if (node.type === 'definition') {
|
||||
linkDefinitions.push({
|
||||
label: node.label as string,
|
||||
url: node.url as string,
|
||||
title: node.title as string,
|
||||
position: node.position,
|
||||
});
|
||||
}
|
||||
});
|
||||
return new Note(id, title, links, uri, markdown);
|
||||
|
||||
const end = tree.position!.end;
|
||||
const definitions = getFoamDefinitions(linkDefinitions, end);
|
||||
|
||||
return new Note(id, title, links, definitions, end, uri, markdown, eol);
|
||||
}
|
||||
|
||||
interface MarkdownReference {
|
||||
linkText: string;
|
||||
wikiLink: string;
|
||||
pageTitle: string;
|
||||
function getFoamDefinitions(
|
||||
defs: NoteLinkDefinition[],
|
||||
fileEndPoint: Point
|
||||
): NoteLinkDefinition[] {
|
||||
let previousLine = fileEndPoint.line;
|
||||
let foamDefinitions = [];
|
||||
|
||||
// walk through each definition in reverse order
|
||||
// (last one first)
|
||||
for (const def of defs.reverse()) {
|
||||
// if this definition is more than 2 lines above the
|
||||
// previous one below it (or file end), that means we
|
||||
// have exited the trailing definition block, and should bail
|
||||
const start = def.position!.start.line;
|
||||
if (start < previousLine - 2) {
|
||||
break;
|
||||
}
|
||||
|
||||
foamDefinitions.unshift(def);
|
||||
previousLine = def.position!.end.line;
|
||||
}
|
||||
|
||||
return foamDefinitions;
|
||||
}
|
||||
|
||||
export function stringifyMarkdownLinkReferenceDefinition(
|
||||
definition: NoteLinkDefinition
|
||||
) {
|
||||
let text = `[${definition.label}]: ${definition.url}`;
|
||||
if (definition.title) {
|
||||
text = `${text} "${definition.title}"`;
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
export function createMarkdownReferences(
|
||||
graph: NoteGraph,
|
||||
noteId: string
|
||||
): MarkdownReference[] {
|
||||
noteId: string,
|
||||
includeExtension: boolean
|
||||
): NoteLinkDefinition[] {
|
||||
const source = graph.getNote(noteId);
|
||||
|
||||
// Should never occur since we're already in a file,
|
||||
@@ -72,7 +120,7 @@ export function createMarkdownReferences(
|
||||
// but int the future we may want to surface these too
|
||||
if (!target) {
|
||||
console.log(
|
||||
`Link '${link.to}' in '${noteId}' points to a non-existing note.`
|
||||
`Warning: Link '${link.to}' in '${noteId}' points to a non-existing note.`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
@@ -81,15 +129,18 @@ export function createMarkdownReferences(
|
||||
path.dirname(source.path),
|
||||
target.path
|
||||
);
|
||||
const relativePathWithoutExtension = dropExtension(relativePath);
|
||||
|
||||
// [wiki-link-text]: wiki-link "Page title"
|
||||
const pathToNote = includeExtension
|
||||
? relativePath
|
||||
: dropExtension(relativePath);
|
||||
|
||||
// [wiki-link-text]: path/to/file.md "Page title"
|
||||
return {
|
||||
linkText: link.to,
|
||||
wikiLink: relativePathWithoutExtension,
|
||||
pageTitle: target.title,
|
||||
label: link.text,
|
||||
url: pathToNote,
|
||||
title: target.title || target.id,
|
||||
};
|
||||
})
|
||||
.filter(Boolean)
|
||||
.sort() as MarkdownReference[];
|
||||
.sort() as NoteLinkDefinition[];
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Graph, Edge } from 'graphlib';
|
||||
import { Position } from 'unist';
|
||||
import { Position, Point } from 'unist';
|
||||
import GithubSlugger from 'github-slugger';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
type ID = string;
|
||||
|
||||
@@ -15,45 +17,70 @@ export interface NoteLink {
|
||||
position: Position;
|
||||
}
|
||||
|
||||
export interface NoteLinkDefinition {
|
||||
label: string;
|
||||
url: string;
|
||||
title?: string;
|
||||
position?: Position;
|
||||
}
|
||||
|
||||
export class Note {
|
||||
public id: ID;
|
||||
public title: string;
|
||||
public title: string | null;
|
||||
public source: string;
|
||||
public path: string;
|
||||
public end: Point;
|
||||
public eol: string;
|
||||
public links: NoteLink[];
|
||||
public definitions: NoteLinkDefinition[];
|
||||
|
||||
constructor(
|
||||
id: ID,
|
||||
title: string,
|
||||
title: string | null,
|
||||
links: NoteLink[],
|
||||
definitions: NoteLinkDefinition[],
|
||||
end: Point,
|
||||
path: string,
|
||||
source: string
|
||||
source: string,
|
||||
eol: string
|
||||
) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.source = source;
|
||||
this.path = path;
|
||||
this.links = links;
|
||||
this.definitions = definitions;
|
||||
this.end = end;
|
||||
this.eol = eol;
|
||||
}
|
||||
}
|
||||
|
||||
export type NoteGraphEventHandler = (e: { note: Note }) => void;
|
||||
|
||||
export class NoteGraph {
|
||||
private graph: Graph;
|
||||
private events: EventEmitter;
|
||||
|
||||
constructor() {
|
||||
this.graph = new Graph();
|
||||
this.events = new EventEmitter();
|
||||
}
|
||||
|
||||
public setNote(note: Note) {
|
||||
if (this.graph.hasNode(note.id)) {
|
||||
const noteExists = this.graph.hasNode(note.id);
|
||||
if (noteExists) {
|
||||
(this.graph.outEdges(note.id) || []).forEach(edge => {
|
||||
this.graph.removeEdge(edge);
|
||||
});
|
||||
}
|
||||
|
||||
this.graph.setNode(note.id, note);
|
||||
note.links.forEach(link => {
|
||||
this.graph.setEdge(note.id, link.to, link.text);
|
||||
const slugger = new GithubSlugger();
|
||||
this.graph.setEdge(note.id, slugger.slug(link.to), link.text);
|
||||
});
|
||||
|
||||
this.events.emit(noteExists ? 'update' : 'add', { note });
|
||||
}
|
||||
|
||||
public getNotes(): Note[] {
|
||||
@@ -69,25 +96,42 @@ export class NoteGraph {
|
||||
|
||||
public getAllLinks(noteId: ID): Link[] {
|
||||
return (this.graph.nodeEdges(noteId) || []).map(edge =>
|
||||
convertEdgeToLink(edge)
|
||||
convertEdgeToLink(edge, this.graph)
|
||||
);
|
||||
}
|
||||
|
||||
public getForwardLinks(noteId: ID): Link[] {
|
||||
return (this.graph.outEdges(noteId) || []).map(edge =>
|
||||
convertEdgeToLink(edge)
|
||||
convertEdgeToLink(edge, this.graph)
|
||||
);
|
||||
}
|
||||
|
||||
public getBacklinks(noteId: ID): Link[] {
|
||||
return (this.graph.inEdges(noteId) || []).map(edge =>
|
||||
convertEdgeToLink(edge)
|
||||
convertEdgeToLink(edge, this.graph)
|
||||
);
|
||||
}
|
||||
|
||||
public unstable_onNoteAdded(callback: NoteGraphEventHandler) {
|
||||
this.events.addListener('add', callback);
|
||||
}
|
||||
|
||||
public unstable_onNoteUpdated(callback: NoteGraphEventHandler) {
|
||||
this.events.addListener('update', callback);
|
||||
}
|
||||
|
||||
public unstable_removeEventListener(callback: NoteGraphEventHandler) {
|
||||
this.events.removeListener('add', callback);
|
||||
this.events.removeListener('update', callback);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.events.removeAllListeners();
|
||||
}
|
||||
}
|
||||
|
||||
const convertEdgeToLink = (edge: Edge): Link => ({
|
||||
const convertEdgeToLink = (edge: Edge, graph: Graph): Link => ({
|
||||
from: edge.v,
|
||||
to: edge.w,
|
||||
text: edge.name || edge.w,
|
||||
text: graph.edge(edge.v, edge.w),
|
||||
});
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
import { titleCase } from 'title-case';
|
||||
|
||||
export function dropExtension(path: string): string {
|
||||
const parts = path.split('.');
|
||||
parts.pop();
|
||||
return parts.join('.');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param filename
|
||||
* @returns title cased heading after removing special characters
|
||||
*/
|
||||
export const getHeadingFromFileName = (filename: string): string => {
|
||||
return titleCase(filename.replace(/[^\w\s]/gi, ' '));
|
||||
};
|
||||
|
||||
3
packages/foam-core/test/__migration__/Roam Document.md
Normal file
3
packages/foam-core/test/__migration__/Roam Document.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Roam Document
|
||||
|
||||
[[Second Roam Document]]
|
||||
@@ -0,0 +1 @@
|
||||
# Second Roam Document
|
||||
@@ -0,0 +1 @@
|
||||
This file is missing a title
|
||||
11
packages/foam-core/test/__scaffold__/first-document.md
Normal file
11
packages/foam-core/test/__scaffold__/first-document.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# First Document
|
||||
|
||||
Here's some [unrelated] content.
|
||||
|
||||
[unrelated]: http://unrelated.com 'This link should not be changed'
|
||||
|
||||
[[file-without-title]]
|
||||
|
||||
[//begin]: # 'Autogenerated link references for markdown compatibility'
|
||||
[second-document]: second-document 'Second Document'
|
||||
[//end]: # 'Autogenerated link references'
|
||||
9
packages/foam-core/test/__scaffold__/index.md
Normal file
9
packages/foam-core/test/__scaffold__/index.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Index
|
||||
|
||||
This file is intentionally missing the link reference definitions
|
||||
|
||||
[[first-document]]
|
||||
|
||||
[[second-document]]
|
||||
|
||||
[[file-without-title]]
|
||||
9
packages/foam-core/test/__scaffold__/second-document.md
Normal file
9
packages/foam-core/test/__scaffold__/second-document.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Second Document
|
||||
|
||||
This is just a link target for now.
|
||||
|
||||
We can use it for other things later if needed.
|
||||
|
||||
[//begin]: # 'Autogenerated link references for markdown compatibility'
|
||||
[first-document]: first-document 'First Document'
|
||||
[//end]: # 'Autogenerated link references'
|
||||
12
packages/foam-core/test/__scaffold__/third-document.md
Normal file
12
packages/foam-core/test/__scaffold__/third-document.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Third Document
|
||||
|
||||
All the link references are correct in this file.
|
||||
|
||||
[[first-document]]
|
||||
|
||||
[[second-document]]
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[first-document]: first-document "First Document"
|
||||
[second-document]: second-document "Second Document"
|
||||
[//end]: # "Autogenerated link references"
|
||||
@@ -1,16 +1,25 @@
|
||||
import { NoteGraph, Note } from '../src/note-graph';
|
||||
|
||||
const position = {
|
||||
start: { line: 0, column: 0},
|
||||
end: { line: 0, column: 0}
|
||||
start: { line: 0, column: 0 },
|
||||
end: { line: 0, column: 0 },
|
||||
};
|
||||
|
||||
const documentEnd = position.end;
|
||||
const eol = '\n';
|
||||
|
||||
describe('Note graph', () => {
|
||||
it('Adds notes to graph', () => {
|
||||
const graph = new NoteGraph();
|
||||
graph.setNote(new Note('page-a', 'page-a', [], '/page-a.md', ''));
|
||||
graph.setNote(new Note('page-b', 'page-b', [], '/page-b.md', ''));
|
||||
graph.setNote(new Note('page-c', 'page-c', [], '/page-c.md', ''));
|
||||
graph.setNote(
|
||||
new Note('page-a', 'page-a', [], [], documentEnd, eol, '/page-a.md', '')
|
||||
);
|
||||
graph.setNote(
|
||||
new Note('page-b', 'page-b', [], [], documentEnd, eol, '/page-b.md', '')
|
||||
);
|
||||
graph.setNote(
|
||||
new Note('page-c', 'page-c', [], [], documentEnd, eol, '/page-c.md', '')
|
||||
);
|
||||
|
||||
expect(
|
||||
graph
|
||||
@@ -22,17 +31,24 @@ describe('Note graph', () => {
|
||||
|
||||
it('Detects forward links', () => {
|
||||
const graph = new NoteGraph();
|
||||
graph.setNote(new Note('page-a', 'page-a', [], '/page-a.md', ''));
|
||||
graph.setNote(
|
||||
new Note('page-a', 'page-a', [], [], documentEnd, eol, '/page-a.md', '')
|
||||
);
|
||||
graph.setNote(
|
||||
new Note(
|
||||
'page-b',
|
||||
'page-b',
|
||||
[{ to: 'page-a', text: 'go', position }],
|
||||
[],
|
||||
documentEnd,
|
||||
eol,
|
||||
'/page-b.md',
|
||||
''
|
||||
)
|
||||
);
|
||||
graph.setNote(new Note('page-c', 'page-c', [], '/page-c.md', ''));
|
||||
graph.setNote(
|
||||
new Note('page-c', 'page-c', [], [], documentEnd, eol, '/page-c.md', '')
|
||||
);
|
||||
|
||||
expect(
|
||||
graph
|
||||
@@ -44,17 +60,24 @@ describe('Note graph', () => {
|
||||
|
||||
it('Detects backlinks', () => {
|
||||
const graph = new NoteGraph();
|
||||
graph.setNote(new Note('page-a', 'page-a', [], '/page-a.md', ''));
|
||||
graph.setNote(
|
||||
new Note('page-a', 'page-a', [], [], documentEnd, eol, '/page-a.md', '')
|
||||
);
|
||||
graph.setNote(
|
||||
new Note(
|
||||
'page-b',
|
||||
'page-b',
|
||||
[{ to: 'page-a', text: 'go', position }],
|
||||
[],
|
||||
documentEnd,
|
||||
eol,
|
||||
'/page-b.md',
|
||||
''
|
||||
)
|
||||
);
|
||||
graph.setNote(new Note('page-c', 'page-c', [], '/page-c.md', ''));
|
||||
graph.setNote(
|
||||
new Note('page-c', 'page-c', [], [], documentEnd, eol, '/page-c.md', '')
|
||||
);
|
||||
|
||||
expect(
|
||||
graph
|
||||
@@ -67,7 +90,9 @@ describe('Note graph', () => {
|
||||
it('Fails when accessing non-existing node', () => {
|
||||
expect(() => {
|
||||
const graph = new NoteGraph();
|
||||
graph.setNote(new Note('page-a', 'page-a', [], '/path-b.md', ''));
|
||||
graph.setNote(
|
||||
new Note('page-a', 'page-a', [], [], documentEnd, eol, '/path-b.md', '')
|
||||
);
|
||||
graph.getNote('non-existing');
|
||||
}).toThrow();
|
||||
});
|
||||
@@ -79,6 +104,9 @@ describe('Note graph', () => {
|
||||
'page-a',
|
||||
'page-a',
|
||||
[{ to: 'non-existing', text: 'does not exist', position }],
|
||||
[],
|
||||
documentEnd,
|
||||
eol,
|
||||
'/path-b.md',
|
||||
''
|
||||
)
|
||||
@@ -88,17 +116,24 @@ describe('Note graph', () => {
|
||||
|
||||
it('Updates links when modifying note', () => {
|
||||
const graph = new NoteGraph();
|
||||
graph.setNote(new Note('page-a', 'page-a', [], '/page-a.md', ''));
|
||||
graph.setNote(
|
||||
new Note('page-a', 'page-a', [], [], documentEnd, eol, '/page-a.md', '')
|
||||
);
|
||||
graph.setNote(
|
||||
new Note(
|
||||
'page-b',
|
||||
'page-b',
|
||||
[{ to: 'page-a', text: 'go', position }],
|
||||
[],
|
||||
documentEnd,
|
||||
eol,
|
||||
'/page-b.md',
|
||||
''
|
||||
)
|
||||
);
|
||||
graph.setNote(new Note('page-c', 'page-c', [], '/page-c.md', ''));
|
||||
graph.setNote(
|
||||
new Note('page-c', 'page-c', [], [], documentEnd, eol, '/page-c.md', '')
|
||||
);
|
||||
|
||||
expect(
|
||||
graph
|
||||
@@ -124,6 +159,9 @@ describe('Note graph', () => {
|
||||
'page-b',
|
||||
'page-b',
|
||||
[{ to: 'page-c', text: 'go', position }],
|
||||
[],
|
||||
documentEnd,
|
||||
eol,
|
||||
'/path-2b.md',
|
||||
''
|
||||
)
|
||||
|
||||
81
packages/foam-core/test/janitor/apply-text-edit.test.ts
Normal file
81
packages/foam-core/test/janitor/apply-text-edit.test.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { applyTextEdit } from '../../src/janitor/apply-text-edit';
|
||||
|
||||
describe('applyTextEdit', () => {
|
||||
it('should return text with applied TextEdit in the end of the string', () => {
|
||||
const textEdit = {
|
||||
newText: `\n 4. this is fourth line`,
|
||||
range: {
|
||||
start: { line: 3, column: 1, offset: 79 },
|
||||
end: { line: 3, column: 1, offset: 79 },
|
||||
},
|
||||
};
|
||||
|
||||
const text = `
|
||||
1. this is first line
|
||||
2. this is second line
|
||||
3. this is third line
|
||||
`;
|
||||
|
||||
const expected = `
|
||||
1. this is first line
|
||||
2. this is second line
|
||||
3. this is third line
|
||||
4. this is fourth line
|
||||
`;
|
||||
|
||||
const actual = applyTextEdit(text, textEdit);
|
||||
|
||||
expect(actual).toBe(expected);
|
||||
});
|
||||
|
||||
it('should return text with applied TextEdit at the top of the string', () => {
|
||||
const textEdit = {
|
||||
newText: `\n 1. this is first line`,
|
||||
range: {
|
||||
start: { line: 0, column: 0, offset: 0 },
|
||||
end: { line: 0, column: 0, offset: 0 },
|
||||
},
|
||||
};
|
||||
|
||||
const text = `
|
||||
2. this is second line
|
||||
3. this is third line
|
||||
`;
|
||||
|
||||
const expected = `
|
||||
1. this is first line
|
||||
2. this is second line
|
||||
3. this is third line
|
||||
`;
|
||||
|
||||
const actual = applyTextEdit(text, textEdit);
|
||||
|
||||
expect(actual).toBe(expected);
|
||||
});
|
||||
|
||||
it('should return text with applied TextEdit in the middle of the string', () => {
|
||||
const textEdit = {
|
||||
newText: `\n 2. this is the updated second line`,
|
||||
range: {
|
||||
start: { line: 0, column: 0, offset: 26 },
|
||||
end: { line: 0, column: 0, offset: 53 },
|
||||
},
|
||||
};
|
||||
|
||||
const text = `
|
||||
1. this is first line
|
||||
2. this is second line
|
||||
3. this is third line
|
||||
`;
|
||||
|
||||
const expected = `
|
||||
1. this is first line
|
||||
2. this is the updated second line
|
||||
3. this is third line
|
||||
`;
|
||||
|
||||
const actual = applyTextEdit(text, textEdit);
|
||||
|
||||
expect(actual).toBe(expected);
|
||||
});
|
||||
});
|
||||
49
packages/foam-core/test/janitor/generateHeadings.test.ts
Normal file
49
packages/foam-core/test/janitor/generateHeadings.test.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import * as path from 'path';
|
||||
import { NoteGraph, Note } from '../../src/note-graph';
|
||||
import { generateHeading } from '../../src/janitor';
|
||||
import { initializeNoteGraph } from '../../src/initialize-note-graph';
|
||||
|
||||
describe('generateHeadings', () => {
|
||||
let _graph: NoteGraph;
|
||||
|
||||
beforeAll(async () => {
|
||||
_graph = await initializeNoteGraph(path.join(__dirname, '../__scaffold__'));
|
||||
});
|
||||
|
||||
it('should add heading to a file that does not have them', () => {
|
||||
const note = _graph.getNote('file-without-title') as Note;
|
||||
const expected = {
|
||||
newText: `# File without Title
|
||||
|
||||
`,
|
||||
range: {
|
||||
start: {
|
||||
line: 0,
|
||||
column: 0,
|
||||
offset: 0,
|
||||
},
|
||||
end: {
|
||||
line: 0,
|
||||
column: 0,
|
||||
offset: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const actual = generateHeading(note!);
|
||||
|
||||
expect(actual!.range.start).toEqual(expected.range.start);
|
||||
expect(actual!.range.end).toEqual(expected.range.end);
|
||||
expect(actual!.newText).toEqual(expected.newText);
|
||||
});
|
||||
|
||||
it('should not cause any changes to a file that has a heading', () => {
|
||||
const note = _graph.getNote('index') as Note;
|
||||
|
||||
const expected = null;
|
||||
|
||||
const actual = generateHeading(note!);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
110
packages/foam-core/test/janitor/generateLinkReferences.test.ts
Normal file
110
packages/foam-core/test/janitor/generateLinkReferences.test.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import * as path from 'path';
|
||||
import { NoteGraph, Note } from '../../src/note-graph';
|
||||
import { generateLinkReferences } from '../../src/janitor';
|
||||
import { initializeNoteGraph } from '../../src/initialize-note-graph';
|
||||
|
||||
describe('generateLinkReferences', () => {
|
||||
let _graph: NoteGraph;
|
||||
|
||||
beforeAll(async () => {
|
||||
_graph = await initializeNoteGraph(path.join(__dirname, '../__scaffold__'));
|
||||
});
|
||||
|
||||
it('initialised test graph correctly', () => {
|
||||
expect(_graph.getNotes().length).toEqual(5);
|
||||
});
|
||||
|
||||
it('should add link references to a file that does not have them', () => {
|
||||
const note = _graph.getNote('index') as Note;
|
||||
const expected = {
|
||||
newText: `
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[first-document]: first-document "First Document"
|
||||
[second-document]: second-document "Second Document"
|
||||
[file-without-title]: file-without-title "file-without-title"
|
||||
[//end]: # "Autogenerated link references"`,
|
||||
range: {
|
||||
start: {
|
||||
line: 10,
|
||||
column: 1,
|
||||
offset: 140,
|
||||
},
|
||||
end: {
|
||||
line: 10,
|
||||
column: 1,
|
||||
offset: 140,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const actual = generateLinkReferences(note!, _graph, false);
|
||||
|
||||
expect(actual!.range.start).toEqual(expected.range.start);
|
||||
expect(actual!.range.end).toEqual(expected.range.end);
|
||||
expect(actual!.newText).toEqual(expected.newText);
|
||||
});
|
||||
|
||||
it('should remove link definitions from a file that has them, if no links are present', () => {
|
||||
const note = _graph.getNote('second-document') as Note;
|
||||
|
||||
const expected = {
|
||||
newText: '',
|
||||
range: {
|
||||
start: {
|
||||
line: 7,
|
||||
column: 1,
|
||||
offset: 105,
|
||||
},
|
||||
end: {
|
||||
line: 9,
|
||||
column: 43,
|
||||
offset: 269,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const actual = generateLinkReferences(note!, _graph, false);
|
||||
|
||||
expect(actual!.range.start).toEqual(expected.range.start);
|
||||
expect(actual!.range.end).toEqual(expected.range.end);
|
||||
expect(actual!.newText).toEqual(expected.newText);
|
||||
});
|
||||
|
||||
it('should update link definitions if they are present but changed', () => {
|
||||
const note = _graph.getNote('first-document') as Note;
|
||||
|
||||
const expected = {
|
||||
newText: `[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[file-without-title]: file-without-title "file-without-title"
|
||||
[//end]: # "Autogenerated link references"`,
|
||||
range: {
|
||||
start: {
|
||||
line: 9,
|
||||
column: 1,
|
||||
offset: 145,
|
||||
},
|
||||
end: {
|
||||
line: 11,
|
||||
column: 43,
|
||||
offset: 312,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const actual = generateLinkReferences(note!, _graph, false);
|
||||
|
||||
expect(actual!.range.start).toEqual(expected.range.start);
|
||||
expect(actual!.range.end).toEqual(expected.range.end);
|
||||
expect(actual!.newText).toEqual(expected.newText);
|
||||
});
|
||||
|
||||
it('should not cause any changes if link reference definitions were up to date', () => {
|
||||
const note = _graph.getNote('third-document') as Note;
|
||||
|
||||
const expected = null;
|
||||
|
||||
const actual = generateLinkReferences(note!, _graph, false);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,8 @@
|
||||
import { createNoteFromMarkdown } from '../src/markdown-provider';
|
||||
import { NoteGraph } from '../src/note-graph';
|
||||
import {
|
||||
createNoteFromMarkdown,
|
||||
createMarkdownReferences,
|
||||
} from '../src/markdown-provider';
|
||||
import { NoteGraph, Note } from '../src/note-graph';
|
||||
|
||||
const pageA = `
|
||||
# Page A
|
||||
@@ -18,12 +21,16 @@ const pageC = `
|
||||
# Page C
|
||||
`;
|
||||
|
||||
const pageD = `
|
||||
This file has no heading.
|
||||
`;
|
||||
|
||||
describe('Markdown loader', () => {
|
||||
it('Converts markdown to notes', () => {
|
||||
const graph = new NoteGraph();
|
||||
graph.setNote(createNoteFromMarkdown('page-a', pageA));
|
||||
graph.setNote(createNoteFromMarkdown('page-b', pageB));
|
||||
graph.setNote(createNoteFromMarkdown('page-c', pageC));
|
||||
graph.setNote(createNoteFromMarkdown('page-a', pageA, '\n'));
|
||||
graph.setNote(createNoteFromMarkdown('page-b', pageB, '\n'));
|
||||
graph.setNote(createNoteFromMarkdown('page-c', pageC, '\n'));
|
||||
|
||||
expect(
|
||||
graph
|
||||
@@ -35,9 +42,9 @@ describe('Markdown loader', () => {
|
||||
|
||||
it('Parses wikilinks correctly', () => {
|
||||
const graph = new NoteGraph();
|
||||
graph.setNote(createNoteFromMarkdown('page-a', pageA));
|
||||
graph.setNote(createNoteFromMarkdown('page-b', pageB));
|
||||
graph.setNote(createNoteFromMarkdown('page-c', pageC));
|
||||
graph.setNote(createNoteFromMarkdown('page-a', pageA, '\n'));
|
||||
graph.setNote(createNoteFromMarkdown('page-b', pageB, '\n'));
|
||||
graph.setNote(createNoteFromMarkdown('page-c', pageC, '\n'));
|
||||
|
||||
expect(graph.getBacklinks('page-b').map(link => link.from)).toEqual([
|
||||
'page-a',
|
||||
@@ -48,3 +55,65 @@ describe('Markdown loader', () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Note Title', () => {
|
||||
it('should initialize note title if heading exists', () => {
|
||||
const graph = new NoteGraph();
|
||||
graph.setNote(createNoteFromMarkdown('page-a', pageA, '\n'));
|
||||
|
||||
const pageANoteTitle = (graph.getNote('page-a') as Note).title;
|
||||
expect(pageANoteTitle).toBe('Page A');
|
||||
});
|
||||
|
||||
it('should not initialize note title if heading does not exist', () => {
|
||||
const graph = new NoteGraph();
|
||||
graph.setNote(createNoteFromMarkdown('page-d', pageD, '\n'));
|
||||
|
||||
const pageANoteTitle = (graph.getNote('page-d') as Note).title;
|
||||
expect(pageANoteTitle).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('wikilinks definitions', () => {
|
||||
it('can generate links without file extension when includeExtension = false', () => {
|
||||
const graph = new NoteGraph();
|
||||
const noteA = createNoteFromMarkdown('dir1/page-a.md', pageA, '\n');
|
||||
const noteB = createNoteFromMarkdown('dir1/page-b.md', pageB, '\n');
|
||||
const noteC = createNoteFromMarkdown('dir1/page-c.md', pageC, '\n');
|
||||
graph.setNote(noteA);
|
||||
graph.setNote(noteB);
|
||||
graph.setNote(noteC);
|
||||
|
||||
const noExtRefs = createMarkdownReferences(graph, noteA.id, false);
|
||||
expect(noExtRefs.map(r => r.url)).toEqual(['page-b', 'page-c']);
|
||||
});
|
||||
|
||||
it('can generate links with file extension when includeExtension = true', () => {
|
||||
const graph = new NoteGraph();
|
||||
const noteA = createNoteFromMarkdown('dir1/page-a.md', pageA, '\n');
|
||||
const noteB = createNoteFromMarkdown('dir1/page-b.md', pageB, '\n');
|
||||
const noteC = createNoteFromMarkdown('dir1/page-c.md', pageC, '\n');
|
||||
graph.setNote(noteA);
|
||||
graph.setNote(noteB);
|
||||
graph.setNote(noteC);
|
||||
|
||||
const extRefs = createMarkdownReferences(graph, noteA.id, true);
|
||||
expect(extRefs.map(r => r.url)).toEqual(['page-b.md', 'page-c.md']);
|
||||
});
|
||||
|
||||
it('use relative paths', () => {
|
||||
const graph = new NoteGraph();
|
||||
const noteA = createNoteFromMarkdown('dir1/page-a.md', pageA, '\n');
|
||||
const noteB = createNoteFromMarkdown('dir2/page-b.md', pageB, '\n');
|
||||
const noteC = createNoteFromMarkdown('dir3/page-c.md', pageC, '\n');
|
||||
graph.setNote(noteA);
|
||||
graph.setNote(noteB);
|
||||
graph.setNote(noteC);
|
||||
|
||||
const extRefs = createMarkdownReferences(graph, noteA.id, true);
|
||||
expect(extRefs.map(r => r.url)).toEqual([
|
||||
'../dir2/page-b.md',
|
||||
'../dir3/page-c.md',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": ["src", "types"],
|
||||
"include": [
|
||||
"src",
|
||||
"types"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"declaration": true,
|
||||
@@ -10,13 +13,14 @@
|
||||
"rootDir": "./src",
|
||||
// for references
|
||||
"baseUrl": "src",
|
||||
"lib": ["esnext"],
|
||||
|
||||
"lib": [
|
||||
"esnext"
|
||||
],
|
||||
"module": "esnext",
|
||||
"importHelpers": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
// "noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
@@ -27,4 +31,4 @@
|
||||
// },
|
||||
// "jsx": "react",
|
||||
},
|
||||
}
|
||||
}
|
||||
2
packages/foam-core/types/utils.d.ts
vendored
2
packages/foam-core/types/utils.d.ts
vendored
@@ -1 +1 @@
|
||||
declare module 'remark-wiki-link';
|
||||
declare module 'remark-wiki-link';
|
||||
@@ -4,6 +4,18 @@ 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.3.0] - 2020-07-25
|
||||
|
||||
New features:
|
||||
|
||||
- [Daily Notes](https://foambubble.github.io/foam/daily-notes)
|
||||
- [Janitor](https://foambubble.github.io/foam/workspace-janitor) for updating headings and link references across your workspace
|
||||
|
||||
Fixes and improvements:
|
||||
|
||||
- [Configuration setting for generating link reference definitions with file extension](https://foambubble.github.io/foam/link-reference-definitions#configuration) to support standard markdown tools, such as GitHub web UI
|
||||
- [Improvements to how new notes are indexed](https://github.com/foambubble/foam/pull/156)
|
||||
|
||||
## [0.2.0] - 2020-07-12
|
||||
|
||||
Improvements:
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "Generate markdown reference lists from wikilinks in a workspace",
|
||||
"author": "Jani Eväkallio",
|
||||
"repository": "https://github.com/foambubble/foam",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"license": "MIT",
|
||||
"publisher": "foam",
|
||||
"engines": {
|
||||
@@ -16,7 +16,8 @@
|
||||
"activationEvents": [
|
||||
"workspaceContains:.vscode/foam.json",
|
||||
"onCommand:foam-vscode.update-wikilinks",
|
||||
"onCommand:foam-vscode.open-daily-note"
|
||||
"onCommand:foam-vscode.open-daily-note",
|
||||
"onCommand:foam-vscode.janitor"
|
||||
],
|
||||
"main": "./out/extension.js",
|
||||
"contributes": {
|
||||
@@ -28,11 +29,27 @@
|
||||
{
|
||||
"command": "foam-vscode.open-daily-note",
|
||||
"title": "Foam: Open Daily Note"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.janitor",
|
||||
"title": "Foam: Run Janitor (Experimental)"
|
||||
}
|
||||
],
|
||||
"configuration": {
|
||||
"title": "Foam",
|
||||
"properties": {
|
||||
"foam.edit.linkReferenceDefinitions": {
|
||||
"type": "string",
|
||||
"default": "withoutExtensions",
|
||||
"enum": [
|
||||
"withExtensions",
|
||||
"withoutExtensions"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"Include extension in wikilinks paths",
|
||||
"Remove extension in wikilink paths"
|
||||
]
|
||||
},
|
||||
"foam.openDailyNote.fileExtension": {
|
||||
"type": "string",
|
||||
"scope": "resource",
|
||||
@@ -71,10 +88,12 @@
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "yarn npm-install && yarn run compile",
|
||||
"compile": "tsc -p ./",
|
||||
"clean": "rimraf out",
|
||||
"build": "tsc -p ./",
|
||||
"test": "echo No tests in VSCode extensions yet",
|
||||
"lint": "eslint src --ext ts",
|
||||
"watch": "tsc -watch -p ./",
|
||||
"vscode:prepublish": "yarn npm-install && yarn run build",
|
||||
"npm-install": "rimraf node_modules && npm i",
|
||||
"npm-cleanup": "rimraf package-lock.json node_modules && yarn",
|
||||
"package-extension": "npx vsce package && yarn npm-cleanup",
|
||||
@@ -95,6 +114,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"dateformat": "^3.0.3",
|
||||
"foam-core": "^0.2.0"
|
||||
"foam-core": "0.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,37 +5,82 @@
|
||||
"use strict";
|
||||
|
||||
import * as fs from "fs";
|
||||
import { workspace, ExtensionContext } from "vscode";
|
||||
import {
|
||||
workspace,
|
||||
ExtensionContext,
|
||||
window,
|
||||
EndOfLine,
|
||||
Uri,
|
||||
FileSystemWatcher
|
||||
} from "vscode";
|
||||
|
||||
import {
|
||||
createNoteFromMarkdown,
|
||||
createFoam,
|
||||
FoamConfig,
|
||||
Foam
|
||||
} from "foam-core";
|
||||
|
||||
import { createNoteFromMarkdown, createFoam, FoamConfig } from "foam-core";
|
||||
import { features } from "./features";
|
||||
|
||||
let workspaceWatcher: FileSystemWatcher | null = null;
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
const foamPromise = bootstrap(getConfig())
|
||||
const foamPromise = bootstrap(getConfig());
|
||||
features.forEach(f => {
|
||||
f.activate(context, foamPromise);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
export function deactivate() {
|
||||
workspaceWatcher?.dispose();
|
||||
}
|
||||
|
||||
function isLocalMarkdownFile(uri: Uri) {
|
||||
return uri.scheme === "file" && uri.path.match(/\.(md|mdx|markdown)/i);
|
||||
}
|
||||
|
||||
async function registerFile(foam: Foam, localUri: Uri) {
|
||||
// read file from disk (async)
|
||||
const path = localUri.fsPath;
|
||||
const data = await fs.promises.readFile(path);
|
||||
const markdown = (data || "").toString();
|
||||
|
||||
// create note
|
||||
const eol =
|
||||
window.activeTextEditor?.document?.eol === EndOfLine.CRLF ? "\r\n" : "\n";
|
||||
const note = createNoteFromMarkdown(path, markdown, eol);
|
||||
|
||||
// add to graph
|
||||
foam.notes.setNote(note);
|
||||
return note;
|
||||
}
|
||||
|
||||
const bootstrap = async (config: FoamConfig) => {
|
||||
const files = await workspace.findFiles("**/*");
|
||||
const foam = createFoam(config);
|
||||
await Promise.all(
|
||||
files
|
||||
.filter(f => f.scheme === "file" && f.path.match(/\.(md|mdx|markdown)/i))
|
||||
.map(f => {
|
||||
return fs.promises.readFile(f.fsPath).then(data => {
|
||||
const markdown = (data || "").toString();
|
||||
foam.notes.setNote(createNoteFromMarkdown(f.fsPath, markdown));
|
||||
});
|
||||
})
|
||||
const addFile = (uri: Uri) => registerFile(foam, uri);
|
||||
|
||||
await Promise.all(files.filter(isLocalMarkdownFile).map(addFile));
|
||||
|
||||
workspaceWatcher = workspace.createFileSystemWatcher(
|
||||
"**/*",
|
||||
false,
|
||||
true,
|
||||
true
|
||||
);
|
||||
|
||||
workspaceWatcher.onDidCreate(uri => {
|
||||
if (isLocalMarkdownFile(uri)) {
|
||||
addFile(uri).then(() => {
|
||||
console.log(`Added ${uri} to workspace`);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return foam;
|
||||
};
|
||||
|
||||
const getConfig = () => {
|
||||
return {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const getConfig = () => {
|
||||
return {};
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import createReferences from "./wikilink-reference-generation";
|
||||
import openDailyNote from "./open-daily-note";
|
||||
import janitor from "./janitor";
|
||||
import { FoamFeature } from "../types";
|
||||
|
||||
export const features: FoamFeature[] = [createReferences, openDailyNote];
|
||||
export const features: FoamFeature[] = [createReferences, openDailyNote, janitor];
|
||||
|
||||
173
packages/foam-vscode/src/features/janitor.ts
Normal file
173
packages/foam-vscode/src/features/janitor.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import {
|
||||
window,
|
||||
workspace,
|
||||
ExtensionContext,
|
||||
commands,
|
||||
Position,
|
||||
Range
|
||||
} from "vscode";
|
||||
import fs = require("fs");
|
||||
import { FoamFeature } from "../types";
|
||||
import {
|
||||
applyTextEdit,
|
||||
generateLinkReferences,
|
||||
generateHeading,
|
||||
Foam,
|
||||
Note
|
||||
} from "foam-core";
|
||||
|
||||
import { includeExtensions } from "../settings";
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: async (context: ExtensionContext, foamPromise: Promise<Foam>) => {
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand("foam-vscode.janitor", async () =>
|
||||
janitor(await foamPromise)
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
async function janitor(foam: Foam) {
|
||||
try {
|
||||
const outcome = await runJanitor(foam);
|
||||
if (outcome.processedFileCount === 0) {
|
||||
window.showInformationMessage(
|
||||
"Foam Janitor didn't file any notes to clean up"
|
||||
);
|
||||
} else if (!outcome.changedAnyFiles) {
|
||||
window.showInformationMessage(
|
||||
`Foam Janitor checked ${outcome.processedFileCount} files, and found nothing to clean up!`
|
||||
);
|
||||
} else {
|
||||
window.showInformationMessage(
|
||||
`Foam Janitor checked ${outcome.processedFileCount} files and updated ${outcome.updatedDefinitionListCount} out-of-date definition lists and added ${outcome.updatedHeadingCount} missing headings. Please check the changes before committing them into version control!`
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
window.showErrorMessage(`Foam Janitor attempted to clean your workspace but ran into an error. Please check that we didn't break anything before committing any changes to version control, and pass the following error message to the Foam team on GitHub issues:
|
||||
${e.message}
|
||||
${e.stackTrace}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function runJanitor(foam: Foam) {
|
||||
const notes = foam.notes.getNotes().filter(Boolean);
|
||||
|
||||
let processedFileCount = 0;
|
||||
let updatedHeadingCount = 0;
|
||||
let updatedDefinitionListCount = 0;
|
||||
|
||||
const dirtyTextDocuments = workspace.textDocuments.filter(
|
||||
textDocument =>
|
||||
(textDocument.languageId === "markdown" ||
|
||||
textDocument.languageId === "mdx") &&
|
||||
textDocument.isDirty
|
||||
);
|
||||
|
||||
const dirtyEditorsFileName = dirtyTextDocuments.map(
|
||||
dirtyTextDocument => dirtyTextDocument.fileName
|
||||
);
|
||||
|
||||
const dirtyNotes: Note[] = notes.filter(note =>
|
||||
dirtyEditorsFileName.includes(note.path)
|
||||
);
|
||||
|
||||
const nonDirtyNotes: Note[] = notes.filter(
|
||||
note => !dirtyEditorsFileName.includes(note.path)
|
||||
);
|
||||
|
||||
// Apply Text Edits to Non Dirty Notes using fs module just like CLI
|
||||
|
||||
const fileWritePromises = nonDirtyNotes.map(note => {
|
||||
processedFileCount += 1;
|
||||
|
||||
let heading = generateHeading(note);
|
||||
if (heading) {
|
||||
console.log("fs.write heading " + note.path + " " + note.title);
|
||||
updatedHeadingCount += 1;
|
||||
}
|
||||
|
||||
let definitions = generateLinkReferences(
|
||||
note,
|
||||
foam.notes,
|
||||
includeExtensions()
|
||||
);
|
||||
if (definitions) {
|
||||
updatedDefinitionListCount += 1;
|
||||
}
|
||||
|
||||
if (!heading && !definitions) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Apply Edits
|
||||
// Note: The ordering matters. Definitions need to be inserted
|
||||
// before heading, since inserting a heading changes line numbers below
|
||||
let text = note.source;
|
||||
text = definitions ? applyTextEdit(text, definitions) : text;
|
||||
text = heading ? applyTextEdit(text, heading) : text;
|
||||
|
||||
return fs.promises.writeFile(note.path, text);
|
||||
});
|
||||
|
||||
await Promise.all(fileWritePromises);
|
||||
|
||||
// Handle dirty editors in serial, as VSCode only allows
|
||||
// edits to be applied to active text editors
|
||||
for (const doc of dirtyTextDocuments) {
|
||||
processedFileCount += 1;
|
||||
|
||||
const editor = await window.showTextDocument(doc);
|
||||
const note = dirtyNotes.find(n => n.path === editor.document.fileName);
|
||||
|
||||
// Get edits
|
||||
const heading = generateHeading(note);
|
||||
let definitions = generateLinkReferences(
|
||||
note,
|
||||
foam.notes,
|
||||
includeExtensions()
|
||||
);
|
||||
|
||||
if (heading || definitions) {
|
||||
// Apply Edits
|
||||
await editor.edit(editBuilder => {
|
||||
// Note: The ordering matters. Definitions need to be inserted
|
||||
// before heading, since inserting a heading changes line numbers below
|
||||
if (definitions) {
|
||||
updatedDefinitionListCount += 1;
|
||||
const start = new Position(
|
||||
definitions.range.start.line - 1,
|
||||
definitions.range.start.column
|
||||
);
|
||||
const end = new Position(
|
||||
definitions.range.end.line - 1,
|
||||
definitions.range.end.column
|
||||
);
|
||||
const range = new Range(start, end);
|
||||
editBuilder.replace(range, definitions!.newText);
|
||||
}
|
||||
|
||||
if (heading) {
|
||||
console.log("editor.write heading " + note.title);
|
||||
|
||||
updatedHeadingCount += 1;
|
||||
const start = new Position(
|
||||
heading.range.start.line,
|
||||
heading.range.start.column
|
||||
);
|
||||
editBuilder.insert(start, heading.newText);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
updatedHeadingCount,
|
||||
updatedDefinitionListCount,
|
||||
processedFileCount,
|
||||
changedAnyFiles: updatedHeadingCount + updatedDefinitionListCount
|
||||
};
|
||||
}
|
||||
|
||||
export default feature;
|
||||
@@ -5,11 +5,13 @@ import {
|
||||
WorkspaceConfiguration,
|
||||
ExtensionContext,
|
||||
commands,
|
||||
Selection,
|
||||
} from "vscode";
|
||||
import { dirname, join } from "path";
|
||||
import dateFormat = require("dateformat");
|
||||
import fs = require("fs");
|
||||
import { FoamFeature } from "../types";
|
||||
import { docConfig } from '../utils';
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: async (context: ExtensionContext) => {
|
||||
@@ -25,8 +27,8 @@ async function openDailyNote() {
|
||||
|
||||
const dailyNotePath = getDailyNotePath(foamConfiguration, currentDate);
|
||||
|
||||
createDailyNoteIfNotExists(foamConfiguration, dailyNotePath, currentDate);
|
||||
await focusDailyNote(dailyNotePath);
|
||||
const isNew = await createDailyNoteIfNotExists(foamConfiguration, dailyNotePath, currentDate);
|
||||
await focusDailyNote(dailyNotePath, isNew);
|
||||
}
|
||||
|
||||
function getDailyNotePath(configuration: WorkspaceConfiguration, date: Date) {
|
||||
@@ -56,9 +58,9 @@ async function createDailyNoteIfNotExists(
|
||||
configuration: WorkspaceConfiguration,
|
||||
dailyNotePath: string,
|
||||
currentDate: Date
|
||||
) {
|
||||
) {
|
||||
if (await pathExists(dailyNotePath)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
createDailyNoteDirectoryIfNotExists(dailyNotePath);
|
||||
@@ -69,8 +71,10 @@ async function createDailyNoteIfNotExists(
|
||||
|
||||
await fs.promises.writeFile(
|
||||
dailyNotePath,
|
||||
`# ${dateFormat(currentDate, titleFormat, false)}\r\n`
|
||||
`# ${dateFormat(currentDate, titleFormat, false)}${docConfig.eol}${docConfig.eol}`
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async function createDailyNoteDirectoryIfNotExists(dailyNotePath: string) {
|
||||
@@ -81,9 +85,16 @@ async function createDailyNoteDirectoryIfNotExists(dailyNotePath: string) {
|
||||
}
|
||||
}
|
||||
|
||||
async function focusDailyNote(dailyNotePath: string) {
|
||||
async function focusDailyNote(dailyNotePath: string, isNewNote: boolean) {
|
||||
const document = await workspace.openTextDocument(Uri.parse(dailyNotePath));
|
||||
window.showTextDocument(document);
|
||||
const editor = await window.showTextDocument(document);
|
||||
|
||||
// Move the cursor to end of the file
|
||||
if (isNewNote) {
|
||||
const { lineCount } = editor.document;
|
||||
const { range } = editor.document.lineAt(lineCount - 1);
|
||||
editor.selection = new Selection(range.end, range.end);
|
||||
}
|
||||
}
|
||||
|
||||
async function pathExists(path: string) {
|
||||
|
||||
@@ -13,24 +13,40 @@ import {
|
||||
Position
|
||||
} from "vscode";
|
||||
|
||||
import { createMarkdownReferences, createNoteFromMarkdown, NoteGraph, Foam } from "foam-core";
|
||||
import {
|
||||
createMarkdownReferences,
|
||||
stringifyMarkdownLinkReferenceDefinition,
|
||||
createNoteFromMarkdown,
|
||||
NoteGraph,
|
||||
Foam,
|
||||
LINK_REFERENCE_DEFINITION_HEADER,
|
||||
LINK_REFERENCE_DEFINITION_FOOTER
|
||||
} from "foam-core";
|
||||
import { basename } from "path";
|
||||
import { hasEmptyTrailing, docConfig, loadDocConfig, isMdEditor, mdDocSelector, getText, dropExtension } from "../utils";
|
||||
import {
|
||||
hasEmptyTrailing,
|
||||
docConfig,
|
||||
loadDocConfig,
|
||||
isMdEditor,
|
||||
mdDocSelector,
|
||||
getText,
|
||||
dropExtension
|
||||
} from "../utils";
|
||||
import { FoamFeature } from "../types";
|
||||
import { includeExtensions } from "../settings";
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: async (context: ExtensionContext, foamPromise: Promise<Foam>) => {
|
||||
activate: async (context: ExtensionContext, foamPromise: Promise<Foam>) => {
|
||||
const foam = await foamPromise;
|
||||
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand(
|
||||
"foam-vscode.update-wikilinks",
|
||||
() => updateReferenceList(foam.notes)
|
||||
commands.registerCommand("foam-vscode.update-wikilinks", () =>
|
||||
updateReferenceList(foam.notes)
|
||||
),
|
||||
|
||||
workspace.onWillSaveTextDocument(e => {
|
||||
if (e.document.languageId === "markdown") {
|
||||
foam.notes.setNote(
|
||||
createNoteFromMarkdown(e.document.fileName, e.document.getText())
|
||||
);
|
||||
updateDocumentInNoteGraph(foam, e.document);
|
||||
e.waitUntil(updateReferenceList(foam.notes));
|
||||
}
|
||||
}),
|
||||
@@ -39,22 +55,37 @@ const feature: FoamFeature = {
|
||||
new WikilinkReferenceCodeLensProvider(foam.notes)
|
||||
)
|
||||
);
|
||||
|
||||
// when a file is created as a result of peekDefinition
|
||||
// action on a wikilink, add definition update references
|
||||
foam.notes.unstable_onNoteAdded(e => {
|
||||
let editor = window.activeTextEditor;
|
||||
if (!editor || !isMdEditor(editor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateDocumentInNoteGraph(foam, editor.document);
|
||||
updateReferenceList(foam.notes);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const REFERENCE_HEADER = `[//begin]: # "Autogenerated link references for markdown compatibility"`;
|
||||
const REFERENCE_FOOTER = `[//end]: # "Autogenerated link references"`;
|
||||
function updateDocumentInNoteGraph(foam: Foam, document: TextDocument) {
|
||||
foam.notes.setNote(
|
||||
createNoteFromMarkdown(document.fileName, document.getText(), docConfig.eol)
|
||||
);
|
||||
}
|
||||
|
||||
async function createReferenceList(foam: NoteGraph) {
|
||||
let editor = window.activeTextEditor;
|
||||
|
||||
if (!editor || !isMdEditor(editor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let refs = await generateReferenceList(foam, editor.document);
|
||||
if (refs && refs.length) {
|
||||
await editor.edit(function(editBuilder) {
|
||||
await editor.edit(function (editBuilder) {
|
||||
if (editor) {
|
||||
const spacing = hasEmptyTrailing
|
||||
? docConfig.eol
|
||||
@@ -97,19 +128,27 @@ async function updateReferenceList(foam: NoteGraph) {
|
||||
}
|
||||
}
|
||||
|
||||
async function generateReferenceList(foam: NoteGraph, doc: TextDocument): Promise<string[]> {
|
||||
|
||||
async function generateReferenceList(
|
||||
foam: NoteGraph,
|
||||
doc: TextDocument
|
||||
): Promise<string[]> {
|
||||
const filePath = doc.fileName;
|
||||
|
||||
const id = dropExtension(basename(filePath));
|
||||
|
||||
const references = uniq(
|
||||
createMarkdownReferences(foam, id).map(
|
||||
link => `[${link.linkText}]: ${link.wikiLink} "${link.pageTitle}"`
|
||||
createMarkdownReferences(foam, id, includeExtensions()).map(
|
||||
stringifyMarkdownLinkReferenceDefinition
|
||||
)
|
||||
);
|
||||
|
||||
if (references.length) {
|
||||
return [REFERENCE_HEADER, ...references, REFERENCE_FOOTER];
|
||||
return [
|
||||
LINK_REFERENCE_DEFINITION_HEADER,
|
||||
...references,
|
||||
LINK_REFERENCE_DEFINITION_FOOTER
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
@@ -122,8 +161,8 @@ async function generateReferenceList(foam: NoteGraph, doc: TextDocument): Promis
|
||||
function detectReferenceListRange(doc: TextDocument): Range {
|
||||
const fullText = doc.getText();
|
||||
|
||||
const headerIndex = fullText.indexOf(REFERENCE_HEADER);
|
||||
const footerIndex = fullText.lastIndexOf(REFERENCE_FOOTER);
|
||||
const headerIndex = fullText.indexOf(LINK_REFERENCE_DEFINITION_HEADER);
|
||||
const footerIndex = fullText.lastIndexOf(LINK_REFERENCE_DEFINITION_FOOTER);
|
||||
|
||||
if (headerIndex < 0) {
|
||||
return null;
|
||||
@@ -141,15 +180,15 @@ function detectReferenceListRange(doc: TextDocument): Range {
|
||||
|
||||
return new Range(
|
||||
new Position(headerLine, 0),
|
||||
new Position(footerLine, REFERENCE_FOOTER.length)
|
||||
new Position(footerLine, LINK_REFERENCE_DEFINITION_FOOTER.length)
|
||||
);
|
||||
}
|
||||
|
||||
class WikilinkReferenceCodeLensProvider implements CodeLensProvider {
|
||||
private foam: NoteGraph
|
||||
private foam: NoteGraph;
|
||||
|
||||
constructor(foam: NoteGraph) {
|
||||
this.foam = foam
|
||||
this.foam = foam;
|
||||
}
|
||||
|
||||
public provideCodeLenses(
|
||||
|
||||
18
packages/foam-vscode/src/settings.ts
Normal file
18
packages/foam-vscode/src/settings.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { workspace } from 'vscode';
|
||||
|
||||
export enum LinkReferenceDefinitionsSetting {
|
||||
withExtensions = "withExtensions",
|
||||
withoutExtensions = "withoutExtensions"
|
||||
};
|
||||
|
||||
export function includeExtensions() {
|
||||
const linkDefinitionSetting: LinkReferenceDefinitionsSetting =
|
||||
workspace
|
||||
.getConfiguration("foam.edit")
|
||||
.get<LinkReferenceDefinitionsSetting>("linkReferenceDefinitions") ??
|
||||
LinkReferenceDefinitionsSetting.withoutExtensions;
|
||||
|
||||
return linkDefinitionSetting === LinkReferenceDefinitionsSetting.withExtensions;
|
||||
}
|
||||
|
||||
|
||||
58
readme.md
58
readme.md
@@ -7,7 +7,7 @@
|
||||
# Foam
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
[](#contributors-)
|
||||
[](#contributors-)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
**Foam** is a personal knowledge management and sharing system inspired by [Roam Research](https://roamresearch.com/), built on [Visual Studio Code](https://code.visualstudio.com/) and [GitHub](https://github.com/).
|
||||
@@ -58,37 +58,43 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<!-- markdownlint-disable -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><a href="https://jevakallio.dev/"><img src="https://avatars1.githubusercontent.com/u/1203949?v=4" width="60px;" alt=""/><br /><sub><b>Jani Eväkallio</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jevakallio" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jevakallio" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://joeprevite.com/"><img src="https://avatars3.githubusercontent.com/u/3806031?v=4" width="60px;" alt=""/><br /><sub><b>Joe Previte</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jsjoeio" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jsjoeio" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/riccardoferretti"><img src="https://avatars3.githubusercontent.com/u/457005?v=4" width="60px;" alt=""/><br /><sub><b>Riccardo</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=riccardoferretti" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=riccardoferretti" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://ojanaho.com/"><img src="https://avatars0.githubusercontent.com/u/2180090?v=4" width="60px;" alt=""/><br /><sub><b>Janne Ojanaho</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jojanaho" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jojanaho" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://bypaulshen.com/"><img src="https://avatars3.githubusercontent.com/u/2266187?v=4" width="60px;" alt=""/><br /><sub><b>Paul Shen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=paulshen" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/coffenbacher"><img src="https://avatars0.githubusercontent.com/u/245867?v=4" width="60px;" alt=""/><br /><sub><b>coffenbacher</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=coffenbacher" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://mathieu.dutour.me/"><img src="https://avatars2.githubusercontent.com/u/3254314?v=4" width="60px;" alt=""/><br /><sub><b>Mathieu Dutour</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=mathieudutour" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://jevakallio.dev/"><img src="https://avatars1.githubusercontent.com/u/1203949?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Jani Eväkallio</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jevakallio" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jevakallio" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://joeprevite.com/"><img src="https://avatars3.githubusercontent.com/u/3806031?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Joe Previte</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jsjoeio" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jsjoeio" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/riccardoferretti"><img src="https://avatars3.githubusercontent.com/u/457005?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Riccardo</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=riccardoferretti" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=riccardoferretti" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://ojanaho.com/"><img src="https://avatars0.githubusercontent.com/u/2180090?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Janne Ojanaho</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jojanaho" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jojanaho" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://bypaulshen.com/"><img src="https://avatars3.githubusercontent.com/u/2266187?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Paul Shen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=paulshen" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/coffenbacher"><img src="https://avatars0.githubusercontent.com/u/245867?v=4?s=60" width="60px;" alt=""/><br /><sub><b>coffenbacher</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=coffenbacher" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://mathieu.dutour.me/"><img src="https://avatars2.githubusercontent.com/u/3254314?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Mathieu Dutour</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=mathieudutour" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/presidentelect"><img src="https://avatars2.githubusercontent.com/u/1242300?v=4" width="60px;" alt=""/><br /><sub><b>Michael Hansen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=presidentelect" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://klickverbot.at/"><img src="https://avatars1.githubusercontent.com/u/19335?v=4" width="60px;" alt=""/><br /><sub><b>David Nadlinger</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dnadlinger" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://pluckd.co/"><img src="https://avatars2.githubusercontent.com/u/20598571?v=4" width="60px;" alt=""/><br /><sub><b>Fernando</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MrCordeiro" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/jfgonzalez7"><img src="https://avatars3.githubusercontent.com/u/58857736?v=4" width="60px;" alt=""/><br /><sub><b>Juan Gonzalez</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jfgonzalez7" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.louiechristie.com/"><img src="https://avatars1.githubusercontent.com/u/6807448?v=4" width="60px;" alt=""/><br /><sub><b>Louie Christie</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=louiechristie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://supersandro.de/"><img src="https://avatars2.githubusercontent.com/u/7258858?v=4" width="60px;" alt=""/><br /><sub><b>Sandro</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=SuperSandro2000" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Skn0tt"><img src="https://avatars1.githubusercontent.com/u/14912729?v=4" width="60px;" alt=""/><br /><sub><b>Simon Knott</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Skn0tt" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/presidentelect"><img src="https://avatars2.githubusercontent.com/u/1242300?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Michael Hansen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=presidentelect" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://klickverbot.at/"><img src="https://avatars1.githubusercontent.com/u/19335?v=4?s=60" width="60px;" alt=""/><br /><sub><b>David Nadlinger</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dnadlinger" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://pluckd.co/"><img src="https://avatars2.githubusercontent.com/u/20598571?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Fernando</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MrCordeiro" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/jfgonzalez7"><img src="https://avatars3.githubusercontent.com/u/58857736?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Juan Gonzalez</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jfgonzalez7" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.louiechristie.com/"><img src="https://avatars1.githubusercontent.com/u/6807448?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Louie Christie</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=louiechristie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://supersandro.de/"><img src="https://avatars2.githubusercontent.com/u/7258858?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Sandro</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=SuperSandro2000" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Skn0tt"><img src="https://avatars1.githubusercontent.com/u/14912729?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Simon Knott</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Skn0tt" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://styfle.dev/"><img src="https://avatars1.githubusercontent.com/u/229881?v=4" width="60px;" alt=""/><br /><sub><b>Steven</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=styfle" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Georift"><img src="https://avatars2.githubusercontent.com/u/859430?v=4" width="60px;" alt=""/><br /><sub><b>Tim</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Georift" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/sauravkhdoolia"><img src="https://avatars1.githubusercontent.com/u/34188267?v=4" width="60px;" alt=""/><br /><sub><b>Saurav Khdoolia</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=sauravkhdoolia" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://anku.netlify.com/"><img src="https://avatars1.githubusercontent.com/u/22813027?v=4" width="60px;" alt=""/><br /><sub><b>Ankit Tiwari</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=anku255" title="Documentation">📖</a> <a href="https://github.com/foambubble/foam/commits?author=anku255" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/ayushbaweja"><img src="https://avatars1.githubusercontent.com/u/44344063?v=4" width="60px;" alt=""/><br /><sub><b>Ayush Baweja</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ayushbaweja" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/TaiChi-IO"><img src="https://avatars3.githubusercontent.com/u/65092992?v=4" width="60px;" alt=""/><br /><sub><b>TaiChi-IO</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=TaiChi-IO" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/juanfrank77"><img src="https://avatars1.githubusercontent.com/u/12146882?v=4" width="60px;" alt=""/><br /><sub><b>Juan F Gonzalez </b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=juanfrank77" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://styfle.dev/"><img src="https://avatars1.githubusercontent.com/u/229881?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Steven</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=styfle" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Georift"><img src="https://avatars2.githubusercontent.com/u/859430?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Tim</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Georift" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/sauravkhdoolia"><img src="https://avatars1.githubusercontent.com/u/34188267?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Saurav Khdoolia</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=sauravkhdoolia" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://anku.netlify.com/"><img src="https://avatars1.githubusercontent.com/u/22813027?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Ankit Tiwari</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=anku255" title="Documentation">📖</a> <a href="https://github.com/foambubble/foam/commits?author=anku255" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/ayushbaweja"><img src="https://avatars1.githubusercontent.com/u/44344063?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Ayush Baweja</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ayushbaweja" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/TaiChi-IO"><img src="https://avatars3.githubusercontent.com/u/65092992?v=4?s=60" width="60px;" alt=""/><br /><sub><b>TaiChi-IO</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=TaiChi-IO" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/juanfrank77"><img src="https://avatars1.githubusercontent.com/u/12146882?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Juan F Gonzalez </b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=juanfrank77" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://sanketdg.github.io"><img src="https://avatars3.githubusercontent.com/u/8980971?v=4" width="60px;" alt=""/><br /><sub><b>Sanket Dasgupta</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=SanketDG" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/nstafie"><img src="https://avatars1.githubusercontent.com/u/10801854?v=4" width="60px;" alt=""/><br /><sub><b>Nicholas Stafie</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=nstafie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/francishamel"><img src="https://avatars3.githubusercontent.com/u/36383308?v=4" width="60px;" alt=""/><br /><sub><b>Francis Hamel</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=francishamel" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://digiguru.co.uk"><img src="https://avatars1.githubusercontent.com/u/619436?v=4" width="60px;" alt=""/><br /><sub><b>digiguru</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=digiguru" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=digiguru" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://sanketdg.github.io"><img src="https://avatars3.githubusercontent.com/u/8980971?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Sanket Dasgupta</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=SanketDG" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/nstafie"><img src="https://avatars1.githubusercontent.com/u/10801854?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Nicholas Stafie</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=nstafie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/francishamel"><img src="https://avatars3.githubusercontent.com/u/36383308?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Francis Hamel</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=francishamel" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://digiguru.co.uk"><img src="https://avatars1.githubusercontent.com/u/619436?v=4?s=60" width="60px;" alt=""/><br /><sub><b>digiguru</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=digiguru" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=digiguru" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/chirag-singhal"><img src="https://avatars3.githubusercontent.com/u/42653703?v=4?s=60" width="60px;" alt=""/><br /><sub><b>CHIRAG SINGHAL</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=chirag-singhal" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/lostintangent"><img src="https://avatars3.githubusercontent.com/u/116461?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Jonathan Carter</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=lostintangent" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.synesthesia.co.uk"><img src="https://avatars3.githubusercontent.com/u/181399?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Julian Elve</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=synesthesia" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/thomaskoppelaar"><img src="https://avatars3.githubusercontent.com/u/36331365?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Thomas Koppelaar</b></sub></a><br /><a href="#question-thomaskoppelaar" title="Answering Questions">💬</a> <a href="https://github.com/foambubble/foam/commits?author=thomaskoppelaar" title="Code">💻</a> <a href="#userTesting-thomaskoppelaar" title="User Testing">📓</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user