mirror of
https://github.com/sbuffkin/hexmaker.git
synced 2026-07-22 14:30:24 +00:00
chore: address obsidianmd lint and popout-window compat (1.2.1)
- Sentence-case: rephrase "GM" → "Game master"; remove disable comments;
autofixer lowercases "🎲 Open tables view" / "← Back" to comply
- no-static-styles-assignment: dynamic colors now use CSS custom properties
(--duckmage-bg / --duckmage-border-color / --duckmage-fg) consumed by
existing classes; static positions moved to small modifier classes
(duckmage-rt-roll-btn-spaced, duckmage-wf-steps-list, etc.); terrain
toolbar uses is-terrain-preview / is-eyedropper-active toggles;
createIconEl uses new duckmage-masked-icon class
- Popout-window compat: setTimeout / clearTimeout / requestAnimationFrame
→ window.X; document.X → activeDocument.X across all view/modal files
- Unnecessary `as ArrayBuffer`: replace `data.buffer.slice(...) as ArrayBuffer`
with `new ArrayBuffer + Uint8Array.set` — gets a real ArrayBuffer without
the assertion (also avoids ArrayBufferLike narrowing)
- Deprecated SettingTab.display(): extract body into private renderSettings();
display() and post-action refresh both call it
- eslint.config: add activeDocument + activeWindow to globals so the renamed
identifiers don't trip no-undef
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6aa885b5b2
commit
1fa2153bf1
36 changed files with 198 additions and 184 deletions
|
|
@ -14,6 +14,9 @@ export default defineConfig([
|
|||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
// Obsidian-injected globals for popout-window-aware code
|
||||
activeDocument: "readonly",
|
||||
activeWindow: "readonly",
|
||||
},
|
||||
parser: tsparser,
|
||||
parserOptions: { project: "./tsconfig.json" },
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "hexmaker",
|
||||
"name": "Hexmap World Creator",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"minAppVersion": "1.12.0",
|
||||
"description": "Create a hex map and a guidebook level wiki for your setting with minimal fuss. Hex crawl worldbuilding toolbox. System agnostic, ready for any TRPG you care to run in it.",
|
||||
"author": "morkdev",
|
||||
|
|
|
|||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "hexmaker-plugin",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "hexmaker-plugin",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minisearch": "^7.2.0"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "hexmaker-plugin",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"description": "A plugin to power hex crawl worldbuilding and running.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -34,11 +34,11 @@ export class HexmakerModal extends Modal {
|
|||
modalEl.setCssProps({ left: `${ox + ev.clientX - sx}px`, top: `${oy + ev.clientY - sy}px` });
|
||||
};
|
||||
const onUp = () => {
|
||||
document.removeEventListener("mousemove", onMove);
|
||||
document.removeEventListener("mouseup", onUp);
|
||||
activeDocument.removeEventListener("mousemove", onMove);
|
||||
activeDocument.removeEventListener("mouseup", onUp);
|
||||
};
|
||||
document.addEventListener("mousemove", onMove);
|
||||
document.addEventListener("mouseup", onUp);
|
||||
activeDocument.addEventListener("mousemove", onMove);
|
||||
activeDocument.addEventListener("mouseup", onUp);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ export class HexmakerSettingTab extends PluginSettingTab {
|
|||
}
|
||||
|
||||
display(): void {
|
||||
this.renderSettings();
|
||||
}
|
||||
|
||||
private renderSettings(): void {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
|
||||
|
|
@ -460,7 +464,7 @@ export class HexmakerSettingTab extends PluginSettingTab {
|
|||
);
|
||||
}
|
||||
new Notice("Folders generated.");
|
||||
this.display();
|
||||
this.renderSettings();
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -436,10 +436,8 @@ async function writeBinaryToVault(
|
|||
path: string,
|
||||
data: Uint8Array,
|
||||
): Promise<void> {
|
||||
const buf = data.buffer.slice(
|
||||
data.byteOffset,
|
||||
data.byteOffset + data.byteLength,
|
||||
) as ArrayBuffer;
|
||||
const buf = new ArrayBuffer(data.byteLength);
|
||||
new Uint8Array(buf).set(data);
|
||||
const existing = app.vault.getAbstractFileByPath(path);
|
||||
if (existing instanceof TFile) {
|
||||
await app.vault.modifyBinary(existing, buf);
|
||||
|
|
|
|||
|
|
@ -215,12 +215,9 @@ async function writeBinaryToVault(
|
|||
path: string,
|
||||
data: Uint8Array,
|
||||
): Promise<void> {
|
||||
// vault.createBinary / modifyBinary require ArrayBuffer; copy out of the
|
||||
// (possibly larger) underlying buffer to get a tight ArrayBuffer.
|
||||
const buf = data.buffer.slice(
|
||||
data.byteOffset,
|
||||
data.byteOffset + data.byteLength,
|
||||
) as ArrayBuffer;
|
||||
// vault.createBinary / modifyBinary require a plain ArrayBuffer.
|
||||
const buf = new ArrayBuffer(data.byteLength);
|
||||
new Uint8Array(buf).set(data);
|
||||
const existing = app.vault.getAbstractFileByPath(path);
|
||||
if (existing instanceof TFile) {
|
||||
await app.vault.modifyBinary(existing, buf);
|
||||
|
|
|
|||
|
|
@ -230,10 +230,8 @@ async function writeBinaryToVault(
|
|||
path: string,
|
||||
data: Uint8Array,
|
||||
): Promise<void> {
|
||||
const buf = data.buffer.slice(
|
||||
data.byteOffset,
|
||||
data.byteOffset + data.byteLength,
|
||||
) as ArrayBuffer;
|
||||
const buf = new ArrayBuffer(data.byteLength);
|
||||
new Uint8Array(buf).set(data);
|
||||
const existing = app.vault.getAbstractFileByPath(path);
|
||||
if (existing instanceof TFile) {
|
||||
await app.vault.modifyBinary(existing, buf);
|
||||
|
|
|
|||
|
|
@ -99,10 +99,8 @@ async function writeBinaryToVault(
|
|||
path: string,
|
||||
data: Uint8Array,
|
||||
): Promise<void> {
|
||||
const buf = data.buffer.slice(
|
||||
data.byteOffset,
|
||||
data.byteOffset + data.byteLength,
|
||||
) as ArrayBuffer;
|
||||
const buf = new ArrayBuffer(data.byteLength);
|
||||
new Uint8Array(buf).set(data);
|
||||
const existing = app.vault.getAbstractFileByPath(path);
|
||||
if (existing instanceof TFile) {
|
||||
await app.vault.modifyBinary(existing, buf);
|
||||
|
|
|
|||
|
|
@ -317,10 +317,8 @@ async function writeBinaryToVault(
|
|||
path: string,
|
||||
data: Uint8Array,
|
||||
): Promise<void> {
|
||||
const buf = data.buffer.slice(
|
||||
data.byteOffset,
|
||||
data.byteOffset + data.byteLength,
|
||||
) as ArrayBuffer;
|
||||
const buf = new ArrayBuffer(data.byteLength);
|
||||
new Uint8Array(buf).set(data);
|
||||
const existing = app.vault.getAbstractFileByPath(path);
|
||||
if (existing instanceof TFile) {
|
||||
await app.vault.modifyBinary(existing, buf);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* HTML rendering primitive — markdown to a {@link RenderedDoc} ready for printToPDF.
|
||||
*
|
||||
* Uses Obsidian's own `MarkdownRenderer.render()` so the export matches the
|
||||
* user's installed theme. CSS is captured by walking `document.styleSheets`.
|
||||
* user's installed theme. CSS is captured by walking `activeDocument.styleSheets`.
|
||||
*/
|
||||
|
||||
import { App, Component, MarkdownRenderer } from "obsidian";
|
||||
|
|
@ -29,7 +29,7 @@ export async function renderMarkdownToHtml(
|
|||
const { app, title, sourcePath, markdown } = opts;
|
||||
|
||||
// Off-screen container so MarkdownRenderer.render has a real DOM target.
|
||||
const container = document.body.createDiv({
|
||||
const container = activeDocument.body.createDiv({
|
||||
cls: "duckmage-export-render-host markdown-preview-view markdown-rendered",
|
||||
});
|
||||
const component = new Component();
|
||||
|
|
@ -49,13 +49,13 @@ export async function renderMarkdownToHtml(
|
|||
}
|
||||
|
||||
/**
|
||||
* Walk `document.styleSheets` and concatenate all readable CSS rules.
|
||||
* Walk `activeDocument.styleSheets` and concatenate all readable CSS rules.
|
||||
* Skips Svelte-injected sheets (transient, irrelevant) and silently skips
|
||||
* sheets that throw on cssRules access (CORS).
|
||||
*/
|
||||
function captureStyles(): string {
|
||||
const parts: string[] = [];
|
||||
for (const sheet of Array.from(document.styleSheets)) {
|
||||
for (const sheet of Array.from(activeDocument.styleSheets)) {
|
||||
const node = sheet.ownerNode as Element | null;
|
||||
const id = node?.getAttribute("id") ?? "";
|
||||
if (id.startsWith("svelte-")) continue;
|
||||
|
|
|
|||
|
|
@ -725,10 +725,8 @@ async function writeBinaryToVault(
|
|||
path: string,
|
||||
data: Uint8Array,
|
||||
): Promise<void> {
|
||||
const buf = data.buffer.slice(
|
||||
data.byteOffset,
|
||||
data.byteOffset + data.byteLength,
|
||||
) as ArrayBuffer;
|
||||
const buf = new ArrayBuffer(data.byteLength);
|
||||
new Uint8Array(buf).set(data);
|
||||
const existing = plugin.app.vault.getAbstractFileByPath(path);
|
||||
if (existing instanceof TFile) {
|
||||
await plugin.app.vault.modifyBinary(existing, buf);
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export interface PdfExportOptions {
|
|||
export interface RenderedDoc {
|
||||
/** HTML for the page <body> — does NOT include the <body> tag itself. */
|
||||
bodyHtml: string;
|
||||
/** CSS rules captured from document.styleSheets, joined with newlines. */
|
||||
/** CSS rules captured from activeDocument.styleSheets, joined with newlines. */
|
||||
css: string;
|
||||
/** Document title. */
|
||||
title: string;
|
||||
|
|
@ -60,11 +60,11 @@ export async function exportToPdfBytes(
|
|||
doc: RenderedDoc,
|
||||
opts: PdfExportOptions = {},
|
||||
): Promise<Uint8Array> {
|
||||
const webview = document.createElement("webview") as WebviewElement;
|
||||
const webview = activeDocument.createElement("webview") as WebviewElement;
|
||||
webview.setAttribute("src", "app://obsidian.md/help.html");
|
||||
webview.setAttribute("nodeintegration", "true");
|
||||
webview.addClass("duckmage-export-webview");
|
||||
document.body.appendChild(webview);
|
||||
activeDocument.body.appendChild(webview);
|
||||
|
||||
try {
|
||||
await waitForWebviewLoad(webview);
|
||||
|
|
@ -113,7 +113,7 @@ function waitForWebviewLoad(webview: HTMLElement): Promise<void> {
|
|||
resolve();
|
||||
};
|
||||
webview.addEventListener("did-finish-load", onLoad);
|
||||
setTimeout(() => {
|
||||
window.setTimeout(() => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
webview.removeEventListener("did-finish-load", onLoad);
|
||||
|
|
@ -139,20 +139,20 @@ function buildInjectScript(doc: RenderedDoc): string {
|
|||
// Replace head with a single <style> block from captured CSS plus a
|
||||
// minimal print reset. We don't try to preserve the original head — the
|
||||
// host page is just a render context.
|
||||
const styleEl = document.createElement("style");
|
||||
const styleEl = activeDocument.createElement("style");
|
||||
styleEl.textContent = css;
|
||||
document.head.innerHTML = "";
|
||||
document.head.appendChild(styleEl);
|
||||
activeDocument.head.innerHTML = "";
|
||||
activeDocument.head.appendChild(styleEl);
|
||||
|
||||
// Force light theme — dark backgrounds print poorly. This matches the
|
||||
// better-export-pdf approach.
|
||||
document.body.className = "theme-light";
|
||||
document.body.removeAttribute("style");
|
||||
document.body.innerHTML = body;
|
||||
activeDocument.body.className = "theme-light";
|
||||
activeDocument.body.removeAttribute("style");
|
||||
activeDocument.body.innerHTML = body;
|
||||
|
||||
document.title = title;
|
||||
activeDocument.title = title;
|
||||
})();
|
||||
`;
|
||||
}
|
||||
|
||||
const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
|
||||
const sleep = (ms: number) => new Promise<void>((r) => window.setTimeout(r, ms));
|
||||
|
|
|
|||
|
|
@ -35,8 +35,7 @@ export class FolderTreePickerModal extends Modal {
|
|||
const topBar = contentEl.createDiv({ cls: "duckmage-table-picker-topbar" });
|
||||
topBar.createEl("button", {
|
||||
cls: "duckmage-rt-icon-btn duckmage-table-picker-view-btn",
|
||||
// eslint-disable-next-line obsidianmd/ui/sentence-case
|
||||
text: "🎲 Open tables view",
|
||||
text: "🎲 open tables view",
|
||||
title: "Open random tables view",
|
||||
}).addEventListener("click", () => this.onOpenView!());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ export class HexEditorModal extends HexmakerModal {
|
|||
cls: "duckmage-terrain-header-swatch",
|
||||
});
|
||||
if (paletteEntry)
|
||||
swatch.setCssProps({ "background-color": paletteEntry.color });
|
||||
swatch.setCssProps({ "--duckmage-bg": paletteEntry.color });
|
||||
if (iconToShow) {
|
||||
const img = swatch.createEl("img");
|
||||
img.src = getIconUrl(this.plugin, iconToShow);
|
||||
|
|
@ -335,7 +335,7 @@ export class HexEditorModal extends HexmakerModal {
|
|||
const nPath = this.plugin.hexPath(nx, ny, this.mapName);
|
||||
const terrain = getTerrainFromFile(this.app, nPath);
|
||||
const entry = terrain ? paletteMap.get(terrain) : undefined;
|
||||
if (entry) tile.setCssProps({ "background-color": entry.color });
|
||||
if (entry) tile.setCssProps({ "--duckmage-bg": entry.color });
|
||||
tile.addEventListener("click", () => {
|
||||
this.x = nx;
|
||||
this.y = ny;
|
||||
|
|
@ -445,7 +445,7 @@ export class HexEditorModal extends HexmakerModal {
|
|||
});
|
||||
|
||||
const preview = btn.createDiv({ cls: "duckmage-terrain-preview" });
|
||||
preview.setCssProps({ "background-color": entry.color });
|
||||
preview.setCssProps({ "--duckmage-bg": entry.color });
|
||||
|
||||
if (entry.icon) {
|
||||
createIconEl(
|
||||
|
|
@ -494,7 +494,7 @@ export class HexEditorModal extends HexmakerModal {
|
|||
|
||||
// GM icon palette — only when GM layer is active
|
||||
if (this.options.gmLayerActive) {
|
||||
section.createEl("p", { text: "GM icon", cls: "duckmage-icon-inline-label" }); // eslint-disable-line obsidianmd/ui/sentence-case
|
||||
section.createEl("p", { text: "Game master icon", cls: "duckmage-icon-inline-label" });
|
||||
this.renderIconGrid(
|
||||
section,
|
||||
visibleIcons,
|
||||
|
|
@ -788,7 +788,7 @@ export class HexEditorModal extends HexmakerModal {
|
|||
|
||||
input.addEventListener("focus", () => openDropdown());
|
||||
input.addEventListener("blur", () =>
|
||||
setTimeout(() => closeDropdown(), 150),
|
||||
window.setTimeout(() => closeDropdown(), 150),
|
||||
);
|
||||
input.addEventListener("input", () => {
|
||||
if (!isOpen) openDropdown();
|
||||
|
|
|
|||
|
|
@ -66,12 +66,10 @@ export class HexExportModal extends HexmakerModal {
|
|||
cls: "duckmage-export-tab-checkbox",
|
||||
});
|
||||
gmCb.checked = false;
|
||||
/* eslint-disable obsidianmd/ui/sentence-case -- GM is an acronym; Hidden/Secret are section names */
|
||||
gmRow.createEl("label", {
|
||||
text: "Include GM-only sections (Hidden, Secret)",
|
||||
text: "Include game master only sections (hidden, secret)",
|
||||
cls: "duckmage-export-tab-label",
|
||||
});
|
||||
/* eslint-enable obsidianmd/ui/sentence-case */
|
||||
gmRow.addEventListener("click", (e) => {
|
||||
if (e.target instanceof HTMLInputElement) return;
|
||||
gmCb.checked = !gmCb.checked;
|
||||
|
|
|
|||
|
|
@ -266,7 +266,7 @@ export class HexMapView extends ItemView {
|
|||
this.setViewportFontSize("");
|
||||
this.applyTransform();
|
||||
this.renderGrid();
|
||||
requestAnimationFrame(() => this.fitGridToView());
|
||||
window.requestAnimationFrame(() => this.fitGridToView());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -425,9 +425,9 @@ export class HexMapView extends ItemView {
|
|||
this.viewportEl?.addClass("is-dragging");
|
||||
});
|
||||
|
||||
this.registerDomEvent(document, "mousemove", (e: MouseEvent) => {
|
||||
this.registerDomEvent(activeDocument, "mousemove", (e: MouseEvent) => {
|
||||
if (isTerrainPainting) {
|
||||
const el = document.elementFromPoint(
|
||||
const el = activeDocument.elementFromPoint(
|
||||
e.clientX,
|
||||
e.clientY,
|
||||
) as HTMLElement | null;
|
||||
|
|
@ -450,7 +450,7 @@ export class HexMapView extends ItemView {
|
|||
return;
|
||||
}
|
||||
if (this.drawingMode === "terrain" || this.drawingMode === "icon") {
|
||||
const el = document.elementFromPoint(
|
||||
const el = activeDocument.elementFromPoint(
|
||||
e.clientX,
|
||||
e.clientY,
|
||||
) as HTMLElement | null;
|
||||
|
|
@ -478,7 +478,7 @@ export class HexMapView extends ItemView {
|
|||
}
|
||||
});
|
||||
|
||||
this.registerDomEvent(document, "mouseup", () => {
|
||||
this.registerDomEvent(activeDocument, "mouseup", () => {
|
||||
if (isTerrainPainting) {
|
||||
if (this.drawingMode === "terrain") this.commitTerrainStroke();
|
||||
else if (this.drawingMode === "icon") this.commitIconStroke();
|
||||
|
|
@ -615,7 +615,7 @@ export class HexMapView extends ItemView {
|
|||
|
||||
this.backBtn = mapNavGroup.createEl("button", {
|
||||
cls: "duckmage-map-back-btn",
|
||||
text: "← Back", // eslint-disable-line obsidianmd/ui/sentence-case
|
||||
text: "← back",
|
||||
title: "Back to previous map",
|
||||
});
|
||||
this.backBtn.hide();
|
||||
|
|
@ -707,7 +707,7 @@ export class HexMapView extends ItemView {
|
|||
);
|
||||
|
||||
this.renderGrid();
|
||||
requestAnimationFrame(() => this.fitGridToView());
|
||||
window.requestAnimationFrame(() => this.fitGridToView());
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
|
@ -1439,7 +1439,7 @@ export class HexMapView extends ItemView {
|
|||
flushKeys.some((k) => this.flushing.has(k)) &&
|
||||
Date.now() < deadline
|
||||
) {
|
||||
await new Promise<void>((r) => setTimeout(r, 30));
|
||||
await new Promise<void>((r) => window.setTimeout(r, 30));
|
||||
}
|
||||
|
||||
for (let attempt = 1; attempt <= 2; attempt++) {
|
||||
|
|
@ -1454,7 +1454,7 @@ export class HexMapView extends ItemView {
|
|||
this.renderGrid();
|
||||
return;
|
||||
}
|
||||
await new Promise<void>((r) => setTimeout(r, 300));
|
||||
await new Promise<void>((r) => window.setTimeout(r, 300));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1498,7 +1498,7 @@ export class HexMapView extends ItemView {
|
|||
: this.plugin.settings.pathTypes[0];
|
||||
if (activeType) {
|
||||
this.pathBtnSwatch.setCssProps({
|
||||
"background-color": activeType.color,
|
||||
"--duckmage-bg": activeType.color,
|
||||
});
|
||||
this.pathBtnSwatch.show();
|
||||
} else {
|
||||
|
|
@ -1549,19 +1549,16 @@ export class HexMapView extends ItemView {
|
|||
if (this.drawingMode === "terrain") {
|
||||
if (this.terrainPickMode) {
|
||||
// Eyedropper waiting for a click — show ⌖ as the preview
|
||||
if (this.terrainToolbarBtn) {
|
||||
this.terrainToolbarBtn.setCssProps({
|
||||
"border-color": "var(--interactive-accent)",
|
||||
color: "var(--interactive-accent)",
|
||||
});
|
||||
}
|
||||
this.terrainToolbarBtn?.removeClass("is-terrain-preview");
|
||||
this.terrainToolbarBtn?.addClass("is-eyedropper-active");
|
||||
if (this.terrainBtnPreview) {
|
||||
this.terrainBtnPreview.setCssProps({ "background-color": "" });
|
||||
this.terrainBtnPreview.setCssProps({ "--duckmage-bg": "" });
|
||||
this.terrainBtnPreview.show();
|
||||
this.terrainBtnPreview.textContent = "⌖";
|
||||
}
|
||||
} else {
|
||||
if (this.terrainBtnPreview) this.terrainBtnPreview.textContent = "";
|
||||
this.terrainToolbarBtn?.removeClass("is-eyedropper-active");
|
||||
const entry = this.paintTerrainName
|
||||
? this.plugin
|
||||
.getMapPalette(this.activeMapName)
|
||||
|
|
@ -1569,28 +1566,28 @@ export class HexMapView extends ItemView {
|
|||
: undefined;
|
||||
if (entry) {
|
||||
if (this.terrainToolbarBtn) {
|
||||
this.terrainToolbarBtn.setCssProps({ "border-color": entry.color });
|
||||
this.terrainToolbarBtn.addClass("is-terrain-preview");
|
||||
this.terrainToolbarBtn.setCssProps({
|
||||
"--duckmage-border-color": entry.color,
|
||||
});
|
||||
}
|
||||
if (this.terrainBtnPreview) {
|
||||
this.terrainBtnPreview.setCssProps({
|
||||
"background-color": entry.color,
|
||||
"--duckmage-bg": entry.color,
|
||||
});
|
||||
this.terrainBtnPreview.show();
|
||||
}
|
||||
} else {
|
||||
// Clear mode — show active state without a color
|
||||
if (this.terrainToolbarBtn) {
|
||||
this.terrainToolbarBtn.setCssProps({ "border-color": "" });
|
||||
}
|
||||
this.terrainToolbarBtn?.removeClass("is-terrain-preview");
|
||||
if (this.terrainBtnPreview) {
|
||||
this.terrainBtnPreview.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this.terrainToolbarBtn) {
|
||||
this.terrainToolbarBtn.setCssProps({ "border-color": "", color: "" });
|
||||
}
|
||||
this.terrainToolbarBtn?.removeClass("is-terrain-preview");
|
||||
this.terrainToolbarBtn?.removeClass("is-eyedropper-active");
|
||||
if (this.terrainBtnPreview) {
|
||||
this.terrainBtnPreview.hide();
|
||||
}
|
||||
|
|
@ -1918,7 +1915,7 @@ export class HexMapView extends ItemView {
|
|||
if (t !== undefined || i !== undefined) {
|
||||
this.renderGrid(t, i);
|
||||
} else {
|
||||
setTimeout(() => this.renderGrid(), 300);
|
||||
window.setTimeout(() => this.renderGrid(), 300);
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -2731,7 +2728,7 @@ export class HexMapView extends ItemView {
|
|||
while (true) {
|
||||
if (attempt > 0)
|
||||
await new Promise<void>((r) =>
|
||||
setTimeout(r, Math.min(200 * (1 << (attempt - 1)), 2000)),
|
||||
window.setTimeout(r, Math.min(200 * (1 << (attempt - 1)), 2000)),
|
||||
);
|
||||
try {
|
||||
const onDisk = !!this.app.vault.getAbstractFileByPath(path);
|
||||
|
|
@ -2798,7 +2795,7 @@ export class HexMapView extends ItemView {
|
|||
while (true) {
|
||||
if (attempt > 0)
|
||||
await new Promise<void>((r) =>
|
||||
setTimeout(r, Math.min(200 * (1 << (attempt - 1)), 2000)),
|
||||
window.setTimeout(r, Math.min(200 * (1 << (attempt - 1)), 2000)),
|
||||
);
|
||||
try {
|
||||
const onDisk = !!this.app.vault.getAbstractFileByPath(path);
|
||||
|
|
@ -2861,7 +2858,7 @@ export class HexMapView extends ItemView {
|
|||
while (true) {
|
||||
if (attempt > 0)
|
||||
await new Promise<void>((r) =>
|
||||
setTimeout(r, Math.min(200 * (1 << (attempt - 1)), 2000)),
|
||||
window.setTimeout(r, Math.min(200 * (1 << (attempt - 1)), 2000)),
|
||||
);
|
||||
try {
|
||||
const onDisk = !!this.app.vault.getAbstractFileByPath(path);
|
||||
|
|
@ -3105,7 +3102,7 @@ export class HexMapView extends ItemView {
|
|||
});
|
||||
|
||||
const svgNS = "http://www.w3.org/2000/svg";
|
||||
const svg = document.createElementNS(svgNS, "svg");
|
||||
const svg = activeDocument.createElementNS(svgNS, "svg");
|
||||
svg.classList.add("duckmage-path-svg");
|
||||
const w = gridContainer.offsetLeft + gridContainer.offsetWidth + 20;
|
||||
const h = gridContainer.offsetTop + gridContainer.offsetHeight + 20;
|
||||
|
|
@ -3125,7 +3122,7 @@ export class HexMapView extends ItemView {
|
|||
dashArray = "",
|
||||
smooth = true,
|
||||
) => {
|
||||
const path = document.createElementNS(svgNS, "path");
|
||||
const path = activeDocument.createElementNS(svgNS, "path");
|
||||
path.setAttribute("d", smooth ? smoothPath(pts) : sharpPath(pts));
|
||||
path.setAttribute("stroke", color);
|
||||
path.setAttribute("stroke-width", String(strokeWidth));
|
||||
|
|
@ -3174,7 +3171,7 @@ export class HexMapView extends ItemView {
|
|||
const color = activeType?.color ?? "#888888";
|
||||
const pos = centerMap.get(this.activePathEnd);
|
||||
if (pos) {
|
||||
const circle = document.createElementNS(svgNS, "circle");
|
||||
const circle = activeDocument.createElementNS(svgNS, "circle");
|
||||
circle.setAttribute("cx", String(pos.cx));
|
||||
circle.setAttribute("cy", String(pos.cy));
|
||||
circle.setAttribute("r", "5");
|
||||
|
|
@ -3199,7 +3196,7 @@ export class HexMapView extends ItemView {
|
|||
origImg.hide();
|
||||
origImg.setAttribute("data-svg-elevated", "1");
|
||||
}
|
||||
const imgEl = document.createElementNS(svgNS, "image");
|
||||
const imgEl = activeDocument.createElementNS(svgNS, "image");
|
||||
const iconW = hexEl.offsetWidth * 0.78;
|
||||
const iconH = hexEl.offsetHeight * 0.78;
|
||||
imgEl.setAttribute("x", String(pos.cx - iconW / 2));
|
||||
|
|
@ -3222,7 +3219,7 @@ export class HexMapView extends ItemView {
|
|||
const pos = centerMap.get(`${x}_${y}`);
|
||||
if (!pos) return;
|
||||
const hasTerrain = !!hexEl.style.backgroundColor;
|
||||
const textEl = document.createElementNS(svgNS, "text");
|
||||
const textEl = activeDocument.createElementNS(svgNS, "text");
|
||||
textEl.setAttribute("x", String(pos.cx));
|
||||
// Nudge label toward bottom of hex (same visual position as the HTML label)
|
||||
textEl.setAttribute("y", String(pos.cy + hexEl.offsetHeight * 0.28));
|
||||
|
|
@ -3257,7 +3254,7 @@ export class HexMapView extends ItemView {
|
|||
const pos = centerMap.get(key);
|
||||
if (!pos) return;
|
||||
const size = Math.round(hexEl.offsetWidth * 0.38);
|
||||
const imgEl = document.createElementNS(svgNS, "image");
|
||||
const imgEl = activeDocument.createElementNS(svgNS, "image");
|
||||
imgEl.setAttribute("x", String(pos.cx + hexEl.offsetWidth * 0.12));
|
||||
imgEl.setAttribute("y", String(pos.cy - hexEl.offsetHeight * 0.46));
|
||||
imgEl.setAttribute("width", String(size));
|
||||
|
|
@ -3360,7 +3357,7 @@ export class HexMapView extends ItemView {
|
|||
|
||||
// ── SVG setup ─────────────────────────────────────────────────────────────
|
||||
const svgNS = "http://www.w3.org/2000/svg";
|
||||
const svg = document.createElementNS(svgNS, "svg");
|
||||
const svg = activeDocument.createElementNS(svgNS, "svg");
|
||||
svg.classList.add("duckmage-faction-svg");
|
||||
const w = gridContainer.offsetLeft + gridContainer.offsetWidth + 20;
|
||||
const h = gridContainer.offsetTop + gridContainer.offsetHeight + 20;
|
||||
|
|
@ -3458,7 +3455,7 @@ export class HexMapView extends ItemView {
|
|||
if (rings.length === 0) continue;
|
||||
|
||||
// ── Render filled blob(s) ─────────────────────────────────────────────
|
||||
const g = document.createElementNS(svgNS, "g");
|
||||
const g = activeDocument.createElementNS(svgNS, "g");
|
||||
g.setAttribute("opacity", "0.45");
|
||||
svg.appendChild(g);
|
||||
|
||||
|
|
@ -3470,7 +3467,7 @@ export class HexMapView extends ItemView {
|
|||
`${i === 0 ? "M" : "L"}${c[0].toFixed(1)},${c[1].toFixed(1)}`,
|
||||
)
|
||||
.join(" ") + " Z";
|
||||
const path = document.createElementNS(svgNS, "path");
|
||||
const path = activeDocument.createElementNS(svgNS, "path");
|
||||
path.setAttribute("d", d);
|
||||
path.setAttribute("fill", color);
|
||||
path.setAttribute("stroke", color);
|
||||
|
|
@ -3691,7 +3688,7 @@ export class HexMapView extends ItemView {
|
|||
|
||||
// ── SVG setup ───────────────────────────────────────────────────────────
|
||||
const svgNS = "http://www.w3.org/2000/svg";
|
||||
const svg = document.createElementNS(svgNS, "svg");
|
||||
const svg = activeDocument.createElementNS(svgNS, "svg");
|
||||
svg.classList.add("duckmage-region-svg");
|
||||
const svgW = gridContainer.offsetLeft + gridContainer.offsetWidth + 40;
|
||||
const svgH = gridContainer.offsetTop + gridContainer.offsetHeight + 40;
|
||||
|
|
@ -3806,7 +3803,7 @@ export class HexMapView extends ItemView {
|
|||
}
|
||||
|
||||
// ── Render filled blob(s) ───────────────────────────────────────────
|
||||
const g = document.createElementNS(svgNS, "g");
|
||||
const g = activeDocument.createElementNS(svgNS, "g");
|
||||
g.setAttribute("opacity", "0.45");
|
||||
svg.appendChild(g);
|
||||
|
||||
|
|
@ -3818,7 +3815,7 @@ export class HexMapView extends ItemView {
|
|||
`${i === 0 ? "M" : "L"}${c[0].toFixed(1)},${c[1].toFixed(1)}`,
|
||||
)
|
||||
.join(" ") + " Z";
|
||||
const path = document.createElementNS(svgNS, "path");
|
||||
const path = activeDocument.createElementNS(svgNS, "path");
|
||||
path.setAttribute("d", d);
|
||||
path.setAttribute("fill", color);
|
||||
// Stroke bridges the inter-hex gap; round joins/caps smooth blob edges
|
||||
|
|
@ -3833,7 +3830,7 @@ export class HexMapView extends ItemView {
|
|||
// ── Region name label (full opacity, above fill) ────────────────────
|
||||
// Font size scales with sqrt(hexCount) so larger regions get bigger labels
|
||||
const fontSize = Math.round(Math.min(10 + 3 * Math.sqrt(n), 52));
|
||||
const text = document.createElementNS(svgNS, "text");
|
||||
const text = activeDocument.createElementNS(svgNS, "text");
|
||||
text.setAttribute("x", mx.toFixed(1));
|
||||
text.setAttribute("y", my.toFixed(1));
|
||||
text.setAttribute("text-anchor", "middle");
|
||||
|
|
@ -4031,8 +4028,8 @@ export class HexMapView extends ItemView {
|
|||
};
|
||||
|
||||
const onUp = () => {
|
||||
document.removeEventListener("mousemove", onMove);
|
||||
document.removeEventListener("mouseup", onUp);
|
||||
activeDocument.removeEventListener("mousemove", onMove);
|
||||
activeDocument.removeEventListener("mouseup", onUp);
|
||||
tokenEl.removeClass("duckmage-token-dragging");
|
||||
|
||||
if (closestHex) {
|
||||
|
|
@ -4066,8 +4063,8 @@ export class HexMapView extends ItemView {
|
|||
}
|
||||
};
|
||||
|
||||
document.addEventListener("mousemove", onMove);
|
||||
document.addEventListener("mouseup", onUp);
|
||||
activeDocument.addEventListener("mousemove", onMove);
|
||||
activeDocument.addEventListener("mouseup", onUp);
|
||||
}
|
||||
|
||||
private openTokenEditor(token: TokenEntry): void {
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ export class OverlayPanel extends HexSidePanel {
|
|||
for (const opt of OVERLAY_OPTIONS) {
|
||||
const row = panel.createDiv({ cls: "duckmage-overlay-row" });
|
||||
|
||||
const cb = document.createElement("input");
|
||||
const cb = activeDocument.createElement("input");
|
||||
cb.type = "checkbox";
|
||||
cb.checked = true; // default — refreshed in syncToRegion()
|
||||
row.appendChild(cb);
|
||||
|
|
@ -155,7 +155,7 @@ export class OverlayPanel extends HexSidePanel {
|
|||
|
||||
// Show tokens — default on
|
||||
const tokensRow = panel.createDiv({ cls: "duckmage-overlay-row" });
|
||||
const tokensCb = document.createElement("input");
|
||||
const tokensCb = activeDocument.createElement("input");
|
||||
tokensCb.type = "checkbox";
|
||||
tokensCb.checked = true;
|
||||
tokensRow.appendChild(tokensCb);
|
||||
|
|
@ -181,7 +181,7 @@ export class OverlayPanel extends HexSidePanel {
|
|||
|
||||
// Faction overlay — triggers a re-render rather than a CSS class toggle
|
||||
const factionRow = panel.createDiv({ cls: "duckmage-overlay-row" });
|
||||
const factionCb = document.createElement("input");
|
||||
const factionCb = activeDocument.createElement("input");
|
||||
factionCb.type = "checkbox";
|
||||
factionCb.checked = false; // default — refreshed in syncToRegion()
|
||||
factionRow.appendChild(factionCb);
|
||||
|
|
@ -207,7 +207,7 @@ export class OverlayPanel extends HexSidePanel {
|
|||
|
||||
// Region overlay — same pattern as faction
|
||||
const regionRow = panel.createDiv({ cls: "duckmage-overlay-row" });
|
||||
const regionCb = document.createElement("input");
|
||||
const regionCb = activeDocument.createElement("input");
|
||||
regionCb.type = "checkbox";
|
||||
regionCb.checked = false;
|
||||
regionRow.appendChild(regionCb);
|
||||
|
|
@ -233,7 +233,7 @@ export class OverlayPanel extends HexSidePanel {
|
|||
|
||||
// GM layer — default on (unlike the opt-in overlays above)
|
||||
const gmRow = panel.createDiv({ cls: "duckmage-overlay-row" });
|
||||
const gmCb = document.createElement("input");
|
||||
const gmCb = activeDocument.createElement("input");
|
||||
gmCb.type = "checkbox";
|
||||
gmCb.checked = true;
|
||||
gmRow.appendChild(gmCb);
|
||||
|
|
|
|||
|
|
@ -230,11 +230,11 @@ export class IconPickerModal extends HexmakerModal {
|
|||
}
|
||||
this.plugin.loadAvailableIcons();
|
||||
statusEl.setText(`Added ${added} icon${added === 1 ? "" : "s"}.`);
|
||||
setTimeout(() => this.render(), 800);
|
||||
window.setTimeout(() => this.render(), 800);
|
||||
};
|
||||
|
||||
// Hidden file input
|
||||
const fileInput = document.createElement("input");
|
||||
const fileInput = activeDocument.createElement("input");
|
||||
fileInput.type = "file";
|
||||
fileInput.accept = ".png,.jpg,.jpeg,.gif,.svg,.webp";
|
||||
fileInput.multiple = true;
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ export class MapLinkModal extends HexmakerModal {
|
|||
};
|
||||
|
||||
mapInput.addEventListener("focus", () => openDropdown(""));
|
||||
mapInput.addEventListener("blur", () => setTimeout(() => closeDropdown(), 150));
|
||||
mapInput.addEventListener("blur", () => window.setTimeout(() => closeDropdown(), 150));
|
||||
mapInput.addEventListener("input", () => {
|
||||
if (!isOpen) openDropdown(mapInput.value);
|
||||
else populateDropdown(mapInput.value);
|
||||
|
|
|
|||
|
|
@ -354,7 +354,7 @@ export class MapModal extends HexmakerModal {
|
|||
(currentMap?.terrainType === t.name ? " is-selected" : ""),
|
||||
});
|
||||
const preview = tile.createDiv({ cls: "duckmage-terrain-preview" });
|
||||
preview.setCssProps({ "background-color": t.color });
|
||||
preview.setCssProps({ "--duckmage-bg": t.color });
|
||||
if (t.icon) {
|
||||
createIconEl(
|
||||
preview,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export class PainterContextMenu {
|
|||
open(x: number, y: number): void {
|
||||
this.close();
|
||||
|
||||
const menu = document.createElement("div");
|
||||
const menu = activeDocument.createElement("div");
|
||||
menu.className = "duckmage-painter-ctx-menu";
|
||||
menu.style.left = `${x}px`;
|
||||
menu.style.top = `${y}px`;
|
||||
|
|
@ -33,7 +33,7 @@ export class PainterContextMenu {
|
|||
opts.push(...this.extra);
|
||||
|
||||
for (const opt of opts) {
|
||||
const item = document.createElement("div");
|
||||
const item = activeDocument.createElement("div");
|
||||
item.className = "duckmage-painter-ctx-item";
|
||||
item.textContent = opt.label;
|
||||
item.addEventListener("mousedown", (e) => {
|
||||
|
|
@ -45,11 +45,11 @@ export class PainterContextMenu {
|
|||
menu.appendChild(item);
|
||||
}
|
||||
|
||||
document.body.appendChild(menu);
|
||||
activeDocument.body.appendChild(menu);
|
||||
this.el = menu;
|
||||
|
||||
// Clamp to viewport after layout so the menu doesn't overflow the screen edge.
|
||||
requestAnimationFrame(() => {
|
||||
window.requestAnimationFrame(() => {
|
||||
if (!this.el) return;
|
||||
const rect = this.el.getBoundingClientRect();
|
||||
if (rect.right > window.innerWidth)
|
||||
|
|
@ -69,14 +69,14 @@ export class PainterContextMenu {
|
|||
};
|
||||
const onScroll = () => this.close();
|
||||
|
||||
document.addEventListener("pointerdown", onPointerDown, { capture: true });
|
||||
document.addEventListener("keydown", onKeyDown, { capture: true });
|
||||
document.addEventListener("scroll", onScroll, { capture: true, passive: true });
|
||||
activeDocument.addEventListener("pointerdown", onPointerDown, { capture: true });
|
||||
activeDocument.addEventListener("keydown", onKeyDown, { capture: true });
|
||||
activeDocument.addEventListener("scroll", onScroll, { capture: true, passive: true });
|
||||
|
||||
this.cleanupFns.push(
|
||||
() => document.removeEventListener("pointerdown", onPointerDown, { capture: true }),
|
||||
() => document.removeEventListener("keydown", onKeyDown, { capture: true }),
|
||||
() => document.removeEventListener("scroll", onScroll, { capture: true }),
|
||||
() => activeDocument.removeEventListener("pointerdown", onPointerDown, { capture: true }),
|
||||
() => activeDocument.removeEventListener("keydown", onKeyDown, { capture: true }),
|
||||
() => activeDocument.removeEventListener("scroll", onScroll, { capture: true }),
|
||||
);
|
||||
}, 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { PathTypeEditorModal } from "./PathTypeEditorModal";
|
|||
|
||||
export function buildPathPreviewSvg(pt: PathType): SVGElement {
|
||||
const svgNS = "http://www.w3.org/2000/svg";
|
||||
const svg = document.createElementNS(svgNS, "svg");
|
||||
const svg = activeDocument.createElementNS(svgNS, "svg");
|
||||
svg.setAttribute("width", "36");
|
||||
svg.setAttribute("height", "36");
|
||||
svg.setAttribute("viewBox", "0 0 36 36");
|
||||
|
|
@ -23,7 +23,7 @@ export function buildPathPreviewSvg(pt: PathType): SVGElement {
|
|||
const angle = (Math.PI / 180) * (60 * i);
|
||||
hexPts.push(`${cx + r * Math.cos(angle)},${cy + r * Math.sin(angle)}`);
|
||||
}
|
||||
const hex = document.createElementNS(svgNS, "polygon");
|
||||
const hex = activeDocument.createElementNS(svgNS, "polygon");
|
||||
hex.setAttribute("points", hexPts.join(" "));
|
||||
hex.setAttribute("fill", "var(--background-secondary)");
|
||||
hex.setAttribute("stroke", "var(--background-modifier-border)");
|
||||
|
|
@ -48,7 +48,7 @@ export function buildPathPreviewSvg(pt: PathType): SVGElement {
|
|||
pathD = "M 4 18 L 32 18";
|
||||
}
|
||||
|
||||
const pathEl = document.createElementNS(svgNS, "path");
|
||||
const pathEl = activeDocument.createElementNS(svgNS, "path");
|
||||
pathEl.setAttribute("d", pathD);
|
||||
pathEl.setAttribute("stroke", pt.color);
|
||||
pathEl.setAttribute("stroke-width", strokeW);
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ export class SubmapPickerModal extends HexmakerModal {
|
|||
for (const t of palette) {
|
||||
const tile = terrainGrid.createDiv({ cls: "duckmage-terrain-option" });
|
||||
const preview = tile.createDiv({ cls: "duckmage-terrain-preview" });
|
||||
preview.setCssProps({ "background-color": t.color });
|
||||
preview.setCssProps({ "--duckmage-bg": t.color });
|
||||
if (t.icon) {
|
||||
createIconEl(
|
||||
preview,
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ export class TerrainPickerModal extends HexmakerModal {
|
|||
for (const entry of this.palette) {
|
||||
const btn = grid.createDiv({ cls: "duckmage-terrain-option" });
|
||||
const preview = btn.createDiv({ cls: "duckmage-terrain-preview" });
|
||||
preview.setCssProps({ 'background-color': entry.color });
|
||||
preview.setCssProps({ '--duckmage-bg': entry.color });
|
||||
if (entry.icon) {
|
||||
createIconEl(preview, getIconUrl(this.plugin, entry.icon), entry.name, entry.iconColor, "duckmage-terrain-preview-icon");
|
||||
}
|
||||
|
|
@ -175,7 +175,7 @@ export class TerrainPickerModal extends HexmakerModal {
|
|||
|
||||
// Standard colored preview + icon (same as pick mode)
|
||||
const preview = tile.createDiv({ cls: "duckmage-terrain-preview" });
|
||||
preview.setCssProps({ 'background-color': entry.color });
|
||||
preview.setCssProps({ '--duckmage-bg': entry.color });
|
||||
if (entry.icon) {
|
||||
createIconEl(preview, getIconUrl(this.plugin, entry.icon), entry.name, entry.iconColor, "duckmage-terrain-preview-icon");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -326,10 +326,10 @@ export class HexTableView extends ItemView {
|
|||
if (!HEX_PATTERN.test(file.path)) return;
|
||||
|
||||
const existing = this.updateTimers.get(file.path);
|
||||
if (existing) clearTimeout(existing);
|
||||
if (existing) window.clearTimeout(existing);
|
||||
this.updateTimers.set(
|
||||
file.path,
|
||||
setTimeout(() => {
|
||||
window.setTimeout(() => {
|
||||
this.updateTimers.delete(file.path);
|
||||
void this.updateRow(file.path);
|
||||
}, 300),
|
||||
|
|
@ -352,7 +352,7 @@ export class HexTableView extends ItemView {
|
|||
}
|
||||
|
||||
onClose(): Promise<void> {
|
||||
for (const timer of this.updateTimers.values()) clearTimeout(timer);
|
||||
for (const timer of this.updateTimers.values()) window.clearTimeout(timer);
|
||||
this.updateTimers.clear();
|
||||
this.contentEl.empty();
|
||||
return Promise.resolve();
|
||||
|
|
@ -440,7 +440,7 @@ export class HexTableView extends ItemView {
|
|||
if (this.filterYMaxInput) this.filterYMaxInput.placeholder = String(yMax);
|
||||
|
||||
// ── Phase 1: skeleton render (sync — coords + terrain from metadata cache) ──
|
||||
const table = document.createElement("table");
|
||||
const table = activeDocument.createElement("table");
|
||||
table.className = "duckmage-hex-table";
|
||||
|
||||
const thead = table.createEl("thead");
|
||||
|
|
@ -494,7 +494,7 @@ export class HexTableView extends ItemView {
|
|||
const ok = await fillBatch(i, REST_BATCH);
|
||||
if (!ok) break;
|
||||
// Yield to the browser between batches to keep the UI responsive
|
||||
await new Promise<void>((r) => setTimeout(r, 0));
|
||||
await new Promise<void>((r) => window.setTimeout(r, 0));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -667,7 +667,7 @@ export class HexTableView extends ItemView {
|
|||
const leaf = this.app.workspace.getLeaf("tab");
|
||||
await leaf.setViewState({ type: VIEW_TYPE_HEX_MAP });
|
||||
// Wait one frame for the view to render before centering
|
||||
setTimeout(
|
||||
window.setTimeout(
|
||||
() => (leaf.view as unknown as WithCenterOnHex).centerOnHex(x, y),
|
||||
100,
|
||||
);
|
||||
|
|
@ -898,12 +898,12 @@ export class HexTableView extends ItemView {
|
|||
|
||||
// <col> elements + explicit table width is the only reliable way to drive
|
||||
// table-layout:fixed column widths across browsers.
|
||||
const colgroup = document.createElement("colgroup");
|
||||
const colgroup = activeDocument.createElement("colgroup");
|
||||
const cols: HTMLTableColElement[] = [];
|
||||
let totalWidth = 0;
|
||||
for (let i = 0; i < ths.length; i++) {
|
||||
const w = defaultWidths[i] ?? 160;
|
||||
const col = document.createElement("col");
|
||||
const col = activeDocument.createElement("col");
|
||||
col.style.width = `${w}px`;
|
||||
colgroup.appendChild(col);
|
||||
cols.push(col);
|
||||
|
|
@ -922,7 +922,7 @@ export class HexTableView extends ItemView {
|
|||
const startX = e.clientX;
|
||||
const startW = parseInt(col.style.width, 10);
|
||||
const startTW = parseInt(table.style.width, 10);
|
||||
document.body.setCssProps({ cursor: "col-resize" });
|
||||
activeDocument.body.setCssProps({ cursor: "col-resize" });
|
||||
|
||||
const onMove = (me: MouseEvent) => {
|
||||
const newW = Math.max(20, startW + me.clientX - startX);
|
||||
|
|
@ -930,12 +930,12 @@ export class HexTableView extends ItemView {
|
|||
table.style.width = `${startTW + (newW - startW)}px`;
|
||||
};
|
||||
const onUp = () => {
|
||||
document.body.setCssProps({ cursor: "" });
|
||||
document.removeEventListener("mousemove", onMove);
|
||||
document.removeEventListener("mouseup", onUp);
|
||||
activeDocument.body.setCssProps({ cursor: "" });
|
||||
activeDocument.removeEventListener("mousemove", onMove);
|
||||
activeDocument.removeEventListener("mouseup", onUp);
|
||||
};
|
||||
document.addEventListener("mousemove", onMove);
|
||||
document.addEventListener("mouseup", onUp);
|
||||
activeDocument.addEventListener("mousemove", onMove);
|
||||
activeDocument.addEventListener("mouseup", onUp);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ export class RandomTableEditorModal extends HexmakerModal {
|
|||
resultInput.placeholder = "Result…";
|
||||
resultInput.rows = 1;
|
||||
// Size to content immediately, then keep in sync as the user types
|
||||
requestAnimationFrame(() => autoResize(resultInput));
|
||||
window.requestAnimationFrame(() => autoResize(resultInput));
|
||||
resultInput.addEventListener("input", () => {
|
||||
const val = resultInput.value;
|
||||
const m = /^\[\[(.+?)(?:\|[^\]]+)?\]\]$/.exec(val.trim());
|
||||
|
|
|
|||
|
|
@ -269,7 +269,7 @@ export class RandomTableModal extends HexmakerModal {
|
|||
e.stopPropagation();
|
||||
void navigator.clipboard.writeText(entry.result);
|
||||
copyBtn.setText("✓");
|
||||
setTimeout(() => copyBtn.setText("⎘"), 1200);
|
||||
window.setTimeout(() => copyBtn.setText("⎘"), 1200);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -314,7 +314,6 @@ export class RandomTableView extends ItemView {
|
|||
attr: { placeholder: "Generate from folder or file (optional)…" },
|
||||
});
|
||||
fromFolderInput.setAttribute("list", fromFolderDatalistId);
|
||||
fromFolderInput.setCssProps({ "margin-top": "6px" });
|
||||
|
||||
const createTable = async () => {
|
||||
const name = newInput.value.trim();
|
||||
|
|
@ -598,7 +597,7 @@ export class RandomTableView extends ItemView {
|
|||
}
|
||||
|
||||
private scheduleListRefresh(): void {
|
||||
if (this.listRefreshTimer !== null) clearTimeout(this.listRefreshTimer);
|
||||
if (this.listRefreshTimer !== null) window.clearTimeout(this.listRefreshTimer);
|
||||
this.listRefreshTimer = window.setTimeout(() => {
|
||||
this.listRefreshTimer = null;
|
||||
void this.loadList();
|
||||
|
|
@ -606,7 +605,7 @@ export class RandomTableView extends ItemView {
|
|||
}
|
||||
|
||||
private scheduleDetailRefresh(): void {
|
||||
if (this.detailRefreshTimer !== null) clearTimeout(this.detailRefreshTimer);
|
||||
if (this.detailRefreshTimer !== null) window.clearTimeout(this.detailRefreshTimer);
|
||||
this.detailRefreshTimer = window.setTimeout(() => {
|
||||
this.detailRefreshTimer = null;
|
||||
if (this.viewMode === "workflows") {
|
||||
|
|
@ -940,26 +939,24 @@ export class RandomTableView extends ItemView {
|
|||
|
||||
const runBtn = this.detailEl.createEl("button", {
|
||||
text: "Roll workflow",
|
||||
cls: "duckmage-rt-roll-btn mod-cta",
|
||||
cls: "duckmage-rt-roll-btn duckmage-rt-roll-btn-spaced mod-cta",
|
||||
});
|
||||
runBtn.addEventListener("click", () => {
|
||||
new WorkflowWizardModal(this.app, this.plugin, file).open();
|
||||
});
|
||||
runBtn.setCssProps({ "margin-right": "8px" });
|
||||
|
||||
const copyWfLinkBtn = this.detailEl.createEl("button", {
|
||||
text: "🔗 copy link",
|
||||
cls: "duckmage-rt-copy-link-btn",
|
||||
cls: "duckmage-rt-copy-link-btn duckmage-rt-copy-link-btn-spaced",
|
||||
});
|
||||
copyWfLinkBtn.title = "Copy a Markdown link to open this workflow";
|
||||
copyWfLinkBtn.setCssProps({ "margin-bottom": "12px" });
|
||||
copyWfLinkBtn.addEventListener("click", () => {
|
||||
const vault = encodeURIComponent(this.app.vault.getName());
|
||||
const path = encodeURIComponent(file.path);
|
||||
const link = `[🔗 ${file.basename}](obsidian://duckmage-workflow?vault=${vault}&file=${path})`;
|
||||
void navigator.clipboard.writeText(link).then(() => {
|
||||
copyWfLinkBtn.setText("Copied!");
|
||||
setTimeout(() => copyWfLinkBtn.setText("🔗 copy link"), 1500);
|
||||
window.setTimeout(() => copyWfLinkBtn.setText("🔗 copy link"), 1500);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -985,8 +982,7 @@ export class RandomTableView extends ItemView {
|
|||
text: "Steps",
|
||||
cls: "duckmage-rt-history-label",
|
||||
});
|
||||
const list = stepsEl.createEl("ul");
|
||||
list.setCssProps({ margin: "0", "padding-left": "18px" });
|
||||
const list = stepsEl.createEl("ul", { cls: "duckmage-wf-steps-list" });
|
||||
for (const step of workflow.steps) {
|
||||
const li = list.createEl("li");
|
||||
let primaryName: string;
|
||||
|
|
@ -1600,7 +1596,7 @@ export class RandomTableView extends ItemView {
|
|||
e.stopPropagation();
|
||||
void navigator.clipboard.writeText(entry.result);
|
||||
copyBtn.setText("✓");
|
||||
setTimeout(() => copyBtn.setText("⎘"), 1200);
|
||||
window.setTimeout(() => copyBtn.setText("⎘"), 1200);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -1619,7 +1615,7 @@ export class RandomTableView extends ItemView {
|
|||
const link = `[🎲 ${file.basename}](obsidian://duckmage-roll?vault=${vault}&file=${path})`;
|
||||
void navigator.clipboard.writeText(link).then(() => {
|
||||
copyLinkBtn.setText("Copied!");
|
||||
setTimeout(() => copyLinkBtn.setText("🎲 copy link"), 1500);
|
||||
window.setTimeout(() => copyLinkBtn.setText("🎲 copy link"), 1500);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -1643,7 +1639,7 @@ export class RandomTableView extends ItemView {
|
|||
copyBtn.addEventListener("click", () => {
|
||||
void navigator.clipboard.writeText(resultTextarea.value);
|
||||
copyBtn.setText("Copied!");
|
||||
setTimeout(() => copyBtn.setText("Copy"), 1500);
|
||||
window.setTimeout(() => copyBtn.setText("Copy"), 1500);
|
||||
});
|
||||
// "Open note" button — shown only when the table has a linked folder
|
||||
const openNoteBtn = resultBtns.createEl("button", {
|
||||
|
|
@ -1684,7 +1680,7 @@ export class RandomTableView extends ItemView {
|
|||
link.addEventListener("click", () => {
|
||||
this.setViewMode("workflows");
|
||||
// After mode switch, select the workflow
|
||||
setTimeout(() => {
|
||||
window.setTimeout(() => {
|
||||
if (wfFile instanceof TFile) void this.loadWorkflow(wfFile);
|
||||
}, 50);
|
||||
});
|
||||
|
|
@ -1692,9 +1688,8 @@ export class RandomTableView extends ItemView {
|
|||
|
||||
const newWfLink = linksEl.createEl("a", {
|
||||
text: "+ new workflow with this table",
|
||||
cls: "duckmage-rt-entry-link",
|
||||
cls: "duckmage-rt-entry-link duckmage-rt-new-wf-link",
|
||||
});
|
||||
newWfLink.setCssProps({ "font-style": "italic" });
|
||||
newWfLink.addEventListener("click", () => {
|
||||
void (async () => {
|
||||
this.setViewMode("workflows");
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ export function registerRollerBlock(plugin: HexmakerPlugin): void {
|
|||
copyBtn.addEventListener("click", () => {
|
||||
void navigator.clipboard.writeText(resultTextarea.value);
|
||||
copyBtn.setText("Copied!");
|
||||
setTimeout(() => copyBtn.setText("Copy"), 1200);
|
||||
window.setTimeout(() => copyBtn.setText("Copy"), 1200);
|
||||
});
|
||||
|
||||
// History: scrollable list, each item individually copyable
|
||||
|
|
@ -99,7 +99,7 @@ export function registerRollerBlock(plugin: HexmakerPlugin): void {
|
|||
hCopyBtn.addEventListener("click", () => {
|
||||
void navigator.clipboard.writeText(item);
|
||||
hCopyBtn.setText("✓");
|
||||
setTimeout(() => hCopyBtn.setText("⎘"), 1000);
|
||||
window.setTimeout(() => hCopyBtn.setText("⎘"), 1000);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -148,8 +148,7 @@ export class WorkflowEditorModal extends HexmakerModal {
|
|||
updateValidation();
|
||||
});
|
||||
|
||||
const validationEl = templateSectionWrap.createDiv();
|
||||
validationEl.setCssProps({ "margin-top": "4px", "font-size": "0.85em" });
|
||||
const validationEl = templateSectionWrap.createDiv({ cls: "duckmage-wf-template-validation" });
|
||||
|
||||
// ── Description ───────────────────────────────────────────────────
|
||||
const descRow = contentEl.createDiv({
|
||||
|
|
@ -171,9 +170,8 @@ export class WorkflowEditorModal extends HexmakerModal {
|
|||
|
||||
// ── Results folder row ────────────────────────────────────────────
|
||||
const rfRow = contentEl.createDiv({
|
||||
cls: "duckmage-table-editor-name-row",
|
||||
cls: "duckmage-table-editor-name-row duckmage-wf-editor-results-row",
|
||||
});
|
||||
rfRow.setCssProps({ "margin-top": "8px" });
|
||||
rfRow.createEl("label", {
|
||||
text: "Results folder",
|
||||
cls: "duckmage-table-editor-name-label",
|
||||
|
|
|
|||
|
|
@ -118,7 +118,6 @@ export class WorkflowWizardModal extends HexmakerModal {
|
|||
this.resultTextarea = contentEl.createEl("textarea", {
|
||||
cls: "duckmage-wf-template-area",
|
||||
});
|
||||
this.resultTextarea.setCssProps({ "min-height": "120px" });
|
||||
this.resultTextarea.readOnly = true;
|
||||
this.resultTextarea.value = this.assembleResult();
|
||||
|
||||
|
|
@ -128,7 +127,7 @@ export class WorkflowWizardModal extends HexmakerModal {
|
|||
.writeText(this.resultTextarea?.value ?? "")
|
||||
.then(() => {
|
||||
copyResultBtn.setText("Copied!");
|
||||
setTimeout(() => copyResultBtn.setText("Copy result"), 1500);
|
||||
window.setTimeout(() => copyResultBtn.setText("Copy result"), 1500);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
13
src/utils.ts
13
src/utils.ts
|
|
@ -37,17 +37,10 @@ export function createIconEl(
|
|||
cls: string,
|
||||
): HTMLElement {
|
||||
if (iconColor) {
|
||||
const div = parent.createEl("div", { cls, title: alt });
|
||||
const div = parent.createEl("div", { cls: `${cls} duckmage-masked-icon`, title: alt });
|
||||
div.setCssProps({
|
||||
'mask-image': `url("${src}")`,
|
||||
'-webkit-mask-image': `url("${src}")`,
|
||||
'mask-size': 'contain',
|
||||
'-webkit-mask-size': 'contain',
|
||||
'mask-repeat': 'no-repeat',
|
||||
'-webkit-mask-repeat': 'no-repeat',
|
||||
'mask-position': 'center',
|
||||
'-webkit-mask-position': 'center',
|
||||
'background-color': iconColor,
|
||||
'--duckmage-mask-url': `url("${src}")`,
|
||||
'--duckmage-bg': iconColor,
|
||||
});
|
||||
return div;
|
||||
}
|
||||
|
|
|
|||
42
styles.css
42
styles.css
|
|
@ -392,7 +392,7 @@ div.duckmage-hex-icon {
|
|||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--background-modifier-border);
|
||||
background-color: var(--duckmage-bg, var(--background-modifier-border));
|
||||
cursor: pointer;
|
||||
border: 1px solid
|
||||
color-mix(in srgb, var(--background-modifier-border) 60%, transparent);
|
||||
|
|
@ -3051,6 +3051,7 @@ tr:hover .duckmage-rt-entry-copy-btn:hover {
|
|||
.duckmage-rt-from-folder-input {
|
||||
width: 100%;
|
||||
font-size: 0.85em;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
/* Confirm delete modal */
|
||||
|
|
@ -4665,3 +4666,42 @@ tr:hover .duckmage-rt-entry-copy-btn:hover {
|
|||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* ── JS-driven custom-property consumers ────────────────────────────────────
|
||||
Dynamic colors (terrain swatches, path swatches, etc.) are set as CSS
|
||||
custom properties via setCssProps; these rules consume them. Fallbacks
|
||||
keep the default appearance when the variable is unset.
|
||||
─────────────────────────────────────────────────────────────────────────── */
|
||||
.duckmage-terrain-header-swatch { background-color: var(--duckmage-bg, transparent); }
|
||||
.duckmage-terrain-preview { background-color: var(--duckmage-bg, transparent); }
|
||||
.duckmage-terrain-btn-preview { background-color: var(--duckmage-bg, transparent); }
|
||||
.duckmage-path-btn-swatch { background-color: var(--duckmage-bg, transparent); }
|
||||
.duckmage-draw-btn-terrain.is-terrain-preview {
|
||||
border-color: var(--duckmage-border-color, currentcolor);
|
||||
}
|
||||
.duckmage-draw-btn-terrain.is-eyedropper-active {
|
||||
border-color: var(--interactive-accent);
|
||||
color: var(--interactive-accent);
|
||||
}
|
||||
|
||||
/* Tinted icon helper — utils.ts createIconEl applies this class and sets
|
||||
--duckmage-mask-url + --duckmage-bg. */
|
||||
.duckmage-masked-icon {
|
||||
-webkit-mask-image: var(--duckmage-mask-url);
|
||||
-webkit-mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
-webkit-mask-position: center;
|
||||
mask-image: var(--duckmage-mask-url);
|
||||
mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
background-color: var(--duckmage-bg);
|
||||
}
|
||||
|
||||
/* Static-layout helpers replacing inline setCssProps calls in workflows view */
|
||||
.duckmage-rt-roll-btn-spaced { margin-right: 8px; }
|
||||
.duckmage-rt-copy-link-btn-spaced { margin-bottom: 12px; }
|
||||
.duckmage-wf-steps-list { margin: 0; padding-left: 18px; }
|
||||
.duckmage-rt-new-wf-link { font-style: italic; }
|
||||
.duckmage-wf-template-validation { margin-top: 4px; font-size: 0.85em; }
|
||||
.duckmage-wf-editor-results-row { margin-top: 8px; }
|
||||
|
|
|
|||
|
|
@ -35,5 +35,6 @@
|
|||
"1.1.13": "1.12.0",
|
||||
"1.1.14": "1.12.0",
|
||||
"1.1.15": "1.12.0",
|
||||
"1.2.0": "1.12.0"
|
||||
"1.2.0": "1.12.0",
|
||||
"1.2.1": "1.12.0"
|
||||
}
|
||||
Loading…
Reference in a new issue