OcttKB/Wiki-OcttKB/plugins/echarts/$__plugins_Gk0Wk_echarts_ad...

8 lines
18 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[
{
"title": "$:/plugins/Gk0Wk/echarts/addons/TheBrain.ts",
"text": "/* eslint-disable max-lines */\nimport type { IParseTreeNode } from 'tiddlywiki';\nimport type { IScriptAddon } from '../../scriptAddon';\n\nconst colors = [\n '#5470c6',\n '#91cc75',\n '#fac858',\n '#ee6666',\n '#73c0de',\n '#3ba272',\n '#fc8452',\n '#9a60b4',\n '#ea7ccc',\n];\nconst CategoriesEn = [\n 'Focusing',\n 'History',\n 'Link To',\n 'Backlink From',\n 'Tag To',\n 'Tag By',\n 'Transclude',\n].map((name, index) => ({\n name,\n itemStyle: { color: colors[index % colors.length] },\n}));\nconst CategoriesZh = [\n '聚焦',\n '历史',\n '链接',\n '反链',\n '标签',\n '作为标签',\n '嵌套',\n].map((name, index) => ({\n name,\n itemStyle: { color: colors[index % colors.length] },\n}));\nconst attributes = new Set<string>([\n 'focussedTiddler',\n 'levels',\n 'graphTitle',\n 'aliasField',\n 'excludeFilter',\n 'previewDelay',\n]);\nconst getPlatteColor = (name: string) =>\n $tw.wiki.renderText(\n 'text/plain',\n 'text/vnd.tiddlywiki',\n `<$transclude tiddler={{$:/palette}} index=\"${name}\"><$transclude tiddler=\"$:/palettes/Vanilla\" index=\"${name}\"><$transclude tiddler=\"$:/config/DefaultColourMappings/${name}\"/></$transclude></$transclude>`,\n {},\n );\n\nconst findIcon = (title: string) => {\n const fields = $tw.wiki.getTiddler(title)?.fields;\n if (!fields?.icon) {\n return undefined;\n }\n const iconFields = $tw.wiki.getTiddler(fields.icon as string)?.fields;\n if (!iconFields) {\n if (/^https?:\\/\\//.test(fields.icon as string)) {\n return `image://${fields.icon as string}`;\n }\n return undefined;\n }\n if (iconFields._canonical_uri) {\n return `image://${iconFields._canonical_uri}`;\n } else if (iconFields.title.startsWith('$:/core/images/')) {\n return undefined;\n } else {\n return `image://data:${iconFields.type};base64,${iconFields.text}`;\n }\n};\nconst getAliasOrTitle = (\n tiddlerTitle: string,\n aliasField: string | undefined,\n): [string, boolean] => {\n if (aliasField === undefined || aliasField === 'title') {\n return [tiddlerTitle, Boolean($tw.wiki.getTiddler(tiddlerTitle))];\n }\n const tiddler = $tw.wiki.getTiddler(tiddlerTitle);\n if (tiddler) {\n const aliasValue = tiddler.fields[aliasField];\n return [\n typeof aliasValue === 'string'\n ? $tw.wiki.renderText('text/plain', 'text/vnd.tiddlywiki', aliasValue, {\n variables: { currentTiddler: tiddlerTitle },\n })\n : tiddlerTitle,\n true,\n ];\n } else {\n return [tiddlerTitle, false];\n }\n};\n\ninterface ITheBrainState {\n currentlyFocused?: string;\n historyTiddlers: string[];\n viewingTiddlers: Set<string>;\n focusing?: string;\n}\n\nconst TheBrainAddon: IScriptAddon<ITheBrainState> = {\n onMount: (myChart, attributes) => {\n myChart.on('click', { dataType: 'node' }, (event: any) => {\n new $tw.Story().navigateTiddler(event.data.name);\n });\n return {\n historyTiddlers: [],\n viewingTiddlers: new Set(),\n focusing: attributes.focussedTiddler,\n };\n },\n shouldUpdate: (\n { viewingTiddlers, focusing, currentlyFocused },\n changedTiddlers,\n changedAttributes,\n ) => {\n return (\n Object.keys(changedTiddlers).some(title => viewingTiddlers.has(title)) ||\n Object.keys(changedAttributes).some(attribute =>\n attributes.has(attribute),\n ) ||\n (focusing === undefined &&\n $tw.wiki.getTiddlerText('$:/temp/focussedTiddler') !== currentlyFocused)\n );\n },\n // eslint-disable-next-line complexity\n onUpdate: (\n myCharts,\n state,\n addonAttributes: {\n focussedTiddler?: string;\n levels?: number;\n graphTitle?: string;\n aliasField?: string;\n excludeFilter?: string;\n previewDelay?: string;\n },\n ) => {\n /** 参数focussedTiddler 是图的中央节点 */\n let focussedTiddler =\n addonAttributes.focussedTiddler ||\n $tw.wiki.getTiddlerText('$:/temp/focussedTiddler')!;\n state.viewingTiddlers.clear();\n state.focusing = addonAttributes.focussedTiddler;\n state.currentlyFocused = focussedTiddler;\n if (!focussedTiddler) {\n return;\n }\n state.viewingTiddlers.add(focussedTiddler);\n if ($tw.wiki.getTiddler(focussedTiddler)?.fields['draft.of']) {\n focussedTiddler = $tw.wiki.getTiddler(focussedTiddler)!.fields[\n 'draft.of'\n ] as string;\n }\n const nodes: any[] = [];\n const edges: any[] = [];\n const ifChinese =\n $tw.wiki.getTiddlerText('$:/language')?.includes('zh') === true;\n /** 参数levels 指定图向外展开几级 */\n let levels = Number(addonAttributes.levels);\n if (Number.isNaN(levels)) {\n levels = 1;\n }\n levels = Math.max(levels, 0);\n /** 参数graphTitle 指定右下角显示的标题 */\n const graphTitle =\n addonAttributes.graphTitle || (ifChinese ? '聚焦' : 'Focusing Map');\n /** 参数aliasField 用于指定展示为节点标题的字段,例如 caption */\n const aliasField =\n addonAttributes.aliasField === ''\n ? undefined\n : addonAttributes.aliasField;\n /** 参数excludeFilter 用于排除部分节点 */\n const excludeFilter =\n addonAttributes.excludeFilter === ''\n ? undefined\n : $tw.wiki.compileFilter(\n addonAttributes.excludeFilter ?? '[prefix[$:/]]',\n );\n const nodeMap: Map<string, boolean> = new Map();\n\n // 聚焦点\n nodes.push({\n name: focussedTiddler,\n // fixed: true,\n category: 0,\n label: {\n formatter: getAliasOrTitle(focussedTiddler, aliasField)[0],\n fontWeight: 'bold',\n fontSize: '15px',\n },\n symbol: findIcon(focussedTiddler),\n symbolSize: 15,\n select: {\n disabled: true,\n },\n itemStyle: {\n opacity: 1,\n borderColor: `${colors[0]}66`,\n borderWidth: 15,\n },\n isTag: false,\n tooltip: {\n show: false,\n },\n });\n\n // 初始化:当前关注的 Tiddler\n let tiddlerQueue = [focussedTiddler];\n if (excludeFilter) {\n const tiddlers = new Set<string>(tiddlerQueue);\n for (const excluded of excludeFilter.call($tw.wiki, tiddlerQueue)) {\n tiddlers.delete(excluded);\n }\n tiddlerQueue = Array.from(tiddlers);\n }\n nodeMap.set(focussedTiddler, true);\n nodeMap.set('', false);\n\n const tryPush = (\n title: string,\n node: (label: string, exist: boolean) => any,\n edge: (exist: boolean) => any,\n ) => {\n if (excludeFilter && excludeFilter.call($tw.wiki, [title]).length > 0) {\n return false;\n }\n const nodeState = nodeMap.get(title);\n const [label, exist] =\n nodeState === undefined\n ? getAliasOrTitle(title, aliasField)\n : ['', nodeState];\n if (nodeState === undefined) {\n nodes.push(node(label, exist));\n nodeMap.set(title, exist);\n if (exist) {\n tiddlerQueue.push(title);\n }\n }\n edges.push(edge(exist));\n return exist;\n };\n\n // 广搜 levels 层\n while (tiddlerQueue.length && levels-- > 0) {\n const tiddlers = tiddlerQueue;\n tiddlerQueue = [];\n for (const tiddler of tiddlers) {\n // 链接\n for (const linksTo of $tw.wiki.getTiddlerLinks(tiddler)) {\n tryPush(\n linksTo,\n (label, exist) => ({\n name: linksTo,\n label: { formatter: label },\n itemStyle: { opacity: exist ? 1 : 0.65 },\n symbol: findIcon(linksTo),\n category: 2,\n isTag: false,\n }),\n exist => ({\n source: tiddler,\n target: linksTo,\n lineStyle: {\n color: colors[2],\n type: exist ? 'solid' : 'dashed',\n },\n }),\n );\n }\n // 反链\n for (const backlinksFrom of $tw.wiki.getTiddlerBacklinks(tiddler)) {\n tryPush(\n backlinksFrom,\n (label, exist) => ({\n name: backlinksFrom,\n label: { formatter: label },\n itemStyle: { opacity: exist ? 1 : 0.65 },\n symbol: findIcon(backlinksFrom),\n category: 3,\n isTag: false,\n }),\n exist => ({\n source: backlinksFrom,\n target: tiddler,\n lineStyle: {\n color: colors[3],\n type: exist ? 'solid' : 'dashed',\n },\n }),\n );\n }\n // 标签\n for (const tag of $tw.wiki.getTiddler(focussedTiddler)?.fields?.tags ??\n []) {\n tryPush(\n tag,\n (label, exist) => ({\n name: tag,\n label: { formatter: label },\n itemStyle: { opacity: exist ? 1 : 0.65 },\n symbol: findIcon(tag),\n category: 4,\n isTag: true,\n }),\n exist => ({\n source: tiddler,\n target: tag,\n lineStyle: {\n color: colors[4],\n type: exist ? 'solid' : 'dashed',\n },\n }),\n );\n }\n // 作为标签\n for (const tagBy of $tw.wiki.getTiddlersWithTag(tiddler)) {\n tryPush(\n tagBy,\n (label, exist) => ({\n name: tagBy,\n label: { formatter: label },\n itemStyle: { opacity: exist ? 1 : 0.65 },\n symbol: findIcon(tagBy),\n category: 5,\n isTag: false,\n }),\n exist => ({\n source: tagBy,\n target: tiddler,\n lineStyle: {\n color: colors[5],\n type: exist ? 'solid' : 'dashed',\n },\n }),\n );\n }\n // 嵌入\n const tiddler_ = $tw.wiki.getTiddler(tiddler);\n if (tiddler_) {\n const type = tiddler_.fields.type || 'text/vnd.tiddlywiki';\n if (type === 'text/vnd.tiddlywiki' || type === 'text/x-markdown') {\n const transcluded: Set<string> = new Set();\n const findTransclude = (children: IParseTreeNode[]) => {\n const { length } = children;\n for (let i = 0; i < length; i++) {\n const node = children[i];\n if (node.type === 'tiddler') {\n const title = node.attributes!.tiddler?.value as\n | string\n | undefined;\n if (title) {\n transcluded.add(title);\n }\n } else if (Array.isArray((node as any).children)) {\n findTransclude((node as any).children);\n }\n }\n };\n findTransclude($tw.wiki.parseTiddler(tiddler).tree);\n // eslint-disable-next-line max-depth\n for (const transcludeTiddler of transcluded) {\n tryPush(\n transcludeTiddler,\n (label, exist) => ({\n name: transcludeTiddler,\n label: { formatter: label },\n itemStyle: { opacity: exist ? 1 : 0.65 },\n symbol: findIcon(transcludeTiddler),\n category: 6,\n isTag: false,\n }),\n exist => ({\n source: tiddler,\n target: transcludeTiddler,\n lineStyle: {\n color: colors[6],\n type: exist ? 'solid' : 'dashed',\n },\n }),\n );\n }\n }\n }\n }\n }\n\n // 历史路径\n let nextTiddler = focussedTiddler;\n const historyMap: Set<string> = new Set();\n for (let index = state.historyTiddlers.length - 2; index >= 0; index--) {\n const tiddlerTitle = state.historyTiddlers[index];\n if (\n historyMap.has(tiddlerTitle) ||\n tiddlerTitle === nextTiddler ||\n tiddlerTitle.startsWith('$:/')\n ) {\n continue;\n }\n tryPush(\n tiddlerTitle,\n (label, exist) => ({\n name: tiddlerTitle,\n label: { formatter: label, fontSize: '10px' },\n category: 1,\n symbol: findIcon(tiddlerTitle),\n symbolSize: 3,\n itemStyle: { opacity: exist ? 0.65 : 0.4 },\n isTag: false,\n }),\n // eslint-disable-next-line @typescript-eslint/no-loop-func\n exist => ({\n source: tiddlerTitle,\n target: nextTiddler,\n lineStyle: {\n color: colors[1],\n type: exist ? 'dashed' : 'dotted',\n opacity: 0.5,\n },\n }),\n );\n nextTiddler = tiddlerTitle;\n }\n\n // 更新历史\n const historyIndex = state.historyTiddlers.indexOf(focussedTiddler);\n if (historyIndex > -1) {\n state.historyTiddlers.splice(historyIndex, 1);\n }\n state.historyTiddlers.push(focussedTiddler);\n state.historyTiddlers.slice(-10);\n\n let lastTitle = '';\n let cache: Element[] | undefined;\n const cachedTooltipFormatter = ({\n data: { name, isTag },\n dataType,\n }: {\n data: { name: string; isTag: boolean };\n dataType: string;\n }) => {\n if (dataType !== 'node') {\n return [];\n }\n if (name !== lastTitle || !cache) {\n const container = $tw.utils.domMaker('div', {\n style: {\n maxWidth: '40vw',\n maxHeight: '50vh',\n overflowY: 'auto',\n whiteSpace: 'normal',\n },\n class: 'gk0wk-echarts-thebrain-popuptiddler-container',\n });\n if (isTag) {\n const ul = $tw.utils.domMaker('ul', {});\n const tiddlers = $tw.wiki.getTiddlersWithTag(name);\n const len = tiddlers.length;\n for (let i = 0; i < len; i++) {\n const tiddler = tiddlers[i];\n const li = $tw.utils.domMaker('li', {});\n const a = $tw.utils.domMaker('a', {\n text: tiddler,\n class:\n 'tc-tiddlylink tc-tiddlylink-resolves tc-popup-handle tc-popup-absolute',\n style: {\n cursor: 'pointer',\n },\n });\n // eslint-disable-next-line @typescript-eslint/no-loop-func\n a.addEventListener('click', () =>\n new $tw.Story().navigateTiddler(tiddler),\n );\n li.appendChild(a);\n ul.appendChild(li);\n }\n cache = [ul];\n } else {\n // 不可以直接 renderText, 那种是 headless 渲染\n $tw.wiki\n .makeWidget(\n $tw.wiki.parseTiddler(\n '$:/plugins/Gk0Wk/echarts/addons/TheBrainPopup',\n ),\n {\n document,\n parseAsInline: true,\n variables: { currentTiddler: name },\n } as any,\n )\n .render(container, null);\n cache = [\n container,\n $tw.utils.domMaker('style', {\n innerHTML: `.gk0wk-echarts-thebrain-popuptiddler-container::-webkit-scrollbar {display: none;} .gk0wk-echarts-thebrain-popuptiddler-container .tc-tiddler-controls { display: none; }`,\n }),\n ];\n }\n lastTitle = name;\n }\n return cache;\n };\n\n let previewDelay = Number(addonAttributes.previewDelay || '1000');\n if (!Number.isSafeInteger(previewDelay)) {\n previewDelay = -1;\n }\n myCharts.setOption({\n backgroundColor: 'transparent',\n legend: [\n {\n data: (ifChinese ? CategoriesZh : CategoriesEn).map(a => {\n return a.name;\n }),\n icon: 'circle',\n },\n ],\n title: {\n text: graphTitle,\n show: true,\n top: 'bottom',\n left: 'right',\n },\n toolbox: {\n show: true,\n left: 0,\n bottom: 0,\n feature: {\n restore: {},\n saveAsImage: {},\n },\n },\n tooltip: {\n position: 'top',\n formatter: cachedTooltipFormatter,\n triggerOn: previewDelay >= 0 ? 'mousemove' : 'none',\n enterable: true,\n showDelay: Math.max(0, previewDelay),\n hideDelay: 800,\n confine: true,\n textStyle: {\n color: 'inherit',\n fontFamily: 'inherit',\n fontSize: 'inherit',\n },\n appendToBody: true,\n backgroundColor: getPlatteColor('page-background'),\n borderColor: getPlatteColor('very-muted-foreground'),\n },\n series: [\n {\n name: graphTitle,\n type: 'graph',\n layout: 'force',\n top: 0,\n bottom: 0,\n left: 0,\n right: 0,\n height: '100%',\n width: '100%',\n nodes,\n edges,\n categories: ifChinese ? CategoriesZh : CategoriesEn,\n roam: true,\n draggable: true,\n zoom: 4,\n label: {\n position: 'right',\n show: true,\n backgroundColor: 'transparent',\n },\n labelLayout: {\n moveOverlap: true,\n },\n force: {\n repulsion: 50,\n },\n cursor: 'pointer',\n symbolSize: 6,\n edgeSymbol: ['none', 'arrow'],\n edgeSymbolSize: [0, 5],\n lineStyle: {\n width: 1,\n opacity: 0.75,\n curveness: 0.15,\n },\n itemStyle: {\n opacity: 0.9,\n },\n },\n ],\n } as any);\n },\n};\n\nexport default TheBrainAddon;\n/* eslint-enable max-lines */\n",
"Modern.TiddlyDev#IncludeSource": "true",
"type": "application/javascript"
}
]