├── src
├── config.ts
├── main.ts
├── env.d.ts
├── lib
│ ├── M3Creator.ts
│ └── SiYuan.ts
└── App.vue
├── .vscode
└── extensions.json
├── export.png
├── preview.png
├── public
└── favicon.ico
├── widget.json
├── tsconfig.node.json
├── .gitignore
├── index.html
├── vite.config.ts
├── tsconfig.json
├── package.json
├── README.md
├── .github
└── workflows
│ └── publish.yml
└── pnpm-lock.yaml
/src/config.ts:
--------------------------------------------------------------------------------
1 | export const TOKEN = ""
2 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["johnsoncodehk.volar"]
3 | }
4 |
--------------------------------------------------------------------------------
/export.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpaqueGlass/SiYuan-Xmind-fork/OG-dev/export.png
--------------------------------------------------------------------------------
/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpaqueGlass/SiYuan-Xmind-fork/OG-dev/preview.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpaqueGlass/SiYuan-Xmind-fork/OG-dev/public/favicon.ico
--------------------------------------------------------------------------------
/widget.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Note Map",
3 | "author": "InEase",
4 | "url": "https://github.com/InEase/SiYuan-Xmind",
5 | "version": "1.1.0"
6 | }
7 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import App from './App.vue'
3 | import TDesign from 'tdesign-vue-next';
4 |
5 | createApp(App).use(TDesign).mount('#app')
6 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "esnext",
5 | "moduleResolution": "node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": [
9 | "vite.config.ts"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare module '*.vue' {
4 | import type { DefineComponent } from 'vue'
5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
6 | const component: DefineComponent<{}, {}, any>
7 | export default component
8 | }
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 | package-lock.json
26 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | SiYuan To Xmind Export
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import path from "path"
3 | import vue from '@vitejs/plugin-vue'
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig({
7 | plugins: [vue()],
8 | resolve: {
9 | alias: {
10 | "@": path.resolve(__dirname, "src")
11 | },
12 | extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json']
13 | },
14 | server: {
15 | cors: true,
16 | open: true,
17 | proxy: {
18 | "^/api": {
19 | target: "http://127.0.0.1:6806",
20 | changeOrigin: true,
21 | }
22 | }
23 | },
24 | base: './',
25 | root: './'
26 | })
27 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "useDefineForClassFields": true,
5 | "module": "esnext",
6 | "moduleResolution": "node",
7 | "strict": true,
8 | "jsx": "preserve",
9 | "sourceMap": true,
10 | "resolveJsonModule": true,
11 | "esModuleInterop": true,
12 | "lib": [
13 | "esnext",
14 | "dom"
15 | ],
16 | "baseUrl": ".",
17 | "paths": {
18 | "@/*": [
19 | "src/*"
20 | ] // 相对位置需要配置baseUrl才能识别,否则会报错
21 | }
22 | },
23 | "include": [
24 | "src/**/*.ts",
25 | "src/**/*.d.ts",
26 | "src/**/*.tsx",
27 | "src/**/*.vue"
28 | ],
29 | "references": [
30 | {
31 | "path": "./tsconfig.node.json"
32 | }
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "siyuan-xmind",
3 | "private": true,
4 | "version": "0.0.0",
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vue-tsc --noEmit && vite build",
8 | "build:no-check": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "d3": "^6.7.0",
13 | "markmap-common": "^0.14.2",
14 | "markmap-lib": "^0.14.3",
15 | "markmap-view": "^0.14.3",
16 | "save-file": "^2.3.1",
17 | "tdesign-vue-next": "^1.0.1",
18 | "vue": "^3.2.25",
19 | "xmindmark": "^0.2.1"
20 | },
21 | "devDependencies": {
22 | "@types/node": "^17.0.23",
23 | "@vitejs/plugin-vue": "^2.3.0",
24 | "typescript": "^4.5.4",
25 | "vite": "^2.9.0",
26 | "vue-tsc": "^0.29.8"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Note Map
2 |
3 | Current Version: v1.1
4 |
5 | ✨ 一键导出当前目录下所有大纲并组合为思维导图
6 |
7 | 支持在思源内预览思维导图,也支持导出为`.xmind`格式(需要用**Xmind**打开哦~)
8 |
9 | 可以用于复习时,对着大纲回想笔记内容~
10 |
11 | ## 效果图
12 |
13 | 感谢九炎大佬贡献的思源内思维导图预览功能~!
14 |
15 | 
16 |
17 | 
18 |
19 | ## 食用方法
20 |
21 | 1. 引入挂件
22 |
23 | 2. 点击按钮
24 |
25 | 3. 完成!
26 |
27 | ### 手动构建
28 |
29 | 仓库有两个分支:
30 |
31 | 1. `master` 源代码,开发工作在这个分支上完成,通过action构建到`main`以减小挂件体积
32 |
33 | 2. `main` 构建分支 (因为挂件集市索引图片默认是main分支)
34 |
35 | 如果需要手动构建,请切换到`master`分支后:
36 |
37 | ```
38 | pnpm i
39 | pnpm build
40 | ```
41 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | # .github/workflows/publish.yml
2 | name: Generate a build and push to another branch
3 |
4 | on:
5 | push:
6 | branches:
7 | - master
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | name: Build and Push
13 | steps:
14 | - name: git-checkout
15 | uses: actions/checkout@v2
16 |
17 | - name: Install all dependencies
18 | run: npm install
19 |
20 | - name: Build
21 | run: npm run build:no-check # The build command of your project
22 |
23 | - name: Copy Necessary elements
24 | run: |
25 | cp widget.json dist/widget.json
26 | cp README.md dist/README.md
27 | cp preview.png dist/preview.png
28 |
29 | - name: Push
30 | uses: s0/git-publish-subdir-action@develop
31 | env:
32 | REPO: self
33 | BRANCH: main # The branch name where you want to push the assets
34 | FOLDER: dist # The directory where your assets are generated
35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # GitHub will automatically add this - you don't need to bother getting a token
36 | MESSAGE: "Build: ({sha}) {msg}" # The commit message
37 |
--------------------------------------------------------------------------------
/src/lib/M3Creator.ts:
--------------------------------------------------------------------------------
1 | import { parseXMindMarkToXMindFile } from 'xmindmark'
2 | import save from 'save-file'
3 | import { listDocsByPath, getDocOutline, DocOutline, getBlockKramdown } from './SiYuan'
4 |
5 | // m3字符串的格式大概就是这样的
6 | const xmindMarkFileContent = `
7 | Central Topic
8 | - Main Topic 1
9 | - Main Topic 2
10 | `
11 |
12 | const indent = " "
13 | export let outlineScope: Array = [1, 6];
14 | /**
15 | *
16 | * @param notebook
17 | * @param path
18 | * @param mode this_doc_only 或 with_child_doc 或 with_child_doc_outline
19 | * @param index
20 | * @returns
21 | */
22 | export async function ListFile(notebook: string, path: string, mode:string, index = 0): Promise {
23 | let result = ""
24 | // 列出当前目录下的全部文件
25 | const docs:any = await listDocsByPath(path, notebook);
26 | if (mode !== "this_doc_only") {
27 | await docs.files.reduce(
28 | async (memo:any, file:any) => {
29 | await memo;
30 | let tempFileName = file.name;
31 | if (mode == "with_child_doc") tempFileName = tempFileName.substring(0, tempFileName.length - 3);
32 | result += `${indent.repeat(index)}- ${tempFileName.replaceAll(" ", " ")}\n`;
33 | if (mode == "with_child_doc_outline") {
34 | const outline : any = await getDocOutline(file.id)
35 | if (outline.length > 0) {
36 | result += ExtractOutline(outline, index + 1)
37 | }
38 | }
39 |
40 | if (file.subFileCount > 0) {
41 | result += await ListFile(notebook, file.path, mode, index + 1)
42 | }
43 | }, undefined
44 | );
45 | }
46 |
47 | if (result == "" && mode != "with_child_doc") {
48 | let dividedPath = path.split("/");
49 | let docid : any;
50 | if (dividedPath.length > 0) {
51 | docid = dividedPath[dividedPath.length - 1];
52 | docid = docid.substring(0, docid.length - 3);
53 | }
54 | const outline : any = await getDocOutline(docid);
55 | if (outline.length > 0) {
56 | result += ExtractOutline(outline, index);
57 | }
58 | }
59 | return result
60 | }
61 |
62 | function ExtractOutline(outlines: DocOutline[] | undefined | null, index: number, limit: Array = outlineScope): string {
63 | if (!outlines) {
64 | return ""
65 | }
66 | let result = ""
67 | outlines.map((outline:any) => {
68 | // 替换标题中的空格
69 | let content = outline.content;
70 | // 获取两种情况下的大纲文本
71 | if (content != null || content != undefined) {
72 | content = content.replaceAll(" ", " ");
73 | }else{
74 | content = outline.name.replaceAll(" ", " ");
75 | }
76 | // 范围判断
77 | let outlineHeadingNum = parseInt(outline.subType.substring(1, 2));
78 | if (outlineHeadingNum >= limit[0]) {
79 | result += `${indent.repeat(index)}- ${content}\n`;
80 | }
81 | // 下一层级大纲
82 | if (outlineHeadingNum + 1 <= limit[1]) {
83 | if (outline.type == "outline") {
84 | result += ExtractOutline(outline.blocks, outlineHeadingNum >= limit[0]? index + 1 : index)
85 | }else if (outline.type == "NodeHeading") {
86 | result += ExtractOutline(outline.children, outlineHeadingNum >= limit[0]? index + 1 : index);
87 | }
88 | }
89 | })
90 | return result
91 | }
92 |
93 | export async function ListNodeParser(id:string) {
94 | let result = "";
95 | if (id == "") return "";
96 | let request = await getBlockKramdown(id);
97 | console.log("Original\n", request.kramdown);
98 | // 清理样式语法标记
99 | result = request.kramdown.replaceAll(new RegExp(/{:[^}]*}/, "gm"), "");
100 | // 判断是否需要添加中心主题
101 | let firstBullet = result.match(new RegExp(/^\* .*\n/, "gm"));
102 | // 若只有一个根,用根作为中心主题(移除根的无序列表标记* 并使所有行去除开头两个空格)
103 | if (firstBullet && firstBullet.length == 1) {
104 | result = result.replace("* ", "");
105 | result = result.replaceAll(new RegExp(/^ /, "gm"), "");
106 | }else{
107 | // 替代中心主题
108 | result = "-" + "\n" + result;
109 | }
110 | // 清理空行
111 | result = result.replaceAll(new RegExp(/^ *\n/, "gm"), "");
112 | // 转换*标记(*前后必须为空格)
113 | result = result.replaceAll(new RegExp(/(?<= )\*(?= )/, "gm"), "-");
114 | // 转换行开头的*
115 | result = result.replaceAll(new RegExp(/^\*/, "gm"), "-");
116 | console.log(result);
117 | return result;
118 | }
119 |
120 | export const save_xmind = async (str: string) => {
121 | const xmindArrayBuffer = await parseXMindMarkToXMindFile(str)
122 | await save(xmindArrayBuffer, 'result.xmind')
123 | }
124 |
--------------------------------------------------------------------------------
/src/lib/SiYuan.ts:
--------------------------------------------------------------------------------
1 | import { TOKEN } from "@/config"
2 | import { resolve } from "path"
3 | import { ref } from "vue"
4 |
5 | interface ResponseBody {
6 | code: number
7 | data: any
8 | msg: string
9 | }
10 |
11 | async function Request(url: string, data?: any): Promise {
12 | let resData = null
13 | await fetch(url, {
14 | body: JSON.stringify(data),
15 | method: 'POST',
16 | headers: {
17 | Authorization: `Token `,
18 | }
19 | }).then(function (response) { resData = response.json() })
20 | // @ts-ignore
21 | return resData
22 | }
23 |
24 |
25 | async function Apply(response: Promise) {
26 | let r: ResponseBody = await response
27 | if (!r) {
28 | return null
29 | }
30 | return r.code === 0 ? r.data : null
31 | }
32 |
33 | export interface FilesUnderPath {
34 | box: string
35 | files: File[]
36 | path: string
37 | }
38 |
39 | export interface File {
40 | alias: string
41 | bookmark: string
42 | count: number
43 | ctime: number // 创建时间
44 | hCtime: string // 格式化的创建时间
45 | hMtime: string // 相对修改时间
46 | hSize: string // 文件大小
47 | icon: string
48 | id: string
49 | memo: string
50 | mtime: number // 修改时间
51 | name: string
52 | name1: string
53 | path: string // 路径
54 | size: number
55 | sort: number // 排序
56 | subFileCount: number // 子文件数目
57 | }
58 |
59 | export function listDocsByPath(path: string, notebook: string): Promise {
60 | let data = {
61 | path,
62 | notebook,
63 | }
64 | let url = '/api/filetree/listDocsByPath'
65 | let result = Apply(Request(url, data))
66 | return result
67 | }
68 |
69 | export interface StandardResponse {
70 | code: number
71 | data: T
72 | msg: string
73 | }
74 |
75 | export interface NoteBookData {
76 | closed: boolean
77 | icon: string
78 | id: string
79 | name: string
80 | sort: number
81 | }
82 |
83 | export function lsNotebooks(): Promise