@ecomcx/ecomweb-theme-sdk 1.25.0 → 1.27.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/dist/content.js CHANGED
@@ -19,7 +19,7 @@ export async function PostContent({ post, content, className }) {
19
19
  }
20
20
  if (!doc?.blocks?.length)
21
21
  return null;
22
- const rendered = await renderContent(doc, await contentContext(site, post), (blocks) => applyFilters("post.blocks", blocks, { site, post }, site));
22
+ const rendered = await renderContent(doc, { ...(await contentContext(site, post)), source: post ? `post:${post.id}` : undefined }, (blocks) => applyFilters("post.blocks", blocks, { site, post }, site));
23
23
  return _jsx("div", { className: cx(className ?? "entry-content", "ecx-content"), children: rendered });
24
24
  }
25
25
  /**
@@ -31,7 +31,7 @@ export async function EditableRegion({ name, data, className, children }) {
31
31
  const doc = (await getTemplates().catch(() => null))?.parts[name];
32
32
  const site = await getSite();
33
33
  const own = doc?.blocks?.length
34
- ? _jsx("div", { className: cx(`ecx-region ecx-region-${name}`, "ecx-content", className), children: await renderContent(doc, await contentContext(site, data?.post, data)) })
34
+ ? _jsx("div", { className: cx(`ecx-region ecx-region-${name}`, "ecx-content", className), children: await renderContent(doc, { ...(await contentContext(site, data?.post, data)), source: `part:${name}` }) })
35
35
  : _jsx(_Fragment, { children: children });
36
36
  return _jsx(_Fragment, { children: await regionOverride(name, own, site, data) });
37
37
  }
@@ -45,9 +45,11 @@ async function regionOverride(name, fallback, site, data) {
45
45
  /** Ngữ cảnh vẽ nội dung dùng chung cho bài viết và template khối. */
46
46
  export async function contentContext(site, post, data) {
47
47
  const t = await getT();
48
+ // Bản nháp (xem trực tiếp / Trình dựng) không vào cache công khai → được gắn vị trí khối cho khung soạn
49
+ const markBlocks = await import("next/headers").then(async ({ draftMode }) => (await draftMode()).isEnabled).catch(() => false);
48
50
  const blocks = (await getActiveExtensions(site)).flatMap((d) => d.blockTypes ?? []);
49
51
  return {
50
- site, post, data, blocks,
52
+ site, post, data, blocks, markBlocks,
51
53
  resolve: { shortcode: (name) => findShortcode(name, site), block: (type) => findBlock(type, site) },
52
54
  labels: { download: t("file.download"), toc: t("toc.title") },
53
55
  boundary: (name, key, el) => _jsx(ExtensionBoundary, { name: name, children: el }, key),
package/dist/dynamic.js CHANGED
@@ -112,14 +112,14 @@ export async function renderDynamic(node, key, ctx, children) {
112
112
  const doc = await ctx.pattern(a.ref);
113
113
  if (!doc)
114
114
  return null;
115
- return _jsx(Fragment, { children: await renderContent(doc, { ...ctx, partDepth: (ctx.partDepth ?? 0) + 1 }) }, key);
115
+ return _jsx(Fragment, { children: await renderContent(doc, { ...ctx, partDepth: (ctx.partDepth ?? 0) + 1, source: `pattern:${a.ref}` }) }, key);
116
116
  }
117
117
  case "templatePart": {
118
118
  if (!a.slug || !ctx.parts || (ctx.partDepth ?? 0) >= 4)
119
119
  return null;
120
120
  const doc = await ctx.parts(String(a.slug));
121
121
  const Tag = (["header", "footer", "aside", "section"].includes(a.tagName) ? a.tagName : "div");
122
- const part = doc ? _jsx(Tag, { className: cx("ecx-template-part", `ecx-template-part-${a.slug}`), children: await renderContent(doc, { ...ctx, partDepth: (ctx.partDepth ?? 0) + 1 }) }) : null;
122
+ const part = doc ? _jsx(Tag, { className: cx("ecx-template-part", `ecx-template-part-${a.slug}`), children: await renderContent(doc, { ...ctx, partDepth: (ctx.partDepth ?? 0) + 1, source: `part:${a.slug}` }) }) : null;
123
123
  const el = ctx.region ? await ctx.region(String(a.slug), part) : part;
124
124
  return el == null ? null : _jsx(Fragment, { children: el }, key);
125
125
  }
package/dist/rich.d.ts CHANGED
@@ -48,6 +48,14 @@ export interface RenderContext {
48
48
  id: string;
49
49
  text: string;
50
50
  }>;
51
+ /**
52
+ * Đang xem bản nháp (Trình dựng / xem trực tiếp): gắn data-ecx-doc + data-ecx-path cho từng khối để khung soạn ở admin
53
+ * biết người dùng bấm vào khối nào. `source` = tài liệu đang vẽ ("template:single", "part:header", "post:12", "pattern:3").
54
+ */
55
+ markBlocks?: boolean;
56
+ source?: string;
57
+ /** Khoá của khối richtext đang vẽ — đường dẫn khối = phần khoá sau nó (do renderBlock đặt). */
58
+ pathRoot?: string;
51
59
  }
52
60
  type Node = Omit<RichNode, "attrs" | "content" | "marks"> & {
53
61
  attrs?: Record<string, any>;
package/dist/rich.js CHANGED
@@ -152,7 +152,21 @@ function decorate(el, a, key, ctx) {
152
152
  async function renderNode(node, key, ctx) {
153
153
  const el = await renderNodeInner(node, key, ctx);
154
154
  const a = node.attrs;
155
- return a && (a.style || a.blockStyle || a.className || a.anchor) ? decorate(el, a, key, ctx) : el;
155
+ const out = a && (a.style || a.blockStyle || a.className || a.anchor) ? decorate(el, a, key, ctx) : el;
156
+ return ctx.markBlocks && ctx.source && ctx.pathRoot != null && key.startsWith(ctx.pathRoot + "-") && !UNMARKED.has(node.type)
157
+ ? markBlock(out, ctx.source, key.slice(ctx.pathRoot.length + 1), key)
158
+ : out;
159
+ }
160
+ /** Nút không chọn riêng được trong khung soạn (hoặc bọc thêm thẻ sẽ sai cấu trúc HTML: li trong ul, ô trong bảng). */
161
+ const UNMARKED = new Set(["text", "hardBreak", "listItem", "taskItem", "tableRow", "tableCell", "tableHeader"]);
162
+ /** Gắn vị trí khối: thẻ HTML thì thêm thuộc tính, component thì bọc thẻ display:contents (không đổi bố cục). */
163
+ function markBlock(el, source, path, key) {
164
+ if (el == null || el === false)
165
+ return el;
166
+ const attrs = { "data-ecx-doc": source, "data-ecx-path": path };
167
+ if (isValidElement(el) && typeof el.type === "string")
168
+ return cloneElement(el, attrs);
169
+ return _jsx("div", { style: { display: "contents" }, ...attrs, children: el }, key);
156
170
  }
157
171
  async function renderNodeInner(node, key, ctx) {
158
172
  const children = async () => Promise.all((node.content ?? []).map((c, i) => renderNode(c, `${key}-${i}`, ctx)));
@@ -359,7 +373,7 @@ async function renderHtml(html, key, ctx) {
359
373
  }
360
374
  async function renderBlock(block, key, ctx) {
361
375
  switch (block.type) {
362
- case "richtext": return block.data ? await renderNode(block.data, key, ctx) : null;
376
+ case "richtext": return block.data ? await renderNode(block.data, key, ctx.markBlocks ? { ...ctx, pathRoot: key } : ctx) : null;
363
377
  case "html": return await renderHtml(String(block.data?.html ?? ""), key, ctx);
364
378
  default: {
365
379
  const Comp = await ctx.resolve.block(block.type);
@@ -7,5 +7,5 @@ export default async function BlockTemplate({ data, site, template }) {
7
7
  const doc = (await getTemplates()).templates[template];
8
8
  if (!doc)
9
9
  return null;
10
- return _jsx("div", { className: `ecx-template ecx-template-${template} ecx-content`, children: await renderContent(doc, await contentContext(site, data.post, data)) });
10
+ return _jsx("div", { className: `ecx-template ecx-template-${template} ecx-content`, children: await renderContent(doc, { ...(await contentContext(site, data.post, data)), source: `template:${template}` }) });
11
11
  }
package/dist/types.d.ts CHANGED
@@ -277,6 +277,8 @@ export interface ThemeManifest {
277
277
  template?: string;
278
278
  locked?: boolean;
279
279
  }>;
280
+ /** Nguồn ngoài website theme cần nạp (CSP): { "style-src": ["https://fonts.googleapis.com"], "font-src": ["https://fonts.gstatic.com"] }. */
281
+ csp?: Partial<Record<"script-src" | "style-src" | "img-src" | "font-src" | "connect-src" | "frame-src" | "media-src" | "worker-src" | "form-action", string[]>>;
280
282
  [key: string]: unknown;
281
283
  }
282
284
  type Loader<T> = () => Promise<{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecomcx/ecomweb-theme-sdk",
3
- "version": "1.25.0",
3
+ "version": "1.27.0",
4
4
  "description": "SDK để viết theme và phần frontend của plugin cho Ecomweb",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",