domma-cms 0.44.0 → 0.45.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.
- package/CLAUDE.md +53 -53
- package/README.md +27 -27
- package/admin/css/admin.css +1 -1
- package/admin/dist/domma/domma-tools.css +3 -3
- package/admin/dist/domma/domma-tools.min.js +3 -3
- package/admin/index.html +2 -2
- package/admin/js/app.js +1 -1
- package/admin/js/lib/card-builder.js +2 -2
- package/admin/js/lib/crud-tutorial.js +1 -1
- package/admin/js/lib/effect-defs.js +1 -1
- package/admin/js/lib/effects-builder.js +1 -1
- package/admin/js/lib/image-editor.js +1 -1
- package/admin/js/lib/markdown-toolbar.js +2 -2
- package/admin/js/lib/project-context.js +1 -1
- package/admin/js/lib/project-quick-create.js +1 -1
- package/admin/js/lib/scribe-composer.js +1 -1
- package/admin/js/lib/server-restart.js +1 -1
- package/admin/js/lib/shortcode-context-menu.js +2 -2
- package/admin/js/lib/simple-editor.js +1 -1
- package/admin/js/lib/themes.js +1 -1
- package/admin/js/lib/timeline-builder.js +1 -1
- package/admin/js/templates/action-editor.html +8 -8
- package/admin/js/templates/api-endpoint-editor.html +7 -7
- package/admin/js/templates/api-endpoints.html +1 -1
- package/admin/js/templates/api-reference.html +1 -1
- package/admin/js/templates/block-editor.html +6 -6
- package/admin/js/templates/collection-editor.html +1 -1
- package/admin/js/templates/component-editor.html +2 -2
- package/admin/js/templates/dashboard/cache.html +3 -3
- package/admin/js/templates/dashboard/journeys.html +3 -3
- package/admin/js/templates/dashboard/kpi-strip.html +3 -3
- package/admin/js/templates/dashboard/top-pages.html +1 -1
- package/admin/js/templates/dashboard/traffic-chart.html +1 -1
- package/admin/js/templates/docs/api-actions.html +1 -1
- package/admin/js/templates/docs/api-collections.html +4 -4
- package/admin/js/templates/docs/api-pages.html +2 -2
- package/admin/js/templates/docs/api-plugins.html +1 -1
- package/admin/js/templates/docs/api-settings.html +2 -2
- package/admin/js/templates/docs/api-users.html +1 -1
- package/admin/js/templates/docs/components-howto.html +4 -4
- package/admin/js/templates/docs/components-reference.html +21 -21
- package/admin/js/templates/docs/components-rules.html +14 -14
- package/admin/js/templates/docs/components-walkthrough.html +13 -13
- package/admin/js/templates/docs/tutorial-crud.html +49 -49
- package/admin/js/templates/docs/tutorial-forms.html +4 -4
- package/admin/js/templates/docs/tutorial-plugin.html +10 -10
- package/admin/js/templates/docs/usage-actions.html +6 -6
- package/admin/js/templates/docs/usage-cta-shortcode.html +9 -9
- package/admin/js/templates/docs/usage-dconfig.html +4 -4
- package/admin/js/templates/docs/usage-navigation.html +1 -1
- package/admin/js/templates/docs/usage-pages.html +1 -1
- package/admin/js/templates/docs/usage-shortcodes.html +24 -24
- package/admin/js/templates/docs/usage-users-roles.html +3 -3
- package/admin/js/templates/docs/usage-views.html +4 -4
- package/admin/js/templates/documentation.html +1 -1
- package/admin/js/templates/effects.html +752 -752
- package/admin/js/templates/form-editor.html +5 -5
- package/admin/js/templates/forms.html +17 -17
- package/admin/js/templates/login.html +5 -5
- package/admin/js/templates/menu-editor.html +10 -10
- package/admin/js/templates/my-profile.html +17 -17
- package/admin/js/templates/navigation.html +11 -11
- package/admin/js/templates/notifications.html +1 -1
- package/admin/js/templates/page-editor.html +4 -4
- package/admin/js/templates/pro-docs.html +14 -14
- package/admin/js/templates/role-editor.html +70 -70
- package/admin/js/templates/roles.html +10 -10
- package/admin/js/templates/settings.html +10 -10
- package/admin/js/templates/tutorials.html +3 -3
- package/admin/js/templates/view-editor.html +15 -15
- package/admin/js/views/action-editor.js +1 -1
- package/admin/js/views/actions-list.js +1 -1
- package/admin/js/views/api-endpoint-editor.js +2 -2
- package/admin/js/views/api-endpoints.js +2 -2
- package/admin/js/views/api-tokens.js +3 -3
- package/admin/js/views/blocks.js +2 -2
- package/admin/js/views/collection-editor.js +1 -1
- package/admin/js/views/collection-entries.js +1 -1
- package/admin/js/views/collections.js +1 -1
- package/admin/js/views/component-editor.js +1 -1
- package/admin/js/views/components.js +4 -4
- package/admin/js/views/dashboard/widgets/cache.js +1 -1
- package/admin/js/views/dashboard/widgets/journeys.js +1 -1
- package/admin/js/views/dashboard/widgets/kpi-strip.js +1 -1
- package/admin/js/views/form-editor.js +7 -7
- package/admin/js/views/form-submissions.js +1 -1
- package/admin/js/views/forms.js +1 -1
- package/admin/js/views/layouts.js +1 -1
- package/admin/js/views/login.js +2 -2
- package/admin/js/views/menu-editor.js +3 -3
- package/admin/js/views/menu-locations.js +1 -1
- package/admin/js/views/menus.js +3 -3
- package/admin/js/views/navigation.js +8 -8
- package/admin/js/views/page-editor.js +40 -40
- package/admin/js/views/pages.js +3 -3
- package/admin/js/views/plugin-code.js +2 -2
- package/admin/js/views/plugins.js +1 -1
- package/admin/js/views/project-settings.js +1 -1
- package/admin/js/views/projects.js +1 -1
- package/admin/js/views/roles.js +1 -1
- package/admin/js/views/user-editor.js +1 -1
- package/admin/js/views/users.js +3 -3
- package/admin/js/views/view-editor.js +1 -1
- package/admin/js/views/view-preview.js +1 -1
- package/admin/js/views/views-list.js +1 -1
- package/bin/cli.js +8 -8
- package/bin/lib/config-merge.js +44 -44
- package/bin/update.js +16 -16
- package/config/plugins.json +1 -1
- package/package.json +2 -2
- package/plugins/_template/admin/views/index.js +1 -1
- package/plugins/_template/config.js +1 -1
- package/plugins/_template/plugin.js +1 -1
- package/plugins/analytics/admin/templates/analytics.html +3 -3
- package/plugins/analytics/admin/views/analytics.js +1 -1
- package/plugins/analytics/config.js +1 -1
- package/plugins/analytics/plugin.js +4 -4
- package/plugins/analytics/plugin.json +1 -1
- package/plugins/analytics/public/inject-body.html +2 -2
- package/plugins/analytics/public/inject-head.html +1 -1
- package/plugins/blog/admin/templates/blog.html +4 -4
- package/plugins/blog/admin/views/blog.js +3 -3
- package/plugins/blog/admin/views/categories.js +2 -2
- package/plugins/blog/admin/views/comments.js +4 -4
- package/plugins/blog/admin/views/post-editor.js +1 -1
- package/plugins/blog/plugin.js +2 -2
- package/plugins/blog/plugin.json +4 -4
- package/plugins/contacts/admin/views/contacts.js +8 -8
- package/plugins/contacts/plugin.js +4 -4
- package/plugins/contacts/plugin.json +1 -1
- package/plugins/invoice/admin/templates/editor.html +6 -6
- package/plugins/invoice/admin/templates/index.html +4 -4
- package/plugins/invoice/admin/views/editor.js +3 -3
- package/plugins/invoice/admin/views/index.js +4 -4
- package/plugins/invoice/admin/views/issuers.js +1 -1
- package/plugins/invoice/admin/views/party-view.js +3 -3
- package/plugins/invoice/admin/views/receivers.js +1 -1
- package/plugins/invoice/config.js +1 -1
- package/plugins/invoice/plugin.js +1 -1
- package/plugins/invoice/plugin.json +4 -4
- package/plugins/notes/admin/views/notes.js +4 -4
- package/plugins/notes/plugin.js +1 -1
- package/plugins/notes/plugin.json +1 -1
- package/plugins/site-search/admin/templates/site-search.html +3 -3
- package/plugins/site-search/admin/views/site-search.js +1 -1
- package/plugins/site-search/config.js +1 -1
- package/plugins/site-search/plugin.js +4 -4
- package/plugins/site-search/plugin.json +1 -1
- package/plugins/surveys/admin/templates/results.html +1 -1
- package/plugins/surveys/admin/templates/survey-editor.html +4 -4
- package/plugins/surveys/admin/views/audience.js +6 -6
- package/plugins/surveys/admin/views/results.js +3 -3
- package/plugins/surveys/admin/views/survey-editor.js +1 -1
- package/plugins/surveys/admin/views/surveys.js +2 -2
- package/plugins/surveys/plugin.js +1 -1
- package/plugins/surveys/plugin.json +4 -4
- package/plugins/theme-switcher/admin/views/theme-switcher.js +1 -1
- package/plugins/theme-switcher/plugin.json +3 -3
- package/plugins/theme-switcher/public/inject-body.html +28 -28
- package/plugins/theme-switcher/public/inject-head.html +1 -1
- package/plugins/todo/plugin.js +1 -1
- package/public/css/forms.css +1 -1
- package/public/js/collection-browser.js +1 -1
- package/public/js/collection-context.js +1 -1
- package/public/js/form-logic-engine.js +1 -1
- package/public/js/forms.js +2 -1
- package/public/js/site.js +1 -1
- package/scripts/build.js +14 -14
- package/scripts/create-plugin.js +2 -2
- package/scripts/fresh.js +2 -2
- package/scripts/gen-instance-secret.js +1 -1
- package/scripts/pro.js +12 -12
- package/scripts/reset.js +2 -2
- package/scripts/setup.js +15 -15
- package/scripts/users.js +5 -5
- package/scripts/verify-assets.mjs +6 -6
- package/server/config.js +1 -1
- package/server/middleware/auth.js +253 -253
- package/server/middleware/managerAuth.js +3 -3
- package/server/routes/api/actions.js +4 -4
- package/server/routes/api/api-endpoints.js +7 -7
- package/server/routes/api/api-tokens.js +5 -5
- package/server/routes/api/auth.js +309 -309
- package/server/routes/api/blocks.js +3 -3
- package/server/routes/api/collections.js +18 -18
- package/server/routes/api/dashboard.js +5 -5
- package/server/routes/api/effects.js +2 -2
- package/server/routes/api/endpoints-public.js +5 -5
- package/server/routes/api/forms.js +47 -47
- package/server/routes/api/menu-locations.js +3 -3
- package/server/routes/api/menus.js +7 -7
- package/server/routes/api/navigation.js +42 -42
- package/server/routes/api/notifications.js +2 -2
- package/server/routes/api/pages.js +14 -2
- package/server/routes/api/plugin-marketplace.js +3 -3
- package/server/routes/api/plugins.js +6 -6
- package/server/routes/api/projects.js +9 -9
- package/server/routes/api/scaffold.js +7 -7
- package/server/routes/api/settings.js +6 -6
- package/server/routes/api/sidebar.js +1 -1
- package/server/routes/api/users.js +5 -5
- package/server/routes/api/versions.js +1 -1
- package/server/routes/api/views.js +1 -1
- package/server/routes/docs-public.js +3 -3
- package/server/routes/public.js +14 -14
- package/server/server.js +34 -16
- package/server/services/actions.js +19 -19
- package/server/services/adapters/FileAdapter.js +2 -2
- package/server/services/adapters/MongoAdapter.js +5 -5
- package/server/services/apiEndpoints.js +15 -15
- package/server/services/apiTokens.js +7 -7
- package/server/services/blocks.js +13 -13
- package/server/services/cache/drivers/MemoryDriver.js +3 -3
- package/server/services/cache/drivers/NoneDriver.js +1 -1
- package/server/services/cache/index.js +5 -5
- package/server/services/collections.js +14 -14
- package/server/services/components.js +9 -9
- package/server/services/connectionManager.js +4 -4
- package/server/services/content.js +25 -2
- package/server/services/docs.js +14 -14
- package/server/services/email.js +167 -167
- package/server/services/filterEngine.js +6 -6
- package/server/services/forms.js +18 -10
- package/server/services/health.js +1 -1
- package/server/services/hooks.js +7 -7
- package/server/services/images.js +4 -4
- package/server/services/managerClient.js +1 -1
- package/server/services/markdown.js +79 -79
- package/server/services/menuRender.js +5 -5
- package/server/services/menus-migration.js +3 -3
- package/server/services/menus.js +31 -31
- package/server/services/permissionRegistry.js +1 -1
- package/server/services/pluginFiles.js +5 -5
- package/server/services/pluginInstaller.js +17 -17
- package/server/services/pluginScaffold.js +1 -1
- package/server/services/plugins.js +17 -17
- package/server/services/presetCollections.js +2 -2
- package/server/services/projects.js +35 -26
- package/server/services/recipes/contact-list.json +1 -1
- package/server/services/recipes/onboarding.json +8 -8
- package/server/services/references.js +8 -8
- package/server/services/renderer.js +9 -9
- package/server/services/roles.js +9 -9
- package/server/services/rowAccess.js +10 -10
- package/server/services/scaffolder.js +30 -30
- package/server/services/sidebar-migration.js +10 -10
- package/server/services/sitemap.js +1 -1
- package/server/services/userProfiles.js +199 -199
- package/server/services/userRoles.js +7 -7
- package/server/services/users.js +302 -302
- package/server/services/versions.js +3 -3
- package/server/services/viewPipeline.js +6 -6
- package/server/services/views.js +11 -11
- package/server/templates/page.html +5 -5
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<div class="docs-body">
|
|
8
8
|
|
|
9
9
|
<p class="lead">
|
|
10
|
-
Step-by-step, end-to-end guides for building real things with Domma CMS. Pick a track below
|
|
10
|
+
Step-by-step, end-to-end guides for building real things with Domma CMS. Pick a track below -
|
|
11
11
|
each opens as its own page, and you can jump straight to any tutorial from the
|
|
12
12
|
<strong>Documentation → Tutorials</strong> menu in the sidebar.
|
|
13
13
|
</p>
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
<h3 style="margin-top:0;"><span data-icon="database"></span> Building a CRUD App</h3>
|
|
20
20
|
<p class="text-muted" style="margin-bottom:0;">
|
|
21
21
|
The headline tutorial. Compose Collections, Forms, Actions and Pages into a complete
|
|
22
|
-
data-driven app
|
|
22
|
+
data-driven app - with workflows, row-level access and a live scaffolder to try at the end.
|
|
23
23
|
</p>
|
|
24
24
|
</div>
|
|
25
25
|
</a>
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
<h3 style="margin-top:0;"><span data-icon="layout"></span> Form Follow-Up</h3>
|
|
40
40
|
<p class="text-muted" style="margin-bottom:0;">
|
|
41
41
|
What happens after a form is submitted: email notifications, webhooks, CMS Actions, and
|
|
42
|
-
success message vs. redirect
|
|
42
|
+
success message vs. redirect - plus the fixed execution order of the pipeline.
|
|
43
43
|
</p>
|
|
44
44
|
</div>
|
|
45
45
|
</a>
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
<div>
|
|
40
40
|
<label class="form-label">Source Collection <span style="color:var(--dm-danger,#f87171);">*</span></label>
|
|
41
41
|
<select id="view-source" class="form-input">
|
|
42
|
-
<option value=""
|
|
42
|
+
<option value="">- select collection -</option>
|
|
43
43
|
</select>
|
|
44
44
|
<small class="text-muted">Changing the collection will refresh field lists in Pipeline and
|
|
45
45
|
Display tabs.</small>
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
<div>
|
|
48
48
|
<label class="form-label">Project</label>
|
|
49
49
|
<select id="view-project" class="form-input">
|
|
50
|
-
<option value=""
|
|
50
|
+
<option value="">- none -</option>
|
|
51
51
|
</select>
|
|
52
52
|
<small class="text-muted">Tag this view to a project for grouping.</small>
|
|
53
53
|
</div>
|
|
@@ -74,13 +74,13 @@
|
|
|
74
74
|
<h2>Aggregation Stages</h2>
|
|
75
75
|
<div style="display:flex;gap:.5rem;">
|
|
76
76
|
<select id="add-stage-type" class="form-input form-input--sm">
|
|
77
|
-
<option value="$match">$match
|
|
78
|
-
<option value="$sort">$sort
|
|
79
|
-
<option value="$lookup">$lookup
|
|
80
|
-
<option value="$project">$project
|
|
81
|
-
<option value="$unwind">$unwind
|
|
82
|
-
<option value="$addFields">$addFields
|
|
83
|
-
<option value="$group">$group
|
|
77
|
+
<option value="$match">$match - filter rows</option>
|
|
78
|
+
<option value="$sort">$sort - order rows</option>
|
|
79
|
+
<option value="$lookup">$lookup - join collection</option>
|
|
80
|
+
<option value="$project">$project - reshape fields</option>
|
|
81
|
+
<option value="$unwind">$unwind - flatten array</option>
|
|
82
|
+
<option value="$addFields">$addFields - compute fields</option>
|
|
83
|
+
<option value="$group">$group - aggregate</option>
|
|
84
84
|
</select>
|
|
85
85
|
<button id="add-stage-btn" class="btn btn-ghost btn-sm">
|
|
86
86
|
<span data-icon="plus"></span> Add Stage
|
|
@@ -98,9 +98,9 @@
|
|
|
98
98
|
<div class="card">
|
|
99
99
|
<div class="card-body" style="font-size:.8rem;color:var(--dm-text-muted,#888);">
|
|
100
100
|
<strong>Guided stages</strong> ($match, $sort) show a visual condition builder.
|
|
101
|
-
Other stages use a raw JSON textarea
|
|
102
|
-
<code>{ from, localField, foreignField, as }</code>
|
|
103
|
-
Field dropdowns are populated from the source collection
|
|
101
|
+
Other stages use a raw JSON textarea - enter only the <em>inner</em> config (e.g. for $lookup, enter
|
|
102
|
+
<code>{ from, localField, foreignField, as }</code> - do not wrap in <code>{ "$lookup": ... }</code>).
|
|
103
|
+
Field dropdowns are populated from the source collection - select a collection on the Source tab
|
|
104
104
|
first.
|
|
105
105
|
</div>
|
|
106
106
|
</div>
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
<div id="view-block-section" style="display:none;">
|
|
122
122
|
<label class="form-label">Block Template</label>
|
|
123
123
|
<select id="view-block-name" class="form-input">
|
|
124
|
-
<option value=""
|
|
124
|
+
<option value="">- select block -</option>
|
|
125
125
|
</select>
|
|
126
126
|
<small class="text-muted">
|
|
127
127
|
Choose a reusable HTML block template.
|
|
@@ -183,8 +183,8 @@
|
|
|
183
183
|
<div>
|
|
184
184
|
<label class="form-label">Mode</label>
|
|
185
185
|
<select id="view-rowlevel-mode" class="form-input">
|
|
186
|
-
<option value="owner">Owner
|
|
187
|
-
<option value="field">Field Match
|
|
186
|
+
<option value="owner">Owner - entry was created by the current user</option>
|
|
187
|
+
<option value="field">Field Match - a field on the entry matches the user</option>
|
|
188
188
|
</select>
|
|
189
189
|
</div>
|
|
190
190
|
<div id="view-rowlevel-field-group" style="display:none;">
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{api as _}from"../api.js";let N=null,L={};async function J(e){const t=e.find("#action-project").get(0);if(t)try{(await _.projects.list()).forEach(l=>{const n=document.createElement("option");n.value=l.slug,n.textContent=l.name||l.slug,t.appendChild(n)})}catch{}}const z={deleteEntry:[],moveToCollection:[{name:"targetCollection",label:"Target Collection Slug",placeholder:"e.g. archived-applications"}],webhook:[{name:"url",label:"URL",placeholder:"https://hooks.example.com/notify"},{name:"method",label:"Method",placeholder:"POST"},{name:"body",label:"Body (JSON)",placeholder:'{"email": "{{entry.data.email}}"}',multiline:!0}],email:[{name:"to",label:"To",placeholder:"{{entry.data.email}}"},{name:"subject",label:"Subject",placeholder:"Your application update"},{name:"template",label:"Body",placeholder:"Your application has been approved.",multiline:!0}]};export const actionEditorView={templateUrl:"/admin/js/templates/action-editor.html",async onMount(e){N=null,L={};const t=window.location.hash.match(/\/actions\/edit\/([^/?#]+)/);t&&(N=t[1]),E.tabs(e.find("#action-editor-tabs").get(0)),await P(e),await U(e),await J(e),N&&(e.find("#action-editor-title").text("Edit Action"),await B(e,N)),e.find("#add-step-btn").off("click").on("click",()=>{const a=e.find("#add-step-type").val()||"updateField";M(e,{type:a,config:{}})}),e.find("#save-action-btn").off("click").on("click",async()=>{await Q(e)}),K(e),Domma.icons.scan()}};async function P(e){const t=e.find("#action-collection").get(0);if(t)try{(await _.collections.list()).forEach(a=>{const l=document.createElement("option");l.value=a.slug,l.textContent=`${a.title} (${a.slug})`,t.appendChild(l)})}catch{}}async function U(e){const t=e.find("#action-roles-checkboxes").get(0);t&&["admin","manager","editor","subscriber"].forEach(a=>{const l=document.createElement("label");l.style.cssText="display:flex;align-items:center;gap:.5rem;cursor:pointer;";const n=document.createElement("input");n.type="checkbox",n.value=a,n.dataset.role=a,n.className="action-role-cb",n.checked=a==="admin",l.appendChild(n),l.appendChild(document.createTextNode(a)),t.appendChild(l)})}async function B(e,t){try{const a=await _.actions.get(t);if(!a){E.toast("Action not found.",{type:"error"}),R.navigate("/actions");return}Y(e,a)}catch(a){E.toast(a.message||"Failed to load action.",{type:"error"}),R.navigate("/actions")}}function K(e){const t=e.find("#action-rowlevel-enabled").get(0),a=e.find("#action-rowlevel-config").get(0),l=e.find("#action-rowlevel-mode").get(0),n=e.find("#action-rowlevel-field-group").get(0);t&&(t.addEventListener("change",()=>{a&&(a.style.display=t.checked?"flex":"none")}),l&&l.addEventListener("change",()=>{n&&(n.style.display=l.value==="field"?"":"none")}))}function Y(e,t){e.find("#action-title").val(t.title||""),e.find("#action-slug").val(t.slug||""),e.find("#action-description").val(t.description||""),e.find("#action-collection").val(t.collection||""),L=t.meta||{},e.find("#action-project").val(t.meta?.project||""),e.find("#action-bundled").prop("checked",!!t.bundled),e.find("#action-trigger-type").val(t.trigger?.type||"manual"),e.find("#action-trigger-label").val(t.trigger?.label||"Run"),e.find("#action-trigger-icon").val(t.trigger?.icon||"zap"),e.find("#action-trigger-confirm").val(t.trigger?.confirmMessage||"");const a=t.access?.roles||["admin"];e.find(".action-role-cb").each(function(){this.checked=a.includes(this.value)});const l=t.access?.rowLevel;l&&(e.find("#action-rowlevel-enabled").prop("checked",!0),e.find("#action-rowlevel-config").css("display","flex"),e.find("#action-rowlevel-mode").val(l.mode||"owner"),e.find("#action-rowlevel-userkey").val(l.userKey||"id"),l.mode==="field"&&(e.find("#action-rowlevel-field-group").css("display",""),e.find("#action-rowlevel-field").val(l.field||"")));const n=e.find("#action-steps-list").get(0);if(n){const s=n.querySelector(".steps-empty-placeholder");for(s&&s.remove();n.firstChild;)n.removeChild(n.firstChild);const i=document.createElement("p");i.className="text-muted steps-empty-placeholder",i.textContent="No steps yet. Add a step to define what this action does.",i.style.cssText="text-align:center;padding:2rem 0;",n.appendChild(i)}(t.steps||[]).forEach(s=>M(e,s))}function M(e,t){const a=e.find("#action-steps-list").get(0);if(!a)return;const l=a.querySelector(".steps-empty-placeholder");l&&l.remove();const n=z[t.type]||[],s=document.createElement("div");s.className="card mb-2 step-card",s.dataset.stepType=t.type;const i=document.createElement("div");i.className="card-header",i.style.cssText="display:flex;align-items:center;gap:.5rem;";const p=document.createElement("code");p.textContent=t.type,p.style.cssText="flex:1;font-size:.85rem;";const r=document.createElement("button");r.type="button",r.className="btn btn-sm btn-ghost";const f=document.createElement("span");f.setAttribute("data-icon","trash"),r.appendChild(f),r.addEventListener("click",()=>{if(s.remove(),!a.querySelector(".step-card")){const o=document.createElement("p");o.className="text-muted steps-empty-placeholder",o.textContent="No steps yet. Add a step to define what this action does.",o.style.cssText="text-align:center;padding:2rem 0;",a.appendChild(o)}}),i.appendChild(p),i.appendChild(r);const h=document.createElement("div");if(h.className="card-body",n.length===0){const o=document.createElement("p");o.className="text-muted",o.textContent=t.type==="deleteEntry"?"This step deletes the entry. No configuration required.":"No additional configuration required.",o.style.margin="0",h.appendChild(o)}t.type==="updateField"?G(h,t,e):n.forEach(o=>{const g=document.createElement("div");g.style.cssText="margin-bottom:.75rem;";const b=document.createElement("label");b.className="form-label",b.textContent=o.label,g.appendChild(b);let m;o.multiline?(m=document.createElement("textarea"),m.rows=3,m.style.cssText="font-family:monospace;font-size:.8rem;resize:vertical;",m.value=typeof t.config?.[o.name]=="object"?JSON.stringify(t.config[o.name],null,2):t.config?.[o.name]??""):(m=document.createElement("input"),m.type="text",m.value=t.config?.[o.name]??""),m.className=`form-input step-field-${o.name}`,m.placeholder=o.placeholder||"",m.dataset.field=o.name,g.appendChild(m),h.appendChild(g)}),s.appendChild(i),s.appendChild(h),a.appendChild(s),Domma.icons.scan(s)}async function G(e,t,a){const l=a.find("#action-collection").val(),n=document.createElement("div");n.style.cssText="margin-bottom:.75rem;";const s=document.createElement("label");s.className="form-label",s.textContent="Field",n.appendChild(s);const i=document.createElement("select");i.className="form-input step-field-field",i.dataset.field="field";const p=document.createElement("input");p.type="text",p.className="form-input mt-2 step-field-field-custom",p.placeholder="Field name, e.g. status",p.style.display="none";const r=document.createElement("div");r.style.cssText="margin-bottom:.75rem;";const f=document.createElement("label");f.className="form-label",f.textContent="New Value",r.appendChild(f);const h=document.createElement("div");r.appendChild(h);const o=document.createElement("button");o.type="button",o.className="btn btn-ghost btn-sm mt-2",o.style.cssText="font-size:.75rem;padding:.2rem .5rem;",o.textContent="Template Variables";const g=document.createElement("div");g.style.cssText="display:none;background:var(--dm-surface-raised,#1e1e2e);border-radius:.4rem;padding:.75rem;margin-top:.5rem;font-size:.8rem;line-height:1.7;";const b=document.createElement("strong");b.textContent="Available template variables:",g.appendChild(b);const m=document.createElement("table");m.style.cssText="width:100%;border-collapse:collapse;margin-top:.4rem;",[["{{now}}","Current ISO timestamp"],["{{user.id}}","Executing user's ID"],["{{user.name}}","Executing user's name"],["{{user.email}}","Executing user's email"],["{{entry.id}}","Entry's unique ID"],["{{entry.data.fieldName}}","Any field value from the entry"],["{{env.CMS_PUBLIC_*}}","Public environment variables"]].forEach(([d,u])=>{const x=document.createElement("tr"),w=document.createElement("td");w.style.cssText="padding:.2rem .5rem .2rem 0;opacity:.7;white-space:nowrap;";const T=document.createElement("code");T.textContent=d,w.appendChild(T);const c=document.createElement("td");c.textContent=u,x.appendChild(w),x.appendChild(c),m.appendChild(x)}),g.appendChild(m),o.addEventListener("click",()=>{const d=g.style.display!=="none";g.style.display=d?"none":"",o.textContent=d?"Template Variables":"Hide Variables"}),r.appendChild(o),r.appendChild(g),e.appendChild(n),e.appendChild(r);let S=[];if(l)try{S=(await _.collections.get(l)).fields||[]}catch{}const F=document.createElement("option");F.value="",F.textContent=S.length?"\u2014 select a field \u2014":"\u2014 no fields available \u2014",i.appendChild(F),S.forEach(d=>{const u=document.createElement("option");u.value=d.name,u.textContent=`${d.label} (${d.name})`,u.dataset.fieldType=d.type,u.dataset.fieldOptions=d.type==="select"?JSON.stringify(d.options||[]):"",i.appendChild(u)});const j=document.createElement("option");j.value="__custom__",j.textContent="\u2014 enter manually \u2014",i.appendChild(j);const C=t.config?.field||"",V=C&&[...i.options].find(d=>d.value===C);V?i.value=C:C&&(i.value="__custom__",p.value=C,p.style.display=""),n.appendChild(i),n.appendChild(p);function I(d,u){h.textContent="";const x=d?[...i.options].find(c=>c.value===d):null,w=x?.dataset.fieldType==="select",T=w?JSON.parse(x.dataset.fieldOptions||"[]"):[];if(w&&T.length){const c=document.createElement("select");c.className="form-input step-field-value",c.dataset.field="value";const O=document.createElement("option");O.value="",O.textContent="\u2014 select a value \u2014",c.appendChild(O),T.forEach(y=>{const k=typeof y=="string"?y:y.value??"",D=typeof y=="string"?y:y.label||y.value||k;if(!k||k==="undefined")return;const q=document.createElement("option");q.value=k,q.textContent=D,c.appendChild(q)});const A=document.createElement("option");A.value="__custom__",A.textContent="\u2014 enter manually \u2014",c.appendChild(A);const v=document.createElement("input");v.type="text",v.className="form-input mt-2",v.placeholder="e.g. approved or {{now}}",v.style.display="none",u&&[...c.options].find(y=>y.value===u&&y.value!=="__custom__")?c.value=u:u&&(c.value="__custom__",v.value=u,v.style.display=""),c.addEventListener("change",()=>{const y=c.value==="__custom__";v.style.display=y?"":"none",y||(v.value="")}),h.appendChild(c),h.appendChild(v)}else{const c=document.createElement("input");c.type="text",c.className="form-input step-field-value",c.dataset.field="value",c.placeholder="e.g. approved or {{now}}",c.value=u||"",h.appendChild(c)}}I(V?C:null,t.config?.value||""),i.addEventListener("change",()=>{const d=i.value==="__custom__";p.style.display=d?"":"none",d||(p.value=""),I(d?null:i.value,"")})}function H(e){const t=[];return e.find(".step-card").each(function(){const a=this.dataset.stepType,l={};if(a==="updateField"){let n=this.querySelector(".step-field-field")?.value?.trim()||"";n==="__custom__"&&(n=this.querySelector(".step-field-field-custom")?.value?.trim()||""),l.field=n;const s=this.querySelector(".step-field-value");let i=s?.value?.trim()||"";i==="__custom__"&&(i=s?.nextElementSibling?.value?.trim()||""),l.value=i}else(z[a]||[]).forEach(n=>{const s=this.querySelector(`.step-field-${n.name}`);if(!s)return;const i=s.value.trim();if(n.multiline&&i)try{l[n.name]=JSON.parse(i)}catch{l[n.name]=i}else l[n.name]=i});t.push({type:a,config:l})}),t}async function Q(e){const t=e.find("#action-title").val().trim();if(!t){E.toast("Title is required.",{type:"warning"});return}const a=e.find("#action-collection").val();if(!a){E.toast("Target collection is required (General tab).",{type:"warning"});return}const l=[];e.find(".action-role-cb:checked").each(function(){l.push(this.value)});const n=e.find("#action-rowlevel-enabled").is(":checked");let s=null;if(n){const f=e.find("#action-rowlevel-mode").val()||"owner",h=e.find("#action-rowlevel-userkey").val()||"id";if(s={mode:f,userKey:h},f==="field"){const o=e.find("#action-rowlevel-field").val().trim();if(!o){E.toast("Field name is required for Field Match mode.",{type:"warning"});return}s.field=o}}const i=e.find("#action-project").val()||"",p={title:t,slug:e.find("#action-slug").val().trim()||void 0,description:e.find("#action-description").val().trim(),collection:a,...e.find("#action-bundled").is(":checked")?{bundled:!0}:{},trigger:{type:e.find("#action-trigger-type").val()||"manual",label:e.find("#action-trigger-label").val().trim()||"Run",icon:e.find("#action-trigger-icon").val().trim()||"zap",confirmMessage:e.find("#action-trigger-confirm").val().trim()||null},steps:H(e),access:{roles:l,rowLevel:s},meta:{...L||{},project:i||null}},r=e.find("#save-action-btn").get(0);r&&(r.disabled=!0);try{if(N)await _.actions.update(N,p),E.toast("Action updated.",{type:"success"});else{const f=await _.actions.create(p);E.toast("Action created.",{type:"success"}),R.navigate(`/actions/edit/${f.slug}`)}}catch(f){E.toast(f.message||"Failed to save action.",{type:"error"})}finally{r&&(r.disabled=!1)}}
|
|
1
|
+
import{api as _}from"../api.js";let N=null,L={};async function J(e){const t=e.find("#action-project").get(0);if(t)try{(await _.projects.list()).forEach(l=>{const n=document.createElement("option");n.value=l.slug,n.textContent=l.name||l.slug,t.appendChild(n)})}catch{}}const z={deleteEntry:[],moveToCollection:[{name:"targetCollection",label:"Target Collection Slug",placeholder:"e.g. archived-applications"}],webhook:[{name:"url",label:"URL",placeholder:"https://hooks.example.com/notify"},{name:"method",label:"Method",placeholder:"POST"},{name:"body",label:"Body (JSON)",placeholder:'{"email": "{{entry.data.email}}"}',multiline:!0}],email:[{name:"to",label:"To",placeholder:"{{entry.data.email}}"},{name:"subject",label:"Subject",placeholder:"Your application update"},{name:"template",label:"Body",placeholder:"Your application has been approved.",multiline:!0}]};export const actionEditorView={templateUrl:"/admin/js/templates/action-editor.html",async onMount(e){N=null,L={};const t=window.location.hash.match(/\/actions\/edit\/([^/?#]+)/);t&&(N=t[1]),E.tabs(e.find("#action-editor-tabs").get(0)),await P(e),await U(e),await J(e),N&&(e.find("#action-editor-title").text("Edit Action"),await B(e,N)),e.find("#add-step-btn").off("click").on("click",()=>{const a=e.find("#add-step-type").val()||"updateField";M(e,{type:a,config:{}})}),e.find("#save-action-btn").off("click").on("click",async()=>{await Q(e)}),K(e),Domma.icons.scan()}};async function P(e){const t=e.find("#action-collection").get(0);if(t)try{(await _.collections.list()).forEach(a=>{const l=document.createElement("option");l.value=a.slug,l.textContent=`${a.title} (${a.slug})`,t.appendChild(l)})}catch{}}async function U(e){const t=e.find("#action-roles-checkboxes").get(0);t&&["admin","manager","editor","subscriber"].forEach(a=>{const l=document.createElement("label");l.style.cssText="display:flex;align-items:center;gap:.5rem;cursor:pointer;";const n=document.createElement("input");n.type="checkbox",n.value=a,n.dataset.role=a,n.className="action-role-cb",n.checked=a==="admin",l.appendChild(n),l.appendChild(document.createTextNode(a)),t.appendChild(l)})}async function B(e,t){try{const a=await _.actions.get(t);if(!a){E.toast("Action not found.",{type:"error"}),R.navigate("/actions");return}Y(e,a)}catch(a){E.toast(a.message||"Failed to load action.",{type:"error"}),R.navigate("/actions")}}function K(e){const t=e.find("#action-rowlevel-enabled").get(0),a=e.find("#action-rowlevel-config").get(0),l=e.find("#action-rowlevel-mode").get(0),n=e.find("#action-rowlevel-field-group").get(0);t&&(t.addEventListener("change",()=>{a&&(a.style.display=t.checked?"flex":"none")}),l&&l.addEventListener("change",()=>{n&&(n.style.display=l.value==="field"?"":"none")}))}function Y(e,t){e.find("#action-title").val(t.title||""),e.find("#action-slug").val(t.slug||""),e.find("#action-description").val(t.description||""),e.find("#action-collection").val(t.collection||""),L=t.meta||{},e.find("#action-project").val(t.meta?.project||""),e.find("#action-bundled").prop("checked",!!t.bundled),e.find("#action-trigger-type").val(t.trigger?.type||"manual"),e.find("#action-trigger-label").val(t.trigger?.label||"Run"),e.find("#action-trigger-icon").val(t.trigger?.icon||"zap"),e.find("#action-trigger-confirm").val(t.trigger?.confirmMessage||"");const a=t.access?.roles||["admin"];e.find(".action-role-cb").each(function(){this.checked=a.includes(this.value)});const l=t.access?.rowLevel;l&&(e.find("#action-rowlevel-enabled").prop("checked",!0),e.find("#action-rowlevel-config").css("display","flex"),e.find("#action-rowlevel-mode").val(l.mode||"owner"),e.find("#action-rowlevel-userkey").val(l.userKey||"id"),l.mode==="field"&&(e.find("#action-rowlevel-field-group").css("display",""),e.find("#action-rowlevel-field").val(l.field||"")));const n=e.find("#action-steps-list").get(0);if(n){const s=n.querySelector(".steps-empty-placeholder");for(s&&s.remove();n.firstChild;)n.removeChild(n.firstChild);const i=document.createElement("p");i.className="text-muted steps-empty-placeholder",i.textContent="No steps yet. Add a step to define what this action does.",i.style.cssText="text-align:center;padding:2rem 0;",n.appendChild(i)}(t.steps||[]).forEach(s=>M(e,s))}function M(e,t){const a=e.find("#action-steps-list").get(0);if(!a)return;const l=a.querySelector(".steps-empty-placeholder");l&&l.remove();const n=z[t.type]||[],s=document.createElement("div");s.className="card mb-2 step-card",s.dataset.stepType=t.type;const i=document.createElement("div");i.className="card-header",i.style.cssText="display:flex;align-items:center;gap:.5rem;";const p=document.createElement("code");p.textContent=t.type,p.style.cssText="flex:1;font-size:.85rem;";const r=document.createElement("button");r.type="button",r.className="btn btn-sm btn-ghost";const f=document.createElement("span");f.setAttribute("data-icon","trash"),r.appendChild(f),r.addEventListener("click",()=>{if(s.remove(),!a.querySelector(".step-card")){const o=document.createElement("p");o.className="text-muted steps-empty-placeholder",o.textContent="No steps yet. Add a step to define what this action does.",o.style.cssText="text-align:center;padding:2rem 0;",a.appendChild(o)}}),i.appendChild(p),i.appendChild(r);const h=document.createElement("div");if(h.className="card-body",n.length===0){const o=document.createElement("p");o.className="text-muted",o.textContent=t.type==="deleteEntry"?"This step deletes the entry. No configuration required.":"No additional configuration required.",o.style.margin="0",h.appendChild(o)}t.type==="updateField"?G(h,t,e):n.forEach(o=>{const g=document.createElement("div");g.style.cssText="margin-bottom:.75rem;";const b=document.createElement("label");b.className="form-label",b.textContent=o.label,g.appendChild(b);let m;o.multiline?(m=document.createElement("textarea"),m.rows=3,m.style.cssText="font-family:monospace;font-size:.8rem;resize:vertical;",m.value=typeof t.config?.[o.name]=="object"?JSON.stringify(t.config[o.name],null,2):t.config?.[o.name]??""):(m=document.createElement("input"),m.type="text",m.value=t.config?.[o.name]??""),m.className=`form-input step-field-${o.name}`,m.placeholder=o.placeholder||"",m.dataset.field=o.name,g.appendChild(m),h.appendChild(g)}),s.appendChild(i),s.appendChild(h),a.appendChild(s),Domma.icons.scan(s)}async function G(e,t,a){const l=a.find("#action-collection").val(),n=document.createElement("div");n.style.cssText="margin-bottom:.75rem;";const s=document.createElement("label");s.className="form-label",s.textContent="Field",n.appendChild(s);const i=document.createElement("select");i.className="form-input step-field-field",i.dataset.field="field";const p=document.createElement("input");p.type="text",p.className="form-input mt-2 step-field-field-custom",p.placeholder="Field name, e.g. status",p.style.display="none";const r=document.createElement("div");r.style.cssText="margin-bottom:.75rem;";const f=document.createElement("label");f.className="form-label",f.textContent="New Value",r.appendChild(f);const h=document.createElement("div");r.appendChild(h);const o=document.createElement("button");o.type="button",o.className="btn btn-ghost btn-sm mt-2",o.style.cssText="font-size:.75rem;padding:.2rem .5rem;",o.textContent="Template Variables";const g=document.createElement("div");g.style.cssText="display:none;background:var(--dm-surface-raised,#1e1e2e);border-radius:.4rem;padding:.75rem;margin-top:.5rem;font-size:.8rem;line-height:1.7;";const b=document.createElement("strong");b.textContent="Available template variables:",g.appendChild(b);const m=document.createElement("table");m.style.cssText="width:100%;border-collapse:collapse;margin-top:.4rem;",[["{{now}}","Current ISO timestamp"],["{{user.id}}","Executing user's ID"],["{{user.name}}","Executing user's name"],["{{user.email}}","Executing user's email"],["{{entry.id}}","Entry's unique ID"],["{{entry.data.fieldName}}","Any field value from the entry"],["{{env.CMS_PUBLIC_*}}","Public environment variables"]].forEach(([d,u])=>{const x=document.createElement("tr"),w=document.createElement("td");w.style.cssText="padding:.2rem .5rem .2rem 0;opacity:.7;white-space:nowrap;";const T=document.createElement("code");T.textContent=d,w.appendChild(T);const c=document.createElement("td");c.textContent=u,x.appendChild(w),x.appendChild(c),m.appendChild(x)}),g.appendChild(m),o.addEventListener("click",()=>{const d=g.style.display!=="none";g.style.display=d?"none":"",o.textContent=d?"Template Variables":"Hide Variables"}),r.appendChild(o),r.appendChild(g),e.appendChild(n),e.appendChild(r);let S=[];if(l)try{S=(await _.collections.get(l)).fields||[]}catch{}const F=document.createElement("option");F.value="",F.textContent=S.length?"- select a field -":"- no fields available -",i.appendChild(F),S.forEach(d=>{const u=document.createElement("option");u.value=d.name,u.textContent=`${d.label} (${d.name})`,u.dataset.fieldType=d.type,u.dataset.fieldOptions=d.type==="select"?JSON.stringify(d.options||[]):"",i.appendChild(u)});const j=document.createElement("option");j.value="__custom__",j.textContent="- enter manually -",i.appendChild(j);const C=t.config?.field||"",V=C&&[...i.options].find(d=>d.value===C);V?i.value=C:C&&(i.value="__custom__",p.value=C,p.style.display=""),n.appendChild(i),n.appendChild(p);function I(d,u){h.textContent="";const x=d?[...i.options].find(c=>c.value===d):null,w=x?.dataset.fieldType==="select",T=w?JSON.parse(x.dataset.fieldOptions||"[]"):[];if(w&&T.length){const c=document.createElement("select");c.className="form-input step-field-value",c.dataset.field="value";const O=document.createElement("option");O.value="",O.textContent="- select a value -",c.appendChild(O),T.forEach(y=>{const k=typeof y=="string"?y:y.value??"",D=typeof y=="string"?y:y.label||y.value||k;if(!k||k==="undefined")return;const q=document.createElement("option");q.value=k,q.textContent=D,c.appendChild(q)});const A=document.createElement("option");A.value="__custom__",A.textContent="- enter manually -",c.appendChild(A);const v=document.createElement("input");v.type="text",v.className="form-input mt-2",v.placeholder="e.g. approved or {{now}}",v.style.display="none",u&&[...c.options].find(y=>y.value===u&&y.value!=="__custom__")?c.value=u:u&&(c.value="__custom__",v.value=u,v.style.display=""),c.addEventListener("change",()=>{const y=c.value==="__custom__";v.style.display=y?"":"none",y||(v.value="")}),h.appendChild(c),h.appendChild(v)}else{const c=document.createElement("input");c.type="text",c.className="form-input step-field-value",c.dataset.field="value",c.placeholder="e.g. approved or {{now}}",c.value=u||"",h.appendChild(c)}}I(V?C:null,t.config?.value||""),i.addEventListener("change",()=>{const d=i.value==="__custom__";p.style.display=d?"":"none",d||(p.value=""),I(d?null:i.value,"")})}function H(e){const t=[];return e.find(".step-card").each(function(){const a=this.dataset.stepType,l={};if(a==="updateField"){let n=this.querySelector(".step-field-field")?.value?.trim()||"";n==="__custom__"&&(n=this.querySelector(".step-field-field-custom")?.value?.trim()||""),l.field=n;const s=this.querySelector(".step-field-value");let i=s?.value?.trim()||"";i==="__custom__"&&(i=s?.nextElementSibling?.value?.trim()||""),l.value=i}else(z[a]||[]).forEach(n=>{const s=this.querySelector(`.step-field-${n.name}`);if(!s)return;const i=s.value.trim();if(n.multiline&&i)try{l[n.name]=JSON.parse(i)}catch{l[n.name]=i}else l[n.name]=i});t.push({type:a,config:l})}),t}async function Q(e){const t=e.find("#action-title").val().trim();if(!t){E.toast("Title is required.",{type:"warning"});return}const a=e.find("#action-collection").val();if(!a){E.toast("Target collection is required (General tab).",{type:"warning"});return}const l=[];e.find(".action-role-cb:checked").each(function(){l.push(this.value)});const n=e.find("#action-rowlevel-enabled").is(":checked");let s=null;if(n){const f=e.find("#action-rowlevel-mode").val()||"owner",h=e.find("#action-rowlevel-userkey").val()||"id";if(s={mode:f,userKey:h},f==="field"){const o=e.find("#action-rowlevel-field").val().trim();if(!o){E.toast("Field name is required for Field Match mode.",{type:"warning"});return}s.field=o}}const i=e.find("#action-project").val()||"",p={title:t,slug:e.find("#action-slug").val().trim()||void 0,description:e.find("#action-description").val().trim(),collection:a,...e.find("#action-bundled").is(":checked")?{bundled:!0}:{},trigger:{type:e.find("#action-trigger-type").val()||"manual",label:e.find("#action-trigger-label").val().trim()||"Run",icon:e.find("#action-trigger-icon").val().trim()||"zap",confirmMessage:e.find("#action-trigger-confirm").val().trim()||null},steps:H(e),access:{roles:l,rowLevel:s},meta:{...L||{},project:i||null}},r=e.find("#save-action-btn").get(0);r&&(r.disabled=!0);try{if(N)await _.actions.update(N,p),E.toast("Action updated.",{type:"success"});else{const f=await _.actions.create(p);E.toast("Action created.",{type:"success"}),R.navigate(`/actions/edit/${f.slug}`)}}catch(f){E.toast(f.message||"Failed to save action.",{type:"error"})}finally{r&&(r.disabled=!1)}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{api as r}from"../api.js";import{filterByProject as p,getProjectFromHash as m}from"../lib/project-context.js";import{keepTableIconsAlive as
|
|
1
|
+
import{api as r}from"../api.js";import{filterByProject as p,getProjectFromHash as m,renderProjectFilterNotice as u}from"../lib/project-context.js";import{keepTableIconsAlive as g}from"../lib/table-icons.js";function i(e){return String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}export const actionsListView={templateUrl:"/admin/js/templates/actions-list.html",async onMount(e){await d(e),g(e.get(0)),e.off("click",".js-delete-action").on("click",".js-delete-action",async function(){const n=this.dataset.slug;if(await E.confirm(`Delete action "${n}"? This cannot be undone.`))try{await r.actions.delete(n),E.toast("Action deleted.",{type:"success"}),await d(e)}catch{E.toast("Failed to delete action.",{type:"error"})}}),e.find("#create-action-btn").off("click").on("click",()=>{R.navigate("/actions/new")}),Domma.icons.scan()}};async function d(e){let n=[];const l=E.loader(e.get(0),{type:"dots"});try{n=await r.actions.list(),e.find("#actions-pro-notice").hide()}catch(t){t.message?.includes("MongoDB")||t.message?.includes("pro mode")||t.message?.includes("connection")?e.find("#actions-pro-notice").show():E.toast("Could not load actions.",{type:"error"})}finally{l.destroy()}const c=m();if(c){const t=n.length;n=p(n,c),u(e,{total:t,shown:n.length,slug:c,allHref:"#/actions",noun:"actions"})}T.create("#actions-table",{pageSize:25,data:n,columns:[{key:"title",title:"Title",render:(t,a)=>{const o=document.createElement("a");return o.href=`#/actions/edit/${i(a.slug)}`,o.textContent=t,o.style.fontWeight="600",o.outerHTML}},{key:"slug",title:"Slug",render:t=>`<code>${i(t)}</code>`},{key:"collection",title:"Collection",render:t=>`<code>${i(t||"-")}</code>`},{key:"trigger",title:"Trigger",render:t=>i(t?.label||t?.type||"manual")},{key:"steps",title:"Steps",render:t=>String(Array.isArray(t)?t.length:0)},{key:"access",title:"Roles",render:t=>(t?.roles||[]).map(a=>`<span class="badge badge-secondary">${i(a)}</span>`).join(" ")},{key:"slug",title:"Actions",render:t=>{const a=document.createElement("div");a.style.cssText="display:flex;gap:.4rem;justify-content:flex-end;";const o=document.createElement("a");o.href=`#/actions/edit/${i(t)}`,o.className="btn btn-sm btn-ghost",o.setAttribute("data-tooltip","Edit"),o.innerHTML='<span data-icon="edit" data-icon-size="14"></span>';const s=document.createElement("button");return s.className="btn btn-sm btn-ghost js-delete-action",s.dataset.slug=t,s.setAttribute("data-tooltip","Delete"),s.innerHTML='<span data-icon="trash" data-icon-size="14"></span>',a.appendChild(o),a.appendChild(s),a.outerHTML}}],emptyMessage:'No actions yet. Click "New Action" to create your first action.'}),Domma.icons.scan(),document.querySelectorAll("#actions-table [data-tooltip]").forEach(t=>{E.tooltip(t,{content:t.getAttribute("data-tooltip"),position:"top"})})}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{api as _}from"../api.js";const pe=[["_eq","equals"],["_ne","not equal"],["_gt",">"],["_gte",">="],["_lt","<"],["_lte","<="],["_in","in list"],["_nin","not in list"],["_contains","contains"],["_starts","starts with"],["_ends","ends with"],["_exists","exists"]],we=["id","createdBy"],Se=/\{\{\s*(params|query)\.([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;function qe(x){const v=new Set;for(const n of Object.values(x?.filter||{}))if(typeof n=="string")for(const N of n.matchAll(Se))N[1]==="query"&&v.add(N[2]);return[...v]}function Ae(x){return String(x??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}let O=null;function ke(x){for(const[v]of pe)if(x.endsWith(v))return{field:x.slice(0,-v.length),op:v};return{field:x,op:"_eq"}}export const apiEndpointEditorView={templateUrl:"/admin/js/templates/api-endpoint-editor.html",async onMount(x){const v=x.get(0),n=e=>v.querySelector("#"+e)||document.getElementById(e),N=location.hash.split("?")[0].match(/\/api-endpoints\/edit\/([^/?#]+)/),q=N?decodeURIComponent(N[1]):null;n("ep-title").textContent=q?"Edit API Endpoint":"New API Endpoint",E.tabs(n("ep-editor-tabs"));const G=E.loader(v,{type:"dots"}),[ue,me,fe,u]=await Promise.all([_.projects.list().catch(()=>[]),_.collections.list().catch(()=>[]),_.get("/collections/roles/entries?limit=100").catch(()=>({entries:[]})),q?_.apiEndpoints.get(q).catch(()=>null):Promise.resolve(null)]);if(q&&!u){G.destroy(),E.toast("Endpoint not found.",{type:"error"}),location.hash="#/api-endpoints";return}const ye=me.filter(e=>!e.systemManaged&&!e.preset),he=(fe.entries||[]).map(e=>e.data?.name).filter(Boolean),k=n("endpoint-project");for(const e of ue){const t=document.createElement("option");t.value=e.slug,t.textContent=`${e.name} (${e.slug})`,k.appendChild(t)}const y=n("ep-collection");function ee(e){return e.meta&&e.meta.project||"core"}function z(e){const t=e!==void 0?e:y.value,r=k.value,o=ye.filter(a=>{const s=ee(a);return s===r||s==="core"});y.textContent="";for(const a of o){const s=document.createElement("option");s.value=a.slug,s.textContent=`${a.title} (${a.slug})${ee(a)==="core"&&r!=="core"?" \u2014 core":""}`,y.appendChild(s)}if(!o.length){const a=document.createElement("option");a.value="",a.textContent="No collections available in this project",y.appendChild(a)}t&&[...y.options].some(a=>a.value===t)&&(y.value=t)}z();const L=n("ep-auth");for(const[e,t]of[["public","Public \u2014 no credentials"],["token","Token \u2014 project API token"]]){const r=document.createElement("option");r.value=e,r.textContent=t,L.appendChild(r)}for(const e of he){const t=document.createElement("option");t.value=e,t.textContent=`Role: ${e}`,L.appendChild(t)}let T=[];async function R(){const e=y.value;if(T=[],e)try{T=((await _.collections.get(e))?.fields||[]).map(r=>r.name).filter(Boolean)}catch{}te();for(const t of K.querySelectorAll(".ep-filter-row"))U(t.querySelector(".ep-f-field"));ne()}function F(){return[...T,...we]}function te(e=[]){const t=n("ep-fields-list");t.textContent="";for(const r of T){const o=document.createElement("label");o.style.cssText="display:flex;align-items:center;gap:.35rem;cursor:pointer;font-size:.9rem;";const a=document.createElement("input");a.type="checkbox",a.className="ep-field-cb",a.value=r,a.checked=e.includes(r),a.addEventListener("change",h),o.appendChild(a),o.appendChild(document.createTextNode(r)),t.appendChild(o)}if(!T.length){const r=document.createElement("p");r.className="text-muted",r.style.cssText="font-size:.85rem;margin:0;",r.textContent="No fields found on this collection.",t.appendChild(r)}}function ne(e){const t=n("ep-sort"),r=e!==void 0?e:t.value;t.textContent="";for(const o of["createdAt",...F()]){const a=document.createElement("option");a.value=o==="createdAt"?"":o,a.textContent=o==="createdAt"?"createdAt (default)":o,t.appendChild(a)}t.value=r||""}function U(e,t){const r=t!==void 0?t:e.value;e.textContent="";for(const a of F()){const s=document.createElement("option");s.value=a,s.textContent=a,e.appendChild(s)}const o=document.createElement("option");if(o.value="__custom__",o.textContent="custom dot-path\u2026",e.appendChild(o),r&&[...e.options].some(a=>a.value===r))e.value=r;else if(r){const a=document.createElement("option");a.value=r,a.textContent=r,e.insertBefore(a,o),e.value=r}}const K=n("ep-filter-rows");function M(e="",t="_eq",r=""){const o=document.createElement("div");o.className="ep-filter-row",o.style.cssText="display:grid;grid-template-columns:2fr 1.2fr 2fr auto;gap:.5rem;margin-bottom:.5rem;align-items:center;";const a=document.createElement("select");a.className="form-input ep-f-field",U(a,e||void 0),a.addEventListener("change",()=>{if(a.value==="__custom__"){const i=prompt("Dot-path field (e.g. address.city):","");i&&i.trim()?U(a,i.trim()):a.value=F()[0]||""}h()});const s=document.createElement("select");s.className="form-input ep-f-op";for(const[i,j]of pe){const w=document.createElement("option");w.value=i,w.textContent=j,s.appendChild(w)}s.value=t,s.addEventListener("change",h);const c=document.createElement("input");c.type="text",c.className="form-input ep-f-value",c.placeholder="e.g. {{params.date}} or {{query.team}} or a literal",c.value=r,c.addEventListener("input",h);const d=document.createElement("button");d.className="btn btn-sm btn-ghost",d.innerHTML='<span data-icon="trash"></span>',d.addEventListener("click",()=>{o.remove(),h()}),o.appendChild(a),o.appendChild(s),o.appendChild(c),o.appendChild(d),K.appendChild(o),Domma.icons.scan(o)}function ve(){const e={};for(const t of K.querySelectorAll(".ep-filter-row")){const r=t.querySelector(".ep-f-field").value,o=t.querySelector(".ep-f-op").value,a=t.querySelector(".ep-f-value").value;!r||r==="__custom__"||(e[o==="_eq"?r:r+o]=a)}return e}function I(){return{name:n("ep-name").value.trim(),project:k.value,path:n("ep-path").value.trim(),collection:y.value,auth:L.value,mode:n("ep-mode").value,filter:ve(),sort:n("ep-sort").value||null,order:n("ep-order").value,limit:parseInt(n("ep-limit").value,10)||0,fields:[...v.querySelectorAll(".ep-field-cb:checked")].map(e=>e.value),enabled:n("ep-enabled").checked}}let B=null,g=null;function D(){return!B||JSON.stringify(I())!==B}function h(){oe(),Z()}function oe(){n("ep-url-preview").textContent=`/api/x/${k.value||"<project>"}${n("ep-path").value||"/\u2026"}`}function ge(e){return(e.match(/:([a-zA-Z_][a-zA-Z0-9_]*)/g)||[]).map(t=>t.slice(1))}function Z(){const e=D();n("tryit-dirty-note").style.display=e?"":"none",n("tryit-send").disabled=e,n("tryit-token-group").style.display=g?.auth==="token"?"":"none";const t=n("tryit-params"),r={};for(const s of t.querySelectorAll("input"))r[s.dataset.param]=s.value;t.textContent="";for(const s of ge(g?.path||"")){const c=document.createElement("div");c.className="form-group";const d=document.createElement("label");d.className="form-label",d.textContent=`:${s}`;const i=document.createElement("input");i.type="text",i.className="form-input",i.dataset.param=s,i.value=r[s]||"",c.appendChild(d),c.appendChild(i),t.appendChild(c)}const o=n("tryit-query-params"),a={};for(const s of o.querySelectorAll("input"))a[s.dataset.queryParam]=s.value;o.textContent="";for(const s of qe(g)){const c=document.createElement("div");c.className="form-group";const d=document.createElement("label");d.className="form-label",d.textContent=`?${s}`;const i=document.createElement("input");i.type="text",i.className="form-input",i.dataset.queryParam=s,i.value=a[s]||"",i.placeholder=`optional \u2014 filters by ${s}`,c.appendChild(d),c.appendChild(i),o.appendChild(c)}}let J="";const W=`apitryit:hist:${q||"new"}`,Ee=12;function ae(){const e=S.get(W);return Array.isArray(e)?e:[]}function re(e){const t=ae().filter(o=>o.url!==e.url),r=[e,...t].slice(0,Ee);S.set(W,r),Y()}function Ce(e){for(const t of n("tryit-params").querySelectorAll("input"))t.value=e.params?.[t.dataset.param]??"";for(const t of n("tryit-query-params").querySelectorAll("input"))t.value=e.queryParams?.[t.dataset.queryParam]??"";n("tryit-query").value=e.extra||"",Q()}function Y(){const e=n("tryit-history-list"),t=n("tryit-history-card");if(!e||!t)return;const r=ae();t.style.display=r.length?"":"none",e.textContent="";for(const o of r){const a=document.createElement("button");a.type="button",a.className="btn btn-ghost btn-sm",a.style.cssText="display:flex;align-items:center;gap:.5rem;width:100%;justify-content:flex-start;text-align:left;font-family:var(--dm-font-mono,monospace);font-size:.78rem;padding:.35rem .5rem;";const s=document.createElement("span"),c=o.ok;s.className="badge "+(c?"badge-success":o.status>=500||o.status===0?"badge-danger":"badge-warning"),s.style.cssText="flex:0 0 auto;",s.textContent=o.status||"ERR";const d=document.createElement("span");d.style.cssText="flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;",d.textContent=o.url.replace(`/api/x/${g?.project||""}`,"")||o.url,d.title=o.url,a.appendChild(s),a.appendChild(d),a.addEventListener("click",()=>Ce(o)),e.appendChild(a)}}const m={key:"var(--dm-info)",index:"var(--dm-text-muted)",string:"var(--dm-success)",number:"var(--dm-warning-dark)",boolean:"var(--dm-secondary)",nul:"var(--dm-danger)",punc:"var(--dm-text-muted)",preview:"var(--dm-text-muted)"};function f(e,t,r){const o=document.createElement("span");return o.style.color=e,r&&(o.style.cssText+=";"+r),o.textContent=t,o}function se(e,t){return t?f(m.index,String(e)):f(m.key,JSON.stringify(String(e)))}function xe(e){return e===null?f(m.nul,"null"):typeof e=="string"?f(m.string,JSON.stringify(e)):typeof e=="number"?f(m.number,String(e)):typeof e=="boolean"?f(m.boolean,String(e)):f(m.string,JSON.stringify(e))}function ie(e,t,r){const o=e&&typeof e=="object",a=o&&(Array.isArray(e)?e.length===0:Object.keys(e).length===0);if(!o||a){const p=document.createElement("div");return p.style.cssText="white-space:pre-wrap;word-break:break-word;padding-left:1.2em;",t!==void 0&&(p.appendChild(se(t,r)),p.appendChild(f(m.punc,": "))),p.appendChild(o?f(m.punc,Array.isArray(e)?"[]":"{}"):xe(e)),p}const s=Array.isArray(e),c=s?e.map((p,b)=>[b,p,!0]):Object.entries(e).map(([p,b])=>[p,b,!1]),d=document.createElement("div"),i=document.createElement("div");i.style.cssText="white-space:pre-wrap;cursor:pointer;";const j=f(m.punc,"\u25BE","display:inline-block;width:1.2em;");i.appendChild(j),t!==void 0&&(i.appendChild(se(t,r)),i.appendChild(f(m.punc,": "))),i.appendChild(f(m.punc,s?"[":"{"));const w=f(m.preview,` ${c.length} ${s?"item":"key"}${c.length===1?"":"s"} `,"font-style:italic;"),P=f(m.punc,s?"]":"}");i.appendChild(w),i.appendChild(P),d.appendChild(i);const A=document.createElement("div");A.style.cssText="padding-left:1.2em;border-left:1px solid var(--dm-border-light);";for(const[p,b,X]of c)A.appendChild(ie(b,p,X));d.appendChild(A);const $=document.createElement("div");$.style.paddingLeft="1.2em",$.appendChild(f(m.punc,s?"]":"}")),d.appendChild($);let l=!1;const C=p=>{l=p,j.textContent=p?"\u25B8":"\u25BE",w.style.display=p?"":"none",P.style.display=p?"":"none",A.style.display=p?"none":"",$.style.display=p?"none":""};return C(!1),i.addEventListener("click",()=>C(!l)),d}let V="";function le(e){n("tryit-json").style.display=e?"":"none",n("tryit-body").style.display=e?"none":"",n("tryit-json-toggle").textContent=e?"Raw":"Tree"}function ce(e){V=e;const t=n("tryit-results-placeholder");t&&(t.style.display="none"),n("tryit-response-card").style.display="";let r,o=!0;try{r=JSON.parse(e)}catch{o=!1}n("tryit-body").textContent=o?JSON.stringify(r,null,2):e;const a=n("tryit-json");a.textContent="",o&&a.appendChild(ie(r)),n("tryit-json-toggle").style.display=o?"":"none",le(o)}async function Q(){if(!g)return;const e={},t={};let r=g.path;for(const l of n("tryit-params").querySelectorAll("input")){e[l.dataset.param]=l.value;const C=new RegExp(":"+l.dataset.param.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")+"(?=/|$)","g");r=r.replace(C,encodeURIComponent(l.value))}const o=[];for(const l of n("tryit-query-params").querySelectorAll("input")){const C=l.value.trim();t[l.dataset.queryParam]=l.value,C&&o.push(`${encodeURIComponent(l.dataset.queryParam)}=${encodeURIComponent(C)}`)}const a=n("tryit-query").value.trim();a&&o.push(a);const s=o.join("&"),c=`/api/x/${g.project}${r}${s?"?"+s:""}`,d={ts:Date.now(),url:c,params:e,queryParams:t,extra:a},i={},j=n("tryit-token").value.trim();g.auth==="token"&&j&&(i.Authorization=`Bearer ${j}`);const w=location.origin+c,P=l=>`'${String(l).replace(/'/g,"'\\''")}'`,A=[`curl ${P(w)}`];for(const[l,C]of Object.entries(i))A.push(` -H ${P(`${l}: ${C}`)}`);J=A.join(` \\
|
|
1
|
+
import{api as _}from"../api.js";const pe=[["_eq","equals"],["_ne","not equal"],["_gt",">"],["_gte",">="],["_lt","<"],["_lte","<="],["_in","in list"],["_nin","not in list"],["_contains","contains"],["_starts","starts with"],["_ends","ends with"],["_exists","exists"]],we=["id","createdBy"],Se=/\{\{\s*(params|query)\.([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;function qe(x){const v=new Set;for(const n of Object.values(x?.filter||{}))if(typeof n=="string")for(const N of n.matchAll(Se))N[1]==="query"&&v.add(N[2]);return[...v]}function Ae(x){return String(x??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}let O=null;function ke(x){for(const[v]of pe)if(x.endsWith(v))return{field:x.slice(0,-v.length),op:v};return{field:x,op:"_eq"}}export const apiEndpointEditorView={templateUrl:"/admin/js/templates/api-endpoint-editor.html",async onMount(x){const v=x.get(0),n=e=>v.querySelector("#"+e)||document.getElementById(e),N=location.hash.split("?")[0].match(/\/api-endpoints\/edit\/([^/?#]+)/),q=N?decodeURIComponent(N[1]):null;n("ep-title").textContent=q?"Edit API Endpoint":"New API Endpoint",E.tabs(n("ep-editor-tabs"));const G=E.loader(v,{type:"dots"}),[ue,me,fe,u]=await Promise.all([_.projects.list().catch(()=>[]),_.collections.list().catch(()=>[]),_.get("/collections/roles/entries?limit=100").catch(()=>({entries:[]})),q?_.apiEndpoints.get(q).catch(()=>null):Promise.resolve(null)]);if(q&&!u){G.destroy(),E.toast("Endpoint not found.",{type:"error"}),location.hash="#/api-endpoints";return}const ye=me.filter(e=>!e.systemManaged&&!e.preset),he=(fe.entries||[]).map(e=>e.data?.name).filter(Boolean),k=n("endpoint-project");for(const e of ue){const t=document.createElement("option");t.value=e.slug,t.textContent=`${e.name} (${e.slug})`,k.appendChild(t)}const y=n("ep-collection");function ee(e){return e.meta&&e.meta.project||"core"}function z(e){const t=e!==void 0?e:y.value,r=k.value,o=ye.filter(a=>{const s=ee(a);return s===r||s==="core"});y.textContent="";for(const a of o){const s=document.createElement("option");s.value=a.slug,s.textContent=`${a.title} (${a.slug})${ee(a)==="core"&&r!=="core"?" - core":""}`,y.appendChild(s)}if(!o.length){const a=document.createElement("option");a.value="",a.textContent="No collections available in this project",y.appendChild(a)}t&&[...y.options].some(a=>a.value===t)&&(y.value=t)}z();const L=n("ep-auth");for(const[e,t]of[["public","Public - no credentials"],["token","Token - project API token"]]){const r=document.createElement("option");r.value=e,r.textContent=t,L.appendChild(r)}for(const e of he){const t=document.createElement("option");t.value=e,t.textContent=`Role: ${e}`,L.appendChild(t)}let T=[];async function R(){const e=y.value;if(T=[],e)try{T=((await _.collections.get(e))?.fields||[]).map(r=>r.name).filter(Boolean)}catch{}te();for(const t of K.querySelectorAll(".ep-filter-row"))U(t.querySelector(".ep-f-field"));ne()}function F(){return[...T,...we]}function te(e=[]){const t=n("ep-fields-list");t.textContent="";for(const r of T){const o=document.createElement("label");o.style.cssText="display:flex;align-items:center;gap:.35rem;cursor:pointer;font-size:.9rem;";const a=document.createElement("input");a.type="checkbox",a.className="ep-field-cb",a.value=r,a.checked=e.includes(r),a.addEventListener("change",h),o.appendChild(a),o.appendChild(document.createTextNode(r)),t.appendChild(o)}if(!T.length){const r=document.createElement("p");r.className="text-muted",r.style.cssText="font-size:.85rem;margin:0;",r.textContent="No fields found on this collection.",t.appendChild(r)}}function ne(e){const t=n("ep-sort"),r=e!==void 0?e:t.value;t.textContent="";for(const o of["createdAt",...F()]){const a=document.createElement("option");a.value=o==="createdAt"?"":o,a.textContent=o==="createdAt"?"createdAt (default)":o,t.appendChild(a)}t.value=r||""}function U(e,t){const r=t!==void 0?t:e.value;e.textContent="";for(const a of F()){const s=document.createElement("option");s.value=a,s.textContent=a,e.appendChild(s)}const o=document.createElement("option");if(o.value="__custom__",o.textContent="custom dot-path\u2026",e.appendChild(o),r&&[...e.options].some(a=>a.value===r))e.value=r;else if(r){const a=document.createElement("option");a.value=r,a.textContent=r,e.insertBefore(a,o),e.value=r}}const K=n("ep-filter-rows");function M(e="",t="_eq",r=""){const o=document.createElement("div");o.className="ep-filter-row",o.style.cssText="display:grid;grid-template-columns:2fr 1.2fr 2fr auto;gap:.5rem;margin-bottom:.5rem;align-items:center;";const a=document.createElement("select");a.className="form-input ep-f-field",U(a,e||void 0),a.addEventListener("change",()=>{if(a.value==="__custom__"){const i=prompt("Dot-path field (e.g. address.city):","");i&&i.trim()?U(a,i.trim()):a.value=F()[0]||""}h()});const s=document.createElement("select");s.className="form-input ep-f-op";for(const[i,j]of pe){const w=document.createElement("option");w.value=i,w.textContent=j,s.appendChild(w)}s.value=t,s.addEventListener("change",h);const c=document.createElement("input");c.type="text",c.className="form-input ep-f-value",c.placeholder="e.g. {{params.date}} or {{query.team}} or a literal",c.value=r,c.addEventListener("input",h);const d=document.createElement("button");d.className="btn btn-sm btn-ghost",d.innerHTML='<span data-icon="trash"></span>',d.addEventListener("click",()=>{o.remove(),h()}),o.appendChild(a),o.appendChild(s),o.appendChild(c),o.appendChild(d),K.appendChild(o),Domma.icons.scan(o)}function ve(){const e={};for(const t of K.querySelectorAll(".ep-filter-row")){const r=t.querySelector(".ep-f-field").value,o=t.querySelector(".ep-f-op").value,a=t.querySelector(".ep-f-value").value;!r||r==="__custom__"||(e[o==="_eq"?r:r+o]=a)}return e}function I(){return{name:n("ep-name").value.trim(),project:k.value,path:n("ep-path").value.trim(),collection:y.value,auth:L.value,mode:n("ep-mode").value,filter:ve(),sort:n("ep-sort").value||null,order:n("ep-order").value,limit:parseInt(n("ep-limit").value,10)||0,fields:[...v.querySelectorAll(".ep-field-cb:checked")].map(e=>e.value),enabled:n("ep-enabled").checked}}let B=null,g=null;function D(){return!B||JSON.stringify(I())!==B}function h(){oe(),Z()}function oe(){n("ep-url-preview").textContent=`/api/x/${k.value||"<project>"}${n("ep-path").value||"/\u2026"}`}function ge(e){return(e.match(/:([a-zA-Z_][a-zA-Z0-9_]*)/g)||[]).map(t=>t.slice(1))}function Z(){const e=D();n("tryit-dirty-note").style.display=e?"":"none",n("tryit-send").disabled=e,n("tryit-token-group").style.display=g?.auth==="token"?"":"none";const t=n("tryit-params"),r={};for(const s of t.querySelectorAll("input"))r[s.dataset.param]=s.value;t.textContent="";for(const s of ge(g?.path||"")){const c=document.createElement("div");c.className="form-group";const d=document.createElement("label");d.className="form-label",d.textContent=`:${s}`;const i=document.createElement("input");i.type="text",i.className="form-input",i.dataset.param=s,i.value=r[s]||"",c.appendChild(d),c.appendChild(i),t.appendChild(c)}const o=n("tryit-query-params"),a={};for(const s of o.querySelectorAll("input"))a[s.dataset.queryParam]=s.value;o.textContent="";for(const s of qe(g)){const c=document.createElement("div");c.className="form-group";const d=document.createElement("label");d.className="form-label",d.textContent=`?${s}`;const i=document.createElement("input");i.type="text",i.className="form-input",i.dataset.queryParam=s,i.value=a[s]||"",i.placeholder=`optional - filters by ${s}`,c.appendChild(d),c.appendChild(i),o.appendChild(c)}}let J="";const W=`apitryit:hist:${q||"new"}`,Ee=12;function ae(){const e=S.get(W);return Array.isArray(e)?e:[]}function re(e){const t=ae().filter(o=>o.url!==e.url),r=[e,...t].slice(0,Ee);S.set(W,r),Y()}function Ce(e){for(const t of n("tryit-params").querySelectorAll("input"))t.value=e.params?.[t.dataset.param]??"";for(const t of n("tryit-query-params").querySelectorAll("input"))t.value=e.queryParams?.[t.dataset.queryParam]??"";n("tryit-query").value=e.extra||"",Q()}function Y(){const e=n("tryit-history-list"),t=n("tryit-history-card");if(!e||!t)return;const r=ae();t.style.display=r.length?"":"none",e.textContent="";for(const o of r){const a=document.createElement("button");a.type="button",a.className="btn btn-ghost btn-sm",a.style.cssText="display:flex;align-items:center;gap:.5rem;width:100%;justify-content:flex-start;text-align:left;font-family:var(--dm-font-mono,monospace);font-size:.78rem;padding:.35rem .5rem;";const s=document.createElement("span"),c=o.ok;s.className="badge "+(c?"badge-success":o.status>=500||o.status===0?"badge-danger":"badge-warning"),s.style.cssText="flex:0 0 auto;",s.textContent=o.status||"ERR";const d=document.createElement("span");d.style.cssText="flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;",d.textContent=o.url.replace(`/api/x/${g?.project||""}`,"")||o.url,d.title=o.url,a.appendChild(s),a.appendChild(d),a.addEventListener("click",()=>Ce(o)),e.appendChild(a)}}const m={key:"var(--dm-info)",index:"var(--dm-text-muted)",string:"var(--dm-success)",number:"var(--dm-warning-dark)",boolean:"var(--dm-secondary)",nul:"var(--dm-danger)",punc:"var(--dm-text-muted)",preview:"var(--dm-text-muted)"};function f(e,t,r){const o=document.createElement("span");return o.style.color=e,r&&(o.style.cssText+=";"+r),o.textContent=t,o}function se(e,t){return t?f(m.index,String(e)):f(m.key,JSON.stringify(String(e)))}function xe(e){return e===null?f(m.nul,"null"):typeof e=="string"?f(m.string,JSON.stringify(e)):typeof e=="number"?f(m.number,String(e)):typeof e=="boolean"?f(m.boolean,String(e)):f(m.string,JSON.stringify(e))}function ie(e,t,r){const o=e&&typeof e=="object",a=o&&(Array.isArray(e)?e.length===0:Object.keys(e).length===0);if(!o||a){const p=document.createElement("div");return p.style.cssText="white-space:pre-wrap;word-break:break-word;padding-left:1.2em;",t!==void 0&&(p.appendChild(se(t,r)),p.appendChild(f(m.punc,": "))),p.appendChild(o?f(m.punc,Array.isArray(e)?"[]":"{}"):xe(e)),p}const s=Array.isArray(e),c=s?e.map((p,b)=>[b,p,!0]):Object.entries(e).map(([p,b])=>[p,b,!1]),d=document.createElement("div"),i=document.createElement("div");i.style.cssText="white-space:pre-wrap;cursor:pointer;";const j=f(m.punc,"\u25BE","display:inline-block;width:1.2em;");i.appendChild(j),t!==void 0&&(i.appendChild(se(t,r)),i.appendChild(f(m.punc,": "))),i.appendChild(f(m.punc,s?"[":"{"));const w=f(m.preview,` ${c.length} ${s?"item":"key"}${c.length===1?"":"s"} `,"font-style:italic;"),P=f(m.punc,s?"]":"}");i.appendChild(w),i.appendChild(P),d.appendChild(i);const A=document.createElement("div");A.style.cssText="padding-left:1.2em;border-left:1px solid var(--dm-border-light);";for(const[p,b,X]of c)A.appendChild(ie(b,p,X));d.appendChild(A);const $=document.createElement("div");$.style.paddingLeft="1.2em",$.appendChild(f(m.punc,s?"]":"}")),d.appendChild($);let l=!1;const C=p=>{l=p,j.textContent=p?"\u25B8":"\u25BE",w.style.display=p?"":"none",P.style.display=p?"":"none",A.style.display=p?"none":"",$.style.display=p?"none":""};return C(!1),i.addEventListener("click",()=>C(!l)),d}let V="";function le(e){n("tryit-json").style.display=e?"":"none",n("tryit-body").style.display=e?"none":"",n("tryit-json-toggle").textContent=e?"Raw":"Tree"}function ce(e){V=e;const t=n("tryit-results-placeholder");t&&(t.style.display="none"),n("tryit-response-card").style.display="";let r,o=!0;try{r=JSON.parse(e)}catch{o=!1}n("tryit-body").textContent=o?JSON.stringify(r,null,2):e;const a=n("tryit-json");a.textContent="",o&&a.appendChild(ie(r)),n("tryit-json-toggle").style.display=o?"":"none",le(o)}async function Q(){if(!g)return;const e={},t={};let r=g.path;for(const l of n("tryit-params").querySelectorAll("input")){e[l.dataset.param]=l.value;const C=new RegExp(":"+l.dataset.param.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")+"(?=/|$)","g");r=r.replace(C,encodeURIComponent(l.value))}const o=[];for(const l of n("tryit-query-params").querySelectorAll("input")){const C=l.value.trim();t[l.dataset.queryParam]=l.value,C&&o.push(`${encodeURIComponent(l.dataset.queryParam)}=${encodeURIComponent(C)}`)}const a=n("tryit-query").value.trim();a&&o.push(a);const s=o.join("&"),c=`/api/x/${g.project}${r}${s?"?"+s:""}`,d={ts:Date.now(),url:c,params:e,queryParams:t,extra:a},i={},j=n("tryit-token").value.trim();g.auth==="token"&&j&&(i.Authorization=`Bearer ${j}`);const w=location.origin+c,P=l=>`'${String(l).replace(/'/g,"'\\''")}'`,A=[`curl ${P(w)}`];for(const[l,C]of Object.entries(i))A.push(` -H ${P(`${l}: ${C}`)}`);J=A.join(` \\
|
|
2
2
|
`),n("tryit-result").style.display="",n("tryit-curl").textContent=J;const $=n("tryit-send");$.disabled=!0;try{const l=await fetch(c,{headers:i}),C=await l.text(),p=n("tryit-status");p.textContent=`${l.status} ${l.statusText}`,p.className="badge "+(l.ok?"badge-success":l.status>=500?"badge-danger":"badge-warning");const b=[];l.headers.forEach((X,be)=>b.push(`${be}: ${X}`)),n("tryit-headers-group").style.display="",n("tryit-headers").textContent=b.length?b.join(`
|
|
3
|
-
`):"(none exposed)",ce(C),re({...d,status:l.status,statusText:l.statusText,ok:l.ok})}catch(l){n("tryit-status").textContent="Network error",n("tryit-status").className="badge badge-danger",n("tryit-headers-group").style.display="none",ce(String(l.message||l)),re({...d,status:0,statusText:"Network error",ok:!1})}finally{$.disabled=D()}}if(u){if(n("ep-name").value=u.name||"",k.value=u.project,k.disabled=!0,n("ep-path").value=u.path||"",z(u.collection),y.value!==u.collection){const e=document.createElement("option");e.value=u.collection,e.textContent=`${u.collection} (other project
|
|
3
|
+
`):"(none exposed)",ce(C),re({...d,status:l.status,statusText:l.statusText,ok:l.ok})}catch(l){n("tryit-status").textContent="Network error",n("tryit-status").className="badge badge-danger",n("tryit-headers-group").style.display="none",ce(String(l.message||l)),re({...d,status:0,statusText:"Network error",ok:!1})}finally{$.disabled=D()}}if(u){if(n("ep-name").value=u.name||"",k.value=u.project,k.disabled=!0,n("ep-path").value=u.path||"",z(u.collection),y.value!==u.collection){const e=document.createElement("option");e.value=u.collection,e.textContent=`${u.collection} (other project - no longer allowed)`,y.appendChild(e),y.value=u.collection}L.value=u.auth||"public",n("ep-mode").value=u.mode||"list",n("ep-order").value=u.order||"desc",n("ep-limit").value=u.limit??50,n("ep-enabled").checked=u.enabled!==!1,await R(),te(u.fields||[]),ne(u.sort||"");for(const[e,t]of Object.entries(u.filter||{})){const{field:r,op:o}=ke(e);M(r,o,String(t))}g=u,B=JSON.stringify(I())}else await R(),M();oe(),Z(),G.destroy();for(const e of["ep-name","ep-path","ep-limit"])n(e).addEventListener("input",h);for(const e of["ep-mode","ep-order","ep-sort"])n(e).addEventListener("change",h);n("ep-enabled").addEventListener("change",h),k.addEventListener("change",async()=>{z(),await R(),h()}),L.addEventListener("change",h),y.addEventListener("change",async()=>{await R(),h()}),n("ep-add-filter").addEventListener("click",()=>{M(),h()}),n("tryit-send").addEventListener("click",Q),n("tryit-history-clear").addEventListener("click",()=>{S.set(W,[]),Y()}),n("tryit-json-toggle").addEventListener("click",()=>{le(n("tryit-json").style.display==="none")}),n("tryit-body-copy").addEventListener("click",async()=>{if(!V){E.toast("Send a request first.",{type:"info"});return}try{await navigator.clipboard.writeText(V),E.toast("Response copied.",{type:"success"})}catch{E.toast("Copy failed.",{type:"error"})}}),n("tryit-copy-curl").addEventListener("click",async()=>{if(!J){E.toast("Send a request first.",{type:"info"});return}try{await navigator.clipboard.writeText(J),E.toast("curl command copied.",{type:"success"})}catch{E.toast("Copy failed.",{type:"error"})}});async function de(){const e=I();if(!e.name){E.toast("A name is required.",{type:"error"});return}if(!e.path.startsWith("/")){E.toast('The path must start with "/".',{type:"error"});return}try{if(q)g=await _.apiEndpoints.update(q,e),B=JSON.stringify(I()),E.toast("Endpoint saved.",{type:"success"}),Z();else{const t=await _.apiEndpoints.create(e);E.toast("Endpoint created.",{type:"success"}),location.hash="#/api-endpoints/edit/"+encodeURIComponent(t.id)}}catch(t){E.toast(`Save failed: ${t.message||t}`,{type:"error"})}}n("ep-save").addEventListener("click",de);function H(e){if(!document.getElementById("ep-save")){document.removeEventListener("keydown",H),O===H&&(O=null);return}if(!(e.metaKey||e.ctrlKey))return;const r=e.key.toLowerCase();r==="s"?(e.preventDefault(),de()):r==="enter"&&(e.preventDefault(),g&&!D()?Q():D()&&E.toast("Save first - Send tests the saved definition.",{type:"info"}))}O&&document.removeEventListener("keydown",O),O=H,document.addEventListener("keydown",H),Y(),Domma.icons.scan(v)}};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{api as
|
|
1
|
+
import{api as d}from"../api.js";import{filterByProject as p,getProjectFromHash as c,renderProjectFilterNotice as r}from"../lib/project-context.js";import{keepTableIconsAlive as b}from"../lib/table-icons.js";function n(a){return String(a??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function g(a){return a==="public"?'<span class="badge badge-warning">Public</span>':a==="token"?'<span class="badge badge-info">Token</span>':`<span class="badge badge-secondary">${n(a)}</span>`}export const apiEndpointsView={templateUrl:"/admin/js/templates/api-endpoints.html",async onMount(a){const l=E.loader(a.get(0),{type:"dots"});let o=await d.apiEndpoints.list().catch(()=>[]);l.destroy();const s=c();if(s){const e=o.length;o=p(o,s),r(a,{total:e,shown:o.length,slug:s,allHref:"#/api-endpoints",noun:"endpoints"})}T.create("#api-endpoints-table",{pageSize:25,data:o,emptyMessage:'No endpoints yet. Click "New endpoint" to build one.',columns:[{key:"name",title:"Name",sortable:!0},{key:"path",title:"URL",render:(e,t)=>`<code>/api/x/${n(t.project)}${n(t.path)}</code>`},{key:"collection",title:"Collection",sortable:!0,render:e=>`<code>${n(e)}</code>`},{key:"mode",title:"Mode",render:e=>n(e)},{key:"auth",title:"Auth",render:e=>g(e)},{key:"enabled",title:"Status",render:e=>e?'<span class="badge badge-success">Enabled</span>':'<span class="badge badge-secondary">Disabled</span>'},{key:"_actions",title:"Actions",render:(e,t)=>`
|
|
2
2
|
<span style="display:inline-flex;gap:0.25rem;">
|
|
3
3
|
<a class="btn btn-sm btn-ghost" href="#/api-endpoints/edit/${encodeURIComponent(t.id)}" data-tooltip="Edit"><span data-icon="edit" data-icon-size="14"></span></a>
|
|
4
4
|
<button class="btn btn-sm btn-ghost btn-toggle-endpoint" data-id="${n(t.id)}" data-enabled="${t.enabled?"1":""}" data-tooltip="${t.enabled?"Disable":"Enable"}"><span data-icon="${t.enabled?"pause":"play"}" data-icon-size="14"></span></button>
|
|
5
5
|
<button class="btn btn-sm btn-ghost btn-delete-endpoint" data-id="${n(t.id)}" data-name="${n(t.name)}" data-tooltip="Delete"><span data-icon="trash" data-icon-size="14"></span></button>
|
|
6
6
|
</span>
|
|
7
|
-
`}]}),Domma.icons.scan(a.get(0)),
|
|
7
|
+
`}]}),Domma.icons.scan(a.get(0)),b(a.get(0)),document.querySelectorAll("#api-endpoints-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}),a.off("click",".btn-toggle-endpoint").on("click",".btn-toggle-endpoint",async function(){const e=$(this).data("id"),t=!!$(this).data("enabled");try{await d.apiEndpoints.update(e,{enabled:!t}),E.toast(`Endpoint ${t?"disabled":"enabled"}.`,{type:"success"}),location.reload()}catch(i){E.toast(`Update failed: ${i.message||i}`,{type:"error"})}}),a.off("click",".btn-delete-endpoint").on("click",".btn-delete-endpoint",async function(){const e=$(this).data("id"),t=$(this).data("name");if(await E.confirm(`Delete endpoint "${t}"? External callers using it will immediately get 404s. This cannot be undone.`))try{await d.apiEndpoints.remove(e),E.toast("Endpoint deleted.",{type:"success"}),location.reload()}catch(i){E.toast(`Delete failed: ${i.message||i}`,{type:"error"})}})}};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import{api as m}from"../api.js";import{keepTableIconsAlive as g}from"../lib/table-icons.js";function d(a){return String(a??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function f(a){return!Array.isArray(a)||a.length===0?'<span class="text-muted">All collections in project</span>':d(a.map(l=>{const r=Array.isArray(l.verbs)&&l.verbs.length?l.verbs.join(","):"all";return`${l.collection}: ${r}`}).join("; "))}function h(a){const l=[];for(const r of(a||"").split(`
|
|
2
|
-
`)){const n=r.trim();if(!n)continue;const[o,e]=n.split(":").map(s=>s.trim());if(!o)return{error:`Invalid scope line: "${n}"`};const t={collection:o};if(e){const s=e.split(",").map(
|
|
3
|
-
enquiries: create, read`,n.appendChild(b("Name",o)),n.appendChild(b("Project",e,"The token only works on collections in this project.")),n.appendChild(b("Expires (optional)",t,"Leave empty for a non-expiring token.")),n.appendChild(b("Scopes (optional)",s,'One per line: "collection: verb, verb". Leave empty to allow every collection in the project.'));const
|
|
2
|
+
`)){const n=r.trim();if(!n)continue;const[o,e]=n.split(":").map(s=>s.trim());if(!o)return{error:`Invalid scope line: "${n}"`};const t={collection:o};if(e){const s=e.split(",").map(i=>i.trim()).filter(Boolean),c=s.find(i=>!["create","read","update","delete"].includes(i));if(c)return{error:`Unknown verb "${c}" in scope "${n}"`};t.verbs=s}l.push(t)}return{scopes:l}}function b(a,l,r){const n=document.createElement("div");n.style.marginBottom=".75rem";const o=document.createElement("label");if(o.className="form-label",o.textContent=a,n.appendChild(o),n.appendChild(l),r){const e=document.createElement("p");e.className="text-muted",e.style.cssText="font-size:.8rem;margin:.25rem 0 0;",e.textContent=r,n.appendChild(e)}return n}function v(a,l){const r=E.slideover({title:"Token created",size:"sm",position:"right"}),n=document.createElement("div");n.style.padding="1rem";const o=document.createElement("p");o.innerHTML="<strong>Copy this token now.</strong> It is shown once and cannot be recovered - only a hash is stored.",o.style.marginBottom=".75rem",n.appendChild(o);const e=document.createElement("code");e.textContent=a,e.style.cssText="display:block;padding:.6rem;word-break:break-all;user-select:all;margin-bottom:.75rem;",n.appendChild(e);const t=document.createElement("button");t.className="btn btn-primary",t.textContent="Copy to clipboard",t.style.marginRight=".5rem",t.addEventListener("click",async()=>{try{await navigator.clipboard.writeText(a),t.textContent="Copied!"}catch{E.toast("Copy failed - select the token text manually.",{type:"error"})}}),n.appendChild(t);const s=document.createElement("button");s.className="btn btn-secondary",s.textContent="Done",s.addEventListener("click",()=>{r.close(),l()}),n.appendChild(s),r.element.appendChild(n),r.open()}function x(a,l){const r=E.slideover({title:"New API Token",size:"md",position:"right"}),n=document.createElement("div");n.style.padding=".5rem 1rem 1rem";const o=document.createElement("input");o.type="text",o.className="form-input",o.placeholder="e.g. mobile-app, ci-pipeline";const e=document.createElement("select");e.className="form-input";for(const i of a){const p=document.createElement("option");p.value=i.slug,p.textContent=`${i.name} (${i.slug})`,e.appendChild(p)}const t=document.createElement("input");t.type="datetime-local",t.className="form-input";const s=document.createElement("textarea");s.className="form-input",s.rows=3,s.placeholder=`jobs: read
|
|
3
|
+
enquiries: create, read`,n.appendChild(b("Name",o)),n.appendChild(b("Project",e,"The token only works on collections in this project.")),n.appendChild(b("Expires (optional)",t,"Leave empty for a non-expiring token.")),n.appendChild(b("Scopes (optional)",s,'One per line: "collection: verb, verb". Leave empty to allow every collection in the project.'));const c=document.createElement("button");c.className="btn btn-primary",c.textContent="Create token",c.addEventListener("click",async()=>{const i=o.value.trim();if(!i){E.toast("A token name is required.",{type:"error"});return}const{scopes:p,error:y}=h(s.value);if(y){E.toast(y,{type:"error"});return}const k={name:i,project:e.value,scopes:p,expiresAt:t.value?new Date(t.value).toISOString():null};try{c.disabled=!0;const{plaintext:u}=await m.apiTokens.create(k);r.close(),v(u,l)}catch(u){c.disabled=!1,E.toast(`Create failed: ${u.message||u}`,{type:"error"})}}),n.appendChild(c),r.element.appendChild(n),r.open(),setTimeout(()=>o.focus(),100)}export const apiTokensView={templateUrl:"/admin/js/templates/api-tokens.html",async onMount(a){const l=E.loader(a.get(0),{type:"dots"}),[r,n]=await Promise.all([m.apiTokens.list().catch(()=>[]),m.projects.list().catch(()=>[])]);l.destroy(),T.create("#api-tokens-table",{pageSize:25,data:r,emptyMessage:'No API tokens yet. Click "New token" to create one.',columns:[{key:"name",title:"Name",sortable:!0},{key:"project",title:"Project",sortable:!0,render:e=>`<code>${d(e)}</code>`},{key:"tokenHint",title:"Token",render:e=>`<code>dcms_\u2026${d(e||"????")}</code>`},{key:"scopes",title:"Scopes",render:e=>f(e)},{key:"enabled",title:"Status",render:(e,t)=>t.expiresAt&&Date.parse(t.expiresAt)<Date.now()?'<span class="badge badge-warning">Expired</span>':e?'<span class="badge badge-success">Enabled</span>':'<span class="badge badge-secondary">Disabled</span>'},{key:"expiresAt",title:"Expires",render:e=>e?D(e).format("D MMM YYYY, HH:mm"):'<span class="text-muted">-</span>'},{key:"lastUsedAt",title:"Last used",render:e=>e?D(e).format("D MMM YYYY, HH:mm"):'<span class="text-muted">Never</span>'},{key:"_actions",title:"Actions",render:(e,t)=>`
|
|
4
4
|
<span style="display:inline-flex;gap:0.25rem;">
|
|
5
5
|
<button class="btn btn-sm btn-ghost btn-toggle-token" data-id="${d(t.id)}" data-enabled="${t.enabled?"1":""}" data-tooltip="${t.enabled?"Disable":"Enable"}"><span data-icon="${t.enabled?"pause":"play"}" data-icon-size="14"></span></button>
|
|
6
6
|
<button class="btn btn-sm btn-ghost btn-revoke-token" data-id="${d(t.id)}" data-name="${d(t.name)}" data-tooltip="Revoke"><span data-icon="trash" data-icon-size="14"></span></button>
|
|
7
7
|
</span>
|
|
8
|
-
`}]}),Domma.icons.scan(a.get(0)),g(a.get(0)),document.querySelectorAll("#api-tokens-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})});const o=()=>location.reload();a.off("click","#btn-new-token").on("click","#btn-new-token",()=>{if(!n.length){E.toast("No projects available
|
|
8
|
+
`}]}),Domma.icons.scan(a.get(0)),g(a.get(0)),document.querySelectorAll("#api-tokens-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})});const o=()=>location.reload();a.off("click","#btn-new-token").on("click","#btn-new-token",()=>{if(!n.length){E.toast("No projects available - create a project first.",{type:"error"});return}x(n,o)}),a.off("click",".btn-toggle-token").on("click",".btn-toggle-token",async function(){const e=$(this).data("id"),t=!!$(this).data("enabled");try{await m.apiTokens.update(e,{enabled:!t}),E.toast(`Token ${t?"disabled":"enabled"}.`,{type:"success"}),o()}catch(s){E.toast(`Update failed: ${s.message||s}`,{type:"error"})}}),a.off("click",".btn-revoke-token").on("click",".btn-revoke-token",async function(){const e=$(this).data("id"),t=$(this).data("name");if(await E.confirm(`Revoke token "${t}"? External callers using it will immediately lose access. This cannot be undone.`))try{await m.apiTokens.revoke(e),E.toast("Token revoked.",{type:"success"}),o()}catch(s){E.toast(`Revoke failed: ${s.message||s}`,{type:"error"})}})}};
|
package/admin/js/views/blocks.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{api as i}from"../api.js";import{filterByProject as m,getProjectFromHash as u}from"../lib/project-context.js";import{keepTableIconsAlive as
|
|
1
|
+
import{api as i}from"../api.js";import{filterByProject as m,getProjectFromHash as u,renderProjectFilterNotice as f}from"../lib/project-context.js";import{keepTableIconsAlive as k}from"../lib/table-icons.js";function d(e){return String(e??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function b(e){for(;e.firstChild;)e.removeChild(e.firstChild)}export const blocksView={templateUrl:"/admin/js/templates/blocks.html",async onMount(e){h(),await l(e),g(e),Domma.icons.scan(e.get(0)),k(e.get(0))}};let p=!1;function h(){p||(p=!0,I.register("download",{viewBox:"0 0 24 24",paths:["M12 3v12","M7 10l5 5 5-5","M5 19h14"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("upload",{viewBox:"0 0 24 24",paths:["M12 21V9","M7 14l5-5 5 5","M5 5h14"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("plus",{viewBox:"0 0 24 24",paths:["M12 5v14","M5 12h14"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}))}async function l(e){const r=e.find("#blocks-table-container").get(0);if(!r)return;let o=[];const n=E.loader(e.get(0),{type:"dots"});try{o=await i.blocks.list()}catch(t){b(r);const s=document.createElement("p");s.className="text-muted",s.textContent=`Failed to load blocks: ${t.message}`,r.appendChild(s);return}finally{n.destroy()}const a=u();if(a){const t=o.length;o=m(o,a),f(e,{total:t,shown:o.length,slug:a,allHref:"#/blocks",noun:"blocks"})}T.create(r,{pageSize:25,data:o,emptyMessage:'No blocks yet. Click "New Block" to create your first template.',columns:[{key:"name",title:"Name",render:t=>`<a href="#/blocks/edit/${d(t)}">${d(t)}.html</a>`},{key:"size",title:"Size",render:t=>`${t} B`},{key:"updatedAt",title:"Updated",render:t=>t?new Date(t).toLocaleString():"-"},{key:"name",title:"Actions",render:t=>{const s=d(t);return`<div style="display:flex;gap:.4rem;justify-content:flex-end;">
|
|
2
2
|
<a href="#/blocks/edit/${s}" class="btn btn-sm btn-ghost" data-tooltip="Edit"><span data-icon="edit" data-icon-size="14"></span></a>
|
|
3
3
|
<button class="btn btn-sm btn-ghost js-export-block" data-name="${s}" data-tooltip="Export as .dmblock.json"><span data-icon="download" data-icon-size="14"></span></button>
|
|
4
4
|
<button class="btn btn-sm btn-ghost js-delete-block" data-name="${s}" data-tooltip="Delete"><span data-icon="trash" data-icon-size="14"></span></button>
|
|
5
|
-
</div>`}}]}),e.off("click",".js-delete-block").on("click",".js-delete-block",async function(){await y(this.dataset.name,e)}),e.off("click",".js-export-block").on("click",".js-export-block",async function(){try{await i.blocks.exportBundle(this.dataset.name)}catch(t){E.toast(t.message||"Export failed.",{type:"error"})}}),Domma.icons.scan(
|
|
5
|
+
</div>`}}]}),e.off("click",".js-delete-block").on("click",".js-delete-block",async function(){await y(this.dataset.name,e)}),e.off("click",".js-export-block").on("click",".js-export-block",async function(){try{await i.blocks.exportBundle(this.dataset.name)}catch(t){E.toast(t.message||"Export failed.",{type:"error"})}}),Domma.icons.scan(r),r.querySelectorAll("[data-tooltip]").forEach(t=>{E.tooltip(t,{content:t.getAttribute("data-tooltip"),position:"top"})})}async function y(e,r){if(await E.confirm(`Delete block "${e}"? This cannot be undone.`))try{await i.blocks.delete(e),E.toast("Block deleted.",{type:"success"}),await l(r)}catch(n){E.toast(n.message||"Failed to delete block.",{type:"error"})}}function g(e){const r=e.find("#import-block-btn").get(0),o=e.find("#import-block-file").get(0);!r||!o||(r.addEventListener("click",()=>o.click()),o.addEventListener("change",async()=>{const n=o.files?.[0];if(!n)return;o.value="";let a;try{const t=await n.text();a=JSON.parse(t)}catch{E.toast("Not a valid .dmblock.json file (could not parse JSON).",{type:"error"});return}if(!a||typeof a!="object"||!a.name||typeof a.html!="string"){E.toast("Bundle is missing a name or html field.",{type:"error"});return}try{const t=await i.blocks.importBundle(a);E.toast(`Imported "${t.name}".`,{type:"success"}),await l(e)}catch(t){if(t.code==="CONFLICT"){if(!await E.confirm(`A block named "${t.name}" already exists. Overwrite it with the imported version?`)){E.toast("Import cancelled.",{type:"info"});return}try{const c=await i.blocks.importBundle(a,{overwrite:!0});E.toast(`Overwrote "${c.name}".`,{type:"success"}),await l(e)}catch(c){E.toast(c.message||"Import failed.",{type:"error"})}return}E.toast(t.message||"Import failed.",{type:"error"})}}))}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import{api as B}from"../api.js";const ee=[{value:"string",label:"Text (single line)"},{value:"email",label:"Email"},{value:"tel",label:"Phone"},{value:"number",label:"Number"},{value:"textarea",label:"Textarea (multi-line)"},{value:"select",label:"Dropdown (select)"},{value:"radio",label:"Radio buttons"},{value:"checkbox",label:"Single checkbox"},{value:"checkbox-group",label:"Checkbox group"},{value:"date",label:"Date"},{value:"time",label:"Time"},{value:"url",label:"URL"},{value:"hidden",label:"Hidden field"}],Q=new Set(["select","radio","checkbox-group"]),te=["public","token","subscriber","editor","manager","admin"],le=["create","read","update","delete"];let x=[],$=null,q=!0,z=null,A="file",X={};function ne(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function ae(e){return ee.find(t=>t.value===e)?.label||e}function oe(e){const t={...x[e]},a=document.getElementById(`fb-label-${e}`),o=document.getElementById(`fb-name-${e}`),s=document.getElementById(`fb-type-${e}`),g=document.getElementById(`fb-required-${e}`),y=document.getElementById(`fb-placeholder-${e}`),b=document.getElementById(`fb-helper-${e}`);if(a&&(t.label=a.value.trim()||t.label),o&&(t.name=o.value.trim()||t.name),s&&(t.type=s.value||t.type),g&&(t.required=g.checked),y&&(t.placeholder=y.value.trim()),b&&(t.helper=b.value.trim()),Q.has(t.type)){const n=document.getElementById(`fb-options-${e}`);n&&(t.options=n.value.split(`
|
|
2
2
|
`).filter(c=>c.trim()).map(c=>{const[h,...w]=c.split(":");return{value:h.trim(),label:w.join(":").trim()||h.trim()}}))}const l=document.getElementById(`fb-span-${e}`);if(document.getElementById(`fb-fullwidth-${e}`)?.checked)t.fullWidth=!0,delete t.span;else{delete t.fullWidth;const n=parseInt(l?.value,10);n>1?t.span=n:delete t.span}return t}function Z(){return x.map((e,t)=>oe(t))}function de(e,t){const a=document.createElement("div");a.className="fb-field-card",a.dataset.index=t,a.style.cssText="border:1px solid var(--dm-border,#333);border-radius:8px;margin-bottom:.75rem;overflow:hidden;";const o=document.createElement("div");o.className="fb-field-header",o.style.cssText="display:flex;align-items:center;gap:.5rem;padding:.6rem .75rem;background:var(--dm-card-header-bg,rgba(255,255,255,.03));cursor:pointer;user-select:none;";const s=document.createElement("span");s.innerHTML='<span data-icon="menu" data-icon-size="14" style="display:inline-flex;align-items:center;vertical-align:middle;"></span>',s.title="Drag to reorder",s.style.cssText="cursor:grab;opacity:.4;flex-shrink:0;display:inline-flex;align-items:center;",a.draggable=!0,s.addEventListener("mousedown",()=>{a.draggable=!0}),a.addEventListener("dragstart",d=>{z=t,d.dataTransfer.effectAllowed="move",a.style.opacity="0.4"}),a.addEventListener("dragend",()=>{a.style.opacity="",document.querySelectorAll(".fb-field-card").forEach(d=>d.classList.remove("fb-drag-over"))}),a.addEventListener("dragover",d=>{d.preventDefault(),d.dataTransfer.dropEffect="move",document.querySelectorAll(".fb-field-card").forEach(N=>N.classList.remove("fb-drag-over")),a.classList.add("fb-drag-over")}),a.addEventListener("dragleave",()=>{a.classList.remove("fb-drag-over")}),a.addEventListener("drop",d=>{if(d.preventDefault(),a.classList.remove("fb-drag-over"),z===null||z===t)return;x=Z();const[N]=x.splice(z,1);x.splice(t,0,N),z=null,O(document.getElementById("fields-list"))});const g=document.createElement("span");g.className="fb-field-summary",g.style.cssText="flex:1;font-weight:500;font-size:.9rem;",g.textContent=e.label||"(Untitled field)";const y=document.createElement("span");y.style.cssText="font-size:.75rem;opacity:.5;",y.textContent=ae(e.type);const b=document.createElement("span");b.className="fb-field-chevron",b.innerHTML='<span data-icon="chevron-down" data-icon-size="12" style="display:inline-flex;align-items:center;vertical-align:middle;"></span>',b.style.cssText="opacity:.5;transition:transform .2s;display:inline-flex;align-items:center;";const l=document.createElement("button");l.type="button",l.innerHTML='<span data-icon="x" data-icon-size="13" style="display:inline-flex;align-items:center;vertical-align:middle;"></span>',l.className="btn btn-sm btn-ghost",l.style.cssText="padding:.15rem .45rem;line-height:1;opacity:.6;",l.title="Remove field",l.addEventListener("click",d=>{d.stopPropagation(),x.splice(t,1),O(document.getElementById("fields-list"))}),o.appendChild(s),o.appendChild(g),o.appendChild(y),o.appendChild(b),o.appendChild(l),Domma.icons.scan(o);const n=document.createElement("div");n.className="fb-field-body",n.style.cssText="padding:.75rem;display:none;";const c=document.createElement("div");c.style.cssText="display:grid;grid-template-columns:1fr 1fr 1fr;gap:.6rem;margin-bottom:.6rem;";const h=document.createElement("div"),w=document.createElement("label");w.className="form-label",w.textContent="Label";const i=document.createElement("input");i.id=`fb-label-${t}`,i.type="text",i.className="form-input",i.value=e.label||"",i.addEventListener("input",()=>{g.textContent=i.value.trim()||"(Untitled field)";const d=document.getElementById(`fb-name-${t}`);d&&!d.dataset.manual&&(d.value=ne(i.value).replace(/-/g,"_"))}),h.appendChild(w),h.appendChild(i);const p=document.createElement("div"),u=document.createElement("label");u.className="form-label",u.textContent="Name (key)";const m=document.createElement("input");m.id=`fb-name-${t}`,m.type="text",m.className="form-input",m.value=e.name||"",m.addEventListener("input",()=>{m.dataset.manual="1"}),p.appendChild(u),p.appendChild(m);const C=document.createElement("div"),L=document.createElement("label");L.className="form-label",L.textContent="Type";const v=document.createElement("select");v.id=`fb-type-${t}`,v.className="form-input",ee.forEach(d=>{const N=document.createElement("option");N.value=d.value,N.textContent=d.label,d.value===e.type&&(N.selected=!0),v.appendChild(N)}),v.addEventListener("change",()=>{y.textContent=ae(v.value);const d=n.querySelector(".fb-options-wrap");d&&(d.style.display=Q.has(v.value)?"":"none")}),C.appendChild(L),C.appendChild(v),c.appendChild(h),c.appendChild(p),c.appendChild(C);const f=document.createElement("div");f.style.cssText="display:grid;grid-template-columns:1fr 1fr auto;gap:.6rem;align-items:end;margin-bottom:.6rem;";const r=document.createElement("div"),T=document.createElement("label");T.className="form-label",T.textContent="Placeholder";const I=document.createElement("input");I.id=`fb-placeholder-${t}`,I.type="text",I.className="form-input",I.value=e.placeholder||"",r.appendChild(T),r.appendChild(I);const H=document.createElement("div"),_=document.createElement("label");_.className="form-label",_.textContent="Helper text";const F=document.createElement("input");F.id=`fb-helper-${t}`,F.type="text",F.className="form-input",F.value=e.helper||"",H.appendChild(_),H.appendChild(F);const V=document.createElement("div");V.style.cssText="padding-bottom:.35rem;";const D=document.createElement("label");D.style.cssText="display:flex;align-items:center;gap:.4rem;cursor:pointer;white-space:nowrap;";const M=document.createElement("input");M.id=`fb-required-${t}`,M.type="checkbox",M.checked=!!e.required,D.appendChild(M),D.appendChild(document.createTextNode("Required")),V.appendChild(D),f.appendChild(r),f.appendChild(H),f.appendChild(V);const j=document.createElement("div");j.className="fb-options-wrap",j.style.display=Q.has(e.type)?"":"none";const Y=document.createElement("label");Y.className="form-label",Y.textContent="Options (one per line: value: Label)";const W=document.createElement("textarea");W.id=`fb-options-${t}`,W.className="form-input",W.rows=4,W.value=(e.options||[]).map(d=>typeof d=="string"?`${d}: ${d}`:`${d.value??""}: ${d.label??d.value??""}`).join(`
|
|
3
|
-
`),j.appendChild(Y),j.appendChild(W);const k=document.createElement("div");k.className="fb-grid-row",k.style.gridTemplateColumns="1fr auto",k.style.gap=".6rem",k.style.alignItems="end",k.style.marginBottom=".6rem",k.style.display=document.getElementById("collection-layout")?.value==="grid"?"grid":"none";const G=document.createElement("div"),J=document.createElement("label");J.className="form-label",J.textContent="Column Span";const S=document.createElement("input");S.id=`fb-span-${t}`,S.type="number",S.className="form-input",S.min="1",S.max="6",S.value=e.span>1?String(e.span):"1",G.appendChild(J),G.appendChild(S);const K=document.createElement("div");K.style.cssText="padding-bottom:.35rem;";const U=document.createElement("label");U.style.cssText="display:flex;align-items:center;gap:.4rem;cursor:pointer;white-space:nowrap;";const P=document.createElement("input");return P.id=`fb-fullwidth-${t}`,P.type="checkbox",P.checked=!!e.fullWidth,U.appendChild(P),U.appendChild(document.createTextNode("Full Width")),K.appendChild(U),k.appendChild(G),k.appendChild(K),n.appendChild(c),n.appendChild(f),n.appendChild(j),n.appendChild(k),o.addEventListener("click",()=>{const d=n.style.display!=="none";n.style.display=d?"none":"",b.style.transform=d?"":"rotate(180deg)"}),a.appendChild(o),a.appendChild(n),a}function O(e){if(e){if(e.textContent="",x.length===0){const t=document.createElement("p");t.className="text-muted",t.id="fields-empty-msg",t.style.cssText="text-align:center;padding:2rem 0;",t.textContent='No fields yet. Click "Add Field" to get started.',e.appendChild(t);return}x.forEach((t,a)=>{e.appendChild(de(t,a))})}}function ie(e,t,a){t.textContent="",le.forEach(i=>{const p=e?.[i]||{enabled:!1,access:"admin"},u=document.createElement("div");u.style.cssText="display:grid;grid-template-columns:140px 1fr 160px;gap:.75rem;align-items:center;padding:.6rem 0;border-bottom:1px solid var(--dm-border,#333);";const m=document.createElement("strong");m.textContent=i.charAt(0).toUpperCase()+i.slice(1),m.style.cssText="font-size:.9rem;";const C=document.createElement("label");C.style.cssText="display:flex;align-items:center;gap:.45rem;cursor:pointer;font-size:.875rem;";const L=document.createElement("input");L.type="checkbox",L.id=`api-${i}-enabled`,L.checked=!!p.enabled,C.appendChild(L),C.appendChild(document.createTextNode("Enable public access"));const v=document.createElement("select");if(v.id=`api-${i}-access`,v.className="form-input",te.forEach(f=>{const r=document.createElement("option");r.value=f,r.textContent=f.charAt(0).toUpperCase()+f.slice(1),f===p.access&&(r.selected=!0),v.appendChild(r)}),u.appendChild(m),u.appendChild(C),u.appendChild(v),t.appendChild(u),i==="read"){const f=document.createElement("div");f.style.cssText="display:grid;grid-template-columns:140px 1fr;gap:.75rem;align-items:center;padding:.6rem 0;border-bottom:1px solid var(--dm-border,#333);";const r=document.createElement("label");r.htmlFor="api-read-fields",r.style.cssText="font-size:.85rem;color:var(--dm-text-muted,#aaa);",r.textContent="Read fields";const T=document.createElement("input");T.type="text",T.id="api-read-fields",T.className="form-input",T.placeholder="Comma-separated allowlist \u2014 empty = all fields",T.value=Array.isArray(p.fields)?p.fields.join(", "):"",f.appendChild(r),f.appendChild(T),t.appendChild(f)}});const o=a||{},s=document.createElement("div");s.style.cssText="display:grid;grid-template-columns:140px 1fr 160px;gap:.75rem;align-items:center;padding:.6rem 0;border-top:2px solid var(--dm-border,#333);";const g=document.createElement("strong");g.textContent="Export",g.style.cssText="font-size:.9rem;";const y=document.createElement("label");y.style.cssText="display:flex;align-items:center;gap:.45rem;cursor:pointer;font-size:.875rem;";const b=document.createElement("input");b.type="checkbox",b.id="export-enabled",b.checked=o.enabled!==!1,y.appendChild(b),y.appendChild(document.createTextNode("Allow export"));const l=document.createElement("select");l.id="export-access",l.className="form-input",te.filter(i=>i!=="token").forEach(i=>{const p=document.createElement("option");p.value=i,p.textContent=i.charAt(0).toUpperCase()+i.slice(1),i===o.access&&(p.selected=!0),l.appendChild(p)}),s.appendChild(g),s.appendChild(y),s.appendChild(l),t.appendChild(s);const n=document.createElement("div");n.style.cssText="display:grid;grid-template-columns:140px 1fr;gap:.75rem;align-items:center;padding:.6rem 0;border-bottom:1px solid var(--dm-border,#333);";const c=document.createElement("label");c.style.cssText="font-size:.85rem;color:var(--dm-text-muted,#aaa);",c.textContent="Formats";const h=document.createElement("div");h.style.cssText="display:flex;gap:1rem;";const w=Array.isArray(o.formats)?o.formats:["csv","json"];["csv","json"].forEach(i=>{const p=document.createElement("label");p.style.cssText="display:flex;align-items:center;gap:.35rem;cursor:pointer;font-size:.875rem;";const u=document.createElement("input");u.type="checkbox",u.id=`export-format-${i}`,u.checked=w.includes(i),p.appendChild(u),p.appendChild(document.createTextNode(i.toUpperCase())),h.appendChild(p)}),n.appendChild(c),n.appendChild(h),t.appendChild(n)}function ce(e,t){E.dropdown("#storage-adapter-trigger",{items:[{label:"File (default)",value:"file"},{label:"MongoDB",value:"mongodb"}],onSelect:({item:o})=>{e.find("#storage-adapter").val(o.value),e.find("#storage-adapter-label").text(o.label);const s=o.value==="mongodb";e.find("#storage-connection-group").toggle(s),e.find("#storage-migration-warning").toggle(s&&!q)}});const a=t.map(o=>({label:o,value:o}));E.dropdown("#storage-connection-trigger",{items:a.length?a:[{label:"default",value:"default"}],onSelect:({item:o})=>{e.find("#storage-connection").val(o.value),e.find("#storage-connection-label").text(o.label)}})}function se(){return(document.getElementById("storage-adapter")?.value||"file")==="mongodb"?{adapter:"mongodb",connection:document.getElementById("storage-connection")?.value||"default"}:{adapter:"file"}}function re(){const e={};return le.forEach(t=>{const a=document.getElementById(`api-${t}-enabled`)?.checked??!1,o=document.getElementById(`api-${t}-access`)?.value||"admin";if(e[t]={enabled:a,access:o},t==="read"){const s=(document.getElementById("api-read-fields")?.value||"").split(",").map(g=>g.trim()).filter(Boolean);s.length&&(e[t].fields=s)}}),e}function pe(){const e=["csv","json"].filter(t=>document.getElementById(`export-format-${t}`)?.checked);return{enabled:document.getElementById("export-enabled")?.checked??!0,access:document.getElementById("export-access")?.value||"subscriber",formats:e}}export const collectionEditorView={templateUrl:"/admin/js/templates/collection-editor.html",async onMount(e){x=[],$=null,q=!0;const t=window.location.hash.match(/\/collections\/edit\/([^/?#]+)/);t&&($=t[1],q=!1),E.tabs(e.find("#collection-tabs").get(0)),e.find("#collection-layout").get(0)?.addEventListener("change",function(){const l=this.value==="grid";e.find("#collection-columns-group").get(0).style.display=l?"":"none",document.querySelectorAll(".fb-grid-row").forEach(n=>{n.style.display=l?"grid":"none"})});const a=e.find("#fields-list").get(0),o=e.find("#api-access-rows").get(0);X={};const s=e.find("#collection-project").get(0);if(s)try{(await B.projects.list()).forEach(n=>{const c=document.createElement("option");c.value=n.slug,c.textContent=n.name||n.slug,s.appendChild(c)})}catch{}const g=await B.collections.proStatus();g?.pro&&$!=="roles"&&(e.find("#storage-tab-btn").show(),ce(e,g.connections));let y={create:{enabled:!1,access:"admin"},read:{enabled:!0,access:"public"},update:{enabled:!1,access:"admin"},delete:{enabled:!1,access:"admin"}},b={enabled:!0,access:"subscriber",formats:["csv","json"]};if(q){const l=e.find("#field-title").get(0),n=e.find("#field-slug").get(0);l&&n&&(l.addEventListener("input",()=>{n.dataset.manual||(n.value=ne(l.value))}),n.addEventListener("input",()=>{n.dataset.manual="1"}))}else try{const l=await B.collections.get($);if(!l){E.toast("Collection not found.",{type:"error"}),R.navigate("/collections");return}const n=e.find("#editor-title-text").get(0);n&&(n.textContent=l.title),e.find("#field-title").val(l.title||""),e.find("#field-slug").val(l.slug||""),e.find("#field-slug").prop("readonly",!0),e.find("#slug-hint").get(0).textContent="Slug cannot be changed after creation.",e.find("#field-description").val(l.description||""),e.find("#collection-layout").val(l.layout||"stacked"),e.find("#collection-columns").val(l.columns||2),e.find("#collection-bundled").prop("checked",!!l.bundled),X=l.meta||{},e.find("#collection-project").val(l.meta?.project||""),e.find("#collection-columns-group").get(0).style.display=l.layout==="grid"?"":"none",x=l.fields||[],y=l.api||y,b=l.export||b,A=l.storage?.adapter||"file",l.storage&&(e.find("#storage-adapter").val(l.storage.adapter||"file"),e.find("#storage-adapter-label").text(l.storage.adapter==="mongodb"?"MongoDB":"File (default)"),l.storage.adapter==="mongodb"&&(e.find("#storage-connection-group").show(),e.find("#storage-connection").val(l.storage.connection||"default"),e.find("#storage-connection-label").text(l.storage.connection||"default"))),$==="roles"&&e.find("#storage-tab-btn").hide()}catch{E.toast("Failed to load collection.",{type:"error"}),R.navigate("/collections");return}O(a),ie(y,o,b),e.find("#add-field-btn").off("click").on("click",()=>{x=Z(),x.push({id:`field-${Date.now()}`,name:"",label:"",type:"string",required:!1,placeholder:"",helper:"",options:[],validation:[],logic:null}),O(a);const l=a.querySelectorAll(".fb-field-card");if(l.length){const n=l[l.length-1],c=n.querySelector(".fb-field-body"),h=n.querySelector(".fb-field-chevron");c&&(c.style.display=""),h&&(h.style.transform="rotate(180deg)"),n.querySelector(`#fb-label-${x.length-1}`)?.focus()}}),e.find("#save-collection-btn").off("click").on("click",async()=>{const l=e.find("#field-title").val().trim(),n=e.find("#field-slug").val().trim(),c=e.find("#field-description").val().trim();if(!l){E.toast("Title is required.",{type:"warning"});return}const h=Z(),w=re(),i=pe(),p=e.find("#collection-layout").val()||"stacked",u=parseInt(e.find("#collection-columns").val(),10)||2,m=se(),C=e.find("#collection-bundled").is(":checked"),L=e.find("#collection-project").val()||"",v={...X||{},project:L||null},f=e.find("#save-collection-btn");f.prop("disabled",!0);try{if(q){const r=await B.collections.create({title:l,slug:n,description:c,layout:p,columns:u,fields:h,api:w,export:i,storage:m,meta:v,...C?{bundled:!0}:{}});$=r.slug,A=m.adapter||"file",q=!1,E.toast("Collection created.",{type:"success"}),R.navigate(`/collections/edit/${r.slug}`)}else if((m.adapter||"file")!==A){let r=0;try{r=(await B.collections.listEntries($,{limit:1}))?.total??0}catch{}const T=A==="file"?`file \u2192 ${m.adapter}`:`${A} \u2192 ${m.adapter||"file"}`;if(r>0&&await E.confirm(`You changed the storage adapter (${T}).
|
|
3
|
+
`),j.appendChild(Y),j.appendChild(W);const k=document.createElement("div");k.className="fb-grid-row",k.style.gridTemplateColumns="1fr auto",k.style.gap=".6rem",k.style.alignItems="end",k.style.marginBottom=".6rem",k.style.display=document.getElementById("collection-layout")?.value==="grid"?"grid":"none";const G=document.createElement("div"),J=document.createElement("label");J.className="form-label",J.textContent="Column Span";const S=document.createElement("input");S.id=`fb-span-${t}`,S.type="number",S.className="form-input",S.min="1",S.max="6",S.value=e.span>1?String(e.span):"1",G.appendChild(J),G.appendChild(S);const K=document.createElement("div");K.style.cssText="padding-bottom:.35rem;";const U=document.createElement("label");U.style.cssText="display:flex;align-items:center;gap:.4rem;cursor:pointer;white-space:nowrap;";const P=document.createElement("input");return P.id=`fb-fullwidth-${t}`,P.type="checkbox",P.checked=!!e.fullWidth,U.appendChild(P),U.appendChild(document.createTextNode("Full Width")),K.appendChild(U),k.appendChild(G),k.appendChild(K),n.appendChild(c),n.appendChild(f),n.appendChild(j),n.appendChild(k),o.addEventListener("click",()=>{const d=n.style.display!=="none";n.style.display=d?"none":"",b.style.transform=d?"":"rotate(180deg)"}),a.appendChild(o),a.appendChild(n),a}function O(e){if(e){if(e.textContent="",x.length===0){const t=document.createElement("p");t.className="text-muted",t.id="fields-empty-msg",t.style.cssText="text-align:center;padding:2rem 0;",t.textContent='No fields yet. Click "Add Field" to get started.',e.appendChild(t);return}x.forEach((t,a)=>{e.appendChild(de(t,a))})}}function ie(e,t,a){t.textContent="",le.forEach(i=>{const p=e?.[i]||{enabled:!1,access:"admin"},u=document.createElement("div");u.style.cssText="display:grid;grid-template-columns:140px 1fr 160px;gap:.75rem;align-items:center;padding:.6rem 0;border-bottom:1px solid var(--dm-border,#333);";const m=document.createElement("strong");m.textContent=i.charAt(0).toUpperCase()+i.slice(1),m.style.cssText="font-size:.9rem;";const C=document.createElement("label");C.style.cssText="display:flex;align-items:center;gap:.45rem;cursor:pointer;font-size:.875rem;";const L=document.createElement("input");L.type="checkbox",L.id=`api-${i}-enabled`,L.checked=!!p.enabled,C.appendChild(L),C.appendChild(document.createTextNode("Enable public access"));const v=document.createElement("select");if(v.id=`api-${i}-access`,v.className="form-input",te.forEach(f=>{const r=document.createElement("option");r.value=f,r.textContent=f.charAt(0).toUpperCase()+f.slice(1),f===p.access&&(r.selected=!0),v.appendChild(r)}),u.appendChild(m),u.appendChild(C),u.appendChild(v),t.appendChild(u),i==="read"){const f=document.createElement("div");f.style.cssText="display:grid;grid-template-columns:140px 1fr;gap:.75rem;align-items:center;padding:.6rem 0;border-bottom:1px solid var(--dm-border,#333);";const r=document.createElement("label");r.htmlFor="api-read-fields",r.style.cssText="font-size:.85rem;color:var(--dm-text-muted,#aaa);",r.textContent="Read fields";const T=document.createElement("input");T.type="text",T.id="api-read-fields",T.className="form-input",T.placeholder="Comma-separated allowlist - empty = all fields",T.value=Array.isArray(p.fields)?p.fields.join(", "):"",f.appendChild(r),f.appendChild(T),t.appendChild(f)}});const o=a||{},s=document.createElement("div");s.style.cssText="display:grid;grid-template-columns:140px 1fr 160px;gap:.75rem;align-items:center;padding:.6rem 0;border-top:2px solid var(--dm-border,#333);";const g=document.createElement("strong");g.textContent="Export",g.style.cssText="font-size:.9rem;";const y=document.createElement("label");y.style.cssText="display:flex;align-items:center;gap:.45rem;cursor:pointer;font-size:.875rem;";const b=document.createElement("input");b.type="checkbox",b.id="export-enabled",b.checked=o.enabled!==!1,y.appendChild(b),y.appendChild(document.createTextNode("Allow export"));const l=document.createElement("select");l.id="export-access",l.className="form-input",te.filter(i=>i!=="token").forEach(i=>{const p=document.createElement("option");p.value=i,p.textContent=i.charAt(0).toUpperCase()+i.slice(1),i===o.access&&(p.selected=!0),l.appendChild(p)}),s.appendChild(g),s.appendChild(y),s.appendChild(l),t.appendChild(s);const n=document.createElement("div");n.style.cssText="display:grid;grid-template-columns:140px 1fr;gap:.75rem;align-items:center;padding:.6rem 0;border-bottom:1px solid var(--dm-border,#333);";const c=document.createElement("label");c.style.cssText="font-size:.85rem;color:var(--dm-text-muted,#aaa);",c.textContent="Formats";const h=document.createElement("div");h.style.cssText="display:flex;gap:1rem;";const w=Array.isArray(o.formats)?o.formats:["csv","json"];["csv","json"].forEach(i=>{const p=document.createElement("label");p.style.cssText="display:flex;align-items:center;gap:.35rem;cursor:pointer;font-size:.875rem;";const u=document.createElement("input");u.type="checkbox",u.id=`export-format-${i}`,u.checked=w.includes(i),p.appendChild(u),p.appendChild(document.createTextNode(i.toUpperCase())),h.appendChild(p)}),n.appendChild(c),n.appendChild(h),t.appendChild(n)}function ce(e,t){E.dropdown("#storage-adapter-trigger",{items:[{label:"File (default)",value:"file"},{label:"MongoDB",value:"mongodb"}],onSelect:({item:o})=>{e.find("#storage-adapter").val(o.value),e.find("#storage-adapter-label").text(o.label);const s=o.value==="mongodb";e.find("#storage-connection-group").toggle(s),e.find("#storage-migration-warning").toggle(s&&!q)}});const a=t.map(o=>({label:o,value:o}));E.dropdown("#storage-connection-trigger",{items:a.length?a:[{label:"default",value:"default"}],onSelect:({item:o})=>{e.find("#storage-connection").val(o.value),e.find("#storage-connection-label").text(o.label)}})}function se(){return(document.getElementById("storage-adapter")?.value||"file")==="mongodb"?{adapter:"mongodb",connection:document.getElementById("storage-connection")?.value||"default"}:{adapter:"file"}}function re(){const e={};return le.forEach(t=>{const a=document.getElementById(`api-${t}-enabled`)?.checked??!1,o=document.getElementById(`api-${t}-access`)?.value||"admin";if(e[t]={enabled:a,access:o},t==="read"){const s=(document.getElementById("api-read-fields")?.value||"").split(",").map(g=>g.trim()).filter(Boolean);s.length&&(e[t].fields=s)}}),e}function pe(){const e=["csv","json"].filter(t=>document.getElementById(`export-format-${t}`)?.checked);return{enabled:document.getElementById("export-enabled")?.checked??!0,access:document.getElementById("export-access")?.value||"subscriber",formats:e}}export const collectionEditorView={templateUrl:"/admin/js/templates/collection-editor.html",async onMount(e){x=[],$=null,q=!0;const t=window.location.hash.match(/\/collections\/edit\/([^/?#]+)/);t&&($=t[1],q=!1),E.tabs(e.find("#collection-tabs").get(0)),e.find("#collection-layout").get(0)?.addEventListener("change",function(){const l=this.value==="grid";e.find("#collection-columns-group").get(0).style.display=l?"":"none",document.querySelectorAll(".fb-grid-row").forEach(n=>{n.style.display=l?"grid":"none"})});const a=e.find("#fields-list").get(0),o=e.find("#api-access-rows").get(0);X={};const s=e.find("#collection-project").get(0);if(s)try{(await B.projects.list()).forEach(n=>{const c=document.createElement("option");c.value=n.slug,c.textContent=n.name||n.slug,s.appendChild(c)})}catch{}const g=await B.collections.proStatus();g?.pro&&$!=="roles"&&(e.find("#storage-tab-btn").show(),ce(e,g.connections));let y={create:{enabled:!1,access:"admin"},read:{enabled:!0,access:"public"},update:{enabled:!1,access:"admin"},delete:{enabled:!1,access:"admin"}},b={enabled:!0,access:"subscriber",formats:["csv","json"]};if(q){const l=e.find("#field-title").get(0),n=e.find("#field-slug").get(0);l&&n&&(l.addEventListener("input",()=>{n.dataset.manual||(n.value=ne(l.value))}),n.addEventListener("input",()=>{n.dataset.manual="1"}))}else try{const l=await B.collections.get($);if(!l){E.toast("Collection not found.",{type:"error"}),R.navigate("/collections");return}const n=e.find("#editor-title-text").get(0);n&&(n.textContent=l.title),e.find("#field-title").val(l.title||""),e.find("#field-slug").val(l.slug||""),e.find("#field-slug").prop("readonly",!0),e.find("#slug-hint").get(0).textContent="Slug cannot be changed after creation.",e.find("#field-description").val(l.description||""),e.find("#collection-layout").val(l.layout||"stacked"),e.find("#collection-columns").val(l.columns||2),e.find("#collection-bundled").prop("checked",!!l.bundled),X=l.meta||{},e.find("#collection-project").val(l.meta?.project||""),e.find("#collection-columns-group").get(0).style.display=l.layout==="grid"?"":"none",x=l.fields||[],y=l.api||y,b=l.export||b,A=l.storage?.adapter||"file",l.storage&&(e.find("#storage-adapter").val(l.storage.adapter||"file"),e.find("#storage-adapter-label").text(l.storage.adapter==="mongodb"?"MongoDB":"File (default)"),l.storage.adapter==="mongodb"&&(e.find("#storage-connection-group").show(),e.find("#storage-connection").val(l.storage.connection||"default"),e.find("#storage-connection-label").text(l.storage.connection||"default"))),$==="roles"&&e.find("#storage-tab-btn").hide()}catch{E.toast("Failed to load collection.",{type:"error"}),R.navigate("/collections");return}O(a),ie(y,o,b),e.find("#add-field-btn").off("click").on("click",()=>{x=Z(),x.push({id:`field-${Date.now()}`,name:"",label:"",type:"string",required:!1,placeholder:"",helper:"",options:[],validation:[],logic:null}),O(a);const l=a.querySelectorAll(".fb-field-card");if(l.length){const n=l[l.length-1],c=n.querySelector(".fb-field-body"),h=n.querySelector(".fb-field-chevron");c&&(c.style.display=""),h&&(h.style.transform="rotate(180deg)"),n.querySelector(`#fb-label-${x.length-1}`)?.focus()}}),e.find("#save-collection-btn").off("click").on("click",async()=>{const l=e.find("#field-title").val().trim(),n=e.find("#field-slug").val().trim(),c=e.find("#field-description").val().trim();if(!l){E.toast("Title is required.",{type:"warning"});return}const h=Z(),w=re(),i=pe(),p=e.find("#collection-layout").val()||"stacked",u=parseInt(e.find("#collection-columns").val(),10)||2,m=se(),C=e.find("#collection-bundled").is(":checked"),L=e.find("#collection-project").val()||"",v={...X||{},project:L||null},f=e.find("#save-collection-btn");f.prop("disabled",!0);try{if(q){const r=await B.collections.create({title:l,slug:n,description:c,layout:p,columns:u,fields:h,api:w,export:i,storage:m,meta:v,...C?{bundled:!0}:{}});$=r.slug,A=m.adapter||"file",q=!1,E.toast("Collection created.",{type:"success"}),R.navigate(`/collections/edit/${r.slug}`)}else if((m.adapter||"file")!==A){let r=0;try{r=(await B.collections.listEntries($,{limit:1}))?.total??0}catch{}const T=A==="file"?`file \u2192 ${m.adapter}`:`${A} \u2192 ${m.adapter||"file"}`;if(r>0&&await E.confirm(`You changed the storage adapter (${T}).
|
|
4
4
|
|
|
5
5
|
Migrate ${r} existing ${r===1?"entry":"entries"} to the new storage?`)){const I=await B.collections.migrateStorage($,m);A=m.adapter||"file",E.toast(`Migrated ${I.migrated} of ${I.total} entries.`,{type:"success"})}else await B.collections.update($,{title:l,description:c,layout:p,columns:u,fields:h,api:w,export:i,storage:m,meta:v,...C?{bundled:!0}:{bundled:!1}}),A=m.adapter||"file",E.toast("Collection saved.",{type:"success"})}else await B.collections.update($,{title:l,description:c,layout:p,columns:u,fields:h,api:w,export:i,storage:m,meta:v,...C?{bundled:!0}:{bundled:!1}}),E.toast("Collection saved.",{type:"success"})}catch(r){E.toast(r.message||"Failed to save.",{type:"error"})}finally{f.prop("disabled",!1)}}),Domma.icons.scan()}};
|