Compare commits

..

No commits in common. "86abfadb527aa6ba306218d321f9febd2faea0c4" and "12a37cc734851772d547fd6dd71fe1b507aa6cb9" have entirely different histories.

7 changed files with 34 additions and 54 deletions

View File

@ -82,9 +82,9 @@ class ConfigForm {
); );
if (task && window.librePortalSPA && typeof window.librePortalSPA.navigate === 'function') { if (task && window.librePortalSPA && typeof window.librePortalSPA.navigate === 'function') {
setTimeout(() => window.librePortalSPA.navigate(window.taskPath('all', task.id)), 400); setTimeout(() => window.librePortalSPA.navigate(`/tasks/all?task=${task.id}`), 400);
} else if (task && window.navigateToRoute) { } else if (task && window.navigateToRoute) {
setTimeout(() => window.navigateToRoute(window.taskPath('all', task.id)), 400); setTimeout(() => window.navigateToRoute(`tasks/all?task=${task.id}`), 400);
} }
} catch (error) { } catch (error) {
console.error('ConfigForm: Error saving configuration:', error); console.error('ConfigForm: Error saving configuration:', error);

View File

@ -213,7 +213,7 @@ Object.assign(TasksManager.prototype, {
<!-- Task metadata --> <!-- Task metadata -->
<div class="task-meta"> <div class="task-meta">
<div class="meta-item"> <div class="meta-item">
<strong>Task ID:</strong> <a href="${window.taskPath ? window.taskPath('all', task.id) : '/tasks/all'}" class="task-id-link" data-task-id="${task.id}">${task.id}</a> <strong>Task ID:</strong> <a href="/tasks/all?task=${task.id}" class="task-id-link" data-task-id="${task.id}">${task.id}</a>
</div> </div>
<div class="meta-item"> <div class="meta-item">
<strong>Type:</strong> ${task.type || 'unknown'} <strong>Type:</strong> ${task.type || 'unknown'}

View File

@ -142,13 +142,18 @@ class TasksManager {
const isMainTasksPage = currentUrl.pathname === '/tasks' || currentUrl.pathname.startsWith('/tasks/') || currentUrl.pathname === '/tasks.html'; const isMainTasksPage = currentUrl.pathname === '/tasks' || currentUrl.pathname.startsWith('/tasks/') || currentUrl.pathname === '/tasks.html';
if (isMainTasksPage) { if (isMainTasksPage) {
// Category + single-task deep link from the path (/tasks/<category>/<id>), // Category from the path (/tasks/<category>), else legacy ?=<category>.
// with legacy ?=<category> and ?task=<id> queries still honoured. const seg = currentUrl.pathname.replace(/^\/tasks\/?/, '').split('/')[0];
const parts = (typeof window.taskPartsFromPath === 'function') this.currentCategory = seg || searchParams.get('') || 'all';
? window.taskPartsFromPath(currentUrl.pathname, currentUrl.search)
: { category: '', taskId: searchParams.get('task') || '' }; // Only check for specific task parameter if we're not coming from an app page
this.currentCategory = parts.category || searchParams.get('') || 'all'; const taskParam = searchParams.get('task');
this.highlightedTaskId = parts.taskId || null; if (taskParam) {
this.highlightedTaskId = taskParam;
} else {
// Clear any existing highlighted task when on main tasks page without task param
this.highlightedTaskId = null;
}
} else { } else {
// Not on main tasks page, get default filter from localStorage // Not on main tasks page, get default filter from localStorage
this.currentCategory = localStorage.getItem('tasksDefaultFilter') || 'all'; this.currentCategory = localStorage.getItem('tasksDefaultFilter') || 'all';
@ -158,11 +163,11 @@ class TasksManager {
} }
updateURL(category, taskId = null) { updateURL(category, taskId = null) {
// Update URL without page reload and without hash. The task deep link is a // Update URL without page reload and without hash
// path segment (/tasks/<category>/<id>) — see window.taskPath. let newURL = `/tasks/${category || 'all'}`;
const newURL = (typeof window.taskPath === 'function') if (taskId) {
? window.taskPath(category, taskId) newURL += `?task=${taskId}`;
: `/tasks/${category || 'all'}${taskId ? '/' + String(taskId).replace(/^task_/, '') : ''}`; }
// Prevent the SPA from interfering // Prevent the SPA from interfering
if (window.librePortalSPA) { if (window.librePortalSPA) {
@ -179,7 +184,7 @@ class TasksManager {
//// // console.log('🔧 Initializing TasksManager...'); //// // console.log('🔧 Initializing TasksManager...');
// Re-read the URL on every (re)mount. The SPA reuses this singleton, so a // Re-read the URL on every (re)mount. The SPA reuses this singleton, so a
// navigation to /tasks/<cat>/<id> must refresh the category + deep-link // navigation to /tasks/<cat>?task=X must refresh the category + deep-link
// state — a constructor-only read goes stale after the first visit. // state — a constructor-only read goes stale after the first visit.
this.initializeFromURL(); this.initializeFromURL();
@ -410,7 +415,7 @@ class TasksManager {
const onAppPage = window.location.pathname.startsWith('/app') && !window.location.pathname.startsWith('/apps'); const onAppPage = window.location.pathname.startsWith('/app') && !window.location.pathname.startsWith('/apps');
const url = (onAppPage && appName) const url = (onAppPage && appName)
? window.appPath(appName, 'tasks', null, taskId) ? window.appPath(appName, 'tasks', null, taskId)
: window.taskPath('all', taskId); : `/tasks/all?task=${taskId}`;
// Per-action emoji (install ✅, backup 💾, restore 📦, …) in the // Per-action emoji (install ✅, backup 💾, restore 📦, …) in the
// notification's leftmost icon slot, mirroring task-list rows. // notification's leftmost icon slot, mirroring task-list rows.
const customIcon = typeIcon ? `<span style="font-size:18px;line-height:1;">${typeIcon}</span>` : null; const customIcon = typeIcon ? `<span style="font-size:18px;line-height:1;">${typeIcon}</span>` : null;

View File

@ -657,27 +657,6 @@ window.appPartsFromPath = function (pathname) {
return { app, tab, sub }; return { app, tab, sub };
}; };
// Global tasks page path helpers — bring the single-task deep link in line with
// /app/<name> and /admin/<…>: it's a path segment (/tasks/<category>/<id>), not
// a ?task= query. Task ids are guaranteed `task_<digits>_<base36>`, so the
// `task_` prefix is dropped in the URL for brevity and restored on read. A
// legacy ?task=<id> query is still parsed so older links and bookmarks resolve.
window.taskPath = function (category, taskId) {
let p = '/tasks/' + encodeURIComponent(category || 'all');
if (taskId) p += '/' + encodeURIComponent(String(taskId).replace(/^task_/, ''));
return p;
};
window.taskPartsFromPath = function (pathname, search) {
const segs = String(pathname || '').replace(/^\/tasks\/?/, '').split('/').filter(Boolean);
const category = segs[0] || '';
let taskId = segs[1] ? decodeURIComponent(segs[1]) : '';
if (!taskId && search) {
try { taskId = new URLSearchParams(search).get('task') || ''; } catch (_) {}
}
if (taskId && !/^task_/.test(taskId)) taskId = 'task_' + taskId;
return { category, taskId };
};
// Global navigation function for click handlers // Global navigation function for click handlers
window.navigateToRoute = function(href) { window.navigateToRoute = function(href) {
if (window.spaClean) { if (window.spaClean) {

View File

@ -114,7 +114,7 @@ class NotificationSystem {
// Action button (context-aware) // Action button (context-aware)
if (appName && appUrl) { if (appName && appUrl) {
let buttonText = 'Manage'; let buttonText = 'Manage';
if (appUrl.includes('task=') || /\/tasks\/[^/]+\/[^/?]+/.test(appUrl)) { if (appUrl.includes('task=')) {
buttonText = 'View Task'; buttonText = 'View Task';
} else if (type === 'success' && message.includes('install')) { } else if (type === 'success' && message.includes('install')) {
buttonText = 'Configure'; buttonText = 'Configure';
@ -399,17 +399,14 @@ window.handleNotificationNavigation = (url) => {
console.warn('⚠️ appTabbedManager not available'); console.warn('⚠️ appTabbedManager not available');
} }
} else if (currentPath.includes('/tasks')) { } else if (currentPath.includes('/tasks')) {
// We're on the tasks page, navigate to the specified task. The target may // We're on the tasks page, navigate to the specified task
// carry the id as a /tasks/<cat>/<id> path segment or a legacy ?task=. if (taskId) {
const tasksTaskId = taskId console.log('🔗 On tasks page, opening task:', taskId);
|| (window.taskPartsFromPath ? window.taskPartsFromPath(urlObj.pathname, urlObj.search).taskId : ''); window.history.pushState({}, '', `/tasks/all?task=${taskId}`);
if (tasksTaskId) {
console.log('🔗 On tasks page, opening task:', tasksTaskId);
window.history.pushState({}, '', window.taskPath ? window.taskPath('all', tasksTaskId) : url);
setTimeout(() => { setTimeout(() => {
if (typeof window.toggleTaskDetails === 'function') { if (typeof window.toggleTaskDetails === 'function') {
console.log('🔗 Opening task details for:', tasksTaskId); console.log('🔗 Opening task details for:', taskId);
window.toggleTaskDetails(tasksTaskId); window.toggleTaskDetails(taskId);
} }
}, 300); }, 300);
return true; return true;

View File

@ -784,11 +784,10 @@ class SetupWizard {
this.container.classList.add('setup-launched'); this.container.classList.add('setup-launched');
setTimeout(() => { setTimeout(() => {
// Path-based route (the app uses /… URLs); the specific task is a path // Path-based route (the app uses /… URLs); the specific task is still
// segment (/tasks/all/<id>) and ?from=setup flags the install handoff so // selected via ?task=. Navigate via the SPA helper, with an absolute-path
// the page follows the running queue. Navigate via the SPA helper, with // full-load fallback.
// an absolute-path full-load fallback. const target = `/tasks/all?task=${encodeURIComponent(firstTaskId)}&from=setup`;
const target = `${window.taskPath('all', firstTaskId)}?from=setup`;
if (typeof window.navigateToRoute === 'function' && window.spaClean) { if (typeof window.navigateToRoute === 'function' && window.spaClean) {
window.navigateToRoute(target); window.navigateToRoute(target);
this.hide(); this.hide();

View File

@ -406,7 +406,7 @@ async configUpdate(changes) {
if (currentUrl.includes('/app/') && appName) { if (currentUrl.includes('/app/') && appName) {
taskUrl = window.appPath(appName, 'tasks', null, task.id); taskUrl = window.appPath(appName, 'tasks', null, task.id);
} else { } else {
taskUrl = window.taskPath('all', task.id); taskUrl = `/tasks/all?task=${task.id}`;
} }
if (window.notificationSystem) { if (window.notificationSystem) {