mirror of
https://github.com/fzumpe/foundry-usernotes.git
synced 2026-06-06 21:00:03 +02:00
Replaced Scripts by ESModules and added cool stuff
This commit is contained in:
parent
1045e66262
commit
3d09e19c52
10
module.json
10
module.json
@ -1,22 +1,22 @@
|
||||
{
|
||||
"id": "user-notes",
|
||||
"title": "User Notes",
|
||||
"description": "User Notes: ein einfaches lokales Notizfenster für Foundry VTT v14. Notizen werden im Browser-localStorage pro Welt und Benutzer gespeichert.",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.0",
|
||||
"authors": [
|
||||
{
|
||||
"name": "ChatGPT"
|
||||
"name": "Florian Zumpe"
|
||||
}
|
||||
],
|
||||
"compatibility": {
|
||||
"minimum": "14",
|
||||
"verified": "14"
|
||||
},
|
||||
"scripts": [
|
||||
"esmodules": [
|
||||
"scripts/user-notes.js"
|
||||
],
|
||||
"styles": [
|
||||
"styles/user-notes.css"
|
||||
"styles/user-notes-window.css",
|
||||
"styles/user-notes-settings.css"
|
||||
],
|
||||
"languages": [],
|
||||
"url": "https://github.com/fzumpe/foundry-usernotes",
|
||||
|
||||
19
scripts/user-notes-constants.js
Normal file
19
scripts/user-notes-constants.js
Normal file
@ -0,0 +1,19 @@
|
||||
export const USER_NOTES_MODULE_ID = "user-notes";
|
||||
export const USER_NOTES_WINDOW_ID = "user-notes-window";
|
||||
export const USER_NOTES_TOOL_ID = "user-notes-open";
|
||||
|
||||
export const USER_NOTES_DEFAULT_POSITION = {
|
||||
left: 200,
|
||||
top: 150,
|
||||
width: 500,
|
||||
height: 400
|
||||
};
|
||||
|
||||
export const USER_NOTES_MIN_WIDTH = 280;
|
||||
export const USER_NOTES_MIN_HEIGHT = 180;
|
||||
export const USER_NOTES_VIEWPORT_MARGIN = 40;
|
||||
|
||||
export const USER_NOTES_DEFAULT_BACKGROUND = "rgba(25, 24, 19, 0.96)";
|
||||
export const USER_NOTES_DEFAULT_TEXT_COLOR = "#f0f0e0";
|
||||
export const USER_NOTES_DEFAULT_TEXTAREA_BACKGROUND = "rgba(255, 255, 255, 0.92)";
|
||||
export const USER_NOTES_DEFAULT_TEXTAREA_COLOR = "#111111";
|
||||
48
scripts/user-notes-controls.js
Normal file
48
scripts/user-notes-controls.js
Normal file
@ -0,0 +1,48 @@
|
||||
import {
|
||||
USER_NOTES_MODULE_ID,
|
||||
USER_NOTES_TOOL_ID
|
||||
} from "./user-notes-constants.js";
|
||||
|
||||
import {
|
||||
userNotesOpenNotes
|
||||
} from "./user-notes-window.js";
|
||||
|
||||
export function userNotesRegisterTokenControl(controls) {
|
||||
console.log("User Notes | registering token control", controls);
|
||||
|
||||
const tokenControl = controls?.tokens;
|
||||
|
||||
if (!tokenControl) {
|
||||
console.warn("User Notes | controls.tokens fehlt", controls);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tokenControl.tools) {
|
||||
console.warn("User Notes | controls.tokens.tools fehlt", tokenControl);
|
||||
return;
|
||||
}
|
||||
|
||||
tokenControl.tools[USER_NOTES_TOOL_ID] = {
|
||||
name: USER_NOTES_TOOL_ID,
|
||||
title: "User Notes öffnen",
|
||||
icon: "fa-solid fa-note-sticky",
|
||||
order: Object.keys(tokenControl.tools).length + 1,
|
||||
button: true,
|
||||
visible: true,
|
||||
|
||||
onChange: (event, active) => {
|
||||
console.log("User Notes | onChange", { event, active });
|
||||
userNotesOpenNotes();
|
||||
},
|
||||
|
||||
onClick: event => {
|
||||
console.log("User Notes | onClick", { event });
|
||||
userNotesOpenNotes();
|
||||
}
|
||||
};
|
||||
|
||||
console.log(
|
||||
"User Notes | token control registered",
|
||||
tokenControl.tools[USER_NOTES_TOOL_ID]
|
||||
);
|
||||
}
|
||||
427
scripts/user-notes-settings.js
Normal file
427
scripts/user-notes-settings.js
Normal file
@ -0,0 +1,427 @@
|
||||
import {
|
||||
USER_NOTES_MODULE_ID,
|
||||
USER_NOTES_WINDOW_ID
|
||||
} from "./user-notes-constants.js";
|
||||
|
||||
import {
|
||||
userNotesLoadAppearance,
|
||||
userNotesSaveAppearance,
|
||||
userNotesRemoveSavedAppearance
|
||||
} from "./user-notes-storage.js";
|
||||
|
||||
let resetPositionCallback = null;
|
||||
|
||||
const USER_NOTES_APPEARANCE_DEFAULTS = {
|
||||
windowBackgroundColor: "#191813",
|
||||
windowBackgroundAlpha: 0.96,
|
||||
windowTextColor: "#f0f0e0",
|
||||
textareaBackgroundColor: "#ffffff",
|
||||
textareaBackgroundAlpha: 0.92,
|
||||
textareaTextColor: "#111111"
|
||||
};
|
||||
|
||||
function userNotesClampAlpha(value, fallback = 1) {
|
||||
const number = Number(value);
|
||||
|
||||
if (!Number.isFinite(number)) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return Math.max(0, Math.min(1, number));
|
||||
}
|
||||
|
||||
function userNotesNormalizeHexColor(value, fallback) {
|
||||
const normalized = String(value ?? "").trim();
|
||||
|
||||
if (/^#[0-9a-f]{6}$/i.test(normalized)) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function userNotesHexToRgba(hex, alpha) {
|
||||
const normalized = userNotesNormalizeHexColor(hex, "#000000");
|
||||
const match = normalized.match(/^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i);
|
||||
|
||||
if (!match) {
|
||||
return `rgba(0, 0, 0, ${userNotesClampAlpha(alpha)})`;
|
||||
}
|
||||
|
||||
const red = parseInt(match[1], 16);
|
||||
const green = parseInt(match[2], 16);
|
||||
const blue = parseInt(match[3], 16);
|
||||
|
||||
return `rgba(${red}, ${green}, ${blue}, ${userNotesClampAlpha(alpha)})`;
|
||||
}
|
||||
|
||||
function userNotesLoadValidatedAppearance() {
|
||||
const values = userNotesLoadAppearance(USER_NOTES_APPEARANCE_DEFAULTS);
|
||||
|
||||
return {
|
||||
windowBackgroundColor: userNotesNormalizeHexColor(
|
||||
values.windowBackgroundColor,
|
||||
USER_NOTES_APPEARANCE_DEFAULTS.windowBackgroundColor
|
||||
),
|
||||
windowBackgroundAlpha: userNotesClampAlpha(
|
||||
values.windowBackgroundAlpha,
|
||||
USER_NOTES_APPEARANCE_DEFAULTS.windowBackgroundAlpha
|
||||
),
|
||||
windowTextColor: userNotesNormalizeHexColor(
|
||||
values.windowTextColor,
|
||||
USER_NOTES_APPEARANCE_DEFAULTS.windowTextColor
|
||||
),
|
||||
textareaBackgroundColor: userNotesNormalizeHexColor(
|
||||
values.textareaBackgroundColor,
|
||||
USER_NOTES_APPEARANCE_DEFAULTS.textareaBackgroundColor
|
||||
),
|
||||
textareaBackgroundAlpha: userNotesClampAlpha(
|
||||
values.textareaBackgroundAlpha,
|
||||
USER_NOTES_APPEARANCE_DEFAULTS.textareaBackgroundAlpha
|
||||
),
|
||||
textareaTextColor: userNotesNormalizeHexColor(
|
||||
values.textareaTextColor,
|
||||
USER_NOTES_APPEARANCE_DEFAULTS.textareaTextColor
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
export function userNotesRegisterSettings(onResetPosition) {
|
||||
resetPositionCallback = onResetPosition;
|
||||
|
||||
game.settings.registerMenu(USER_NOTES_MODULE_ID, "appearanceSettings", {
|
||||
name: "Darstellung",
|
||||
label: "Farben und Transparenz einstellen",
|
||||
hint: "Öffnet lokale Colorpicker für Fenster, Notizfeld und Schriftfarben. Die Werte werden nur im localStorage dieses Browsers gespeichert.",
|
||||
icon: "fas fa-palette",
|
||||
type: UserNotesAppearanceSettings,
|
||||
restricted: false
|
||||
});
|
||||
|
||||
game.settings.registerMenu(USER_NOTES_MODULE_ID, "resetWindowPosition", {
|
||||
name: "Position und Größe zurücksetzen",
|
||||
label: "Jetzt zurücksetzen",
|
||||
hint: "Setzt nur Position und Größe des User-Notes-Fensters für diesen Browser zurück. Notizen, Farben und Transparenzwerte bleiben unverändert.",
|
||||
icon: "fas fa-undo",
|
||||
type: UserNotesDirectResetWindowPosition,
|
||||
restricted: false
|
||||
});
|
||||
}
|
||||
|
||||
export function userNotesApplySettingsToOpenWindow() {
|
||||
const win = document.getElementById(USER_NOTES_WINDOW_ID);
|
||||
|
||||
if (!win) {
|
||||
return;
|
||||
}
|
||||
|
||||
userNotesApplyWindowSettings(win);
|
||||
}
|
||||
|
||||
export function userNotesApplyWindowSettings(win) {
|
||||
const appearance = userNotesLoadValidatedAppearance();
|
||||
|
||||
win.style.setProperty(
|
||||
"--user-notes-window-background",
|
||||
userNotesHexToRgba(
|
||||
appearance.windowBackgroundColor,
|
||||
appearance.windowBackgroundAlpha
|
||||
)
|
||||
);
|
||||
|
||||
win.style.setProperty(
|
||||
"--user-notes-window-text-color",
|
||||
appearance.windowTextColor
|
||||
);
|
||||
|
||||
win.style.setProperty(
|
||||
"--user-notes-textarea-background",
|
||||
userNotesHexToRgba(
|
||||
appearance.textareaBackgroundColor,
|
||||
appearance.textareaBackgroundAlpha
|
||||
)
|
||||
);
|
||||
|
||||
win.style.setProperty(
|
||||
"--user-notes-textarea-color",
|
||||
appearance.textareaTextColor
|
||||
);
|
||||
}
|
||||
|
||||
class UserNotesDirectResetWindowPosition extends FormApplication {
|
||||
render(_force, _options) {
|
||||
if (typeof resetPositionCallback === "function") {
|
||||
resetPositionCallback();
|
||||
ui.notifications?.info("User Notes: Position und Größe wurden zurückgesetzt.");
|
||||
} else {
|
||||
console.warn("User Notes | resetPositionCallback is not available");
|
||||
ui.notifications?.warn("User Notes: Reset-Funktion ist nicht verfügbar.");
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
class UserNotesAppearanceSettings extends FormApplication {
|
||||
static get defaultOptions() {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
id: "user-notes-appearance-settings",
|
||||
title: "User Notes Darstellung",
|
||||
template: null,
|
||||
width: 520,
|
||||
height: "auto",
|
||||
closeOnSubmit: false,
|
||||
submitOnChange: false,
|
||||
submitOnClose: false
|
||||
});
|
||||
}
|
||||
|
||||
getData() {
|
||||
const appearance = userNotesLoadValidatedAppearance();
|
||||
|
||||
return {
|
||||
...appearance,
|
||||
windowBackgroundAlphaPercent: Math.round(appearance.windowBackgroundAlpha * 100),
|
||||
textareaBackgroundAlphaPercent: Math.round(appearance.textareaBackgroundAlpha * 100)
|
||||
};
|
||||
}
|
||||
|
||||
async _renderInner(data) {
|
||||
const html = `
|
||||
<div class="user-notes-appearance-settings">
|
||||
<p class="notes">
|
||||
Diese Werte werden lokal im Browser gespeichert.
|
||||
Foundry-Settings werden dadurch nicht überschrieben und die Seite wird nicht neu geladen.
|
||||
</p>
|
||||
|
||||
<fieldset>
|
||||
<legend>Fenster</legend>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Hintergrundfarbe</label>
|
||||
<div class="form-fields">
|
||||
<input type="color" name="windowBackgroundColor" value="${data.windowBackgroundColor}">
|
||||
<input type="text" name="windowBackgroundColorText" value="${data.windowBackgroundColor}">
|
||||
</div>
|
||||
<p class="hint">Farbe des äußeren Notizfensters.</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Hintergrundtransparenz</label>
|
||||
<div class="form-fields user-notes-range-row">
|
||||
<input type="range" name="windowBackgroundAlpha" min="0" max="1" step="0.01" value="${data.windowBackgroundAlpha}">
|
||||
<output>${data.windowBackgroundAlphaPercent}%</output>
|
||||
</div>
|
||||
<p class="hint">0% ist vollständig transparent, 100% ist vollständig deckend.</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Schriftfarbe</label>
|
||||
<div class="form-fields">
|
||||
<input type="color" name="windowTextColor" value="${data.windowTextColor}">
|
||||
<input type="text" name="windowTextColorText" value="${data.windowTextColor}">
|
||||
</div>
|
||||
<p class="hint">Farbe für Titelleiste, Status und Buttons.</p>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Notizfeld</legend>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Hintergrundfarbe</label>
|
||||
<div class="form-fields">
|
||||
<input type="color" name="textareaBackgroundColor" value="${data.textareaBackgroundColor}">
|
||||
<input type="text" name="textareaBackgroundColorText" value="${data.textareaBackgroundColor}">
|
||||
</div>
|
||||
<p class="hint">Farbe des eigentlichen Textfeldes.</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Hintergrundtransparenz</label>
|
||||
<div class="form-fields user-notes-range-row">
|
||||
<input type="range" name="textareaBackgroundAlpha" min="0" max="1" step="0.01" value="${data.textareaBackgroundAlpha}">
|
||||
<output>${data.textareaBackgroundAlphaPercent}%</output>
|
||||
</div>
|
||||
<p class="hint">0% ist vollständig transparent, 100% ist vollständig deckend.</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Schriftfarbe</label>
|
||||
<div class="form-fields">
|
||||
<input type="color" name="textareaTextColor" value="${data.textareaTextColor}">
|
||||
<input type="text" name="textareaTextColorText" value="${data.textareaTextColor}">
|
||||
</div>
|
||||
<p class="hint">Farbe des Textes innerhalb des Notizfeldes.</p>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<footer class="sheet-footer user-notes-appearance-footer">
|
||||
<div class="user-notes-primary-actions">
|
||||
<button type="button" class="user-notes-apply-appearance">
|
||||
<i class="fas fa-check"></i>
|
||||
Anwenden
|
||||
</button>
|
||||
|
||||
<button type="button" class="user-notes-save-appearance">
|
||||
<i class="fas fa-save"></i>
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button type="button" class="user-notes-reset-appearance">
|
||||
<i class="fas fa-undo"></i>
|
||||
Standardfarben
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return $(html);
|
||||
}
|
||||
|
||||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
|
||||
const root = html[0];
|
||||
|
||||
if (!root) {
|
||||
console.warn("User Notes | appearance settings root element not found");
|
||||
return;
|
||||
}
|
||||
|
||||
html.on("submit", event => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
});
|
||||
|
||||
const syncColorPair = (colorName, textName) => {
|
||||
const colorInput = root.querySelector(`input[name="${colorName}"]`);
|
||||
const textInput = root.querySelector(`input[name="${textName}"]`);
|
||||
|
||||
if (!colorInput || !textInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
colorInput.addEventListener("input", () => {
|
||||
textInput.value = colorInput.value;
|
||||
});
|
||||
|
||||
textInput.addEventListener("input", () => {
|
||||
if (/^#[0-9a-f]{6}$/i.test(textInput.value)) {
|
||||
colorInput.value = textInput.value;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
syncColorPair("windowBackgroundColor", "windowBackgroundColorText");
|
||||
syncColorPair("windowTextColor", "windowTextColorText");
|
||||
syncColorPair("textareaBackgroundColor", "textareaBackgroundColorText");
|
||||
syncColorPair("textareaTextColor", "textareaTextColorText");
|
||||
|
||||
for (const range of root.querySelectorAll('input[type="range"]')) {
|
||||
const output = range
|
||||
.closest(".user-notes-range-row")
|
||||
?.querySelector("output");
|
||||
|
||||
const updateOutput = () => {
|
||||
if (output) {
|
||||
output.textContent = `${Math.round(Number(range.value) * 100)}%`;
|
||||
}
|
||||
};
|
||||
|
||||
range.addEventListener("input", updateOutput);
|
||||
range.addEventListener("change", updateOutput);
|
||||
updateOutput();
|
||||
}
|
||||
|
||||
root.querySelector(".user-notes-apply-appearance")?.addEventListener("click", event => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
this.userNotesSaveAppearanceFromDialog(root, {
|
||||
closeDialog: false,
|
||||
notify: true
|
||||
});
|
||||
});
|
||||
|
||||
root.querySelector(".user-notes-save-appearance")?.addEventListener("click", event => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
this.userNotesSaveAppearanceFromDialog(root, {
|
||||
closeDialog: true,
|
||||
notify: true
|
||||
});
|
||||
});
|
||||
|
||||
root.querySelector(".user-notes-reset-appearance")?.addEventListener("click", event => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
userNotesRemoveSavedAppearance();
|
||||
userNotesApplySettingsToOpenWindow();
|
||||
|
||||
ui.notifications?.info("User Notes: Standardfarben wurden wiederhergestellt.");
|
||||
this.render(true);
|
||||
});
|
||||
}
|
||||
|
||||
userNotesSaveAppearanceFromDialog(root, options = {}) {
|
||||
const closeDialog = options.closeDialog ?? true;
|
||||
const notify = options.notify ?? true;
|
||||
|
||||
const getInputValue = name => {
|
||||
const input = root.querySelector(`[name="${name}"]`);
|
||||
return input?.value;
|
||||
};
|
||||
|
||||
const appearance = {
|
||||
windowBackgroundColor: userNotesNormalizeHexColor(
|
||||
getInputValue("windowBackgroundColorText") || getInputValue("windowBackgroundColor"),
|
||||
USER_NOTES_APPEARANCE_DEFAULTS.windowBackgroundColor
|
||||
),
|
||||
windowBackgroundAlpha: userNotesClampAlpha(
|
||||
getInputValue("windowBackgroundAlpha"),
|
||||
USER_NOTES_APPEARANCE_DEFAULTS.windowBackgroundAlpha
|
||||
),
|
||||
windowTextColor: userNotesNormalizeHexColor(
|
||||
getInputValue("windowTextColorText") || getInputValue("windowTextColor"),
|
||||
USER_NOTES_APPEARANCE_DEFAULTS.windowTextColor
|
||||
),
|
||||
textareaBackgroundColor: userNotesNormalizeHexColor(
|
||||
getInputValue("textareaBackgroundColorText") || getInputValue("textareaBackgroundColor"),
|
||||
USER_NOTES_APPEARANCE_DEFAULTS.textareaBackgroundColor
|
||||
),
|
||||
textareaBackgroundAlpha: userNotesClampAlpha(
|
||||
getInputValue("textareaBackgroundAlpha"),
|
||||
USER_NOTES_APPEARANCE_DEFAULTS.textareaBackgroundAlpha
|
||||
),
|
||||
textareaTextColor: userNotesNormalizeHexColor(
|
||||
getInputValue("textareaTextColorText") || getInputValue("textareaTextColor"),
|
||||
USER_NOTES_APPEARANCE_DEFAULTS.textareaTextColor
|
||||
)
|
||||
};
|
||||
|
||||
userNotesSaveAppearance(appearance);
|
||||
userNotesApplySettingsToOpenWindow();
|
||||
|
||||
if (notify) {
|
||||
ui.notifications?.info(
|
||||
closeDialog
|
||||
? "User Notes: Darstellung wurde gespeichert."
|
||||
: "User Notes: Darstellung wurde angewendet."
|
||||
);
|
||||
}
|
||||
|
||||
if (closeDialog) {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
async _updateObject(_event, _formData) {
|
||||
// Wird absichtlich nicht verwendet.
|
||||
// Die Buttons speichern direkt in localStorage, damit kein nativer GET-Submit stattfindet.
|
||||
}
|
||||
}
|
||||
67
scripts/user-notes-storage.js
Normal file
67
scripts/user-notes-storage.js
Normal file
@ -0,0 +1,67 @@
|
||||
import {
|
||||
USER_NOTES_MODULE_ID
|
||||
} from "./user-notes-constants.js";
|
||||
|
||||
export function userNotesStorageKey() {
|
||||
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`;
|
||||
}
|
||||
|
||||
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`;
|
||||
}
|
||||
|
||||
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`;
|
||||
}
|
||||
|
||||
export function userNotesLoadNotes() {
|
||||
return window.localStorage.getItem(userNotesStorageKey()) ?? "";
|
||||
}
|
||||
|
||||
export function userNotesSaveNotes(value) {
|
||||
window.localStorage.setItem(userNotesStorageKey(), value);
|
||||
}
|
||||
|
||||
export function userNotesRemoveSavedPosition() {
|
||||
window.localStorage.removeItem(userNotesPositionKey());
|
||||
}
|
||||
|
||||
export function userNotesLoadAppearance(defaults) {
|
||||
try {
|
||||
const raw = window.localStorage.getItem(userNotesAppearanceKey());
|
||||
|
||||
if (!raw) {
|
||||
return { ...defaults };
|
||||
}
|
||||
|
||||
const parsed = JSON.parse(raw);
|
||||
|
||||
return {
|
||||
...defaults,
|
||||
...parsed
|
||||
};
|
||||
} catch (err) {
|
||||
console.warn("User Notes | Could not load appearance settings", err);
|
||||
return { ...defaults };
|
||||
}
|
||||
}
|
||||
|
||||
export function userNotesSaveAppearance(values) {
|
||||
window.localStorage.setItem(
|
||||
userNotesAppearanceKey(),
|
||||
JSON.stringify(values)
|
||||
);
|
||||
}
|
||||
|
||||
export function userNotesRemoveSavedAppearance() {
|
||||
window.localStorage.removeItem(userNotesAppearanceKey());
|
||||
}
|
||||
358
scripts/user-notes-window.js
Normal file
358
scripts/user-notes-window.js
Normal file
@ -0,0 +1,358 @@
|
||||
import {
|
||||
USER_NOTES_MODULE_ID,
|
||||
USER_NOTES_WINDOW_ID,
|
||||
USER_NOTES_DEFAULT_POSITION,
|
||||
USER_NOTES_MIN_WIDTH,
|
||||
USER_NOTES_MIN_HEIGHT,
|
||||
USER_NOTES_VIEWPORT_MARGIN
|
||||
} from "./user-notes-constants.js";
|
||||
|
||||
import {
|
||||
userNotesLoadNotes,
|
||||
userNotesSaveNotes,
|
||||
userNotesPositionKey,
|
||||
userNotesRemoveSavedPosition
|
||||
} from "./user-notes-storage.js";
|
||||
|
||||
import {
|
||||
userNotesApplyWindowSettings
|
||||
} from "./user-notes-settings.js";
|
||||
|
||||
let userNotesSaveTimer = null;
|
||||
|
||||
export function userNotesSetStatus(text) {
|
||||
const status = document.querySelector(
|
||||
`#${USER_NOTES_WINDOW_ID} .user-notes-status`
|
||||
);
|
||||
|
||||
if (status) {
|
||||
status.textContent = text;
|
||||
}
|
||||
}
|
||||
|
||||
export function userNotesDebouncedSave(value) {
|
||||
window.clearTimeout(userNotesSaveTimer);
|
||||
userNotesSetStatus("Ungespeichert …");
|
||||
|
||||
userNotesSaveTimer = window.setTimeout(() => {
|
||||
userNotesSaveNotes(value);
|
||||
userNotesSetStatus("Gespeichert");
|
||||
}, 250);
|
||||
}
|
||||
|
||||
export function userNotesApplyPosition(win, position) {
|
||||
win.style.left = `${position.left}px`;
|
||||
win.style.top = `${position.top}px`;
|
||||
win.style.width = `${position.width}px`;
|
||||
win.style.height = `${position.height}px`;
|
||||
}
|
||||
|
||||
export function userNotesClampPosition(position) {
|
||||
const viewportWidth = Math.max(
|
||||
window.innerWidth,
|
||||
USER_NOTES_MIN_WIDTH + USER_NOTES_VIEWPORT_MARGIN
|
||||
);
|
||||
|
||||
const viewportHeight = Math.max(
|
||||
window.innerHeight,
|
||||
USER_NOTES_MIN_HEIGHT + USER_NOTES_VIEWPORT_MARGIN
|
||||
);
|
||||
|
||||
const maxWidth = Math.max(
|
||||
USER_NOTES_MIN_WIDTH,
|
||||
viewportWidth - USER_NOTES_VIEWPORT_MARGIN
|
||||
);
|
||||
|
||||
const maxHeight = Math.max(
|
||||
USER_NOTES_MIN_HEIGHT,
|
||||
viewportHeight - USER_NOTES_VIEWPORT_MARGIN
|
||||
);
|
||||
|
||||
const width = Math.max(
|
||||
USER_NOTES_MIN_WIDTH,
|
||||
Math.min(position.width, maxWidth)
|
||||
);
|
||||
|
||||
const height = Math.max(
|
||||
USER_NOTES_MIN_HEIGHT,
|
||||
Math.min(position.height, maxHeight)
|
||||
);
|
||||
|
||||
const maxLeft = Math.max(0, viewportWidth - width - 20);
|
||||
const maxTop = Math.max(0, viewportHeight - height - 20);
|
||||
|
||||
const left = Math.max(0, Math.min(position.left, maxLeft));
|
||||
const top = Math.max(0, Math.min(position.top, maxTop));
|
||||
|
||||
return {
|
||||
left,
|
||||
top,
|
||||
width,
|
||||
height
|
||||
};
|
||||
}
|
||||
|
||||
export function userNotesRestorePosition(win) {
|
||||
try {
|
||||
const raw = window.localStorage.getItem(userNotesPositionKey());
|
||||
|
||||
if (!raw) {
|
||||
userNotesApplyPosition(
|
||||
win,
|
||||
userNotesClampPosition(USER_NOTES_DEFAULT_POSITION)
|
||||
);
|
||||
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,
|
||||
width: Number.isFinite(pos.width) ? pos.width : USER_NOTES_DEFAULT_POSITION.width,
|
||||
height: Number.isFinite(pos.height) ? pos.height : USER_NOTES_DEFAULT_POSITION.height
|
||||
};
|
||||
|
||||
userNotesApplyPosition(
|
||||
win,
|
||||
userNotesClampPosition(restoredPosition)
|
||||
);
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
`${USER_NOTES_MODULE_ID} | Could not restore note window position`,
|
||||
err
|
||||
);
|
||||
|
||||
userNotesApplyPosition(
|
||||
win,
|
||||
userNotesClampPosition(USER_NOTES_DEFAULT_POSITION)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function userNotesSavePosition(win) {
|
||||
if (!win || win.hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = win.getBoundingClientRect();
|
||||
|
||||
if (rect.width < USER_NOTES_MIN_WIDTH || rect.height < USER_NOTES_MIN_HEIGHT) {
|
||||
return;
|
||||
}
|
||||
|
||||
const position = userNotesClampPosition({
|
||||
left: Math.round(rect.left),
|
||||
top: Math.round(rect.top),
|
||||
width: Math.round(rect.width),
|
||||
height: Math.round(rect.height)
|
||||
});
|
||||
|
||||
window.localStorage.setItem(
|
||||
userNotesPositionKey(),
|
||||
JSON.stringify(position)
|
||||
);
|
||||
}
|
||||
|
||||
export function userNotesResetPositionAndSize() {
|
||||
userNotesRemoveSavedPosition();
|
||||
|
||||
const win = document.getElementById(USER_NOTES_WINDOW_ID);
|
||||
|
||||
if (win) {
|
||||
userNotesApplyPosition(
|
||||
win,
|
||||
userNotesClampPosition(USER_NOTES_DEFAULT_POSITION)
|
||||
);
|
||||
|
||||
userNotesSavePosition(win);
|
||||
}
|
||||
}
|
||||
|
||||
export function userNotesBringToFront(win) {
|
||||
const currentTop = Number.parseInt(win.style.zIndex || "100000", 10);
|
||||
win.style.zIndex = String(Math.max(currentTop + 1, 100000));
|
||||
}
|
||||
|
||||
export function userNotesMakeDraggable(win) {
|
||||
const handle = win.querySelector(".user-notes-titlebar");
|
||||
|
||||
if (!handle) {
|
||||
return;
|
||||
}
|
||||
|
||||
let drag = null;
|
||||
|
||||
handle.addEventListener("pointerdown", event => {
|
||||
const target = event.target;
|
||||
|
||||
if (target instanceof HTMLElement && target.closest("button")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = win.getBoundingClientRect();
|
||||
|
||||
drag = {
|
||||
pointerId: event.pointerId,
|
||||
startX: event.clientX,
|
||||
startY: event.clientY,
|
||||
left: rect.left,
|
||||
top: rect.top
|
||||
};
|
||||
|
||||
userNotesBringToFront(win);
|
||||
handle.setPointerCapture(event.pointerId);
|
||||
});
|
||||
|
||||
handle.addEventListener("pointermove", event => {
|
||||
if (!drag || drag.pointerId !== event.pointerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = win.getBoundingClientRect();
|
||||
|
||||
const clamped = userNotesClampPosition({
|
||||
left: drag.left + event.clientX - drag.startX,
|
||||
top: drag.top + event.clientY - drag.startY,
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
});
|
||||
|
||||
win.style.left = `${clamped.left}px`;
|
||||
win.style.top = `${clamped.top}px`;
|
||||
});
|
||||
|
||||
handle.addEventListener("pointerup", event => {
|
||||
if (!drag || drag.pointerId !== event.pointerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
drag = null;
|
||||
userNotesSavePosition(win);
|
||||
});
|
||||
|
||||
handle.addEventListener("pointercancel", event => {
|
||||
if (!drag || drag.pointerId !== event.pointerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
drag = null;
|
||||
userNotesSavePosition(win);
|
||||
});
|
||||
}
|
||||
|
||||
export function userNotesOpenNotes() {
|
||||
let win = document.getElementById(USER_NOTES_WINDOW_ID);
|
||||
|
||||
if (win) {
|
||||
win.hidden = false;
|
||||
userNotesRestorePosition(win);
|
||||
userNotesApplyWindowSettings(win);
|
||||
userNotesBringToFront(win);
|
||||
win.querySelector("textarea")?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
win = document.createElement("section");
|
||||
win.id = USER_NOTES_WINDOW_ID;
|
||||
win.className = "user-notes-window";
|
||||
|
||||
win.innerHTML = `
|
||||
<header class="user-notes-titlebar">
|
||||
<div class="user-notes-title">
|
||||
<i class="fas fa-note-sticky" aria-hidden="true"></i>
|
||||
<span>User Notes</span>
|
||||
</div>
|
||||
|
||||
<div class="user-notes-controls">
|
||||
<span class="user-notes-status">Gespeichert</span>
|
||||
|
||||
<button type="button" class="user-notes-save" title="Jetzt speichern">
|
||||
<i class="fas fa-save" aria-hidden="true"></i>
|
||||
</button>
|
||||
|
||||
<button type="button" class="user-notes-close" title="Schließen">
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="user-notes-content">
|
||||
<textarea class="user-notes-textarea" spellcheck="true" placeholder="Notizen für diese Welt und diesen Benutzer …"></textarea>
|
||||
</main>
|
||||
`;
|
||||
|
||||
document.body.appendChild(win);
|
||||
|
||||
userNotesApplyWindowSettings(win);
|
||||
userNotesRestorePosition(win);
|
||||
|
||||
const textarea = win.querySelector(".user-notes-textarea");
|
||||
|
||||
if (!(textarea instanceof HTMLTextAreaElement)) {
|
||||
console.error(`${USER_NOTES_MODULE_ID} | Notes textarea could not be created.`);
|
||||
return;
|
||||
}
|
||||
|
||||
textarea.value = userNotesLoadNotes();
|
||||
|
||||
textarea.addEventListener("input", event => {
|
||||
userNotesDebouncedSave(event.currentTarget.value);
|
||||
});
|
||||
|
||||
win.querySelector(".user-notes-save")?.addEventListener("click", () => {
|
||||
userNotesSaveNotes(textarea.value);
|
||||
userNotesSetStatus("Gespeichert");
|
||||
userNotesSavePosition(win);
|
||||
});
|
||||
|
||||
win.querySelector(".user-notes-close")?.addEventListener("click", () => {
|
||||
userNotesSaveNotes(textarea.value);
|
||||
userNotesSavePosition(win);
|
||||
win.hidden = true;
|
||||
});
|
||||
|
||||
win.addEventListener("pointerdown", () => {
|
||||
userNotesBringToFront(win);
|
||||
});
|
||||
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
if (win.hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = win.getBoundingClientRect();
|
||||
|
||||
if (rect.width < USER_NOTES_MIN_WIDTH || rect.height < USER_NOTES_MIN_HEIGHT) {
|
||||
return;
|
||||
}
|
||||
|
||||
userNotesSavePosition(win);
|
||||
});
|
||||
|
||||
resizeObserver.observe(win);
|
||||
|
||||
userNotesMakeDraggable(win);
|
||||
userNotesBringToFront(win);
|
||||
textarea.focus();
|
||||
}
|
||||
|
||||
export function userNotesRefreshOpenWindow() {
|
||||
const oldWin = document.getElementById(USER_NOTES_WINDOW_ID);
|
||||
|
||||
if (!oldWin) {
|
||||
return;
|
||||
}
|
||||
|
||||
const textarea = oldWin.querySelector(".user-notes-textarea");
|
||||
|
||||
if (textarea instanceof HTMLTextAreaElement) {
|
||||
userNotesSaveNotes(textarea.value);
|
||||
}
|
||||
|
||||
userNotesSavePosition(oldWin);
|
||||
|
||||
oldWin.remove();
|
||||
|
||||
userNotesOpenNotes();
|
||||
}
|
||||
@ -1,236 +1,44 @@
|
||||
/*
|
||||
* User Notes for Foundry VTT v14
|
||||
* Stores notes in window.localStorage per world and per user.
|
||||
*/
|
||||
import {
|
||||
userNotesRegisterSettings
|
||||
} from "./user-notes-settings.js";
|
||||
|
||||
const LBN_MODULE_ID = "user-notes";
|
||||
const LBN_WINDOW_ID = "lbn-notes-window";
|
||||
const LBN_BUTTON_ID = "lbn-open-notes";
|
||||
import {
|
||||
userNotesRegisterTokenControl
|
||||
} from "./user-notes-controls.js";
|
||||
|
||||
let lbnSaveTimer = null;
|
||||
let lbnObserverTimer = null;
|
||||
import {
|
||||
userNotesOpenNotes,
|
||||
userNotesResetPositionAndSize,
|
||||
userNotesRefreshOpenWindow
|
||||
} from "./user-notes-window.js";
|
||||
|
||||
function lbnStorageKey() {
|
||||
const worldId = game.world?.id ?? game.world?.data?.id ?? "unknown-world";
|
||||
const userId = game.user?.id ?? "unknown-user";
|
||||
return `${LBN_MODULE_ID}.${worldId}.${userId}.notes`;
|
||||
}
|
||||
console.log("User Notes | ES module loaded");
|
||||
|
||||
function lbnPositionKey() {
|
||||
const worldId = game.world?.id ?? game.world?.data?.id ?? "unknown-world";
|
||||
const userId = game.user?.id ?? "unknown-user";
|
||||
return `${LBN_MODULE_ID}.${worldId}.${userId}.position`;
|
||||
}
|
||||
globalThis.UserNotes = {
|
||||
open: userNotesOpenNotes,
|
||||
resetPosition: userNotesResetPositionAndSize,
|
||||
refresh: userNotesRefreshOpenWindow
|
||||
};
|
||||
|
||||
function lbnLoadNotes() {
|
||||
return window.localStorage.getItem(lbnStorageKey()) ?? "";
|
||||
}
|
||||
Hooks.once("init", () => {
|
||||
console.log("User Notes | init hook fired");
|
||||
|
||||
function lbnSaveNotes(value) {
|
||||
window.localStorage.setItem(lbnStorageKey(), value);
|
||||
}
|
||||
|
||||
function lbnSetStatus(text) {
|
||||
const status = document.querySelector(`#${LBN_WINDOW_ID} .lbn-status`);
|
||||
if (status) status.textContent = text;
|
||||
}
|
||||
|
||||
function lbnDebouncedSave(value) {
|
||||
window.clearTimeout(lbnSaveTimer);
|
||||
lbnSetStatus("Ungespeichert …");
|
||||
lbnSaveTimer = window.setTimeout(() => {
|
||||
lbnSaveNotes(value);
|
||||
lbnSetStatus("Gespeichert");
|
||||
}, 250);
|
||||
}
|
||||
|
||||
function lbnRestorePosition(win) {
|
||||
try {
|
||||
const raw = window.localStorage.getItem(lbnPositionKey());
|
||||
if (!raw) return;
|
||||
const pos = JSON.parse(raw);
|
||||
if (Number.isFinite(pos.left)) win.style.left = `${pos.left}px`;
|
||||
if (Number.isFinite(pos.top)) win.style.top = `${pos.top}px`;
|
||||
if (Number.isFinite(pos.width)) win.style.width = `${pos.width}px`;
|
||||
if (Number.isFinite(pos.height)) win.style.height = `${pos.height}px`;
|
||||
userNotesRegisterSettings(userNotesResetPositionAndSize);
|
||||
console.log("User Notes | settings registered");
|
||||
} catch (err) {
|
||||
console.warn(`${LBN_MODULE_ID} | Could not restore note window position`, err);
|
||||
}
|
||||
}
|
||||
|
||||
function lbnSavePosition(win) {
|
||||
const rect = win.getBoundingClientRect();
|
||||
window.localStorage.setItem(lbnPositionKey(), JSON.stringify({
|
||||
left: Math.round(rect.left),
|
||||
top: Math.round(rect.top),
|
||||
width: Math.round(rect.width),
|
||||
height: Math.round(rect.height)
|
||||
}));
|
||||
}
|
||||
|
||||
function lbnMakeDraggable(win) {
|
||||
const handle = win.querySelector(".lbn-titlebar");
|
||||
if (!handle) return;
|
||||
|
||||
let drag = null;
|
||||
|
||||
handle.addEventListener("pointerdown", event => {
|
||||
if (event.target.closest("button")) return;
|
||||
|
||||
const rect = win.getBoundingClientRect();
|
||||
drag = {
|
||||
pointerId: event.pointerId,
|
||||
startX: event.clientX,
|
||||
startY: event.clientY,
|
||||
left: rect.left,
|
||||
top: rect.top
|
||||
};
|
||||
|
||||
handle.setPointerCapture(event.pointerId);
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
handle.addEventListener("pointermove", event => {
|
||||
if (!drag || drag.pointerId !== event.pointerId) return;
|
||||
|
||||
const nextLeft = Math.max(0, Math.min(window.innerWidth - 80, drag.left + event.clientX - drag.startX));
|
||||
const nextTop = Math.max(0, Math.min(window.innerHeight - 40, drag.top + event.clientY - drag.startY));
|
||||
|
||||
win.style.left = `${nextLeft}px`;
|
||||
win.style.top = `${nextTop}px`;
|
||||
});
|
||||
|
||||
handle.addEventListener("pointerup", event => {
|
||||
if (!drag || drag.pointerId !== event.pointerId) return;
|
||||
drag = null;
|
||||
lbnSavePosition(win);
|
||||
});
|
||||
}
|
||||
|
||||
function lbnOpenNotes() {
|
||||
let win = document.getElementById(LBN_WINDOW_ID);
|
||||
|
||||
if (win) {
|
||||
win.hidden = false;
|
||||
win.classList.add("active");
|
||||
win.querySelector("textarea")?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
win = document.createElement("section");
|
||||
win.id = LBN_WINDOW_ID;
|
||||
win.className = "lbn-notes-window";
|
||||
win.innerHTML = `
|
||||
<header class="lbn-titlebar">
|
||||
<div class="lbn-title">
|
||||
<i class="fas fa-note-sticky" aria-hidden="true"></i>
|
||||
<span>User Notes</span>
|
||||
</div>
|
||||
<div class="lbn-controls">
|
||||
<span class="lbn-status">Gespeichert</span>
|
||||
<button type="button" class="lbn-save" title="Jetzt speichern">
|
||||
<i class="fas fa-save" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button type="button" class="lbn-close" title="Schließen">
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<textarea class="lbn-textarea" spellcheck="true" placeholder="Notizen für diese Welt und diesen Benutzer …"></textarea>
|
||||
`;
|
||||
|
||||
document.body.appendChild(win);
|
||||
|
||||
const textarea = win.querySelector(".lbn-textarea");
|
||||
textarea.value = lbnLoadNotes();
|
||||
|
||||
textarea.addEventListener("input", event => {
|
||||
lbnDebouncedSave(event.currentTarget.value);
|
||||
});
|
||||
|
||||
win.querySelector(".lbn-save").addEventListener("click", () => {
|
||||
lbnSaveNotes(textarea.value);
|
||||
lbnSetStatus("Gespeichert");
|
||||
});
|
||||
|
||||
win.querySelector(".lbn-close").addEventListener("click", () => {
|
||||
lbnSaveNotes(textarea.value);
|
||||
lbnSavePosition(win);
|
||||
win.hidden = true;
|
||||
});
|
||||
|
||||
new ResizeObserver(() => lbnSavePosition(win)).observe(win);
|
||||
|
||||
lbnRestorePosition(win);
|
||||
lbnMakeDraggable(win);
|
||||
textarea.focus();
|
||||
}
|
||||
|
||||
function lbnAsHTMLElement(html) {
|
||||
if (html instanceof HTMLElement) return html;
|
||||
if (html?.[0] instanceof HTMLElement) return html[0]; // legacy jQuery-style hook argument
|
||||
if (html?.element instanceof HTMLElement) return html.element;
|
||||
return null;
|
||||
}
|
||||
|
||||
function lbnFindPlayersElement(renderedElement = null) {
|
||||
return renderedElement?.id === "players"
|
||||
? renderedElement
|
||||
: renderedElement?.querySelector?.("#players")
|
||||
?? document.querySelector("#players")
|
||||
?? document.querySelector("[data-application-id='players']")
|
||||
?? document.querySelector(".players");
|
||||
}
|
||||
|
||||
function lbnInjectButton(renderedElement = null) {
|
||||
const players = lbnFindPlayersElement(renderedElement);
|
||||
if (!players || players.querySelector(`#${LBN_BUTTON_ID}`)) return;
|
||||
|
||||
const header =
|
||||
players.querySelector(".window-header")
|
||||
?? players.querySelector("header")
|
||||
?? players.querySelector("h3")
|
||||
?? players.querySelector("h2")
|
||||
?? players;
|
||||
|
||||
const button = document.createElement("button");
|
||||
button.id = LBN_BUTTON_ID;
|
||||
button.type = "button";
|
||||
button.className = "lbn-player-note-button";
|
||||
button.title = "User Notes öffnen";
|
||||
button.setAttribute("aria-label", "User Notes öffnen");
|
||||
button.innerHTML = `<i class="fas fa-note-sticky" aria-hidden="true"></i>`;
|
||||
|
||||
button.addEventListener("click", event => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
lbnOpenNotes();
|
||||
});
|
||||
|
||||
header.appendChild(button);
|
||||
}
|
||||
|
||||
Hooks.once("ready", () => {
|
||||
lbnInjectButton();
|
||||
|
||||
// Fallback: Die Spieler-/Benutzerliste kann bei Theme-, Layout- oder Popout-Änderungen neu gerendert werden.
|
||||
const observer = new MutationObserver(() => {
|
||||
window.clearTimeout(lbnObserverTimer);
|
||||
lbnObserverTimer = window.setTimeout(() => lbnInjectButton(), 100);
|
||||
});
|
||||
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
});
|
||||
|
||||
Hooks.on("renderApplicationV2", (app, html) => {
|
||||
const element = lbnAsHTMLElement(html) ?? app?.element ?? null;
|
||||
if (app?.constructor?.name === "Players" || lbnFindPlayersElement(element)) {
|
||||
window.queueMicrotask(() => lbnInjectButton(element));
|
||||
console.error("User Notes | error during init", err);
|
||||
ui.notifications?.error("User Notes: Fehler beim Initialisieren. Details stehen in der Browser-Konsole.");
|
||||
}
|
||||
});
|
||||
|
||||
// Kompatibilitäts-Fallback für Installationen/Module, die noch einen spezifischen PlayerList-Hook auslösen.
|
||||
Hooks.on("renderPlayerList", (_app, html) => {
|
||||
const element = lbnAsHTMLElement(html);
|
||||
window.queueMicrotask(() => lbnInjectButton(element));
|
||||
Hooks.on("getSceneControlButtons", controls => {
|
||||
console.log("User Notes | getSceneControlButtons fired");
|
||||
|
||||
try {
|
||||
userNotesRegisterTokenControl(controls);
|
||||
} catch (err) {
|
||||
console.error("User Notes | error while registering token control", err);
|
||||
ui.notifications?.error("User Notes: Fehler beim Registrieren des Token-Controls.");
|
||||
}
|
||||
});
|
||||
|
||||
90
styles/user-notes-settings.css
Normal file
90
styles/user-notes-settings.css
Normal file
@ -0,0 +1,90 @@
|
||||
.user-notes-appearance-settings {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.user-notes-appearance-settings .notes {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.user-notes-appearance-settings fieldset {
|
||||
margin: 0 0 0.75rem 0;
|
||||
padding: 0.75rem;
|
||||
|
||||
border: 1px solid var(--color-border-light-primary, #7a695a);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.user-notes-appearance-settings legend {
|
||||
padding: 0 0.35rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.user-notes-appearance-settings .form-group {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.user-notes-appearance-settings .form-fields {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.user-notes-appearance-settings input[type="color"] {
|
||||
width: 3.5rem;
|
||||
min-width: 3.5rem;
|
||||
height: 2rem;
|
||||
|
||||
padding: 0;
|
||||
border: 1px solid var(--color-border-light-primary, #7a695a);
|
||||
border-radius: 4px;
|
||||
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.user-notes-appearance-settings input[type="text"] {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.user-notes-range-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.user-notes-range-row input[type="range"] {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.user-notes-range-row output {
|
||||
width: 3rem;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.user-notes-appearance-settings .sheet-footer,
|
||||
.user-notes-appearance-footer {
|
||||
margin-top: 0.75rem;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.user-notes-primary-actions {
|
||||
flex: 1 1 auto;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.user-notes-primary-actions button {
|
||||
flex: 1 1 0;
|
||||
}
|
||||
|
||||
.user-notes-appearance-footer > .user-notes-reset-appearance {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
133
styles/user-notes-window.css
Normal file
133
styles/user-notes-window.css
Normal file
@ -0,0 +1,133 @@
|
||||
.user-notes-window {
|
||||
position: fixed;
|
||||
z-index: 100000;
|
||||
left: 200px;
|
||||
top: 150px;
|
||||
|
||||
width: 500px;
|
||||
height: 400px;
|
||||
|
||||
min-width: 280px;
|
||||
min-height: 180px;
|
||||
|
||||
max-width: calc(100vw - 40px);
|
||||
max-height: calc(100vh - 40px);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
resize: both;
|
||||
overflow: hidden;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
border: 1px solid var(--color-border-dark, #000);
|
||||
border-radius: 6px;
|
||||
|
||||
background: var(--user-notes-window-background, rgba(25, 24, 19, 0.96));
|
||||
color: var(--user-notes-window-text-color, #f0f0e0);
|
||||
|
||||
box-shadow: 0 0 16px rgba(0, 0, 0, 0.55);
|
||||
}
|
||||
|
||||
.user-notes-window[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.user-notes-titlebar {
|
||||
flex: 0 0 auto;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
|
||||
padding: 0.45rem 0.55rem;
|
||||
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
border-bottom: 1px solid var(--color-border-dark, #000);
|
||||
|
||||
cursor: move;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.user-notes-title,
|
||||
.user-notes-controls {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.user-notes-title {
|
||||
min-width: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.user-notes-status {
|
||||
opacity: 0.75;
|
||||
font-size: 0.78rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.user-notes-controls button {
|
||||
width: 1.55rem;
|
||||
height: 1.55rem;
|
||||
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
padding: 0;
|
||||
|
||||
border: 1px solid var(--color-border-light-primary, #7a695a);
|
||||
border-radius: 4px;
|
||||
|
||||
background: rgba(0, 0, 0, 0.18);
|
||||
color: var(--user-notes-window-text-color, #f0f0e0);
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.user-notes-controls button:hover {
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
}
|
||||
|
||||
.user-notes-content {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
|
||||
display: flex;
|
||||
padding: 0.5rem;
|
||||
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.user-notes-textarea {
|
||||
flex: 1 1 auto;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
|
||||
resize: none;
|
||||
|
||||
box-sizing: border-box;
|
||||
padding: 0.55rem;
|
||||
|
||||
border: 1px solid var(--color-border-light-primary, #7a695a);
|
||||
border-radius: 4px;
|
||||
|
||||
background: var(--user-notes-textarea-background, rgba(255, 255, 255, 0.92));
|
||||
color: var(--user-notes-textarea-color, #111111);
|
||||
|
||||
font-family: inherit;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.user-notes-textarea:focus {
|
||||
outline: 1px solid var(--color-border-highlight, #ff6400);
|
||||
outline-offset: 0;
|
||||
}
|
||||
@ -1,100 +0,0 @@
|
||||
.lbn-player-note-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.65rem;
|
||||
height: 1.65rem;
|
||||
margin-left: 0.35rem;
|
||||
padding: 0;
|
||||
border: 1px solid var(--color-border-light-primary, #7a695a);
|
||||
border-radius: 4px;
|
||||
background: rgba(0, 0, 0, 0.18);
|
||||
color: var(--color-text-light-highlight, #f0f0e0);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.lbn-player-note-button:hover {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
.lbn-notes-window {
|
||||
position: fixed;
|
||||
z-index: 100000;
|
||||
left: 140px;
|
||||
top: 120px;
|
||||
width: 420px;
|
||||
height: 330px;
|
||||
min-width: 260px;
|
||||
min-height: 180px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
resize: both;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-border-dark, #000);
|
||||
border-radius: 6px;
|
||||
background: var(--color-bg, #1f1f1f);
|
||||
box-shadow: 0 0 16px rgba(0, 0, 0, 0.55);
|
||||
}
|
||||
|
||||
.lbn-notes-window[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.lbn-titlebar {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
min-height: 2rem;
|
||||
padding: 0.25rem 0.4rem;
|
||||
background: rgba(0, 0, 0, 0.34);
|
||||
color: var(--color-text-light-highlight, #f0f0e0);
|
||||
cursor: move;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.lbn-title,
|
||||
.lbn-controls {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.lbn-status {
|
||||
opacity: 0.75;
|
||||
font-size: 0.78rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.lbn-controls button {
|
||||
width: 1.55rem;
|
||||
height: 1.55rem;
|
||||
padding: 0;
|
||||
border: 1px solid var(--color-border-light-primary, #7a695a);
|
||||
border-radius: 4px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.lbn-controls button:hover {
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
}
|
||||
|
||||
.lbn-textarea {
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
box-sizing: border-box;
|
||||
resize: none;
|
||||
border: 0;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
padding: 0.65rem;
|
||||
font-family: var(--font-primary, sans-serif);
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.4;
|
||||
color: var(--color-text-light-highlight, #f0f0e0);
|
||||
background: rgba(255, 255, 255, 0.045);
|
||||
outline: none;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user