mirror of
https://github.com/foambubble/foam.git
synced 2026-01-11 06:58:11 -05:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23cf5a021e | ||
|
|
8231ed14c5 | ||
|
|
3bea283c04 | ||
|
|
a3cffe8418 | ||
|
|
675e7fa216 | ||
|
|
87d12bf3af | ||
|
|
e118ab74b5 | ||
|
|
04a61eed0e | ||
|
|
350b3005f1 | ||
|
|
f7293b1eb4 | ||
|
|
672eb6ed20 | ||
|
|
37a9bc49bc | ||
|
|
38741ca52e | ||
|
|
ed762618ed | ||
|
|
21a32382a2 | ||
|
|
7e6c041b87 | ||
|
|
c9a0a1d53c | ||
|
|
0516088656 | ||
|
|
f98ff336bf |
@@ -851,6 +851,24 @@
|
||||
"contributions": [
|
||||
"tool"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "chrisUsick",
|
||||
"name": "Chris Usick",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/6589365?v=4",
|
||||
"profile": "http://cu-dev.ca",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "josephdecock",
|
||||
"name": "Joe DeCock",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1145533?v=4",
|
||||
"profile": "https://github.com/josephdecock",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
@@ -227,6 +227,8 @@ If that sounds like something you're interested in, I'd love to have you along o
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://Cliffordfajardo.com"><img src="https://avatars.githubusercontent.com/u/6743796?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Clifford Fajardo </b></sub></a><br /><a href="#tool-cliffordfajardo" title="Tools">🔧</a></td>
|
||||
<td align="center"><a href="http://cu-dev.ca"><img src="https://avatars.githubusercontent.com/u/6589365?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Chris Usick</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=chrisUsick" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/josephdecock"><img src="https://avatars.githubusercontent.com/u/1145533?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Joe DeCock</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=josephdecock" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
],
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.18.0"
|
||||
"version": "0.18.3"
|
||||
}
|
||||
|
||||
@@ -4,6 +4,26 @@ 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.18.3] - 2022-04-17
|
||||
|
||||
Fixes and Improvements:
|
||||
- Better reporting when links fail to resolve
|
||||
- Failing link resolution during graph computation no longer fatal
|
||||
|
||||
## [0.18.2] - 2022-04-14
|
||||
|
||||
Fixes and Improvements:
|
||||
- Fixed parsing error on empty direct links (#980 - thanks @chrisUsick)
|
||||
- Improved rendering in preview of wikilinks that have link definitions (#979 - thanks @josephdecock)
|
||||
- Restored handling of section-only wikilinks (#981)
|
||||
|
||||
## [0.18.1] - 2022-04-13
|
||||
|
||||
Fixes and Improvements:
|
||||
- Fixed parsing error for direct links with square brackets in them (#977)
|
||||
- Improved markdown direct link resolution (#972)
|
||||
- Improved templates support for custom paths (#970)
|
||||
|
||||
## [0.18.0] - 2022-04-11
|
||||
|
||||
Features:
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"type": "git"
|
||||
},
|
||||
"homepage": "https://github.com/foambubble/foam",
|
||||
"version": "0.18.0",
|
||||
"version": "0.18.3",
|
||||
"license": "MIT",
|
||||
"publisher": "foam",
|
||||
"engines": {
|
||||
|
||||
@@ -107,8 +107,18 @@ export class FoamGraph implements IDisposable {
|
||||
|
||||
for (const resource of this.workspace.resources()) {
|
||||
for (const link of resource.links) {
|
||||
const targetUri = this.workspace.resolveLink(resource, link);
|
||||
this.connect(resource.uri, targetUri, link);
|
||||
try {
|
||||
const targetUri = this.workspace.resolveLink(resource, link);
|
||||
this.connect(resource.uri, targetUri, link);
|
||||
} catch (e) {
|
||||
Logger.error(
|
||||
`Error while resolving link ${
|
||||
link.rawText
|
||||
} in ${resource.uri.toFsPath()}, skipping.`,
|
||||
link,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ describe('MarkdownLink', () => {
|
||||
.links[0];
|
||||
const parsed = MarkdownLink.analyzeLink(link);
|
||||
expect(parsed.target).toEqual('wikilink');
|
||||
expect(parsed.section).toBeUndefined();
|
||||
expect(parsed.alias).toBeUndefined();
|
||||
expect(parsed.section).toEqual('');
|
||||
expect(parsed.alias).toEqual('');
|
||||
});
|
||||
it('should parse target and section', () => {
|
||||
const link = parser.parse(
|
||||
@@ -23,16 +23,36 @@ describe('MarkdownLink', () => {
|
||||
const parsed = MarkdownLink.analyzeLink(link);
|
||||
expect(parsed.target).toEqual('wikilink');
|
||||
expect(parsed.section).toEqual('section');
|
||||
expect(parsed.alias).toBeUndefined();
|
||||
expect(parsed.alias).toEqual('');
|
||||
});
|
||||
it('should parse target and alias', () => {
|
||||
const link = parser.parse(getRandomURI(), `this is a [[wikilink|alias]]`)
|
||||
.links[0];
|
||||
const parsed = MarkdownLink.analyzeLink(link);
|
||||
expect(parsed.target).toEqual('wikilink');
|
||||
expect(parsed.section).toBeUndefined();
|
||||
expect(parsed.section).toEqual('');
|
||||
expect(parsed.alias).toEqual('alias');
|
||||
});
|
||||
it('should parse links with square brackets #975', () => {
|
||||
const link = parser.parse(
|
||||
getRandomURI(),
|
||||
`this is a [[wikilink [with] brackets]]`
|
||||
).links[0];
|
||||
const parsed = MarkdownLink.analyzeLink(link);
|
||||
expect(parsed.target).toEqual('wikilink [with] brackets');
|
||||
expect(parsed.section).toEqual('');
|
||||
expect(parsed.alias).toEqual('');
|
||||
});
|
||||
it('should parse links with square brackets in alias #975', () => {
|
||||
const link = parser.parse(
|
||||
getRandomURI(),
|
||||
`this is a [[wikilink|alias [with] brackets]]`
|
||||
).links[0];
|
||||
const parsed = MarkdownLink.analyzeLink(link);
|
||||
expect(parsed.target).toEqual('wikilink');
|
||||
expect(parsed.section).toEqual('');
|
||||
expect(parsed.alias).toEqual('alias [with] brackets');
|
||||
});
|
||||
it('should parse target and alias with escaped separator', () => {
|
||||
const link = parser.parse(
|
||||
getRandomURI(),
|
||||
@@ -40,7 +60,7 @@ describe('MarkdownLink', () => {
|
||||
).links[0];
|
||||
const parsed = MarkdownLink.analyzeLink(link);
|
||||
expect(parsed.target).toEqual('wikilink');
|
||||
expect(parsed.section).toBeUndefined();
|
||||
expect(parsed.section).toEqual('');
|
||||
expect(parsed.alias).toEqual('alias');
|
||||
});
|
||||
it('should parse target section and alias', () => {
|
||||
@@ -57,9 +77,9 @@ describe('MarkdownLink', () => {
|
||||
const link = parser.parse(getRandomURI(), `this is a [[#section]]`)
|
||||
.links[0];
|
||||
const parsed = MarkdownLink.analyzeLink(link);
|
||||
expect(parsed.target).toBeUndefined();
|
||||
expect(parsed.target).toEqual('');
|
||||
expect(parsed.section).toEqual('section');
|
||||
expect(parsed.alias).toBeUndefined();
|
||||
expect(parsed.alias).toEqual('');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -69,7 +89,7 @@ describe('MarkdownLink', () => {
|
||||
.links[0];
|
||||
const parsed = MarkdownLink.analyzeLink(link);
|
||||
expect(parsed.target).toEqual('to/path.md');
|
||||
expect(parsed.section).toBeUndefined();
|
||||
expect(parsed.section).toEqual('');
|
||||
expect(parsed.alias).toEqual('link');
|
||||
});
|
||||
it('should parse target and section', () => {
|
||||
@@ -89,10 +109,28 @@ describe('MarkdownLink', () => {
|
||||
range: Range.create(0, 0),
|
||||
};
|
||||
const parsed = MarkdownLink.analyzeLink(link);
|
||||
expect(parsed.target).toBeUndefined();
|
||||
expect(parsed.target).toEqual('');
|
||||
expect(parsed.section).toEqual('section');
|
||||
expect(parsed.alias).toEqual('link');
|
||||
});
|
||||
it('should parse links with square brackets in label #975', () => {
|
||||
const link = parser.parse(
|
||||
getRandomURI(),
|
||||
`this is a [inbox [xyz]](to/path.md)`
|
||||
).links[0];
|
||||
const parsed = MarkdownLink.analyzeLink(link);
|
||||
expect(parsed.target).toEqual('to/path.md');
|
||||
expect(parsed.section).toEqual('');
|
||||
expect(parsed.alias).toEqual('inbox [xyz]');
|
||||
});
|
||||
it('should parse links with empty label #975', () => {
|
||||
const link = parser.parse(getRandomURI(), `this is a [](to/path.md)`)
|
||||
.links[0];
|
||||
const parsed = MarkdownLink.analyzeLink(link);
|
||||
expect(parsed.target).toEqual('to/path.md');
|
||||
expect(parsed.section).toEqual('');
|
||||
expect(parsed.alias).toEqual('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('rename wikilink', () => {
|
||||
|
||||
@@ -5,27 +5,35 @@ export abstract class MarkdownLink {
|
||||
/\[\[([^#|]+)?#?([^|]+)?\|?(.*)?\]\]/
|
||||
);
|
||||
private static directLinkRegex = new RegExp(
|
||||
/\[([^\]]+)\]\(([^#]*)?#?([^\]]+)?\)/
|
||||
/\[(.*)\]\(([^#]*)?#?([^\]]+)?\)/
|
||||
);
|
||||
|
||||
public static analyzeLink(link: ResourceLink) {
|
||||
if (link.type === 'wikilink') {
|
||||
const [, target, section, alias] = this.wikilinkRegex.exec(link.rawText);
|
||||
return {
|
||||
target: target?.replace(/\\/g, ''),
|
||||
section,
|
||||
alias,
|
||||
};
|
||||
try {
|
||||
if (link.type === 'wikilink') {
|
||||
const [, target, section, alias] = this.wikilinkRegex.exec(
|
||||
link.rawText
|
||||
);
|
||||
return {
|
||||
target: target?.replace(/\\/g, '') ?? '',
|
||||
section: section ?? '',
|
||||
alias: alias ?? '',
|
||||
};
|
||||
}
|
||||
if (link.type === 'link') {
|
||||
const [, alias, target, section] = this.directLinkRegex.exec(
|
||||
link.rawText
|
||||
);
|
||||
return {
|
||||
target: target ?? '',
|
||||
section: section ?? '',
|
||||
alias: alias ?? '',
|
||||
};
|
||||
}
|
||||
throw new Error(`Link of type ${link.type} is not supported`);
|
||||
} catch (e) {
|
||||
throw new Error(`Couldn't parse link ${link.rawText} - ${e}`);
|
||||
}
|
||||
if (link.type === 'link') {
|
||||
const [, alias, target, section] = this.directLinkRegex.exec(
|
||||
link.rawText
|
||||
);
|
||||
return { target, section, alias };
|
||||
}
|
||||
throw new Error(
|
||||
`Unexpected state: link of type ${link.type} is not supported`
|
||||
);
|
||||
}
|
||||
|
||||
public static createUpdateLinkEdit(
|
||||
|
||||
@@ -147,6 +147,39 @@ describe('Link resolution', () => {
|
||||
expect(ws.resolveLink(noteA, noteA.links[1])).toEqual(noteC.uri);
|
||||
expect(ws.resolveLink(noteA, noteA.links[2])).toEqual(noteD.uri);
|
||||
});
|
||||
|
||||
it('should resolve wikilink with section identifier', () => {
|
||||
const noteA = createTestNote({
|
||||
uri: '/path/to/page-a.md',
|
||||
links: [
|
||||
// uppercased filename, lowercased slug
|
||||
{ slug: 'page-b#section' },
|
||||
],
|
||||
});
|
||||
const noteB = createTestNote({ uri: '/somewhere/PAGE-B.md' });
|
||||
const ws = createTestWorkspace()
|
||||
.set(noteA)
|
||||
.set(noteB);
|
||||
|
||||
expect(ws.resolveLink(noteA, noteA.links[0])).toEqual(
|
||||
noteB.uri.withFragment('section')
|
||||
);
|
||||
});
|
||||
|
||||
it('should resolve section-only wikilinks', () => {
|
||||
const noteA = createTestNote({
|
||||
uri: '/path/to/page-a.md',
|
||||
links: [
|
||||
// uppercased filename, lowercased slug
|
||||
{ slug: '#section' },
|
||||
],
|
||||
});
|
||||
const ws = createTestWorkspace().set(noteA);
|
||||
|
||||
expect(ws.resolveLink(noteA, noteA.links[0])).toEqual(
|
||||
noteA.uri.withFragment('section')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Markdown direct links', () => {
|
||||
|
||||
@@ -133,9 +133,16 @@ export class MarkdownResourceProvider implements ResourceProvider {
|
||||
break;
|
||||
}
|
||||
case 'link': {
|
||||
// force ambiguous links to be treated as relative
|
||||
const path =
|
||||
target.startsWith('/') ||
|
||||
target.startsWith('./') ||
|
||||
target.startsWith('../')
|
||||
? target
|
||||
: './' + target;
|
||||
targetUri =
|
||||
workspace.find(target, resource.uri)?.uri ??
|
||||
URI.placeholder(resource.uri.resolve(target).path);
|
||||
workspace.find(path, resource.uri)?.uri ??
|
||||
URI.placeholder(resource.uri.resolve(path).path);
|
||||
if (section && !targetUri.isPlaceholder()) {
|
||||
targetUri = targetUri.withFragment(section);
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ describe('Daily note template', () => {
|
||||
const config = workspace.getConfiguration('foam');
|
||||
const uri = getDailyNotePath(config, targetDate);
|
||||
|
||||
await createDailyNoteIfNotExists(config, uri, targetDate);
|
||||
await createDailyNoteIfNotExists(targetDate);
|
||||
|
||||
const doc = await showInEditor(uri);
|
||||
const content = doc.editor.document.getText();
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { workspace, WorkspaceConfiguration } from 'vscode';
|
||||
import dateFormat from 'dateformat';
|
||||
import { existsInFs } from './core/utils/path';
|
||||
import { focusNote } from './utils';
|
||||
import { URI } from './core/model/uri';
|
||||
import { fromVsCodeUri } from './utils/vsc-utils';
|
||||
import { fromVsCodeUri, toVsCodeUri } from './utils/vsc-utils';
|
||||
import { NoteFactory } from './services/templates';
|
||||
|
||||
/**
|
||||
@@ -15,21 +14,14 @@ import { NoteFactory } from './services/templates';
|
||||
* @param date A given date to be formatted as filename.
|
||||
*/
|
||||
export async function openDailyNoteFor(date?: Date) {
|
||||
const foamConfiguration = workspace.getConfiguration('foam');
|
||||
const currentDate = date instanceof Date ? date : new Date();
|
||||
const targetDate = date instanceof Date ? date : new Date();
|
||||
|
||||
const dailyNotePath = getDailyNotePath(foamConfiguration, currentDate);
|
||||
|
||||
const isNew = await createDailyNoteIfNotExists(
|
||||
foamConfiguration,
|
||||
dailyNotePath,
|
||||
currentDate
|
||||
);
|
||||
const { didCreateFile, uri } = await createDailyNoteIfNotExists(targetDate);
|
||||
// if a new file is created, the editor is automatically created
|
||||
// but forcing the focus will block the template placeholders from working
|
||||
// so we only explicitly focus on the note if the file already exists
|
||||
if (!isNew) {
|
||||
await focusNote(dailyNotePath, isNew);
|
||||
if (!didCreateFile) {
|
||||
await focusNote(uri, didCreateFile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,37 +88,31 @@ export function getDailyNoteFileName(
|
||||
* In the case that the folders referenced in the file path also do not exist,
|
||||
* this function will create all folders in the path.
|
||||
*
|
||||
* @param configuration The current workspace configuration.
|
||||
* @param dailyNotePath The path to daily note file.
|
||||
* @param currentDate The current date, to be used as a title.
|
||||
* @returns Wether the file was created.
|
||||
*/
|
||||
export async function createDailyNoteIfNotExists(
|
||||
configuration: WorkspaceConfiguration,
|
||||
dailyNotePath: URI,
|
||||
targetDate: Date
|
||||
) {
|
||||
if (await existsInFs(dailyNotePath.toFsPath())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function createDailyNoteIfNotExists(targetDate: Date) {
|
||||
const configuration = workspace.getConfiguration('foam');
|
||||
const pathFromLegacyConfiguration = getDailyNotePath(
|
||||
configuration,
|
||||
targetDate
|
||||
);
|
||||
const titleFormat: string =
|
||||
configuration.get('openDailyNote.titleFormat') ??
|
||||
configuration.get('openDailyNote.filenameFormat');
|
||||
|
||||
const templateFallbackText = `---
|
||||
foam_template:
|
||||
name: New Daily Note
|
||||
description: Foam's default daily note template
|
||||
filepath: "${workspace.asRelativePath(
|
||||
toVsCodeUri(pathFromLegacyConfiguration)
|
||||
)}"
|
||||
---
|
||||
# ${dateFormat(targetDate, titleFormat, false)}
|
||||
`;
|
||||
|
||||
await NoteFactory.createFromDailyNoteTemplate(
|
||||
dailyNotePath,
|
||||
return await NoteFactory.createFromDailyNoteTemplate(
|
||||
pathFromLegacyConfiguration,
|
||||
templateFallbackText,
|
||||
targetDate
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { FoamWorkspace } from '../core/model/workspace';
|
||||
import { FoamGraph } from '../core/model/graph';
|
||||
import { Resource, ResourceLink } from '../core/model/note';
|
||||
import { Range } from '../core/model/range';
|
||||
import { fromVsCodeUri } from '../utils/vsc-utils';
|
||||
import { fromVsCodeUri, toVsCodeUri } from '../utils/vsc-utils';
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: async (
|
||||
@@ -133,7 +133,7 @@ export class BacklinkTreeItem extends vscode.TreeItem {
|
||||
this.label = `${link.range.start.line}: ${this.label}`;
|
||||
this.command = {
|
||||
command: 'vscode.open',
|
||||
arguments: [resource.uri, { selection: link.range }],
|
||||
arguments: [toVsCodeUri(resource.uri), { selection: link.range }],
|
||||
title: 'Go to link',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ const feature: FoamFeature = {
|
||||
() => {
|
||||
const resolver = new Resolver(new Map(), new Date());
|
||||
|
||||
NoteFactory.createFromTemplate(
|
||||
return NoteFactory.createFromTemplate(
|
||||
DEFAULT_TEMPLATE_URI,
|
||||
resolver,
|
||||
undefined,
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { ExtensionContext, commands, workspace } from 'vscode';
|
||||
import { ExtensionContext, commands } from 'vscode';
|
||||
import { FoamFeature } from '../types';
|
||||
import { openDailyNoteFor } from '../dated-notes';
|
||||
import { getFoamVsCodeConfig } from '../services/config';
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: (context: ExtensionContext) => {
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand('foam-vscode.open-daily-note', openDailyNoteFor)
|
||||
);
|
||||
if (
|
||||
workspace.getConfiguration('foam').get('openDailyNote.onStartup', false)
|
||||
) {
|
||||
|
||||
if (getFoamVsCodeConfig('openDailyNote.onStartup', false)) {
|
||||
commands.executeCommand('foam-vscode.open-daily-note');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
createDailyNoteIfNotExists,
|
||||
getDailyNoteFileName,
|
||||
openDailyNoteFor,
|
||||
getDailyNotePath,
|
||||
} from '../dated-notes';
|
||||
import { FoamFeature } from '../types';
|
||||
|
||||
@@ -215,11 +214,7 @@ const datedNoteCommand = (date: Date) => {
|
||||
return openDailyNoteFor(date);
|
||||
}
|
||||
if (foamNavigateOnSelect === 'createNote') {
|
||||
return createDailyNoteIfNotExists(
|
||||
foamConfig,
|
||||
getDailyNotePath(foamConfig, date),
|
||||
date
|
||||
);
|
||||
return createDailyNoteIfNotExists(date);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
markdownItWithFoamLinks,
|
||||
markdownItWithFoamTags,
|
||||
markdownItWithNoteInclusion,
|
||||
markdownItWithRemoveLinkReferences,
|
||||
} from './preview-navigation';
|
||||
|
||||
describe('Link generation in preview', () => {
|
||||
@@ -22,7 +23,11 @@ describe('Link generation in preview', () => {
|
||||
links: [{ slug: 'placeholder' }],
|
||||
});
|
||||
const ws = new FoamWorkspace().set(noteA);
|
||||
const md = markdownItWithFoamLinks(MarkdownIt(), ws);
|
||||
|
||||
const md = [
|
||||
markdownItWithFoamLinks,
|
||||
markdownItWithRemoveLinkReferences,
|
||||
].reduce((acc, extension) => extension(acc, ws), MarkdownIt());
|
||||
|
||||
it('generates a link to a note', () => {
|
||||
expect(md.render(`[[note-a]]`)).toEqual(
|
||||
@@ -41,6 +46,14 @@ describe('Link generation in preview', () => {
|
||||
`<p><a class='foam-placeholder-link' title="Link to non-existing resource" href="javascript:void(0);">random-text</a></p>\n`
|
||||
);
|
||||
});
|
||||
|
||||
it('generates a wikilink even when there is a link reference', () => {
|
||||
const note = `[[note-a]]
|
||||
[note-a]: <note-a.md> "Note A"`;
|
||||
expect(md.render(note)).toEqual(
|
||||
`<p><a class='foam-note-link' title='${noteA.title}' href='/path/to/note-a.md' data-href='/path/to/note-a.md'>note-a</a>\n[note-a]: <note-a.md> "Note A"</p>\n`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Stylable tag generation in preview', () => {
|
||||
|
||||
@@ -155,18 +155,17 @@ export const markdownItWithRemoveLinkReferences = (
|
||||
) => {
|
||||
md.inline.ruler.before('link', 'clear-references', state => {
|
||||
if (state.env.references) {
|
||||
Object.keys(state.env.references).forEach(refKey => {
|
||||
// Forget about reference links that contain an alias divider
|
||||
// Aliased reference links will lead the MarkdownParser to include wrong link references
|
||||
if (refKey.includes(ALIAS_DIVIDER_CHAR)) {
|
||||
delete state.env.references[refKey];
|
||||
}
|
||||
const src = state.src.toLowerCase();
|
||||
const foamLinkRegEx = /\[\[([^[\]]+?)\]\]/g;
|
||||
const foamLinks = [...src.matchAll(foamLinkRegEx)].map(m =>
|
||||
m[1].toLowerCase()
|
||||
);
|
||||
|
||||
// When the reference is present due to an inclusion of that note, we
|
||||
// need to remove that reference. This ensures the MarkdownIt parser
|
||||
// will not replace the wikilink syntax with an <a href> link and as a result
|
||||
// break our inclusion logic.
|
||||
if (state.src.toLowerCase().includes(`![[${refKey.toLowerCase()}]]`)) {
|
||||
Object.keys(state.env.references).forEach(refKey => {
|
||||
// Remove all references that have corresponding wikilinks.
|
||||
// If the markdown parser sees a reference, it will format it before
|
||||
// we get a chance to create the wikilink.
|
||||
if (foamLinks.includes(refKey.toLowerCase())) {
|
||||
delete state.env.references[refKey];
|
||||
}
|
||||
});
|
||||
|
||||
@@ -164,16 +164,14 @@ export class TagReference extends vscode.TreeItem {
|
||||
public readonly title: string;
|
||||
constructor(public readonly tag: Tag, public readonly note: Resource) {
|
||||
super(note.title, vscode.TreeItemCollapsibleState.None);
|
||||
const uri = toVsCodeUri(note.uri);
|
||||
this.title = note.title;
|
||||
this.description = note.uri.path.replace(
|
||||
vscode.workspace.getWorkspaceFolder(toVsCodeUri(note.uri))?.uri.path,
|
||||
''
|
||||
);
|
||||
this.description = vscode.workspace.asRelativePath(uri);
|
||||
this.tooltip = undefined;
|
||||
this.command = {
|
||||
command: 'vscode.open',
|
||||
arguments: [
|
||||
note.uri,
|
||||
uri,
|
||||
{
|
||||
preview: true,
|
||||
selection: toVsCodeRange(tag.range),
|
||||
|
||||
@@ -37,6 +37,13 @@ const feature: FoamFeature = {
|
||||
});
|
||||
}
|
||||
case 'placeholder': {
|
||||
const title = uri.getName();
|
||||
if (uri.isAbsolute()) {
|
||||
return NoteFactory.createForPlaceholderWikilink(
|
||||
title,
|
||||
URI.file(uri.path)
|
||||
);
|
||||
}
|
||||
const basedir =
|
||||
vscode.workspace.workspaceFolders.length > 0
|
||||
? vscode.workspace.workspaceFolders[0].uri
|
||||
@@ -46,7 +53,6 @@ const feature: FoamFeature = {
|
||||
if (basedir === undefined) {
|
||||
return;
|
||||
}
|
||||
const title = uri.getName();
|
||||
const target = fromVsCodeUri(basedir)
|
||||
.resolve(uri, true)
|
||||
.changeExtension('', '.md');
|
||||
|
||||
@@ -4,8 +4,8 @@ export interface ConfigurationMonitor<T> extends Disposable {
|
||||
(): T;
|
||||
}
|
||||
|
||||
export const getFoamVsCodeConfig = <T>(key: string): T =>
|
||||
workspace.getConfiguration('foam').get(key);
|
||||
export const getFoamVsCodeConfig = <T>(key: string, defaultValue?: T): T =>
|
||||
workspace.getConfiguration('foam').get(key, defaultValue);
|
||||
|
||||
export const updateFoamVsCodeConfig = <T>(key: string, value: T) =>
|
||||
workspace.getConfiguration().update('foam.' + key, value);
|
||||
|
||||
@@ -48,8 +48,9 @@ export async function createDocAndFocus(
|
||||
toVsCodeUri(filepath),
|
||||
new TextEncoder().encode('')
|
||||
);
|
||||
await focusNote(filepath, true, viewColumn);
|
||||
await window.activeTextEditor.insertSnippet(text);
|
||||
const note = await focusNote(filepath, true, viewColumn);
|
||||
await note.editor.insertSnippet(text);
|
||||
await note.document.save();
|
||||
}
|
||||
|
||||
export async function replaceSelection(
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
replaceSelection,
|
||||
} from './editor';
|
||||
import { Resolver } from './variable-resolver';
|
||||
import dateFormat from 'dateformat';
|
||||
|
||||
/**
|
||||
* The templates directory
|
||||
@@ -69,6 +70,32 @@ export async function getTemplates(): Promise<URI[]> {
|
||||
return templates;
|
||||
}
|
||||
|
||||
export async function getTemplateInfo(
|
||||
templateUri: URI,
|
||||
templateFallbackText = '',
|
||||
resolver: Resolver
|
||||
) {
|
||||
const templateText = existsSync(templateUri.toFsPath())
|
||||
? await workspace.fs
|
||||
.readFile(toVsCodeUri(templateUri))
|
||||
.then(bytes => bytes.toString())
|
||||
: templateFallbackText;
|
||||
|
||||
const templateWithResolvedVariables = await resolver.resolveText(
|
||||
templateText
|
||||
);
|
||||
|
||||
const [
|
||||
templateMetadata,
|
||||
templateWithFoamFrontmatterRemoved,
|
||||
] = extractFoamTemplateFrontmatterMetadata(templateWithResolvedVariables);
|
||||
|
||||
return {
|
||||
metadata: templateMetadata,
|
||||
text: templateWithFoamFrontmatterRemoved,
|
||||
};
|
||||
}
|
||||
|
||||
export const NoteFactory = {
|
||||
/**
|
||||
* Creates a new note using a template.
|
||||
@@ -81,72 +108,67 @@ export const NoteFactory = {
|
||||
templateUri: URI,
|
||||
resolver: Resolver,
|
||||
filepathFallbackURI?: URI,
|
||||
templateFallbackText = ''
|
||||
): Promise<void> => {
|
||||
const templateText = existsSync(templateUri.toFsPath())
|
||||
? await workspace.fs
|
||||
.readFile(toVsCodeUri(templateUri))
|
||||
.then(bytes => bytes.toString())
|
||||
: templateFallbackText;
|
||||
|
||||
const selectedContent = findSelectionContent();
|
||||
|
||||
if (selectedContent?.content) {
|
||||
resolver.define('FOAM_SELECTED_TEXT', selectedContent?.content);
|
||||
}
|
||||
|
||||
let templateWithResolvedVariables: string;
|
||||
templateFallbackText = '',
|
||||
onFileExists?: (filePath: URI) => Promise<string | undefined>
|
||||
): Promise<{ didCreateFile: boolean; uri: URI | undefined }> => {
|
||||
try {
|
||||
templateWithResolvedVariables = await resolver.resolveText(templateText);
|
||||
onFileExists = onFileExists
|
||||
? onFileExists
|
||||
: (existingFile: URI) => {
|
||||
const filename = existingFile.getBasename();
|
||||
return askUserForFilepathConfirmation(existingFile, filename);
|
||||
};
|
||||
|
||||
const template = await getTemplateInfo(
|
||||
templateUri,
|
||||
templateFallbackText,
|
||||
resolver
|
||||
);
|
||||
|
||||
const selectedContent = findSelectionContent();
|
||||
if (selectedContent?.content) {
|
||||
resolver.define('FOAM_SELECTED_TEXT', selectedContent?.content);
|
||||
}
|
||||
|
||||
const templateSnippet = new SnippetString(template.text);
|
||||
|
||||
let newFilePath = await determineNewNoteFilepath(
|
||||
template.metadata.get('filepath'),
|
||||
filepathFallbackURI,
|
||||
resolver
|
||||
);
|
||||
while (existsSync(newFilePath.toFsPath())) {
|
||||
const proposedNewFilepath = await onFileExists(newFilePath);
|
||||
|
||||
if (proposedNewFilepath === undefined) {
|
||||
return { didCreateFile: false, uri: newFilePath };
|
||||
}
|
||||
newFilePath = URI.file(proposedNewFilepath);
|
||||
}
|
||||
|
||||
await createDocAndFocus(
|
||||
templateSnippet,
|
||||
newFilePath,
|
||||
selectedContent ? ViewColumn.Beside : ViewColumn.Active
|
||||
);
|
||||
|
||||
if (selectedContent !== undefined) {
|
||||
const newNoteTitle = newFilePath.getName();
|
||||
|
||||
await replaceSelection(
|
||||
selectedContent.document,
|
||||
selectedContent.selection,
|
||||
`[[${newNoteTitle}]]`
|
||||
);
|
||||
}
|
||||
|
||||
return { didCreateFile: true, uri: newFilePath };
|
||||
} catch (err) {
|
||||
if (err instanceof UserCancelledOperation) {
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
const [
|
||||
templateMetadata,
|
||||
templateWithFoamFrontmatterRemoved,
|
||||
] = extractFoamTemplateFrontmatterMetadata(templateWithResolvedVariables);
|
||||
const templateSnippet = new SnippetString(
|
||||
templateWithFoamFrontmatterRemoved
|
||||
);
|
||||
|
||||
let filepath = await determineNewNoteFilepath(
|
||||
templateMetadata.get('filepath'),
|
||||
filepathFallbackURI,
|
||||
resolver
|
||||
);
|
||||
|
||||
if (existsSync(filepath.toFsPath())) {
|
||||
const filename = filepath.getBasename();
|
||||
const newFilepath = await askUserForFilepathConfirmation(
|
||||
filepath,
|
||||
filename
|
||||
);
|
||||
|
||||
if (newFilepath === undefined) {
|
||||
return;
|
||||
}
|
||||
filepath = URI.file(newFilepath);
|
||||
}
|
||||
|
||||
await createDocAndFocus(
|
||||
templateSnippet,
|
||||
filepath,
|
||||
selectedContent ? ViewColumn.Beside : ViewColumn.Active
|
||||
);
|
||||
|
||||
if (selectedContent !== undefined) {
|
||||
const newNoteTitle = filepath.getName();
|
||||
|
||||
await replaceSelection(
|
||||
selectedContent.document,
|
||||
selectedContent.selection,
|
||||
`[[${newNoteTitle}]]`
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -158,13 +180,17 @@ export const NoteFactory = {
|
||||
filepathFallbackURI: URI,
|
||||
templateFallbackText: string,
|
||||
targetDate: Date
|
||||
): Promise<void> => {
|
||||
const resolver = new Resolver(new Map(), targetDate);
|
||||
): Promise<{ didCreateFile: boolean; uri: URI | undefined }> => {
|
||||
const resolver = new Resolver(
|
||||
new Map().set('FOAM_TITLE', dateFormat(targetDate, 'yyyy-mm-dd', false)),
|
||||
targetDate
|
||||
);
|
||||
return NoteFactory.createFromTemplate(
|
||||
DAILY_NOTE_TEMPLATE_URI,
|
||||
resolver,
|
||||
filepathFallbackURI,
|
||||
templateFallbackText
|
||||
templateFallbackText,
|
||||
_ => Promise.resolve(undefined)
|
||||
);
|
||||
},
|
||||
|
||||
@@ -176,7 +202,7 @@ export const NoteFactory = {
|
||||
createForPlaceholderWikilink: (
|
||||
wikilinkPlaceholder: string,
|
||||
filepathFallbackURI: URI
|
||||
): Promise<void> => {
|
||||
): Promise<{ didCreateFile: boolean; uri: URI | undefined }> => {
|
||||
const resolver = new Resolver(
|
||||
new Map().set('FOAM_TITLE', wikilinkPlaceholder),
|
||||
new Date()
|
||||
|
||||
@@ -161,6 +161,8 @@ export async function focusNote(
|
||||
const { range } = editor.document.lineAt(lineCount - 1);
|
||||
editor.selection = new Selection(range.end, range.end);
|
||||
}
|
||||
|
||||
return { document, editor };
|
||||
}
|
||||
|
||||
export function getContainsTooltip(titles: string[]): string {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
👀*This is an early stage project under rapid development. For updates join the [Foam community Discord](https://foambubble.github.io/join-discord/g)! 💬*
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
[](#contributors-)
|
||||
[](#contributors-)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
[](https://foambubble.github.io/join-discord/g)
|
||||
@@ -315,6 +315,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://Cliffordfajardo.com"><img src="https://avatars.githubusercontent.com/u/6743796?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Clifford Fajardo </b></sub></a><br /><a href="#tool-cliffordfajardo" title="Tools">🔧</a></td>
|
||||
<td align="center"><a href="http://cu-dev.ca"><img src="https://avatars.githubusercontent.com/u/6589365?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Chris Usick</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=chrisUsick" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/josephdecock"><img src="https://avatars.githubusercontent.com/u/1145533?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Joe DeCock</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=josephdecock" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user