@unbrained/pm-web 1.0.0

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.
Files changed (150) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +107 -0
  3. package/dist/auth.js +20 -0
  4. package/dist/auth.js.map +1 -0
  5. package/dist/crypto.js +42 -0
  6. package/dist/crypto.js.map +1 -0
  7. package/dist/db.js +111 -0
  8. package/dist/db.js.map +1 -0
  9. package/dist/index.js +88 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/middleware/auth.js +16 -0
  12. package/dist/middleware/auth.js.map +1 -0
  13. package/dist/routes/admin.js +207 -0
  14. package/dist/routes/admin.js.map +1 -0
  15. package/dist/routes/auth.js +163 -0
  16. package/dist/routes/auth.js.map +1 -0
  17. package/dist/routes/github.js +354 -0
  18. package/dist/routes/github.js.map +1 -0
  19. package/dist/routes/groups.js +180 -0
  20. package/dist/routes/groups.js.map +1 -0
  21. package/dist/routes/pm.js +2446 -0
  22. package/dist/routes/pm.js.map +1 -0
  23. package/dist/routes/projects.js +151 -0
  24. package/dist/routes/projects.js.map +1 -0
  25. package/dist/routes/sharing.js +155 -0
  26. package/dist/routes/sharing.js.map +1 -0
  27. package/dist/server.js +64 -0
  28. package/dist/server.js.map +1 -0
  29. package/dist/services/pm-runner.js +190 -0
  30. package/dist/services/pm-runner.js.map +1 -0
  31. package/dist/services/sse.js +111 -0
  32. package/dist/services/sse.js.map +1 -0
  33. package/manifest.json +15 -0
  34. package/package.json +111 -0
  35. package/public/icons/icon-192.png +0 -0
  36. package/public/icons/icon-512.png +0 -0
  37. package/public/index.html +265 -0
  38. package/public/manifest.json +66 -0
  39. package/public/src/api.js +28 -0
  40. package/public/src/api.js.map +1 -0
  41. package/public/src/api.ts +29 -0
  42. package/public/src/app.js +926 -0
  43. package/public/src/app.js.map +1 -0
  44. package/public/src/app.ts +929 -0
  45. package/public/src/components/modals.js +62 -0
  46. package/public/src/components/modals.js.map +1 -0
  47. package/public/src/components/modals.ts +73 -0
  48. package/public/src/components/toast.js +10 -0
  49. package/public/src/components/toast.js.map +1 -0
  50. package/public/src/components/toast.ts +13 -0
  51. package/public/src/constants.js +30 -0
  52. package/public/src/constants.js.map +1 -0
  53. package/public/src/constants.ts +41 -0
  54. package/public/src/state.js +15 -0
  55. package/public/src/state.js.map +1 -0
  56. package/public/src/state.ts +19 -0
  57. package/public/src/types.js +5 -0
  58. package/public/src/types.js.map +1 -0
  59. package/public/src/types.ts +253 -0
  60. package/public/src/utils.js +57 -0
  61. package/public/src/utils.js.map +1 -0
  62. package/public/src/utils.ts +56 -0
  63. package/public/src/views/activity.js +47 -0
  64. package/public/src/views/activity.js.map +1 -0
  65. package/public/src/views/activity.ts +41 -0
  66. package/public/src/views/admin.js +435 -0
  67. package/public/src/views/admin.js.map +1 -0
  68. package/public/src/views/admin.ts +504 -0
  69. package/public/src/views/auth.js +81 -0
  70. package/public/src/views/auth.js.map +1 -0
  71. package/public/src/views/auth.ts +74 -0
  72. package/public/src/views/calendar.js +133 -0
  73. package/public/src/views/calendar.js.map +1 -0
  74. package/public/src/views/calendar.ts +129 -0
  75. package/public/src/views/comments-audit.js +109 -0
  76. package/public/src/views/comments-audit.js.map +1 -0
  77. package/public/src/views/comments-audit.ts +108 -0
  78. package/public/src/views/config.js +322 -0
  79. package/public/src/views/config.js.map +1 -0
  80. package/public/src/views/config.ts +344 -0
  81. package/public/src/views/context.js +98 -0
  82. package/public/src/views/context.js.map +1 -0
  83. package/public/src/views/context.ts +100 -0
  84. package/public/src/views/create.js +293 -0
  85. package/public/src/views/create.js.map +1 -0
  86. package/public/src/views/create.ts +246 -0
  87. package/public/src/views/dedupe.js +51 -0
  88. package/public/src/views/dedupe.js.map +1 -0
  89. package/public/src/views/dedupe.ts +43 -0
  90. package/public/src/views/export.js +300 -0
  91. package/public/src/views/export.js.map +1 -0
  92. package/public/src/views/export.ts +274 -0
  93. package/public/src/views/github.js +360 -0
  94. package/public/src/views/github.js.map +1 -0
  95. package/public/src/views/github.ts +308 -0
  96. package/public/src/views/graph-canvas.js +1986 -0
  97. package/public/src/views/graph-canvas.js.map +1 -0
  98. package/public/src/views/graph-canvas.ts +2218 -0
  99. package/public/src/views/graph.js +1824 -0
  100. package/public/src/views/graph.js.map +1 -0
  101. package/public/src/views/graph.ts +1891 -0
  102. package/public/src/views/groups.js +186 -0
  103. package/public/src/views/groups.js.map +1 -0
  104. package/public/src/views/groups.ts +172 -0
  105. package/public/src/views/guide.js +151 -0
  106. package/public/src/views/guide.js.map +1 -0
  107. package/public/src/views/guide.ts +162 -0
  108. package/public/src/views/health.js +105 -0
  109. package/public/src/views/health.js.map +1 -0
  110. package/public/src/views/health.ts +102 -0
  111. package/public/src/views/items.js +1306 -0
  112. package/public/src/views/items.js.map +1 -0
  113. package/public/src/views/items.ts +1196 -0
  114. package/public/src/views/normalize.js +67 -0
  115. package/public/src/views/normalize.js.map +1 -0
  116. package/public/src/views/normalize.ts +58 -0
  117. package/public/src/views/plan.js +454 -0
  118. package/public/src/views/plan.js.map +1 -0
  119. package/public/src/views/plan.ts +496 -0
  120. package/public/src/views/projects.js +204 -0
  121. package/public/src/views/projects.js.map +1 -0
  122. package/public/src/views/projects.ts +196 -0
  123. package/public/src/views/router.js +227 -0
  124. package/public/src/views/router.js.map +1 -0
  125. package/public/src/views/router.ts +188 -0
  126. package/public/src/views/search.js +103 -0
  127. package/public/src/views/search.js.map +1 -0
  128. package/public/src/views/search.ts +94 -0
  129. package/public/src/views/settings.js +272 -0
  130. package/public/src/views/settings.js.map +1 -0
  131. package/public/src/views/settings.ts +190 -0
  132. package/public/src/views/shared.js +49 -0
  133. package/public/src/views/shared.js.map +1 -0
  134. package/public/src/views/shared.ts +49 -0
  135. package/public/src/views/sharing.js +152 -0
  136. package/public/src/views/sharing.js.map +1 -0
  137. package/public/src/views/sharing.ts +139 -0
  138. package/public/src/views/stats.js +92 -0
  139. package/public/src/views/stats.js.map +1 -0
  140. package/public/src/views/stats.ts +88 -0
  141. package/public/src/views/templates.js +117 -0
  142. package/public/src/views/templates.js.map +1 -0
  143. package/public/src/views/templates.ts +113 -0
  144. package/public/src/views/validate.js +54 -0
  145. package/public/src/views/validate.js.map +1 -0
  146. package/public/src/views/validate.ts +48 -0
  147. package/public/styles.css +2231 -0
  148. package/public/sw.js +318 -0
  149. package/public/tsconfig.json +20 -0
  150. package/sql/schema.sql +105 -0
@@ -0,0 +1,204 @@
1
+ // ═══════════════════════════════════════════════════════════════
2
+ // PROJECTS VIEW
3
+ // ═══════════════════════════════════════════════════════════════
4
+ import { state } from '../state.js';
5
+ import { api } from '../api.js';
6
+ import { escHtml, fmtDate } from '../utils.js';
7
+ import { hideModal, createModal, confirmDialog } from '../components/modals.js';
8
+ import { toast } from '../components/toast.js';
9
+ import { showView } from '../views/router.js';
10
+ export async function loadProjects() {
11
+ const data = await api('GET', '/projects');
12
+ state.projects = data.projects || [];
13
+ renderProjectSelector();
14
+ }
15
+ export function renderProjectSelector() {
16
+ const sel = document.getElementById('project-selector');
17
+ if (!sel)
18
+ return;
19
+ const cur = sel.value;
20
+ sel.innerHTML = '<option value="">— Select project —</option>' +
21
+ state.projects.map(p => `<option value="${p.id}"${p.id === cur ? ' selected' : ''}>${escHtml(p.name)}</option>`).join('');
22
+ }
23
+ async function fetchProjectSchema(projectId) {
24
+ try {
25
+ const schema = await api('GET', `/projects/${projectId}/pm/schema`);
26
+ if (schema && Array.isArray(schema.types) && schema.types.length) {
27
+ state.schema = schema;
28
+ }
29
+ }
30
+ catch (_) { /* fallback to constants */ }
31
+ }
32
+ export async function onProjectSelect(id) {
33
+ if (!id) {
34
+ state.currentProject = null;
35
+ state.schema = null;
36
+ window.__app?.disconnectSSE?.();
37
+ const pmSection = document.getElementById('sidebar-pm-section');
38
+ if (pmSection)
39
+ pmSection.style.display = 'none';
40
+ showView('projects');
41
+ return;
42
+ }
43
+ const proj = state.projects.find(p => p.id === id);
44
+ if (!proj)
45
+ return;
46
+ state.currentProject = proj;
47
+ const pmSection = document.getElementById('sidebar-pm-section');
48
+ if (pmSection)
49
+ pmSection.style.display = '';
50
+ const projName = document.getElementById('sidebar-project-name');
51
+ if (projName)
52
+ projName.textContent = proj.name;
53
+ window.__app?.connectSSE?.(proj.id);
54
+ // Fetch schema in background — views use fallback until it resolves
55
+ fetchProjectSchema(proj.id);
56
+ showView('items');
57
+ loadItemsBadge();
58
+ }
59
+ export async function loadItemsBadge() {
60
+ if (!state.currentProject)
61
+ return;
62
+ try {
63
+ const data = await api('GET', `/projects/${state.currentProject.id}/pm/list?status=open&limit=200`);
64
+ const count = (data.items || []).length;
65
+ const badge = document.getElementById('badge-items');
66
+ if (badge)
67
+ badge.textContent = count ? String(count) : '';
68
+ }
69
+ catch (_) { /* ignore */ }
70
+ }
71
+ export function renderProjectsView() {
72
+ const el = document.getElementById('content-projects');
73
+ if (!el)
74
+ return;
75
+ if (state.projects.length === 0) {
76
+ el.innerHTML = `
77
+ <div class="page-header">
78
+ <div><div class="page-title">Projects</div><div class="page-subtitle">Your project workspaces</div></div>
79
+ <div class="page-actions"><button class="btn btn-primary" onclick="window.__app.showModal('create-project-modal')">+ New Project</button></div>
80
+ </div>
81
+ <div class="welcome-state">
82
+ <div class="welcome-icon">⊞</div>
83
+ <div class="welcome-title">No projects yet</div>
84
+ <div class="welcome-text">Create your first project to start managing tasks, features, bugs, and more with git-native storage.</div>
85
+ <button class="btn btn-primary btn-lg" onclick="window.__app.showModal('create-project-modal')">+ Create Project</button>
86
+ </div>`;
87
+ return;
88
+ }
89
+ el.innerHTML = `
90
+ <div class="page-header">
91
+ <div><div class="page-title">Projects</div><div class="page-subtitle">${state.projects.length} project${state.projects.length !== 1 ? 's' : ''}</div></div>
92
+ <div class="page-actions"><button class="btn btn-primary" onclick="window.__app.showModal('create-project-modal')">+ New Project</button></div>
93
+ </div>
94
+ <div class="projects-grid">
95
+ ${state.projects.map(p => `
96
+ <div class="project-card" onclick="window.__app.selectProject('${p.id}')">
97
+ <button class="btn btn-ghost btn-sm project-card-del" onclick="event.stopPropagation();window.__app.deleteProject('${p.id}','${escHtml(p.name)}')" title="Delete project">✕</button>
98
+ <div class="project-card-name">${escHtml(p.name)}</div>
99
+ <div class="project-card-slug mono">${escHtml(p.slug)}</div>
100
+ <div class="project-card-desc">${escHtml(p.description || 'No description')}</div>
101
+ <div class="project-card-meta">
102
+ <span class="project-card-prefix">${escHtml(p.prefix)}</span>
103
+ <span class="project-card-date">${fmtDate(p.created_at)}</span>
104
+ </div>
105
+ </div>`).join('')}
106
+ </div>`;
107
+ }
108
+ export function selectProject(id) {
109
+ const sel = document.getElementById('project-selector');
110
+ if (sel)
111
+ sel.value = id;
112
+ onProjectSelect(id);
113
+ }
114
+ export function deleteProject(id, name) {
115
+ confirmDialog('Delete Project?', `Delete project "${name}"? This cannot be undone.`, async () => {
116
+ try {
117
+ await api('DELETE', `/projects/${id}`);
118
+ toast('Project deleted', 'success');
119
+ if (state.currentProject?.id === id) {
120
+ state.currentProject = null;
121
+ const pmSection = document.getElementById('sidebar-pm-section');
122
+ if (pmSection)
123
+ pmSection.style.display = 'none';
124
+ }
125
+ await loadProjects();
126
+ if (state.currentView === 'projects')
127
+ renderProjectsView();
128
+ }
129
+ catch (err) {
130
+ toast(err instanceof Error ? err.message : String(err), 'error');
131
+ }
132
+ }, true);
133
+ }
134
+ export function buildCreateProjectModal() {
135
+ createModal('create-project-modal', 'New Project', `
136
+ <form id="create-project-form" onsubmit="window.__app.submitCreateProject(event)">
137
+ <div class="form-group">
138
+ <label class="form-label">Project Name *</label>
139
+ <input class="form-input" id="cp-name" type="text" placeholder="My Awesome Project" required>
140
+ </div>
141
+ <div class="form-group">
142
+ <label class="form-label">ID Prefix *</label>
143
+ <input class="form-input" id="cp-prefix" type="text" placeholder="myproj" pattern="[a-z0-9\\-]+" required>
144
+ <div style="font-size:11px;color:var(--text-muted);margin-top:4px">Lowercase letters, numbers, hyphens. Used as item ID prefix (e.g. myproj-1)</div>
145
+ </div>
146
+ <div class="form-group">
147
+ <label class="form-label">Description</label>
148
+ <input class="form-input" id="cp-desc" type="text" placeholder="What is this project about?">
149
+ </div>
150
+ <div class="form-error" id="cp-error" style="display:none"></div>
151
+ </form>`, `<button class="btn btn-ghost" onclick="window.__app.hideModal('create-project-modal')">Cancel</button>
152
+ <button class="btn btn-primary" onclick="window.__app.submitCreateProject2()"><span>Create Project</span></button>`);
153
+ const nameEl = document.getElementById('cp-name');
154
+ const prefixEl = document.getElementById('cp-prefix');
155
+ if (nameEl && prefixEl) {
156
+ nameEl.addEventListener('input', function () {
157
+ const prefix = this.value.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 20);
158
+ if (!prefixEl._touched)
159
+ prefixEl.value = prefix;
160
+ });
161
+ prefixEl.addEventListener('input', function () { this._touched = !!this.value; });
162
+ }
163
+ }
164
+ export async function submitCreateProject(e) {
165
+ e?.preventDefault();
166
+ await submitCreateProject2();
167
+ }
168
+ async function submitCreateProject2() {
169
+ const nameEl = document.getElementById('cp-name');
170
+ const prefixEl = document.getElementById('cp-prefix');
171
+ const descEl = document.getElementById('cp-desc');
172
+ const errEl = document.getElementById('cp-error');
173
+ if (!nameEl || !prefixEl || !errEl)
174
+ return;
175
+ const name = nameEl.value.trim();
176
+ const prefix = prefixEl.value.trim();
177
+ const desc = descEl?.value.trim() || '';
178
+ errEl.style.display = 'none';
179
+ if (!name || !prefix) {
180
+ errEl.textContent = 'Name and prefix are required.';
181
+ errEl.style.display = 'block';
182
+ return;
183
+ }
184
+ try {
185
+ const data = await api('POST', '/projects', { name, prefix, description: desc });
186
+ state.projects.unshift(data.project);
187
+ renderProjectSelector();
188
+ hideModal('create-project-modal');
189
+ toast('Project created!', 'success');
190
+ nameEl.value = '';
191
+ prefixEl.value = '';
192
+ prefixEl._touched = false;
193
+ if (descEl)
194
+ descEl.value = '';
195
+ if (state.currentView === 'projects')
196
+ renderProjectsView();
197
+ }
198
+ catch (err) {
199
+ errEl.textContent = err instanceof Error ? err.message : String(err);
200
+ errEl.style.display = 'block';
201
+ }
202
+ }
203
+ export { submitCreateProject2 };
204
+ //# sourceMappingURL=projects.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projects.js","sourceRoot":"","sources":["projects.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,196 @@
1
+ // ═══════════════════════════════════════════════════════════════
2
+ // PROJECTS VIEW
3
+ // ═══════════════════════════════════════════════════════════════
4
+ import { state } from '../state.js';
5
+ import { api } from '../api.js';
6
+ import { escHtml, fmtDate } from '../utils.js';
7
+ import { showModal, hideModal, createModal, confirmDialog } from '../components/modals.js';
8
+ import { toast } from '../components/toast.js';
9
+ import { showView } from '../views/router.js';
10
+
11
+ export async function loadProjects(): Promise<void> {
12
+ const data = await api('GET','/projects');
13
+ state.projects = data.projects || [];
14
+ renderProjectSelector();
15
+ }
16
+
17
+ export function renderProjectSelector(): void {
18
+ const sel = document.getElementById('project-selector') as HTMLSelectElement | null;
19
+ if (!sel) return;
20
+ const cur = sel.value;
21
+ sel.innerHTML = '<option value="">— Select project —</option>' +
22
+ state.projects.map(p=>`<option value="${p.id}"${p.id===cur?' selected':''}>${escHtml(p.name)}</option>`).join('');
23
+ }
24
+
25
+ async function fetchProjectSchema(projectId: string): Promise<void> {
26
+ try {
27
+ const schema = await api('GET', `/projects/${projectId}/pm/schema`);
28
+ if (schema && Array.isArray(schema.types) && schema.types.length) {
29
+ state.schema = schema as import('../types.js').ProjectSchema;
30
+ }
31
+ } catch (_) { /* fallback to constants */ }
32
+ }
33
+
34
+ export async function onProjectSelect(id: string): Promise<void> {
35
+ if (!id) {
36
+ state.currentProject = null;
37
+ state.schema = null;
38
+ (window as any).__app?.disconnectSSE?.();
39
+ const pmSection = document.getElementById('sidebar-pm-section');
40
+ if (pmSection) pmSection.style.display = 'none';
41
+ showView('projects');
42
+ return;
43
+ }
44
+ const proj = state.projects.find(p=>p.id===id);
45
+ if (!proj) return;
46
+ state.currentProject = proj;
47
+ const pmSection = document.getElementById('sidebar-pm-section');
48
+ if (pmSection) pmSection.style.display = '';
49
+ const projName = document.getElementById('sidebar-project-name');
50
+ if (projName) projName.textContent = proj.name;
51
+ (window as any).__app?.connectSSE?.(proj.id);
52
+ // Fetch schema in background — views use fallback until it resolves
53
+ fetchProjectSchema(proj.id);
54
+ showView('items');
55
+ loadItemsBadge();
56
+ }
57
+
58
+ export async function loadItemsBadge(): Promise<void> {
59
+ if (!state.currentProject) return;
60
+ try {
61
+ const data = await api('GET',`/projects/${state.currentProject.id}/pm/list?status=open&limit=200`);
62
+ const count = (data.items||[]).length;
63
+ const badge = document.getElementById('badge-items');
64
+ if (badge) badge.textContent = count ? String(count) : '';
65
+ } catch(_) { /* ignore */ }
66
+ }
67
+
68
+ export function renderProjectsView(): void {
69
+ const el = document.getElementById('content-projects');
70
+ if (!el) return;
71
+ if (state.projects.length === 0) {
72
+ el.innerHTML = `
73
+ <div class="page-header">
74
+ <div><div class="page-title">Projects</div><div class="page-subtitle">Your project workspaces</div></div>
75
+ <div class="page-actions"><button class="btn btn-primary" onclick="window.__app.showModal('create-project-modal')">+ New Project</button></div>
76
+ </div>
77
+ <div class="welcome-state">
78
+ <div class="welcome-icon">⊞</div>
79
+ <div class="welcome-title">No projects yet</div>
80
+ <div class="welcome-text">Create your first project to start managing tasks, features, bugs, and more with git-native storage.</div>
81
+ <button class="btn btn-primary btn-lg" onclick="window.__app.showModal('create-project-modal')">+ Create Project</button>
82
+ </div>`;
83
+ return;
84
+ }
85
+ el.innerHTML = `
86
+ <div class="page-header">
87
+ <div><div class="page-title">Projects</div><div class="page-subtitle">${state.projects.length} project${state.projects.length!==1?'s':''}</div></div>
88
+ <div class="page-actions"><button class="btn btn-primary" onclick="window.__app.showModal('create-project-modal')">+ New Project</button></div>
89
+ </div>
90
+ <div class="projects-grid">
91
+ ${state.projects.map(p=>`
92
+ <div class="project-card" onclick="window.__app.selectProject('${p.id}')">
93
+ <button class="btn btn-ghost btn-sm project-card-del" onclick="event.stopPropagation();window.__app.deleteProject('${p.id}','${escHtml(p.name)}')" title="Delete project">✕</button>
94
+ <div class="project-card-name">${escHtml(p.name)}</div>
95
+ <div class="project-card-slug mono">${escHtml(p.slug)}</div>
96
+ <div class="project-card-desc">${escHtml(p.description||'No description')}</div>
97
+ <div class="project-card-meta">
98
+ <span class="project-card-prefix">${escHtml(p.prefix)}</span>
99
+ <span class="project-card-date">${fmtDate(p.created_at)}</span>
100
+ </div>
101
+ </div>`).join('')}
102
+ </div>`;
103
+ }
104
+
105
+ export function selectProject(id: string): void {
106
+ const sel = document.getElementById('project-selector') as HTMLSelectElement | null;
107
+ if (sel) sel.value = id;
108
+ onProjectSelect(id);
109
+ }
110
+
111
+ export function deleteProject(id: string, name: string): void {
112
+ confirmDialog('Delete Project?', `Delete project "${name}"? This cannot be undone.`, async () => {
113
+ try {
114
+ await api('DELETE',`/projects/${id}`);
115
+ toast('Project deleted','success');
116
+ if (state.currentProject?.id === id) {
117
+ state.currentProject = null;
118
+ const pmSection = document.getElementById('sidebar-pm-section');
119
+ if (pmSection) pmSection.style.display = 'none';
120
+ }
121
+ await loadProjects();
122
+ if (state.currentView === 'projects') renderProjectsView();
123
+ } catch(err: unknown) {
124
+ toast(err instanceof Error ? err.message : String(err),'error');
125
+ }
126
+ }, true);
127
+ }
128
+
129
+ export function buildCreateProjectModal(): void {
130
+ createModal('create-project-modal','New Project',`
131
+ <form id="create-project-form" onsubmit="window.__app.submitCreateProject(event)">
132
+ <div class="form-group">
133
+ <label class="form-label">Project Name *</label>
134
+ <input class="form-input" id="cp-name" type="text" placeholder="My Awesome Project" required>
135
+ </div>
136
+ <div class="form-group">
137
+ <label class="form-label">ID Prefix *</label>
138
+ <input class="form-input" id="cp-prefix" type="text" placeholder="myproj" pattern="[a-z0-9\\-]+" required>
139
+ <div style="font-size:11px;color:var(--text-muted);margin-top:4px">Lowercase letters, numbers, hyphens. Used as item ID prefix (e.g. myproj-1)</div>
140
+ </div>
141
+ <div class="form-group">
142
+ <label class="form-label">Description</label>
143
+ <input class="form-input" id="cp-desc" type="text" placeholder="What is this project about?">
144
+ </div>
145
+ <div class="form-error" id="cp-error" style="display:none"></div>
146
+ </form>`,
147
+ `<button class="btn btn-ghost" onclick="window.__app.hideModal('create-project-modal')">Cancel</button>
148
+ <button class="btn btn-primary" onclick="window.__app.submitCreateProject2()"><span>Create Project</span></button>`
149
+ );
150
+
151
+ const nameEl = document.getElementById('cp-name');
152
+ const prefixEl = document.getElementById('cp-prefix') as (HTMLInputElement & { _touched?: boolean }) | null;
153
+ if (nameEl && prefixEl) {
154
+ nameEl.addEventListener('input', function() {
155
+ const prefix = (this as HTMLInputElement).value.toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,'').slice(0,20);
156
+ if (!prefixEl._touched) prefixEl.value = prefix;
157
+ });
158
+ prefixEl.addEventListener('input', function() { (this as HTMLInputElement & { _touched?: boolean })._touched = !!(this as HTMLInputElement).value; });
159
+ }
160
+ }
161
+
162
+ export async function submitCreateProject(e?: Event): Promise<void> {
163
+ e?.preventDefault();
164
+ await submitCreateProject2();
165
+ }
166
+
167
+ async function submitCreateProject2(): Promise<void> {
168
+ const nameEl = document.getElementById('cp-name') as HTMLInputElement | null;
169
+ const prefixEl = document.getElementById('cp-prefix') as HTMLInputElement | null;
170
+ const descEl = document.getElementById('cp-desc') as HTMLInputElement | null;
171
+ const errEl = document.getElementById('cp-error') as HTMLElement | null;
172
+ if (!nameEl || !prefixEl || !errEl) return;
173
+
174
+ const name = nameEl.value.trim();
175
+ const prefix = prefixEl.value.trim();
176
+ const desc = descEl?.value.trim() || '';
177
+ errEl.style.display = 'none';
178
+ if (!name || !prefix) { errEl.textContent = 'Name and prefix are required.'; errEl.style.display = 'block'; return; }
179
+ try {
180
+ const data = await api('POST','/projects',{name,prefix,description:desc});
181
+ state.projects.unshift(data.project);
182
+ renderProjectSelector();
183
+ hideModal('create-project-modal');
184
+ toast('Project created!','success');
185
+ nameEl.value = '';
186
+ prefixEl.value = '';
187
+ (prefixEl as HTMLInputElement & { _touched?: boolean })._touched = false;
188
+ if (descEl) descEl.value = '';
189
+ if (state.currentView === 'projects') renderProjectsView();
190
+ } catch(err: unknown) {
191
+ errEl.textContent = err instanceof Error ? err.message : String(err);
192
+ errEl.style.display = 'block';
193
+ }
194
+ }
195
+
196
+ export { submitCreateProject2 };
@@ -0,0 +1,227 @@
1
+ // ═══════════════════════════════════════════════════════════════
2
+ // ROUTER — View switching with URL routing
3
+ // ═══════════════════════════════════════════════════════════════
4
+ import { state } from '../state.js';
5
+ import { VIEW_NAMES } from '../constants.js';
6
+ import { renderProjectsView } from './projects.js';
7
+ import { renderItemsView } from './items.js';
8
+ import { renderCreateView } from './create.js';
9
+ import { renderActivityView } from './activity.js';
10
+ import { renderSearchView } from './search.js';
11
+ import { renderStatsView } from './stats.js';
12
+ import { renderCalendarView } from './calendar.js';
13
+ import { renderContextView } from './context.js';
14
+ import { renderGraphView } from './graph.js';
15
+ import { renderSharingView } from './sharing.js';
16
+ import { renderGroupsView } from './groups.js';
17
+ import { renderHealthView } from './health.js';
18
+ import { renderDedupeAuditView } from './dedupe.js';
19
+ import { renderValidateView } from './validate.js';
20
+ import { renderSettingsView } from './settings.js';
21
+ import { renderGitHubView } from './github.js';
22
+ import { renderExportView } from './export.js';
23
+ import { renderNormalizeView } from './normalize.js';
24
+ import { renderSharedView } from './shared.js';
25
+ import { renderTemplatesView } from './templates.js';
26
+ import { renderCommentsAuditView } from './comments-audit.js';
27
+ import { renderConfigView } from './config.js';
28
+ import { renderGuideView } from './guide.js';
29
+ import { renderAdminView } from './admin.js';
30
+ import { initPlanView } from './plan.js';
31
+ // View name → URL path mapping
32
+ const VIEW_TO_PATH = {
33
+ 'projects': '/',
34
+ 'items': '/items',
35
+ 'create': '/create',
36
+ 'activity': '/activity',
37
+ 'search': '/search',
38
+ 'stats': '/stats',
39
+ 'calendar': '/calendar',
40
+ 'context': '/context',
41
+ 'graph': '/graph',
42
+ 'sharing': '/sharing',
43
+ 'groups': '/groups',
44
+ 'health': '/health',
45
+ 'dedupe': '/dedupe',
46
+ 'validate': '/validate',
47
+ 'settings': '/settings',
48
+ 'github': '/github',
49
+ 'export': '/export',
50
+ 'normalize': '/normalize',
51
+ 'shared': '/shared',
52
+ 'templates': '/templates',
53
+ 'comments-audit': '/comments-audit',
54
+ 'config': '/config',
55
+ 'guide': '/guide',
56
+ 'admin': '/admin',
57
+ 'plan': '/plan',
58
+ };
59
+ // Reverse: URL path → view name
60
+ const PATH_TO_VIEW = {};
61
+ for (const [view, path] of Object.entries(VIEW_TO_PATH)) {
62
+ PATH_TO_VIEW[path] = view;
63
+ }
64
+ // Whether pushState was just called (to ignore the resulting popstate)
65
+ let navigatingInternally = false;
66
+ // Callback invoked when the view changes — used to notify presence service
67
+ let onViewChange = null;
68
+ export function setOnViewChange(cb) {
69
+ onViewChange = cb;
70
+ }
71
+ export function getPathForView(view) {
72
+ return VIEW_TO_PATH[view] || '/';
73
+ }
74
+ export function getViewForPath(path) {
75
+ // Normalize: remove trailing slash except for root
76
+ const normalized = path.replace(/\/$/, '') || '/';
77
+ // Direct match
78
+ if (PATH_TO_VIEW[normalized])
79
+ return PATH_TO_VIEW[normalized];
80
+ // Try matching first segment for deeper paths (e.g. /items/DETAIL-1 → items)
81
+ const firstSegment = '/' + normalized.slice(1).split('/')[0];
82
+ return PATH_TO_VIEW[firstSegment] || 'projects';
83
+ }
84
+ export function showView(view, pushState = true) {
85
+ if (view === 'admin' && !state.user?.is_admin) {
86
+ history.replaceState({ view: 'projects' }, '', '/');
87
+ view = 'projects';
88
+ }
89
+ state.currentView = view;
90
+ // Update URL via pushState
91
+ if (pushState) {
92
+ const path = getPathForView(view);
93
+ if (window.location.pathname !== path) {
94
+ navigatingInternally = true;
95
+ history.pushState({ view }, '', path);
96
+ }
97
+ }
98
+ // Full-screen graph mode: hide sidebar
99
+ document.body.classList.toggle('graph-mode', view === 'graph');
100
+ VIEW_NAMES.forEach(v => {
101
+ const el = document.getElementById(`content-${v}`);
102
+ if (el)
103
+ el.style.display = v === view ? '' : 'none';
104
+ });
105
+ document.querySelectorAll('.sidebar-item[data-view]').forEach(el => {
106
+ el.classList.toggle('active', el.dataset.view === view);
107
+ });
108
+ if (view === 'projects') {
109
+ document.querySelectorAll('#sidebar-projects-section .sidebar-item').forEach((el, i) => {
110
+ el.classList.toggle('active', i === 0);
111
+ });
112
+ }
113
+ switch (view) {
114
+ case 'projects':
115
+ renderProjectsView();
116
+ break;
117
+ case 'items':
118
+ renderItemsView();
119
+ break;
120
+ case 'create':
121
+ renderCreateView();
122
+ break;
123
+ case 'activity':
124
+ renderActivityView();
125
+ break;
126
+ case 'search':
127
+ renderSearchView();
128
+ break;
129
+ case 'stats':
130
+ renderStatsView();
131
+ break;
132
+ case 'calendar':
133
+ renderCalendarView();
134
+ break;
135
+ case 'context':
136
+ renderContextView();
137
+ break;
138
+ case 'graph':
139
+ renderGraphView();
140
+ break;
141
+ case 'sharing':
142
+ renderSharingView();
143
+ break;
144
+ case 'groups':
145
+ renderGroupsView();
146
+ break;
147
+ case 'health':
148
+ renderHealthView();
149
+ break;
150
+ case 'dedupe':
151
+ renderDedupeAuditView();
152
+ break;
153
+ case 'validate':
154
+ renderValidateView();
155
+ break;
156
+ case 'settings':
157
+ renderSettingsView();
158
+ break;
159
+ case 'github':
160
+ renderGitHubView();
161
+ break;
162
+ case 'export':
163
+ renderExportView();
164
+ break;
165
+ case 'normalize':
166
+ renderNormalizeView();
167
+ break;
168
+ case 'shared':
169
+ renderSharedView();
170
+ break;
171
+ case 'templates':
172
+ renderTemplatesView();
173
+ break;
174
+ case 'comments-audit':
175
+ renderCommentsAuditView();
176
+ break;
177
+ case 'config':
178
+ renderConfigView();
179
+ break;
180
+ case 'guide':
181
+ renderGuideView();
182
+ break;
183
+ case 'admin':
184
+ renderAdminView();
185
+ break;
186
+ case 'plan':
187
+ void initPlanView();
188
+ break;
189
+ }
190
+ updateMobileNav(view);
191
+ // Notify presence service of view change
192
+ onViewChange?.(view);
193
+ // Scroll main content to top on view change
194
+ const mainContent = document.getElementById('main-content');
195
+ if (mainContent)
196
+ mainContent.scrollTop = 0;
197
+ // Move focus to main content for accessibility
198
+ const activeArea = document.getElementById(`content-${view}`);
199
+ if (activeArea) {
200
+ activeArea.setAttribute('tabindex', '-1');
201
+ activeArea.focus({ preventScroll: true });
202
+ }
203
+ }
204
+ function updateMobileNav(view) {
205
+ document.querySelectorAll('.mobile-bottom-nav-item').forEach(el => {
206
+ el.classList.toggle('active', el.dataset.mobview === view);
207
+ });
208
+ const nav = document.getElementById('mobile-bottom-nav');
209
+ if (nav) {
210
+ nav.classList.toggle('visible', !!state.currentProject && view !== 'projects');
211
+ }
212
+ }
213
+ // Handle browser back/forward
214
+ function onPopState(e) {
215
+ // Ignore if we just pushed state (some browsers fire popstate after pushState)
216
+ if (navigatingInternally) {
217
+ navigatingInternally = false;
218
+ return;
219
+ }
220
+ const view = e.state?.view || getViewForPath(window.location.pathname);
221
+ showView(view, false);
222
+ }
223
+ // Initialize popstate listener
224
+ if (typeof window !== 'undefined') {
225
+ window.addEventListener('popstate', onPopState);
226
+ }
227
+ //# sourceMappingURL=router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["router.ts"],"names":[],"mappings":"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"}