window.fontTokens = window.fontTokens || {};
window.fontOverrides = window.fontOverrides || {};
window.selectedFontFiles = window.selectedFontFiles || [];
-
+
// Create references that can be reassigned
var fontCatalog = window.fontCatalog;
var fontTokens = window.fontTokens;
var fontOverrides = window.fontOverrides;
var selectedFontFiles = window.selectedFontFiles;
+ // Base URL for API calls (shared scope)
+ var baseUrl = window.location.origin;
+
function initializeFontsTab() {
// Allow re-initialization on each HTMX content swap
// The window._fontsScriptLoaded guard prevents function redeclaration
@@ -368,7 +370,6 @@
Font Preview
try {
// Use absolute URLs to ensure they work when loaded via HTMX
- const baseUrl = window.location.origin;
const [catalogRes, tokensRes, overridesRes] = await Promise.all([
fetch(`${baseUrl}/api/v3/fonts/catalog`),
fetch(`${baseUrl}/api/v3/fonts/tokens`),
@@ -488,21 +489,127 @@
Font Preview
return;
}
- const lines = Object.entries(fontCatalog).map(([name, fontInfo]) => {
+ // System fonts that cannot be deleted
+ const systemFonts = [
+ 'pressstart2p-regular', 'pressstart2p', 'press start 2p',
+ '4x6-font', '4x6', '5by7.regular', '5by7', '5x7',
+ '5x8', '6x9', '6x10', '6x12', '6x13',
+ '7x13', '7x14', '8x13', '9x15', '9x18', '10x20',
+ 'matrixchunky8', 'matrixlight6', 'tom-thumb'
+ ];
+
+ const fontEntries = Object.entries(fontCatalog).map(([name, fontInfo]) => {
const fontPath = typeof fontInfo === 'string' ? fontInfo : (fontInfo?.path || '');
- // Only prefix with "assets/fonts/" if path is a bare filename (no "/" and doesn't start with "assets/")
- // If path is absolute (starts with "/") or already has "assets/" prefix, use as-is
- const fullPath = (fontPath.startsWith('/') || fontPath.startsWith('assets/'))
- ? fontPath
- : `assets/fonts/${fontPath}`;
- return `${name}: ${fullPath}`;
+ const filename = typeof fontInfo === 'object' ? (fontInfo.filename || name) : name;
+ const displayName = typeof fontInfo === 'object' ? (fontInfo.display_name || name) : name;
+ const fontType = typeof fontInfo === 'object' ? (fontInfo.type || '').toUpperCase() : '';
+ const isSystem = systemFonts.some(sf => name.toLowerCase().includes(sf) || displayName.toLowerCase().includes(sf));
+ return { name, filename, displayName, fontType, fontPath, isSystem };
+ }).sort((a, b) => a.displayName.localeCompare(b.displayName));
+
+ // Build list using DOM APIs to prevent XSS
+ container.innerHTML = '';
+ fontEntries.forEach(font => {
+ const row = document.createElement('div');
+ row.className = 'flex items-center justify-between py-1 border-b border-gray-700 last:border-0';
+
+ const nameSpan = document.createElement('span');
+ nameSpan.className = 'truncate flex-1';
+ nameSpan.textContent = font.displayName;
+
+ if (font.fontType) {
+ const typeSpan = document.createElement('span');
+ typeSpan.className = 'text-gray-500 ml-1';
+ typeSpan.textContent = `(${font.fontType})`;
+ nameSpan.appendChild(typeSpan);
+ }
+
+ row.appendChild(nameSpan);
+
+ if (font.isSystem) {
+ const systemBadge = document.createElement('span');
+ systemBadge.className = 'text-gray-600 text-xs ml-2';
+ systemBadge.textContent = '[system]';
+ row.appendChild(systemBadge);
+ } else {
+ const deleteBtn = document.createElement('button');
+ deleteBtn.className = 'text-red-400 hover:text-red-300 text-xs ml-2';
+ deleteBtn.title = 'Delete font';
+ deleteBtn.textContent = '[delete]';
+ deleteBtn.dataset.fontName = font.name;
+ deleteBtn.addEventListener('click', function() {
+ deleteFont(this.dataset.fontName);
+ });
+ row.appendChild(deleteBtn);
+ }
+
+ container.appendChild(row);
});
- container.textContent = lines.join('\n');
+}
+
+async function deleteFont(fontFamily) {
+ if (!confirm(`Are you sure you want to delete the font "${fontFamily}"? This action cannot be undone.`)) {
+ return;
+ }
+
+ try {
+ const response = await fetch(`${baseUrl}/api/v3/fonts/${encodeURIComponent(fontFamily)}`, {
+ method: 'DELETE'
+ });
+
+ const data = await response.json();
+
+ if (data.status === 'success') {
+ showNotification(data.message || `Font "${fontFamily}" deleted successfully`, 'success');
+ // Refresh font data and UI
+ await loadFontData();
+ populateFontSelects();
+ // Clear font-selector widget cache if available
+ if (typeof window.clearFontSelectorCache === 'function') {
+ window.clearFontSelectorCache();
+ }
+ } else {
+ showNotification(data.message || `Failed to delete font "${fontFamily}"`, 'error');
+ }
+ } catch (error) {
+ console.error('Error deleting font:', error);
+ showNotification(`Error deleting font: ${error.message}`, 'error');
+ }
}
function populateFontSelects() {
- // This would populate the select options with actual font data
- // For now, using placeholder options
+ // Populate font family dropdowns from catalog
+ const overrideSelect = document.getElementById('override-family');
+ const previewSelect = document.getElementById('preview-family');
+
+ if (!overrideSelect || !previewSelect) return;
+
+ // Get font entries sorted by display name
+ const fontEntries = Object.entries(fontCatalog).map(([key, info]) => {
+ const filename = typeof info === 'object' ? (info.filename || key) : key;
+ const displayName = typeof info === 'object' ? (info.display_name || key) : key;
+ const fontType = typeof info === 'object' ? (info.type || 'unknown').toUpperCase() : '';
+ return { key, filename, displayName, fontType };
+ }).sort((a, b) => a.displayName.localeCompare(b.displayName));
+
+ // Build options HTML
+ const optionsHtml = fontEntries.map(font => {
+ const typeLabel = font.fontType ? ` (${font.fontType})` : '';
+ return ``;
+ }).join('');
+
+ // Populate override select (keep the default option)
+ overrideSelect.innerHTML = '' + optionsHtml;
+
+ // Populate preview select
+ previewSelect.innerHTML = optionsHtml;
+
+ // Select first font in preview if available
+ if (fontEntries.length > 0) {
+ previewSelect.value = fontEntries[0].filename;
+ }
+
+ console.log(`Populated font selects with ${fontEntries.length} fonts`);
}
async function addFontOverride() {
@@ -639,30 +746,59 @@