@unbrained/pm-web 2026.9.18 → 2026.9.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +6 -4
- package/dist/app.d.ts +10 -0
- package/dist/app.js +19 -9
- package/dist/app.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/routes/admin.js +6 -6
- package/dist/routes/admin.js.map +1 -1
- package/dist/routes/auth.js +2 -2
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/extensions.js +24 -37
- package/dist/routes/extensions.js.map +1 -1
- package/dist/routes/github.js +169 -107
- package/dist/routes/github.js.map +1 -1
- package/dist/routes/groups.js +9 -14
- package/dist/routes/groups.js.map +1 -1
- package/dist/routes/pm.js +532 -1015
- package/dist/routes/pm.js.map +1 -1
- package/dist/routes/projects.js +10 -6
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/route-helpers.d.ts +74 -0
- package/dist/routes/route-helpers.js +85 -0
- package/dist/routes/route-helpers.js.map +1 -0
- package/dist/routes/sharing.js +30 -18
- package/dist/routes/sharing.js.map +1 -1
- package/dist/server.js +1 -20
- package/dist/server.js.map +1 -1
- package/dist/services/mutation-event-watcher.js +3 -28
- package/dist/services/mutation-event-watcher.js.map +1 -1
- package/dist/services/pm-runner.js +3 -1
- package/dist/services/pm-runner.js.map +1 -1
- package/dist/services/project-watcher.js +14 -38
- package/dist/services/project-watcher.js.map +1 -1
- package/dist/services/watcher-utils.d.ts +42 -0
- package/dist/services/watcher-utils.js +60 -0
- package/dist/services/watcher-utils.js.map +1 -0
- package/manifest.json +2 -2
- package/package.json +17 -6
- package/public/src/app.js +22 -52
- package/public/src/app.js.map +1 -1
- package/public/src/app.ts +18 -75
- package/public/src/browser-window.js +29 -0
- package/public/src/browser-window.js.map +1 -0
- package/public/src/browser-window.ts +56 -0
- package/public/src/components/modals.js +1 -1
- package/public/src/components/modals.js.map +1 -1
- package/public/src/components/modals.ts +1 -1
- package/public/src/i18n.js +14 -6
- package/public/src/i18n.js.map +1 -1
- package/public/src/i18n.ts +16 -6
- package/public/src/sw.ts +1 -0
- package/public/src/utils.js +58 -0
- package/public/src/utils.js.map +1 -1
- package/public/src/utils.ts +53 -0
- package/public/src/views/admin.js +2 -5
- package/public/src/views/admin.js.map +1 -1
- package/public/src/views/admin.ts +2 -2
- package/public/src/views/auth.js +5 -1
- package/public/src/views/auth.js.map +1 -1
- package/public/src/views/auth.ts +6 -1
- package/public/src/views/calendar.js +1 -2
- package/public/src/views/calendar.js.map +1 -1
- package/public/src/views/calendar.ts +1 -2
- package/public/src/views/config.js +14 -21
- package/public/src/views/config.js.map +1 -1
- package/public/src/views/config.ts +14 -19
- package/public/src/views/context.js +11 -12
- package/public/src/views/context.js.map +1 -1
- package/public/src/views/context.ts +13 -12
- package/public/src/views/create.js +3 -11
- package/public/src/views/create.js.map +1 -1
- package/public/src/views/create.ts +3 -3
- package/public/src/views/export.js +2 -3
- package/public/src/views/export.js.map +1 -1
- package/public/src/views/export.ts +2 -3
- package/public/src/views/github.js +33 -47
- package/public/src/views/github.js.map +1 -1
- package/public/src/views/github.ts +35 -28
- package/public/src/views/graph-canvas.js +92 -99
- package/public/src/views/graph-canvas.js.map +1 -1
- package/public/src/views/graph-canvas.ts +94 -93
- package/public/src/views/graph.js +88 -73
- package/public/src/views/graph.js.map +1 -1
- package/public/src/views/graph.ts +97 -63
- package/public/src/views/groups.js +2 -5
- package/public/src/views/groups.js.map +1 -1
- package/public/src/views/groups.ts +2 -2
- package/public/src/views/guide.js +11 -10
- package/public/src/views/guide.js.map +1 -1
- package/public/src/views/guide.ts +12 -10
- package/public/src/views/items.js +100 -130
- package/public/src/views/items.js.map +1 -1
- package/public/src/views/items.ts +99 -117
- package/public/src/views/normalize.js +2 -9
- package/public/src/views/normalize.js.map +1 -1
- package/public/src/views/normalize.ts +2 -9
- package/public/src/views/packages.js +7 -4
- package/public/src/views/packages.js.map +1 -1
- package/public/src/views/packages.ts +8 -4
- package/public/src/views/plan-execution.js.map +1 -1
- package/public/src/views/plan-execution.ts +5 -15
- package/public/src/views/plan.js +2 -1
- package/public/src/views/plan.js.map +1 -1
- package/public/src/views/plan.ts +6 -17
- package/public/src/views/projects.js +5 -6
- package/public/src/views/projects.js.map +1 -1
- package/public/src/views/projects.ts +5 -6
- package/public/src/views/search.js +5 -1
- package/public/src/views/search.js.map +1 -1
- package/public/src/views/search.ts +7 -1
- package/public/src/views/settings.js +30 -55
- package/public/src/views/settings.js.map +1 -1
- package/public/src/views/settings.ts +23 -17
- package/public/src/views/shared.js +2 -5
- package/public/src/views/shared.js.map +1 -1
- package/public/src/views/shared.ts +2 -5
- package/public/src/views/sharing.js +2 -5
- package/public/src/views/sharing.js.map +1 -1
- package/public/src/views/sharing.ts +2 -2
- package/public/src/views/templates.js +9 -16
- package/public/src/views/templates.js.map +1 -1
- package/public/src/views/templates.ts +9 -14
- package/public/src/views/validate.js +2 -9
- package/public/src/views/validate.js.map +1 -1
- package/public/src/views/validate.ts +2 -9
- package/public/sw.js +1 -0
package/public/src/app.ts
CHANGED
|
@@ -4,16 +4,16 @@
|
|
|
4
4
|
import { state } from './state.js';
|
|
5
5
|
import { api, csrfTokenFromCookie } from './api.js';
|
|
6
6
|
import type { AuthMeResponse, SearchResponse } from './api-types.js';
|
|
7
|
-
import { showView, setOnViewChange } from './views/router.js';
|
|
7
|
+
import { showView, setOnViewChange, getViewForPath } from './views/router.js';
|
|
8
8
|
import { loadProjects, onProjectSelect, loadItemsBadge, renderProjectsView, selectProject, deleteProject, buildCreateProjectModal, submitCreateProject, submitCreateProject2 } from './views/projects.js';
|
|
9
|
-
import
|
|
9
|
+
import * as itemsView from './views/items.js';
|
|
10
10
|
import { submitCreateItem, submitCreateItemAndOpen } from './views/create.js';
|
|
11
11
|
import { renderActivityView } from './views/activity.js';
|
|
12
12
|
import { renderSearchView, setSearchMode, reindexProject, debouncedSearch, doSearch } from './views/search.js';
|
|
13
13
|
import { renderStatsView } from './views/stats.js';
|
|
14
14
|
import { renderCalendarView, calNav, showDayItems } from './views/calendar.js';
|
|
15
15
|
import { renderContextView } from './views/context.js';
|
|
16
|
-
import { renderGraphView } from './views/graph.js';
|
|
16
|
+
import { renderGraphView, refreshGraphData as refreshGraphViewData } from './views/graph.js';
|
|
17
17
|
|
|
18
18
|
/** Switch to the graph view, wait for it to mount, render it, then focus the
|
|
19
19
|
* node with the given id by invoking the graph view's select-node hook. */
|
|
@@ -23,7 +23,7 @@ async function openGraphAt(nodeId: string): Promise<void> {
|
|
|
23
23
|
setTimeout(async () => {
|
|
24
24
|
await renderGraphView();
|
|
25
25
|
// Select the node via the graph canvas
|
|
26
|
-
|
|
26
|
+
browserWindow().__graphSelectNode?.(nodeId);
|
|
27
27
|
}, 50);
|
|
28
28
|
}
|
|
29
29
|
import { renderSharingView, openShareModal, submitShare, removeShare } from './views/sharing.js';
|
|
@@ -47,7 +47,8 @@ import { initI18n, setLocale as i18nSetLocale, applyTranslations } from './i18n.
|
|
|
47
47
|
import { initPlanView, openPlanDetail, openCreatePlanModal, submitCreatePlan, openAddStepModal, submitAddStep, planCompleteStep, planBlockStepPrompt, submitBlockStep, planRemoveStep, planApprove, planMaterializePrompt, submitMaterializePlan, copyPlanAgentBrief, copyPlanNextStepPrompt, planEditPrompt, submitEditPlan, planDeletePrompt } from './views/plan.js';
|
|
48
48
|
import { showModal, hideModal, createModal, closeAllModals } from './components/modals.js';
|
|
49
49
|
import { toast } from './components/toast.js';
|
|
50
|
-
import { escHtml } from './utils.js';
|
|
50
|
+
import { escHtml, typeIcon, priorityDot, statusBadge, updateHeaderUser } from './utils.js';
|
|
51
|
+
import { browserWindow, isBeforeInstallPromptEvent, type BeforeInstallPromptEvent } from './browser-window.js';
|
|
51
52
|
import { initTheme, cycleTheme } from './theme.js';
|
|
52
53
|
|
|
53
54
|
// Apply the persisted theme immediately on script load (before boot) so the UI
|
|
@@ -195,7 +196,6 @@ async function doGlobalSearch(): Promise<void> {
|
|
|
195
196
|
try {
|
|
196
197
|
const data = await api<SearchResponse>('POST',`/projects/${state.currentProject.id}/pm/search`,{query});
|
|
197
198
|
const results = data.results || data.items || [];
|
|
198
|
-
const { escHtml, typeIcon, priorityDot, statusBadge } = await import('./utils.js');
|
|
199
199
|
if (resultsEl) resultsEl.innerHTML = results.length === 0
|
|
200
200
|
? `<div class="empty-state" style="padding:24px"><div class="empty-state-text">No results for "${escHtml(query)}"</div></div>`
|
|
201
201
|
: `<div class="item-list">${results.map((item) =>`
|
|
@@ -213,10 +213,6 @@ async function doGlobalSearch(): Promise<void> {
|
|
|
213
213
|
// PWA install
|
|
214
214
|
// The DOM lib has no `BeforeInstallPromptEvent` type, so model the two members
|
|
215
215
|
// the install flow actually uses (`prompt()` and `userChoice`).
|
|
216
|
-
interface BeforeInstallPromptEvent extends Event {
|
|
217
|
-
prompt(): Promise<void>;
|
|
218
|
-
readonly userChoice: Promise<{ outcome: 'accepted' | 'dismissed'; platform: string }>;
|
|
219
|
-
}
|
|
220
216
|
|
|
221
217
|
let deferredPrompt: BeforeInstallPromptEvent | null = null;
|
|
222
218
|
|
|
@@ -226,7 +222,6 @@ const __app = {
|
|
|
226
222
|
// Views
|
|
227
223
|
showView,
|
|
228
224
|
renderProjectsView,
|
|
229
|
-
renderItemsView,
|
|
230
225
|
renderActivityView,
|
|
231
226
|
renderSearchView,
|
|
232
227
|
renderStatsView,
|
|
@@ -274,34 +269,7 @@ const __app = {
|
|
|
274
269
|
submitCreateProject2,
|
|
275
270
|
|
|
276
271
|
// Items
|
|
277
|
-
|
|
278
|
-
switchDetailTab,
|
|
279
|
-
addComment,
|
|
280
|
-
addNote,
|
|
281
|
-
appendItem,
|
|
282
|
-
updateItem,
|
|
283
|
-
closeItem,
|
|
284
|
-
confirmDeleteItem,
|
|
285
|
-
claimItem,
|
|
286
|
-
releaseItem,
|
|
287
|
-
startItem,
|
|
288
|
-
pauseItem,
|
|
289
|
-
addDep,
|
|
290
|
-
removeDep,
|
|
291
|
-
addLearning,
|
|
292
|
-
addTest,
|
|
293
|
-
addFileLink,
|
|
294
|
-
setStatusFilter,
|
|
295
|
-
applyItemFilters,
|
|
296
|
-
clearFilters,
|
|
297
|
-
copyFilterLink,
|
|
298
|
-
showBulkUpdateModal,
|
|
299
|
-
previewBulkUpdate,
|
|
300
|
-
applyBulkUpdate,
|
|
301
|
-
showBulkCloseModal,
|
|
302
|
-
previewBulkClose,
|
|
303
|
-
applyBulkClose,
|
|
304
|
-
useItemAsTemplate,
|
|
272
|
+
...itemsView,
|
|
305
273
|
|
|
306
274
|
// Create
|
|
307
275
|
submitCreateItem,
|
|
@@ -421,28 +389,7 @@ const __app = {
|
|
|
421
389
|
* handlers reach through `window.__app` is enumerated here via `typeof __app`. */
|
|
422
390
|
export type AppBridge = typeof __app;
|
|
423
391
|
|
|
424
|
-
|
|
425
|
-
// helpers assigned below. `beforeinstallprompt` is augmented on WindowEventMap
|
|
426
|
-
// so the listener receives our `BeforeInstallPromptEvent`.
|
|
427
|
-
declare global {
|
|
428
|
-
interface Window {
|
|
429
|
-
__app?: AppBridge;
|
|
430
|
-
installPwa?: () => void;
|
|
431
|
-
dismissInstallBanner?: () => void;
|
|
432
|
-
/**
|
|
433
|
-
* Graph view's keydown handler, parked on `window` so the view can detach
|
|
434
|
-
* the exact same function reference when it tears down.
|
|
435
|
-
*/
|
|
436
|
-
__graphKeyHandler?: (e: KeyboardEvent) => void;
|
|
437
|
-
/** Set by the graph view so other views can focus a node by item id. */
|
|
438
|
-
__graphSelectNode?: (id: string) => void;
|
|
439
|
-
}
|
|
440
|
-
interface WindowEventMap {
|
|
441
|
-
beforeinstallprompt: BeforeInstallPromptEvent;
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
window.__app = __app;
|
|
392
|
+
browserWindow().__app = __app;
|
|
446
393
|
|
|
447
394
|
// ═══════════════════════════════════════════════════════════════
|
|
448
395
|
// SSE REAL-TIME SYNC
|
|
@@ -594,7 +541,7 @@ function connectSSE(projectId: string, attempt = 0): void {
|
|
|
594
541
|
const doRefreshView = () => {
|
|
595
542
|
const view = state.currentView;
|
|
596
543
|
if (view === 'items') {
|
|
597
|
-
fetchAndRenderItems();
|
|
544
|
+
itemsView.fetchAndRenderItems();
|
|
598
545
|
} else if (view === 'activity') {
|
|
599
546
|
renderActivityView();
|
|
600
547
|
} else if (view === 'stats') {
|
|
@@ -613,7 +560,7 @@ function connectSSE(projectId: string, attempt = 0): void {
|
|
|
613
560
|
// Graph-synced events (Neo4j sync complete) do a full graph reload
|
|
614
561
|
const refreshGraph = () => {
|
|
615
562
|
if (state.currentView === 'graph') {
|
|
616
|
-
|
|
563
|
+
void renderGraphView();
|
|
617
564
|
}
|
|
618
565
|
};
|
|
619
566
|
|
|
@@ -639,7 +586,7 @@ function connectSSE(projectId: string, attempt = 0): void {
|
|
|
639
586
|
} catch { /* ignore parse errors */ }
|
|
640
587
|
}
|
|
641
588
|
if (state.currentView === 'graph') {
|
|
642
|
-
|
|
589
|
+
void refreshGraphViewData();
|
|
643
590
|
} else {
|
|
644
591
|
refreshView();
|
|
645
592
|
}
|
|
@@ -716,11 +663,7 @@ export async function bootApp(): Promise<void> {
|
|
|
716
663
|
if (mainApp) mainApp.style.display = 'flex';
|
|
717
664
|
|
|
718
665
|
const u = state.user!;
|
|
719
|
-
|
|
720
|
-
const avatarEl = document.getElementById('user-avatar');
|
|
721
|
-
if (avatarEl) avatarEl.textContent = initials;
|
|
722
|
-
const nameEl = document.getElementById('user-name-display');
|
|
723
|
-
if (nameEl) nameEl.textContent = u.display_name||u.email;
|
|
666
|
+
updateHeaderUser(u);
|
|
724
667
|
document.querySelectorAll<HTMLElement>('.admin-only').forEach((el) => {
|
|
725
668
|
el.style.display = u.is_admin ? '' : 'none';
|
|
726
669
|
});
|
|
@@ -766,7 +709,6 @@ async function handleLaunchAction(): Promise<void> {
|
|
|
766
709
|
}
|
|
767
710
|
|
|
768
711
|
// Restore view from URL path (supports refresh/bookmark)
|
|
769
|
-
const { getViewForPath } = await import('./views/router.js');
|
|
770
712
|
const view = getViewForPath(window.location.pathname);
|
|
771
713
|
|
|
772
714
|
// If view requires a project and none is selected, try to select first one
|
|
@@ -945,9 +887,10 @@ document.addEventListener('keydown', e => {
|
|
|
945
887
|
// ═══════════════════════════════════════════════════════════════
|
|
946
888
|
// PWA INSTALL PROMPT
|
|
947
889
|
// ═══════════════════════════════════════════════════════════════
|
|
948
|
-
window.addEventListener('beforeinstallprompt', (
|
|
949
|
-
|
|
950
|
-
|
|
890
|
+
window.addEventListener('beforeinstallprompt', (event) => {
|
|
891
|
+
if (!isBeforeInstallPromptEvent(event)) return;
|
|
892
|
+
event.preventDefault();
|
|
893
|
+
deferredPrompt = event;
|
|
951
894
|
const banner = document.getElementById('install-banner');
|
|
952
895
|
if (banner && !localStorage.getItem('pm-web-install-dismissed')) {
|
|
953
896
|
banner.classList.add('visible');
|
|
@@ -961,7 +904,7 @@ window.addEventListener('appinstalled', () => {
|
|
|
961
904
|
toast('pm-web installed!', 'success');
|
|
962
905
|
});
|
|
963
906
|
|
|
964
|
-
|
|
907
|
+
browserWindow().installPwa = function(): void {
|
|
965
908
|
if (!deferredPrompt) return;
|
|
966
909
|
deferredPrompt.prompt();
|
|
967
910
|
deferredPrompt.userChoice.then((result) => {
|
|
@@ -974,7 +917,7 @@ window.installPwa = function(): void {
|
|
|
974
917
|
});
|
|
975
918
|
};
|
|
976
919
|
|
|
977
|
-
|
|
920
|
+
browserWindow().dismissInstallBanner = function(): void {
|
|
978
921
|
localStorage.setItem('pm-web-install-dismissed', '1');
|
|
979
922
|
const banner = document.getElementById('install-banner');
|
|
980
923
|
if (banner) banner.classList.remove('visible');
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed view of the window properties this SPA attaches at runtime.
|
|
3
|
+
*
|
|
4
|
+
* Inline onclick handlers still reach `window.__app`. TypeScript call sites
|
|
5
|
+
* use this accessor so the attachment does not need a global namespace.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Return the current window as the augmented browser window.
|
|
9
|
+
*
|
|
10
|
+
* The extra properties are optional and written by this package; a missing
|
|
11
|
+
* window is a non-browser host and must fail loudly rather than pretend the
|
|
12
|
+
* bridge exists.
|
|
13
|
+
*/
|
|
14
|
+
export function browserWindow() {
|
|
15
|
+
if (typeof window === "undefined") {
|
|
16
|
+
throw new Error("browser window is unavailable");
|
|
17
|
+
}
|
|
18
|
+
return window;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Whether a DOM event is the install prompt this package listens for.
|
|
22
|
+
*
|
|
23
|
+
* @param event - The event delivered to a `beforeinstallprompt` listener.
|
|
24
|
+
* @returns True when the event exposes `prompt` and `userChoice`.
|
|
25
|
+
*/
|
|
26
|
+
export function isBeforeInstallPromptEvent(event) {
|
|
27
|
+
return "prompt" in event && "userChoice" in event;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=browser-window.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-window.js","sourceRoot":"","sources":["browser-window.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA4BH;;;;;;GAMG;AACH,MAAM,UAAU,aAAa;IAC3B,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IACnD,CAAC;IACD,OAAO,MAAuB,CAAC;AACjC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,0BAA0B,CAAC,KAAY;IACrD,OAAO,QAAQ,IAAI,KAAK,IAAI,YAAY,IAAI,KAAK,CAAC;AACpD,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed view of the window properties this SPA attaches at runtime.
|
|
3
|
+
*
|
|
4
|
+
* Inline onclick handlers still reach `window.__app`. TypeScript call sites
|
|
5
|
+
* use this accessor so the attachment does not need a global namespace.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Methods other modules call through the window bridge. */
|
|
9
|
+
export interface AppBridge {
|
|
10
|
+
openItemDetail(id: string): void;
|
|
11
|
+
showView(view: string, pushState?: boolean): void;
|
|
12
|
+
toast(message: string, type?: string): void;
|
|
13
|
+
disconnectSSE(): void;
|
|
14
|
+
connectSSE(projectId: string, attempt?: number): void;
|
|
15
|
+
loadItemsBadge(): void;
|
|
16
|
+
hideModal(id: string): void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Window plus the properties this package writes onto it. */
|
|
20
|
+
export interface BrowserWindow extends Window {
|
|
21
|
+
__app?: AppBridge;
|
|
22
|
+
installPwa?: () => void;
|
|
23
|
+
dismissInstallBanner?: () => void;
|
|
24
|
+
__graphKeyHandler?: (event: KeyboardEvent) => void;
|
|
25
|
+
__graphSelectNode?: (id: string) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The browser `beforeinstallprompt` event, which the DOM lib does not declare. */
|
|
29
|
+
export interface BeforeInstallPromptEvent extends Event {
|
|
30
|
+
prompt(): Promise<void>;
|
|
31
|
+
readonly userChoice: Promise<{ outcome: "accepted" | "dismissed"; platform: string }>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Return the current window as the augmented browser window.
|
|
36
|
+
*
|
|
37
|
+
* The extra properties are optional and written by this package; a missing
|
|
38
|
+
* window is a non-browser host and must fail loudly rather than pretend the
|
|
39
|
+
* bridge exists.
|
|
40
|
+
*/
|
|
41
|
+
export function browserWindow(): BrowserWindow {
|
|
42
|
+
if (typeof window === "undefined") {
|
|
43
|
+
throw new Error("browser window is unavailable");
|
|
44
|
+
}
|
|
45
|
+
return window as BrowserWindow;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Whether a DOM event is the install prompt this package listens for.
|
|
50
|
+
*
|
|
51
|
+
* @param event - The event delivered to a `beforeinstallprompt` listener.
|
|
52
|
+
* @returns True when the event exposes `prompt` and `userChoice`.
|
|
53
|
+
*/
|
|
54
|
+
export function isBeforeInstallPromptEvent(event: Event): event is BeforeInstallPromptEvent {
|
|
55
|
+
return "prompt" in event && "userChoice" in event;
|
|
56
|
+
}
|
|
@@ -25,7 +25,7 @@ export function closeAllModals() {
|
|
|
25
25
|
* optional footer markup, append it under `#modal-container`, and wire its
|
|
26
26
|
* backdrop-click-to-close behavior. Returns the created modal element. */
|
|
27
27
|
export function createModal(id, title, bodyHtml, footerHtml, wide = false) {
|
|
28
|
-
|
|
28
|
+
const existing = document.getElementById(id);
|
|
29
29
|
if (existing)
|
|
30
30
|
existing.remove();
|
|
31
31
|
const el = document.createElement('div');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"modals.js","sourceRoot":"","sources":["modals.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,SAAS;AACT,kEAAkE;AAClE,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtC,gFAAgF;AAChF,MAAM,UAAU,SAAS,CAAC,EAAU;IAClC,MAAM,KAAK,GAAG,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;IAC1C,IAAI,KAAK;QAAE,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC;AAC1C,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,SAAS,CAAC,EAAU;IAClC,MAAM,KAAK,GAAG,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;IAC1C,IAAI,KAAK;QAAE,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC;AAC1C,CAAC;AAED;mBACmB;AACnB,MAAM,UAAU,cAAc;IAC5B,QAAQ,CAAC,gBAAgB,CAAC,iBAAiB,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;QACtD,CAAiB,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC;IAC5C,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;0EAE0E;AAC1E,MAAM,UAAU,WAAW,CACzB,EAAU,EACV,KAAa,EACb,QAAgB,EAChB,UAAkB,EAClB,IAAI,GAAG,KAAK;IAEZ,
|
|
1
|
+
{"version":3,"file":"modals.js","sourceRoot":"","sources":["modals.ts"],"names":[],"mappings":"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"}
|
|
@@ -33,7 +33,7 @@ export function createModal(
|
|
|
33
33
|
footerHtml: string,
|
|
34
34
|
wide = false
|
|
35
35
|
): HTMLElement {
|
|
36
|
-
|
|
36
|
+
const existing = document.getElementById(id);
|
|
37
37
|
if (existing) existing.remove();
|
|
38
38
|
const el = document.createElement('div');
|
|
39
39
|
el.id = id;
|
package/public/src/i18n.js
CHANGED
|
@@ -41,7 +41,7 @@ export function resolveLocale(opts) {
|
|
|
41
41
|
// wants that value: `null` skips the step. A property that is absent falls
|
|
42
42
|
// back to the real browser global, so the no-argument browser path still
|
|
43
43
|
// works. This keeps the function deterministic and unit-testable in Node.
|
|
44
|
-
const hasStorage = opts
|
|
44
|
+
const hasStorage = opts !== undefined && opts !== null && Object.hasOwn(opts, 'storage');
|
|
45
45
|
const storage = hasStorage ? opts.storage : safeLocalStorage();
|
|
46
46
|
if (storage) {
|
|
47
47
|
let stored = null;
|
|
@@ -52,7 +52,7 @@ export function resolveLocale(opts) {
|
|
|
52
52
|
if (stored && isSupported(stored))
|
|
53
53
|
return stored;
|
|
54
54
|
}
|
|
55
|
-
const hasNav = opts
|
|
55
|
+
const hasNav = opts !== undefined && opts !== null && Object.hasOwn(opts, 'navLang');
|
|
56
56
|
const navLang = hasNav ? opts.navLang : safeNavLang();
|
|
57
57
|
if (navLang) {
|
|
58
58
|
const prefix = navLang.toLowerCase().split('-')[0];
|
|
@@ -112,6 +112,14 @@ let initialized = false;
|
|
|
112
112
|
* fetch can never overwrite a newer selection.
|
|
113
113
|
*/
|
|
114
114
|
let localeReqId = 0;
|
|
115
|
+
/** Commit the English fallback catalog. Isolated so an awaited fetch cannot race the write. */
|
|
116
|
+
function rememberEnglishCatalog(catalog) {
|
|
117
|
+
enCatalog = catalog;
|
|
118
|
+
}
|
|
119
|
+
/** Mark i18n ready. Isolated so the flag write is not paired with a stale read across await. */
|
|
120
|
+
function markI18nInitialized() {
|
|
121
|
+
initialized = true;
|
|
122
|
+
}
|
|
115
123
|
/** Fetch a locale catalog JSON. Best-effort: returns {} on any failure. */
|
|
116
124
|
async function fetchCatalog(locale) {
|
|
117
125
|
try {
|
|
@@ -141,7 +149,7 @@ export async function initI18n() {
|
|
|
141
149
|
currentLocale = resolveLocale();
|
|
142
150
|
}
|
|
143
151
|
if (!Object.keys(enCatalog).length) {
|
|
144
|
-
|
|
152
|
+
rememberEnglishCatalog(await fetchCatalog('en'));
|
|
145
153
|
}
|
|
146
154
|
if (currentLocale === 'en') {
|
|
147
155
|
activeCatalog = enCatalog;
|
|
@@ -149,7 +157,7 @@ export async function initI18n() {
|
|
|
149
157
|
else {
|
|
150
158
|
activeCatalog = await fetchCatalog(currentLocale);
|
|
151
159
|
}
|
|
152
|
-
|
|
160
|
+
markI18nInitialized();
|
|
153
161
|
syncHtmlLang(currentLocale);
|
|
154
162
|
applyTranslations();
|
|
155
163
|
}
|
|
@@ -187,7 +195,7 @@ export async function setLocale(locale) {
|
|
|
187
195
|
// A newer setLocale superseded this one: do not assign enCatalog.
|
|
188
196
|
if (myReqId !== localeReqId)
|
|
189
197
|
return;
|
|
190
|
-
|
|
198
|
+
rememberEnglishCatalog(en);
|
|
191
199
|
}
|
|
192
200
|
const fetched = next === 'en' ? enCatalog : await fetchCatalog(next);
|
|
193
201
|
// Stale request: discard the fetched catalog so it can never overwrite a
|
|
@@ -195,7 +203,7 @@ export async function setLocale(locale) {
|
|
|
195
203
|
if (myReqId !== localeReqId)
|
|
196
204
|
return;
|
|
197
205
|
activeCatalog = fetched;
|
|
198
|
-
|
|
206
|
+
markI18nInitialized();
|
|
199
207
|
syncHtmlLang(next);
|
|
200
208
|
applyTranslations();
|
|
201
209
|
}
|
package/public/src/i18n.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"i18n.js","sourceRoot":"","sources":["i18n.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,wDAAwD;AACxD,kEAAkE;AAClE,EAAE;AACF,yEAAyE;AACzE,6EAA6E;AAC7E,2EAA2E;AAC3E,yEAAyE;AACzE,2EAA2E;AAC3E,EAAE;AACF,8EAA8E;AAC9E,4EAA4E;AAC5E,uEAAuE;AACvE,gEAAgE;AAChE,EAAE;AACF,+DAA+D;AAC/D,6EAA6E;AAC7E,oEAAoE;AACpE,sEAAsE;AACtE,wCAAwC;AAExC,4DAA4D;AAC5D,MAAM,CAAC,MAAM,kBAAkB,GAAG,UAAU,CAAC;AAE7C,gFAAgF;AAChF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAU,CAAC;AAKnE,MAAM,cAAc,GAAoB,IAAI,CAAC;AAE7C;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,aAAa,CAAC,IAG7B;IACC,0EAA0E;IAC1E,2EAA2E;IAC3E,yEAAyE;IACzE,0EAA0E;IAC1E,MAAM,UAAU,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"i18n.js","sourceRoot":"","sources":["i18n.ts"],"names":[],"mappings":"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"}
|
package/public/src/i18n.ts
CHANGED
|
@@ -51,14 +51,14 @@ export function resolveLocale(opts?: {
|
|
|
51
51
|
// wants that value: `null` skips the step. A property that is absent falls
|
|
52
52
|
// back to the real browser global, so the no-argument browser path still
|
|
53
53
|
// works. This keeps the function deterministic and unit-testable in Node.
|
|
54
|
-
const hasStorage = opts
|
|
54
|
+
const hasStorage = opts !== undefined && opts !== null && Object.hasOwn(opts, 'storage');
|
|
55
55
|
const storage = hasStorage ? opts!.storage : safeLocalStorage();
|
|
56
56
|
if (storage) {
|
|
57
57
|
let stored: string | null = null;
|
|
58
58
|
try { stored = storage.getItem(LOCALE_STORAGE_KEY); } catch { /* privacy mode */ }
|
|
59
59
|
if (stored && isSupported(stored)) return stored;
|
|
60
60
|
}
|
|
61
|
-
const hasNav = opts
|
|
61
|
+
const hasNav = opts !== undefined && opts !== null && Object.hasOwn(opts, 'navLang');
|
|
62
62
|
const navLang = hasNav ? opts!.navLang : safeNavLang();
|
|
63
63
|
if (navLang) {
|
|
64
64
|
const prefix = navLang.toLowerCase().split('-')[0];
|
|
@@ -128,6 +128,16 @@ let initialized = false;
|
|
|
128
128
|
*/
|
|
129
129
|
let localeReqId = 0;
|
|
130
130
|
|
|
131
|
+
/** Commit the English fallback catalog. Isolated so an awaited fetch cannot race the write. */
|
|
132
|
+
function rememberEnglishCatalog(catalog: Record<string, string>): void {
|
|
133
|
+
enCatalog = catalog;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Mark i18n ready. Isolated so the flag write is not paired with a stale read across await. */
|
|
137
|
+
function markI18nInitialized(): void {
|
|
138
|
+
initialized = true;
|
|
139
|
+
}
|
|
140
|
+
|
|
131
141
|
/** Fetch a locale catalog JSON. Best-effort: returns {} on any failure. */
|
|
132
142
|
async function fetchCatalog(locale: string): Promise<Record<string, string>> {
|
|
133
143
|
try {
|
|
@@ -156,14 +166,14 @@ export async function initI18n(): Promise<void> {
|
|
|
156
166
|
currentLocale = resolveLocale();
|
|
157
167
|
}
|
|
158
168
|
if (!Object.keys(enCatalog).length) {
|
|
159
|
-
|
|
169
|
+
rememberEnglishCatalog(await fetchCatalog('en'));
|
|
160
170
|
}
|
|
161
171
|
if (currentLocale === 'en') {
|
|
162
172
|
activeCatalog = enCatalog;
|
|
163
173
|
} else {
|
|
164
174
|
activeCatalog = await fetchCatalog(currentLocale);
|
|
165
175
|
}
|
|
166
|
-
|
|
176
|
+
markI18nInitialized();
|
|
167
177
|
syncHtmlLang(currentLocale);
|
|
168
178
|
applyTranslations();
|
|
169
179
|
}
|
|
@@ -201,14 +211,14 @@ export async function setLocale(locale: string): Promise<void> {
|
|
|
201
211
|
const en = await fetchCatalog('en');
|
|
202
212
|
// A newer setLocale superseded this one: do not assign enCatalog.
|
|
203
213
|
if (myReqId !== localeReqId) return;
|
|
204
|
-
|
|
214
|
+
rememberEnglishCatalog(en);
|
|
205
215
|
}
|
|
206
216
|
const fetched = next === 'en' ? enCatalog : await fetchCatalog(next);
|
|
207
217
|
// Stale request: discard the fetched catalog so it can never overwrite a
|
|
208
218
|
// newer selection (e.g. a slow German fetch finishing after en was chosen).
|
|
209
219
|
if (myReqId !== localeReqId) return;
|
|
210
220
|
activeCatalog = fetched;
|
|
211
|
-
|
|
221
|
+
markI18nInitialized();
|
|
212
222
|
syncHtmlLang(next);
|
|
213
223
|
applyTranslations();
|
|
214
224
|
}
|
package/public/src/sw.ts
CHANGED
package/public/src/utils.js
CHANGED
|
@@ -73,6 +73,64 @@ export function setLoading(btn, yes, text) {
|
|
|
73
73
|
btn.textContent = text;
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
+
/** Restores a button to its enabled state with the given label, updating the
|
|
77
|
+
* first inner span when present. No-op when `btn` is null. */
|
|
78
|
+
export function resetButton(btn, label) {
|
|
79
|
+
if (btn) {
|
|
80
|
+
btn.disabled = false;
|
|
81
|
+
const sp = btn.querySelector('span');
|
|
82
|
+
if (sp)
|
|
83
|
+
sp.textContent = label;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** Sets a form element's value by id, skipping empty or undefined values.
|
|
87
|
+
* Looks up the element as input, select, or textarea. */
|
|
88
|
+
export function setFormValue(id, val) {
|
|
89
|
+
if (!val)
|
|
90
|
+
return;
|
|
91
|
+
const el = document.getElementById(id);
|
|
92
|
+
if (el)
|
|
93
|
+
el.value = val;
|
|
94
|
+
}
|
|
95
|
+
/** Renders a single issue or change row with an icon, message, and optional
|
|
96
|
+
* item link. The `contentStyle` parameter controls the inner div's style
|
|
97
|
+
* (pass an empty string for no style attribute). */
|
|
98
|
+
export function issueRow(icon, iconColor, message, itemId, contentStyle = 'flex:1') {
|
|
99
|
+
return `<div style="display:flex;gap:8px;align-items:flex-start;padding:8px 0;border-bottom:1px solid var(--border)">
|
|
100
|
+
<span style="color:${iconColor};flex-shrink:0">${icon}</span>
|
|
101
|
+
<div${contentStyle ? ` style="${contentStyle}"` : ''}>
|
|
102
|
+
<div style="font-size:13px">${escHtml(message)}</div>
|
|
103
|
+
${itemId ? `<div style="font-size:11px;color:var(--text-muted);margin-top:2px"><a href="#" onclick="window.__app.openItemDetail('${escHtml(itemId)}');return false" style="color:var(--accent)">${escHtml(itemId)}</a></div>` : ''}
|
|
104
|
+
</div>
|
|
105
|
+
</div>`;
|
|
106
|
+
}
|
|
107
|
+
/** Renders the common name, slug, and description portion of a project card,
|
|
108
|
+
* leaving the wrapper and meta row for the caller. */
|
|
109
|
+
export function projectCardInfo(p) {
|
|
110
|
+
return `<div class="project-card-name">${escHtml(p.name)}</div>
|
|
111
|
+
<div class="project-card-slug mono">${escHtml(p.slug)}</div>
|
|
112
|
+
<div class="project-card-desc">${escHtml(p.description || 'No description')}</div>`;
|
|
113
|
+
}
|
|
114
|
+
/** Updates the header avatar initials and display name from the given user's
|
|
115
|
+
* profile data. */
|
|
116
|
+
export function updateHeaderUser(u) {
|
|
117
|
+
const initials = (u.display_name || u.email || '?').split(' ').map(w => w[0]).join('').slice(0, 2).toUpperCase();
|
|
118
|
+
const avatarEl = document.getElementById('user-avatar');
|
|
119
|
+
if (avatarEl)
|
|
120
|
+
avatarEl.textContent = initials;
|
|
121
|
+
const nameEl = document.getElementById('user-name-display');
|
|
122
|
+
if (nameEl)
|
|
123
|
+
nameEl.textContent = u.display_name || u.email;
|
|
124
|
+
}
|
|
125
|
+
/** Shows an error message in the given inline error element and makes it
|
|
126
|
+
* visible, extracting the message from an unknown error value. No-op when
|
|
127
|
+
* `errEl` is null. */
|
|
128
|
+
export function showError(errEl, err) {
|
|
129
|
+
if (errEl) {
|
|
130
|
+
errEl.textContent = err instanceof Error ? err.message : String(err);
|
|
131
|
+
errEl.style.display = 'block';
|
|
132
|
+
}
|
|
133
|
+
}
|
|
76
134
|
/** Build an HTML string of `n` placeholder skeleton rows for loading states. */
|
|
77
135
|
export function skeletonRows(n = 5) {
|
|
78
136
|
return Array.from({ length: n }, () => '<div class="skeleton skeleton-row"></div>').join('');
|
package/public/src/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["utils.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,YAAY;AACZ,kEAAkE;AAClE,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["utils.ts"],"names":[],"mappings":"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"}
|