@roottale/cms-core 0.62.0 → 0.64.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/index.d.cts CHANGED
@@ -595,4 +595,21 @@ declare function cmsImageStyle(attrs: Record<string, unknown>): {
595
595
  height?: string | undefined;
596
596
  };
597
597
 
598
- export { BLOCK_NAME_PATTERN, type BindingResolver, type Block, type BlockBinding, type BlockDefinition, type BlockRegistry, CMS_FONT_SIZES, CMS_IMAGE_WIDTHS, CMS_LINE_HEIGHTS, CMS_PARAGRAPH_SPACINGS, type CategoryTreeEntry, type CategoryTreePresentation, type CategoryTreeTerm, type ClientHydrationSpec, DEFAULT_SITE_PATTERN_PRESENTATION, EMPTY_SITE_PATTERN_PLACEMENTS, type EmbedProvider, FIELD_BINDING_SOURCE, FIELD_BINDING_SOURCE_ALIAS, type FaqEntry, IMAGE_ALT_MAX_LENGTH, INTERNAL_CONTENT_LINK_CLASS, INTERNAL_CONTENT_LINK_PENDING_ATTR, INTERNAL_CONTENT_LINK_PENDING_CLASS, INTERNAL_CONTENT_LINK_TOKEN_SOURCE, type InternalContentLinkResolver, type InternalContentLinkToken, LEGACY_BLOG_DETAIL_PATH, type NormalizedEmbed, PHASE_1_CORE_BLOCKS, PHASE_1_CSP, POST_FOOTER_SLOT, type PatternSlots, type Phase1CoreBlockName, type PreviewTokenPayload, type PublicPathContext, type PublicPathPost, type PublicPathPresentation, type RoutablePost, type RouteCollection, SITE_PATTERN_BUTTON_COLORS_MAX, SITE_PATTERN_KEY_PATTERN, SITE_PATTERN_PLACEMENTS_KEY, SITE_PATTERN_SLOTS, type SitePatternLayout, type SitePatternLinkStyle, type SitePatternPlacementRule, type SitePatternPlacements, type SitePatternPresentation, type SitePatternSlotKey, applyBlockBindings, bindingSrcValue, bindingTextValue, blockSchema, boundFieldValue, chainResolvers, cmsFontSize, cmsImageAlign, cmsImageStyle, cmsImageWidth, cmsLineHeight, cmsParagraphSpacing, createFieldBindingResolver, emptyPatternSlots, ensureImportedHtmlDocumentRoots, ensureStableIds, extractFaqEntries, faqPageJsonLd, findInternalContentLinkTokens, findSitePatternPlacementRule, htmlToPlainText, injectStableIdsTree, internalContentKeyFromPath, internalContentLinkResolverFrom, internalContentPathIndex, isAllowedEmbedSrc, isAllowedMapEmbedSrc, isInCollectionMode, isSitePatternSlotKey, isValidBlock, isValidSitePatternKey, issuePreviewToken, normalizeEmbed, normalizeHexColor, normalizeImageAlt, parseBlock, postsForCollection, postsInAnyCollection, postsNotInAnyCollection, readSitePatternPlacements, readSitePatternPresentation, referencedSitePatternKeys, renderInternalContentLinksInHtml, renderInternalContentLinksInText, resolveCategoryPath, resolveCategoryTreeChain, resolveCategoryTreeEntryPath, resolveCategoryTreeHubPaths, resolvePatternSlots, resolvePostCategorySlug, resolvePostCollection, resolvePostPath, resolvePostPublicPath, resolvePostUrl, sanitizeHtml, sanitizeImportedCss, sanitizeImportedHtml, scopeImportedCss, serializeBlock, setSitePatternPlacementRule, sitePatternButtonColor, sitePatternPresentationAttributes, validateCollections, verifyPreviewToken };
598
+ interface SectionLock {
599
+ move?: boolean;
600
+ remove?: boolean;
601
+ structure?: boolean;
602
+ }
603
+ type SectionLockViolationCode = "locked_section_inserted" | "locked_section_removed" | "locked_section_moved" | "locked_section_lock_changed" | "locked_section_structure_changed";
604
+ interface SectionLockViolation {
605
+ code: SectionLockViolationCode;
606
+ sectionId: string;
607
+ sectionType: string;
608
+ field: string;
609
+ }
610
+ declare function isSectionContentPropPath(sectionType: string, path: readonly (string | number)[]): boolean;
611
+ /** 고객 콘텐츠 저장 경계. 기존 lock 플래그와 무관하게 제작 구조를 보호한다. */
612
+ declare function findSectionLockViolations(beforeDoc: unknown, afterDoc: unknown): SectionLockViolation[];
613
+ declare function formatSectionLockViolations(violations: readonly SectionLockViolation[]): string;
614
+
615
+ export { BLOCK_NAME_PATTERN, type BindingResolver, type Block, type BlockBinding, type BlockDefinition, type BlockRegistry, CMS_FONT_SIZES, CMS_IMAGE_WIDTHS, CMS_LINE_HEIGHTS, CMS_PARAGRAPH_SPACINGS, type CategoryTreeEntry, type CategoryTreePresentation, type CategoryTreeTerm, type ClientHydrationSpec, DEFAULT_SITE_PATTERN_PRESENTATION, EMPTY_SITE_PATTERN_PLACEMENTS, type EmbedProvider, FIELD_BINDING_SOURCE, FIELD_BINDING_SOURCE_ALIAS, type FaqEntry, IMAGE_ALT_MAX_LENGTH, INTERNAL_CONTENT_LINK_CLASS, INTERNAL_CONTENT_LINK_PENDING_ATTR, INTERNAL_CONTENT_LINK_PENDING_CLASS, INTERNAL_CONTENT_LINK_TOKEN_SOURCE, type InternalContentLinkResolver, type InternalContentLinkToken, LEGACY_BLOG_DETAIL_PATH, type NormalizedEmbed, PHASE_1_CORE_BLOCKS, PHASE_1_CSP, POST_FOOTER_SLOT, type PatternSlots, type Phase1CoreBlockName, type PreviewTokenPayload, type PublicPathContext, type PublicPathPost, type PublicPathPresentation, type RoutablePost, type RouteCollection, SITE_PATTERN_BUTTON_COLORS_MAX, SITE_PATTERN_KEY_PATTERN, SITE_PATTERN_PLACEMENTS_KEY, SITE_PATTERN_SLOTS, type SectionLock, type SectionLockViolation, type SectionLockViolationCode, type SitePatternLayout, type SitePatternLinkStyle, type SitePatternPlacementRule, type SitePatternPlacements, type SitePatternPresentation, type SitePatternSlotKey, applyBlockBindings, bindingSrcValue, bindingTextValue, blockSchema, boundFieldValue, chainResolvers, cmsFontSize, cmsImageAlign, cmsImageStyle, cmsImageWidth, cmsLineHeight, cmsParagraphSpacing, createFieldBindingResolver, emptyPatternSlots, ensureImportedHtmlDocumentRoots, ensureStableIds, extractFaqEntries, faqPageJsonLd, findInternalContentLinkTokens, findSectionLockViolations, findSitePatternPlacementRule, formatSectionLockViolations, htmlToPlainText, injectStableIdsTree, internalContentKeyFromPath, internalContentLinkResolverFrom, internalContentPathIndex, isAllowedEmbedSrc, isAllowedMapEmbedSrc, isInCollectionMode, isSectionContentPropPath, isSitePatternSlotKey, isValidBlock, isValidSitePatternKey, issuePreviewToken, normalizeEmbed, normalizeHexColor, normalizeImageAlt, parseBlock, postsForCollection, postsInAnyCollection, postsNotInAnyCollection, readSitePatternPlacements, readSitePatternPresentation, referencedSitePatternKeys, renderInternalContentLinksInHtml, renderInternalContentLinksInText, resolveCategoryPath, resolveCategoryTreeChain, resolveCategoryTreeEntryPath, resolveCategoryTreeHubPaths, resolvePatternSlots, resolvePostCategorySlug, resolvePostCollection, resolvePostPath, resolvePostPublicPath, resolvePostUrl, sanitizeHtml, sanitizeImportedCss, sanitizeImportedHtml, scopeImportedCss, serializeBlock, setSitePatternPlacementRule, sitePatternButtonColor, sitePatternPresentationAttributes, validateCollections, verifyPreviewToken };
package/dist/index.d.ts CHANGED
@@ -595,4 +595,21 @@ declare function cmsImageStyle(attrs: Record<string, unknown>): {
595
595
  height?: string | undefined;
596
596
  };
597
597
 
598
- export { BLOCK_NAME_PATTERN, type BindingResolver, type Block, type BlockBinding, type BlockDefinition, type BlockRegistry, CMS_FONT_SIZES, CMS_IMAGE_WIDTHS, CMS_LINE_HEIGHTS, CMS_PARAGRAPH_SPACINGS, type CategoryTreeEntry, type CategoryTreePresentation, type CategoryTreeTerm, type ClientHydrationSpec, DEFAULT_SITE_PATTERN_PRESENTATION, EMPTY_SITE_PATTERN_PLACEMENTS, type EmbedProvider, FIELD_BINDING_SOURCE, FIELD_BINDING_SOURCE_ALIAS, type FaqEntry, IMAGE_ALT_MAX_LENGTH, INTERNAL_CONTENT_LINK_CLASS, INTERNAL_CONTENT_LINK_PENDING_ATTR, INTERNAL_CONTENT_LINK_PENDING_CLASS, INTERNAL_CONTENT_LINK_TOKEN_SOURCE, type InternalContentLinkResolver, type InternalContentLinkToken, LEGACY_BLOG_DETAIL_PATH, type NormalizedEmbed, PHASE_1_CORE_BLOCKS, PHASE_1_CSP, POST_FOOTER_SLOT, type PatternSlots, type Phase1CoreBlockName, type PreviewTokenPayload, type PublicPathContext, type PublicPathPost, type PublicPathPresentation, type RoutablePost, type RouteCollection, SITE_PATTERN_BUTTON_COLORS_MAX, SITE_PATTERN_KEY_PATTERN, SITE_PATTERN_PLACEMENTS_KEY, SITE_PATTERN_SLOTS, type SitePatternLayout, type SitePatternLinkStyle, type SitePatternPlacementRule, type SitePatternPlacements, type SitePatternPresentation, type SitePatternSlotKey, applyBlockBindings, bindingSrcValue, bindingTextValue, blockSchema, boundFieldValue, chainResolvers, cmsFontSize, cmsImageAlign, cmsImageStyle, cmsImageWidth, cmsLineHeight, cmsParagraphSpacing, createFieldBindingResolver, emptyPatternSlots, ensureImportedHtmlDocumentRoots, ensureStableIds, extractFaqEntries, faqPageJsonLd, findInternalContentLinkTokens, findSitePatternPlacementRule, htmlToPlainText, injectStableIdsTree, internalContentKeyFromPath, internalContentLinkResolverFrom, internalContentPathIndex, isAllowedEmbedSrc, isAllowedMapEmbedSrc, isInCollectionMode, isSitePatternSlotKey, isValidBlock, isValidSitePatternKey, issuePreviewToken, normalizeEmbed, normalizeHexColor, normalizeImageAlt, parseBlock, postsForCollection, postsInAnyCollection, postsNotInAnyCollection, readSitePatternPlacements, readSitePatternPresentation, referencedSitePatternKeys, renderInternalContentLinksInHtml, renderInternalContentLinksInText, resolveCategoryPath, resolveCategoryTreeChain, resolveCategoryTreeEntryPath, resolveCategoryTreeHubPaths, resolvePatternSlots, resolvePostCategorySlug, resolvePostCollection, resolvePostPath, resolvePostPublicPath, resolvePostUrl, sanitizeHtml, sanitizeImportedCss, sanitizeImportedHtml, scopeImportedCss, serializeBlock, setSitePatternPlacementRule, sitePatternButtonColor, sitePatternPresentationAttributes, validateCollections, verifyPreviewToken };
598
+ interface SectionLock {
599
+ move?: boolean;
600
+ remove?: boolean;
601
+ structure?: boolean;
602
+ }
603
+ type SectionLockViolationCode = "locked_section_inserted" | "locked_section_removed" | "locked_section_moved" | "locked_section_lock_changed" | "locked_section_structure_changed";
604
+ interface SectionLockViolation {
605
+ code: SectionLockViolationCode;
606
+ sectionId: string;
607
+ sectionType: string;
608
+ field: string;
609
+ }
610
+ declare function isSectionContentPropPath(sectionType: string, path: readonly (string | number)[]): boolean;
611
+ /** 고객 콘텐츠 저장 경계. 기존 lock 플래그와 무관하게 제작 구조를 보호한다. */
612
+ declare function findSectionLockViolations(beforeDoc: unknown, afterDoc: unknown): SectionLockViolation[];
613
+ declare function formatSectionLockViolations(violations: readonly SectionLockViolation[]): string;
614
+
615
+ export { BLOCK_NAME_PATTERN, type BindingResolver, type Block, type BlockBinding, type BlockDefinition, type BlockRegistry, CMS_FONT_SIZES, CMS_IMAGE_WIDTHS, CMS_LINE_HEIGHTS, CMS_PARAGRAPH_SPACINGS, type CategoryTreeEntry, type CategoryTreePresentation, type CategoryTreeTerm, type ClientHydrationSpec, DEFAULT_SITE_PATTERN_PRESENTATION, EMPTY_SITE_PATTERN_PLACEMENTS, type EmbedProvider, FIELD_BINDING_SOURCE, FIELD_BINDING_SOURCE_ALIAS, type FaqEntry, IMAGE_ALT_MAX_LENGTH, INTERNAL_CONTENT_LINK_CLASS, INTERNAL_CONTENT_LINK_PENDING_ATTR, INTERNAL_CONTENT_LINK_PENDING_CLASS, INTERNAL_CONTENT_LINK_TOKEN_SOURCE, type InternalContentLinkResolver, type InternalContentLinkToken, LEGACY_BLOG_DETAIL_PATH, type NormalizedEmbed, PHASE_1_CORE_BLOCKS, PHASE_1_CSP, POST_FOOTER_SLOT, type PatternSlots, type Phase1CoreBlockName, type PreviewTokenPayload, type PublicPathContext, type PublicPathPost, type PublicPathPresentation, type RoutablePost, type RouteCollection, SITE_PATTERN_BUTTON_COLORS_MAX, SITE_PATTERN_KEY_PATTERN, SITE_PATTERN_PLACEMENTS_KEY, SITE_PATTERN_SLOTS, type SectionLock, type SectionLockViolation, type SectionLockViolationCode, type SitePatternLayout, type SitePatternLinkStyle, type SitePatternPlacementRule, type SitePatternPlacements, type SitePatternPresentation, type SitePatternSlotKey, applyBlockBindings, bindingSrcValue, bindingTextValue, blockSchema, boundFieldValue, chainResolvers, cmsFontSize, cmsImageAlign, cmsImageStyle, cmsImageWidth, cmsLineHeight, cmsParagraphSpacing, createFieldBindingResolver, emptyPatternSlots, ensureImportedHtmlDocumentRoots, ensureStableIds, extractFaqEntries, faqPageJsonLd, findInternalContentLinkTokens, findSectionLockViolations, findSitePatternPlacementRule, formatSectionLockViolations, htmlToPlainText, injectStableIdsTree, internalContentKeyFromPath, internalContentLinkResolverFrom, internalContentPathIndex, isAllowedEmbedSrc, isAllowedMapEmbedSrc, isInCollectionMode, isSectionContentPropPath, isSitePatternSlotKey, isValidBlock, isValidSitePatternKey, issuePreviewToken, normalizeEmbed, normalizeHexColor, normalizeImageAlt, parseBlock, postsForCollection, postsInAnyCollection, postsNotInAnyCollection, readSitePatternPlacements, readSitePatternPresentation, referencedSitePatternKeys, renderInternalContentLinksInHtml, renderInternalContentLinksInText, resolveCategoryPath, resolveCategoryTreeChain, resolveCategoryTreeEntryPath, resolveCategoryTreeHubPaths, resolvePatternSlots, resolvePostCategorySlug, resolvePostCollection, resolvePostPath, resolvePostPublicPath, resolvePostUrl, sanitizeHtml, sanitizeImportedCss, sanitizeImportedHtml, scopeImportedCss, serializeBlock, setSitePatternPlacementRule, sitePatternButtonColor, sitePatternPresentationAttributes, validateCollections, verifyPreviewToken };
package/dist/index.js CHANGED
@@ -1600,6 +1600,244 @@ function cmsImageStyle(attrs) {
1600
1600
  } : {}
1601
1601
  };
1602
1602
  }
1603
+
1604
+ // src/section-locks.ts
1605
+ var CONTENT_PROP_PATHS = {
1606
+ hero: [
1607
+ ["eyebrow"],
1608
+ ["title"],
1609
+ ["description"],
1610
+ ["imageSrc"],
1611
+ ["imageAlt"],
1612
+ ["cta", "label"],
1613
+ ["cta", "href"]
1614
+ ],
1615
+ "rich-text": [["heading"], ["body"]],
1616
+ services: [
1617
+ ["heading"],
1618
+ ["description"],
1619
+ ["items", Number.NaN, "title"],
1620
+ ["items", Number.NaN, "description"],
1621
+ ["items", Number.NaN, "href"]
1622
+ ],
1623
+ faq: [
1624
+ ["heading"],
1625
+ ["items", Number.NaN, "question"],
1626
+ ["items", Number.NaN, "answer"]
1627
+ ],
1628
+ "cta-banner": [["title"], ["description"], ["cta", "label"], ["cta", "href"]],
1629
+ "blog-list": [["heading"], ["emptyMessage"]],
1630
+ "lead-form": [["heading"], ["description"], ["submitLabel"]],
1631
+ trust: [
1632
+ ["heading"],
1633
+ ["metrics", Number.NaN, "value"],
1634
+ ["metrics", Number.NaN, "unit"],
1635
+ ["metrics", Number.NaN, "label"],
1636
+ ["metrics", Number.NaN, "source"],
1637
+ ["credentials", Number.NaN, "name"],
1638
+ ["credentials", Number.NaN, "issuer"],
1639
+ ["credentials", Number.NaN, "kind"],
1640
+ ["credentials", Number.NaN, "image"],
1641
+ ["logos", Number.NaN, "src"],
1642
+ ["logos", Number.NaN, "alt"],
1643
+ ["logos", Number.NaN, "href"],
1644
+ ["disclaimer"]
1645
+ ],
1646
+ contact: [
1647
+ ["heading"],
1648
+ ["form", "enabled"],
1649
+ ["phone"],
1650
+ ["address"],
1651
+ ["hours", Number.NaN, "day"],
1652
+ ["hours", Number.NaN, "open"],
1653
+ ["hours", Number.NaN, "close"],
1654
+ ["hours", Number.NaN, "note"],
1655
+ ["mapEmbedUrl"]
1656
+ ],
1657
+ expertise: [
1658
+ ["heading"],
1659
+ ["description"],
1660
+ ["items", Number.NaN, "label"],
1661
+ ["items", Number.NaN, "kicker"],
1662
+ ["items", Number.NaN, "title"],
1663
+ ["items", Number.NaN, "body"],
1664
+ ["items", Number.NaN, "imageSrc"],
1665
+ ["items", Number.NaN, "imageAlt"],
1666
+ ["items", Number.NaN, "tags", Number.NaN],
1667
+ ["items", Number.NaN, "href"],
1668
+ ["items", Number.NaN, "ctaLabel"]
1669
+ ],
1670
+ spaces: [
1671
+ ["heading"],
1672
+ ["description"],
1673
+ ["spaces", Number.NaN, "label"],
1674
+ ["spaces", Number.NaN, "kicker"],
1675
+ ["spaces", Number.NaN, "title"],
1676
+ ["spaces", Number.NaN, "description"],
1677
+ ["spaces", Number.NaN, "imageSrc"],
1678
+ ["spaces", Number.NaN, "imageAlt"],
1679
+ ["spaces", Number.NaN, "tags", Number.NaN],
1680
+ ["disclaimer"]
1681
+ ],
1682
+ concerns: [
1683
+ ["heading"],
1684
+ ["description"],
1685
+ ["figureImage"],
1686
+ ["figureAlt"],
1687
+ ["regions", Number.NaN, "label"],
1688
+ ["regions", Number.NaN, "title"],
1689
+ ["regions", Number.NaN, "description"],
1690
+ ["regions", Number.NaN, "items", Number.NaN],
1691
+ ["regions", Number.NaN, "href"],
1692
+ ["regions", Number.NaN, "ctaLabel"],
1693
+ ["disclaimer"]
1694
+ ]
1695
+ };
1696
+ function valueEquals(left, right) {
1697
+ return JSON.stringify(left) === JSON.stringify(right);
1698
+ }
1699
+ function isRecord2(value) {
1700
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1701
+ }
1702
+ function collectChangedPaths(before, after, prefix = []) {
1703
+ if (valueEquals(before, after)) return [];
1704
+ if (Array.isArray(before) && Array.isArray(after)) {
1705
+ if (before.length !== after.length) return [prefix];
1706
+ return before.flatMap(
1707
+ (item, index) => collectChangedPaths(item, after[index], [...prefix, index])
1708
+ );
1709
+ }
1710
+ if ((isRecord2(before) || before == null) && (isRecord2(after) || after == null)) {
1711
+ const beforeRecord = isRecord2(before) ? before : {};
1712
+ const afterRecord = isRecord2(after) ? after : {};
1713
+ const keys = /* @__PURE__ */ new Set([...Object.keys(beforeRecord), ...Object.keys(afterRecord)]);
1714
+ if (keys.size === 0) return [prefix];
1715
+ return [...keys].flatMap(
1716
+ (key) => collectChangedPaths(beforeRecord[key], afterRecord[key], [...prefix, key])
1717
+ );
1718
+ }
1719
+ return [prefix];
1720
+ }
1721
+ function contentPathMatches(pattern, path) {
1722
+ if (pattern.length !== path.length) return false;
1723
+ return pattern.every((segment, index) => {
1724
+ const actual = path[index];
1725
+ return Number.isNaN(segment) ? typeof actual === "number" : segment === actual;
1726
+ });
1727
+ }
1728
+ function isSectionContentPropPath(sectionType, path) {
1729
+ const patterns = CONTENT_PROP_PATHS[sectionType] ?? [];
1730
+ return patterns.some((pattern) => contentPathMatches(pattern, path));
1731
+ }
1732
+ function collectSectionSnapshots(doc) {
1733
+ const sections = [];
1734
+ function visit(node, path) {
1735
+ if (!isRecord2(node)) return;
1736
+ if (node.type === "section") {
1737
+ const attrs = isRecord2(node.attrs) ? node.attrs : {};
1738
+ sections.push({
1739
+ id: typeof attrs._id === "string" ? attrs._id : "",
1740
+ path,
1741
+ sectionType: typeof attrs.sectionType === "string" ? attrs.sectionType : "",
1742
+ attrs,
1743
+ props: attrs.props ?? {}
1744
+ });
1745
+ }
1746
+ if (Array.isArray(node.content)) {
1747
+ node.content.forEach((child, index) => visit(child, `${path}.${index}`));
1748
+ }
1749
+ }
1750
+ visit(doc, "doc");
1751
+ return sections;
1752
+ }
1753
+ function findSectionLockViolations(beforeDoc, afterDoc) {
1754
+ const before = collectSectionSnapshots(beforeDoc);
1755
+ const after = collectSectionSnapshots(afterDoc);
1756
+ const afterById = new Map(after.map((section) => [section.id, section]));
1757
+ const beforeById = new Map(before.map((section) => [section.id, section]));
1758
+ const violations = [];
1759
+ for (const sections of [before, after]) {
1760
+ const ids = /* @__PURE__ */ new Set();
1761
+ for (const section of sections) {
1762
+ if (!section.id || ids.has(section.id)) {
1763
+ violations.push({
1764
+ code: "locked_section_structure_changed",
1765
+ sectionId: section.id,
1766
+ sectionType: section.sectionType,
1767
+ field: "_id"
1768
+ });
1769
+ }
1770
+ ids.add(section.id);
1771
+ }
1772
+ }
1773
+ for (const section of before) {
1774
+ const next = afterById.get(section.id);
1775
+ if (!next) {
1776
+ violations.push({
1777
+ code: "locked_section_removed",
1778
+ sectionId: section.id,
1779
+ sectionType: section.sectionType,
1780
+ field: "(node)"
1781
+ });
1782
+ continue;
1783
+ }
1784
+ if (section.path !== next.path) {
1785
+ violations.push({
1786
+ code: "locked_section_moved",
1787
+ sectionId: section.id,
1788
+ sectionType: section.sectionType,
1789
+ field: "(index)"
1790
+ });
1791
+ }
1792
+ const fields = /* @__PURE__ */ new Set([
1793
+ ...Object.keys(section.attrs),
1794
+ ...Object.keys(next.attrs)
1795
+ ]);
1796
+ fields.delete("props");
1797
+ for (const field of fields) {
1798
+ if (!valueEquals(section.attrs[field], next.attrs[field])) {
1799
+ violations.push({
1800
+ code: field === "lock" ? "locked_section_lock_changed" : "locked_section_structure_changed",
1801
+ sectionId: section.id,
1802
+ sectionType: section.sectionType,
1803
+ field
1804
+ });
1805
+ }
1806
+ }
1807
+ for (const path of collectChangedPaths(section.props, next.props)) {
1808
+ if (!isSectionContentPropPath(section.sectionType, path)) {
1809
+ violations.push({
1810
+ code: "locked_section_structure_changed",
1811
+ sectionId: section.id,
1812
+ sectionType: section.sectionType,
1813
+ field: `props.${path.join(".") || "(root)"}`
1814
+ });
1815
+ }
1816
+ }
1817
+ }
1818
+ for (const section of after) {
1819
+ if (beforeById.has(section.id)) continue;
1820
+ violations.push({
1821
+ code: "locked_section_inserted",
1822
+ sectionId: section.id,
1823
+ sectionType: section.sectionType,
1824
+ field: "(node)"
1825
+ });
1826
+ }
1827
+ return violations;
1828
+ }
1829
+ function formatSectionLockViolations(violations) {
1830
+ return violations.map((violation) => {
1831
+ const label = `${violation.sectionType || "section"} \uC139\uC158(${violation.sectionId})`;
1832
+ if (violation.code === "locked_section_inserted") return `${label} \uCD94\uAC00`;
1833
+ if (violation.code === "locked_section_removed") return `${label} \uC0AD\uC81C`;
1834
+ if (violation.code === "locked_section_moved") return `${label} \uC774\uB3D9`;
1835
+ if (violation.code === "locked_section_lock_changed") {
1836
+ return `${label} \uC7A0\uAE08 \uBCC0\uACBD`;
1837
+ }
1838
+ return `${label} \uAD6C\uC870 \uBCC0\uACBD(${violation.field})`;
1839
+ }).join(" / ");
1840
+ }
1603
1841
  export {
1604
1842
  BLOCK_NAME_PATTERN,
1605
1843
  CMS_FONT_SIZES,
@@ -1642,7 +1880,9 @@ export {
1642
1880
  extractFaqEntries,
1643
1881
  faqPageJsonLd,
1644
1882
  findInternalContentLinkTokens,
1883
+ findSectionLockViolations,
1645
1884
  findSitePatternPlacementRule,
1885
+ formatSectionLockViolations,
1646
1886
  htmlToPlainText,
1647
1887
  injectStableIdsTree,
1648
1888
  internalContentKeyFromPath,
@@ -1651,6 +1891,7 @@ export {
1651
1891
  isAllowedEmbedSrc,
1652
1892
  isAllowedMapEmbedSrc,
1653
1893
  isInCollectionMode,
1894
+ isSectionContentPropPath,
1654
1895
  isSitePatternSlotKey,
1655
1896
  isValidBlock,
1656
1897
  isValidSitePatternKey,