add: outline view
This commit is contained in:
parent
ba6ad6818c
commit
258a076740
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
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -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[
|
||||
|
|
|
|||
190
src/views.ts
190
src/views.ts
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue