@reviewflow/ui 0.0.0-stage → 6.21.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.
Files changed (92) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +95 -2
  3. package/package.json +102 -4
  4. package/src/board/bucket-section.tsx +94 -0
  5. package/src/board/dataSource.ts +44 -0
  6. package/src/board/facts.ts +183 -0
  7. package/src/board/index.ts +12 -0
  8. package/src/board/pull-request-board.tsx +239 -0
  9. package/src/board/pull-request-row.tsx +181 -0
  10. package/src/components/branch-diff/diff-themes.ts +6 -0
  11. package/src/components/branch-diff/diff-worker-pool.tsx +12 -0
  12. package/src/components/branch-diff/diff-worker-pool.web.tsx +56 -0
  13. package/src/components/branch-diff/diff-worker.ts +7 -0
  14. package/src/components/branch-diff/use-collapsed-files.ts +49 -0
  15. package/src/components/branch-diff/use-diff-settings.tsx +97 -0
  16. package/src/components/branch-diff/whitespace-button.tsx +32 -0
  17. package/src/components/copyable-code-block.tsx +101 -0
  18. package/src/components/label-pill.tsx +84 -0
  19. package/src/components/link-in-row.ts +6 -0
  20. package/src/components/markdown-suggestion.tsx +60 -0
  21. package/src/components/markdown-text.tsx +1067 -0
  22. package/src/components/pull-request-actions-merge.tsx +341 -0
  23. package/src/components/pull-request-actions-review.tsx +218 -0
  24. package/src/components/pull-request-checks.tsx +86 -0
  25. package/src/components/pull-request-commit-files.ts +143 -0
  26. package/src/components/pull-request-commits.tsx +221 -0
  27. package/src/components/pull-request-conversation-card.tsx +220 -0
  28. package/src/components/pull-request-conversation-edit.tsx +147 -0
  29. package/src/components/pull-request-conversation-event.tsx +267 -0
  30. package/src/components/pull-request-conversation-person.tsx +28 -0
  31. package/src/components/pull-request-conversation-thread.tsx +158 -0
  32. package/src/components/pull-request-diff-annotations.tsx +443 -0
  33. package/src/components/pull-request-diff-collapse.ts +43 -0
  34. package/src/components/pull-request-diff-data.ts +174 -0
  35. package/src/components/pull-request-diff-impacts-summary.tsx +97 -0
  36. package/src/components/pull-request-diff-impacts.ts +45 -0
  37. package/src/components/pull-request-diff-jump.ts +87 -0
  38. package/src/components/pull-request-diff-lines.ts +103 -0
  39. package/src/components/pull-request-diff-overview.tsx +416 -0
  40. package/src/components/pull-request-diff-view.tsx +438 -0
  41. package/src/components/pull-request-diff-viewer.tsx +1374 -0
  42. package/src/components/pull-request-files.tsx +533 -0
  43. package/src/components/pull-request-markdown-input.tsx +91 -0
  44. package/src/components/pull-request-panel.tsx +1270 -0
  45. package/src/components/pull-request-part-notice.tsx +82 -0
  46. package/src/components/pull-request-reactions.tsx +178 -0
  47. package/src/components/pull-request-review.tsx +164 -0
  48. package/src/components/pull-request-revisions.tsx +273 -0
  49. package/src/components/pull-request-screen.tsx +299 -0
  50. package/src/components/pull-request-suggestion-actions.tsx +201 -0
  51. package/src/components/pull-request-timeline.tsx +270 -0
  52. package/src/components/repository-attachment-banner.tsx +42 -0
  53. package/src/components/scroll-parent.ts +50 -0
  54. package/src/css.d.ts +1 -0
  55. package/src/dataSource.ts +242 -0
  56. package/src/editor/alerts.ts +105 -0
  57. package/src/editor/code-editor.native.tsx +25 -0
  58. package/src/editor/code-editor.tsx +112 -0
  59. package/src/editor/codeEditorExtensions.ts +65 -0
  60. package/src/editor/codeHighlight.ts +71 -0
  61. package/src/editor/commands.ts +175 -0
  62. package/src/editor/decorations.ts +425 -0
  63. package/src/editor/editable-file.tsx +224 -0
  64. package/src/editor/emojiCompletion.ts +110 -0
  65. package/src/editor/extensions.ts +149 -0
  66. package/src/editor/frontmatter.ts +76 -0
  67. package/src/editor/inlineFormat.ts +114 -0
  68. package/src/editor/lineTypes.ts +55 -0
  69. package/src/editor/markdown-editor.css +511 -0
  70. package/src/editor/markdown-editor.native.tsx +29 -0
  71. package/src/editor/markdown-editor.tsx +295 -0
  72. package/src/editor/nodeClasses.ts +69 -0
  73. package/src/index.ts +75 -0
  74. package/src/markdown-github/flatten.ts +49 -0
  75. package/src/markdown-github/html-tokens.ts +93 -0
  76. package/src/markdown-github/pair-html.ts +354 -0
  77. package/src/markdown-github/safe-url.ts +15 -0
  78. package/src/markdown-github/table-columns.ts +23 -0
  79. package/src/markdown-github/toggle-task.ts +164 -0
  80. package/src/nativewind-env.d.ts +1 -0
  81. package/src/prStatus.ts +90 -0
  82. package/src/services/PullRequestDraftsProvider.tsx +179 -0
  83. package/src/services/PullRequestReviewDraftProvider.tsx +178 -0
  84. package/src/services/PullRequestSuggestionBatchProvider.tsx +112 -0
  85. package/src/services/emojify.ts +13 -0
  86. package/src/services/errorToMessage.ts +6 -0
  87. package/src/services/expiringDraftStore.ts +102 -0
  88. package/src/services/formatDataAge.ts +12 -0
  89. package/src/services/formatRelativeDate.ts +56 -0
  90. package/src/services/githubImages.tsx +33 -0
  91. package/src/services/isViewerAuthor.ts +7 -0
  92. package/src/slots.tsx +34 -0
@@ -0,0 +1,110 @@
1
+ import type {
2
+ Completion,
3
+ CompletionContext,
4
+ CompletionResult,
5
+ } from "@codemirror/autocomplete";
6
+ import { autocompletion } from "@codemirror/autocomplete";
7
+ import { syntaxTree } from "@codemirror/language";
8
+ import type { Extension } from "@codemirror/state";
9
+ import type { SyntaxNode } from "@lezer/common";
10
+ import type { Gemoji } from "gemoji";
11
+ import { gemoji } from "gemoji";
12
+
13
+ const typedShortcode = /:[\w+-]+/;
14
+
15
+ /** A `:` after one of these is a time, a ratio or a key, not a shortcode. */
16
+ const glued = /[\w:]/;
17
+
18
+ /** GitHub renders no emoji inside these. */
19
+ const codeNodes = new Set([
20
+ "CodeBlock",
21
+ "CodeText",
22
+ "FencedCode",
23
+ "InlineCode",
24
+ ]);
25
+
26
+ const maxOptions = 30;
27
+
28
+ const isInCode = (context: CompletionContext): boolean => {
29
+ let node: SyntaxNode | null = syntaxTree(context.state).resolveInner(
30
+ context.pos,
31
+ -1,
32
+ );
33
+ while (node !== null) {
34
+ if (codeNodes.has(node.name)) return true;
35
+ node = node.parent;
36
+ }
37
+ return false;
38
+ };
39
+
40
+ interface Match {
41
+ entry: Gemoji;
42
+ name: string;
43
+ /** lower is better: exact name, name prefix, tag prefix, name substring */
44
+ rank: number;
45
+ order: number;
46
+ }
47
+
48
+ const matchEntry = (
49
+ entry: Gemoji,
50
+ query: string,
51
+ order: number,
52
+ ): Match | null => {
53
+ const exact = entry.names.find((name) => name === query);
54
+ if (exact !== undefined) return { entry, name: exact, rank: 0, order };
55
+ const prefixed = entry.names.find((name) => name.startsWith(query));
56
+ if (prefixed !== undefined) return { entry, name: prefixed, rank: 1, order };
57
+ const [primary = ""] = entry.names;
58
+ if (entry.tags.some((tag) => tag.startsWith(query))) {
59
+ return { entry, name: primary, rank: 2, order };
60
+ }
61
+ if (query.length < 2) return null;
62
+ const containing = entry.names.find((name) => name.includes(query));
63
+ if (containing !== undefined) {
64
+ return { entry, name: containing, rank: 3, order };
65
+ }
66
+ return null;
67
+ };
68
+
69
+ /** The emoji a typed `:query` names, best first, as GitHub's own box offers them. */
70
+ export const emojiMatches = (
71
+ query: string,
72
+ ): { emoji: string; name: string }[] =>
73
+ gemoji
74
+ .map((entry, order) => matchEntry(entry, query.toLowerCase(), order))
75
+ .filter((match) => match !== null)
76
+ .toSorted(
77
+ (a, b) =>
78
+ a.rank - b.rank || a.name.length - b.name.length || a.order - b.order,
79
+ )
80
+ .slice(0, maxOptions)
81
+ .map(({ entry, name }) => ({ emoji: entry.emoji, name }));
82
+
83
+ /**
84
+ * Completes a `:shortcode:` as GitHub's comment box does, inserting the
85
+ * shortcode rather than the character: the source stays what GitHub's own
86
+ * editor would have written, and both render the same.
87
+ */
88
+ export const emojiCompletionSource = (
89
+ context: CompletionContext,
90
+ ): CompletionResult | null => {
91
+ const typed = context.matchBefore(typedShortcode);
92
+ if (typed === null) return null;
93
+ const before =
94
+ typed.from === 0 ? "" : context.state.sliceDoc(typed.from - 1, typed.from);
95
+ if (glued.test(before) || isInCode(context)) return null;
96
+
97
+ const options: Completion[] = emojiMatches(typed.text.slice(1)).map(
98
+ ({ emoji, name }) => ({
99
+ label: `:${name}:`,
100
+ displayLabel: `${emoji} :${name}:`,
101
+ }),
102
+ );
103
+ if (options.length === 0) return null;
104
+ return { from: typed.from, options, filter: false };
105
+ };
106
+
107
+ export const emojiCompletion: Extension = autocompletion({
108
+ override: [emojiCompletionSource],
109
+ icons: false,
110
+ });
@@ -0,0 +1,149 @@
1
+ import { defaultKeymap, history, historyKeymap } from "@codemirror/commands";
2
+ import { css } from "@codemirror/lang-css";
3
+ import { html } from "@codemirror/lang-html";
4
+ import { javascript } from "@codemirror/lang-javascript";
5
+ import { json } from "@codemirror/lang-json";
6
+ import {
7
+ commonmarkLanguage,
8
+ markdown,
9
+ markdownKeymap,
10
+ } from "@codemirror/lang-markdown";
11
+ import { yaml } from "@codemirror/lang-yaml";
12
+ import { LanguageDescription } from "@codemirror/language";
13
+ import {
14
+ highlightSelectionMatches,
15
+ search,
16
+ searchKeymap,
17
+ } from "@codemirror/search";
18
+ import type { Extension } from "@codemirror/state";
19
+ import { Annotation, EditorState, Prec } from "@codemirror/state";
20
+ import { EditorView, drawSelection, keymap } from "@codemirror/view";
21
+ import { GFM } from "@lezer/markdown";
22
+ import { codeHighlighting } from "./codeHighlight.ts";
23
+ import { formattingKeymap } from "./commands.ts";
24
+ import { markdownDecorations } from "./decorations.ts";
25
+ import { emojiCompletion } from "./emojiCompletion.ts";
26
+ import { frontmatter } from "./frontmatter.ts";
27
+
28
+ /**
29
+ * Bundled statically: `lang-markdown` already depends on `lang-html`, which
30
+ * brings `lang-javascript` and `lang-css`, so only json and yaml add weight.
31
+ */
32
+ const codeLanguages = [
33
+ LanguageDescription.of({
34
+ name: "typescript",
35
+ alias: ["ts"],
36
+ support: javascript({ typescript: true }),
37
+ }),
38
+ LanguageDescription.of({
39
+ name: "tsx",
40
+ support: javascript({ typescript: true, jsx: true }),
41
+ }),
42
+ LanguageDescription.of({
43
+ name: "javascript",
44
+ alias: ["js", "mjs", "cjs"],
45
+ support: javascript(),
46
+ }),
47
+ LanguageDescription.of({ name: "jsx", support: javascript({ jsx: true }) }),
48
+ LanguageDescription.of({ name: "json", support: json() }),
49
+ LanguageDescription.of({ name: "html", support: html() }),
50
+ LanguageDescription.of({ name: "css", support: css() }),
51
+ LanguageDescription.of({ name: "yaml", alias: ["yml"], support: yaml() }),
52
+ ];
53
+
54
+ /**
55
+ * The markdown language with GFM and frontmatter. GFM has to go through
56
+ * `markdown()`'s `extensions` option: configuring the raw parser with it is a
57
+ * silent no-op. HTML tag completion, which `lang-html` would bring, is off —
58
+ * this is a prose editor.
59
+ */
60
+ export const markdownLanguage = (): Extension =>
61
+ markdown({
62
+ base: commonmarkLanguage,
63
+ extensions: [GFM, frontmatter],
64
+ codeLanguages,
65
+ completeHTMLTags: false,
66
+ addKeymap: false,
67
+ });
68
+
69
+ /**
70
+ * Marks a change that came from the caller's `value` rather than from typing,
71
+ * so it is not reported back through `onChange`.
72
+ */
73
+ export const externalUpdate = Annotation.define<boolean>();
74
+
75
+ /**
76
+ * The single change turning `current` into `next`: the common prefix and
77
+ * suffix are kept, so the selection and the undo history outside the changed
78
+ * span survive. A character diff would find a smaller edit and take minutes on
79
+ * a large replacement.
80
+ */
81
+ export const changeBetween = (
82
+ current: string,
83
+ next: string,
84
+ ): { from: number; to: number; insert: string } | null => {
85
+ if (current === next) return null;
86
+ const limit = Math.min(current.length, next.length);
87
+ let prefix = 0;
88
+ while (prefix < limit && current[prefix] === next[prefix]) prefix += 1;
89
+ let suffix = 0;
90
+ while (
91
+ suffix < limit - prefix &&
92
+ current[current.length - 1 - suffix] === next[next.length - 1 - suffix]
93
+ ) {
94
+ suffix += 1;
95
+ }
96
+ return {
97
+ from: prefix,
98
+ to: current.length - suffix,
99
+ insert: next.slice(prefix, next.length - suffix),
100
+ };
101
+ };
102
+
103
+ interface MarkdownEditorExtensionsOptions {
104
+ readOnly: boolean;
105
+ /** `:shortcode:` completion, for a body GitHub renders */
106
+ emoji: boolean;
107
+ onChange: (value: string) => void;
108
+ }
109
+
110
+ /**
111
+ * Everything the editor is, chosen once at mount: read-only is not toggled on
112
+ * a live view, the caller remounts instead.
113
+ */
114
+ export const markdownEditorExtensions = ({
115
+ readOnly,
116
+ emoji,
117
+ onChange,
118
+ }: MarkdownEditorExtensionsOptions): Extension[] => [
119
+ markdownLanguage(),
120
+ codeHighlighting,
121
+ markdownDecorations,
122
+ EditorView.lineWrapping,
123
+ drawSelection(),
124
+ highlightSelectionMatches(),
125
+ search({ top: true }),
126
+ EditorState.readOnly.of(readOnly),
127
+ EditorView.editable.of(!readOnly),
128
+ ...(readOnly
129
+ ? [keymap.of([...searchKeymap, ...defaultKeymap])]
130
+ : [
131
+ history(),
132
+ ...(emoji ? [emojiCompletion] : []),
133
+ Prec.high(keymap.of(formattingKeymap)),
134
+ keymap.of([
135
+ ...markdownKeymap,
136
+ ...searchKeymap,
137
+ ...historyKeymap,
138
+ ...defaultKeymap,
139
+ ]),
140
+ EditorView.updateListener.of((update) => {
141
+ const typed = update.transactions.some(
142
+ (transaction) =>
143
+ transaction.docChanged &&
144
+ transaction.annotation(externalUpdate) !== true,
145
+ );
146
+ if (typed) onChange(update.state.doc.toString());
147
+ }),
148
+ ]),
149
+ ];
@@ -0,0 +1,76 @@
1
+ import { yamlLanguage } from "@codemirror/lang-yaml";
2
+ import { parseMixed } from "@lezer/common";
3
+ import type {
4
+ BlockContext,
5
+ Element,
6
+ Line,
7
+ MarkdownConfig,
8
+ } from "@lezer/markdown";
9
+
10
+ /**
11
+ * A leading `---` … `---` block, parsed as yaml. **Every template starts with
12
+ * one.** Without this, the opening `---` is a horizontal rule, the yaml below
13
+ * it becomes the title of a setext heading closed by the second `---`, and the
14
+ * whole block renders as one H2.
15
+ *
16
+ * Strict on purpose: at position 0 only, three dashes exactly, and the line
17
+ * after the opening fence must not be blank. A block parser cannot look further
18
+ * ahead than one line nor give a line back, so a block missing its closing
19
+ * fence runs to the end of the document; the blank-line rule keeps a document
20
+ * that opens on a horizontal rule from being swallowed that way.
21
+ *
22
+ * Ported from vscode-extension-markdown-beautiful-editor
23
+ * (`src/webview/cm/lang/frontmatter.ts`).
24
+ */
25
+ const isFence = (text: string): boolean => /^---[ \t]*$/u.test(text);
26
+
27
+ const parseFrontmatter = (cx: BlockContext, line: Line): boolean => {
28
+ if (
29
+ cx.lineStart !== 0 ||
30
+ line.pos !== 0 ||
31
+ !isFence(line.text) ||
32
+ cx.peekLine().trim() === ""
33
+ ) {
34
+ return false;
35
+ }
36
+
37
+ const from = cx.lineStart;
38
+ const openMark = cx.elt("FrontmatterMark", from, from + line.text.length);
39
+ const contentFrom = from + line.text.length + 1;
40
+ let contentTo = contentFrom;
41
+ let closeMark: Element | null = null;
42
+ let to = from + line.text.length;
43
+
44
+ while (cx.nextLine()) {
45
+ const lineEnd = cx.lineStart + line.text.length;
46
+ to = lineEnd;
47
+ if (isFence(line.text)) {
48
+ closeMark = cx.elt("FrontmatterMark", cx.lineStart, lineEnd);
49
+ cx.nextLine();
50
+ break;
51
+ }
52
+ contentTo = lineEnd;
53
+ }
54
+
55
+ const children = [openMark];
56
+ if (contentTo > contentFrom) {
57
+ children.push(cx.elt("FrontmatterContent", contentFrom, contentTo));
58
+ }
59
+ if (closeMark !== null) children.push(closeMark);
60
+ cx.addElement(cx.elt("Frontmatter", from, to, children));
61
+ return true;
62
+ };
63
+
64
+ export const frontmatter: MarkdownConfig = {
65
+ defineNodes: [
66
+ { name: "Frontmatter", block: true },
67
+ "FrontmatterMark",
68
+ "FrontmatterContent",
69
+ ],
70
+ parseBlock: [
71
+ { name: "Frontmatter", before: "HorizontalRule", parse: parseFrontmatter },
72
+ ],
73
+ wrap: parseMixed((node) =>
74
+ node.name === "FrontmatterContent" ? { parser: yamlLanguage.parser } : null,
75
+ ),
76
+ };
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Toggling bold, italic, code, strikethrough or a link over a selection inside
3
+ * one line, as a pure string transform: the editor turns its selection into
4
+ * offsets and applies the returned splice. Ported from
5
+ * vscode-extension-markdown-beautiful-editor (`src/shared/inlineFormat.ts`).
6
+ */
7
+ export type InlineFormat =
8
+ | "bold"
9
+ | "code"
10
+ | "italic"
11
+ | "link"
12
+ | "strikethrough";
13
+
14
+ const markers: Record<Exclude<InlineFormat, "link">, string> = {
15
+ bold: "**",
16
+ italic: "*",
17
+ code: "`",
18
+ strikethrough: "~~",
19
+ };
20
+
21
+ /** how many of the marker's character sit right before `at` */
22
+ const runBefore = (text: string, at: number, marker: string): number => {
23
+ const char = marker.charAt(0);
24
+ let index = at;
25
+ while (index > 0 && text.charAt(index - 1) === char) index--;
26
+ return at - index;
27
+ };
28
+
29
+ const runAfter = (text: string, at: number, marker: string): number => {
30
+ const char = marker.charAt(0);
31
+ let index = at;
32
+ while (index < text.length && text.charAt(index) === char) index++;
33
+ return index - at;
34
+ };
35
+
36
+ /**
37
+ * Whether a run of `*`, `~` or `` ` `` on both sides of the text applies the
38
+ * format: `**` is bold and not italic, `***` both; a code span is delimited by
39
+ * one backtick, which a run of two does not close.
40
+ */
41
+ const isFormatted = (format: Exclude<InlineFormat, "link">, run: number) => {
42
+ if (format === "italic") return run === 1 || run === 3;
43
+ if (format === "code") return run === 1;
44
+ return run >= 2;
45
+ };
46
+
47
+ const inlineLinkPattern = /^\[(?<label>[^\]]+)\]\([^)]+\)$/u;
48
+
49
+ export interface InlineFormatChangeInput {
50
+ /** the whole line the selection is in */
51
+ text: string;
52
+ from: number;
53
+ to: number;
54
+ format: InlineFormat;
55
+ }
56
+
57
+ export interface InlineFormatChange {
58
+ /** spliced over `[from, to)`, which can be wider than the selection when markers just outside it are consumed */
59
+ insert: string;
60
+ from: number;
61
+ to: number;
62
+ /** where the cursor lands, in the resulting line */
63
+ selection: number;
64
+ }
65
+
66
+ /**
67
+ * - `link` unwraps a selection that is exactly `[label](dest)`, and wraps
68
+ * anything else as `[selection](url)`.
69
+ * - The others remove markers sitting just outside the selection, or wrapping
70
+ * it from inside, and wrap it otherwise.
71
+ *
72
+ * Null for an empty selection: there is nothing to toggle.
73
+ */
74
+ export const inlineFormatChange = ({
75
+ text,
76
+ from,
77
+ to,
78
+ format,
79
+ }: InlineFormatChangeInput): InlineFormatChange | null => {
80
+ const selected = text.slice(from, to);
81
+ if (selected === "") return null;
82
+
83
+ if (format === "link") {
84
+ const label = inlineLinkPattern.exec(selected)?.groups?.label;
85
+ const insert = label ?? `[${selected}](url)`;
86
+ return { insert, from, to, selection: from + insert.length };
87
+ }
88
+
89
+ const marker = markers[format];
90
+ const outside = Math.min(
91
+ runBefore(text, from, marker),
92
+ runAfter(text, to, marker),
93
+ );
94
+ if (isFormatted(format, outside)) {
95
+ return {
96
+ insert: selected,
97
+ from: from - marker.length,
98
+ to: to + marker.length,
99
+ selection: from - marker.length + selected.length,
100
+ };
101
+ }
102
+
103
+ const inside = Math.min(
104
+ runAfter(selected, 0, marker),
105
+ runBefore(selected, selected.length, marker),
106
+ );
107
+ if (isFormatted(format, inside) && selected.length > inside * 2) {
108
+ const inner = selected.slice(marker.length, -marker.length);
109
+ return { insert: inner, from, to, selection: from + inner.length };
110
+ }
111
+
112
+ const wrapped = `${marker}${selected}${marker}`;
113
+ return { insert: wrapped, from, to, selection: from + wrapped.length };
114
+ };
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Turning a line into a heading, a list item, a quote or a fence, as a pure
3
+ * string transform: strip whatever prefix the line has, then apply the new
4
+ * one. Ported from vscode-extension-markdown-beautiful-editor
5
+ * (`src/shared/lineTypes.ts`).
6
+ */
7
+ export type LineType =
8
+ | "code"
9
+ | "h1"
10
+ | "h2"
11
+ | "h3"
12
+ | "ol"
13
+ | "paragraph"
14
+ | "quote"
15
+ | "task"
16
+ | "ul";
17
+
18
+ /**
19
+ * Most specific first. These consume the separator space too, so what is left
20
+ * is the bare content.
21
+ */
22
+ export const stripLinePrefix = (line: string): string =>
23
+ line
24
+ .replace(/^(?:-{3,}|\*{3,}|_{3,})\s*$/u, "")
25
+ .replace(/^#{1,6}\s/u, "")
26
+ .replace(/^[-*+]\s\[[ x]\]\s/iu, "")
27
+ .replace(/^[-*+]\s/u, "")
28
+ .replace(/^\d+\.\s/u, "")
29
+ .replace(/^>+\s?/u, "")
30
+ .replace(/^```\w*\s*/u, "");
31
+
32
+ export const applyLinePrefix = (content: string, type: LineType): string => {
33
+ switch (type) {
34
+ case "paragraph":
35
+ return content;
36
+ case "h1":
37
+ return `# ${content}`;
38
+ case "h2":
39
+ return `## ${content}`;
40
+ case "h3":
41
+ return `### ${content}`;
42
+ case "ul":
43
+ return `- ${content}`;
44
+ case "ol":
45
+ return `1. ${content}`;
46
+ case "task":
47
+ return `- [ ] ${content}`;
48
+ case "quote":
49
+ return `> ${content}`;
50
+ case "code":
51
+ return `\`\`\`\n${content}\n\`\`\``;
52
+ default:
53
+ return content;
54
+ }
55
+ };