Prettier format and chores (#448)

* fix #442 - link to known-issues replaced

* formatted settings + enable format on save + improved jest config

* removed editorconfig in foam-cli

* formatted foam-vscode

* removed author in package.json files

* minor changes to readme files

* fixed husky pre-commit hook
This commit is contained in:
Riccardo
2021-01-14 17:00:03 +01:00
committed by GitHub
parent 5a44fbc26f
commit cd92468311
32 changed files with 438 additions and 443 deletions

45
.vscode/settings.json vendored
View File

@@ -1,24 +1,25 @@
// Place your settings in this file to overwrite default and user settings.
{
"files.exclude": {
// set these to true to hide folders with the compiled JS files,
"packages/**/out": false,
"packages/**/dist": false
},
"search.exclude": {
// set this to false to include compiled JS folders in search results
"packages/**/out": true,
"packages/**/dist": true
},
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
"typescript.tsc.autoDetect": "off",
"foam.files.ignore": [
"**/.vscode/**/*",
"**/_layouts/**/*",
"**/_site/**/*",
"**/node_modules/**/*"
],
"editor.defaultFormatter": "esbenp.prettier-vscode",
// "editor.formatOnSave": true,
"prettier.requireConfig": true
}
"files.exclude": {
// set these to true to hide folders with the compiled JS files,
"packages/**/out": false,
"packages/**/dist": false
},
"search.exclude": {
// set this to false to include compiled JS folders in search results
"packages/**/out": true,
"packages/**/dist": true
},
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
"typescript.tsc.autoDetect": "off",
"foam.files.ignore": [
"**/.vscode/**/*",
"**/_layouts/**/*",
"**/_site/**/*",
"**/node_modules/**/*"
],
"editor.defaultFormatter": "esbenp.prettier-vscode",
"prettier.requireConfig": true,
"editor.formatOnSave": true,
"jest.debugCodeLens.showWhenTestStateIn": ["fail", "unknown", "pass"]
}

View File

@@ -76,7 +76,7 @@ To learn more about how to use **Foam**, read the [[recipes]].
Getting stuck in the setup? Read the [[frequently-asked-questions]].
There are [[known-issues]], and I'm sure, many unknown issues! Please [report them on GitHub](http://github.com/foambubble/foam/issues)!
Check our [issues on GitHub](http://github.com/foambubble/foam/issues) if you get stuck on something, and create a new one if something doesn't seem right!
## Features

View File

@@ -3,7 +3,6 @@
"version": "0.2.0",
"description": "Foam",
"repository": "git@github.com:foambubble/foam.git",
"author": "Jani Eväkallio <jani.evakallio@gmail.com>",
"license": "MIT",
"private": "true",
"workspaces": [
@@ -33,7 +32,7 @@
},
"husky": {
"hooks": {
"pre-commit": "tsdx lint"
"pre-commit": "yarn lint"
}
},
"prettier": {

View File

@@ -1,11 +0,0 @@
root = true
[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

View File

@@ -2,7 +2,6 @@
"name": "foam-cli",
"description": "Foam CLI",
"version": "0.7.4",
"author": "Jani Eväkallio @jevakallio",
"bin": {
"foam": "./bin/run"
},

View File

@@ -1,20 +1,21 @@
# Foam Core
Repository for tooling used by the other modules
This module contains the core functions, model, and API of Foam.
It is used by its clients to integrate Foam in various use cases, from VsCode extension, to CLI, to CI integrations.
## Local Development
Below is a list of commands you will probably find useful.
### `npm start` or `yarn start`
### `yarn watch`
Runs the project in development/watch mode. Your project will be rebuilt upon changes.
### `npm run build` or `yarn build`
### `yarn build`
Bundles the package to the `dist` folder. The package is optimized and bundled with Rollup into multiple formats (CommonJS, UMD, and ES Module).
### `npm test` or `yarn test`
### `yarn test`
Runs the test watcher (Jest) in an interactive mode.
By default, runs tests related to files changed since the last commit.

View File

@@ -1,6 +1,5 @@
{
"name": "foam-core",
"author": "Jani Eväkallio",
"repository": "https://github.com/foambubble/foam",
"version": "0.7.4",
"license": "MIT",

View File

@@ -2,7 +2,6 @@
"name": "foam-vscode",
"displayName": "Foam for VSCode (Wikilinks to Markdown)",
"description": "Generate markdown reference lists from wikilinks in a workspace",
"author": "Jani Eväkallio",
"repository": {
"url": "https://github.com/foambubble/foam",
"type": "git"
@@ -180,7 +179,7 @@
"build": "tsc -p ./",
"pretest": "yarn build",
"test": "node ./out/test/run-tests.js",
"lint": "eslint src --ext ts",
"lint": "tsdx lint",
"clean": "rimraf out",
"watch": "tsc --build ./tsconfig.json ../foam-core/tsconfig.json --watch",
"vscode:start-debugging": "yarn clean && yarn watch",

View File

@@ -1,17 +1,11 @@
import {
Selection,
Uri,
window,
workspace,
WorkspaceConfiguration
} from "vscode";
import { dirname, join } from "path";
import dateFormat from "dateformat";
import * as fs from "fs";
import { docConfig, focusNote, pathExists } from "./utils";
import { workspace, WorkspaceConfiguration } from 'vscode';
import { dirname, join } from 'path';
import dateFormat from 'dateformat';
import * as fs from 'fs';
import { docConfig, focusNote, pathExists } from './utils';
async function openDailyNoteFor(date?: Date) {
const foamConfiguration = workspace.getConfiguration("foam");
const foamConfiguration = workspace.getConfiguration('foam');
const currentDate = date !== undefined ? date : new Date();
const dailyNotePath = getDailyNotePath(foamConfiguration, currentDate);
@@ -26,7 +20,7 @@ async function openDailyNoteFor(date?: Date) {
function getDailyNotePath(configuration: WorkspaceConfiguration, date: Date) {
const rootDirectory = workspace.workspaceFolders[0].uri.fsPath;
const dailyNoteDirectory: string =
configuration.get("openDailyNote.directory") ?? ".";
configuration.get('openDailyNote.directory') ?? '.';
const dailyNoteFilename = getDailyNoteFileName(configuration, date);
return join(rootDirectory, dailyNoteDirectory, dailyNoteFilename);
@@ -37,10 +31,10 @@ function getDailyNoteFileName(
date: Date
): string {
const filenameFormat: string = configuration.get(
"openDailyNote.filenameFormat"
'openDailyNote.filenameFormat'
);
const fileExtension: string = configuration.get(
"openDailyNote.fileExtension"
'openDailyNote.fileExtension'
);
return `${dateFormat(date, filenameFormat, false)}.${fileExtension}`;
@@ -58,8 +52,8 @@ async function createDailyNoteIfNotExists(
await createDailyNoteDirectoryIfNotExists(dailyNotePath);
const titleFormat: string =
configuration.get("openDailyNote.titleFormat") ??
configuration.get("openDailyNote.filenameFormat");
configuration.get('openDailyNote.titleFormat') ??
configuration.get('openDailyNote.filenameFormat');
await fs.promises.writeFile(
dailyNotePath,
@@ -83,5 +77,5 @@ export {
openDailyNoteFor,
getDailyNoteFileName,
createDailyNoteIfNotExists,
getDailyNotePath
getDailyNotePath,
};

View File

@@ -1,19 +1,16 @@
"use strict";
import { workspace, ExtensionContext, window, Uri } from "vscode";
import { workspace, ExtensionContext, window } from 'vscode';
import {
bootstrap,
FoamConfig,
Foam,
Services,
Logger,
FileDataStore
} from "foam-core";
FileDataStore,
} from 'foam-core';
import { features } from "./features";
import { getConfigFromVscode } from "./services/config";
import { VsCodeOutputLogger, exposeLogger } from "./services/logging";
import { VsCodeDataStore } from "./services/datastore";
import { features } from './features';
import { getConfigFromVscode } from './services/config';
import { VsCodeOutputLogger, exposeLogger } from './services/logging';
export async function activate(context: ExtensionContext) {
const logger = new VsCodeOutputLogger();
@@ -21,14 +18,14 @@ export async function activate(context: ExtensionContext) {
exposeLogger(context, logger);
try {
Logger.info("Starting Foam");
Logger.info('Starting Foam');
const config: FoamConfig = getConfigFromVscode();
const watcher = workspace.createFileSystemWatcher("**/*");
const watcher = workspace.createFileSystemWatcher('**/*');
const dataStore = new FileDataStore(config, watcher);
const services: Services = {
dataStore: dataStore
dataStore: dataStore,
};
const foamPromise: Promise<Foam> = bootstrap(config, services);
@@ -41,7 +38,7 @@ export async function activate(context: ExtensionContext) {
context.subscriptions.push(dataStore, foam, watcher);
} catch (e) {
Logger.error("An error occurred while bootstrapping Foam", e);
Logger.error('An error occurred while bootstrapping Foam', e);
window.showErrorMessage(
`An error occurred while bootstrapping Foam. ${e.stack}`
);

View File

@@ -1,12 +1,4 @@
import {
env,
window,
Uri,
workspace,
Position,
Selection,
commands,
} from 'vscode';
import { env, window, Uri, Position, Selection, commands } from 'vscode';
import * as vscode from 'vscode';
describe('copyWithoutBrackets', () => {

View File

@@ -1,21 +1,19 @@
import {
window,
env,
ExtensionContext,
commands,
} from "vscode";
import { FoamFeature } from "../types";
import { removeBrackets } from "../utils";
import { window, env, ExtensionContext, commands } from 'vscode';
import { FoamFeature } from '../types';
import { removeBrackets } from '../utils';
const feature: FoamFeature = {
activate: (context: ExtensionContext) => {
context.subscriptions.push(
commands.registerCommand("foam-vscode.copy-without-brackets", copyWithoutBrackets)
commands.registerCommand(
'foam-vscode.copy-without-brackets',
copyWithoutBrackets
)
);
},
};
async function copyWithoutBrackets () {
async function copyWithoutBrackets() {
// Get the active text editor
const editor = window.activeTextEditor;

View File

@@ -4,17 +4,17 @@ import {
ExtensionContext,
workspace,
Uri,
SnippetString
} from "vscode";
import * as path from "path";
import { FoamFeature } from "../types";
import { TextEncoder } from "util";
import { focusNote } from "../utils";
SnippetString,
} from 'vscode';
import * as path from 'path';
import { FoamFeature } from '../types';
import { TextEncoder } from 'util';
import { focusNote } from '../utils';
const templatesDir = `${workspace.workspaceFolders[0].uri.path}/.foam/templates`;
async function getTemplates(): Promise<string[]> {
const templates = await workspace.findFiles(".foam/templates/**.md");
const templates = await workspace.findFiles('.foam/templates/**.md');
// parse title, not whole file!
return templates.map(template => path.basename(template.path));
}
@@ -23,7 +23,7 @@ const feature: FoamFeature = {
activate: (context: ExtensionContext) => {
context.subscriptions.push(
commands.registerCommand(
"foam-vscode.create-note-from-template",
'foam-vscode.create-note-from-template',
async () => {
const templates = await getTemplates();
const activeFile = window.activeTextEditor?.document?.fileName;
@@ -34,14 +34,14 @@ const feature: FoamFeature = {
const selectedTemplate = await window.showQuickPick(templates);
const folder = await window.showInputBox({
prompt: `Where should the template be created?`,
value: currentDir
value: currentDir,
});
let filename = await window.showInputBox({
prompt: `Enter the filename for the new note`,
value: ``,
validateInput: value =>
value.length ? undefined : "Please enter a value!"
value.length ? undefined : 'Please enter a value!',
});
filename = path.extname(filename).length
? filename
@@ -54,14 +54,14 @@ const feature: FoamFeature = {
const snippet = new SnippetString(templateText.toString());
await workspace.fs.writeFile(
Uri.file(targetFile),
new TextEncoder().encode("")
new TextEncoder().encode('')
);
await focusNote(targetFile, true);
await window.activeTextEditor.insertSnippet(snippet);
}
)
);
}
},
};
export default feature;

View File

@@ -1,10 +1,10 @@
import * as vscode from "vscode";
import * as path from "path";
import { FoamFeature } from "../types";
import { Foam, Logger } from "foam-core";
import { TextDecoder } from "util";
import { getGraphStyle, getTitleMaxLength } from "../settings";
import { isSome } from "../utils";
import * as vscode from 'vscode';
import * as path from 'path';
import { FoamFeature } from '../types';
import { Foam, Logger } from 'foam-core';
import { TextDecoder } from 'util';
import { getGraphStyle, getTitleMaxLength } from '../settings';
import { isSome } from '../utils';
const feature: FoamFeature = {
activate: (context: vscode.ExtensionContext, foamPromise: Promise<Foam>) => {
@@ -19,11 +19,11 @@ const feature: FoamFeature = {
}
});
vscode.commands.registerCommand("foam-vscode.show-graph", async () => {
vscode.commands.registerCommand('foam-vscode.show-graph', async () => {
if (panel) {
const columnToShowIn = vscode.window.activeTextEditor
? vscode.window.activeTextEditor.viewColumn
: undefined;
? vscode.window.activeTextEditor.viewColumn
: undefined;
panel.reveal(columnToShowIn);
} else {
const foam = await foamPromise;
@@ -43,83 +43,83 @@ const feature: FoamFeature = {
});
vscode.window.onDidChangeActiveTextEditor(e => {
if (e.document.uri.scheme === "file") {
if (e.document.uri.scheme === 'file') {
const note = foam.notes.getNote(e.document.uri);
if (isSome(note)) {
panel.webview.postMessage({
type: "didSelectNote",
payload: note.uri.path
type: 'didSelectNote',
payload: note.uri.path,
});
}
}
});
}
});
}
},
};
function updateGraph(panel: vscode.WebviewPanel, foam: Foam) {
const graph = generateGraphData(foam);
panel.webview.postMessage({
type: "didUpdateGraphData",
payload: graph
type: 'didUpdateGraphData',
payload: graph,
});
}
function generateGraphData(foam: Foam) {
const graph = {
nodes: {},
edges: new Set()
edges: new Set(),
};
foam.notes.getNotes().forEach(n => {
const links = foam.notes.getForwardLinks(n.uri);
graph.nodes[n.uri.path] = {
id: n.uri.path,
type: "note",
type: 'note',
uri: n.uri,
title: cutTitle(n.title)
title: cutTitle(n.title),
};
links.forEach(link => {
if (!(link.to.path in graph.nodes)) {
graph.nodes[link.to.path] = {
id: link.to,
type: "placeholder",
type: 'placeholder',
uri: `virtual:${link.to}`,
title:
"slug" in link.link
'slug' in link.link
? cutTitle(link.link.slug)
: cutTitle(link.link.label)
: cutTitle(link.link.label),
};
}
graph.edges.add({
source: link.from.path,
target: link.to.path
target: link.to.path,
});
});
});
return {
nodes: graph.nodes,
links: Array.from(graph.edges)
links: Array.from(graph.edges),
};
}
function cutTitle(title: string): string {
const maxLen = getTitleMaxLength();
if (maxLen > 0 && title.length > maxLen) {
return title.substring(0, maxLen).concat("...");
return title.substring(0, maxLen).concat('...');
}
return title;
}
async function createGraphPanel(foam: Foam, context: vscode.ExtensionContext) {
const panel = vscode.window.createWebviewPanel(
"foam-graph",
"Foam Graph",
'foam-graph',
'Foam Graph',
vscode.ViewColumn.Two,
{
enableScripts: true,
retainContextWhenHidden: true
retainContextWhenHidden: true,
}
);
@@ -128,7 +128,7 @@ async function createGraphPanel(foam: Foam, context: vscode.ExtensionContext) {
panel.webview.onDidReceiveMessage(
async message => {
switch (message.type) {
case "webviewDidLoad":
case 'webviewDidLoad':
const styles = getGraphStyle();
panel.webview.postMessage({
type: 'didUpdateStyle',
@@ -137,7 +137,7 @@ async function createGraphPanel(foam: Foam, context: vscode.ExtensionContext) {
updateGraph(panel, foam);
break;
case "webviewDidSelectNode":
case 'webviewDidSelectNode':
const noteUri = vscode.Uri.parse(message.payload);
const selectedNote = foam.notes.getNote(noteUri);
@@ -149,8 +149,8 @@ async function createGraphPanel(foam: Foam, context: vscode.ExtensionContext) {
}
break;
case "error":
Logger.error("An error occurred in the graph view", message.payload);
case 'error':
Logger.error('An error occurred in the graph view', message.payload);
break;
}
},
@@ -166,27 +166,27 @@ async function getWebviewContent(
panel: vscode.WebviewPanel
) {
const webviewPath = vscode.Uri.file(
path.join(context.extensionPath, "static", "dataviz.html")
path.join(context.extensionPath, 'static', 'dataviz.html')
);
const file = await vscode.workspace.fs.readFile(webviewPath);
const text = new TextDecoder("utf-8").decode(file);
const text = new TextDecoder('utf-8').decode(file);
const webviewUri = (fileName: string) =>
panel.webview
.asWebviewUri(
vscode.Uri.file(path.join(context.extensionPath, "static", fileName))
vscode.Uri.file(path.join(context.extensionPath, 'static', fileName))
)
.toString();
const graphDirectory = path.join("graphs", "default");
const graphDirectory = path.join('graphs', 'default');
const textWithVariables = text
.replace(
"${graphPath}",
"{{" + path.join(graphDirectory, "graph.js") + "}}"
'${graphPath}', // eslint-disable-line
'{{' + path.join(graphDirectory, 'graph.js') + '}}'
)
.replace(
"${graphStylesPath}",
"{{" + path.join(graphDirectory, "graph.css") + "}}"
'${graphStylesPath}', // eslint-disable-line
'{{' + path.join(graphDirectory, 'graph.css') + '}}'
);
// Basic templating. Will replace the script paths with the

View File

@@ -4,31 +4,31 @@ import {
ExtensionContext,
commands,
Range,
ProgressLocation
} from "vscode";
import * as fs from "fs";
import { FoamFeature } from "../types";
ProgressLocation,
} from 'vscode';
import * as fs from 'fs';
import { FoamFeature } from '../types';
import {
applyTextEdit,
generateLinkReferences,
generateHeading,
Foam
} from "foam-core";
Foam,
} from 'foam-core';
import {
getWikilinkDefinitionSetting,
LinkReferenceDefinitionsSetting
} from "../settings";
import { astPositionToVsCodePosition } from "../utils";
LinkReferenceDefinitionsSetting,
} from '../settings';
import { astPositionToVsCodePosition } from '../utils';
const feature: FoamFeature = {
activate: (context: ExtensionContext, foamPromise: Promise<Foam>) => {
context.subscriptions.push(
commands.registerCommand("foam-vscode.janitor", async () =>
commands.registerCommand('foam-vscode.janitor', async () =>
janitor(await foamPromise)
)
);
}
},
};
async function janitor(foam: Foam) {
@@ -44,7 +44,7 @@ async function janitor(foam: Foam) {
const outcome = await window.withProgress(
{
location: ProgressLocation.Notification,
title: `Running Foam Janitor across ${noOfFiles} files!`
title: `Running Foam Janitor across ${noOfFiles} files!`,
},
() => runJanitor(foam)
);
@@ -75,8 +75,8 @@ async function runJanitor(foam: Foam) {
const dirtyTextDocuments = workspace.textDocuments.filter(
textDocument =>
(textDocument.languageId === "markdown" ||
textDocument.languageId === "mdx") &&
(textDocument.languageId === 'markdown' ||
textDocument.languageId === 'mdx') &&
textDocument.isDirty
);
@@ -151,6 +151,7 @@ async function runJanitor(foam: Foam) {
if (heading || definitions) {
// Apply Edits
/* eslint-disable */
await editor.edit(editBuilder => {
// Note: The ordering matters. Definitions need to be inserted
// before heading, since inserting a heading changes line numbers below
@@ -169,13 +170,14 @@ async function runJanitor(foam: Foam) {
editBuilder.replace(start, heading.newText);
}
});
/* eslint-enable */
}
}
return {
updatedHeadingCount,
updatedDefinitionListCount,
changedAnyFiles: updatedHeadingCount + updatedDefinitionListCount
changedAnyFiles: updatedHeadingCount + updatedDefinitionListCount,
};
}

View File

@@ -1,13 +1,13 @@
import { ExtensionContext, commands } from "vscode";
import { FoamFeature } from "../types";
import { openDailyNoteFor } from "../dated-notes";
import { ExtensionContext, commands } from 'vscode';
import { FoamFeature } from '../types';
import { openDailyNoteFor } from '../dated-notes';
const feature: FoamFeature = {
activate: (context: ExtensionContext) => {
context.subscriptions.push(
commands.registerCommand("foam-vscode.open-daily-note", openDailyNoteFor)
commands.registerCommand('foam-vscode.open-daily-note', openDailyNoteFor)
);
}
},
};
export default feature;

View File

@@ -7,15 +7,15 @@ import {
CompletionItem,
CompletionItemKind,
CompletionList,
CompletionTriggerKind
} from "vscode";
CompletionTriggerKind,
} from 'vscode';
import {
createDailyNoteIfNotExists,
getDailyNoteFileName,
openDailyNoteFor,
getDailyNotePath
} from "../dated-notes";
import { FoamFeature } from "../types";
getDailyNotePath,
} from '../dated-notes';
import { FoamFeature } from '../types';
interface DateSnippet {
snippet: string;
@@ -24,18 +24,18 @@ interface DateSnippet {
}
const daysOfWeek = [
{ day: "sunday", index: 0 },
{ day: "monday", index: 1 },
{ day: "tuesday", index: 2 },
{ day: "wednesday", index: 3 },
{ day: "thursday", index: 4 },
{ day: "friday", index: 5 },
{ day: "saturday", index: 6 }
{ day: 'sunday', index: 0 },
{ day: 'monday', index: 1 },
{ day: 'tuesday', index: 2 },
{ day: 'wednesday', index: 3 },
{ day: 'thursday', index: 4 },
{ day: 'friday', index: 5 },
{ day: 'saturday', index: 6 },
];
type AfterCompletionOptions = "noop" | "createNote" | "navigateToNote";
const foamConfig = workspace.getConfiguration("foam");
type AfterCompletionOptions = 'noop' | 'createNote' | 'navigateToNote';
const foamConfig = workspace.getConfiguration('foam');
const foamNavigateOnSelect: AfterCompletionOptions = foamConfig.get(
"dateSnippets.afterCompletion"
'dateSnippets.afterCompletion'
);
const generateDayOfWeekSnippets = (): DateSnippet[] => {
@@ -51,7 +51,7 @@ const generateDayOfWeekSnippets = (): DateSnippet[] => {
return {
date: target,
detail: `Get a daily note link for ${day}`,
snippet: `/${day}`
snippet: `/${day}`,
};
});
return snippets;
@@ -64,49 +64,57 @@ const createCompletionItem = ({ snippet, date, detail }: DateSnippet) => {
);
completionItem.insertText = getDailyNoteLink(date);
completionItem.detail = `${completionItem.insertText} - ${detail}`;
if (foamNavigateOnSelect !== "noop") {
if (foamNavigateOnSelect !== 'noop') {
completionItem.command = {
command: "foam-vscode.open-dated-note",
title: "Open a note for the given date",
arguments: [date]
command: 'foam-vscode.open-dated-note',
title: 'Open a note for the given date',
arguments: [date],
};
}
return completionItem;
};
const getDailyNoteLink = (date: Date) => {
const foamExtension = foamConfig.get("openDailyNote.fileExtension");
const foamExtension = foamConfig.get('openDailyNote.fileExtension');
const name = getDailyNoteFileName(foamConfig, date);
return `[[${name.replace(`.${foamExtension}`, "")}]]`;
return `[[${name.replace(`.${foamExtension}`, '')}]]`;
};
const snippets: (() => DateSnippet)[] = [
() => ({
detail: "Insert a link to today's daily note",
snippet: "/day",
date: new Date()
snippet: '/day',
date: new Date(),
}),
() => ({
detail: "Insert a link to today's daily note",
snippet: "/today",
date: new Date()
snippet: '/today',
date: new Date(),
}),
() => {
const today = new Date();
return {
detail: "Insert a link to tomorrow's daily note",
snippet: "/tomorrow",
date: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1)
snippet: '/tomorrow',
date: new Date(
today.getFullYear(),
today.getMonth(),
today.getDate() + 1
),
};
},
() => {
const today = new Date();
return {
detail: "Insert a link to yesterday's daily note",
snippet: "/yesterday",
date: new Date(today.getFullYear(), today.getMonth(), today.getDate() - 1)
snippet: '/yesterday',
date: new Date(
today.getFullYear(),
today.getMonth(),
today.getDate() - 1
),
};
}
},
];
const computedSnippets: ((number: number) => DateSnippet)[] = [
@@ -119,7 +127,7 @@ const computedSnippets: ((number: number) => DateSnippet)[] = [
today.getFullYear(),
today.getMonth(),
today.getDate() + days
)
),
};
},
(weeks: number) => {
@@ -131,7 +139,7 @@ const computedSnippets: ((number: number) => DateSnippet)[] = [
today.getFullYear(),
today.getMonth(),
today.getDate() + 7 * weeks
)
),
};
},
(months: number) => {
@@ -143,7 +151,7 @@ const computedSnippets: ((number: number) => DateSnippet)[] = [
today.getFullYear(),
today.getMonth() + months,
today.getDate()
)
),
};
},
(years: number) => {
@@ -155,9 +163,9 @@ const computedSnippets: ((number: number) => DateSnippet)[] = [
today.getFullYear() + years,
today.getMonth(),
today.getDate()
)
),
};
}
},
];
const completions: CompletionItemProvider = {
@@ -170,10 +178,10 @@ const completions: CompletionItemProvider = {
const completionItems = [
...snippets.map(item => createCompletionItem(item())),
...generateDayOfWeekSnippets().map(item => createCompletionItem(item))
...generateDayOfWeekSnippets().map(item => createCompletionItem(item)),
];
return completionItems;
}
},
};
const computedCompletions: CompletionItemProvider = {
@@ -187,7 +195,7 @@ const computedCompletions: CompletionItemProvider = {
const range = document.getWordRangeAtPosition(position, /\S+/);
const snippetString = document.getText(range);
const matches = snippetString.match(/(\d+)/);
const number: string = matches ? matches[0] : "1";
const number: string = matches ? matches[0] : '1';
const completionItems = computedSnippets.map(item => {
const completionItem = createCompletionItem(item(parseInt(number)));
completionItem.range = range;
@@ -195,14 +203,14 @@ const computedCompletions: CompletionItemProvider = {
});
// We still want the list to be treated as "incomplete", because the user may add another number
return new CompletionList(completionItems, true);
}
},
};
const datedNoteCommand = (date: Date) => {
if (foamNavigateOnSelect === "navigateToNote") {
if (foamNavigateOnSelect === 'navigateToNote') {
return openDailyNoteFor(date);
}
if (foamNavigateOnSelect === "createNote") {
if (foamNavigateOnSelect === 'createNote') {
return createDailyNoteIfNotExists(
foamConfig,
getDailyNotePath(foamConfig, date),
@@ -214,18 +222,18 @@ const datedNoteCommand = (date: Date) => {
const feature: FoamFeature = {
activate: (context: ExtensionContext) => {
context.subscriptions.push(
commands.registerCommand("foam-vscode.open-dated-note", date =>
commands.registerCommand('foam-vscode.open-dated-note', date =>
datedNoteCommand(date)
)
);
languages.registerCompletionItemProvider("markdown", completions, "/");
languages.registerCompletionItemProvider('markdown', completions, '/');
languages.registerCompletionItemProvider(
"markdown",
'markdown',
computedCompletions,
"/",
"+"
'/',
'+'
);
}
},
};
export default feature;

View File

@@ -1,6 +1,6 @@
import * as vscode from "vscode";
import { FoamFeature } from "../../types";
import { Foam, Note } from "foam-core";
import * as vscode from 'vscode';
import { FoamFeature } from '../../types';
import { Foam, Note } from 'foam-core';
const feature: FoamFeature = {
activate: async (
@@ -11,12 +11,12 @@ const feature: FoamFeature = {
const provider = new TagsProvider(foam);
context.subscriptions.push(
vscode.window.registerTreeDataProvider(
"foam-vscode.tags-explorer",
'foam-vscode.tags-explorer',
provider
)
);
foam.notes.onDidUpdateNote(() => provider.refresh());
}
},
};
export default feature;
@@ -68,7 +68,7 @@ export class TagsProvider implements vscode.TreeDataProvider<TagTreeItem> {
});
return Promise.resolve([
new TagSearch(element.tag),
...references.sort((a, b) => a.title.localeCompare(b.title))
...references.sort((a, b) => a.title.localeCompare(b.title)),
]);
}
if (!element) {
@@ -89,13 +89,13 @@ export class Tag extends vscode.TreeItem {
) {
super(tag, vscode.TreeItemCollapsibleState.Collapsed);
this.description = `${this.noteUris.length} reference${
this.noteUris.length !== 1 ? "s" : ""
this.noteUris.length !== 1 ? 's' : ''
}`;
this.tooltip = this.description;
}
iconPath = new vscode.ThemeIcon("symbol-number");
contextValue = "tag";
iconPath = new vscode.ThemeIcon('symbol-number');
contextValue = 'tag';
}
export class TagSearch extends vscode.TreeItem {
@@ -104,21 +104,21 @@ export class TagSearch extends vscode.TreeItem {
const searchString = `#${tag}`;
this.tooltip = `Search ${searchString} in workspace`;
this.command = {
command: "workbench.action.findInFiles",
command: 'workbench.action.findInFiles',
arguments: [
{
query: searchString,
triggerSearch: true,
matchWholeWord: true,
isCaseSensitive: true
}
isCaseSensitive: true,
},
],
title: "Search"
title: 'Search',
};
}
iconPath = new vscode.ThemeIcon("search");
contextValue = "tag-search";
iconPath = new vscode.ThemeIcon('search');
contextValue = 'tag-search';
}
export class TagReference extends vscode.TreeItem {
@@ -142,18 +142,18 @@ export class TagReference extends vscode.TreeItem {
// TODO I like about this showing the git state of the note, but I don't like the md icon
this.resourceUri = resourceUri;
this.command = {
command: "vscode.open",
command: 'vscode.open',
arguments: [
resourceUri,
{
preview: true,
selection: selection
}
selection: selection,
},
],
title: "Open File"
title: 'Open File',
};
}
iconPath = new vscode.ThemeIcon("note");
contextValue = "reference";
iconPath = new vscode.ThemeIcon('note');
contextValue = 'reference';
}

View File

@@ -1,4 +1,4 @@
import { uniq } from "lodash";
import { uniq } from 'lodash';
import {
CancellationToken,
CodeLens,
@@ -10,8 +10,8 @@ import {
TextDocument,
window,
workspace,
Position
} from "vscode";
Position,
} from 'vscode';
import {
createMarkdownReferences,
@@ -19,33 +19,33 @@ import {
NoteGraphAPI,
Foam,
LINK_REFERENCE_DEFINITION_HEADER,
LINK_REFERENCE_DEFINITION_FOOTER
} from "foam-core";
LINK_REFERENCE_DEFINITION_FOOTER,
} from 'foam-core';
import {
hasEmptyTrailing,
docConfig,
loadDocConfig,
isMdEditor,
mdDocSelector,
getText
} from "../utils";
import { FoamFeature } from "../types";
getText,
} from '../utils';
import { FoamFeature } from '../types';
import {
getWikilinkDefinitionSetting,
LinkReferenceDefinitionsSetting
} from "../settings";
LinkReferenceDefinitionsSetting,
} from '../settings';
const feature: FoamFeature = {
activate: async (context: ExtensionContext, foamPromise: Promise<Foam>) => {
const foam = await foamPromise;
context.subscriptions.push(
commands.registerCommand("foam-vscode.update-wikilinks", () =>
commands.registerCommand('foam-vscode.update-wikilinks', () =>
updateReferenceList(foam.notes)
),
workspace.onWillSaveTextDocument(e => {
if (e.document.languageId === "markdown") {
if (e.document.languageId === 'markdown') {
updateDocumentInNoteGraph(foam, e.document);
e.waitUntil(updateReferenceList(foam.notes));
}
@@ -67,7 +67,7 @@ const feature: FoamFeature = {
updateDocumentInNoteGraph(foam, editor.document);
updateReferenceList(foam.notes);
});
}
},
};
function updateDocumentInNoteGraph(foam: Foam, document: TextDocument) {
@@ -119,7 +119,7 @@ async function updateReferenceList(foam: NoteGraphAPI) {
// references must always be preceded by an empty line
const spacing = doc.lineAt(range.start.line - 1).isEmptyOrWhitespace
? ""
? ''
: docConfig.eol;
await editor.edit(editBuilder => {
@@ -161,7 +161,7 @@ function generateReferenceList(
return [
LINK_REFERENCE_DEFINITION_HEADER,
...references,
LINK_REFERENCE_DEFINITION_FOOTER
LINK_REFERENCE_DEFINITION_FOOTER,
];
}
@@ -220,14 +220,14 @@ class WikilinkReferenceCodeLensProvider implements CodeLensProvider {
const oldRefs = getText(range).replace(/\r?\n|\r/g, docConfig.eol);
const newRefs = refs.join(docConfig.eol);
let status = oldRefs === newRefs ? "up to date" : "out of date";
let status = oldRefs === newRefs ? 'up to date' : 'out of date';
return [
new CodeLens(range, {
arguments: [],
title: `Link references (${status})`,
command: ""
})
command: '',
}),
];
}
}

View File

@@ -1,6 +1,6 @@
import { workspace } from "vscode";
import { FoamConfig, createConfigFromFolders } from "foam-core";
import { getIgnoredFilesSetting } from "../settings";
import { workspace } from 'vscode';
import { FoamConfig, createConfigFromFolders } from 'foam-core';
import { getIgnoredFilesSetting } from '../settings';
// TODO this is still to be improved - foam config should
// not be dependent on vscode but at the moment it's convenient
@@ -10,6 +10,6 @@ export const getConfigFromVscode = (): FoamConfig => {
const excludeGlobs = getIgnoredFilesSetting();
return createConfigFromFolders(workspaceFolders, {
ignore: excludeGlobs.map(g => g.toString())
ignore: excludeGlobs.map(g => g.toString()),
});
};

View File

@@ -4,11 +4,11 @@ import {
URI,
FoamConfig,
IDisposable,
Logger
} from "foam-core";
import { workspace, FileSystemWatcher, EventEmitter } from "vscode";
import { TextDecoder } from "util";
import { isSome } from "../utils";
Logger,
} from 'foam-core';
import { workspace, FileSystemWatcher, EventEmitter } from 'vscode';
import { TextDecoder } from 'util';
import { isSome } from '../utils';
export class VsCodeDataStore implements IDataStore, IDisposable {
onDidCreateEmitter = new EventEmitter<URI>();
@@ -22,23 +22,23 @@ export class VsCodeDataStore implements IDataStore, IDisposable {
files: URI[];
constructor(private config: FoamConfig) {
this.watcher = workspace.createFileSystemWatcher("**/*");
this.watcher = workspace.createFileSystemWatcher('**/*');
this.watcher.onDidCreate(async uri => {
await this.listFiles();
if (this.isMatch(uri)) {
Logger.info("Created: ", uri);
Logger.info('Created: ', uri);
this.onDidCreateEmitter.fire(uri);
}
});
this.watcher.onDidChange(uri => {
if (this.isMatch(uri)) {
Logger.info("Updated: ", uri);
Logger.info('Updated: ', uri);
this.onDidChangeEmitter.fire(uri);
}
});
this.watcher.onDidDelete(uri => {
if (this.isMatch(uri)) {
Logger.info("Deleted: ", uri);
Logger.info('Deleted: ', uri);
this.files = this.files.filter(f => f.path !== uri.path);
this.onDidDeleteEmitter.fire(uri);
}
@@ -47,8 +47,8 @@ export class VsCodeDataStore implements IDataStore, IDisposable {
async listFiles(): Promise<URI[]> {
this.files = await workspace.findFiles(
`{${this.config.includeGlobs.join(",")}}`,
`{${this.config.ignoreGlobs.join(",")}}`
`{${this.config.includeGlobs.join(',')}}`,
`{${this.config.ignoreGlobs.join(',')}}`
);
return this.files;

View File

@@ -1,17 +1,17 @@
import { window, commands, ExtensionContext } from "vscode";
import { ILogger, IDisposable, LogLevel, BaseLogger } from "foam-core";
import { getFoamLoggerLevel } from "../settings";
import { window, commands, ExtensionContext } from 'vscode';
import { ILogger, IDisposable, LogLevel, BaseLogger } from 'foam-core';
import { getFoamLoggerLevel } from '../settings';
export interface VsCodeLogger extends ILogger, IDisposable {
show();
}
export class VsCodeOutputLogger extends BaseLogger implements VsCodeLogger {
private channel = window.createOutputChannel("Foam");
private channel = window.createOutputChannel('Foam');
constructor() {
super(getFoamLoggerLevel());
this.channel.appendLine("Foam Logging: " + getFoamLoggerLevel());
this.channel.appendLine('Foam Logging: ' + getFoamLoggerLevel());
}
log(lvl: LogLevel, msg?: any, ...extra: any[]): void {
@@ -42,12 +42,12 @@ export const exposeLogger = (
logger: VsCodeLogger
): void => {
context.subscriptions.push(
commands.registerCommand("foam-vscode.set-log-level", async () => {
const items: LogLevel[] = ["debug", "info", "warn", "error"];
commands.registerCommand('foam-vscode.set-log-level', async () => {
const items: LogLevel[] = ['debug', 'info', 'warn', 'error'];
const level = await window.showQuickPick(
items.map(item => ({
label: item,
description: item === logger.getLevel() && "Current"
description: item === logger.getLevel() && 'Current',
}))
);
logger.setLevel(level.label);

View File

@@ -1,28 +1,28 @@
import * as path from "path";
import fs from "fs";
import os from "os";
import { runTests } from "vscode-test";
import * as path from 'path';
import fs from 'fs';
import os from 'os';
import { runTests } from 'vscode-test';
async function main() {
try {
// The folder containing the Extension Manifest package.json
// Passed to `--extensionDevelopmentPath`
const extensionDevelopmentPath = path.resolve(__dirname, "../../");
const extensionDevelopmentPath = path.resolve(__dirname, '../../');
// The path to the extension test script
// Passed to --extensionTestsPath
const extensionTestsPath = path.resolve(__dirname, "./suite");
const extensionTestsPath = path.resolve(__dirname, './suite');
const tmpWorkspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), "foam-"));
const tmpWorkspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'foam-'));
// Download VS Code, unzip it and run the integration test
await runTests({
extensionDevelopmentPath,
extensionTestsPath,
launchArgs: [tmpWorkspaceDir, "--disable-extensions"],
launchArgs: [tmpWorkspaceDir, '--disable-extensions'],
});
} catch (err) {
console.error("Failed to run tests");
console.error('Failed to run tests');
process.exit(1);
}
}

View File

@@ -9,10 +9,10 @@
* and so on..
*/
import path from "path";
import { runCLI } from "@jest/core";
import path from 'path';
import { runCLI } from '@jest/core';
const rootDir = path.resolve(__dirname, "../..");
const rootDir = path.resolve(__dirname, '../..');
export function run(): Promise<void> {
process.stdout.write = (buffer: string) => {
@@ -23,25 +23,25 @@ export function run(): Promise<void> {
console.error(buffer);
return true;
};
process.env.FORCE_COLOR = "1";
process.env.NODE_ENV = "test";
process.env.FORCE_COLOR = '1';
process.env.NODE_ENV = 'test';
return new Promise(async (resolve, reject) => {
try {
const { results } = await runCLI(
{
rootDir,
roots: ["<rootDir>/src"],
transform: JSON.stringify({ "^.+\\.ts$": "ts-jest" }),
roots: ['<rootDir>/src'],
transform: JSON.stringify({ '^.+\\.ts$': 'ts-jest' }),
runInBand: true,
testRegex: "\\.(test|spec)\\.ts$",
testRegex: '\\.(test|spec)\\.ts$',
testEnvironment:
"<rootDir>/src/test/support/extended-vscode-environment.js",
setupFiles: ["<rootDir>/src/test/support/jest-setup.ts"],
setupFilesAfterEnv: ["jest-extended"],
'<rootDir>/src/test/support/extended-vscode-environment.js',
setupFiles: ['<rootDir>/src/test/support/jest-setup.ts'],
setupFilesAfterEnv: ['jest-extended'],
globals: JSON.stringify({
"ts-jest": {
tsconfig: path.resolve(rootDir, "./tsconfig.json"),
'ts-jest': {
tsconfig: path.resolve(rootDir, './tsconfig.json'),
},
}),
testTimeout: 20000,

View File

@@ -1,5 +1,5 @@
// Based on https://github.com/svsool/vscode-memo/blob/master/src/test/env/ExtendedVscodeEnvironment.js
const VscodeEnvironment = require("jest-environment-vscode");
const VscodeEnvironment = require('jest-environment-vscode');
class ExtendedVscodeEnvironment extends VscodeEnvironment {
async setup() {

View File

@@ -1,2 +1,2 @@
// Based on https://github.com/svsool/vscode-memo/blob/master/src/test/config/jestSetup.ts
jest.mock("vscode", () => (global as any).vscode, { virtual: true });
jest.mock('vscode', () => (global as any).vscode, { virtual: true });

View File

@@ -1,6 +1,6 @@
import { ExtensionContext } from "vscode";
import { Foam } from "foam-core";
import { ExtensionContext } from 'vscode';
import { Foam } from 'foam-core';
export interface FoamFeature {
activate: (context: ExtensionContext, foamPromise: Promise<Foam>) => void
activate: (context: ExtensionContext, foamPromise: Promise<Foam>) => void;
}

View File

@@ -1,61 +1,65 @@
import { dropExtension, removeBrackets, toTitleCase } from './utils';
describe("dropExtension", () => {
test("returns file name without extension", () => {
describe('dropExtension', () => {
test('returns file name without extension', () => {
expect(dropExtension('file.md')).toEqual('file');
});
});
describe("removeBrackets", () => {
it("removes the brackets", () => {
const input = "hello world [[this-is-it]]";
describe('removeBrackets', () => {
it('removes the brackets', () => {
const input = 'hello world [[this-is-it]]';
const actual = removeBrackets(input);
const expected = "hello world This Is It";
const expected = 'hello world This Is It';
expect(actual).toEqual(expected);
});
it("removes the brackets and the md file extension", () => {
const input = "hello world [[this-is-it.md]]";
it('removes the brackets and the md file extension', () => {
const input = 'hello world [[this-is-it.md]]';
const actual = removeBrackets(input);
const expected = "hello world This Is It";
const expected = 'hello world This Is It';
expect(actual).toEqual(expected);
});
it("removes the brackets and the mdx file extension", () => {
const input = "hello world [[this-is-it.mdx]]";
it('removes the brackets and the mdx file extension', () => {
const input = 'hello world [[this-is-it.mdx]]';
const actual = removeBrackets(input);
const expected = "hello world This Is It";
const expected = 'hello world This Is It';
expect(actual).toEqual(expected);
});
it("removes the brackets and the markdown file extension", () => {
const input = "hello world [[this-is-it.markdown]]";
it('removes the brackets and the markdown file extension', () => {
const input = 'hello world [[this-is-it.markdown]]';
const actual = removeBrackets(input);
const expected = "hello world This Is It";
const expected = 'hello world This Is It';
expect(actual).toEqual(expected);
});
it("removes the brackets even with numbers", () => {
const input = "hello world [[2020-07-21.markdown]]";
it('removes the brackets even with numbers', () => {
const input = 'hello world [[2020-07-21.markdown]]';
const actual = removeBrackets(input);
const expected = "hello world 2020 07 21";
const expected = 'hello world 2020 07 21';
expect(actual).toEqual(expected);
});
it("removes brackets for more than one word", () => {
const input = "I am reading this as part of the [[book-club]] put on by [[egghead]] folks (Lauro).";
it('removes brackets for more than one word', () => {
const input =
'I am reading this as part of the [[book-club]] put on by [[egghead]] folks (Lauro).';
const actual = removeBrackets(input);
const expected = "I am reading this as part of the Book Club put on by Egghead folks (Lauro).";
const expected =
'I am reading this as part of the Book Club put on by Egghead folks (Lauro).';
expect(actual).toEqual(expected);
});
});
describe("toTitleCase", () => {
it("title cases a word", () => {
const input = "look at this really long sentence but I am calling it a word";
describe('toTitleCase', () => {
it('title cases a word', () => {
const input =
'look at this really long sentence but I am calling it a word';
const actual = toTitleCase(input);
const expected = "Look At This Really Long Sentence But I Am Calling It A Word";
const expected =
'Look At This Really Long Sentence But I Am Calling It A Word';
expect(actual).toEqual(expected);
});
it("works on one word", () => {
const input = "word";
it('works on one word', () => {
const input = 'word';
const actual = toTitleCase(input);
const expected = "Word";
const expected = 'Word';
expect(actual).toEqual(expected);
});
});
});

View File

@@ -7,10 +7,10 @@ import {
TextEditor,
workspace,
Uri,
Selection
} from "vscode";
import * as fs from "fs";
import { Logger } from "foam-core";
Selection,
} from 'vscode';
import * as fs from 'fs';
import { Logger } from 'foam-core';
interface Point {
line: number;
@@ -18,34 +18,34 @@ interface Point {
offset?: number;
}
export const docConfig = { tab: " ", eol: "\r\n" };
export const docConfig = { tab: ' ', eol: '\r\n' };
export const mdDocSelector = [
{ language: "markdown", scheme: "file" },
{ language: "markdown", scheme: "untitled" }
{ language: 'markdown', scheme: 'file' },
{ language: 'markdown', scheme: 'untitled' },
];
export function loadDocConfig() {
// Load workspace config
let activeEditor = window.activeTextEditor;
if (!activeEditor) {
Logger.debug("Failed to load config, no active editor");
Logger.debug('Failed to load config, no active editor');
return;
}
docConfig.eol = activeEditor.document.eol === EndOfLine.CRLF ? "\r\n" : "\n";
docConfig.eol = activeEditor.document.eol === EndOfLine.CRLF ? '\r\n' : '\n';
let tabSize = Number(activeEditor.options.tabSize);
let insertSpaces = activeEditor.options.insertSpaces;
if (insertSpaces) {
docConfig.tab = " ".repeat(tabSize);
docConfig.tab = ' '.repeat(tabSize);
} else {
docConfig.tab = "\t";
docConfig.tab = '\t';
}
}
export function isMdEditor(editor: TextEditor) {
return editor && editor.document && editor.document.languageId === "markdown";
return editor && editor.document && editor.document.languageId === 'markdown';
}
export function detectGeneratedCode(
@@ -61,7 +61,7 @@ export function detectGeneratedCode(
if (headerLine < 0 || headerLine >= footerLine) {
return {
range: null,
lines: []
lines: [],
};
}
@@ -70,7 +70,7 @@ export function detectGeneratedCode(
new Position(headerLine, 0),
new Position(footerLine, lines[footerLine].length + 1)
),
lines: lines.slice(headerLine + 1, footerLine + 1)
lines: lines.slice(headerLine + 1, footerLine + 1),
};
}
@@ -83,9 +83,9 @@ export function getText(range: Range): string {
}
export function dropExtension(path: string): string {
const parts = path.split(".");
const parts = path.split('.');
parts.pop();
return parts.join(".");
return parts.join('.');
}
/**
@@ -103,17 +103,17 @@ export const astPositionToVsCodePosition = (point: Point): Position => {
*/
export function removeBrackets(s: string): string {
// take in the string, split on space
const stringSplitBySpace = s.split(" ");
const stringSplitBySpace = s.split(' ');
// loop through words
const modifiedWords = stringSplitBySpace.map(currentWord => {
if (currentWord.includes("[[")) {
if (currentWord.includes('[[')) {
// all of these transformations will turn this "[[you-are-awesome]]"
// to this "you are awesome"
let word = currentWord.replace(/(\[\[)/g, "");
word = word.replace(/(\]\])/g, "");
word = word.replace(/(.mdx|.md|.markdown)/g, "");
word = word.replace(/[-]/g, " ");
let word = currentWord.replace(/(\[\[)/g, '');
word = word.replace(/(\]\])/g, '');
word = word.replace(/(.mdx|.md|.markdown)/g, '');
word = word.replace(/[-]/g, ' ');
// then we titlecase the word so "you are awesome"
// becomes "You Are Awesome"
@@ -125,7 +125,7 @@ export function removeBrackets(s: string): string {
return currentWord;
});
return modifiedWords.join(" ");
return modifiedWords.join(' ');
}
/**
@@ -135,9 +135,9 @@ export function removeBrackets(s: string): string {
*/
export function toTitleCase(word: string): string {
return word
.split(" ")
.split(' ')
.map(word => word[0].toUpperCase() + word.substring(1))
.join(" ");
.join(' ');
}
/**

View File

@@ -11,7 +11,10 @@
</style>
</head>
<body>
<div id="graph" style="position: absolute; top: 0; left: 0; right: 0; bottom: 0;"></div>
<div
id="graph"
style="position: absolute; top: 0; left: 0; right: 0; bottom: 0;"
></div>
<!-- To test the graph locally in a broswer:
1. copy the json data object received in the message payload
2. paste the content in test-data.js

View File

@@ -1,15 +1,15 @@
const CONTAINER_ID = "graph";
const CONTAINER_ID = 'graph';
/** The style fallback. This values should only be set when all else failed. */
const styleFallback = {
background: "#202020",
background: '#202020',
fontSize: 12,
highlightedForeground: "#f9c74f",
highlightedForeground: '#f9c74f',
node: {
note: "#277da1",
placeholder: "#545454",
unknown: "#f94144"
}
note: '#277da1',
placeholder: '#545454',
unknown: '#f94144',
},
};
function getStyle(name) {
@@ -36,27 +36,29 @@ let model = {
nodeInfo: {},
data: {
nodes: [],
links: []
links: [],
},
/** The style property.
* It tries to be set using VSCode values,
* in the case it fails, use the fallback style values.
*/
style: {
background: getStyle(`--vscode-panel-background`)
?? styleFallback.background,
fontSize: parseInt(getStyle(`--vscode-font-size`) ?? styleFallback.fontSize) - 2,
highlightedForeground: getStyle("--vscode-list-highlightForeground")
?? styleFallback.highlightedForeground,
background:
getStyle(`--vscode-panel-background`) ?? styleFallback.background,
fontSize:
parseInt(getStyle(`--vscode-font-size`) ?? styleFallback.fontSize) - 2,
highlightedForeground:
getStyle('--vscode-list-highlightForeground') ??
styleFallback.highlightedForeground,
node: {
note: getStyle("--vscode-editor-foreground")
?? styleFallback.node.note,
placeholder: getStyle("--vscode-list-deemphasizedForeground")
?? styleFallback.node.placeholder,
unknown: getStyle("--vscode-editor-foreground")
?? styleFallback.node.unknown
}
}
note: getStyle('--vscode-editor-foreground') ?? styleFallback.node.note,
placeholder:
getStyle('--vscode-list-deemphasizedForeground') ??
styleFallback.node.placeholder,
unknown:
getStyle('--vscode-editor-foreground') ?? styleFallback.node.unknown,
},
},
};
const graph = ForceGraph();
@@ -103,7 +105,7 @@ const Actions = {
});
remaining.forEach(nodeId => {
m.data.nodes.push({
id: nodeId
id: nodeId,
});
});
m.data.links = links; // links can be swapped out without problem
@@ -140,28 +142,34 @@ const Actions = {
return;
}
model.style = {
background: newStyle.background
?? getStyle(`--vscode-panel-background`)
?? styleFallback.background,
fontSize: newStyle.fontSize
?? parseInt(getStyle(`--vscode-font-size`) ?? styleFallback.fontSize) - 2,
highlightedForeground: newStyle.highlightedForeground
?? getStyle("--vscode-list-highlightForeground")
?? styleFallback.highlightedForeground,
background:
newStyle.background ??
getStyle(`--vscode-panel-background`) ??
styleFallback.background,
fontSize:
newStyle.fontSize ??
parseInt(getStyle(`--vscode-font-size`) ?? styleFallback.fontSize) - 2,
highlightedForeground:
newStyle.highlightedForeground ??
getStyle('--vscode-list-highlightForeground') ??
styleFallback.highlightedForeground,
node: {
note: newStyle.node?.note
?? getStyle("--vscode-editor-foreground")
?? styleFallback.node.note,
placeholder: newStyle.node?.placeholder
?? getStyle("--vscode-list-deemphasizedForeground")
?? styleFallback.node.placeholder,
unknown: newStyle.node?.unknown
?? getStyle("--vscode-editor-foreground")
?? styleFallback.node.unknow,
note:
newStyle.node?.note ??
getStyle('--vscode-editor-foreground') ??
styleFallback.node.note,
placeholder:
newStyle.node?.placeholder ??
getStyle('--vscode-list-deemphasizedForeground') ??
styleFallback.node.placeholder,
unknown:
newStyle.node?.unknown ??
getStyle('--vscode-editor-foreground') ??
styleFallback.node.unknow,
},
};
graph.backgroundColor(model.style.background);
}
},
};
function initDataviz(channel) {
@@ -170,13 +178,13 @@ function initDataviz(channel) {
.graphData(model.data)
.backgroundColor(model.style.background)
.linkHoverPrecision(8)
.d3Force("x", d3.forceX())
.d3Force("y", d3.forceY())
.d3Force("collide", d3.forceCollide(graph.nodeRelSize()))
.d3Force('x', d3.forceX())
.d3Force('y', d3.forceY())
.d3Force('collide', d3.forceCollide(graph.nodeRelSize()))
.linkWidth(0.2)
.linkDirectionalParticles(1)
.linkDirectionalParticleWidth(link =>
getLinkState(link, model) === "highlighted" ? 1 : 0
getLinkState(link, model) === 'highlighted' ? 1 : 0
)
.nodeCanvasObject((node, ctx, globalScale) => {
const info = model.nodeInfo[node.id];
@@ -189,7 +197,7 @@ function initDataviz(channel) {
const fontSize = model.style.fontSize / globalScale;
let textColor = d3.rgb(fill);
textColor.opacity =
getNodeState(node.id, model) === "highlighted"
getNodeState(node.id, model) === 'highlighted'
? 1
: labelAlpha(globalScale);
const label = info.title;
@@ -204,16 +212,16 @@ function initDataviz(channel) {
Actions.highlightNode(node?.id);
})
.onNodeClick((node, event) => {
if (event.getModifierState("Control") || event.getModifierState("Meta")) {
if (event.getModifierState('Control') || event.getModifierState('Meta')) {
channel.postMessage({
type: "webviewDidSelectNode",
payload: node.id
type: 'webviewDidSelectNode',
payload: node.id,
});
}
Actions.selectNode(node.id, event.getModifierState("Shift"));
Actions.selectNode(node.id, event.getModifierState('Shift'));
})
.onBackgroundClick(event => {
Actions.selectNode(null, event.getModifierState("Shift"));
Actions.selectNode(null, event.getModifierState('Shift'));
});
}
@@ -236,53 +244,53 @@ function augmentGraphInfo(data) {
function getNodeColor(nodeId, model) {
const info = model.nodeInfo[nodeId];
const style = model.style;
const typeFill = style.node[info.type || "unknown"];
const typeFill = style.node[info.type || 'unknown'];
switch (getNodeState(nodeId, model)) {
case "regular":
case 'regular':
return { fill: typeFill, border: typeFill };
case "lessened":
case 'lessened':
const darker = d3.hsl(typeFill).darker(3);
return { fill: darker, border: darker };
case "highlighted":
case 'highlighted':
return {
fill: typeFill,
border: style.highlightedForeground
border: style.highlightedForeground,
};
default:
throw new Error("Unknown type for node", nodeId);
throw new Error('Unknown type for node', nodeId);
}
}
function getLinkColor(link, model) {
const style = model.style;
switch (getLinkState(link, model)) {
case "regular":
case 'regular':
return d3.hsl(style.node.note).darker(2);
case "highlighted":
case 'highlighted':
return style.highlightedForeground;
case "lessened":
case 'lessened':
return d3.hsl(style.node.note).darker(4);
default:
throw new Error("Unknown type for link", link);
throw new Error('Unknown type for link', link);
}
}
function getNodeState(nodeId, model) {
return model.selectedNodes.has(nodeId) || model.hoverNode === nodeId
? "highlighted"
? 'highlighted'
: model.focusNodes.size === 0
? "regular"
? 'regular'
: model.focusNodes.has(nodeId)
? "regular"
: "lessened";
? 'regular'
: 'lessened';
}
function getLinkState(link, model) {
return model.focusNodes.size === 0
? "regular"
? 'regular'
: model.focusLinks.has(link)
? "highlighted"
: "lessened";
? 'highlighted'
: 'lessened';
}
const Draw = ctx => ({
@@ -296,12 +304,12 @@ const Draw = ctx => ({
},
text: function(text, x, y, size, color) {
ctx.font = `${size}px Sans-Serif`;
ctx.textAlign = "center";
ctx.textBaseline = "top";
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillStyle = color;
ctx.fillText(text, x, y);
return this;
}
},
});
// init the app
@@ -310,34 +318,34 @@ try {
window.onload = () => {
initDataviz(vscode);
console.log("ready");
console.log('ready');
vscode.postMessage({
type: "webviewDidLoad"
type: 'webviewDidLoad',
});
};
window.addEventListener("error", error => {
window.addEventListener('error', error => {
vscode.postMessage({
type: "error",
type: 'error',
payload: {
message: error.message,
filename: error.filename,
lineno: error.lineno,
colno: error.colno,
error: error.error
}
error: error.error,
},
});
});
window.addEventListener("message", event => {
window.addEventListener('message', event => {
const message = event.data;
switch (message.type) {
case "didUpdateGraphData":
case 'didUpdateGraphData':
const graphData = augmentGraphInfo(message.payload);
console.log("didUpdateGraphData", graphData);
console.log('didUpdateGraphData', graphData);
Actions.refresh(graphData);
break;
case "didSelectNote":
case 'didSelectNote':
const noteId = message.payload;
const node = graph.graphData().nodes.find(node => node.id === noteId);
if (node) {
@@ -345,28 +353,28 @@ try {
Actions.selectNode(noteId);
}
break;
case "didUpdateStyle":
case 'didUpdateStyle':
const style = message.payload;
Actions.updateStyle(style);
break;
}
});
} catch {
console.log("VsCode not detected");
console.log('VsCode not detected');
}
window.addEventListener("resize", () => {
window.addEventListener('resize', () => {
graph.width(window.innerWidth).height(window.innerHeight);
});
// For testing
if (window.data) {
console.log("Test mode");
console.log('Test mode');
window.model = model;
window.graph = graph;
window.onload = () => {
initDataviz({
postMessage: message => console.log("message", message)
postMessage: message => console.log('message', message),
});
const graphData = augmentGraphInfo(window.data);
Actions.refresh(graphData);

View File

@@ -38,6 +38,8 @@ Quick links to next documentation sections
- [Call To Adventure](https://foambubble.github.io/foam#call-to-adventure)
- [Thanks and attribution](https://foambubble.github.io/foam#thanks-and-attribution)
You can also browse the [docs folder](https://github.com/foambubble/foam/tree/master/docs).
## License
Foam is licensed under the [MIT license](LICENSE).