@37signals/lexxy 0.9.31 → 0.9.33
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 +1818 -659
- package/dist/lexxy_helpers.esm.js +61 -23
- package/dist/stylesheets/lexxy-editor.css +103 -74
- package/dist/stylesheets/lexxy-variables.css +4 -4
- package/package.json +1 -1
|
@@ -44,12 +44,13 @@ function highlightElement(preElement) {
|
|
|
44
44
|
const grammar = Prism.languages?.[language];
|
|
45
45
|
if (!grammar) return
|
|
46
46
|
|
|
47
|
-
// Read the source text and <mark> ranges in a single walk,
|
|
48
|
-
// rewrites the element. Sharing one traversal keeps the
|
|
49
|
-
// aligned with the code string and preserves leading whitespace —
|
|
50
|
-
// either of them separately (e.g. textContent through DOMParser)
|
|
51
|
-
// leading whitespace and shifts every range, re-indenting the
|
|
52
|
-
|
|
47
|
+
// Read the source text and the <mark> and <a> ranges in a single walk,
|
|
48
|
+
// before Prism rewrites the element. Sharing one traversal keeps the range
|
|
49
|
+
// offsets aligned with the code string and preserves leading whitespace —
|
|
50
|
+
// deriving either of them separately (e.g. textContent through DOMParser)
|
|
51
|
+
// collapses leading whitespace and shifts every range, re-indenting the
|
|
52
|
+
// rendered block.
|
|
53
|
+
const { code, highlights, links } = extractCodeAndMarkup(preElement);
|
|
53
54
|
|
|
54
55
|
const highlightedHtml = Prism.highlight(code, grammar, language);
|
|
55
56
|
preElement.innerHTML = highlightedHtml;
|
|
@@ -58,18 +59,24 @@ function highlightElement(preElement) {
|
|
|
58
59
|
applyHighlightRanges(preElement, highlights);
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
if (links.length > 0) {
|
|
63
|
+
applyLinkRanges(preElement, links);
|
|
64
|
+
}
|
|
65
|
+
|
|
61
66
|
preElement.dataset.highlighted = "true";
|
|
62
67
|
}
|
|
63
68
|
|
|
64
|
-
// Walk the <pre> once, building Prism's source text and the <mark>
|
|
65
|
-
// together: a text node contributes its text verbatim, a <br>
|
|
66
|
-
// newline, and a <mark> records the slice of code it
|
|
67
|
-
// outputs come from the same walk, every range offset is
|
|
68
|
-
// `code` — so the
|
|
69
|
-
// block's leading whitespace survives (HTML parsing would
|
|
70
|
-
|
|
69
|
+
// Walk the <pre> once, building Prism's source text and the <mark> and <a>
|
|
70
|
+
// ranges together: a text node contributes its text verbatim, a <br>
|
|
71
|
+
// contributes a newline, and a <mark> or <a> records the slice of code it
|
|
72
|
+
// covers. Because all outputs come from the same walk, every range offset is
|
|
73
|
+
// just a position in `code` — so the markup can't drift out of sync with the
|
|
74
|
+
// source, and the block's leading whitespace survives (HTML parsing would
|
|
75
|
+
// collapse it).
|
|
76
|
+
function extractCodeAndMarkup(preElement) {
|
|
71
77
|
const root = preElement.querySelector("code") || preElement;
|
|
72
78
|
const highlights = [];
|
|
79
|
+
const links = [];
|
|
73
80
|
let code = "";
|
|
74
81
|
|
|
75
82
|
function walk(node) {
|
|
@@ -87,6 +94,12 @@ function extractCodeAndHighlights(preElement) {
|
|
|
87
94
|
if (style) {
|
|
88
95
|
highlights.push({ start, end: code.length, style });
|
|
89
96
|
}
|
|
97
|
+
} else if (node.tagName === "A" && node.getAttribute("href")) {
|
|
98
|
+
const start = code.length;
|
|
99
|
+
for (const child of node.childNodes) {
|
|
100
|
+
walk(child);
|
|
101
|
+
}
|
|
102
|
+
links.push({ start, end: code.length, attributes: linkAttributes(node) });
|
|
90
103
|
} else {
|
|
91
104
|
for (const child of node.childNodes) {
|
|
92
105
|
walk(child);
|
|
@@ -99,7 +112,17 @@ function extractCodeAndHighlights(preElement) {
|
|
|
99
112
|
walk(child);
|
|
100
113
|
}
|
|
101
114
|
|
|
102
|
-
return { code, highlights }
|
|
115
|
+
return { code, highlights, links }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function linkAttributes(element) {
|
|
119
|
+
const attributes = { href: element.getAttribute("href") };
|
|
120
|
+
for (const name of [ "target", "rel", "title" ]) {
|
|
121
|
+
if (element.getAttribute(name)) {
|
|
122
|
+
attributes[name] = element.getAttribute(name);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return attributes
|
|
103
126
|
}
|
|
104
127
|
|
|
105
128
|
function extractStyle(element) {
|
|
@@ -109,16 +132,32 @@ function extractStyle(element) {
|
|
|
109
132
|
return parts.length > 0 ? parts.join(" ") : null
|
|
110
133
|
}
|
|
111
134
|
|
|
112
|
-
// Wrap character ranges in <mark> elements within a Prism-highlighted
|
|
113
|
-
// Each range is applied independently, re-collecting text nodes
|
|
114
|
-
// account for splits from previous ranges.
|
|
135
|
+
// Wrap character ranges in <mark> or <a> elements within a Prism-highlighted
|
|
136
|
+
// DOM tree. Each range is applied independently, re-collecting text nodes
|
|
137
|
+
// each time to account for splits from previous ranges.
|
|
115
138
|
function applyHighlightRanges(element, highlights) {
|
|
116
139
|
for (const { start, end, style } of highlights) {
|
|
117
|
-
wrapRange(element, start, end,
|
|
140
|
+
wrapRange(element, start, end, () => {
|
|
141
|
+
const mark = document.createElement("mark");
|
|
142
|
+
mark.setAttribute("style", style);
|
|
143
|
+
return mark
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function applyLinkRanges(element, links) {
|
|
149
|
+
for (const { start, end, attributes } of links) {
|
|
150
|
+
wrapRange(element, start, end, () => {
|
|
151
|
+
const anchor = document.createElement("a");
|
|
152
|
+
for (const [ name, value ] of Object.entries(attributes)) {
|
|
153
|
+
anchor.setAttribute(name, value);
|
|
154
|
+
}
|
|
155
|
+
return anchor
|
|
156
|
+
});
|
|
118
157
|
}
|
|
119
158
|
}
|
|
120
159
|
|
|
121
|
-
function wrapRange(container, rangeStart, rangeEnd,
|
|
160
|
+
function wrapRange(container, rangeStart, rangeEnd, createWrapper) {
|
|
122
161
|
const textNodes = collectTextNodes(container);
|
|
123
162
|
|
|
124
163
|
// Process in reverse so DOM mutations don't shift earlier text node offsets
|
|
@@ -133,14 +172,13 @@ function wrapRange(container, rangeStart, rangeEnd, style) {
|
|
|
133
172
|
const text = node.textContent;
|
|
134
173
|
const parent = node.parentNode;
|
|
135
174
|
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
mark.textContent = text.slice(relStart, relEnd);
|
|
175
|
+
const wrapper = createWrapper();
|
|
176
|
+
wrapper.textContent = text.slice(relStart, relEnd);
|
|
139
177
|
|
|
140
178
|
if (relEnd < text.length) {
|
|
141
179
|
parent.insertBefore(document.createTextNode(text.slice(relEnd)), node.nextSibling);
|
|
142
180
|
}
|
|
143
|
-
parent.insertBefore(
|
|
181
|
+
parent.insertBefore(wrapper, node.nextSibling);
|
|
144
182
|
|
|
145
183
|
if (relStart > 0) {
|
|
146
184
|
node.textContent = text.slice(0, relStart);
|
|
@@ -210,12 +210,6 @@
|
|
|
210
210
|
inset-inline-end: 0;
|
|
211
211
|
position: absolute;
|
|
212
212
|
}
|
|
213
|
-
|
|
214
|
-
lexxy-node-delete-button {
|
|
215
|
-
inset-block-start: 1.5ch;
|
|
216
|
-
inset-inline-end: 1.5ch;
|
|
217
|
-
inset-inline-start: unset;
|
|
218
|
-
}
|
|
219
213
|
}
|
|
220
214
|
|
|
221
215
|
&.attachment--error {
|
|
@@ -327,15 +321,6 @@
|
|
|
327
321
|
outline-offset: var(--lexxy-focus-ring-offset);
|
|
328
322
|
}
|
|
329
323
|
|
|
330
|
-
.attachment--file {
|
|
331
|
-
lexxy-node-delete-button {
|
|
332
|
-
inset-block-start: 50%;
|
|
333
|
-
inset-inline-end: 1ch;
|
|
334
|
-
inset-inline-start: unset;
|
|
335
|
-
transform: translate(0, -50%);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
324
|
/* Image galleries */
|
|
340
325
|
/* ------------------------------------------------------------------------ */
|
|
341
326
|
|
|
@@ -372,43 +357,28 @@
|
|
|
372
357
|
}
|
|
373
358
|
}
|
|
374
359
|
|
|
375
|
-
.attachment__caption {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
border-radius: var(--lexxy-radius);
|
|
381
|
-
box-sizing: border-box;
|
|
382
|
-
color: inherit;
|
|
360
|
+
.attachment__caption--editable {
|
|
361
|
+
box-sizing: border-box;
|
|
362
|
+
cursor: text;
|
|
363
|
+
|
|
364
|
+
.attachment__caption-text {
|
|
383
365
|
display: block;
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
max-inline-size: 100%;
|
|
388
|
-
padding: 0;
|
|
366
|
+
min-block-size: 1lh;
|
|
367
|
+
overflow-wrap: anywhere;
|
|
368
|
+
white-space: pre-wrap;
|
|
389
369
|
text-align: center;
|
|
370
|
+
}
|
|
390
371
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
&:focus {
|
|
396
|
-
background: var(--lexxy-color-canvas);
|
|
397
|
-
outline: 0;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
&:placeholder-shown {
|
|
401
|
-
overflow: hidden;
|
|
402
|
-
text-overflow: ellipsis;
|
|
403
|
-
white-space: nowrap;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
@supports (field-sizing: content) {
|
|
407
|
-
field-sizing: content;
|
|
408
|
-
inline-size: 100%;
|
|
409
|
-
}
|
|
372
|
+
&[data-placeholder] .attachment__caption-text {
|
|
373
|
+
overflow: hidden;
|
|
374
|
+
text-overflow: ellipsis;
|
|
375
|
+
white-space: nowrap;
|
|
410
376
|
}
|
|
411
377
|
}
|
|
378
|
+
|
|
379
|
+
.attachment__caption--editing .attachment__caption-text {
|
|
380
|
+
display: none;
|
|
381
|
+
}
|
|
412
382
|
}
|
|
413
383
|
|
|
414
384
|
/* Reserve the mounted height before upgrade so it doesn't shift layout on mount. */
|
|
@@ -636,7 +606,7 @@
|
|
|
636
606
|
padding: 1ch;
|
|
637
607
|
position: relative;
|
|
638
608
|
|
|
639
|
-
&[aria-
|
|
609
|
+
&[aria-checked="true"] {
|
|
640
610
|
background-color: var(--lexxy-color-selected);
|
|
641
611
|
|
|
642
612
|
&:hover {
|
|
@@ -782,6 +752,10 @@
|
|
|
782
752
|
grid-template-columns: repeat(auto-fill, minmax(var(--lexxy-toolbar-button-size), 1fr));
|
|
783
753
|
max-inline-size: var(--max-inline-size);
|
|
784
754
|
|
|
755
|
+
.lexxy-highlight-colors__group {
|
|
756
|
+
display: contents;
|
|
757
|
+
}
|
|
758
|
+
|
|
785
759
|
button {
|
|
786
760
|
block-size: unset;
|
|
787
761
|
inline-size: 100%;
|
|
@@ -796,7 +770,7 @@
|
|
|
796
770
|
position: absolute;
|
|
797
771
|
}
|
|
798
772
|
|
|
799
|
-
&[aria-
|
|
773
|
+
&[aria-checked="true"] {
|
|
800
774
|
background-color: transparent;
|
|
801
775
|
box-shadow: 0 0 0 2px currentColor inset;
|
|
802
776
|
|
|
@@ -830,8 +804,7 @@
|
|
|
830
804
|
position: absolute;
|
|
831
805
|
z-index: 2;
|
|
832
806
|
|
|
833
|
-
button
|
|
834
|
-
summary {
|
|
807
|
+
button {
|
|
835
808
|
align-items: center;
|
|
836
809
|
background: transparent;
|
|
837
810
|
block-size: var(--button-size);
|
|
@@ -849,7 +822,7 @@
|
|
|
849
822
|
|
|
850
823
|
@media(any-hover: hover) {
|
|
851
824
|
&:hover:not([aria-disabled="true"]),
|
|
852
|
-
[
|
|
825
|
+
&.lexxy-table-control__more-menu-trigger[aria-expanded="true"] {
|
|
853
826
|
background: var(--lexxy-color-ink-medium);
|
|
854
827
|
|
|
855
828
|
svg {
|
|
@@ -880,8 +853,10 @@
|
|
|
880
853
|
}
|
|
881
854
|
}
|
|
882
855
|
|
|
883
|
-
|
|
856
|
+
.lexxy-table-control__more-menu-trigger {
|
|
857
|
+
aspect-ratio: auto;
|
|
884
858
|
box-shadow: 0 0 0 1px inset rgba(128, 128, 128, 0.3);
|
|
859
|
+
font-weight: normal;
|
|
885
860
|
padding: 0 1.25ch;
|
|
886
861
|
}
|
|
887
862
|
|
|
@@ -909,6 +884,7 @@
|
|
|
909
884
|
white-space: nowrap;
|
|
910
885
|
|
|
911
886
|
.lexxy-table-control__more-menu {
|
|
887
|
+
display: flex;
|
|
912
888
|
gap: 0;
|
|
913
889
|
position: relative;
|
|
914
890
|
|
|
@@ -924,6 +900,10 @@
|
|
|
924
900
|
position: absolute;
|
|
925
901
|
transform: translateX(-50%);
|
|
926
902
|
|
|
903
|
+
&[hidden] {
|
|
904
|
+
display: none;
|
|
905
|
+
}
|
|
906
|
+
|
|
927
907
|
button {
|
|
928
908
|
aspect-ratio: unset;
|
|
929
909
|
flex-direction: row;
|
|
@@ -994,20 +974,40 @@
|
|
|
994
974
|
|
|
995
975
|
|
|
996
976
|
/* --------------------------------------------------------------------------
|
|
997
|
-
/* Attachment
|
|
977
|
+
/* Attachment toolbar */
|
|
998
978
|
|
|
999
|
-
&:is(lexxy-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
979
|
+
&:is(lexxy-attachment-toolbar) {
|
|
980
|
+
display: flex;
|
|
981
|
+
/* The anchor is measured in px from the editor's top-left, so these stay physical. */
|
|
982
|
+
left: calc(var(--lexxy-anchor-left) + var(--lexxy-anchor-width));
|
|
983
|
+
top: var(--lexxy-anchor-top);
|
|
984
|
+
transform: translate(-100%, 0);
|
|
1004
985
|
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
pointer-events: auto;
|
|
986
|
+
&[hidden] {
|
|
987
|
+
display: none;
|
|
1008
988
|
}
|
|
1009
989
|
|
|
1010
|
-
.
|
|
990
|
+
/* The divider is thinner than the button. */
|
|
991
|
+
&[data-node-type="horizontal_divider"] {
|
|
992
|
+
--button-size: 1.8lh;
|
|
993
|
+
|
|
994
|
+
top: calc(var(--lexxy-anchor-top) + var(--lexxy-anchor-height) / 2);
|
|
995
|
+
transform: translate(calc(-100% - 0.5ch), -50%);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
&[data-presentation="inline"] {
|
|
999
|
+
--button-size: 1.6lh;
|
|
1000
|
+
|
|
1001
|
+
top: calc(var(--lexxy-anchor-top) + var(--lexxy-anchor-height) / 2);
|
|
1002
|
+
transform: translate(0.5ch, -50%);
|
|
1003
|
+
|
|
1004
|
+
&[data-overflow] {
|
|
1005
|
+
transform: translate(-100%, -50%);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
.lexxy-attachment-toolbar__remove:hover,
|
|
1010
|
+
.lexxy-attachment-toolbar__remove:focus-visible {
|
|
1011
1011
|
background-color: var(--lexxy-color-red);
|
|
1012
1012
|
}
|
|
1013
1013
|
}
|
|
@@ -1135,20 +1135,49 @@ action-text-attachment[content-type^="application/vnd.actiontext"] {
|
|
|
1135
1135
|
|
|
1136
1136
|
outline: none;
|
|
1137
1137
|
}
|
|
1138
|
+
}
|
|
1138
1139
|
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1140
|
+
/* --------------------------------------------------------------------------
|
|
1141
|
+
/* Screen readers need access to announcements and selection labels. */
|
|
1142
|
+
:where(lexxy-live-region, .lexxy-fake-selection) {
|
|
1143
|
+
block-size: 1px;
|
|
1144
|
+
border: 0;
|
|
1145
|
+
clip-path: inset(50%);
|
|
1146
|
+
inline-size: 1px;
|
|
1147
|
+
margin: -1px;
|
|
1148
|
+
overflow: hidden;
|
|
1149
|
+
padding: 0;
|
|
1150
|
+
position: absolute;
|
|
1151
|
+
white-space: nowrap;
|
|
1152
|
+
}
|
|
1142
1153
|
|
|
1143
|
-
|
|
1144
|
-
|
|
1154
|
+
/* The DOM Selection API requires selectable text. */
|
|
1155
|
+
.lexxy-fake-selection {
|
|
1156
|
+
-webkit-user-select: text;
|
|
1145
1157
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1158
|
+
@supports (user-select: text) {
|
|
1159
|
+
user-select: text;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1148
1162
|
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1163
|
+
|
|
1164
|
+
/* Screen readers need the caption field outside the editor's textbox. */
|
|
1165
|
+
:where(.lexxy-caption-editor) {
|
|
1166
|
+
position: absolute;
|
|
1167
|
+
z-index: 2;
|
|
1168
|
+
box-sizing: border-box;
|
|
1169
|
+
background: var(--lexxy-color-canvas);
|
|
1170
|
+
border: 0;
|
|
1171
|
+
border-radius: var(--lexxy-radius);
|
|
1172
|
+
outline: 0;
|
|
1173
|
+
padding: 0;
|
|
1174
|
+
overflow: hidden;
|
|
1175
|
+
|
|
1176
|
+
@supports (resize: none) {
|
|
1177
|
+
resize: none;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
&[hidden] {
|
|
1181
|
+
display: none;
|
|
1153
1182
|
}
|
|
1154
1183
|
}
|
|
@@ -49,11 +49,11 @@
|
|
|
49
49
|
|
|
50
50
|
--highlight-bg-1: rgba(229, 223, 6, 0.3);
|
|
51
51
|
--highlight-bg-2: rgba(255, 185, 87, 0.3);
|
|
52
|
-
--highlight-bg-3: rgba(255, 118, 118, 0.3);
|
|
52
|
+
--highlight-bg-3: rgba(255, 118, 118, 0.3);
|
|
53
53
|
--highlight-bg-4: rgba(248, 137, 216, 0.3);
|
|
54
|
-
--highlight-bg-5: rgba(190, 165, 255, 0.3);
|
|
54
|
+
--highlight-bg-5: rgba(190, 165, 255, 0.3);
|
|
55
55
|
--highlight-bg-6: rgba(124, 192, 252, 0.3);
|
|
56
|
-
--highlight-bg-7: rgba(140, 255, 129, 0.3);
|
|
56
|
+
--highlight-bg-7: rgba(140, 255, 129, 0.3);
|
|
57
57
|
--highlight-bg-8: rgba(221, 170, 123, 0.3);
|
|
58
58
|
--highlight-bg-9: rgba(200, 200, 200, 0.3);
|
|
59
59
|
|
|
@@ -84,4 +84,4 @@
|
|
|
84
84
|
--lexxy-radius: 0.5ch;
|
|
85
85
|
--lexxy-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
86
86
|
--lexxy-z-popup: 1000;
|
|
87
|
-
}
|
|
87
|
+
}
|