feat: gaps in tailwind migration closed

This commit is contained in:
2026-05-04 12:27:07 +02:00
parent eca89e51d2
commit 4ab0bc7b4e
24 changed files with 3198 additions and 3005 deletions

46
assets/js/utils/color.js Normal file
View File

@@ -0,0 +1,46 @@
import { clamp } from "./dom.js";
export const cssVar = (name, fallback) => {
const value = window.getComputedStyle(document.documentElement).getPropertyValue(name).trim();
return value || fallback;
};
const parseRgbColor = (value) => {
if (!value) {
return null;
}
const hex = value.match(/^#([0-9a-f]{6})$/i);
if (hex) {
return {
r: Number.parseInt(hex[1].slice(0, 2), 16),
g: Number.parseInt(hex[1].slice(2, 4), 16),
b: Number.parseInt(hex[1].slice(4, 6), 16)
};
}
const rgb = value.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)/i);
if (!rgb) {
return null;
}
return {
r: Number.parseInt(rgb[1], 10),
g: Number.parseInt(rgb[2], 10),
b: Number.parseInt(rgb[3], 10)
};
};
export const normalizeMonacoColor = (value, fallback) => {
const rgb = parseRgbColor(value);
if (!rgb) {
return fallback;
}
return `#${[rgb.r, rgb.g, rgb.b]
.map((channel) => clamp(channel, 0, 255).toString(16).padStart(2, "0"))
.join("")}`;
};

34
assets/js/utils/dom.js Normal file
View File

@@ -0,0 +1,34 @@
export const clamp = (value, min, max) => Math.max(min, Math.min(value, max));
export const parseJsonObject = (value) => {
if (!value) {
return null;
}
try {
const parsed = JSON.parse(value);
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
} catch (_error) {
return null;
}
};
export const setMediaThumbnailLoaded = (image, loaded) => {
const thumbnail = image?.closest(".media-thumbnail");
if (!thumbnail) {
return;
}
if (loaded) {
thumbnail.classList.add("is-loaded");
} else {
thumbnail.classList.remove("is-loaded");
}
};
export const syncMediaThumbnailState = (root) => {
root.querySelectorAll(".media-thumbnail-image").forEach((image) => {
setMediaThumbnailLoaded(image, Boolean(image.complete && image.naturalWidth > 0));
});
};

43
assets/js/utils/layout.js Normal file
View File

@@ -0,0 +1,43 @@
import { clamp } from "./dom.js";
import { SIDEBAR_STORAGE_KEY, ASSISTANT_STORAGE_KEY } from "../constants.js";
export const shellWidth = (selector) => {
const shell = document.querySelector(selector);
if (!shell) {
return 0;
}
const width = Number.parseInt(shell.style.width || "0", 10);
return Number.isNaN(width) ? Math.round(shell.getBoundingClientRect().width) : width;
};
export const setShellWidth = (selector, width) => {
const shell = document.querySelector(selector);
if (shell) {
shell.style.width = `${width}px`;
shell.classList.remove("is-hidden");
}
};
export const persistWidth = (target, width) => {
const key = target === "assistant" ? ASSISTANT_STORAGE_KEY : SIDEBAR_STORAGE_KEY;
window.localStorage.setItem(key, String(width));
};
export const readStoredSize = (key, fallback, min, max) => {
const raw = window.localStorage.getItem(key);
if (!raw) {
return fallback;
}
const parsed = Number.parseInt(raw, 10);
if (Number.isNaN(parsed)) {
return fallback;
}
return clamp(parsed, min, max);
};

View File

@@ -0,0 +1,33 @@
export const loadScript = (src) =>
new Promise((resolve, reject) => {
const existing = document.querySelector(`script[src="${src}"]`);
if (existing) {
if (existing.dataset.loaded === "true") {
resolve();
return;
}
existing.addEventListener("load", () => resolve(), { once: true });
existing.addEventListener("error", () => reject(new Error(`Failed to load ${src}`)), {
once: true
});
return;
}
const script = document.createElement("script");
script.src = src;
script.async = true;
script.addEventListener(
"load",
() => {
script.dataset.loaded = "true";
resolve();
},
{ once: true }
);
script.addEventListener("error", () => reject(new Error(`Failed to load ${src}`)), {
once: true
});
document.head.appendChild(script);
});

View File

@@ -0,0 +1,30 @@
export const normalizeShortcutKey = (key) => String(key || "").toLowerCase();
export const shortcutTargetIsEditable = (event) => {
const tag = event.target?.tagName || null;
return event.target?.isContentEditable || ["INPUT", "TEXTAREA", "SELECT"].includes(tag);
};
export const shortcutMatchesEvent = (shortcut, event) => {
const primary = event.metaKey || event.ctrlKey;
return (
normalizeShortcutKey(event.key) === normalizeShortcutKey(shortcut.key) &&
primary === Boolean(shortcut.primary) &&
event.shiftKey === Boolean(shortcut.shift) &&
event.altKey === Boolean(shortcut.alt)
);
};
export const parseShortcutConfig = (value) => {
if (!value) {
return [];
}
try {
const parsed = JSON.parse(value);
return Array.isArray(parsed) ? parsed : [];
} catch (_error) {
return [];
}
};