@brightsideoy/kama 0.1.0-alpha.120 → 0.1.0-alpha.121

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.
@@ -0,0 +1,113 @@
1
+ // src/utils/tiptap.ts
2
+
3
+ export interface TiptapNode {
4
+ type: string;
5
+ text?: string;
6
+ attrs?: Record<string, any>;
7
+ marks?: Array<{ type: string; attrs?: Record<string, any> }>;
8
+ content?: TiptapNode[];
9
+ }
10
+
11
+ /**
12
+ * Traverses a ProseMirror / Tiptap AST node and converts it into HTML.
13
+ */
14
+ export function renderTiptapNode(node?: TiptapNode | null): string {
15
+ if (!node) return "";
16
+ if (typeof node === "string") return node;
17
+
18
+ // 1. Text Node Handling (with Marks)
19
+ if (node.type === "text") {
20
+ let text = escapeHtml(node.text || "");
21
+ if (node.marks && Array.isArray(node.marks)) {
22
+ node.marks.forEach((mark) => {
23
+ switch (mark.type) {
24
+ case "bold":
25
+ case "strong":
26
+ text = `<strong>${text}</strong>`;
27
+ break;
28
+ case "italic":
29
+ case "em":
30
+ text = `<em>${text}</em>`;
31
+ break;
32
+ case "underline":
33
+ text = `<u>${text}</u>`;
34
+ break;
35
+ case "strike":
36
+ text = `<s>${text}</s>`;
37
+ break;
38
+ case "code":
39
+ text = `<code>${text}</code>`;
40
+ break;
41
+ case "link": {
42
+ const href = mark.attrs?.href ? escapeHtml(mark.attrs.href) : "#";
43
+ const target = mark.attrs?.target ? ` target="${escapeHtml(mark.attrs.target)}"` : "";
44
+ const rel = mark.attrs?.target === "_blank" ? ' rel="noopener noreferrer"' : "";
45
+ text = `<a href="${href}"${target}${rel}>${text}</a>`;
46
+ break;
47
+ }
48
+ }
49
+ });
50
+ }
51
+ return text;
52
+ }
53
+
54
+ // 2. Traversal of Child Nodes
55
+ const childrenHtml = Array.isArray(node.content)
56
+ ? node.content.map(renderTiptapNode).join("")
57
+ : "";
58
+
59
+ // 3. Block Level Mapping
60
+ switch (node.type) {
61
+ case "doc":
62
+ return childrenHtml;
63
+ case "paragraph":
64
+ return childrenHtml.trim() ? `<p>${childrenHtml}</p>` : "";
65
+ case "heading": {
66
+ const level = Math.min(Math.max(node.attrs?.level || 2, 1), 6);
67
+ return `<h${level}>${childrenHtml}</h${level}>`;
68
+ }
69
+ case "bulletList":
70
+ return `<ul>${childrenHtml}</ul>`;
71
+ case "orderedList": {
72
+ const start = node.attrs?.start ? ` start="${node.attrs.start}"` : "";
73
+ return `<ol${start}>${childrenHtml}</ol>`;
74
+ }
75
+ case "listItem":
76
+ return `<li>${childrenHtml}</li>`;
77
+ case "blockquote":
78
+ return `<blockquote>${childrenHtml}</blockquote>`;
79
+ case "codeBlock": {
80
+ const lang = node.attrs?.language ? ` class="language-${escapeHtml(node.attrs.language)}"` : "";
81
+ return `<pre><code${lang}>${childrenHtml}</code></pre>`;
82
+ }
83
+ case "horizontalRule":
84
+ return "<hr />";
85
+ case "hardBreak":
86
+ return "<br />";
87
+ default:
88
+ return childrenHtml;
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Universal helper that handles plain strings, legacy objects, and Tiptap ASTs safely.
94
+ */
95
+ export function extractHtml(value: any): string {
96
+ if (!value) return "";
97
+ if (typeof value === "string") return value; // Already HTML/string
98
+ if (typeof value === "object") {
99
+ if (value.type === "doc") return renderTiptapNode(value);
100
+ if (value.html) return value.html;
101
+ if (value.markup) return value.markup;
102
+ }
103
+ return String(value);
104
+ }
105
+
106
+ function escapeHtml(str: string): string {
107
+ return str
108
+ .replace(/&/g, "&amp;")
109
+ .replace(/</g, "&lt;")
110
+ .replace(/>/g, "&gt;")
111
+ .replace(/"/g, "&quot;")
112
+ .replace(/'/g, "&#039;");
113
+ }
@@ -1,52 +0,0 @@
1
- id: _page
2
- label: Page Old
3
- inTree: true
4
- inVault: true
5
- hasCanvas: true
6
-
7
- layout:
8
- columns: "10fr 4fr"
9
- areas:
10
- - ["header", "header"]
11
- - ["main", "sidebar"]
12
- - ["footer", "footer"]
13
-
14
- regions:
15
- header:
16
- label: Header
17
- inherit: parent
18
- main:
19
- label: Content
20
- sidebar:
21
- label: Sidebar
22
- footer:
23
- label: Footer
24
- inherit: parent
25
-
26
- allowedBlocks:
27
- - header
28
- - hero
29
- - text
30
- - image_card
31
-
32
- # 👉 TABS DEFINITION
33
- tabs:
34
- settings:
35
- label: "Page"
36
- fields:
37
- # Imports core fields: title, slug, isHidden
38
- - fieldSet: page-base
39
-
40
- # Overrides & custom properties
41
- - menuTitle:
42
- label: Menu Title
43
- type: text
44
- dbType: text
45
- public: true
46
- placeholder: "Short title for navigation menus"
47
-
48
- # SEO / OpenGraph metadata
49
- - fieldSet: meta
50
-
51
- # Role permissions & access control
52
- - fieldSet: access
@@ -1,151 +0,0 @@
1
- ---
2
- // src/layouts/Layout.astro
3
- import Header from "@/components/global/Header.astro";
4
- import Footer from "@/components/global/Footer.astro";
5
- import SeoHead from "@/components/seo/SeoHead.astro";
6
- import type { TreeNode, ContentBlock } from "@/lib/cms";
7
-
8
- interface Props {
9
- node: Record<string, any>;
10
- content: Record<string, ContentBlock[]>;
11
- normalizedSlug: string;
12
- isHomePage: boolean;
13
- hasSidebar?: boolean;
14
- headerBlocks?: ContentBlock[];
15
- tree?: TreeNode[];
16
- }
17
-
18
- const {
19
- node = {},
20
- content = {},
21
- normalizedSlug,
22
- isHomePage,
23
- headerBlocks = [],
24
- tree,
25
- } = Astro.props;
26
-
27
- const footerBlocks = content?.footer || [];
28
- ---
29
-
30
- <html lang="en">
31
- <head>
32
- <!-- 🎯 All the heavy SEO math is securely isolated in this component -->
33
- <SeoHead
34
- node={node}
35
- content={content}
36
- normalizedSlug={normalizedSlug}
37
- isHomePage={isHomePage}
38
- headerBlocks={headerBlocks}
39
- />
40
- </head>
41
- <body>
42
- <Header node={node} tree={tree} />
43
-
44
- <main class="content-block">
45
- <div class="content-container">
46
- <slot />
47
- </div>
48
- </main>
49
-
50
- <Footer blocks={footerBlocks} contentMap={content} />
51
- </body>
52
- </html>
53
-
54
- <style is:global>
55
- *,
56
- *::before,
57
- *::after {
58
- box-sizing: border-box;
59
- border-radius: 0 !important;
60
- }
61
-
62
- html {
63
- font-size: 16px;
64
- -webkit-text-size-adjust: 100%;
65
- -webkit-font-smoothing: antialiased;
66
- -moz-osx-font-smoothing: grayscale;
67
- text-rendering: optimizeLegibility;
68
- }
69
-
70
- body {
71
- margin: 0;
72
- padding: 0;
73
- font-family:
74
- system-ui,
75
- -apple-system,
76
- BlinkMacSystemFont,
77
- "Segoe UI",
78
- Roboto,
79
- Helvetica,
80
- Arial,
81
- sans-serif;
82
- font-display: swap;
83
- background-color: #ffffff;
84
- color: #1e293b;
85
- min-height: 100vh;
86
- display: flex;
87
- flex-direction: column;
88
- line-height: 1.65;
89
- letter-spacing: -0.011em;
90
- }
91
-
92
- h1,
93
- h2,
94
- h3,
95
- h4,
96
- h5,
97
- h6 {
98
- color: #0f172a;
99
- font-weight: 700;
100
- line-height: 1.25;
101
- letter-spacing: -0.022em;
102
- margin-top: 1.6em;
103
- margin-bottom: 0.6em;
104
- }
105
-
106
- h1 {
107
- font-size: 2.25rem;
108
- }
109
- h2 {
110
- font-size: 1.65rem;
111
- }
112
- h3 {
113
- font-size: 1.35rem;
114
- }
115
-
116
- p {
117
- margin-top: 0;
118
- margin-bottom: 1.25em;
119
- color: #334155;
120
- font-size: 1.03rem;
121
- }
122
-
123
- a {
124
- color: #0284c7;
125
- text-decoration-thickness: 1px;
126
- text-underline-offset: 3px;
127
- transition: color 0.15s ease;
128
- }
129
-
130
- a:hover {
131
- color: #0369a1;
132
- }
133
- li > p:last-child {
134
- margin-bottom: 0;
135
- }
136
- </style>
137
-
138
- <style>
139
- .content-block {
140
- flex: 1;
141
- padding: 40px 0 64px 0;
142
- }
143
-
144
- .content-container {
145
- max-width: 1400px;
146
- margin: 0 auto;
147
- padding: 0 24px;
148
- width: 100%;
149
- box-sizing: border-box;
150
- }
151
- </style>