@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/CHANGELOG.md +21 -0
- package/dist/index.cjs +244 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.js +241 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @roottale/cms-core
|
|
2
2
|
|
|
3
|
+
## 0.64.0
|
|
4
|
+
|
|
5
|
+
## 0.63.0
|
|
6
|
+
|
|
7
|
+
### Minor Changes
|
|
8
|
+
|
|
9
|
+
- a858d8e: 목차 배치를 사이트 코드의 `RootTaleBlogPost.tocPosition`으로 관리할 수 있습니다.
|
|
10
|
+
명시한 값은 CMS 설정보다 우선하며, 생략한 기존 연동은 CMS 배치를 유지합니다.
|
|
11
|
+
현재 배치를 prop으로 명시하면 실제 화면을 바꾸지 않고 관리 주체를 코드로 옮깁니다.
|
|
12
|
+
|
|
13
|
+
공통 블록 디자인은 `RootTaleBlogPost.footerPatternPresentation`과
|
|
14
|
+
`RootTalePostPattern.presentation`으로 전체 대체할 수 있습니다. `null`은 원격
|
|
15
|
+
디자인 주입을 생략하고 사이트 CSS에 맡기며, 생략한 기존 연동은 원격값을 유지합니다.
|
|
16
|
+
고객 콘텐츠와 제작 디자인을 분리하는 문서·발행/미리보기 예제를 함께 갱신합니다.
|
|
17
|
+
|
|
18
|
+
공개 `RootTaleCssVars` 타입의 비공개 UI 패키지 참조도 제거해 독립 소비자의
|
|
19
|
+
strict 타입 검사를 지원합니다.
|
|
20
|
+
|
|
21
|
+
섹션 구성·디자인 변경 검사 함수를 공개 CMS core에서 공유해 편집기와 저장 단계가
|
|
22
|
+
같은 고객 편집 경계를 적용합니다.
|
|
23
|
+
|
|
3
24
|
## 0.62.0
|
|
4
25
|
|
|
5
26
|
## 0.61.1
|
package/dist/index.cjs
CHANGED
|
@@ -71,7 +71,9 @@ __export(src_exports, {
|
|
|
71
71
|
extractFaqEntries: () => extractFaqEntries,
|
|
72
72
|
faqPageJsonLd: () => faqPageJsonLd,
|
|
73
73
|
findInternalContentLinkTokens: () => findInternalContentLinkTokens,
|
|
74
|
+
findSectionLockViolations: () => findSectionLockViolations,
|
|
74
75
|
findSitePatternPlacementRule: () => findSitePatternPlacementRule,
|
|
76
|
+
formatSectionLockViolations: () => formatSectionLockViolations,
|
|
75
77
|
htmlToPlainText: () => htmlToPlainText,
|
|
76
78
|
injectStableIdsTree: () => injectStableIdsTree,
|
|
77
79
|
internalContentKeyFromPath: () => internalContentKeyFromPath,
|
|
@@ -80,6 +82,7 @@ __export(src_exports, {
|
|
|
80
82
|
isAllowedEmbedSrc: () => isAllowedEmbedSrc,
|
|
81
83
|
isAllowedMapEmbedSrc: () => isAllowedMapEmbedSrc,
|
|
82
84
|
isInCollectionMode: () => isInCollectionMode,
|
|
85
|
+
isSectionContentPropPath: () => isSectionContentPropPath,
|
|
83
86
|
isSitePatternSlotKey: () => isSitePatternSlotKey,
|
|
84
87
|
isValidBlock: () => isValidBlock,
|
|
85
88
|
isValidSitePatternKey: () => isValidSitePatternKey,
|
|
@@ -1721,6 +1724,244 @@ function cmsImageStyle(attrs) {
|
|
|
1721
1724
|
} : {}
|
|
1722
1725
|
};
|
|
1723
1726
|
}
|
|
1727
|
+
|
|
1728
|
+
// src/section-locks.ts
|
|
1729
|
+
var CONTENT_PROP_PATHS = {
|
|
1730
|
+
hero: [
|
|
1731
|
+
["eyebrow"],
|
|
1732
|
+
["title"],
|
|
1733
|
+
["description"],
|
|
1734
|
+
["imageSrc"],
|
|
1735
|
+
["imageAlt"],
|
|
1736
|
+
["cta", "label"],
|
|
1737
|
+
["cta", "href"]
|
|
1738
|
+
],
|
|
1739
|
+
"rich-text": [["heading"], ["body"]],
|
|
1740
|
+
services: [
|
|
1741
|
+
["heading"],
|
|
1742
|
+
["description"],
|
|
1743
|
+
["items", Number.NaN, "title"],
|
|
1744
|
+
["items", Number.NaN, "description"],
|
|
1745
|
+
["items", Number.NaN, "href"]
|
|
1746
|
+
],
|
|
1747
|
+
faq: [
|
|
1748
|
+
["heading"],
|
|
1749
|
+
["items", Number.NaN, "question"],
|
|
1750
|
+
["items", Number.NaN, "answer"]
|
|
1751
|
+
],
|
|
1752
|
+
"cta-banner": [["title"], ["description"], ["cta", "label"], ["cta", "href"]],
|
|
1753
|
+
"blog-list": [["heading"], ["emptyMessage"]],
|
|
1754
|
+
"lead-form": [["heading"], ["description"], ["submitLabel"]],
|
|
1755
|
+
trust: [
|
|
1756
|
+
["heading"],
|
|
1757
|
+
["metrics", Number.NaN, "value"],
|
|
1758
|
+
["metrics", Number.NaN, "unit"],
|
|
1759
|
+
["metrics", Number.NaN, "label"],
|
|
1760
|
+
["metrics", Number.NaN, "source"],
|
|
1761
|
+
["credentials", Number.NaN, "name"],
|
|
1762
|
+
["credentials", Number.NaN, "issuer"],
|
|
1763
|
+
["credentials", Number.NaN, "kind"],
|
|
1764
|
+
["credentials", Number.NaN, "image"],
|
|
1765
|
+
["logos", Number.NaN, "src"],
|
|
1766
|
+
["logos", Number.NaN, "alt"],
|
|
1767
|
+
["logos", Number.NaN, "href"],
|
|
1768
|
+
["disclaimer"]
|
|
1769
|
+
],
|
|
1770
|
+
contact: [
|
|
1771
|
+
["heading"],
|
|
1772
|
+
["form", "enabled"],
|
|
1773
|
+
["phone"],
|
|
1774
|
+
["address"],
|
|
1775
|
+
["hours", Number.NaN, "day"],
|
|
1776
|
+
["hours", Number.NaN, "open"],
|
|
1777
|
+
["hours", Number.NaN, "close"],
|
|
1778
|
+
["hours", Number.NaN, "note"],
|
|
1779
|
+
["mapEmbedUrl"]
|
|
1780
|
+
],
|
|
1781
|
+
expertise: [
|
|
1782
|
+
["heading"],
|
|
1783
|
+
["description"],
|
|
1784
|
+
["items", Number.NaN, "label"],
|
|
1785
|
+
["items", Number.NaN, "kicker"],
|
|
1786
|
+
["items", Number.NaN, "title"],
|
|
1787
|
+
["items", Number.NaN, "body"],
|
|
1788
|
+
["items", Number.NaN, "imageSrc"],
|
|
1789
|
+
["items", Number.NaN, "imageAlt"],
|
|
1790
|
+
["items", Number.NaN, "tags", Number.NaN],
|
|
1791
|
+
["items", Number.NaN, "href"],
|
|
1792
|
+
["items", Number.NaN, "ctaLabel"]
|
|
1793
|
+
],
|
|
1794
|
+
spaces: [
|
|
1795
|
+
["heading"],
|
|
1796
|
+
["description"],
|
|
1797
|
+
["spaces", Number.NaN, "label"],
|
|
1798
|
+
["spaces", Number.NaN, "kicker"],
|
|
1799
|
+
["spaces", Number.NaN, "title"],
|
|
1800
|
+
["spaces", Number.NaN, "description"],
|
|
1801
|
+
["spaces", Number.NaN, "imageSrc"],
|
|
1802
|
+
["spaces", Number.NaN, "imageAlt"],
|
|
1803
|
+
["spaces", Number.NaN, "tags", Number.NaN],
|
|
1804
|
+
["disclaimer"]
|
|
1805
|
+
],
|
|
1806
|
+
concerns: [
|
|
1807
|
+
["heading"],
|
|
1808
|
+
["description"],
|
|
1809
|
+
["figureImage"],
|
|
1810
|
+
["figureAlt"],
|
|
1811
|
+
["regions", Number.NaN, "label"],
|
|
1812
|
+
["regions", Number.NaN, "title"],
|
|
1813
|
+
["regions", Number.NaN, "description"],
|
|
1814
|
+
["regions", Number.NaN, "items", Number.NaN],
|
|
1815
|
+
["regions", Number.NaN, "href"],
|
|
1816
|
+
["regions", Number.NaN, "ctaLabel"],
|
|
1817
|
+
["disclaimer"]
|
|
1818
|
+
]
|
|
1819
|
+
};
|
|
1820
|
+
function valueEquals(left, right) {
|
|
1821
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
1822
|
+
}
|
|
1823
|
+
function isRecord2(value) {
|
|
1824
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1825
|
+
}
|
|
1826
|
+
function collectChangedPaths(before, after, prefix = []) {
|
|
1827
|
+
if (valueEquals(before, after)) return [];
|
|
1828
|
+
if (Array.isArray(before) && Array.isArray(after)) {
|
|
1829
|
+
if (before.length !== after.length) return [prefix];
|
|
1830
|
+
return before.flatMap(
|
|
1831
|
+
(item, index) => collectChangedPaths(item, after[index], [...prefix, index])
|
|
1832
|
+
);
|
|
1833
|
+
}
|
|
1834
|
+
if ((isRecord2(before) || before == null) && (isRecord2(after) || after == null)) {
|
|
1835
|
+
const beforeRecord = isRecord2(before) ? before : {};
|
|
1836
|
+
const afterRecord = isRecord2(after) ? after : {};
|
|
1837
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(beforeRecord), ...Object.keys(afterRecord)]);
|
|
1838
|
+
if (keys.size === 0) return [prefix];
|
|
1839
|
+
return [...keys].flatMap(
|
|
1840
|
+
(key) => collectChangedPaths(beforeRecord[key], afterRecord[key], [...prefix, key])
|
|
1841
|
+
);
|
|
1842
|
+
}
|
|
1843
|
+
return [prefix];
|
|
1844
|
+
}
|
|
1845
|
+
function contentPathMatches(pattern, path) {
|
|
1846
|
+
if (pattern.length !== path.length) return false;
|
|
1847
|
+
return pattern.every((segment, index) => {
|
|
1848
|
+
const actual = path[index];
|
|
1849
|
+
return Number.isNaN(segment) ? typeof actual === "number" : segment === actual;
|
|
1850
|
+
});
|
|
1851
|
+
}
|
|
1852
|
+
function isSectionContentPropPath(sectionType, path) {
|
|
1853
|
+
const patterns = CONTENT_PROP_PATHS[sectionType] ?? [];
|
|
1854
|
+
return patterns.some((pattern) => contentPathMatches(pattern, path));
|
|
1855
|
+
}
|
|
1856
|
+
function collectSectionSnapshots(doc) {
|
|
1857
|
+
const sections = [];
|
|
1858
|
+
function visit(node, path) {
|
|
1859
|
+
if (!isRecord2(node)) return;
|
|
1860
|
+
if (node.type === "section") {
|
|
1861
|
+
const attrs = isRecord2(node.attrs) ? node.attrs : {};
|
|
1862
|
+
sections.push({
|
|
1863
|
+
id: typeof attrs._id === "string" ? attrs._id : "",
|
|
1864
|
+
path,
|
|
1865
|
+
sectionType: typeof attrs.sectionType === "string" ? attrs.sectionType : "",
|
|
1866
|
+
attrs,
|
|
1867
|
+
props: attrs.props ?? {}
|
|
1868
|
+
});
|
|
1869
|
+
}
|
|
1870
|
+
if (Array.isArray(node.content)) {
|
|
1871
|
+
node.content.forEach((child, index) => visit(child, `${path}.${index}`));
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
visit(doc, "doc");
|
|
1875
|
+
return sections;
|
|
1876
|
+
}
|
|
1877
|
+
function findSectionLockViolations(beforeDoc, afterDoc) {
|
|
1878
|
+
const before = collectSectionSnapshots(beforeDoc);
|
|
1879
|
+
const after = collectSectionSnapshots(afterDoc);
|
|
1880
|
+
const afterById = new Map(after.map((section) => [section.id, section]));
|
|
1881
|
+
const beforeById = new Map(before.map((section) => [section.id, section]));
|
|
1882
|
+
const violations = [];
|
|
1883
|
+
for (const sections of [before, after]) {
|
|
1884
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1885
|
+
for (const section of sections) {
|
|
1886
|
+
if (!section.id || ids.has(section.id)) {
|
|
1887
|
+
violations.push({
|
|
1888
|
+
code: "locked_section_structure_changed",
|
|
1889
|
+
sectionId: section.id,
|
|
1890
|
+
sectionType: section.sectionType,
|
|
1891
|
+
field: "_id"
|
|
1892
|
+
});
|
|
1893
|
+
}
|
|
1894
|
+
ids.add(section.id);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
for (const section of before) {
|
|
1898
|
+
const next = afterById.get(section.id);
|
|
1899
|
+
if (!next) {
|
|
1900
|
+
violations.push({
|
|
1901
|
+
code: "locked_section_removed",
|
|
1902
|
+
sectionId: section.id,
|
|
1903
|
+
sectionType: section.sectionType,
|
|
1904
|
+
field: "(node)"
|
|
1905
|
+
});
|
|
1906
|
+
continue;
|
|
1907
|
+
}
|
|
1908
|
+
if (section.path !== next.path) {
|
|
1909
|
+
violations.push({
|
|
1910
|
+
code: "locked_section_moved",
|
|
1911
|
+
sectionId: section.id,
|
|
1912
|
+
sectionType: section.sectionType,
|
|
1913
|
+
field: "(index)"
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
const fields = /* @__PURE__ */ new Set([
|
|
1917
|
+
...Object.keys(section.attrs),
|
|
1918
|
+
...Object.keys(next.attrs)
|
|
1919
|
+
]);
|
|
1920
|
+
fields.delete("props");
|
|
1921
|
+
for (const field of fields) {
|
|
1922
|
+
if (!valueEquals(section.attrs[field], next.attrs[field])) {
|
|
1923
|
+
violations.push({
|
|
1924
|
+
code: field === "lock" ? "locked_section_lock_changed" : "locked_section_structure_changed",
|
|
1925
|
+
sectionId: section.id,
|
|
1926
|
+
sectionType: section.sectionType,
|
|
1927
|
+
field
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
for (const path of collectChangedPaths(section.props, next.props)) {
|
|
1932
|
+
if (!isSectionContentPropPath(section.sectionType, path)) {
|
|
1933
|
+
violations.push({
|
|
1934
|
+
code: "locked_section_structure_changed",
|
|
1935
|
+
sectionId: section.id,
|
|
1936
|
+
sectionType: section.sectionType,
|
|
1937
|
+
field: `props.${path.join(".") || "(root)"}`
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
for (const section of after) {
|
|
1943
|
+
if (beforeById.has(section.id)) continue;
|
|
1944
|
+
violations.push({
|
|
1945
|
+
code: "locked_section_inserted",
|
|
1946
|
+
sectionId: section.id,
|
|
1947
|
+
sectionType: section.sectionType,
|
|
1948
|
+
field: "(node)"
|
|
1949
|
+
});
|
|
1950
|
+
}
|
|
1951
|
+
return violations;
|
|
1952
|
+
}
|
|
1953
|
+
function formatSectionLockViolations(violations) {
|
|
1954
|
+
return violations.map((violation) => {
|
|
1955
|
+
const label = `${violation.sectionType || "section"} \uC139\uC158(${violation.sectionId})`;
|
|
1956
|
+
if (violation.code === "locked_section_inserted") return `${label} \uCD94\uAC00`;
|
|
1957
|
+
if (violation.code === "locked_section_removed") return `${label} \uC0AD\uC81C`;
|
|
1958
|
+
if (violation.code === "locked_section_moved") return `${label} \uC774\uB3D9`;
|
|
1959
|
+
if (violation.code === "locked_section_lock_changed") {
|
|
1960
|
+
return `${label} \uC7A0\uAE08 \uBCC0\uACBD`;
|
|
1961
|
+
}
|
|
1962
|
+
return `${label} \uAD6C\uC870 \uBCC0\uACBD(${violation.field})`;
|
|
1963
|
+
}).join(" / ");
|
|
1964
|
+
}
|
|
1724
1965
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1725
1966
|
0 && (module.exports = {
|
|
1726
1967
|
BLOCK_NAME_PATTERN,
|
|
@@ -1764,7 +2005,9 @@ function cmsImageStyle(attrs) {
|
|
|
1764
2005
|
extractFaqEntries,
|
|
1765
2006
|
faqPageJsonLd,
|
|
1766
2007
|
findInternalContentLinkTokens,
|
|
2008
|
+
findSectionLockViolations,
|
|
1767
2009
|
findSitePatternPlacementRule,
|
|
2010
|
+
formatSectionLockViolations,
|
|
1768
2011
|
htmlToPlainText,
|
|
1769
2012
|
injectStableIdsTree,
|
|
1770
2013
|
internalContentKeyFromPath,
|
|
@@ -1773,6 +2016,7 @@ function cmsImageStyle(attrs) {
|
|
|
1773
2016
|
isAllowedEmbedSrc,
|
|
1774
2017
|
isAllowedMapEmbedSrc,
|
|
1775
2018
|
isInCollectionMode,
|
|
2019
|
+
isSectionContentPropPath,
|
|
1776
2020
|
isSitePatternSlotKey,
|
|
1777
2021
|
isValidBlock,
|
|
1778
2022
|
isValidSitePatternKey,
|