mirror of
https://github.com/jonjampen/obsidian-xournalpp.git
synced 2026-07-22 05:12:03 +00:00
Template creation and management modals (#25)
This commit is contained in:
parent
b2b5d5d979
commit
a8158117e2
10 changed files with 708 additions and 28 deletions
BIN
.test.autosave.xopp
Normal file
BIN
.test.autosave.xopp
Normal file
Binary file not shown.
|
|
@ -13,6 +13,7 @@
|
|||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.6",
|
||||
"@types/pako": "^2.0.4",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.0",
|
||||
"builtin-modules": "3.3.0",
|
||||
|
|
@ -20,5 +21,8 @@
|
|||
"obsidian": "latest",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.7.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"pako": "^2.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,13 @@ export default class CreateXoppModalManager {
|
|||
linksToInsert: string;
|
||||
openAfterCreate = false;
|
||||
|
||||
constructor(app: App, plugin: XoppPlugin, filePath = "", editor: Editor | null = null, linksToInsert = "") {
|
||||
constructor(
|
||||
app: App,
|
||||
plugin: XoppPlugin,
|
||||
filePath = "",
|
||||
editor: Editor | null = null,
|
||||
linksToInsert = ""
|
||||
) {
|
||||
this.app = app;
|
||||
this.plugin = plugin;
|
||||
this.filePath = filePath;
|
||||
|
|
@ -64,6 +70,7 @@ export default class CreateXoppModalManager {
|
|||
fileName += ".xopp";
|
||||
|
||||
const newNotePath = folderPath === "/" ? fileName : `${folderPath}/${fileName}`;
|
||||
|
||||
const selectedTemplatePath =
|
||||
templatePath && templatePath.length > 0
|
||||
? templatePath
|
||||
|
|
|
|||
144
src/TemplateCreationModalManager.ts
Normal file
144
src/TemplateCreationModalManager.ts
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
import XoppPlugin from "main";
|
||||
import { App, normalizePath, Notice } from "obsidian";
|
||||
import type { TemplateSpec } from "src/modals/TemplateCreationModal";
|
||||
import * as pako from "pako";
|
||||
|
||||
const MM_TO_PT = 72 / 25.4;
|
||||
|
||||
export const PAGE_PRESETS = {
|
||||
A3: { widthMm: 297, heightMm: 420 },
|
||||
A4: { widthMm: 210, heightMm: 297 },
|
||||
A5: { widthMm: 148, heightMm: 210 },
|
||||
"US Legal": { widthMm: 215.9, heightMm: 355.6 },
|
||||
"US Letter": { widthMm: 215.9, heightMm: 279.4 },
|
||||
"16x9": { widthMm: 180, heightMm: 320 },
|
||||
"4x3": { widthMm: 240, heightMm: 320 },
|
||||
};
|
||||
|
||||
function getPageSizeToPt(spec: TemplateSpec): {
|
||||
widthPt: number;
|
||||
heightPt: number;
|
||||
} {
|
||||
let widthMm: number;
|
||||
let heightMm: number;
|
||||
|
||||
if (spec.pageSizePreset === "Custom") {
|
||||
if (spec.customWidthMm && spec.customHeightMm) {
|
||||
widthMm = spec.customWidthMm;
|
||||
heightMm = spec.customHeightMm;
|
||||
} else {
|
||||
const fallback = PAGE_PRESETS.A4;
|
||||
widthMm = fallback.widthMm;
|
||||
heightMm = fallback.heightMm;
|
||||
}
|
||||
} else {
|
||||
const preset = PAGE_PRESETS[spec.pageSizePreset];
|
||||
widthMm = preset.widthMm;
|
||||
heightMm = preset.heightMm;
|
||||
}
|
||||
|
||||
if (spec.orientation === "landscape") {
|
||||
[widthMm, heightMm] = [heightMm, widthMm];
|
||||
}
|
||||
|
||||
return {
|
||||
widthPt: widthMm * MM_TO_PT,
|
||||
heightPt: heightMm * MM_TO_PT,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeColor(raw: string | undefined | null): string {
|
||||
if (!raw) return "#000000ff";
|
||||
|
||||
let c = raw.trim();
|
||||
if (!c.startsWith("#")) c = "#" + c;
|
||||
|
||||
if (c.length === 4) {
|
||||
const r = c[1],
|
||||
g = c[2],
|
||||
b = c[3];
|
||||
c = `#${r}${r}${g}${g}${b}${b}`;
|
||||
}
|
||||
|
||||
if (c.length === 7) {
|
||||
c = c + "ff";
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
function buildBackgroundTag(spec: TemplateSpec): string {
|
||||
const color = normalizeColor(spec.backgroundColor || "#FFFFFF");
|
||||
|
||||
const tag = `<background type="solid" color="${color}" style="${spec.backgroundStyle}"/>`;
|
||||
return tag;
|
||||
}
|
||||
|
||||
function buildTemplateXML(spec: TemplateSpec): string {
|
||||
const { widthPt, heightPt } = getPageSizeToPt(spec);
|
||||
const backgroundTag = buildBackgroundTag(spec);
|
||||
|
||||
return `<?xml version="1.0" standalone="no"?>
|
||||
<xournal creator="xournalpp 1.2.5" fileversion="1">
|
||||
<title>Xournal++ document - see https://xournalpp.github.io/</title>
|
||||
<page width="${widthPt}" height="${heightPt}">
|
||||
${backgroundTag}
|
||||
<layer/>
|
||||
</page>
|
||||
</xournal>`;
|
||||
}
|
||||
|
||||
export async function writeTemplateFile(
|
||||
app: App,
|
||||
folder: string,
|
||||
spec: TemplateSpec
|
||||
): Promise<string> {
|
||||
const safeFolder = folder.replace(/^\/+/, "");
|
||||
const fileName = spec.name.replace(/\.xopp$/i, "") + ".xopp";
|
||||
const vaultPath = normalizePath(
|
||||
safeFolder ? `${safeFolder}/${fileName}` : fileName
|
||||
);
|
||||
|
||||
if (safeFolder && !app.vault.getAbstractFileByPath(safeFolder)) {
|
||||
await app.vault.createFolder(safeFolder);
|
||||
}
|
||||
|
||||
const xml = buildTemplateXML(spec);
|
||||
|
||||
if(spec.gzip) {
|
||||
const gzipped = pako.gzip(xml);
|
||||
const arrayBuffer = gzipped.buffer.slice(
|
||||
gzipped.byteOffset,
|
||||
gzipped.byteOffset + gzipped.byteLength
|
||||
);
|
||||
await app.vault.adapter.writeBinary(vaultPath, arrayBuffer);
|
||||
} else {
|
||||
await app.vault.adapter.write(vaultPath, xml);
|
||||
}
|
||||
|
||||
return vaultPath;
|
||||
}
|
||||
|
||||
export async function createTemplate(
|
||||
plugin: XoppPlugin,
|
||||
spec: TemplateSpec
|
||||
): Promise<string> {
|
||||
if (!spec.name) {
|
||||
new Notice("Please enter a template name");
|
||||
throw new Error("Missing template name");
|
||||
}
|
||||
|
||||
const folder =
|
||||
plugin.settings.templatesFolder?.trim() || "XournalTemplates";
|
||||
|
||||
if (!plugin.settings.templatesFolder) {
|
||||
plugin.settings.templatesFolder = folder;
|
||||
await plugin.saveSettings();
|
||||
}
|
||||
|
||||
const vaultPath = await writeTemplateFile(plugin.app, folder, spec);
|
||||
|
||||
new Notice(`Xournal++ template created: ${vaultPath}`);
|
||||
|
||||
return vaultPath;
|
||||
}
|
||||
154
src/TemplateEditingModalManager.ts
Normal file
154
src/TemplateEditingModalManager.ts
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
import { Notice, TFile, TFolder } from "obsidian";
|
||||
import { ParsedTemplateEditing } from "./modals/TemplateEditingModal";
|
||||
import { PAGE_PRESETS } from "./TemplateCreationModalManager";
|
||||
import { TemplateBackgroundStyle, TemplateSpec } from "./modals/TemplateCreationModal";
|
||||
import XoppPlugin from "main";
|
||||
import * as pako from "pako";
|
||||
|
||||
type PagePresetName = keyof typeof PAGE_PRESETS;
|
||||
|
||||
const PT_TO_MM = 25.4 / 72;
|
||||
|
||||
async function readXoppXml (plugin: XoppPlugin, file: TFile): Promise<{ xml: string; isGzipped: boolean }> {
|
||||
const buffer = await plugin.app.vault.adapter.readBinary(file.path);
|
||||
const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
|
||||
|
||||
const isGzipped = bytes.length >= 2 && bytes[0] === 0x1f && bytes[1] === 0x8b;
|
||||
|
||||
if (isGzipped) {
|
||||
const xmlBytes = pako.ungzip(bytes);
|
||||
const xml = new TextDecoder("utf-8").decode(xmlBytes);
|
||||
return { xml, isGzipped: true };
|
||||
} else {
|
||||
const xml = new TextDecoder("utf-8").decode(bytes);
|
||||
return { xml, isGzipped: false };
|
||||
}
|
||||
}
|
||||
|
||||
function detectPageSizePreset(widthPt: number, heightPt: number) : {
|
||||
preset: PagePresetName | "Custom";
|
||||
widthMm: number;
|
||||
heightMm: number;
|
||||
orientation: "portrait" | "landscape";
|
||||
} {
|
||||
|
||||
const widthMm = widthPt * PT_TO_MM;
|
||||
const heightMm = heightPt * PT_TO_MM;
|
||||
|
||||
const presets = PAGE_PRESETS;
|
||||
|
||||
const orientation: "portrait" | "landscape" =
|
||||
widthMm >= heightMm ? "landscape" : "portrait";
|
||||
|
||||
const w = Math.min(widthMm, heightMm);
|
||||
const h = Math.max(widthMm, heightMm);
|
||||
|
||||
const toleranceMm = 1;
|
||||
|
||||
for (const [name, preset] of Object.entries(presets) as [PagePresetName, {widthMm:number;heightMm:number}][]) {
|
||||
const pw = preset.widthMm;
|
||||
const ph = preset.heightMm;
|
||||
|
||||
if (Math.abs(w - Math.min(pw, ph)) <= toleranceMm && Math.abs(h - Math.max(pw, ph)) <= toleranceMm) {
|
||||
return { preset: name, widthMm, heightMm, orientation };
|
||||
}
|
||||
}
|
||||
|
||||
return { preset: "Custom", widthMm, heightMm, orientation };
|
||||
}
|
||||
|
||||
function parseBackground(el: Element): {
|
||||
backgroundStyle: TemplateBackgroundStyle;
|
||||
backgroundColor: string;
|
||||
spacingMm?: number;
|
||||
marginMm?: number;
|
||||
} {
|
||||
const style = el.getAttribute("style") ?? "";
|
||||
|
||||
const backgroundStyle: TemplateBackgroundStyle = style as TemplateBackgroundStyle;
|
||||
|
||||
const rawColor = el.getAttribute("color") || "#ffffffff";
|
||||
|
||||
return {
|
||||
backgroundStyle,
|
||||
backgroundColor: rawColor
|
||||
};
|
||||
}
|
||||
|
||||
function parseXoppTemplateSpec(xml: string, fileName: string): TemplateSpec {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(xml, "application/xml");
|
||||
|
||||
const pageEl = doc.querySelector("page");
|
||||
if (!pageEl) throw new Error("Invalid template: missing <page> element.");
|
||||
|
||||
const widthPt = parseFloat(pageEl.getAttribute("width") ?? "0");
|
||||
const heightPt = parseFloat(pageEl.getAttribute("height") ?? "0");
|
||||
|
||||
const { preset: pageSizePreset, widthMm, heightMm, orientation } = detectPageSizePreset(widthPt, heightPt);
|
||||
|
||||
const bgEl = pageEl.querySelector("background");
|
||||
const bg = bgEl
|
||||
? parseBackground(bgEl)
|
||||
: {
|
||||
backgroundStyle: "plain" as TemplateBackgroundStyle,
|
||||
backgroundColor: "#ffffffff",
|
||||
spacingMm: undefined,
|
||||
marginMm: undefined,
|
||||
};
|
||||
|
||||
const baseName = fileName.replace(/^.*\//, "").replace(/\.xopp$/i, "");
|
||||
|
||||
const spec: TemplateSpec = {
|
||||
name: baseName,
|
||||
pageSizePreset,
|
||||
orientation,
|
||||
backgroundStyle: bg.backgroundStyle,
|
||||
backgroundColor: bg.backgroundColor,
|
||||
};
|
||||
|
||||
if (pageSizePreset === "Custom") {
|
||||
spec.customWidthMm = widthMm;
|
||||
spec.customHeightMm = heightMm;
|
||||
}
|
||||
|
||||
return spec;
|
||||
}
|
||||
|
||||
export async function parseTemplateFile(
|
||||
plugin: XoppPlugin,
|
||||
templatePath: string,
|
||||
onFinished: () => void
|
||||
): Promise<void> {
|
||||
const file = plugin.app.vault.getAbstractFileByPath(templatePath);
|
||||
if (!file || !(file instanceof TFile)) {
|
||||
new Notice("Template file not found.");
|
||||
console.error("Template file not found:", templatePath);
|
||||
return;
|
||||
}
|
||||
|
||||
const { xml, isGzipped } = await readXoppXml(plugin, file);
|
||||
|
||||
const spec: TemplateSpec = parseXoppTemplateSpec(xml, file.name);
|
||||
spec.gzip = isGzipped;
|
||||
|
||||
new ParsedTemplateEditing(plugin.app, plugin, spec, onFinished).open();
|
||||
}
|
||||
|
||||
export async function fetchTemplates(plugin: XoppPlugin): Promise<TFile[]> {
|
||||
const folderPath = plugin.settings.templatesFolder?.trim();
|
||||
if (!folderPath) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const folder = plugin.app.vault.getAbstractFileByPath(folderPath);
|
||||
if (!folder || !(folder instanceof TFolder)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const templates = folder.children.filter(
|
||||
(child) => child instanceof TFile && child.name.endsWith(".xopp")
|
||||
) as TFile[];
|
||||
|
||||
return templates;
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@ import XoppPlugin from "main";
|
|||
import { App, Setting, PluginSettingTab, TFile } from "obsidian";
|
||||
import ConfirmationModal from "./modals/ConfirmationModal";
|
||||
import { exportAllXoppToPDF } from "src/xopp2pdf";
|
||||
import TemplateCreationModal from "src/modals/TemplateCreationModal";
|
||||
import TemplateEditingModal from "./modals/TemplateEditingModal";
|
||||
|
||||
export class XoppSettingsTab extends PluginSettingTab {
|
||||
plugin: XoppPlugin;
|
||||
|
|
@ -16,18 +18,6 @@ export class XoppSettingsTab extends PluginSettingTab {
|
|||
|
||||
containerEl.empty();
|
||||
|
||||
const templatesFolder = this.plugin.settings.templatesFolder?.trim();
|
||||
let templateFiles: TFile[] = [];
|
||||
if (templatesFolder) {
|
||||
templateFiles = this.app.vault
|
||||
.getFiles()
|
||||
.filter(
|
||||
(f: TFile) =>
|
||||
f.path.startsWith(templatesFolder + "/") &&
|
||||
f.extension === "xopp"
|
||||
);
|
||||
}
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Auto export Xournal++ files")
|
||||
.setDesc(
|
||||
|
|
@ -85,21 +75,6 @@ export class XoppSettingsTab extends PluginSettingTab {
|
|||
});
|
||||
});
|
||||
|
||||
// new Setting(containerEl)
|
||||
// .setName("Xournal++ template path")
|
||||
// .setDesc(
|
||||
// "The relative path of the template for any new Xournal++ file (leave empty to use the default template)."
|
||||
// )
|
||||
// .addText((toggle) => {
|
||||
// toggle
|
||||
// .setValue(this.plugin.settings.templatePath)
|
||||
// .setPlaceholder("e.g. templates/template.xopp")
|
||||
// .onChange(async (value) => {
|
||||
// this.plugin.settings.templatePath = value;
|
||||
// await this.plugin.saveSettings();
|
||||
// });
|
||||
// });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Xournal++ templates folder")
|
||||
.setDesc(
|
||||
|
|
@ -131,6 +106,19 @@ export class XoppSettingsTab extends PluginSettingTab {
|
|||
"The default template to use when creating new Xournal++ files from the templates folder."
|
||||
)
|
||||
.addDropdown((dropdown) => {
|
||||
const templatesFolder =
|
||||
this.plugin.settings.templatesFolder?.trim();
|
||||
let templateFiles: TFile[] = [];
|
||||
if (templatesFolder) {
|
||||
templateFiles = this.app.vault
|
||||
.getFiles()
|
||||
.filter(
|
||||
(f: TFile) =>
|
||||
f.path.startsWith(templatesFolder + "/") &&
|
||||
f.extension === "xopp"
|
||||
);
|
||||
}
|
||||
|
||||
if (templateFiles.length === 0) {
|
||||
dropdown.addOption("", "No templates found");
|
||||
} else {
|
||||
|
|
@ -146,6 +134,44 @@ export class XoppSettingsTab extends PluginSettingTab {
|
|||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Create a new Xournal++ template")
|
||||
.setDesc(
|
||||
"Create a new Xournal++ template file in the templates folder."
|
||||
)
|
||||
.addButton((button) => {
|
||||
button
|
||||
.setButtonText("Create Template")
|
||||
.setCta()
|
||||
.onClick(async () => {
|
||||
new TemplateCreationModal(
|
||||
this.app,
|
||||
this.plugin,
|
||||
async (createdPath) => {
|
||||
if (!this.plugin.settings.defaultTemplatePath) {
|
||||
this.plugin.settings.defaultTemplatePath =
|
||||
createdPath;
|
||||
}
|
||||
|
||||
await this.plugin.saveSettings();
|
||||
this.display();
|
||||
}
|
||||
).open();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Edit existing Xournal++ templates")
|
||||
.setDesc("A GUI to edit or delete existing Xournal++ templates.")
|
||||
.addButton((button) => {
|
||||
button
|
||||
.setButtonText("Manage Templates")
|
||||
.setCta()
|
||||
.onClick(async () => {
|
||||
new TemplateEditingModal(this.app, this.plugin).open();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Default path for new Xournal++ files")
|
||||
.setDesc(
|
||||
|
|
|
|||
177
src/modals/TemplateCreationModal.ts
Normal file
177
src/modals/TemplateCreationModal.ts
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
import XoppPlugin from "main";
|
||||
import { App, ButtonComponent, Modal, Setting } from "obsidian";
|
||||
import { createTemplate } from "src/TemplateCreationModalManager";
|
||||
|
||||
export type TemplateBackgroundStyle =
|
||||
| "plain"
|
||||
| "lined"
|
||||
| "ruled"
|
||||
| "staves"
|
||||
| "graph"
|
||||
| "dotted"
|
||||
| "isodotted"
|
||||
| "isograph";
|
||||
|
||||
export interface TemplateSpec {
|
||||
name: string;
|
||||
pageSizePreset:
|
||||
| "A3"
|
||||
| "A4"
|
||||
| "A5"
|
||||
| "US Letter"
|
||||
| "US Legal"
|
||||
| "16x9"
|
||||
| "4x3"
|
||||
| "Custom";
|
||||
customWidthMm?: number;
|
||||
customHeightMm?: number;
|
||||
orientation: "portrait" | "landscape";
|
||||
backgroundStyle: TemplateBackgroundStyle;
|
||||
backgroundColor: string;
|
||||
gzip? : boolean;
|
||||
}
|
||||
|
||||
export default class TemplateCreationModal extends Modal {
|
||||
plugin: XoppPlugin;
|
||||
onCreated: (vaultPath: string) => void;
|
||||
|
||||
constructor(
|
||||
app: App,
|
||||
plugin: XoppPlugin,
|
||||
onCreated: (vaultPath: string) => void
|
||||
) {
|
||||
super(app);
|
||||
this.plugin = plugin;
|
||||
this.onCreated = onCreated;
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
contentEl.createEl("h3", { text: "Create Xournal++ template" });
|
||||
|
||||
const spec: TemplateSpec = {
|
||||
name: "",
|
||||
pageSizePreset: "A4",
|
||||
orientation: "portrait",
|
||||
backgroundStyle: "plain",
|
||||
backgroundColor: "#ffffff",
|
||||
gzip: false,
|
||||
};
|
||||
|
||||
const doCreate = async () => {
|
||||
try {
|
||||
const vaultPath = await createTemplate(this.plugin, spec);
|
||||
this.onCreated(vaultPath);
|
||||
this.close();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName("Template Name")
|
||||
.setDesc("Name of the new template file")
|
||||
.addText((text) =>
|
||||
text.onChange((value) => {
|
||||
spec.name = value.trim();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(contentEl).setName("Page Size").addDropdown((dropdown) => {
|
||||
dropdown
|
||||
.addOption("A3", "A3")
|
||||
.addOption("A4", "A4")
|
||||
.addOption("A5", "A5")
|
||||
.addOption("US Letter", "US Letter")
|
||||
.addOption("US Legal", "US Legal")
|
||||
.addOption("16x9", "16x9")
|
||||
.addOption("4x3", "4x3")
|
||||
.addOption("Custom", "Custom")
|
||||
.onChange((value) => {
|
||||
spec.pageSizePreset =
|
||||
value as TemplateSpec["pageSizePreset"];
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName("Orientation")
|
||||
.addDropdown((dropdown) => {
|
||||
dropdown
|
||||
.addOption("portrait", "Portrait")
|
||||
.addOption("landscape", "Landscape")
|
||||
.onChange((value) => {
|
||||
spec.orientation = value as TemplateSpec["orientation"];
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName("Background Style")
|
||||
.addDropdown((dropdown) => {
|
||||
dropdown
|
||||
.addOption("plain", "Plain")
|
||||
.addOption("lined", "Lined")
|
||||
.addOption("ruled", "Ruled")
|
||||
.addOption("staves", "Staves")
|
||||
.addOption("graph", "Graph")
|
||||
.addOption("dotted", "Dotted")
|
||||
.addOption("isodotted", "Isometric dotted")
|
||||
.addOption("isograph", "Isometric graph")
|
||||
.onChange((value) => {
|
||||
spec.backgroundStyle =
|
||||
value as TemplateSpec["backgroundStyle"];
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName("Background Color")
|
||||
.setDesc("Hex color, e.g. #ffffff")
|
||||
.addText((text) =>
|
||||
text.setValue("#ffffff").onChange((value) => {
|
||||
spec.backgroundColor = value.trim();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName("Compress as gzipped .xopp")
|
||||
.setDesc("Whether to gzip compress the template file")
|
||||
.addToggle((toggle) =>
|
||||
toggle.setValue(!!spec.gzip).onChange((value) => {
|
||||
spec.gzip = value;
|
||||
})
|
||||
);
|
||||
|
||||
const buttonRow = contentEl.createDiv({
|
||||
cls: "xopp-creation-editing-template-button-row",
|
||||
});
|
||||
|
||||
new ButtonComponent(buttonRow)
|
||||
.setButtonText("Create")
|
||||
.setCta()
|
||||
.onClick(async () => {
|
||||
doCreate();
|
||||
this.close();
|
||||
});
|
||||
|
||||
new ButtonComponent(buttonRow)
|
||||
.setButtonText("Cancel")
|
||||
.onClick(() => this.close());
|
||||
|
||||
this.modalEl.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (!e.shiftKey) {
|
||||
doCreate();
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
161
src/modals/TemplateEditingModal.ts
Normal file
161
src/modals/TemplateEditingModal.ts
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
import XoppPlugin from "main";
|
||||
import { App, ButtonComponent, Modal, Setting } from "obsidian";
|
||||
import {
|
||||
parseTemplateFile,
|
||||
fetchTemplates,
|
||||
} from "src/TemplateEditingModalManager";
|
||||
import { TemplateSpec } from "./TemplateCreationModal";
|
||||
import { createTemplate } from "src/TemplateCreationModalManager";
|
||||
|
||||
export default class TemplateEditingModal extends Modal {
|
||||
plugin: XoppPlugin;
|
||||
|
||||
constructor(app: App, plugin: XoppPlugin) {
|
||||
super(app);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
contentEl.createEl("h3", { text: "Edit Xournal++ templates" });
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName("Choose a template")
|
||||
.addDropdown(async (dropdown) => {
|
||||
const templates = await fetchTemplates(this.plugin);
|
||||
|
||||
if (templates.length === 0) {
|
||||
dropdown.addOption("", "No templates found");
|
||||
} else {
|
||||
templates.forEach((template) => {
|
||||
dropdown.addOption(template.path, template.name);
|
||||
});
|
||||
}
|
||||
|
||||
dropdown.setValue("");
|
||||
|
||||
dropdown.onChange(async (value) => {
|
||||
await parseTemplateFile(this.plugin, value, () => { this.close()});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ParsedTemplateEditing extends Modal {
|
||||
plugin: XoppPlugin;
|
||||
initialSpec: TemplateSpec;
|
||||
onFinished: () => void;
|
||||
|
||||
constructor(app: App, plugin: XoppPlugin, templateSpec: TemplateSpec, onFinished: () => void) {
|
||||
super(app);
|
||||
this.plugin = plugin;
|
||||
this.initialSpec = templateSpec;
|
||||
this.onFinished = onFinished;
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
contentEl.createEl("h3", {
|
||||
text: `Editing template: ${this.initialSpec.name}`,
|
||||
});
|
||||
|
||||
const spec: TemplateSpec = { ...this.initialSpec };
|
||||
|
||||
new Setting(contentEl).setName("Page Size").addDropdown((dropdown) => {
|
||||
dropdown
|
||||
.addOption("A3", "A3")
|
||||
.addOption("A4", "A4")
|
||||
.addOption("A5", "A5")
|
||||
.addOption("US Letter", "US Letter")
|
||||
.addOption("US Legal", "US Legal")
|
||||
.addOption("16x9", "16x9")
|
||||
.addOption("4x3", "4x3")
|
||||
.addOption("Custom", "Custom")
|
||||
.setValue(this.initialSpec.pageSizePreset)
|
||||
.onChange((value) => {
|
||||
spec.pageSizePreset =
|
||||
value as TemplateSpec["pageSizePreset"];
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName("Orientation")
|
||||
.addDropdown((dropdown) => {
|
||||
dropdown
|
||||
.addOption("portrait", "Portrait")
|
||||
.addOption("landscape", "Landscape")
|
||||
.setValue(this.initialSpec.orientation)
|
||||
.onChange((value) => {
|
||||
spec.orientation = value as TemplateSpec["orientation"];
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName("Background Style")
|
||||
.addDropdown((dropdown) => {
|
||||
dropdown
|
||||
.addOption("plain", "Plain")
|
||||
.addOption("lined", "Lined")
|
||||
.addOption("ruled", "Ruled")
|
||||
.addOption("staves", "Staves")
|
||||
.addOption("graph", "Graph")
|
||||
.addOption("dotted", "Dotted")
|
||||
.addOption("isodotted", "Isometric dotted")
|
||||
.addOption("isograph", "Isometric graph")
|
||||
.setValue(this.initialSpec.backgroundStyle)
|
||||
.onChange((value) => {
|
||||
spec.backgroundStyle =
|
||||
value as TemplateSpec["backgroundStyle"];
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(contentEl).setName("Background color").addText((text) => {
|
||||
text.setPlaceholder("#ffffffff")
|
||||
.setValue(spec.backgroundColor || "")
|
||||
.onChange((value) => {
|
||||
spec.backgroundColor = value.trim();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName("Compress as gzipped .xopp")
|
||||
.setDesc("Whether to gzip compress the template file")
|
||||
.addToggle((toggle) =>
|
||||
toggle.setValue(!!spec.gzip).onChange((value) => {
|
||||
spec.gzip = value;
|
||||
})
|
||||
);
|
||||
|
||||
const buttonRow = contentEl.createDiv({
|
||||
cls: "xopp-creation-editing-template-button-row",
|
||||
});
|
||||
|
||||
new ButtonComponent(buttonRow)
|
||||
.setButtonText("Confirm Edits")
|
||||
.setCta()
|
||||
.onClick(async () => {
|
||||
await createTemplate(this.plugin, spec);
|
||||
this.close();
|
||||
this.onFinished();
|
||||
});
|
||||
|
||||
new ButtonComponent(buttonRow)
|
||||
.setButtonText("Cancel")
|
||||
.onClick(() => this.close());
|
||||
|
||||
this.modalEl.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (!e.shiftKey) {
|
||||
createTemplate(this.plugin, spec);
|
||||
this.close();
|
||||
this.onFinished();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -71,6 +71,7 @@ export default class XoppFileNameModal extends Modal {
|
|||
fileName = i;
|
||||
});
|
||||
|
||||
textComponent.inputEl.focus();
|
||||
// Using a timeout to ensure the focus is set after the modal is fully rendered.
|
||||
setTimeout(() => textComponent.inputEl.focus(), 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -17,3 +17,9 @@
|
|||
.clickable-tag {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.xopp-creation-editing-template-button-row {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
Loading…
Reference in a new issue