add: Knowledge add line

This commit is contained in:
xiangyu 2022-04-29 16:03:01 +08:00
parent 017e6d4721
commit 703a505d59
9 changed files with 271 additions and 34 deletions

View File

@ -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);

View File

@ -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;

View File

@ -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.`);
}

View File

@ -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 };

View File

@ -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;
}

1
typing/addon.d.ts vendored
View File

@ -2,4 +2,5 @@ declare interface Knowledge4Zotero {
events: import("../src/events");
views: import("../src/view");
prefs: import("../src/prefs");
knowledge: import("../src/knowledge").Knowledge;
}

3
typing/global.d.ts vendored
View File

@ -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;

58
typing/treemodel.d.ts vendored Normal file
View File

@ -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>> };
}

2
typing/xul.d.ts vendored
View File

@ -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 {