@railway/inkwell 1.4.0 → 2.1.0
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/index.cjs +195 -5
- package/dist/index.d.cts +16 -2
- package/dist/index.d.ts +16 -2
- package/dist/index.js +195 -5
- package/package.json +2 -1
- package/src/styles.css +116 -37
package/dist/index.cjs
CHANGED
|
@@ -368,6 +368,73 @@ function computeInlineDecorations(entry) {
|
|
|
368
368
|
strikeMarker: true
|
|
369
369
|
});
|
|
370
370
|
}
|
|
371
|
+
const linkRanges = [];
|
|
372
|
+
const linkRegex = /(?<!!)\[([^\]\n]+)\]\(([^)\s]+)\)/g;
|
|
373
|
+
while ((match = linkRegex.exec(text)) !== null) {
|
|
374
|
+
if (isInCode(match.index)) continue;
|
|
375
|
+
const start = match.index;
|
|
376
|
+
const end = start + match[0].length;
|
|
377
|
+
const labelLen = match[1].length;
|
|
378
|
+
const urlLen = match[2].length;
|
|
379
|
+
const openBracket = start;
|
|
380
|
+
const labelStart = start + 1;
|
|
381
|
+
const labelEnd = labelStart + labelLen;
|
|
382
|
+
const closeBracket = labelEnd;
|
|
383
|
+
const openParen = closeBracket + 1;
|
|
384
|
+
const urlStart = openParen + 1;
|
|
385
|
+
const urlEnd = urlStart + urlLen;
|
|
386
|
+
const closeParen = urlEnd;
|
|
387
|
+
linkRanges.push({ start, end });
|
|
388
|
+
ranges.push({
|
|
389
|
+
anchor: { path: [...path, 0], offset: openBracket },
|
|
390
|
+
focus: { path: [...path, 0], offset: openBracket + 1 },
|
|
391
|
+
linkMarker: true
|
|
392
|
+
});
|
|
393
|
+
ranges.push({
|
|
394
|
+
anchor: { path: [...path, 0], offset: labelStart },
|
|
395
|
+
focus: { path: [...path, 0], offset: labelEnd },
|
|
396
|
+
link: true
|
|
397
|
+
});
|
|
398
|
+
ranges.push({
|
|
399
|
+
anchor: { path: [...path, 0], offset: closeBracket },
|
|
400
|
+
focus: { path: [...path, 0], offset: closeBracket + 1 },
|
|
401
|
+
linkMarker: true
|
|
402
|
+
});
|
|
403
|
+
ranges.push({
|
|
404
|
+
anchor: { path: [...path, 0], offset: openParen },
|
|
405
|
+
focus: { path: [...path, 0], offset: openParen + 1 },
|
|
406
|
+
linkMarker: true
|
|
407
|
+
});
|
|
408
|
+
ranges.push({
|
|
409
|
+
anchor: { path: [...path, 0], offset: urlStart },
|
|
410
|
+
focus: { path: [...path, 0], offset: urlEnd },
|
|
411
|
+
linkUrl: true
|
|
412
|
+
});
|
|
413
|
+
ranges.push({
|
|
414
|
+
anchor: { path: [...path, 0], offset: closeParen },
|
|
415
|
+
focus: { path: [...path, 0], offset: closeParen + 1 },
|
|
416
|
+
linkMarker: true
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
const isInLink = (offset) => linkRanges.some((r) => offset >= r.start && offset < r.end);
|
|
420
|
+
const urlRegex = /(?:https?:\/\/|www\.)[^\s<>()[\]]+/g;
|
|
421
|
+
while ((match = urlRegex.exec(text)) !== null) {
|
|
422
|
+
if (isInCode(match.index)) continue;
|
|
423
|
+
if (isInLink(match.index)) continue;
|
|
424
|
+
let matched = match[0];
|
|
425
|
+
let start = match.index;
|
|
426
|
+
let end = start + matched.length;
|
|
427
|
+
while (matched.length > 0 && /[.,;:!?]/.test(matched[matched.length - 1])) {
|
|
428
|
+
matched = matched.slice(0, -1);
|
|
429
|
+
end--;
|
|
430
|
+
}
|
|
431
|
+
if (matched.length === 0) continue;
|
|
432
|
+
ranges.push({
|
|
433
|
+
anchor: { path: [...path, 0], offset: start },
|
|
434
|
+
focus: { path: [...path, 0], offset: end },
|
|
435
|
+
link: true
|
|
436
|
+
});
|
|
437
|
+
}
|
|
371
438
|
return ranges;
|
|
372
439
|
}
|
|
373
440
|
function computeFenceDecorations(entry) {
|
|
@@ -775,17 +842,30 @@ function ImageElement({
|
|
|
775
842
|
}
|
|
776
843
|
function RenderLeaf({ attributes, children, leaf }) {
|
|
777
844
|
const l = leaf;
|
|
778
|
-
if (l.boldMarker || l.italicMarker || l.strikeMarker) {
|
|
845
|
+
if (l.boldMarker || l.italicMarker || l.strikeMarker || l.linkMarker) {
|
|
779
846
|
return /* @__PURE__ */ jsxRuntime.jsx("span", { ...attributes, className: editorClass("marker"), children });
|
|
780
847
|
}
|
|
781
848
|
if (l.codeMarker) {
|
|
782
849
|
return /* @__PURE__ */ jsxRuntime.jsx("span", { ...attributes, className: editorClass("backtick"), children });
|
|
783
850
|
}
|
|
851
|
+
if (l.linkUrl) {
|
|
852
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
853
|
+
"span",
|
|
854
|
+
{
|
|
855
|
+
...attributes,
|
|
856
|
+
className: `${editorClass("marker")} ${editorClass("link-url")}`,
|
|
857
|
+
children
|
|
858
|
+
}
|
|
859
|
+
);
|
|
860
|
+
}
|
|
784
861
|
let content = children;
|
|
785
862
|
if (l.bold) content = /* @__PURE__ */ jsxRuntime.jsx("strong", { children: content });
|
|
786
863
|
if (l.italic) content = /* @__PURE__ */ jsxRuntime.jsx("em", { children: content });
|
|
787
864
|
if (l.strikethrough) content = /* @__PURE__ */ jsxRuntime.jsx("del", { children: content });
|
|
788
865
|
if (l.inlineCode) content = /* @__PURE__ */ jsxRuntime.jsx("code", { children: content });
|
|
866
|
+
if (l.link) {
|
|
867
|
+
content = /* @__PURE__ */ jsxRuntime.jsx("span", { className: editorClass("link"), children: content });
|
|
868
|
+
}
|
|
789
869
|
if (l.hljs) {
|
|
790
870
|
content = /* @__PURE__ */ jsxRuntime.jsx("span", { className: l.hljs, children: content });
|
|
791
871
|
}
|
|
@@ -827,6 +907,7 @@ var HEADING_RE2 = /^#{1,6}$/;
|
|
|
827
907
|
var UNORDERED_LIST_CONTINUE_RE = /^(\s*)([-*+]) \S/;
|
|
828
908
|
var UNORDERED_LIST_EMPTY_RE = /^(\s*)([-*+]) ?$/;
|
|
829
909
|
var HEADING_LINE_RE = /^(#{1,6})\s/;
|
|
910
|
+
var PASTED_URL_RE = /^(?:https?:\/\/|www\.)\S+$/i;
|
|
830
911
|
function classifyLine(text, deco) {
|
|
831
912
|
const headingMatch = HEADING_LINE_RE.exec(text);
|
|
832
913
|
if (headingMatch) {
|
|
@@ -1161,6 +1242,14 @@ function withMarkdown(editor, featuresRef) {
|
|
|
1161
1242
|
editor.insertData = (data) => {
|
|
1162
1243
|
const text = data.getData("text/plain");
|
|
1163
1244
|
if (text) {
|
|
1245
|
+
const trimmed = text.trim();
|
|
1246
|
+
const sel = editor.selection;
|
|
1247
|
+
if (PASTED_URL_RE.test(trimmed) && sel && !slate.Range.isCollapsed(sel) && slate.Editor.string(editor, sel).length > 0) {
|
|
1248
|
+
const selectedText = slate.Editor.string(editor, sel);
|
|
1249
|
+
slate.Transforms.delete(editor);
|
|
1250
|
+
slate.Transforms.insertText(editor, `[${selectedText}](${trimmed})`);
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1164
1253
|
const nodes = deserialize(text, featuresRef.current);
|
|
1165
1254
|
slate.Transforms.insertNodes(editor, nodes);
|
|
1166
1255
|
return;
|
|
@@ -3163,6 +3252,97 @@ function CopyCodeBlock({
|
|
|
3163
3252
|
/* @__PURE__ */ jsxRuntime.jsx("pre", { ref: preRef, ...props, children })
|
|
3164
3253
|
] });
|
|
3165
3254
|
}
|
|
3255
|
+
function rehypeTrimCodeBlockTrailingNewline() {
|
|
3256
|
+
return (tree) => {
|
|
3257
|
+
unistUtilVisit.visit(tree, "element", (node, _index, parent) => {
|
|
3258
|
+
if (node.tagName !== "code") return;
|
|
3259
|
+
if (!parent || parent.type !== "element" || parent.tagName !== "pre") {
|
|
3260
|
+
return;
|
|
3261
|
+
}
|
|
3262
|
+
const spine = [node];
|
|
3263
|
+
let cursor = node;
|
|
3264
|
+
while (cursor.type === "element") {
|
|
3265
|
+
const children = cursor.children;
|
|
3266
|
+
if (!children.length) return;
|
|
3267
|
+
const last = children[children.length - 1];
|
|
3268
|
+
if (last.type !== "element" && last.type !== "text") return;
|
|
3269
|
+
cursor = last;
|
|
3270
|
+
if (cursor.type === "element") spine.push(cursor);
|
|
3271
|
+
}
|
|
3272
|
+
if (!cursor.value.endsWith("\n")) return;
|
|
3273
|
+
cursor.value = cursor.value.slice(0, -1);
|
|
3274
|
+
if (cursor.value === "") {
|
|
3275
|
+
const owner = spine[spine.length - 1];
|
|
3276
|
+
owner.children.pop();
|
|
3277
|
+
}
|
|
3278
|
+
});
|
|
3279
|
+
};
|
|
3280
|
+
}
|
|
3281
|
+
function splitTextOnNewlines(text) {
|
|
3282
|
+
if (!text.value.includes("\n")) return [text];
|
|
3283
|
+
const parts = text.value.split("\n");
|
|
3284
|
+
const result = [];
|
|
3285
|
+
for (let i = 0; i < parts.length; i++) {
|
|
3286
|
+
if (parts[i] !== "") {
|
|
3287
|
+
result.push({ type: "text", value: parts[i] });
|
|
3288
|
+
}
|
|
3289
|
+
if (i < parts.length - 1) {
|
|
3290
|
+
result.push({ type: "break" });
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
return result;
|
|
3294
|
+
}
|
|
3295
|
+
function expandParagraphChildren(children) {
|
|
3296
|
+
let changed = false;
|
|
3297
|
+
const next = [];
|
|
3298
|
+
for (const child of children) {
|
|
3299
|
+
if (child.type === "text" && child.value.includes("\n")) {
|
|
3300
|
+
next.push(...splitTextOnNewlines(child));
|
|
3301
|
+
changed = true;
|
|
3302
|
+
} else {
|
|
3303
|
+
next.push(child);
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
return changed ? next : null;
|
|
3307
|
+
}
|
|
3308
|
+
function remarkSoftBreakAsBreak() {
|
|
3309
|
+
return (tree) => {
|
|
3310
|
+
unistUtilVisit.visit(tree, "paragraph", (node) => {
|
|
3311
|
+
const expanded = expandParagraphChildren(node.children);
|
|
3312
|
+
if (expanded) node.children = expanded;
|
|
3313
|
+
});
|
|
3314
|
+
};
|
|
3315
|
+
}
|
|
3316
|
+
function remarkSoftBreakAsParagraph() {
|
|
3317
|
+
return (tree) => {
|
|
3318
|
+
unistUtilVisit.visit(tree, "paragraph", (node, index, parent) => {
|
|
3319
|
+
if (!parent || index == null) return;
|
|
3320
|
+
const expanded = expandParagraphChildren(node.children) ?? node.children.slice();
|
|
3321
|
+
const breakIndices = [];
|
|
3322
|
+
for (let i = 0; i < expanded.length; i++) {
|
|
3323
|
+
if (expanded[i].type === "break") breakIndices.push(i);
|
|
3324
|
+
}
|
|
3325
|
+
if (breakIndices.length === 0) {
|
|
3326
|
+
return;
|
|
3327
|
+
}
|
|
3328
|
+
const newParagraphs = [];
|
|
3329
|
+
let start = 0;
|
|
3330
|
+
for (const breakIdx of [...breakIndices, expanded.length]) {
|
|
3331
|
+
if (breakIdx > start) {
|
|
3332
|
+
newParagraphs.push({
|
|
3333
|
+
type: "paragraph",
|
|
3334
|
+
children: expanded.slice(start, breakIdx)
|
|
3335
|
+
});
|
|
3336
|
+
}
|
|
3337
|
+
start = breakIdx + 1;
|
|
3338
|
+
}
|
|
3339
|
+
parent.children.splice(index, 1, ...newParagraphs);
|
|
3340
|
+
return [unistUtilVisit.SKIP, index + newParagraphs.length];
|
|
3341
|
+
});
|
|
3342
|
+
};
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3345
|
+
// src/renderer/markdown-parser.ts
|
|
3166
3346
|
var MENTION_TAG_PREFIX = "inkwell-mention-";
|
|
3167
3347
|
function rehypeMentions(mentions) {
|
|
3168
3348
|
return () => (tree) => {
|
|
@@ -3225,7 +3405,14 @@ function rehypeMentions(mentions) {
|
|
|
3225
3405
|
};
|
|
3226
3406
|
}
|
|
3227
3407
|
function createProcessor2(options = {}) {
|
|
3228
|
-
const proc = unified.unified().use(remarkParse__default.default).use(remarkGfm__default.default).use(remarkNoTables).use(remarkFlattenBlockquotes)
|
|
3408
|
+
const proc = unified.unified().use(remarkParse__default.default).use(remarkGfm__default.default).use(remarkNoTables).use(remarkFlattenBlockquotes);
|
|
3409
|
+
const softBreak = options.softBreak ?? "paragraph";
|
|
3410
|
+
if (softBreak === "br") {
|
|
3411
|
+
proc.use(remarkSoftBreakAsBreak);
|
|
3412
|
+
} else if (softBreak === "paragraph") {
|
|
3413
|
+
proc.use(remarkSoftBreakAsParagraph);
|
|
3414
|
+
}
|
|
3415
|
+
proc.use(remarkRehype__default.default);
|
|
3229
3416
|
const plugins = options.rehypePlugins ?? [
|
|
3230
3417
|
[rehypeHighlight__default.default, { detect: true }]
|
|
3231
3418
|
];
|
|
@@ -3237,6 +3424,7 @@ function createProcessor2(options = {}) {
|
|
|
3237
3424
|
proc.use(plugin);
|
|
3238
3425
|
}
|
|
3239
3426
|
}
|
|
3427
|
+
proc.use(rehypeTrimCodeBlockTrailingNewline);
|
|
3240
3428
|
proc.use(rehypeSanitize__default.default, {
|
|
3241
3429
|
...rehypeSanitize.defaultSchema,
|
|
3242
3430
|
tagNames: [...rehypeSanitize.defaultSchema.tagNames ?? [], "span"],
|
|
@@ -3284,7 +3472,8 @@ function InkwellRenderer({
|
|
|
3284
3472
|
className,
|
|
3285
3473
|
components,
|
|
3286
3474
|
rehypePlugins,
|
|
3287
|
-
mentions
|
|
3475
|
+
mentions,
|
|
3476
|
+
softBreak
|
|
3288
3477
|
}) {
|
|
3289
3478
|
const mergedComponents = react.useMemo(
|
|
3290
3479
|
() => ({ pre: CopyCodeBlock, ...components }),
|
|
@@ -3294,9 +3483,10 @@ function InkwellRenderer({
|
|
|
3294
3483
|
() => parseMarkdown(content, {
|
|
3295
3484
|
components: mergedComponents,
|
|
3296
3485
|
rehypePlugins,
|
|
3297
|
-
mentions
|
|
3486
|
+
mentions,
|
|
3487
|
+
softBreak
|
|
3298
3488
|
}),
|
|
3299
|
-
[content, mergedComponents, rehypePlugins, mentions]
|
|
3489
|
+
[content, mergedComponents, rehypePlugins, mentions, softBreak]
|
|
3300
3490
|
);
|
|
3301
3491
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `inkwell-renderer ${className ?? ""}`, children: rendered });
|
|
3302
3492
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -110,6 +110,16 @@ interface InkwellEditorProps {
|
|
|
110
110
|
/** Called when submitOnEnter handles Enter. */
|
|
111
111
|
onSubmit?: (content: string) => void;
|
|
112
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* How `InkwellRenderer` handles single-newline soft breaks in source markdown.
|
|
115
|
+
* - `"paragraph"` (default): split the enclosing paragraph at the soft break,
|
|
116
|
+
* producing two `<p>` elements with normal paragraph margins.
|
|
117
|
+
* - `"br"`: emit a `<br />`. Matches GFM-style behavior (and Showdown with
|
|
118
|
+
* `simpleLineBreaks: true`).
|
|
119
|
+
* - `"preserve"`: keep as a literal `\n` text node. The browser collapses it
|
|
120
|
+
* to whitespace per CSS. Matches strict CommonMark.
|
|
121
|
+
*/
|
|
122
|
+
type InkwellSoftBreakBehavior = "preserve" | "br" | "paragraph";
|
|
113
123
|
interface InkwellRendererProps {
|
|
114
124
|
/** Markdown source content string. */
|
|
115
125
|
content: string;
|
|
@@ -121,11 +131,15 @@ interface InkwellRendererProps {
|
|
|
121
131
|
rehypePlugins?: RehypePluginConfig[];
|
|
122
132
|
/** Mention patterns to expand in rendered text. */
|
|
123
133
|
mentions?: MentionRenderer[];
|
|
134
|
+
/** How to render single-newline soft breaks. Defaults to `"paragraph"`. */
|
|
135
|
+
softBreak?: InkwellSoftBreakBehavior;
|
|
124
136
|
}
|
|
125
137
|
interface ParseMarkdownOptions {
|
|
126
138
|
components?: InkwellComponents;
|
|
127
139
|
rehypePlugins?: RehypePluginConfig[];
|
|
128
140
|
mentions?: MentionRenderer[];
|
|
141
|
+
/** How to render single-newline soft breaks. Defaults to `"paragraph"`. */
|
|
142
|
+
softBreak?: InkwellSoftBreakBehavior;
|
|
129
143
|
}
|
|
130
144
|
interface MentionRenderer {
|
|
131
145
|
/** Regular expression applied to text-node content. */
|
|
@@ -482,11 +496,11 @@ declare function createSnippetsPlugin({ snippets, name, trigger, }: SnippetsPlug
|
|
|
482
496
|
*/
|
|
483
497
|
declare function htmlToMarkdown(html: string): string;
|
|
484
498
|
|
|
485
|
-
declare function InkwellRenderer({ content, className, components, rehypePlugins, mentions, }: InkwellRendererProps): ReactNode;
|
|
499
|
+
declare function InkwellRenderer({ content, className, components, rehypePlugins, mentions, softBreak, }: InkwellRendererProps): ReactNode;
|
|
486
500
|
|
|
487
501
|
/**
|
|
488
502
|
* Parse a markdown string into React elements synchronously
|
|
489
503
|
*/
|
|
490
504
|
declare function parseMarkdown(content: string, options?: ParseMarkdownOptions): ReactNode;
|
|
491
505
|
|
|
492
|
-
export { type Attachment, type AttachmentUploadResult, type AttachmentsHandle, type AttachmentsPluginOptions, type BubbleMenuItem, type BubbleMenuItemProps, type BubbleMenuOptions, type CompletionsPluginOptions, type EmojiItem, type EmojiPluginOptions, type InkwellComponents, InkwellEditor, type InkwellEditorClassNames, type InkwellEditorFocusOptions, type InkwellEditorHandle, type InkwellEditorProps, type InkwellEditorState, type InkwellEditorStyles, type InkwellFeatures, type InkwellPlugin, type InkwellPluginActivation, type InkwellPluginEditor, type InkwellPluginPlaceholder, InkwellRenderer, type InkwellRendererProps, type MentionItem, type MentionRenderer, type MentionsPluginOptions, type ParseMarkdownOptions, type PluginInsertDataContext, type PluginKeyDownContext, type PluginRenderProps, type RehypePluginConfig, type SlashCommandArg, type SlashCommandChoice, type SlashCommandExecution, type SlashCommandItem, type SlashCommandsPluginOptions, type Snippet, type SnippetsPluginOptions, type SubscribeForwardedKey, createAttachmentsPlugin, createBubbleMenuPlugin, createCompletionsPlugin, createEmojiPlugin, createMentionsPlugin, createSlashCommandsPlugin, createSnippetsPlugin, defaultBubbleMenuItems, defaultEmojis, htmlToMarkdown, parseMarkdown };
|
|
506
|
+
export { type Attachment, type AttachmentUploadResult, type AttachmentsHandle, type AttachmentsPluginOptions, type BubbleMenuItem, type BubbleMenuItemProps, type BubbleMenuOptions, type CompletionsPluginOptions, type EmojiItem, type EmojiPluginOptions, type InkwellComponents, InkwellEditor, type InkwellEditorClassNames, type InkwellEditorFocusOptions, type InkwellEditorHandle, type InkwellEditorProps, type InkwellEditorState, type InkwellEditorStyles, type InkwellFeatures, type InkwellPlugin, type InkwellPluginActivation, type InkwellPluginEditor, type InkwellPluginPlaceholder, InkwellRenderer, type InkwellRendererProps, type InkwellSoftBreakBehavior, type MentionItem, type MentionRenderer, type MentionsPluginOptions, type ParseMarkdownOptions, type PluginInsertDataContext, type PluginKeyDownContext, type PluginRenderProps, type RehypePluginConfig, type SlashCommandArg, type SlashCommandChoice, type SlashCommandExecution, type SlashCommandItem, type SlashCommandsPluginOptions, type Snippet, type SnippetsPluginOptions, type SubscribeForwardedKey, createAttachmentsPlugin, createBubbleMenuPlugin, createCompletionsPlugin, createEmojiPlugin, createMentionsPlugin, createSlashCommandsPlugin, createSnippetsPlugin, defaultBubbleMenuItems, defaultEmojis, htmlToMarkdown, parseMarkdown };
|
package/dist/index.d.ts
CHANGED
|
@@ -110,6 +110,16 @@ interface InkwellEditorProps {
|
|
|
110
110
|
/** Called when submitOnEnter handles Enter. */
|
|
111
111
|
onSubmit?: (content: string) => void;
|
|
112
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* How `InkwellRenderer` handles single-newline soft breaks in source markdown.
|
|
115
|
+
* - `"paragraph"` (default): split the enclosing paragraph at the soft break,
|
|
116
|
+
* producing two `<p>` elements with normal paragraph margins.
|
|
117
|
+
* - `"br"`: emit a `<br />`. Matches GFM-style behavior (and Showdown with
|
|
118
|
+
* `simpleLineBreaks: true`).
|
|
119
|
+
* - `"preserve"`: keep as a literal `\n` text node. The browser collapses it
|
|
120
|
+
* to whitespace per CSS. Matches strict CommonMark.
|
|
121
|
+
*/
|
|
122
|
+
type InkwellSoftBreakBehavior = "preserve" | "br" | "paragraph";
|
|
113
123
|
interface InkwellRendererProps {
|
|
114
124
|
/** Markdown source content string. */
|
|
115
125
|
content: string;
|
|
@@ -121,11 +131,15 @@ interface InkwellRendererProps {
|
|
|
121
131
|
rehypePlugins?: RehypePluginConfig[];
|
|
122
132
|
/** Mention patterns to expand in rendered text. */
|
|
123
133
|
mentions?: MentionRenderer[];
|
|
134
|
+
/** How to render single-newline soft breaks. Defaults to `"paragraph"`. */
|
|
135
|
+
softBreak?: InkwellSoftBreakBehavior;
|
|
124
136
|
}
|
|
125
137
|
interface ParseMarkdownOptions {
|
|
126
138
|
components?: InkwellComponents;
|
|
127
139
|
rehypePlugins?: RehypePluginConfig[];
|
|
128
140
|
mentions?: MentionRenderer[];
|
|
141
|
+
/** How to render single-newline soft breaks. Defaults to `"paragraph"`. */
|
|
142
|
+
softBreak?: InkwellSoftBreakBehavior;
|
|
129
143
|
}
|
|
130
144
|
interface MentionRenderer {
|
|
131
145
|
/** Regular expression applied to text-node content. */
|
|
@@ -482,11 +496,11 @@ declare function createSnippetsPlugin({ snippets, name, trigger, }: SnippetsPlug
|
|
|
482
496
|
*/
|
|
483
497
|
declare function htmlToMarkdown(html: string): string;
|
|
484
498
|
|
|
485
|
-
declare function InkwellRenderer({ content, className, components, rehypePlugins, mentions, }: InkwellRendererProps): ReactNode;
|
|
499
|
+
declare function InkwellRenderer({ content, className, components, rehypePlugins, mentions, softBreak, }: InkwellRendererProps): ReactNode;
|
|
486
500
|
|
|
487
501
|
/**
|
|
488
502
|
* Parse a markdown string into React elements synchronously
|
|
489
503
|
*/
|
|
490
504
|
declare function parseMarkdown(content: string, options?: ParseMarkdownOptions): ReactNode;
|
|
491
505
|
|
|
492
|
-
export { type Attachment, type AttachmentUploadResult, type AttachmentsHandle, type AttachmentsPluginOptions, type BubbleMenuItem, type BubbleMenuItemProps, type BubbleMenuOptions, type CompletionsPluginOptions, type EmojiItem, type EmojiPluginOptions, type InkwellComponents, InkwellEditor, type InkwellEditorClassNames, type InkwellEditorFocusOptions, type InkwellEditorHandle, type InkwellEditorProps, type InkwellEditorState, type InkwellEditorStyles, type InkwellFeatures, type InkwellPlugin, type InkwellPluginActivation, type InkwellPluginEditor, type InkwellPluginPlaceholder, InkwellRenderer, type InkwellRendererProps, type MentionItem, type MentionRenderer, type MentionsPluginOptions, type ParseMarkdownOptions, type PluginInsertDataContext, type PluginKeyDownContext, type PluginRenderProps, type RehypePluginConfig, type SlashCommandArg, type SlashCommandChoice, type SlashCommandExecution, type SlashCommandItem, type SlashCommandsPluginOptions, type Snippet, type SnippetsPluginOptions, type SubscribeForwardedKey, createAttachmentsPlugin, createBubbleMenuPlugin, createCompletionsPlugin, createEmojiPlugin, createMentionsPlugin, createSlashCommandsPlugin, createSnippetsPlugin, defaultBubbleMenuItems, defaultEmojis, htmlToMarkdown, parseMarkdown };
|
|
506
|
+
export { type Attachment, type AttachmentUploadResult, type AttachmentsHandle, type AttachmentsPluginOptions, type BubbleMenuItem, type BubbleMenuItemProps, type BubbleMenuOptions, type CompletionsPluginOptions, type EmojiItem, type EmojiPluginOptions, type InkwellComponents, InkwellEditor, type InkwellEditorClassNames, type InkwellEditorFocusOptions, type InkwellEditorHandle, type InkwellEditorProps, type InkwellEditorState, type InkwellEditorStyles, type InkwellFeatures, type InkwellPlugin, type InkwellPluginActivation, type InkwellPluginEditor, type InkwellPluginPlaceholder, InkwellRenderer, type InkwellRendererProps, type InkwellSoftBreakBehavior, type MentionItem, type MentionRenderer, type MentionsPluginOptions, type ParseMarkdownOptions, type PluginInsertDataContext, type PluginKeyDownContext, type PluginRenderProps, type RehypePluginConfig, type SlashCommandArg, type SlashCommandChoice, type SlashCommandExecution, type SlashCommandItem, type SlashCommandsPluginOptions, type Snippet, type SnippetsPluginOptions, type SubscribeForwardedKey, createAttachmentsPlugin, createBubbleMenuPlugin, createCompletionsPlugin, createEmojiPlugin, createMentionsPlugin, createSlashCommandsPlugin, createSnippetsPlugin, defaultBubbleMenuItems, defaultEmojis, htmlToMarkdown, parseMarkdown };
|
package/dist/index.js
CHANGED
|
@@ -353,6 +353,73 @@ function computeInlineDecorations(entry) {
|
|
|
353
353
|
strikeMarker: true
|
|
354
354
|
});
|
|
355
355
|
}
|
|
356
|
+
const linkRanges = [];
|
|
357
|
+
const linkRegex = /(?<!!)\[([^\]\n]+)\]\(([^)\s]+)\)/g;
|
|
358
|
+
while ((match = linkRegex.exec(text)) !== null) {
|
|
359
|
+
if (isInCode(match.index)) continue;
|
|
360
|
+
const start = match.index;
|
|
361
|
+
const end = start + match[0].length;
|
|
362
|
+
const labelLen = match[1].length;
|
|
363
|
+
const urlLen = match[2].length;
|
|
364
|
+
const openBracket = start;
|
|
365
|
+
const labelStart = start + 1;
|
|
366
|
+
const labelEnd = labelStart + labelLen;
|
|
367
|
+
const closeBracket = labelEnd;
|
|
368
|
+
const openParen = closeBracket + 1;
|
|
369
|
+
const urlStart = openParen + 1;
|
|
370
|
+
const urlEnd = urlStart + urlLen;
|
|
371
|
+
const closeParen = urlEnd;
|
|
372
|
+
linkRanges.push({ start, end });
|
|
373
|
+
ranges.push({
|
|
374
|
+
anchor: { path: [...path, 0], offset: openBracket },
|
|
375
|
+
focus: { path: [...path, 0], offset: openBracket + 1 },
|
|
376
|
+
linkMarker: true
|
|
377
|
+
});
|
|
378
|
+
ranges.push({
|
|
379
|
+
anchor: { path: [...path, 0], offset: labelStart },
|
|
380
|
+
focus: { path: [...path, 0], offset: labelEnd },
|
|
381
|
+
link: true
|
|
382
|
+
});
|
|
383
|
+
ranges.push({
|
|
384
|
+
anchor: { path: [...path, 0], offset: closeBracket },
|
|
385
|
+
focus: { path: [...path, 0], offset: closeBracket + 1 },
|
|
386
|
+
linkMarker: true
|
|
387
|
+
});
|
|
388
|
+
ranges.push({
|
|
389
|
+
anchor: { path: [...path, 0], offset: openParen },
|
|
390
|
+
focus: { path: [...path, 0], offset: openParen + 1 },
|
|
391
|
+
linkMarker: true
|
|
392
|
+
});
|
|
393
|
+
ranges.push({
|
|
394
|
+
anchor: { path: [...path, 0], offset: urlStart },
|
|
395
|
+
focus: { path: [...path, 0], offset: urlEnd },
|
|
396
|
+
linkUrl: true
|
|
397
|
+
});
|
|
398
|
+
ranges.push({
|
|
399
|
+
anchor: { path: [...path, 0], offset: closeParen },
|
|
400
|
+
focus: { path: [...path, 0], offset: closeParen + 1 },
|
|
401
|
+
linkMarker: true
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
const isInLink = (offset) => linkRanges.some((r) => offset >= r.start && offset < r.end);
|
|
405
|
+
const urlRegex = /(?:https?:\/\/|www\.)[^\s<>()[\]]+/g;
|
|
406
|
+
while ((match = urlRegex.exec(text)) !== null) {
|
|
407
|
+
if (isInCode(match.index)) continue;
|
|
408
|
+
if (isInLink(match.index)) continue;
|
|
409
|
+
let matched = match[0];
|
|
410
|
+
let start = match.index;
|
|
411
|
+
let end = start + matched.length;
|
|
412
|
+
while (matched.length > 0 && /[.,;:!?]/.test(matched[matched.length - 1])) {
|
|
413
|
+
matched = matched.slice(0, -1);
|
|
414
|
+
end--;
|
|
415
|
+
}
|
|
416
|
+
if (matched.length === 0) continue;
|
|
417
|
+
ranges.push({
|
|
418
|
+
anchor: { path: [...path, 0], offset: start },
|
|
419
|
+
focus: { path: [...path, 0], offset: end },
|
|
420
|
+
link: true
|
|
421
|
+
});
|
|
422
|
+
}
|
|
356
423
|
return ranges;
|
|
357
424
|
}
|
|
358
425
|
function computeFenceDecorations(entry) {
|
|
@@ -760,17 +827,30 @@ function ImageElement({
|
|
|
760
827
|
}
|
|
761
828
|
function RenderLeaf({ attributes, children, leaf }) {
|
|
762
829
|
const l = leaf;
|
|
763
|
-
if (l.boldMarker || l.italicMarker || l.strikeMarker) {
|
|
830
|
+
if (l.boldMarker || l.italicMarker || l.strikeMarker || l.linkMarker) {
|
|
764
831
|
return /* @__PURE__ */ jsx("span", { ...attributes, className: editorClass("marker"), children });
|
|
765
832
|
}
|
|
766
833
|
if (l.codeMarker) {
|
|
767
834
|
return /* @__PURE__ */ jsx("span", { ...attributes, className: editorClass("backtick"), children });
|
|
768
835
|
}
|
|
836
|
+
if (l.linkUrl) {
|
|
837
|
+
return /* @__PURE__ */ jsx(
|
|
838
|
+
"span",
|
|
839
|
+
{
|
|
840
|
+
...attributes,
|
|
841
|
+
className: `${editorClass("marker")} ${editorClass("link-url")}`,
|
|
842
|
+
children
|
|
843
|
+
}
|
|
844
|
+
);
|
|
845
|
+
}
|
|
769
846
|
let content = children;
|
|
770
847
|
if (l.bold) content = /* @__PURE__ */ jsx("strong", { children: content });
|
|
771
848
|
if (l.italic) content = /* @__PURE__ */ jsx("em", { children: content });
|
|
772
849
|
if (l.strikethrough) content = /* @__PURE__ */ jsx("del", { children: content });
|
|
773
850
|
if (l.inlineCode) content = /* @__PURE__ */ jsx("code", { children: content });
|
|
851
|
+
if (l.link) {
|
|
852
|
+
content = /* @__PURE__ */ jsx("span", { className: editorClass("link"), children: content });
|
|
853
|
+
}
|
|
774
854
|
if (l.hljs) {
|
|
775
855
|
content = /* @__PURE__ */ jsx("span", { className: l.hljs, children: content });
|
|
776
856
|
}
|
|
@@ -812,6 +892,7 @@ var HEADING_RE2 = /^#{1,6}$/;
|
|
|
812
892
|
var UNORDERED_LIST_CONTINUE_RE = /^(\s*)([-*+]) \S/;
|
|
813
893
|
var UNORDERED_LIST_EMPTY_RE = /^(\s*)([-*+]) ?$/;
|
|
814
894
|
var HEADING_LINE_RE = /^(#{1,6})\s/;
|
|
895
|
+
var PASTED_URL_RE = /^(?:https?:\/\/|www\.)\S+$/i;
|
|
815
896
|
function classifyLine(text, deco) {
|
|
816
897
|
const headingMatch = HEADING_LINE_RE.exec(text);
|
|
817
898
|
if (headingMatch) {
|
|
@@ -1146,6 +1227,14 @@ function withMarkdown(editor, featuresRef) {
|
|
|
1146
1227
|
editor.insertData = (data) => {
|
|
1147
1228
|
const text = data.getData("text/plain");
|
|
1148
1229
|
if (text) {
|
|
1230
|
+
const trimmed = text.trim();
|
|
1231
|
+
const sel = editor.selection;
|
|
1232
|
+
if (PASTED_URL_RE.test(trimmed) && sel && !Range.isCollapsed(sel) && Editor.string(editor, sel).length > 0) {
|
|
1233
|
+
const selectedText = Editor.string(editor, sel);
|
|
1234
|
+
Transforms.delete(editor);
|
|
1235
|
+
Transforms.insertText(editor, `[${selectedText}](${trimmed})`);
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1149
1238
|
const nodes = deserialize(text, featuresRef.current);
|
|
1150
1239
|
Transforms.insertNodes(editor, nodes);
|
|
1151
1240
|
return;
|
|
@@ -3148,6 +3237,97 @@ function CopyCodeBlock({
|
|
|
3148
3237
|
/* @__PURE__ */ jsx("pre", { ref: preRef, ...props, children })
|
|
3149
3238
|
] });
|
|
3150
3239
|
}
|
|
3240
|
+
function rehypeTrimCodeBlockTrailingNewline() {
|
|
3241
|
+
return (tree) => {
|
|
3242
|
+
visit(tree, "element", (node, _index, parent) => {
|
|
3243
|
+
if (node.tagName !== "code") return;
|
|
3244
|
+
if (!parent || parent.type !== "element" || parent.tagName !== "pre") {
|
|
3245
|
+
return;
|
|
3246
|
+
}
|
|
3247
|
+
const spine = [node];
|
|
3248
|
+
let cursor = node;
|
|
3249
|
+
while (cursor.type === "element") {
|
|
3250
|
+
const children = cursor.children;
|
|
3251
|
+
if (!children.length) return;
|
|
3252
|
+
const last = children[children.length - 1];
|
|
3253
|
+
if (last.type !== "element" && last.type !== "text") return;
|
|
3254
|
+
cursor = last;
|
|
3255
|
+
if (cursor.type === "element") spine.push(cursor);
|
|
3256
|
+
}
|
|
3257
|
+
if (!cursor.value.endsWith("\n")) return;
|
|
3258
|
+
cursor.value = cursor.value.slice(0, -1);
|
|
3259
|
+
if (cursor.value === "") {
|
|
3260
|
+
const owner = spine[spine.length - 1];
|
|
3261
|
+
owner.children.pop();
|
|
3262
|
+
}
|
|
3263
|
+
});
|
|
3264
|
+
};
|
|
3265
|
+
}
|
|
3266
|
+
function splitTextOnNewlines(text) {
|
|
3267
|
+
if (!text.value.includes("\n")) return [text];
|
|
3268
|
+
const parts = text.value.split("\n");
|
|
3269
|
+
const result = [];
|
|
3270
|
+
for (let i = 0; i < parts.length; i++) {
|
|
3271
|
+
if (parts[i] !== "") {
|
|
3272
|
+
result.push({ type: "text", value: parts[i] });
|
|
3273
|
+
}
|
|
3274
|
+
if (i < parts.length - 1) {
|
|
3275
|
+
result.push({ type: "break" });
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
return result;
|
|
3279
|
+
}
|
|
3280
|
+
function expandParagraphChildren(children) {
|
|
3281
|
+
let changed = false;
|
|
3282
|
+
const next = [];
|
|
3283
|
+
for (const child of children) {
|
|
3284
|
+
if (child.type === "text" && child.value.includes("\n")) {
|
|
3285
|
+
next.push(...splitTextOnNewlines(child));
|
|
3286
|
+
changed = true;
|
|
3287
|
+
} else {
|
|
3288
|
+
next.push(child);
|
|
3289
|
+
}
|
|
3290
|
+
}
|
|
3291
|
+
return changed ? next : null;
|
|
3292
|
+
}
|
|
3293
|
+
function remarkSoftBreakAsBreak() {
|
|
3294
|
+
return (tree) => {
|
|
3295
|
+
visit(tree, "paragraph", (node) => {
|
|
3296
|
+
const expanded = expandParagraphChildren(node.children);
|
|
3297
|
+
if (expanded) node.children = expanded;
|
|
3298
|
+
});
|
|
3299
|
+
};
|
|
3300
|
+
}
|
|
3301
|
+
function remarkSoftBreakAsParagraph() {
|
|
3302
|
+
return (tree) => {
|
|
3303
|
+
visit(tree, "paragraph", (node, index, parent) => {
|
|
3304
|
+
if (!parent || index == null) return;
|
|
3305
|
+
const expanded = expandParagraphChildren(node.children) ?? node.children.slice();
|
|
3306
|
+
const breakIndices = [];
|
|
3307
|
+
for (let i = 0; i < expanded.length; i++) {
|
|
3308
|
+
if (expanded[i].type === "break") breakIndices.push(i);
|
|
3309
|
+
}
|
|
3310
|
+
if (breakIndices.length === 0) {
|
|
3311
|
+
return;
|
|
3312
|
+
}
|
|
3313
|
+
const newParagraphs = [];
|
|
3314
|
+
let start = 0;
|
|
3315
|
+
for (const breakIdx of [...breakIndices, expanded.length]) {
|
|
3316
|
+
if (breakIdx > start) {
|
|
3317
|
+
newParagraphs.push({
|
|
3318
|
+
type: "paragraph",
|
|
3319
|
+
children: expanded.slice(start, breakIdx)
|
|
3320
|
+
});
|
|
3321
|
+
}
|
|
3322
|
+
start = breakIdx + 1;
|
|
3323
|
+
}
|
|
3324
|
+
parent.children.splice(index, 1, ...newParagraphs);
|
|
3325
|
+
return [SKIP, index + newParagraphs.length];
|
|
3326
|
+
});
|
|
3327
|
+
};
|
|
3328
|
+
}
|
|
3329
|
+
|
|
3330
|
+
// src/renderer/markdown-parser.ts
|
|
3151
3331
|
var MENTION_TAG_PREFIX = "inkwell-mention-";
|
|
3152
3332
|
function rehypeMentions(mentions) {
|
|
3153
3333
|
return () => (tree) => {
|
|
@@ -3210,7 +3390,14 @@ function rehypeMentions(mentions) {
|
|
|
3210
3390
|
};
|
|
3211
3391
|
}
|
|
3212
3392
|
function createProcessor2(options = {}) {
|
|
3213
|
-
const proc = unified().use(remarkParse).use(remarkGfm).use(remarkNoTables).use(remarkFlattenBlockquotes)
|
|
3393
|
+
const proc = unified().use(remarkParse).use(remarkGfm).use(remarkNoTables).use(remarkFlattenBlockquotes);
|
|
3394
|
+
const softBreak = options.softBreak ?? "paragraph";
|
|
3395
|
+
if (softBreak === "br") {
|
|
3396
|
+
proc.use(remarkSoftBreakAsBreak);
|
|
3397
|
+
} else if (softBreak === "paragraph") {
|
|
3398
|
+
proc.use(remarkSoftBreakAsParagraph);
|
|
3399
|
+
}
|
|
3400
|
+
proc.use(remarkRehype);
|
|
3214
3401
|
const plugins = options.rehypePlugins ?? [
|
|
3215
3402
|
[rehypeHighlight, { detect: true }]
|
|
3216
3403
|
];
|
|
@@ -3222,6 +3409,7 @@ function createProcessor2(options = {}) {
|
|
|
3222
3409
|
proc.use(plugin);
|
|
3223
3410
|
}
|
|
3224
3411
|
}
|
|
3412
|
+
proc.use(rehypeTrimCodeBlockTrailingNewline);
|
|
3225
3413
|
proc.use(rehypeSanitize, {
|
|
3226
3414
|
...defaultSchema,
|
|
3227
3415
|
tagNames: [...defaultSchema.tagNames ?? [], "span"],
|
|
@@ -3269,7 +3457,8 @@ function InkwellRenderer({
|
|
|
3269
3457
|
className,
|
|
3270
3458
|
components,
|
|
3271
3459
|
rehypePlugins,
|
|
3272
|
-
mentions
|
|
3460
|
+
mentions,
|
|
3461
|
+
softBreak
|
|
3273
3462
|
}) {
|
|
3274
3463
|
const mergedComponents = useMemo(
|
|
3275
3464
|
() => ({ pre: CopyCodeBlock, ...components }),
|
|
@@ -3279,9 +3468,10 @@ function InkwellRenderer({
|
|
|
3279
3468
|
() => parseMarkdown(content, {
|
|
3280
3469
|
components: mergedComponents,
|
|
3281
3470
|
rehypePlugins,
|
|
3282
|
-
mentions
|
|
3471
|
+
mentions,
|
|
3472
|
+
softBreak
|
|
3283
3473
|
}),
|
|
3284
|
-
[content, mergedComponents, rehypePlugins, mentions]
|
|
3474
|
+
[content, mergedComponents, rehypePlugins, mentions, softBreak]
|
|
3285
3475
|
);
|
|
3286
3476
|
return /* @__PURE__ */ jsx("div", { className: `inkwell-renderer ${className ?? ""}`, children: rendered });
|
|
3287
3477
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@railway/inkwell",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Inkwell is a Markdown editor and renderer for React with an extensible plugin system.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@testing-library/jest-dom": "^6.9.1",
|
|
64
64
|
"@testing-library/react": "^16.3.2",
|
|
65
|
+
"@types/hast": "^3.0.4",
|
|
65
66
|
"@types/mdast": "^4.0.4",
|
|
66
67
|
"@types/node": "^24.0.0",
|
|
67
68
|
"@types/react": "^19.0.0",
|
package/src/styles.css
CHANGED
|
@@ -62,6 +62,34 @@
|
|
|
62
62
|
"JetBrains Mono", "Fira Code", ui-monospace, SFMono-Regular, Menlo,
|
|
63
63
|
Consolas, monospace;
|
|
64
64
|
--inkwell-radius: 6px;
|
|
65
|
+
|
|
66
|
+
/* Typography & spacing. Defined once and consumed by both
|
|
67
|
+
`.inkwell-editor` and `.inkwell-renderer` so the two surfaces stay
|
|
68
|
+
WYSIWYG — what you type matches what renders. Override any token
|
|
69
|
+
on either surface to retune both (or use a per-surface selector
|
|
70
|
+
to retune just one). For chat-composer or compact embeds, set
|
|
71
|
+
`--inkwell-space-paragraph: 0` on the editor surface. */
|
|
72
|
+
--inkwell-font-size: 0.95rem;
|
|
73
|
+
--inkwell-line-height: 1.6;
|
|
74
|
+
--inkwell-heading-weight: 600;
|
|
75
|
+
--inkwell-heading-line-height: 1.3;
|
|
76
|
+
--inkwell-h1-size: 1.75em;
|
|
77
|
+
--inkwell-h2-size: 1.4em;
|
|
78
|
+
--inkwell-h3-size: 1.2em;
|
|
79
|
+
--inkwell-h4-size: 1em;
|
|
80
|
+
--inkwell-h5-size: 0.9em;
|
|
81
|
+
--inkwell-h6-size: 0.8em;
|
|
82
|
+
--inkwell-code-font-size: 0.85em;
|
|
83
|
+
|
|
84
|
+
--inkwell-space-paragraph: 0.5em;
|
|
85
|
+
--inkwell-space-heading: 0.75em;
|
|
86
|
+
--inkwell-space-blockquote: 1em;
|
|
87
|
+
--inkwell-space-list: 1em;
|
|
88
|
+
--inkwell-space-list-item: 0.25em;
|
|
89
|
+
--inkwell-list-indent: 1.5em;
|
|
90
|
+
--inkwell-space-code-block: 1em;
|
|
91
|
+
--inkwell-space-image: 1em;
|
|
92
|
+
--inkwell-space-hr: 2em;
|
|
65
93
|
}
|
|
66
94
|
|
|
67
95
|
@media (prefers-color-scheme: dark) {
|
|
@@ -118,8 +146,8 @@
|
|
|
118
146
|
border-radius: var(--inkwell-radius);
|
|
119
147
|
background: var(--inkwell-bg);
|
|
120
148
|
color: var(--inkwell-text);
|
|
121
|
-
line-height:
|
|
122
|
-
font-size:
|
|
149
|
+
line-height: var(--inkwell-line-height);
|
|
150
|
+
font-size: var(--inkwell-font-size);
|
|
123
151
|
transition: border-color 0.15s ease;
|
|
124
152
|
}
|
|
125
153
|
:where(.inkwell-editor:focus-within) {
|
|
@@ -127,8 +155,20 @@
|
|
|
127
155
|
}
|
|
128
156
|
|
|
129
157
|
/* `position: relative` on paragraphs is structural — Slate decorations and
|
|
130
|
-
inline children position against it.
|
|
131
|
-
|
|
158
|
+
inline children position against it.
|
|
159
|
+
|
|
160
|
+
Margin stays at `0` here even though the renderer's paragraphs use
|
|
161
|
+
`--inkwell-space-paragraph`. Reason: the editor's content model emits
|
|
162
|
+
one `<p>` per source line, so a blank line in Markdown becomes an
|
|
163
|
+
empty `<p>` node between two paragraphs (a cursor target, kept for
|
|
164
|
+
round-trip fidelity). With a non-zero paragraph margin, those empty
|
|
165
|
+
paragraphs add their own top/bottom margin on top of the real
|
|
166
|
+
paragraphs' margins — visually multiplying the gap and breaking the
|
|
167
|
+
WYSIWYG promise in the other direction (editor looks more airy than
|
|
168
|
+
the renderer). Until the empty-paragraph encoding is reworked, the
|
|
169
|
+
editor opts out of the shared paragraph-spacing token. Consumers
|
|
170
|
+
who want non-zero spacing in the editor can set the margin
|
|
171
|
+
themselves with a higher-specificity rule. */
|
|
132
172
|
.inkwell-editor p {
|
|
133
173
|
position: relative;
|
|
134
174
|
}
|
|
@@ -152,42 +192,61 @@
|
|
|
152
192
|
padding: 0.1em 0.35em;
|
|
153
193
|
border-radius: 4px;
|
|
154
194
|
font-family: var(--inkwell-font-mono);
|
|
155
|
-
font-size:
|
|
195
|
+
font-size: var(--inkwell-code-font-size);
|
|
196
|
+
}
|
|
197
|
+
/* Visible link text — mirrors `.inkwell-renderer a` so the editor stays
|
|
198
|
+
WYSIWYG. Applies to both the label inside `[text](url)` and the entire
|
|
199
|
+
text of a bare URL autolink. */
|
|
200
|
+
:where(.inkwell-editor-link) {
|
|
201
|
+
color: var(--inkwell-accent);
|
|
202
|
+
text-decoration: underline;
|
|
203
|
+
text-underline-offset: 2px;
|
|
204
|
+
}
|
|
205
|
+
/* URL token inside `(...)` of a markdown link. Inherits the dim color
|
|
206
|
+
from the `.inkwell-editor-marker` class it ships alongside; this rule
|
|
207
|
+
exists so consumers have a stable hook to restyle the URL separately
|
|
208
|
+
from generic markers (different color, hover state, etc.) without
|
|
209
|
+
touching every dimmed bracket / asterisk in the editor. Empty by
|
|
210
|
+
design — overriding `text-decoration: underline` etc. is a consumer
|
|
211
|
+
call. */
|
|
212
|
+
:where(.inkwell-editor-link-url) {
|
|
213
|
+
text-decoration: none;
|
|
156
214
|
}
|
|
157
215
|
|
|
158
216
|
:where(.inkwell-editor-blockquote) {
|
|
159
217
|
border-left: 3px solid var(--inkwell-border-strong);
|
|
160
218
|
padding-left: 0.85em;
|
|
161
|
-
margin:
|
|
219
|
+
margin: var(--inkwell-space-blockquote) 0;
|
|
162
220
|
color: var(--inkwell-text-muted);
|
|
163
221
|
}
|
|
164
222
|
|
|
165
223
|
:where(.inkwell-editor-heading) {
|
|
166
|
-
font-weight:
|
|
167
|
-
line-height:
|
|
224
|
+
font-weight: var(--inkwell-heading-weight);
|
|
225
|
+
line-height: var(--inkwell-heading-line-height);
|
|
226
|
+
margin: var(--inkwell-space-heading) 0;
|
|
168
227
|
color: var(--inkwell-text);
|
|
169
228
|
}
|
|
170
229
|
:where(.inkwell-editor-heading-1) {
|
|
171
|
-
font-size:
|
|
230
|
+
font-size: var(--inkwell-h1-size);
|
|
172
231
|
}
|
|
173
232
|
:where(.inkwell-editor-heading-2) {
|
|
174
|
-
font-size:
|
|
233
|
+
font-size: var(--inkwell-h2-size);
|
|
175
234
|
}
|
|
176
235
|
:where(.inkwell-editor-heading-3) {
|
|
177
|
-
font-size:
|
|
236
|
+
font-size: var(--inkwell-h3-size);
|
|
178
237
|
}
|
|
179
238
|
:where(.inkwell-editor-heading-4) {
|
|
180
|
-
font-size:
|
|
239
|
+
font-size: var(--inkwell-h4-size);
|
|
181
240
|
}
|
|
182
241
|
:where(.inkwell-editor-heading-5) {
|
|
183
|
-
font-size:
|
|
242
|
+
font-size: var(--inkwell-h5-size);
|
|
184
243
|
}
|
|
185
244
|
:where(.inkwell-editor-heading-6) {
|
|
186
|
-
font-size:
|
|
245
|
+
font-size: var(--inkwell-h6-size);
|
|
187
246
|
}
|
|
188
247
|
|
|
189
248
|
:where(.inkwell-editor-image) {
|
|
190
|
-
margin:
|
|
249
|
+
margin: var(--inkwell-space-image) 0;
|
|
191
250
|
border-radius: var(--inkwell-radius);
|
|
192
251
|
overflow: hidden;
|
|
193
252
|
border: 1px solid transparent;
|
|
@@ -257,7 +316,7 @@
|
|
|
257
316
|
:where(.inkwell-editor .inkwell-editor-code-fence),
|
|
258
317
|
:where(.inkwell-renderer pre code) {
|
|
259
318
|
font-family: var(--inkwell-font-mono);
|
|
260
|
-
font-size:
|
|
319
|
+
font-size: var(--inkwell-code-font-size);
|
|
261
320
|
line-height: 1.55;
|
|
262
321
|
}
|
|
263
322
|
/* Wrapping behavior for code lines stays structural — Slate emits one
|
|
@@ -442,40 +501,61 @@
|
|
|
442
501
|
`!important`. */
|
|
443
502
|
:where(.inkwell-renderer) {
|
|
444
503
|
color: var(--inkwell-text);
|
|
445
|
-
line-height:
|
|
446
|
-
font-size:
|
|
504
|
+
line-height: var(--inkwell-line-height);
|
|
505
|
+
font-size: var(--inkwell-font-size);
|
|
447
506
|
}
|
|
448
507
|
:where(.inkwell-renderer :first-child) {
|
|
449
508
|
margin-top: 0;
|
|
450
509
|
}
|
|
451
510
|
:where(.inkwell-renderer h1) {
|
|
452
|
-
font-size:
|
|
453
|
-
font-weight:
|
|
454
|
-
|
|
511
|
+
font-size: var(--inkwell-h1-size);
|
|
512
|
+
font-weight: var(--inkwell-heading-weight);
|
|
513
|
+
line-height: var(--inkwell-heading-line-height);
|
|
514
|
+
margin: var(--inkwell-space-heading) 0;
|
|
455
515
|
}
|
|
456
516
|
:where(.inkwell-renderer h2) {
|
|
457
|
-
font-size:
|
|
458
|
-
font-weight:
|
|
459
|
-
|
|
517
|
+
font-size: var(--inkwell-h2-size);
|
|
518
|
+
font-weight: var(--inkwell-heading-weight);
|
|
519
|
+
line-height: var(--inkwell-heading-line-height);
|
|
520
|
+
margin: var(--inkwell-space-heading) 0;
|
|
460
521
|
}
|
|
461
522
|
:where(.inkwell-renderer h3) {
|
|
462
|
-
font-size:
|
|
463
|
-
font-weight:
|
|
464
|
-
|
|
523
|
+
font-size: var(--inkwell-h3-size);
|
|
524
|
+
font-weight: var(--inkwell-heading-weight);
|
|
525
|
+
line-height: var(--inkwell-heading-line-height);
|
|
526
|
+
margin: var(--inkwell-space-heading) 0;
|
|
527
|
+
}
|
|
528
|
+
:where(.inkwell-renderer h4) {
|
|
529
|
+
font-size: var(--inkwell-h4-size);
|
|
530
|
+
font-weight: var(--inkwell-heading-weight);
|
|
531
|
+
line-height: var(--inkwell-heading-line-height);
|
|
532
|
+
margin: var(--inkwell-space-heading) 0;
|
|
533
|
+
}
|
|
534
|
+
:where(.inkwell-renderer h5) {
|
|
535
|
+
font-size: var(--inkwell-h5-size);
|
|
536
|
+
font-weight: var(--inkwell-heading-weight);
|
|
537
|
+
line-height: var(--inkwell-heading-line-height);
|
|
538
|
+
margin: var(--inkwell-space-heading) 0;
|
|
539
|
+
}
|
|
540
|
+
:where(.inkwell-renderer h6) {
|
|
541
|
+
font-size: var(--inkwell-h6-size);
|
|
542
|
+
font-weight: var(--inkwell-heading-weight);
|
|
543
|
+
line-height: var(--inkwell-heading-line-height);
|
|
544
|
+
margin: var(--inkwell-space-heading) 0;
|
|
465
545
|
}
|
|
466
546
|
:where(.inkwell-renderer p) {
|
|
467
|
-
margin:
|
|
547
|
+
margin: var(--inkwell-space-paragraph) 0;
|
|
468
548
|
}
|
|
469
549
|
:where(.inkwell-renderer blockquote) {
|
|
470
550
|
border-left: 3px solid var(--inkwell-border-strong);
|
|
471
551
|
padding-left: 0.85em;
|
|
472
|
-
margin:
|
|
552
|
+
margin: var(--inkwell-space-blockquote) 0;
|
|
473
553
|
color: var(--inkwell-text-muted);
|
|
474
554
|
}
|
|
475
555
|
:where(.inkwell-renderer ul),
|
|
476
556
|
:where(.inkwell-renderer ol) {
|
|
477
|
-
padding-left:
|
|
478
|
-
margin:
|
|
557
|
+
padding-left: var(--inkwell-list-indent);
|
|
558
|
+
margin: var(--inkwell-space-list) 0;
|
|
479
559
|
}
|
|
480
560
|
:where(.inkwell-renderer ul) {
|
|
481
561
|
list-style: disc;
|
|
@@ -484,7 +564,7 @@
|
|
|
484
564
|
list-style: decimal;
|
|
485
565
|
}
|
|
486
566
|
:where(.inkwell-renderer li) {
|
|
487
|
-
margin:
|
|
567
|
+
margin: var(--inkwell-space-list-item) 0;
|
|
488
568
|
}
|
|
489
569
|
:where(.inkwell-renderer code) {
|
|
490
570
|
background: var(--inkwell-code-bg);
|
|
@@ -492,7 +572,7 @@
|
|
|
492
572
|
padding: 0.1em 0.35em;
|
|
493
573
|
border-radius: 4px;
|
|
494
574
|
font-family: var(--inkwell-font-mono);
|
|
495
|
-
font-size:
|
|
575
|
+
font-size: var(--inkwell-code-font-size);
|
|
496
576
|
}
|
|
497
577
|
/* Code-block wrapper position is structural — the copy button absolutely
|
|
498
578
|
positions inside it. */
|
|
@@ -533,7 +613,7 @@
|
|
|
533
613
|
color: var(--inkwell-text);
|
|
534
614
|
}
|
|
535
615
|
:where(.inkwell-renderer pre) {
|
|
536
|
-
margin:
|
|
616
|
+
margin: var(--inkwell-space-code-block) 0;
|
|
537
617
|
border-radius: var(--inkwell-radius);
|
|
538
618
|
overflow: auto;
|
|
539
619
|
border: 1px solid var(--inkwell-border);
|
|
@@ -544,7 +624,6 @@
|
|
|
544
624
|
padding: 0.85em 1em;
|
|
545
625
|
background: transparent;
|
|
546
626
|
color: var(--inkwell-text);
|
|
547
|
-
font-size: 0.82em;
|
|
548
627
|
}
|
|
549
628
|
:where(.inkwell-renderer a) {
|
|
550
629
|
color: var(--inkwell-accent);
|
|
@@ -554,7 +633,7 @@
|
|
|
554
633
|
:where(.inkwell-renderer hr) {
|
|
555
634
|
border: none;
|
|
556
635
|
border-top: 1px solid var(--inkwell-border);
|
|
557
|
-
margin:
|
|
636
|
+
margin: var(--inkwell-space-hr) 0;
|
|
558
637
|
}
|
|
559
638
|
:where(.inkwell-renderer strong) {
|
|
560
639
|
font-weight: 600;
|
|
@@ -569,5 +648,5 @@
|
|
|
569
648
|
max-width: 100%;
|
|
570
649
|
height: auto;
|
|
571
650
|
border-radius: var(--inkwell-radius);
|
|
572
|
-
margin:
|
|
651
|
+
margin: var(--inkwell-space-image) 0;
|
|
573
652
|
}
|