mossmd 0.8.1

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.
Files changed (119) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/LICENSE +21 -0
  3. package/README.md +336 -0
  4. package/dist/collab/index.d.ts +14 -0
  5. package/dist/collab/index.d.ts.map +1 -0
  6. package/dist/collab/index.js +6 -0
  7. package/dist/collab/index.js.map +1 -0
  8. package/dist/core/code-languages.d.ts +3 -0
  9. package/dist/core/code-languages.d.ts.map +1 -0
  10. package/dist/core/code-languages.js +130 -0
  11. package/dist/core/code-languages.js.map +1 -0
  12. package/dist/core/decoration-utils.d.ts +4 -0
  13. package/dist/core/decoration-utils.d.ts.map +1 -0
  14. package/dist/core/decoration-utils.js +25 -0
  15. package/dist/core/decoration-utils.js.map +1 -0
  16. package/dist/core/edit-helpers.d.ts +13 -0
  17. package/dist/core/edit-helpers.d.ts.map +1 -0
  18. package/dist/core/edit-helpers.js +318 -0
  19. package/dist/core/edit-helpers.js.map +1 -0
  20. package/dist/core/icons.d.ts +16 -0
  21. package/dist/core/icons.d.ts.map +1 -0
  22. package/dist/core/icons.js +86 -0
  23. package/dist/core/icons.js.map +1 -0
  24. package/dist/core/inline-preview.d.ts +32 -0
  25. package/dist/core/inline-preview.d.ts.map +1 -0
  26. package/dist/core/inline-preview.js +925 -0
  27. package/dist/core/inline-preview.js.map +1 -0
  28. package/dist/core/list-editing.d.ts +10 -0
  29. package/dist/core/list-editing.d.ts.map +1 -0
  30. package/dist/core/list-editing.js +244 -0
  31. package/dist/core/list-editing.js.map +1 -0
  32. package/dist/core/list-model.d.ts +40 -0
  33. package/dist/core/list-model.d.ts.map +1 -0
  34. package/dist/core/list-model.js +114 -0
  35. package/dist/core/list-model.js.map +1 -0
  36. package/dist/core/list-navigation.d.ts +3 -0
  37. package/dist/core/list-navigation.d.ts.map +1 -0
  38. package/dist/core/list-navigation.js +234 -0
  39. package/dist/core/list-navigation.js.map +1 -0
  40. package/dist/core/markdown-context.d.ts +3 -0
  41. package/dist/core/markdown-context.d.ts.map +1 -0
  42. package/dist/core/markdown-context.js +10 -0
  43. package/dist/core/markdown-context.js.map +1 -0
  44. package/dist/core/preview-activity.d.ts +18 -0
  45. package/dist/core/preview-activity.d.ts.map +1 -0
  46. package/dist/core/preview-activity.js +199 -0
  47. package/dist/core/preview-activity.js.map +1 -0
  48. package/dist/core/preview-widgets.d.ts +65 -0
  49. package/dist/core/preview-widgets.d.ts.map +1 -0
  50. package/dist/core/preview-widgets.js +359 -0
  51. package/dist/core/preview-widgets.js.map +1 -0
  52. package/dist/core/read-only.d.ts +26 -0
  53. package/dist/core/read-only.d.ts.map +1 -0
  54. package/dist/core/read-only.js +49 -0
  55. package/dist/core/read-only.js.map +1 -0
  56. package/dist/core/tree-progress.d.ts +29 -0
  57. package/dist/core/tree-progress.d.ts.map +1 -0
  58. package/dist/core/tree-progress.js +143 -0
  59. package/dist/core/tree-progress.js.map +1 -0
  60. package/dist/editor.d.ts +219 -0
  61. package/dist/editor.d.ts.map +1 -0
  62. package/dist/editor.js +551 -0
  63. package/dist/editor.js.map +1 -0
  64. package/dist/features/callout/index.d.ts +9 -0
  65. package/dist/features/callout/index.d.ts.map +1 -0
  66. package/dist/features/callout/index.js +223 -0
  67. package/dist/features/callout/index.js.map +1 -0
  68. package/dist/features/file-blocks/index.d.ts +12 -0
  69. package/dist/features/file-blocks/index.d.ts.map +1 -0
  70. package/dist/features/file-blocks/index.js +397 -0
  71. package/dist/features/file-blocks/index.js.map +1 -0
  72. package/dist/features/image/index.d.ts +28 -0
  73. package/dist/features/image/index.d.ts.map +1 -0
  74. package/dist/features/image/index.js +699 -0
  75. package/dist/features/image/index.js.map +1 -0
  76. package/dist/features/index.d.ts +17 -0
  77. package/dist/features/index.d.ts.map +1 -0
  78. package/dist/features/index.js +9 -0
  79. package/dist/features/index.js.map +1 -0
  80. package/dist/features/search/index.d.ts +4 -0
  81. package/dist/features/search/index.d.ts.map +1 -0
  82. package/dist/features/search/index.js +131 -0
  83. package/dist/features/search/index.js.map +1 -0
  84. package/dist/features/slash-commands/index.d.ts +52 -0
  85. package/dist/features/slash-commands/index.d.ts.map +1 -0
  86. package/dist/features/slash-commands/index.js +422 -0
  87. package/dist/features/slash-commands/index.js.map +1 -0
  88. package/dist/features/table/index.d.ts +44 -0
  89. package/dist/features/table/index.d.ts.map +1 -0
  90. package/dist/features/table/index.js +1262 -0
  91. package/dist/features/table/index.js.map +1 -0
  92. package/dist/features/upload/index.d.ts +80 -0
  93. package/dist/features/upload/index.d.ts.map +1 -0
  94. package/dist/features/upload/index.js +790 -0
  95. package/dist/features/upload/index.js.map +1 -0
  96. package/dist/features/wiki-links/index.d.ts +27 -0
  97. package/dist/features/wiki-links/index.d.ts.map +1 -0
  98. package/dist/features/wiki-links/index.js +577 -0
  99. package/dist/features/wiki-links/index.js.map +1 -0
  100. package/dist/index.d.ts +17 -0
  101. package/dist/index.d.ts.map +1 -0
  102. package/dist/index.js +12 -0
  103. package/dist/index.js.map +1 -0
  104. package/dist/styles/content.css +247 -0
  105. package/dist/styles/inline-preview.css +2116 -0
  106. package/dist/styles/tokens.css +234 -0
  107. package/dist/syntax/highlight.d.ts +10 -0
  108. package/dist/syntax/highlight.d.ts.map +1 -0
  109. package/dist/syntax/highlight.js +75 -0
  110. package/dist/syntax/highlight.js.map +1 -0
  111. package/dist/syntax/index.d.ts +16 -0
  112. package/dist/syntax/index.d.ts.map +1 -0
  113. package/dist/syntax/index.js +29 -0
  114. package/dist/syntax/index.js.map +1 -0
  115. package/dist/theme/index.d.ts +6 -0
  116. package/dist/theme/index.d.ts.map +1 -0
  117. package/dist/theme/index.js +204 -0
  118. package/dist/theme/index.js.map +1 -0
  119. package/package.json +237 -0
@@ -0,0 +1,2116 @@
1
+ /*
2
+ * @mossmd — inline live-preview styles.
3
+ *
4
+ * Every rule below sets font-size / weight / decoration purely by CSS
5
+ * class, and does NOT depend on cursor state. Line heights therefore
6
+ * stay constant whether the line is "active" (syntax tokens visible)
7
+ * or "inactive" (tokens hidden by the inline-preview extension's
8
+ * replace-decorations) — that's what eliminates the layout-shift
9
+ * problem from widget-replacement approaches.
10
+ *
11
+ * Vertical-rhythm rules use `padding` on .cm-line, not `margin`. CM6
12
+ * measures each line's bounding-box height for virtualization;
13
+ * padding is inside the box (measured), margin is outside (not
14
+ * measured), so only padding yields the reader's spacing without
15
+ * confusing CM6.
16
+ *
17
+ * Theming contract: every color / font / size token below reads from
18
+ * a `--moss-*` custom property with an inline fallback. The
19
+ * defaults target a dark background; override the prefixed vars on
20
+ * any ancestor of the editor to theme it for your app.
21
+ */
22
+
23
+ @import './tokens.css';
24
+
25
+ .cm-moss-icon {
26
+ display: block;
27
+ flex: 0 0 auto;
28
+ max-width: 100%;
29
+ max-height: 100%;
30
+ overflow: visible;
31
+ color: inherit;
32
+ stroke: currentColor;
33
+ pointer-events: none;
34
+ }
35
+
36
+ /* Root container — fills its parent so the CM6 scroller can take over
37
+ * scrolling behavior. Previously relied on Tailwind `h-full w-full`
38
+ * applied in the React layer, which only worked for consumers who had
39
+ * Tailwind configured. Baking the sizing into the package means
40
+ * dropping the editor into any height-bounded parent just works. */
41
+ .moss-cm-editor {
42
+ position: relative;
43
+ height: 100%;
44
+ width: 100%;
45
+ }
46
+
47
+ /* Content container — constrain to a comfortable reading width. This
48
+ * is where the prose feel mostly comes from: line length in the
49
+ * 60–75ch range is what lets the eye sweep naturally. The full editor
50
+ * frame can still be wide (for search UI, sidebars, etc.) — only the
51
+ * text column is narrowed. */
52
+ .moss-cm-editor .cm-content {
53
+ max-width: var(--moss-measure, 70ch);
54
+ margin-inline: auto;
55
+ padding-inline: 0.5rem;
56
+ font-family: var(--moss-font, system-ui, -apple-system, BlinkMacSystemFont, sans-serif);
57
+ font-size: var(--moss-body-size, 1.0625rem);
58
+ line-height: var(--moss-body-leading, 1.7);
59
+ color: var(--moss-fg, #dcddde);
60
+ text-wrap: pretty;
61
+ }
62
+
63
+ .cm-line.cm-moss-empty-line {
64
+ font-size: var(--moss-body-size, 1.0625rem);
65
+ line-height: 1.25;
66
+ min-height: calc(var(--moss-body-size, 1.0625rem) * var(--moss-body-leading, 1.7));
67
+ padding-top: max(0px, calc(var(--moss-body-size, 1.0625rem) * (var(--moss-body-leading, 1.7) - 1.25) / 2));
68
+ padding-bottom: max(0px, calc(var(--moss-body-size, 1.0625rem) * (var(--moss-body-leading, 1.7) - 1.25) / 2));
69
+ }
70
+
71
+ /* Heading lines. Padding-top (not margin-top) creates the rhythm so
72
+ * CM6's line-height measurement stays correct.
73
+ *
74
+ * These values are intentionally small (≤ 0.2em). Prior values
75
+ * (0.4em–0.7em) produced a tall visual strip above the heading's
76
+ * text that belongs to the heading's hit-box but looks identical to
77
+ * the empty separator line above it. Users would click the visual
78
+ * "blank space above the heading" and land on the heading by
79
+ * surprise. The empty markdown line before a heading (which almost
80
+ * every real doc has) supplies the rest of the rhythm naturally. */
81
+ .cm-line.cm-moss-h1 {
82
+ font-size: 1.35em;
83
+ font-weight: 700;
84
+ line-height: 1.3;
85
+ letter-spacing: -0.015em;
86
+ padding-top: 0.15em;
87
+ padding-bottom: 0.1em;
88
+ color: var(--moss-fg, #dcddde);
89
+ }
90
+
91
+ .cm-line.cm-moss-h2 {
92
+ font-size: 1.2em;
93
+ font-weight: 700;
94
+ line-height: 1.35;
95
+ padding-top: 0.15em;
96
+ padding-bottom: 0.1em;
97
+ color: var(--moss-fg, #dcddde);
98
+ }
99
+
100
+ .cm-line.cm-moss-h3 {
101
+ font-size: 1.1em;
102
+ font-weight: 600;
103
+ line-height: 1.4;
104
+ padding-top: 0.12em;
105
+ color: var(--moss-fg, #dcddde);
106
+ }
107
+
108
+ .cm-line.cm-moss-h4 {
109
+ font-size: 1em;
110
+ font-weight: 600;
111
+ padding-top: 0.1em;
112
+ color: var(--moss-fg, #dcddde);
113
+ }
114
+
115
+ .cm-line.cm-moss-h5 {
116
+ font-size: 0.95em;
117
+ font-weight: 600;
118
+ padding-top: 0.08em;
119
+ color: var(--moss-fg, #dcddde);
120
+ }
121
+
122
+ .cm-line.cm-moss-h6 {
123
+ font-size: 0.9em;
124
+ font-weight: 600;
125
+ letter-spacing: 0.03em;
126
+ text-transform: uppercase;
127
+ padding-top: 0.08em;
128
+ color: var(--moss-fg-muted, #888);
129
+ }
130
+
131
+ /* Blockquote lines — each line in the quote gets a left rail. Putting
132
+ * the rail on every line means the visual continues for multi-line
133
+ * quotes even though each line is an independent DOM element. */
134
+ .cm-line.cm-moss-blockquote {
135
+ padding-left: 1rem;
136
+ border-left: 3px solid var(--moss-accent-soft, color-mix(in srgb, #7c3aed 72%, white 28%));
137
+ color: var(--moss-fg-muted, #888);
138
+ }
139
+
140
+ .cm-line.cm-moss-callout {
141
+ --moss-callout-accent: var(--moss-accent-bright, #a78bfa);
142
+ --moss-callout-bg: color-mix(in srgb, var(--moss-callout-accent) 10%, transparent 90%);
143
+ --moss-callout-border: color-mix(in srgb, var(--moss-callout-accent) 72%, var(--moss-border, #3d3d3d) 28%);
144
+ background: var(--moss-callout-bg);
145
+ border-left-color: var(--moss-callout-border);
146
+ color: var(--moss-fg, #dcddde);
147
+ }
148
+
149
+ .cm-line.cm-moss-callout-first {
150
+ border-top-right-radius: var(--moss-radius, 6px);
151
+ }
152
+
153
+ .cm-line.cm-moss-callout-last {
154
+ border-bottom-right-radius: var(--moss-radius, 6px);
155
+ }
156
+
157
+ .cm-line.cm-moss-callout-note,
158
+ .cm-moss-callout-label-note {
159
+ --moss-callout-accent: #82aaff;
160
+ }
161
+
162
+ .cm-line.cm-moss-callout-info,
163
+ .cm-line.cm-moss-callout-todo,
164
+ .cm-moss-callout-label-info,
165
+ .cm-moss-callout-label-todo {
166
+ --moss-callout-accent: #89ddff;
167
+ }
168
+
169
+ .cm-line.cm-moss-callout-tip,
170
+ .cm-line.cm-moss-callout-success,
171
+ .cm-moss-callout-label-tip,
172
+ .cm-moss-callout-label-success {
173
+ --moss-callout-accent: #c3e88d;
174
+ }
175
+
176
+ .cm-line.cm-moss-callout-warning,
177
+ .cm-moss-callout-label-warning {
178
+ --moss-callout-accent: #ffcb6b;
179
+ }
180
+
181
+ .cm-line.cm-moss-callout-important,
182
+ .cm-moss-callout-label-important {
183
+ --moss-callout-accent: #c792ea;
184
+ }
185
+
186
+ .cm-line.cm-moss-callout-danger,
187
+ .cm-line.cm-moss-callout-failure,
188
+ .cm-line.cm-moss-callout-bug,
189
+ .cm-line.cm-moss-callout-caution,
190
+ .cm-moss-callout-label-danger,
191
+ .cm-moss-callout-label-failure,
192
+ .cm-moss-callout-label-bug,
193
+ .cm-moss-callout-label-caution {
194
+ --moss-callout-accent: #f07178;
195
+ }
196
+
197
+ .cm-line.cm-moss-callout-question,
198
+ .cm-line.cm-moss-callout-example,
199
+ .cm-line.cm-moss-callout-abstract,
200
+ .cm-moss-callout-label-question,
201
+ .cm-moss-callout-label-example,
202
+ .cm-moss-callout-label-abstract {
203
+ --moss-callout-accent: #c792ea;
204
+ }
205
+
206
+ .cm-line.cm-moss-callout-quote,
207
+ .cm-moss-callout-label-quote {
208
+ --moss-callout-accent: var(--moss-fg-muted, #888);
209
+ }
210
+
211
+ .cm-moss-callout-label {
212
+ display: inline-flex;
213
+ align-items: center;
214
+ min-height: 1.35em;
215
+ margin-right: 0.45em;
216
+ color: var(--moss-callout-accent, var(--moss-accent-bright, #a78bfa));
217
+ font-size: 0.78em;
218
+ font-weight: 700;
219
+ letter-spacing: 0.02em;
220
+ text-transform: uppercase;
221
+ }
222
+
223
+ .cm-moss-callout-title {
224
+ font-weight: 650;
225
+ color: var(--moss-fg, #dcddde);
226
+ }
227
+
228
+ /* Fenced-code lines — subtle backdrop plus a left rail so the block
229
+ * reads as a contained unit. The rail is an *inset box-shadow*, not a
230
+ * border, so it adds no width and never perturbs the line-box geometry
231
+ * CM6 measures for virtualization (a real border would shift the
232
+ * padding math between active/inactive states). */
233
+ .cm-line.cm-moss-fenced-code {
234
+ font-family: var(--moss-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
235
+ font-size: 0.92em;
236
+ position: relative;
237
+ background-color: color-mix(in srgb, var(--moss-code-bg, #2d2d2d) 86%, transparent 14%);
238
+ background-image:
239
+ radial-gradient(
240
+ circle at 1px 1px,
241
+ color-mix(in srgb, var(--moss-fg-faint, #666) 22%, transparent 78%) 1px,
242
+ transparent 1.1px
243
+ );
244
+ background-size: 14px 14px;
245
+ border-left: 1px solid color-mix(in srgb, var(--moss-border, #3d3d3d) 76%, transparent 24%);
246
+ border-right: 1px solid color-mix(in srgb, var(--moss-border, #3d3d3d) 76%, transparent 24%);
247
+ padding-left: 0.7rem;
248
+ padding-right: 2.4rem;
249
+ }
250
+
251
+ .cm-line.cm-moss-fenced-code-start {
252
+ border-top: 1px solid color-mix(in srgb, var(--moss-border, #3d3d3d) 76%, transparent 24%);
253
+ border-top-left-radius: var(--moss-radius, 6px);
254
+ border-top-right-radius: var(--moss-radius, 6px);
255
+ padding-top: 0.28rem;
256
+ }
257
+
258
+ .cm-line.cm-moss-fenced-code-end {
259
+ border-bottom: 1px solid color-mix(in srgb, var(--moss-border, #3d3d3d) 76%, transparent 24%);
260
+ border-bottom-left-radius: var(--moss-radius, 6px);
261
+ border-bottom-right-radius: var(--moss-radius, 6px);
262
+ padding-bottom: 0.28rem;
263
+ }
264
+
265
+ /* `drawSelection()` paints behind content, where the opaque code backdrop
266
+ * would cover it. A selection-only inline decoration mirrors the same token
267
+ * above the block background while leaving syntax-highlighted text on top. */
268
+ .cm-moss-fenced-selection {
269
+ background-color: var(
270
+ --moss-selection-bg,
271
+ color-mix(in srgb, #7c3aed 28%, #1e1e1e 72%)
272
+ );
273
+ }
274
+
275
+ .cm-moss-code-copy {
276
+ position: absolute;
277
+ top: 0.15rem;
278
+ inset-inline-end: 0.15rem;
279
+ z-index: 2;
280
+ display: inline-flex;
281
+ align-items: center;
282
+ justify-content: center;
283
+ width: 1.9rem;
284
+ height: 1.9rem;
285
+ padding: 0;
286
+ background: transparent;
287
+ border: 1px solid transparent;
288
+ border-radius: var(--moss-radius, 6px);
289
+ color: var(--moss-fg-muted, #888);
290
+ cursor: pointer;
291
+ opacity: 0.7;
292
+ transition: background-color 0.12s ease, color 0.12s ease, opacity 0.12s ease, border-color 0.12s ease;
293
+ }
294
+
295
+ .cm-line.cm-moss-fenced-code:hover .cm-moss-code-copy,
296
+ .cm-moss-code-copy:focus-visible {
297
+ opacity: 1;
298
+ background: var(--moss-bg-surface, #2d2d2d);
299
+ color: var(--moss-fg, #dcddde);
300
+ border-color: var(--moss-border, #3d3d3d);
301
+ }
302
+
303
+ .cm-moss-code-copy.is-copied {
304
+ opacity: 1;
305
+ background: var(--moss-accent, #7c3aed);
306
+ color: #fff;
307
+ border-color: var(--moss-accent, #7c3aed);
308
+ }
309
+
310
+ .cm-moss-code-copy svg {
311
+ width: 1rem;
312
+ height: 1rem;
313
+ }
314
+
315
+ /* Inline content marks — applied on the text inside **…**, *…*, `…`,
316
+ * ~~…~~, [text](url). Styling only; no height changes. */
317
+ .cm-moss-strong {
318
+ font-weight: 700;
319
+ color: var(--moss-fg, #dcddde);
320
+ }
321
+
322
+ .cm-moss-em {
323
+ font-style: italic;
324
+ }
325
+
326
+ .cm-moss-inline-code {
327
+ font-family: var(--moss-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
328
+ font-size: 0.88em;
329
+ color: var(--moss-fg, #dcddde);
330
+ background: var(--moss-code-bg, color-mix(in srgb, #2d2d2d 88%, black 12%));
331
+ border-radius: 0.35rem;
332
+ padding: 0.08em 0.35em;
333
+ word-break: break-word;
334
+ }
335
+
336
+ .cm-moss-strike {
337
+ text-decoration: line-through;
338
+ color: var(--moss-fg-muted, #888);
339
+ }
340
+
341
+ .cm-moss-highlight {
342
+ background: color-mix(in srgb, var(--moss-accent-bright, #a78bfa) 20%, transparent 80%);
343
+ border-radius: 0.2em;
344
+ color: var(--moss-fg, #dcddde);
345
+ box-decoration-break: clone;
346
+ -webkit-box-decoration-break: clone;
347
+ }
348
+
349
+ /* Delimiter characters for emphasis marks we supplemented manually
350
+ * (see `supplementMidTypingEmphasis` in inline-preview.ts). Each
351
+ * class mirrors what an `EmphasisMark` token would pick up through
352
+ * syntax highlighting when lezer does parse the pattern: faint
353
+ * color from `processingInstruction`, plus the parent scope's
354
+ * weight / style / decoration so the delimiter glyph keeps the
355
+ * same metrics whether lezer recognized the emphasis or not.
356
+ * Without the matching weight / style, `**` would visibly change
357
+ * width as the user types through a trailing space. */
358
+ .cm-moss-strong-mark {
359
+ color: var(--moss-fg-faint, #666);
360
+ font-weight: 700;
361
+ }
362
+
363
+ .cm-moss-em-mark {
364
+ color: var(--moss-fg-faint, #666);
365
+ font-style: italic;
366
+ }
367
+
368
+ .cm-moss-strike-mark {
369
+ color: var(--moss-fg-faint, #666);
370
+ text-decoration: line-through;
371
+ }
372
+
373
+ .cm-moss-highlight-mark {
374
+ color: var(--moss-fg-faint, #666);
375
+ background: transparent;
376
+ }
377
+
378
+ .cm-moss-link {
379
+ color: var(--moss-link, #818cf8);
380
+ text-decoration: underline;
381
+ text-decoration-thickness: 1px;
382
+ text-underline-offset: 0.15em;
383
+ /* No pointer cursor on the link text — the text is editable like
384
+ * any other content. The pointer cursor is scoped to the trailing
385
+ * icon below, which is the actual open-in-new-tab affordance. */
386
+ }
387
+
388
+ .cm-moss-link:hover {
389
+ color: var(--moss-link-hover, #a5b4fc);
390
+ }
391
+
392
+ /* External-link icon. In prose it's a `::after` pseudo-element; inside
393
+ * table cells it's a real `.cm-moss-link-icon` element (a pseudo can't
394
+ * receive a click, so the in-cell icon wouldn't open the link). Both
395
+ * share the masked-SVG visual so the color tracks the link color via
396
+ * `background-color`. */
397
+ .cm-moss-link::after,
398
+ .cm-moss-link-icon {
399
+ display: inline-block;
400
+ width: 0.78em;
401
+ height: 0.78em;
402
+ margin-left: 0.32em;
403
+ vertical-align: -0.02em;
404
+ cursor: pointer;
405
+ background-color: color-mix(in srgb, var(--moss-link, #818cf8) 82%, white 18%);
406
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg%3E%3Cpath d='M10.0002 5H8.2002C7.08009 5 6.51962 5 6.0918 5.21799C5.71547 5.40973 5.40973 5.71547 5.21799 6.0918C5 6.51962 5 7.08009 5 8.2002V15.8002C5 16.9203 5 17.4801 5.21799 17.9079C5.40973 18.2842 5.71547 18.5905 6.0918 18.7822C6.5192 19 7.07899 19 8.19691 19H15.8031C16.921 19 17.48 19 17.9074 18.7822C18.2837 18.5905 18.5905 18.2839 18.7822 17.9076C19 17.4802 19 16.921 19 15.8031V14M20 9V4M20 4H15M20 4L13 11' stroke='%23000000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/g%3E%3C/svg%3E");
407
+ mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg%3E%3Cpath d='M10.0002 5H8.2002C7.08009 5 6.51962 5 6.0918 5.21799C5.71547 5.40973 5.40973 5.71547 5.21799 6.0918C5 6.51962 5 7.08009 5 8.2002V15.8002C5 16.9203 5 17.4801 5.21799 17.9079C5.40973 18.2842 5.71547 18.5905 6.0918 18.7822C6.5192 19 7.07899 19 8.19691 19H15.8031C16.921 19 17.48 19 17.9074 18.7822C18.2837 18.5905 18.5905 18.2839 18.7822 17.9076C19 17.4802 19 16.921 19 15.8031V14M20 9V4M20 4H15M20 4L13 11' stroke='%23000000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/g%3E%3C/svg%3E");
408
+ -webkit-mask-repeat: no-repeat;
409
+ mask-repeat: no-repeat;
410
+ -webkit-mask-size: contain;
411
+ mask-size: contain;
412
+ opacity: 0.9;
413
+ }
414
+
415
+ .cm-moss-link::after {
416
+ content: "";
417
+ }
418
+
419
+ /* The real in-cell icon must be clickable but never selectable / part
420
+ * of caret navigation. */
421
+ .cm-moss-link-icon {
422
+ pointer-events: auto;
423
+ user-select: none;
424
+ -webkit-user-select: none;
425
+ }
426
+
427
+ /* In cells the icon is a real element — suppress the pseudo so the icon
428
+ * doesn't render twice. */
429
+ .cm-moss-table-cell-source .cm-moss-link::after {
430
+ content: none;
431
+ }
432
+
433
+ .cm-moss-link:hover::after,
434
+ .cm-moss-link-icon:hover {
435
+ background-color: color-mix(in srgb, var(--moss-link-hover, #a5b4fc) 84%, white 16%);
436
+ opacity: 1;
437
+ }
438
+
439
+ .cm-moss-wiki-link {
440
+ color: var(--moss-link, #818cf8);
441
+ text-decoration: underline;
442
+ text-decoration-thickness: 1px;
443
+ text-underline-offset: 0.15em;
444
+ cursor: pointer;
445
+ }
446
+
447
+ .cm-moss-wiki-link:hover {
448
+ color: var(--moss-link-hover, #a5b4fc);
449
+ }
450
+
451
+ .cm-moss-wiki-link-missing,
452
+ .cm-moss-wiki-link-unresolved {
453
+ color: var(--moss-fg-muted, #888);
454
+ text-decoration-style: dotted;
455
+ }
456
+
457
+ .cm-moss-wiki-link-loading {
458
+ color: var(--moss-fg-muted, #888);
459
+ }
460
+
461
+ .cm-line .cm-moss-hr-source,
462
+ .cm-line .cm-moss-hr-source * {
463
+ font-weight: 400;
464
+ font-style: normal;
465
+ text-decoration: none;
466
+ color: inherit;
467
+ }
468
+
469
+ .cm-moss-hr-widget {
470
+ display: inline-flex;
471
+ align-items: center;
472
+ justify-content: center;
473
+ width: 100%;
474
+ min-height: 1.7em;
475
+ gap: 0.6em;
476
+ vertical-align: middle;
477
+ pointer-events: none;
478
+ }
479
+
480
+ .cm-moss-hr-segment {
481
+ flex: 1 1 auto;
482
+ min-width: 0;
483
+ border-top: 1px solid var(--moss-border, #3d3d3d);
484
+ }
485
+
486
+ .cm-moss-hr-widget-wavy .cm-moss-hr-segment {
487
+ border-top: none;
488
+ height: 8px;
489
+ background-color: var(--moss-border, #3d3d3d);
490
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 8' preserveAspectRatio='none'%3E%3Cpath d='M0 4 C4 0.5 12 0.5 16 4 S28 7.5 32 4' fill='none' stroke='white' stroke-width='1.25' stroke-linecap='round'/%3E%3C/svg%3E");
491
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 8' preserveAspectRatio='none'%3E%3Cpath d='M0 4 C4 0.5 12 0.5 16 4 S28 7.5 32 4' fill='none' stroke='white' stroke-width='1.25' stroke-linecap='round'/%3E%3C/svg%3E");
492
+ mask-repeat: repeat-x;
493
+ -webkit-mask-repeat: repeat-x;
494
+ mask-position: center;
495
+ -webkit-mask-position: center;
496
+ mask-size: 32px 8px;
497
+ -webkit-mask-size: 32px 8px;
498
+ }
499
+
500
+ .cm-moss-hr-symbol {
501
+ flex: 0 1 auto;
502
+ min-width: 1.45em;
503
+ max-width: min(12em, calc(100% - 2em));
504
+ color: var(--moss-fg-muted, #888);
505
+ font-size: 0.95em;
506
+ line-height: 1.35;
507
+ text-align: center;
508
+ overflow: hidden;
509
+ text-overflow: ellipsis;
510
+ white-space: nowrap;
511
+ }
512
+
513
+ /* The source of a standalone image is replaced visually by its block
514
+ * widget. Keep the source line for CM6 position mapping, but remove its
515
+ * line box so it does not read as an empty row above the image. */
516
+ .cm-line.cm-moss-image-source-line {
517
+ height: 0;
518
+ min-height: 0;
519
+ padding-top: 0;
520
+ padding-bottom: 0;
521
+ line-height: 0;
522
+ }
523
+
524
+ /* Image block widget — rendered below the source line containing
525
+ * the `![alt](url)` markdown. Natural image size up to the editor's
526
+ * content width (`max-width: 100%` of .cm-content, which is the
527
+ * reading-width column). Smaller images render at their own size,
528
+ * left-aligned. Aspect ratio preserved by `height: auto`. */
529
+ .cm-moss-image {
530
+ position: relative;
531
+ display: flex;
532
+ flex-direction: column;
533
+ align-items: center;
534
+ justify-content: center;
535
+ overflow: visible;
536
+ padding: 0.35em 0;
537
+ }
538
+
539
+ .cm-moss-image-frame {
540
+ position: relative;
541
+ flex: 0 0 auto;
542
+ display: inline-block;
543
+ box-sizing: border-box;
544
+ max-width: 100%;
545
+ line-height: 0;
546
+ }
547
+
548
+ /* Remote images have no intrinsic height until they finish loading. Keep
549
+ * their widget measurable so inserting an image never creates a blank,
550
+ * zero-height block that shifts the document when the request completes. */
551
+ .cm-moss-image-frame-placeholder {
552
+ width: min(28rem, 100%);
553
+ min-height: 10rem;
554
+ aspect-ratio: 16 / 9;
555
+ border-radius: 0.4em;
556
+ background: var(--moss-bg-surface, rgba(127, 127, 127, 0.08));
557
+ overflow: hidden;
558
+ }
559
+
560
+ .cm-moss-image-placeholder {
561
+ position: absolute;
562
+ inset: 0;
563
+ display: grid;
564
+ place-items: center;
565
+ color: var(--moss-fg-muted, #888);
566
+ }
567
+
568
+ .cm-moss-image-placeholder svg {
569
+ opacity: 0.72;
570
+ }
571
+
572
+ .cm-moss-image-frame-placeholder > img {
573
+ opacity: 0;
574
+ }
575
+
576
+ /* Once a width has been chosen explicitly, keep the image usable while
577
+ * preserving the responsive percentage in the source. The `min()` keeps
578
+ * the frame from overflowing a narrow phone viewport. */
579
+ .cm-moss-image-frame-sized {
580
+ min-width: min(160px, 100%);
581
+ }
582
+
583
+ .cm-moss-image img {
584
+ display: block;
585
+ width: auto;
586
+ max-width: 100%;
587
+ height: auto;
588
+ border-radius: 0.4em;
589
+ }
590
+
591
+ /* The source line is kept at zero height for position mapping. When its
592
+ * entire range is selected through the rendered widget, suppress CM6's
593
+ * caret layer so the caret cannot appear at the line's top-left corner. */
594
+ .moss-cm-editor.moss-cm-image-selection-active .cm-cursorLayer {
595
+ visibility: hidden;
596
+ }
597
+
598
+ .cm-moss-image.cm-moss-image-selected .cm-moss-image-frame {
599
+ outline: 1px solid color-mix(
600
+ in srgb,
601
+ var(--moss-accent, #7c3aed) 52%,
602
+ var(--moss-border, #3d3d3d) 48%
603
+ );
604
+ outline-offset: 2px;
605
+ border-radius: 0.45em;
606
+ }
607
+
608
+ /* The edit affordance is overlaid on the image so it never adds a row to
609
+ * the document or changes the block widget's measured height. It appears
610
+ * on hover and keyboard focus, while the image itself remains a quiet
611
+ * reading surface. */
612
+ .cm-moss-image-edit {
613
+ position: absolute;
614
+ top: 0.7em;
615
+ right: 0.7em;
616
+ z-index: 1;
617
+ width: 2em;
618
+ height: 2em;
619
+ display: inline-flex;
620
+ align-items: center;
621
+ justify-content: center;
622
+ padding: 0;
623
+ border: 1px solid var(--moss-border, rgba(127, 127, 127, 0.45));
624
+ border-radius: 0.35em;
625
+ background: var(--moss-bg-panel, #252525);
626
+ color: var(--moss-fg, #dcddde);
627
+ cursor: pointer;
628
+ opacity: 0;
629
+ transition: opacity 0.12s ease, background-color 0.12s ease;
630
+ }
631
+
632
+ .cm-moss-image-frame:hover .cm-moss-image-edit,
633
+ .cm-moss-image-frame:focus-within .cm-moss-image-edit,
634
+ .cm-moss-image-frame:hover .cm-moss-image-preview,
635
+ .cm-moss-image-frame:focus-within .cm-moss-image-preview,
636
+ .cm-moss-image-frame:hover .cm-moss-image-resize,
637
+ .cm-moss-image-frame:focus-within .cm-moss-image-resize {
638
+ opacity: 1;
639
+ }
640
+
641
+ .cm-moss-image-edit:hover {
642
+ background: var(--moss-bg-surface, #2d2d2d);
643
+ }
644
+
645
+ .cm-moss-image-preview {
646
+ position: absolute;
647
+ top: 0.7em;
648
+ left: 0.7em;
649
+ z-index: 1;
650
+ width: 2em;
651
+ height: 2em;
652
+ display: inline-flex;
653
+ align-items: center;
654
+ justify-content: center;
655
+ padding: 0;
656
+ border: 1px solid var(--moss-border, rgba(127, 127, 127, 0.45));
657
+ border-radius: 0.35em;
658
+ background: var(--moss-bg-panel, #252525);
659
+ color: var(--moss-fg, #dcddde);
660
+ cursor: pointer;
661
+ opacity: 0;
662
+ transition: opacity 0.12s ease, background-color 0.12s ease;
663
+ }
664
+
665
+ .cm-moss-image-preview:hover {
666
+ background: var(--moss-bg-surface, #2d2d2d);
667
+ }
668
+
669
+ .cm-moss-image-resize {
670
+ position: absolute;
671
+ right: 0.55em;
672
+ bottom: 0.55em;
673
+ z-index: 1;
674
+ width: 2em;
675
+ height: 2em;
676
+ display: inline-flex;
677
+ align-items: center;
678
+ justify-content: center;
679
+ padding: 0;
680
+ border: 1px solid var(--moss-border, rgba(127, 127, 127, 0.45));
681
+ border-radius: 0.35em;
682
+ background: var(--moss-bg-panel, #252525);
683
+ color: var(--moss-fg, #dcddde);
684
+ cursor: nwse-resize;
685
+ opacity: 0;
686
+ touch-action: none;
687
+ transition: opacity 0.12s ease, background-color 0.12s ease;
688
+ }
689
+
690
+ .cm-moss-image-resize:hover {
691
+ background: var(--moss-bg-surface, #2d2d2d);
692
+ }
693
+
694
+ /* Maximize2 is rotated so its diagonal reads from the image's top-left
695
+ * toward the bottom-right resize handle. */
696
+ .cm-moss-image-resize svg {
697
+ transform: rotate(90deg);
698
+ }
699
+
700
+ @media (hover: none), (pointer: coarse) {
701
+ .cm-moss-image-edit,
702
+ .cm-moss-image-preview,
703
+ .cm-moss-image-resize {
704
+ width: 2.75rem;
705
+ height: 2.75rem;
706
+ opacity: 1;
707
+ }
708
+ }
709
+
710
+ .cm-moss-image-edit:focus-visible,
711
+ .cm-moss-image-preview:focus-visible,
712
+ .cm-moss-image-editor-button:focus-visible,
713
+ .cm-moss-image-editor input:focus-visible {
714
+ outline: 2px solid var(--moss-accent, #7c3aed);
715
+ outline-offset: 2px;
716
+ }
717
+
718
+ /* This editor floats over the rendered image. Absolute positioning keeps
719
+ * opening it out of the CodeMirror heightmap and document flow. */
720
+ .cm-moss-image-editor {
721
+ position: absolute;
722
+ top: 0.7em;
723
+ right: 0.7em;
724
+ z-index: 2;
725
+ box-sizing: border-box;
726
+ width: min(22rem, calc(100% - 1rem));
727
+ display: grid;
728
+ gap: 0.55em;
729
+ padding: 0.8em;
730
+ border: 1px solid var(--moss-border, rgba(127, 127, 127, 0.38));
731
+ border-radius: 0.5em;
732
+ background: var(--moss-bg-panel, #252525);
733
+ box-shadow: 0 0.7em 2em rgba(0, 0, 0, 0.28);
734
+ }
735
+
736
+ .cm-moss-image-editor-title {
737
+ color: var(--moss-fg, #dcddde);
738
+ font-size: 0.9em;
739
+ font-weight: 650;
740
+ }
741
+
742
+ .cm-moss-image-editor-field {
743
+ display: grid;
744
+ gap: 0.25em;
745
+ color: var(--moss-fg-muted, #9a9da4);
746
+ font-size: 0.72em;
747
+ }
748
+
749
+ .cm-moss-image-editor input {
750
+ box-sizing: border-box;
751
+ width: 100%;
752
+ min-width: 0;
753
+ padding: 0.45em 0.55em;
754
+ border: 1px solid var(--moss-border, rgba(127, 127, 127, 0.34));
755
+ border-radius: 0.3em;
756
+ background: var(--moss-bg-surface, #2d2d2d);
757
+ color: var(--moss-fg, #dcddde);
758
+ font: inherit;
759
+ font-size: 1.05em;
760
+ }
761
+
762
+ .cm-moss-image-editor-actions {
763
+ display: flex;
764
+ justify-content: flex-end;
765
+ gap: 0.4em;
766
+ padding-top: 0.15em;
767
+ }
768
+
769
+ .cm-moss-image-editor-button {
770
+ width: 1.9em;
771
+ height: 1.9em;
772
+ display: inline-flex;
773
+ align-items: center;
774
+ justify-content: center;
775
+ padding: 0;
776
+ border: 1px solid var(--moss-border, rgba(127, 127, 127, 0.34));
777
+ border-radius: 0.35em;
778
+ background: transparent;
779
+ color: var(--moss-fg-muted, #9a9da4);
780
+ cursor: pointer;
781
+ }
782
+
783
+ .cm-moss-image-editor-button:hover {
784
+ background: var(--moss-bg-surface, #2d2d2d);
785
+ color: var(--moss-fg, #dcddde);
786
+ }
787
+
788
+ .cm-moss-image-editor-button.is-primary {
789
+ border-color: var(--moss-accent, #7c3aed);
790
+ background: var(--moss-accent, #7c3aed);
791
+ color: #ffffff;
792
+ }
793
+
794
+ .cm-moss-image-editor-error {
795
+ min-height: 1.1em;
796
+ color: var(--moss-hl-invalid, #ff5370);
797
+ font-size: 0.72em;
798
+ }
799
+
800
+ /* The preview is mounted on document.body, outside CodeMirror's scroller,
801
+ * so a large image is never clipped by the editor viewport. */
802
+ .cm-moss-image-preview-backdrop {
803
+ position: fixed;
804
+ inset: 0;
805
+ z-index: 1000;
806
+ box-sizing: border-box;
807
+ display: flex;
808
+ align-items: center;
809
+ justify-content: center;
810
+ padding: 5vh 5vw;
811
+ background: rgba(0, 0, 0, 0.68);
812
+ backdrop-filter: blur(2px);
813
+ -webkit-backdrop-filter: blur(2px);
814
+ cursor: zoom-out;
815
+ }
816
+
817
+ .cm-moss-image-preview-dialog {
818
+ position: relative;
819
+ display: flex;
820
+ max-width: 96vw;
821
+ max-height: 90vh;
822
+ flex-direction: column;
823
+ align-items: center;
824
+ gap: 0.65rem;
825
+ cursor: default;
826
+ }
827
+
828
+ .cm-moss-image-preview-dialog > img {
829
+ display: block;
830
+ max-width: 90vw;
831
+ max-height: 82vh;
832
+ width: auto;
833
+ height: auto;
834
+ object-fit: contain;
835
+ border-radius: 0.35rem;
836
+ box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.45);
837
+ }
838
+
839
+ .cm-moss-image-preview-close {
840
+ position: absolute;
841
+ top: 0.6rem;
842
+ right: 0.6rem;
843
+ z-index: 1;
844
+ width: 1.9rem;
845
+ height: 1.9rem;
846
+ display: inline-flex;
847
+ align-items: center;
848
+ justify-content: center;
849
+ padding: 0;
850
+ border: 1px solid color-mix(in srgb, white 32%, transparent);
851
+ border-radius: var(--moss-radius, 6px);
852
+ background: color-mix(in srgb, var(--moss-bg-panel, #252525) 82%, transparent);
853
+ color: var(--moss-fg, #dcddde);
854
+ cursor: pointer;
855
+ opacity: 0.9;
856
+ transition: background-color 0.12s ease, border-color 0.12s ease, opacity 0.12s ease;
857
+ }
858
+
859
+ .cm-moss-image-preview-close:hover {
860
+ border-color: color-mix(in srgb, white 52%, transparent);
861
+ background: color-mix(in srgb, var(--moss-bg-surface, #2d2d2d) 92%, transparent);
862
+ opacity: 1;
863
+ }
864
+
865
+ .cm-moss-image-preview-caption {
866
+ max-width: min(90vw, 70ch);
867
+ color: rgba(255, 255, 255, 0.85);
868
+ font-size: 0.9rem;
869
+ line-height: 1.4;
870
+ text-align: center;
871
+ }
872
+
873
+ .cm-moss-image-preview-close:focus-visible {
874
+ outline: 2px solid var(--moss-accent, #a78bfa);
875
+ outline-offset: 2px;
876
+ }
877
+
878
+ /* When a caption is present, switch the wrap to a column so the
879
+ * figcaption stacks below the image instead of beside it. */
880
+ .cm-moss-image.cm-moss-image-has-caption {
881
+ flex-direction: column;
882
+ align-items: center;
883
+ }
884
+
885
+ .cm-moss-image-caption {
886
+ margin: 0.4em 0 0;
887
+ text-align: center;
888
+ font-size: 0.85em;
889
+ color: var(--moss-fg-muted, #888);
890
+ max-width: 100%;
891
+ }
892
+
893
+ /* ------------------------------------------------------------------ */
894
+ /* File link block.
895
+ *
896
+ * Same treatment as image blocks — rendered below a source line that
897
+ * contains a standalone file link `[label](url)`. Layout mirrors the
898
+ * upload-progress widget so the flow "upload widget → file-block
899
+ * widget" looks like a single continuous transition rather than a
900
+ * pop-and-replace. Left: preview (thumb for image URLs, glyph+ext
901
+ * badge for generic files). Right: name + info row. */
902
+ .cm-moss-file-block {
903
+ position: relative;
904
+ display: flex;
905
+ align-items: center;
906
+ gap: 0.85em;
907
+ padding: 0.6em 3.5em 0.6em 0.9em;
908
+ margin: 0.25em 0;
909
+ border: 1px solid var(--moss-border, rgba(127, 127, 127, 0.22));
910
+ border-radius: 0.55em;
911
+ background: var(--moss-file-block-bg, rgba(127, 127, 127, 0.06));
912
+ transition: background-color 0.12s ease, border-color 0.12s ease;
913
+ }
914
+
915
+ .cm-moss-file-block:hover {
916
+ background: var(--moss-file-block-hover-bg, rgba(127, 127, 127, 0.1));
917
+ border-color: var(--moss-border, rgba(127, 127, 127, 0.35));
918
+ }
919
+
920
+ .cm-moss-file-block-preview {
921
+ flex: 0 0 auto;
922
+ width: 4.2em;
923
+ height: 4.2em;
924
+ display: inline-flex;
925
+ align-items: center;
926
+ justify-content: center;
927
+ overflow: hidden;
928
+ border-radius: 0.5em;
929
+ background: var(--moss-file-block-preview-bg, rgba(127, 127, 127, 0.12));
930
+ }
931
+
932
+ .cm-moss-file-block-preview.cm-moss-file-block-preview-image {
933
+ background: transparent;
934
+ }
935
+
936
+ .cm-moss-file-block-preview-image img {
937
+ width: 100%;
938
+ height: 100%;
939
+ object-fit: cover;
940
+ display: block;
941
+ }
942
+
943
+ .cm-moss-file-block-preview.cm-moss-file-block-preview-file {
944
+ flex-direction: column;
945
+ align-items: center;
946
+ justify-content: center;
947
+ gap: 0.18em;
948
+ color: var(--moss-fg-muted, #9a9da4);
949
+ }
950
+
951
+ .cm-moss-file-block-glyph {
952
+ display: inline-flex;
953
+ align-items: center;
954
+ justify-content: center;
955
+ flex: 1 1 auto;
956
+ min-height: 0;
957
+ }
958
+
959
+ .cm-moss-file-block-glyph svg {
960
+ width: 2.6em;
961
+ height: 2.6em;
962
+ }
963
+
964
+ .cm-moss-file-block-ext {
965
+ font-size: 0.6em;
966
+ font-weight: 700;
967
+ letter-spacing: 0.05em;
968
+ line-height: 1;
969
+ padding: 0.2em 0.45em;
970
+ border-radius: 0.3em;
971
+ background: var(--moss-bg-surface, #2d2d2d);
972
+ color: var(--moss-fg, #dcddde);
973
+ text-transform: uppercase;
974
+ }
975
+
976
+ .cm-moss-file-block-meta {
977
+ flex: 1 1 auto;
978
+ min-width: 0;
979
+ display: flex;
980
+ flex-direction: column;
981
+ gap: 0.15em;
982
+ }
983
+
984
+ .cm-moss-file-block-name {
985
+ font-size: 0.95em;
986
+ font-weight: 600;
987
+ color: var(--moss-fg, #dcddde);
988
+ white-space: nowrap;
989
+ overflow: hidden;
990
+ text-overflow: ellipsis;
991
+ }
992
+
993
+ .cm-moss-file-block-info {
994
+ font-size: 0.8em;
995
+ color: var(--moss-fg-muted, #888);
996
+ white-space: nowrap;
997
+ overflow: hidden;
998
+ text-overflow: ellipsis;
999
+ }
1000
+
1001
+ /* Keep file actions attached to the card itself. They are absolutely
1002
+ * positioned so they never add width or height to the block widget. */
1003
+ .cm-moss-file-block-actions {
1004
+ position: absolute;
1005
+ top: 50%;
1006
+ right: 0.7em;
1007
+ z-index: 1;
1008
+ display: flex;
1009
+ align-items: center;
1010
+ gap: 0.3em;
1011
+ transform: translateY(-50%);
1012
+ opacity: 0;
1013
+ transition: opacity 0.12s ease;
1014
+ }
1015
+
1016
+ .cm-moss-file-block-download,
1017
+ .cm-moss-file-block-delete {
1018
+ width: 2em;
1019
+ height: 2em;
1020
+ display: inline-flex;
1021
+ align-items: center;
1022
+ justify-content: center;
1023
+ padding: 0;
1024
+ border: 1px solid var(--moss-border, rgba(127, 127, 127, 0.45));
1025
+ border-radius: 0.35em;
1026
+ background: var(--moss-bg-panel, #252525);
1027
+ color: var(--moss-fg, #dcddde);
1028
+ cursor: pointer;
1029
+ }
1030
+
1031
+ .cm-moss-file-block:hover .cm-moss-file-block-actions,
1032
+ .cm-moss-file-block:focus-within .cm-moss-file-block-actions {
1033
+ opacity: 1;
1034
+ }
1035
+
1036
+ .cm-moss-file-block-download:hover,
1037
+ .cm-moss-file-block-delete:hover {
1038
+ background: var(--moss-bg-surface, #2d2d2d);
1039
+ }
1040
+
1041
+ @media (hover: none), (pointer: coarse) {
1042
+ .cm-moss-file-block-actions {
1043
+ opacity: 1;
1044
+ }
1045
+
1046
+ .cm-moss-file-block-download,
1047
+ .cm-moss-file-block-delete {
1048
+ width: 2.75rem;
1049
+ height: 2.75rem;
1050
+ }
1051
+ }
1052
+
1053
+ .cm-moss-file-block-download:focus-visible,
1054
+ .cm-moss-file-block-delete:focus-visible {
1055
+ outline: 2px solid var(--moss-accent, #7c3aed);
1056
+ outline-offset: 2px;
1057
+ }
1058
+
1059
+ /* The source line remains in the document for position mapping, but its
1060
+ * line box is removed so the rendered file card is the only visible block. */
1061
+ .cm-line.cm-moss-file-block-source-line {
1062
+ height: 0;
1063
+ min-height: 0;
1064
+ padding-top: 0;
1065
+ padding-bottom: 0;
1066
+ line-height: 0;
1067
+ }
1068
+
1069
+ /* ------------------------------------------------------------------ */
1070
+ /* Upload progress block.
1071
+ *
1072
+ * Rendered by mossUploadBlocks while an upload is in flight. Layout:
1073
+ * preview on the left (image thumb or file icon), body on the right
1074
+ * with filename + size, a progress bar, status text, and (on error)
1075
+ * retry / cancel buttons. Matches the image block's padding so the
1076
+ * transition from "uploading" → final markdown + image widget is
1077
+ * visually smooth.
1078
+ * ------------------------------------------------------------------ */
1079
+ .cm-moss-upload {
1080
+ display: flex;
1081
+ align-items: center;
1082
+ gap: 0.75em;
1083
+ padding: 0.5em 0.75em;
1084
+ margin: 0.2em 0;
1085
+ border: 1px solid var(--moss-border, rgba(127, 127, 127, 0.18));
1086
+ border-radius: 0.5em;
1087
+ background: var(--moss-upload-bg, rgba(127, 127, 127, 0.06));
1088
+ }
1089
+
1090
+ .cm-moss-upload-preview {
1091
+ flex: 0 0 auto;
1092
+ width: 3.5em;
1093
+ height: 3.5em;
1094
+ display: inline-flex;
1095
+ align-items: center;
1096
+ justify-content: center;
1097
+ overflow: hidden;
1098
+ border-radius: 0.4em;
1099
+ background: var(--moss-bg-surface, #2d2d2d);
1100
+ }
1101
+
1102
+ .cm-moss-upload-preview img {
1103
+ width: 100%;
1104
+ height: 100%;
1105
+ object-fit: cover;
1106
+ display: block;
1107
+ }
1108
+
1109
+ .cm-moss-upload-preview.cm-moss-upload-preview-file {
1110
+ flex-direction: column;
1111
+ align-items: center;
1112
+ justify-content: center;
1113
+ gap: 0.18em;
1114
+ color: var(--moss-fg-muted, #9a9da4);
1115
+ }
1116
+
1117
+ .cm-moss-upload-file-glyph {
1118
+ display: inline-flex;
1119
+ align-items: center;
1120
+ justify-content: center;
1121
+ flex: 1 1 auto;
1122
+ min-height: 0;
1123
+ }
1124
+
1125
+ .cm-moss-upload-file-glyph svg {
1126
+ width: 2.2em;
1127
+ height: 2.2em;
1128
+ }
1129
+
1130
+ .cm-moss-upload-ext {
1131
+ font-size: 0.62em;
1132
+ font-weight: 700;
1133
+ letter-spacing: 0.05em;
1134
+ line-height: 1;
1135
+ padding: 0.18em 0.4em;
1136
+ border-radius: 0.25em;
1137
+ background: var(--moss-bg-surface, #2d2d2d);
1138
+ color: var(--moss-fg, #dcddde);
1139
+ text-transform: uppercase;
1140
+ }
1141
+
1142
+ .cm-moss-upload-body {
1143
+ flex: 1 1 auto;
1144
+ min-width: 0;
1145
+ display: flex;
1146
+ flex-direction: column;
1147
+ gap: 0.3em;
1148
+ }
1149
+
1150
+ .cm-moss-upload-meta {
1151
+ font-size: 0.85em;
1152
+ color: var(--moss-fg, #dcddde);
1153
+ white-space: nowrap;
1154
+ overflow: hidden;
1155
+ text-overflow: ellipsis;
1156
+ }
1157
+
1158
+ .cm-moss-upload-progress {
1159
+ width: 100%;
1160
+ height: 0.35em;
1161
+ border-radius: 999px;
1162
+ background: var(--moss-bg-surface, #2d2d2d);
1163
+ overflow: hidden;
1164
+ }
1165
+
1166
+ .cm-moss-upload-bar {
1167
+ height: 100%;
1168
+ width: 0%;
1169
+ border-radius: 999px;
1170
+ background: var(--moss-accent, #4facfe);
1171
+ transition: width 0.15s ease;
1172
+ }
1173
+
1174
+ .cm-moss-upload-status {
1175
+ font-size: 0.8em;
1176
+ color: var(--moss-fg-muted, #888);
1177
+ }
1178
+
1179
+ .cm-moss-upload-status.is-error {
1180
+ color: var(--moss-hl-invalid, #ff5370);
1181
+ }
1182
+
1183
+ .cm-moss-upload-actions {
1184
+ display: inline-flex;
1185
+ gap: 0.4em;
1186
+ }
1187
+
1188
+ .cm-moss-upload-btn {
1189
+ display: inline-flex;
1190
+ align-items: center;
1191
+ justify-content: center;
1192
+ width: 1.75em;
1193
+ height: 1.75em;
1194
+ padding: 0;
1195
+ border: 1px solid var(--moss-border, rgba(127, 127, 127, 0.2));
1196
+ border-radius: 0.35em;
1197
+ background: transparent;
1198
+ color: var(--moss-fg-muted, #888);
1199
+ cursor: pointer;
1200
+ transition: background-color 0.12s ease, color 0.12s ease;
1201
+ }
1202
+
1203
+ .cm-moss-upload-btn:hover {
1204
+ background: var(--moss-bg-surface, #2d2d2d);
1205
+ color: var(--moss-fg, #dcddde);
1206
+ }
1207
+
1208
+ .cm-moss-upload-btn svg {
1209
+ width: 1em;
1210
+ height: 1em;
1211
+ }
1212
+
1213
+ /* ------------------------------------------------------------------ */
1214
+ /* Slash-commands side `+` button + popup tweaks.
1215
+ *
1216
+ * The `+` button is rendered by mossSlashCommands (sideButton: true)
1217
+ * as a non-block widget at line.from when the cursor sits on an empty
1218
+ * line. It is visually pulled into the left gutter without adding a row.
1219
+ *
1220
+ * The autocomplete popup keeps the editor's panel tokens but gets its own
1221
+ * compact menu geometry: content-sized width, comfortable hit rows, and a
1222
+ * little more separation from the document below it.
1223
+ * ------------------------------------------------------------------- */
1224
+ .moss-cm-editor .cm-line {
1225
+ position: relative;
1226
+ }
1227
+
1228
+ .cm-moss-side-plus {
1229
+ position: absolute;
1230
+ left: -2.8em;
1231
+ top: 50%;
1232
+ transform: translateY(-50%);
1233
+ width: 1.7em;
1234
+ height: 1.7em;
1235
+ display: inline-flex;
1236
+ align-items: center;
1237
+ justify-content: center;
1238
+ padding: 0;
1239
+ margin: 0;
1240
+ border: 1px solid var(--moss-border, rgba(127, 127, 127, 0.45));
1241
+ border-radius: 6px;
1242
+ background: var(--moss-side-plus-bg, rgba(127, 127, 127, 0.22));
1243
+ color: var(--moss-fg-muted, #b4b8bf);
1244
+ font: inherit;
1245
+ font-size: 1em;
1246
+ line-height: 1;
1247
+ cursor: pointer;
1248
+ user-select: none;
1249
+ opacity: 1;
1250
+ transition: opacity 0.12s ease, background-color 0.12s ease,
1251
+ color 0.12s ease, border-color 0.12s ease;
1252
+ }
1253
+
1254
+ .cm-moss-side-plus svg {
1255
+ width: 1em;
1256
+ height: 1em;
1257
+ display: block;
1258
+ }
1259
+
1260
+ /* Subtle hover: a muted neutral wash + slightly stronger border.
1261
+ * No accent color — keeps the affordance quiet but discoverable. */
1262
+ .cm-moss-side-plus:hover,
1263
+ .cm-moss-side-plus:focus-visible {
1264
+ background: var(--moss-bg-surface, #2d2d2d);
1265
+ border-color: var(--moss-border, rgba(127, 127, 127, 0.7));
1266
+ color: var(--moss-fg, #dcddde);
1267
+ outline: none;
1268
+ }
1269
+
1270
+ .moss-cm-editor .cm-tooltip.cm-tooltip-autocomplete {
1271
+ box-sizing: border-box;
1272
+ width: min(11rem, calc(100vw - 1rem));
1273
+ min-width: 0;
1274
+ max-width: min(16rem, calc(100vw - 1rem));
1275
+ font-family: var(--moss-font, system-ui, sans-serif);
1276
+ font-size: 0.875rem;
1277
+ padding: 0.28rem;
1278
+ border-radius: var(--moss-radius, 6px);
1279
+ box-shadow: 0 0.6rem 1.5rem rgba(0, 0, 0, 0.26);
1280
+ }
1281
+
1282
+ .moss-cm-editor .cm-tooltip.cm-tooltip-autocomplete > ul {
1283
+ min-width: 0;
1284
+ width: 100%;
1285
+ max-height: min(17rem, 40vh);
1286
+ margin: 0;
1287
+ padding: 0;
1288
+ }
1289
+
1290
+ .moss-cm-editor .cm-tooltip.cm-tooltip-autocomplete > ul > li {
1291
+ box-sizing: border-box;
1292
+ display: flex;
1293
+ align-items: center;
1294
+ gap: 0.6rem;
1295
+ width: 100%;
1296
+ min-width: 0;
1297
+ min-height: 1.95rem;
1298
+ padding: 0.34rem 0.5rem;
1299
+ border-radius: 0.35rem;
1300
+ line-height: 1.25;
1301
+ transition: background-color 0.12s ease, color 0.12s ease;
1302
+ }
1303
+
1304
+ .moss-cm-editor .cm-tooltip.cm-tooltip-autocomplete > ul > li:hover {
1305
+ background: color-mix(
1306
+ in srgb,
1307
+ var(--moss-accent, #7c3aed) 10%,
1308
+ var(--moss-bg-surface, #2d2d2d) 90%
1309
+ );
1310
+ color: var(--moss-fg, #dcddde);
1311
+ }
1312
+
1313
+ .cm-tooltip-autocomplete li[aria-selected] {
1314
+ background: var(
1315
+ --moss-accent-bg,
1316
+ color-mix(in srgb, var(--moss-accent, #7c3aed) 16%, transparent)
1317
+ );
1318
+ color: var(--moss-fg, #dcddde);
1319
+ }
1320
+
1321
+ .cm-tooltip-autocomplete .cm-completionLabel {
1322
+ display: block;
1323
+ min-width: 0;
1324
+ flex: 1 1 auto;
1325
+ overflow: hidden;
1326
+ text-overflow: ellipsis;
1327
+ white-space: nowrap;
1328
+ }
1329
+
1330
+ /* "icon + title" layout: drop the secondary detail text and render a
1331
+ * leading icon per command kind. Slash command renderers insert the
1332
+ * actual SVG node; CSS only owns sizing and layout. */
1333
+ .cm-tooltip-autocomplete .cm-completionDetail {
1334
+ display: none;
1335
+ }
1336
+
1337
+ /* Override CM6's defaults: it sets `opacity: .6` (too faint for our
1338
+ * stroke icons), `box-sizing: content-box` + `padding-right: .6em`
1339
+ * (width/height then exclude padding, so the SVG gets clipped to a
1340
+ * sub-em box and reads as nearly invisible).
1341
+ * Switch to border-box, drop the right padding (we use margin
1342
+ * instead), and pin opacity to 1 so the icon is the visual anchor. */
1343
+ .cm-tooltip-autocomplete .cm-completionIcon {
1344
+ width: 1.15em;
1345
+ min-width: 1.15em;
1346
+ height: 1.15em;
1347
+ flex: 0 0 1.15em;
1348
+ margin-right: 0;
1349
+ padding: 0;
1350
+ vertical-align: middle;
1351
+ display: inline-block;
1352
+ box-sizing: border-box;
1353
+ opacity: 1;
1354
+ background-repeat: no-repeat;
1355
+ background-position: center;
1356
+ background-size: contain;
1357
+ }
1358
+
1359
+ .cm-tooltip-autocomplete .cm-completionIcon svg {
1360
+ display: block;
1361
+ width: 100%;
1362
+ height: 100%;
1363
+ stroke: currentColor;
1364
+ }
1365
+
1366
+ .cm-tooltip-autocomplete .cm-completionIcon::after {
1367
+ content: none;
1368
+ }
1369
+
1370
+ /* Hide the popup's scrollbar without disabling scroll. CM6's list
1371
+ * is a <ul> with its own max-height + overflow:auto. */
1372
+ .cm-tooltip-autocomplete ul,
1373
+ .cm-tooltip-autocomplete .cm-completionList {
1374
+ scrollbar-width: none;
1375
+ -ms-overflow-style: none;
1376
+ }
1377
+
1378
+ .cm-tooltip-autocomplete ul::-webkit-scrollbar,
1379
+ .cm-tooltip-autocomplete .cm-completionList::-webkit-scrollbar {
1380
+ display: none;
1381
+ width: 0;
1382
+ height: 0;
1383
+ }
1384
+
1385
+ /* Bullet widget — replaces the raw `-` / `*` marker on bullet-list
1386
+ * items. Matches the visual weight of a real list marker while
1387
+ * staying in the muted-secondary color so the item text dominates. */
1388
+ /* Shared marker alcove — forces bullet, task checkbox, and
1389
+ * ordered-list number into the same fixed horizontal width so the
1390
+ * inline-preview line padding / text-indent math is stable across
1391
+ * list kinds. Width here must match LIST_ALCOVE_EM in
1392
+ * inline-preview.ts. Without the fixed width, wrapped-line
1393
+ * indent lands in different places for each kind because the raw
1394
+ * marker widths differ per font. */
1395
+ .cm-moss-list-marker {
1396
+ display: inline-block;
1397
+ width: 0.9em;
1398
+ margin-right: 0.3em;
1399
+ text-align: right;
1400
+ }
1401
+
1402
+ /* Active lines keep the source separator space so the caret can map to the
1403
+ * real Markdown position. Let that space provide the separation instead of
1404
+ * adding the alcove margin a second time. */
1405
+ .cm-moss-list-marker-active {
1406
+ margin-right: 0;
1407
+ }
1408
+
1409
+ /* Ordered markers keep their real source separator in the DOM on every
1410
+ * line, so their alcove must not add a second separator via margin. */
1411
+ .cm-moss-ordered-marker {
1412
+ margin-right: 0;
1413
+ }
1414
+
1415
+ /* List heads stay visually distinct from ordinary prose while the raw
1416
+ * Markdown remains the source of truth. Consumers can tune these tokens
1417
+ * independently without changing the list layout. */
1418
+ .cm-line .cm-moss-ordered-marker,
1419
+ .cm-line .cm-moss-ordered-marker * {
1420
+ color: var(--moss-list-marker, var(--moss-list-ordered, var(--moss-accent-bright, #a78bfa)));
1421
+ font-weight: 600;
1422
+ }
1423
+
1424
+ .cm-moss-unordered-marker {
1425
+ color: var(--moss-list-marker, var(--moss-list-unordered, var(--moss-fg-muted, #888)));
1426
+ }
1427
+
1428
+ /* Bullet-only visual styling on top of the shared alcove. */
1429
+ .cm-moss-bullet {
1430
+ color: var(--moss-fg-muted, #888);
1431
+ font-weight: 700;
1432
+ }
1433
+
1434
+ /* GFM task-list checkbox widget — replaces `[ ]` / `[x]` in a task
1435
+ * item. Styled as a small custom circle rather than the platform's
1436
+ * native checkbox so it reads consistently across browsers.
1437
+ * Width+margin still total 1.2em to match ALCOVE_EM in
1438
+ * inline-preview.ts. The trailing space after `[ ]` in the source is
1439
+ * swallowed by the replace decoration. */
1440
+ input.cm-moss-task-checkbox {
1441
+ width: 1.05em;
1442
+ height: 1.05em;
1443
+ margin: 0 0.31em 0 -0.16em;
1444
+ font-size: inherit;
1445
+ vertical-align: 0.1em;
1446
+ appearance: none;
1447
+ -webkit-appearance: none;
1448
+ box-sizing: border-box;
1449
+ border: 1px solid var(--moss-fg-muted, #888);
1450
+ border-radius: 50%;
1451
+ background: transparent;
1452
+ cursor: pointer;
1453
+ position: relative;
1454
+ display: inline-grid;
1455
+ place-content: center;
1456
+ transform: translateY(-0.04em);
1457
+ transition: border-color 140ms ease, box-shadow 140ms ease;
1458
+ }
1459
+
1460
+ input.cm-moss-task-checkbox::after {
1461
+ content: "";
1462
+ width: 0.26em;
1463
+ height: 0.5em;
1464
+ border-right: 0.12em solid #ffffff;
1465
+ border-bottom: 0.12em solid #ffffff;
1466
+ transform: rotate(45deg) translate(-0.03em, -0.04em);
1467
+ opacity: 0;
1468
+ }
1469
+
1470
+ input.cm-moss-task-checkbox:checked {
1471
+ background: var(--moss-accent, #7c3aed);
1472
+ border-color: var(--moss-accent, #7c3aed);
1473
+ }
1474
+
1475
+ input.cm-moss-task-checkbox:checked::after {
1476
+ opacity: 1;
1477
+ }
1478
+
1479
+ input.cm-moss-task-checkbox:hover,
1480
+ .cm-moss-task-status:hover {
1481
+ border-color: var(--moss-accent-bright, var(--moss-accent, #7c3aed));
1482
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--moss-accent, #7c3aed) 16%, transparent 84%);
1483
+ }
1484
+
1485
+ input.cm-moss-task-checkbox:focus-visible {
1486
+ outline: none;
1487
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--moss-accent, #7c3aed) 28%, transparent 72%);
1488
+ }
1489
+
1490
+ /* Forced-colors mode removes box shadows, so retain a visible keyboard
1491
+ * focus indicator using system colors instead. */
1492
+ @media (forced-colors: active) {
1493
+ input.cm-moss-task-checkbox:focus-visible {
1494
+ outline: 2px solid Highlight;
1495
+ outline-offset: 2px;
1496
+ box-shadow: none;
1497
+ }
1498
+ }
1499
+
1500
+ /* Custom task statuses use their assigned Lucide icon in the same marker
1501
+ * alcove. A button keeps the status keyboard and pointer accessible without
1502
+ * pretending that every state is a binary checkbox. */
1503
+ .cm-moss-task-status {
1504
+ width: 1.05em;
1505
+ height: 1.05em;
1506
+ margin: 0 0.31em 0 -0.16em;
1507
+ padding: 0;
1508
+ border: 0;
1509
+ background: transparent;
1510
+ box-sizing: border-box;
1511
+ color: var(--moss-fg-muted, #888);
1512
+ cursor: pointer;
1513
+ font: inherit;
1514
+ font-size: 1em;
1515
+ line-height: 1;
1516
+ vertical-align: -0.08em;
1517
+ display: inline-flex;
1518
+ align-items: center;
1519
+ justify-content: center;
1520
+ place-content: center;
1521
+ border-radius: 50%;
1522
+ transition: color 140ms ease, border-color 140ms ease, box-shadow 140ms ease;
1523
+ }
1524
+
1525
+ .cm-moss-task-status-empty {
1526
+ border: 1px solid var(--moss-fg-muted, #888);
1527
+ }
1528
+
1529
+ .cm-moss-task-status svg {
1530
+ width: 1em;
1531
+ height: 1em;
1532
+ stroke-width: 2.5;
1533
+ }
1534
+
1535
+ .cm-moss-task-status:hover {
1536
+ color: var(--moss-accent-bright, var(--moss-accent, #7c3aed));
1537
+ }
1538
+
1539
+ .cm-moss-task-status:focus-visible {
1540
+ outline: 2px solid color-mix(in srgb, var(--moss-accent, #7c3aed) 42%, transparent 58%);
1541
+ outline-offset: 2px;
1542
+ }
1543
+
1544
+ /* Whole-line strike for completed task items. */
1545
+ .cm-line.cm-moss-task-done {
1546
+ color: var(--moss-fg-faint, #666);
1547
+ text-decoration: line-through;
1548
+ }
1549
+
1550
+ /* WYSIWYG table block widget — emitted by `mossTables()` in
1551
+ * table-widget.ts. The Table node's source range is fully replaced
1552
+ * by this rendered `<table>`; users interact with cells directly
1553
+ * via `contenteditable`, with Tab / Shift-Tab moving focus between
1554
+ * cells and Tab past the last cell appending a new row.
1555
+ *
1556
+ * Horizontal scroll on the wrapper so wide tables (common in
1557
+ * RSS-ingested content) don't squish their cells to unreadable
1558
+ * widths on narrow viewports. The inner `<table>` uses
1559
+ * `width: max-content` + `min-width: 100%` so it fills the reading
1560
+ * column when narrow and grows past it (triggering the scrollbar)
1561
+ * when content needs more room. `overscroll-behavior-x: contain`
1562
+ * keeps a horizontal flick from bubbling up and scrolling the
1563
+ * whole editor sideways. */
1564
+ /* Table wrapper — horizontal scroll contained here so wide
1565
+ * tables don't push the editor's content box wider than the
1566
+ * viewport. Paired with `.cm-content { min-width: 0 }` in the
1567
+ * theme, which is what actually keeps the content box pinned to
1568
+ * parent width (CM6's base theme sets `min-width: max-content`,
1569
+ * which otherwise lets a wide child expand the whole scroller).
1570
+ * Without that pairing no amount of table-level overflow
1571
+ * constraints would hold — the symptom was the editor briefly
1572
+ * going wider than the viewport on mobile when a wide table
1573
+ * entered the rendered viewport. */
1574
+ .cm-moss-table {
1575
+ /* Use padding, not margin, for vertical rhythm around the table.
1576
+ * CM6's heightmap measures block-widget heights via
1577
+ * `getBoundingClientRect`, which EXCLUDES margin but INCLUDES
1578
+ * padding. A `margin: 0.5em 0` here meant the heightmap allocated
1579
+ * less height than the DOM layout flow actually consumed — 1em
1580
+ * (~17px) less on typical font sizes. That drift shifted every
1581
+ * line below the table in CM6's heightmap vs the DOM, so clicks
1582
+ * at a visual Y were routed to the doc line BELOW the one
1583
+ * visually targeted. User-visible symptom: clicking an empty
1584
+ * line above a heading placed the caret on the heading; clicking
1585
+ * text in a task item with the caret on the empty line below
1586
+ * left the caret in place (because the "wrong" line was the same
1587
+ * as the current caret). Padding keeps the bbox honest so CM6's
1588
+ * heightmap matches the DOM. */
1589
+ padding: 0.5em 0;
1590
+ overflow-x: auto;
1591
+ overscroll-behavior-x: contain;
1592
+ -webkit-overflow-scrolling: touch;
1593
+ }
1594
+
1595
+ .cm-moss-table table {
1596
+ border-collapse: collapse;
1597
+ width: max-content;
1598
+ min-width: 100%;
1599
+ font-family: var(--moss-font, system-ui, sans-serif);
1600
+ }
1601
+
1602
+ .cm-moss-table th,
1603
+ .cm-moss-table td {
1604
+ position: relative;
1605
+ border: 1px solid var(--moss-border, #3d3d3d);
1606
+ padding: 0.35em 0.6em;
1607
+ text-align: left;
1608
+ vertical-align: top;
1609
+ min-width: 2em;
1610
+ min-height: 1.5em;
1611
+ }
1612
+
1613
+ .cm-moss-table th {
1614
+ font-weight: 700;
1615
+ background: var(--moss-code-bg, color-mix(in srgb, #2d2d2d 88%, black 12%));
1616
+ }
1617
+
1618
+ /* Cells aren't contenteditable themselves; the inner source element
1619
+ * is. Outline the whole cell when focus lands inside so the edit
1620
+ * target reads as "the cell" even though the DOM is split. */
1621
+ .cm-moss-table th:focus-within,
1622
+ .cm-moss-table td:focus-within {
1623
+ outline: 2px solid var(--moss-accent, #7c3aed);
1624
+ outline-offset: -2px;
1625
+ }
1626
+
1627
+ /* The action button sits on the upper-right border intersection of the
1628
+ * focused cell. It is absolute so it never changes table sizing or row
1629
+ * height. */
1630
+ .cm-moss-table-menu-trigger {
1631
+ position: absolute;
1632
+ top: -0.7em;
1633
+ right: -0.7em;
1634
+ z-index: 2;
1635
+ width: 1.6em;
1636
+ height: 1.6em;
1637
+ display: inline-flex;
1638
+ align-items: center;
1639
+ justify-content: center;
1640
+ padding: 0;
1641
+ border: 1px solid var(--moss-border, #3d3d3d);
1642
+ border-radius: 0.35em;
1643
+ background: var(--moss-bg-panel, #252525);
1644
+ color: var(--moss-fg-muted, #888);
1645
+ cursor: pointer;
1646
+ opacity: 0;
1647
+ pointer-events: none;
1648
+ transition: opacity 0.12s ease, background-color 0.12s ease,
1649
+ color 0.12s ease, transform 0.12s ease;
1650
+ }
1651
+
1652
+ .cm-moss-table th:focus-within .cm-moss-table-menu-trigger,
1653
+ .cm-moss-table td:focus-within .cm-moss-table-menu-trigger {
1654
+ opacity: 1;
1655
+ pointer-events: auto;
1656
+ }
1657
+
1658
+ .cm-moss-table-menu-trigger:hover,
1659
+ .cm-moss-table-menu-trigger:focus-visible {
1660
+ background: var(--moss-bg-surface, #2d2d2d);
1661
+ color: var(--moss-fg, #dcddde);
1662
+ transform: scale(1.05);
1663
+ }
1664
+
1665
+ .cm-moss-table-menu-trigger:focus-visible {
1666
+ outline: 2px solid var(--moss-accent, #7c3aed);
1667
+ outline-offset: 2px;
1668
+ }
1669
+
1670
+ .cm-moss-table-menu-trigger svg {
1671
+ width: 1em;
1672
+ height: 1em;
1673
+ }
1674
+
1675
+ /* The editable source inside a cell — always shows the raw markdown
1676
+ * the user types, whether or not the cell also has a rendered image
1677
+ * preview below. Blank outline because the cell's :focus-within
1678
+ * ring is what signals the active edit target. */
1679
+ .cm-moss-table-cell-source {
1680
+ outline: none;
1681
+ min-height: 1.2em;
1682
+ white-space: pre-wrap;
1683
+ word-break: break-word;
1684
+ }
1685
+
1686
+ /* Inline-mark delimiters (`**`, `__`, `*`, `_`, `~~`, `[`, `]`, `(`, `)`,
1687
+ * URLs) inside table cells. Hidden by default so the reader sees only
1688
+ * the rendered content — bold text without `**`, link text without
1689
+ * brackets or URL. The delimiters remain in the DOM (so textContent
1690
+ * round-trips to the raw markdown on input) but take zero space and
1691
+ * the caret can't navigate into them.
1692
+ *
1693
+ * When the caret enters a mark wrap, JS adds `active` and the
1694
+ * descendant delimiters reveal — so the user can actually edit them.
1695
+ * Nested marks (bold-containing-italic) all reveal together when any
1696
+ * of them contains the caret, because the JS walks ancestors. */
1697
+ .cm-moss-table-cell-source .cm-moss-mark {
1698
+ display: none;
1699
+ }
1700
+
1701
+ .cm-moss-table-cell-source .cm-moss-strong-wrap.active > .cm-moss-mark,
1702
+ .cm-moss-table-cell-source .cm-moss-em-wrap.active > .cm-moss-mark,
1703
+ .cm-moss-table-cell-source .cm-moss-strike-wrap.active > .cm-moss-mark,
1704
+ .cm-moss-table-cell-source .cm-moss-highlight-wrap.active > .cm-moss-mark,
1705
+ .cm-moss-table-cell-source .cm-moss-link-wrap.active > .cm-moss-mark {
1706
+ display: inline;
1707
+ color: var(--moss-fg-faint, #666);
1708
+ font-weight: normal;
1709
+ font-style: normal;
1710
+ text-decoration: none;
1711
+ }
1712
+
1713
+ /* Link URL: monospace hint + size cut so a long URL doesn't dominate
1714
+ * the cell's width once its wrap becomes active. */
1715
+ .cm-moss-table-cell-source .cm-moss-link-wrap.active > .cm-moss-link-url {
1716
+ font-family: var(--moss-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
1717
+ font-size: 0.88em;
1718
+ }
1719
+
1720
+ .cm-moss-table-cell-source .cm-moss-highlight {
1721
+ background: color-mix(in srgb, var(--moss-accent-bright, #a78bfa) 20%, transparent 80%);
1722
+ border-radius: 0.18em;
1723
+ color: var(--moss-fg, #dcddde);
1724
+ box-decoration-break: clone;
1725
+ -webkit-box-decoration-break: clone;
1726
+ }
1727
+
1728
+ /* Rendered image preview strip below the source text. Mirrors how
1729
+ * block-level images render outside a table: the `![alt](url)`
1730
+ * markdown is the source of truth, but the user sees the image in
1731
+ * the resting state — the raw markdown only appears when the cell
1732
+ * is focused for editing (see :focus-within rule below). */
1733
+ .cm-moss-table-cell-preview {
1734
+ margin-top: 0.35em;
1735
+ display: flex;
1736
+ flex-wrap: wrap;
1737
+ gap: 0.3em;
1738
+ user-select: none;
1739
+ }
1740
+
1741
+ /* Mirror the block-image invariant from outside tables: on an
1742
+ * inactive cell, the raw `![alt](url)` markdown hides and only the
1743
+ * rendered image shows. Focus the cell (click or Tab) to bring the
1744
+ * source back so it can be edited.
1745
+ *
1746
+ * We visually hide the source without `display: none` so it stays in
1747
+ * the DOM and remains focusable — Tab nav calls `source.focus()`
1748
+ * explicitly and would silently fail on a display:none element. The
1749
+ * position:absolute + zero-size pattern keeps the element interactive
1750
+ * while taking no visible space. */
1751
+ .cm-moss-table th[data-has-image]:not(:focus-within) .cm-moss-table-cell-source,
1752
+ .cm-moss-table td[data-has-image]:not(:focus-within) .cm-moss-table-cell-source {
1753
+ position: absolute;
1754
+ width: 1px;
1755
+ height: 1px;
1756
+ overflow: hidden;
1757
+ opacity: 0;
1758
+ pointer-events: none;
1759
+ white-space: nowrap;
1760
+ }
1761
+
1762
+ /* Inline images inside table cells. Capped so a tall image doesn't
1763
+ * balloon a single row into half the viewport. Users who want larger
1764
+ * in-cell images can do without for now — we'd need Obsidian's
1765
+ * `[[file|width]]` alias syntax to make it controllable. */
1766
+ .cm-moss-table-cell-image {
1767
+ display: block;
1768
+ max-width: 14em;
1769
+ max-height: 5em;
1770
+ border-radius: 0.2em;
1771
+ cursor: pointer;
1772
+ }
1773
+
1774
+ /* Table actions menu — opened from the focused cell's upper-right trigger.
1775
+ * Mounted on document.body so positioning isn't affected by CM6's overflow
1776
+ * scroll. */
1777
+ .cm-moss-table-menu {
1778
+ position: fixed;
1779
+ z-index: 1000;
1780
+ min-width: 170px;
1781
+ padding: 0.25rem 0;
1782
+ background: var(--moss-bg-panel, #252525);
1783
+ border: 1px solid var(--moss-border, #3d3d3d);
1784
+ border-radius: var(--moss-radius, 6px);
1785
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
1786
+ font-family: var(--moss-font, system-ui, sans-serif);
1787
+ font-size: 0.82rem;
1788
+ color: var(--moss-fg, #dcddde);
1789
+ }
1790
+
1791
+ .cm-moss-table-menu-item {
1792
+ display: flex;
1793
+ align-items: center;
1794
+ gap: 0.55rem;
1795
+ box-sizing: border-box;
1796
+ width: 100%;
1797
+ text-align: left;
1798
+ min-height: 2rem;
1799
+ padding: 0.38rem 0.7rem;
1800
+ background: transparent;
1801
+ border: 0;
1802
+ color: inherit;
1803
+ font: inherit;
1804
+ cursor: pointer;
1805
+ transition: background-color 0.12s ease, color 0.12s ease;
1806
+ }
1807
+
1808
+ .cm-moss-table-menu-item:hover,
1809
+ .cm-moss-table-menu-item:focus-visible {
1810
+ background: var(--moss-bg-surface, #2d2d2d);
1811
+ outline: none;
1812
+ }
1813
+
1814
+ .cm-moss-table-menu-item.is-destructive {
1815
+ color: var(--moss-hl-invalid, #ff5370);
1816
+ }
1817
+
1818
+ .cm-moss-table-menu-item.is-destructive:hover,
1819
+ .cm-moss-table-menu-item.is-destructive:focus-visible {
1820
+ background: color-mix(
1821
+ in srgb,
1822
+ var(--moss-hl-invalid, #ff5370) 12%,
1823
+ var(--moss-bg-surface, #2d2d2d) 88%
1824
+ );
1825
+ }
1826
+
1827
+ .cm-moss-table-menu-item-icon {
1828
+ display: inline-flex;
1829
+ flex: 0 0 1rem;
1830
+ align-items: center;
1831
+ justify-content: center;
1832
+ color: var(--moss-fg-muted, #888);
1833
+ }
1834
+
1835
+ .cm-moss-table-menu-item.is-destructive .cm-moss-table-menu-item-icon {
1836
+ color: currentColor;
1837
+ }
1838
+
1839
+ .cm-moss-table-menu-item-icon svg {
1840
+ width: 1rem;
1841
+ height: 1rem;
1842
+ }
1843
+
1844
+ .cm-moss-table-menu-item-label {
1845
+ min-width: 0;
1846
+ overflow: hidden;
1847
+ text-overflow: ellipsis;
1848
+ white-space: nowrap;
1849
+ }
1850
+
1851
+ .cm-moss-table-menu-sep {
1852
+ height: 1px;
1853
+ margin: 0.25rem 0;
1854
+ background: var(--moss-border, #3d3d3d);
1855
+ }
1856
+
1857
+ /* Find-in-document panel — a floating control strip owned by the
1858
+ * search feature. The CM panel only reserves the top/bottom slot;
1859
+ * the visible chrome is split into input and actions pills. */
1860
+ .moss-cm-editor .cm-editor {
1861
+ position: relative;
1862
+ }
1863
+
1864
+ .moss-cm-editor .cm-panels:has(.moss-search-panel) {
1865
+ position: absolute;
1866
+ left: 0;
1867
+ right: 0;
1868
+ z-index: 10;
1869
+ background: transparent;
1870
+ border: 0;
1871
+ color: var(--moss-fg, #dcddde);
1872
+ pointer-events: none;
1873
+ }
1874
+
1875
+ .moss-cm-editor .cm-panels-top:has(.moss-search-panel) {
1876
+ top: 0.5rem;
1877
+ }
1878
+
1879
+ .moss-cm-editor .cm-panels-top:has(.moss-search-panel-center) {
1880
+ top: 50%;
1881
+ transform: translateY(-50%);
1882
+ }
1883
+
1884
+ .moss-cm-editor .cm-panels-bottom:has(.moss-search-panel) {
1885
+ bottom: 0.5rem;
1886
+ }
1887
+
1888
+ .cm-panel.moss-search-panel {
1889
+ padding: 0;
1890
+ background: transparent;
1891
+ border: 0;
1892
+ font-family: var(--moss-font, system-ui, sans-serif);
1893
+ color: var(--moss-fg, #dcddde);
1894
+ pointer-events: none;
1895
+ }
1896
+
1897
+ .cm-panel.moss-search-panel-top {
1898
+ border-bottom: 0;
1899
+ }
1900
+
1901
+ .cm-panel.moss-search-panel-bottom {
1902
+ border-top: 0;
1903
+ }
1904
+
1905
+ .cm-panel.moss-search-panel-center {
1906
+ border: 0;
1907
+ }
1908
+
1909
+ .cm-panel.moss-search-panel form {
1910
+ display: flex;
1911
+ align-items: center;
1912
+ justify-content: center;
1913
+ gap: 0.45rem;
1914
+ box-sizing: border-box;
1915
+ width: fit-content;
1916
+ max-width: min(var(--moss-measure, 70ch), calc(100% - 1rem));
1917
+ margin-inline: auto;
1918
+ padding: 0;
1919
+ background: transparent;
1920
+ border: 0;
1921
+ border-radius: 0;
1922
+ box-shadow: none;
1923
+ backdrop-filter: none;
1924
+ pointer-events: auto;
1925
+ }
1926
+
1927
+ .cm-panel.moss-search-panel .cm-moss-search-input-pill,
1928
+ .cm-panel.moss-search-panel .cm-moss-search-actions-pill {
1929
+ display: inline-flex;
1930
+ align-items: center;
1931
+ min-height: 1.85rem;
1932
+ background: color-mix(in srgb, var(--moss-bg-surface, #2d2d2d) 92%, transparent);
1933
+ border: 1px solid color-mix(in srgb, var(--moss-fg, #dcddde) 10%, transparent);
1934
+ border-radius: 999px;
1935
+ box-shadow:
1936
+ 0 1px 0 rgba(255, 255, 255, 0.06) inset,
1937
+ 0 1px 2px rgba(0, 0, 0, 0.3),
1938
+ 0 8px 20px rgba(0, 0, 0, 0.2);
1939
+ backdrop-filter: blur(14px);
1940
+ }
1941
+
1942
+ .cm-panel.moss-search-panel .cm-moss-search-input-pill {
1943
+ gap: 0.35rem;
1944
+ max-width: min(24rem, calc(100vw - 5rem));
1945
+ padding: 0.16rem 0.55rem;
1946
+ }
1947
+
1948
+ .cm-panel.moss-search-panel .cm-moss-search-input-pill:focus-within {
1949
+ border-color: color-mix(in srgb, var(--moss-accent, #7c3aed) 42%, transparent);
1950
+ box-shadow:
1951
+ 0 1px 0 rgba(255, 255, 255, 0.06) inset,
1952
+ 0 0 0 2px color-mix(in srgb, var(--moss-accent, #7c3aed) 20%, transparent),
1953
+ 0 8px 20px rgba(0, 0, 0, 0.2);
1954
+ }
1955
+
1956
+ .cm-panel.moss-search-panel .cm-moss-search-actions-pill {
1957
+ gap: 0.1rem;
1958
+ padding: 0.18rem 0.25rem;
1959
+ }
1960
+
1961
+ [data-theme="light"] .cm-panel.moss-search-panel .cm-moss-search-input-pill,
1962
+ [data-theme="light"] .cm-panel.moss-search-panel .cm-moss-search-actions-pill {
1963
+ border-color: color-mix(in srgb, var(--moss-fg, #1a1a1a) 10%, transparent);
1964
+ box-shadow:
1965
+ 0 1px 0 rgba(0, 0, 0, 0.03) inset,
1966
+ 0 1px 3px rgba(0, 0, 0, 0.06),
1967
+ 0 6px 16px rgba(0, 0, 0, 0.08);
1968
+ }
1969
+
1970
+ .cm-panel.cm-search.moss-search-panel input.cm-moss-search-input[type="text"] {
1971
+ appearance: none;
1972
+ flex: 1 1 auto;
1973
+ width: clamp(9rem, 28vw, 17rem);
1974
+ min-width: 6rem;
1975
+ height: 1.35rem;
1976
+ padding: 0;
1977
+ background: transparent;
1978
+ border: 0;
1979
+ border-radius: 0;
1980
+ box-shadow: none;
1981
+ color: inherit;
1982
+ font: inherit;
1983
+ font-size: 0.75rem;
1984
+ outline: none;
1985
+ }
1986
+
1987
+ .cm-panel.cm-search.moss-search-panel input.cm-moss-search-input[type="text"]:focus {
1988
+ background: transparent;
1989
+ border: 0;
1990
+ box-shadow: none;
1991
+ outline: none;
1992
+ }
1993
+
1994
+ .cm-panel.cm-search.moss-search-panel input.cm-moss-search-input[type="text"]::placeholder {
1995
+ color: var(--moss-fg-muted, #888);
1996
+ }
1997
+
1998
+ .cm-panel.moss-search-panel .cm-moss-search-count {
1999
+ flex: 0 0 auto;
2000
+ color: var(--moss-fg-muted, #888);
2001
+ font-size: 0.625rem;
2002
+ white-space: nowrap;
2003
+ min-width: 0;
2004
+ padding-inline: 0.1rem;
2005
+ user-select: none;
2006
+ }
2007
+
2008
+ .cm-panel.cm-search.moss-search-panel button.cm-moss-search-btn {
2009
+ appearance: none;
2010
+ flex: 0 0 auto;
2011
+ display: inline-flex;
2012
+ align-items: center;
2013
+ justify-content: center;
2014
+ width: 1.45rem;
2015
+ height: 1.45rem;
2016
+ padding: 0;
2017
+ background: transparent;
2018
+ border: 0;
2019
+ border-radius: 999px;
2020
+ box-shadow: none;
2021
+ color: var(--moss-fg-muted, #888);
2022
+ cursor: pointer;
2023
+ transition: background-color 0.12s ease, color 0.12s ease, border-color 0.12s ease;
2024
+ }
2025
+
2026
+ .cm-panel.cm-search.moss-search-panel button.cm-moss-search-btn:hover {
2027
+ background: var(--moss-bg-surface, #2d2d2d);
2028
+ color: var(--moss-fg, #dcddde);
2029
+ }
2030
+
2031
+ .cm-panel.cm-search.moss-search-panel button.cm-moss-search-btn:focus-visible {
2032
+ outline: none;
2033
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--moss-accent, #7c3aed) 30%, transparent);
2034
+ color: var(--moss-fg, #dcddde);
2035
+ }
2036
+
2037
+ .cm-panel.cm-search.moss-search-panel button.cm-moss-search-btn svg {
2038
+ display: block;
2039
+ width: 1rem;
2040
+ height: 1rem;
2041
+ color: inherit;
2042
+ stroke: currentColor;
2043
+ fill: none;
2044
+ pointer-events: none;
2045
+ }
2046
+
2047
+ .cm-panel.cm-search.moss-search-panel button.cm-moss-search-btn svg * {
2048
+ stroke: currentColor;
2049
+ }
2050
+
2051
+ /* Highlight matches in the document. Active match gets a stronger
2052
+ * fill; other matches share the same tone in a lighter shade. The
2053
+ * background-color custom properties are pre-mixed in src/index.css
2054
+ * to alpha-blend cleanly over whatever theme backdrop is active. */
2055
+ .cm-searchMatch {
2056
+ background: var(--moss-search-bg, rgba(124, 58, 237, 0.28));
2057
+ border-radius: 2px;
2058
+ }
2059
+
2060
+ .cm-searchMatch-selected {
2061
+ background: var(--moss-search-bg-active, rgba(124, 58, 237, 0.6));
2062
+ }
2063
+
2064
+ /* Initial reveal highlight — paired with the `initialRevealText` prop
2065
+ * and the `revealText()` imperative method on the editor handle.
2066
+ * Starts at a stronger accent-tinted background, settles to a softer
2067
+ * one, then fades to transparent over ~3.2s so the reader's eye
2068
+ * catches the match on arrival without the highlight lingering.
2069
+ *
2070
+ * Two tokens so consumers can theme either the peak or the settled
2071
+ * state independently of the main search-match colors. */
2072
+ .cm-initialRevealMatch {
2073
+ background: var(
2074
+ --moss-initial-reveal-bg,
2075
+ color-mix(in srgb, var(--moss-accent-soft, #a78bfa) 28%, transparent 72%)
2076
+ );
2077
+ border-radius: 0.2rem;
2078
+ animation: moss-initial-reveal-fade 3.2s ease-out forwards;
2079
+ }
2080
+
2081
+ @keyframes moss-initial-reveal-fade {
2082
+ 0% {
2083
+ background: var(
2084
+ --moss-initial-reveal-bg-strong,
2085
+ color-mix(in srgb, var(--moss-accent-soft, #a78bfa) 42%, transparent 58%)
2086
+ );
2087
+ }
2088
+ 65% {
2089
+ background: var(
2090
+ --moss-initial-reveal-bg,
2091
+ color-mix(in srgb, var(--moss-accent-soft, #a78bfa) 28%, transparent 72%)
2092
+ );
2093
+ }
2094
+ 100% {
2095
+ background: transparent;
2096
+ }
2097
+ }
2098
+
2099
+ /* Read-only ("reading") mode. The `cm-moss-readonly` class is set on
2100
+ * the editor wrapper by `readOnlyExtension` (src/read-only.ts). In this
2101
+ * mode nothing reveals its source and the whole document is a reading
2102
+ * surface, so interactive affordances change accordingly. */
2103
+
2104
+ /* The entire link is the open target now (not just the trailing icon),
2105
+ * so the text gets the pointer cursor too — for both inline links and
2106
+ * links rendered inside table cells. */
2107
+ .cm-moss-readonly .cm-moss-link,
2108
+ .cm-moss-readonly .cm-moss-wiki-link,
2109
+ .cm-moss-readonly .cm-moss-link-wrap {
2110
+ cursor: pointer;
2111
+ }
2112
+
2113
+ /* Table cells are inert: no caret, no text cursor. */
2114
+ .cm-moss-readonly .cm-moss-table-cell-source {
2115
+ cursor: default;
2116
+ }