zotero-better-notes/src/modules/workspace/tab.ts

406 lines
12 KiB
TypeScript

import { config } from "../../../package.json";
import { ICONS } from "../../utils/config";
import { showHint } from "../../utils/hint";
import { getString } from "../../utils/locale";
import { getPref, setPref } from "../../utils/prefs";
import { waitUtilAsync } from "../../utils/wait";
// TODO: uncouple these imports
import {} from "./content";
import { messageHandler } from "./message";
export const TAB_TYPE = "betternotes";
export function registerWorkspaceTab() {
const tabContainer = document.querySelector("#tab-bar-container");
if (!tabContainer) {
return;
}
tabContainer.removeAttribute("hidden");
const mut = new (ztoolkit.getGlobal("MutationObserver"))((muts) => {
tabContainer.removeAttribute("hidden");
});
mut.observe(tabContainer, {
attributes: true,
attributeFilter: ["hidden"],
});
waitUtilAsync(() =>
Boolean(ztoolkit.getGlobal("ZoteroContextPane")._notifierID),
).then(() => {
addWorkspaceTab();
});
window.addEventListener("message", (e) => messageHandler(e), false);
}
export function unregisterWorkspaceTab() {
addon.data.workspace.tab.id && Zotero_Tabs.close(addon.data.workspace.tab.id);
}
async function addWorkspaceTab() {
const { id, container } = Zotero_Tabs.add({
type: TAB_TYPE,
title: getString("tab.name"),
index: 1,
data: {
itemID: addon.data.workspace.mainId,
},
select: false,
onClose: () => {
setWorkspaceTabStatus(false);
if (addon.data.alive) {
addWorkspaceTab();
}
},
});
await waitUtilAsync(() =>
Boolean(document.querySelector(`.tabs-wrapper .tab[data-id=${id}]`)),
);
const tabElem = document.querySelector(
`.tabs-wrapper .tab[data-id=${id}]`,
) as HTMLDivElement;
tabElem.style.width = "30px";
tabElem.style.minWidth = "30px";
tabElem.style.maxWidth = "30px";
tabElem.style.padding = "0px";
const content = tabElem.querySelector(".tab-name") as HTMLDivElement;
const close = tabElem.querySelector(".tab-close") as HTMLDivElement;
content.style.verticalAlign = "middle";
content.style.width = "20px";
content.style.height = "20px";
content.style.display = "inline";
content.innerHTML = "";
ztoolkit.UI.appendElement(
{
tag: "span",
classList: ["icon-bg"],
styles: {
backgroundImage: `url("chrome://${config.addonRef}/content/icons/favicon.png")`,
},
},
content,
);
close.style.visibility = "hidden";
addon.data.workspace.tab.id = id;
container.setAttribute("workspace-type", "tab");
addon.data.workspace.tab.container = container;
}
function hoverWorkspaceTab(hovered: boolean) {
Array.from(document.querySelectorAll(".tab-toggle")).forEach((elem) => {
(elem as HTMLDivElement).style.visibility = hovered ? "visible" : "hidden";
});
const tabElem = document.querySelector(
`.tabs-wrapper .tab[data-id=${addon.data.workspace.tab.id}]`,
) as HTMLDivElement;
const content = tabElem.querySelector(".tab-name") as HTMLDivElement;
content.removeAttribute("style");
if (hovered) {
if (ztoolkit.isZotero7()) {
content.style["-moz-box-pack" as any] = "start";
} else {
content.style.position = "absolute";
content.style.left = "22px";
}
}
}
function updateWorkspaceTabToggleButton(
type: "outline" | "preview" | "notes",
state: "open" | "collapsed",
) {
const elem = document.querySelector(
`#betternotes-tab-toggle-${type}`,
) as HTMLDivElement;
if (!elem) {
return;
}
if (state !== "collapsed") {
state = "open";
}
elem.innerHTML = ICONS[`workspace_${type}_${state}`];
}
function registerWorkspaceTabPaneObserver() {
const outlineSplitter = document.querySelector(
"#betternotes-workspace-outline-splitter",
);
const outlineMut = new (ztoolkit.getGlobal("MutationObserver"))((muts) => {
updateWorkspaceTabToggleButton(
"outline",
outlineSplitter!.getAttribute("state")! as "open" | "collapsed",
);
});
outlineMut.observe(outlineSplitter!, {
attributes: true,
attributeFilter: ["state"],
});
const previewSplitter = document.querySelector(
"#betternotes-workspace-preview-splitter",
);
const previeweMut = new (ztoolkit.getGlobal("MutationObserver"))((muts) => {
updateWorkspaceTabToggleButton(
"preview",
previewSplitter!.getAttribute("state")! as "open" | "collapsed",
);
});
previeweMut.observe(previewSplitter!, {
attributes: true,
attributeFilter: ["state"],
});
const notesSplitter = document.querySelector("#zotero-context-splitter");
const notesMut = new (ztoolkit.getGlobal("MutationObserver"))((muts) => {
updateWorkspaceTabToggleButton(
"notes",
notesSplitter!.getAttribute("state")! as "open" | "collapsed",
);
});
notesMut.observe(notesSplitter!, {
attributes: true,
attributeFilter: ["state"],
});
}
function isContextPaneInitialized() {
return (
(document.querySelector(".notes-pane-deck")?.childElementCount || 0) > 0
);
}
export async function activateWorkspaceTab() {
if (Zotero_Tabs.selectedType === TAB_TYPE && isContextPaneInitialized()) {
(
document.querySelector("#zotero-tab-toolbar") as XUL.Box
).style.visibility = "collapse";
const toolbar = document.querySelector(
"#zotero-context-toolbar-extension",
) as XUL.Box;
toolbar.style.visibility = "collapse";
toolbar.nextElementSibling?.setAttribute("selectedIndex", "1");
}
if (addon.data.workspace.tab.active) {
ztoolkit.log("workspace tab is already active");
return;
}
setWorkspaceTabStatus(true);
// reset tab style
await waitUtilAsync(() =>
Boolean(
document.querySelector(
`.tabs-wrapper .tab[data-id=${addon.data.workspace.tab.id}]`,
),
),
);
const tabElem = document.querySelector(
`.tabs-wrapper .tab[data-id=${addon.data.workspace.tab.id}]`,
) as HTMLDivElement;
tabElem.removeAttribute("style");
const content = tabElem.querySelector(".tab-name") as HTMLDivElement;
const close = tabElem.querySelector(".tab-close") as HTMLDivElement;
content.removeAttribute("style");
content.append(document.createTextNode(getString("tab.name")));
close.style.removeProperty("visibility");
ztoolkit.UI.insertElementBefore(
{
tag: "fragment",
children: [
{
tag: "div",
id: "betternotes-tab-toggle-outline",
classList: ["tab-close", "tab-toggle"],
styles: {
right: "56px",
},
properties: {
innerHTML: ICONS.workspace_outline_open,
},
listeners: [
{
type: "click",
listener: (ev) => {
addon.hooks.onToggleWorkspacePane(
"outline",
undefined,
addon.data.workspace.tab.container,
);
},
},
],
},
{
tag: "div",
id: "betternotes-tab-toggle-preview",
classList: ["tab-close", "tab-toggle"],
styles: {
right: "40px",
},
properties: {
innerHTML: ICONS.workspace_preview_collapsed,
},
listeners: [
{
type: "click",
listener: (ev) => {
addon.hooks.onToggleWorkspacePane(
"preview",
undefined,
addon.data.workspace.tab.container,
);
},
},
],
},
{
tag: "div",
id: "betternotes-tab-toggle-notes",
classList: ["tab-close", "tab-toggle"],
styles: {
right: "24px",
},
properties: {
innerHTML:
document
.querySelector("#zotero-context-splitter")
?.getAttribute("state") === "open"
? ICONS.workspace_notes_open
: ICONS.workspace_notes_collapsed,
},
listeners: [
{
type: "click",
listener: (ev) => {
if (isContextPaneInitialized()) {
addon.hooks.onToggleWorkspacePane("notes");
return;
}
showHint(getString("workspace.notesPane.hint"));
},
},
],
},
],
},
close,
);
hoverWorkspaceTab(false);
tabElem.addEventListener("mouseenter", () => {
if (Zotero_Tabs.selectedType !== "betternotes") {
return;
}
hoverWorkspaceTab(true);
});
tabElem.addEventListener("mousedown", () => hoverWorkspaceTab(true));
tabElem.addEventListener("mouseleave", () => hoverWorkspaceTab(false));
tabElem.addEventListener("mousedown", async (ev) => {
if (ev.button !== 2) {
return;
}
await Zotero.Promise.delay(300);
const menu = document
.querySelector("#zotero-itemmenu")
?.parentElement?.lastElementChild?.querySelector("menu")
?.querySelector("menupopup")?.lastElementChild;
menu?.addEventListener("click", () => {
addon.hooks.onOpenWorkspace("window");
});
});
// load workspace content
const container = addon.data.workspace.tab.container;
initWorkspaceTabDragDrop(container, tabElem);
addon.hooks.onInitWorkspace(container);
registerWorkspaceTabPaneObserver();
}
export function deActivateWorkspaceTab() {
if (!isContextPaneInitialized()) {
return;
}
(
document.querySelector("#zotero-tab-toolbar") as XUL.Box
).style.removeProperty("visibility");
const toolbar = document.querySelector(
"#zotero-context-toolbar-extension",
) as XUL.Box;
toolbar.style.removeProperty("visibility");
}
function setWorkspaceTabStatus(status: boolean) {
addon.data.workspace.tab.active = status;
setPref("workspace.tab.active", status);
}
function initWorkspaceTabDragDrop(
container?: XUL.Box,
tabElem?: HTMLDivElement,
) {
if (!container) {
return;
}
const rect = tabElem?.getBoundingClientRect();
ztoolkit.UI.appendElement(
{
tag: "div",
id: "bn-workspace-tab-drop",
styles: {
background: "#252526",
opacity: "0.6",
width: "100%",
height: "100px",
position: "fixed",
left: "0px",
top: `${rect?.bottom}px`,
textAlign: "center",
display: "flex",
visibility: "hidden",
zIndex: "65535",
},
properties: {
hidden: true,
ondrop: (ev: DragEvent) => {
addon.hooks.onOpenWorkspace("window");
},
ondragenter: (ev: DragEvent) => {
ev.preventDefault();
ev.stopPropagation();
dropElem.style.opacity = "0.9";
if (ev.dataTransfer) {
ev.dataTransfer.dropEffect = "move";
}
},
ondragover: (ev: DragEvent) => {
ev.preventDefault();
ev.stopPropagation();
},
ondragleave: (ev: DragEvent) => {
ev.preventDefault();
ev.stopPropagation();
dropElem.style.opacity = "0.6";
},
},
children: [
{
tag: "div",
styles: {
margin: "auto",
textAlign: "center",
color: "#fff",
},
properties: {
innerHTML: getString("tab.openInWindow"),
},
},
],
enableElementRecord: false,
},
container,
);
const dropElem = container.querySelector(
"#bn-workspace-tab-drop",
) as HTMLDivElement;
tabElem?.addEventListener("dragstart", (ev) => {
dropElem.style.visibility = "visible";
});
tabElem?.addEventListener("dragend", (ev) => {
dropElem.style.visibility = "hidden";
});
}