mirror of
https://github.com/foambubble/foam.git
synced 2026-01-11 06:58:11 -05:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a5f45c0ce | ||
|
|
df32d9e708 | ||
|
|
b3d4691bfa | ||
|
|
f5260f7d3f | ||
|
|
9b4b7ec84d | ||
|
|
52b7f86a9f | ||
|
|
2db7060124 | ||
|
|
a4f04b3b6b | ||
|
|
b5a8a5d7c7 | ||
|
|
f5a29e431c | ||
|
|
5a7a1ba89f | ||
|
|
b054bafc78 | ||
|
|
8acb60253a | ||
|
|
3c69508dcb | ||
|
|
a368be9b47 |
@@ -1004,6 +1004,24 @@
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Skakerman",
|
||||
"name": "Scott Akerman",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/15224439?v=4",
|
||||
"profile": "http://scottakerman.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "jimgraham",
|
||||
"name": "Jim Graham",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/430293?v=4",
|
||||
"profile": "http://www.jim-graham.net/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
43
.github/workflows/update-docs.yml
vendored
Normal file
43
.github/workflows/update-docs.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: Update Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- docs/user/**/*
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update-docs:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: foambubble/foam-template
|
||||
path: foam-template
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
path: foam
|
||||
- name: Copy and fixup user docs files
|
||||
id: copy
|
||||
run: |
|
||||
rm -r foam-template/docs
|
||||
cp -r foam/docs/user foam-template/docs
|
||||
|
||||
# Strip autogenerated wikileaks references because
|
||||
# they are not an appropriate default user experience.
|
||||
(cd foam-template/docs; sed -i '/\[\/\/begin\]/,/\[\/\/end\]/d' $(find . -type f -name \*.md))
|
||||
|
||||
# Set the commit message format
|
||||
echo "message=Docs sync @ $(cd foam; git log --pretty='format:%h %s')" >> $GITHUB_OUTPUT
|
||||
- uses: peter-evans/create-pull-request@v4
|
||||
with:
|
||||
token: ${{ secrets.FOAM_DOCS_SYNC_TOKEN }}
|
||||
path: foam-template
|
||||
commit-message: ${{ steps.copy.outputs.message }}
|
||||
branch: bot/foam-docs-sync
|
||||
delete-branch: true
|
||||
title: Sync docs from foam
|
||||
body: Copy docs from main foam repo
|
||||
@@ -247,6 +247,8 @@ If that sounds like something you're interested in, I'd love to have you along o
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://jonathanpberger.com/"><img src="https://avatars.githubusercontent.com/u/41085?v=4?s=60" width="60px;" alt="jonathan berger"/><br /><sub><b>jonathan berger</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jonathanpberger" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/badsketch"><img src="https://avatars.githubusercontent.com/u/8953212?v=4?s=60" width="60px;" alt="Daniel Wang"/><br /><sub><b>Daniel Wang</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=badsketch" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://yongliangliu.com"><img src="https://avatars.githubusercontent.com/u/41845017?v=4?s=60" width="60px;" alt="Liu YongLiang"/><br /><sub><b>Liu YongLiang</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=tlylt" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://scottakerman.com"><img src="https://avatars.githubusercontent.com/u/15224439?v=4?s=60" width="60px;" alt="Scott Akerman"/><br /><sub><b>Scott Akerman</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Skakerman" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.jim-graham.net/"><img src="https://avatars.githubusercontent.com/u/430293?v=4?s=60" width="60px;" alt="Jim Graham"/><br /><sub><b>Jim Graham</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jimgraham" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
- [Frequently Asked Questions](#frequently-asked-questions)
|
||||
- [Links/Graphs/BackLinks don't work. How do I enable them?](#linksgraphsbacklinks-dont-work-how-do-i-enable-them)
|
||||
- [I don't want Foam enabled for all my workspaces](#i-dont-want-foam-enabled-for-all-my-workspaces)
|
||||
- [I want to publish the graph view to GitHub pages or Vercel](#i-want-to-publish-the-graph-view-to-github-pages-or-vercel)
|
||||
|
||||
## Links/Graphs/BackLinks don't work. How do I enable them?
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# Using Foam
|
||||
|
||||
Foam is a collection VS Code extensions and recipes that power up the editor into a full-blown note taking system.
|
||||
This folder contains user documentation describing how to get started using Foam, what its main features are, and strategies for getting the most out of Foam.
|
||||
The full docs are included in the `foam-template` repo that most users start from.
|
||||
Foam is a collection VS Code extensions and recipes that power up the editor
|
||||
into a full-blown note taking system. This folder contains user documentation
|
||||
describing how to get started using Foam, what its main features are, and
|
||||
strategies for getting the most out of Foam. The full docs are included in the
|
||||
`foam-template` repo that most users start from.
|
||||
|
||||
> See also [[frequently-asked-questions]].
|
||||
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
],
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.21.0"
|
||||
"version": "0.21.2"
|
||||
}
|
||||
|
||||
@@ -4,6 +4,25 @@ 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.21.2] - 2023-04-11
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Fixed embed with relative paths (#1168, #1170)
|
||||
- Improved multi-root folder support for daily notes (#1126, #1175)
|
||||
- Improved use of tag completion (#1183 - thanks @jimgraham)
|
||||
- Fixed relative path use in note creation when using templates (#1170)
|
||||
|
||||
Internal:
|
||||
|
||||
- Sync user docs with foam-template docs (#1180 - thanks @infogulch)
|
||||
|
||||
## [0.21.1] - 2023-02-24
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Fixed note creation from placeholder (#1172)
|
||||
|
||||
## [0.21.0] - 2023-02-16
|
||||
|
||||
Features:
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"type": "git"
|
||||
},
|
||||
"homepage": "https://github.com/foambubble/foam",
|
||||
"version": "0.21.0",
|
||||
"version": "0.21.2",
|
||||
"license": "MIT",
|
||||
"publisher": "foam",
|
||||
"engines": {
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import os from 'os';
|
||||
import detectNewline from 'detect-newline';
|
||||
import { Position } from '../model/position';
|
||||
import { Range } from '../model/range';
|
||||
|
||||
export interface TextEdit {
|
||||
range: Range;
|
||||
newText: string;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param text text on which the textEdit will be applied
|
||||
* @param textEdit
|
||||
* @returns {string} text with the applied textEdit
|
||||
*/
|
||||
export const applyTextEdit = (text: string, textEdit: TextEdit): string => {
|
||||
const eol = detectNewline(text) || os.EOL;
|
||||
const lines = text.split(eol);
|
||||
const characters = text.split('');
|
||||
const startOffset = getOffset(lines, textEdit.range.start, eol);
|
||||
const endOffset = getOffset(lines, textEdit.range.end, eol);
|
||||
const deleteCount = endOffset - startOffset;
|
||||
|
||||
const textToAppend = `${textEdit.newText}`;
|
||||
characters.splice(startOffset, deleteCount, textToAppend);
|
||||
return characters.join('');
|
||||
};
|
||||
|
||||
const getOffset = (
|
||||
lines: string[],
|
||||
position: Position,
|
||||
eol: string
|
||||
): number => {
|
||||
const eolLen = eol.length;
|
||||
let offset = 0;
|
||||
let i = 0;
|
||||
while (i < position.line && i < lines.length) {
|
||||
offset = offset + lines[i].length + eolLen;
|
||||
i++;
|
||||
}
|
||||
return offset + Math.min(position.character, lines[i]?.length ?? 0);
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import matter from 'gray-matter';
|
||||
import { TextEdit } from './apply-text-edit';
|
||||
import { Resource } from '../model/note';
|
||||
import { Range } from '../model/range';
|
||||
import { TextEdit } from '../services/text-edit';
|
||||
import { getHeadingFromFileName } from '../utils';
|
||||
|
||||
export const generateHeading = async (
|
||||
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
stringifyMarkdownLinkReferenceDefinition,
|
||||
} from '../services/markdown-provider';
|
||||
import { FoamWorkspace } from '../model/workspace';
|
||||
import { TextEdit } from './apply-text-edit';
|
||||
import { Position } from '../model/position';
|
||||
import { TextEdit } from '../services/text-edit';
|
||||
|
||||
export const LINK_REFERENCE_DEFINITION_HEADER = `[//begin]: # "Autogenerated link references for markdown compatibility"`;
|
||||
export const LINK_REFERENCE_DEFINITION_FOOTER = `[//end]: # "Autogenerated link references"`;
|
||||
|
||||
@@ -5,6 +5,7 @@ export interface ResourceLink {
|
||||
type: 'wikilink' | 'link';
|
||||
rawText: string;
|
||||
range: Range;
|
||||
isEmbed: boolean;
|
||||
}
|
||||
|
||||
export interface NoteLinkDefinition {
|
||||
|
||||
@@ -107,6 +107,7 @@ describe('MarkdownLink', () => {
|
||||
type: 'link',
|
||||
rawText: '[link](#section)',
|
||||
range: Range.create(0, 0),
|
||||
isEmbed: false,
|
||||
};
|
||||
const parsed = MarkdownLink.analyzeLink(link);
|
||||
expect(parsed.target).toEqual('');
|
||||
@@ -161,7 +162,7 @@ describe('MarkdownLink', () => {
|
||||
target: 'new-link',
|
||||
});
|
||||
expect(edit.newText).toEqual(`[[new-link#section]]`);
|
||||
expect(edit.selection).toEqual(link.range);
|
||||
expect(edit.range).toEqual(link.range);
|
||||
});
|
||||
it('should rename the section only', () => {
|
||||
const link = parser.parse(
|
||||
@@ -172,7 +173,7 @@ describe('MarkdownLink', () => {
|
||||
section: 'new-section',
|
||||
});
|
||||
expect(edit.newText).toEqual(`[[wikilink#new-section]]`);
|
||||
expect(edit.selection).toEqual(link.range);
|
||||
expect(edit.range).toEqual(link.range);
|
||||
});
|
||||
it('should rename both target and section', () => {
|
||||
const link = parser.parse(
|
||||
@@ -184,7 +185,7 @@ describe('MarkdownLink', () => {
|
||||
section: 'new-section',
|
||||
});
|
||||
expect(edit.newText).toEqual(`[[new-link#new-section]]`);
|
||||
expect(edit.selection).toEqual(link.range);
|
||||
expect(edit.range).toEqual(link.range);
|
||||
});
|
||||
it('should be able to remove the section', () => {
|
||||
const link = parser.parse(
|
||||
@@ -195,7 +196,7 @@ describe('MarkdownLink', () => {
|
||||
section: '',
|
||||
});
|
||||
expect(edit.newText).toEqual(`[[wikilink]]`);
|
||||
expect(edit.selection).toEqual(link.range);
|
||||
expect(edit.range).toEqual(link.range);
|
||||
});
|
||||
it('should be able to rename the alias', () => {
|
||||
const link = parser.parse(getRandomURI(), `this is a [[wikilink|alias]]`)
|
||||
@@ -204,7 +205,7 @@ describe('MarkdownLink', () => {
|
||||
alias: 'new-alias',
|
||||
});
|
||||
expect(edit.newText).toEqual(`[[wikilink|new-alias]]`);
|
||||
expect(edit.selection).toEqual(link.range);
|
||||
expect(edit.range).toEqual(link.range);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -216,7 +217,7 @@ describe('MarkdownLink', () => {
|
||||
target: 'to/another-path.md',
|
||||
});
|
||||
expect(edit.newText).toEqual(`[link](to/another-path.md)`);
|
||||
expect(edit.selection).toEqual(link.range);
|
||||
expect(edit.range).toEqual(link.range);
|
||||
});
|
||||
it('should rename the section only', () => {
|
||||
const link = parser.parse(
|
||||
@@ -227,7 +228,7 @@ describe('MarkdownLink', () => {
|
||||
section: 'section2',
|
||||
});
|
||||
expect(edit.newText).toEqual(`[link](to/path.md#section2)`);
|
||||
expect(edit.selection).toEqual(link.range);
|
||||
expect(edit.range).toEqual(link.range);
|
||||
});
|
||||
it('should rename both target and section', () => {
|
||||
const link = parser.parse(
|
||||
@@ -239,7 +240,7 @@ describe('MarkdownLink', () => {
|
||||
section: 'section2',
|
||||
});
|
||||
expect(edit.newText).toEqual(`[link](to/another-path.md#section2)`);
|
||||
expect(edit.selection).toEqual(link.range);
|
||||
expect(edit.range).toEqual(link.range);
|
||||
});
|
||||
it('should be able to remove the section', () => {
|
||||
const link = parser.parse(
|
||||
@@ -250,7 +251,7 @@ describe('MarkdownLink', () => {
|
||||
section: '',
|
||||
});
|
||||
expect(edit.newText).toEqual(`[link](to/path.md)`);
|
||||
expect(edit.selection).toEqual(link.range);
|
||||
expect(edit.range).toEqual(link.range);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -46,16 +46,17 @@ export abstract class MarkdownLink {
|
||||
const newAlias = delta.alias ?? alias ?? '';
|
||||
const sectionDivider = newSection ? '#' : '';
|
||||
const aliasDivider = newAlias ? '|' : '';
|
||||
const embed = link.isEmbed ? '!' : '';
|
||||
if (link.type === 'wikilink') {
|
||||
return {
|
||||
newText: `[[${newTarget}${sectionDivider}${newSection}${aliasDivider}${newAlias}]]`,
|
||||
selection: link.range,
|
||||
newText: `${embed}[[${newTarget}${sectionDivider}${newSection}${aliasDivider}${newAlias}]]`,
|
||||
range: link.range,
|
||||
};
|
||||
}
|
||||
if (link.type === 'link') {
|
||||
return {
|
||||
newText: `[${newAlias}](${newTarget}${sectionDivider}${newSection})`,
|
||||
selection: link.range,
|
||||
newText: `${embed}[${newAlias}](${newTarget}${sectionDivider}${newSection})`,
|
||||
range: link.range,
|
||||
};
|
||||
}
|
||||
throw new Error(
|
||||
|
||||
@@ -39,6 +39,7 @@ describe('Markdown parsing', () => {
|
||||
const link = note.links[0];
|
||||
expect(link.type).toEqual('link');
|
||||
expect(link.rawText).toEqual('[link to page b](../doc/page-b.md)');
|
||||
expect(link.isEmbed).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should detect links that have formatting in label', () => {
|
||||
@@ -48,6 +49,15 @@ describe('Markdown parsing', () => {
|
||||
expect(note.links.length).toEqual(1);
|
||||
const link = note.links[0];
|
||||
expect(link.type).toEqual('link');
|
||||
expect(link.isEmbed).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should detect embed links', () => {
|
||||
const note = createNoteFromMarkdown('this is ');
|
||||
expect(note.links.length).toEqual(1);
|
||||
const link = note.links[0];
|
||||
expect(link.type).toEqual('link');
|
||||
expect(link.isEmbed).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should detect wikilinks', () => {
|
||||
@@ -61,6 +71,16 @@ describe('Markdown parsing', () => {
|
||||
link = note.links[1];
|
||||
expect(link.type).toEqual('wikilink');
|
||||
expect(link.rawText).toEqual('[[a file]]');
|
||||
expect(link.isEmbed).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should detect wikilink embeds', () => {
|
||||
const note = createNoteFromMarkdown('Some content and ![[an embed]]');
|
||||
expect(note.links.length).toEqual(1);
|
||||
const link = note.links[0];
|
||||
expect(link.type).toEqual('wikilink');
|
||||
expect(link.rawText).toEqual('![[an embed]]');
|
||||
expect(link.isEmbed).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should detect wikilinks that have aliases', () => {
|
||||
@@ -74,6 +94,7 @@ describe('Markdown parsing', () => {
|
||||
link = note.links[1];
|
||||
expect(link.type).toEqual('wikilink');
|
||||
expect(link.rawText).toEqual('[[other link | spaced]]');
|
||||
expect(link.isEmbed).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should skip wikilinks in codeblocks', () => {
|
||||
|
||||
@@ -299,18 +299,33 @@ const wikilinkPlugin: ParserPlugin = {
|
||||
name: 'wikilink',
|
||||
visit: (node, note, noteSource) => {
|
||||
if (node.type === 'wikiLink') {
|
||||
const isEmbed =
|
||||
noteSource.charAt(node.position!.start.offset - 1) === '!';
|
||||
|
||||
const literalContent = noteSource.substring(
|
||||
node.position!.start.offset!,
|
||||
isEmbed
|
||||
? node.position!.start.offset! - 1
|
||||
: node.position!.start.offset!,
|
||||
node.position!.end.offset!
|
||||
);
|
||||
|
||||
const range = isEmbed
|
||||
? Range.create(
|
||||
node.position.start.line - 1,
|
||||
node.position.start.column - 2,
|
||||
node.position.end.line - 1,
|
||||
node.position.end.column - 1
|
||||
)
|
||||
: astPositionToFoamRange(node.position!);
|
||||
|
||||
note.links.push({
|
||||
type: 'wikilink',
|
||||
rawText: literalContent,
|
||||
range: astPositionToFoamRange(node.position!),
|
||||
range,
|
||||
isEmbed,
|
||||
});
|
||||
}
|
||||
if (node.type === 'link') {
|
||||
if (node.type === 'link' || node.type === 'image') {
|
||||
const targetUri = (node as any).url;
|
||||
const uri = note.uri.resolve(targetUri);
|
||||
if (uri.scheme !== 'file' || uri.path === note.uri.path) {
|
||||
@@ -324,6 +339,7 @@ const wikilinkPlugin: ParserPlugin = {
|
||||
type: 'link',
|
||||
rawText: literalContent,
|
||||
range: astPositionToFoamRange(node.position!),
|
||||
isEmbed: literalContent.startsWith('!'),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Range } from '../model/range';
|
||||
import { Logger } from '../utils/log';
|
||||
import { applyTextEdit } from './apply-text-edit';
|
||||
import { TextEdit } from './text-edit';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
@@ -23,7 +23,7 @@ describe('applyTextEdit', () => {
|
||||
3. this is third line
|
||||
4. this is fourth line`;
|
||||
|
||||
const actual = applyTextEdit(text, textEdit);
|
||||
const actual = TextEdit.apply(text, textEdit);
|
||||
|
||||
expect(actual).toBe(expected);
|
||||
});
|
||||
@@ -45,7 +45,7 @@ describe('applyTextEdit', () => {
|
||||
3. this is third line
|
||||
`;
|
||||
|
||||
const actual = applyTextEdit(text, textEdit);
|
||||
const actual = TextEdit.apply(text, textEdit);
|
||||
|
||||
expect(actual).toBe(expected);
|
||||
});
|
||||
@@ -68,7 +68,7 @@ describe('applyTextEdit', () => {
|
||||
3. this is third line
|
||||
`;
|
||||
|
||||
const actual = applyTextEdit(text, textEdit);
|
||||
const actual = TextEdit.apply(text, textEdit);
|
||||
|
||||
expect(actual).toBe(expected);
|
||||
});
|
||||
44
packages/foam-vscode/src/core/services/text-edit.ts
Normal file
44
packages/foam-vscode/src/core/services/text-edit.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import detectNewline from 'detect-newline';
|
||||
import { Position } from '../model/position';
|
||||
import { Range } from '../model/range';
|
||||
|
||||
export interface TextEdit {
|
||||
range: Range;
|
||||
newText: string;
|
||||
}
|
||||
|
||||
export abstract class TextEdit {
|
||||
/**
|
||||
*
|
||||
* @param text text on which the textEdit will be applied
|
||||
* @param textEdit
|
||||
* @returns {string} text with the applied textEdit
|
||||
*/
|
||||
public static apply(text: string, textEdit: TextEdit): string {
|
||||
const eol = detectNewline.graceful(text);
|
||||
const lines = text.split(eol);
|
||||
const characters = text.split('');
|
||||
const startOffset = getOffset(lines, textEdit.range.start, eol);
|
||||
const endOffset = getOffset(lines, textEdit.range.end, eol);
|
||||
const deleteCount = endOffset - startOffset;
|
||||
|
||||
const textToAppend = `${textEdit.newText}`;
|
||||
characters.splice(startOffset, deleteCount, textToAppend);
|
||||
return characters.join('');
|
||||
}
|
||||
}
|
||||
|
||||
const getOffset = (
|
||||
lines: string[],
|
||||
position: Position,
|
||||
eol: string
|
||||
): number => {
|
||||
const eolLen = eol.length;
|
||||
let offset = 0;
|
||||
let i = 0;
|
||||
while (i < position.line && i < lines.length) {
|
||||
offset = offset + lines[i].length + eolLen;
|
||||
i++;
|
||||
}
|
||||
return offset + Math.min(position.character, lines[i]?.length ?? 0);
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import { isSome } from './core';
|
||||
const HASHTAG_REGEX =
|
||||
export const HASHTAG_REGEX =
|
||||
/(?<=^|\s)#([0-9]*[\p{L}\p{Emoji_Presentation}/_-][\p{L}\p{Emoji_Presentation}\p{N}/_-]*)/gmu;
|
||||
const WORD_REGEX =
|
||||
/(?<=^|\s)([0-9]*[\p{L}\p{Emoji_Presentation}/_-][\p{L}\p{Emoji_Presentation}\p{N}/_-]*)/gmu;
|
||||
|
||||
@@ -86,9 +86,7 @@ export async function createDailyNoteIfNotExists(targetDate: Date) {
|
||||
|
||||
const templateFallbackText = `---
|
||||
foam_template:
|
||||
filepath: "${workspace.asRelativePath(
|
||||
toVsCodeUri(pathFromLegacyConfiguration)
|
||||
)}"
|
||||
filepath: "${pathFromLegacyConfiguration.toFsPath()}"
|
||||
---
|
||||
# ${dateFormat(targetDate, titleFormat, false)}
|
||||
`;
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
showInEditor,
|
||||
} from '../../test/test-utils-vscode';
|
||||
import { fromVsCodeUri } from '../../utils/vsc-utils';
|
||||
import { CREATE_NOTE_COMMAND } from './create-note';
|
||||
|
||||
describe('create-note command', () => {
|
||||
afterEach(() => {
|
||||
@@ -188,3 +189,17 @@ describe('create-note command', () => {
|
||||
await deleteFile(base);
|
||||
});
|
||||
});
|
||||
|
||||
describe('factories', () => {
|
||||
describe('forPlaceholder', () => {
|
||||
it('adds the .md extension to notes created for placeholders', async () => {
|
||||
await closeEditors();
|
||||
const command = CREATE_NOTE_COMMAND.forPlaceholder('my-placeholder');
|
||||
await commands.executeCommand(command.name, command.params);
|
||||
|
||||
const doc = window.activeTextEditor.document;
|
||||
expect(doc.uri.path).toMatch(/my-placeholder.md$/);
|
||||
expect(doc.getText()).toMatch(/^# my-placeholder/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -114,11 +114,17 @@ export const CREATE_NOTE_COMMAND = {
|
||||
placeholder: string,
|
||||
extra: Partial<CreateNoteArgs> = {}
|
||||
): CommandDescriptor<CreateNoteArgs> => {
|
||||
const title = placeholder.endsWith('.md')
|
||||
? placeholder.replace(/\.md$/, '')
|
||||
: placeholder;
|
||||
const notePath = placeholder.endsWith('.md')
|
||||
? placeholder
|
||||
: placeholder + '.md';
|
||||
return {
|
||||
name: CREATE_NOTE_COMMAND.command,
|
||||
params: {
|
||||
title: placeholder,
|
||||
notePath: placeholder,
|
||||
title,
|
||||
notePath,
|
||||
...extra,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -20,8 +20,8 @@ import { Foam } from '../../core/model/foam';
|
||||
import { Resource } from '../../core/model/note';
|
||||
import { generateHeading, generateLinkReferences } from '../../core/janitor';
|
||||
import { Range } from '../../core/model/range';
|
||||
import { applyTextEdit } from '../../core/janitor/apply-text-edit';
|
||||
import detectNewline from 'detect-newline';
|
||||
import { TextEdit } from '../../core/services/text-edit';
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: (context: ExtensionContext, foamPromise: Promise<Foam>) => {
|
||||
@@ -130,8 +130,8 @@ async function runJanitor(foam: Foam) {
|
||||
// Note: The ordering matters. Definitions need to be inserted
|
||||
// before heading, since inserting a heading changes line numbers below
|
||||
let text = noteText;
|
||||
text = definitions ? applyTextEdit(text, definitions) : text;
|
||||
text = heading ? applyTextEdit(text, heading) : text;
|
||||
text = definitions ? TextEdit.apply(text, definitions) : text;
|
||||
text = heading ? TextEdit.apply(text, heading) : text;
|
||||
|
||||
return workspace.fs.writeFile(toVsCodeUri(note.uri), Buffer.from(text));
|
||||
});
|
||||
|
||||
@@ -22,7 +22,11 @@ const feature: FoamFeature = {
|
||||
markdownItFoamTags,
|
||||
markdownItWikilinkNavigation,
|
||||
markdownItRemoveLinkReferences,
|
||||
].reduce((acc, extension) => extension(acc, foam.workspace), md);
|
||||
].reduce(
|
||||
(acc, extension) =>
|
||||
extension(acc, foam.workspace, foam.services.parser),
|
||||
md
|
||||
);
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -24,7 +24,7 @@ describe('Displaying included notes in preview', () => {
|
||||
CONFIG_EMBED_NOTE_IN_CONTAINER,
|
||||
false,
|
||||
() => {
|
||||
const md = markdownItWikilinkEmbed(MarkdownIt(), ws);
|
||||
const md = markdownItWikilinkEmbed(MarkdownIt(), ws, parser);
|
||||
|
||||
expect(
|
||||
md.render(`This is the root node.
|
||||
@@ -51,7 +51,7 @@ describe('Displaying included notes in preview', () => {
|
||||
CONFIG_EMBED_NOTE_IN_CONTAINER,
|
||||
true,
|
||||
() => {
|
||||
const md = markdownItWikilinkEmbed(MarkdownIt(), ws);
|
||||
const md = markdownItWikilinkEmbed(MarkdownIt(), ws, parser);
|
||||
|
||||
const res = md.render(`This is the root node. ![[note-a]]`);
|
||||
expect(res).toContain('This is the root node');
|
||||
@@ -68,19 +68,19 @@ describe('Displaying included notes in preview', () => {
|
||||
const note = await createFile(
|
||||
`
|
||||
# Section 1
|
||||
This is the first section of note D
|
||||
This is the first section of note E
|
||||
|
||||
# Section 2
|
||||
This is the second section of note D
|
||||
This is the second section of note E
|
||||
|
||||
# Section 3
|
||||
This is the third section of note D
|
||||
This is the third section of note E
|
||||
`,
|
||||
['note-e.md']
|
||||
);
|
||||
const parser = createMarkdownParser([]);
|
||||
const ws = new FoamWorkspace().set(parser.parse(note.uri, note.content));
|
||||
const md = markdownItWikilinkEmbed(MarkdownIt(), ws);
|
||||
const md = markdownItWikilinkEmbed(MarkdownIt(), ws, parser);
|
||||
|
||||
await withModifiedFoamConfiguration(
|
||||
CONFIG_EMBED_NOTE_IN_CONTAINER,
|
||||
@@ -93,7 +93,7 @@ This is the third section of note D
|
||||
).toMatch(
|
||||
`<p>This is the root node.</p>
|
||||
<p><h1>Section 2</h1>
|
||||
<p>This is the second section of note D</p>
|
||||
<p>This is the second section of note E</p>
|
||||
</p>`
|
||||
);
|
||||
}
|
||||
@@ -102,8 +102,48 @@ This is the third section of note D
|
||||
await deleteFile(note);
|
||||
});
|
||||
|
||||
it('should render an included section in container mode', async () => {
|
||||
const note = await createFile(
|
||||
`
|
||||
# Section 1
|
||||
This is the first section of note E
|
||||
|
||||
# Section 2
|
||||
This is the second section of note E
|
||||
|
||||
# Section 3
|
||||
This is the third section of note E
|
||||
`,
|
||||
['note-e-container.md']
|
||||
);
|
||||
const parser = createMarkdownParser([]);
|
||||
const ws = new FoamWorkspace().set(parser.parse(note.uri, note.content));
|
||||
|
||||
await withModifiedFoamConfiguration(
|
||||
CONFIG_EMBED_NOTE_IN_CONTAINER,
|
||||
true,
|
||||
() => {
|
||||
const md = markdownItWikilinkEmbed(MarkdownIt(), ws, parser);
|
||||
|
||||
const res = md.render(
|
||||
`This is the root node. ![[note-e-container#Section 3]]`
|
||||
);
|
||||
expect(res).toContain('This is the root node');
|
||||
expect(res).toContain('embed-container-note');
|
||||
expect(res).toContain('Section 3');
|
||||
expect(res).toContain('This is the third section of note E');
|
||||
}
|
||||
);
|
||||
|
||||
await deleteFile(note);
|
||||
});
|
||||
|
||||
it('should fallback to the bare text when the note is not found', () => {
|
||||
const md = markdownItWikilinkEmbed(MarkdownIt(), new FoamWorkspace());
|
||||
const md = markdownItWikilinkEmbed(
|
||||
MarkdownIt(),
|
||||
new FoamWorkspace(),
|
||||
parser
|
||||
);
|
||||
|
||||
expect(md.render(`This is the root node. ![[non-existing-note]]`)).toMatch(
|
||||
`<p>This is the root node. ![[non-existing-note]]</p>`
|
||||
@@ -122,12 +162,12 @@ This is the third section of note D
|
||||
const ws = new FoamWorkspace()
|
||||
.set(parser.parse(noteA.uri, noteA.content))
|
||||
.set(parser.parse(noteB.uri, noteB.content));
|
||||
const md = markdownItWikilinkEmbed(MarkdownIt(), ws);
|
||||
const md = markdownItWikilinkEmbed(MarkdownIt(), ws, parser);
|
||||
const res = md.render(noteBText);
|
||||
|
||||
expect(res).toContain('This is the text of note B which includes');
|
||||
expect(res).toContain('This is the text of note A which includes');
|
||||
expect(res).toContain('Cyclic link detected for wikilink: note-a');
|
||||
expect(res).toContain('Cyclic link detected for wikilink');
|
||||
|
||||
deleteFile(noteA);
|
||||
deleteFile(noteB);
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
/*global markdownit:readonly*/
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { readFileSync } from 'fs';
|
||||
import { workspace as vsWorkspace } from 'vscode';
|
||||
import markdownItRegex from 'markdown-it-regex';
|
||||
import { isSome } from '../../utils';
|
||||
import { FoamWorkspace } from '../../core/model/workspace';
|
||||
import { Logger } from '../../core/utils/log';
|
||||
import { Resource } from '../../core/model/note';
|
||||
import { Resource, ResourceParser } from '../../core/model/note';
|
||||
import { getFoamVsCodeConfig } from '../../services/config';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { readFileSync } from 'fs';
|
||||
import { fromVsCodeUri, toVsCodeUri } from '../../utils/vsc-utils';
|
||||
import { MarkdownLink } from '../../core/services/markdown-link';
|
||||
import { Position } from '../../core/model/position';
|
||||
import { TextEdit } from '../../core/services/text-edit';
|
||||
|
||||
export const CONFIG_EMBED_NOTE_IN_CONTAINER = 'preview.embedNoteInContainer';
|
||||
const refsStack: string[] = [];
|
||||
|
||||
export const markdownItWikilinkEmbed = (
|
||||
md: markdownit,
|
||||
workspace: FoamWorkspace
|
||||
workspace: FoamWorkspace,
|
||||
parser: ResourceParser
|
||||
) => {
|
||||
return md.use(markdownItRegex, {
|
||||
name: 'embed-wikilinks',
|
||||
@@ -41,9 +47,22 @@ export const markdownItWikilinkEmbed = (
|
||||
let content = `Embed for [[${wikilink}]]`;
|
||||
switch (includedNote.type) {
|
||||
case 'note': {
|
||||
const noteText = readFileSync(
|
||||
includedNote.uri.toFsPath()
|
||||
).toString();
|
||||
let noteText = readFileSync(includedNote.uri.toFsPath()).toString();
|
||||
const section = Resource.findSection(
|
||||
includedNote,
|
||||
includedNote.uri.fragment
|
||||
);
|
||||
if (isSome(section)) {
|
||||
const rows = noteText.split('\n');
|
||||
noteText = rows
|
||||
.slice(section.range.start.line, section.range.end.line)
|
||||
.join('\n');
|
||||
}
|
||||
noteText = withLinksRelativeToWorkspaceRoot(
|
||||
noteText,
|
||||
parser,
|
||||
workspace
|
||||
);
|
||||
content = getFoamVsCodeConfig(CONFIG_EMBED_NOTE_IN_CONTAINER)
|
||||
? `<div class="embed-container-note">${md.render(noteText)}</div>`
|
||||
: noteText;
|
||||
@@ -62,16 +81,6 @@ Embed for attachments is not supported
|
||||
)}</div>`;
|
||||
break;
|
||||
}
|
||||
const section = Resource.findSection(
|
||||
includedNote,
|
||||
includedNote.uri.fragment
|
||||
);
|
||||
if (isSome(section)) {
|
||||
const rows = content.split('\n');
|
||||
content = rows
|
||||
.slice(section.range.start.line, section.range.end.line)
|
||||
.join('\n');
|
||||
}
|
||||
const html = md.render(content);
|
||||
refsStack.pop();
|
||||
return html;
|
||||
@@ -86,4 +95,32 @@ Embed for attachments is not supported
|
||||
});
|
||||
};
|
||||
|
||||
function withLinksRelativeToWorkspaceRoot(
|
||||
noteText: string,
|
||||
parser: ResourceParser,
|
||||
workspace: FoamWorkspace
|
||||
) {
|
||||
const note = parser.parse(
|
||||
fromVsCodeUri(vsWorkspace.workspaceFolders[0].uri),
|
||||
noteText
|
||||
);
|
||||
const edits = note.links
|
||||
.map(link => {
|
||||
const info = MarkdownLink.analyzeLink(link);
|
||||
const resource = workspace.find(info.target);
|
||||
const pathFromRoot = vsWorkspace.asRelativePath(
|
||||
toVsCodeUri(resource.uri)
|
||||
);
|
||||
return MarkdownLink.createUpdateLinkEdit(link, {
|
||||
target: pathFromRoot,
|
||||
});
|
||||
})
|
||||
.sort((a, b) => Position.compareTo(b.range.start, a.range.start));
|
||||
const text = edits.reduce(
|
||||
(text, edit) => TextEdit.apply(text, edit),
|
||||
noteText
|
||||
);
|
||||
return text;
|
||||
}
|
||||
|
||||
export default markdownItWikilinkEmbed;
|
||||
|
||||
@@ -23,6 +23,7 @@ export const markdownItWikilinkNavigation = (
|
||||
rawText: '[[' + wikilink + ']]',
|
||||
type: 'wikilink',
|
||||
range: Range.create(0, 0),
|
||||
isEmbed: false,
|
||||
});
|
||||
const formattedSection = section ? `#${section}` : '';
|
||||
const label = isEmpty(alias) ? `${target}${formattedSection}` : alias;
|
||||
|
||||
@@ -45,7 +45,7 @@ const feature: FoamFeature = {
|
||||
);
|
||||
renameEdits.replace(
|
||||
toVsCodeUri(connection.source),
|
||||
toVsCodeRange(edit.selection),
|
||||
toVsCodeRange(edit.range),
|
||||
edit.newText
|
||||
);
|
||||
break;
|
||||
@@ -62,7 +62,7 @@ const feature: FoamFeature = {
|
||||
);
|
||||
renameEdits.replace(
|
||||
toVsCodeUri(connection.source),
|
||||
toVsCodeRange(edit.selection),
|
||||
toVsCodeRange(edit.range),
|
||||
edit.newText
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -93,4 +93,18 @@ describe('Tag Completion', () => {
|
||||
expect(foamTags.tags.get('primary')).toBeTruthy();
|
||||
expect(tags).toBeNull();
|
||||
});
|
||||
|
||||
it('should not provide suggestions when inside a markdown heading #1182', async () => {
|
||||
const { uri } = await createFile('# primary heading 1');
|
||||
const { doc } = await showInEditor(uri);
|
||||
const provider = new TagCompletionProvider(foamTags);
|
||||
|
||||
const tags = await provider.provideCompletionItems(
|
||||
doc,
|
||||
new vscode.Position(0, 7)
|
||||
);
|
||||
|
||||
expect(foamTags.tags.get('primary')).toBeTruthy();
|
||||
expect(tags).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { Foam } from '../core/model/foam';
|
||||
import { FoamTags } from '../core/model/tags';
|
||||
import { HASHTAG_REGEX } from '../core/utils/hashtags';
|
||||
import { FoamFeature } from '../types';
|
||||
import { mdDocSelector } from '../utils';
|
||||
import { SECTION_REGEX } from './link-completion';
|
||||
|
||||
export const TAG_REGEX = /#(.*)/;
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: async (
|
||||
@@ -36,8 +34,7 @@ export class TagCompletionProvider
|
||||
.lineAt(position)
|
||||
.text.substr(0, position.character);
|
||||
|
||||
const requiresAutocomplete =
|
||||
cursorPrefix.match(TAG_REGEX) && !cursorPrefix.match(SECTION_REGEX);
|
||||
const requiresAutocomplete = cursorPrefix.match(HASHTAG_REGEX);
|
||||
|
||||
if (!requiresAutocomplete) {
|
||||
return null;
|
||||
|
||||
@@ -349,13 +349,11 @@ export const NoteFactory = {
|
||||
resolver
|
||||
);
|
||||
|
||||
const newFilePath = asAbsoluteWorkspaceUri(
|
||||
template.metadata.has('filepath')
|
||||
? URI.file(template.metadata.get('filepath'))
|
||||
: isSome(filepathFallbackURI)
|
||||
? filepathFallbackURI
|
||||
: await getPathFromTitle(resolver)
|
||||
);
|
||||
const newFilePath = template.metadata.has('filepath')
|
||||
? URI.file(template.metadata.get('filepath'))
|
||||
: isSome(filepathFallbackURI)
|
||||
? filepathFallbackURI
|
||||
: await getPathFromTitle(resolver);
|
||||
|
||||
return NoteFactory.createNote(
|
||||
newFilePath,
|
||||
|
||||
@@ -89,11 +89,13 @@ export const createTestNote = (params: {
|
||||
type: 'wikilink',
|
||||
range: range,
|
||||
rawText: `[[${link.slug}]]`,
|
||||
isEmbed: false,
|
||||
}
|
||||
: {
|
||||
type: 'link',
|
||||
range: range,
|
||||
rawText: `[link text](${link.to})`,
|
||||
isEmbed: false,
|
||||
};
|
||||
})
|
||||
: [],
|
||||
|
||||
@@ -5,9 +5,7 @@
|
||||
👀*This is an early stage project under rapid development. For updates join the [Foam community Discord](https://foambubble.github.io/join-discord/g)! 💬*
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
|
||||
[](#contributors-)
|
||||
|
||||
[](#contributors-)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
[](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode)
|
||||
@@ -341,6 +339,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://jonathanpberger.com/"><img src="https://avatars.githubusercontent.com/u/41085?v=4?s=60" width="60px;" alt="jonathan berger"/><br /><sub><b>jonathan berger</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jonathanpberger" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/badsketch"><img src="https://avatars.githubusercontent.com/u/8953212?v=4?s=60" width="60px;" alt="Daniel Wang"/><br /><sub><b>Daniel Wang</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=badsketch" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://yongliangliu.com"><img src="https://avatars.githubusercontent.com/u/41845017?v=4?s=60" width="60px;" alt="Liu YongLiang"/><br /><sub><b>Liu YongLiang</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=tlylt" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://scottakerman.com"><img src="https://avatars.githubusercontent.com/u/15224439?v=4?s=60" width="60px;" alt="Scott Akerman"/><br /><sub><b>Scott Akerman</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Skakerman" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.jim-graham.net/"><img src="https://avatars.githubusercontent.com/u/430293?v=4?s=60" width="60px;" alt="Jim Graham"/><br /><sub><b>Jim Graham</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jimgraham" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Reference in New Issue
Block a user