add: Knowledge add line
This commit is contained in:
parent
017e6d4721
commit
703a505d59
|
|
@ -1,11 +1,13 @@
|
|||
import AddonEvents from "./events";
|
||||
import AddonViews from "./views";
|
||||
import AddonPrefs from "./prefs";
|
||||
import { Knowledge } from "./knowledge";
|
||||
|
||||
class Knowledge4Zotero {
|
||||
public events: AddonEvents;
|
||||
public views: AddonViews;
|
||||
public prefs: AddonPrefs;
|
||||
public knowledge: Knowledge;
|
||||
|
||||
constructor() {
|
||||
this.events = new AddonEvents(this);
|
||||
|
|
|
|||
|
|
@ -8,9 +8,8 @@ class AddonBase {
|
|||
class EditorMessage {
|
||||
public type: string;
|
||||
public content: {
|
||||
itemID?: string;
|
||||
event?: XULEvent;
|
||||
editorInstance?: EditorInstance
|
||||
editorInstance?: EditorInstance;
|
||||
};
|
||||
constructor(type: string, content: object) {
|
||||
this.type = type;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { AddonBase, EditorMessage } from "./base";
|
||||
import { Knowledge } from "./knowledge";
|
||||
|
||||
class AddonEvents extends AddonBase {
|
||||
constructor(parent: Knowledge4Zotero) {
|
||||
|
|
@ -7,11 +8,12 @@ class AddonEvents extends AddonBase {
|
|||
|
||||
public async onInit() {
|
||||
Zotero.debug("Knowledge4Zotero: init called");
|
||||
this.addNoteInstanceListener();
|
||||
this._Addon.knowledge = new Knowledge(undefined);
|
||||
this.addEditorInstanceListener();
|
||||
this.resetState();
|
||||
}
|
||||
|
||||
public addNoteInstanceListener() {
|
||||
public addEditorInstanceListener() {
|
||||
Zotero.Notes._registerEditorInstance = Zotero.Notes.registerEditorInstance;
|
||||
Zotero.Notes.registerEditorInstance = (instance: EditorInstance) => {
|
||||
Zotero.Notes._registerEditorInstance(instance);
|
||||
|
|
@ -28,7 +30,10 @@ class AddonEvents extends AddonBase {
|
|||
event: string,
|
||||
message: EditorMessage
|
||||
) {
|
||||
let editor: Element = await this._Addon.views.getEditor(instance);
|
||||
await instance._initPromise;
|
||||
let editor: Element = this._Addon.views.getEditor(
|
||||
instance._iframeWindow.document
|
||||
);
|
||||
editor.addEventListener(event, (e: XULEvent) => {
|
||||
message.content.event = e;
|
||||
message.content.editorInstance = instance;
|
||||
|
|
@ -36,6 +41,21 @@ class AddonEvents extends AddonBase {
|
|||
});
|
||||
}
|
||||
|
||||
public async addEditorDocumentEventListener(
|
||||
instance: EditorInstance,
|
||||
event: string,
|
||||
message: EditorMessage
|
||||
) {
|
||||
await instance._initPromise;
|
||||
let doc: Document = instance._iframeWindow.document;
|
||||
|
||||
doc.addEventListener(event, (e: XULEvent) => {
|
||||
message.content.event = e;
|
||||
message.content.editorInstance = instance;
|
||||
this.onEditorEvent(message);
|
||||
});
|
||||
}
|
||||
|
||||
public async onEditorEvent(message: EditorMessage) {
|
||||
Zotero.debug(
|
||||
`Knowledge4Zotero: onEditorEvent\n${message.type}\n${message.content}`
|
||||
|
|
@ -64,21 +84,34 @@ class AddonEvents extends AddonBase {
|
|||
"addToKnowledge",
|
||||
"addToKnowledge",
|
||||
"Add Note Link to Knowledge Workspace",
|
||||
new EditorMessage("addToKnowledge", {})
|
||||
new EditorMessage("addToKnowledge", {
|
||||
itemID: message.content.editorInstance._item.id,
|
||||
})
|
||||
);
|
||||
this.addEditorEventListener(
|
||||
message.content.editorInstance,
|
||||
"click",
|
||||
new EditorMessage("noteEditorClick", {})
|
||||
);
|
||||
if (!message.content.editorInstance._knowledgeSelectionInitialized) {
|
||||
this.addEditorDocumentEventListener(
|
||||
message.content.editorInstance,
|
||||
"selectionchange",
|
||||
new EditorMessage("noteEditorSelectionChange", {})
|
||||
);
|
||||
message.content.editorInstance._knowledgeSelectionInitialized = true;
|
||||
}
|
||||
} else if (message.type === "addToKnowledge") {
|
||||
/*
|
||||
message.content = {
|
||||
editorInstance
|
||||
}
|
||||
*/
|
||||
// TODO: Complete this part
|
||||
Zotero.debug("Knowledge4Zotero: addToKnowledge");
|
||||
this._Addon.knowledge.addLink(
|
||||
-1,
|
||||
message.content.editorInstance._item.id
|
||||
);
|
||||
} else if (message.type === "setMainKnowledge") {
|
||||
/*
|
||||
message.content = {
|
||||
|
|
@ -128,19 +161,58 @@ class AddonEvents extends AddonBase {
|
|||
} else if (message.type === "noteEditorClick") {
|
||||
let el: XUL.Element = message.content.event.target;
|
||||
if (el.children.length !== 0) {
|
||||
// This is not a line element
|
||||
return;
|
||||
}
|
||||
let urlIndex = el.innerHTML.search(/zotero:\/\/note\//g);
|
||||
if (urlIndex >= 0) {
|
||||
let noteID = parseInt(
|
||||
el.innerHTML.substring(urlIndex + "zotero://note/".length)
|
||||
if (
|
||||
el.tagName === "A" &&
|
||||
(el as HTMLLinkElement).href.search(/zotero:\/\/note\//g) >= 0
|
||||
) {
|
||||
let urlIndex = (el as HTMLLinkElement).href.search(
|
||||
/zotero:\/\/note\//g
|
||||
);
|
||||
let note = Zotero.Items.get(noteID);
|
||||
if (note && note.isNote()) {
|
||||
// TODO: Open note
|
||||
Zotero.debug(`Knowledge4Zotero: noteEditorClick ${note.id}`);
|
||||
if (urlIndex >= 0) {
|
||||
let noteID = parseInt(
|
||||
(el as HTMLLinkElement).href.substring(
|
||||
urlIndex + "zotero://note/".length
|
||||
)
|
||||
);
|
||||
let note = Zotero.Items.get(noteID);
|
||||
if (note && note.isNote()) {
|
||||
// TODO: Open note
|
||||
Zotero.debug(`Knowledge4Zotero: noteEditorClick ${note.id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (message.type === "noteEditorSelectionChange") {
|
||||
if (
|
||||
message.content.editorInstance._item.id ===
|
||||
Zotero.Prefs.get("Knowledge4Zotero.mainKnowledgeID")
|
||||
) {
|
||||
// Update current line index
|
||||
let focusNode =
|
||||
message.content.editorInstance._iframeWindow.document.getSelection()
|
||||
.focusNode;
|
||||
if (!focusNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
function getChildIndex(node: Node) {
|
||||
return Array.prototype.indexOf.call(node.parentNode.childNodes, node);
|
||||
}
|
||||
|
||||
// Make sure this is a direct child node of editor
|
||||
while (
|
||||
!focusNode.parentElement.className ||
|
||||
focusNode.parentElement.className.indexOf("primary-editor") === -1
|
||||
) {
|
||||
focusNode = focusNode.parentNode;
|
||||
}
|
||||
|
||||
let currentLineIndex = getChildIndex(focusNode);
|
||||
this._Addon.knowledge.currentLine = currentLineIndex;
|
||||
Zotero.debug(`Knowledge4Zotero: line ${currentLineIndex} selected.`);
|
||||
}
|
||||
} else {
|
||||
Zotero.debug(`Knowledge4Zotero: message not handled.`);
|
||||
}
|
||||
|
|
|
|||
127
src/knowledge.ts
127
src/knowledge.ts
|
|
@ -1,27 +1,104 @@
|
|||
const TreeModel = require("./treemodel");
|
||||
|
||||
class Knowledge {
|
||||
_note: ZoteroItem;
|
||||
currentLine: number;
|
||||
constructor(noteItem: ZoteroItem) {
|
||||
this._note = noteItem;
|
||||
this.currentLine = 0;
|
||||
// this.createKnowledgeItem();
|
||||
}
|
||||
// createKnowledgeItem() {
|
||||
// return;
|
||||
// }
|
||||
|
||||
addNoteLink(noteItem: ZoteroItem) {}
|
||||
|
||||
getOutline(): Node {
|
||||
let parser = Components.classes[
|
||||
"@mozilla.org/xmlextras/domparser;1"
|
||||
].createInstance(Components.interfaces.nsIDOMParser);
|
||||
let doc = parser.parseFromString(this._note.getNote(), "text/html");
|
||||
|
||||
let metadataContainer: Element = doc.querySelector(
|
||||
"body > div[data-schema-version]"
|
||||
getWorkspaceNote() {
|
||||
return Zotero.Items.get(
|
||||
Zotero.Prefs.get("Knowledge4Zotero.mainKnowledgeID")
|
||||
);
|
||||
}
|
||||
|
||||
getLines(): string[] {
|
||||
let note = this.getWorkspaceNote();
|
||||
if (!note) {
|
||||
return [];
|
||||
}
|
||||
let noteText: string = note.getNote();
|
||||
let containerIndex = noteText.search(/<div data-schema-version="8">/g);
|
||||
if (containerIndex != -1) {
|
||||
noteText = noteText.substring(
|
||||
containerIndex + '<div data-schema-version="8">'.length,
|
||||
noteText.length - "</div>".length
|
||||
);
|
||||
}
|
||||
let noteLines: string[] = noteText.split("\n").filter((s) => s);
|
||||
return noteLines;
|
||||
}
|
||||
|
||||
getLineParent(lineIndex: number = -1): TreeModel.Node<object> {
|
||||
if (lineIndex < 0) {
|
||||
lineIndex = this.currentLine;
|
||||
}
|
||||
let nodes = this.getOutlineList();
|
||||
if (!nodes.length || nodes[0].model.lineIndex > lineIndex) {
|
||||
// There is no parent node
|
||||
return undefined;
|
||||
} else if (nodes[nodes.length - 1].model.lineIndex <= lineIndex) {
|
||||
return nodes[nodes.length - 1];
|
||||
} else {
|
||||
for (let i = 0; i < nodes.length - 1; i++) {
|
||||
if (
|
||||
nodes[i].model.lineIndex <= lineIndex &&
|
||||
nodes[i + 1].model.lineIndex > lineIndex
|
||||
) {
|
||||
return nodes[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addLine(text: string, lineIndex: number) {
|
||||
let note = this.getWorkspaceNote();
|
||||
if (!note) {
|
||||
return;
|
||||
}
|
||||
let noteLines = this.getLines();
|
||||
noteLines.splice(lineIndex, 0, text);
|
||||
note.setNote(`<div data-schema-version="8">${noteLines.join("\n")}</div>`);
|
||||
note.saveTx();
|
||||
}
|
||||
|
||||
addLineToParent(text: string, lineIndex: number = -1) {
|
||||
if (lineIndex < 0) {
|
||||
lineIndex = this.currentLine;
|
||||
}
|
||||
let parentNode = this.getLineParent();
|
||||
if (!parentNode) {
|
||||
this.addLine(text, lineIndex);
|
||||
return;
|
||||
}
|
||||
let nodes = this.getOutlineList();
|
||||
let i = 0;
|
||||
for (let node of nodes) {
|
||||
if (node.model.lineIndex === parentNode.model.lineIndex) {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
// Get next header line index
|
||||
i++;
|
||||
if (i >= nodes.length) {
|
||||
i = nodes.length - 1;
|
||||
}
|
||||
// Add line before next header, which is also the end of current parent header
|
||||
this.addLine(text, nodes[i].model.lineIndex);
|
||||
}
|
||||
|
||||
addLink(lineIndex: number, noteID: number) {
|
||||
this.addLineToParent(`<a href="zotero://note/${noteID}" rel="noopener noreferrer nofollow">${Zotero.Items.get(noteID).getNoteTitle()}</a>`, lineIndex);
|
||||
}
|
||||
|
||||
getOutline(): TreeModel.Node<object> {
|
||||
// See http://jnuno.com/tree-model-js
|
||||
let metadataContainer = this.parseNoteHTML();
|
||||
let tree = new TreeModel();
|
||||
/*
|
||||
tree-model/index.js: line 40
|
||||
|
|
@ -60,9 +137,35 @@ class Knowledge {
|
|||
return root;
|
||||
}
|
||||
|
||||
getOutlineList(doFilter: boolean = true): TreeModel.Node<object>[] {
|
||||
return this.getOutline().all(
|
||||
(node) => !doFilter || node.model.lineIndex >= 0
|
||||
);
|
||||
}
|
||||
|
||||
jumpToNote(noteLinke: string) {}
|
||||
|
||||
export() {}
|
||||
|
||||
parseNoteHTML(): Element {
|
||||
let note = this.getWorkspaceNote();
|
||||
if (!note) {
|
||||
return undefined;
|
||||
}
|
||||
let noteText = note.getNote();
|
||||
if (noteText.search(/<div data-schema-version/g) === -1) {
|
||||
noteText = `<div data-schema-version="8">${noteText}\n</div>`;
|
||||
}
|
||||
let parser = Components.classes[
|
||||
"@mozilla.org/xmlextras/domparser;1"
|
||||
].createInstance(Components.interfaces.nsIDOMParser);
|
||||
let doc = parser.parseFromString(noteText, "text/html");
|
||||
|
||||
let metadataContainer: Element = doc.querySelector(
|
||||
"body > div[data-schema-version]"
|
||||
);
|
||||
return metadataContainer;
|
||||
}
|
||||
}
|
||||
|
||||
export { Knowledge };
|
||||
|
|
|
|||
11
src/views.ts
11
src/views.ts
|
|
@ -18,12 +18,8 @@ class AddonViews extends AddonBase {
|
|||
};
|
||||
}
|
||||
|
||||
async getEditor(instance: EditorInstance) {
|
||||
await instance._initPromise;
|
||||
let editor =
|
||||
instance._iframeWindow.document.getElementsByClassName(
|
||||
"primary-editor"
|
||||
)[0];
|
||||
getEditor(_document: Document) {
|
||||
let editor = _document.getElementsByClassName("primary-editor")[0];
|
||||
return editor;
|
||||
}
|
||||
|
||||
|
|
@ -58,7 +54,8 @@ class AddonViews extends AddonBase {
|
|||
}
|
||||
|
||||
async scrollToLine(instance: EditorInstance, lineIndex: number) {
|
||||
let editor = await this.getEditor(instance);
|
||||
await instance._initPromise;
|
||||
let editor = this.getEditor(instance._iframeWindow.document);
|
||||
if (lineIndex > editor.children.length) {
|
||||
lineIndex = editor.children.length - 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,4 +2,5 @@ declare interface Knowledge4Zotero {
|
|||
events: import("../src/events");
|
||||
views: import("../src/view");
|
||||
prefs: import("../src/prefs");
|
||||
knowledge: import("../src/knowledge").Knowledge;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,6 +84,8 @@ declare interface ZoteroItem {
|
|||
isRegularItem: () => boolean;
|
||||
isNote: () => boolean;
|
||||
getNote: () => string;
|
||||
setNote: (string) => void;
|
||||
getNoteTitle: () => string;
|
||||
isAttachment: () => boolean;
|
||||
isAnnotation?: () => boolean;
|
||||
itemTypeID: number;
|
||||
|
|
@ -163,6 +165,7 @@ declare class ReaderObj {
|
|||
}
|
||||
|
||||
declare class EditorInstance {
|
||||
[attr: string]: any;
|
||||
_iframeWindow: XULWindow;
|
||||
_item: ZoteroItem;
|
||||
_initPromise: Promise;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
// Project: https://github.com/joaonuno/tree-model-js
|
||||
// Definitions by: Abhas Bhattacharya <https://github.com/bendtherules>
|
||||
// TypeScript Version: 2.2
|
||||
|
||||
declare class TreeModel {
|
||||
constructor(config?: TreeModel.Config);
|
||||
|
||||
private config: TreeModel.Config;
|
||||
|
||||
parse<T>(model: TreeModel.Model<T>): TreeModel.Node<T>;
|
||||
}
|
||||
|
||||
declare namespace TreeModel {
|
||||
class Node<T> {
|
||||
constructor(config: any, model: Model<T>);
|
||||
|
||||
isRoot(): boolean;
|
||||
hasChildren(): boolean;
|
||||
addChild(child: Node<T>): Node<T>;
|
||||
addChildAtIndex(child: Node<T>, index: number): Node<T>;
|
||||
setIndex(index: number): Node<T>;
|
||||
getPath(): Array<Node<T>>;
|
||||
getIndex(): number;
|
||||
|
||||
walk(options: Options, fn: NodeVisitorFunction<T>, ctx?: object): void;
|
||||
walk(fn: NodeVisitorFunction<T>, ctx?: object): void;
|
||||
|
||||
all(options: Options, fn: NodeVisitorFunction<T>, ctx?: object): Array<Node<T>>;
|
||||
all(fn: NodeVisitorFunction<T>, ctx?: object): Array<Node<T>>;
|
||||
|
||||
first(options: Options, fn: NodeVisitorFunction<T>, ctx?: object): Node<T> | undefined;
|
||||
first(fn: NodeVisitorFunction<T>, ctx?: object): Node<T> | undefined;
|
||||
|
||||
drop(): Node<T>;
|
||||
|
||||
[propName: string]: any;
|
||||
}
|
||||
|
||||
interface Config {
|
||||
/**
|
||||
* The name for the children array property. Default is "children".
|
||||
*/
|
||||
childrenPropertyName?: string;
|
||||
modelComparatorFn?: ComparatorFunction;
|
||||
[propName: string]: any;
|
||||
}
|
||||
|
||||
interface Options {
|
||||
strategy: StrategyName;
|
||||
}
|
||||
|
||||
type StrategyName = "pre" | "post" | "breadth";
|
||||
|
||||
type ComparatorFunction = (left: any, right: any) => boolean;
|
||||
type NodeVisitorFunction<T> = (visitingNode: Node<T>) => boolean;
|
||||
|
||||
type Model<T> = T & { children?: Array<Model<T>> };
|
||||
}
|
||||
|
|
@ -68,6 +68,8 @@ declare class ClassList {
|
|||
|
||||
declare class XULEvent extends Event {
|
||||
public target: XUL.Element;
|
||||
clientX: number;
|
||||
clientY: number;
|
||||
}
|
||||
|
||||
declare class XULWindow extends Window {
|
||||
|
|
|
|||
Loading…
Reference in New Issue