@soroush.tech/markdown 0.1.0 → 1.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/README.md +5 -5
- package/dist/Editor.cjs +2 -1
- package/dist/Editor.d.cts +6 -1
- package/dist/Editor.d.mts +6 -1
- package/dist/Editor.mjs +2 -1
- package/dist/LiveEdit-DyBGqI3s.cjs +219 -0
- package/dist/LiveEdit-KDKu7Yvn.mjs +211 -0
- package/dist/LiveEdit.cjs +3 -0
- package/dist/LiveEdit.d.cts +25 -0
- package/dist/LiveEdit.d.mts +25 -0
- package/dist/LiveEdit.mjs +2 -0
- package/dist/{Preview-BVT70uXa.cjs → Preview-CQ0z3gzx.cjs} +8 -1
- package/dist/{Preview-C1CwRDj5.mjs → Preview-nvh2sGBQ.mjs} +2 -1
- package/dist/Preview.cjs +1 -1
- package/dist/Preview.d.cts +1 -52
- package/dist/Preview.d.mts +1 -52
- package/dist/Preview.mjs +1 -1
- package/dist/index-CPWcFD6T.d.mts +53 -0
- package/dist/index-CxdJ1SgP.d.cts +53 -0
- package/dist/index.cjs +3 -1
- package/dist/index.d.cts +5 -3
- package/dist/index.d.mts +5 -3
- package/dist/index.mjs +3 -2
- package/package.json +9 -5
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
[](https://www.npmjs.com/package/@soroush.tech/markdown)
|
|
2
|
-
[](https://www.npmjs.com/package/@soroush.tech/markdown)
|
|
1
|
+
[](https://www.npmjs.com/package/@soroush.tech/markdown)
|
|
2
|
+
[](https://www.npmjs.com/package/@soroush.tech/markdown)
|
|
3
3
|
[](https://app.codecov.io/gh/soroush-tech/core?flags%5B0%5D=markdown)
|
|
4
|
-
[](https://www.npmjs.com/package/@soroush.tech/markdown)
|
|
5
|
-
[](https://www.npmjs.com/package/@soroush.tech/markdown)
|
|
6
|
-
[](./LICENSE)
|
|
4
|
+
[](https://www.npmjs.com/package/@soroush.tech/markdown)
|
|
5
|
+
[](https://www.npmjs.com/package/@soroush.tech/markdown)
|
|
6
|
+
[](./LICENSE)
|
|
7
7
|
|
|
8
8
|
## A **headless markdown editor and preview**.
|
|
9
9
|
|
package/dist/Editor.cjs
CHANGED
|
@@ -26,7 +26,7 @@ const noop = () => {};
|
|
|
26
26
|
* driven by context (so the `Toolbar` can act on it); on its own it is a plain controlled field
|
|
27
27
|
* via `value`/`onChange`.
|
|
28
28
|
*/
|
|
29
|
-
function Editor({ value: valueProp, onChange: onChangeProp, placeholder = "Write your article in Markdown…", minRows = 16, variant, color, textColor: textColorProp, size, name, id, error, required, disabled, showShortcutHint = true }) {
|
|
29
|
+
function Editor({ value: valueProp, onChange: onChangeProp, placeholder = "Write your article in Markdown…", minRows = 16, maxRows, variant, color, textColor: textColorProp, size, name, id, error, required, disabled, showShortcutHint = true }) {
|
|
30
30
|
const context = (0, react.useContext)(require_MarkdownContext.MarkdownContext);
|
|
31
31
|
const theme = (0, _soroush_tech_design_system_theme.useTheme)();
|
|
32
32
|
const textColor = textColorProp ?? (0, _soroush_tech_design_system_theme.themeDefault)(theme, "textColor", "initial");
|
|
@@ -106,6 +106,7 @@ function Editor({ value: valueProp, onChange: onChangeProp, placeholder = "Write
|
|
|
106
106
|
required,
|
|
107
107
|
disabled,
|
|
108
108
|
minRows,
|
|
109
|
+
maxRows,
|
|
109
110
|
value,
|
|
110
111
|
placeholder,
|
|
111
112
|
onChange: (event) => onChange(event.target.value),
|
package/dist/Editor.d.cts
CHANGED
|
@@ -9,6 +9,11 @@ interface EditorProps {
|
|
|
9
9
|
placeholder?: string;
|
|
10
10
|
/** Minimum visible rows of the source textarea. Default: `16`. */
|
|
11
11
|
minRows?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Maximum rows before the auto-grow textarea stops growing and scrolls
|
|
14
|
+
* internally — keeps its border box visible inside height-bound panes.
|
|
15
|
+
*/
|
|
16
|
+
maxRows?: number;
|
|
12
17
|
/** Visual style of the source input — forwarded to `TextInput`. Default: `'default'`. */
|
|
13
18
|
variant?: TextInputVariant;
|
|
14
19
|
/** Focus/active border colour — forwarded to `TextInput`. */
|
|
@@ -35,6 +40,6 @@ interface EditorProps {
|
|
|
35
40
|
* driven by context (so the `Toolbar` can act on it); on its own it is a plain controlled field
|
|
36
41
|
* via `value`/`onChange`.
|
|
37
42
|
*/
|
|
38
|
-
declare function Editor({ value: valueProp, onChange: onChangeProp, placeholder, minRows, variant, color, textColor: textColorProp, size, name, id, error, required, disabled, showShortcutHint }: Readonly<EditorProps>): import("react").JSX.Element;
|
|
43
|
+
declare function Editor({ value: valueProp, onChange: onChangeProp, placeholder, minRows, maxRows, variant, color, textColor: textColorProp, size, name, id, error, required, disabled, showShortcutHint }: Readonly<EditorProps>): import("react").JSX.Element;
|
|
39
44
|
//#endregion
|
|
40
45
|
export { Editor, EditorProps };
|
package/dist/Editor.d.mts
CHANGED
|
@@ -9,6 +9,11 @@ interface EditorProps {
|
|
|
9
9
|
placeholder?: string;
|
|
10
10
|
/** Minimum visible rows of the source textarea. Default: `16`. */
|
|
11
11
|
minRows?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Maximum rows before the auto-grow textarea stops growing and scrolls
|
|
14
|
+
* internally — keeps its border box visible inside height-bound panes.
|
|
15
|
+
*/
|
|
16
|
+
maxRows?: number;
|
|
12
17
|
/** Visual style of the source input — forwarded to `TextInput`. Default: `'default'`. */
|
|
13
18
|
variant?: TextInputVariant;
|
|
14
19
|
/** Focus/active border colour — forwarded to `TextInput`. */
|
|
@@ -35,6 +40,6 @@ interface EditorProps {
|
|
|
35
40
|
* driven by context (so the `Toolbar` can act on it); on its own it is a plain controlled field
|
|
36
41
|
* via `value`/`onChange`.
|
|
37
42
|
*/
|
|
38
|
-
declare function Editor({ value: valueProp, onChange: onChangeProp, placeholder, minRows, variant, color, textColor: textColorProp, size, name, id, error, required, disabled, showShortcutHint }: Readonly<EditorProps>): import("react").JSX.Element;
|
|
43
|
+
declare function Editor({ value: valueProp, onChange: onChangeProp, placeholder, minRows, maxRows, variant, color, textColor: textColorProp, size, name, id, error, required, disabled, showShortcutHint }: Readonly<EditorProps>): import("react").JSX.Element;
|
|
39
44
|
//#endregion
|
|
40
45
|
export { Editor, EditorProps };
|
package/dist/Editor.mjs
CHANGED
|
@@ -25,7 +25,7 @@ const noop = () => {};
|
|
|
25
25
|
* driven by context (so the `Toolbar` can act on it); on its own it is a plain controlled field
|
|
26
26
|
* via `value`/`onChange`.
|
|
27
27
|
*/
|
|
28
|
-
function Editor({ value: valueProp, onChange: onChangeProp, placeholder = "Write your article in Markdown…", minRows = 16, variant, color, textColor: textColorProp, size, name, id, error, required, disabled, showShortcutHint = true }) {
|
|
28
|
+
function Editor({ value: valueProp, onChange: onChangeProp, placeholder = "Write your article in Markdown…", minRows = 16, maxRows, variant, color, textColor: textColorProp, size, name, id, error, required, disabled, showShortcutHint = true }) {
|
|
29
29
|
const context = useContext(MarkdownContext);
|
|
30
30
|
const theme = useTheme();
|
|
31
31
|
const textColor = textColorProp ?? themeDefault(theme, "textColor", "initial");
|
|
@@ -105,6 +105,7 @@ function Editor({ value: valueProp, onChange: onChangeProp, placeholder = "Write
|
|
|
105
105
|
required,
|
|
106
106
|
disabled,
|
|
107
107
|
minRows,
|
|
108
|
+
maxRows,
|
|
108
109
|
value,
|
|
109
110
|
placeholder,
|
|
110
111
|
onChange: (event) => onChange(event.target.value),
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
const require_Preview = require("./Preview-CQ0z3gzx.cjs");
|
|
2
|
+
const require_MarkdownContext = require("./MarkdownContext.cjs");
|
|
3
|
+
let react = require("react");
|
|
4
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
5
|
+
let _soroush_tech_design_system = require("@soroush.tech/design-system");
|
|
6
|
+
let _soroush_tech_design_system_View = require("@soroush.tech/design-system/View");
|
|
7
|
+
let remark_gfm = require("remark-gfm");
|
|
8
|
+
remark_gfm = require_Preview.__toESM(remark_gfm, 1);
|
|
9
|
+
let rehype_parse = require("rehype-parse");
|
|
10
|
+
rehype_parse = require_Preview.__toESM(rehype_parse, 1);
|
|
11
|
+
let rehype_remark = require("rehype-remark");
|
|
12
|
+
rehype_remark = require_Preview.__toESM(rehype_remark, 1);
|
|
13
|
+
let remark_stringify = require("remark-stringify");
|
|
14
|
+
remark_stringify = require_Preview.__toESM(remark_stringify, 1);
|
|
15
|
+
//#region src/utils/blockToMarkdown.ts
|
|
16
|
+
const processor = (0, require("unified").unified)().use(rehype_parse.default, { fragment: true }).use(rehype_remark.default).use(remark_gfm.default).use(remark_stringify.default, {
|
|
17
|
+
bullet: "-",
|
|
18
|
+
fence: "`",
|
|
19
|
+
rule: "-"
|
|
20
|
+
});
|
|
21
|
+
/**
|
|
22
|
+
* Serializes an edited block's live DOM back to markdown. The preview renders
|
|
23
|
+
* non-content UI inside a block (the code-copy control, decorative SVGs) —
|
|
24
|
+
* those are stripped from a clone first so only real content round-trips.
|
|
25
|
+
*/
|
|
26
|
+
function blockToMarkdown(element) {
|
|
27
|
+
const clone = element.cloneNode(true);
|
|
28
|
+
for (const junk of clone.querySelectorAll(".code-copy, button, svg")) junk.remove();
|
|
29
|
+
return String(processor.processSync(clone.innerHTML)).trimEnd();
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/utils/splitBlocks.ts
|
|
33
|
+
const FENCE = /^[ \t]{0,3}(`{3,}|~{3,})/;
|
|
34
|
+
/**
|
|
35
|
+
* The fence state after `line`: unchanged when the line carries no marker, the
|
|
36
|
+
* marker itself when one opens a block, and `null` when one closes it.
|
|
37
|
+
*
|
|
38
|
+
* A closing marker must use the same character, be at least as long, and carry
|
|
39
|
+
* nothing after it. The info string is what separates the two roles — a
|
|
40
|
+
* ```` ```ts ```` line inside an open ```` ``` ```` block opens nothing and closes
|
|
41
|
+
* nothing, it is content. Without that check the block would end early and its
|
|
42
|
+
* remaining blank lines would split it into further blocks.
|
|
43
|
+
*/
|
|
44
|
+
const nextFence = (line, fence) => {
|
|
45
|
+
const match = FENCE.exec(line);
|
|
46
|
+
if (!match) return fence;
|
|
47
|
+
const [matched, marker] = match;
|
|
48
|
+
if (fence === null) return marker;
|
|
49
|
+
return line.slice(matched.length).trim() === "" && marker.startsWith(fence[0]) && marker.length >= fence.length ? null : fence;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Splits markdown into top-level blocks separated by blank lines, keeping
|
|
53
|
+
* fenced code blocks whole — blank lines inside a ``` / ~~~ fence never split.
|
|
54
|
+
* Offsets index the original string, so an edited block's source can be
|
|
55
|
+
* spliced back losslessly.
|
|
56
|
+
*/
|
|
57
|
+
function splitBlocks(value) {
|
|
58
|
+
const blocks = [];
|
|
59
|
+
let blockStart = -1;
|
|
60
|
+
let blockEnd = -1;
|
|
61
|
+
let fence = null;
|
|
62
|
+
let offset = 0;
|
|
63
|
+
for (const line of value.split("\n")) {
|
|
64
|
+
const isBlank = line.trim() === "";
|
|
65
|
+
if (blockStart >= 0 && isBlank && fence === null) {
|
|
66
|
+
blocks.push({
|
|
67
|
+
source: value.slice(blockStart, blockEnd),
|
|
68
|
+
start: blockStart,
|
|
69
|
+
end: blockEnd
|
|
70
|
+
});
|
|
71
|
+
blockStart = -1;
|
|
72
|
+
} else if (!isBlank || fence !== null) {
|
|
73
|
+
if (blockStart < 0) blockStart = offset;
|
|
74
|
+
blockEnd = offset + line.length;
|
|
75
|
+
fence = nextFence(line, fence);
|
|
76
|
+
}
|
|
77
|
+
offset += line.length + 1;
|
|
78
|
+
}
|
|
79
|
+
if (blockStart >= 0) blocks.push({
|
|
80
|
+
source: value.slice(blockStart, blockEnd),
|
|
81
|
+
start: blockStart,
|
|
82
|
+
end: blockEnd
|
|
83
|
+
});
|
|
84
|
+
return blocks;
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/LiveEdit/LiveEdit.tsx
|
|
88
|
+
const LiveEditRoot = (0, _soroush_tech_design_system.styled)(_soroush_tech_design_system_View.View, {
|
|
89
|
+
name: "MarkdownLiveEdit",
|
|
90
|
+
label: "MarkdownLiveEdit"
|
|
91
|
+
})({
|
|
92
|
+
minWidth: 0,
|
|
93
|
+
maxWidth: "100%",
|
|
94
|
+
overflowWrap: "break-word"
|
|
95
|
+
});
|
|
96
|
+
const LiveEditBlock = (0, _soroush_tech_design_system.styled)(_soroush_tech_design_system_View.View, {
|
|
97
|
+
name: "MarkdownLiveEdit",
|
|
98
|
+
slot: "block",
|
|
99
|
+
label: "MarkdownLiveEditBlock"
|
|
100
|
+
})(({ theme }) => ({
|
|
101
|
+
cursor: "text",
|
|
102
|
+
borderRadius: theme?.radii?.sm,
|
|
103
|
+
"&:hover, &:focus-within, &:focus": {
|
|
104
|
+
outline: `${theme?.borderWidths?.thin} dashed ${theme?.border?.primary}`,
|
|
105
|
+
outlineOffset: "2px"
|
|
106
|
+
},
|
|
107
|
+
"&:empty::before": {
|
|
108
|
+
content: "attr(data-placeholder)",
|
|
109
|
+
color: theme?.text?.secondary
|
|
110
|
+
}
|
|
111
|
+
}));
|
|
112
|
+
const MERMAID_FENCE = /^\s{0,3}(?:`{3,}|~{3,})\s*mermaid\b/;
|
|
113
|
+
const noop = () => {};
|
|
114
|
+
/** An empty document still needs one editable block to type into. */
|
|
115
|
+
const toBlocks = (value) => {
|
|
116
|
+
const blocks = splitBlocks(value);
|
|
117
|
+
return blocks.length > 0 ? blocks : [{
|
|
118
|
+
source: "",
|
|
119
|
+
start: 0,
|
|
120
|
+
end: value.length
|
|
121
|
+
}];
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* One contentEditable block. The preview element is created once per mount —
|
|
125
|
+
* while the browser owns the DOM during typing, React must never reconcile
|
|
126
|
+
* the subtree under the caret. The parent remounts the block (source-derived
|
|
127
|
+
* key) whenever its markdown actually changes.
|
|
128
|
+
*/
|
|
129
|
+
function EditableBlock({ index, source, isEditable, placeholder, slotProps, onEdit, onDone }) {
|
|
130
|
+
const [content] = (0, react.useState)(() => source === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Preview.Preview, {
|
|
131
|
+
slotProps,
|
|
132
|
+
children: source
|
|
133
|
+
}));
|
|
134
|
+
const handleKeyDown = (event) => {
|
|
135
|
+
if (event.key !== "Escape") return;
|
|
136
|
+
event.preventDefault();
|
|
137
|
+
event.currentTarget.blur();
|
|
138
|
+
};
|
|
139
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveEditBlock, {
|
|
140
|
+
"aria-label": "Edit block",
|
|
141
|
+
tabIndex: isEditable ? 0 : void 0,
|
|
142
|
+
contentEditable: isEditable,
|
|
143
|
+
suppressContentEditableWarning: true,
|
|
144
|
+
"data-placeholder": source === "" ? placeholder : void 0,
|
|
145
|
+
onInput: (event) => onEdit(index, event.currentTarget),
|
|
146
|
+
onBlur: onDone,
|
|
147
|
+
onKeyDown: handleKeyDown,
|
|
148
|
+
children: content
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* A preview you write on directly: the document renders exactly like
|
|
153
|
+
* `Preview`, split into blocks, and every block is `contentEditable` — no
|
|
154
|
+
* textarea. While a block is being typed in, the browser owns its DOM; each
|
|
155
|
+
* input serializes that block back to markdown and splices it into `value`,
|
|
156
|
+
* so `onChange` stays live and untouched blocks keep their exact source.
|
|
157
|
+
* Blur (or Escape) re-splits the document. Mermaid blocks are read-only —
|
|
158
|
+
* a diagram has no text to edit in place. Inside a `Control` it is driven by
|
|
159
|
+
* context; standalone it is a plain controlled component.
|
|
160
|
+
*/
|
|
161
|
+
function LiveEdit({ value: valueProp, onChange: onChangeProp, placeholder = "Click to start writing…", slotProps }) {
|
|
162
|
+
const context = (0, react.useContext)(require_MarkdownContext.MarkdownContext);
|
|
163
|
+
const value = context?.value ?? valueProp ?? "";
|
|
164
|
+
const onChange = context?.onChange ?? onChangeProp ?? noop;
|
|
165
|
+
const [blocks, setBlocks] = (0, react.useState)(() => toBlocks(value));
|
|
166
|
+
const [generation, setGeneration] = (0, react.useState)(0);
|
|
167
|
+
const offsetsRef = (0, react.useRef)(null);
|
|
168
|
+
const getOffsets = () => {
|
|
169
|
+
if (offsetsRef.current?.generation !== generation) offsetsRef.current = {
|
|
170
|
+
generation,
|
|
171
|
+
offsets: blocks.map(({ start, end }) => ({
|
|
172
|
+
start,
|
|
173
|
+
end
|
|
174
|
+
}))
|
|
175
|
+
};
|
|
176
|
+
return offsetsRef.current.offsets;
|
|
177
|
+
};
|
|
178
|
+
const resplit = (next) => {
|
|
179
|
+
setBlocks(toBlocks(next));
|
|
180
|
+
setGeneration((current) => current + 1);
|
|
181
|
+
};
|
|
182
|
+
const syncedValueRef = (0, react.useRef)(value);
|
|
183
|
+
if (value !== syncedValueRef.current) {
|
|
184
|
+
syncedValueRef.current = value;
|
|
185
|
+
resplit(value);
|
|
186
|
+
}
|
|
187
|
+
const handleEdit = (index, element) => {
|
|
188
|
+
const markdown = blockToMarkdown(element);
|
|
189
|
+
const offsets = getOffsets();
|
|
190
|
+
const block = offsets[index];
|
|
191
|
+
const current = syncedValueRef.current;
|
|
192
|
+
const next = current.slice(0, block.start) + markdown + current.slice(block.end);
|
|
193
|
+
const delta = markdown.length - (block.end - block.start);
|
|
194
|
+
block.end = block.start + markdown.length;
|
|
195
|
+
for (const later of offsets.slice(index + 1)) {
|
|
196
|
+
later.start += delta;
|
|
197
|
+
later.end += delta;
|
|
198
|
+
}
|
|
199
|
+
syncedValueRef.current = next;
|
|
200
|
+
onChange(next);
|
|
201
|
+
};
|
|
202
|
+
const handleDone = () => resplit(syncedValueRef.current);
|
|
203
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveEditRoot, { children: blocks.map((block, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EditableBlock, {
|
|
204
|
+
index,
|
|
205
|
+
source: block.source,
|
|
206
|
+
isEditable: !MERMAID_FENCE.test(block.source),
|
|
207
|
+
placeholder,
|
|
208
|
+
slotProps,
|
|
209
|
+
onEdit: handleEdit,
|
|
210
|
+
onDone: handleDone
|
|
211
|
+
}, `${generation}:${index}`)) });
|
|
212
|
+
}
|
|
213
|
+
//#endregion
|
|
214
|
+
Object.defineProperty(exports, "LiveEdit", {
|
|
215
|
+
enumerable: true,
|
|
216
|
+
get: function() {
|
|
217
|
+
return LiveEdit;
|
|
218
|
+
}
|
|
219
|
+
});
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { MarkdownContext } from "./MarkdownContext.mjs";
|
|
2
|
+
import { t as Preview } from "./Preview-nvh2sGBQ.mjs";
|
|
3
|
+
import { useContext, useRef, useState } from "react";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
import { styled } from "@soroush.tech/design-system";
|
|
6
|
+
import { View } from "@soroush.tech/design-system/View";
|
|
7
|
+
import remarkGfm from "remark-gfm";
|
|
8
|
+
import rehypeParse from "rehype-parse";
|
|
9
|
+
import rehypeRemark from "rehype-remark";
|
|
10
|
+
import remarkStringify from "remark-stringify";
|
|
11
|
+
import { unified } from "unified";
|
|
12
|
+
//#region src/utils/blockToMarkdown.ts
|
|
13
|
+
const processor = unified().use(rehypeParse, { fragment: true }).use(rehypeRemark).use(remarkGfm).use(remarkStringify, {
|
|
14
|
+
bullet: "-",
|
|
15
|
+
fence: "`",
|
|
16
|
+
rule: "-"
|
|
17
|
+
});
|
|
18
|
+
/**
|
|
19
|
+
* Serializes an edited block's live DOM back to markdown. The preview renders
|
|
20
|
+
* non-content UI inside a block (the code-copy control, decorative SVGs) —
|
|
21
|
+
* those are stripped from a clone first so only real content round-trips.
|
|
22
|
+
*/
|
|
23
|
+
function blockToMarkdown(element) {
|
|
24
|
+
const clone = element.cloneNode(true);
|
|
25
|
+
for (const junk of clone.querySelectorAll(".code-copy, button, svg")) junk.remove();
|
|
26
|
+
return String(processor.processSync(clone.innerHTML)).trimEnd();
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/utils/splitBlocks.ts
|
|
30
|
+
const FENCE = /^[ \t]{0,3}(`{3,}|~{3,})/;
|
|
31
|
+
/**
|
|
32
|
+
* The fence state after `line`: unchanged when the line carries no marker, the
|
|
33
|
+
* marker itself when one opens a block, and `null` when one closes it.
|
|
34
|
+
*
|
|
35
|
+
* A closing marker must use the same character, be at least as long, and carry
|
|
36
|
+
* nothing after it. The info string is what separates the two roles — a
|
|
37
|
+
* ```` ```ts ```` line inside an open ```` ``` ```` block opens nothing and closes
|
|
38
|
+
* nothing, it is content. Without that check the block would end early and its
|
|
39
|
+
* remaining blank lines would split it into further blocks.
|
|
40
|
+
*/
|
|
41
|
+
const nextFence = (line, fence) => {
|
|
42
|
+
const match = FENCE.exec(line);
|
|
43
|
+
if (!match) return fence;
|
|
44
|
+
const [matched, marker] = match;
|
|
45
|
+
if (fence === null) return marker;
|
|
46
|
+
return line.slice(matched.length).trim() === "" && marker.startsWith(fence[0]) && marker.length >= fence.length ? null : fence;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Splits markdown into top-level blocks separated by blank lines, keeping
|
|
50
|
+
* fenced code blocks whole — blank lines inside a ``` / ~~~ fence never split.
|
|
51
|
+
* Offsets index the original string, so an edited block's source can be
|
|
52
|
+
* spliced back losslessly.
|
|
53
|
+
*/
|
|
54
|
+
function splitBlocks(value) {
|
|
55
|
+
const blocks = [];
|
|
56
|
+
let blockStart = -1;
|
|
57
|
+
let blockEnd = -1;
|
|
58
|
+
let fence = null;
|
|
59
|
+
let offset = 0;
|
|
60
|
+
for (const line of value.split("\n")) {
|
|
61
|
+
const isBlank = line.trim() === "";
|
|
62
|
+
if (blockStart >= 0 && isBlank && fence === null) {
|
|
63
|
+
blocks.push({
|
|
64
|
+
source: value.slice(blockStart, blockEnd),
|
|
65
|
+
start: blockStart,
|
|
66
|
+
end: blockEnd
|
|
67
|
+
});
|
|
68
|
+
blockStart = -1;
|
|
69
|
+
} else if (!isBlank || fence !== null) {
|
|
70
|
+
if (blockStart < 0) blockStart = offset;
|
|
71
|
+
blockEnd = offset + line.length;
|
|
72
|
+
fence = nextFence(line, fence);
|
|
73
|
+
}
|
|
74
|
+
offset += line.length + 1;
|
|
75
|
+
}
|
|
76
|
+
if (blockStart >= 0) blocks.push({
|
|
77
|
+
source: value.slice(blockStart, blockEnd),
|
|
78
|
+
start: blockStart,
|
|
79
|
+
end: blockEnd
|
|
80
|
+
});
|
|
81
|
+
return blocks;
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/LiveEdit/LiveEdit.tsx
|
|
85
|
+
const LiveEditRoot = styled(View, {
|
|
86
|
+
name: "MarkdownLiveEdit",
|
|
87
|
+
label: "MarkdownLiveEdit"
|
|
88
|
+
})({
|
|
89
|
+
minWidth: 0,
|
|
90
|
+
maxWidth: "100%",
|
|
91
|
+
overflowWrap: "break-word"
|
|
92
|
+
});
|
|
93
|
+
const LiveEditBlock = styled(View, {
|
|
94
|
+
name: "MarkdownLiveEdit",
|
|
95
|
+
slot: "block",
|
|
96
|
+
label: "MarkdownLiveEditBlock"
|
|
97
|
+
})(({ theme }) => ({
|
|
98
|
+
cursor: "text",
|
|
99
|
+
borderRadius: theme?.radii?.sm,
|
|
100
|
+
"&:hover, &:focus-within, &:focus": {
|
|
101
|
+
outline: `${theme?.borderWidths?.thin} dashed ${theme?.border?.primary}`,
|
|
102
|
+
outlineOffset: "2px"
|
|
103
|
+
},
|
|
104
|
+
"&:empty::before": {
|
|
105
|
+
content: "attr(data-placeholder)",
|
|
106
|
+
color: theme?.text?.secondary
|
|
107
|
+
}
|
|
108
|
+
}));
|
|
109
|
+
const MERMAID_FENCE = /^\s{0,3}(?:`{3,}|~{3,})\s*mermaid\b/;
|
|
110
|
+
const noop = () => {};
|
|
111
|
+
/** An empty document still needs one editable block to type into. */
|
|
112
|
+
const toBlocks = (value) => {
|
|
113
|
+
const blocks = splitBlocks(value);
|
|
114
|
+
return blocks.length > 0 ? blocks : [{
|
|
115
|
+
source: "",
|
|
116
|
+
start: 0,
|
|
117
|
+
end: value.length
|
|
118
|
+
}];
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* One contentEditable block. The preview element is created once per mount —
|
|
122
|
+
* while the browser owns the DOM during typing, React must never reconcile
|
|
123
|
+
* the subtree under the caret. The parent remounts the block (source-derived
|
|
124
|
+
* key) whenever its markdown actually changes.
|
|
125
|
+
*/
|
|
126
|
+
function EditableBlock({ index, source, isEditable, placeholder, slotProps, onEdit, onDone }) {
|
|
127
|
+
const [content] = useState(() => source === "" ? null : /* @__PURE__ */ jsx(Preview, {
|
|
128
|
+
slotProps,
|
|
129
|
+
children: source
|
|
130
|
+
}));
|
|
131
|
+
const handleKeyDown = (event) => {
|
|
132
|
+
if (event.key !== "Escape") return;
|
|
133
|
+
event.preventDefault();
|
|
134
|
+
event.currentTarget.blur();
|
|
135
|
+
};
|
|
136
|
+
return /* @__PURE__ */ jsx(LiveEditBlock, {
|
|
137
|
+
"aria-label": "Edit block",
|
|
138
|
+
tabIndex: isEditable ? 0 : void 0,
|
|
139
|
+
contentEditable: isEditable,
|
|
140
|
+
suppressContentEditableWarning: true,
|
|
141
|
+
"data-placeholder": source === "" ? placeholder : void 0,
|
|
142
|
+
onInput: (event) => onEdit(index, event.currentTarget),
|
|
143
|
+
onBlur: onDone,
|
|
144
|
+
onKeyDown: handleKeyDown,
|
|
145
|
+
children: content
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* A preview you write on directly: the document renders exactly like
|
|
150
|
+
* `Preview`, split into blocks, and every block is `contentEditable` — no
|
|
151
|
+
* textarea. While a block is being typed in, the browser owns its DOM; each
|
|
152
|
+
* input serializes that block back to markdown and splices it into `value`,
|
|
153
|
+
* so `onChange` stays live and untouched blocks keep their exact source.
|
|
154
|
+
* Blur (or Escape) re-splits the document. Mermaid blocks are read-only —
|
|
155
|
+
* a diagram has no text to edit in place. Inside a `Control` it is driven by
|
|
156
|
+
* context; standalone it is a plain controlled component.
|
|
157
|
+
*/
|
|
158
|
+
function LiveEdit({ value: valueProp, onChange: onChangeProp, placeholder = "Click to start writing…", slotProps }) {
|
|
159
|
+
const context = useContext(MarkdownContext);
|
|
160
|
+
const value = context?.value ?? valueProp ?? "";
|
|
161
|
+
const onChange = context?.onChange ?? onChangeProp ?? noop;
|
|
162
|
+
const [blocks, setBlocks] = useState(() => toBlocks(value));
|
|
163
|
+
const [generation, setGeneration] = useState(0);
|
|
164
|
+
const offsetsRef = useRef(null);
|
|
165
|
+
const getOffsets = () => {
|
|
166
|
+
if (offsetsRef.current?.generation !== generation) offsetsRef.current = {
|
|
167
|
+
generation,
|
|
168
|
+
offsets: blocks.map(({ start, end }) => ({
|
|
169
|
+
start,
|
|
170
|
+
end
|
|
171
|
+
}))
|
|
172
|
+
};
|
|
173
|
+
return offsetsRef.current.offsets;
|
|
174
|
+
};
|
|
175
|
+
const resplit = (next) => {
|
|
176
|
+
setBlocks(toBlocks(next));
|
|
177
|
+
setGeneration((current) => current + 1);
|
|
178
|
+
};
|
|
179
|
+
const syncedValueRef = useRef(value);
|
|
180
|
+
if (value !== syncedValueRef.current) {
|
|
181
|
+
syncedValueRef.current = value;
|
|
182
|
+
resplit(value);
|
|
183
|
+
}
|
|
184
|
+
const handleEdit = (index, element) => {
|
|
185
|
+
const markdown = blockToMarkdown(element);
|
|
186
|
+
const offsets = getOffsets();
|
|
187
|
+
const block = offsets[index];
|
|
188
|
+
const current = syncedValueRef.current;
|
|
189
|
+
const next = current.slice(0, block.start) + markdown + current.slice(block.end);
|
|
190
|
+
const delta = markdown.length - (block.end - block.start);
|
|
191
|
+
block.end = block.start + markdown.length;
|
|
192
|
+
for (const later of offsets.slice(index + 1)) {
|
|
193
|
+
later.start += delta;
|
|
194
|
+
later.end += delta;
|
|
195
|
+
}
|
|
196
|
+
syncedValueRef.current = next;
|
|
197
|
+
onChange(next);
|
|
198
|
+
};
|
|
199
|
+
const handleDone = () => resplit(syncedValueRef.current);
|
|
200
|
+
return /* @__PURE__ */ jsx(LiveEditRoot, { children: blocks.map((block, index) => /* @__PURE__ */ jsx(EditableBlock, {
|
|
201
|
+
index,
|
|
202
|
+
source: block.source,
|
|
203
|
+
isEditable: !MERMAID_FENCE.test(block.source),
|
|
204
|
+
placeholder,
|
|
205
|
+
slotProps,
|
|
206
|
+
onEdit: handleEdit,
|
|
207
|
+
onDone: handleDone
|
|
208
|
+
}, `${generation}:${index}`)) });
|
|
209
|
+
}
|
|
210
|
+
//#endregion
|
|
211
|
+
export { LiveEdit as t };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { r as PreviewSlotProps } from "./index-CxdJ1SgP.cjs";
|
|
2
|
+
//#region src/LiveEdit/LiveEdit.d.ts
|
|
3
|
+
interface LiveEditProps {
|
|
4
|
+
/** Standalone source value — used when rendered outside a `Control`. Ignored inside one. */
|
|
5
|
+
value?: string;
|
|
6
|
+
/** Standalone change handler — used when rendered outside a `Control`. Ignored inside one. */
|
|
7
|
+
onChange?: (value: string) => void;
|
|
8
|
+
/** Shown inside the (empty) document's first block until something is typed. */
|
|
9
|
+
placeholder?: string;
|
|
10
|
+
/** Per-element prop overrides forwarded to every block's preview. */
|
|
11
|
+
slotProps?: PreviewSlotProps;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A preview you write on directly: the document renders exactly like
|
|
15
|
+
* `Preview`, split into blocks, and every block is `contentEditable` — no
|
|
16
|
+
* textarea. While a block is being typed in, the browser owns its DOM; each
|
|
17
|
+
* input serializes that block back to markdown and splices it into `value`,
|
|
18
|
+
* so `onChange` stays live and untouched blocks keep their exact source.
|
|
19
|
+
* Blur (or Escape) re-splits the document. Mermaid blocks are read-only —
|
|
20
|
+
* a diagram has no text to edit in place. Inside a `Control` it is driven by
|
|
21
|
+
* context; standalone it is a plain controlled component.
|
|
22
|
+
*/
|
|
23
|
+
declare function LiveEdit({ value: valueProp, onChange: onChangeProp, placeholder, slotProps }: Readonly<LiveEditProps>): import("react").JSX.Element;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { LiveEdit, LiveEditProps };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { r as PreviewSlotProps } from "./index-CPWcFD6T.mjs";
|
|
2
|
+
//#region src/LiveEdit/LiveEdit.d.ts
|
|
3
|
+
interface LiveEditProps {
|
|
4
|
+
/** Standalone source value — used when rendered outside a `Control`. Ignored inside one. */
|
|
5
|
+
value?: string;
|
|
6
|
+
/** Standalone change handler — used when rendered outside a `Control`. Ignored inside one. */
|
|
7
|
+
onChange?: (value: string) => void;
|
|
8
|
+
/** Shown inside the (empty) document's first block until something is typed. */
|
|
9
|
+
placeholder?: string;
|
|
10
|
+
/** Per-element prop overrides forwarded to every block's preview. */
|
|
11
|
+
slotProps?: PreviewSlotProps;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A preview you write on directly: the document renders exactly like
|
|
15
|
+
* `Preview`, split into blocks, and every block is `contentEditable` — no
|
|
16
|
+
* textarea. While a block is being typed in, the browser owns its DOM; each
|
|
17
|
+
* input serializes that block back to markdown and splices it into `value`,
|
|
18
|
+
* so `onChange` stays live and untouched blocks keep their exact source.
|
|
19
|
+
* Blur (or Escape) re-splits the document. Mermaid blocks are read-only —
|
|
20
|
+
* a diagram has no text to edit in place. Inside a `Control` it is driven by
|
|
21
|
+
* context; standalone it is a plain controlled component.
|
|
22
|
+
*/
|
|
23
|
+
declare function LiveEdit({ value: valueProp, onChange: onChangeProp, placeholder, slotProps }: Readonly<LiveEditProps>): import("react").JSX.Element;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { LiveEdit, LiveEditProps };
|
|
@@ -26,6 +26,7 @@ let react = require("react");
|
|
|
26
26
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
27
27
|
let _soroush_tech_design_system_Typography = require("@soroush.tech/design-system/Typography");
|
|
28
28
|
let _soroush_tech_design_system = require("@soroush.tech/design-system");
|
|
29
|
+
let _soroush_tech_design_system_View = require("@soroush.tech/design-system/View");
|
|
29
30
|
let react_markdown = require("react-markdown");
|
|
30
31
|
react_markdown = __toESM(react_markdown, 1);
|
|
31
32
|
let remark_gfm = require("remark-gfm");
|
|
@@ -37,7 +38,6 @@ let _soroush_tech_design_system_Checkbox = require("@soroush.tech/design-system/
|
|
|
37
38
|
let _soroush_tech_design_system_Image = require("@soroush.tech/design-system/Image");
|
|
38
39
|
let _soroush_tech_design_system_Link = require("@soroush.tech/design-system/Link");
|
|
39
40
|
let _soroush_tech_design_system_Table = require("@soroush.tech/design-system/Table");
|
|
40
|
-
let _soroush_tech_design_system_View = require("@soroush.tech/design-system/View");
|
|
41
41
|
function mermaidSource(children) {
|
|
42
42
|
return (Array.isArray(children) ? children.join("") : String(children ?? "")).replace(/\n$/, "");
|
|
43
43
|
}
|
|
@@ -190,6 +190,7 @@ const components = {
|
|
|
190
190
|
const isBlock = classes.includes("language-");
|
|
191
191
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_soroush_tech_design_system_Typography.Typography, {
|
|
192
192
|
as: "code",
|
|
193
|
+
className,
|
|
193
194
|
color: "initial",
|
|
194
195
|
variant: "inherit",
|
|
195
196
|
fontFamily: "mono",
|
|
@@ -274,3 +275,9 @@ Object.defineProperty(exports, "Preview", {
|
|
|
274
275
|
return Preview;
|
|
275
276
|
}
|
|
276
277
|
});
|
|
278
|
+
Object.defineProperty(exports, "__toESM", {
|
|
279
|
+
enumerable: true,
|
|
280
|
+
get: function() {
|
|
281
|
+
return __toESM;
|
|
282
|
+
}
|
|
283
|
+
});
|
|
@@ -4,6 +4,7 @@ import { createContext, isValidElement, useContext } from "react";
|
|
|
4
4
|
import { jsx } from "react/jsx-runtime";
|
|
5
5
|
import { Typography } from "@soroush.tech/design-system/Typography";
|
|
6
6
|
import { styled } from "@soroush.tech/design-system";
|
|
7
|
+
import { View } from "@soroush.tech/design-system/View";
|
|
7
8
|
import ReactMarkdown from "react-markdown";
|
|
8
9
|
import remarkGfm from "remark-gfm";
|
|
9
10
|
import rehypeHighlight from "rehype-highlight";
|
|
@@ -12,7 +13,6 @@ import { Checkbox } from "@soroush.tech/design-system/Checkbox";
|
|
|
12
13
|
import { Image } from "@soroush.tech/design-system/Image";
|
|
13
14
|
import { Link } from "@soroush.tech/design-system/Link";
|
|
14
15
|
import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@soroush.tech/design-system/Table";
|
|
15
|
-
import { View } from "@soroush.tech/design-system/View";
|
|
16
16
|
function mermaidSource(children) {
|
|
17
17
|
return (Array.isArray(children) ? children.join("") : String(children ?? "")).replace(/\n$/, "");
|
|
18
18
|
}
|
|
@@ -165,6 +165,7 @@ const components = {
|
|
|
165
165
|
const isBlock = classes.includes("language-");
|
|
166
166
|
return /* @__PURE__ */ jsx(Typography, {
|
|
167
167
|
as: "code",
|
|
168
|
+
className,
|
|
168
169
|
color: "initial",
|
|
169
170
|
variant: "inherit",
|
|
170
171
|
fontFamily: "mono",
|
package/dist/Preview.cjs
CHANGED
package/dist/Preview.d.cts
CHANGED
|
@@ -1,53 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { r as MermaidProps } from "./index-BO0LyUUG.cjs";
|
|
3
|
-
import { ViewProps } from "@soroush.tech/design-system/View";
|
|
4
|
-
import { QuoteProps } from "@soroush.tech/design-system/Quote";
|
|
5
|
-
import { CheckboxProps } from "@soroush.tech/design-system/Checkbox";
|
|
6
|
-
import { ImageProps } from "@soroush.tech/design-system/Image";
|
|
7
|
-
import { LinkProps } from "@soroush.tech/design-system/Link";
|
|
8
|
-
import { TableBodyProps, TableCellProps, TableHeadProps, TableProps, TableRowProps } from "@soroush.tech/design-system/Table";
|
|
9
|
-
import { TypographyProps } from "@soroush.tech/design-system/Typography";
|
|
10
|
-
//#region src/Preview/Preview.d.ts
|
|
11
|
-
/**
|
|
12
|
-
* Per-element prop overrides, merged over the design-system defaults (the caller's props win).
|
|
13
|
-
* Keyed by markdown element — e.g. `slotProps={{ p: { color: 'primary' }, h1: { variant: 'h2' } }}`.
|
|
14
|
-
*/
|
|
15
|
-
interface PreviewSlotProps {
|
|
16
|
-
h1?: TypographyProps;
|
|
17
|
-
h2?: TypographyProps;
|
|
18
|
-
h3?: TypographyProps;
|
|
19
|
-
h4?: TypographyProps;
|
|
20
|
-
h5?: TypographyProps;
|
|
21
|
-
h6?: TypographyProps;
|
|
22
|
-
p?: TypographyProps;
|
|
23
|
-
strong?: TypographyProps;
|
|
24
|
-
em?: TypographyProps;
|
|
25
|
-
li?: TypographyProps;
|
|
26
|
-
code?: TypographyProps;
|
|
27
|
-
a?: LinkProps;
|
|
28
|
-
ul?: ViewProps;
|
|
29
|
-
ol?: ViewProps;
|
|
30
|
-
hr?: ViewProps;
|
|
31
|
-
blockquote?: QuoteProps;
|
|
32
|
-
img?: ImageProps;
|
|
33
|
-
th?: TableCellProps;
|
|
34
|
-
td?: TableCellProps;
|
|
35
|
-
pre?: CodeBlockProps;
|
|
36
|
-
input?: CheckboxProps;
|
|
37
|
-
table?: TableProps;
|
|
38
|
-
thead?: TableHeadProps;
|
|
39
|
-
tbody?: TableBodyProps;
|
|
40
|
-
tr?: TableRowProps;
|
|
41
|
-
/** Props for a ` ```mermaid ` fence's `Mermaid` renderer — including `diagram` (viewer) props. */
|
|
42
|
-
mermaid?: Omit<MermaidProps, 'chart'>;
|
|
43
|
-
}
|
|
44
|
-
interface PreviewProps {
|
|
45
|
-
/** The markdown source to render. */
|
|
46
|
-
children: string;
|
|
47
|
-
/** Per-element prop overrides, merged over the defaults. */
|
|
48
|
-
slotProps?: PreviewSlotProps;
|
|
49
|
-
}
|
|
50
|
-
/** Renders a markdown string with every element mapped to a design-system primitive. */
|
|
51
|
-
declare function Preview({ children, slotProps }: Readonly<PreviewProps>): import("react").JSX.Element;
|
|
52
|
-
//#endregion
|
|
1
|
+
import { n as PreviewProps, r as PreviewSlotProps, t as Preview } from "./index-CxdJ1SgP.cjs";
|
|
53
2
|
export { Preview, PreviewProps, PreviewSlotProps };
|
package/dist/Preview.d.mts
CHANGED
|
@@ -1,53 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { r as MermaidProps } from "./index-BO0LyUUG.mjs";
|
|
3
|
-
import { TypographyProps } from "@soroush.tech/design-system/Typography";
|
|
4
|
-
import { QuoteProps } from "@soroush.tech/design-system/Quote";
|
|
5
|
-
import { CheckboxProps } from "@soroush.tech/design-system/Checkbox";
|
|
6
|
-
import { ImageProps } from "@soroush.tech/design-system/Image";
|
|
7
|
-
import { LinkProps } from "@soroush.tech/design-system/Link";
|
|
8
|
-
import { TableBodyProps, TableCellProps, TableHeadProps, TableProps, TableRowProps } from "@soroush.tech/design-system/Table";
|
|
9
|
-
import { ViewProps } from "@soroush.tech/design-system/View";
|
|
10
|
-
//#region src/Preview/Preview.d.ts
|
|
11
|
-
/**
|
|
12
|
-
* Per-element prop overrides, merged over the design-system defaults (the caller's props win).
|
|
13
|
-
* Keyed by markdown element — e.g. `slotProps={{ p: { color: 'primary' }, h1: { variant: 'h2' } }}`.
|
|
14
|
-
*/
|
|
15
|
-
interface PreviewSlotProps {
|
|
16
|
-
h1?: TypographyProps;
|
|
17
|
-
h2?: TypographyProps;
|
|
18
|
-
h3?: TypographyProps;
|
|
19
|
-
h4?: TypographyProps;
|
|
20
|
-
h5?: TypographyProps;
|
|
21
|
-
h6?: TypographyProps;
|
|
22
|
-
p?: TypographyProps;
|
|
23
|
-
strong?: TypographyProps;
|
|
24
|
-
em?: TypographyProps;
|
|
25
|
-
li?: TypographyProps;
|
|
26
|
-
code?: TypographyProps;
|
|
27
|
-
a?: LinkProps;
|
|
28
|
-
ul?: ViewProps;
|
|
29
|
-
ol?: ViewProps;
|
|
30
|
-
hr?: ViewProps;
|
|
31
|
-
blockquote?: QuoteProps;
|
|
32
|
-
img?: ImageProps;
|
|
33
|
-
th?: TableCellProps;
|
|
34
|
-
td?: TableCellProps;
|
|
35
|
-
pre?: CodeBlockProps;
|
|
36
|
-
input?: CheckboxProps;
|
|
37
|
-
table?: TableProps;
|
|
38
|
-
thead?: TableHeadProps;
|
|
39
|
-
tbody?: TableBodyProps;
|
|
40
|
-
tr?: TableRowProps;
|
|
41
|
-
/** Props for a ` ```mermaid ` fence's `Mermaid` renderer — including `diagram` (viewer) props. */
|
|
42
|
-
mermaid?: Omit<MermaidProps, 'chart'>;
|
|
43
|
-
}
|
|
44
|
-
interface PreviewProps {
|
|
45
|
-
/** The markdown source to render. */
|
|
46
|
-
children: string;
|
|
47
|
-
/** Per-element prop overrides, merged over the defaults. */
|
|
48
|
-
slotProps?: PreviewSlotProps;
|
|
49
|
-
}
|
|
50
|
-
/** Renders a markdown string with every element mapped to a design-system primitive. */
|
|
51
|
-
declare function Preview({ children, slotProps }: Readonly<PreviewProps>): import("react").JSX.Element;
|
|
52
|
-
//#endregion
|
|
1
|
+
import { n as PreviewProps, r as PreviewSlotProps, t as Preview } from "./index-CPWcFD6T.mjs";
|
|
53
2
|
export { Preview, PreviewProps, PreviewSlotProps };
|
package/dist/Preview.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as Preview } from "./Preview-
|
|
1
|
+
import { t as Preview } from "./Preview-nvh2sGBQ.mjs";
|
|
2
2
|
export { Preview };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { a as CodeBlockProps } from "./index-BtMTBOcM.mjs";
|
|
2
|
+
import { r as MermaidProps } from "./index-BO0LyUUG.mjs";
|
|
3
|
+
import { TypographyProps } from "@soroush.tech/design-system/Typography";
|
|
4
|
+
import { ViewProps } from "@soroush.tech/design-system/View";
|
|
5
|
+
import { QuoteProps } from "@soroush.tech/design-system/Quote";
|
|
6
|
+
import { CheckboxProps } from "@soroush.tech/design-system/Checkbox";
|
|
7
|
+
import { ImageProps } from "@soroush.tech/design-system/Image";
|
|
8
|
+
import { LinkProps } from "@soroush.tech/design-system/Link";
|
|
9
|
+
import { TableBodyProps, TableCellProps, TableHeadProps, TableProps, TableRowProps } from "@soroush.tech/design-system/Table";
|
|
10
|
+
//#region src/Preview/Preview.d.ts
|
|
11
|
+
/**
|
|
12
|
+
* Per-element prop overrides, merged over the design-system defaults (the caller's props win).
|
|
13
|
+
* Keyed by markdown element — e.g. `slotProps={{ p: { color: 'primary' }, h1: { variant: 'h2' } }}`.
|
|
14
|
+
*/
|
|
15
|
+
interface PreviewSlotProps {
|
|
16
|
+
h1?: TypographyProps;
|
|
17
|
+
h2?: TypographyProps;
|
|
18
|
+
h3?: TypographyProps;
|
|
19
|
+
h4?: TypographyProps;
|
|
20
|
+
h5?: TypographyProps;
|
|
21
|
+
h6?: TypographyProps;
|
|
22
|
+
p?: TypographyProps;
|
|
23
|
+
strong?: TypographyProps;
|
|
24
|
+
em?: TypographyProps;
|
|
25
|
+
li?: TypographyProps;
|
|
26
|
+
code?: TypographyProps;
|
|
27
|
+
a?: LinkProps;
|
|
28
|
+
ul?: ViewProps;
|
|
29
|
+
ol?: ViewProps;
|
|
30
|
+
hr?: ViewProps;
|
|
31
|
+
blockquote?: QuoteProps;
|
|
32
|
+
img?: ImageProps;
|
|
33
|
+
th?: TableCellProps;
|
|
34
|
+
td?: TableCellProps;
|
|
35
|
+
pre?: CodeBlockProps;
|
|
36
|
+
input?: CheckboxProps;
|
|
37
|
+
table?: TableProps;
|
|
38
|
+
thead?: TableHeadProps;
|
|
39
|
+
tbody?: TableBodyProps;
|
|
40
|
+
tr?: TableRowProps;
|
|
41
|
+
/** Props for a ` ```mermaid ` fence's `Mermaid` renderer — including `diagram` (viewer) props. */
|
|
42
|
+
mermaid?: Omit<MermaidProps, 'chart'>;
|
|
43
|
+
}
|
|
44
|
+
interface PreviewProps {
|
|
45
|
+
/** The markdown source to render. */
|
|
46
|
+
children: string;
|
|
47
|
+
/** Per-element prop overrides, merged over the defaults. */
|
|
48
|
+
slotProps?: PreviewSlotProps;
|
|
49
|
+
}
|
|
50
|
+
/** Renders a markdown string with every element mapped to a design-system primitive. */
|
|
51
|
+
declare function Preview({ children, slotProps }: Readonly<PreviewProps>): import("react").JSX.Element;
|
|
52
|
+
//#endregion
|
|
53
|
+
export { PreviewProps as n, PreviewSlotProps as r, Preview as t };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { a as CodeBlockProps } from "./index-BtMTBOcM.cjs";
|
|
2
|
+
import { r as MermaidProps } from "./index-BO0LyUUG.cjs";
|
|
3
|
+
import { ViewProps } from "@soroush.tech/design-system/View";
|
|
4
|
+
import { QuoteProps } from "@soroush.tech/design-system/Quote";
|
|
5
|
+
import { CheckboxProps } from "@soroush.tech/design-system/Checkbox";
|
|
6
|
+
import { ImageProps } from "@soroush.tech/design-system/Image";
|
|
7
|
+
import { LinkProps } from "@soroush.tech/design-system/Link";
|
|
8
|
+
import { TableBodyProps, TableCellProps, TableHeadProps, TableProps, TableRowProps } from "@soroush.tech/design-system/Table";
|
|
9
|
+
import { TypographyProps } from "@soroush.tech/design-system/Typography";
|
|
10
|
+
//#region src/Preview/Preview.d.ts
|
|
11
|
+
/**
|
|
12
|
+
* Per-element prop overrides, merged over the design-system defaults (the caller's props win).
|
|
13
|
+
* Keyed by markdown element — e.g. `slotProps={{ p: { color: 'primary' }, h1: { variant: 'h2' } }}`.
|
|
14
|
+
*/
|
|
15
|
+
interface PreviewSlotProps {
|
|
16
|
+
h1?: TypographyProps;
|
|
17
|
+
h2?: TypographyProps;
|
|
18
|
+
h3?: TypographyProps;
|
|
19
|
+
h4?: TypographyProps;
|
|
20
|
+
h5?: TypographyProps;
|
|
21
|
+
h6?: TypographyProps;
|
|
22
|
+
p?: TypographyProps;
|
|
23
|
+
strong?: TypographyProps;
|
|
24
|
+
em?: TypographyProps;
|
|
25
|
+
li?: TypographyProps;
|
|
26
|
+
code?: TypographyProps;
|
|
27
|
+
a?: LinkProps;
|
|
28
|
+
ul?: ViewProps;
|
|
29
|
+
ol?: ViewProps;
|
|
30
|
+
hr?: ViewProps;
|
|
31
|
+
blockquote?: QuoteProps;
|
|
32
|
+
img?: ImageProps;
|
|
33
|
+
th?: TableCellProps;
|
|
34
|
+
td?: TableCellProps;
|
|
35
|
+
pre?: CodeBlockProps;
|
|
36
|
+
input?: CheckboxProps;
|
|
37
|
+
table?: TableProps;
|
|
38
|
+
thead?: TableHeadProps;
|
|
39
|
+
tbody?: TableBodyProps;
|
|
40
|
+
tr?: TableRowProps;
|
|
41
|
+
/** Props for a ` ```mermaid ` fence's `Mermaid` renderer — including `diagram` (viewer) props. */
|
|
42
|
+
mermaid?: Omit<MermaidProps, 'chart'>;
|
|
43
|
+
}
|
|
44
|
+
interface PreviewProps {
|
|
45
|
+
/** The markdown source to render. */
|
|
46
|
+
children: string;
|
|
47
|
+
/** Per-element prop overrides, merged over the defaults. */
|
|
48
|
+
slotProps?: PreviewSlotProps;
|
|
49
|
+
}
|
|
50
|
+
/** Renders a markdown string with every element mapped to a design-system primitive. */
|
|
51
|
+
declare function Preview({ children, slotProps }: Readonly<PreviewProps>): import("react").JSX.Element;
|
|
52
|
+
//#endregion
|
|
53
|
+
export { PreviewProps as n, PreviewSlotProps as r, Preview as t };
|
package/dist/index.cjs
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_Preview = require("./Preview-
|
|
2
|
+
const require_Preview = require("./Preview-CQ0z3gzx.cjs");
|
|
3
3
|
const require_MarkdownContext = require("./MarkdownContext.cjs");
|
|
4
4
|
const require_Control = require("./Control.cjs");
|
|
5
5
|
const require_Toolbar = require("./Toolbar-Ba3l-oBU.cjs");
|
|
6
6
|
const require_Editor = require("./Editor.cjs");
|
|
7
7
|
const require_CodeBlock = require("./CodeBlock-Cymx1ZAY.cjs");
|
|
8
8
|
const require_Mermaid = require("./Mermaid-DqP2Q0bt.cjs");
|
|
9
|
+
const require_LiveEdit = require("./LiveEdit-DyBGqI3s.cjs");
|
|
9
10
|
exports.CodeBlock = require_CodeBlock.CodeBlock;
|
|
10
11
|
exports.Control = require_Control.Control;
|
|
11
12
|
exports.Editor = require_Editor.Editor;
|
|
13
|
+
exports.LiveEdit = require_LiveEdit.LiveEdit;
|
|
12
14
|
exports.MarkdownContext = require_MarkdownContext.MarkdownContext;
|
|
13
15
|
exports.Mermaid = require_Mermaid.Mermaid;
|
|
14
16
|
exports.Preview = require_Preview.Preview;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { a as CodeBlockProps, i as CodeBlock, n as syntaxDark, r as syntaxLight, t as ThemeSyntax } from "./index-BtMTBOcM.cjs";
|
|
2
2
|
import { Control, ControlProps } from "./Control.cjs";
|
|
3
3
|
import { Editor, EditorProps } from "./Editor.cjs";
|
|
4
|
-
import { i as useMarkdownContext, n as MarkdownContextValue, r as MarkdownSelection, t as MarkdownContext } from "./index-rGZIhUgj.cjs";
|
|
5
4
|
import { n as Mermaid, r as MermaidProps, t as DiagramProps } from "./index-BO0LyUUG.cjs";
|
|
6
|
-
import {
|
|
5
|
+
import { n as PreviewProps, r as PreviewSlotProps, t as Preview } from "./index-CxdJ1SgP.cjs";
|
|
6
|
+
import { LiveEdit, LiveEditProps } from "./LiveEdit.cjs";
|
|
7
|
+
import { i as useMarkdownContext, n as MarkdownContextValue, r as MarkdownSelection, t as MarkdownContext } from "./index-rGZIhUgj.cjs";
|
|
7
8
|
import { TablePicker, TablePickerProps, Toolbar } from "./Toolbar.cjs";
|
|
8
9
|
//#region src/index.d.ts
|
|
9
10
|
/**
|
|
@@ -31,6 +32,7 @@ interface MermaidThemeVariables {
|
|
|
31
32
|
declare module '@soroush.tech/design-system/theme' {
|
|
32
33
|
interface ThemeComponents {
|
|
33
34
|
MarkdownEditor?: import('@soroush.tech/design-system/theme').ComponentConfig;
|
|
35
|
+
MarkdownLiveEdit?: import('@soroush.tech/design-system/theme').ComponentConfig<Record<string, unknown>, 'root' | 'block'>;
|
|
34
36
|
MarkdownPreview?: import('@soroush.tech/design-system/theme').ComponentConfig;
|
|
35
37
|
MarkdownToolbar?: import('@soroush.tech/design-system/theme').ComponentConfig<Record<string, unknown>, 'root' | 'strike' | 'tablePickerCell'>;
|
|
36
38
|
CodeBlock?: import('@soroush.tech/design-system/theme').ComponentConfig<Record<string, unknown>, 'root' | 'surface'>;
|
|
@@ -47,4 +49,4 @@ declare module '@soroush.tech/design-system/theme' {
|
|
|
47
49
|
}
|
|
48
50
|
}
|
|
49
51
|
//#endregion
|
|
50
|
-
export { CodeBlock, CodeBlockProps, Control, ControlProps, DiagramProps, Editor, EditorProps, MarkdownContext, MarkdownContextValue, MarkdownSelection, Mermaid, MermaidProps, MermaidThemeVariables, Preview, PreviewProps, PreviewSlotProps, TablePicker, TablePickerProps, ThemeSyntax, Toolbar, syntaxDark, syntaxLight, useMarkdownContext };
|
|
52
|
+
export { CodeBlock, CodeBlockProps, Control, ControlProps, DiagramProps, Editor, EditorProps, LiveEdit, LiveEditProps, MarkdownContext, MarkdownContextValue, MarkdownSelection, Mermaid, MermaidProps, MermaidThemeVariables, Preview, PreviewProps, PreviewSlotProps, TablePicker, TablePickerProps, ThemeSyntax, Toolbar, syntaxDark, syntaxLight, useMarkdownContext };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { a as CodeBlockProps, i as CodeBlock, n as syntaxDark, r as syntaxLight, t as ThemeSyntax } from "./index-BtMTBOcM.mjs";
|
|
2
2
|
import { Control, ControlProps } from "./Control.mjs";
|
|
3
3
|
import { Editor, EditorProps } from "./Editor.mjs";
|
|
4
|
-
import { i as useMarkdownContext, n as MarkdownContextValue, r as MarkdownSelection, t as MarkdownContext } from "./index-rGZIhUgj.mjs";
|
|
5
4
|
import { n as Mermaid, r as MermaidProps, t as DiagramProps } from "./index-BO0LyUUG.mjs";
|
|
6
|
-
import {
|
|
5
|
+
import { n as PreviewProps, r as PreviewSlotProps, t as Preview } from "./index-CPWcFD6T.mjs";
|
|
6
|
+
import { LiveEdit, LiveEditProps } from "./LiveEdit.mjs";
|
|
7
|
+
import { i as useMarkdownContext, n as MarkdownContextValue, r as MarkdownSelection, t as MarkdownContext } from "./index-rGZIhUgj.mjs";
|
|
7
8
|
import { TablePicker, TablePickerProps, Toolbar } from "./Toolbar.mjs";
|
|
8
9
|
//#region src/index.d.ts
|
|
9
10
|
/**
|
|
@@ -31,6 +32,7 @@ interface MermaidThemeVariables {
|
|
|
31
32
|
declare module '@soroush.tech/design-system/theme' {
|
|
32
33
|
interface ThemeComponents {
|
|
33
34
|
MarkdownEditor?: import('@soroush.tech/design-system/theme').ComponentConfig;
|
|
35
|
+
MarkdownLiveEdit?: import('@soroush.tech/design-system/theme').ComponentConfig<Record<string, unknown>, 'root' | 'block'>;
|
|
34
36
|
MarkdownPreview?: import('@soroush.tech/design-system/theme').ComponentConfig;
|
|
35
37
|
MarkdownToolbar?: import('@soroush.tech/design-system/theme').ComponentConfig<Record<string, unknown>, 'root' | 'strike' | 'tablePickerCell'>;
|
|
36
38
|
CodeBlock?: import('@soroush.tech/design-system/theme').ComponentConfig<Record<string, unknown>, 'root' | 'surface'>;
|
|
@@ -47,4 +49,4 @@ declare module '@soroush.tech/design-system/theme' {
|
|
|
47
49
|
}
|
|
48
50
|
}
|
|
49
51
|
//#endregion
|
|
50
|
-
export { CodeBlock, CodeBlockProps, Control, ControlProps, DiagramProps, Editor, EditorProps, MarkdownContext, MarkdownContextValue, MarkdownSelection, Mermaid, MermaidProps, MermaidThemeVariables, Preview, PreviewProps, PreviewSlotProps, TablePicker, TablePickerProps, ThemeSyntax, Toolbar, syntaxDark, syntaxLight, useMarkdownContext };
|
|
52
|
+
export { CodeBlock, CodeBlockProps, Control, ControlProps, DiagramProps, Editor, EditorProps, LiveEdit, LiveEditProps, MarkdownContext, MarkdownContextValue, MarkdownSelection, Mermaid, MermaidProps, MermaidThemeVariables, Preview, PreviewProps, PreviewSlotProps, TablePicker, TablePickerProps, ThemeSyntax, Toolbar, syntaxDark, syntaxLight, useMarkdownContext };
|
package/dist/index.mjs
CHANGED
|
@@ -4,5 +4,6 @@ import { n as TablePicker, t as Toolbar } from "./Toolbar-C0SNI-JK.mjs";
|
|
|
4
4
|
import { Editor } from "./Editor.mjs";
|
|
5
5
|
import { n as syntaxLight, r as CodeBlock, t as syntaxDark } from "./CodeBlock-CVlrvVPP.mjs";
|
|
6
6
|
import { t as Mermaid } from "./Mermaid-BclI9A7e.mjs";
|
|
7
|
-
import { t as Preview } from "./Preview-
|
|
8
|
-
|
|
7
|
+
import { t as Preview } from "./Preview-nvh2sGBQ.mjs";
|
|
8
|
+
import { t as LiveEdit } from "./LiveEdit-KDKu7Yvn.mjs";
|
|
9
|
+
export { CodeBlock, Control, Editor, LiveEdit, MarkdownContext, Mermaid, Preview, TablePicker, Toolbar, syntaxDark, syntaxLight, useMarkdownContext };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soroush.tech/markdown",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Headless, composable markdown editor + renderer for @soroush.tech/design-system — Control, Toolbar, Editor, and Preview built on react-markdown, remark-gfm, and rehype-highlight.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|
|
@@ -58,12 +58,16 @@
|
|
|
58
58
|
"mermaid": "^11.4.0",
|
|
59
59
|
"react-markdown": "^10.1.0",
|
|
60
60
|
"rehype-highlight": "^7.0.2",
|
|
61
|
-
"
|
|
61
|
+
"rehype-parse": "^9.0.1",
|
|
62
|
+
"rehype-remark": "^10.0.1",
|
|
63
|
+
"remark-gfm": "^4.0.1",
|
|
64
|
+
"remark-stringify": "^11.0.0",
|
|
65
|
+
"unified": "^11.0.5"
|
|
62
66
|
},
|
|
63
67
|
"peerDependencies": {
|
|
64
68
|
"react": "^19.0.0",
|
|
65
69
|
"react-dom": "^19.0.0",
|
|
66
|
-
"@soroush.tech/design-system": "^1.
|
|
70
|
+
"@soroush.tech/design-system": "^1.1.0"
|
|
67
71
|
},
|
|
68
72
|
"devDependencies": {
|
|
69
73
|
"@storybook/react-vite": "^10.5.0",
|
|
@@ -86,9 +90,9 @@
|
|
|
86
90
|
"tsdown": "^0.22.7",
|
|
87
91
|
"typescript": "~6.0.3",
|
|
88
92
|
"vitest": "^4.1.10",
|
|
89
|
-
"@soroush.tech/
|
|
93
|
+
"@soroush.tech/design-system": "1.1.0",
|
|
90
94
|
"@soroush.tech/eslint-config": "0.0.0",
|
|
91
|
-
"@soroush.tech/
|
|
95
|
+
"@soroush.tech/hooks": "0.1.0"
|
|
92
96
|
},
|
|
93
97
|
"scripts": {
|
|
94
98
|
"build": "tsdown",
|