Merge claude/2

This commit is contained in:
librelad 2026-05-27 15:05:02 +01:00
commit 338cd801fd

View File

@ -78,6 +78,38 @@ class TasksManager {
return action.split(/[_-]/).map(w => w ? w.charAt(0).toUpperCase() + w.slice(1) : '').join(' ');
}
// Shared "task → notification payload" resolver. Every task surface
// (started/completed toast, delete modal, delete confirmation) builds
// the same identity (display name, app icon, friendly action title,
// emoji type-icon) — keep it in one place so they read consistently.
_taskNotificationDescriptor(task) {
const appName = (task && task.app) || null;
const action = (task && task.type) || '';
const isSystemTask = action.startsWith('setup-') || appName === 'system';
let actionTitle = this.formatActionTitle(action);
// Tool tasks: prefer the catalog-defined label.
const toolCmdMatch = ((task && task.command) || '').match(/libreportal app tool (\S+) (\S+)/);
if (toolCmdMatch) {
const toolId = toolCmdMatch[2];
let toolLabel = null;
const cat = window.toolsCatalog;
if (cat && cat.apps && cat.apps[toolCmdMatch[1]] && Array.isArray(cat.apps[toolCmdMatch[1]].tools)) {
const t = cat.apps[toolCmdMatch[1]].tools.find(x => x.id === toolId);
if (t && t.label) toolLabel = t.label;
}
if (!toolLabel) toolLabel = toolId.split(/[_-]/).map(w => w ? w.charAt(0).toUpperCase() + w.slice(1) : '').join(' ');
actionTitle = toolLabel;
}
const displayName = isSystemTask
? 'LibrePortal'
: ((appName && window.getAppDisplayName) ? window.getAppDisplayName(appName) : (appName || ''));
const icon = isSystemTask
? '/icons/libreportal.svg'
: (appName ? `/icons/apps/${encodeURIComponent(appName)}.svg` : null);
const typeIcon = (this.getTaskTypeIcon ? this.getTaskTypeIcon(task)?.icon : '') || '';
return { appName, isSystemTask, actionTitle, displayName, icon, typeIcon };
}
// Initialize task system after scripts are loaded
initializeTaskSystem() {
try {
@ -1402,49 +1434,13 @@ class TasksManager {
// Layout matches the "<App> task started!" format used by task-actions
// and backup-manager so started/completed look like a matched pair.
if (window.notificationSystem && task) {
const appName = task.app || null;
const action = task.type || 'task';
let actionTitle = this.formatActionTitle(action);
// Tool tasks: override the generic "Tool" label with the tool's
// friendly name (e.g. "Manage Shortcuts") so completion toasts
// match what the user clicked.
const toolCmdMatch = (task.command || '').match(/libreportal app tool (\S+) (\S+)/);
if (toolCmdMatch) {
const toolApp = toolCmdMatch[1];
const toolId = toolCmdMatch[2];
let toolLabel = null;
const cat = window.toolsCatalog;
if (cat && cat.apps && cat.apps[toolApp] && Array.isArray(cat.apps[toolApp].tools)) {
const t = cat.apps[toolApp].tools.find(x => x.id === toolId);
if (t && t.label) toolLabel = t.label;
}
if (!toolLabel) {
toolLabel = toolId.split('_').map(w => w ? w.charAt(0).toUpperCase() + w.slice(1) : '').join(' ');
}
actionTitle = toolLabel;
}
// System-level tasks resolve to LibrePortal as the subject + use the
// LibrePortal logo as the app-icon. Covers `app: 'system'` (config
// update, system update) and `setup-*` types (setup wizard phases).
const isSystemTask = action.startsWith('setup-') || appName === 'system';
const displayName = isSystemTask
? 'LibrePortal'
: ((appName && window.getAppDisplayName)
? window.getAppDisplayName(appName)
: (appName || (task.command || `Task ${taskId}`)));
const { appName, actionTitle, displayName, icon, typeIcon } = this._taskNotificationDescriptor(task);
const onAppPage = window.location.pathname.startsWith('/app') && !window.location.pathname.startsWith('/apps');
const url = (onAppPage && appName)
? window.appPath(appName, 'tasks', null, taskId)
: `/tasks/all?task=${taskId}`;
const icon = isSystemTask
? '/icons/libreportal.svg'
: (appName ? `/icons/apps/${appName}.svg` : null);
// Match the per-action emoji used in the task list rows (see
// `getTaskTypeIcon`). Passed as the 6th `customIcon` arg so the
// notification's leftmost icon slot shows the task *type* (install
// ✅, backup 💾, restore 📦, …) instead of the generic level tick.
const typeIcon = (this.getTaskTypeIcon ? this.getTaskTypeIcon(task) : null)?.icon || '';
// Per-action emoji (install ✅, backup 💾, restore 📦, …) in the
// notification's leftmost icon slot, mirroring task-list rows.
const customIcon = typeIcon ? `<span style="font-size:18px;line-height:1;">${typeIcon}</span>` : null;
let body;
@ -2196,11 +2192,16 @@ class TasksManager {
this.generateAppCategories();
if (window.notificationSystem) {
// Type icon from whatever the task was, with a trash fallback
// because deletion is conceptually a 🗑️ action.
const typeIcon = (task && this.getTaskTypeIcon ? this.getTaskTypeIcon(task)?.icon : '') || '🗑️';
const customIcon = `<span style="font-size:18px;line-height:1;">${typeIcon}</span>`;
window.notificationSystem.show('Task deleted successfully', 'info', null, null, null, customIcon);
// Match the "<App> task <verb>" two-line shape used by started/
// completed/failed/cancelled so deletion reads as part of the same
// family. Type emoji falls back to 🗑️ since deletion is a 🗑️ action.
const { appName, actionTitle, displayName, icon, typeIcon } = this._taskNotificationDescriptor(task);
const emoji = typeIcon || '🗑️';
const customIcon = `<span style="font-size:18px;line-height:1;">${emoji}</span>`;
const body = displayName
? `<strong>${displayName}</strong><br>${actionTitle} task deleted.`
: `${actionTitle || 'Task'} deleted.`;
window.notificationSystem.show(body, 'info', appName, null, icon, customIcon);
}
} catch (error) {
console.error('Error deleting task:', error);
@ -2219,36 +2220,15 @@ class TasksManager {
const escHtml = (s) => String(s == null ? '' : s)
.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
// Build a friendly title + app icon so the modal reads "Install Ipinfo"
// with the app's logo, not the raw "libreportal app install ipinfo"
// command. Mirrors the completion-notification flow above so the
// visual identity of a task is consistent across surfaces.
const appName = (task && task.app) || null;
const action = (task && task.type) || '';
const isSystemTask = action.startsWith('setup-') || appName === 'system';
let actionTitle = this.formatActionTitle(action);
// Tool tasks: prefer the tool's curated label.
const toolCmdMatch = (task && task.command || '').match(/libreportal app tool (\S+) (\S+)/);
if (toolCmdMatch) {
const toolId = toolCmdMatch[2];
let toolLabel = null;
const cat = window.toolsCatalog;
if (cat && cat.apps && cat.apps[toolCmdMatch[1]] && Array.isArray(cat.apps[toolCmdMatch[1]].tools)) {
const t = cat.apps[toolCmdMatch[1]].tools.find(x => x.id === toolId);
if (t && t.label) toolLabel = t.label;
}
if (!toolLabel) toolLabel = toolId.split(/[_-]/).map(w => w ? w.charAt(0).toUpperCase() + w.slice(1) : '').join(' ');
actionTitle = toolLabel;
}
const displayName = isSystemTask
? ''
: ((appName && window.getAppDisplayName) ? window.getAppDisplayName(appName) : (appName || ''));
const taskLabel = displayName
? `${actionTitle} ${displayName}`
// Friendly title + app icon — mirrors the completion-toast format so
// the modal's identity matches every other surface.
const { isSystemTask, actionTitle, displayName, icon: taskIcon } = this._taskNotificationDescriptor(task);
// For the modal we omit the "LibrePortal" subject (the eyebrow already
// says "Delete Task") and just lead with the action: "Update Config".
const modalSubject = isSystemTask ? '' : displayName;
const taskLabel = modalSubject
? `${actionTitle} ${modalSubject}`
: (actionTitle || (task && task.id) || 'Unknown task');
const taskIcon = isSystemTask
? '/icons/libreportal.svg'
: (appName ? `/icons/apps/${encodeURIComponent(appName)}.svg` : null);
const taskStatus = (task && task.status) || 'unknown';
const warningTitle = isActive ? 'Active task' : 'This cannot be undone';