@isaiahiroko/tilmart-ngx-apps-admin 0.2.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/README.md +9 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-articles.module-OeLRnPwN.mjs +154 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-articles.module-OeLRnPwN.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-audit.module-C9gvw8X2.mjs +84 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-audit.module-C9gvw8X2.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-baskets.module-_lHPl1dT.mjs +294 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-baskets.module-_lHPl1dT.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-catalog.module-Bvx2tVOi.mjs +224 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-catalog.module-Bvx2tVOi.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-claims.module-Ccsd8Yx3.mjs +106 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-claims.module-Ccsd8Yx3.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-compatibility-rules.module-xN-JUWwp.mjs +261 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-compatibility-rules.module-xN-JUWwp.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-content.module-DLdKAUqc.mjs +365 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-content.module-DLdKAUqc.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-coupons.module-CQNCSlsd.mjs +106 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-coupons.module-CQNCSlsd.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-customers.module-12XdV9zo.mjs +106 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-customers.module-12XdV9zo.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-data-quality.module-Bfgh3zE5.mjs +286 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-data-quality.module-Bfgh3zE5.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-data-requests.module-C6tPNlup.mjs +104 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-data-requests.module-C6tPNlup.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-disputes.module-DmUoAS0Z.mjs +73 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-disputes.module-DmUoAS0Z.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-homepage.module-BZ3UmxE3.mjs +404 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-homepage.module-BZ3UmxE3.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-inbox.module-BZDTa1L-.mjs +290 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-inbox.module-BZDTa1L-.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-macros.module-BO4bdER2.mjs +112 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-macros.module-BO4bdER2.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-moderation.module-CZOCrias.mjs +116 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-moderation.module-CZOCrias.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-navigation.module-DoKzzRgn.mjs +281 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-navigation.module-DoKzzRgn.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-order-board.module-DIOjhptn.mjs +115 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-order-board.module-DIOjhptn.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-orders.module-Ddw49QW0.mjs +185 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-orders.module-Ddw49QW0.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payments.module-BhqAtTZs.mjs +106 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payments.module-BhqAtTZs.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payouts.module-CoiscPIZ.mjs +70 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payouts.module-CoiscPIZ.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-procurement.module-qXW-g0pf.mjs +274 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-procurement.module-qXW-g0pf.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-promotions.module-DG9uTDqX.mjs +106 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-promotions.module-DG9uTDqX.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-queues.module-K-zYYVFy.mjs +84 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-queues.module-K-zYYVFy.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-redirects.module-Hm2dO_bC.mjs +106 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-redirects.module-Hm2dO_bC.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-referrals.module-QLNhuh7q.mjs +106 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-referrals.module-QLNhuh7q.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-reports.module-DBZov0We.mjs +104 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-reports.module-DBZov0We.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-returns.module-Bd2gdo6_.mjs +106 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-returns.module-Bd2gdo6_.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-rich-editor.module-DW9Gy_QQ.mjs +81 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-rich-editor.module-DW9Gy_QQ.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-routes.module-BkVY06ci.mjs +105 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-routes.module-BkVY06ci.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-settings.module-DKeZTldO.mjs +222 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-settings.module-DKeZTldO.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-shipments.module-XuaOKr8v.mjs +150 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-shipments.module-XuaOKr8v.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-stock.module-DYiAkF7E.mjs +107 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-stock.module-DYiAkF7E.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-trace.module-Pwr-82Rc.mjs +92 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-trace.module-Pwr-82Rc.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-users.module-Bx_7fI2g.mjs +111 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-users.module-Bx_7fI2g.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin.mjs +475 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin.mjs.map +1 -0
- package/package.json +34 -0
- package/types/isaiahiroko-tilmart-ngx-apps-admin.d.ts +32 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Component, NgModule } from '@angular/core';
|
|
3
|
+
import * as i1$2 from '@angular/router';
|
|
4
|
+
import { RouterModule } from '@angular/router';
|
|
5
|
+
import { BaseDirective, using, notify, SharedModule } from '@thescaffold/ngx-core';
|
|
6
|
+
import * as i1$1 from '@thescaffold/ngx-ui';
|
|
7
|
+
import { ElementsModule } from '@thescaffold/ngx-ui';
|
|
8
|
+
import { R as RichEditorComponent, a as RichEditorModule } from './isaiahiroko-tilmart-ngx-apps-admin-rich-editor.module-DW9Gy_QQ.mjs';
|
|
9
|
+
import { takeUntil, firstValueFrom } from 'rxjs';
|
|
10
|
+
import * as i1 from '@angular/forms';
|
|
11
|
+
|
|
12
|
+
const BLOCK_TYPES = [
|
|
13
|
+
{ value: 'rich_text', label: 'Rich text', fields: [{ key: 'html', label: 'Body', kind: 'rich' }] },
|
|
14
|
+
{
|
|
15
|
+
value: 'hero',
|
|
16
|
+
label: 'Hero',
|
|
17
|
+
fields: [
|
|
18
|
+
{ key: 'heading', label: 'Heading', kind: 'text' },
|
|
19
|
+
{ key: 'subheading', label: 'Subheading', kind: 'text' },
|
|
20
|
+
{ key: 'imageUrl', label: 'Image URL', kind: 'text' },
|
|
21
|
+
{ key: 'ctaLabel', label: 'Button label', kind: 'text' },
|
|
22
|
+
{ key: 'ctaUrl', label: 'Button URL', kind: 'text' },
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
value: 'cta',
|
|
27
|
+
label: 'Call to action',
|
|
28
|
+
fields: [
|
|
29
|
+
{ key: 'heading', label: 'Heading', kind: 'text' },
|
|
30
|
+
{ key: 'body', label: 'Body', kind: 'text' },
|
|
31
|
+
{ key: 'buttonLabel', label: 'Button label', kind: 'text' },
|
|
32
|
+
{ key: 'buttonUrl', label: 'Button URL', kind: 'text' },
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
value: 'product_rail',
|
|
37
|
+
label: 'Product rail',
|
|
38
|
+
fields: [
|
|
39
|
+
{ key: 'categoryId', label: 'From category ID', kind: 'id' },
|
|
40
|
+
{ key: 'productIds', label: 'Or product IDs', kind: 'ids' },
|
|
41
|
+
{ key: 'limit', label: 'Max items', kind: 'num' },
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
value: 'category_grid',
|
|
46
|
+
label: 'Category grid',
|
|
47
|
+
fields: [{ key: 'categoryIds', label: 'Category IDs', kind: 'ids' }],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
value: 'image',
|
|
51
|
+
label: 'Image',
|
|
52
|
+
fields: [
|
|
53
|
+
{ key: 'url', label: 'Image URL', kind: 'text' },
|
|
54
|
+
{ key: 'alt', label: 'Alt text', kind: 'text' },
|
|
55
|
+
{ key: 'linkUrl', label: 'Links to', kind: 'text' },
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
{ value: 'gallery', label: 'Gallery', fields: [{ key: 'imageUrls', label: 'Image URLs', kind: 'ids' }] },
|
|
59
|
+
{ value: 'faq', label: 'FAQ', fields: [{ key: 'items', label: 'Items — [{"q":"…","a":"…"}]', kind: 'json' }] },
|
|
60
|
+
{ value: 'columns', label: 'Columns', fields: [{ key: 'columns', label: 'Columns JSON', kind: 'json' }] },
|
|
61
|
+
{
|
|
62
|
+
value: 'embed',
|
|
63
|
+
label: 'Embed',
|
|
64
|
+
fields: [
|
|
65
|
+
{ key: 'url', label: 'Embed URL', kind: 'text' },
|
|
66
|
+
{ key: 'html', label: 'Or raw HTML', kind: 'text' },
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
const STATUSES = ['draft', 'published', 'archived'];
|
|
71
|
+
const TEMPLATES = ['default', 'landing', 'full-width'];
|
|
72
|
+
// P6.10, CMS-01 — the CMS page builder. Left: the Page list. Right: the
|
|
73
|
+
// selected page's metadata + its drag-ordered ContentBlock list, each
|
|
74
|
+
// with a type-aware editor (`rich_text` uses the `@thescaffold/editor-*`
|
|
75
|
+
// WYSIWYG). The storefront page renderer is a later task; ops CRUD is
|
|
76
|
+
// covered by `admin:*:apps:commerce:*`.
|
|
77
|
+
class ContentComponent extends BaseDirective {
|
|
78
|
+
constructor() {
|
|
79
|
+
super(...arguments);
|
|
80
|
+
this.pageSource = this.apiService.on('apps/commerce/page');
|
|
81
|
+
this.blockSource = this.apiService.on('apps/commerce/content-block');
|
|
82
|
+
this.blockTypes = BLOCK_TYPES;
|
|
83
|
+
this.statuses = STATUSES;
|
|
84
|
+
this.templates = TEMPLATES;
|
|
85
|
+
this.loading = true;
|
|
86
|
+
this.pages = [];
|
|
87
|
+
this.blocks = [];
|
|
88
|
+
this.blocksLoading = false;
|
|
89
|
+
this.newBlockType = BLOCK_TYPES[0].value;
|
|
90
|
+
this.blockModel = {};
|
|
91
|
+
this.rawHtml = false;
|
|
92
|
+
this.newPageSlug = '';
|
|
93
|
+
this.newPageTitle = '';
|
|
94
|
+
this.dragIndex = null;
|
|
95
|
+
this.init = () => {
|
|
96
|
+
this.loadPages();
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
blockTypeLabel(v) {
|
|
100
|
+
return BLOCK_TYPES.find((t) => t.value === v)?.label ?? v;
|
|
101
|
+
}
|
|
102
|
+
blockTypeDef(v) {
|
|
103
|
+
return BLOCK_TYPES.find((t) => t.value === v);
|
|
104
|
+
}
|
|
105
|
+
get orderedBlocks() {
|
|
106
|
+
return [...this.blocks].sort((a, b) => a.position - b.position);
|
|
107
|
+
}
|
|
108
|
+
// ── pages ─────────────────────────────────────────────────────────
|
|
109
|
+
loadPages() {
|
|
110
|
+
this.loading = true;
|
|
111
|
+
this.pageSource
|
|
112
|
+
.getMany({ perPage: 200 })
|
|
113
|
+
.pipe(takeUntil(this.$))
|
|
114
|
+
.subscribe(using((res) => {
|
|
115
|
+
this.pages = (res?.data ?? []).sort((a, b) => String(a.title).localeCompare(String(b.title)));
|
|
116
|
+
this.loading = false;
|
|
117
|
+
if (this.selected) {
|
|
118
|
+
this.selected = this.pages.find((p) => p.id === this.selected.id);
|
|
119
|
+
}
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
async createPage() {
|
|
123
|
+
const slug = this.newPageSlug.trim().toLowerCase().replace(/\s+/g, '-');
|
|
124
|
+
const title = this.newPageTitle.trim();
|
|
125
|
+
if (!slug || !title)
|
|
126
|
+
return;
|
|
127
|
+
try {
|
|
128
|
+
const res = await firstValueFrom(this.pageSource.create({ slug, title, status: 'draft', template: 'default' }));
|
|
129
|
+
this.newPageSlug = '';
|
|
130
|
+
this.newPageTitle = '';
|
|
131
|
+
notify('Page created.', undefined, 'success');
|
|
132
|
+
this.loadPages();
|
|
133
|
+
if (res?.data?.id)
|
|
134
|
+
setTimeout(() => this.select({ ...res.data }), 200);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
notify('Could not create the page (slug may be taken).', undefined, 'error');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
select(page) {
|
|
141
|
+
this.selected = page;
|
|
142
|
+
this.editingBlock = undefined;
|
|
143
|
+
this.loadBlocks();
|
|
144
|
+
}
|
|
145
|
+
async savePage() {
|
|
146
|
+
if (!this.selected)
|
|
147
|
+
return;
|
|
148
|
+
const p = this.selected;
|
|
149
|
+
try {
|
|
150
|
+
await firstValueFrom(this.pageSource.update(p.id, {
|
|
151
|
+
slug: p.slug,
|
|
152
|
+
title: p.title,
|
|
153
|
+
excerpt: p.excerpt || undefined,
|
|
154
|
+
status: p.status,
|
|
155
|
+
template: p.template,
|
|
156
|
+
publishAt: p.publishAt ? new Date(p.publishAt).toISOString() : null,
|
|
157
|
+
}));
|
|
158
|
+
notify('Page saved.', undefined, 'success');
|
|
159
|
+
this.loadPages();
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
notify('Could not save the page.', undefined, 'error');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async deletePage() {
|
|
166
|
+
if (!this.selected)
|
|
167
|
+
return;
|
|
168
|
+
try {
|
|
169
|
+
await firstValueFrom(this.pageSource.remove(this.selected.id));
|
|
170
|
+
this.selected = undefined;
|
|
171
|
+
this.blocks = [];
|
|
172
|
+
notify('Page deleted.', undefined, 'success');
|
|
173
|
+
this.loadPages();
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
notify('Could not delete the page.', undefined, 'error');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// ── blocks ────────────────────────────────────────────────────────
|
|
180
|
+
loadBlocks() {
|
|
181
|
+
if (!this.selected)
|
|
182
|
+
return;
|
|
183
|
+
this.blocksLoading = true;
|
|
184
|
+
this.blockSource
|
|
185
|
+
.getMany({ pageId: this.selected.id, perPage: 200 })
|
|
186
|
+
.pipe(takeUntil(this.$))
|
|
187
|
+
.subscribe(using((res) => {
|
|
188
|
+
this.blocks = (res?.data ?? [])
|
|
189
|
+
.filter((b) => b.pageId === this.selected.id)
|
|
190
|
+
.map((b) => ({ ...b, config: b.config ?? {} }))
|
|
191
|
+
.sort((a, b) => a.position - b.position);
|
|
192
|
+
this.blocksLoading = false;
|
|
193
|
+
}));
|
|
194
|
+
}
|
|
195
|
+
async addBlock() {
|
|
196
|
+
if (!this.selected)
|
|
197
|
+
return;
|
|
198
|
+
try {
|
|
199
|
+
await firstValueFrom(this.blockSource.create({
|
|
200
|
+
pageId: this.selected.id,
|
|
201
|
+
type: this.newBlockType,
|
|
202
|
+
position: this.blocks.length,
|
|
203
|
+
config: {},
|
|
204
|
+
enabled: true,
|
|
205
|
+
}));
|
|
206
|
+
this.loadBlocks();
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
notify('Could not add the block.', undefined, 'error');
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
editBlock(b) {
|
|
213
|
+
this.editingBlock = b;
|
|
214
|
+
this.rawHtml = false;
|
|
215
|
+
this.blockModel = {};
|
|
216
|
+
for (const field of this.blockTypeDef(b.type)?.fields ?? []) {
|
|
217
|
+
const value = (b.config ?? {})[field.key];
|
|
218
|
+
this.blockModel[field.key] =
|
|
219
|
+
field.kind === 'json'
|
|
220
|
+
? JSON.stringify(value ?? (field.key === 'items' || field.key === 'columns' ? [] : {}), null, 2)
|
|
221
|
+
: Array.isArray(value)
|
|
222
|
+
? value.join(', ')
|
|
223
|
+
: value != null
|
|
224
|
+
? String(value)
|
|
225
|
+
: '';
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
cancelBlockEdit() {
|
|
229
|
+
this.editingBlock = undefined;
|
|
230
|
+
this.blockModel = {};
|
|
231
|
+
}
|
|
232
|
+
async saveBlock() {
|
|
233
|
+
if (!this.editingBlock)
|
|
234
|
+
return;
|
|
235
|
+
const def = this.blockTypeDef(this.editingBlock.type);
|
|
236
|
+
const config = { ...(this.editingBlock.config ?? {}) };
|
|
237
|
+
for (const field of def?.fields ?? []) {
|
|
238
|
+
const raw = (this.blockModel[field.key] ?? '').trim();
|
|
239
|
+
if (field.kind === 'ids') {
|
|
240
|
+
config[field.key] = raw ? raw.split(',').map((s) => s.trim()).filter(Boolean) : [];
|
|
241
|
+
}
|
|
242
|
+
else if (field.kind === 'num') {
|
|
243
|
+
config[field.key] = raw ? Number(raw) : undefined;
|
|
244
|
+
}
|
|
245
|
+
else if (field.kind === 'json') {
|
|
246
|
+
try {
|
|
247
|
+
config[field.key] = raw ? JSON.parse(raw) : [];
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
notify(`"${field.label}" is not valid JSON.`, undefined, 'error');
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
config[field.key] = raw || undefined;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
await firstValueFrom(this.blockSource.update(this.editingBlock.id, { config }));
|
|
260
|
+
this.editingBlock.config = config;
|
|
261
|
+
notify('Block saved.', undefined, 'success');
|
|
262
|
+
this.cancelBlockEdit();
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
notify('Could not save the block.', undefined, 'error');
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
async toggleBlock(b) {
|
|
269
|
+
const next = !b.enabled;
|
|
270
|
+
try {
|
|
271
|
+
await firstValueFrom(this.blockSource.update(b.id, { enabled: next }));
|
|
272
|
+
b.enabled = next;
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
notify('Could not update the block.', undefined, 'error');
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
async removeBlock(b) {
|
|
279
|
+
try {
|
|
280
|
+
await firstValueFrom(this.blockSource.remove(b.id));
|
|
281
|
+
this.blocks = this.blocks.filter((x) => x.id !== b.id);
|
|
282
|
+
if (this.editingBlock?.id === b.id)
|
|
283
|
+
this.cancelBlockEdit();
|
|
284
|
+
notify('Block removed.', undefined, 'success');
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
notify('Could not remove the block.', undefined, 'error');
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
onDragStart(index) {
|
|
291
|
+
this.dragIndex = index;
|
|
292
|
+
}
|
|
293
|
+
onDragOver(event) {
|
|
294
|
+
event.preventDefault();
|
|
295
|
+
}
|
|
296
|
+
onDrop(targetIndex) {
|
|
297
|
+
if (this.dragIndex === null || this.dragIndex === targetIndex)
|
|
298
|
+
return;
|
|
299
|
+
const ordered = this.orderedBlocks;
|
|
300
|
+
const [moved] = ordered.splice(this.dragIndex, 1);
|
|
301
|
+
ordered.splice(targetIndex, 0, moved);
|
|
302
|
+
this.dragIndex = null;
|
|
303
|
+
void this.persistOrder(ordered);
|
|
304
|
+
}
|
|
305
|
+
moveBlock(index, dir) {
|
|
306
|
+
const ordered = this.orderedBlocks;
|
|
307
|
+
const target = index + dir;
|
|
308
|
+
if (target < 0 || target >= ordered.length)
|
|
309
|
+
return;
|
|
310
|
+
[ordered[index], ordered[target]] = [ordered[target], ordered[index]];
|
|
311
|
+
void this.persistOrder(ordered);
|
|
312
|
+
}
|
|
313
|
+
async persistOrder(ordered) {
|
|
314
|
+
const changed = [];
|
|
315
|
+
ordered.forEach((b, i) => {
|
|
316
|
+
if (b.position !== i) {
|
|
317
|
+
b.position = i;
|
|
318
|
+
changed.push(b);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
this.blocks = ordered;
|
|
322
|
+
try {
|
|
323
|
+
await Promise.all(changed.map((b) => firstValueFrom(this.blockSource.update(b.id, { position: b.position }))));
|
|
324
|
+
}
|
|
325
|
+
catch {
|
|
326
|
+
notify('Could not save the new order.', undefined, 'error');
|
|
327
|
+
this.loadBlocks();
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
onRichChange(field, html) {
|
|
331
|
+
this.blockModel[field] = html ?? '';
|
|
332
|
+
}
|
|
333
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: ContentComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
334
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: ContentComponent, isStandalone: false, selector: "x-content", usesInheritance: true, ngImport: i0, template: "<div class=\"x-cms-head\">\n <div class=\"x-cms-title\">Content pages</div>\n <div class=\"x-cms-sub\">Ops-authored storefront pages \u2014 metadata and a block list per page.</div>\n</div>\n\n@if (loading) {\n <x-element-loader></x-element-loader>\n} @else {\n <div class=\"x-cms-layout\">\n <aside class=\"x-cms-list\">\n <div class=\"x-cms-new\">\n <input type=\"text\" placeholder=\"slug\" [(ngModel)]=\"newPageSlug\" />\n <input type=\"text\" placeholder=\"Title\" [(ngModel)]=\"newPageTitle\" />\n <button type=\"button\" (click)=\"createPage()\">Add page</button>\n </div>\n @for (p of pages; track p.id) {\n <button\n type=\"button\"\n class=\"x-cms-list-item\"\n [class.x-cms-list-active]=\"selected?.id === p.id\"\n (click)=\"select(p)\"\n >\n <span class=\"x-cms-list-title\">{{ p.title }}</span>\n <span class=\"x-cms-list-meta\">/{{ p.slug }} \u00B7 {{ p.status }}</span>\n </button>\n } @empty {\n <div class=\"x-cms-empty\">No pages yet.</div>\n }\n </aside>\n\n <section class=\"x-cms-editor\">\n @if (!selected) {\n <div class=\"x-cms-empty\">Pick a page, or add one.</div>\n } @else {\n <div class=\"x-cms-meta\">\n <div class=\"x-cms-f-row\">\n <label class=\"x-cms-f\">\n Title\n <input type=\"text\" [(ngModel)]=\"selected.title\" />\n </label>\n <label class=\"x-cms-f\">\n Slug\n <input type=\"text\" [(ngModel)]=\"selected.slug\" />\n </label>\n </div>\n <label class=\"x-cms-f\">\n Excerpt\n <input type=\"text\" [(ngModel)]=\"selected.excerpt\" />\n </label>\n <div class=\"x-cms-f-row\">\n <label class=\"x-cms-f\">\n Status\n <select [(ngModel)]=\"selected.status\">\n @for (s of statuses; track s) { <option [value]=\"s\">{{ s }}</option> }\n </select>\n </label>\n <label class=\"x-cms-f\">\n Template\n <select [(ngModel)]=\"selected.template\">\n @for (t of templates; track t) { <option [value]=\"t\">{{ t }}</option> }\n </select>\n </label>\n <label class=\"x-cms-f\">\n Publish at\n <input type=\"datetime-local\" [(ngModel)]=\"selected.publishAt\" />\n </label>\n </div>\n <div class=\"x-cms-meta-actions\">\n <button type=\"button\" class=\"x-cms-btn x-cms-btn-primary\" (click)=\"savePage()\">Save page</button>\n <button type=\"button\" class=\"x-cms-btn x-cms-danger\" (click)=\"deletePage()\">Delete page</button>\n </div>\n </div>\n\n <div class=\"x-cms-blocks-head\">\n <span>Blocks</span>\n <span class=\"x-cms-add-block\">\n <select [(ngModel)]=\"newBlockType\">\n @for (t of blockTypes; track t.value) { <option [value]=\"t.value\">{{ t.label }}</option> }\n </select>\n <button type=\"button\" class=\"x-cms-btn\" (click)=\"addBlock()\">Add block</button>\n </span>\n </div>\n\n @if (blocksLoading) {\n <x-element-loader></x-element-loader>\n } @else if (!orderedBlocks.length) {\n <div class=\"x-cms-empty\">No blocks on this page yet.</div>\n } @else {\n <div class=\"x-cms-blocks\">\n @for (b of orderedBlocks; track b.id; let i = $index) {\n <div\n class=\"x-cms-block\"\n [class.x-cms-block-off]=\"!b.enabled\"\n draggable=\"true\"\n (dragstart)=\"onDragStart(i)\"\n (dragover)=\"onDragOver($event)\"\n (drop)=\"onDrop(i)\"\n >\n <div class=\"x-cms-block-head\">\n <span class=\"x-cms-grip\">\u283F</span>\n <div class=\"x-cms-block-order\">\n <button type=\"button\" (click)=\"moveBlock(i, -1)\" [disabled]=\"i === 0\">\u25B2</button>\n <button type=\"button\" (click)=\"moveBlock(i, 1)\" [disabled]=\"i === orderedBlocks.length - 1\">\u25BC</button>\n </div>\n <span class=\"x-cms-block-type\">{{ blockTypeLabel(b.type) }}</span>\n <span class=\"x-cms-block-actions\">\n <button type=\"button\" class=\"x-cms-toggle\" (click)=\"toggleBlock(b)\">{{ b.enabled ? 'On' : 'Off' }}</button>\n <button type=\"button\" class=\"x-cms-link\" (click)=\"editBlock(b)\">Edit</button>\n <button type=\"button\" class=\"x-cms-link x-cms-danger\" (click)=\"removeBlock(b)\">Delete</button>\n </span>\n </div>\n\n @if (editingBlock?.id === b.id) {\n <div class=\"x-cms-block-edit\">\n @for (field of blockTypeDef(b.type)?.fields ?? []; track field.key) {\n <label class=\"x-cms-f\">\n {{ field.label }}\n @if (field.kind === 'rich') {\n @if (rawHtml) {\n <textarea rows=\"6\" [(ngModel)]=\"blockModel[field.key]\"></textarea>\n } @else {\n <x-rich-editor\n [content]=\"blockModel[field.key]\"\n (contentChange)=\"onRichChange(field.key, $event)\"\n ></x-rich-editor>\n }\n <button type=\"button\" class=\"x-cms-link\" (click)=\"rawHtml = !rawHtml\">\n {{ rawHtml ? 'Use the editor' : 'Edit raw HTML' }}\n </button>\n } @else if (field.kind === 'json') {\n <textarea rows=\"5\" [(ngModel)]=\"blockModel[field.key]\"></textarea>\n } @else {\n <input\n [type]=\"field.kind === 'num' ? 'number' : 'text'\"\n [(ngModel)]=\"blockModel[field.key]\"\n />\n }\n </label>\n }\n <div class=\"x-cms-edit-actions\">\n <button type=\"button\" class=\"x-cms-btn x-cms-btn-primary\" (click)=\"saveBlock()\">Save block</button>\n <button type=\"button\" class=\"x-cms-btn\" (click)=\"cancelBlockEdit()\">Cancel</button>\n </div>\n </div>\n }\n </div>\n }\n </div>\n }\n }\n </section>\n </div>\n}\n\n<style>\n .x-cms-head { margin-bottom: 20px; }\n .x-cms-title { font-size: 24px; font-weight: 800; color: var(--color-text); }\n .x-cms-sub { font-size: 13.5px; color: var(--color-text-muted); margin-top: 2px; }\n .x-cms-empty { font-size: 14px; color: var(--color-text-muted); padding: 12px 0; }\n .x-cms-layout { display: grid; grid-template-columns: 260px 1fr; gap: 20px; align-items: start; }\n .x-cms-list { display: flex; flex-direction: column; gap: 6px; }\n .x-cms-new { display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; }\n .x-cms-new input, .x-cms-f input, .x-cms-f select, .x-cms-f textarea,\n .x-cms-add-block select {\n border: 1px solid var(--color-border); border-radius: var(--radius-md);\n padding: 6px 10px; font-size: 13px; background: var(--color-surface); color: var(--color-text);\n }\n .x-cms-new button, .x-cms-btn {\n border: 1px solid var(--color-border); background: transparent; border-radius: var(--radius-md);\n padding: 6px 14px; font-size: 12.5px; font-weight: 700; cursor: pointer; color: var(--color-text-muted);\n }\n .x-cms-btn-primary { border-color: var(--color-accent); color: var(--color-accent-hover); }\n .x-cms-list-item {\n text-align: left; border: 1px solid var(--color-border); border-radius: var(--radius-md);\n background: var(--color-surface); padding: 8px 10px; cursor: pointer;\n display: flex; flex-direction: column; gap: 2px;\n }\n .x-cms-list-active { border-color: var(--color-accent); }\n .x-cms-list-title { font-size: 13px; font-weight: 700; color: var(--color-text); }\n .x-cms-list-meta { font-size: 11px; color: var(--color-text-muted); }\n .x-cms-editor { min-width: 0; }\n .x-cms-meta {\n background: var(--color-surface); border: 1px solid var(--color-border);\n border-radius: var(--radius-lg); padding: 16px; display: flex; flex-direction: column; gap: 10px;\n margin-bottom: 20px;\n }\n .x-cms-f { display: flex; flex-direction: column; gap: 4px; flex: 1; font-size: 11.5px; color: var(--color-text-muted); font-weight: 700; }\n .x-cms-f-row { display: flex; gap: 10px; }\n .x-cms-meta-actions, .x-cms-edit-actions { display: flex; gap: 8px; }\n .x-cms-blocks-head {\n display: flex; justify-content: space-between; align-items: center;\n font-size: 13px; font-weight: 800; color: var(--color-text); margin-bottom: 10px;\n }\n .x-cms-add-block { display: flex; gap: 8px; }\n .x-cms-blocks { display: flex; flex-direction: column; gap: 10px; }\n .x-cms-block { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: 12px 14px; }\n .x-cms-block-off { opacity: 0.55; }\n .x-cms-block-head { display: flex; align-items: center; gap: 10px; }\n .x-cms-grip { cursor: grab; color: var(--color-text-muted); }\n .x-cms-block-order { display: flex; flex-direction: column; }\n .x-cms-block-order button { border: 0; background: transparent; cursor: pointer; color: var(--color-text-muted); font-size: 10px; line-height: 1.1; padding: 1px 3px; }\n .x-cms-block-order button:disabled { opacity: 0.3; cursor: default; }\n .x-cms-block-type { font-weight: 800; text-transform: uppercase; letter-spacing: 0.4px; font-size: 11px; color: var(--color-accent-hover); flex: 1; }\n .x-cms-block-actions { display: flex; gap: 8px; align-items: center; }\n .x-cms-toggle { border: 1px solid var(--color-border); border-radius: var(--radius-sm); background: transparent; padding: 3px 10px; font-size: 11.5px; font-weight: 700; cursor: pointer; color: var(--color-text-muted); }\n .x-cms-link { border: 0; background: transparent; cursor: pointer; font-size: 12px; font-weight: 700; color: var(--color-accent-hover); }\n .x-cms-danger { color: var(--color-danger, #c0362c); }\n .x-cms-block-edit { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--color-border); display: flex; flex-direction: column; gap: 10px; }\n x-rich-editor { display: block; border: 1px solid var(--color-border); border-radius: var(--radius-md); min-height: 160px; }\n</style>\n", styles: [".x-cms-head{margin-bottom:20px}.x-cms-title{font-size:24px;font-weight:800;color:var(--color-text)}.x-cms-sub{font-size:13.5px;color:var(--color-text-muted);margin-top:2px}.x-cms-empty{font-size:14px;color:var(--color-text-muted);padding:12px 0}.x-cms-layout{display:grid;grid-template-columns:260px 1fr;gap:20px;align-items:start}.x-cms-list{display:flex;flex-direction:column;gap:6px}.x-cms-new{display:flex;flex-direction:column;gap:6px;margin-bottom:10px}.x-cms-new input,.x-cms-f input,.x-cms-f select,.x-cms-f textarea,.x-cms-add-block select{border:1px solid var(--color-border);border-radius:var(--radius-md);padding:6px 10px;font-size:13px;background:var(--color-surface);color:var(--color-text)}.x-cms-new button,.x-cms-btn{border:1px solid var(--color-border);background:transparent;border-radius:var(--radius-md);padding:6px 14px;font-size:12.5px;font-weight:700;cursor:pointer;color:var(--color-text-muted)}.x-cms-btn-primary{border-color:var(--color-accent);color:var(--color-accent-hover)}.x-cms-list-item{text-align:left;border:1px solid var(--color-border);border-radius:var(--radius-md);background:var(--color-surface);padding:8px 10px;cursor:pointer;display:flex;flex-direction:column;gap:2px}.x-cms-list-active{border-color:var(--color-accent)}.x-cms-list-title{font-size:13px;font-weight:700;color:var(--color-text)}.x-cms-list-meta{font-size:11px;color:var(--color-text-muted)}.x-cms-editor{min-width:0}.x-cms-meta{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:16px;display:flex;flex-direction:column;gap:10px;margin-bottom:20px}.x-cms-f{display:flex;flex-direction:column;gap:4px;flex:1;font-size:11.5px;color:var(--color-text-muted);font-weight:700}.x-cms-f-row{display:flex;gap:10px}.x-cms-meta-actions,.x-cms-edit-actions{display:flex;gap:8px}.x-cms-blocks-head{display:flex;justify-content:space-between;align-items:center;font-size:13px;font-weight:800;color:var(--color-text);margin-bottom:10px}.x-cms-add-block{display:flex;gap:8px}.x-cms-blocks{display:flex;flex-direction:column;gap:10px}.x-cms-block{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:12px 14px}.x-cms-block-off{opacity:.55}.x-cms-block-head{display:flex;align-items:center;gap:10px}.x-cms-grip{cursor:grab;color:var(--color-text-muted)}.x-cms-block-order{display:flex;flex-direction:column}.x-cms-block-order button{border:0;background:transparent;cursor:pointer;color:var(--color-text-muted);font-size:10px;line-height:1.1;padding:1px 3px}.x-cms-block-order button:disabled{opacity:.3;cursor:default}.x-cms-block-type{font-weight:800;text-transform:uppercase;letter-spacing:.4px;font-size:11px;color:var(--color-accent-hover);flex:1}.x-cms-block-actions{display:flex;gap:8px;align-items:center}.x-cms-toggle{border:1px solid var(--color-border);border-radius:var(--radius-sm);background:transparent;padding:3px 10px;font-size:11.5px;font-weight:700;cursor:pointer;color:var(--color-text-muted)}.x-cms-link{border:0;background:transparent;cursor:pointer;font-size:12px;font-weight:700;color:var(--color-accent-hover)}.x-cms-danger{color:var(--color-danger, #c0362c)}.x-cms-block-edit{margin-top:12px;padding-top:12px;border-top:1px solid var(--color-border);display:flex;flex-direction:column;gap:10px}x-rich-editor{display:block;border:1px solid var(--color-border);border-radius:var(--radius-md);min-height:160px}\n"], dependencies: [{ kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i1$1.LoaderComponent, selector: "x-element-loader", inputs: ["data"] }, { kind: "component", type: RichEditorComponent, selector: "x-rich-editor", inputs: ["content"], outputs: ["contentChange"] }] }); }
|
|
335
|
+
}
|
|
336
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: ContentComponent, decorators: [{
|
|
337
|
+
type: Component,
|
|
338
|
+
args: [{ standalone: false, selector: 'x-content', template: "<div class=\"x-cms-head\">\n <div class=\"x-cms-title\">Content pages</div>\n <div class=\"x-cms-sub\">Ops-authored storefront pages \u2014 metadata and a block list per page.</div>\n</div>\n\n@if (loading) {\n <x-element-loader></x-element-loader>\n} @else {\n <div class=\"x-cms-layout\">\n <aside class=\"x-cms-list\">\n <div class=\"x-cms-new\">\n <input type=\"text\" placeholder=\"slug\" [(ngModel)]=\"newPageSlug\" />\n <input type=\"text\" placeholder=\"Title\" [(ngModel)]=\"newPageTitle\" />\n <button type=\"button\" (click)=\"createPage()\">Add page</button>\n </div>\n @for (p of pages; track p.id) {\n <button\n type=\"button\"\n class=\"x-cms-list-item\"\n [class.x-cms-list-active]=\"selected?.id === p.id\"\n (click)=\"select(p)\"\n >\n <span class=\"x-cms-list-title\">{{ p.title }}</span>\n <span class=\"x-cms-list-meta\">/{{ p.slug }} \u00B7 {{ p.status }}</span>\n </button>\n } @empty {\n <div class=\"x-cms-empty\">No pages yet.</div>\n }\n </aside>\n\n <section class=\"x-cms-editor\">\n @if (!selected) {\n <div class=\"x-cms-empty\">Pick a page, or add one.</div>\n } @else {\n <div class=\"x-cms-meta\">\n <div class=\"x-cms-f-row\">\n <label class=\"x-cms-f\">\n Title\n <input type=\"text\" [(ngModel)]=\"selected.title\" />\n </label>\n <label class=\"x-cms-f\">\n Slug\n <input type=\"text\" [(ngModel)]=\"selected.slug\" />\n </label>\n </div>\n <label class=\"x-cms-f\">\n Excerpt\n <input type=\"text\" [(ngModel)]=\"selected.excerpt\" />\n </label>\n <div class=\"x-cms-f-row\">\n <label class=\"x-cms-f\">\n Status\n <select [(ngModel)]=\"selected.status\">\n @for (s of statuses; track s) { <option [value]=\"s\">{{ s }}</option> }\n </select>\n </label>\n <label class=\"x-cms-f\">\n Template\n <select [(ngModel)]=\"selected.template\">\n @for (t of templates; track t) { <option [value]=\"t\">{{ t }}</option> }\n </select>\n </label>\n <label class=\"x-cms-f\">\n Publish at\n <input type=\"datetime-local\" [(ngModel)]=\"selected.publishAt\" />\n </label>\n </div>\n <div class=\"x-cms-meta-actions\">\n <button type=\"button\" class=\"x-cms-btn x-cms-btn-primary\" (click)=\"savePage()\">Save page</button>\n <button type=\"button\" class=\"x-cms-btn x-cms-danger\" (click)=\"deletePage()\">Delete page</button>\n </div>\n </div>\n\n <div class=\"x-cms-blocks-head\">\n <span>Blocks</span>\n <span class=\"x-cms-add-block\">\n <select [(ngModel)]=\"newBlockType\">\n @for (t of blockTypes; track t.value) { <option [value]=\"t.value\">{{ t.label }}</option> }\n </select>\n <button type=\"button\" class=\"x-cms-btn\" (click)=\"addBlock()\">Add block</button>\n </span>\n </div>\n\n @if (blocksLoading) {\n <x-element-loader></x-element-loader>\n } @else if (!orderedBlocks.length) {\n <div class=\"x-cms-empty\">No blocks on this page yet.</div>\n } @else {\n <div class=\"x-cms-blocks\">\n @for (b of orderedBlocks; track b.id; let i = $index) {\n <div\n class=\"x-cms-block\"\n [class.x-cms-block-off]=\"!b.enabled\"\n draggable=\"true\"\n (dragstart)=\"onDragStart(i)\"\n (dragover)=\"onDragOver($event)\"\n (drop)=\"onDrop(i)\"\n >\n <div class=\"x-cms-block-head\">\n <span class=\"x-cms-grip\">\u283F</span>\n <div class=\"x-cms-block-order\">\n <button type=\"button\" (click)=\"moveBlock(i, -1)\" [disabled]=\"i === 0\">\u25B2</button>\n <button type=\"button\" (click)=\"moveBlock(i, 1)\" [disabled]=\"i === orderedBlocks.length - 1\">\u25BC</button>\n </div>\n <span class=\"x-cms-block-type\">{{ blockTypeLabel(b.type) }}</span>\n <span class=\"x-cms-block-actions\">\n <button type=\"button\" class=\"x-cms-toggle\" (click)=\"toggleBlock(b)\">{{ b.enabled ? 'On' : 'Off' }}</button>\n <button type=\"button\" class=\"x-cms-link\" (click)=\"editBlock(b)\">Edit</button>\n <button type=\"button\" class=\"x-cms-link x-cms-danger\" (click)=\"removeBlock(b)\">Delete</button>\n </span>\n </div>\n\n @if (editingBlock?.id === b.id) {\n <div class=\"x-cms-block-edit\">\n @for (field of blockTypeDef(b.type)?.fields ?? []; track field.key) {\n <label class=\"x-cms-f\">\n {{ field.label }}\n @if (field.kind === 'rich') {\n @if (rawHtml) {\n <textarea rows=\"6\" [(ngModel)]=\"blockModel[field.key]\"></textarea>\n } @else {\n <x-rich-editor\n [content]=\"blockModel[field.key]\"\n (contentChange)=\"onRichChange(field.key, $event)\"\n ></x-rich-editor>\n }\n <button type=\"button\" class=\"x-cms-link\" (click)=\"rawHtml = !rawHtml\">\n {{ rawHtml ? 'Use the editor' : 'Edit raw HTML' }}\n </button>\n } @else if (field.kind === 'json') {\n <textarea rows=\"5\" [(ngModel)]=\"blockModel[field.key]\"></textarea>\n } @else {\n <input\n [type]=\"field.kind === 'num' ? 'number' : 'text'\"\n [(ngModel)]=\"blockModel[field.key]\"\n />\n }\n </label>\n }\n <div class=\"x-cms-edit-actions\">\n <button type=\"button\" class=\"x-cms-btn x-cms-btn-primary\" (click)=\"saveBlock()\">Save block</button>\n <button type=\"button\" class=\"x-cms-btn\" (click)=\"cancelBlockEdit()\">Cancel</button>\n </div>\n </div>\n }\n </div>\n }\n </div>\n }\n }\n </section>\n </div>\n}\n\n<style>\n .x-cms-head { margin-bottom: 20px; }\n .x-cms-title { font-size: 24px; font-weight: 800; color: var(--color-text); }\n .x-cms-sub { font-size: 13.5px; color: var(--color-text-muted); margin-top: 2px; }\n .x-cms-empty { font-size: 14px; color: var(--color-text-muted); padding: 12px 0; }\n .x-cms-layout { display: grid; grid-template-columns: 260px 1fr; gap: 20px; align-items: start; }\n .x-cms-list { display: flex; flex-direction: column; gap: 6px; }\n .x-cms-new { display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; }\n .x-cms-new input, .x-cms-f input, .x-cms-f select, .x-cms-f textarea,\n .x-cms-add-block select {\n border: 1px solid var(--color-border); border-radius: var(--radius-md);\n padding: 6px 10px; font-size: 13px; background: var(--color-surface); color: var(--color-text);\n }\n .x-cms-new button, .x-cms-btn {\n border: 1px solid var(--color-border); background: transparent; border-radius: var(--radius-md);\n padding: 6px 14px; font-size: 12.5px; font-weight: 700; cursor: pointer; color: var(--color-text-muted);\n }\n .x-cms-btn-primary { border-color: var(--color-accent); color: var(--color-accent-hover); }\n .x-cms-list-item {\n text-align: left; border: 1px solid var(--color-border); border-radius: var(--radius-md);\n background: var(--color-surface); padding: 8px 10px; cursor: pointer;\n display: flex; flex-direction: column; gap: 2px;\n }\n .x-cms-list-active { border-color: var(--color-accent); }\n .x-cms-list-title { font-size: 13px; font-weight: 700; color: var(--color-text); }\n .x-cms-list-meta { font-size: 11px; color: var(--color-text-muted); }\n .x-cms-editor { min-width: 0; }\n .x-cms-meta {\n background: var(--color-surface); border: 1px solid var(--color-border);\n border-radius: var(--radius-lg); padding: 16px; display: flex; flex-direction: column; gap: 10px;\n margin-bottom: 20px;\n }\n .x-cms-f { display: flex; flex-direction: column; gap: 4px; flex: 1; font-size: 11.5px; color: var(--color-text-muted); font-weight: 700; }\n .x-cms-f-row { display: flex; gap: 10px; }\n .x-cms-meta-actions, .x-cms-edit-actions { display: flex; gap: 8px; }\n .x-cms-blocks-head {\n display: flex; justify-content: space-between; align-items: center;\n font-size: 13px; font-weight: 800; color: var(--color-text); margin-bottom: 10px;\n }\n .x-cms-add-block { display: flex; gap: 8px; }\n .x-cms-blocks { display: flex; flex-direction: column; gap: 10px; }\n .x-cms-block { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: 12px 14px; }\n .x-cms-block-off { opacity: 0.55; }\n .x-cms-block-head { display: flex; align-items: center; gap: 10px; }\n .x-cms-grip { cursor: grab; color: var(--color-text-muted); }\n .x-cms-block-order { display: flex; flex-direction: column; }\n .x-cms-block-order button { border: 0; background: transparent; cursor: pointer; color: var(--color-text-muted); font-size: 10px; line-height: 1.1; padding: 1px 3px; }\n .x-cms-block-order button:disabled { opacity: 0.3; cursor: default; }\n .x-cms-block-type { font-weight: 800; text-transform: uppercase; letter-spacing: 0.4px; font-size: 11px; color: var(--color-accent-hover); flex: 1; }\n .x-cms-block-actions { display: flex; gap: 8px; align-items: center; }\n .x-cms-toggle { border: 1px solid var(--color-border); border-radius: var(--radius-sm); background: transparent; padding: 3px 10px; font-size: 11.5px; font-weight: 700; cursor: pointer; color: var(--color-text-muted); }\n .x-cms-link { border: 0; background: transparent; cursor: pointer; font-size: 12px; font-weight: 700; color: var(--color-accent-hover); }\n .x-cms-danger { color: var(--color-danger, #c0362c); }\n .x-cms-block-edit { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--color-border); display: flex; flex-direction: column; gap: 10px; }\n x-rich-editor { display: block; border: 1px solid var(--color-border); border-radius: var(--radius-md); min-height: 160px; }\n</style>\n" }]
|
|
339
|
+
}] });
|
|
340
|
+
|
|
341
|
+
class ContentModule {
|
|
342
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: ContentModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
343
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.16", ngImport: i0, type: ContentModule, declarations: [ContentComponent], imports: [SharedModule,
|
|
344
|
+
ElementsModule,
|
|
345
|
+
RichEditorModule, i1$2.RouterModule] }); }
|
|
346
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: ContentModule, imports: [SharedModule,
|
|
347
|
+
ElementsModule,
|
|
348
|
+
RichEditorModule,
|
|
349
|
+
RouterModule.forChild([{ path: '', component: ContentComponent }])] }); }
|
|
350
|
+
}
|
|
351
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: ContentModule, decorators: [{
|
|
352
|
+
type: NgModule,
|
|
353
|
+
args: [{
|
|
354
|
+
declarations: [ContentComponent],
|
|
355
|
+
imports: [
|
|
356
|
+
SharedModule,
|
|
357
|
+
ElementsModule,
|
|
358
|
+
RichEditorModule,
|
|
359
|
+
RouterModule.forChild([{ path: '', component: ContentComponent }]),
|
|
360
|
+
],
|
|
361
|
+
}]
|
|
362
|
+
}] });
|
|
363
|
+
|
|
364
|
+
export { ContentModule };
|
|
365
|
+
//# sourceMappingURL=isaiahiroko-tilmart-ngx-apps-admin-content.module-DLdKAUqc.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isaiahiroko-tilmart-ngx-apps-admin-content.module-DLdKAUqc.mjs","sources":["../../../projects/admin/src/lib/pages/content/content.component.ts","../../../projects/admin/src/lib/pages/content/content.component.html","../../../projects/admin/src/lib/pages/content/content.module.ts"],"sourcesContent":["import { Component } from '@angular/core';\nimport { BaseDirective, notify, using } from '@thescaffold/ngx-core';\nimport { firstValueFrom, takeUntil } from 'rxjs';\n\ninterface PageVm {\n id: string;\n slug: string;\n title: string;\n excerpt?: string;\n status: string;\n template: string;\n publishAt?: string;\n seoMetaId?: string;\n}\n\ninterface BlockVm {\n id: string;\n pageId: string;\n type: string;\n position: number;\n config: Record<string, any>;\n enabled: boolean;\n}\n\ninterface BlockTypeDef {\n value: string;\n label: string;\n // 'rich' = the WYSIWYG editor; the rest are plain inputs / json.\n fields: { key: string; label: string; kind: 'rich' | 'text' | 'ids' | 'id' | 'num' | 'json' }[];\n}\n\nconst BLOCK_TYPES: BlockTypeDef[] = [\n { value: 'rich_text', label: 'Rich text', fields: [{ key: 'html', label: 'Body', kind: 'rich' }] },\n {\n value: 'hero',\n label: 'Hero',\n fields: [\n { key: 'heading', label: 'Heading', kind: 'text' },\n { key: 'subheading', label: 'Subheading', kind: 'text' },\n { key: 'imageUrl', label: 'Image URL', kind: 'text' },\n { key: 'ctaLabel', label: 'Button label', kind: 'text' },\n { key: 'ctaUrl', label: 'Button URL', kind: 'text' },\n ],\n },\n {\n value: 'cta',\n label: 'Call to action',\n fields: [\n { key: 'heading', label: 'Heading', kind: 'text' },\n { key: 'body', label: 'Body', kind: 'text' },\n { key: 'buttonLabel', label: 'Button label', kind: 'text' },\n { key: 'buttonUrl', label: 'Button URL', kind: 'text' },\n ],\n },\n {\n value: 'product_rail',\n label: 'Product rail',\n fields: [\n { key: 'categoryId', label: 'From category ID', kind: 'id' },\n { key: 'productIds', label: 'Or product IDs', kind: 'ids' },\n { key: 'limit', label: 'Max items', kind: 'num' },\n ],\n },\n {\n value: 'category_grid',\n label: 'Category grid',\n fields: [{ key: 'categoryIds', label: 'Category IDs', kind: 'ids' }],\n },\n {\n value: 'image',\n label: 'Image',\n fields: [\n { key: 'url', label: 'Image URL', kind: 'text' },\n { key: 'alt', label: 'Alt text', kind: 'text' },\n { key: 'linkUrl', label: 'Links to', kind: 'text' },\n ],\n },\n { value: 'gallery', label: 'Gallery', fields: [{ key: 'imageUrls', label: 'Image URLs', kind: 'ids' }] },\n { value: 'faq', label: 'FAQ', fields: [{ key: 'items', label: 'Items — [{\"q\":\"…\",\"a\":\"…\"}]', kind: 'json' }] },\n { value: 'columns', label: 'Columns', fields: [{ key: 'columns', label: 'Columns JSON', kind: 'json' }] },\n {\n value: 'embed',\n label: 'Embed',\n fields: [\n { key: 'url', label: 'Embed URL', kind: 'text' },\n { key: 'html', label: 'Or raw HTML', kind: 'text' },\n ],\n },\n];\n\nconst STATUSES = ['draft', 'published', 'archived'];\nconst TEMPLATES = ['default', 'landing', 'full-width'];\n\n// P6.10, CMS-01 — the CMS page builder. Left: the Page list. Right: the\n// selected page's metadata + its drag-ordered ContentBlock list, each\n// with a type-aware editor (`rich_text` uses the `@thescaffold/editor-*`\n// WYSIWYG). The storefront page renderer is a later task; ops CRUD is\n// covered by `admin:*:apps:commerce:*`.\n@Component({\n standalone: false,\n selector: 'x-content',\n templateUrl: './content.component.html',\n})\nexport class ContentComponent extends BaseDirective {\n private readonly pageSource = this.apiService.on('apps/commerce/page');\n private readonly blockSource = this.apiService.on('apps/commerce/content-block');\n\n readonly blockTypes = BLOCK_TYPES;\n readonly statuses = STATUSES;\n readonly templates = TEMPLATES;\n\n loading = true;\n pages: PageVm[] = [];\n selected?: PageVm;\n\n blocks: BlockVm[] = [];\n blocksLoading = false;\n newBlockType = BLOCK_TYPES[0].value;\n editingBlock?: BlockVm;\n blockModel: Record<string, string> = {};\n rawHtml = false;\n\n newPageSlug = '';\n newPageTitle = '';\n\n private dragIndex: number | null = null;\n\n override init = () => {\n this.loadPages();\n };\n\n blockTypeLabel(v: string): string {\n return BLOCK_TYPES.find((t) => t.value === v)?.label ?? v;\n }\n\n blockTypeDef(v: string): BlockTypeDef | undefined {\n return BLOCK_TYPES.find((t) => t.value === v);\n }\n\n get orderedBlocks(): BlockVm[] {\n return [...this.blocks].sort((a, b) => a.position - b.position);\n }\n\n // ── pages ─────────────────────────────────────────────────────────\n\n loadPages(): void {\n this.loading = true;\n this.pageSource\n .getMany({ perPage: 200 } as any)\n .pipe(takeUntil(this.$))\n .subscribe(\n using((res: any) => {\n this.pages = (res?.data ?? []).sort((a: any, b: any) =>\n String(a.title).localeCompare(String(b.title)),\n );\n this.loading = false;\n if (this.selected) {\n this.selected = this.pages.find((p) => p.id === this.selected!.id);\n }\n }),\n );\n }\n\n async createPage(): Promise<void> {\n const slug = this.newPageSlug.trim().toLowerCase().replace(/\\s+/g, '-');\n const title = this.newPageTitle.trim();\n if (!slug || !title) return;\n try {\n const res: any = await firstValueFrom(\n this.pageSource.create({ slug, title, status: 'draft', template: 'default' } as any),\n );\n this.newPageSlug = '';\n this.newPageTitle = '';\n notify('Page created.', undefined, 'success');\n this.loadPages();\n if (res?.data?.id) setTimeout(() => this.select({ ...res.data }), 200);\n } catch {\n notify('Could not create the page (slug may be taken).', undefined, 'error');\n }\n }\n\n select(page: PageVm): void {\n this.selected = page;\n this.editingBlock = undefined;\n this.loadBlocks();\n }\n\n async savePage(): Promise<void> {\n if (!this.selected) return;\n const p = this.selected;\n try {\n await firstValueFrom(\n this.pageSource.update(p.id, {\n slug: p.slug,\n title: p.title,\n excerpt: p.excerpt || undefined,\n status: p.status,\n template: p.template,\n publishAt: p.publishAt ? new Date(p.publishAt).toISOString() : null,\n } as any),\n );\n notify('Page saved.', undefined, 'success');\n this.loadPages();\n } catch {\n notify('Could not save the page.', undefined, 'error');\n }\n }\n\n async deletePage(): Promise<void> {\n if (!this.selected) return;\n try {\n await firstValueFrom(this.pageSource.remove(this.selected.id));\n this.selected = undefined;\n this.blocks = [];\n notify('Page deleted.', undefined, 'success');\n this.loadPages();\n } catch {\n notify('Could not delete the page.', undefined, 'error');\n }\n }\n\n // ── blocks ────────────────────────────────────────────────────────\n\n private loadBlocks(): void {\n if (!this.selected) return;\n this.blocksLoading = true;\n this.blockSource\n .getMany({ pageId: this.selected.id, perPage: 200 } as any)\n .pipe(takeUntil(this.$))\n .subscribe(\n using((res: any) => {\n this.blocks = ((res?.data ?? []) as BlockVm[])\n .filter((b) => b.pageId === this.selected!.id)\n .map((b) => ({ ...b, config: b.config ?? {} }))\n .sort((a, b) => a.position - b.position);\n this.blocksLoading = false;\n }),\n );\n }\n\n async addBlock(): Promise<void> {\n if (!this.selected) return;\n try {\n await firstValueFrom(\n this.blockSource.create({\n pageId: this.selected.id,\n type: this.newBlockType,\n position: this.blocks.length,\n config: {},\n enabled: true,\n } as any),\n );\n this.loadBlocks();\n } catch {\n notify('Could not add the block.', undefined, 'error');\n }\n }\n\n editBlock(b: BlockVm): void {\n this.editingBlock = b;\n this.rawHtml = false;\n this.blockModel = {};\n for (const field of this.blockTypeDef(b.type)?.fields ?? []) {\n const value = (b.config ?? {})[field.key];\n this.blockModel[field.key] =\n field.kind === 'json'\n ? JSON.stringify(value ?? (field.key === 'items' || field.key === 'columns' ? [] : {}), null, 2)\n : Array.isArray(value)\n ? value.join(', ')\n : value != null\n ? String(value)\n : '';\n }\n }\n\n cancelBlockEdit(): void {\n this.editingBlock = undefined;\n this.blockModel = {};\n }\n\n async saveBlock(): Promise<void> {\n if (!this.editingBlock) return;\n const def = this.blockTypeDef(this.editingBlock.type);\n const config: Record<string, any> = { ...(this.editingBlock.config ?? {}) };\n for (const field of def?.fields ?? []) {\n const raw = (this.blockModel[field.key] ?? '').trim();\n if (field.kind === 'ids') {\n config[field.key] = raw ? raw.split(',').map((s) => s.trim()).filter(Boolean) : [];\n } else if (field.kind === 'num') {\n config[field.key] = raw ? Number(raw) : undefined;\n } else if (field.kind === 'json') {\n try {\n config[field.key] = raw ? JSON.parse(raw) : [];\n } catch {\n notify(`\"${field.label}\" is not valid JSON.`, undefined, 'error');\n return;\n }\n } else {\n config[field.key] = raw || undefined;\n }\n }\n try {\n await firstValueFrom(this.blockSource.update(this.editingBlock.id, { config } as any));\n this.editingBlock.config = config;\n notify('Block saved.', undefined, 'success');\n this.cancelBlockEdit();\n } catch {\n notify('Could not save the block.', undefined, 'error');\n }\n }\n\n async toggleBlock(b: BlockVm): Promise<void> {\n const next = !b.enabled;\n try {\n await firstValueFrom(this.blockSource.update(b.id, { enabled: next } as any));\n b.enabled = next;\n } catch {\n notify('Could not update the block.', undefined, 'error');\n }\n }\n\n async removeBlock(b: BlockVm): Promise<void> {\n try {\n await firstValueFrom(this.blockSource.remove(b.id));\n this.blocks = this.blocks.filter((x) => x.id !== b.id);\n if (this.editingBlock?.id === b.id) this.cancelBlockEdit();\n notify('Block removed.', undefined, 'success');\n } catch {\n notify('Could not remove the block.', undefined, 'error');\n }\n }\n\n onDragStart(index: number): void {\n this.dragIndex = index;\n }\n\n onDragOver(event: DragEvent): void {\n event.preventDefault();\n }\n\n onDrop(targetIndex: number): void {\n if (this.dragIndex === null || this.dragIndex === targetIndex) return;\n const ordered = this.orderedBlocks;\n const [moved] = ordered.splice(this.dragIndex, 1);\n ordered.splice(targetIndex, 0, moved);\n this.dragIndex = null;\n void this.persistOrder(ordered);\n }\n\n moveBlock(index: number, dir: -1 | 1): void {\n const ordered = this.orderedBlocks;\n const target = index + dir;\n if (target < 0 || target >= ordered.length) return;\n [ordered[index], ordered[target]] = [ordered[target], ordered[index]];\n void this.persistOrder(ordered);\n }\n\n private async persistOrder(ordered: BlockVm[]): Promise<void> {\n const changed: BlockVm[] = [];\n ordered.forEach((b, i) => {\n if (b.position !== i) {\n b.position = i;\n changed.push(b);\n }\n });\n this.blocks = ordered;\n try {\n await Promise.all(\n changed.map((b) =>\n firstValueFrom(this.blockSource.update(b.id, { position: b.position } as any)),\n ),\n );\n } catch {\n notify('Could not save the new order.', undefined, 'error');\n this.loadBlocks();\n }\n }\n\n onRichChange(field: string, html: string): void {\n this.blockModel[field] = html ?? '';\n }\n}\n","<div class=\"x-cms-head\">\n <div class=\"x-cms-title\">Content pages</div>\n <div class=\"x-cms-sub\">Ops-authored storefront pages — metadata and a block list per page.</div>\n</div>\n\n@if (loading) {\n <x-element-loader></x-element-loader>\n} @else {\n <div class=\"x-cms-layout\">\n <aside class=\"x-cms-list\">\n <div class=\"x-cms-new\">\n <input type=\"text\" placeholder=\"slug\" [(ngModel)]=\"newPageSlug\" />\n <input type=\"text\" placeholder=\"Title\" [(ngModel)]=\"newPageTitle\" />\n <button type=\"button\" (click)=\"createPage()\">Add page</button>\n </div>\n @for (p of pages; track p.id) {\n <button\n type=\"button\"\n class=\"x-cms-list-item\"\n [class.x-cms-list-active]=\"selected?.id === p.id\"\n (click)=\"select(p)\"\n >\n <span class=\"x-cms-list-title\">{{ p.title }}</span>\n <span class=\"x-cms-list-meta\">/{{ p.slug }} · {{ p.status }}</span>\n </button>\n } @empty {\n <div class=\"x-cms-empty\">No pages yet.</div>\n }\n </aside>\n\n <section class=\"x-cms-editor\">\n @if (!selected) {\n <div class=\"x-cms-empty\">Pick a page, or add one.</div>\n } @else {\n <div class=\"x-cms-meta\">\n <div class=\"x-cms-f-row\">\n <label class=\"x-cms-f\">\n Title\n <input type=\"text\" [(ngModel)]=\"selected.title\" />\n </label>\n <label class=\"x-cms-f\">\n Slug\n <input type=\"text\" [(ngModel)]=\"selected.slug\" />\n </label>\n </div>\n <label class=\"x-cms-f\">\n Excerpt\n <input type=\"text\" [(ngModel)]=\"selected.excerpt\" />\n </label>\n <div class=\"x-cms-f-row\">\n <label class=\"x-cms-f\">\n Status\n <select [(ngModel)]=\"selected.status\">\n @for (s of statuses; track s) { <option [value]=\"s\">{{ s }}</option> }\n </select>\n </label>\n <label class=\"x-cms-f\">\n Template\n <select [(ngModel)]=\"selected.template\">\n @for (t of templates; track t) { <option [value]=\"t\">{{ t }}</option> }\n </select>\n </label>\n <label class=\"x-cms-f\">\n Publish at\n <input type=\"datetime-local\" [(ngModel)]=\"selected.publishAt\" />\n </label>\n </div>\n <div class=\"x-cms-meta-actions\">\n <button type=\"button\" class=\"x-cms-btn x-cms-btn-primary\" (click)=\"savePage()\">Save page</button>\n <button type=\"button\" class=\"x-cms-btn x-cms-danger\" (click)=\"deletePage()\">Delete page</button>\n </div>\n </div>\n\n <div class=\"x-cms-blocks-head\">\n <span>Blocks</span>\n <span class=\"x-cms-add-block\">\n <select [(ngModel)]=\"newBlockType\">\n @for (t of blockTypes; track t.value) { <option [value]=\"t.value\">{{ t.label }}</option> }\n </select>\n <button type=\"button\" class=\"x-cms-btn\" (click)=\"addBlock()\">Add block</button>\n </span>\n </div>\n\n @if (blocksLoading) {\n <x-element-loader></x-element-loader>\n } @else if (!orderedBlocks.length) {\n <div class=\"x-cms-empty\">No blocks on this page yet.</div>\n } @else {\n <div class=\"x-cms-blocks\">\n @for (b of orderedBlocks; track b.id; let i = $index) {\n <div\n class=\"x-cms-block\"\n [class.x-cms-block-off]=\"!b.enabled\"\n draggable=\"true\"\n (dragstart)=\"onDragStart(i)\"\n (dragover)=\"onDragOver($event)\"\n (drop)=\"onDrop(i)\"\n >\n <div class=\"x-cms-block-head\">\n <span class=\"x-cms-grip\">⠿</span>\n <div class=\"x-cms-block-order\">\n <button type=\"button\" (click)=\"moveBlock(i, -1)\" [disabled]=\"i === 0\">▲</button>\n <button type=\"button\" (click)=\"moveBlock(i, 1)\" [disabled]=\"i === orderedBlocks.length - 1\">▼</button>\n </div>\n <span class=\"x-cms-block-type\">{{ blockTypeLabel(b.type) }}</span>\n <span class=\"x-cms-block-actions\">\n <button type=\"button\" class=\"x-cms-toggle\" (click)=\"toggleBlock(b)\">{{ b.enabled ? 'On' : 'Off' }}</button>\n <button type=\"button\" class=\"x-cms-link\" (click)=\"editBlock(b)\">Edit</button>\n <button type=\"button\" class=\"x-cms-link x-cms-danger\" (click)=\"removeBlock(b)\">Delete</button>\n </span>\n </div>\n\n @if (editingBlock?.id === b.id) {\n <div class=\"x-cms-block-edit\">\n @for (field of blockTypeDef(b.type)?.fields ?? []; track field.key) {\n <label class=\"x-cms-f\">\n {{ field.label }}\n @if (field.kind === 'rich') {\n @if (rawHtml) {\n <textarea rows=\"6\" [(ngModel)]=\"blockModel[field.key]\"></textarea>\n } @else {\n <x-rich-editor\n [content]=\"blockModel[field.key]\"\n (contentChange)=\"onRichChange(field.key, $event)\"\n ></x-rich-editor>\n }\n <button type=\"button\" class=\"x-cms-link\" (click)=\"rawHtml = !rawHtml\">\n {{ rawHtml ? 'Use the editor' : 'Edit raw HTML' }}\n </button>\n } @else if (field.kind === 'json') {\n <textarea rows=\"5\" [(ngModel)]=\"blockModel[field.key]\"></textarea>\n } @else {\n <input\n [type]=\"field.kind === 'num' ? 'number' : 'text'\"\n [(ngModel)]=\"blockModel[field.key]\"\n />\n }\n </label>\n }\n <div class=\"x-cms-edit-actions\">\n <button type=\"button\" class=\"x-cms-btn x-cms-btn-primary\" (click)=\"saveBlock()\">Save block</button>\n <button type=\"button\" class=\"x-cms-btn\" (click)=\"cancelBlockEdit()\">Cancel</button>\n </div>\n </div>\n }\n </div>\n }\n </div>\n }\n }\n </section>\n </div>\n}\n\n<style>\n .x-cms-head { margin-bottom: 20px; }\n .x-cms-title { font-size: 24px; font-weight: 800; color: var(--color-text); }\n .x-cms-sub { font-size: 13.5px; color: var(--color-text-muted); margin-top: 2px; }\n .x-cms-empty { font-size: 14px; color: var(--color-text-muted); padding: 12px 0; }\n .x-cms-layout { display: grid; grid-template-columns: 260px 1fr; gap: 20px; align-items: start; }\n .x-cms-list { display: flex; flex-direction: column; gap: 6px; }\n .x-cms-new { display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; }\n .x-cms-new input, .x-cms-f input, .x-cms-f select, .x-cms-f textarea,\n .x-cms-add-block select {\n border: 1px solid var(--color-border); border-radius: var(--radius-md);\n padding: 6px 10px; font-size: 13px; background: var(--color-surface); color: var(--color-text);\n }\n .x-cms-new button, .x-cms-btn {\n border: 1px solid var(--color-border); background: transparent; border-radius: var(--radius-md);\n padding: 6px 14px; font-size: 12.5px; font-weight: 700; cursor: pointer; color: var(--color-text-muted);\n }\n .x-cms-btn-primary { border-color: var(--color-accent); color: var(--color-accent-hover); }\n .x-cms-list-item {\n text-align: left; border: 1px solid var(--color-border); border-radius: var(--radius-md);\n background: var(--color-surface); padding: 8px 10px; cursor: pointer;\n display: flex; flex-direction: column; gap: 2px;\n }\n .x-cms-list-active { border-color: var(--color-accent); }\n .x-cms-list-title { font-size: 13px; font-weight: 700; color: var(--color-text); }\n .x-cms-list-meta { font-size: 11px; color: var(--color-text-muted); }\n .x-cms-editor { min-width: 0; }\n .x-cms-meta {\n background: var(--color-surface); border: 1px solid var(--color-border);\n border-radius: var(--radius-lg); padding: 16px; display: flex; flex-direction: column; gap: 10px;\n margin-bottom: 20px;\n }\n .x-cms-f { display: flex; flex-direction: column; gap: 4px; flex: 1; font-size: 11.5px; color: var(--color-text-muted); font-weight: 700; }\n .x-cms-f-row { display: flex; gap: 10px; }\n .x-cms-meta-actions, .x-cms-edit-actions { display: flex; gap: 8px; }\n .x-cms-blocks-head {\n display: flex; justify-content: space-between; align-items: center;\n font-size: 13px; font-weight: 800; color: var(--color-text); margin-bottom: 10px;\n }\n .x-cms-add-block { display: flex; gap: 8px; }\n .x-cms-blocks { display: flex; flex-direction: column; gap: 10px; }\n .x-cms-block { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: 12px 14px; }\n .x-cms-block-off { opacity: 0.55; }\n .x-cms-block-head { display: flex; align-items: center; gap: 10px; }\n .x-cms-grip { cursor: grab; color: var(--color-text-muted); }\n .x-cms-block-order { display: flex; flex-direction: column; }\n .x-cms-block-order button { border: 0; background: transparent; cursor: pointer; color: var(--color-text-muted); font-size: 10px; line-height: 1.1; padding: 1px 3px; }\n .x-cms-block-order button:disabled { opacity: 0.3; cursor: default; }\n .x-cms-block-type { font-weight: 800; text-transform: uppercase; letter-spacing: 0.4px; font-size: 11px; color: var(--color-accent-hover); flex: 1; }\n .x-cms-block-actions { display: flex; gap: 8px; align-items: center; }\n .x-cms-toggle { border: 1px solid var(--color-border); border-radius: var(--radius-sm); background: transparent; padding: 3px 10px; font-size: 11.5px; font-weight: 700; cursor: pointer; color: var(--color-text-muted); }\n .x-cms-link { border: 0; background: transparent; cursor: pointer; font-size: 12px; font-weight: 700; color: var(--color-accent-hover); }\n .x-cms-danger { color: var(--color-danger, #c0362c); }\n .x-cms-block-edit { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--color-border); display: flex; flex-direction: column; gap: 10px; }\n x-rich-editor { display: block; border: 1px solid var(--color-border); border-radius: var(--radius-md); min-height: 160px; }\n</style>\n","import { NgModule } from '@angular/core';\nimport { RouterModule } from '@angular/router';\nimport { SharedModule } from '@thescaffold/ngx-core';\nimport { ElementsModule } from '@thescaffold/ngx-ui';\nimport { RichEditorModule } from '../_shared/rich-editor/rich-editor.module';\nimport { ContentComponent } from './content.component';\n\n@NgModule({\n declarations: [ContentComponent],\n imports: [\n SharedModule,\n ElementsModule,\n RichEditorModule,\n RouterModule.forChild([{ path: '', component: ContentComponent }]),\n ],\n})\nexport class ContentModule {}\n"],"names":["i2","i3.RichEditorComponent","i1"],"mappings":"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"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Component, NgModule } from '@angular/core';
|
|
3
|
+
import * as i1$1 from '@angular/router';
|
|
4
|
+
import { RouterModule } from '@angular/router';
|
|
5
|
+
import { BaseDirective, using, titleCase, SharedModule } from '@thescaffold/ngx-core';
|
|
6
|
+
import * as i1 from '@thescaffold/ngx-ui';
|
|
7
|
+
import { ElementsModule, WebgetsModule } from '@thescaffold/ngx-ui';
|
|
8
|
+
import { BehaviorSubject, takeUntil, map } from 'rxjs';
|
|
9
|
+
import * as i2 from '@angular/common';
|
|
10
|
+
|
|
11
|
+
// P7.1 — ops console section pass (PRD 6.2, TRD 9.2). A standard list
|
|
12
|
+
// view on `x-webget-collection` over `apps/commerce/coupon`, matching the
|
|
13
|
+
// established admin pattern (users, routes, inbox). ops CRUD is covered
|
|
14
|
+
// by `admin:*:apps:commerce:*`.
|
|
15
|
+
class CouponsComponent extends BaseDirective {
|
|
16
|
+
constructor() {
|
|
17
|
+
super(...arguments);
|
|
18
|
+
this.pluralName = 'coupons';
|
|
19
|
+
this.dataSource = this.apiService.on('apps/commerce/coupon');
|
|
20
|
+
this.columns = [
|
|
21
|
+
{ key: 'code', value: 'Code' },
|
|
22
|
+
{ key: 'usageCount', value: 'Used' },
|
|
23
|
+
{ key: 'usageLimit', value: 'Limit' },
|
|
24
|
+
{ key: 'enabled', value: 'Enabled', type: 'class', class: 'badge text-bg-light border' },
|
|
25
|
+
];
|
|
26
|
+
this.page = 1;
|
|
27
|
+
this.perPage = 25;
|
|
28
|
+
this.list$ = new BehaviorSubject(null);
|
|
29
|
+
this.init = () => {
|
|
30
|
+
this.list();
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
list(page = this.page, perPage = this.perPage) {
|
|
34
|
+
this.dataSource
|
|
35
|
+
.getMany({ page, perPage })
|
|
36
|
+
.pipe(takeUntil(this.$), map((response) => this.toCollection(response)))
|
|
37
|
+
.subscribe(using((data) => this.list$.next(data)));
|
|
38
|
+
}
|
|
39
|
+
toCollection(response) {
|
|
40
|
+
return {
|
|
41
|
+
title: titleCase(this.pluralName),
|
|
42
|
+
columns: this.columns,
|
|
43
|
+
rows: response?.data,
|
|
44
|
+
hasHeaders: true,
|
|
45
|
+
hasTextFilter: true,
|
|
46
|
+
page: response?.meta?.current_page,
|
|
47
|
+
perPage: response?.meta?.per_page,
|
|
48
|
+
total: response?.meta?.total,
|
|
49
|
+
onPage: (page) => this.list(page),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: CouponsComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
53
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: CouponsComponent, isStandalone: false, selector: "x-coupons", usesInheritance: true, ngImport: i0, template: `
|
|
54
|
+
<div class="x-page-head">
|
|
55
|
+
<div class="x-page-title">Coupons</div>
|
|
56
|
+
<div class="x-page-subtitle">Redeemable codes bound to a promotion, with usage and spend limits.</div>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
@if (list$ | async; as list) {
|
|
60
|
+
<x-webget-collection [data]="list"></x-webget-collection>
|
|
61
|
+
} @else {
|
|
62
|
+
<x-element-loader></x-element-loader>
|
|
63
|
+
}
|
|
64
|
+
`, isInline: true, styles: [".x-page-head{margin-bottom:20px}.x-page-title{font-size:24px;font-weight:800;color:var(--color-text)}.x-page-subtitle{font-size:13.5px;color:var(--color-text-muted);margin-top:2px}\n"], dependencies: [{ kind: "component", type: i1.LoaderComponent, selector: "x-element-loader", inputs: ["data"] }, { kind: "component", type: i1.CollectionComponent, selector: "x-webget-collection", inputs: ["data"], outputs: ["search", "filter", "refresh", "select", "page", "show"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }] }); }
|
|
65
|
+
}
|
|
66
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: CouponsComponent, decorators: [{
|
|
67
|
+
type: Component,
|
|
68
|
+
args: [{ standalone: false, selector: 'x-coupons', template: `
|
|
69
|
+
<div class="x-page-head">
|
|
70
|
+
<div class="x-page-title">Coupons</div>
|
|
71
|
+
<div class="x-page-subtitle">Redeemable codes bound to a promotion, with usage and spend limits.</div>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
@if (list$ | async; as list) {
|
|
75
|
+
<x-webget-collection [data]="list"></x-webget-collection>
|
|
76
|
+
} @else {
|
|
77
|
+
<x-element-loader></x-element-loader>
|
|
78
|
+
}
|
|
79
|
+
`, styles: [".x-page-head{margin-bottom:20px}.x-page-title{font-size:24px;font-weight:800;color:var(--color-text)}.x-page-subtitle{font-size:13.5px;color:var(--color-text-muted);margin-top:2px}\n"] }]
|
|
80
|
+
}] });
|
|
81
|
+
|
|
82
|
+
class CouponsModule {
|
|
83
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: CouponsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
84
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.16", ngImport: i0, type: CouponsModule, declarations: [CouponsComponent], imports: [SharedModule,
|
|
85
|
+
ElementsModule,
|
|
86
|
+
WebgetsModule, i1$1.RouterModule] }); }
|
|
87
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: CouponsModule, imports: [SharedModule,
|
|
88
|
+
ElementsModule,
|
|
89
|
+
WebgetsModule,
|
|
90
|
+
RouterModule.forChild([{ path: '', component: CouponsComponent }])] }); }
|
|
91
|
+
}
|
|
92
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: CouponsModule, decorators: [{
|
|
93
|
+
type: NgModule,
|
|
94
|
+
args: [{
|
|
95
|
+
declarations: [CouponsComponent],
|
|
96
|
+
imports: [
|
|
97
|
+
SharedModule,
|
|
98
|
+
ElementsModule,
|
|
99
|
+
WebgetsModule,
|
|
100
|
+
RouterModule.forChild([{ path: '', component: CouponsComponent }]),
|
|
101
|
+
],
|
|
102
|
+
}]
|
|
103
|
+
}] });
|
|
104
|
+
|
|
105
|
+
export { CouponsModule };
|
|
106
|
+
//# sourceMappingURL=isaiahiroko-tilmart-ngx-apps-admin-coupons.module-CQNCSlsd.mjs.map
|