zotero-better-notes/src/reader/readerViews.ts

349 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* This file contains reader annotation pane code.
*/
const CryptoJS = require("crypto-js");
import Knowledge4Zotero from "../addon";
import AddonBase from "../module";
import { EditorMessage } from "../utils";
class ReaderViews extends AddonBase {
icons: object;
constructor(parent: Knowledge4Zotero) {
super(parent);
this.icons = {
createNote: `<svg t="1651630304116" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14011" width="16" height="16"><path d="M791.30324 369.7c-5 5-6.2 12.7-2.8 18.9 17.5 31.9 27.4 68.5 27.4 107.4 0 56.2-20.7 107.6-54.9 147-4.5 5.1-5.1 12.6-1.8 18.4l39.2 67.9c3.3 5.7 9.6 8.7 16.1 7.8 6-0.8 12.1-1.2 18.3-1.2 70.1 0.5 128 59.7 127.1 129.7-0.9 69.7-57.4 125.9-127.1 126.4-70.9 0.5-128.9-57.1-128.9-128 0-38.1 16.7-72.3 43.1-95.8l-37-64c-4.2-7.3-13.3-10-20.9-6.4-29.3 14.2-62.3 22.2-97.2 22.2-26.7 0-52.3-4.7-76-13.2-7.3-2.6-15.4 0.3-19.3 7l-24.9 43.1c-3.1 5.4-2.8 12.1 0.8 17.2 15 21.2 23.7 47.1 23.5 75.1-0.7 69.5-57.5 126.2-127 126.8-71.6 0.6-129.8-57.7-129.1-129.4 0.8-69.7 58-126.5 127.8-126.6 12 0 23.7 1.6 34.8 4.7 7 2 14.5-1.1 18.2-7.4l21.7-37.6c3.7-6.4 2.5-14.6-2.9-19.6-33.6-31.2-57.5-72.6-67-119.2-1.5-7.5-8-12.9-15.7-12.9h-92c-6.9 0-13.1 4.5-15.2 11.1C232.80324 590.2 184.70324 627 128.00324 627 57.00324 627-0.49676 569.2 0.00324 498.1 0.40324 427.5 58.60324 370.3 129.20324 371c54.2 0.5 100.4 34.8 118.5 82.8C250.00324 460 256.00324 464 262.60324 464h94.1c7.6 0 14.2-5.3 15.7-12.7 11-54.2 41.5-101.3 84-133.6 6.4-4.9 8.2-13.8 4.2-20.8l-2.2-3.8c-3.5-6-10.3-9-17.1-7.7-8.8 1.8-18 2.7-27.4 2.5-69.5-1-126.9-60.1-126-129.6 0.9-70.3 58.4-126.9 129-126.3 69.3 0.6 126 57 127 126.2 0.4 31.6-10.6 60.7-29.3 83.2-4.3 5.2-5 12.5-1.6 18.3l6.6 11.4c3.6 6.2 10.8 9.3 17.7 7.5 17.5-4.4 35.8-6.7 54.6-6.7 52.3 0 100.4 17.9 138.6 48 6.4 5 15.5 4.5 21.2-1.2l24.2-24.2c4.7-4.7 6-11.8 3.3-17.8-7.3-16.1-11.3-34-11.3-52.8 0-70.7 57.3-128 128-128 70.6 0 128 57.4 128 128 0 70.7-57.3 128-128 128-20.7 0-40.2-4.9-57.5-13.6-6.2-3.1-13.7-2-18.7 2.9l-28.4 28.5z" p-id="14012" fill="#ffd400"></path></svg>`,
ocrTex: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><defs><style>.cls-1{fill-opacity: 0;}.cls-2{fill:#ffd400;}</style></defs><rect class="cls-1" width="24" height="24"/><path class="cls-2" d="M9,7.1H2.33L2.14,9.56H2.4c.15-1.77.32-2.14,2-2.14a3.39,3.39,0,0,1,.59,0c.23,0,.23.16.23.41v5.77c0,.37,0,.53-1.15.53H3.61v.34c.45,0,1.56,0,2.06,0s1.64,0,2.09,0v-.34H7.32c-1.15,0-1.15-.16-1.15-.53V7.86c0-.22,0-.37.19-.41a3.9,3.9,0,0,1,.63,0c1.65,0,1.81.37,2,2.14h.27L9,7.1Z"/><path class="cls-2" d="M14.91,14.15h-.27c-.28,1.68-.53,2.48-2.41,2.48H10.78c-.52,0-.54-.08-.54-.44V13.27h1c1.06,0,1.19.35,1.19,1.28h.27v-2.9h-.27c0,.94-.13,1.28-1.19,1.28h-1V10.3c0-.36,0-.44.54-.44h1.41c1.68,0,2,.61,2.14,2.13h.27l-.3-2.46H8.14v.33H8.4c.84,0,.86.12.86.52v5.73c0,.4,0,.52-.86.52H8.14V17h6.31Z"/><path class="cls-2" d="M18.22,10.27l1.5-2.2a1.67,1.67,0,0,1,1.58-.71V7H18.69v.33c.44,0,.68.25.68.5a.37.37,0,0,1-.1.26L18,10,16.61,7.85a.46.46,0,0,1-.07-.16c0-.13.24-.32.7-.33V7c-.37,0-1.18,0-1.59,0s-1,0-1.43,0v.33h.21c.6,0,.81.08,1,.38l2,3-1.79,2.64a1.67,1.67,0,0,1-1.58.73v.34H16.7v-.34c-.5,0-.69-.31-.69-.51s0-.14.11-.26l1.55-2.3,1.73,2.62s.06.09.06.12-.24.32-.72.33v.34c.39,0,1.19,0,1.6,0s1,0,1.42,0v-.34h-.2c-.58,0-.81-.06-1-.4l-2.3-3.49Z"/></svg>`,
};
}
async addReaderAnnotationButton(reader: _ZoteroReaderInstance) {
if (!reader) {
return false;
}
await reader._initPromise;
let updateCount = 0;
const _document = reader._iframeWindow.document;
for (const moreButton of _document.getElementsByClassName("more")) {
if (moreButton.getAttribute("knowledgeinit") === "true") {
updateCount += 1;
continue;
}
moreButton.setAttribute("knowledgeinit", "true");
const createNoteButton = _document.createElement("div");
createNoteButton.setAttribute("style", "margin: 5px;");
createNoteButton.title = "Quick Note";
createNoteButton.innerHTML = this.icons["createNote"];
let annotationWrapper = moreButton;
while (!annotationWrapper.getAttribute("data-sidebar-annotation-id")) {
annotationWrapper = annotationWrapper.parentElement;
}
const itemKey = annotationWrapper.getAttribute(
"data-sidebar-annotation-id"
);
const libraryID = (Zotero.Items.get(reader.itemID) as Zotero.Item)
.libraryID;
const annotationItem = await Zotero.Items.getByLibraryAndKeyAsync(
libraryID,
itemKey
);
createNoteButton.addEventListener("click", async (e) => {
await this.createNoteFromAnnotation(annotationItem, e);
e.preventDefault();
});
createNoteButton.addEventListener("mouseover", (e: XUL.XULEvent) => {
createNoteButton.setAttribute(
"style",
"background: #F0F0F0; margin: 5px;"
);
});
createNoteButton.addEventListener("mouseout", (e: XUL.XULEvent) => {
createNoteButton.setAttribute("style", "margin: 5px;");
});
moreButton.before(createNoteButton);
if (annotationItem.annotationType === "image") {
// Image OCR
const ocrButton = _document.createElement("div");
ocrButton.setAttribute("style", "margin: 5px;");
ocrButton.innerHTML = this.icons["ocrTex"];
ocrButton.title = "OCR LaTex";
ocrButton.addEventListener("click", async (e) => {
await this.OCRImageAnnotation(
(
ocrButton.parentElement.parentElement
.nextSibling as HTMLImageElement
).src,
annotationItem
);
e.preventDefault();
});
ocrButton.addEventListener("mouseover", (e: XUL.XULEvent) => {
ocrButton.setAttribute("style", "background: #F0F0F0; margin: 5px;");
});
ocrButton.addEventListener("mouseout", (e: XUL.XULEvent) => {
ocrButton.setAttribute("style", "margin: 5px;");
});
moreButton.before(ocrButton);
}
updateCount += 1;
}
return reader.annotationItemIDs.length === updateCount;
}
public async buildReaderAnnotationButtons() {
Zotero.debug("Knowledge4Zotero: buildReaderAnnotationButton");
for (const reader of Zotero.Reader._readers) {
Zotero.debug("reader found");
let t = 0;
while (t < 100 && !(await this.addReaderAnnotationButton(reader))) {
await Zotero.Promise.delay(50);
t += 1;
}
}
}
private async createNoteFromAnnotation(
annotationItem: Zotero.Item,
event: MouseEvent
) {
if (annotationItem.annotationComment) {
const text = annotationItem.annotationComment;
let link = this._Addon.NoteParse.parseLinkInText(text);
if (link) {
const note = (await this._Addon.NoteUtils.getNoteFromLink(link)).item;
if (note && note.id) {
await this._Addon.ZoteroEvents.onEditorEvent(
new EditorMessage("onNoteLink", {
params: {
item: note,
infoText: "OK",
forceStandalone: event.shiftKey,
},
})
);
return;
}
}
}
const note: Zotero.Item = new Zotero.Item("note");
note.libraryID = annotationItem.libraryID;
note.parentID = annotationItem.parentItem.parentID;
await note.saveTx();
ZoteroPane.openNoteWindow(note.id);
let editorInstance: Zotero.EditorInstance =
this._Addon.WorkspaceWindow.getEditorInstance(note);
let t = 0;
// Wait for editor instance
while (t < 10 && !editorInstance) {
await Zotero.Promise.delay(500);
t += 1;
editorInstance = this._Addon.WorkspaceWindow.getEditorInstance(note);
}
const renderredTemplate =
await this._Addon.TemplateController.renderTemplateAsync(
"[QuickNoteV4]",
"annotationItem, topItem, noteItem",
[annotationItem, annotationItem.parentItem.parentItem, note]
);
await this._Addon.NoteUtils.addLineToNote(
note,
renderredTemplate,
0,
false,
"before"
);
const tags = annotationItem.getTags();
for (const tag of tags) {
note.addTag(tag.tag, tag.type);
}
await note.saveTx();
annotationItem.annotationComment = `${
annotationItem.annotationComment ? annotationItem.annotationComment : ""
}\nnote link: "${this._Addon.NoteUtils.getNoteLink(note)}"`;
await annotationItem.saveTx();
}
private async OCRImageAnnotation(src: string, annotationItem: Zotero.Item) {
/*
message.content = {
params: { src: string, annotationItem: Zotero.Item }
}
*/
let result: string;
let success: boolean;
const engine = Zotero.Prefs.get("Knowledge4Zotero.OCREngine");
if (engine === "mathpix") {
const xhr = await Zotero.HTTP.request(
"POST",
"https://api.mathpix.com/v3/text",
{
headers: {
"Content-Type": "application/json; charset=utf-8",
app_id: Zotero.Prefs.get("Knowledge4Zotero.OCRMathpix.Appid"),
app_key: Zotero.Prefs.get("Knowledge4Zotero.OCRMathpix.Appkey"),
},
body: JSON.stringify({
src: src,
math_inline_delimiters: ["$", "$"],
math_display_delimiters: ["$$", "$$"],
rm_spaces: true,
}),
responseType: "json",
}
);
console.log(xhr);
if (xhr && xhr.status && xhr.status === 200 && xhr.response.text) {
result = xhr.response.text;
success = true;
} else {
result =
xhr.status === 200 ? xhr.response.error : `${xhr.status} Error`;
success = false;
}
} else if (engine === "xunfei") {
/**
* 1.Dochttps://www.xfyun.cn/doc/words/formula-discern/API.html
* 2.Error codehttps://www.xfyun.cn/document/error-code
* @author iflytek
*/
const config = {
hostUrl: "https://rest-api.xfyun.cn/v2/itr",
host: "rest-api.xfyun.cn",
appid: Zotero.Prefs.get("Knowledge4Zotero.OCRXunfei.APPID"),
apiSecret: Zotero.Prefs.get("Knowledge4Zotero.OCRXunfei.APISecret"),
apiKey: Zotero.Prefs.get("Knowledge4Zotero.OCRXunfei.APIKey"),
uri: "/v2/itr",
};
let date = new Date().toUTCString();
let postBody = getPostBody();
let digest = getDigest(postBody);
const xhr = await Zotero.HTTP.request("POST", config.hostUrl, {
headers: {
"Content-Type": "application/json",
Accept: "application/json,version=1.0",
Host: config.host,
Date: date,
Digest: digest,
Authorization: getAuthStr(date, digest),
},
body: JSON.stringify(postBody),
responseType: "json",
});
if (xhr?.response?.code === 0) {
result = xhr.response.data.region
.filter((r) => r.type === "text")
.map((r) => r.recog.content)
.join(" ")
.replace(/ifly-latex-(begin)?(end)?/g, "$");
console.log(xhr);
success = true;
} else {
result =
xhr.status === 200
? `${xhr.response.code} ${xhr.response.message}`
: `${xhr.status} Error`;
success = false;
}
function getPostBody() {
let digestObj = {
common: {
app_id: config.appid,
},
business: {
ent: "teach-photo-print",
aue: "raw",
},
data: {
image: src.split(",").pop(),
},
};
return digestObj;
}
function getDigest(body) {
return (
"SHA-256=" +
CryptoJS.enc.Base64.stringify(CryptoJS.SHA256(JSON.stringify(body)))
);
}
function getAuthStr(date, digest) {
let signatureOrigin = `host: ${config.host}\ndate: ${date}\nPOST ${config.uri} HTTP/1.1\ndigest: ${digest}`;
let signatureSha = CryptoJS.HmacSHA256(
signatureOrigin,
config.apiSecret
);
let signature = CryptoJS.enc.Base64.stringify(signatureSha);
let authorizationOrigin = `api_key="${config.apiKey}", algorithm="hmac-sha256", headers="host date request-line digest", signature="${signature}"`;
return authorizationOrigin;
}
} else if (engine === "bing") {
const xhr = await Zotero.HTTP.request(
"POST",
"https://www.bing.com/cameraexp/api/v1/getlatex",
{
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
data: src.split(",").pop(),
inputForm: "Image",
clientInfo: { platform: "edge" },
}),
responseType: "json",
}
);
if (xhr && xhr.status && xhr.status === 200 && !xhr.response.isError) {
result = xhr.response.latex
? `$${xhr.response.latex}$`
: xhr.response.ocrText;
success = true;
} else {
result =
xhr.status === 200
? xhr.response.errorMessage
: `${xhr.status} Error`;
success = false;
}
} else {
result = "OCR Engine Not Found";
success = false;
}
if (success) {
annotationItem.annotationComment = `${
annotationItem.annotationComment
? `${annotationItem.annotationComment}\n`
: ""
}${result}`;
await annotationItem.saveTx();
this._Addon.ZoteroViews.showProgressWindow(
"Better Notes OCR",
`OCR Result: ${result}`
);
} else {
this._Addon.ZoteroViews.showProgressWindow(
"Better Notes OCR",
result,
"fail"
);
}
}
}
export default ReaderViews;