diff --git a/addon/chrome/content/icons/section-16.svg b/addon/chrome/content/icons/section-16.svg new file mode 100644 index 00000000..04cfc582 --- /dev/null +++ b/addon/chrome/content/icons/section-16.svg @@ -0,0 +1,15 @@ + + + + + + + + + \ No newline at end of file diff --git a/addon/chrome/content/icons/section-20.svg b/addon/chrome/content/icons/section-20.svg new file mode 100644 index 00000000..fd0b0628 --- /dev/null +++ b/addon/chrome/content/icons/section-20.svg @@ -0,0 +1,15 @@ + + + + + + + + + \ No newline at end of file diff --git a/addon/chrome/content/icons/swap.svg b/addon/chrome/content/icons/swap.svg new file mode 100644 index 00000000..dd83a128 --- /dev/null +++ b/addon/chrome/content/icons/swap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addon/locale/en-US/mainWindow.ftl b/addon/locale/en-US/mainWindow.ftl new file mode 100644 index 00000000..097c86c9 --- /dev/null +++ b/addon/locale/en-US/mainWindow.ftl @@ -0,0 +1,4 @@ +itemPaneSection-header = + .label = Translate +itemPaneSection-sidenav = + .tooltiptext = Translate diff --git a/addon/locale/it-IT/mainWindow.ftl b/addon/locale/it-IT/mainWindow.ftl new file mode 100644 index 00000000..097c86c9 --- /dev/null +++ b/addon/locale/it-IT/mainWindow.ftl @@ -0,0 +1,4 @@ +itemPaneSection-header = + .label = Translate +itemPaneSection-sidenav = + .tooltiptext = Translate diff --git a/addon/locale/zh-CN/mainWindow.ftl b/addon/locale/zh-CN/mainWindow.ftl new file mode 100644 index 00000000..09956d6d --- /dev/null +++ b/addon/locale/zh-CN/mainWindow.ftl @@ -0,0 +1,4 @@ +itemPaneSection-header = + .label = 翻译 +itemPaneSection-sidenav = + .tooltiptext = 翻译 diff --git a/addon/manifest.json b/addon/manifest.json index 35b3d7d1..53ee0ea7 100644 --- a/addon/manifest.json +++ b/addon/manifest.json @@ -13,7 +13,7 @@ "zotero": { "id": "__addonID__", "update_url": "__updateURL__", - "strict_min_version": "6.999", + "strict_min_version": "7.0.0-beta.70", "strict_max_version": "7.0.*" } } diff --git a/src/addon.ts b/src/addon.ts index ef2b8d89..89c26ebf 100644 --- a/src/addon.ts +++ b/src/addon.ts @@ -18,7 +18,7 @@ class Addon { }; panel: { tabOptionId: string; - activePanels: HTMLElement[]; + activePanels: Record void>; windowPanel: Window | null; }; popup: { @@ -47,7 +47,7 @@ class Addon { ztoolkit: createZToolkit(), locale: {}, prefs: { window: null }, - panel: { tabOptionId: "", activePanels: [], windowPanel: null }, + panel: { tabOptionId: "", activePanels: {}, windowPanel: null }, popup: { currentPopup: null }, translate: { selectedText: "", diff --git a/src/hooks.ts b/src/hooks.ts index d0f9f110..031d70d7 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -73,6 +73,11 @@ async function onMainWindowLoad(win: Window): Promise { ]); // Create ztoolkit for every window addon.data.ztoolkit = createZToolkit(); + + (win as any).MozXULElement.insertFTLIfNeeded( + `${config.addonRef}-mainWindow.ftl`, + ); + registerReaderTabPanel(); registerPrefsWindow(); registerMenu(); @@ -87,6 +92,10 @@ async function onMainWindowLoad(win: Window): Promise { async function onMainWindowUnload(win: Window): Promise { ztoolkit.unregisterAll(); + + win.document + .querySelector(`[href="${config.addonRef}-mainWindow.ftl"]`) + ?.remove(); } function onShutdown(): void { diff --git a/src/modules/popup.ts b/src/modules/popup.ts index 436c9e59..aeaee374 100644 --- a/src/modules/popup.ts +++ b/src/modules/popup.ts @@ -63,14 +63,22 @@ export function updateReaderPopup() { audiobox, ); } - translateButton.hidden = task.status !== "waiting"; + if (task.status !== "waiting") { + translateButton.style.display = "none"; + } else { + translateButton.style.removeProperty("display"); + } textarea.hidden = hidePopupTextarea || task.status === "waiting"; textarea.value = task.result || task.raw; textarea.style.fontSize = `${getPref("fontSize")}px`; textarea.style.lineHeight = `${ Number(getPref("lineHeight")) * Number(getPref("fontSize")) }px`; - addToNoteButton.hidden = !ZoteroContextPane.getActiveEditor(); + if (!ZoteroContextPane.activeEditor) { + addToNoteButton.style.display = "none"; + } else { + addToNoteButton.style.removeProperty("display"); + } updatePopupSize(popup, textarea); } @@ -113,9 +121,14 @@ export function buildReaderPopup( ignoreIfExists: true, }, { - tag: "div", + tag: "button", + namespace: "html", id: makeId("translate"), - classList: ["wide-button", `${config.addonRef}-readerpopup`], + classList: [ + "toolbar-button", + "wide-button", + `${config.addonRef}-readerpopup`, + ], properties: { innerHTML: `${SVGIcon}${getString("readerpopup-translate-label")}`, hidden: getPref("enableAuto"), @@ -217,9 +230,17 @@ export function buildReaderPopup( ], }, { - tag: "div", + tag: "button", + namespace: "html", id: makeId("addtonote"), - classList: ["wide-button", `${config.addonRef}-readerpopup`], + classList: [ + "toolbar-button", + "wide-button", + `${config.addonRef}-readerpopup`, + ], + styles: { + marginTop: "8px", + }, properties: { innerHTML: `${SVGIcon}${Zotero.getString("pdfReader.addToNote")}`, }, @@ -229,7 +250,7 @@ export function buildReaderPopup( type: "click", listener: async (ev) => { const noteEditor = - ZoteroContextPane && ZoteroContextPane.getActiveEditor(); + ZoteroContextPane && ZoteroContextPane.activeEditor; if (!noteEditor) { return; } diff --git a/src/modules/tabpanel.ts b/src/modules/tabpanel.ts index 2183ef4d..b55cefef 100644 --- a/src/modules/tabpanel.ts +++ b/src/modules/tabpanel.ts @@ -10,157 +10,82 @@ import { } from "../utils/task"; export function registerReaderTabPanel() { - ztoolkit.ReaderTabPanel.register( - getString("readerpanel-label"), - ( - panel: XUL.TabPanel | undefined, - ownerDeck: XUL.Deck, - ownerWindow: Window, - readerInstance: _ZoteroTypes.ReaderInstance, - ) => { - if (ownerDeck.selectedPanel?.children[0].tagName === "vbox") { - panel = createPanel(ownerDeck, readerInstance._instanceID); - } - panel && buildPanel(panel, readerInstance._instanceID); + Zotero.ItemPaneManager.registerSection({ + paneID: "translate", + pluginID: config.addonID, + header: { + l10nID: `${config.addonRef}-itemPaneSection-header`, + icon: `chrome://${config.addonRef}/content/icons/section-16.svg`, }, - { - selectPanel: getPref("autoFocus") as boolean, + sidenav: { + l10nID: `${config.addonRef}-itemPaneSection-sidenav`, + icon: `chrome://${config.addonRef}/content/icons/section-20.svg`, }, - ).then((tabId) => { - addon.data.panel.tabOptionId = tabId; - }); - new (ztoolkit.getGlobal("MutationObserver"))((_muts) => { - updateTextAreasSize(); - }).observe(document.querySelector("#zotero-context-pane")!, { - attributes: true, - attributeFilter: ["width"], + onInit, + onDestroy, + onRender, + onItemChange, }); - document - .querySelector("#zotero-context-pane") - ?.querySelector("grippy") - ?.addEventListener("click", (ev) => { - updateTextAreasSize(); - }); - updateTextAreasSize(true); } async function openWindowPanel() { - if (addon.data.panel.windowPanel && !addon.data.panel.windowPanel.closed) { - addon.data.panel.windowPanel.close(); - } - const dialogData = { - loadLock: Zotero.Promise.defer(), - }; - const win: Window = ztoolkit.getGlobal("openDialog")( - `chrome://${config.addonRef}/content/standalone.xhtml`, - `${config.addonRef}-standalone`, - `chrome,extrachrome,menubar,resizable=yes,scrollbars,status,dialog=no,${ - getPref("keepWindowTop") ? ",alwaysRaised=yes" : "" - }`, - dialogData, - ); - await dialogData.loadLock.promise; - buildPanel( - win.document.querySelector("#panel-container") as XUL.Box, - "standalone", - ); - win.addEventListener("resize", (ev) => { - updateTextAreaSize(win.document); - }); - buildExtraPanel(win.document.querySelector("#extra-container") as XUL.Box); - updateTextAreaSize(win.document); - addon.data.panel.windowPanel = win; + window.alert("Not implemented yet, please wait for the next update."); + return; + // if (addon.data.panel.windowPanel && !addon.data.panel.windowPanel.closed) { + // addon.data.panel.windowPanel.close(); + // } + // const dialogData = { + // loadLock: Zotero.Promise.defer(), + // }; + // const win: Window = ztoolkit.getGlobal("openDialog")( + // `chrome://${config.addonRef}/content/standalone.xhtml`, + // `${config.addonRef}-standalone`, + // `chrome,extrachrome,menubar,resizable=yes,scrollbars,status,dialog=no,${ + // getPref("keepWindowTop") ? ",alwaysRaised=yes" : "" + // }`, + // dialogData, + // ); + // await dialogData.loadLock.promise; + // // onInit(win.document.querySelector("#panel-container") as XUL.Box); + // buildExtraPanel(win.document.querySelector("#extra-container") as XUL.Box); + // addon.data.panel.windowPanel = win; } export function updateReaderTabPanels() { - ztoolkit.ReaderTabPanel.changeTabPanel(addon.data.panel.tabOptionId, { - selectPanel: getPref("autoFocus") as boolean, - }); - cleanPanels(); - addon.data.panel.activePanels.forEach((panel) => updatePanel(panel)); - if (addon.data.panel.windowPanel && !addon.data.panel.windowPanel.closed) { - updateExtraPanel(addon.data.panel.windowPanel.document); - } - updateTextAreasSize(true); -} - -function createPanel(ownerDeck: XUL.Deck, refID: string) { - const container = ownerDeck.selectedPanel; - container.innerHTML = ""; - ztoolkit.UI.appendElement( - { - tag: "tabbox", - id: `${config.addonRef}-${refID}-extra-tabbox`, - classList: ["zotero-view-tabbox"], - attributes: { - flex: "1", - }, - ignoreIfExists: true, - children: [ - { - tag: "tabs", - classList: ["zotero-editpane-tabs"], - attributes: { - orient: "horizontal", - }, - children: [ - { - tag: "tab", - attributes: { - label: getString("readerpanel-label"), - }, - }, - ], - }, - { - tag: "tabpanels", - classList: ["zotero-view-item"], - attributes: { - flex: "1", - }, - children: [ - { - tag: "tabpanel", - attributes: { - flex: "1", - }, - }, - ], - }, - ], - }, - container, + // ztoolkit.ReaderTabPanel.changeTabPanel(addon.data.panel.tabOptionId, { + // selectPanel: getPref("autoFocus") as boolean, + // }); + Object.values(addon.data.panel.activePanels).forEach((refresh: any) => + refresh(), ); - return container.querySelector("tabpanel") as XUL.TabPanel; + // if (addon.data.panel.windowPanel && !addon.data.panel.windowPanel.closed) { + // updateExtraPanel(addon.data.panel.windowPanel.document); + // } + // updateTextAreasSize(true); } -function buildPanel(panel: HTMLElement, refID: string, force: boolean = false) { - const makeId = (type: string) => `${config.addonRef}-${refID}-panel-${type}`; - const itemID = Zotero.Reader._readers.find( - (reader) => reader._instanceID === refID, - )?._item?.id; - panel.setAttribute("item-id", String(itemID) || ""); - // Manually existance check to avoid unnecessary element creation with ... - if (!force && panel.querySelector(`#${makeId("root")}`)) { - return; - } +function onInit({ + body, + refresh, + item, +}: _ZoteroTypes.ItemPaneManager.SectionInitHookArgs) { + const paneUID = Zotero_Tabs.selectedID; + body.dataset.paneUid = paneUID; + addon.data.panel.activePanels[paneUID] = refresh; + + const makeClass = (type: string) => `${paneUID}-${type}`; + + body.style.display = "flex"; + body.style.flexDirection = "column"; + body.style.gap = "6px"; + ztoolkit.UI.appendElement( { - tag: "vbox", - id: makeId("root"), - classList: [`${config.addonRef}-panel-root`], - attributes: { - flex: "1", - align: "stretch", - }, - styles: { - padding: "8px", - }, - ignoreIfExists: true, + tag: "fragment", children: [ { tag: "hbox", - id: makeId("engine"), + classList: [makeClass("engine")], attributes: { flex: "0", align: "center", @@ -168,7 +93,7 @@ function buildPanel(panel: HTMLElement, refID: string, force: boolean = false) { children: [ { tag: "menulist", - id: makeId("services"), + classList: [makeClass("services")], attributes: { flex: "0", native: "true", @@ -210,11 +135,13 @@ function buildPanel(panel: HTMLElement, refID: string, force: boolean = false) { tag: "button", namespace: "xul", attributes: { - label: `${getString( - "readerpanel-translate-button-label", - )}(${getString("ctrl")} + T)`, + label: getString("readerpanel-translate-button-label"), + tooltiptext: `(${getString("ctrl")} + T)`, flex: "1", }, + styles: { + minWidth: "auto", + }, listeners: [ { type: "click", @@ -222,8 +149,8 @@ function buildPanel(panel: HTMLElement, refID: string, force: boolean = false) { if (!getLastTranslateTask()) { addTranslateTask( ( - panel.querySelector( - `#${makeId( + body.querySelector( + `.${makeClass( getPref("rawResultOrder") ? "resulttext" : "rawtext", @@ -243,18 +170,15 @@ function buildPanel(panel: HTMLElement, refID: string, force: boolean = false) { }, { tag: "hbox", - id: makeId("lang"), + classList: [makeClass("lang")], attributes: { flex: "0", align: "center", }, - styles: { - marginTop: "8px", - }, children: [ { tag: "menulist", - id: makeId("langfrom"), + classList: [makeClass("langfrom")], attributes: { flex: "1", native: "true", @@ -265,6 +189,7 @@ function buildPanel(panel: HTMLElement, refID: string, force: boolean = false) { listener: (e: Event) => { const newValue = (e.target as XUL.MenuList).value; setPref("sourceLanguage", newValue); + const itemID = item?.id; itemID && (addon.data.translate.cachedSourceLanguage[itemID] = newValue); @@ -286,17 +211,21 @@ function buildPanel(panel: HTMLElement, refID: string, force: boolean = false) { ], }, { - tag: "div", + tag: "toolbarbutton", styles: { - paddingLeft: "8px", - paddingRight: "8px", + width: "24px", + height: "24px", + fill: "var(--fill-secondary)", + stroke: "var(--fill-secondary)", + listStyleImage: `url(chrome://${config.addonRef}/content/icons/swap.svg)`, }, - properties: { - innerHTML: "↔️", + attributes: { + style: `width: 24px; height: 24px; fill: var(--fill-secondary); stroke: var(--fill-secondary); -moz-context-properties: fill,fill-opacity,stroke,stroke-opacity; list-style-image: url(chrome://${config.addonRef}/content/icons/swap.svg)`, + tooltiptext: "Swap languages", }, listeners: [ { - type: "click", + type: "command", listener: (ev) => { const langfrom = getPref("sourceLanguage") as string; const langto = getPref("targetLanguage") as string; @@ -309,7 +238,7 @@ function buildPanel(panel: HTMLElement, refID: string, force: boolean = false) { }, { tag: "menulist", - id: makeId("langto"), + classList: [makeClass("langto")], attributes: { flex: "1", native: "true", @@ -339,359 +268,389 @@ function buildPanel(panel: HTMLElement, refID: string, force: boolean = false) { ], }, { - tag: "hbox", - id: makeId("auto"), + tag: "div", + styles: { + borderTop: "var(--material-border)", + }, + }, + { + tag: "editable-text", + namespace: "xul", + classList: [makeClass("rawtext")], attributes: { - flex: "0", - align: "center", + multiline: "true", + placeholder: "Select or type to translate", }, styles: { - marginTop: "8px", + minHeight: "100px", + maxHeight: "calc((100vh - 100px) / 2)", }, - children: [ - { - tag: "div", - styles: { - paddingLeft: "8px", - }, - properties: { - innerHTML: getString("readerpanel-auto-description-label"), - }, - }, - { - tag: "checkbox", - styles: { - paddingLeft: "8px", - }, - id: makeId("autotrans"), - attributes: { - label: getString("readerpanel-auto-selection-label"), - native: "true", - }, - listeners: [ - { - type: "command", - listener: (e: Event) => { - setPref("enableAuto", (e.target as XUL.Checkbox).checked); - addon.hooks.onReaderTabPanelRefresh(); - }, - }, - ], - }, + listeners: [ { - tag: "checkbox", - styles: { - paddingLeft: "8px", - }, - id: makeId("autoannot"), - attributes: { - label: getString("readerpanel-auto-annotation-label"), - native: "true", + type: "change", + listener: (ev) => { + const task = getLastTranslateTask({ + id: body.getAttribute("translate-task-id") || "", + }); + if (!task) { + return; + } + const reverseRawResult = getPref("rawResultOrder"); + if (!reverseRawResult) { + task.raw = (ev.target as HTMLTextAreaElement).value; + } else { + task.result = (ev.target as HTMLTextAreaElement).value; + } + putTranslateTaskAtHead(task.id); }, - listeners: [ - { - type: "command", - listener: (e: Event) => { - setPref( - "enableComment", - (e.target as XUL.Checkbox).checked, - ); - addon.hooks.onReaderTabPanelRefresh(); - }, - }, - ], }, ], }, { - tag: "hbox", - id: makeId("concat"), + tag: "div", styles: { - marginTop: "8px", - }, - attributes: { - flex: "0", - align: "center", + borderTop: "var(--material-border)", }, - children: [ - { - tag: "div", - styles: { - paddingLeft: "8px", - }, - properties: { - innerHTML: getString("readerpanel-concat-description-label"), - }, - }, - { - tag: "checkbox", - styles: { - paddingLeft: "8px", - }, - id: makeId("concat"), - attributes: { - label: `${getString( - "readerpanel-concat-enable-label", - )}/${getString("alt")}`, - native: "true", - }, - listeners: [ - { - type: "command", - listener: (e) => { - addon.data.translate.concatCheckbox = ( - e.target as XUL.Checkbox - ).checked; - addon.hooks.onReaderTabPanelRefresh(); - }, - }, - ], - }, - { - tag: "button", - namespace: "xul", - attributes: { - label: getString("readerpanel-concat-clear-label"), - flex: "0", - }, - listeners: [ - { - type: "click", - listener: (e) => { - const task = getLastTranslateTask(); - if (task) { - task.raw = ""; - task.result = ""; - addon.hooks.onReaderTabPanelRefresh(); - } - }, - }, - ], - }, - ], }, { - tag: "hbox", - id: makeId("raw"), + tag: "editable-text", + namespace: "xul", + classList: [makeClass("resulttext")], attributes: { - flex: "1", - spellcheck: false, + multiline: "true", + placeholder: "Translate result", }, styles: { - marginTop: "8px", + minHeight: "100px", + maxHeight: "calc((100vh - 100px) / 2)", }, - children: [ + listeners: [ { - tag: "textarea", - id: makeId("rawtext"), - styles: { - resize: "none", - fontFamily: "inherit", + type: "change", + listener: (ev) => { + const task = getLastTranslateTask({ + id: body.getAttribute("translate-task-id") || "", + }); + if (!task) { + return; + } + const reverseRawResult = getPref("rawResultOrder"); + if (!reverseRawResult) { + task.result = (ev.target as HTMLTextAreaElement).value; + } else { + task.raw = (ev.target as HTMLTextAreaElement).value; + } + putTranslateTaskAtHead(task.id); }, - listeners: [ - { - type: "input", - listener: (ev) => { - const task = getLastTranslateTask({ - id: panel.getAttribute("translate-task-id") || "", - }); - if (!task) { - return; - } - const reverseRawResult = getPref("rawResultOrder"); - if (!reverseRawResult) { - task.raw = (ev.target as HTMLTextAreaElement).value; - } else { - task.result = (ev.target as HTMLTextAreaElement).value; - } - putTranslateTaskAtHead(task.id); - }, - }, - ], }, ], }, { - tag: "splitter", - id: makeId("splitter"), - attributes: { collapse: "after" }, + tag: "div", styles: { - height: "3px", + borderTop: "var(--material-border)", }, - children: [ - { - tag: "grippy", - }, - ], }, { - tag: "hbox", - id: makeId("result"), - attributes: { - flex: "1", - spellcheck: false, + tag: "div", + styles: { + display: "grid", + gridTemplateColumns: "max-content 1fr", + columnGap: "8px", + rowGap: "2px", + width: "inherit", }, children: [ { - tag: "textarea", - id: makeId("resulttext"), + tag: "div", + classList: [makeClass("auto")], styles: { - resize: "none", - fontFamily: "inherit", + display: "grid", + gridTemplateColumns: "subgrid", + gridColumn: "span 2", }, - listeners: [ + children: [ { - type: "input", - listener: (ev) => { - const task = getLastTranslateTask({ - id: panel.getAttribute("translate-task-id") || "", - }); - if (!task) { - return; - } - const reverseRawResult = getPref("rawResultOrder"); - if (!reverseRawResult) { - task.result = (ev.target as HTMLTextAreaElement).value; - } else { - task.raw = (ev.target as HTMLTextAreaElement).value; - } - putTranslateTaskAtHead(task.id); + tag: "div", + styles: { + display: "flex", + justifyContent: "center", + flexDirection: "column", + color: "var(--fill-secondary)", + }, + properties: { + innerHTML: getString("readerpanel-auto-description-label"), }, }, + { + tag: "div", + styles: { + display: "flex", + flexDirection: "row", + }, + children: [ + { + tag: "checkbox", + classList: [makeClass("autotrans")], + attributes: { + label: getString("readerpanel-auto-selection-label"), + native: "true", + }, + listeners: [ + { + type: "command", + listener: (e: Event) => { + setPref( + "enableAuto", + (e.target as XUL.Checkbox).checked, + ); + addon.hooks.onReaderTabPanelRefresh(); + }, + }, + ], + }, + { + tag: "checkbox", + classList: [makeClass("autoannot")], + attributes: { + label: getString("readerpanel-auto-annotation-label"), + native: "true", + }, + listeners: [ + { + type: "command", + listener: (e: Event) => { + setPref( + "enableComment", + (e.target as XUL.Checkbox).checked, + ); + addon.hooks.onReaderTabPanelRefresh(); + }, + }, + ], + }, + ], + }, ], }, - ], - }, - { - tag: "hbox", - id: makeId("copy"), - attributes: { - flex: "0", - align: "center", - }, - styles: { - marginTop: "8px", - }, - children: [ { tag: "div", - properties: { - innerHTML: getString("readerpanel-copy-description-label"), - }, - }, - { - tag: "button", - namespace: "xul", - attributes: { - label: getString("readerpanel-copy-raw-label"), - flex: "1", + classList: [makeClass("concat")], + styles: { + display: "grid", + gridTemplateColumns: "subgrid", + gridColumn: "span 2", }, - listeners: [ + children: [ { - type: "click", - listener: (e: Event) => { - const task = getLastTranslateTask({ - id: panel.getAttribute("translate-task-id") || "", - }); - if (!task) { - return; - } - new ztoolkit.Clipboard() - .addText(task.raw, "text/unicode") - .copy(); + tag: "div", + styles: { + display: "flex", + justifyContent: "center", + flexDirection: "column", + color: "var(--fill-secondary)", + }, + properties: { + innerHTML: getString("readerpanel-concat-enable-label"), }, }, - ], - }, - { - tag: "button", - namespace: "xul", - attributes: { - label: getString("readerpanel-copy-result-label"), - flex: "1", - }, - listeners: [ { - type: "click", - listener: (e: Event) => { - const task = getLastTranslateTask({ - id: panel.getAttribute("translate-task-id") || "", - }); - if (!task) { - return; - } - new ztoolkit.Clipboard() - .addText(task.result, "text/unicode") - .copy(); + tag: "div", + styles: { + display: "flex", + flexDirection: "row", }, + children: [ + { + tag: "checkbox", + classList: [makeClass("concat")], + attributes: { + label: `${getString( + "readerpanel-concat-enable-label", + )}/${getString("alt")}`, + native: "true", + }, + listeners: [ + { + type: "command", + listener: (e) => { + addon.data.translate.concatCheckbox = ( + e.target as XUL.Checkbox + ).checked; + addon.hooks.onReaderTabPanelRefresh(); + }, + }, + ], + }, + { + tag: "button", + namespace: "xul", + attributes: { + label: getString("readerpanel-concat-clear-label"), + flex: "0", + }, + styles: { + minWidth: "auto", + }, + listeners: [ + { + type: "click", + listener: (e) => { + const task = getLastTranslateTask(); + if (task) { + task.raw = ""; + task.result = ""; + addon.hooks.onReaderTabPanelRefresh(); + } + }, + }, + ], + }, + ], }, ], }, { - tag: "button", - namespace: "xul", - attributes: { - label: getString("readerpanel-copy-both-label"), - flex: "1", + tag: "div", + classList: [makeClass("copy")], + styles: { + display: "grid", + gridTemplateColumns: "subgrid", + gridColumn: "span 2", }, - listeners: [ + children: [ { - type: "click", - listener: (e: Event) => { - const task = getLastTranslateTask({ - id: panel.getAttribute("translate-task-id") || "", - }); - if (!task) { - return; - } - new ztoolkit.Clipboard() - .addText( - `${task.raw}\n----\n${task.result}`, - "text/unicode", - ) - .copy(); + tag: "div", + styles: { + display: "flex", + justifyContent: "center", + flexDirection: "column", + color: "var(--fill-secondary)", }, + properties: { + innerHTML: getString("readerpanel-copy-description-label"), + }, + }, + { + tag: "div", + styles: { + display: "flex", + flexDirection: "row", + }, + children: [ + { + tag: "button", + namespace: "xul", + attributes: { + label: getString("readerpanel-copy-raw-label"), + flex: "1", + }, + styles: { + minWidth: "auto", + }, + listeners: [ + { + type: "click", + listener: (e: Event) => { + const task = getLastTranslateTask({ + id: body.getAttribute("translate-task-id") || "", + }); + if (!task) { + return; + } + new ztoolkit.Clipboard() + .addText(task.raw, "text/unicode") + .copy(); + }, + }, + ], + }, + { + tag: "button", + namespace: "xul", + attributes: { + label: getString("readerpanel-copy-result-label"), + flex: "1", + }, + styles: { + minWidth: "auto", + }, + listeners: [ + { + type: "click", + listener: (e: Event) => { + const task = getLastTranslateTask({ + id: body.getAttribute("translate-task-id") || "", + }); + if (!task) { + return; + } + new ztoolkit.Clipboard() + .addText(task.result, "text/unicode") + .copy(); + }, + }, + ], + }, + { + tag: "button", + namespace: "xul", + attributes: { + label: getString("readerpanel-copy-both-label"), + flex: "1", + }, + styles: { + minWidth: "auto", + }, + listeners: [ + { + type: "click", + listener: (e: Event) => { + const task = getLastTranslateTask({ + id: body.getAttribute("translate-task-id") || "", + }); + if (!task) { + return; + } + new ztoolkit.Clipboard() + .addText( + `${task.raw}\n----\n${task.result}`, + "text/unicode", + ) + .copy(); + }, + }, + ], + }, + ], }, ], }, ], }, { - tag: "hbox", - id: makeId("openwindow"), - styles: { - marginTop: "8px", - }, + tag: "button", + namespace: "xul", attributes: { - flex: "0", - align: "center", + label: getString("readerpanel-openwindow-open-label"), + flex: "1", }, - children: [ + styles: { + minWidth: "auto", + }, + listeners: [ { - tag: "button", - namespace: "xul", - attributes: { - label: getString("readerpanel-openwindow-open-label"), - flex: "1", + type: "click", + listener: (e: Event) => { + openWindowPanel(); }, - listeners: [ - { - type: "click", - listener: (e: Event) => { - openWindowPanel(); - }, - }, - ], }, ], }, ], }, - panel, + body, ); - updatePanel(panel); - updateTextAreaSize(panel); - recordPanel(panel); } function buildExtraPanel(panel: XUL.Box) { @@ -923,27 +882,35 @@ function buildExtraPanel(panel: XUL.Box) { ); } -function updatePanel(panel: HTMLElement) { - const idPrefix = panel - .querySelector(`.${config.addonRef}-panel-root`)! - .id.split("-") - .slice(0, -1) - .join("-"); - const makeId = (type: string) => `${idPrefix}-${type}`; +function onItemChange({ + tabType, + setEnabled, +}: _ZoteroTypes.ItemPaneManager.SectionHookArgs) { + if (tabType !== "reader") { + setEnabled(false); + } + return true; +} + +function onRender({ + body, + item, +}: _ZoteroTypes.ItemPaneManager.SectionHookArgs) { + const makeClass = (type: string) => `${body.dataset.paneUid}-${type}`; const updateHidden = (type: string, pref: string) => { - const elem = panel.querySelector(`#${makeId(type)}`) as XUL.Box; + const elem = body.querySelector(`.${makeClass(type)}`) as XUL.Box; elem.hidden = !getPref(pref) as boolean; }; const setCheckBox = (type: string, checked: boolean) => { - const elem = panel.querySelector(`#${makeId(type)}`) as XUL.Checkbox; + const elem = body.querySelector(`.${makeClass(type)}`) as XUL.Checkbox; elem.checked = checked; }; const setValue = (type: string, value: string) => { - const elem = panel.querySelector(`#${makeId(type)}`) as XUL.Textbox; + const elem = body.querySelector(`.${makeClass(type)}`) as XUL.Textbox; elem.value = value; }; const setTextBoxStyle = (type: string) => { - const elem = panel.querySelector(`#${makeId(type)}`) as XUL.Textbox; + const elem = body.querySelector(`.${makeClass(type)}`) as XUL.Textbox; elem.style.fontSize = `${getPref("fontSize")}px`; elem.style.lineHeight = getPref("lineHeight") as string; }; @@ -952,15 +919,12 @@ function updatePanel(panel: HTMLElement) { updateHidden("lang", "showSidebarLanguage"); updateHidden("auto", "showSidebarSettings"); updateHidden("concat", "showSidebarConcat"); - updateHidden("raw", "showSidebarRaw"); - updateHidden("splitter", "showSidebarRaw"); + updateHidden("rawtext", "showSidebarRaw"); updateHidden("copy", "showSidebarCopy"); setValue("services", getPref("translateSource") as string); - const { fromLanguage, toLanguage } = autoDetectLanguage( - Zotero.Items.get(panel.getAttribute("item-id") as string), - ); + const { fromLanguage, toLanguage } = autoDetectLanguage(item); setValue("langfrom", fromLanguage); setValue("langto", toLanguage); @@ -973,15 +937,12 @@ function updatePanel(panel: HTMLElement) { return; } // For manually update translation task - panel.setAttribute("translate-task-id", lastTask.id); + body.setAttribute("translate-task-id", lastTask.id); const reverseRawResult = getPref("rawResultOrder"); setValue("rawtext", reverseRawResult ? lastTask.result : lastTask.raw); setValue("resulttext", reverseRawResult ? lastTask.raw : lastTask.result); setTextBoxStyle("rawtext"); setTextBoxStyle("resulttext"); - panel - .querySelector(`#${makeId("splitter")}`) - ?.setAttribute("collapse", reverseRawResult ? "after" : "before"); } function updateExtraPanel(container: HTMLElement | Document) { @@ -996,36 +957,8 @@ function updateExtraPanel(container: HTMLElement | Document) { }); } -function updateTextAreaSize( - container: HTMLElement | Document, - noDelay: boolean = false, -) { - const setTimeout = ztoolkit.getGlobal("setTimeout"); - Array.from(container.querySelectorAll("textarea")).forEach((elem) => { - if (noDelay) { - elem.style.width = `${elem.parentElement?.scrollWidth}px`; - return; - } - elem.style.width = "0px"; - setTimeout(() => { - elem.style.width = `${elem.parentElement?.scrollWidth}px`; - }, 0); - }); -} - -function updateTextAreasSize(noDelay: boolean = false) { - cleanPanels(); - addon.data.panel.activePanels.forEach((panel) => - updateTextAreaSize(panel, noDelay), - ); -} - -function recordPanel(panel: HTMLElement) { - addon.data.panel.activePanels.push(panel); -} - -function cleanPanels() { - addon.data.panel.activePanels = addon.data.panel.activePanels.filter( - (elem) => elem.parentElement && (elem as any).ownerGlobal, - ); +function onDestroy(options: any) { + const { body } = options; + const paneUID = body.dataset.paneUid; + delete addon.data.panel.activePanels[paneUID]; }