@elixpo/lixeditor 2.1.6
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 +139 -0
- package/dist/index.cjs +2364 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.js +2337 -0
- package/dist/index.js.map +7 -0
- package/dist/styles/blocks.css +1154 -0
- package/dist/styles/blog-image.css +468 -0
- package/dist/styles/editor.css +499 -0
- package/dist/styles/index.css +11 -0
- package/dist/styles/menus.css +518 -0
- package/dist/styles/preview.css +620 -0
- package/dist/styles/variables.css +75 -0
- package/package.json +53 -0
- package/src/blocks/BlockEquation.jsx +122 -0
- package/src/blocks/ButtonBlock.jsx +90 -0
- package/src/blocks/DateInline.jsx +170 -0
- package/src/blocks/ImageBlock.jsx +274 -0
- package/src/blocks/InlineEquation.jsx +108 -0
- package/src/blocks/MermaidBlock.jsx +430 -0
- package/src/blocks/PDFEmbedBlock.jsx +200 -0
- package/src/blocks/SubpageBlock.jsx +180 -0
- package/src/blocks/TableOfContents.jsx +44 -0
- package/src/blocks/index.js +8 -0
- package/src/editor/KeyboardShortcutsModal.jsx +126 -0
- package/src/editor/LinkPreviewTooltip.jsx +165 -0
- package/src/editor/LixEditor.jsx +342 -0
- package/src/hooks/useLixTheme.js +55 -0
- package/src/index.js +41 -0
- package/src/preview/LixPreview.jsx +191 -0
- package/src/preview/renderBlocks.js +163 -0
- package/src/styles/blocks.css +1154 -0
- package/src/styles/blog-image.css +468 -0
- package/src/styles/editor.css +499 -0
- package/src/styles/index.css +11 -0
- package/src/styles/menus.css +518 -0
- package/src/styles/preview.css +620 -0
- package/src/styles/variables.css +75 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts BlockNote document blocks to HTML string.
|
|
3
|
+
* Used by LixPreview for server-side or static rendering.
|
|
4
|
+
*/
|
|
5
|
+
export function renderBlocksToHTML(blocks) {
|
|
6
|
+
if (!blocks || !blocks.length) return '';
|
|
7
|
+
|
|
8
|
+
function inlineToHTML(content) {
|
|
9
|
+
if (!content || !Array.isArray(content)) return '';
|
|
10
|
+
return content.map((c) => {
|
|
11
|
+
if (c.type === 'inlineEquation' && c.props?.latex) {
|
|
12
|
+
return `<span class="lix-inline-equation" data-latex="${encodeURIComponent(c.props.latex)}"></span>`;
|
|
13
|
+
}
|
|
14
|
+
if (c.type === 'dateInline' && c.props?.date) {
|
|
15
|
+
let formatted;
|
|
16
|
+
try { formatted = new Date(c.props.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); }
|
|
17
|
+
catch { formatted = c.props.date; }
|
|
18
|
+
return `<span class="lix-date-chip"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg> ${formatted}</span>`;
|
|
19
|
+
}
|
|
20
|
+
// Links wrap child content
|
|
21
|
+
if (c.type === 'link' && c.href) {
|
|
22
|
+
const linkText = c.content ? inlineToHTML(c.content) : (c.text || c.href).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
23
|
+
return `<a href="${c.href}">${linkText || c.href}</a>`;
|
|
24
|
+
}
|
|
25
|
+
let text = (c.text || '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
26
|
+
if (!text) return '';
|
|
27
|
+
const s = c.styles || {};
|
|
28
|
+
if (s.bold) text = `<strong>${text}</strong>`;
|
|
29
|
+
if (s.italic) text = `<em>${text}</em>`;
|
|
30
|
+
if (s.strike) text = `<del>${text}</del>`;
|
|
31
|
+
if (s.code) text = `<code>${text}</code>`;
|
|
32
|
+
if (s.underline) text = `<u>${text}</u>`;
|
|
33
|
+
if (s.textColor) text = `<span style="color:${s.textColor}">${text}</span>`;
|
|
34
|
+
if (s.backgroundColor) text = `<span style="background:${s.backgroundColor};border-radius:3px;padding:0 2px">${text}</span>`;
|
|
35
|
+
return text;
|
|
36
|
+
}).join('');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Collect headings for TOC
|
|
40
|
+
const headings = [];
|
|
41
|
+
function collectHeadings(blockList) {
|
|
42
|
+
for (const block of blockList) {
|
|
43
|
+
if (block.type === 'heading') {
|
|
44
|
+
const text = (block.content || []).map(c => c.text || '').join('');
|
|
45
|
+
if (text.trim()) {
|
|
46
|
+
const id = `h-${text.trim().toLowerCase().replace(/[^\w]+/g, '-').slice(0, 40)}`;
|
|
47
|
+
headings.push({ id, text: text.trim(), level: block.props?.level || 1 });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (block.children?.length) collectHeadings(block.children);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
collectHeadings(blocks);
|
|
54
|
+
|
|
55
|
+
function renderBlock(block) {
|
|
56
|
+
const content = inlineToHTML(block.content);
|
|
57
|
+
const childrenHTML = block.children?.length ? renderListGroup(block.children) : '';
|
|
58
|
+
|
|
59
|
+
switch (block.type) {
|
|
60
|
+
case 'tableOfContents':
|
|
61
|
+
return '__TOC_PLACEHOLDER__';
|
|
62
|
+
case 'heading': {
|
|
63
|
+
const level = block.props?.level || 1;
|
|
64
|
+
const text = (block.content || []).map(c => c.text || '').join('');
|
|
65
|
+
const id = `h-${text.trim().toLowerCase().replace(/[^\w]+/g, '-').slice(0, 40)}`;
|
|
66
|
+
return `<h${level} id="${id}">${content}</h${level}>${childrenHTML}`;
|
|
67
|
+
}
|
|
68
|
+
case 'bulletListItem':
|
|
69
|
+
return `<li class="lix-bullet">${content}${childrenHTML}</li>`;
|
|
70
|
+
case 'numberedListItem':
|
|
71
|
+
return `<li class="lix-numbered">${content}${childrenHTML}</li>`;
|
|
72
|
+
case 'checkListItem': {
|
|
73
|
+
const checked = !!block.props?.checked;
|
|
74
|
+
const cb = `<span class="lix-checkbox${checked ? ' lix-checkbox--checked' : ''}"><span class="lix-checkbox-icon">${checked ? '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg>' : ''}</span></span>`;
|
|
75
|
+
return `<li class="lix-check${checked ? ' lix-check--checked' : ''}">${cb}<span class="lix-check-text">${content}</span>${childrenHTML}</li>`;
|
|
76
|
+
}
|
|
77
|
+
case 'blockEquation':
|
|
78
|
+
if (block.props?.latex) return `<div class="lix-block-equation" data-latex="${encodeURIComponent(block.props.latex)}"></div>${childrenHTML}`;
|
|
79
|
+
return childrenHTML;
|
|
80
|
+
case 'mermaidBlock':
|
|
81
|
+
if (block.props?.diagram) return `<div class="lix-mermaid-block" data-diagram="${encodeURIComponent(block.props.diagram)}"></div>${childrenHTML}`;
|
|
82
|
+
return childrenHTML;
|
|
83
|
+
case 'divider':
|
|
84
|
+
return `<hr class="lix-divider" />${childrenHTML}`;
|
|
85
|
+
case 'codeBlock': {
|
|
86
|
+
const lang = block.props?.language || '';
|
|
87
|
+
const code = (block.content || []).map(c => c.text || '').join('');
|
|
88
|
+
return `<pre><code class="language-${lang}">${code.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')}</code></pre>${childrenHTML}`;
|
|
89
|
+
}
|
|
90
|
+
case 'image':
|
|
91
|
+
if (block.props?.url) {
|
|
92
|
+
return `<figure><img src="${block.props.url}" alt="${block.props?.caption || ''}" />${block.props?.caption ? `<figcaption>${block.props.caption}</figcaption>` : ''}</figure>${childrenHTML}`;
|
|
93
|
+
}
|
|
94
|
+
return childrenHTML;
|
|
95
|
+
case 'table': {
|
|
96
|
+
const rows = block.content?.rows || [];
|
|
97
|
+
if (!rows.length) return childrenHTML;
|
|
98
|
+
const headerRows = block.content?.headerRows || 0;
|
|
99
|
+
let table = '<table>';
|
|
100
|
+
rows.forEach((row, ri) => {
|
|
101
|
+
table += '<tr>';
|
|
102
|
+
(row.cells || []).forEach(cell => {
|
|
103
|
+
const tag = ri < headerRows ? 'th' : 'td';
|
|
104
|
+
let cellContent;
|
|
105
|
+
if (Array.isArray(cell)) cellContent = cell;
|
|
106
|
+
else if (cell?.content) cellContent = Array.isArray(cell.content) ? cell.content : [];
|
|
107
|
+
else cellContent = [];
|
|
108
|
+
table += `<${tag}>${inlineToHTML(cellContent)}</${tag}>`;
|
|
109
|
+
});
|
|
110
|
+
table += '</tr>';
|
|
111
|
+
});
|
|
112
|
+
table += '</table>';
|
|
113
|
+
return table + childrenHTML;
|
|
114
|
+
}
|
|
115
|
+
case 'paragraph':
|
|
116
|
+
default:
|
|
117
|
+
if (content) return `<p>${content}</p>${childrenHTML}`;
|
|
118
|
+
return childrenHTML || '';
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function renderListGroup(blockList) {
|
|
123
|
+
if (!blockList?.length) return '';
|
|
124
|
+
const out = [];
|
|
125
|
+
let i = 0;
|
|
126
|
+
while (i < blockList.length) {
|
|
127
|
+
const block = blockList[i];
|
|
128
|
+
if (block.type === 'bulletListItem') {
|
|
129
|
+
let items = '';
|
|
130
|
+
while (i < blockList.length && blockList[i].type === 'bulletListItem') { items += renderBlock(blockList[i]); i++; }
|
|
131
|
+
out.push(`<ul>${items}</ul>`);
|
|
132
|
+
} else if (block.type === 'numberedListItem') {
|
|
133
|
+
let items = '';
|
|
134
|
+
while (i < blockList.length && blockList[i].type === 'numberedListItem') { items += renderBlock(blockList[i]); i++; }
|
|
135
|
+
out.push(`<ol>${items}</ol>`);
|
|
136
|
+
} else if (block.type === 'checkListItem') {
|
|
137
|
+
let items = '';
|
|
138
|
+
while (i < blockList.length && blockList[i].type === 'checkListItem') { items += renderBlock(blockList[i]); i++; }
|
|
139
|
+
out.push(`<ul class="lix-checklist">${items}</ul>`);
|
|
140
|
+
} else {
|
|
141
|
+
out.push(renderBlock(block));
|
|
142
|
+
i++;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return out.join('\n');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let html = renderListGroup(blocks);
|
|
149
|
+
|
|
150
|
+
// Build inline TOC
|
|
151
|
+
if (headings.length > 0) {
|
|
152
|
+
const tocItems = headings.map(h => {
|
|
153
|
+
const indent = (h.level - 1) * 16;
|
|
154
|
+
return `<li><a href="#${h.id}" class="lix-toc-link" style="padding-left:${indent}px">${h.text}</a></li>`;
|
|
155
|
+
}).join('');
|
|
156
|
+
const tocHTML = `<div class="lix-toc-block"><p class="lix-toc-label">Table of Contents</p><ul class="lix-toc-list">${tocItems}</ul></div>`;
|
|
157
|
+
html = html.replace('__TOC_PLACEHOLDER__', tocHTML);
|
|
158
|
+
} else {
|
|
159
|
+
html = html.replace('__TOC_PLACEHOLDER__', '');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return html;
|
|
163
|
+
}
|