domma-cms 0.73.0 → 0.75.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 (64) hide show
  1. package/CLAUDE.md +3 -1
  2. package/admin/js/api.js +1 -1
  3. package/admin/js/app.js +4 -4
  4. package/admin/js/lib/actions-arrange.js +1 -0
  5. package/admin/js/lib/api-endpoints-arrange.js +1 -0
  6. package/admin/js/lib/blocks-arrange.js +1 -0
  7. package/admin/js/lib/collections-arrange.js +1 -0
  8. package/admin/js/lib/components-arrange.js +1 -0
  9. package/admin/js/lib/context-menus-arrange.js +1 -0
  10. package/admin/js/lib/forms-arrange.js +1 -0
  11. package/admin/js/lib/media-arrange.js +1 -0
  12. package/admin/js/lib/menus-arrange.js +1 -0
  13. package/admin/js/lib/pages-arrange.js +1 -0
  14. package/admin/js/lib/projects-arrange.js +1 -0
  15. package/admin/js/lib/sidebar-renderer.js +3 -3
  16. package/admin/js/lib/theme-settings-arrange.js +1 -0
  17. package/admin/js/lib/views-arrange.js +1 -0
  18. package/admin/js/templates/actions-list.html +73 -14
  19. package/admin/js/templates/api-endpoints.html +63 -10
  20. package/admin/js/templates/blocks.html +76 -15
  21. package/admin/js/templates/collections.html +108 -52
  22. package/admin/js/templates/components.html +82 -15
  23. package/admin/js/templates/context-menus.html +55 -13
  24. package/admin/js/templates/forms.html +67 -14
  25. package/admin/js/templates/media.html +99 -13
  26. package/admin/js/templates/menus.html +57 -11
  27. package/admin/js/templates/pages.html +78 -23
  28. package/admin/js/templates/projects.html +69 -10
  29. package/admin/js/templates/theme.html +164 -132
  30. package/admin/js/templates/views-list.html +63 -9
  31. package/admin/js/views/actions-list.js +37 -1
  32. package/admin/js/views/api-endpoints.js +11 -7
  33. package/admin/js/views/blocks.js +34 -5
  34. package/admin/js/views/collections.js +36 -1
  35. package/admin/js/views/components.js +27 -5
  36. package/admin/js/views/context-menus.js +10 -5
  37. package/admin/js/views/forms.js +57 -1
  38. package/admin/js/views/index.js +1 -1
  39. package/admin/js/views/media.js +40 -1
  40. package/admin/js/views/menus.js +11 -5
  41. package/admin/js/views/pages.js +28 -8
  42. package/admin/js/views/projects.js +58 -3
  43. package/admin/js/views/theme.js +55 -1
  44. package/admin/js/views/views-list.js +11 -1
  45. package/package.json +1 -1
  46. package/server/routes/api/actions.js +64 -2
  47. package/server/routes/api/api-endpoints.js +41 -0
  48. package/server/routes/api/blocks.js +170 -8
  49. package/server/routes/api/collections.js +53 -1
  50. package/server/routes/api/components.js +46 -0
  51. package/server/routes/api/context-menus.js +8 -0
  52. package/server/routes/api/forms.js +123 -10
  53. package/server/routes/api/media.js +56 -1
  54. package/server/routes/api/menus.js +53 -6
  55. package/server/routes/api/pages.js +46 -0
  56. package/server/routes/api/projects.js +16 -2
  57. package/server/routes/api/theme.js +55 -0
  58. package/server/routes/api/views.js +27 -3
  59. package/server/server.js +19 -0
  60. package/server/services/contextMenus.js +41 -0
  61. package/server/services/projects.js +86 -26
  62. package/server/services/views.js +38 -0
  63. package/admin/js/templates/project-editor.html +0 -45
  64. package/admin/js/views/project-editor.js +0 -1
@@ -475,3 +475,41 @@ export async function executeView(slug, {page = 1, limit = 25, user = null} = {}
475
475
  ? executeMongoView(view, { page, limit, user })
476
476
  : executeFileView(view, { page, limit, user });
477
477
  }
478
+
479
+ /**
480
+ * The sidebar badge on Views (its countUrl, GET /api/views/_count): how many
481
+ * views, red when one reads from a collection that no longer exists - such a
482
+ * view errors on every page that shows it. Plain text: the popover sets it
483
+ * with textContent.
484
+ *
485
+ * @param {object[]} views
486
+ * @param {Set<string>} collectionSlugs - the collections that exist
487
+ * @returns {{count: number, label: string, tone: string, details: object}}
488
+ */
489
+ export function viewsBadge(views = [], collectionSlugs = new Set()) {
490
+ const broken = views.filter(v => v.pipeline?.source && !collectionSlugs.has(v.pipeline.source));
491
+ const pub = views.filter(v => v.access?.public).length;
492
+ const n = views.length;
493
+ const ordered = [...broken, ...views.filter(v => !broken.includes(v))];
494
+ return {
495
+ count: n,
496
+ label: `${n} view${n === 1 ? '' : 's'}${broken.length ? `, ${broken.length} reading a missing collection` : ''}`,
497
+ tone: broken.length ? 'danger' : 'info',
498
+ details: {
499
+ title: 'Views',
500
+ rows: [
501
+ {label: 'Views', value: String(n)},
502
+ {label: 'Public', value: String(pub)},
503
+ {label: 'Missing source', value: String(broken.length), ...(broken.length && {tone: 'danger'})}
504
+ ],
505
+ items: ordered.slice(0, 8).map(v => ({
506
+ text: String(v.title || v.slug),
507
+ meta: broken.includes(v)
508
+ ? `source "${v.pipeline.source}" is gone`
509
+ : [v.pipeline?.source, v.display?.mode || 'table', v.access?.public ? 'public' : ''].filter(Boolean).join(' · '),
510
+ ...(broken.includes(v) && {tone: 'danger'})
511
+ })),
512
+ empty: 'No views yet.'
513
+ }
514
+ };
515
+ }
@@ -1,45 +0,0 @@
1
- <div class="page-header">
2
- <h2><span data-icon="folder"></span> <span id="pe-title">Edit project</span></h2>
3
- <div class="header-actions">
4
- <a class="btn btn-secondary" href="#/projects"><span data-icon="arrow-left"></span> Back</a>
5
- <button class="btn btn-primary" id="pe-save"><span data-icon="check"></span> Save</button>
6
- </div>
7
- </div>
8
-
9
- <div class="card">
10
- <div class="card-header">
11
- <span data-icon="info"></span> Project details
12
- </div>
13
- <div class="card-body">
14
- <div class="grid grid-cols-2 dm-pe-grid">
15
- <div class="form-group">
16
- <label for="pe-slug">Slug <span class="required">*</span></label>
17
- <input type="text" id="pe-slug" name="slug" class="form-input" required>
18
- <small class="form-hint">Lowercase alphanumeric + hyphen; immutable after create</small>
19
- </div>
20
- <div class="form-group">
21
- <label for="pe-name">Name <span class="required">*</span></label>
22
- <input type="text" id="pe-name" name="name" class="form-input" required>
23
- </div>
24
- <div class="form-group col-span-2">
25
- <label for="pe-description">Description</label>
26
- <textarea id="pe-description" name="description" class="form-input" rows="2"></textarea>
27
- </div>
28
- <div class="form-group">
29
- <label for="pe-icon">Icon</label>
30
- <div id="pe-icon-mount"></div>
31
- <small class="form-hint">Click <code>⊞</code> to browse Domma icons; defaults to <code>folder</code></small>
32
- </div>
33
- <div class="form-group">
34
- <label for="pe-rootUrl">Root URL</label>
35
- <input type="text" id="pe-rootUrl" name="rootUrl" class="form-input" placeholder="/members">
36
- <small class="form-hint">URL prefix for auto-inheritance (must start with /)</small>
37
- </div>
38
- <div class="form-group">
39
- <label for="pe-sortOrder">Sort order</label>
40
- <input type="number" id="pe-sortOrder" name="sortOrder" class="form-input" value="0">
41
- <small class="form-hint">Lower = higher in the sidebar</small>
42
- </div>
43
- </div>
44
- </div>
45
- </div>
@@ -1 +0,0 @@
1
- import{api as d}from"../api.js";import{makeIconInput as h}from"../lib/shortcode-modal.js";export const projectEditorView={templateUrl:"/admin/js/templates/project-editor.html",async onMount(t){const p=location.hash.match(/^#\/projects\/edit\/(.+)$/),i=p?decodeURIComponent(p[1]):null,s=!i;let e={slug:"",name:"",description:"",icon:"folder",rootUrl:"",sortOrder:0};if(!s){const o=E.loader(t.get(0),{type:"dots"});try{e=await d.projects.get(i)}catch(r){E.toast(`Failed to load project: ${r.message||r}`,{type:"error"}),location.hash="#/projects";return}finally{o.destroy()}}t.find("#pe-title").text(s?"New project":`Edit "${e.slug}"`);const l=t.find("#pe-slug"),n=t.find("#pe-name"),m=t.find("#pe-description"),u=t.find("#pe-rootUrl"),f=t.find("#pe-sortOrder");l.val(e.slug||""),n.val(e.name||""),m.val(e.description||""),u.val(e.rootUrl||""),f.val(e.sortOrder??0),s||l.prop("disabled",!0);const g=t.find("#pe-icon-mount").get(0),a=h("e.g. folder, users, box",e.icon||"folder");a.input.id="pe-icon",a.input.classList.add("form-input"),g.appendChild(a.el),Domma.icons.scan(t.get(0)),t.find("#pe-save").on("click",async()=>{const o={slug:l.val().trim(),name:n.val().trim(),description:m.val().trim(),icon:a.input.value.trim()||"folder",sortOrder:Number.parseInt(f.val(),10)||0},r=u.val().trim();r&&(o.rootUrl=r);try{if(s)await d.projects.create(o);else{const{slug:c,...v}=o;await d.projects.update(i,v)}E.toast("Saved.",{type:"success"}),location.hash="#/projects"}catch(c){E.toast(`Save failed: ${c.message||c}`,{type:"error"})}})}};