├── .npmrc ├── src ├── NodeFlow │ ├── .gitignore │ ├── component │ │ ├── nodeItem │ │ │ ├── base │ │ │ │ ├── README.md │ │ │ │ ├── EnumItem.vue │ │ │ │ └── StringItem.vue │ │ │ ├── flow │ │ │ │ ├── README.md │ │ │ │ ├── FlowItem.vue │ │ │ │ ├── FlowDelayItem.vue │ │ │ │ ├── FlowEvalItem.vue │ │ │ │ └── FlowReqItem.vue │ │ │ ├── README.md │ │ │ ├── feat │ │ │ │ ├── README.md │ │ │ │ ├── StartItem.vue │ │ │ │ └── FeatItem.vue │ │ │ ├── color │ │ │ │ └── ColorItem.vue │ │ │ ├── ItemNodeSlot.vue │ │ │ └── view │ │ │ │ └── MarkdownItem.vue │ │ ├── README.md │ │ ├── node │ │ │ ├── README.md │ │ │ ├── ComfyUINodeGroup.vue │ │ │ ├── ObcanvasNode.vue │ │ │ ├── ComfyUINode.vue │ │ │ ├── CommonNode.vue │ │ │ ├── ItemNode2.vue │ │ │ └── ItemNode.vue │ │ ├── utils │ │ │ ├── dropdownButton.vue │ │ │ └── InteractionControls.vue │ │ └── container │ │ │ ├── fullScreen.ts │ │ │ └── NodeFlowContainerS.vue │ ├── README.md │ ├── index.ts │ ├── utils │ │ ├── main │ │ │ ├── setting.ts │ │ │ └── factoryVueDom.ts │ │ ├── serializeTool │ │ │ └── serializeFlowData.ts │ │ ├── jsonTool │ │ │ ├── factoryFlowData_obcanvas.ts │ │ │ ├── factoryFlowData.ts │ │ │ ├── factoryFlowData_vueflow.ts │ │ │ └── factoryFlowData_item.ts │ │ └── testData │ │ │ └── ueData.js │ ├── tsconfig.json │ ├── stores │ │ └── stores.ts │ └── package.json ├── General │ ├── EditableBlock │ │ ├── LICENSE │ │ ├── README.deprecated.md │ │ ├── LLog.ts │ │ ├── README.zh.md │ │ └── README.md │ └── README.md ├── EditableBlockApp │ ├── README.deprecated.md │ ├── public │ │ └── favicon.ico │ ├── src │ │ ├── components │ │ │ ├── goldenLayout │ │ │ │ ├── README.md │ │ │ │ ├── GlComponent.vue │ │ │ │ └── predefined-layouts.ts │ │ │ ├── MarkdownEditor.vue │ │ │ ├── MarkdownCodeMirror2.vue │ │ │ ├── MarkdownViewer.vue │ │ │ └── MarkdownCodeMirror.vue │ │ ├── main.ts │ │ ├── codemirrorAdapt │ │ │ ├── EditableBlock_Code_Widget.ts │ │ │ └── EditableBlock_Cm.ts │ │ ├── App.vue │ │ ├── index_mdit.ts │ │ └── utils │ │ │ └── preset_map.js │ ├── README.md │ ├── index.html │ ├── tsconfig.json │ ├── vite.config.js │ └── package.json ├── NodeFlowApp │ ├── public │ │ └── favicon.ico │ ├── src │ │ ├── components │ │ │ ├── goldenLayout │ │ │ │ ├── README.md │ │ │ │ ├── predefined-layouts.ts │ │ │ │ └── GlComponent.vue │ │ │ ├── AutoEditor.vue │ │ │ ├── NodeEditor.vue │ │ │ ├── JsonEditor.vue │ │ │ └── NodeList.vue │ │ ├── main.ts │ │ └── App.vue │ ├── README.md │ ├── index.html │ ├── package.json │ ├── tsconfig.json │ └── vite.config.js ├── NodeFlowObsidian │ ├── shims-vue.d.ts │ ├── tsconfig.json │ ├── main.css │ ├── package.json │ ├── NodeFlowFileView.ts │ ├── vite.config.js │ ├── NodeFlowView.ts │ ├── esbuild.config.mjs │ ├── README.md │ └── main.ts ├── NodeFlowVuepress │ ├── index.ts │ ├── MyVueFlow.vue │ ├── index_mdit.ts │ ├── clientConfig.ts │ └── README.md └── README.md ├── .eslintignore ├── pnpm-workspace.yaml ├── versions.json ├── .vscode ├── settings.json └── extensions.json ├── docs ├── image.png └── README.md ├── .editorconfig ├── shims.d.ts ├── .gitignore ├── tsconfig.json ├── version-bump.mjs ├── .eslintrc ├── manifest.json ├── package.json ├── .github └── workflows │ └── nodejs-build.yml └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | tag-version-prefix="" -------------------------------------------------------------------------------- /src/NodeFlow/.gitignore: -------------------------------------------------------------------------------- 1 | *.min.css 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | npm node_modules 2 | build -------------------------------------------------------------------------------- /src/General/EditableBlock/LICENSE: -------------------------------------------------------------------------------- 1 | NO LICENSE 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - src/* 3 | catalog: 4 | -------------------------------------------------------------------------------- /src/EditableBlockApp/README.deprecated.md: -------------------------------------------------------------------------------- 1 | 该文件夹将弃用,转而将其内容置于一个独立项目 2 | -------------------------------------------------------------------------------- /src/General/EditableBlock/README.deprecated.md: -------------------------------------------------------------------------------- 1 | 该文件夹将弃用,转而将其内容置于一个独立项目 2 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.0.0": "0.9.7", 3 | "1.0.1": "0.12.0" 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "commentTranslate.hover.enabled": false 3 | } -------------------------------------------------------------------------------- /src/NodeFlow/component/nodeItem/base/README.md: -------------------------------------------------------------------------------- 1 | # 字符串节点项 2 | 3 | 输入输出字符串 4 | -------------------------------------------------------------------------------- /docs/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LincZero/obsidian-node-flow/HEAD/docs/image.png -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # NodeFlow Docs 2 | 3 | 详见 https://linczero.github.io/MdNote_Public/ProductDoc/Plugin/NodeFlow/ 4 | -------------------------------------------------------------------------------- /src/NodeFlowApp/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LincZero/obsidian-node-flow/HEAD/src/NodeFlowApp/public/favicon.ico -------------------------------------------------------------------------------- /src/EditableBlockApp/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LincZero/obsidian-node-flow/HEAD/src/EditableBlockApp/public/favicon.ico -------------------------------------------------------------------------------- /src/NodeFlow/component/nodeItem/flow/README.md: -------------------------------------------------------------------------------- 1 | # 流程节点项 2 | 3 | 特点: 都包含流程socket,节点名均以 `flow` 开头 4 | 5 | 功能上,往往只关注邻居节点 (上一个和下一个),而不关注全部节点 6 | -------------------------------------------------------------------------------- /src/NodeFlowApp/src/components/goldenLayout/README.md: -------------------------------------------------------------------------------- 1 | 需要组件:`npm i -S golden-layout` 2 | 3 | 参考示例库:https://github.com/emedware/v3-gl-ext 4 | -------------------------------------------------------------------------------- /src/EditableBlockApp/src/components/goldenLayout/README.md: -------------------------------------------------------------------------------- 1 | 需要组件:`pnpm i -S golden-layout` 2 | 3 | 参考示例库:https://github.com/emedware/v3-gl-ext 4 | -------------------------------------------------------------------------------- /src/NodeFlow/component/nodeItem/README.md: -------------------------------------------------------------------------------- 1 | # 节点项 2 | 3 | 节点由不同的节点项组成,使用不同的节点项可以轻易自定义节点 (甚至是无代码的情况下自定义节点) 4 | 5 | ## 命名 6 | 7 | 节点项组件的结尾均以 `Item` 结尾 8 | -------------------------------------------------------------------------------- /src/NodeFlow/component/nodeItem/feat/README.md: -------------------------------------------------------------------------------- 1 | # 调试/功能节点项 2 | 3 | 特点: 都包含功能按钮。基本没有IO口,相当于就是把节点当成控制面板用 (万物皆节点项,这些功能也不例外) 4 | 5 | comfyui 的许多不表现为节点的插件,其实也是节点,也是万物皆节点的一种思想 6 | -------------------------------------------------------------------------------- /src/General/README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | general, shared, utils 都是通用部分,但这里规范: 4 | 5 | - general: 要求必须与该项目无关,即该文件夹中的内容,可以迁移到其他项目中复用 6 | - shared: 多个组件需要共用 7 | - utils: 小工具,由于都是小工具所以有时能复用,但本身不强调复用 8 | -------------------------------------------------------------------------------- /src/NodeFlowObsidian/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { DefineComponent } from 'vue' 3 | const component: DefineComponent<{}, {}, any> 4 | export default component 5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 2 9 | tab_width = 4 10 | -------------------------------------------------------------------------------- /src/NodeFlow/README.md: -------------------------------------------------------------------------------- 1 | # NodeFlow核心模块 2 | 3 | 使用示例见与该文件夹同目录下的另外几个用例 (obsidian、app、vuepress) 4 | 5 | 注意:使用时首先需要为 `nfSetting` 的适配函数提供实现 6 | 7 | ## 使用 8 | 9 | 避免直接使用 vueflow api 10 | 11 | 12 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "spook.easysass", 5 | "intellsmi.comment-translate", 6 | "wiensss.region-highlighter" 7 | ] 8 | } -------------------------------------------------------------------------------- /src/NodeFlow/index.ts: -------------------------------------------------------------------------------- 1 | export { nfSetting } from "./utils/main/setting" 2 | export { factoryVueDom } from "./utils/main/factoryVueDom" // [环境]非obsidian环境要注释掉 3 | import { serializeFlowData } from './utils/serializeTool/serializeFlowData' // [环境]非obsidian环境(或非可写环境)要注释掉 4 | -------------------------------------------------------------------------------- /src/NodeFlowVuepress/index.ts: -------------------------------------------------------------------------------- 1 | import { getDirname, path } from "@vuepress/utils" 2 | 3 | export default (options, ctx) => { 4 | return { 5 | name: 'vuepress-plugin-vue-flow', 6 | clientConfigFile: path.resolve(__dirname, 'clientConfig.ts'), 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /shims.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { ComponentOptions, DefineComponent } from 'vue'; 3 | // const componentOptions: ComponentOptions; 4 | // export default componentOptions; 5 | const defineComponent: DefineComponent; 6 | export default defineComponent; 7 | } -------------------------------------------------------------------------------- /src/NodeFlowApp/README.md: -------------------------------------------------------------------------------- 1 | # NodeFlowApp 2 | 3 | App版本的编译:(两种方法都可以) 4 | 5 | 方法一: 6 | 7 | ```bash 8 | cd src/NodeFlowApp 9 | npm install 10 | npm run dev # or npm run build 11 | ``` 12 | 13 | 方法二: 14 | 15 | ```bash 16 | npm run app:dev # or npm run app:build 17 | ``` 18 | -------------------------------------------------------------------------------- /src/EditableBlockApp/README.md: -------------------------------------------------------------------------------- 1 | # EditableCodeblock App 2 | 3 | App版本的编译: 4 | 5 | 方法一: 6 | 7 | ```bash 8 | cd src/EditableCodeblockApp 9 | pnpm install 10 | pnpm dev # or pnpm build 11 | ``` 12 | 13 | 方法二: 14 | 15 | ```bash 16 | pnpm app2:dev # or onpm app2:build 17 | ``` 18 | -------------------------------------------------------------------------------- /src/NodeFlow/component/README.md: -------------------------------------------------------------------------------- 1 | # 组件.README 2 | 3 | 这里模拟一下继承/实现树 4 | 5 | 组件包含树 6 | 7 | - NodeFlowContainerS | 区域容器, 通用按钮 8 | - NodeFlow | 画布, 节点有关的按钮 9 | - ItemNode | 节点 10 | - NodeItems | 节点项 11 | 12 | 继承树 13 | 14 | - ItemNodeSlot.vue | 节点项的抽象基类 15 | - …… | 节点项 16 | -------------------------------------------------------------------------------- /src/NodeFlowApp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | NodeFlow App 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/EditableBlockApp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | EditableCodeblock App 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/NodeFlow/utils/main/setting.ts: -------------------------------------------------------------------------------- 1 | /// 全局变量,以及部分适配器操作 2 | /// 需要注意适配操作极为关键。因为该项目会用于编译多个场景,不同场景使用不同的依赖组 3 | 4 | export let nfSetting: any = { 5 | app: null, // App, 6 | ctx: null, // MarkdownPostProcessorContext, 7 | cahce_workspace: null, 8 | isDebug: false, 9 | md: null, // MarkdownIt 10 | fn_renderMarkdown: ()=>{}, // ! Need to defined yourself. How to render markdown 11 | fn_request: ()=>{}, // ! Need to defined yourself. How to request http 12 | fn_newView: async ()=>{} // ! Need to defined yourself. Only Obsidian version need 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vscode 2 | # .vscode 3 | 4 | # Intellij 5 | *.iml 6 | .idea 7 | 8 | # npm 9 | node_modules 10 | 11 | # Don't include the compiled main.js file in the repo. 12 | # They should be uploaded to GitHub releases instead. 13 | dist 14 | /main.js 15 | /styles.css 16 | */NodeFlowObsidian/main.js 17 | */NodeFlowObsidian/styles.css 18 | 19 | # Exclude sourcemaps 20 | *.map 21 | 22 | # obsidian 23 | data.json 24 | 25 | # Exclude macOS Finder (System Explorer) View States 26 | .DS_Store 27 | 28 | # src里面的.css后面还得编译一次再压缩,没必要保留min版本 29 | src/**/*.min.css 30 | 31 | /tmp/ 32 | /temp/ 33 | -------------------------------------------------------------------------------- /src/NodeFlowApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-flow-app", 3 | "version": "1.1.0", 4 | "description": "", 5 | "scripts": { 6 | "dev": "vite --config ./vite.config.js", 7 | "build": "vite build --config ./vite.config.js" 8 | }, 9 | "author": "LincZero", 10 | "license": "GNU Affero General Public License v3.0", 11 | "devDependencies": { 12 | "@vitejs/plugin-vue": "^5.2.1", 13 | "sass": "^1.84.0", 14 | "sass-embedded": "^1.83.4", 15 | "vite": "^6.1.0", 16 | "vue": "^3.2.31" 17 | }, 18 | "dependencies": { 19 | "@kyvg/vue3-notification": "^3.4.1", 20 | "golden-layout": "^2.6.0", 21 | "markdown-it": "^14.1.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/NodeFlowApp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "ESNext", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "isolatedModules": true, 13 | "jsx":"preserve", 14 | "lib": [ 15 | "DOM", 16 | "ES5", 17 | "ES6", 18 | "ES7", 19 | "ES2020", 20 | ] 21 | }, 22 | "include": [ 23 | "**/*.ts", 24 | "**/*.tsx", 25 | "**/*.vue", 26 | "src/*.js" 27 | ], 28 | "exclude": ["../NodeFlowObsidian/", "../NodeFlowVuepress/"] 29 | } 30 | -------------------------------------------------------------------------------- /src/NodeFlowObsidian/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "ESNext", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "isolatedModules": true, 13 | "jsx":"preserve", 14 | "lib": [ 15 | "DOM", 16 | "ES5", 17 | "ES6", 18 | "ES7", 19 | "ES2020", 20 | ] 21 | }, 22 | "include": [ 23 | "**/*.ts", 24 | "**/*.tsx", 25 | "**/*.vue", 26 | "src/*.js" 27 | ], 28 | "exclude": ["../NodeFlowApp/", "../NodeFlowVuepress/"] 29 | } 30 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # 多平台编译说明 2 | 3 | ## 目录结构 4 | 5 | 先看目录结构: 6 | 7 | - src/ 8 | - NodeFlow/ | 通用部分 9 | - NodeFlowApp/ | App版本 10 | - NodeFlowObsidian/ | Obsidian版本 11 | - NodeFlowVuepress/ | Vuepress版本 12 | 13 | 编译方法: 14 | 15 | - 通过外层 `tsconfig.json` 中的 `excluede` 选项, 16 | 可以排除掉不需要部分,避免读取不必要的文件,导致编译出错 17 | - 然后根据大家的 package.json 分别启动和编译。具体的编译方法在非通用的三个文件夹内的 `README.md` 文件有更深入的说明 18 | 19 | 共用部分: 20 | 21 | 其中整个NodeFlow文件夹是共用的,没有任何差异。该文件夹外的部分是不公用的 22 | 23 | - obsidian 24 | - 入口:使用共用模块里的factoryVueDom 25 | - vuepress 26 | - 入口:直接使用一个vue组件作为入口 27 | - app 28 | - 入口:直接使用一个vue组件作为入口 29 | 30 | ```bash 31 | pnpm install 32 | 33 | pnpm run ob:build 34 | pnpm run app:dev 35 | ``` 36 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "ESNext", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "isolatedModules": true, 13 | "jsx":"preserve", 14 | "lib": [ 15 | "DOM", 16 | "ES5", 17 | "ES6", 18 | "ES7", 19 | "ES2020", 20 | "DOM.Iterable" 21 | ] 22 | }, 23 | "include": [ 24 | "**/*.ts", 25 | "**/*.tsx", 26 | "**/*.vue", 27 | "src/*.js" 28 | ], 29 | "exclude": ["src/NodeFlowApp/", "src/NodeFlowVuepress/"] 30 | } 31 | -------------------------------------------------------------------------------- /src/EditableBlockApp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@/*": ["../../src/*"] 6 | }, 7 | "inlineSourceMap": true, 8 | "inlineSources": true, 9 | "module": "ESNext", 10 | "target": "ESNext", 11 | "allowJs": true, 12 | "noImplicitAny": true, 13 | "moduleResolution": "node", 14 | "importHelpers": true, 15 | "isolatedModules": true, 16 | "jsx":"preserve", 17 | "lib": [ 18 | "DOM", 19 | "ES5", 20 | "ES6", 21 | "ES7", 22 | "ES2020", 23 | ] 24 | }, 25 | "include": [ 26 | "**/*.ts", 27 | "**/*.tsx", 28 | "**/*.vue", 29 | "src/*.js" 30 | ], 31 | 32 | } 33 | -------------------------------------------------------------------------------- /version-bump.mjs: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from "fs"; 2 | 3 | const targetVersion = process.env.npm_package_version; 4 | 5 | // read minAppVersion from manifest.json and bump version to target version 6 | let manifest = JSON.parse(readFileSync("manifest.json", "utf8")); 7 | const { minAppVersion } = manifest; 8 | manifest.version = targetVersion; 9 | writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t")); 10 | 11 | // update versions.json with target version and minAppVersion from manifest.json 12 | let versions = JSON.parse(readFileSync("versions.json", "utf8")); 13 | versions[targetVersion] = minAppVersion; 14 | writeFileSync("versions.json", JSON.stringify(versions, null, "\t")); 15 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "env": { "node": true }, 5 | "plugins": [ 6 | "@typescript-eslint" 7 | ], 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/eslint-recommended", 11 | "plugin:@typescript-eslint/recommended" 12 | ], 13 | "parserOptions": { 14 | "sourceType": "module" 15 | }, 16 | "rules": { 17 | "no-unused-vars": "off", 18 | "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], 19 | "@typescript-eslint/ban-ts-comment": "off", 20 | "no-prototype-builtins": "off", 21 | "@typescript-eslint/no-empty-function": "off" 22 | } 23 | } -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "node-flow", 3 | "name": "NodeFlow", 4 | "version": "1.1.0", 5 | "minAppVersion": "0.15.0", 6 | "description": "Render node streams like `ComfyUi`, `UE`, `Houdini`, `Blender`, etc., to make it easy to write relevant notes. json describes the chart, compared to screenshots, making it easier to modify later. The plugin is also compatible with blogs.", 7 | "author": "LincZero", 8 | "authorUrl": "https://github.com/LincZero/", 9 | "fundingUrl": { 10 | "Support Page": "https://github.com/LincZero#thank-you-very-much-for-your-support", 11 | "中国大陆备用链接": "https://linczero-github-io.pages.dev/MdNote_Other/LincZero/#thank-you-very-much-for-your-support" 12 | }, 13 | "isDesktopOnly": false 14 | } 15 | -------------------------------------------------------------------------------- /src/NodeFlowObsidian/main.css: -------------------------------------------------------------------------------- 1 | /** 2 | * 自定义样式的位置 3 | * 4 | * ## 原理 5 | * 6 | * 流程:vue组件的样式首先会生成在main.css中,然后src/style/main.css需要合并main.css再一起输出到styles.css中 7 | * 8 | * 即有三个来源: 9 | * 10 | * - vue文件编译生成的样式文件 11 | * - NodeFlow Core的样式文件 12 | * - Obsidian补充的样式文件 13 | * 14 | * 这里会合并这些样式生成一个最终的样式文件 15 | * 16 | * ## 注意点 17 | * 18 | * 你不应该直接编辑style.css文件,而应该编辑该文件 19 | */ 20 | 21 | @import "../../main.css"; /* Vue文件中的style会自动转化为该文件,在Vue构建时自动生成。这里包含一下最后转../../styles.css */ 22 | @import "../NodeFlow/style/vue_custom.css"; /* NodeFlow组件的样式 */ 23 | 24 | .nf-autoDie { 25 | height: 100%; 26 | } 27 | /* 阅读模式 */ 28 | .markdown-reading-view .normal-size { 29 | margin-bottom: 24px; 30 | } 31 | -------------------------------------------------------------------------------- /src/NodeFlow/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "ESNext", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "isolatedModules": true, 13 | "jsx":"preserve", 14 | "lib": [ 15 | "DOM", 16 | "ES5", 17 | "ES6", 18 | "ES7", 19 | "ES2020", 20 | "DOM.Iterable", 21 | ] 22 | }, 23 | "include": [ 24 | "**/*.ts", 25 | "**/*.tsx", 26 | "**/*.vue", 27 | "src/*.js" 28 | ], 29 | "exclude": ["../NodeFlowObsidian/", "../NodeFlowVuepress/", "../NodeFlowApp/"] 30 | } 31 | -------------------------------------------------------------------------------- /src/NodeFlowApp/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import path from 'path'; 3 | import vue from '@vitejs/plugin-vue'; 4 | 5 | export default defineConfig({ 6 | plugins: [vue()], 7 | css: { 8 | preprocessorOptions: { 9 | scss: { 10 | // additionalData: `@import "./src/styles/global.scss";` 11 | } 12 | } 13 | }, 14 | base: '/obsidian-node-flow/', // [!code] 临时,需要根据你要部署的位置进行修改 15 | server: { 16 | port: 3060 17 | }, 18 | root: path.resolve(__dirname, './'), // 确保 Vite 使用正确的根目录 19 | build: { 20 | outDir: 'dist', 21 | rollupOptions: { 22 | // input: { 23 | // main: path.resolve(__dirname, './src/main.ts'), 24 | // }, 25 | input: path.resolve(__dirname, './index.html'), 26 | }, 27 | }, 28 | }); 29 | -------------------------------------------------------------------------------- /src/NodeFlowVuepress/MyVueFlow.vue: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 29 | 30 | 32 | -------------------------------------------------------------------------------- /src/NodeFlow/component/node/README.md: -------------------------------------------------------------------------------- 1 | # 节点 2 | 3 | ## 命名 4 | 5 | 均以 `Node` 结尾 6 | 7 | ## ItemNode 8 | 9 | ### 节点项类型与节点类型 10 | 11 | - 旧做法:定义了不同的节点类型 12 | - 新做法:不定义不同的的节点类型,而是定义不同的节点项,不同的节点类型其本质上是使用了不同的节点项 13 | - 优点:能实现更灵活的组合 14 | - 优点:**节点本身也属于节点项**,这能够实现节点包含节点的功能 15 | 16 | ## 多格式解析的做法 17 | 18 | 有两种 19 | 20 | - 一是格式直接解析、解码 21 | 不同的数据先转化成合适的json数据(部分统一化),然后使用不同的节点结构 22 | 部分统一化的标准是:以下字段的齐全 23 | ```json 24 | nodes 25 | id 26 | data 27 | position 28 | type 29 | edges 30 | id 31 | data 32 | position 33 | type 34 | ``` 35 | - 二是格式转化成统一的格式再解析 36 | 不同的数据都要转化成相同的json结构(完全统一化),然后使用同一种节点类型 37 | 完全统一化的标准是:包含nodes.data和edges.data内部字段的统一 38 | 39 | 这个设计在任意软件中都是一样的,例如图片格式、视频格式等。 40 | 41 | 例如我可以有mp4和avi解码器(效率性能更高、可实时进行),也可以全部转成mp4再一起读(效率更差但程序更简单)。 42 | 43 | 而我这里的json互转中,性能不太重要,通用性和代码架构更重要。我一开始是方案一,后面追加了方案二。现在是两种方案都能用 44 | -------------------------------------------------------------------------------- /src/NodeFlowVuepress/index_mdit.ts: -------------------------------------------------------------------------------- 1 | import MarkdownIt from "markdown-it"; 2 | 3 | function nfRender_fence(md: MarkdownIt, options?: any): void { 4 | const oldFence = md.renderer.rules.fence || function(tokens, idx, options, env, self) { 5 | return self.renderToken(tokens, idx, options); 6 | }; 7 | 8 | md.renderer.rules.fence = (tokens, idx, options, env, self) => { 9 | // 查看是否匹配 10 | let token = tokens[idx] 11 | if (!token.info.toLowerCase().startsWith("nodeflow-")) { return oldFence(tokens, idx, options, env, self) } 12 | 13 | // 渲染 14 | // type vueflow, comfyui. v1.1.0不再需要对type进行.slice(9) 15 | return `` 16 | } 17 | } 18 | 19 | export default function nodeflow_mdit(md: MarkdownIt, options?: any): void { 20 | md.use(nfRender_fence) 21 | } 22 | -------------------------------------------------------------------------------- /src/NodeFlow/stores/stores.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 全局状态存储器 (仅部分环境需要,部分环境不需要这部分) 3 | * 4 | * 主要是golden-layout布局中,layout之间都是松耦合关系,无法通过props或inject等方式传参。都是使用的这个 5 | * 6 | * 信息源使用优先级:props > inject > globalState 7 | * 8 | * - 伪全局 (目前是这个,后面应该要修改成真全局),一个nfNodes对应一个 9 | * - 真全局 10 | */ 11 | 12 | import { createGlobalState } from '@vueuse/core' 13 | import { ref } from 'vue' 14 | import { NFNodes } from '../component/utils/NFNodes' 15 | 16 | export const useGlobalState = createGlobalState( 17 | () => { 18 | // TODO 选择对象可以简化去掉冗余,应该封装到 nfNodes 里 19 | const selected = ref([]) // 自行维护的已选列表. type: ref([]) 20 | const selected2 = ref(null) // vueflow维护的已选列表 (之前有bug, 后来莫名其妙修好了) 21 | const nfNodes = ref(null) 22 | // const _useVueFlow = ref(null) 23 | // let updateViewFlag = ref(false) 24 | return { 25 | selected, 26 | selected2, 27 | nfNodes 28 | } 29 | } 30 | ) 31 | -------------------------------------------------------------------------------- /src/EditableBlockApp/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import path from 'path'; 3 | import vue from '@vitejs/plugin-vue'; 4 | 5 | export default defineConfig({ 6 | plugins: [vue()], 7 | define: { 8 | '__VUE_PROD_HYDRATION_MISMATCH_DETAILS__': JSON.stringify(true), 9 | // 添加其他需要的特性标志 10 | }, 11 | css: { 12 | preprocessorOptions: { 13 | scss: { 14 | // additionalData: `@import "./src/styles/global.scss";` 15 | } 16 | } 17 | }, 18 | base: '/obsidian-node-flow/', // [!code] 临时,需要根据你要部署的位置进行修改 19 | server: { 20 | host: 'localhost', 21 | port: 3012, 22 | }, 23 | root: path.resolve(__dirname, './'), // 确保 Vite 使用正确的根目录 24 | build: { 25 | outDir: 'dist', 26 | rollupOptions: { 27 | // input: { 28 | // main: path.resolve(__dirname, './src/main.ts'), 29 | // }, 30 | input: path.resolve(__dirname, './index.html'), 31 | }, 32 | }, 33 | }); 34 | -------------------------------------------------------------------------------- /src/EditableBlockApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "editable-codeblock-app", 3 | "version": "1.1.0", 4 | "description": "", 5 | "scripts": { 6 | "dev": "vite --config ./vite.config.js", 7 | "build": "vite build --config ./vite.config.js" 8 | }, 9 | "author": "LincZero", 10 | "license": "GNU Affero General Public License v3.0", 11 | "devDependencies": { 12 | "@vitejs/plugin-vue": "^5.2.1", 13 | "sass": "^1.84.0", 14 | "sass-embedded": "^1.83.4", 15 | "vite": "^6.1.0", 16 | "vue": "^3.2.31" 17 | }, 18 | "dependencies": { 19 | "@codemirror/commands": "^6.8.1", 20 | "@codemirror/lang-markdown": "^6.3.3", 21 | "@codemirror/language": "^6.11.2", 22 | "@codemirror/state": "^6.5.2", 23 | "@codemirror/theme-one-dark": "^6.1.3", 24 | "@codemirror/view": "^6.38.1", 25 | "@kyvg/vue3-notification": "^3.4.1", 26 | "@retronav/ixora": "^0.3.3", 27 | "codemirror": "^6.0.2", 28 | "golden-layout": "^2.6.0", 29 | "markdown-it": "^14.1.0", 30 | "prismjs": "^1.30.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/NodeFlowObsidian/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-node-flow", 3 | "version": "1.1.0", 4 | "description": "Render node streams like `ComfyUi`, `UE`, `Houdini`, `Blender`, etc., to make it easy to write relevant notes. json describes the chart, compared to screenshots, making it easier to modify later. The plugin is also compatible with blogs.", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "node ./esbuild.config.mjs", 8 | "build": "tsc -noEmit -skipLibCheck && node ./esbuild.config.mjs production", 9 | "build2": "tsc -noEmit -skipLibCheck && vite build --config ./vite.config.js" 10 | }, 11 | "keywords": [ 12 | "Obsidian", 13 | "ComfyUi", 14 | "UE", 15 | "Houdini", 16 | "Blender", 17 | "nodeflow" 18 | ], 19 | "author": "LincZero", 20 | "license": "GPL-3.0-only", 21 | "dependencies": { 22 | "vue": "^3.2.31" 23 | }, 24 | "devDependencies": { 25 | "@the_tree/esbuild-plugin-vue3": "^0.3.1", 26 | "builtin-modules": "^3.2.0", 27 | "esbuild": "^0.14.49", 28 | "obsidian": "^1.7.2", 29 | "typescript": "^5.6.3" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-flow", 3 | "version": "1.1.0", 4 | "description": "Render node streams like `ComfyUi`, `UE`, `Houdini`, `Blender`, etc., to make it easy to write relevant notes. json describes the chart, compared to screenshots, making it easier to modify later. The plugin is also compatible with blogs.", 5 | "main": "main.js", 6 | "scripts": { 7 | "ob:dev": "cd ./src/NodeFlowObsidian && pnpm dev", 8 | "ob:build": "cd ./src/NodeFlowObsidian && pnpm build", 9 | "ob:build2": "cd ./src/NodeFlowObsidian && pnpm build2", 10 | "ob:version": "node version-bump.mjs && git add manifest.json versions.json", 11 | "app:dev": "cd ./src/NodeFlowApp && pnpm dev", 12 | "app:build": "cd ./src/NodeFlowApp && pnpm build", 13 | "app2:dev": "cd ./src/EditableBlockApp && pnpm dev", 14 | "app2:build": "cd ./src/EditableBlockApp && pnpm build" 15 | }, 16 | "keywords": [ 17 | "Obsidian", 18 | "ComfyUi", 19 | "UE", 20 | "Houdini", 21 | "Blender", 22 | "nodeflow" 23 | ], 24 | "author": "LincZero", 25 | "license": "GPL-3.0-only", 26 | "dependencies": { 27 | }, 28 | "devDependencies": { 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/NodeFlowApp/src/components/AutoEditor.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 24 | 25 | 32 | 33 | 43 | -------------------------------------------------------------------------------- /src/EditableBlockApp/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from './App.vue'; 3 | // import './main.css' 4 | 5 | // // 适配器 6 | // import { nfSetting } from "../../NodeFlow/index" 7 | // // @env [环境] md渲染, mdit版本 8 | // import MarkdownIt from "markdown-it"; 9 | // const md = MarkdownIt() 10 | // nfSetting.fn_renderMarkdown = ((markdown: string, el: HTMLElement, ctx?: any): void => { 11 | // el.classList.add("markdown-rendered") 12 | 13 | // const result: string = (md as MarkdownIt).render(markdown) 14 | // const el_child = document.createElement("div"); el.appendChild(el_child); el_child.innerHTML = result; 15 | 16 | // // 好像在Client端没办法获取到vuepress的md对象…… 17 | // // if (!nfSetting.md) { 18 | // // console.warn("无法渲染markdown", nfSetting) 19 | // // el.innerHTML = markdown 20 | // // } 21 | // // else {} 22 | // }) 23 | // // @env [环境] http接口,其他环境版本。需要注意ob requestUrl和fetch的返回值不一样,前者还有一层status和json 24 | // nfSetting.fn_request = async ( 25 | // url: string, 26 | // method: string | undefined, 27 | // headers: Record | undefined, 28 | // body: string | ArrayBuffer | undefined 29 | // ) => { 30 | // const responseData = await fetch(url, {method, headers, body}); 31 | // return responseData 32 | // } 33 | 34 | createApp(App).mount('#app'); 35 | -------------------------------------------------------------------------------- /src/NodeFlowApp/src/components/goldenLayout/predefined-layouts.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ComponentItemConfig, 3 | ItemType, 4 | LayoutConfig, 5 | } from "golden-layout"; 6 | 7 | const miniRowConfig: LayoutConfig = { 8 | root: { 9 | type: ItemType.row, 10 | content: [ 11 | { 12 | type: ItemType.component, 13 | title: "智能编辑器", 14 | header: { show: "top", popout: false }, 15 | // isClosable: false, 16 | componentType: "AutoEditor", 17 | componentState: undefined, 18 | width: 26, 19 | } as ComponentItemConfig, 20 | { 21 | type: ItemType.component, 22 | title: "画布", 23 | header: { show: "top", popout: false }, 24 | componentType: "NodeFlow", 25 | width: 74, 26 | } as ComponentItemConfig, 27 | { 28 | type: ItemType.stack, 29 | header: { show: "top", popout: false }, 30 | width: 20, 31 | content: [ 32 | { 33 | type: ItemType.component, 34 | title: "后端连接器", 35 | // icon: "fa fa-plug", 36 | componentType: "BackendConnector", 37 | } as ComponentItemConfig, 38 | { 39 | type: ItemType.component, 40 | title: "节点模板", 41 | componentType: "NodeList", 42 | } as ComponentItemConfig, 43 | ] 44 | }, 45 | ] 46 | } 47 | }; 48 | 49 | export const prefinedLayouts = { 50 | miniRow: miniRowConfig, 51 | } 52 | -------------------------------------------------------------------------------- /src/NodeFlowObsidian/NodeFlowFileView.ts: -------------------------------------------------------------------------------- 1 | import { factoryVueDom } from "../NodeFlow/utils/main/factoryVueDom"; 2 | import { TextFileView } from "obsidian"; 3 | 4 | export const NodeFlowFileViewFlag = "NodeFlowFileView"; 5 | 6 | /** 7 | * 用于在Obsidian注册新的文件格式,以自定义方式打开新的格式 8 | */ 9 | export class NodeFlowFileView extends TextFileView { 10 | getViewType() { 11 | return NodeFlowFileViewFlag; 12 | } 13 | 14 | getViewData() { 15 | return this.data; 16 | } 17 | 18 | setViewData(data: string, clear: boolean) { 19 | this.data = data; 20 | 21 | let jsonType:string = "nodeflow-comfyui" 22 | if (this.file.name.endsWith("workflow_json")) jsonType = "nodeflow-comfyui" 23 | else if (this.file.name.endsWith("canvas_json")) jsonType = "nodeflow-canvas" 24 | else if (this.file.name.endsWith(".workflow.json")) jsonType = "nodeflow-comfyui" 25 | else if (this.file.name.endsWith(".canvas.json")) jsonType = "nodeflow-canvas" 26 | // TODO 通过json内容来判断json类型 27 | 28 | this.contentEl.empty(); 29 | const div_child = this.contentEl.createEl("div"); div_child.classList.add("nf-autoDie"); div_child.setAttribute("style", "height: 100%"); 30 | factoryVueDom(jsonType, div_child, this.data, false); // (需要挂载到一个会死亡的div) 31 | } 32 | 33 | clear() { 34 | this.data = ""; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/NodeFlow/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-flow-core", 3 | "version": "1.1.0", 4 | "description": "Render node streams like `ComfyUi`, `UE`, `Houdini`, `Blender`, etc., to make it easy to write relevant notes. json describes the chart, compared to screenshots, making it easier to modify later. The plugin is also compatible with blogs.", 5 | "main": "main.js", 6 | "scripts": {}, 7 | "keywords": [ 8 | "Obsidian", 9 | "ComfyUi", 10 | "UE", 11 | "Houdini", 12 | "Blender", 13 | "nodeflow" 14 | ], 15 | "author": "LincZero", 16 | "license": "GPL-3.0-only", 17 | "dependencies": { 18 | "@dagrejs/dagre": "^1.1.4", 19 | "@vue-flow/background": "^1.3.2", 20 | "@vue-flow/core": "^1.42.1", 21 | "@vueuse/core": "^13.5.0", 22 | "markdown-it": "^14.1.0", 23 | "prismjs": "^1.29.0", 24 | "vue": "^3.2.31" 25 | }, 26 | "devDependencies": { 27 | "@types/node": "^22.13.11", 28 | "@types/vue": "^2.0.0", 29 | "@typescript-eslint/eslint-plugin": "^8.10.0", 30 | "@typescript-eslint/parser": "^8.10.0", 31 | "@vitejs/plugin-vue": "^5.2.3", 32 | "builtin-modules": "^3.2.0", 33 | "esbuild": "^0.14.49", 34 | "eslint": "^9.0.0", 35 | "hash-sum": "^2.0.0", 36 | "sass-embedded": "^1.86.0", 37 | "tslib": "2.3.1", 38 | "typescript": "^5.6.3", 39 | "vite": "^6.2.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/NodeFlow/component/node/ComfyUINodeGroup.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 26 | 27 | 32 | 33 | 56 | -------------------------------------------------------------------------------- /src/NodeFlowApp/src/components/goldenLayout/GlComponent.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 55 | -------------------------------------------------------------------------------- /src/EditableBlockApp/src/components/goldenLayout/GlComponent.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 55 | -------------------------------------------------------------------------------- /src/NodeFlow/utils/main/factoryVueDom.ts: -------------------------------------------------------------------------------- 1 | import { createApp, App as VueApp } from 'vue'; 2 | import NodeFlowContainerS from '../../component/container/NodeFlowContainerS.vue'; 3 | 4 | import { factoryFlowData, failedFlowData } from '../jsonTool/factoryFlowData' 5 | 6 | import { nfSetting } from '../../utils/main/setting' 7 | import { NFNodes } from '../../component/utils/NFNodes'; 8 | 9 | /// 在div内创建指定的 Vue UI 10 | export function factoryVueDom( 11 | jsonType: string = "nodeflow-item", 12 | div: HTMLElement, 13 | mdStr: string = "", 14 | isMini: boolean = true, 15 | fn_save: (str: string) => void = ()=>{ console.warn("The save hook is not set") } 16 | ):void { 17 | // 代码块,替换为节点流画布 18 | const targetEl = div 19 | mountVue(targetEl, isMini) 20 | 21 | /// 将targetVue挂载到targetEl上 22 | function mountVue (targetEl:HTMLElement, _isMini:boolean) { 23 | const nfNodes = NFNodes.useFactoryNFNodes() 24 | nfNodes.jsonType.value = jsonType 25 | nfNodes.jsonStr.value = mdStr 26 | 27 | // 根据新json生成节点流 28 | const _app = createApp(NodeFlowContainerS, { // `` (检索型注释) 29 | nfNodes: nfNodes, 30 | fn_newView: async ()=>{ // 闭包 31 | const targetEl: HTMLElement = await nfSetting.fn_newView() 32 | mountVue(targetEl, false) 33 | }, 34 | fn_save: fn_save, 35 | isMini: _isMini 36 | }); 37 | _app.mount(targetEl); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/NodeFlowObsidian/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { resolve } from 'path'; 3 | import builtins from 'builtin-modules'; 4 | import vue from '@vitejs/plugin-vue' 5 | 6 | export default defineConfig({ 7 | plugins: [ 8 | vue() 9 | ], 10 | outDir: '/dist/vite/', 11 | build: { 12 | lib: { 13 | entry: resolve(__dirname, './main.ts'), 14 | name: 'ExpressApp', 15 | fileName: 'index', 16 | formats: ['cjs'], 17 | }, 18 | rollupOptions: { 19 | external: [ // 将 obsidian 标记为外部依赖 20 | 'obsidian', 21 | 'electron', 22 | '@codemirror/autocomplete', 23 | '@codemirror/closebrackets', 24 | '@codemirror/collab', 25 | '@codemirror/commands', 26 | '@codemirror/comment', 27 | '@codemirror/fold', 28 | '@codemirror/gutter', 29 | '@codemirror/highlight', 30 | '@codemirror/history', 31 | '@codemirror/language', 32 | '@codemirror/lint', 33 | '@codemirror/matchbrackets', 34 | '@codemirror/panel', 35 | '@codemirror/rangeset', 36 | '@codemirror/rectangular-selection', 37 | '@codemirror/search', 38 | '@codemirror/state', 39 | '@codemirror/stream-parser', 40 | '@codemirror/text', 41 | '@codemirror/tooltip', 42 | '@codemirror/view', 43 | ...builtins, 44 | ], 45 | }, 46 | }, 47 | }); 48 | -------------------------------------------------------------------------------- /src/NodeFlowVuepress/clientConfig.ts: -------------------------------------------------------------------------------- 1 | import { defineClientConfig } from 'vuepress/client'; 2 | 3 | import MyVueFlow from "./MyVueFlow.vue"; 4 | 5 | // 适配器 6 | import { nfSetting } from "../NodeFlow/index" 7 | // @env [环境] md渲染, mdit版本 8 | import MarkdownIt from "markdown-it"; 9 | const md = new MarkdownIt({ 10 | html: true, // 启用 HTML 标签解析 11 | breaks: true // 将换行符转换为
标签 12 | }) 13 | nfSetting.fn_renderMarkdown = ((markdown: string, el: HTMLElement, ctx?: any): void => { 14 | el.classList.add("markdown-rendered") 15 | 16 | const result: string = (md as MarkdownIt).render(markdown) 17 | const el_child = document.createElement("div"); el.appendChild(el_child); el_child.innerHTML = result; 18 | 19 | // 好像在Client端没办法获取到vuepress的md对象…… 20 | // if (!nfSetting.md) { 21 | // console.warn("无法渲染markdown", nfSetting) 22 | // el.innerHTML = markdown 23 | // } 24 | // else {} 25 | }) 26 | // @env [环境] http接口,其他环境版本。需要注意ob requestUrl和fetch的返回值不一样,前者还有一层status和json 27 | nfSetting.fn_request = async ( 28 | url: string, 29 | method: string | undefined = 'GET', 30 | headers: Record | undefined = undefined, 31 | body: string | ArrayBuffer | undefined = undefined 32 | ) => { 33 | const responseData = await fetch(url, {method, headers, body}); 34 | return responseData 35 | } 36 | 37 | export default defineClientConfig({ 38 | enhance: ({ app, router, siteData }) => { 39 | app.component("VueFlow", MyVueFlow); 40 | }, 41 | }) 42 | -------------------------------------------------------------------------------- /src/NodeFlow/component/nodeItem/flow/FlowItem.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | 12 | 32 | 33 | 57 | -------------------------------------------------------------------------------- /src/NodeFlowApp/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from './App.vue'; 3 | // import './main.css' 4 | 5 | // 适配器 6 | import { nfSetting } from "../../NodeFlow/index" 7 | // @env [环境] md渲染, mdit版本 8 | import MarkdownIt from "markdown-it"; 9 | const md = new MarkdownIt({ 10 | html: true, // 启用 HTML 标签解析 11 | breaks: true // 将换行符转换为
标签 12 | }) 13 | nfSetting.fn_renderMarkdown = ((markdown: string, el: HTMLElement, ctx?: any): void => { 14 | el.classList.add("markdown-rendered") 15 | 16 | const result: string = (md as MarkdownIt).render(markdown) 17 | const el_child = document.createElement("div"); el.appendChild(el_child); el_child.innerHTML = result; 18 | 19 | // 好像在Client端没办法获取到vuepress的md对象…… 20 | // if (!nfSetting.md) { 21 | // console.warn("无法渲染markdown", nfSetting) 22 | // el.innerHTML = markdown 23 | // } 24 | // else {} 25 | }) 26 | // @env [环境] http接口,其他环境版本。需要注意ob requestUrl和fetch的返回值不一样,前者还有一层status和json 27 | nfSetting.fn_request = async ( 28 | url: string, 29 | method: string | undefined = 'GET', 30 | headers: Record | undefined = undefined, 31 | body: string | ArrayBuffer | undefined = undefined 32 | ) => { 33 | const responseData = await fetch(url, {method, headers, body}); 34 | return responseData 35 | } 36 | 37 | const app = createApp(App) 38 | 39 | // https://kyvg.github.io/vue3-notification/guide/installation.html 40 | import Notifications from '@kyvg/vue3-notification' 41 | app.use(Notifications) 42 | 43 | app.mount('#app') 44 | -------------------------------------------------------------------------------- /src/NodeFlowObsidian/NodeFlowView.ts: -------------------------------------------------------------------------------- 1 | import { nfSetting } from "../NodeFlow/utils/main/setting"; 2 | import { ItemView, WorkspaceLeaf } from 'obsidian'; 3 | 4 | export const NodeFlowViewFlag = "NodeFlowView" 5 | 6 | /** 7 | * 用于在Obsidian中打开新的叶子视图,并显示节点流 8 | */ 9 | export class NodeFlowView extends ItemView { 10 | constructor(leaf: WorkspaceLeaf) { 11 | super(leaf); 12 | } 13 | getViewType(): string { 14 | return NodeFlowViewFlag; 15 | } 16 | getDisplayText(): string { 17 | return NodeFlowViewFlag; 18 | } 19 | getIcon(): string { 20 | return "workflow"; // https://lucide.dev/icons/ 21 | } 22 | async onOpen() { 23 | const container = this.containerEl.children[1]; 24 | container.empty(); 25 | let content = container.createEl("div", { 26 | cls: "nf-shell-view" 27 | }); 28 | } 29 | async onClose() { 30 | } 31 | } 32 | 33 | /// 在Obsidian的新视图中显示节点画布 34 | export async function fn_newView(): Promise { 35 | // 如果没有该Docker视图则创建一个 36 | const cahce_workspace = nfSetting.cahce_workspace 37 | if (cahce_workspace.getLeavesOfType(NodeFlowViewFlag).length === 0) { 38 | await cahce_workspace.getRightLeaf(false).setViewState({ 39 | type: NodeFlowViewFlag, 40 | active: true, 41 | }) 42 | } 43 | const NodeFlowLeaf: WorkspaceLeaf = cahce_workspace.getLeavesOfType(NodeFlowViewFlag)[0] 44 | 45 | // 前置/展开该Docker视图 46 | cahce_workspace.revealLeaf(NodeFlowLeaf) 47 | 48 | // 更新该视图中的内容 49 | const containerEl: HTMLElement = NodeFlowLeaf.view.containerEl; 50 | containerEl.empty(); 51 | return containerEl 52 | } 53 | -------------------------------------------------------------------------------- /src/NodeFlow/component/nodeItem/flow/FlowDelayItem.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | 12 | 37 | 38 | 61 | -------------------------------------------------------------------------------- /src/EditableBlockApp/src/components/MarkdownEditor.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 35 | 36 | 66 | -------------------------------------------------------------------------------- /src/EditableBlockApp/src/components/goldenLayout/predefined-layouts.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ComponentItemConfig, 3 | ItemType, 4 | LayoutConfig, 5 | } from "golden-layout"; 6 | 7 | const miniRowConfig: LayoutConfig = { 8 | root: { 9 | type: ItemType.row, 10 | content: [ 11 | { 12 | type: ItemType.component, 13 | title: "White", 14 | header: { show: "left", popout: false }, 15 | componentType: "White", 16 | width: 0 17 | } as ComponentItemConfig, 18 | // 这样可以保证 white 变动时,这里组的宽度按比例调整 19 | { 20 | type: ItemType.row, 21 | content: [ 22 | { 23 | type: ItemType.component, 24 | title: "MdEditor", 25 | header: { show: "top", popout: false }, 26 | componentType: "MdEditor", 27 | width: 50 28 | } as ComponentItemConfig, 29 | { 30 | type: ItemType.stack, 31 | content: [ 32 | { 33 | type: ItemType.component, 34 | title: "MdViewer", 35 | header: { show: "top", popout: false }, 36 | componentType: "MdViewer", 37 | width: 50 38 | } as ComponentItemConfig, 39 | { 40 | type: ItemType.component, 41 | title: "MdCodeMirror2", 42 | header: { show: "top", popout: false }, 43 | componentType: "MdCodeMirror2", 44 | width: 50 45 | } as ComponentItemConfig, 46 | { 47 | type: ItemType.component, 48 | title: "MdCodeMirror", 49 | header: { show: "top", popout: false }, 50 | componentType: "MdCodeMirror", 51 | width: 50 52 | } as ComponentItemConfig, 53 | ] 54 | }, 55 | ] 56 | }, 57 | { 58 | type: ItemType.component, 59 | title: "White", 60 | header: { show: "right", popout: false }, 61 | componentType: "White", 62 | width: 0 63 | } as ComponentItemConfig, 64 | ] 65 | } 66 | }; 67 | 68 | export const prefinedLayouts = { 69 | miniRow: miniRowConfig, 70 | } 71 | -------------------------------------------------------------------------------- /src/NodeFlow/component/nodeItem/color/ColorItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 40 | 41 | 65 | -------------------------------------------------------------------------------- /src/NodeFlow/component/nodeItem/base/EnumItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 37 | 38 | 77 | -------------------------------------------------------------------------------- /src/General/EditableBlock/LLog.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A simple, general, log tool 3 | * 4 | * feat: 5 | * - level 6 | * - log & notice & file 7 | */ 8 | 9 | export type LogLevel = "debug" | "info" | "warn" | "error" | "none"; 10 | export type LogLevel2 = "debug" | "info" | "warn" | "error"; 11 | 12 | interface LLog_Config { 13 | level?: LogLevel; // min output level 14 | enableTimestamp?: boolean; // is output timestamp 15 | tag?: string; // tag prefix 16 | } 17 | 18 | const levelOrder: LogLevel[] = ["debug", "info", "warn", "error", "none"]; 19 | 20 | export class LLog { 21 | config: Required = { 22 | level: "debug", 23 | enableTimestamp: true, 24 | tag: "", 25 | } 26 | 27 | set_config(cfg: LLog_Config): void { 28 | this.config = { ...this.config, ...cfg } 29 | } 30 | 31 | debug(...args: unknown[]): void { 32 | this.logCore("debug", ...args) 33 | } 34 | 35 | info(...args: unknown[]): void { 36 | this.logCore("info", ...args) 37 | } 38 | 39 | warn(...args: unknown[]): void { 40 | this.logCore("warn", ...args) 41 | } 42 | 43 | error(...args: unknown[]): void { 44 | this.logCore("error", ...args) 45 | } 46 | 47 | static consoleMap = { 48 | debug: console.log, 49 | info: console.info, 50 | warn: console.warn, 51 | error: console.error, 52 | } as const; 53 | 54 | // can override 55 | /// // @return 返回打印内容。可以通过这种方式链式调用添加Notice等操作,进行多重输出 56 | logCore(level: LogLevel2, ...args: unknown[]): void { 57 | if (levelOrder.indexOf(level) < levelOrder.indexOf(this.config.level)) return 58 | 59 | const now = this.config.enableTimestamp ? `[${new Date().toISOString()}]` : "" 60 | const tag = this.config.tag ? `[${this.config.tag}]` : "" 61 | const prefix = [now, tag, `[${level.toUpperCase()}]`].filter(Boolean).join(" ") 62 | 63 | LLog.consoleMap[level]?.(prefix, ...args) 64 | } 65 | } 66 | // Provide an object that is ready to use out of the box. 67 | export const LLOG = new LLog() 68 | -------------------------------------------------------------------------------- /src/NodeFlow/component/nodeItem/feat/StartItem.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 16 | 17 | 40 | 41 | 65 | -------------------------------------------------------------------------------- /src/NodeFlow/utils/serializeTool/serializeFlowData.ts: -------------------------------------------------------------------------------- 1 | // jsonData 类型: {nodes:Node[], edges:Edge[]} 的简化版 (无position等属性) 2 | export function serializeFlowData (jsonType: string, jsonData: any): {code: number, msg: string, data: string} { 3 | if (jsonType == "nodeflow-item") { 4 | return serializeFlowData_item(jsonData) 5 | } 6 | else if (jsonType == "nodeflow-listitem") { 7 | return serializeFlowData_listitem(jsonData) 8 | } 9 | // else if (jsonType == "nodeflow-list") { 10 | // return serializeFlowData_listitem(jsonData) // listitem向下兼容list,这里用listitem的序列化程序就行了 (并不,某个版本开始listitem根部用items代替inputs和outputs) 11 | // } 12 | return { code: -1, msg: "No supported serialization in this jsonType: " + jsonType, data: ""} 13 | } 14 | 15 | function serializeFlowData_item(jsonData: any) { 16 | // 要点:将nodes的type和valueType去掉 17 | const newJsonData: any = { nodes: [], edges: [] } 18 | for (let node of jsonData.nodes) { 19 | newJsonData.nodes.push({ 20 | id: node.id, 21 | data: node.data 22 | }) 23 | } 24 | newJsonData.edges = jsonData.edges 25 | return { code: 0, msg: "", data: JSON.stringify(newJsonData, null, 2) } 26 | } 27 | 28 | // 暂时不支持嵌套nodeitem 29 | // 为了简化,默认值会忽略掉 30 | function serializeFlowData_listitem(jsonData: any) { 31 | let newText = "" 32 | newText += "- nodes\n" 33 | // Node 34 | for (let node of jsonData.nodes) { 35 | newText += ` - ${node.id}${node.id==node.data.label?'':':'+node.data.label}\n` 36 | // Handle 37 | for (let socket of node.data.items) { 38 | newText += ` - ${socket.id}${socket.id==socket.name?'':':'+socket.name}, \ 39 | ${(socket.refType=='v'||socket.refType=='value')?'':socket.refType}${socket.valueType=='item-string'?'':':'+socket.valueType}\ 40 | ${(socket.value.trim()=='')?'':', '+socket.value}\n` 41 | } 42 | } 43 | newText += "- edges\n" 44 | // Edge 45 | for (let edge of jsonData.edges) { 46 | newText += ` - ${edge.source}, ${edge.sourceHandle}, ${edge.target}, ${edge.targetHandle}\n` 47 | } 48 | 49 | return { code: 0, msg: "", data: newText } 50 | } 51 | -------------------------------------------------------------------------------- /src/NodeFlow/component/nodeItem/ItemNodeSlot.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 36 | 37 | 55 | -------------------------------------------------------------------------------- /src/NodeFlowObsidian/esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import esbuild from "esbuild"; 2 | import process from "process"; 3 | import builtins from 'builtin-modules'; 4 | import Vue from "@the_tree/esbuild-plugin-vue3"; // esbuild编译vue文件 5 | // 说是直接支持sass,但是我vue用sass报错: Cannot read properties of undefined (reading 'replace') [plugin vue] 6 | 7 | const banner = 8 | `/* 9 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 10 | if you want to view the source, please visit the github repository of this plugin 11 | */ 12 | `; 13 | 14 | const prod = (process.argv[2] === 'production'); 15 | process.env.NODE_ENV = 'production'; 16 | 17 | // ts 18 | await esbuild.build({ 19 | banner: { 20 | js: banner, 21 | }, 22 | plugins: [ 23 | Vue({ isProd: true }), 24 | ], 25 | entryPoints: ['./main.ts'], 26 | outfile: '../../main.js', 27 | bundle: true, 28 | format: 'cjs', 29 | watch: !prod, // 似乎若升级esbuild后不再支持 30 | target: 'es2016', 31 | logLevel: "info", 32 | sourcemap: prod ? false : 'inline', 33 | minify: prod ? true : false, 34 | treeShaking: true, 35 | external: [ 36 | 'obsidian', 37 | 'electron', 38 | '@codemirror/autocomplete', 39 | '@codemirror/closebrackets', 40 | '@codemirror/collab', 41 | '@codemirror/commands', 42 | '@codemirror/comment', 43 | '@codemirror/fold', 44 | '@codemirror/gutter', 45 | '@codemirror/highlight', 46 | '@codemirror/history', 47 | '@codemirror/language', 48 | '@codemirror/lint', 49 | '@codemirror/matchbrackets', 50 | '@codemirror/panel', 51 | '@codemirror/rangeset', 52 | '@codemirror/rectangular-selection', 53 | '@codemirror/search', 54 | '@codemirror/state', 55 | '@codemirror/stream-parser', 56 | '@codemirror/text', 57 | '@codemirror/tooltip', 58 | '@codemirror/view', 59 | ...builtins], 60 | }).catch(() => process.exit(1)); 61 | 62 | // css 63 | await esbuild.build({ 64 | entryPoints: ["./main.css"], 65 | outfile: "../../styles.css", 66 | watch: !prod, // 似乎若升级esbuild后不再支持 67 | bundle: true, 68 | allowOverwrite: true, 69 | minify: false, 70 | }); 71 | -------------------------------------------------------------------------------- /src/NodeFlow/component/nodeItem/flow/FlowEvalItem.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 25 | 26 | 58 | 59 | 82 | -------------------------------------------------------------------------------- /src/NodeFlow/component/utils/dropdownButton.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 30 | 31 | 74 | -------------------------------------------------------------------------------- /src/NodeFlow/component/node/ObcanvasNode.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 41 | 42 | 93 | -------------------------------------------------------------------------------- /src/NodeFlow/utils/jsonTool/factoryFlowData_obcanvas.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * obsidian canvas数据转通用节点流数据 3 | */ 4 | export function factoryFlowData_obcanvas(parsedData:any|string): {code: number, msg: string, data: object} { 5 | // 使用demo数据 6 | if (typeof parsedData === "string") { 7 | if (parsedData == "demo") { parsedData = JSON.parse(JSON.stringify(testData_obcanvas)) } 8 | else { return {code: -1, msg: "error demo: "+parsedData, data: {}} } 9 | } 10 | 11 | try { 12 | let nodes_new: object[] = [] 13 | const nodes = parsedData.nodes; 14 | nodes.forEach((item:any) => { 15 | nodes_new.push({ 16 | // 数据转移: 17 | id: item.id, 18 | position: { x: item.x, y: item.y }, 19 | ...(item.width ? {width: item.width+'px'} : {}), 20 | ...(item.height ? {height: item.height+'px'} : {}), // 通常只有group类型有高度 21 | data: { 22 | label: (item.hasOwnProperty("text")) ? item.text : // type: text 23 | (item.hasOwnProperty("file")) ? item.file : // type: 24 | (item.hasOwnProperty("label")) ? item.label : // type: group 25 | "Error Type: " + item.type, 26 | type: item.type, 27 | }, 28 | // 数据新增: 29 | type: "obcanvas", 30 | ...(item.type=='group' ? {style: {zIndex: '-1'}}: {}), 31 | }); 32 | }) 33 | 34 | let edges_new: object[] = [] 35 | const edges = parsedData.edges; 36 | edges.forEach((item:any) => { 37 | edges_new.push({ 38 | // 数据转移: 39 | id: item.id, 40 | source: item.fromNode, 41 | target: item.toNode, 42 | sourceHandle: item.fromSide, 43 | targetHandle: item.toSide, 44 | // 数据新增: 45 | // type == "default" 46 | markerEnd: 'arrowclosed', 47 | }); 48 | }) 49 | 50 | return { code: 0, msg: "", data: {nodes: nodes_new, edges: edges_new}} 51 | } catch (error) { 52 | return {code: -1, msg: "error: obcanvas json parse fail: "+error, data: {}} 53 | } 54 | } 55 | 56 | export const testData_obcanvas = { 57 | "nodes":[ 58 | {"id":"d1acdb5136ffb1f1","x":25,"y":70,"width":250,"height":60,"type":"text","text":"## Title\n\n**Test** *1*\n"}, 59 | {"id":"f7dc36d69da1bb36","x":330,"y":70,"width":250,"height":60,"type":"text","text":"~~Test~~ ==2==\n"} 60 | ], 61 | "edges":[ 62 | {"id":"fc3f1bc43902aac9","fromNode":"d1acdb5136ffb1f1","fromSide":"right","toNode":"f7dc36d69da1bb36","toSide":"left"} 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /src/NodeFlow/component/container/fullScreen.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from "vue"; 2 | 3 | /** 4 | * 将div放大到全屏,并自动处理退出全屏事件 5 | * 6 | * 需要特别注意闭包问题。 7 | * 我们可以通过闭包,构建多个函数实体,每个函数有不同的isMini实体! 当切换关闭全屏状态时能准确通知关闭全屏的div组件 8 | * (后来优化了一下,好像不需要,普通传参给监听器就行) 9 | * 10 | * @param div HTMLElement 需要在normal-size和full-size两个类之间切换,请自行 `:class="isMini?'normal-size':'full-size'"` 11 | */ 12 | export function switchFullScreen(div: any, isMini: Ref): Promise { 13 | return new Promise((resolve, reject) => { 14 | if (!div) { return } 15 | // 如果是正常大小,切换到全尺寸 16 | if (isMini.value) { 17 | isMini.value = false; 18 | // document.body.style.overflow = 'hidden'; // 禁用滚动 19 | if (div.requestFullscreen) { 20 | div.requestFullscreen(); 21 | } else if (div.webkitRequestFullScreen) { 22 | div.webkitRequestFullScreen(); 23 | } else if (div.mozRequestFullScreen) { 24 | div.mozRequestFullScreen(); 25 | } else if (div.msRequestFullscreen) { 26 | div.msRequestFullscreen(); // IE11 27 | } 28 | initListener(isMini) 29 | } 30 | // 如果已经是全尺寸,切换回正常大小 31 | else { 32 | isMini.value = true; 33 | // document.body.style.overflow = ''; // 恢复滚动 34 | if (document.exitFullscreen) { 35 | document.exitFullscreen(); 36 | } else if (div.webkitCancelFullScreen) { 37 | div.webkitCancelFullScreen(); 38 | } else if (div.mozCancelFullScreen) { 39 | div.mozCancelFullScreen(); 40 | } else if (div.msExitFullscreen) { 41 | div.msExitFullscreen(); 42 | } 43 | } 44 | }) 45 | } 46 | 47 | /** 48 | * 添加临时监听器,监听由F11或Esc等方式退出全屏状态的事件,监听成功后取消监听 49 | */ 50 | function initListener (isMini: Ref) { 51 | document.addEventListener('fullscreenchange', handleFullScreenChange); 52 | document.addEventListener('webkitfullscreenchange', handleFullScreenChange); 53 | document.addEventListener('mozfullscreenchange', handleFullScreenChange); 54 | document.addEventListener('MSFullscreenChange', handleFullScreenChange); 55 | function handleFullScreenChange() { 56 | if (document.fullscreenElement) return 57 | // 成功退出全屏状态 58 | isMini.value = true; 59 | document.removeEventListener('fullscreenchange', handleFullScreenChange) 60 | document.removeEventListener('webkitfullscreenchange', handleFullScreenChange); 61 | document.removeEventListener('mozfullscreenchange', handleFullScreenChange); 62 | document.removeEventListener('MSFullscreenChange', handleFullScreenChange); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/EditableBlockApp/src/components/MarkdownCodeMirror2.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 75 | 76 | 81 | 82 | 88 | -------------------------------------------------------------------------------- /src/NodeFlowVuepress/README.md: -------------------------------------------------------------------------------- 1 | # NodeFlowVuePress的编译、安装、使用 2 | 3 | ## VuePress安装插件 (源码版) 4 | 5 | 和一般的VuePress插件使用方法一样: 6 | 7 | 1. 复制NodeFlowPlugin文件夹到上述目录 8 | 2. 和使用普通插件一样,配置插件,在config.ts 中添加/修改 plugins 字段 9 | `import vueflowPlugin from "./plugin/VueFlowPlugin"` 并在plugins列表添加 `vueflowPlugin` 10 | (准确路径:src/.vuepress/plugin/VueFlowPlugin/,路径可以自己改) 11 | 3. 和使用普通mdit插件一样,使用里面的 `index_mdit.ts` 12 | 13 | ## VuePress支持直接识别工作流 `.json` 文件的方法 14 | 15 | 在config.ts中添加以下内容: 16 | 17 | ```ts 18 | // @file /src/.vuepress/config.ts 19 | export default defineUserConfig({ 20 | pagePatterns: ["**/*.md", "**/*.json", "!**/*.snippet.md", "!.vuepress", "!node_modules"], // "**/*.pdf" 21 | 22 | // ------------------ 扩展类 ------------------ 23 | theme, 24 | alias, 25 | extendsMarkdown, 26 | plugins, 27 | 28 | // ------------------ 扩展类 - 钩子 ------------ 29 | async onInitialized(app) { 30 | /** 31 | * 对.json后缀进行处理 (需要先设置pagePatterns允许解析json,否则这里遍历不到json文件) 32 | * 这里编辑对应的page信息,视情况甚至可以createPage替换、新增、去除 33 | */ 34 | for (const page of app.pages) { 35 | if (!page.path.endsWith(".json")) continue 36 | { 37 | page.path = page.path+"/" 38 | page.frontmatter.layout = 'Layout' 39 | page.content = "```nodeflow-comfyui\n" + page.content + "\n```" 40 | if(page.sfcBlocks.template?.contentStripped) page.sfcBlocks.template.contentStripped = // HTML内容以这个为准 41 | app.markdown.render(page.content) // 重新渲染该页 42 | } 43 | } 44 | }, 45 | }); 46 | ``` 47 | 48 | ### 依赖问题 49 | 50 | 你在安装的过程中,可能相关依赖未集成到插件中,需要手动安装 51 | 52 | ```bash 53 | npm install @vue-flow/core 54 | npm install @vue-flow/background 55 | npm install @dagrejs/dagre 56 | ``` 57 | 58 | (备注: `--registry www.mirrornpm.com`) 59 | 60 | ### 开发人员补充 61 | 62 | 如果你想创建自己的插件,和前面的 `VuePress使用` 步骤相似,只不过复制Vue组件到对应的目录 (例如 `./plugin/VueFlowPlugin/NodeFlow`) 63 | 64 | 然后并添加一些插件声明的东西。在 `./plugin/VueFlowPlugin` 文件夹下创建 `index.ts` 和 `clientConfig.ts` 文件,并添加内容: 65 | 66 | index.ts 负责声明插件的id 67 | 68 | ```ts 69 | import { getDirname, path } from "@vuepress/utils" 70 | 71 | export default (options, ctx) => { 72 | return { 73 | name: 'vuepress-plugin-vue-flow', 74 | clientConfigFile: path.resolve(__dirname, 'clientConfig.ts'), 75 | } 76 | } 77 | ``` 78 | 79 | config_plugins.ts 负责描述插件的行为,这里描述为该插件用于声明一个全局的vue组件变量 80 | 81 | ```ts 82 | import { defineClientConfig } from 'vuepress/client'; 83 | 84 | import MyVueFlow from "./MyVueFlow.vue"; 85 | 86 | export default defineClientConfig({ 87 | enhance: ({ app, router, siteData }) => { 88 | app.component("VueFlow", MyVueFlow); 89 | }, 90 | }) 91 | ``` 92 | -------------------------------------------------------------------------------- /src/EditableBlockApp/src/codemirrorAdapt/EditableBlock_Code_Widget.ts: -------------------------------------------------------------------------------- 1 | import { 2 | EditorView, 3 | WidgetType, // 装饰器部件 4 | } from '@codemirror/view'; 5 | import { 6 | EditorSelection, 7 | EditorState, 8 | Extension, 9 | } from '@codemirror/state'; 10 | 11 | import { EditableBlock_Cm } from "./EditableBlock_Cm" 12 | import { global_store } from './index_cm' 13 | 14 | /// 自定义CM的装饰器部件 - 代码块 15 | export class CodeblockWidget extends WidgetType { 16 | state: EditorState; 17 | oldView: EditorView; 18 | fromPos: number; // TODO 未能动态更新 19 | toPos: number; 20 | updateContent_all: (newContent: string) => void; // 更新所有 21 | updateContent_local: (newContent: string) => void; // 仅更新 22 | focusLine: number|null = null; // 是否生成后自动聚焦及聚焦位置。由于toDOM时机后缀,所以用这个来控制 23 | 24 | constructor( 25 | state: EditorState, 26 | oldView: EditorView, 27 | readonly content_local_sub: string, 28 | readonly lang: string, 29 | fromPos: number, 30 | toPos: number, 31 | updateContent_all: (newContent: string) => void, 32 | focusLine: number|null = null, 33 | ) { 34 | super() 35 | this.state = state; 36 | this.oldView = oldView; 37 | this.fromPos = fromPos; 38 | this.toPos = toPos; 39 | this.focusLine = focusLine; 40 | // 注意: all是全文,local是影响部分,sub是影响部分再去除代码围栏前后缀的部分 41 | const content_all: string = state.doc.toString(); 42 | 43 | // content_local 44 | this.updateContent_all = updateContent_all 45 | this.updateContent_local = (newContent_local: string) => { // TODO 这里有bug:codeblock mark 标志可能不是 "```" 46 | const before = content_all.substring(0, fromPos); 47 | const after = content_all.substring(toPos); 48 | const langMatch = content_all.substring(fromPos).match(/^```(\w+)?\n/); 49 | const lang = langMatch ? langMatch[1] || '' : ''; 50 | const newContent_all = `${before}\`\`\`${lang}\n${newContent_local}\n\`\`\`${after}`; 51 | this.updateContent_all(newContent_all) 52 | } 53 | } 54 | 55 | toDOM(view: EditorView): HTMLElement { 56 | const container = document.createElement('div'); container.className = 'editable-codeblock-p'; 57 | 58 | // 创建您的 EditableCodeblock 组件,自带光标位置恢复逻辑 59 | const editableCodeblock = new EditableBlock_Cm( 60 | this.state, 61 | this.oldView, 62 | this.fromPos, 63 | this.toPos, 64 | this.lang, 65 | this.content_local_sub, 66 | container, 67 | this.updateContent_local 68 | ) 69 | editableCodeblock.render().then(() => { 70 | if (this.focusLine != null) editableCodeblock.focus(this.focusLine == null ? undefined : this.focusLine) 71 | }) 72 | return container 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/EditableBlockApp/src/components/MarkdownViewer.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 41 | 42 | 53 | 54 | 106 | -------------------------------------------------------------------------------- /src/NodeFlowApp/src/App.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 69 | 70 | 71 | 72 | 73 | 74 | 86 | 87 | 96 | -------------------------------------------------------------------------------- /src/NodeFlowApp/src/components/NodeEditor.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 52 | 53 | 94 | -------------------------------------------------------------------------------- /src/NodeFlow/utils/jsonTool/factoryFlowData.ts: -------------------------------------------------------------------------------- 1 | import { factoryFlowData_vueflow } from "./factoryFlowData_vueflow" 2 | import { factoryFlowData_obcanvas } from "./factoryFlowData_obcanvas" 3 | import { factoryFlowData_comfyui } from "./factoryFlowData_comfyui" 4 | import { factoryFlowData_list } from "./factoryFlowData_list" 5 | import { factoryFlowData_item } from "./factoryFlowData_item" 6 | import { factoryFlowData_listitem } from "./factoryFlowData_listitem" 7 | 8 | /** 9 | * 解析并转化json,将各种类型的json转化为统一的vueflow形式 (统一的 {nodes:[],edges:[]} 格式) 10 | * 11 | * TODO 缺少Schema校验,提高稳定性 12 | * 13 | * @param jsonType 说明了第二个参数的结构类型 14 | * @param json 不一定是json,list版本的语法是纯文本 15 | */ 16 | export function factoryFlowData(jsonType:string = "nodeflow-vueflow", json:string = "{nodes:[],edges:[]}"): {code: number, msg: string, data: any} { 17 | // 统一检查,json是否合法 18 | let parsedData: object|string; 19 | if (json.trim()=="") { 20 | return {code: -1, msg: "error: content is empty", data: {}} 21 | } 22 | else if (json.startsWith("demo")) { 23 | parsedData = json 24 | } 25 | else if (jsonType === "nodeflow-list" || jsonType === "nodeflow-listitem") { 26 | parsedData = json 27 | } 28 | else { 29 | try { 30 | parsedData = JSON.parse(json) 31 | if (!parsedData) { return {code: -1, msg: "Error: not a legitimate json", data: {}} } 32 | } catch (error) { 33 | return {code: -1, msg: "Error: not a legitimate json: " + error, data: {}} 34 | } 35 | } 36 | 37 | // 类型分发 38 | let result: {code: number, msg: string, data: any}; 39 | if (jsonType == "nodeflow-comfyui") { 40 | result = factoryFlowData_comfyui(parsedData) 41 | } else if (jsonType=="nodeflow-obcanvas") { 42 | result = factoryFlowData_obcanvas(parsedData) 43 | } else if (jsonType == "nodeflow-vueflow") { 44 | result = factoryFlowData_vueflow(parsedData) 45 | } else if (jsonType == "nodeflow-list") { 46 | result = factoryFlowData_list(parsedData as string) 47 | } else if (jsonType == "nodeflow-item") { 48 | result = factoryFlowData_item(parsedData) 49 | } else if (jsonType == "nodeflow-listitem") { 50 | result = factoryFlowData_listitem(parsedData as string) 51 | } else { 52 | return {code: -1, msg: "error: invalid json type: " + jsonType, data: {}} 53 | } 54 | 55 | // 再次检查 56 | if (result.code != 0) return result 57 | if (!result.data.hasOwnProperty("nodes")) {return {code: -1, msg: "json without nodes attrs", data: {}}} 58 | if (!result.data.hasOwnProperty("edges")) { result.data.edges = [] } 59 | return result 60 | } 61 | 62 | // 用节点的方式来显示错误信息 63 | export function failedFlowData(msg: string): any { 64 | return { 65 | "data": { 66 | "nodes": [ 67 | { 68 | "id": "ERROR", 69 | "position": {"x": 0, "y": 0}, 70 | "type": "common", 71 | "data": { 72 | "label": "ERROR", 73 | "inputs": [], 74 | "outputs": [], 75 | "values": [ 76 | { "id": "0", "name": "", "value": msg } 77 | ], 78 | } 79 | }, 80 | ], 81 | "edges": [] 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/NodeFlow/component/nodeItem/base/StringItem.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 22 | 23 | 51 | 52 | 103 | -------------------------------------------------------------------------------- /src/NodeFlow/component/node/ComfyUINode.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 66 | 67 | 85 | 86 | 88 | -------------------------------------------------------------------------------- /.github/workflows/nodejs-build.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI/CD Pipeline # 工作流名 2 | 3 | on: # 触发器,定义何时运行此工作流 4 | push: 5 | branches: [master] # 默认分支名! 6 | pull_request: 7 | branches: [master] 8 | workflow_dispatch: # 手动执行 9 | 10 | jobs: 11 | build-obsidian: # 作业 - ob构建部分 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Env - Checkout 15 | uses: actions/checkout@v4 16 | - name: Install pnpm # 安装pnpm (如果你的 package.json::packageManager 字段没有设置, 17 | uses: pnpm/action-setup@v4 # 则要在这里加上 with 版本) 18 | with: 19 | version: 10.10.0 20 | - name: Env - Use Node.js # 环境 - node环境 (使用的是官方提供的action),Node.js版本 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: '22' 24 | cache: pnpm 25 | - name: Build 26 | run: | 27 | pnpm install --frozen-lockfile 28 | pnpm run ob:build 29 | - name: Upload Build Artifact 30 | if: always() # 即使之前的构建步骤失败,也会上载构建产物 31 | uses: actions/upload-artifact@v4 32 | with: 33 | name: build-artifact # 构建产物的名称 34 | path: | # 构建产物的路径 35 | main.js 36 | styles.css 37 | manifest.json 38 | 39 | build-app: # 作业 - NodeFlow App 构建部分 40 | runs-on: ubuntu-latest 41 | steps: 42 | - name: Env - Checkout 43 | uses: actions/checkout@v4 44 | - name: Install pnpm 45 | uses: pnpm/action-setup@v4 46 | with: 47 | version: 10.10.0 48 | - name: Env - Use Node.js 49 | uses: actions/setup-node@v4 50 | with: 51 | node-version: '22' 52 | cache: pnpm 53 | - name: Build 54 | run: | 55 | # pnpm run app:build 56 | pnpm install --frozen-lockfile 57 | cd ./src/NodeFlowApp/ 58 | pnpm build 59 | > ./dist/.nojekyll 60 | - name: Build - Dist to Website Branch 1 61 | uses: JamesIves/github-pages-deploy-action@v4 62 | with: 63 | # 这是文档部署到的分支名称 64 | branch: gh-pages 65 | folder: src/NodeFlowApp/dist 66 | 67 | build-app2: # 作业 - EditableCodeblock App 构建部分 68 | runs-on: ubuntu-latest 69 | needs: build-app # 等待 build-app,避免部署冲突 70 | steps: 71 | - name: Env - Checkout 72 | uses: actions/checkout@v4 73 | - name: Install pnpm 74 | uses: pnpm/action-setup@v4 75 | with: 76 | version: 10.10.0 77 | - name: Env - Use Node.js 78 | uses: actions/setup-node@v4 79 | with: 80 | node-version: '22' 81 | cache: pnpm 82 | - name: Build 83 | run: | 84 | # pnpm run app:build 85 | pnpm install --frozen-lockfile 86 | cd ./src/EditableBlockApp/ 87 | pnpm build 88 | > ./dist/.nojekyll 89 | mv ./dist/index.html ./dist/EditableCodeblock.html 90 | # 注意和 build-app 都是放在 gh-pages 分支 91 | # 两种方法 92 | # 一是部署到 gh-pages 的指定子目录,记得修改 base 配置 93 | # (选用) 二是部署到 gh-pages 根目录,修改修改 index.html 文件名 94 | - name: Build - Dist to Website Branch 2 95 | uses: JamesIves/github-pages-deploy-action@v4 96 | with: 97 | branch: gh-pages 98 | folder: src/EditableBlockApp/dist 99 | # destination-folder: EditableCodeblock 100 | clean: false # 不清理目标目录 101 | -------------------------------------------------------------------------------- /src/NodeFlow/utils/jsonTool/factoryFlowData_vueflow.ts: -------------------------------------------------------------------------------- 1 | export function factoryFlowData_vueflow(parsedData:any|string): {code: number, msg: string, data: object} { 2 | // 使用demo数据 3 | if (typeof parsedData === "string") { 4 | if (parsedData == "demo") { parsedData = JSON.parse(JSON.stringify(testData_vueflow)) } 5 | else if (parsedData == "demo2") { parsedData = JSON.parse(JSON.stringify(testData_vueflow_withoutPos)) } 6 | else if (parsedData == "demo3") { parsedData = JSON.parse(JSON.stringify(testData_vueflow_customNode)) } 7 | else { return {code: -1, msg: "error demo: "+parsedData, data: {}} } 8 | } 9 | 10 | for (let item of parsedData["nodes"]) { 11 | if (!item.hasOwnProperty("position")) { 12 | item.position = { x: 0, y: 0 } 13 | } 14 | if (!item.hasOwnProperty("data")) { 15 | item.data = { "label": "" } 16 | } 17 | if (!item.type) { 18 | item.type = 'process' 19 | } 20 | } 21 | return {code: 0, msg: "", data: parsedData} 22 | } 23 | 24 | export const testData_vueflow = { 25 | "nodes": [ 26 | {"id": "1", "type": "input", "position": {"x": 250, "y": 5}, "data": {"label": "Node 11"}}, 27 | {"id": "2", "position": {"x": 100, "y": 100}, "data": {"label": "Node 12"}}, 28 | {"id": "3", "type": "output", "position": {"x": 400, "y": 200}, "data": {"label": "Node 13"}}, 29 | {"id": "4", "type": "special", "position": {"x": 600, "y": 100}, "data": {"label": "Node 14", "hello": "world"}} 30 | ], 31 | "edges": [ 32 | {"id": "e1->2", "source": "1", "target": "2"}, 33 | {"id": "e2->3", "source": "2", "target": "3", "animated": true}, 34 | {"id": "e3->4", "type": "special", "source": "3", "target": "4", "data": {"hello": "world"}} 35 | ] 36 | } 37 | 38 | export const testData_vueflow_withoutPos = { 39 | nodes: [ 40 | { id: '1' }, 41 | { id: '2' }, 42 | { id: '2a' }, 43 | { id: '2b' }, 44 | { id: '2c' }, 45 | { id: '2d' }, 46 | { id: '3' }, 47 | { id: '4' }, 48 | { id: '5' }, 49 | { id: '6' }, 50 | { id: '7' } 51 | ], 52 | edges: [ 53 | { id: 'e1-2', source: '1', target: '2', type: 'animation', animated: true }, 54 | { id: 'e1-3', source: '1', target: '3', type: 'animation', animated: true }, 55 | { id: 'e2-2a', source: '2', target: '2a', type: 'animation', animated: true }, 56 | { id: 'e2-2b', source: '2', target: '2b', type: 'animation', animated: true }, 57 | { id: 'e2-2c', source: '2', target: '2c', type: 'animation', animated: true }, 58 | { id: 'e2c-2d', source: '2c', target: '2d', type: 'animation', animated: true }, 59 | { id: 'e3-7', source: '3', target: '4', type: 'animation', animated: true }, 60 | { id: 'e4-5', source: '4', target: '5', type: 'animation', animated: true }, 61 | { id: 'e5-6', source: '5', target: '6', type: 'animation', animated: true }, 62 | { id: 'e5-7', source: '5', target: '7', type: 'animation', animated: true } 63 | ] 64 | } 65 | 66 | export const testData_vueflow_customNode = { 67 | nodes: [ 68 | { 69 | id: '1', 70 | type: 'color-selector', 71 | data: { color: '#6F3381' }, 72 | position: { x: 0, y: 50 }, 73 | }, 74 | { 75 | id: '2', 76 | type: 'color-output', 77 | position: { x: 350, y: 114 }, 78 | targetPosition: 'left', 79 | } 80 | ], 81 | edges: [ 82 | { 83 | id: 'e1a-2', 84 | source: '1', 85 | sourceHandle: 'a', 86 | target: '2', 87 | animated: true, 88 | style: { 89 | stroke: '#6F3381', 90 | }, 91 | } 92 | ] 93 | } 94 | -------------------------------------------------------------------------------- /src/NodeFlow/utils/jsonTool/factoryFlowData_item.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 项节点数据 3 | * 4 | * 一些统一的检查校验 5 | */ 6 | export function factoryFlowData_item(parsedData:any|string): {code: number, msg: string, data: object} { 7 | // 使用demo数据 8 | if (typeof parsedData === "string") { 9 | if (parsedData.startsWith("demo")) { parsedData = JSON.parse(JSON.stringify(testData_itemData)) } 10 | else { return {code: -1, msg: "error demo: "+parsedData, data: {}} } 11 | } 12 | 13 | for (let item of parsedData["nodes"]) { 14 | if (!item.hasOwnProperty("position")) { 15 | item.position = { x: 0, y: 0 } 16 | } 17 | if (!item.hasOwnProperty("valueType") || item.valueType == "") { 18 | item.valueType = "item-string" 19 | } 20 | item.type = "item" 21 | } 22 | 23 | return { code: 0, msg: "", data: parsedData} 24 | } 25 | 26 | export const testData_itemData = { 27 | "nodes": [ 28 | { 29 | "id": "6", 30 | "data": { 31 | "label": "itemData testData", 32 | "valueType": "itemData valueType", 33 | // 可选(自动布局) "bounds": { "x": 373, "y": 47, "width": 422.84503173828125, "height": 164.31304931640625 }, 34 | "items": [ 35 | { 36 | "id": "01", 37 | "name": "null", 38 | "refType": "input", 39 | "valueType": "item-string", 40 | "value": "" 41 | }, 42 | { 43 | "id": "02", 44 | "name": "null", 45 | "refType": "output", 46 | "valueType": "item-string", 47 | "value": "" 48 | }, 49 | { 50 | "id": "03", 51 | "name": "color", 52 | "refType": "input", 53 | "valueType": "item-color", 54 | "value": "#2283fc" 55 | }, 56 | { 57 | "id": "1", 58 | "name": "number", 59 | "refType": "input", 60 | // 可选 "ref": "", 用于指向传递过来的节点id和位置? 61 | "valueType": "item-string", 62 | "value": "123" 63 | // 可选 "widgetType": "" 64 | }, 65 | { 66 | "id": "2", 67 | "name": "number", 68 | "refType": "output", 69 | "valueType": "item-string", 70 | "value": "456" 71 | }, 72 | { 73 | "id": "3", 74 | "name": "vName", 75 | "refType": "value", 76 | "valueType": "item-string", 77 | "value": "vValue" 78 | }, 79 | { 80 | "id": "4", 81 | "name": "clip", 82 | "refType": "input", 83 | "valueType": "item-string", 84 | "value": "" 85 | }, 86 | { 87 | "id": "5", 88 | "name": "clip", 89 | "refType": "output", 90 | "valueType": "item-string", 91 | "value": "" 92 | }, 93 | { 94 | "id": "6", 95 | "name": "selectName", 96 | "refType": "value", 97 | "valueType": "item-enum", 98 | "value": "select1|select2|select3" 99 | }, 100 | { 101 | "id": "7", 102 | "name": "markdown", 103 | "refType": "value", 104 | "valueType": "item-markdown", 105 | "value": "## Markdown\n\n**bord** *italic* ==highlight== ~~del~~" 106 | } 107 | ] 108 | } 109 | } 110 | ] 111 | } 112 | -------------------------------------------------------------------------------- /src/EditableBlockApp/src/App.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 51 | 52 | 53 | 54 | 55 | 56 | 121 | 122 | 131 | -------------------------------------------------------------------------------- /src/EditableBlockApp/src/components/MarkdownCodeMirror.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 101 | 102 | 107 | 108 | 114 | -------------------------------------------------------------------------------- /src/EditableBlockApp/src/index_mdit.ts: -------------------------------------------------------------------------------- 1 | // 1. markdown-it 2 | import MarkdownIt from "markdown-it" 3 | 4 | // // 2. markdown-it-container 5 | // import MarkdownItConstructor from "markdown-it-container"; 6 | 7 | // 3. editable-codeblock 8 | import { EditableCodeblock, loadPrism2 } from "../../General/EditableBlock/EditableBlock_Code" 9 | import Prism from "prismjs" // 导入代码高亮插件的core(里面提供了其他官方插件及代码高亮样式主题,你只需要引入即可) 10 | import 'prismjs/components/prism-javascript'; 11 | import 'prismjs/components/prism-json'; 12 | import "prismjs/themes/prism-okaidia.min.css" // 主题, okaidia和tomorrow都是不错黑夜主题 13 | loadPrism2.fn = () => { 14 | return Prism 15 | } 16 | 17 | interface Options { 18 | multiline: boolean; 19 | rowspan: boolean; 20 | headerless: boolean; 21 | multibody: boolean; 22 | autolabel: boolean; 23 | } 24 | 25 | /** 26 | * 渲染 - codeBlock/fence 规则 27 | */ 28 | function render_fence(md: MarkdownIt, options?: Partial): void { 29 | const oldFence = md.renderer.rules.fence || function(tokens, idx, options, env, self) { 30 | return self.renderToken(tokens, idx, options); 31 | }; 32 | 33 | md.renderer.rules.fence = (tokens, idx, options, env, self) => { 34 | // 黑白名单机制:白名单为空标注允许所有规则,黑名单会减少白名单的规则 35 | const mdit_whitelist: string[] = ["js"] 36 | const mdit_blacklist: string[] = [] 37 | 38 | // 查看是否匹配 39 | const token = tokens[idx] 40 | const type = token.info.toLowerCase() 41 | if (mdit_whitelist.length && !mdit_whitelist.includes(type)) { // 检查白名单 42 | return oldFence(tokens, idx, options, env, self) 43 | } 44 | if (mdit_blacklist.length && mdit_blacklist.includes(type)) { // 检查黑名单 45 | return oldFence(tokens, idx, options, env, self) 46 | } 47 | const content = (token.content.endsWith('\n')) ? token.content.slice(0, -1) : token.content // 默认总是会有一个尾换行 48 | 49 | const div = document.createElement("div"); div.classList.add("editable-codeblock-ready"); 50 | div.setAttribute("data-type", type); div.setAttribute("data-content", content); // TODO 可能要编码 51 | let ret = div.outerHTML 52 | return ret 53 | } 54 | } 55 | 56 | // Markdown-it 有一个规则系统,您可以在渲染后添加一个规则 57 | function on_finish(md: MarkdownIt, options?: Partial): void { 58 | md.core.ruler.push('process-fence-blocks', (state: any) => { 59 | // 在整个文档渲染完成后执行 60 | window.requestAnimationFrame(() => { 61 | document.querySelectorAll(".editable-codeblock-ready").forEach(el => { 62 | const type = el.getAttribute("data-type") || "js" 63 | const content = el.getAttribute("data-content") || "" 64 | 65 | el.classList.remove("editable-codeblock-ready"); el.classList.add("editable-codeblock-over", "editable-codeblock-p") 66 | el.removeAttribute("data-type") 67 | el.removeAttribute("data-content") 68 | 69 | const editableCodeblock = new EditableCodeblock(type, content, el as HTMLElement) 70 | editableCodeblock.settings.renderEngine = "prismjs" 71 | editableCodeblock.settings.saveMode = 'oninput' 72 | editableCodeblock.settings.renderMode = 'textarea' // 'editablePre' 可选 73 | // editableCodeblock.settings.renderMode = 'editablePre' 74 | editableCodeblock.render() 75 | }) 76 | }); 77 | return true; 78 | }); 79 | } 80 | 81 | export default function ab_mdit(md: MarkdownIt, options?: Partial): void { 82 | // // 定义默认渲染行为 83 | // ABConvertManager.getInstance().redefine_renderMarkdown((markdown: string, el: HTMLElement): void => { 84 | // el.classList.add("markdown-rendered") 85 | 86 | // const result: string = md.render(markdown) 87 | // const el_child = document.createElement("div"); el.appendChild(el_child); el_child.innerHTML = result; 88 | // }) 89 | 90 | // // 定义环境条件 91 | // ABCSetting.env = "app" 92 | 93 | // md.use(abSelector_squareInline) 94 | // md.use(abSelector_container) 95 | md.use(render_fence) 96 | md.use(on_finish) 97 | } 98 | -------------------------------------------------------------------------------- /src/NodeFlow/component/nodeItem/view/MarkdownItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 57 | 58 | 63 | 64 | 121 | -------------------------------------------------------------------------------- /src/NodeFlowObsidian/README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | ## Obsidian版本的安装 4 | 5 | - 正常在社区商店下载 6 | - 正常在Github的该仓库下下载Release,并手动安装 7 | - 通过 BRAT 等方式安装 8 | 9 | ## Obsidian版本的使用方式 10 | 11 | 详见仓库README 12 | 13 | ## Obsidian版本的编译 14 | 15 | ```bash 16 | pnpm install 17 | 18 | pnpm run ob:build # 使用 esbuild + @the_tree/esbuild-plugin-vue3。可以处理vue,但无法处理vue中的scss。bug: https://github.com/pipe01/esbuild-plugin-vue3/issues/30 19 | 20 | # 或 21 | 22 | pnpm run ob:build2 # 使用 vite + @vitejs/plugin-vue + sass-embedded,可以处理vue,及vue里的scss 23 | ``` 24 | 25 | ## 项目创建模板 26 | 27 | 1. generated 28 | 29 | generated from [guopenghui/obsidian-vue-starter](https://github.com/guopenghui/obsidian-vue-starter) 30 | 31 | 但他这个依赖很旧,编译不稳定,我给改了下,见修改历史:22c2a9c2ad9eac8e0ce1abfb0b4484358eb0e28b 32 | 33 | 然后尝试安装依赖和构建,并在obsidian中查看是否能正常使用 34 | 35 | 2. vue file 36 | 37 | 见修改历史:22c2a9c2ad9eac8e0ce1abfb0b4484358eb0e28b 的下一次commit 38 | 39 | vue file: VueTest.vue 40 | 41 | ```vue 42 | 45 | 46 | 48 | 49 | 54 | ``` 55 | 56 | 3. 在主程序中使用 Vue UI 57 | 58 | main.ts 59 | 60 | ```ts 61 | import type {MarkdownPostProcessorContext} from "obsidian" 62 | import { factoryVueDom } from './vueAdapt' 63 | ... 64 | this.registerMarkdownCodeBlockProcessor("vue-test", 65 | ( 66 | src: string, // 代码块内容 67 | blockEl: HTMLElement, // 代码块渲染的元素 68 | ctx: MarkdownPostProcessorContext // 上下文 69 | ) => { 70 | const root_div = document.createElement("div"); blockEl.appendChild(root_div); root_div.classList.add("vue-shell"); 71 | factoryVueDom(root_div, "vue-test") 72 | } 73 | ) 74 | ``` 75 | 76 | vueAdapt.ts 77 | 78 | ```ts 79 | import { createApp, App as VueApp } from 'vue'; 80 | import VueTest from './component/VueTest.vue'; 81 | 82 | // 在div内创建指定的 Vue UI 83 | export function factoryVueDom(div:HTMLElement, vueUI:string = "vue-test"):void { 84 | const _app = createApp(VueTest, {}); 85 | _app.mount(div); 86 | } 87 | ``` 88 | 89 | 4. ~~原模版不支持sass,需要额外安装点东西~~ 90 | 91 | - 这里我们使用的是esbuild (webpack或其他打包器的的做法有所不同,具体自己查) 92 | - esbuild-sass-plugin 是对的,esbuild-plugin-sass 是错的,注意区分。见 https://www.npmjs.com/package/esbuild-sass-plugin,里面有写具体用法 93 | - `npm i sass sass-loader -D` 是对的,`sass` 换成 `node-sass` 是错的(旧版) 94 | - 坑:我重新安装所有依赖发现的: 95 | ```bash 96 | peerDependencies WARNING esbuild-sass-plugin@^3.3.1 requires a peer of esbuild@>=0.20.1 but H:\Git\Private\Group_FrontEnd\obsidian-node-flow\node_modules\esbuild was installed at esbuild@0.14.54, packageDir: H:\Git\Private\Group_FrontEnd\obsidian-node-flow\node_modules\.store\esbuild-sass-plugin@3.3.1\node_modules\esbuild-sass-plugin 97 | peerDependencies WARNING esbuild-sass-plugin@^3.3.1 requires a peer of sass-embedded@^1.71.1 but none was installed, packageDir: H:\Git\Private\Group_FrontEnd\obsidian-node-flow\node_modules\.store\esbuild-sass-plugin@3.3.1\node_modules\esbuild-sass-plugin 98 | ``` 99 | 升级一个包,然后安装一个包 100 | - 最后我还是没有成功,似乎是esbuild-plugin-vue3的问题,见:https://github.com/pipe01/esbuild-plugin-vue3/issues/30 101 | 最后我选择了使用VSCode插件进行编译,并且不在vue内使用scss 102 | 103 | ## 注意要项 104 | 105 | 注意:VueFlow官网的Examples中的Vue代码,都需要在script标签中标注 `lang="ts"` 106 | 107 | 否则报错: 108 | 109 | ```bash 110 | X [ERROR] [plugin vue] Fail to resolve script type in H:\Git\Private\Group_FrontEnd\obsidian-node-flow\src\component\utils\InteractionControls.vue?type=script 111 | 112 | node_modules/.store/@the_tree+esbuild-plugin-vue3@0.3.1/node_modules/@the_tree/esbuild-plugin-vue3/dist/index.js:301:54: 113 | 301 │ throw new Error("Fail to resolve script type in ".concat(args.path)); 114 | ``` 115 | 116 | 意思是无法解析到vue文件里的script标签 117 | 118 | 或者修改tsconfig.json文件,中添加js支持应该也行,但我现在想强制整个项目都要使用ts,就没这样做 119 | -------------------------------------------------------------------------------- /src/NodeFlow/component/nodeItem/feat/FeatItem.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 20 | 21 | 87 | 88 | 125 | -------------------------------------------------------------------------------- /src/NodeFlow/component/node/CommonNode.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 91 | 92 | 110 | -------------------------------------------------------------------------------- /src/NodeFlow/component/nodeItem/flow/FlowReqItem.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | 12 | 91 | 92 | 115 | -------------------------------------------------------------------------------- /src/General/EditableBlock/README.zh.md: -------------------------------------------------------------------------------- 1 | # 可编辑块 2 | 3 | ## 介绍 4 | 5 | 使指定html元素成为一个高级的可编辑块,并支持许多有用特性。 6 | 7 | 许多特性是普通的 `div[contenteditable=true]` 或 `textarea` 元素做不到的 8 | 9 | ### 功能 10 | 11 | 包括但不限于: (一些在TODO中) 12 | 13 | - 基本编辑功能 14 | - Input | 输入法: 支持中文输入法、输入组合 15 | - Options | 渲染方式、保存方式、代码高亮引擎、缩进风格等 16 | - Style | 黑暗模式、取消拼写检查 17 | - Adapt | 自适应: 高宽、换行、无需手动尺寸。自动判断单行/多行模式,使用不同样式,优化 18 | - [] Shortcut | 快捷键/按键: Tab、Shift Tab、Ctrl + z、方向键切换编辑区 19 | [] 历史栈 | textare支持撤回,但pre-code不支持,缩进等操作也暂不支持撤回 20 | - 代码内容 21 | - Hightlight | 代码高亮与编辑,支持Shiki和Prismjs引擎 22 | - Markdwon内容 23 | - Hightlight | Markdown高亮与编辑,支持CodeMirror引擎 24 | - 其他内容 25 | - obsidian editor | 仅当为obsidian环境可用,直接hack ob的内嵌编辑器 26 | - [] auto color | 文本反色功能 (用于颜色框) 27 | - 高级、嵌套 28 | - Nest | 嵌套: 可编辑器包含编辑器 29 | - Multi Block | 多个编辑器之间光标跳转、选区跨越 30 | - Save | 可作为内嵌编辑器,且将修改内容传输到上游 31 | - Extends | 支持扩展 32 | - [] Multi cursors| 多光标 33 | - [] Extend sytax | 扩展语法。如多行拼接: 使用 `\` 结尾再换行,可以优化显示 34 | - 自定义扩展 | 可以通过继承 `EditableBlock`,自定义扩展 35 | 36 | ### 应用 37 | 38 | 可编辑代码块、可编辑Markdown块 (codemirror) 39 | 40 | 或尽一步地作为Markdwon编辑区域中的可编辑引用块、可编辑列表等 41 | 42 | ## 使用 43 | 44 | 45 | 如需开启XX功能,pnpm isntall ? 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | ## 可编辑代码块 - 设置 55 | 56 | ### 渲染引擎 57 | 58 | Shiki, PrismJS,CodeMirror 59 | 60 | - Shiki: 一个强大的代码高亮引擎。 61 | - 功能更加强大,更多主题和插件 62 | - 插件: meta标注、注释型标注。行高亮、单词高亮、差异化标注、警告/错误标注 63 | - 主题:近80种配色方案:你可以在 https://textmate-grammars-themes.netlify.app 中可视化选择 64 | - *min版不包含该库,无法选用该引擎* 65 | - PrismJS: Obsidian默认在阅读模式中使用的渲染引擎。 66 | - 当选择这个的时候,你也可以选用min版本的本插件,拥有更小的插件体积和更快的加载速度 67 | - 可以与使用obsidian主题的代码配色,可以与一些其他的obsidian风格化插件配合 68 | - CodeMirror: Obsidian默认在实时模式中使用的渲染引擎。当前插件不支持 69 | - 适合实时渲染,性能尚可 70 | - 但代码分析比较粗糙,高亮层数少,效果较差 71 | 72 | ### 渲染方式 73 | 74 | - textarea (默认) 75 | - 优点: 76 | 允许实时编辑,typora般的所见即所得的体验 77 | 支持编辑注释型高亮 78 | 同为块内编辑的obsidian新版本md表格,采用的是这种方式 (但ob表格编辑时不触发重渲染) 79 | - 缺点: 80 | 原理上是将textarea和pre完美重叠在一起,但容易受主题和样式影响导致不完全重叠 81 | textarea的横向滚动无法与pre的同步 (修复: inline-block包括 inline-block + 100%width 的pre和textarea) 82 | - editable pre 83 | - 优点: 84 | 允许实时编辑,typora般的所见即所得的体验 85 | 原理上是 `code[contenteditable='true']` 86 | - 缺点: 87 | 程序上需要手动处理光标位置 88 | *不支持实时编辑注释型高亮* 89 | - pre 90 | - 缺点: 91 | 不允许实时编辑 92 | - codemirror 93 | - 缺点: 94 | V0.5.0及之前唯一支持的方式,不允许实时编辑 95 | 96 | > [!warning] 97 | > 98 | > 如果选用了可实时编辑的方案,最好能在仓库定期备份的情况下使用,避免意外 99 | 100 | ### 自动保存方式 101 | 102 | - onchange 103 | - 优点: 104 | 更好的性能 105 | 程序实现简单更简单,无需手动管理光标位置 106 | - 缺点: 107 | 延时保存,特殊场景可能不会保存修改: 程序突然崩溃。当光标在代码块中时,直接切换到阅读模式,或关闭当前窗口/标签页 108 | - oninput 109 | - 优点: 110 | 实时保存,数据更安全 111 | 同为块内编辑的obsidian新版本md表格,采用的是这种方式 112 | - 缺点: 113 | 性能略差? 每次修改都要重新创建代码块 114 | 程序需要手动管理光标位置,手动防抖。 115 | 需要注意输入法问题,输入候选阶段也会触发 `oninput` 116 | 117 | ### Shiki扩展语法 118 | 119 | 详见: https://shiki.style/packages/transformers (可切换至中文) 120 | 121 | 这是个简单的语法总结: 122 | 123 | - notaion 注释型标注 124 | - diff: `// [!code ++]` `// [!code --]` 差异化 125 | - highlight: `// [!code hl]` `// [!code highlight]` 高亮 126 | - word highlight: `// [!code word::]` `// [!code word:Hello:1]` 单词高亮 127 | - focus: `// [!code focus]` 聚焦 128 | - error level: `// [!code error]` `// [!code warning]` 警告/错误 129 | - (mul line): `// [!code highlight:3]` (多行) 130 | - meta 元数据型标注 131 | - highlight: `{1,3-4}` 132 | - word highlight `//` `/Hello/` 133 | 134 | 示例: see [../README.md](../README.md) or [Shiki document](https://shiki.style/packages/transformers) 135 | 136 | --- 137 | 138 | ## 一些开发杂项(不用翻译,仅自己看) 139 | 140 | ### 调研同类产品避免撞车 141 | 142 | - CodeMirror 143 | - 官网: https://codemirror.net/ 144 | - github: cm5开源,cm6不开 145 | - Monaco Editor (VSCode同款) 146 | - 官网/试用: https://microsoft.github.io/monaco-editor/ 147 | - github: https://github.com/microsoft/monaco-editor ⭐43.3k 148 | - Tiptap(富文本方向) 149 | - 适用于混合内容(文本+代码块) 150 | - Prism Editor 151 | - 只支持prism 152 | - github: https://github.com/mohammed-bahumaish/prisma-editor 153 | 154 | 比较表 155 | 156 | | 项目 | 核心优势 | 适用场景 | 157 | | ------------- | -------------------- | ----------------------------------- | 158 | | CodeMirror | 高度模块化、可定制性强、性能优异 | 需要深度整合和定制的代码编辑功能,作为应用的核心组件。 | 159 | | Monaco Editor | 功能强大、开箱即用、IDE级体验 | 需要一个完整的、功能丰富的代码编辑器,对体积不敏感。 | 160 | | Tiptap | 无头框架、UI完全自定义、富文本体验优秀 | 文档/笔记类应用,需要将代码块作为内容的一部分,对整体编辑体验要求高。 | 161 | 162 | 结论: 我想要支持更多的类型: 163 | obsidian editor、origin codemirror、txt、prism、shiki、color 164 | 只能自行开发了 165 | -------------------------------------------------------------------------------- /src/NodeFlowApp/src/components/JsonEditor.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 56 | 57 | 75 | 76 | 117 | -------------------------------------------------------------------------------- /src/NodeFlowApp/src/components/NodeList.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 42 | 43 | 124 | 125 | 175 | -------------------------------------------------------------------------------- /src/NodeFlow/component/node/ItemNode2.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 13 | 14 | 55 | 56 | 102 | -------------------------------------------------------------------------------- /src/NodeFlow/component/node/ItemNode.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | 23 | 65 | 66 | 112 | -------------------------------------------------------------------------------- /src/General/EditableBlock/README.md: -------------------------------------------------------------------------------- 1 | # EditableBlock 2 | 3 | ## Introduction 4 | 5 | Make the specified HTML element into a sophisticated editable block and support many useful features. 6 | 7 | Many features cannot be achieved by ordinary elements with `contenteditable=true` enabled. 8 | 9 | ### Feat 10 | 11 | Including but not limited to: (一些在TODO中) 12 | 13 | - 基本编辑功能 14 | - Input | 输入法: 支持中文输入法、输入组合 15 | - Options | 渲染方式、保存方式、代码高亮引擎、缩进风格等 16 | - Style | 黑暗模式、取消拼写检查 17 | - Adapt | 自适应: 高宽、换行、无需手动尺寸。自动判断单行/多行模式,使用不同样式,优化 18 | - [ ] Shortcut key | 按键: Tab、Shift Tab、Ctrl + z 19 | textare支持撤回,但pre-code不支持,缩进等操作也暂不支持撤回 20 | - 代码内容 21 | - Hightlight | 代码高亮与编辑,支持Shiki和Prismjs引擎 22 | - Markdwon内容 23 | - Hightlight | Markdown高亮与编辑,支持CodeMirror引擎 24 | - 其他内容 25 | - [ ]auto color | 文本反色功能 (用于颜色框) 26 | - 高级、嵌套 27 | - Nest | 嵌套: 可编辑器包含编辑器 28 | - Save | 可作为内嵌编辑器,且将修改内容传输到上游 29 | - Extends | 支持扩展 30 | - [] Multi cursors| 多光标 31 | - [] Extend sytax | 扩展语法。如多行拼接: 使用 `\` 结尾再换行,可以优化显示 32 | 33 | ### Application scenarios 34 | 35 | Editable code blocks, editable Markdown blocks (codemirror) 36 | Or, further, it can be used as editable reference blocks, editable lists, etc. in the Markdown editing area. 37 | 38 | ## 使用 39 | 40 | ## Editable codeblock - Setting 41 | 42 | ### Rendering engine 43 | 44 | Shiki, PrismJS, CodeMirror 45 | 46 | - Shiki: A powerful code highlighting engine. 47 | - More powerful functions, more themes and plugins 48 | - Plugins: meta annotations, annotated annotations. Line highlighting, word highlighting, differentiated annotation, warning/error annotation 49 | - Theme: Nearly 80 color schemes: You can visually select them at https://textmate-grammars-themes.netlify.app 50 | - *The min version does not include this library and the engine cannot be selected* 51 | - PrismJS: The rendering engine that Obsidian uses by default in reading mode. 52 | - When choosing this one, you can also select the min version of this plugin, which has a smaller plugin size and a faster loading speed 53 | - It can be color-matched with code using obsidian themes and can be used in conjunction with some other obsidian stylization plugins 54 | - CodeMirror: Obsidian is the default rendering engine used in real-time mode. The current plugin is not supported 55 | - It is suitable for real-time rendering and has acceptable performance 56 | - However, the code analysis is rather rough, with a small number of highlighting layers and a poor effect 57 | 58 | ### Rendering method 59 | 60 | - textarea (default) 61 | - Advantage: 62 | Allows real-time editing and offers a Typora-like WYSIWYG experience 63 | Support editing annotation-type highlighting 64 | The new version of Obsidian's md table within block editing uses this approach. (However, the ob table editing does not trigger a re-rendering.) 65 | - Disadvantage: 66 | In principle, textarea and pre are perfectly overlapped together, but they are prone to incomplete overlap due to the influence of themes and styles 67 | The horizontal scrolling of the textarea cannot be synchronized with that of the pre. (fixed) 68 | - editable pre 69 | - Advantage: 70 | Allows real-time editing and offers a Typora-like WYSIWYG experience 71 | In principle, it is `code[contenteditable='true']` 72 | - Disadvantage: 73 | The cursor position needs to be handled manually in the program 74 | *No support editing annotation-type highlighting* 75 | - pre 76 | - Disadvantage: 77 | Real-time editing is not allowed. The rendering effect is more similar to the textarea method 78 | - codemirror 79 | - Disadvantage: 80 | The only supported method for V0.5.0 and earlier versions, which does not allow real-time editing 81 | 82 | > [!warning] 83 | > 84 | > If a real-time editable solution is chosen, it is best to use it when the warehouse is regularly backed up to avoid unexpected situations 85 | 86 | ### AutoSave method 87 | 88 | - onchange 89 | - Advantage: 90 | Great performance. 91 | There is no need to manage the cursor position manually 92 | - Disadvantage: 93 | Delay save, change will loss if: the program crashes suddenly. when cursor in codeblock, switch to readmode or close window/tab 94 | - oninput 95 | - Advantage: 96 | Save immediately, data is more secure. 97 | The new version of Obsidian's md table within block editing uses this approach. 98 | - Disadvantage: 99 | Worse performance? The code block needs to be recreated every time it is modified 100 | The cursor position needs to be handled manually. Debounce manually. 101 | It is necessary to pay attention to the input method issue. The `oninput` will also be triggered during the input candidate stage 102 | 103 | ### Shiki Extend Sytax 104 | 105 | see https://shiki.style/packages/transformers for detail 106 | 107 | This is a simple summary of grammar: 108 | 109 | - notaion 110 | - diff: `// [!code ++]` `// [!code --]` 111 | - highlight: `// [!code hl]` `// [!code highlight]` 112 | - word highlight: `// [!code word::]` `// [!code word:Hello:1]` 113 | - focus: `// [!code focus]` 114 | - error level: `// [!code error]` `// [!code warning]` 115 | - (mul line): `// [!code highlight:3]` 116 | - meta 117 | - highlight: `{1,3-4}` 118 | - word highlight `//` `/Hello/` 119 | 120 | example: see [../README.md](../README.md) or [Shiki document](https://shiki.style/packages/transformers) 121 | -------------------------------------------------------------------------------- /src/NodeFlow/utils/testData/ueData.js: -------------------------------------------------------------------------------- 1 | Begin Object 2 | Class=/Script/BlueprintGraph.K2Node_Tunnel Name="K2Node_Tunnel_0" 3 | bCanHaveOutputs=True 4 | NodePosX=-80 5 | NodePosY=192 6 | NodeGuid=4500150646970605E70A60A3C13D1FAA 7 | CustomProperties Pin ( 8 | PinId=98F7CAA84E2A27C812123FBDF41F03E1, 9 | PinName="BeginCheck", 10 | Direction="EGPD_Output", 11 | PinType.PinCategory="exec", 12 | PinType.PinSubCategory="", 13 | PinType.PinSubCategoryObject=None, 14 | PinType.PinSubCategoryMemberReference=(), 15 | PinType.PinValueType=(), 16 | PinType.ContainerType=None, 17 | PinType.bIsReference=False, 18 | PinType.bIsConst=False, 19 | PinType.bIsWeakPointer=False, 20 | PinType.bIsUObjectWrapper=False, 21 | PinType.bSerializeAsSinglePrecisionFloat=False, 22 | LinkedTo=( 23 | K2Node_IfThenElse_0 5D71C7E4443ED94B5AEDD4A6E9A4F787, 24 | ), 25 | PersistentGuid=00000000000000000000000000000000, 26 | bHidden=False, 27 | bNotConnectable=False, 28 | bDefaultValueIsReadOnly=False, 29 | bDefaultValueIsIgnored=False, 30 | bAdvancedView=False,bOrphanedPin=False, 31 | ) 32 | CustomProperties UserDefinedPin ( 33 | PinName="BeginCheck", 34 | PinType=(PinCategory="exec"), 35 | DesiredPinDirection=EGPD_Output 36 | ) 37 | End Object 38 | Begin Object 39 | Class=/Script/BlueprintGraph.K2Node_IfThenElse 40 | Name="K2Node_IfThenElse_0" 41 | NodePosX=128 42 | NodePosY=192 43 | NodeGuid=85E9DC52476273CDA995849E4CC9B0DA 44 | CustomProperties Pin ( 45 | PinId=5D71C7E4443ED94B5AEDD4A6E9A4F787, 46 | PinName="execute", 47 | PinType.PinCategory="exec", 48 | PinType.PinSubCategory="", 49 | PinType.PinSubCategoryObject=None, 50 | PinType.PinSubCategoryMemberReference=(), 51 | PinType.PinValueType=(), 52 | PinType.ContainerType=None, 53 | PinType.bIsReference=False, 54 | PinType.bIsConst=False, 55 | PinType.bIsWeakPointer=False, 56 | PinType.bIsUObjectWrapper=False, 57 | PinType.bSerializeAsSinglePrecisionFloat=False, 58 | LinkedTo=( 59 | K2Node_Tunnel_0 98F7CAA84E2A27C812123FBDF41F03E1, 60 | ), 61 | PersistentGuid=00000000000000000000000000000000, 62 | bHidden=False, 63 | bNotConnectable=False, 64 | bDefaultValueIsReadOnly=False, 65 | bDefaultValueIsIgnored=False, 66 | bAdvancedView=False, 67 | bOrphanedPin=False, 68 | ) 69 | CustomProperties Pin ( 70 | PinId=30D9E9634E4640AF8FFFAABD46197487, 71 | PinName="Condition", 72 | PinType.PinCategory="bool", 73 | PinType.PinSubCategory="", 74 | PinType.PinSubCategoryObject=None, 75 | PinType.PinSubCategoryMemberReference=(), 76 | PinType.PinValueType=(), 77 | PinType.Cont 78 | 79 | // 这里很奇怪,像是被截断了 80 | 81 | Begin Object 82 | Class=/Script/BlueprintGraph.K2Node_Tunnel 83 | Name="K2Node_Tunnel_0" 84 | bCanHaveOutputs=True 85 | NodePosX=-80 86 | NodePosY=192 87 | NodeGuid=4500150646970605E70A60A3C13D1FAA 88 | CustomProperties Pin ( 89 | PinId=98F7CAA84E2A27C812123FBDF41F03E1, 90 | PinName="BeginCheck", 91 | Direction="EGPD_Output", 92 | PinType.PinCategory="exec", 93 | PinType.PinSubCategory="", 94 | PinType.PinSubCategoryObject=None, 95 | PinType.PinSubCategoryMemberReference=(), 96 | PinType.PinValueType=(), 97 | PinType.ContainerType=None, 98 | PinType.bIsReference=False, 99 | PinType.bIsConst=False, 100 | PinType.bIsWeakPointer=False, 101 | PinType.bIsUObjectWrapper=False, 102 | PinType.bSerializeAsSinglePrecisionFloat=False, 103 | PersistentGuid=00000000000000000000000000000000, 104 | bHidden=False, 105 | bNotConnectable=False, 106 | bDefaultValueIsReadOnly=False, 107 | bDefaultValueIsIgnored=False, 108 | bAdvancedView=False, 109 | bOrphanedPin=False, 110 | ) 111 | CustomProperties UserDefinedPin ( 112 | PinName="BeginCheck", 113 | PinType=(PinCategory="exec"), 114 | DesiredPinDirection=EGPD_Output 115 | ) 116 | End Object 117 | Begin Object 118 | Class=/Script/BlueprintGraph.K2Node_Tunnel 119 | Name="K2Node_Tunnel_1" 120 | bCanHaveInputs=True 121 | NodePosX=512 122 | NodePosY=64 123 | NodeGuid=B25A99B147860ECA426757B2CFA92EE4 124 | CustomProperties Pin ( 125 | PinId=6D264E70477F620620962AA38813CEE1, 126 | PinName="HasEnergy", 127 | PinType.PinCategory="exec", 128 | PinType.PinSubCategory="", 129 | PinType.PinSubCategoryObject=None, 130 | PinType.PinSubCategoryMemberReference=(), 131 | PinType.PinValueType=(), 132 | PinType.ContainerType=None, 133 | PinType.bIsReference=False, 134 | PinType.bIsConst=False, 135 | PinType.bIsWeakPointer=False, 136 | PinType.bIsUObjectWrapper=False, 137 | PinType.bSerializeAsSinglePrecisionFloat=False, 138 | PersistentGuid=00000000000000000000000000000000, 139 | bHidden=False, 140 | bNotConnectable=False, 141 | bDefaultValueIsReadOnly=False, 142 | bDefaultValueIsIgnored=False, 143 | bAdvancedView=False, 144 | bOrphanedPin=False, 145 | ) 146 | CustomProperties Pin ( 147 | PinId=F9B1BBB04D96332C2C709AAF8E579689, 148 | PinName="NoEnergy", 149 | PinType.PinCategory="exec", 150 | PinType.PinSubCategory="", 151 | PinType.PinSubCategoryObject=None, 152 | PinType.PinSubCategoryMemberReference=(), 153 | PinType.PinValueType=(), 154 | PinType.ContainerType=None, 155 | PinType.bIsReference=False, 156 | PinType.bIsConst=False, 157 | PinType.bIsWeakPointer=False, 158 | PinType.bIsUObjectWr 159 | 160 | // 这里很奇怪,像是被截断了 161 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NodeFlow 2 | 3 | > [!warning] 4 | > 5 | > 近期重构中,以及增加功能效果,工作量较大,下一正式版本的 Release 需要等待较久 6 | > 7 | > 将该项目的作用分为两大类: 8 | > 9 | > - 节点式笔记 10 | > - 用于记录节点式笔记,或者记录 comfyui/blender/ue4 等其他节点流软件的节点状况 11 | > - 该功能完全依赖于视觉,节点没有运行功能 12 | > - 功能工作流 (new in V2.0.0) 13 | > - 一些节点项,会为节点提供一些额外的功能。这些功能节点可以被运行起来 14 | > - 开发设计上让该功能尽可能通用。目前着重于文本笔记、网络请求流程、AI文本等工作流场景的建设 15 | 16 | - en 17 | 18 | Render node streams like `ComfyUi`, `UE`, `Houdini`, `Blender`, etc., to make it easy to write relevant notes. 19 | 20 | The plugin allows diagrams to be described using lightweight syntax or Json, and is available in both Obsidian and VuePress blogs 21 | - zh 22 | 23 | 渲染节点流,像 `ComfyUi`, `UE`, `Houdini`, `Blender` 等,使其易于编写相关笔记。 24 | 25 | 允许使用轻量语法或从上述软件导出的Json描述了图表,该插件在Obsidian与VuePress博客中均可使用 26 | 27 | ## Docs (教程, 文档) 28 | 29 | Tutorials, online effects, use cases, and more (教程、在线效果、用例等) 30 | 31 | - [Online effects (在线效果)、demo](https://linczero.github.io/MdNote_Public/ProductDoc/Plugin/NodeFlow/README.show.html), Vuepress Version 32 | - [Online interaction (在线效果2、在线交互)](https://linczero.github.io/obsidian-node-flow/), App Version 33 | 34 | ## Effect display (Partial) (效果展示 - 部分) 35 | 36 | ![](./docs/image.png) 37 | 38 | ## Usage 39 | 40 | ### Method 1: Use code blocks (方式一: 使用代码块) 41 | 42 | Supported code block types (支持的代码块类型): 43 | 44 | ```json 45 | ["nodeflow-test", "nodeflow-vueflow", "nodeflow-obcanvas", "nodeflow-comfyui", "nodeflow-list", "nodeflow-listitem"] 46 | ``` 47 | 48 | - en 49 | These code blocks are of different types. 50 | 1. `nodeflow-comfyui` prefix, using the workflow json exported by comfyui software as content 51 | 2. `nodeflow-obcanvas` prefix, using the content of the obsidian canvas file (open with Notepad, you will find that it is a json format) 52 | 3. `nodeflow-list` prefix, indicating that this is a reference mermaid/plantuml, using light text to describe the chart format. 53 | See [NodeFlow List Grammer](https://linczero.github.io/MdNote_Public/ProductDoc/Plugin/NodeFlow/docs/zh/NodeFlow%20List%20Grammer.html) for details 54 | 4. `nodeflow-list` prefix, in beta testing. Similar to `nodeflow-list`, but can support multiple item types, such as color, markdown, etc. 55 | 5. See the [documentation](https://linczero.github.io/MdNote_Public/ProductDoc/Plugin/NodeFlow/README.show.html) for specific uses of these types 56 | - zh 57 | 这些代码块的类型都不太一样 58 | 1. `nodeflow-comfyui` 前缀的,使用comfyui软件导出的工作流json作为内容 59 | 2. `nodeflow-obcanvas` 前缀的,使用obsidian canvas文件的内容 (用记事本打开,会发现里面是一个json格式) 60 | 3. `nodeflow-list` 前缀的,表示这是一个对标mermaid/plantuml的,用轻文字描述图表的格式。 61 | 具体语法参考 [NodeFlow List Grammer](https://linczero.github.io/MdNote_Public/ProductDoc/Plugin/NodeFlow/docs/zh/NodeFlow%20List%20Grammer.html) 62 | 4. `nodeflow-listitem` 前缀的,beta测试中。与 `nodeflow-list` 相似,但能支持多种项类型,如颜色、markdown等 63 | 5. 这些类型更具体的用法见[文档](https://linczero.github.io/MdNote_Public/ProductDoc/Plugin/NodeFlow/README.show.htm) 64 | 65 | Example - List Grammer (举例 - 列表语法): 66 | 67 | ````md 68 | ```nodeflow-list 69 | - nodes 70 | - node1:KSample 71 | - Latent, o 72 | - model, i 73 | - positive, i 74 | - negative, i 75 | - Latent, i 76 | - seed, 77 | - control_after_generate,, randomize 78 | - steps,, 20 79 | - CFG,, 8.0 80 | - sampler_name,, euler 81 | - scheduler,, normal 82 | - denoise,, 1.00 83 | - io defaultTest, i , test 84 | - io defaultTest, o, test 85 | - t1:noValueTest, 86 | - t2:,, noKeyTest 87 | mul lines test 88 | - node2:KSample 89 | - 潜空间, o 90 | - 模型, i 91 | - 正面条件, i 92 | - 负面条件, i 93 | - 潜空间, i 94 | - 种子, 95 | - 运行后操作, 96 | - 步数, 97 | - CFG, 98 | - 采样器/采样方法, 99 | - 调度器, 100 | - 降噪, 101 | - translate 102 | - edges 103 | - node1,Latent, translate,l 104 | - translate,r, node2, 潜空间 105 | ``` 106 | ```` 107 | 108 | You can alse use the demo use case by filling in `demo`/`demo2`/`demo3` in the code block and checking that it works (你也可以在代码块内填写 `demo`/`demo2`/`demo3` 来使用demo用例,并检查其是否正常工作) 109 | 110 | ### Method 2: Independent file (方式二: 独立文件) 111 | 112 | - en 113 | 114 | Directly put comfyui's exported `workflow.json` file in the library, will automatically identify the json suffix 115 | 116 | Directly modify the extension of comfyui's exported workflow.json file to `.workflow_json`, which can also be correctly identified 117 | - zh 118 | 119 | 直接将comfyui的导出 `workflow.json` 文件放在库里,会自动识别json后缀的 120 | 121 | 直接将comfyui的导出workflow.json文件扩展名修改成`.workflow_json`,也能正确识别 122 | 123 | ## TODO 124 | 125 | - feat 126 | - ~~支持socket的name-value显示~~ (241027) 127 | - ~~Obsidian版本的全屏优化~~ 128 | - enhance 129 | - [非全屏状态下的中间拖拽优化](https://github.com/bcakmakoglu/vue-flow/issues/1557) 130 | - [带节点组的情况下进行自动布局](https://github.com/bcakmakoglu/vue-flow/discussions/1658) 131 | - 代码优化 132 | - ~~整理一下,捋一下各种节点类型,以及对应的data数据,然后把socket与线色的关系再优化一下~~ 133 | - style 134 | - [scss代码优化问题](ttps://github.com/pipe01/esbuild-plugin-vue3/issues/30) 135 | - ~~更好看的工具栏与样式~~ 136 | 137 | ## 更多用法 138 | 139 | 详见 [Online effects (在线效果)、demo](https://linczero.github.io/MdNote_Public/ProductDoc/Plugin/NodeFlow/README.show.html),这里仅再列举部分: 140 | 141 | 复制以下内容到你的obsidian中: 142 | 143 | ````md 144 | ```nodeflow-comfyui 145 | demo 146 | ``` 147 | 148 | ```nodeflow-obcanvas 149 | demo 150 | ``` 151 | 152 | ```nodeflow-list 153 | demo 154 | ``` 155 | 156 | ```nodeflow-vueflow 157 | demo 158 | ``` 159 | 160 | ```nodeflow-item 161 | demo 162 | ``` 163 | 164 | ```nodeflow-listitem 165 | demo 166 | ``` 167 | ```` 168 | -------------------------------------------------------------------------------- /src/EditableBlockApp/src/codemirrorAdapt/EditableBlock_Cm.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Editable Block - CM Version 3 | */ 4 | 5 | import { EditableCodeblock as EditableCodeblock, loadPrism2 } from '../../../General/EditableBlock/EditableBlock_Code'; 6 | 7 | // prism 8 | import Prism from "prismjs" // 导入代码高亮插件的core(里面提供了其他官方插件及代码高亮样式主题,你只需要引入即可) 9 | import 'prismjs/components/prism-javascript'; 10 | import 'prismjs/components/prism-json'; 11 | import "prismjs/themes/prism-okaidia.min.css" // 主题, okaidia和tomorrow都是不错黑夜主题 12 | loadPrism2.fn = () => { 13 | return Prism 14 | } 15 | 16 | // codemirror 17 | import { 18 | EditorView, 19 | } from '@codemirror/view'; 20 | import { 21 | EditorSelection, 22 | EditorState, 23 | } from '@codemirror/state'; 24 | 25 | export class EditableBlock_Cm extends EditableCodeblock { 26 | // editor: Editor|null = null; // obsidian依赖 27 | state: EditorState; 28 | oldView: EditorView; 29 | fromPos: number; // TODO 未能动态更新 30 | toPos: number; 31 | updateContent_local: (newContent_local: string) => void; 32 | 33 | constructor( 34 | state: EditorState, oldView: EditorView, fromPos: number, toPos: number, 35 | lang: string, content: string, container: HTMLElement, 36 | updateContent_local: (newContent_local: string) => void 37 | ) { 38 | super(lang, content, container) 39 | this.settings.renderEngine = "prismjs" 40 | // 如果是oninput saveMode,需要注意: 41 | // 如果页面有多个codeblock,而其中一个codeblock的编辑导致全部重渲染,会导致光标无法确定要在哪个里 42 | // 这会存在问题 43 | this.settings.saveMode = 'oninput' // (可切换) 44 | // this.settings.saveMode = 'onchange' 45 | this.settings.renderMode = 'textarea' 46 | // this.settings.renderMode = 'editablePre' // (可切换) 47 | 48 | this.state = state 49 | this.oldView = oldView 50 | this.fromPos = fromPos 51 | this.toPos = toPos 52 | this.updateContent_local = updateContent_local 53 | } 54 | 55 | /// TODO: fix: after edit, can't up/down to root editor 56 | /// @param el: HTMLTextAreaElement|HTMLInputElement|HTMLPreElement 57 | override enable_editarea_listener(el: HTMLElement, cb_tab?: (ev: KeyboardEvent)=>void, cb_up?: (ev: KeyboardEvent)=>void, cb_down?: (ev: KeyboardEvent)=>void): void { 58 | if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el.isContentEditable)) return 59 | 60 | super.enable_editarea_listener(el, cb_tab, cb_up, cb_down) // `tab` 61 | 62 | // textarea - async part - keydown 63 | el.addEventListener('keydown', (ev: KeyboardEvent) => { // ~~`tab` key~~、`arrow` key 64 | if (ev.key == 'ArrowDown') { 65 | if (cb_down) { cb_down(ev); return } 66 | 67 | // check is the last line 68 | if (el instanceof HTMLInputElement) { 69 | // true 70 | } else if (el instanceof HTMLTextAreaElement) { 71 | const selectionEnd: number = el.selectionEnd 72 | const textBefore = el.value.substring(0, selectionEnd) 73 | const linesBefore = textBefore.split('\n') 74 | if (linesBefore.length !== el.value.split('\n').length) return 75 | } else { 76 | // TODO 77 | return 78 | } 79 | 80 | ev.preventDefault() // safe: tested: `prevent` can still trigger `onChange` 81 | const targetLine: number = this.state.doc.lineAt(this.toPos).number + 1 // state.doc.line 和 state.doc.lineAt 分别 pos 和 line 转换 82 | if (targetLine > this.state.doc.lines - 1) { // when codeblock on the last line 83 | // strategy1: only move to end 84 | // toLine-- 85 | 86 | // strategy2: insert a blank line 87 | const changes = this.state.changes({ 88 | from: this.state.doc.toString().length, 89 | to: this.state.doc.toString().length, 90 | insert: "\n" 91 | }) 92 | this.state.update({changes}) 93 | } 94 | 95 | const targetPos = this.state.doc.line(targetLine).from; 96 | const selection = EditorSelection.create([ 97 | EditorSelection.cursor(targetPos) 98 | ]); 99 | const transaction = this.oldView.state.update({ 100 | selection, 101 | userEvent: "select" 102 | }); 103 | this.oldView.dispatch(transaction); 104 | this.oldView.focus() 105 | return 106 | } 107 | else if (ev.key == 'ArrowUp') { 108 | if (cb_up) { cb_up(ev); return } 109 | 110 | // check is the first line 111 | if (el instanceof HTMLInputElement) { 112 | // true 113 | } else if (el instanceof HTMLTextAreaElement) { 114 | const selectionStart: number = el.selectionStart 115 | const textBefore = el.value.substring(0, selectionStart) 116 | const linesBefore = textBefore.split('\n') 117 | if (linesBefore.length !== 1) return 118 | } else { 119 | // TODO 120 | return 121 | } 122 | 123 | ev.preventDefault() // safe: tested: `prevent` can still trigger `onChange` 124 | let targetLine: number = this.state.doc.lineAt(this.fromPos).number - 1 // state.doc.line 和 state.doc.lineAt 分别 pos 和 line 转换 125 | if (targetLine < 0) { // when codeblock on the frist line 126 | // strategy1: only move to start 127 | // toLine = 0 128 | 129 | // strategy2: insert a blank line 130 | targetLine = 0 131 | const changes = this.state.changes({ 132 | from: 0, 133 | to: 0, 134 | insert: "\n" 135 | }) 136 | this.state.update({changes}) 137 | } 138 | this.oldView.focus() 139 | const targetPos = this.state.doc.line(targetLine).from; 140 | const selection = EditorSelection.create([ 141 | EditorSelection.cursor(targetPos) 142 | ]); 143 | const transaction = this.oldView.state.update({ 144 | selection, 145 | userEvent: "select" 146 | }); 147 | this.oldView.dispatch(transaction); 148 | this.oldView.focus() 149 | return 150 | } 151 | /*else if (ev.key == 'ArrowRight') { 152 | } 153 | else if (ev.key == 'ArrowLeft') { 154 | }*/ 155 | }) 156 | } 157 | 158 | emit_save(isUpdateLanguage: boolean, isUpdateSource: boolean): Promise { 159 | return new Promise((resolve) => { 160 | this.updateContent_local(this.outerInfo.source) 161 | resolve(); 162 | }) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/NodeFlowObsidian/main.ts: -------------------------------------------------------------------------------- 1 | import { MarkdownView, Plugin, TFile } from "obsidian"; 2 | import { MarkdownPostProcessorContext, WorkspaceLeaf } from "obsidian" 3 | import { nfSetting, factoryVueDom } from "../NodeFlow/index" 4 | import { NodeFlowViewFlag, NodeFlowView } from './NodeFlowView' 5 | import { NodeFlowFileViewFlag, NodeFlowFileView } from './NodeFlowFileView' 6 | 7 | // 适配器 8 | // @env [环境] md渲染, obsidian版本 9 | import { MarkdownRenderChild, MarkdownRenderer } from 'obsidian' 10 | nfSetting.fn_renderMarkdown = ((markdown: string, el: HTMLElement, ctx?: any): void => { 11 | el.classList.add("markdown-rendered") 12 | const mdrc: MarkdownRenderChild = new MarkdownRenderChild(el); 13 | if (ctx) ctx.addChild(mdrc); 14 | else if (nfSetting.ctx) nfSetting.ctx.addChild(mdrc); 15 | // @ts-ignore 新接口,但旧接口似乎不支持 16 | MarkdownRenderer.render(nfSetting.app, markdown, el, nfSetting.app.workspace.activeLeaf?.view?.file?.path??"", mdrc) 17 | }) 18 | // @env [环境] http接口,obsidian版本 (obsidian有自己的http接口) 19 | import { requestUrl } from 'obsidian' 20 | nfSetting.fn_request = async ( 21 | url: string, 22 | method: string | undefined = 'GET', 23 | headers: Record | undefined = undefined, 24 | body: string | ArrayBuffer | undefined = undefined 25 | ) => { 26 | const responseData = await requestUrl({ url, method, headers, body }); 27 | return responseData 28 | } 29 | // @env [环境] 新窗口中显示 30 | import { fn_newView } from './NodeFlowView' 31 | nfSetting.fn_newView = fn_newView 32 | 33 | // 设置 34 | interface NodeFlowPluginSettings { 35 | isDebug: boolean; 36 | } 37 | const NODEFLOW_SETTINGS: NodeFlowPluginSettings = { 38 | isDebug: false // This command is used to control whether debugging information is printed 39 | } 40 | 41 | export default class NodeFlowPlugin extends Plugin { 42 | settings: NodeFlowPluginSettings; 43 | 44 | async onload() { 45 | await this.loadSettings(); 46 | nfSetting.app = this.app 47 | 48 | // 注册 - 代码块 49 | const code_type_list = [ 50 | "nodeflow-vueflow", "nodeflow-obcanvas", "nodeflow-comfyui", "nodeflow-list", "nodeflow-item", "nodeflow-listitem" 51 | ] 52 | for (let item of code_type_list) { 53 | this.registerMarkdownCodeBlockProcessor(item, (src: string, blockEl: HTMLElement, ctx: MarkdownPostProcessorContext) => { 54 | nfSetting.ctx = ctx 55 | factoryVueDom(item, blockEl, src, true, (str: string) => { 56 | const { lineEnd, lineStart, text } = ctx.getSectionInfo(blockEl) 57 | 58 | const view = this.app.workspace.getActiveViewOfType(MarkdownView); 59 | const editor = view.editor; 60 | // 不修改代码块的前后围栏部分。开头是围栏头下一行的开头,结束是围栏尾的第一个字符,所以str要保证`\n`结尾 61 | editor.replaceRange(str.endsWith("\n")?str:str+"\n", {ch:0, line:lineStart+1}, {ch:0, line:lineEnd}) 62 | }) 63 | }) 64 | } 65 | 66 | // 注册 - 视图 67 | nfSetting.cahce_workspace = this.app.workspace // 防止在Vue的上下文中,不存在workspace 68 | this.registerView(NodeFlowViewFlag, (leaf) => new NodeFlowView(leaf)) 69 | this.registerView(NodeFlowFileViewFlag, (leaf: WorkspaceLeaf) => new NodeFlowFileView(leaf)); 70 | 71 | // 注册 - 文件类型扩展 (新格式一) 72 | this.registerExtensions(["workflow_json"], NodeFlowFileViewFlag); 73 | this.registerExtensions(["canvas_json"], NodeFlowFileViewFlag); 74 | this.registerExtensions(["json"], NodeFlowFileViewFlag); 75 | 76 | // 注册 - 事件 (新格式二) 77 | this.registerEvent( 78 | this.app.workspace.on('file-open', async (file: TFile) => { 79 | // 通用检查 80 | if (!file) return 81 | // @ts-ignore 类型“WorkspaceLeaf”上不存在属性“containerEl” 82 | // const div_leaf: HTMLElement = this.app.workspace.activeLeaf.containerEl // 弃用,审查员也不会让你用 83 | // const div_leaf: HTMLElement = this.app.workspace.getLeaf().containerEl // bug: 切换到锁定标签页会新增一个新标签页 84 | const div_leaf: HTMLElement = this.app.workspace.getActiveViewOfType(MarkdownView)?.containerEl 85 | if (!div_leaf) return 86 | let div_view: HTMLElement = div_leaf.querySelector(".view-content") 87 | if (!div_view) return 88 | let div_child: HTMLElement; 89 | 90 | // 从文件格式到json格式的映射 91 | let jsonType:string = "" 92 | if (file.name.endsWith("workflow.json.md")) jsonType = "nodeflow-comfyui"; 93 | else if (file.name.endsWith(".canvas.md")) jsonType = "nodeflow-obcanvas"; 94 | 95 | // 视图处理 96 | if (jsonType != "") { 97 | const value:string = await this.app.vault.cachedRead(file) 98 | // 参考excalidraw中的做法: 99 | // .view-content 100 | // .markdown-source-view (-) 101 | // .markdown-reading-view (-) 102 | // .markdown-excalidraw-wrapper (+) 103 | div_child = div_view.querySelector(":scope>.markdown-source-view"); if (div_child) div_child.classList.add("nf-style-display-none"); 104 | div_child = div_view.querySelector(":scope>.markdown-reading-view"); if (div_child) div_child.classList.add("nf-style-display-none"); 105 | div_child = div_view.querySelector(":scope>.nf-autoDie"); if (div_child) { div_view.removeChild(div_child) } // 删除nf视图 106 | div_child = div_view.createEl("div"); div_child.classList.add("nf-autoDie"); // 创建nf视图 107 | factoryVueDom(jsonType, div_child, value, false) // 并挂载 (需要挂载到一个会死亡的div) 108 | } else { 109 | div_child = div_view.querySelector(":scope>.markdown-source-view"); if (div_child) div_child.classList.remove("nf-style-display-none"); 110 | div_child = div_view.querySelector(":scope>.markdown-reading-view"); if (div_child) div_child.classList.remove("nf-style-display-none"); 111 | div_child = div_view.querySelector(":scope>.nf-autoDie"); if (div_child) { div_view.removeChild(div_child) } // 删除nf视图 112 | } 113 | }) 114 | ) 115 | 116 | // 监听 - 禁用滚轮点击/长按 117 | // 无法监听在节点画布中的滚轮点击事件 118 | // document.addEventListener('mousedown', (event: MouseEvent) => { 119 | // if (event.button === 1) { // 检查是否为滚轮点击(中间键) 120 | // event.preventDefault(); // 阻止默认行为 121 | // console.log('滚轮被点击'); 122 | // } 123 | // }); 124 | } 125 | 126 | onunload() {} 127 | 128 | // 设置的加载与保存 129 | async loadSettings() { 130 | const data = await this.loadData() // 如果没有配置文件则为null 131 | this.settings = Object.assign({}, NODEFLOW_SETTINGS, data); // 合并默认值和配置文件的值 132 | 133 | // 如果没有配置文件则生成一个默认值的配置文件 134 | if (!data) { 135 | this.saveData(this.settings); 136 | } 137 | 138 | nfSetting.isDebug = this.settings.isDebug 139 | } 140 | async saveSettings() { 141 | await this.saveData(this.settings); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/NodeFlow/component/utils/InteractionControls.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 106 | 107 | 108 | 163 | 164 | 220 | -------------------------------------------------------------------------------- /src/EditableBlockApp/src/utils/preset_map.js: -------------------------------------------------------------------------------- 1 | // 相较于文档版,这里能放的内容更多 2 | 3 | export const preset_map = { 4 | // 5 | 'Normal markdown': `\ 6 | EditableBlock 开发阶段快速测试平台 + 试用Demo 7 | 8 | ## 插件介绍 9 | 10 | 目的: 主要解决 Markdown 容器的嵌套体验 11 | 12 | > 使用说明 13 | > 14 | > 这里注意会有四个标签页,你可以互相切换或者重新排布,以进行对比: 15 | > - MdEditor: 纯文本 16 | > - MdViewer: 渲染,使用markdown-it引擎 + EditableBlock 插件,不可编辑 17 | > - CodeMirror2: 渲染,使用CodeMirror引擎,可编辑 18 | > - CodeMirror: 渲染,使用CodeMirror引擎 + EditableBlock 插件,可编辑 19 | 20 | > 比较 21 | > - CodeMirror2 22 | > - 逻辑比较像 **Obsidian** 实时模式的编辑体验。 23 | > - 特点:没有 "块" 和 "容器" 的概念,解析结果为多个字符组的数组 24 | > - 缺点:在引用块/列表嵌套其他内容的场景时,编辑困难、渲染难看 25 | > (例如obsidian的引用块去嵌套代码块时、代码块完全无法被渲染) 26 | > - CodeMirror 27 | > - 逻辑更像 **Typora** 的编辑体验 28 | > - 特点:有 "块" 和 "容器" 的概念,在一个块内,存在一个单独的编辑空间。 29 | > 这使得在块内的用户编辑体验不会下降! 即便是块再去嵌套其他块 30 | 31 | > 目前仍位于开发测试阶段,bug多多。 32 | > 特别是引用块 33 | > TODO: 34 | > - 支持onInput和onChange的saveMode切换 35 | > - 更多的快捷键支撑起块级编辑的体验 36 | > - 嵌套后,第二层的控件无法进行编辑保存 37 | > 38 | > TODO: 39 | > - bug: 如果先编辑外部编辑器,再编辑内部编辑器,内部对应的from,to值不正常,导致向上传有问题 40 | 41 | ## 插件Demo 42 | 43 | from: https://www.w3schools.com/js/js_examples.asp 44 | 45 | \`\`\`js 46 | 47 | 48 | 49 | 50 |

What Can JavaScript Do?

51 | 52 |

JavaScript can change HTML content.

53 | 54 | 58 | 59 | 60 | 61 | \`\`\` 62 | 63 | ## 基本md语法 64 | 65 | 标题略 66 | 67 | 内联: **bord** *i* ~~delete~~ ==highlight== \`inline code\` $\\frac 12$ 68 | 69 | 列表 70 | 71 | - 1 72 | - 2 73 | - 3 74 | - 4 75 | - 5 76 | 77 | 引用块 78 | 79 | > [!note] 80 | > This is a note block. 81 | 82 | 代码块 83 | 84 | \`\`\`js 85 | 86 | 87 | 88 | 89 |

What Can JavaScript Do?

90 | 91 |

JavaScript can change HTML content.

92 | 93 | 97 | 98 | 99 | 100 | \`\`\` 101 | 102 | 公式块 103 | 104 | $$ 105 | \\frac 12 106 | $$ 107 | 108 | mermaid块 109 | 110 | (略) 111 | 112 | 表格 113 | 114 | | 1 | 2 | 115 | |---|---| 116 | | 3 | 4 | 117 | 118 | ## 嵌套测试 119 | 120 | 引用块包含简单Markdown 121 | 122 | > 引用块开头 123 | > 124 | > 内联: **bord** *i* ~~delete~~ ==highlight== \`inline code\` $\\frac 12$ 125 | > 126 | > - 列表项 127 | > 128 | > 引用块结尾 129 | 130 | 引用块包含代码块 131 | 132 | > 引用块开头 133 | > 134 | > \`\`\`js 135 | > 136 | > 137 | > 138 | > 139 | >

What Can JavaScript Do?

140 | > 141 | >

JavaScript can change HTML content.

142 | > 143 | > 147 | > 148 | > 149 | > 150 | > \`\`\` 151 | > 152 | > 引用块结尾 153 | 154 | 引用块包含引用块 155 | 156 | > 引用块开头 157 | > 158 | > > 引用块嵌套开头 159 | > > 引用块嵌套内容 160 | > > 引用块嵌套结尾 161 | > 162 | > 引用块结尾 163 | `, 164 | 'English': `\ 165 | EditableBlock Development Phase Quick Testing Platform + Demo 166 | 167 | ## Plugin Introduction 168 | 169 | Purpose: Primarily solves the nested experience of Markdown containers 170 | 171 | > Usage Instructions 172 | > 173 | > Note there will be four tabs. You can switch between them or rearrange for comparison: 174 | > - MdEditor: Plain text 175 | > - MdViewer: Rendered, using markdown-it engine + EditableBlock plugin, non-editable 176 | > - CodeMirror2: Rendered, using CodeMirror engine, editable 177 | > - CodeMirror: Rendered, using CodeMirror engine + EditableBlock plugin, editable 178 | 179 | > Comparison 180 | > - CodeMirror2 181 | > - Logic resembles **Obsidian's** Live Preview editing experience. 182 | > - Characteristics: No concept of "blocks" or "containers", parsing result is an array of character groups 183 | > - Drawbacks: Difficult editing and poor rendering when nesting other content in blockquotes/lists 184 | > (e.g., when nesting code blocks within blockquotes in Obsidian, code blocks fail to render completely) 185 | > - CodeMirror 186 | > - Logic resembles **Typora's** editing experience 187 | > - Characteristics: Features "block" and "container" concepts, creating a separate editing space within each block. 188 | > This maintains editing experience within blocks without degradation! Even when blocks nest other blocks. 189 | 190 | > Currently in development/testing phase with many bugs. 191 | > Especially blockquotes. 192 | > TODO: 193 | > - Support saveMode switching for onInput and onChange 194 | > - More shortcuts to enhance block-level editing experience 195 | > - Nested second-level controls cannot save edits 196 | 197 | ## Plugin Demo 198 | 199 | from: https://www.w3schools.com/js/js_examples.asp 200 | 201 | \`\`\`js 202 | 203 | 204 | 205 | 206 |

What Can JavaScript Do?

207 | 208 |

JavaScript can change HTML content.

209 | 210 | 214 | 215 | 216 | 217 | \`\`\` 218 | 219 | ## Basic Markdown Syntax 220 | 221 | Headers (omitted) 222 | 223 | Inline: **bold** *italic* ~~strikethrough~~ ==highlight== \`inline code\` $\\frac 12$ 224 | 225 | Lists 226 | 227 | - 1 228 | - 2 229 | - 3 230 | - 4 231 | - 5 232 | 233 | Blockquote 234 | 235 | > [!note] 236 | > This is a note block. 237 | 238 | Code block 239 | 240 | \`\`\`js 241 | 242 | 243 | 244 | 245 |

What Can JavaScript Do?

246 | 247 |

JavaScript can change HTML content.

248 | 249 | 253 | 254 | 255 | 256 | \`\`\` 257 | 258 | Formula block 259 | 260 | $$ 261 | \\frac 12 262 | $$ 263 | 264 | Mermaid block 265 | 266 | (omitted) 267 | 268 | Table 269 | 270 | | 1 | 2 | 271 | |---|---| 272 | | 3 | 4 | 273 | 274 | ## Nesting Tests 275 | 276 | Blockquote containing simple Markdown 277 | 278 | > Start of blockquote 279 | > 280 | > Inline: **bold** *italic* ~~strikethrough~~ ==highlight== \`inline code\` $\\frac 12$ 281 | > 282 | > - List item 283 | > 284 | > End of blockquote 285 | 286 | Blockquote containing code block 287 | 288 | > Start of blockquote 289 | > 290 | > \`\`\`js 291 | > 292 | > 293 | > 294 | > 295 | >

What Can JavaScript Do?

296 | > 297 | >

JavaScript can change HTML content.

298 | > 299 | > 303 | > 304 | > 305 | > 306 | > \`\`\` 307 | > 308 | > End of blockquote 309 | 310 | Blockquote containing nested blockquote 311 | 312 | > Start of blockquote 313 | > 314 | > > Start of nested blockquote 315 | > > Nested content 316 | > > End of nested blockquote 317 | > 318 | > End of blockquote 319 | ` 320 | } 321 | -------------------------------------------------------------------------------- /src/NodeFlow/component/container/NodeFlowContainerS.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 48 | 49 | 149 | 150 | 185 | --------------------------------------------------------------------------------