mirror of
https://github.com/foambubble/foam.git
synced 2026-01-10 22:48:09 -05:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af0c2bbaa3 | ||
|
|
700bfc1b26 | ||
|
|
a6d5c04453 | ||
|
|
b0c42cead2 | ||
|
|
6c643adb9d | ||
|
|
ca7fdefaae | ||
|
|
149d5f5a7c | ||
|
|
be80857fd1 | ||
|
|
611fa7359d | ||
|
|
08b7e7a231 | ||
|
|
0a259168c7 | ||
|
|
f3d0569c76 |
@@ -4,5 +4,5 @@
|
||||
],
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.13.2"
|
||||
"version": "0.13.3"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "foam-core",
|
||||
"repository": "https://github.com/foambubble/foam",
|
||||
"version": "0.13.2",
|
||||
"version": "0.13.3",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -16,6 +16,8 @@ export const bootstrap = async (
|
||||
config.ignoreGlobs
|
||||
);
|
||||
const workspace = new FoamWorkspace();
|
||||
await Promise.all(initialProviders.map(p => workspace.registerProvider(p)));
|
||||
|
||||
const graph = FoamGraph.fromWorkspace(workspace, true);
|
||||
|
||||
const foam: Foam = {
|
||||
@@ -33,7 +35,5 @@ export const bootstrap = async (
|
||||
},
|
||||
};
|
||||
|
||||
await Promise.all(initialProviders.map(p => workspace.registerProvider(p)));
|
||||
|
||||
return foam;
|
||||
};
|
||||
|
||||
@@ -70,6 +70,6 @@ const parseConfig = (path: URI) => {
|
||||
try {
|
||||
return JSON.parse(readFileSync(URI.toFsPath(path), 'utf8'));
|
||||
} catch {
|
||||
Logger.debug('Could not read configuration from ' + path);
|
||||
Logger.debug('Could not read configuration from ' + URI.toString(path));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -50,16 +50,16 @@ export class MarkdownResourceProvider implements ResourceProvider {
|
||||
const filesByFolder = await Promise.all(
|
||||
this.matcher.include.map(glob => this.dataStore.list(glob))
|
||||
);
|
||||
const files = this.matcher.match(filesByFolder.flat());
|
||||
const files = this.matcher
|
||||
.match(filesByFolder.flat())
|
||||
.filter(this.supports);
|
||||
|
||||
await Promise.all(
|
||||
files.map(async uri => {
|
||||
Logger.info('Found: ' + URI.toString(uri));
|
||||
if (this.match(uri)) {
|
||||
const content = await this.dataStore.read(uri);
|
||||
if (isSome(content)) {
|
||||
workspace.set(this.parser.parse(uri, content));
|
||||
}
|
||||
const content = await this.dataStore.read(uri);
|
||||
if (isSome(content)) {
|
||||
workspace.set(this.parser.parse(uri, content));
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -67,26 +67,26 @@ export class MarkdownResourceProvider implements ResourceProvider {
|
||||
this.disposables =
|
||||
this.watcherInit?.({
|
||||
onDidChange: async uri => {
|
||||
if (this.matcher.isMatch(uri)) {
|
||||
if (this.matcher.isMatch(uri) && this.supports(uri)) {
|
||||
const content = await this.dataStore.read(uri);
|
||||
isSome(content) &&
|
||||
workspace.set(await this.parser.parse(uri, content));
|
||||
}
|
||||
},
|
||||
onDidCreate: async uri => {
|
||||
if (this.matcher.isMatch(uri)) {
|
||||
if (this.matcher.isMatch(uri) && this.supports(uri)) {
|
||||
const content = await this.dataStore.read(uri);
|
||||
isSome(content) &&
|
||||
workspace.set(await this.parser.parse(uri, content));
|
||||
}
|
||||
},
|
||||
onDidDelete: uri => {
|
||||
this.matcher.isMatch(uri) && workspace.delete(uri);
|
||||
this.supports(uri) && workspace.delete(uri);
|
||||
},
|
||||
}) ?? [];
|
||||
}
|
||||
|
||||
match(uri: URI) {
|
||||
supports(uri: URI) {
|
||||
return URI.isMarkdownFile(uri);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { FoamWorkspace } from './workspace';
|
||||
|
||||
export interface ResourceProvider extends IDisposable {
|
||||
init: (workspace: FoamWorkspace) => Promise<void>;
|
||||
match: (uri: URI) => boolean;
|
||||
supports: (uri: URI) => boolean;
|
||||
read: (uri: URI) => Promise<string | null>;
|
||||
readAsMarkdown: (uri: URI) => Promise<string | null>;
|
||||
fetch: (uri: URI) => Promise<Resource | null>;
|
||||
|
||||
@@ -249,7 +249,7 @@ export abstract class URI {
|
||||
);
|
||||
}
|
||||
static isMarkdownFile(uri: URI): boolean {
|
||||
return uri.path.endsWith('md');
|
||||
return uri.path.endsWith('.md');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ export class FoamWorkspace implements IDisposable {
|
||||
|
||||
public resolveLink(resource: Resource, link: ResourceLink): URI {
|
||||
// TODO add tests
|
||||
const provider = this.providers.find(p => p.match(resource.uri));
|
||||
const provider = this.providers.find(p => p.supports(resource.uri));
|
||||
return (
|
||||
provider?.resolveLink(this, resource, link) ??
|
||||
URI.placeholder(link.target)
|
||||
@@ -147,12 +147,12 @@ export class FoamWorkspace implements IDisposable {
|
||||
}
|
||||
|
||||
public read(uri: URI): Promise<string | null> {
|
||||
const provider = this.providers.find(p => p.match(uri));
|
||||
const provider = this.providers.find(p => p.supports(uri));
|
||||
return provider?.read(uri) ?? Promise.resolve(null);
|
||||
}
|
||||
|
||||
public readAsMarkdown(uri: URI): Promise<string | null> {
|
||||
const provider = this.providers.find(p => p.match(uri));
|
||||
const provider = this.providers.find(p => p.supports(uri));
|
||||
return provider?.readAsMarkdown(uri) ?? Promise.resolve(null);
|
||||
}
|
||||
|
||||
|
||||
@@ -63,9 +63,6 @@ export class Matcher implements IMatcher {
|
||||
const withFolder = folderPlusGlob(folder);
|
||||
this.include.push(
|
||||
...include.map(glob => {
|
||||
// if (glob.endsWith('*')) {
|
||||
// glob = `${glob}\\.{md,mdx,markdown}`;
|
||||
// }
|
||||
return withFolder(glob);
|
||||
})
|
||||
);
|
||||
@@ -141,5 +138,5 @@ export const folderPlusGlob = (folder: string) => (glob: string): string => {
|
||||
if (glob.startsWith('/')) {
|
||||
glob = glob.slice(1);
|
||||
}
|
||||
return `${folder}/${glob}`;
|
||||
return folder.length > 0 ? `${folder}/${glob}` : glob;
|
||||
};
|
||||
|
||||
@@ -61,6 +61,14 @@ describe('Matcher', () => {
|
||||
expect(matcher.isMatch(files[3])).toEqual(false);
|
||||
});
|
||||
|
||||
it('happy path', () => {
|
||||
const matcher = new Matcher([URI.file('/')], ['**/*'], ['**/*.pdf']);
|
||||
expect(matcher.isMatch(URI.file('/file.md'))).toBeTruthy();
|
||||
expect(matcher.isMatch(URI.file('/file.pdf'))).toBeFalsy();
|
||||
expect(matcher.isMatch(URI.file('/dir/file.md'))).toBeTruthy();
|
||||
expect(matcher.isMatch(URI.file('/dir/file.pdf'))).toBeFalsy();
|
||||
});
|
||||
|
||||
it('ignores files in the exclude list', () => {
|
||||
const matcher = new Matcher([testFolder], ['*.md'], ['file1.*']);
|
||||
const files = [
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Resource } from '../../src/model/note';
|
||||
import { Range } from '../../src/model/range';
|
||||
import { MarkdownResourceProvider } from '../../src';
|
||||
|
||||
Logger.setLevel('info');
|
||||
Logger.setLevel('error');
|
||||
|
||||
describe('generateLinkReferences', () => {
|
||||
let _workspace: FoamWorkspace;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Logger } from '../src';
|
||||
import { URI } from '../src/model/uri';
|
||||
import { uriToSlug } from '../src/utils/slug';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
describe('Foam URIs', () => {
|
||||
describe('URI parsing', () => {
|
||||
const base = URI.file('/path/to/file.md');
|
||||
|
||||
@@ -4,12 +4,20 @@ 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.13.3] - 2021-05-09
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Improved Foam template variables resolution: unknown variables are now ignored (#622 - thanks @movermeyer)
|
||||
- Fixed file matching in MarkdownProvider (#617)
|
||||
- Fixed cancelling `Foam: Create New Note` and `Foam: Create New Note From Template` behavior (#623 - thanks @movermeyer)
|
||||
|
||||
## [0.13.2] - 2021-05-06
|
||||
|
||||
Fixes and Improvements:
|
||||
|
||||
- Fixed wikilink completion bug (#592 - thanks @RobinKing)
|
||||
- Added support for stylable tags (#598 - thanks @Barabas)
|
||||
- Added support for stylable tags (#598 - thanks @Barabazs)
|
||||
- Added "Create new note" command (#601 - thanks @movermeyer)
|
||||
- Fixed navigation from placeholder and orphan panel (#600)
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"type": "git"
|
||||
},
|
||||
"homepage": "https://github.com/foambubble/foam",
|
||||
"version": "0.13.2",
|
||||
"version": "0.13.3",
|
||||
"license": "MIT",
|
||||
"publisher": "foam",
|
||||
"engines": {
|
||||
@@ -395,7 +395,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"dateformat": "^3.0.3",
|
||||
"foam-core": "^0.13.2",
|
||||
"foam-core": "^0.13.3",
|
||||
"gray-matter": "^4.0.2",
|
||||
"markdown-it-regex": "^0.2.0",
|
||||
"micromatch": "^4.0.2",
|
||||
|
||||
@@ -20,6 +20,33 @@ describe('createFromTemplate', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('create-note-from-default-template', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('can be cancelled while resolving FOAM_TITLE', async () => {
|
||||
const spy = jest
|
||||
.spyOn(window, 'showInputBox')
|
||||
.mockImplementation(jest.fn(() => Promise.resolve(undefined)));
|
||||
|
||||
const fileWriteSpy = jest.spyOn(workspace.fs, 'writeFile');
|
||||
|
||||
await commands.executeCommand(
|
||||
'foam-vscode.create-note-from-default-template'
|
||||
);
|
||||
|
||||
expect(spy).toBeCalledWith({
|
||||
prompt: `Enter a title for the new note`,
|
||||
value: 'Title of my New Note',
|
||||
validateInput: expect.anything(),
|
||||
});
|
||||
|
||||
expect(fileWriteSpy).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create-new-template', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { window } from 'vscode';
|
||||
import { SnippetString, window } from 'vscode';
|
||||
import {
|
||||
resolveFoamVariables,
|
||||
resolveFoamTemplateVariables,
|
||||
substituteFoamVariables,
|
||||
} from './create-from-template';
|
||||
|
||||
@@ -11,7 +12,7 @@ describe('substituteFoamVariables', () => {
|
||||
# \${AnotherVariable:default_value} <-- Unrelated to foam
|
||||
# \${AnotherVariable:default_value/(.*)/\${1:/upcase}/}} <-- Unrelated to foam
|
||||
# $AnotherVariable} <-- Unrelated to foam
|
||||
# #CURRENT_YEAR-\${CURRENT_MONTH}-$CURRENT_DAY <-- Unrelated to foam
|
||||
# $CURRENT_YEAR-\${CURRENT_MONTH}-$CURRENT_DAY <-- Unrelated to foam
|
||||
`;
|
||||
|
||||
const givenValues = new Map<string, string>();
|
||||
@@ -87,3 +88,36 @@ describe('resolveFoamVariables', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveFoamTemplateVariables', () => {
|
||||
test('Does nothing for template without Foam-specific variables', async () => {
|
||||
const input = `
|
||||
# \${AnotherVariable} <-- Unrelated to foam
|
||||
# \${AnotherVariable:default_value} <-- Unrelated to foam
|
||||
# \${AnotherVariable:default_value/(.*)/\${1:/upcase}/}} <-- Unrelated to foam
|
||||
# $AnotherVariable} <-- Unrelated to foam
|
||||
# $CURRENT_YEAR-\${CURRENT_MONTH}-$CURRENT_DAY <-- Unrelated to foam
|
||||
`;
|
||||
|
||||
const expectedMap = new Map<string, string>();
|
||||
const expectedSnippet = new SnippetString(input);
|
||||
const expected = [expectedMap, expectedSnippet];
|
||||
|
||||
expect(await resolveFoamTemplateVariables(input)).toEqual(expected);
|
||||
});
|
||||
|
||||
test('Does nothing for unknown Foam-specific variables', async () => {
|
||||
const input = `
|
||||
# $FOAM_FOO
|
||||
# \${FOAM_FOO}
|
||||
# \${FOAM_FOO:default_value}
|
||||
# \${FOAM_FOO:default_value/(.*)/\${1:/upcase}/}}
|
||||
`;
|
||||
|
||||
const expectedMap = new Map<string, string>();
|
||||
const expectedSnippet = new SnippetString(input);
|
||||
const expected = [expectedMap, expectedSnippet];
|
||||
|
||||
expect(await resolveFoamTemplateVariables(input)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,6 +18,17 @@ const templatesDir = Uri.joinPath(
|
||||
'templates'
|
||||
);
|
||||
|
||||
export class UserCancelledOperation extends Error {
|
||||
constructor(message?: string) {
|
||||
super('UserCancelledOperation');
|
||||
if (message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const knownFoamVariables = new Set(['FOAM_TITLE']);
|
||||
|
||||
const defaultTemplateDefaultText: string = '# ${FOAM_TITLE}'; // eslint-disable-line no-template-curly-in-string
|
||||
const defaultTemplateUri = Uri.joinPath(templatesDir, 'new-note.md');
|
||||
|
||||
@@ -64,16 +75,21 @@ function findFoamVariables(templateText: string): string[] {
|
||||
output.push(matches[1] || matches[2]);
|
||||
}
|
||||
const uniqVariables = [...new Set(output)];
|
||||
return uniqVariables;
|
||||
const knownVariables = uniqVariables.filter(x => knownFoamVariables.has(x));
|
||||
return knownVariables;
|
||||
}
|
||||
|
||||
function resolveFoamTitle() {
|
||||
return window.showInputBox({
|
||||
async function resolveFoamTitle() {
|
||||
const title = await window.showInputBox({
|
||||
prompt: `Enter a title for the new note`,
|
||||
value: 'Title of my New Note',
|
||||
validateInput: value =>
|
||||
value.trim().length === 0 ? 'Please enter a title' : undefined,
|
||||
});
|
||||
if (title === undefined) {
|
||||
throw new UserCancelledOperation();
|
||||
}
|
||||
return title;
|
||||
}
|
||||
class Resolver {
|
||||
promises = new Map<string, Thenable<string>>();
|
||||
@@ -167,7 +183,7 @@ async function askUserForFilepathConfirmation(
|
||||
});
|
||||
}
|
||||
|
||||
async function resolveFoamTemplateVariables(
|
||||
export async function resolveFoamTemplateVariables(
|
||||
templateText: string
|
||||
): Promise<[Map<string, string>, SnippetString]> {
|
||||
const givenValues = new Map<string, string>();
|
||||
@@ -204,9 +220,18 @@ async function createNoteFromDefaultTemplate(): Promise<void> {
|
||||
? await workspace.fs.readFile(templateUri).then(bytes => bytes.toString())
|
||||
: defaultTemplateDefaultText;
|
||||
|
||||
const [resolvedValues, templateSnippet] = await resolveFoamTemplateVariables(
|
||||
templateText
|
||||
);
|
||||
let resolvedValues, templateSnippet;
|
||||
try {
|
||||
[resolvedValues, templateSnippet] = await resolveFoamTemplateVariables(
|
||||
templateText
|
||||
);
|
||||
} catch (err) {
|
||||
if (err instanceof UserCancelledOperation) {
|
||||
return;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
const defaultSlug = resolvedValues.get('FOAM_TITLE') || 'New Note';
|
||||
const defaultFilename = `${defaultSlug}.md`;
|
||||
@@ -240,9 +265,18 @@ async function createNoteFromTemplate(
|
||||
.readFile(templateUri)
|
||||
.then(bytes => bytes.toString());
|
||||
|
||||
const [resolvedValues, templateSnippet] = await resolveFoamTemplateVariables(
|
||||
templateText
|
||||
);
|
||||
let resolvedValues, templateSnippet;
|
||||
try {
|
||||
[resolvedValues, templateSnippet] = await resolveFoamTemplateVariables(
|
||||
templateText
|
||||
);
|
||||
} catch (err) {
|
||||
if (err instanceof UserCancelledOperation) {
|
||||
return;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
const defaultSlug = resolvedValues.get('FOAM_TITLE') || 'New Note';
|
||||
const defaultFilename = `${defaultSlug}.md`;
|
||||
|
||||
@@ -11,10 +11,13 @@ import {
|
||||
FoamWorkspace,
|
||||
Matcher,
|
||||
MarkdownResourceProvider,
|
||||
Logger,
|
||||
} from 'foam-core';
|
||||
import { TextEncoder } from 'util';
|
||||
import { toVsCodeUri } from '../utils/vsc-utils';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
const position = Range.create(0, 0, 0, 100);
|
||||
|
||||
const documentStart = position.start;
|
||||
|
||||
@@ -12,14 +12,6 @@ import {
|
||||
} from './grouped-resources-tree-data-provider';
|
||||
|
||||
describe('GroupedResourcesTreeDataProvider', () => {
|
||||
const isMatch = (
|
||||
uri: URI,
|
||||
_: number,
|
||||
_g: FoamGraph,
|
||||
workspace: FoamWorkspace
|
||||
) => {
|
||||
return workspace.get(uri).title.length === 3;
|
||||
};
|
||||
const matchingNote1 = createTestNote({ uri: '/path/ABC.md', title: 'ABC' });
|
||||
const matchingNote2 = createTestNote({
|
||||
uri: '/path-bis/XYZ.md',
|
||||
@@ -39,9 +31,6 @@ describe('GroupedResourcesTreeDataProvider', () => {
|
||||
.set(matchingNote2)
|
||||
.set(excludedPathNote)
|
||||
.set(notMatchingNote);
|
||||
const graph = FoamGraph.fromWorkspace(workspace);
|
||||
|
||||
const dataStore = { read: () => '' } as any;
|
||||
|
||||
// Mock config
|
||||
const config: GroupedResourcesConfig = {
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
GroupedResourcesConfig,
|
||||
GroupedResoucesConfigGroupBy,
|
||||
} from '../settings';
|
||||
import { getContainsTooltip, getNoteTooltip, isSome, isNone } from '../utils';
|
||||
import { getContainsTooltip, getNoteTooltip, isSome } from '../utils';
|
||||
import { OPEN_COMMAND } from '../features/utility-commands';
|
||||
import { toVsCodeUri } from './vsc-utils';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user