add: outline view

This commit is contained in:
xiangyu 2022-05-02 01:07:14 +08:00
parent ba6ad6818c
commit 258a076740
12 changed files with 23457 additions and 20 deletions

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -3,6 +3,8 @@
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
<?xml-stylesheet href="chrome://zotero/skin/overlay.css" type="text/css"?>
<?xml-stylesheet href="chrome://zotero-platform/content/overlay.css"?>
<?xml-stylesheet href="chrome://__addonRef__/skin/workspace.css" type="text/css"?>
<?xml-stylesheet href="chrome://__addonRef__/content/lib/css/dx.light.css" type="text/css"?>
<!DOCTYPE window [
<!ENTITY % zoteroDTD SYSTEM "chrome://zotero/locale/zotero.dtd">
@ -11,22 +13,14 @@
%knowledgeDTD;
]>
<window id="zotero-knowledge-workspace" orient="vertical" width="1000" height="350" title="&zotero.__addonRef__.workspace.title;" persist="screenX screenY width height" windowtype="zotero:knowledgeWorkspace" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<window id="zotero-knowledge-workspace" orient="vertical" width="1000" height="350" title="&zotero.__addonRef__.workspace.title;" persist="screenX screenY width height" windowtype="zotero:knowledgeWorkspace" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
<script src="chrome://zotero/content/include.js" />
<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 type="application/javascript">
window.addEventListener(
"load",
function (e) {
let vbox = document.getElementById('zotero-knowledge-outline');
vbox.width = 250;
vbox.minWidth = 250;
vbox.maxWidth = 250;
},
false
);
Zotero.Knowledge4Zotero.views.$ = $;
</script>
<keyset>
@ -35,8 +29,22 @@
<command id="cmd_close" oncommand="window.close();" />
<hbox flex="1">
<vbox id="zotero-knowledge-outline" flex="1" width="250" maxWidth="250" minWidth="250">
<!-- Outline Here -->
<vbox id="zotero-knowledge-outline" flex="1" width="250" maxwidth="250" minwidth="250">
<html:div class="dx-viewport">
<div class="demo-container">
<div class="form">
<div class="drive-panel">
<div class="drive-header dx-treeview-item">
<div class="dx-treeview-item-content">
<i class="dx-icon dx-icon-hierarchy"></i>
<span>Note Outline</span>
</div>
</div>
<div id="treeview"></div>
</div>
</div>
</div>
</html:div>
</vbox>
<splitter id="outline-splitter" collapse="before">
<grippy></grippy>

View File

@ -0,0 +1,35 @@
.form {
display: flex;
}
.form > div {
display: inline-block;
vertical-align: top;
}
#treeview {
margin-top: 10px;
}
.drive-header {
min-height: auto;
padding: 0;
cursor: default;
}
.drive-panel {
padding: 20px 15px;
font-size: 115%;
font-weight: bold;
border-right: 1px solid rgba(165, 165, 165, 0.4);
height: 100%;
}
.drive-panel:last-of-type {
border-right: none;
}
ul,
li {
display: list-item;
}

View File

@ -22,12 +22,15 @@
},
"homepage": "https://github.com/windingwind/Knowledge4Zotero#readme",
"dependencies": {
"@syncfusion/ej2-base": "^20.1.50",
"@syncfusion/ej2-navigations": "^20.1.51",
"compressing": "^1.5.1",
"esbuild": "^0.14.34",
"replace-in-file": "^6.3.2",
"tree-model": "^1.0.7"
},
"devDependencies": {
"@types/jquery": "^3.5.14",
"release-it": "^14.14.0"
}
}

34
src/devexpress.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -35,6 +35,7 @@ class Knowledge extends AddonBase {
this.workspaceWindow = win;
await this.waitWorkspaceReady();
this.setWorkspaceNote("main");
this._Addon.views.buildOutline(this.getWorkspaceNote());
}
}
@ -113,7 +114,7 @@ class Knowledge extends AddonBase {
return [];
}
let noteText: string = note.getNote();
let containerIndex = noteText.search(/<div data-schema-version="8">/g);
let containerIndex = noteText.search(/data-schema-version="8">/g);
if (containerIndex != -1) {
noteText = noteText.substring(
containerIndex + '<div data-schema-version="8">'.length,
@ -124,6 +125,20 @@ class Knowledge extends AddonBase {
return noteLines;
}
setLinesToNote(note: ZoteroItem, noteLines: string[]) {
note = note || this.getWorkspaceNote();
if (!note) {
return [];
}
let noteText: string = note.getNote();
let containerIndex = noteText.search(/data-schema-version="8">/g);
let noteHead = noteText.substring(0, containerIndex);
note.setNote(
`${noteHead}data-schema-version="8">${noteLines.join("\n")}</div>`
);
note.saveTx();
}
getLineParentInNote(
note: ZoteroItem,
lineIndex: number = -1
@ -156,8 +171,7 @@ class Knowledge extends AddonBase {
}
let noteLines = this.getLinesInNote(note);
noteLines.splice(lineIndex, 0, text);
note.setNote(`<div data-schema-version="8">${noteLines.join("\n")}</div>`);
note.saveTx();
this.setLinesToNote(note, noteLines);
}
addSubLineToNote(note: ZoteroItem, text: string, lineIndex: number = -1) {
@ -279,8 +293,7 @@ class Knowledge extends AddonBase {
let newLines = lines
.slice(0, targetIndex)
.concat(movedLines, lines.slice(targetIndex));
note.setNote(`<div data-schema-version="8">${newLines.join("\n")}</div>`);
note.saveTx();
this.setLinesToNote(note, newLines);
}
getNoteTree(note: ZoteroItem): TreeModel.Node<object> {
@ -327,6 +340,8 @@ class Knowledge extends AddonBase {
lastNode = tree.parse({
id: id++,
rank: currentRank,
// @ts-ignore
name: lineElement.innerText,
lineIndex: i,
endIndex: metadataContainer.children.length,
});
@ -505,7 +520,7 @@ class Knowledge extends AddonBase {
return undefined;
}
let noteText = note.getNote();
if (noteText.search(/<div data-schema-version/g) === -1) {
if (noteText.search(/data-schema-version/g) === -1) {
noteText = `<div data-schema-version="8">${noteText}\n</div>`;
}
let parser = Components.classes[

View File

@ -3,6 +3,7 @@ import { AddonBase, EditorMessage } from "./base";
class AddonViews extends AddonBase {
progressWindowIcon: object;
editorIcon: object;
$: any;
constructor(parent: Knowledge4Zotero) {
super(parent);
@ -163,6 +164,195 @@ class AddonViews extends AddonBase {
.children[0].before(treeRow);
}
async buildOutline(note: ZoteroItem) {
let treeList = this._Addon.knowledge.getNoteTreeAsList(note);
const treeData = [];
treeList.map((node: TreeModel.Node<object>) => {
treeData.push({
id: String(node.model.id),
name: node.model.name,
isDirectory: node.hasChildren(),
expanded: true,
parentId:
node.model.rank > 1 ? String(node.parent.model.id) : undefined,
});
});
Zotero.debug(treeList);
Zotero.debug(treeData);
this.$(() => {
this.createTreeView("#treeview", treeData);
this.createSortable("#treeview", "treeData");
});
}
createTreeView(selector, items) {
// @ts-ignore
this.$(selector).dxTreeView({
items,
expandNodesRecursive: false,
dataStructure: "plain",
width: 220,
displayExpr: "name",
});
}
createSortable(selector, driveName) {
// @ts-ignore
this.$(selector).dxSortable({
filter: ".dx-treeview-item",
group: "shared",
data: driveName,
allowDropInsideItem: true,
allowReordering: true,
onDragChange: (e) => {
if (e.fromComponent === e.toComponent) {
const $nodes = e.element.find(".dx-treeview-node");
const isDragIntoChild =
$nodes.eq(e.fromIndex).find($nodes.eq(e.toIndex)).length > 0;
if (isDragIntoChild) {
e.cancel = true;
}
}
},
onDragEnd: (e) => {
if (e.fromComponent === e.toComponent && e.fromIndex === e.toIndex) {
return;
}
const fromTreeView = this.getTreeView();
const toTreeView = this.getTreeView();
const fromNode = this.findNode(fromTreeView, e.fromIndex);
const toNode = this.findNode(toTreeView, this.calculateToIndex(e));
if (
e.dropInsideItem &&
toNode !== null &&
!toNode.itemData.isDirectory
) {
return;
}
const fromTopVisibleNode = this.getTopVisibleNode(fromTreeView);
const toTopVisibleNode = this.getTopVisibleNode(toTreeView);
const fromItems = fromTreeView.option("items");
const toItems = toTreeView.option("items");
this.moveNode(fromNode, toNode, fromItems, toItems, e.dropInsideItem);
fromTreeView.option("items", fromItems);
toTreeView.option("items", toItems);
fromTreeView.scrollToItem(fromTopVisibleNode);
toTreeView.scrollToItem(toTopVisibleNode);
},
});
}
getTreeView() {
// @ts-ignore
return this.$("#treeview").dxTreeView("instance");
}
calculateToIndex(e) {
if (e.fromComponent !== e.toComponent || e.dropInsideItem) {
return e.toIndex;
}
return e.fromIndex >= e.toIndex ? e.toIndex : e.toIndex + 1;
}
findNode(treeView, index) {
const nodeElement = treeView.element().find(".dx-treeview-node")[index];
if (nodeElement) {
return this.findNodeById(
treeView.getNodes(),
nodeElement.getAttribute("data-item-id")
);
}
return null;
}
findNodeById(nodes, id) {
for (let i = 0; i < nodes.length; i += 1) {
if (nodes[i].itemData.id === id) {
return nodes[i];
}
if (nodes[i].children) {
const node = this.findNodeById(nodes[i].children, id);
if (node != null) {
return node;
}
}
}
return null;
}
moveNode(fromNode, toNode, fromItems, toItems, isDropInsideItem) {
const fromIndex = this.findIndex(fromItems, fromNode.itemData.id);
fromItems.splice(fromIndex, 1);
const toIndex =
toNode === null || isDropInsideItem
? toItems.length
: this.findIndex(toItems, toNode.itemData.id);
Zotero.debug(fromNode.itemData);
Zotero.debug(toItems[toIndex]);
this._Addon.events.onEditorEvent(
new EditorMessage("moveOutlineTitle", {
params: {
fromID: fromNode.itemData.id,
toID: toNode ? toNode.itemData.id : -1,
},
})
);
toItems.splice(toIndex, 0, fromNode.itemData);
this.moveChildren(fromNode, fromItems, toItems);
if (isDropInsideItem) {
fromNode.itemData.parentId = toNode.itemData.id;
} else {
fromNode.itemData.parentId =
toNode != null ? toNode.itemData.parentId : undefined;
}
}
moveChildren(node, fromItems, toItems) {
if (!node.itemData.isDirectory) {
return;
}
node.children.forEach((child) => {
if (child.itemData.isDirectory) {
this.moveChildren(child, fromItems, toItems);
}
const fromIndex = this.findIndex(fromItems, child.itemData.id);
fromItems.splice(fromIndex, 1);
toItems.splice(toItems.length, 0, child.itemData);
});
}
findIndex(array, id) {
const idsArray = array.map((elem) => elem.id);
return idsArray.indexOf(id);
}
getTopVisibleNode(component) {
const treeViewElement = component.element().get(0);
const treeViewTopPosition = treeViewElement.getBoundingClientRect().top;
const nodes = treeViewElement.querySelectorAll(".dx-treeview-node");
for (let i = 0; i < nodes.length; i += 1) {
const nodeTopPosition = nodes[i].getBoundingClientRect().top;
if (nodeTopPosition >= treeViewTopPosition) {
return nodes[i];
}
}
return null;
}
showProgressWindow(
header: string,
context: string,