add: mind map

This commit is contained in:
xiangyu 2022-05-06 20:01:21 +08:00
parent 7c66352b7c
commit ef3fb769ce
6 changed files with 2667 additions and 28 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,318 @@
<!DOCTYPE html>
<html lang="en">
<body>
<script src="chrome://__addonRef__/content/lib/js/go.js"></script>
<style>
html,
body,
div {
height: 100%;
width: 100%;
padding: 0;
margin: 0;
}
#canvas {
position:absolute;
height:100%; width:100%;
}
</style>
<div id="allSampleContent" class="p-4 w-full">
<script id="code">
function init() {
window.addEventListener('message', handler, false)
// Since 2.2 you can also author concise templates with method chaining instead of GraphObject.make
// For details, see https://gojs.net/latest/intro/buildingObjects.html
const $ = go.GraphObject.make;
myDiagram = $(go.Diagram, "myDiagramDiv", {
"commandHandler.copiesTree": true,
"commandHandler.copiesParentKey": true,
"commandHandler.deletesTree": true,
"draggingTool.dragsTree": true,
"undoManager.isEnabled": true,
initialAutoScale: go.Diagram.UniformToFill,
layout: $(go.TreeLayout,
{ comparer: go.LayoutVertex.smartComparer })
});
// a node consists of some text with a line shape underneath
myDiagram.nodeTemplate = $(
go.Node,
"Vertical",
{ selectionObjectName: "TEXT" },
$(
go.TextBlock,
{
name: "TEXT",
minSize: new go.Size(30, 15),
editable: true,
},
// remember not only the text string but the scale and the font in the node data
new go.Binding("text", "text").makeTwoWay(),
new go.Binding("scale", "scale").makeTwoWay(),
new go.Binding("font", "font").makeTwoWay()
),
$(
go.Shape,
"LineH",
{
stretch: go.GraphObject.Horizontal,
strokeWidth: 3,
height: 3,
// this line shape is the port -- what links connect with
portId: "",
fromSpot: go.Spot.LeftRightSides,
toSpot: go.Spot.LeftRightSides,
},
new go.Binding("stroke", "brush"),
// make sure links come in from the proper direction and go out appropriately
new go.Binding("fromSpot", "dir", (d) => spotConverter(d, true)),
new go.Binding("toSpot", "dir", (d) => spotConverter(d, false))
),
// remember the locations of each node in the node data
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(
go.Point.stringify
),
// make sure text "grows" in the desired direction
new go.Binding("locationSpot", "dir", (d) =>
spotConverter(d, false)
)
);
// selected nodes show a button for adding children
myDiagram.nodeTemplate.selectionAdornmentTemplate = $(
go.Adornment,
"Spot",
$(
go.Panel,
"Auto",
// this Adornment has a rectangular blue Shape around the selected node
$(go.Shape, { fill: null, stroke: "dodgerblue", strokeWidth: 3 }),
$(go.Placeholder, { margin: new go.Margin(4, 4, 0, 4) })
),
// and this Adornment has a Button to the right of the selected node
$(
"Button",
{
alignment: go.Spot.Right,
alignmentFocus: go.Spot.Left,
click: jumpNode, // define click behavior for this Button in the Adornment
},
$(
go.TextBlock,
"↗️", // the Button content
{ font: "bold 8pt sans-serif" }
)
)
);
// a link is just a Bezier-curved line of the same color as the node to which it is connected
myDiagram.linkTemplate = $(
go.Link,
{
curve: go.Link.Bezier,
fromShortLength: -2,
toShortLength: -2,
selectable: false,
},
$(
go.Shape,
{ strokeWidth: 3 },
new go.Binding("stroke", "toNode", (n) => {
if (n.data.brush) return n.data.brush;
return "black";
}).ofObject()
)
);
myDiagram.addDiagramListener("SelectionMoved", (e) => {
var rootX = myDiagram.findNodeForKey(0).location.x;
myDiagram.selection.each((node) => {
if (node.data.parent !== 0) return; // Only consider nodes connected to the root
var nodeX = node.location.x;
if (rootX < nodeX && node.data.dir !== "right") {
updateNodeDirection(node, "right");
} else if (rootX > nodeX && node.data.dir !== "left") {
updateNodeDirection(node, "left");
}
layoutTree(node);
});
});
// read in the predefined graph using the JSON format data held in the "mySavedModel" textarea
// getData();
window.parent.postMessage({type: "ready"}, "*");
}
function spotConverter(dir, from) {
if (dir === "left") {
return from ? go.Spot.Left : go.Spot.Right;
} else {
return from ? go.Spot.Right : go.Spot.Left;
}
}
function changeTextSize(obj, factor) {
var adorn = obj.part;
adorn.diagram.startTransaction("Change Text Size");
var node = adorn.adornedPart;
var tb = node.findObject("TEXT");
tb.scale *= factor;
adorn.diagram.commitTransaction("Change Text Size");
}
function toggleTextWeight(obj) {
var adorn = obj.part;
adorn.diagram.startTransaction("Change Text Weight");
var node = adorn.adornedPart;
var tb = node.findObject("TEXT");
// assume "bold" is at the start of the font specifier
var idx = tb.font.indexOf("bold");
if (idx < 0) {
tb.font = "bold " + tb.font;
} else {
tb.font = tb.font.slice(idx + 5);
}
adorn.diagram.commitTransaction("Change Text Weight");
}
function updateNodeDirection(node, dir) {
myDiagram.model.setDataProperty(node.data, "dir", dir);
// recursively update the direction of the child nodes
var chl = node.findTreeChildrenNodes(); // gives us an iterator of the child nodes related to this particular node
while (chl.next()) {
updateNodeDirection(chl.value, dir);
}
}
function layoutTree(node) {
if (node.data.key === 0) {
// adding to the root?
layoutAll(); // lay out everything
} else {
// otherwise lay out only the subtree starting at this parent node
var parts = node.findTreeParts();
layoutAngle(parts, node.data.dir === "left" ? 180 : 0);
}
}
function layoutAngle(parts, angle) {
var layout = go.GraphObject.make(go.TreeLayout, {
angle: angle,
arrangement: go.TreeLayout.ArrangementFixedRoots,
nodeSpacing: 5,
layerSpacing: 20,
setsPortSpot: false, // don't set port spots since we're managing them with our spotConverter function
setsChildPortSpot: false,
});
layout.doLayout(parts);
}
function layoutAll() {
var root = myDiagram.findNodeForKey(0);
if (root === null) return;
myDiagram.startTransaction("Layout");
// split the nodes and links into two collections
var rightward = new go.Set(/*go.Part*/);
var leftward = new go.Set(/*go.Part*/);
root.findLinksConnected().each((link) => {
var child = link.toNode;
if (child.data.dir === "left") {
leftward.add(root); // the root node is in both collections
leftward.add(link);
leftward.addAll(child.findTreeParts());
} else {
rightward.add(root); // the root node is in both collections
rightward.add(link);
rightward.addAll(child.findTreeParts());
}
});
// do one layout and then the other without moving the shared root node
layoutAngle(rightward, 0);
layoutAngle(leftward, 180);
myDiagram.commitTransaction("Layout");
}
function getData(){
window.parent.postMessage({type: "getMindMapData"}, "*");
}
function setData(nodes){
const data = {
class: "go.TreeModel",
nodeDataArray: [{ key: 999, text: "Mind Map", parent: undefined }],
};
const colors = []
for (const node of nodes) {
console.log(node.model.link)
const parent = node.parent.model.id === -1? 999:node.parent.model.id;
data.nodeDataArray.push({
key: node.model.id,
text: `${node.model.rank===7?'🔗':''}${node.model.name.slice(0,10)}${node.model.name.length>=10?'...':''}`,
parent: parent,
lineIndex: node.model.lineIndex,
noteLink: node.model.rank===7?node.model.link:'',
brush: go.Brush.randomColor()
});
}
myDiagram.model = go.Model.fromJson(data);
}
function jumpNode(e, obj) {
var adorn = obj.part;
var oldnode = adorn.adornedPart;
var olddata = oldnode.data;
if(olddata.noteLink){
window.parent.postMessage({type: "jumpNote", link: olddata.noteLink}, "*");
}else{
window.parent.postMessage({type: "jumpNode", lineIndex: olddata.lineIndex}, "*");
}
}
function handler(e){
console.log(e)
if(e.data.type === "setMindMapData"){
setData(e.data.nodes)
}
}
window.addEventListener("DOMContentLoaded", init);
window.addEventListener('resize', (e)=>{myDiagram.diagram.layoutDiagram(true)})
</script>
<div id="sample">
<div
id="myDiagramDiv"
style="
width: 100%;
position: relative;
-webkit-tap-highlight-color: rgba(255, 255, 255, 0);
cursor: auto;
font: 13px sans-serif;
overflow: hidden;
"
>
<canvas
tabindex="0"
style="
width: 100%;
height: 100%;
position: absolute;
top: 0px;
left: 0px;
z-index: 2;
user-select: none;
touch-action: none;
cursor: auto;
"
>This text is displayed if your browser does not support the Canvas
HTML element.</canvas
>
</div>
</div>
</body>
</html>

View File

@ -19,8 +19,10 @@
<script src="chrome://zotero/content/note.js" />
<script src="chrome://__addonRef__/content/lib/js/jquery.min.js"></script>
<script src="chrome://__addonRef__/content/lib/js/dx.all.js"></script>
<script src="chrome://__addonRef__/content/lib/js/go.js"></script>
<script type="application/javascript">
Zotero.Knowledge4Zotero.views.$ = $;
Zotero.Knowledge4Zotero.views.go = go;
</script>
<keyset>
@ -30,7 +32,7 @@
<hbox flex="1">
<vbox id="zotero-knowledge-outline" flex="1" width="300" minwidth="250" style="overflow: hidden;">
<html:div class="dx-viewport">
<html:div class="dx-viewport" id="outline-container">
<div class="demo-container">
<div class="form">
<div class="drive-panel">
@ -45,11 +47,18 @@
</div>
</div>
</html:div>
<html:div id="outline-tools" flex="1" height="50" maxheight="50" minheight="50" style="display: flex; flex-flex-direction: row; justify-content: space-between; margin: 0px 30px 0px 30px;">
<html:div id="mindmap-container">
<iframe src="chrome://Knowledge4Zotero/content/mindMap.html" id="mindmapIframe"></iframe>
</html:div>
<html:div id="outline-tools" height="50" maxheight="50" minheight="50" style="display: flex; flex-flex-direction: row; justify-content: space-between; margin: 0px 30px 0px 30px;">
<div class="tooltip">
<div id="outline-selectknowledge"></div>
<span class="tooltiptext">Select Main Knowledge</span>
</div>
<div class="tooltip">
<div id="outline-switchview"></div>
<span class="tooltiptext">Switch Outline/Mind Map View</span>
</div>
<div class="tooltip">
<div id="outline-addafter"></div>
<span class="tooltiptext">Add Heading</span>

View File

@ -111,6 +111,7 @@ const optionsAddon = {
path.join(buildDir, "**/*.rdf"),
path.join(buildDir, "**/*.dtd"),
path.join(buildDir, "**/*.xul"),
path.join(buildDir, "**/*.html"),
path.join(buildDir, "**/*.manifest"),
"update.rdf",
],

View File

@ -42,7 +42,10 @@ class Knowledge extends AddonBase {
this._Addon.views.bindTreeViewResize();
this.setWorkspaceNote("main");
this.currentLine = -1;
this._Addon.views.initKnowledgeWindow(win);
this._Addon.views.switchView(true);
this._Addon.views.buildOutline();
// this._Addon.views.buildMindMap();
}
}
@ -473,6 +476,7 @@ class Knowledge extends AddonBase {
const isLink = lineElement.search(/zotero:\/\/note\//g) !== -1;
if (isHeading || isLink) {
let name = "";
let link = "";
if (isHeading) {
const startIndex = lineElement.search(headerStartReg);
const stopIndex = lineElement.search(headerStopReg);
@ -483,6 +487,8 @@ class Knowledge extends AddonBase {
} else {
name = lineElement.slice(lineElement.search(/">/g) + 2);
name = name.slice(0, name.search(/<\//g));
link = lineElement.slice(lineElement.search(/href="/g) + 6);
link = link.slice(0, link.search(/"/g));
}
while (currentNode.model.rank >= currentRank) {
currentNode = currentNode.parent;
@ -497,6 +503,7 @@ class Knowledge extends AddonBase {
name: name,
lineIndex: i,
endIndex: noteLines.length,
link: link,
});
currentNode.addChild(lastNode);
currentNode = lastNode;
@ -508,7 +515,7 @@ class Knowledge extends AddonBase {
getNoteTreeAsList(
note: ZoteroItem,
filterRoot: boolean = true,
filterLikn: boolean = true
filterLink: boolean = true
): TreeModel.Node<object>[] {
note = note || this.getWorkspaceNote();
if (!note) {
@ -517,7 +524,7 @@ class Knowledge extends AddonBase {
return this.getNoteTree(note).all(
(node) =>
(!filterRoot || node.model.lineIndex >= 0) &&
(!filterLikn || node.model.rank <= 6)
(!filterLink || node.model.rank <= 6)
);
}

View File

@ -4,6 +4,8 @@ class AddonViews extends AddonBase {
progressWindowIcon: object;
editorIcon: object;
$: any;
outlineView: boolean;
_initIframe: typeof Promise;
constructor(parent: Knowledge4Zotero) {
super(parent);
@ -21,7 +23,10 @@ class AddonViews extends AddonBase {
export: `<svg t="1651322116327" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11894" width="24" height="24"><path d="M849.2 599v217H178.5V599c-0.7-23.7-20.1-42.7-44-42.7s-43.3 19-44 42.7v252.5c0 28.9 23.6 52.5 52.5 52.5h741.7c28.9 0 52.5-23.6 52.5-52.5V599c-0.7-23.7-20.1-42.7-44-42.7s-43.3 19-44 42.7z" p-id="11895"></path><path d="M482.7 135.4l-164 164c-17.1 17.1-17.1 45.1 0 62.2s45.1 17.1 62.2 0l85.7-85.7v314.8c0 26 21.3 47.2 47.2 47.2 26 0 47.2-21.3 47.2-47.2V276l85.7 85.7c17.1 17.1 45.1 17.1 62.2 0s17.1-45.1 0-62.2l-164-164c-17.1-17.2-45.1-17.2-62.2-0.1z" p-id="11896"></path></svg>`,
close: `<svg t="1651331457107" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12754" width="24" height="24"><path d="M557.311759 513.248864l265.280473-263.904314c12.54369-12.480043 12.607338-32.704421 0.127295-45.248112-12.512727-12.576374-32.704421-12.607338-45.248112-0.127295L512.127295 467.904421 249.088241 204.063755c-12.447359-12.480043-32.704421-12.54369-45.248112-0.063647-12.512727 12.480043-12.54369 32.735385-0.063647 45.280796l262.975407 263.775299-265.151458 263.744335c-12.54369 12.480043-12.607338 32.704421-0.127295 45.248112 6.239161 6.271845 14.463432 9.440452 22.687703 9.440452 8.160624 0 16.319527-3.103239 22.560409-9.311437l265.216826-263.807983 265.440452 266.240344c6.239161 6.271845 14.432469 9.407768 22.65674 9.407768 8.191587 0 16.352211-3.135923 22.591372-9.34412 12.512727-12.480043 12.54369-32.704421 0.063647-45.248112L557.311759 513.248864z" p-id="12755"></path></svg>`,
openWorkspaceCollectionView: `<svg t="1651317033804" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2432" width="100%" height="100%"><path d="M874.9 459.4c-18.8 0-34 15.2-34 34v355.7c0 18.6-15.5 33.7-34.5 33.7H181.5c-19 0-34.5-15.1-34.5-33.7V232.3c0-18.6 15.5-33.7 34.5-33.7H541c18.8 0 34-15.2 34-34s-15.2-34-34-34H181.5C125 130.6 79 176.2 79 232.3v616.8c0 56 46 101.7 102.5 101.7h624.9c56.5 0 102.5-45.6 102.5-101.7V493.4c0-18.8-15.2-34-34-34z" fill="#b6b6b6" p-id="2433"></path><path d="M885.5 82.7H657.1c-18.8 0-34 15.2-34 34s15.2 34 34 34h169.7L358.5 619.1c-13.3 13.3-13.3 34.8 0 48.1 6.6 6.6 15.3 10 24 10s17.4-3.3 24-10l470-470v169.7c0 18.8 15.2 34 34 34s34-15.2 34-34V141.5c0.1-32.4-26.4-58.8-59-58.8z" fill="#b6b6b6" p-id="2434"></path></svg>`,
switchView: `<svg t="1651813727621" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14171" width="18" height="18"><path d="M1024 733v131c0 53-43 96-96 96h-96c-53 0-96-43-96-96V733c0-53 43-96 96-96 8.8 0 16-7.2 16-16v-77c0-17.7-14.3-32-32-32H560c-8.8 0-16 7.2-16 16v94c0 8.3 6.7 15 15 15h1c53 0 96 43 96 96v131c0 53-43 96-96 96h-96c-53 0-96-43-96-96V733c0-53 43-96 96-96h1c8.3 0 15-6.7 15-15v-94c0-8.8-7.2-16-16-16H208c-17.7 0-32 14.3-32 32v77c0 8.8 7.2 16 16 16 53 0 96 43 96 96v131c0 53-43 96-96 96H96c-53 0-96-43-96-96V733c0-53 43-96 96-96 8.8 0 16-7.2 16-16v-77c0-53 43-96 96-96h256c8.8 0 16-7.2 16-16v-48h-96c-53 0-96-43-96-96V144c0-53 43-96 96-96h256c53 0 96 43 96 96v144c0 53-43 96-96 96h-96v48c0 8.8 7.2 16 16 16h256c53 0 96 43 96 96v77c0 8.8 7.2 16 16 16 53 0 96 43 96 96z" p-id="14172"></path></svg>`,
};
this.outlineView = true;
this._initIframe = Zotero.Promise.defer();
}
getEditor(_document: Document) {
@ -181,13 +186,18 @@ class AddonViews extends AddonBase {
}
if (lineIndex >= indexMap.length) {
lineIndex = indexMap.length - 1;
} else if (indexMap.length < 0) {
} else if (lineIndex < 0) {
lineIndex = 0;
}
lineIndex = indexMap[lineIndex];
if (lineIndex >= editor.children.length) {
lineIndex = editor.children.length - 1;
} else if (lineIndex < 0) {
lineIndex = 0;
}
const mappedIndex = indexMap[lineIndex];
// @ts-ignore
editor.parentNode.scrollTo(0, editor.children[mappedIndex].offsetTop);
editor.parentNode.scrollTo(0, editor.children[lineIndex].offsetTop);
}
addNewKnowledgeButton() {
@ -203,7 +213,7 @@ class AddonViews extends AddonBase {
});
button.setAttribute(
"style",
"list-style-image: url('chrome://Knowledge4Zotero/skin/knowledge.png');"
"list-style-image: url('chrome://Knowledge4Zotero/skin/knowledge@0.5x.png');"
);
addNoteButton.after(button);
}
@ -347,28 +357,103 @@ class AddonViews extends AddonBase {
return reader.annotationItemIDs.length === updateCount;
}
initKnowledgeWindow(_window: Window) {
_window.addEventListener("message", (e) => this.messageHandler(e), false);
}
async messageHandler(e) {
const _window = this._Addon.knowledge.getWorkspaceWindow();
if (e.data.type === "ready") {
this._initIframe.resolve();
} else if (e.data.type === "getMindMapData") {
(
_window.document.getElementById("mindmapIframe") as HTMLIFrameElement
).contentWindow.postMessage(
{
type: "setMindMapData",
nodes: this._Addon.knowledge.getNoteTreeAsList(),
},
"*"
);
} else if (e.data.type === "jumpNode") {
this.scrollToLine(
await this._Addon.knowledge.getWorkspaceEditorInstance(),
e.data.lineIndex
);
} else if (e.data.type === "jumpNote") {
Zotero.debug(e.data);
this._Addon.events.onEditorEvent(
new EditorMessage("onNoteLink", {
params: await this._Addon.knowledge.getNoteFromLink(e.data.link),
})
);
}
}
switchView(status: boolean = undefined) {
if (typeof status === "undefined") {
status = !this.outlineView;
}
const _window = this._Addon.knowledge.getWorkspaceWindow();
const mindmap = _window.document.getElementById("mindmap-container");
const outline = _window.document.getElementById("outline-container");
this.outlineView = status;
if (this.outlineView) {
mindmap.setAttribute("hidden", "hidden");
outline.removeAttribute("hidden");
} else {
outline.setAttribute("hidden", "hidden");
mindmap.removeAttribute("hidden");
}
this.buildOutline(this._Addon.knowledge.getWorkspaceNote());
}
async updateMindMap() {
Zotero.debug("Knowledge4Zotero: updateMindMap");
await this._initIframe;
const _window = this._Addon.knowledge.getWorkspaceWindow();
const iframe = _window.document.getElementById(
"mindmapIframe"
) as HTMLIFrameElement;
iframe.contentWindow.postMessage(
{
type: "setMindMapData",
nodes: this._Addon.knowledge.getNoteTreeAsList(undefined, true, false),
},
"*"
);
}
/*
* Outline Code Start
*/
async buildOutline(note: ZoteroItem) {
this._Addon.knowledge.currentNodeID = -1;
let treeList = this._Addon.knowledge.getNoteTreeAsList(note, true, false);
const treeData = [];
treeList.map((node: TreeModel.Node<object>) => {
treeData.push({
id: String(node.model.id),
name: node.model.name,
rank: node.model.rank,
icon: node.model.rank === 7 ? "textdocument" : undefined,
lineIndex: node.model.lineIndex,
endIndex: node.model.endIndex,
isDirectory: node.hasChildren(),
expanded: true,
parentId:
node.model.rank > 1 ? String(node.parent.model.id) : undefined,
if (this.outlineView) {
this._Addon.knowledge.currentNodeID = -1;
let treeList = this._Addon.knowledge.getNoteTreeAsList(note, true, false);
const treeData = [];
treeList.map((node: TreeModel.Node<object>) => {
treeData.push({
id: String(node.model.id),
name: node.model.name,
rank: node.model.rank,
icon: node.model.rank === 7 ? "textdocument" : undefined,
lineIndex: node.model.lineIndex,
endIndex: node.model.endIndex,
isDirectory: node.hasChildren(),
expanded: true,
parentId:
node.model.rank > 1 ? String(node.parent.model.id) : undefined,
});
});
});
this.$(() => {
this.createTreeView("#treeview", treeData);
this.createSortable("#treeview", "treeData");
});
this.$(() => {
this.createTreeView("#treeview", treeData);
this.createSortable("#treeview", "treeData");
});
} else {
this.updateMindMap();
}
}
createTreeView(selector, items) {
@ -399,6 +484,13 @@ class AddonViews extends AddonBase {
);
},
});
this.$("#outline-switchview").dxButton({
// icon: this.editorIcon["switchView"],
icon: "chart",
onClick: async (e) => {
this.switchView();
},
});
this.$("#outline-addafter").dxButton({
icon: "plus",
onClick: (e) => {
@ -509,6 +601,10 @@ class AddonViews extends AddonBase {
height: `${this.$("window").height() - 130}px`,
width: `${this.$("#zotero-knowledge-outline").width() - 10}px`,
});
this.$("#mindmapIframe").css({
height: `${this.$("window").height() - 58}px`,
width: `${this.$("#zotero-knowledge-outline").width() - 10}px`,
});
}
createSortable(selector, driveName) {
@ -668,6 +764,10 @@ class AddonViews extends AddonBase {
return null;
}
/*
* Outline Code End
*/
showProgressWindow(
header: string,
context: string,