├── .gitignore ├── images ├── logo.png ├── 2020-06-16_1.gif ├── 2020-06-16_2.gif ├── 2020-06-16_3.gif └── 2020-08-17_1.png ├── .vscodeignore ├── src ├── syntaxes │ └── markdown.tmLanguage.json ├── zkFoldingRangeProvider.ts ├── types.ts ├── linkCompletionProvider.ts ├── provideDefinition.ts ├── utils.ts ├── parsing.ts └── extension.ts ├── .vscode ├── extensions.json ├── tasks.json ├── settings.json └── launch.json ├── CHANGELOG.md ├── .eslintrc.json ├── tsconfig.json ├── README_CN.md ├── README.md ├── package.json └── static └── webview.html /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sheilaCat/zknotes/HEAD/images/logo.png -------------------------------------------------------------------------------- /images/2020-06-16_1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sheilaCat/zknotes/HEAD/images/2020-06-16_1.gif -------------------------------------------------------------------------------- /images/2020-06-16_2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sheilaCat/zknotes/HEAD/images/2020-06-16_2.gif -------------------------------------------------------------------------------- /images/2020-06-16_3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sheilaCat/zknotes/HEAD/images/2020-06-16_3.gif -------------------------------------------------------------------------------- /images/2020-08-17_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sheilaCat/zknotes/HEAD/images/2020-08-17_1.png -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | src/** 5 | .gitignore 6 | vsc-extension-quickstart.md 7 | **/tsconfig.json 8 | **/.eslintrc.json 9 | **/*.map 10 | **/*.ts 11 | -------------------------------------------------------------------------------- /src/syntaxes/markdown.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "scopeName": "text.html.markdown", 3 | "patterns": [ 4 | { 5 | "name": "markup.other.zk.link", 6 | "match": "123" 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/zkFoldingRangeProvider.ts: -------------------------------------------------------------------------------- 1 | const vscode = require('vscode'); 2 | const path = require('path'); 3 | 4 | import {FoldingRangeProvider, ProviderResult, FoldingRange, DefinitionLink, workspace} from 'vscode' 5 | 6 | export const zkFoldingRangeProvider: FoldingRangeProvider= { 7 | provideFoldingRanges(document, context, token): ProviderResult { 8 | 9 | // 注入对应内容后折叠 10 | 11 | return [new FoldingRange(0, 3)] 12 | } 13 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export type Edge = { 2 | source: string; 3 | target: string; 4 | }; 5 | 6 | export type Node = { 7 | id: string; 8 | path: string; 9 | label: string; 10 | }; 11 | 12 | export type Graph = { 13 | nodes: Node[]; 14 | edges: Edge[]; 15 | }; 16 | 17 | export type MarkdownNode = { 18 | type: string; 19 | children?: MarkdownNode[]; 20 | url?: string; 21 | value?: string; 22 | depth?: number; 23 | data?: { 24 | permalink?: string; 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "zknotes" extension will be documented in this file. 4 | 5 | [Keep a Changelog](http://keepachangelog.com/) 6 | 7 | ## [Unreleased] 8 | 9 | ... 10 | 11 | ## [0.0.1] - 2020-06-16 12 | 13 | ### Added 14 | - zk formatted headers / timestamp headers 15 | - wiki-links support 16 | 17 | ## [0.0.2] - 2020-08-17 18 | 19 | ### Added 20 | - fork feature from [`markdown-links`](https://github.com/tchayen/markdown-links), support graph view -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off" 11 | } -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "plugins": [ 9 | "@typescript-eslint" 10 | ], 11 | "rules": { 12 | "@typescript-eslint/class-name-casing": "warn", 13 | "@typescript-eslint/semi": "warn", 14 | "curly": "warn", 15 | "eqeqeq": "warn", 16 | "no-throw-literal": "warn", 17 | "semi": "off" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "strict": true /* enable all strict type-checking options */ 12 | /* Additional Checks */ 13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 16 | }, 17 | "exclude": [ 18 | "node_modules", 19 | ".vscode-test" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /src/linkCompletionProvider.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CompletionItem, 3 | SnippetString, 4 | CompletionItemProvider, 5 | workspace 6 | } from 'vscode'; 7 | 8 | export const linkCompletionProvider: CompletionItemProvider = { 9 | async provideCompletionItems() { 10 | const rootPath = workspace.rootPath; 11 | if (!rootPath) { 12 | return []; 13 | } 14 | 15 | let allFiles = await workspace.findFiles('**/*.md') 16 | const fileReg = /(\d{12})_.+/ 17 | // 匹配所有zk文件名 18 | allFiles = allFiles.filter(f => fileReg.test(f.path)) 19 | 20 | let result = [] as CompletionItem[] 21 | allFiles.map(f => { 22 | const name = f.path.replace(/.+(\d{12}.+.).md$/, '$1') 23 | result.push({ 24 | label: name, 25 | insertText: name 26 | }) 27 | }) 28 | 29 | return result 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # zknotes 2 | 3 | [英文](README.md) | 简体中文 4 | 5 | `zknotes`是一款应用`zettelkasten`笔记法的vscode插件。 6 | 7 | ## 特性 8 | 9 | ### 新建基于zk格式的时间戳标题 10 | 11 | 1. `Command + Shift + P/ ⇧⌘P / F1`显示命令面板 12 | 13 | 2. 选择`new zk note`,输入你的标题,自动生成基于 **yyyyMMddHHmm_你的标题** 的md文件 14 | 15 | ![](images/2020-06-16_1.gif) 16 | 17 | ### 支持`[[文件名]]`的wiki-links 18 | 19 | 输入`[[`时显示当前目录下的所有zk格式文件,并且支持跳转到对应文件 20 | 21 | ![](images/2020-06-16_2.gif) 22 | 23 | ### 使用vscode自带的查看定义F12进行block修改 24 | 25 | ![](images/2020-06-16_3.gif) 26 | 27 | ### graphView面板展示神经网络知识图谱 28 | 29 | fork 自 [`markdown-links`](https://github.com/tchayen/markdown-links) 的 graph-view 面板,感谢! 30 | 31 | 1. `Command + Shift + P/ ⇧⌘P / F1`显示命令面板 32 | 33 | 2. 选择`graphView`即可生成对应的神经网络笔记图 34 | 35 | ![](images/2020-08-17_1.png) 36 | 37 | ## 发行说明 38 | 39 | ### 0.0.1 40 | 41 | 首次发布 `zknotes`. 42 | 43 | 特性1: zk格式标题 / 时间戳标题 44 | 45 | 特性2: wiki-links支持 46 | 47 | ### 0.0.2 48 | 49 | 特性1: fork了[`markdown-links`](https://github.com/tchayen/markdown-links)的`graph-view`功能,并根据格式进行兼容 50 | 51 | ## 路线图 52 | 53 | - [x] 时间戳标题 54 | - [x] wiki-links 55 | - [ ] 时间戳标题增加配置 56 | - [x] graph view 57 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "--extensionDevelopmentPath=${workspaceFolder}" 15 | ], 16 | "outFiles": [ 17 | "${workspaceFolder}/out/**/*.js" 18 | ], 19 | "preLaunchTask": "${defaultBuildTask}" 20 | }, 21 | { 22 | "name": "Extension Tests", 23 | "type": "extensionHost", 24 | "request": "launch", 25 | "runtimeExecutable": "${execPath}", 26 | "args": [ 27 | "--extensionDevelopmentPath=${workspaceFolder}", 28 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 29 | ], 30 | "outFiles": [ 31 | "${workspaceFolder}/out/test/**/*.js" 32 | ], 33 | "preLaunchTask": "${defaultBuildTask}" 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /src/provideDefinition.ts: -------------------------------------------------------------------------------- 1 | 2 | const vscode = require('vscode'); 3 | const path = require('path'); 4 | 5 | import {DefinitionProvider, ProviderResult, Definition, DefinitionLink, workspace} from 'vscode' 6 | 7 | export const zkDefinitionProvider: DefinitionProvider = { 8 | provideDefinition(document, position, token): ProviderResult { 9 | const fileName = document.fileName; 10 | const workDir = path.dirname(fileName); 11 | const word = document.getText(document.getWordRangeAtPosition(position)); 12 | const line = document.lineAt(position); 13 | 14 | 15 | console.log('====== 进入 provideDefinition 方法 ======'); 16 | console.log('fileName: ' + fileName); // 当前文件完整路径 17 | console.log('workDir: ' + workDir); // 当前文件所在目录 18 | console.log('word: ' + word); // 当前光标所在单词 19 | console.log('line: ' + line.text); // 当前光标所在行 20 | 21 | // 处理zk文件 22 | if (/\d{12}_.+/.test(fileName)){ 23 | console.log(word, line.text) 24 | 25 | return workspace.findFiles(`${word}.md`).then(files => { 26 | console.log('files: ', files) 27 | let destPath = files[0].path 28 | console.log('destPath: ', destPath) 29 | return new vscode.Location(vscode.Uri.file(destPath), new vscode.Position(0, 0)) 30 | }) 31 | } 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zknotes 2 | 3 | English | [Simplified Chinese](README_CN.md) 4 | 5 | `zknotes` is a vscode plugin for applying `zettelkasten` notation. 6 | 7 | ## Features 8 | 9 | ### New timestamp title based on zk formatting 10 | 11 | 1. `Command + Shift + P/ ⇧⌘P / F1` Display Command Panel 12 | 13 | 2. Select ` new zk note`, enter your title and it will automatically generate a markdown file based on **yyyyMMddHHmm_yourtitle**. 14 | 15 | ![](images/2020-06-16_1.gif) 16 | 17 | ### Support wiki-links with `[[filename]]`. 18 | 19 | All zk files in the current directory are displayed when `[[` is entered, and jump to the corresponding file is supported. 20 | 21 | The wiki-links of `[[]]` show all zk files in the current directory when you type `[[` and support jumping to the corresponding file. 22 | 23 | ![](images/2020-06-16_2.gif) 24 | 25 | ### Using vscode's peek definition F12 for block modifications 26 | 27 | ![](images/2020-06-16_3.gif) 28 | 29 | ### graph-view panel to demonstrate Graph neural networks 30 | 31 | fork graph-view feature from [`markdown-links`](https://github.com/tchayen/markdown-links), thanks! 32 | 33 | 1. `Command + Shift + P/ ⇧⌘P / F1` Display Command Panel 34 | 35 | 2. select `graphView` to generate the corresponding neural network diagram. 36 | 37 | ![](images/2020-08-17_1.png) 38 | 39 | ## Release Notes 40 | 41 | ### 0.0.1 42 | 43 | First release of `zknotes`. 44 | 45 | Feature 1: zk formatted headers / timestamp headers 46 | 47 | Feature 2: wiki-links support 48 | 49 | ### 0.0.2 50 | 51 | Feature 1: fork feature from [`markdown-links`](https://github.com/tchayen/markdown-links), support graph view 52 | 53 | ## Roadmap 54 | 55 | - [x] timestamp title 56 | - [x] wiki-links 57 | - [ ] timestamp title add configuration 58 | - [x] graph view -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zknotes", 3 | "displayName": "zknotes", 4 | "publisher": "sheilacat", 5 | "keywords": [ 6 | "zk", 7 | "zettel", 8 | "zettelkasten", 9 | "zknote", 10 | "zknotes", 11 | "markdown" 12 | ], 13 | "description": "vscode plugin for zk", 14 | "icon": "images/logo.png", 15 | "repository": "https://github.com/sheilaCat/zknotes", 16 | "version": "0.0.2", 17 | "engines": { 18 | "vscode": "^1.45.0" 19 | }, 20 | "categories": [ 21 | "Other" 22 | ], 23 | "activationEvents": [ 24 | "onCommand:zknotes.newNote", 25 | "onLanguage:markdown", 26 | "onCommand:zknotes.graphView" 27 | ], 28 | "main": "./out/extension.js", 29 | "contributes": { 30 | "configuration": { 31 | "type": "object", 32 | "title": "zknotes configuration", 33 | "properties": { 34 | "zknotes.defaultFormat": { 35 | "type": "string", 36 | "default": "yyyyMMddhhmm", 37 | "description": "zk new note default format" 38 | } 39 | }, 40 | "grammars": [ 41 | { 42 | "language": "markdown", 43 | "scopeName": "text.html.markdown", 44 | "path": "./src/syntaxes/markdown.tmLanguage.json", 45 | "embeddedLanguages": { 46 | "meta.embedded.block.frontmatter": "yaml" 47 | } 48 | } 49 | ] 50 | }, 51 | "commands": [ 52 | { 53 | "command": "zknotes.newNote", 54 | "title": "new zk note" 55 | }, 56 | { 57 | "command": "zknotes.graphView", 58 | "title": "graph view" 59 | } 60 | ] 61 | }, 62 | "scripts": { 63 | "vscode:prepublish": "npm run compile", 64 | "compile": "tsc -p ./", 65 | "lint": "eslint src --ext ts", 66 | "watch": "tsc -watch -p ./", 67 | "pretest": "npm run compile && npm run lint", 68 | "test": "node ./out/test/runTest.js" 69 | }, 70 | "devDependencies": { 71 | "@types/vscode": "^1.45.0", 72 | "@types/glob": "^7.1.1", 73 | "@types/mocha": "^7.0.2", 74 | "@types/node": "^13.11.0", 75 | "eslint": "^6.8.0", 76 | "@typescript-eslint/parser": "^2.30.0", 77 | "@typescript-eslint/eslint-plugin": "^2.30.0", 78 | "glob": "^7.1.6", 79 | "mocha": "^7.1.2", 80 | "typescript": "^3.8.3", 81 | "vscode-test": "^1.3.0" 82 | }, 83 | "dependencies": { 84 | "@types/md5": "^2.2.0", 85 | "md5": "^2.2.1", 86 | "remark-frontmatter": "^2.0.0", 87 | "remark-parse": "^8.0.2", 88 | "remark-wiki-link": "^0.0.4", 89 | "unified": "^9.0.0" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | import * as vscode from "vscode"; 4 | import * as md5 from "md5"; 5 | import { MarkdownNode, Graph } from "./types"; 6 | 7 | export function formatDate(date: Date, fmt: string | null) { 8 | var o = { 9 | 'M+': date.getMonth() + 1, //月份 10 | 'd+': date.getDate(), //日 11 | 'h+': date.getHours(), //小时 12 | 'm+': date.getMinutes(), //分 13 | 's+': date.getSeconds(), //秒 14 | 'q+': Math.floor((date.getMonth() + 3) / 3), //季度 15 | 'S': date.getMilliseconds() //毫秒 16 | } as any; 17 | if (!fmt) { 18 | fmt = 'yyyy-MM-dd hh:mm:ss'; 19 | } 20 | if (/(y+)/.test(fmt)) { 21 | fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)); 22 | } 23 | for (var k in o) { 24 | if (new RegExp('(' + k + ')').test(fmt)) { 25 | fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length))); 26 | } 27 | } 28 | return fmt; 29 | } 30 | 31 | export const findLinks = (ast: MarkdownNode): string[] => { 32 | if (ast.type === "link" || ast.type === "definition") { 33 | return [ast.url!]; 34 | } 35 | if (ast.type === "wikiLink") { 36 | return [ast.data!.permalink!]; 37 | } 38 | 39 | const links: string[] = []; 40 | 41 | if (!ast.children) { 42 | return links; 43 | } 44 | 45 | for (const node of ast.children) { 46 | links.push(...findLinks(node)); 47 | } 48 | 49 | return links; 50 | }; 51 | 52 | export const findTitle = (ast: MarkdownNode): string | null => { 53 | if (!ast.children) { 54 | return null; 55 | } 56 | 57 | for (const child of ast.children) { 58 | if ( 59 | child.type === "heading" && 60 | child.depth === 1 && 61 | child.children && 62 | child.children.length > 0 63 | ) { 64 | return child.children[0].value!; 65 | } 66 | } 67 | return null; 68 | }; 69 | 70 | export const id = (path: string): string => { 71 | return md5(path); 72 | 73 | // Extracting file name without extension: 74 | // const fullPath = path.split("/"); 75 | // const fileName = fullPath[fullPath.length - 1]; 76 | // return fileName.split(".")[0]; 77 | }; 78 | 79 | export const getConfiguration = (key: string) => 80 | vscode.workspace.getConfiguration("zknotes")[key]; 81 | 82 | const settingToValue: { [key: string]: vscode.ViewColumn | undefined } = { 83 | active: -1, 84 | beside: -2, 85 | one: 1, 86 | two: 2, 87 | three: 3, 88 | four: 4, 89 | five: 5, 90 | six: 6, 91 | seven: 7, 92 | eight: 8, 93 | nine: 9, 94 | }; 95 | 96 | export const getColumnSetting = (key: string) => { 97 | const column = getConfiguration(key); 98 | return settingToValue[column] || vscode.ViewColumn.One; 99 | }; 100 | 101 | export const getFileIdRegexp = () => { 102 | const DEFAULT_VALUE = "\\d{14}"; 103 | const userValue = getConfiguration("fileIdRegexp") || DEFAULT_VALUE; 104 | 105 | // Ensure the id is not preceeded by [[, which would make it a part of 106 | // wiki-style link, and put the user-supplied regex in a capturing group to 107 | // retrieve matching string. 108 | return new RegExp(`(? `digraph g { 114 | ${graph.nodes 115 | .map((node) => ` ${node.id} [label="${node.label}"];`) 116 | .join("\n")} 117 | ${graph.edges.map((edge) => ` ${edge.source} -> ${edge.target}`).join("\n")} 118 | }`; 119 | 120 | export const exists = (graph: Graph, id: string) => 121 | !!graph.nodes.find((node) => node.id === id); 122 | 123 | export const filterNonExistingEdges = (graph: Graph) => { 124 | graph.edges = graph.edges.filter( 125 | (edge) => exists(graph, edge.source) && exists(graph, edge.target) 126 | ); 127 | }; 128 | -------------------------------------------------------------------------------- /src/parsing.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import * as path from "path"; 3 | import * as unified from "unified"; 4 | import * as markdown from "remark-parse"; 5 | import * as wikiLinkPlugin from "remark-wiki-link"; 6 | import * as frontmatter from "remark-frontmatter"; 7 | import { MarkdownNode, Graph } from "./types"; 8 | import { TextDecoder } from "util"; 9 | import { findTitle, findLinks, id, FILE_ID_REGEXP } from "./utils"; 10 | import { basename } from "path"; 11 | 12 | let idToPath: Record = {}; 13 | 14 | export const idResolver = (id: string) => { 15 | const filePath = idToPath[id]; 16 | if (filePath === undefined) { 17 | return [id]; 18 | } else { 19 | return [filePath]; 20 | } 21 | }; 22 | 23 | const parser = unified() 24 | .use(markdown) 25 | .use(wikiLinkPlugin, { pageResolver: idResolver }) 26 | .use(frontmatter); 27 | 28 | export const parseFile = async (graph: Graph, filePath: string) => { 29 | const buffer = await vscode.workspace.fs.readFile(vscode.Uri.file(filePath)); 30 | const content = new TextDecoder("utf-8").decode(buffer); 31 | const ast: MarkdownNode = parser.parse(content); 32 | 33 | let title: string | null = findTitle(ast); 34 | 35 | const index = graph.nodes.findIndex((node) => node.path === filePath); 36 | 37 | if (!title) { 38 | if (index !== -1) { 39 | graph.nodes.splice(index, 1); 40 | } 41 | 42 | return; 43 | } 44 | 45 | if (index !== -1) { 46 | graph.nodes[index].label = title; 47 | } else { 48 | graph.nodes.push({ id: id(filePath), path: filePath, label: title }); 49 | } 50 | 51 | // Remove edges based on an old version of this file. 52 | graph.edges = graph.edges.filter((edge) => edge.source !== id(filePath)); 53 | 54 | const links = findLinks(ast); 55 | const parentDirectory = filePath.split("/").slice(0, -1).join("/"); 56 | 57 | for (const link of links) { 58 | let target = link; 59 | if (!path.isAbsolute(link)) { 60 | target = path.normalize(`${parentDirectory}/${link}`); 61 | } 62 | 63 | graph.edges.push({ source: id(filePath), target: id(target) }); 64 | } 65 | }; 66 | 67 | export const findFileId = async (filePath: string): Promise => { 68 | const buffer = await vscode.workspace.fs.readFile(vscode.Uri.file(filePath)); 69 | const content = new TextDecoder("utf-8").decode(buffer); 70 | 71 | const match = content.match(FILE_ID_REGEXP); 72 | return match ? match[1] : null; 73 | }; 74 | 75 | export const learnFileId = async (_graph: Graph, filePath: string) => { 76 | const id = await findFileId(filePath); 77 | if (id !== null) { 78 | idToPath[id] = filePath; 79 | } 80 | 81 | const fileName = basename(filePath); 82 | idToPath[fileName] = filePath; 83 | 84 | const fileNameWithoutExt = fileName.split(".").slice(0, -1).join("."); 85 | idToPath[fileNameWithoutExt] = filePath; 86 | }; 87 | 88 | export const parseDirectory = async ( 89 | graph: Graph, 90 | directory: string, 91 | fileCallback: (graph: Graph, path: string) => Promise 92 | ) => { 93 | const files = await vscode.workspace.fs.readDirectory( 94 | vscode.Uri.file(directory) 95 | ); 96 | 97 | const promises: Promise[] = []; 98 | 99 | for (const file of files) { 100 | const fileName = file[0]; 101 | const fileType = file[1]; 102 | const isDirectory = fileType === vscode.FileType.Directory; 103 | const isFile = fileType === vscode.FileType.File; 104 | const hiddenFile = fileName.startsWith("."); 105 | const markdownFile = fileName.endsWith(".md"); 106 | 107 | if (isDirectory && !hiddenFile) { 108 | promises.push( 109 | parseDirectory(graph, `${directory}/${fileName}`, fileCallback) 110 | ); 111 | } else if (isFile && markdownFile) { 112 | promises.push(fileCallback(graph, `${directory}/${fileName}`)); 113 | } 114 | } 115 | 116 | await Promise.all(promises); 117 | }; 118 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | // The module 'vscode' contains the VS Code extensibility API 2 | // Import the module and reference it with the alias vscode in your code below 3 | import * as vscode from 'vscode'; 4 | import { TextDecoder } from "util"; 5 | import * as path from "path"; 6 | import { parseFile, parseDirectory, learnFileId } from "./parsing"; 7 | import { filterNonExistingEdges, getColumnSetting, getConfiguration } from "./utils"; 8 | import { Graph } from "./types"; 9 | 10 | import { 11 | // exists, 12 | // mkdir, 13 | writeFile, 14 | // readFile, 15 | // readdir, 16 | // symlink, 17 | // copyFile, 18 | // stat, 19 | } from 'fs'; 20 | import { formatDate } from './utils'; 21 | 22 | import {languages} from 'vscode' 23 | import { linkCompletionProvider } from './linkCompletionProvider'; 24 | import { zkDefinitionProvider } from './provideDefinition'; 25 | import { zkFoldingRangeProvider } from './zkFoldingRangeProvider'; 26 | 27 | // this method is called when your extension is activated 28 | // your extension is activated the very first time the command is executed 29 | export function activate(context: vscode.ExtensionContext) { 30 | 31 | // Use the console to output diagnostic information (console.log) and errors (console.error) 32 | // This line of code will only be executed once when your extension is activated 33 | console.log('Congratulations, your extension "zknotes" is now active!'); 34 | 35 | // The command has been defined in the package.json file 36 | // Now provide the implementation of the command with registerCommand 37 | // The commandId parameter must match the command field in package.json 38 | let disposable = vscode.commands.registerCommand('zknotes.helloWorld', () => { 39 | // The code you place here will be executed every time your command is executed 40 | 41 | // Display a message box to the user 42 | vscode.window.showInformationMessage('Hello World from zknotes!'); 43 | }); 44 | 45 | context.subscriptions.push(disposable); 46 | 47 | 48 | let createNewNote = vscode.commands.registerCommand('zknotes.newNote', () => { 49 | // 按照zk默认格式进行新文件创建 50 | // yyyyMMddHHmmss_dsds 51 | const root = vscode.workspace.rootPath; 52 | 53 | // 提示输入标题 54 | vscode.window.showInputBox({ 55 | placeHolder: 'input your note title', 56 | prompt: 'enter your zk note title, or esc to cancel' 57 | }).then((inputValue) => { 58 | // 在当前目录下创建新的文件 59 | 60 | // 增加时间戳 61 | const config = vscode.workspace.getConfiguration('zknotes') 62 | const defaultFormat: string = config.get('defaultFormat') || '' 63 | const timestamp = formatDate(new Date(), defaultFormat) 64 | 65 | // 如果设置为空 则不增加前缀 66 | const filename = defaultFormat ? `${timestamp}_${inputValue}` : inputValue 67 | 68 | vscode.window.showInputBox({ 69 | value: filename, 70 | prompt: 'confirm your note title' 71 | }).then(fullFilename => { 72 | const rootFilename = `${root}/${fullFilename}.md` 73 | writeFile(rootFilename, `# ${filename}`, () => { 74 | // 写入1级标题后 打开该文件 75 | vscode.window.showTextDocument(vscode.Uri.file(rootFilename)) 76 | }) 77 | }) 78 | }) 79 | 80 | }); 81 | 82 | context.subscriptions.push(createNewNote) 83 | 84 | /** 85 | * links 键入`[`时自动补全 86 | */ 87 | const linkAutocompletion = languages.registerCompletionItemProvider( 88 | 'markdown', 89 | linkCompletionProvider, 90 | '[', 91 | ); 92 | context.subscriptions.push(linkAutocompletion); 93 | 94 | 95 | /** 96 | * wiki links 可跳转的链接 97 | */ 98 | 99 | const zettelLink = languages.registerDefinitionProvider( 100 | 'markdown', 101 | zkDefinitionProvider 102 | ) 103 | context.subscriptions.push(zettelLink) 104 | 105 | const zettelFold = languages.registerFoldingRangeProvider( 106 | 'markdown', 107 | zkFoldingRangeProvider 108 | ) 109 | context.subscriptions.push(zettelFold) 110 | 111 | /** 112 | * show graph 113 | */ 114 | 115 | context.subscriptions.push( 116 | vscode.commands.registerCommand("zknotes.graphView", async () => { 117 | const column = getColumnSetting("showColumn"); 118 | 119 | const panel = vscode.window.createWebviewPanel( 120 | "markdownLinks", 121 | "Markdown Links", 122 | column, 123 | { 124 | enableScripts: true, 125 | retainContextWhenHidden: true, 126 | } 127 | ); 128 | 129 | if (vscode.workspace.rootPath === undefined) { 130 | vscode.window.showErrorMessage( 131 | "This command can only be activated in open directory" 132 | ); 133 | return; 134 | } 135 | 136 | const graph: Graph = { 137 | nodes: [], 138 | edges: [], 139 | }; 140 | 141 | await parseDirectory(graph, vscode.workspace.rootPath, learnFileId); 142 | await parseDirectory(graph, vscode.workspace.rootPath, parseFile); 143 | filterNonExistingEdges(graph); 144 | 145 | const d3Uri = panel.webview.asWebviewUri( 146 | vscode.Uri.file(path.join(context.extensionPath, "static", "d3.min.js")) 147 | ); 148 | 149 | panel.webview.html = await getWebviewContent(context, graph, d3Uri); 150 | 151 | watch(context, panel, graph); 152 | }) 153 | ); 154 | 155 | const shouldAutoStart = getConfiguration("autoStart"); 156 | 157 | if (shouldAutoStart) { 158 | vscode.commands.executeCommand("zknotes.graphView"); 159 | } 160 | 161 | 162 | } 163 | 164 | // this method is called when your extension is deactivated 165 | export function deactivate() { } 166 | 167 | const watch = ( 168 | context: vscode.ExtensionContext, 169 | panel: vscode.WebviewPanel, 170 | graph: Graph 171 | ) => { 172 | if (vscode.workspace.rootPath === undefined) { 173 | return; 174 | } 175 | 176 | const watcher = vscode.workspace.createFileSystemWatcher( 177 | new vscode.RelativePattern(vscode.workspace.rootPath, "**/*.md"), 178 | false, 179 | false, 180 | false 181 | ); 182 | 183 | const sendGraph = () => { 184 | panel.webview.postMessage({ 185 | type: "refresh", 186 | payload: graph, 187 | }); 188 | }; 189 | 190 | // Watch file changes in case user adds a link. 191 | watcher.onDidChange(async (event) => { 192 | await parseFile(graph, event.path); 193 | filterNonExistingEdges(graph); 194 | sendGraph(); 195 | }); 196 | 197 | watcher.onDidDelete(async (event) => { 198 | const index = graph.nodes.findIndex((node) => node.path === event.path); 199 | if (index === -1) { 200 | return; 201 | } 202 | 203 | graph.nodes.splice(index, 1); 204 | graph.edges = graph.edges.filter( 205 | (edge) => edge.source !== event.path && edge.target !== event.path 206 | ); 207 | 208 | sendGraph(); 209 | }); 210 | 211 | vscode.workspace.onDidOpenTextDocument(async (event) => { 212 | panel.webview.postMessage({ 213 | type: "fileOpen", 214 | payload: { path: event.fileName }, 215 | }); 216 | }); 217 | 218 | vscode.workspace.onDidRenameFiles(async (event) => { 219 | for (const file of event.files) { 220 | const previous = file.oldUri.path; 221 | const next = file.newUri.path; 222 | 223 | for (const edge of graph.edges) { 224 | if (edge.source === previous) { 225 | edge.source = next; 226 | } 227 | 228 | if (edge.target === previous) { 229 | edge.target = next; 230 | } 231 | } 232 | 233 | for (const node of graph.nodes) { 234 | if (node.path === previous) { 235 | node.path = next; 236 | } 237 | } 238 | 239 | sendGraph(); 240 | } 241 | }); 242 | 243 | panel.webview.onDidReceiveMessage( 244 | (message) => { 245 | if (message.type === "click") { 246 | const openPath = vscode.Uri.file(message.payload.path); 247 | const column = getColumnSetting("openColumn"); 248 | 249 | vscode.workspace.openTextDocument(openPath).then((doc) => { 250 | vscode.window.showTextDocument(doc, column); 251 | }); 252 | } 253 | }, 254 | undefined, 255 | context.subscriptions 256 | ); 257 | 258 | panel.onDidDispose(() => { 259 | watcher.dispose(); 260 | }); 261 | }; 262 | 263 | 264 | 265 | async function getWebviewContent( 266 | context: vscode.ExtensionContext, 267 | graph: Graph, 268 | d3Uri: vscode.Uri 269 | ) { 270 | const webviewPath = vscode.Uri.file( 271 | path.join(context.extensionPath, "static", "webview.html") 272 | ); 273 | const file = await vscode.workspace.fs.readFile(webviewPath); 274 | 275 | const text = new TextDecoder("utf-8").decode(file); 276 | 277 | const filled = text 278 | .replace("--REPLACE-WITH-D3-URI--", d3Uri.toString()) 279 | .replace( 280 | "let nodesData = [];", 281 | `let nodesData = ${JSON.stringify(graph.nodes)}` 282 | ) 283 | .replace( 284 | "let linksData = [];", 285 | `let linksData = ${JSON.stringify(graph.edges)}` 286 | ); 287 | 288 | return filled; 289 | } 290 | -------------------------------------------------------------------------------- /static/webview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 52 | 53 | 54 | 55 |
56 | 0 files 57 | 0 links 58 | 1.00x 59 |
60 | 279 | 280 | 281 | --------------------------------------------------------------------------------