refactor: template picker

Use picker window to replace the PromptManager from toolkit
This commit is contained in:
windingwind 2024-10-07 21:05:44 +02:00
parent afab600f53
commit 98fa74ed89
12 changed files with 291 additions and 94 deletions

View File

@ -0,0 +1,19 @@
dialog {
-moz-window-dragging: drag;
max-height: 700px;
}
.viewport-container {
-moz-window-dragging: no-drag;
}
.viewport {
width: 100%;
}
#table-container {
width: 100%;
height: 100%;
overflow: auto;
background: var(--material-background);
}

View File

@ -0,0 +1,58 @@
<?xml version="1.0"?>
<!-- prettier-ignore -->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<!-- prettier-ignore -->
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
<!-- prettier-ignore -->
<?xml-stylesheet href="chrome://zotero-platform/content/zotero.css" type="text/css"?>
<!-- prettier-ignore -->
<?xml-stylesheet href="chrome://__addonRef__/content/styles/templatePicker.css" type="text/css"?>
<!-- prettier-ignore -->
<!DOCTYPE window>
<window
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
id="bn-note-picker"
data-l10n-id="title"
windowtype="__addonRef__-templatePicker"
persist="screenX screenY width height sizemode"
style="min-width: 20em"
drawintitlebar-platforms="mac"
>
<xul:linkset>
<html:link rel="localization" href="browser/menubar.ftl" />
<html:link rel="localization" href="browser/browserSets.ftl" />
<html:link rel="localization" href="toolkit/global/textActions.ftl" />
<html:link rel="localization" href="zotero.ftl" />
<html:link rel="localization" href="__addonRef__-templatePicker.ftl" />
</xul:linkset>
<xul:commandset id="mainCommandSet">
<xul:command id="cmd_close" oncommand="window.close();" />
</xul:commandset>
<xul:keyset id="mainKeyset">
<xul:key
id="key_close"
data-l10n-id="close-shortcut"
command="cmd_close"
modifiers="accel"
reserved="true"
/>
</xul:keyset>
<script src="chrome://zotero/content/include.js"></script>
<script src="chrome://zotero/content/titlebar.js"></script>
<script src="resource://zotero/require.js"></script>
<script src="chrome://zotero/content/customElements.js"></script>
<script src="chrome://__addonRef__/content/scripts/customElements.js"></script>
<script src="chrome://__addonRef__/content/scripts/templatePicker.js"></script>
<dialog buttons="accept, cancel">
<hbox class="viewport-container">
<hbox id="list-container" class="viewport list-viewport">
<html:div id="table-container"></html:div>
</hbox>
</hbox>
</dialog>
</window>

10
package-lock.json generated
View File

@ -35,7 +35,7 @@
"unist-util-visit": "^5.0.0",
"unist-util-visit-parents": "^6.0.1",
"yamljs": "^0.3.0",
"zotero-plugin-toolkit": "^4.0.4"
"zotero-plugin-toolkit": "^4.0.6"
},
"devDependencies": {
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
@ -13588,14 +13588,14 @@
}
},
"node_modules/zotero-plugin-toolkit": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/zotero-plugin-toolkit/-/zotero-plugin-toolkit-4.0.4.tgz",
"integrity": "sha512-FjuyVt6q5uPmQiYCkT9LjvAMpDBAvPfVGsYAhFNN6tTJbiE9YsHIl2XZIfinEAIVAqYd+YPglXaK1pw/TUNuFQ==",
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/zotero-plugin-toolkit/-/zotero-plugin-toolkit-4.0.6.tgz",
"integrity": "sha512-juxIrSrUYTxk+efQJAH7OpfQHSboErMd0Ygu2eZs/xKA1177XlCYhe0sdxlTxI8Y/4UGtRwLicThUddaOfArMQ==",
"dependencies": {
"zotero-types": "^2.2.0"
},
"engines": {
"node": ">=16"
"node": ">=18"
}
},
"node_modules/zotero-types": {

View File

@ -56,7 +56,7 @@
"unist-util-visit": "^5.0.0",
"unist-util-visit-parents": "^6.0.1",
"yamljs": "^0.3.0",
"zotero-plugin-toolkit": "^4.0.4"
"zotero-plugin-toolkit": "^4.0.6"
},
"devDependencies": {
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",

View File

@ -1,4 +1,3 @@
import { Prompt, PromptManager } from "zotero-plugin-toolkit";
import { VirtualizedTableHelper } from "zotero-plugin-toolkit";
import { LargePrefHelper } from "zotero-plugin-toolkit";
@ -72,7 +71,6 @@ class Addon {
worker?: Worker;
};
imageCache: Record<number, string>;
readonly prompt?: Prompt;
hint: {
silent: boolean;
};
@ -120,9 +118,6 @@ class Addon {
},
relation: {},
imageCache: {},
get prompt() {
return new PromptManager().prompt;
},
hint: {
silent: false,
},

View File

@ -81,6 +81,7 @@ import {
updateNoteLinkRelation,
} from "./utils/relation";
import { getWorkspaceByTabID, getWorkspaceByUID } from "./utils/workspace";
import { getString } from "./utils/locale";
const workspace = {
getWorkspaceByTabID,
@ -178,6 +179,10 @@ const relation = {
getAnnotationByLinkTarget,
};
const utils = {
getString,
};
export default {
workspace,
sync,
@ -188,4 +193,5 @@ export default {
editor,
note,
relation,
utils,
};

View File

@ -0,0 +1,133 @@
import { VirtualizedTableHelper } from "zotero-plugin-toolkit";
import { config } from "../../package.json";
document.addEventListener("DOMContentLoaded", (ev) => {
init();
});
document.addEventListener("dialogaccept", () => accept());
const args = window.arguments[0] as any;
const templateData = args.templates;
const multiSelect = args.multiSelect;
let tableHelper: VirtualizedTableHelper;
function init() {
args._initPromise.resolve();
initTable();
}
function accept() {
const selected = tableHelper.treeInstance.selection.selected;
args.selected = Array.from(selected).map(
(index: number) => templateData[index],
);
}
const getString = (Zotero[config.addonRef] as typeof addon).api.utils.getString;
function initTable() {
tableHelper = new VirtualizedTableHelper(window)
.setContainerId("table-container")
.setProp({
id: "templates-table",
// Do not use setLocale, as it modifies the Zotero.Intl.strings
// Set locales directly to columns
columns: [
{
dataKey: "type",
label: "templateEditor-templateType",
width: 60,
fixedWidth: true,
},
{
dataKey: "name",
label: "templateEditor-templateName",
fixedWidth: false,
},
].map((column) =>
Object.assign(column, {
label: getString(column.label),
}),
),
showHeader: true,
multiSelect: multiSelect,
staticColumns: true,
disableFontSizeScaling: true,
})
.setProp("getRowCount", () => templateData.length)
.setProp("getRowData", getRowData)
.setProp("getRowString", (index) => templateData[index] || "")
.setProp("renderItem", (index, selection, oldElem, columns) => {
let div;
if (oldElem) {
div = oldElem;
div.innerHTML = "";
} else {
div = document.createElement("div");
div.className = "row";
}
div.classList.toggle("selected", selection.isSelected(index));
div.classList.toggle("focused", selection.focused == index);
const rowData = getRowData(index);
for (const column of columns) {
const span = document.createElement("span");
// @ts-ignore
span.className = `cell ${column?.className}`;
const cellData = rowData[column.dataKey as keyof typeof rowData];
span.textContent = cellData;
if (column.dataKey === "type") {
span.style.backgroundColor = getRowLabelColor(cellData);
span.style.borderRadius = "4px";
span.style.paddingInline = "4px";
span.style.marginInline = "2px -2px";
span.style.textAlign = "center";
span.textContent = getString(
"templateEditor-templateDisplayType",
cellData,
);
}
div.append(span);
}
return div;
})
.render();
}
function getRowData(index: number) {
const rowData = templateData[index];
if (!rowData) {
return {
name: "",
type: "unknown",
};
}
let templateType = "unknown";
let templateDisplayName = rowData;
if (rowData.toLowerCase().startsWith("[item]")) {
templateType = "item";
templateDisplayName = rowData.slice(6);
} else if (rowData.toLowerCase().startsWith("[text]")) {
templateType = "text";
templateDisplayName = rowData.slice(6);
}
return {
name: templateDisplayName,
type: templateType,
};
}
function getRowLabelColor(type: string) {
switch (type) {
case "system":
return "var(--accent-yellow)";
case "item":
return "var(--accent-green)";
case "text":
return "var(--accent-azure)";
default:
return "var(--accent-red)";
}
}

View File

@ -23,10 +23,7 @@ import {
syncAnnotationNoteTags,
} from "./modules/annotationNote";
import { setSyncing, callSyncing } from "./modules/sync/hooks";
import {
showTemplatePicker,
updateTemplatePicker,
} from "./modules/template/picker";
import { showTemplatePicker } from "./modules/template/picker";
import { showImageViewer } from "./modules/imageViewer";
import { showExportNoteOptions } from "./modules/export/exportWindow";
import { showSyncDiff } from "./modules/sync/diffWindow";
@ -241,8 +238,6 @@ const onSyncing = callSyncing;
const onShowTemplatePicker = showTemplatePicker;
const onUpdateTemplatePicker = updateTemplatePicker;
const onImportTemplateFromClipboard = importTemplateFromClipboard;
const onRefreshTemplatesInNote = refreshTemplatesInNote;
@ -280,7 +275,6 @@ export default {
onInitWorkspace,
onSyncing,
onShowTemplatePicker,
onUpdateTemplatePicker,
onImportTemplateFromClipboard,
onRefreshTemplatesInNote,
onShowImageViewer,

View File

@ -34,10 +34,9 @@ function initTemplates() {
const templateKeys = getTemplateKeys();
for (const defaultTemplate of addon.api.template.DEFAULT_TEMPLATES) {
if (!templateKeys.includes(defaultTemplate.name)) {
setTemplate(defaultTemplate, false);
setTemplate(defaultTemplate);
}
}
addon.hooks.onUpdateTemplatePicker();
}
function getTemplateKeys(): string[] {
@ -52,27 +51,15 @@ function getTemplateText(keyName: string): string {
return addon.data.template.data?.getValue(keyName) || "";
}
function setTemplate(
template: NoteTemplate,
updatePrompt: boolean = true,
): void {
function setTemplate(template: NoteTemplate): void {
addon.data.template.data?.setValue(template.name, template.text);
if (updatePrompt) {
addon.hooks.onUpdateTemplatePicker();
}
}
function removeTemplate(
keyName: string | undefined,
updatePrompt: boolean = true,
): void {
function removeTemplate(keyName: string | undefined): void {
if (!keyName) {
return;
}
addon.data.template.data?.deleteKey(keyName);
if (updatePrompt) {
addon.hooks.onUpdateTemplatePicker();
}
}
function importTemplateFromClipboard(text?: string) {

View File

@ -1,78 +1,57 @@
import { Prompt } from "zotero-plugin-toolkit";
import { addLineToNote } from "../../utils/note";
import { getString } from "../../utils/locale";
import { openTemplatePicker } from "../../utils/templatePicker";
export { updateTemplatePicker, showTemplatePicker };
export { showTemplatePicker };
function showTemplatePicker(
async function showTemplatePicker(
mode: "insert",
data?: { noteId?: number; lineIndex?: number },
): void;
function showTemplatePicker(
): Promise<void>;
async function showTemplatePicker(
mode: "create",
data?: {
noteType?: "standalone" | "item";
parentItemId?: number;
topItemIds?: number[];
},
): void;
function showTemplatePicker(mode: "export", data?: Record<string, never>): void;
function showTemplatePicker(): void;
function showTemplatePicker(
): Promise<void>;
async function showTemplatePicker(
mode: "export",
data?: Record<string, never>,
): Promise<void>;
async function showTemplatePicker(): Promise<void>;
async function showTemplatePicker(
mode: typeof addon.data.template.picker.mode = "insert",
data: Record<string, any> = {},
) {
if (addon.data.prompt) {
addon.data.template.picker.mode = mode;
addon.data.template.picker.data = data;
addon.data.prompt.promptNode.style.display = "flex";
addon.data.prompt.showCommands(
addon.data.prompt.commands.filter(
(cmd) => cmd.label === "BNotes Template",
),
);
addon.data.template.picker.mode = mode;
addon.data.template.picker.data = data;
const selected = await openTemplatePicker();
if (!selected.length) {
return;
}
const name = selected[0];
await handleTemplateOperation(name);
}
function updateTemplatePicker() {
ztoolkit.Prompt.unregisterAll();
const templateKeys = addon.api.template.getTemplateKeys();
ztoolkit.Prompt.register(
templateKeys
.filter(
(template) =>
!addon.api.template.SYSTEM_TEMPLATE_NAMES.includes(template),
)
.map((template) => {
return {
name: `Template: ${template}`,
label: "BNotes Template",
callback: getTemplatePromptHandler(template),
};
}),
);
}
function getTemplatePromptHandler(name: string) {
return async (prompt: Prompt) => {
ztoolkit.log(prompt, name);
prompt.promptNode.style.display = "none";
// TODO: add preview when command is selected
switch (addon.data.template.picker.mode) {
case "create":
await createTemplateNoteCallback(name);
break;
case "export":
await exportTemplateCallback(name);
break;
case "insert":
default:
await insertTemplateCallback(name);
break;
}
addon.data.template.picker.mode = "insert";
addon.data.template.picker.data = {};
};
async function handleTemplateOperation(name: string) {
ztoolkit.log(name);
// TODO: add preview when command is selected
switch (addon.data.template.picker.mode) {
case "create":
await createTemplateNoteCallback(name);
break;
case "export":
await exportTemplateCallback(name);
break;
case "insert":
default:
await insertTemplateCallback(name);
break;
}
addon.data.template.picker.mode = "insert";
addon.data.template.picker.data = {};
}
async function insertTemplateCallback(name: string) {

View File

@ -0,0 +1,29 @@
import { config } from "../../package.json";
export async function openTemplatePicker(
options: {
multiSelect?: boolean;
} = {},
) {
const { multiSelect = false } = options;
const templates = addon.api.template
.getTemplateKeys()
.filter(
(template) =>
!addon.api.template.SYSTEM_TEMPLATE_NAMES.includes(template),
);
const args = {
templates,
multiSelect,
selected: [] as string[],
_initPromise: Zotero.Promise.defer(),
};
Zotero.getMainWindow().openDialog(
`chrome://${config.addonRef}/content/templatePicker.xhtml`,
"_blank",
"chrome,modal,centerscreen,resizable=yes",
args,
);
await args._initPromise.promise;
return args.selected;
}

View File

@ -1,7 +1,6 @@
import {
BasicTool,
UITool,
PromptManager,
MenuManager,
ClipboardHelper,
FilePickerHelper,
@ -43,7 +42,6 @@ function initZToolkit(_ztoolkit: ReturnType<typeof createZToolkit>) {
class MyToolkit extends BasicTool {
UI: UITool;
Prompt: PromptManager;
Menu: MenuManager;
Clipboard: typeof ClipboardHelper;
FilePicker: typeof FilePickerHelper;
@ -56,7 +54,6 @@ class MyToolkit extends BasicTool {
constructor() {
super();
this.UI = new UITool(this);
this.Prompt = new PromptManager(this);
this.Menu = new MenuManager(this);
this.Clipboard = ClipboardHelper;
this.FilePicker = FilePickerHelper;