mirror of
https://github.com/foambubble/foam.git
synced 2026-01-09 14:08:13 -05:00
extracted foam-core and created @foam modules
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,3 +2,5 @@ node_modules
|
||||
.DS_Store
|
||||
.vscode-test/
|
||||
*.vsix
|
||||
*.log
|
||||
dist
|
||||
|
||||
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -42,7 +42,7 @@
|
||||
"name": "Workspace Manager tests",
|
||||
"program": "${workspaceFolder}/node_modules/tsdx/dist/index.js",
|
||||
"args": ["test"],
|
||||
"cwd": "${workspaceFolder}/packages/foam-workspace-manager",
|
||||
"cwd": "${workspaceFolder}/packages/foam-core",
|
||||
"internalConsoleOptions": "openOnSessionStart"
|
||||
}
|
||||
]
|
||||
|
||||
14
package.json
14
package.json
@@ -14,5 +14,19 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"all-contributors-cli": "^6.16.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "tsdx lint"
|
||||
}
|
||||
},
|
||||
"prettier": {
|
||||
"printWidth": 80,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,5 +73,5 @@ _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v3.1.0
|
||||
|
||||
- Run `yarn` somewhere in workspace (ideally root, see [yarn workspace docs](https://classic.yarnpkg.com/en/docs/workspaces/)
|
||||
- This will automatically symlink all package directories so you're using the local copy
|
||||
- In `packages/foam-workspace-manager`, run `yarn start` to rebuild the library on every change
|
||||
- In `packages/foam-core`, run `yarn start` to rebuild the library on every change
|
||||
- In `packages/foam-cli`, make changes and run with `yarn run cli`. This should use latest workspace manager changes.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "foam-cli",
|
||||
"name": "@foam/cli",
|
||||
"description": "Foam CLI",
|
||||
"version": "0.1.0",
|
||||
"author": "Jani Eväkallio @jevakallio",
|
||||
@@ -11,7 +11,6 @@
|
||||
"@oclif/command": "^1",
|
||||
"@oclif/config": "^1",
|
||||
"@oclif/plugin-help": "^3",
|
||||
"foam-workspace-manager": "^0.1.1",
|
||||
"tslib": "^1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -23,7 +22,11 @@
|
||||
"eslint-config-oclif-typescript": "^0.1",
|
||||
"globby": "^10",
|
||||
"ts-node": "^8",
|
||||
"typescript": "^3.3"
|
||||
"typescript": "^3.3",
|
||||
"@foam/core": "^0.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@foam/core": "^0.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {Command, flags} from '@oclif/command'
|
||||
import { WorkspaceManager } from 'foam-workspace-manager';
|
||||
import { NoteGraph } from '@foam/core';
|
||||
export default class Hello extends Command {
|
||||
static description = 'describe the command here'
|
||||
|
||||
@@ -23,7 +23,7 @@ hello world from ./src/hello.ts!
|
||||
const {args, flags} = this.parse(Hello)
|
||||
const name = flags.name ?? 'world'
|
||||
|
||||
const wm = new WorkspaceManager('./foo');
|
||||
const wm = new NoteGraph();
|
||||
wm.addNoteFromMarkdown('page-a.md', `
|
||||
# Page A
|
||||
## Section
|
||||
|
||||
@@ -10,5 +10,6 @@
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
],
|
||||
"references": [{ "path": "../foam-core" }]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Foam Workspace Manager
|
||||
# Foam Core
|
||||
|
||||
Repository for tooling user for managing Foam workspaces
|
||||
Repository for tooling used by the other modules
|
||||
|
||||
## Local Development
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
{
|
||||
"name": "@foam/core",
|
||||
"author": "Jani Eväkallio",
|
||||
"repository": "https://github.com/foambubble/foam",
|
||||
"version": "0.1.1",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
"dist"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "tsdx watch",
|
||||
"build": "tsdx build",
|
||||
@@ -17,36 +14,22 @@
|
||||
"lint": "tsdx lint",
|
||||
"prepare": "tsdx build"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "tsdx lint"
|
||||
}
|
||||
},
|
||||
"prettier": {
|
||||
"printWidth": 80,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5"
|
||||
},
|
||||
"name": "foam-workspace-manager",
|
||||
"author": "Jani Eväkallio",
|
||||
"repository": "https://github.com/foambubble/foam",
|
||||
"module": "dist/foam-workspace-manager.esm.js",
|
||||
"devDependencies": {
|
||||
"@types/graphlib": "^2.1.6",
|
||||
"@types/lodash": "^4.14.157",
|
||||
"husky": "^4.2.5",
|
||||
"tsdx": "^0.13.2",
|
||||
"tslib": "^2.0.0",
|
||||
"typescript": "^3.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/graphlib": "^2.1.6",
|
||||
"@types/lodash": "^4.14.157",
|
||||
"graphlib": "^2.1.8",
|
||||
"lodash": "^4.17.15",
|
||||
"remark-parse": "^8.0.2",
|
||||
"remark-wiki-link": "^0.0.4",
|
||||
"unified": "^9.0.0",
|
||||
"unist-util-visit": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts"
|
||||
}
|
||||
2
packages/foam-core/src/index.ts
Normal file
2
packages/foam-core/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { NoteGraph, Note, NoteLink } from './core'
|
||||
export { createNoteFromMarkdown, createMarkdownReferences } from './markdown-provider'
|
||||
@@ -4,14 +4,9 @@ import wikiLinkPlugin from 'remark-wiki-link';
|
||||
import visit, { CONTINUE, EXIT } from 'unist-util-visit';
|
||||
import { Node, Parent } from 'unist';
|
||||
import * as path from 'path';
|
||||
import { Link, Note, NoteGraph } from '../core';
|
||||
import { Link, Note, NoteGraph } from './core';
|
||||
import { dropExtension } from './utils';
|
||||
|
||||
// @ts-expect-error
|
||||
export function readWorkspaceFile(filename: string): string {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
// pipeline cache
|
||||
let processor: unified.Processor | null = null;
|
||||
|
||||
function parse(markdown: string): Node {
|
||||
@@ -70,34 +65,3 @@ export function createMarkdownReferences(notes: NoteGraph, noteId: string): Mark
|
||||
})
|
||||
.sort()
|
||||
}
|
||||
|
||||
function dropExtension(path: string): string {
|
||||
const parts = path.split(".");
|
||||
parts.pop();
|
||||
return parts.join(".");
|
||||
}
|
||||
|
||||
export function parseNoteTitleFromMarkdown(markdown: string): string | null {
|
||||
let title: string | null = null;
|
||||
const tree = parse(markdown);
|
||||
visit(tree, node => {
|
||||
if (node.type === 'heading' && node.depth === 1) {
|
||||
title = ((node as Parent)!.children[0].value as string) || null;
|
||||
}
|
||||
return title === null;
|
||||
});
|
||||
return title;
|
||||
}
|
||||
|
||||
export function parseNoteLinksFromMarkdown(markdown: string): string[] {
|
||||
let links: string[] = [];
|
||||
const tree = parse(markdown);
|
||||
visit(tree, node => {
|
||||
if (node.type === 'wikiLink') {
|
||||
links.push(node.value as string);
|
||||
}
|
||||
});
|
||||
return links;
|
||||
}
|
||||
|
||||
|
||||
5
packages/foam-core/src/utils.ts
Normal file
5
packages/foam-core/src/utils.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export function dropExtension(path: string): string {
|
||||
const parts = path.split(".");
|
||||
parts.pop();
|
||||
return parts.join(".");
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { NoteGraph, Note } from '../src/core'
|
||||
import { createNoteFromMarkdown } from '../src/utils/utils'
|
||||
|
||||
describe('Note graph', () => {
|
||||
it('Adds notes to graph', () => {
|
||||
@@ -55,42 +54,3 @@ describe('Note graph', () => {
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
|
||||
const pageA = `
|
||||
# Page A
|
||||
|
||||
## Section
|
||||
- [[page-b]]
|
||||
- [[page-c]];
|
||||
`;
|
||||
|
||||
const pageB = `
|
||||
# Page B
|
||||
|
||||
This references [[page-a]]`;
|
||||
|
||||
const pageC = `
|
||||
# Page C
|
||||
`;
|
||||
|
||||
describe('Markdown loader', () => {
|
||||
it('Converts markdown to notes', () => {
|
||||
const graph = new NoteGraph()
|
||||
graph.setNote(createNoteFromMarkdown('page-a', pageA))
|
||||
graph.setNote(createNoteFromMarkdown('page-b', pageB))
|
||||
graph.setNote(createNoteFromMarkdown('page-c', pageC))
|
||||
|
||||
expect(graph.getNotes().map(n => n.id).sort()).toEqual(['page-a', 'page-b', 'page-c'])
|
||||
})
|
||||
|
||||
it('Parses wikilinks correctly', () => {
|
||||
const graph = new NoteGraph()
|
||||
graph.setNote(createNoteFromMarkdown('page-a', pageA))
|
||||
graph.setNote(createNoteFromMarkdown('page-b', pageB))
|
||||
graph.setNote(createNoteFromMarkdown('page-c', pageC))
|
||||
|
||||
expect(graph.getBacklinks('page-b').map(link => link.from)).toEqual(['page-a'])
|
||||
expect(graph.getForwardLinks('page-a').map(link => link.to)).toEqual(['page-b', 'page-c'])
|
||||
})
|
||||
})
|
||||
40
packages/foam-core/test/markdown-provider.test.ts
Normal file
40
packages/foam-core/test/markdown-provider.test.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { createNoteFromMarkdown } from "../src/markdown-provider";
|
||||
import { NoteGraph } from "../src/core";
|
||||
|
||||
const pageA = `
|
||||
# Page A
|
||||
|
||||
## Section
|
||||
- [[page-b]]
|
||||
- [[page-c]];
|
||||
`;
|
||||
|
||||
const pageB = `
|
||||
# Page B
|
||||
|
||||
This references [[page-a]]`;
|
||||
|
||||
const pageC = `
|
||||
# Page C
|
||||
`;
|
||||
|
||||
describe('Markdown loader', () => {
|
||||
it('Converts markdown to notes', () => {
|
||||
const graph = new NoteGraph()
|
||||
graph.setNote(createNoteFromMarkdown('page-a', pageA))
|
||||
graph.setNote(createNoteFromMarkdown('page-b', pageB))
|
||||
graph.setNote(createNoteFromMarkdown('page-c', pageC))
|
||||
|
||||
expect(graph.getNotes().map(n => n.id).sort()).toEqual(['page-a', 'page-b', 'page-c'])
|
||||
})
|
||||
|
||||
it('Parses wikilinks correctly', () => {
|
||||
const graph = new NoteGraph()
|
||||
graph.setNote(createNoteFromMarkdown('page-a', pageA))
|
||||
graph.setNote(createNoteFromMarkdown('page-b', pageB))
|
||||
graph.setNote(createNoteFromMarkdown('page-c', pageC))
|
||||
|
||||
expect(graph.getBacklinks('page-b').map(link => link.from)).toEqual(['page-a'])
|
||||
expect(graph.getForwardLinks('page-a').map(link => link.to)).toEqual(['page-b', 'page-c'])
|
||||
})
|
||||
})
|
||||
@@ -1,23 +1,30 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": ["src", "types"],
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"lib": ["dom", "esnext"],
|
||||
"importHelpers": true,
|
||||
"composite": true,
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"declarationMap": true,
|
||||
// to override config from tsconfig.base.json
|
||||
"outDir": "dist",
|
||||
"rootDir": "./src",
|
||||
// for references
|
||||
"baseUrl": "src",
|
||||
"lib": ["esnext"],
|
||||
|
||||
"module": "esnext",
|
||||
"importHelpers": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"moduleResolution": "node",
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"*": ["src/*", "node_modules/*"]
|
||||
},
|
||||
"jsx": "react",
|
||||
"esModuleInterop": true
|
||||
}
|
||||
// "paths": {
|
||||
// "*": ["src/*", "node_modules/*"]
|
||||
// },
|
||||
// "jsx": "react",
|
||||
},
|
||||
}
|
||||
1
packages/foam-vscode/.gitignore
vendored
1
packages/foam-vscode/.gitignore
vendored
@@ -1,4 +1,3 @@
|
||||
out
|
||||
node_modules
|
||||
.vscode-test/
|
||||
*.vsix
|
||||
|
||||
@@ -49,6 +49,6 @@
|
||||
"vscode-test": "^1.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"foam-workspace-manager": "^0.1.1"
|
||||
"@foam/core": "^0.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
Position,
|
||||
} from "vscode";
|
||||
|
||||
import { createMarkdownReferences } from 'foam-workspace-manager'
|
||||
import { createMarkdownReferences } from '@foam/core'
|
||||
import { basename, dirname, relative } from "path";
|
||||
import * as ws from "./workspace";
|
||||
|
||||
|
||||
78
packages/foam-vscode/src/utils.ts
Normal file
78
packages/foam-vscode/src/utils.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import {
|
||||
CancellationToken,
|
||||
CodeLens,
|
||||
CodeLensProvider,
|
||||
commands,
|
||||
EndOfLine,
|
||||
ExtensionContext,
|
||||
languages,
|
||||
Range,
|
||||
TextEditor,
|
||||
TextDocument,
|
||||
TextDocumentWillSaveEvent,
|
||||
window,
|
||||
workspace,
|
||||
Position,
|
||||
} from "vscode";
|
||||
|
||||
export const docConfig = { tab: " ", eol: "\r\n" };
|
||||
|
||||
export const mdDocSelector = [
|
||||
{ language: "markdown", scheme: "file" },
|
||||
{ language: "markdown", scheme: "untitled" },
|
||||
];
|
||||
|
||||
export function loadDocConfig() {
|
||||
// Load workspace config
|
||||
let activeEditor = window.activeTextEditor;
|
||||
if (!activeEditor) {
|
||||
console.log("Failed to load config, no active editor");
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
} else {
|
||||
docConfig.tab = "\t";
|
||||
}
|
||||
}
|
||||
|
||||
export function detectGeneratedCode(fullText: string, header: string, footer: string): {range: Range | null, lines: string[]} {
|
||||
const lines = fullText.split(docConfig.eol)
|
||||
|
||||
const headerLine = lines.findIndex(line => line === header)
|
||||
const footerLine = lines.findIndex(line => line === footer)
|
||||
|
||||
if (headerLine < 0 || headerLine >= footerLine) {
|
||||
return {
|
||||
range: null,
|
||||
lines: [],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
range: new Range(
|
||||
new Position(headerLine, 0),
|
||||
new Position(footerLine, lines[footerLine].length + 1)
|
||||
),
|
||||
lines: lines.slice(headerLine + 1, footerLine + 1),
|
||||
}
|
||||
}
|
||||
|
||||
export function hasEmptyTrailing(doc: TextDocument): boolean {
|
||||
return doc.lineAt(doc.lineCount - 1).isEmptyOrWhitespace;
|
||||
}
|
||||
|
||||
export function getText(range: Range): string {
|
||||
return window.activeTextEditor.document.getText(range);
|
||||
}
|
||||
|
||||
export function dropExtension(path: string): string {
|
||||
const parts = path.split(".");
|
||||
parts.pop();
|
||||
return parts.join(".");
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
import * as fs from "fs";
|
||||
import { basename } from "path";
|
||||
import { workspace } from "vscode";
|
||||
import { WorkspaceManager, NoteGraph, createNoteFromMarkdown } from "foam-workspace-manager";
|
||||
import { NoteGraph, createNoteFromMarkdown } from "@foam/core";
|
||||
|
||||
// build initial index
|
||||
export const manager = new WorkspaceManager(workspace.rootPath);
|
||||
export const ready = (async () => {
|
||||
const files = await workspace.findFiles("**/*");
|
||||
const foam = new NoteGraph()
|
||||
@@ -15,8 +14,7 @@ export const ready = (async () => {
|
||||
)
|
||||
.map((f) => {
|
||||
return fs.promises.readFile(f.fsPath).then((data) => {
|
||||
let markdown = (data || "").toString();
|
||||
manager.addNoteFromMarkdown(f.fsPath, markdown);
|
||||
const markdown = (data || "").toString();
|
||||
foam.setNote(createNoteFromMarkdown(f.fsPath, markdown))
|
||||
});
|
||||
})
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"target": "es6",
|
||||
"outDir": "out",
|
||||
"lib": ["es6"],
|
||||
@@ -12,5 +14,9 @@
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
},
|
||||
"exclude": ["node_modules", ".vscode-test"]
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", ".vscode-test"],
|
||||
"references": [{
|
||||
"path": "../foam-core/tsconfig.json"
|
||||
}]
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
name: CI
|
||||
on: [push]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Begin CI...
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node 12
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
|
||||
- name: Use cached node_modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: node_modules
|
||||
key: nodeModules-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
nodeModules-
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Lint
|
||||
run: yarn lint
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Test
|
||||
run: yarn test --ci --coverage --maxWorkers=2
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Build
|
||||
run: yarn build
|
||||
env:
|
||||
CI: true
|
||||
4
packages/foam-workspace-manager/.gitignore
vendored
4
packages/foam-workspace-manager/.gitignore
vendored
@@ -1,4 +0,0 @@
|
||||
*.log
|
||||
.DS_Store
|
||||
node_modules
|
||||
dist
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Jani Eväkallio
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,179 +0,0 @@
|
||||
// for readability
|
||||
import { basename } from 'path';
|
||||
import {
|
||||
readWorkspaceFile,
|
||||
parseNoteTitleFromMarkdown,
|
||||
parseNoteLinksFromMarkdown,
|
||||
} from './utils/utils';
|
||||
|
||||
type ID = string;
|
||||
type Index = Map<ID, Set<ID>>;
|
||||
|
||||
export interface Note {
|
||||
/**
|
||||
* Base name of the file without extension, e.g. wiki-link
|
||||
*/
|
||||
id: ID;
|
||||
title: string;
|
||||
filename: string;
|
||||
extension: string;
|
||||
absolutePath: string;
|
||||
markdown: string; // do we need this?
|
||||
}
|
||||
|
||||
export interface NoteWithLinks extends Note {
|
||||
/**
|
||||
* Notes referenced from this note (wikilinks)
|
||||
*/
|
||||
linkedNotes: Note[];
|
||||
|
||||
/**
|
||||
* Notes that reference this note (backlinks) */
|
||||
backlinks: Note[];
|
||||
}
|
||||
|
||||
function getOrInitializeIndexForId(index: Index, id: ID): Set<ID> {
|
||||
let links: Set<ID>;
|
||||
if (index.has(id)) {
|
||||
links = index.get(id)!;
|
||||
} else {
|
||||
index.set(id, (links = new Set<ID>()));
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
export class WorkspaceManager {
|
||||
/**
|
||||
* Workspace base path
|
||||
*/
|
||||
path: string;
|
||||
/**
|
||||
* Note metadata for files in this workspace, keyed by id
|
||||
*/
|
||||
notes: Map<ID, Note> = new Map();
|
||||
|
||||
/**
|
||||
* Link index A->B
|
||||
*/
|
||||
linksFromNoteById: Index = new Map();
|
||||
|
||||
/**
|
||||
* Reverse backlinks B->A
|
||||
*/
|
||||
linksBackToNoteById: Index = new Map();
|
||||
|
||||
constructor(path: string, notes: Note[] = []) {
|
||||
this.path = path;
|
||||
this.notes = new Map<ID, Note>(notes.map(note => [note.id, note]));
|
||||
}
|
||||
|
||||
public getNoteWithLinks(id: ID): NoteWithLinks | null {
|
||||
const note = this.notes.get(id);
|
||||
if (!note) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const linkedNotes = Array.from(
|
||||
getOrInitializeIndexForId(this.linksFromNoteById, id)
|
||||
)
|
||||
.map(id => this.notes.get(id))
|
||||
.filter(Boolean) as Note[];
|
||||
|
||||
const backlinks = Array.from(
|
||||
getOrInitializeIndexForId(this.linksBackToNoteById, id)
|
||||
)
|
||||
.map(id => this.notes.get(id))
|
||||
.filter(Boolean) as Note[];
|
||||
|
||||
return {
|
||||
...note,
|
||||
linkedNotes,
|
||||
backlinks,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param filename File name relative to workspace path
|
||||
*/
|
||||
public async addNoteByFilePath(filePath: string): Promise<Note> {
|
||||
return await this.addNoteFromMarkdown(
|
||||
this.path,
|
||||
await readWorkspaceFile(filePath)
|
||||
);
|
||||
}
|
||||
|
||||
public addNoteFromMarkdown(absolutePath: string, markdown: string): Note {
|
||||
// parse markdown
|
||||
const filename = basename(absolutePath);
|
||||
const parts = filename.split('.');
|
||||
const extension = parts.pop()!;
|
||||
const id = parts.join('.');
|
||||
const title = parseNoteTitleFromMarkdown(markdown);
|
||||
const note: Note = {
|
||||
id,
|
||||
title: title || id,
|
||||
filename,
|
||||
absolutePath,
|
||||
extension,
|
||||
markdown,
|
||||
};
|
||||
|
||||
// extract linksTo
|
||||
return this.addNote(note);
|
||||
}
|
||||
|
||||
public addNote(note: Note): Note {
|
||||
const linkIds = parseNoteLinksFromMarkdown(note.markdown);
|
||||
|
||||
this.notes.set(note.id, note);
|
||||
|
||||
if (linkIds.length > 0) {
|
||||
let linksFromNote = getOrInitializeIndexForId(
|
||||
this.linksFromNoteById,
|
||||
note.id
|
||||
);
|
||||
|
||||
for (const id of linkIds) {
|
||||
linksFromNote.add(id);
|
||||
getOrInitializeIndexForId(this.linksBackToNoteById, id).add(note.id);
|
||||
}
|
||||
}
|
||||
|
||||
return note;
|
||||
}
|
||||
|
||||
/*
|
||||
// Clearly I'm too tired to do this right now
|
||||
public removeNote(a: ID): Note | null {
|
||||
let note = this.notes.get(a);
|
||||
if (!note) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// find references from this note to others
|
||||
let linksFromNote = getOrInitializeIndexForId(this.linksFromNoteById, a);
|
||||
|
||||
// remove the index
|
||||
this.linksFromNoteById.delete(a);
|
||||
|
||||
// find all notes that reference the note we are deleting
|
||||
for (const b in linksFromNote) {
|
||||
const backlinks = getOrInitializeIndexForId(this.linksBackToNoteById, b);
|
||||
if (backlinks.has(a)) {
|
||||
// @todo, trigger event?
|
||||
backlinks.delete(a);
|
||||
}
|
||||
}
|
||||
|
||||
return note;
|
||||
}
|
||||
|
||||
|
||||
// @ts-expect-error
|
||||
public renameNote(note: Note, newFilename: string) {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export { WorkspaceManager, Note, NoteWithLinks } from './WorkspaceManager';
|
||||
export { NoteGraph, Note as FNote, Link } from './core'
|
||||
export { createNoteFromMarkdown, createMarkdownReferences } from './utils/utils'
|
||||
@@ -1,95 +0,0 @@
|
||||
import { WorkspaceManager } from '../src/WorkspaceManager';
|
||||
|
||||
const pageA = `
|
||||
# Page A
|
||||
|
||||
## Section
|
||||
- [[page-b]]
|
||||
- [[page-c]];
|
||||
`;
|
||||
|
||||
const pageB = `
|
||||
# Page B
|
||||
|
||||
This references [[page-a]]`;
|
||||
|
||||
const pageC = `
|
||||
# Page C
|
||||
`;
|
||||
|
||||
const updatedPageC = `
|
||||
# Page C
|
||||
[[page-a]]
|
||||
[[page-b]]
|
||||
`;
|
||||
|
||||
describe.skip('WorkspaceManager', () => {
|
||||
it('links things correctly when added in order', () => {
|
||||
const ws = new WorkspaceManager('dir/');
|
||||
|
||||
ws.addNoteFromMarkdown('page-a.md', pageA);
|
||||
ws.addNoteFromMarkdown('page-b.md', pageB);
|
||||
ws.addNoteFromMarkdown('page-c.md', pageC);
|
||||
|
||||
const note = ws.getNoteWithLinks('page-a');
|
||||
expect(note).not.toBeNull();
|
||||
expect(note!.linkedNotes.map(n => n.id)).toEqual(['page-b', 'page-c']);
|
||||
expect(note!.backlinks.map(n => n.id)).toEqual(['page-b']);
|
||||
});
|
||||
|
||||
it('links things correctly when added out of order', () => {
|
||||
const ws = new WorkspaceManager('dir/');
|
||||
|
||||
ws.addNoteFromMarkdown('page-b.md', pageB);
|
||||
ws.addNoteFromMarkdown('page-a.md', pageA);
|
||||
ws.addNoteFromMarkdown('page-c.md', pageC);
|
||||
|
||||
const note = ws.getNoteWithLinks('page-a');
|
||||
|
||||
expect(note).not.toBeNull();
|
||||
expect(note!.linkedNotes.map(n => n.id)).toEqual(['page-b', 'page-c']);
|
||||
expect(note!.backlinks.map(n => n.id)).toEqual(['page-b']);
|
||||
});
|
||||
|
||||
it('updates links when adding a changed document', () => {
|
||||
const ws = new WorkspaceManager('dir/');
|
||||
|
||||
ws.addNoteFromMarkdown('page-b.md', pageB);
|
||||
ws.addNoteFromMarkdown('page-a.md', pageA);
|
||||
ws.addNoteFromMarkdown('page-c.md', pageC);
|
||||
|
||||
const before = ws.getNoteWithLinks('page-a');
|
||||
|
||||
// change document
|
||||
ws.addNoteFromMarkdown('page-c.md', updatedPageC);
|
||||
|
||||
const after = ws.getNoteWithLinks('page-a');
|
||||
|
||||
expect(before).not.toEqual(after);
|
||||
expect(before!.linkedNotes.map(n => n.id)).toEqual(['page-b', 'page-c']);
|
||||
expect(before!.backlinks.map(n => n.id)).toEqual(['page-b']);
|
||||
|
||||
expect(after!.linkedNotes.map(n => n.id)).toEqual(['page-b', 'page-c']);
|
||||
expect(after!.backlinks.map(n => n.id)).toEqual(['page-b', 'page-c']);
|
||||
});
|
||||
|
||||
/*
|
||||
it('updates links correctly when page is removed', () => {
|
||||
const ws = new WorkspaceManager('dir/');
|
||||
|
||||
ws.addNoteFromMarkdown('page-a.md', pageA);
|
||||
ws.addNoteFromMarkdown('page-b.md', pageB);
|
||||
ws.addNoteFromMarkdown('page-c.md', pageC);
|
||||
|
||||
|
||||
ws.removeNote('page-c');
|
||||
|
||||
const note = ws.getNoteWithLinks('page-a');
|
||||
|
||||
console.log(note);
|
||||
expect(note).not.toBeNull();
|
||||
expect(note!.linkedNotes.map(n => n.id)).toEqual(['page-b']);
|
||||
expect(note!.backlinks.map(n => n.id)).toEqual(['page-b']);
|
||||
});
|
||||
*/
|
||||
});
|
||||
@@ -1,54 +0,0 @@
|
||||
import {
|
||||
parseNoteTitleFromMarkdown,
|
||||
parseNoteLinksFromMarkdown,
|
||||
} from '../../src/utils/utils';
|
||||
|
||||
const pageA = `
|
||||
# Page A
|
||||
|
||||
## Section
|
||||
- [[page-b]]
|
||||
- [[page-c]]
|
||||
`;
|
||||
|
||||
const pageB = `
|
||||
# Page B
|
||||
`;
|
||||
|
||||
const pageC = `
|
||||
foo
|
||||
bar
|
||||
`;
|
||||
|
||||
const pageD = `
|
||||
# Page D
|
||||
hello world
|
||||
|
||||
# Another header
|
||||
hello world
|
||||
`;
|
||||
|
||||
describe('WorkspaceManager', () => {
|
||||
it('finds top level headings', () => {
|
||||
const titleA = parseNoteTitleFromMarkdown(pageA);
|
||||
const titleB = parseNoteTitleFromMarkdown(pageB);
|
||||
const titleC = parseNoteTitleFromMarkdown(pageC);
|
||||
const titleD = parseNoteTitleFromMarkdown(pageD);
|
||||
|
||||
expect(titleA).toEqual('Page A');
|
||||
expect(titleB).toEqual('Page B');
|
||||
expect(titleC).toBeNull();
|
||||
// in case of multiple top level headings, the first one rules
|
||||
expect(titleD).toEqual('Page D');
|
||||
});
|
||||
|
||||
it('finds wikilinks', () => {
|
||||
const linksA = parseNoteLinksFromMarkdown(pageA);
|
||||
const linksB = parseNoteLinksFromMarkdown(pageB);
|
||||
const linksC = parseNoteLinksFromMarkdown(pageC);
|
||||
|
||||
expect(linksA).toEqual(['page-b', 'page-c']);
|
||||
expect(linksB).toEqual([]);
|
||||
expect(linksC).toEqual([]);
|
||||
});
|
||||
});
|
||||
13
tsconfig.base.json
Normal file
13
tsconfig.base.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"include": ["./packages/*/src"],
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@foam/core": ["./packages/foam-core/src"],
|
||||
"@foam/cli": ["./packages/foam-cli/src"],
|
||||
"foam-vscode": ["./packages/foam-vscode/src"]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user