diff --git a/scripts/user-notes-backup.js b/scripts/user-notes-backup.js
new file mode 100644
index 0000000..b4b8e5f
--- /dev/null
+++ b/scripts/user-notes-backup.js
@@ -0,0 +1,474 @@
+import {
+ USER_NOTES_MODULE_ID
+} from "./user-notes-constants.js";
+
+import {
+ userNotesDecodeBase64,
+ userNotesEncodeBase64,
+ userNotesLoadAppearance,
+ userNotesLoadNotes,
+ userNotesLoadPosition,
+ userNotesSaveAppearance,
+ userNotesSaveNotes,
+ userNotesSavePositionData
+} from "./user-notes-storage.js";
+
+import {
+ userNotesEscapePlainTextAsHtml,
+ userNotesSanitizeHtml
+} from "./user-notes-sanitize.js";
+
+const USER_NOTES_EXPORT_SCHEMA_VERSION = 2;
+
+const USER_NOTES_APPEARANCE_DEFAULTS_FOR_BACKUP = {
+ windowBackgroundColor: "#191813",
+ windowBackgroundAlpha: 0.96,
+ windowTextColor: "#f0f0e0",
+ textareaBackgroundColor: "#ffffff",
+ textareaBackgroundAlpha: 0.92,
+ textareaTextColor: "#111111"
+};
+
+function userNotesDownloadJson(filename, data) {
+ const json = JSON.stringify(data, null, 2);
+ const blob = new Blob([json], { type: "application/json" });
+ const url = URL.createObjectURL(blob);
+
+ const link = document.createElement("a");
+ link.href = url;
+ link.download = filename;
+
+ document.body.appendChild(link);
+ link.click();
+ link.remove();
+
+ URL.revokeObjectURL(url);
+}
+
+function userNotesReadJsonFile(file) {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+
+ reader.onload = () => {
+ try {
+ resolve(JSON.parse(String(reader.result ?? "")));
+ } catch (err) {
+ reject(err);
+ }
+ };
+
+ reader.onerror = () => reject(reader.error);
+ reader.readAsText(file);
+ });
+}
+
+function userNotesBuildExportData(options) {
+ const data = {};
+
+ if (options.includeNotes) {
+ data.notes = {
+ format: "html",
+ encoding: "base64",
+ data: userNotesEncodeBase64(
+ userNotesSanitizeHtml(userNotesLoadNotes())
+ )
+ };
+ }
+
+ if (options.includeAppearance) {
+ data.appearance = userNotesLoadAppearance(
+ USER_NOTES_APPEARANCE_DEFAULTS_FOR_BACKUP
+ );
+ }
+
+ if (options.includePosition) {
+ const position = userNotesLoadPosition(null);
+
+ if (position) {
+ data.position = position;
+ }
+ }
+
+ return {
+ module: USER_NOTES_MODULE_ID,
+ schemaVersion: USER_NOTES_EXPORT_SCHEMA_VERSION,
+ exportedAt: new Date().toISOString(),
+ data
+ };
+}
+
+function userNotesNormalizeImportedNotes(notes) {
+ if (typeof notes === "string") {
+ return userNotesEscapePlainTextAsHtml(notes);
+ }
+
+ if (!notes || typeof notes !== "object") {
+ return "";
+ }
+
+ if (notes.encoding === "base64") {
+ return userNotesSanitizeHtml(
+ userNotesDecodeBase64(notes.data ?? notes.content ?? "")
+ );
+ }
+
+ if (notes.format === "html") {
+ return userNotesSanitizeHtml(notes.content ?? notes.data ?? "");
+ }
+
+ if (notes.format === "text") {
+ return userNotesEscapePlainTextAsHtml(notes.content ?? notes.data ?? "");
+ }
+
+ return userNotesSanitizeHtml(notes.content ?? notes.data ?? "");
+}
+
+function userNotesValidateImportData(importData) {
+ if (!importData || typeof importData !== "object") {
+ return false;
+ }
+
+ if (importData.module !== USER_NOTES_MODULE_ID) {
+ return false;
+ }
+
+ if (!importData.data || typeof importData.data !== "object") {
+ return false;
+ }
+
+ return true;
+}
+
+function userNotesApplyImportData(importData, options) {
+ const data = importData?.data ?? {};
+
+ let changed = false;
+
+ if (options.importNotes && data.notes !== undefined) {
+ const importedNotes = userNotesNormalizeImportedNotes(data.notes);
+
+ if (options.noteMode === "append") {
+ const existing = userNotesSanitizeHtml(userNotesLoadNotes());
+ const separator = existing.trim() ? "
" : "";
+
+ userNotesSaveNotes(`${existing}${separator}${importedNotes}`);
+ } else {
+ userNotesSaveNotes(importedNotes);
+ }
+
+ changed = true;
+ }
+
+ if (options.importAppearance && data.appearance) {
+ userNotesSaveAppearance(data.appearance);
+ changed = true;
+ }
+
+ if (options.importPosition && data.position) {
+ userNotesSavePositionData(data.position);
+ changed = true;
+ }
+
+ if (!changed) {
+ return false;
+ }
+
+ try {
+ globalThis.UserNotes?.refresh?.({
+ saveCurrentContent: false
+ });
+ } catch (err) {
+ console.warn(
+ "User Notes | Import was saved, but refreshing the open window failed",
+ err
+ );
+ }
+
+ return true;
+}
+
+class UserNotesExportSettings extends FormApplication {
+ static get defaultOptions() {
+ return foundry.utils.mergeObject(super.defaultOptions, {
+ id: "user-notes-export-settings",
+ title: "User Notes Export",
+ template: null,
+ width: 480,
+ height: "auto",
+ closeOnSubmit: false,
+ submitOnChange: false,
+ submitOnClose: false
+ });
+ }
+
+ async _renderInner() {
+ return $(`
+
+
+ Exportiert ausgewählte lokale Browserdaten als JSON-Datei.
+ Die Datei enthält keine Welt- oder Benutzerdaten und wird beim Import
+ immer in den aktuell geöffneten Scope übernommen.
+
+
+
+ Notizen exportieren
+
+
+
+
+ Farben und Transparenz exportieren
+
+
+
+
+ Fensterposition und Größe exportieren
+
+
+
+
+
+ `);
+ }
+
+ activateListeners(html) {
+ super.activateListeners(html);
+
+ html.on("click", ".user-notes-export-now", event => {
+ event.preventDefault();
+ event.stopPropagation();
+
+ const root = html[0];
+
+ if (!root) {
+ console.warn("User Notes | export settings root element not found");
+ return false;
+ }
+
+ const options = {
+ includeNotes: root.querySelector('[name="includeNotes"]')?.checked ?? false,
+ includeAppearance: root.querySelector('[name="includeAppearance"]')?.checked ?? false,
+ includePosition: root.querySelector('[name="includePosition"]')?.checked ?? false
+ };
+
+ if (!options.includeNotes && !options.includeAppearance && !options.includePosition) {
+ ui.notifications?.warn("User Notes: Bitte mindestens einen Export-Inhalt auswählen.");
+ return false;
+ }
+
+ const exportData = userNotesBuildExportData(options);
+ const date = new Date().toISOString().slice(0, 10);
+
+ userNotesDownloadJson(`user-notes-${date}.json`, exportData);
+
+ ui.notifications?.info("User Notes: Export wurde erstellt.");
+ this.close();
+
+ return false;
+ });
+ }
+
+ async _updateObject(_event, _formData) {
+ // Nicht verwendet. Buttons verarbeiten den Export direkt.
+ }
+}
+
+class UserNotesImportSettings extends FormApplication {
+ constructor(...args) {
+ super(...args);
+ this.importData = null;
+ }
+
+ static get defaultOptions() {
+ return foundry.utils.mergeObject(super.defaultOptions, {
+ id: "user-notes-import-settings",
+ title: "User Notes Import",
+ template: null,
+ width: 560,
+ height: "auto",
+ closeOnSubmit: false,
+ submitOnChange: false,
+ submitOnClose: false
+ });
+ }
+
+ async _renderInner() {
+ const hasData = Boolean(this.importData?.data);
+ const hasNotes = hasData && this.importData.data.notes !== undefined;
+ const hasAppearance = hasData && Boolean(this.importData.data.appearance);
+ const hasPosition = hasData && Boolean(this.importData.data.position);
+
+ return $(`
+
+
+ Importiert eine User-Notes-JSON-Datei in die aktuell geöffnete Welt
+ und für den aktuell eingeloggten Benutzer.
+
+
+
+ Sicherheits-Hinweis: Importiertes HTML wird streng bereinigt.
+ Skripte, Eventhandler, eingebettete aktive Inhalte, unsichere URLs und
+ nicht erlaubte HTML-Elemente werden entfernt. Dadurch können
+ Formatierungen verloren gehen.
+
+
+
+
+
+
+
+ Notizen importieren
+
+
+
+
+
+
+ Farben und Transparenz importieren
+
+
+
+
+ Fensterposition und Größe importieren
+
+
+
+
+
+ `);
+ }
+
+ activateListeners(html) {
+ super.activateListeners(html);
+
+ html.on("change", 'input[name="importFile"]', async event => {
+ const fileInput = event.currentTarget;
+ const file = fileInput.files?.[0];
+
+ if (!file) {
+ return;
+ }
+
+ try {
+ const json = await userNotesReadJsonFile(file);
+
+ if (!userNotesValidateImportData(json)) {
+ ui.notifications?.error("User Notes: Diese JSON-Datei ist kein gültiger User-Notes-Export.");
+ return;
+ }
+
+ this.importData = json;
+
+ console.log("User Notes | import file loaded", {
+ hasNotes: json.data?.notes !== undefined,
+ hasAppearance: Boolean(json.data?.appearance),
+ hasPosition: Boolean(json.data?.position)
+ });
+
+ ui.notifications?.info("User Notes: Importdatei wurde gelesen.");
+ this.render(true);
+ } catch (err) {
+ console.error("User Notes | Import failed while reading JSON", err);
+ ui.notifications?.error("User Notes: JSON-Datei konnte nicht gelesen werden.");
+ }
+ });
+
+ html.on("click", ".user-notes-import-now", event => {
+ event.preventDefault();
+ event.stopPropagation();
+
+ const root = html[0];
+
+ console.log("User Notes | import button clicked", {
+ hasImportData: Boolean(this.importData)
+ });
+
+ if (!root) {
+ console.warn("User Notes | import settings root element not found");
+ return false;
+ }
+
+ if (!this.importData) {
+ ui.notifications?.warn("User Notes: Bitte zuerst eine JSON-Datei auswählen.");
+ return false;
+ }
+
+ const options = {
+ importNotes: root.querySelector('[name="importNotes"]')?.checked ?? false,
+ noteMode: root.querySelector('[name="noteMode"]')?.value ?? "overwrite",
+ importAppearance: root.querySelector('[name="importAppearance"]')?.checked ?? false,
+ importPosition: root.querySelector('[name="importPosition"]')?.checked ?? false
+ };
+
+ console.log("User Notes | import options", options);
+
+ if (!options.importNotes && !options.importAppearance && !options.importPosition) {
+ ui.notifications?.warn("User Notes: Bitte mindestens einen Import-Inhalt auswählen.");
+ return false;
+ }
+
+ try {
+ const changed = userNotesApplyImportData(this.importData, options);
+
+ if (!changed) {
+ ui.notifications?.warn("User Notes: Es wurden keine importierbaren Daten angewendet.");
+ return false;
+ }
+
+ ui.notifications?.info("User Notes: Import wurde angewendet.");
+ this.close();
+ } catch (err) {
+ console.error("User Notes | Import failed while applying data", err);
+ ui.notifications?.error("User Notes: Import konnte nicht angewendet werden. Details stehen in der Browser-Konsole.");
+ }
+
+ return false;
+ });
+ }
+
+ async _updateObject(_event, _formData) {
+ // Nicht verwendet. Buttons verarbeiten den Import direkt.
+ }
+}
+
+export function userNotesRegisterBackupSettings() {
+ game.settings.registerMenu(USER_NOTES_MODULE_ID, "exportData", {
+ name: "Export",
+ label: "Notizen exportieren",
+ hint: "Exportiert ausgewählte lokale User-Notes-Daten als JSON-Datei.",
+ icon: "fas fa-file-export",
+ type: UserNotesExportSettings,
+ restricted: false
+ });
+
+ game.settings.registerMenu(USER_NOTES_MODULE_ID, "importData", {
+ name: "Import",
+ label: "Notizen importieren",
+ hint: "Importiert Notizen, Darstellung und/oder Fensterposition aus einer JSON-Datei. Importiertes HTML wird aus Sicherheitsgründen streng bereinigt.",
+ icon: "fas fa-file-import",
+ type: UserNotesImportSettings,
+ restricted: false
+ });
+}
diff --git a/scripts/user-notes-storage.js b/scripts/user-notes-storage.js
index 8ec28e8..caa693c 100644
--- a/scripts/user-notes-storage.js
+++ b/scripts/user-notes-storage.js
@@ -2,66 +2,270 @@ import {
USER_NOTES_MODULE_ID
} from "./user-notes-constants.js";
-export function userNotesStorageKey() {
+const USER_NOTES_STORAGE_SCHEMA_VERSION = 2;
+
+function userNotesBaseKey() {
const worldId = game.world?.id ?? game.world?.data?.id ?? "unknown-world";
const userId = game.user?.id ?? "unknown-user";
- return `${USER_NOTES_MODULE_ID}.${worldId}.${userId}.notes`;
+ return `${USER_NOTES_MODULE_ID}.${worldId}.${userId}`;
+}
+
+export function userNotesContentKey() {
+ return `${userNotesBaseKey()}.content`;
+}
+
+export function userNotesStateKey() {
+ return `${userNotesBaseKey()}.state`;
+}
+
+/**
+ * Legacy keys from older versions.
+ * Kept only for migration.
+ */
+export function userNotesStorageKey() {
+ return `${userNotesBaseKey()}.notes`;
}
export function userNotesPositionKey() {
- const worldId = game.world?.id ?? game.world?.data?.id ?? "unknown-world";
- const userId = game.user?.id ?? "unknown-user";
-
- return `${USER_NOTES_MODULE_ID}.${worldId}.${userId}.position`;
+ return `${userNotesBaseKey()}.position`;
}
export function userNotesAppearanceKey() {
- const worldId = game.world?.id ?? game.world?.data?.id ?? "unknown-world";
- const userId = game.user?.id ?? "unknown-user";
-
- return `${USER_NOTES_MODULE_ID}.${worldId}.${userId}.appearance`;
+ return `${userNotesBaseKey()}.appearance`;
}
-export function userNotesLoadNotes() {
- return window.localStorage.getItem(userNotesStorageKey()) ?? "";
+export function userNotesEncodeBase64(value) {
+ const bytes = new TextEncoder().encode(String(value ?? ""));
+ const chunkSize = 0x8000;
+ let binary = "";
+
+ for (let index = 0; index < bytes.length; index += chunkSize) {
+ const chunk = bytes.subarray(index, index + chunkSize);
+ binary += String.fromCharCode(...chunk);
+ }
+
+ return window.btoa(binary);
}
-export function userNotesSaveNotes(value) {
- window.localStorage.setItem(userNotesStorageKey(), value);
-}
-
-export function userNotesRemoveSavedPosition() {
- window.localStorage.removeItem(userNotesPositionKey());
-}
-
-export function userNotesLoadAppearance(defaults) {
+export function userNotesDecodeBase64(value) {
try {
- const raw = window.localStorage.getItem(userNotesAppearanceKey());
+ const binary = window.atob(String(value ?? ""));
+ const bytes = new Uint8Array(binary.length);
- if (!raw) {
- return { ...defaults };
+ for (let index = 0; index < binary.length; index++) {
+ bytes[index] = binary.charCodeAt(index);
}
- const parsed = JSON.parse(raw);
-
- return {
- ...defaults,
- ...parsed
- };
+ return new TextDecoder().decode(bytes);
} catch (err) {
- console.warn("User Notes | Could not load appearance settings", err);
- return { ...defaults };
+ console.warn("User Notes | Could not decode base64 content", err);
+ return "";
}
}
-export function userNotesSaveAppearance(values) {
+function userNotesRemoveLegacyStorageKeys() {
+ window.localStorage.removeItem(userNotesStorageKey());
+ window.localStorage.removeItem(userNotesPositionKey());
+ window.localStorage.removeItem(userNotesAppearanceKey());
+}
+
+function userNotesReadJson(key, fallback = null) {
+ try {
+ const raw = window.localStorage.getItem(key);
+
+ if (!raw) {
+ return fallback;
+ }
+
+ return JSON.parse(raw);
+ } catch (err) {
+ console.warn(`User Notes | Could not parse localStorage key: ${key}`, err);
+ return fallback;
+ }
+}
+
+function userNotesWriteJson(key, value) {
window.localStorage.setItem(
- userNotesAppearanceKey(),
- JSON.stringify(values)
+ key,
+ JSON.stringify(value)
);
}
-export function userNotesRemoveSavedAppearance() {
- window.localStorage.removeItem(userNotesAppearanceKey());
+function userNotesCreateEmptyContent() {
+ return {
+ schemaVersion: USER_NOTES_STORAGE_SCHEMA_VERSION,
+ updatedAt: new Date().toISOString(),
+ notes: {
+ format: "html",
+ encoding: "base64",
+ data: ""
+ }
+ };
+}
+
+function userNotesCreateEmptyState() {
+ return {
+ schemaVersion: USER_NOTES_STORAGE_SCHEMA_VERSION,
+ updatedAt: new Date().toISOString(),
+ appearance: null,
+ position: null
+ };
+}
+
+function userNotesLoadContentObject() {
+ const content = userNotesReadJson(
+ userNotesContentKey(),
+ null
+ );
+
+ if (content?.notes?.encoding === "base64") {
+ return content;
+ }
+
+ const legacyNotes = window.localStorage.getItem(userNotesStorageKey());
+
+ if (legacyNotes !== null) {
+ const migrated = userNotesCreateEmptyContent();
+ migrated.notes.data = userNotesEncodeBase64(legacyNotes);
+
+ userNotesWriteJson(userNotesContentKey(), migrated);
+ window.localStorage.removeItem(userNotesStorageKey());
+
+ return migrated;
+ }
+
+ return userNotesCreateEmptyContent();
+}
+
+function userNotesLoadStateObject() {
+ const state = userNotesReadJson(
+ userNotesStateKey(),
+ null
+ );
+
+ if (state && typeof state === "object") {
+ return {
+ ...userNotesCreateEmptyState(),
+ ...state
+ };
+ }
+
+ const migrated = userNotesCreateEmptyState();
+
+ const legacyPosition = userNotesReadJson(
+ userNotesPositionKey(),
+ null
+ );
+
+ const legacyAppearance = userNotesReadJson(
+ userNotesAppearanceKey(),
+ null
+ );
+
+ if (legacyPosition) {
+ migrated.position = legacyPosition;
+ }
+
+ if (legacyAppearance) {
+ migrated.appearance = legacyAppearance;
+ }
+
+ if (legacyPosition || legacyAppearance) {
+ userNotesWriteJson(userNotesStateKey(), migrated);
+ window.localStorage.removeItem(userNotesPositionKey());
+ window.localStorage.removeItem(userNotesAppearanceKey());
+ }
+
+ return migrated;
+}
+
+function userNotesSaveStateObject(state) {
+ userNotesWriteJson(
+ userNotesStateKey(),
+ {
+ ...userNotesCreateEmptyState(),
+ ...state,
+ schemaVersion: USER_NOTES_STORAGE_SCHEMA_VERSION,
+ updatedAt: new Date().toISOString()
+ }
+ );
+
+ userNotesRemoveLegacyStorageKeys();
+}
+
+export function userNotesLoadNotes() {
+ const content = userNotesLoadContentObject();
+
+ if (content?.notes?.encoding === "base64") {
+ return userNotesDecodeBase64(content.notes.data);
+ }
+
+ return "";
+}
+
+export function userNotesSaveNotes(value) {
+ const content = {
+ schemaVersion: USER_NOTES_STORAGE_SCHEMA_VERSION,
+ updatedAt: new Date().toISOString(),
+ notes: {
+ format: "html",
+ encoding: "base64",
+ data: userNotesEncodeBase64(value)
+ }
+ };
+
+ userNotesWriteJson(userNotesContentKey(), content);
+ userNotesRemoveLegacyStorageKeys();
+}
+
+export function userNotesLoadPosition(defaultValue = null) {
+ const state = userNotesLoadStateObject();
+
+ return state.position ?? defaultValue;
+}
+
+export function userNotesSavePositionData(position) {
+ const state = userNotesLoadStateObject();
+
+ state.position = position;
+
+ userNotesSaveStateObject(state);
+}
+
+export function userNotesRemoveSavedPosition() {
+ const state = userNotesLoadStateObject();
+
+ state.position = null;
+
+ userNotesSaveStateObject(state);
+}
+
+export function userNotesLoadAppearance(defaults) {
+ const state = userNotesLoadStateObject();
+
+ if (!state.appearance || typeof state.appearance !== "object") {
+ return { ...defaults };
+ }
+
+ return {
+ ...defaults,
+ ...state.appearance
+ };
+}
+
+export function userNotesSaveAppearance(values) {
+ const state = userNotesLoadStateObject();
+
+ state.appearance = values;
+
+ userNotesSaveStateObject(state);
+}
+
+export function userNotesRemoveSavedAppearance() {
+ const state = userNotesLoadStateObject();
+
+ state.appearance = null;
+
+ userNotesSaveStateObject(state);
}
diff --git a/scripts/user-notes-window.js b/scripts/user-notes-window.js
index d9dec30..bffceb4 100644
--- a/scripts/user-notes-window.js
+++ b/scripts/user-notes-window.js
@@ -9,8 +9,9 @@ import {
import {
userNotesLoadNotes,
+ userNotesLoadPosition,
userNotesSaveNotes,
- userNotesPositionKey,
+ userNotesSavePositionData,
userNotesRemoveSavedPosition
} from "./user-notes-storage.js";
@@ -99,9 +100,9 @@ export function userNotesClampPosition(position) {
export function userNotesRestorePosition(win) {
try {
- const raw = window.localStorage.getItem(userNotesPositionKey());
+ const pos = userNotesLoadPosition(null);
- if (!raw) {
+ if (!pos) {
userNotesApplyPosition(
win,
userNotesClampPosition(USER_NOTES_DEFAULT_POSITION)
@@ -109,8 +110,6 @@ export function userNotesRestorePosition(win) {
return;
}
- const pos = JSON.parse(raw);
-
const restoredPosition = {
left: Number.isFinite(pos.left) ? pos.left : USER_NOTES_DEFAULT_POSITION.left,
top: Number.isFinite(pos.top) ? pos.top : USER_NOTES_DEFAULT_POSITION.top,
@@ -153,10 +152,7 @@ export function userNotesSavePosition(win) {
height: Math.round(rect.height)
});
- window.localStorage.setItem(
- userNotesPositionKey(),
- JSON.stringify(position)
- );
+ userNotesSavePositionData(position);
}
export function userNotesResetPositionAndSize() {
@@ -315,10 +311,15 @@ function userNotesCreateEditor(win, editorElement) {
const editor = globalThis.Jodit.make(editorElement, {
height: "100%",
minHeight: 150,
+
+ allowResizeX: false,
+ allowResizeY: false,
+
toolbarAdaptive: false,
askBeforePasteHTML: false,
askBeforePasteFromWord: false,
defaultActionOnPaste: "insert_clear_html",
+
disablePlugins: [
"about",
"add-new-line",
@@ -334,6 +335,7 @@ function userNotesCreateEditor(win, editorElement) {
"speech-recognize",
"video"
],
+
buttons: [
"bold",
"italic",
@@ -527,14 +529,19 @@ export function userNotesOpenNotes() {
}
}
-export function userNotesRefreshOpenWindow() {
+export function userNotesRefreshOpenWindow(options = {}) {
+ const saveCurrentContent = options.saveCurrentContent ?? true;
+
const oldWin = document.getElementById(USER_NOTES_WINDOW_ID);
if (!oldWin) {
return;
}
- userNotesSaveNotes(userNotesGetEditorValue(oldWin));
+ if (saveCurrentContent) {
+ userNotesSaveNotes(userNotesGetEditorValue(oldWin));
+ }
+
userNotesSavePosition(oldWin);
userNotesDestroyEditor(oldWin);
oldWin.remove();
diff --git a/scripts/user-notes.js b/scripts/user-notes.js
index e9ecd11..670757e 100644
--- a/scripts/user-notes.js
+++ b/scripts/user-notes.js
@@ -2,6 +2,10 @@ import {
userNotesRegisterSettings
} from "./user-notes-settings.js";
+import {
+ userNotesRegisterBackupSettings
+} from "./user-notes-backup.js";
+
import {
userNotesRegisterTokenControl
} from "./user-notes-controls.js";
@@ -23,8 +27,10 @@ Hooks.once("init", () => {
console.log("User Notes | application registered");
userNotesRegisterSettings(userNotesResetPositionAndSize);
-
console.log("User Notes | settings registered");
+
+ userNotesRegisterBackupSettings();
+ console.log("User Notes | export/import settings registered");
} catch (err) {
console.error("User Notes | error during init", err);
@@ -44,4 +50,4 @@ Hooks.on("getSceneControlButtons", controls => {
"User Notes: Fehler beim Registrieren des Token-Controls."
);
}
-});
\ No newline at end of file
+});
diff --git a/styles/user-notes-settings.css b/styles/user-notes-settings.css
index 69ec60c..7217c93 100644
--- a/styles/user-notes-settings.css
+++ b/styles/user-notes-settings.css
@@ -121,7 +121,7 @@
width: 100%;
}
-.user-notes-backup-settings .sheet-footer {
+.user-notes-backup-footer {
margin-top: 0.75rem;
display: flex;
@@ -129,6 +129,34 @@
gap: 0.5rem;
}
-.user-notes-backup-settings .sheet-footer button {
+.user-notes-backup-footer button {
flex: 1 1 auto;
+ height: 2rem;
+ min-height: 2rem;
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.35rem;
+
+ box-sizing: border-box;
+}
+
+.user-notes-checkbox-row {
+ display: grid;
+ grid-template-columns: 1fr 2rem;
+ align-items: center;
+ column-gap: 0.75rem;
+
+ margin: 0 0 0.45rem 0;
+}
+
+.user-notes-checkbox-row label {
+ margin: 0;
+ line-height: 1.3;
+}
+
+.user-notes-checkbox-row input[type="checkbox"] {
+ justify-self: center;
+ margin: 0;
}