@37signals/lexxy 0.8.2-beta → 0.8.6-beta
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/lexxy.esm.js +1640 -1188
- package/dist/lexxy_helpers.esm.js +116 -1
- package/dist/stylesheets/lexxy-content.css +10 -10
- package/dist/stylesheets/lexxy-editor.css +169 -11
- package/package.json +1 -1
|
@@ -50,6 +50,10 @@ function generateDomId(prefix) {
|
|
|
50
50
|
return `${prefix}-${randomPart}`
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
function extractPlainTextFromHtml(innerHtml = "") {
|
|
54
|
+
return parseHtml(innerHtml).body.textContent.trim()
|
|
55
|
+
}
|
|
56
|
+
|
|
53
57
|
function highlightCode() {
|
|
54
58
|
const elements = document.querySelectorAll("pre[data-language]");
|
|
55
59
|
|
|
@@ -65,12 +69,123 @@ function highlightElement(preElement) {
|
|
|
65
69
|
const grammar = Prism.languages?.[language];
|
|
66
70
|
if (!grammar) return
|
|
67
71
|
|
|
72
|
+
// Extract highlight ranges before Prism destroys <mark> elements
|
|
73
|
+
const highlights = extractHighlightRanges(preElement);
|
|
74
|
+
|
|
68
75
|
// unescape HTML entities in the code block
|
|
69
76
|
code = new DOMParser().parseFromString(code, "text/html").body.textContent || "";
|
|
70
77
|
|
|
71
78
|
const highlightedHtml = Prism.highlight(code, grammar, language);
|
|
72
79
|
const codeElement = createElement("code", { "data-language": language, innerHTML: highlightedHtml });
|
|
80
|
+
|
|
81
|
+
if (highlights.length > 0) {
|
|
82
|
+
applyHighlightRanges(codeElement, highlights);
|
|
83
|
+
}
|
|
84
|
+
|
|
73
85
|
preElement.replaceWith(codeElement);
|
|
74
86
|
}
|
|
75
87
|
|
|
76
|
-
|
|
88
|
+
// Walk the DOM tree inside a <pre> element and build a list of
|
|
89
|
+
// { start, end, style } ranges for every <mark> element found.
|
|
90
|
+
function extractHighlightRanges(preElement) {
|
|
91
|
+
const ranges = [];
|
|
92
|
+
const root = preElement.querySelector("code") || preElement;
|
|
93
|
+
|
|
94
|
+
let offset = 0;
|
|
95
|
+
|
|
96
|
+
function walk(node) {
|
|
97
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
98
|
+
offset += node.textContent.length;
|
|
99
|
+
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
100
|
+
if (node.tagName === "BR") {
|
|
101
|
+
offset += 1;
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const isMark = node.tagName === "MARK";
|
|
106
|
+
const start = offset;
|
|
107
|
+
|
|
108
|
+
for (const child of node.childNodes) {
|
|
109
|
+
walk(child);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (isMark) {
|
|
113
|
+
const style = extractStyle(node);
|
|
114
|
+
if (style) {
|
|
115
|
+
ranges.push({ start, end: offset, style });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
for (const child of root.childNodes) {
|
|
122
|
+
walk(child);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return ranges
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function extractStyle(element) {
|
|
129
|
+
const parts = [];
|
|
130
|
+
if (element.style?.color) parts.push(`color: ${element.style.color};`);
|
|
131
|
+
if (element.style?.backgroundColor) parts.push(`background-color: ${element.style.backgroundColor};`);
|
|
132
|
+
return parts.length > 0 ? parts.join(" ") : null
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Wrap character ranges in <mark> elements within a Prism-highlighted DOM tree.
|
|
136
|
+
// Each range is applied independently, re-collecting text nodes each time to
|
|
137
|
+
// account for splits from previous ranges.
|
|
138
|
+
function applyHighlightRanges(element, highlights) {
|
|
139
|
+
for (const { start, end, style } of highlights) {
|
|
140
|
+
wrapRange(element, start, end, style);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function wrapRange(container, rangeStart, rangeEnd, style) {
|
|
145
|
+
const textNodes = collectTextNodes(container);
|
|
146
|
+
|
|
147
|
+
// Process in reverse so DOM mutations don't shift earlier text node offsets
|
|
148
|
+
for (let i = textNodes.length - 1; i >= 0; i--) {
|
|
149
|
+
const { node, start: nodeStart, end: nodeEnd } = textNodes[i];
|
|
150
|
+
const overlapStart = Math.max(rangeStart, nodeStart);
|
|
151
|
+
const overlapEnd = Math.min(rangeEnd, nodeEnd);
|
|
152
|
+
if (overlapStart >= overlapEnd) continue
|
|
153
|
+
|
|
154
|
+
const relStart = overlapStart - nodeStart;
|
|
155
|
+
const relEnd = overlapEnd - nodeStart;
|
|
156
|
+
const text = node.textContent;
|
|
157
|
+
const parent = node.parentNode;
|
|
158
|
+
|
|
159
|
+
const mark = document.createElement("mark");
|
|
160
|
+
mark.setAttribute("style", style);
|
|
161
|
+
mark.textContent = text.slice(relStart, relEnd);
|
|
162
|
+
|
|
163
|
+
if (relEnd < text.length) {
|
|
164
|
+
parent.insertBefore(document.createTextNode(text.slice(relEnd)), node.nextSibling);
|
|
165
|
+
}
|
|
166
|
+
parent.insertBefore(mark, node.nextSibling);
|
|
167
|
+
|
|
168
|
+
if (relStart > 0) {
|
|
169
|
+
node.textContent = text.slice(0, relStart);
|
|
170
|
+
} else {
|
|
171
|
+
parent.removeChild(node);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function collectTextNodes(root) {
|
|
177
|
+
const nodes = [];
|
|
178
|
+
let offset = 0;
|
|
179
|
+
const walker = document.createTreeWalker(root, 4 /* NodeFilter.SHOW_TEXT */);
|
|
180
|
+
|
|
181
|
+
let node;
|
|
182
|
+
while ((node = walker.nextNode())) {
|
|
183
|
+
const length = node.textContent.length;
|
|
184
|
+
nodes.push({ node, start: offset, end: offset + length });
|
|
185
|
+
offset += length;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return nodes
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export { addBlockSpacing, createAttachmentFigure, createElement, dispatch, extractPlainTextFromHtml, generateDomId, highlightCode, isPreviewableImage, parseHtml };
|
|
@@ -55,6 +55,10 @@
|
|
|
55
55
|
text-decoration: underline;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
.lexxy-content__strikethrough.lexxy-content__underline {
|
|
59
|
+
text-decoration: line-through underline;
|
|
60
|
+
}
|
|
61
|
+
|
|
58
62
|
mark,
|
|
59
63
|
.lexxy-content__highlight {
|
|
60
64
|
background-color: transparent;
|
|
@@ -419,16 +423,12 @@
|
|
|
419
423
|
text-align: center;
|
|
420
424
|
|
|
421
425
|
.attachment {
|
|
422
|
-
display: inline-
|
|
426
|
+
display: inline-flex;
|
|
427
|
+
flex-direction: column;
|
|
428
|
+
gap: 0;
|
|
423
429
|
inline-size: calc(33.333% - 0.8ch);
|
|
424
430
|
vertical-align: top;
|
|
425
431
|
|
|
426
|
-
img {
|
|
427
|
-
margin: auto;
|
|
428
|
-
max-block-size: 50rem;
|
|
429
|
-
object-fit: contain;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
432
|
.attachment__caption {
|
|
433
433
|
padding-block-end: 1ch;
|
|
434
434
|
}
|
|
@@ -451,10 +451,12 @@
|
|
|
451
451
|
--lexxy-attachment-image-size: 1em;
|
|
452
452
|
--lexxy-attachment-text-color: currentColor;
|
|
453
453
|
|
|
454
|
+
align-items: center;
|
|
454
455
|
background: var(--lexxy-attachment-bg-color);
|
|
455
456
|
border-radius: var(--lexxy-radius);
|
|
456
457
|
color: var(--lexxy-attachment-text-color);
|
|
457
|
-
display: inline;
|
|
458
|
+
display: inline-flex;
|
|
459
|
+
gap: 0.25ch;
|
|
458
460
|
margin: 0;
|
|
459
461
|
padding: 0;
|
|
460
462
|
position: relative;
|
|
@@ -464,8 +466,6 @@
|
|
|
464
466
|
block-size: var(--lexxy-attachment-image-size);
|
|
465
467
|
border-radius: 50%;
|
|
466
468
|
inline-size: var(--lexxy-attachment-image-size);
|
|
467
|
-
margin-inline-end: 0.25ch;
|
|
468
|
-
vertical-align: middle;
|
|
469
469
|
}
|
|
470
470
|
}
|
|
471
471
|
|
|
@@ -186,6 +186,8 @@
|
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
.attachment {
|
|
189
|
+
background-color: var(--lexxy-color-canvas);
|
|
190
|
+
|
|
189
191
|
progress {
|
|
190
192
|
max-inline-size: 10ch;
|
|
191
193
|
margin: auto;
|
|
@@ -205,6 +207,93 @@
|
|
|
205
207
|
inset-inline-start: unset;
|
|
206
208
|
}
|
|
207
209
|
}
|
|
210
|
+
|
|
211
|
+
&.attachment--error {
|
|
212
|
+
background: color-mix(var(--lexxy-color-red) 10%, transparent);
|
|
213
|
+
padding: 2ch;
|
|
214
|
+
|
|
215
|
+
&:before {
|
|
216
|
+
align-items: center;
|
|
217
|
+
aspect-ratio: 1;
|
|
218
|
+
background: var(--lexxy-color-red);
|
|
219
|
+
block-size: 1.5lh;
|
|
220
|
+
border-radius: 50%;
|
|
221
|
+
color: white;
|
|
222
|
+
content: "!";
|
|
223
|
+
display: flex;
|
|
224
|
+
justify-content: center;
|
|
225
|
+
margin: auto;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
> div {
|
|
229
|
+
flex: 1;
|
|
230
|
+
font-size: 0.85em;
|
|
231
|
+
padding: 1ch;
|
|
232
|
+
text-align: start;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.attachment[draggable] {
|
|
238
|
+
cursor: grab;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.attachment.lexxy-dragging {
|
|
242
|
+
opacity: 0.4;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
[class*="lexxy-drop-target--"] {
|
|
246
|
+
position: relative;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/* Horizontal line indicator for block and list drops */
|
|
250
|
+
.lexxy-drop-target--block-before::before,
|
|
251
|
+
.lexxy-drop-target--block-after::after,
|
|
252
|
+
.lexxy-drop-target--list-before::before,
|
|
253
|
+
.lexxy-drop-target--list-after::after {
|
|
254
|
+
background-color: var(--lexxy-focus-ring-color);
|
|
255
|
+
block-size: 3px;
|
|
256
|
+
border-radius: 1px;
|
|
257
|
+
content: "";
|
|
258
|
+
inset-inline: 0;
|
|
259
|
+
pointer-events: none;
|
|
260
|
+
position: absolute;
|
|
261
|
+
transform: translate(0, 0.5ch);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.lexxy-drop-target--block-before::before,
|
|
265
|
+
.lexxy-drop-target--list-before::before {
|
|
266
|
+
transform: translate(0, -0.5ch);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.lexxy-drop-target--block-before::before,
|
|
270
|
+
.lexxy-drop-target--list-before::before {
|
|
271
|
+
inset-block-start: -2px;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.lexxy-drop-target--block-after::after,
|
|
275
|
+
.lexxy-drop-target--list-after::after {
|
|
276
|
+
inset-block-end: -2px;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/* Vertical line indicator for gallery merge and reorder */
|
|
280
|
+
.lexxy-drop-target--gallery-before::before,
|
|
281
|
+
.lexxy-drop-target--gallery-after::after {
|
|
282
|
+
background-color: var(--lexxy-focus-ring-color);
|
|
283
|
+
border-radius: 1px;
|
|
284
|
+
content: "";
|
|
285
|
+
inset-block: 0;
|
|
286
|
+
inline-size: 3px;
|
|
287
|
+
pointer-events: none;
|
|
288
|
+
position: absolute;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.lexxy-drop-target--gallery-before::before {
|
|
292
|
+
inset-inline-start: -4px;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.lexxy-drop-target--gallery-after::after {
|
|
296
|
+
inset-inline-end: -4px;
|
|
208
297
|
}
|
|
209
298
|
|
|
210
299
|
.attachment:hover:not(.node--selected) {
|
|
@@ -225,27 +314,35 @@
|
|
|
225
314
|
/* ------------------------------------------------------------------------ */
|
|
226
315
|
|
|
227
316
|
.attachment-gallery {
|
|
317
|
+
--lexxy-attachment-gallery-columns: 3;
|
|
228
318
|
--lexxy-attachment-gallery-gap: 0.4ch;
|
|
229
319
|
--lexxy-focus-ring-offset: -6px;
|
|
230
320
|
|
|
231
|
-
display: block;
|
|
232
321
|
padding: 0;
|
|
233
322
|
|
|
234
323
|
.attachment {
|
|
324
|
+
background: transparent;
|
|
325
|
+
box-sizing: border-box;
|
|
235
326
|
margin: var(--lexxy-attachment-gallery-gap);
|
|
236
327
|
padding: 0;
|
|
237
328
|
padding-block-end: var(--lexxy-attachment-gap);
|
|
238
329
|
vertical-align: top;
|
|
239
330
|
|
|
240
|
-
&.attachment--error {
|
|
241
|
-
padding: 2ch;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
331
|
img {
|
|
245
332
|
box-sizing: border-box;
|
|
246
333
|
padding: 1ch;
|
|
247
334
|
padding-block-end: 0;
|
|
248
335
|
}
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
&.attachment--error {
|
|
339
|
+
background: color-mix(var(--lexxy-color-red) 10%, transparent);
|
|
340
|
+
padding: 2ch;
|
|
341
|
+
|
|
342
|
+
> div {
|
|
343
|
+
text-align: center;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
249
346
|
}
|
|
250
347
|
}
|
|
251
348
|
|
|
@@ -339,7 +436,16 @@
|
|
|
339
436
|
padding: 2px;
|
|
340
437
|
position: relative;
|
|
341
438
|
|
|
342
|
-
&[data-attachments="false"] button[name="
|
|
439
|
+
&[data-attachments="false"] button[name="image"],
|
|
440
|
+
&[data-attachments="false"] button[name="file"] {
|
|
441
|
+
display: none;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
&[data-upload="file"] button[name="image"] {
|
|
445
|
+
display: none;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
&[data-upload="image"] button[name="file"] {
|
|
343
449
|
display: none;
|
|
344
450
|
}
|
|
345
451
|
|
|
@@ -406,6 +512,25 @@
|
|
|
406
512
|
user-select: none;
|
|
407
513
|
-webkit-user-select: none;
|
|
408
514
|
|
|
515
|
+
&.lexxy-editor__toolbar-dropdown--chevron {
|
|
516
|
+
summary {
|
|
517
|
+
aspect-ratio: unset;
|
|
518
|
+
gap: 0.5ch;
|
|
519
|
+
grid-template-columns: 2fr 1fr;
|
|
520
|
+
padding-inline: 0.75ch;
|
|
521
|
+
|
|
522
|
+
&:after {
|
|
523
|
+
block-size: 0.3ch;
|
|
524
|
+
border-block-end: 2px solid currentcolor;
|
|
525
|
+
border-inline-end: 2px solid currentcolor;
|
|
526
|
+
content: "";
|
|
527
|
+
display: inline-block;
|
|
528
|
+
inline-size: 0.3ch;
|
|
529
|
+
transform: rotate(45deg);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
409
534
|
summary ~ * {
|
|
410
535
|
background-color: var(--lexxy-color-canvas);
|
|
411
536
|
border: 2px solid var(--lexxy-color-selected-hover);
|
|
@@ -444,6 +569,44 @@
|
|
|
444
569
|
}
|
|
445
570
|
}
|
|
446
571
|
|
|
572
|
+
.lexxy-editor__toolbar-dropdown-list {
|
|
573
|
+
border-start-start-radius: 0;
|
|
574
|
+
flex-direction: column;
|
|
575
|
+
gap: 0.1ch;
|
|
576
|
+
padding: 0.1ch;
|
|
577
|
+
|
|
578
|
+
button {
|
|
579
|
+
align-items: center;
|
|
580
|
+
display: flex;
|
|
581
|
+
flex-direction: row;
|
|
582
|
+
gap: 1ch;
|
|
583
|
+
padding: 1ch;
|
|
584
|
+
|
|
585
|
+
&[aria-pressed="true"] {
|
|
586
|
+
background-color: var(--lexxy-color-selected);
|
|
587
|
+
|
|
588
|
+
&:hover {
|
|
589
|
+
background-color: var(--lexxy-color-selected-hover);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
span {
|
|
594
|
+
font-size: var(--lexxy-text-small);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
svg {
|
|
598
|
+
block-size: 1lh;
|
|
599
|
+
inline-size: 1lh;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
.separator {
|
|
604
|
+
background: var(--lexxy-color-ink-lighter);
|
|
605
|
+
block-size: 1px;
|
|
606
|
+
inline-size: 100%;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
447
610
|
|
|
448
611
|
/* --------------------------------------------------------------------------
|
|
449
612
|
/* Overflow menu */
|
|
@@ -905,7 +1068,6 @@ action-text-attachment[content-type^="application/vnd.actiontext"] {
|
|
|
905
1068
|
}
|
|
906
1069
|
|
|
907
1070
|
lexxy-node-delete-button {
|
|
908
|
-
display: none;
|
|
909
1071
|
inset-inline-start: 0;
|
|
910
1072
|
line-height: 1lh;
|
|
911
1073
|
|
|
@@ -920,8 +1082,4 @@ action-text-attachment[content-type^="application/vnd.actiontext"] {
|
|
|
920
1082
|
}
|
|
921
1083
|
}
|
|
922
1084
|
}
|
|
923
|
-
|
|
924
|
-
&.node--selected lexxy-node-delete-button {
|
|
925
|
-
display: block;
|
|
926
|
-
}
|
|
927
1085
|
}
|