@0m0g1/griot 0.1.2 → 0.1.3
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/package.json +1 -1
- package/src/blocks/BlockSchema.js +20 -90
- package/src/core/Block.js +42 -45
- package/src/core/Document.js +63 -98
- package/src/core/History.js +17 -42
- package/src/editor/Editor.js +405 -138
- package/src/editor/FormatToolbar.js +138 -0
- package/src/editor/Keyboard.js +92 -106
- package/src/inline/InlineLexer.js +69 -72
- package/src/inline/InlineRenderer.js +110 -95
|
@@ -2,116 +2,133 @@
|
|
|
2
2
|
// Renders inline token arrays to either:
|
|
3
3
|
// a) A DocumentFragment (DOM nodes) — used by Viewer and Editor live preview
|
|
4
4
|
// b) An HTML string — used for SSR / export
|
|
5
|
-
//
|
|
6
|
-
// Callbacks:
|
|
7
|
-
// onEventClick(eventId) — called when an [[event:]] chip is clicked
|
|
8
|
-
// onCiteClick(blockId) — called when a [[cite:]] chip is clicked
|
|
9
5
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
10
6
|
|
|
11
7
|
import { tokenizeInline, TOKEN } from './InlineLexer.js';
|
|
12
8
|
|
|
13
|
-
//
|
|
9
|
+
// ── DOM rendering ─────────────────────────────────────────────────────────────
|
|
10
|
+
|
|
14
11
|
export function renderInlineToDOM(text = '', { onEventClick, onCiteClick } = {}) {
|
|
15
12
|
const frag = document.createDocumentFragment();
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
switch (t.type) {
|
|
22
|
-
case TOKEN.TEXT:
|
|
23
|
-
node = document.createTextNode(t.text);
|
|
24
|
-
break;
|
|
25
|
-
|
|
26
|
-
case TOKEN.BOLD:
|
|
27
|
-
node = document.createElement('strong');
|
|
28
|
-
node.textContent = t.text;
|
|
29
|
-
break;
|
|
30
|
-
|
|
31
|
-
case TOKEN.ITALIC:
|
|
32
|
-
node = document.createElement('em');
|
|
33
|
-
node.textContent = t.text;
|
|
34
|
-
break;
|
|
35
|
-
|
|
36
|
-
case TOKEN.CODE: {
|
|
37
|
-
node = document.createElement('code');
|
|
38
|
-
node.className = 'griot-inline-code';
|
|
39
|
-
node.textContent = t.text;
|
|
40
|
-
break;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
case TOKEN.LINK: {
|
|
44
|
-
node = document.createElement('a');
|
|
45
|
-
node.href = t.href;
|
|
46
|
-
node.target = '_blank';
|
|
47
|
-
node.rel = 'noopener noreferrer';
|
|
48
|
-
node.className = 'griot-link';
|
|
49
|
-
node.textContent = t.text;
|
|
50
|
-
break;
|
|
51
|
-
}
|
|
13
|
+
for (const t of tokenizeInline(text)) {
|
|
14
|
+
frag.appendChild(_toNode(t, { onEventClick, onCiteClick }));
|
|
15
|
+
}
|
|
16
|
+
return frag;
|
|
17
|
+
}
|
|
52
18
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
node.type = 'button';
|
|
56
|
-
node.className = 'griot-chip griot-chip--event';
|
|
57
|
-
node.dataset.eventId = t.eventId;
|
|
58
|
-
node.innerHTML = `<span class="griot-chip__icon">⏱</span><span class="griot-chip__label">${escHtml(t.label)}</span>`;
|
|
59
|
-
if (onEventClick) {
|
|
60
|
-
node.addEventListener('click', (e) => {
|
|
61
|
-
e.stopPropagation();
|
|
62
|
-
onEventClick(t.eventId);
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
19
|
+
function _toNode(t, opts) {
|
|
20
|
+
switch (t.type) {
|
|
67
21
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
node.type = 'button';
|
|
71
|
-
node.className = 'griot-chip griot-chip--cite';
|
|
72
|
-
node.dataset.blockId = t.blockId;
|
|
73
|
-
node.innerHTML = `<span class="griot-chip__icon">📖</span><span class="griot-chip__label">${escHtml(t.label)}</span>`;
|
|
74
|
-
if (onCiteClick) {
|
|
75
|
-
node.addEventListener('click', (e) => {
|
|
76
|
-
e.stopPropagation();
|
|
77
|
-
onCiteClick(t.blockId);
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
break;
|
|
81
|
-
}
|
|
22
|
+
case TOKEN.TEXT:
|
|
23
|
+
return document.createTextNode(t.text);
|
|
82
24
|
|
|
83
|
-
|
|
84
|
-
|
|
25
|
+
case TOKEN.BOLD: {
|
|
26
|
+
const el = document.createElement('strong');
|
|
27
|
+
el.textContent = t.text;
|
|
28
|
+
return el;
|
|
29
|
+
}
|
|
30
|
+
case TOKEN.ITALIC: {
|
|
31
|
+
const el = document.createElement('em');
|
|
32
|
+
el.textContent = t.text;
|
|
33
|
+
return el;
|
|
34
|
+
}
|
|
35
|
+
case TOKEN.UNDERLINE: {
|
|
36
|
+
const el = document.createElement('u');
|
|
37
|
+
el.className = 'griot-underline';
|
|
38
|
+
el.textContent = t.text;
|
|
39
|
+
return el;
|
|
40
|
+
}
|
|
41
|
+
case TOKEN.STRIKE: {
|
|
42
|
+
const el = document.createElement('s');
|
|
43
|
+
el.className = 'griot-strike';
|
|
44
|
+
el.textContent = t.text;
|
|
45
|
+
return el;
|
|
46
|
+
}
|
|
47
|
+
case TOKEN.HIGHLIGHT: {
|
|
48
|
+
const el = document.createElement('mark');
|
|
49
|
+
el.className = 'griot-highlight';
|
|
50
|
+
el.textContent = t.text;
|
|
51
|
+
return el;
|
|
52
|
+
}
|
|
53
|
+
case TOKEN.COLOR_MARK: {
|
|
54
|
+
const el = document.createElement('span');
|
|
55
|
+
el.className = 'griot-color-mark';
|
|
56
|
+
el.style.color = t.color;
|
|
57
|
+
el.textContent = t.text;
|
|
58
|
+
return el;
|
|
59
|
+
}
|
|
60
|
+
case TOKEN.CODE: {
|
|
61
|
+
const el = document.createElement('code');
|
|
62
|
+
el.className = 'griot-inline-code';
|
|
63
|
+
el.textContent = t.code;
|
|
64
|
+
return el;
|
|
65
|
+
}
|
|
66
|
+
case TOKEN.IMAGE: {
|
|
67
|
+
const el = document.createElement('img');
|
|
68
|
+
el.src = t.src;
|
|
69
|
+
el.alt = t.alt ?? '';
|
|
70
|
+
el.className = 'griot-inline-img';
|
|
71
|
+
return el;
|
|
72
|
+
}
|
|
73
|
+
case TOKEN.LINK: {
|
|
74
|
+
const el = document.createElement('a');
|
|
75
|
+
el.href = t.href;
|
|
76
|
+
el.target = '_blank';
|
|
77
|
+
el.rel = 'noopener noreferrer';
|
|
78
|
+
el.className = 'griot-link';
|
|
79
|
+
el.textContent = t.label;
|
|
80
|
+
return el;
|
|
81
|
+
}
|
|
82
|
+
case TOKEN.EVENT_REF: {
|
|
83
|
+
const el = document.createElement('button');
|
|
84
|
+
el.type = 'button';
|
|
85
|
+
el.className = 'griot-chip griot-chip--event';
|
|
86
|
+
el.dataset.eventId = t.eventId;
|
|
87
|
+
el.innerHTML = `<span class="griot-chip__icon">⏱</span><span class="griot-chip__label">${escHtml(t.label)}</span>`;
|
|
88
|
+
if (opts.onEventClick) el.addEventListener('click', (e) => { e.stopPropagation(); opts.onEventClick(t.eventId); });
|
|
89
|
+
return el;
|
|
90
|
+
}
|
|
91
|
+
case TOKEN.CITE_REF: {
|
|
92
|
+
const el = document.createElement('button');
|
|
93
|
+
el.type = 'button';
|
|
94
|
+
el.className = 'griot-chip griot-chip--cite';
|
|
95
|
+
el.dataset.blockId = t.blockId;
|
|
96
|
+
el.innerHTML = `<span class="griot-chip__icon">📖</span><span class="griot-chip__label">${escHtml(t.label)}</span>`;
|
|
97
|
+
if (opts.onCiteClick) el.addEventListener('click', (e) => { e.stopPropagation(); opts.onCiteClick(t.blockId); });
|
|
98
|
+
return el;
|
|
85
99
|
}
|
|
86
100
|
|
|
87
|
-
|
|
101
|
+
default:
|
|
102
|
+
return document.createTextNode(t.text ?? '');
|
|
88
103
|
}
|
|
89
|
-
|
|
90
|
-
return frag;
|
|
91
104
|
}
|
|
92
105
|
|
|
93
|
-
//
|
|
106
|
+
// ── HTML string rendering ─────────────────────────────────────────────────────
|
|
107
|
+
|
|
94
108
|
export function renderInlineToHTML(text = '') {
|
|
95
|
-
|
|
96
|
-
|
|
109
|
+
return tokenizeInline(text).map(_toHTML).join('');
|
|
110
|
+
}
|
|
97
111
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
112
|
+
function _toHTML(t) {
|
|
113
|
+
switch (t.type) {
|
|
114
|
+
case TOKEN.TEXT: return escHtml(t.text);
|
|
115
|
+
case TOKEN.BOLD: return `<strong>${escHtml(t.text)}</strong>`;
|
|
116
|
+
case TOKEN.ITALIC: return `<em>${escHtml(t.text)}</em>`;
|
|
117
|
+
case TOKEN.UNDERLINE: return `<u class="griot-underline">${escHtml(t.text)}</u>`;
|
|
118
|
+
case TOKEN.STRIKE: return `<s class="griot-strike">${escHtml(t.text)}</s>`;
|
|
119
|
+
case TOKEN.HIGHLIGHT: return `<mark class="griot-highlight">${escHtml(t.text)}</mark>`;
|
|
120
|
+
case TOKEN.COLOR_MARK: return `<span class="griot-color-mark" style="color:${escAttr(t.color)}">${escHtml(t.text)}</span>`;
|
|
121
|
+
case TOKEN.CODE: return `<code class="griot-inline-code">${escHtml(t.code)}</code>`;
|
|
122
|
+
case TOKEN.IMAGE: return `<img class="griot-inline-img" src="${escAttr(t.src)}" alt="${escAttr(t.alt ?? '')}">`;
|
|
123
|
+
case TOKEN.LINK: return `<a class="griot-link" href="${escAttr(t.href)}" target="_blank" rel="noopener noreferrer">${escHtml(t.label)}</a>`;
|
|
124
|
+
case TOKEN.EVENT_REF: return `<button type="button" class="griot-chip griot-chip--event" data-event-id="${escAttr(t.eventId)}"><span class="griot-chip__icon">⏱</span><span class="griot-chip__label">${escHtml(t.label)}</span></button>`;
|
|
125
|
+
case TOKEN.CITE_REF: return `<button type="button" class="griot-chip griot-chip--cite" data-block-id="${escAttr(t.blockId)}"><span class="griot-chip__icon">📖</span><span class="griot-chip__label">${escHtml(t.label)}</span></button>`;
|
|
126
|
+
default: return escHtml(t.text ?? '');
|
|
109
127
|
}
|
|
110
|
-
|
|
111
|
-
return html;
|
|
112
128
|
}
|
|
113
129
|
|
|
114
|
-
//
|
|
130
|
+
// ── Escape helpers ────────────────────────────────────────────────────────────
|
|
131
|
+
|
|
115
132
|
export function escHtml(s) {
|
|
116
133
|
return String(s ?? '')
|
|
117
134
|
.replace(/&/g, '&')
|
|
@@ -122,7 +139,5 @@ export function escHtml(s) {
|
|
|
122
139
|
}
|
|
123
140
|
|
|
124
141
|
export function escAttr(s) {
|
|
125
|
-
return String(s ?? '')
|
|
126
|
-
|
|
127
|
-
.replace(/"/g, '"');
|
|
128
|
-
}
|
|
142
|
+
return String(s ?? '').replace(/&/g, '&').replace(/"/g, '"');
|
|
143
|
+
}
|