@particle-academy/fancy-code 0.4.6 → 0.5.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 +2 -0
- package/dist/index.cjs +236 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +122 -7
- package/dist/index.d.ts +122 -7
- package/dist/index.js +233 -2
- package/dist/index.js.map +1 -1
- package/dist/styles.css +138 -0
- package/dist/styles.css.map +1 -1
- package/package.json +4 -5
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react from 'react';
|
|
2
2
|
import { RefObject, ReactNode } from 'react';
|
|
3
|
+
import { InputTagProps, InputTagTriggers, InputTagAdapter } from '@particle-academy/react-fancy';
|
|
3
4
|
|
|
4
5
|
interface ThemeColors {
|
|
5
6
|
background: string;
|
|
@@ -114,6 +115,13 @@ interface CodeEditorContextValue {
|
|
|
114
115
|
};
|
|
115
116
|
/** Length of the current selection (0 if none) */
|
|
116
117
|
selectionLength: number;
|
|
118
|
+
/**
|
|
119
|
+
* Ref to the underlying `<textarea>` element. Exposed so external
|
|
120
|
+
* widgets — e.g. `<InputTag>`'s `textareaAdapter` — can attach to
|
|
121
|
+
* the same input the editor renders into. Will be `null` before
|
|
122
|
+
* the editor has mounted.
|
|
123
|
+
*/
|
|
124
|
+
textareaRef: RefObject<HTMLTextAreaElement | null>;
|
|
117
125
|
/** Placeholder text */
|
|
118
126
|
placeholder?: string;
|
|
119
127
|
/** @internal */
|
|
@@ -135,27 +143,27 @@ interface CodeEditorStatusBarProps {
|
|
|
135
143
|
className?: string;
|
|
136
144
|
}
|
|
137
145
|
|
|
138
|
-
declare function CodeEditorPanel({ className }: CodeEditorPanelProps):
|
|
146
|
+
declare function CodeEditorPanel({ className }: CodeEditorPanelProps): react.JSX.Element | null;
|
|
139
147
|
declare namespace CodeEditorPanel {
|
|
140
148
|
var displayName: string;
|
|
141
149
|
}
|
|
142
150
|
|
|
143
|
-
declare function CodeEditorToolbar({ children, className }: CodeEditorToolbarProps):
|
|
151
|
+
declare function CodeEditorToolbar({ children, className }: CodeEditorToolbarProps): react.JSX.Element;
|
|
144
152
|
declare namespace CodeEditorToolbar {
|
|
145
153
|
var displayName: string;
|
|
146
154
|
}
|
|
147
155
|
|
|
148
|
-
declare function CodeEditorToolbarSeparator():
|
|
156
|
+
declare function CodeEditorToolbarSeparator(): react.JSX.Element;
|
|
149
157
|
declare namespace CodeEditorToolbarSeparator {
|
|
150
158
|
var displayName: string;
|
|
151
159
|
}
|
|
152
160
|
|
|
153
|
-
declare function CodeEditorStatusBar({ children, className }: CodeEditorStatusBarProps):
|
|
161
|
+
declare function CodeEditorStatusBar({ children, className }: CodeEditorStatusBarProps): react.JSX.Element;
|
|
154
162
|
declare namespace CodeEditorStatusBar {
|
|
155
163
|
var displayName: string;
|
|
156
164
|
}
|
|
157
165
|
|
|
158
|
-
declare function CodeEditorRoot({ children, className, value: valueProp, defaultValue, onChange, language: languageProp, onLanguageChange, theme, readOnly, lineNumbers: lineNumbersProp, wordWrap: wordWrapProp, tabSize: tabSizeProp, placeholder, minHeight, maxHeight, }: CodeEditorProps):
|
|
166
|
+
declare function CodeEditorRoot({ children, className, value: valueProp, defaultValue, onChange, language: languageProp, onLanguageChange, theme, readOnly, lineNumbers: lineNumbersProp, wordWrap: wordWrapProp, tabSize: tabSizeProp, placeholder, minHeight, maxHeight, }: CodeEditorProps): react.JSX.Element;
|
|
159
167
|
declare namespace CodeEditorRoot {
|
|
160
168
|
var displayName: string;
|
|
161
169
|
}
|
|
@@ -169,6 +177,64 @@ declare const CodeEditor: typeof CodeEditorRoot & {
|
|
|
169
177
|
|
|
170
178
|
declare function useCodeEditor(): CodeEditorContextValue;
|
|
171
179
|
|
|
180
|
+
type MarkdownEditorMode = "split" | "edit" | "preview";
|
|
181
|
+
interface MarkdownEditorProps {
|
|
182
|
+
/** Controlled markdown value. */
|
|
183
|
+
value?: string;
|
|
184
|
+
/** Initial value (uncontrolled). */
|
|
185
|
+
defaultValue?: string;
|
|
186
|
+
/** Called with the new markdown whenever the document changes. */
|
|
187
|
+
onValueChange?: (markdown: string) => void;
|
|
188
|
+
/**
|
|
189
|
+
* Layout: `split` (editor + live preview side by side), `edit` (editor only),
|
|
190
|
+
* or `preview` (rendered only). Default `split`.
|
|
191
|
+
*/
|
|
192
|
+
mode?: MarkdownEditorMode;
|
|
193
|
+
/** Editor theme — `"light" | "dark" | "auto"` or a registered name. Default `auto`. */
|
|
194
|
+
theme?: string;
|
|
195
|
+
/** Prevent editing. */
|
|
196
|
+
readOnly?: boolean;
|
|
197
|
+
/** Show line numbers in the editor (default false — prose reads better without). */
|
|
198
|
+
lineNumbers?: boolean;
|
|
199
|
+
/** Wrap long lines in the editor (default true for prose). */
|
|
200
|
+
wordWrap?: boolean;
|
|
201
|
+
/** Placeholder shown when empty. */
|
|
202
|
+
placeholder?: string;
|
|
203
|
+
/** Editor height bounds (px). */
|
|
204
|
+
minHeight?: number;
|
|
205
|
+
maxHeight?: number;
|
|
206
|
+
className?: string;
|
|
207
|
+
/**
|
|
208
|
+
* Swap the markdown → HTML renderer used by the preview pane (e.g. a full
|
|
209
|
+
* CommonMark library). Defaults to the built-in lightweight renderer.
|
|
210
|
+
*/
|
|
211
|
+
renderPreview?: (markdown: string) => string;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* A markdown-aware editor: a syntax-highlighted `CodeEditor` (the registered
|
|
215
|
+
* `markdown` language) with an optional live preview pane. Controlled via
|
|
216
|
+
* `value` + `onValueChange`. The preview renders with a small built-in
|
|
217
|
+
* dependency-free renderer; pass `renderPreview` to use your own.
|
|
218
|
+
*
|
|
219
|
+
* ```tsx
|
|
220
|
+
* <MarkdownEditor value={md} onValueChange={setMd} mode="split" minHeight={240} />
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
declare function MarkdownEditor({ value, defaultValue, onValueChange, mode, theme, readOnly, lineNumbers, wordWrap, placeholder, minHeight, maxHeight, className, renderPreview, }: MarkdownEditorProps): react.JSX.Element;
|
|
224
|
+
declare namespace MarkdownEditor {
|
|
225
|
+
var displayName: string;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Tiny, dependency-free Markdown → HTML renderer for the MarkdownEditor preview
|
|
230
|
+
* pane. Covers the common subset — headings, bold/italic, inline + fenced code,
|
|
231
|
+
* links, images, blockquotes, ordered/unordered lists, hr, and paragraphs. Input
|
|
232
|
+
* is HTML-escaped first, so raw HTML in the source is shown as text (safe by
|
|
233
|
+
* default). Not a full CommonMark implementation; swap in a real renderer
|
|
234
|
+
* downstream if you need one.
|
|
235
|
+
*/
|
|
236
|
+
declare function renderMarkdown(markdown: string): string;
|
|
237
|
+
|
|
172
238
|
type TokenType = "keyword" | "string" | "comment" | "number" | "operator" | "tag" | "attribute" | "attributeValue" | "punctuation" | "function" | "type" | "variable" | "plain";
|
|
173
239
|
interface Token {
|
|
174
240
|
type: TokenType;
|
|
@@ -177,6 +243,14 @@ interface Token {
|
|
|
177
243
|
}
|
|
178
244
|
type Tokenizer = (source: string) => Token[];
|
|
179
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Minimal, dependency-free Markdown tokenizer for editor highlighting. Line-based
|
|
248
|
+
* for block constructs (fences, headings, blockquotes, list markers) plus a few
|
|
249
|
+
* safe, non-overlapping inline spans (code, bold/italic, links). Good enough to
|
|
250
|
+
* read structure at a glance — not a full CommonMark parse.
|
|
251
|
+
*/
|
|
252
|
+
declare function tokenizeMarkdown(source: string): Token[];
|
|
253
|
+
|
|
180
254
|
interface LanguageDefinition {
|
|
181
255
|
/** Display name shown in UI (e.g., "JavaScript") */
|
|
182
256
|
name: string;
|
|
@@ -194,4 +268,45 @@ declare function registerTheme(theme: ThemeDefinition): void;
|
|
|
194
268
|
declare function getTheme(name: string): ThemeDefinition | undefined;
|
|
195
269
|
declare function getRegisteredThemes(): string[];
|
|
196
270
|
|
|
197
|
-
|
|
271
|
+
/**
|
|
272
|
+
* Glue between `@particle-academy/react-fancy`'s `<InputTag>` and the
|
|
273
|
+
* `<CodeEditor>` from this package. `<CodeEditor>` renders a real DOM
|
|
274
|
+
* `<textarea>` under its overlay, so the existing `textareaAdapter`
|
|
275
|
+
* just works — this module hands it the right ref via the editor
|
|
276
|
+
* context.
|
|
277
|
+
*
|
|
278
|
+
* <CodeEditor value={code} onChange={setCode} language="markdown">
|
|
279
|
+
* <CodeEditor.Toolbar />
|
|
280
|
+
* <CodeEditor.Panel />
|
|
281
|
+
* <CodeEditorInputTag
|
|
282
|
+
* triggers={{
|
|
283
|
+
* "/": { items: commands, insert: (c) => `${c.name} ` },
|
|
284
|
+
* "@": { items: mentions, insert: (m) => `@${m.name} ` },
|
|
285
|
+
* }}
|
|
286
|
+
* />
|
|
287
|
+
* </CodeEditor>
|
|
288
|
+
*
|
|
289
|
+
* Must be rendered as a descendant of `<CodeEditor>` (it reads the
|
|
290
|
+
* editor's context). For ad-hoc wiring outside the component tree,
|
|
291
|
+
* pull the textarea ref off the context and call `textareaAdapter`
|
|
292
|
+
* yourself.
|
|
293
|
+
*/
|
|
294
|
+
/**
|
|
295
|
+
* Hook form — returns an `InputTagAdapter` bound to the surrounding
|
|
296
|
+
* `<CodeEditor>`. Useful when you want to compose your own picker UI
|
|
297
|
+
* around the same adapter.
|
|
298
|
+
*/
|
|
299
|
+
declare function useCodeEditorInputTagAdapter(): InputTagAdapter;
|
|
300
|
+
interface CodeEditorInputTagProps extends Omit<InputTagProps, "adapter"> {
|
|
301
|
+
triggers: InputTagTriggers;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Convenience wrapper — renders `<InputTag>` already wired to the
|
|
305
|
+
* surrounding `<CodeEditor>`'s textarea. Equivalent to:
|
|
306
|
+
*
|
|
307
|
+
* const adapter = useCodeEditorInputTagAdapter();
|
|
308
|
+
* return <InputTag adapter={adapter} {...rest} />;
|
|
309
|
+
*/
|
|
310
|
+
declare function CodeEditorInputTag(props: CodeEditorInputTagProps): react.JSX.Element;
|
|
311
|
+
|
|
312
|
+
export { CodeEditor, type CodeEditorContextValue, CodeEditorInputTag, type CodeEditorInputTagProps, type CodeEditorPanelProps, type CodeEditorProps, type CodeEditorStatusBarProps, type CodeEditorToolbarProps, type LanguageDefinition, MarkdownEditor, type MarkdownEditorMode, type MarkdownEditorProps, type ThemeColors, type ThemeDefinition, type Token, type TokenType, type Tokenizer, getLanguage, getRegisteredLanguages, getRegisteredThemes, getTheme, registerLanguage, registerTheme, renderMarkdown, tokenizeMarkdown, useCodeEditor, useCodeEditorInputTagAdapter };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react from 'react';
|
|
2
2
|
import { RefObject, ReactNode } from 'react';
|
|
3
|
+
import { InputTagProps, InputTagTriggers, InputTagAdapter } from '@particle-academy/react-fancy';
|
|
3
4
|
|
|
4
5
|
interface ThemeColors {
|
|
5
6
|
background: string;
|
|
@@ -114,6 +115,13 @@ interface CodeEditorContextValue {
|
|
|
114
115
|
};
|
|
115
116
|
/** Length of the current selection (0 if none) */
|
|
116
117
|
selectionLength: number;
|
|
118
|
+
/**
|
|
119
|
+
* Ref to the underlying `<textarea>` element. Exposed so external
|
|
120
|
+
* widgets — e.g. `<InputTag>`'s `textareaAdapter` — can attach to
|
|
121
|
+
* the same input the editor renders into. Will be `null` before
|
|
122
|
+
* the editor has mounted.
|
|
123
|
+
*/
|
|
124
|
+
textareaRef: RefObject<HTMLTextAreaElement | null>;
|
|
117
125
|
/** Placeholder text */
|
|
118
126
|
placeholder?: string;
|
|
119
127
|
/** @internal */
|
|
@@ -135,27 +143,27 @@ interface CodeEditorStatusBarProps {
|
|
|
135
143
|
className?: string;
|
|
136
144
|
}
|
|
137
145
|
|
|
138
|
-
declare function CodeEditorPanel({ className }: CodeEditorPanelProps):
|
|
146
|
+
declare function CodeEditorPanel({ className }: CodeEditorPanelProps): react.JSX.Element | null;
|
|
139
147
|
declare namespace CodeEditorPanel {
|
|
140
148
|
var displayName: string;
|
|
141
149
|
}
|
|
142
150
|
|
|
143
|
-
declare function CodeEditorToolbar({ children, className }: CodeEditorToolbarProps):
|
|
151
|
+
declare function CodeEditorToolbar({ children, className }: CodeEditorToolbarProps): react.JSX.Element;
|
|
144
152
|
declare namespace CodeEditorToolbar {
|
|
145
153
|
var displayName: string;
|
|
146
154
|
}
|
|
147
155
|
|
|
148
|
-
declare function CodeEditorToolbarSeparator():
|
|
156
|
+
declare function CodeEditorToolbarSeparator(): react.JSX.Element;
|
|
149
157
|
declare namespace CodeEditorToolbarSeparator {
|
|
150
158
|
var displayName: string;
|
|
151
159
|
}
|
|
152
160
|
|
|
153
|
-
declare function CodeEditorStatusBar({ children, className }: CodeEditorStatusBarProps):
|
|
161
|
+
declare function CodeEditorStatusBar({ children, className }: CodeEditorStatusBarProps): react.JSX.Element;
|
|
154
162
|
declare namespace CodeEditorStatusBar {
|
|
155
163
|
var displayName: string;
|
|
156
164
|
}
|
|
157
165
|
|
|
158
|
-
declare function CodeEditorRoot({ children, className, value: valueProp, defaultValue, onChange, language: languageProp, onLanguageChange, theme, readOnly, lineNumbers: lineNumbersProp, wordWrap: wordWrapProp, tabSize: tabSizeProp, placeholder, minHeight, maxHeight, }: CodeEditorProps):
|
|
166
|
+
declare function CodeEditorRoot({ children, className, value: valueProp, defaultValue, onChange, language: languageProp, onLanguageChange, theme, readOnly, lineNumbers: lineNumbersProp, wordWrap: wordWrapProp, tabSize: tabSizeProp, placeholder, minHeight, maxHeight, }: CodeEditorProps): react.JSX.Element;
|
|
159
167
|
declare namespace CodeEditorRoot {
|
|
160
168
|
var displayName: string;
|
|
161
169
|
}
|
|
@@ -169,6 +177,64 @@ declare const CodeEditor: typeof CodeEditorRoot & {
|
|
|
169
177
|
|
|
170
178
|
declare function useCodeEditor(): CodeEditorContextValue;
|
|
171
179
|
|
|
180
|
+
type MarkdownEditorMode = "split" | "edit" | "preview";
|
|
181
|
+
interface MarkdownEditorProps {
|
|
182
|
+
/** Controlled markdown value. */
|
|
183
|
+
value?: string;
|
|
184
|
+
/** Initial value (uncontrolled). */
|
|
185
|
+
defaultValue?: string;
|
|
186
|
+
/** Called with the new markdown whenever the document changes. */
|
|
187
|
+
onValueChange?: (markdown: string) => void;
|
|
188
|
+
/**
|
|
189
|
+
* Layout: `split` (editor + live preview side by side), `edit` (editor only),
|
|
190
|
+
* or `preview` (rendered only). Default `split`.
|
|
191
|
+
*/
|
|
192
|
+
mode?: MarkdownEditorMode;
|
|
193
|
+
/** Editor theme — `"light" | "dark" | "auto"` or a registered name. Default `auto`. */
|
|
194
|
+
theme?: string;
|
|
195
|
+
/** Prevent editing. */
|
|
196
|
+
readOnly?: boolean;
|
|
197
|
+
/** Show line numbers in the editor (default false — prose reads better without). */
|
|
198
|
+
lineNumbers?: boolean;
|
|
199
|
+
/** Wrap long lines in the editor (default true for prose). */
|
|
200
|
+
wordWrap?: boolean;
|
|
201
|
+
/** Placeholder shown when empty. */
|
|
202
|
+
placeholder?: string;
|
|
203
|
+
/** Editor height bounds (px). */
|
|
204
|
+
minHeight?: number;
|
|
205
|
+
maxHeight?: number;
|
|
206
|
+
className?: string;
|
|
207
|
+
/**
|
|
208
|
+
* Swap the markdown → HTML renderer used by the preview pane (e.g. a full
|
|
209
|
+
* CommonMark library). Defaults to the built-in lightweight renderer.
|
|
210
|
+
*/
|
|
211
|
+
renderPreview?: (markdown: string) => string;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* A markdown-aware editor: a syntax-highlighted `CodeEditor` (the registered
|
|
215
|
+
* `markdown` language) with an optional live preview pane. Controlled via
|
|
216
|
+
* `value` + `onValueChange`. The preview renders with a small built-in
|
|
217
|
+
* dependency-free renderer; pass `renderPreview` to use your own.
|
|
218
|
+
*
|
|
219
|
+
* ```tsx
|
|
220
|
+
* <MarkdownEditor value={md} onValueChange={setMd} mode="split" minHeight={240} />
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
declare function MarkdownEditor({ value, defaultValue, onValueChange, mode, theme, readOnly, lineNumbers, wordWrap, placeholder, minHeight, maxHeight, className, renderPreview, }: MarkdownEditorProps): react.JSX.Element;
|
|
224
|
+
declare namespace MarkdownEditor {
|
|
225
|
+
var displayName: string;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Tiny, dependency-free Markdown → HTML renderer for the MarkdownEditor preview
|
|
230
|
+
* pane. Covers the common subset — headings, bold/italic, inline + fenced code,
|
|
231
|
+
* links, images, blockquotes, ordered/unordered lists, hr, and paragraphs. Input
|
|
232
|
+
* is HTML-escaped first, so raw HTML in the source is shown as text (safe by
|
|
233
|
+
* default). Not a full CommonMark implementation; swap in a real renderer
|
|
234
|
+
* downstream if you need one.
|
|
235
|
+
*/
|
|
236
|
+
declare function renderMarkdown(markdown: string): string;
|
|
237
|
+
|
|
172
238
|
type TokenType = "keyword" | "string" | "comment" | "number" | "operator" | "tag" | "attribute" | "attributeValue" | "punctuation" | "function" | "type" | "variable" | "plain";
|
|
173
239
|
interface Token {
|
|
174
240
|
type: TokenType;
|
|
@@ -177,6 +243,14 @@ interface Token {
|
|
|
177
243
|
}
|
|
178
244
|
type Tokenizer = (source: string) => Token[];
|
|
179
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Minimal, dependency-free Markdown tokenizer for editor highlighting. Line-based
|
|
248
|
+
* for block constructs (fences, headings, blockquotes, list markers) plus a few
|
|
249
|
+
* safe, non-overlapping inline spans (code, bold/italic, links). Good enough to
|
|
250
|
+
* read structure at a glance — not a full CommonMark parse.
|
|
251
|
+
*/
|
|
252
|
+
declare function tokenizeMarkdown(source: string): Token[];
|
|
253
|
+
|
|
180
254
|
interface LanguageDefinition {
|
|
181
255
|
/** Display name shown in UI (e.g., "JavaScript") */
|
|
182
256
|
name: string;
|
|
@@ -194,4 +268,45 @@ declare function registerTheme(theme: ThemeDefinition): void;
|
|
|
194
268
|
declare function getTheme(name: string): ThemeDefinition | undefined;
|
|
195
269
|
declare function getRegisteredThemes(): string[];
|
|
196
270
|
|
|
197
|
-
|
|
271
|
+
/**
|
|
272
|
+
* Glue between `@particle-academy/react-fancy`'s `<InputTag>` and the
|
|
273
|
+
* `<CodeEditor>` from this package. `<CodeEditor>` renders a real DOM
|
|
274
|
+
* `<textarea>` under its overlay, so the existing `textareaAdapter`
|
|
275
|
+
* just works — this module hands it the right ref via the editor
|
|
276
|
+
* context.
|
|
277
|
+
*
|
|
278
|
+
* <CodeEditor value={code} onChange={setCode} language="markdown">
|
|
279
|
+
* <CodeEditor.Toolbar />
|
|
280
|
+
* <CodeEditor.Panel />
|
|
281
|
+
* <CodeEditorInputTag
|
|
282
|
+
* triggers={{
|
|
283
|
+
* "/": { items: commands, insert: (c) => `${c.name} ` },
|
|
284
|
+
* "@": { items: mentions, insert: (m) => `@${m.name} ` },
|
|
285
|
+
* }}
|
|
286
|
+
* />
|
|
287
|
+
* </CodeEditor>
|
|
288
|
+
*
|
|
289
|
+
* Must be rendered as a descendant of `<CodeEditor>` (it reads the
|
|
290
|
+
* editor's context). For ad-hoc wiring outside the component tree,
|
|
291
|
+
* pull the textarea ref off the context and call `textareaAdapter`
|
|
292
|
+
* yourself.
|
|
293
|
+
*/
|
|
294
|
+
/**
|
|
295
|
+
* Hook form — returns an `InputTagAdapter` bound to the surrounding
|
|
296
|
+
* `<CodeEditor>`. Useful when you want to compose your own picker UI
|
|
297
|
+
* around the same adapter.
|
|
298
|
+
*/
|
|
299
|
+
declare function useCodeEditorInputTagAdapter(): InputTagAdapter;
|
|
300
|
+
interface CodeEditorInputTagProps extends Omit<InputTagProps, "adapter"> {
|
|
301
|
+
triggers: InputTagTriggers;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Convenience wrapper — renders `<InputTag>` already wired to the
|
|
305
|
+
* surrounding `<CodeEditor>`'s textarea. Equivalent to:
|
|
306
|
+
*
|
|
307
|
+
* const adapter = useCodeEditorInputTagAdapter();
|
|
308
|
+
* return <InputTag adapter={adapter} {...rest} />;
|
|
309
|
+
*/
|
|
310
|
+
declare function CodeEditorInputTag(props: CodeEditorInputTagProps): react.JSX.Element;
|
|
311
|
+
|
|
312
|
+
export { CodeEditor, type CodeEditorContextValue, CodeEditorInputTag, type CodeEditorInputTagProps, type CodeEditorPanelProps, type CodeEditorProps, type CodeEditorStatusBarProps, type CodeEditorToolbarProps, type LanguageDefinition, MarkdownEditor, type MarkdownEditorMode, type MarkdownEditorProps, type ThemeColors, type ThemeDefinition, type Token, type TokenType, type Tokenizer, getLanguage, getRegisteredLanguages, getRegisteredThemes, getTheme, registerLanguage, registerTheme, renderMarkdown, tokenizeMarkdown, useCodeEditor, useCodeEditorInputTagAdapter };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createContext, useState, useCallback, useRef, useMemo, useSyncExternalStore, useEffect, useContext } from 'react';
|
|
2
|
-
import { cn, useControllableState } from '@particle-academy/react-fancy';
|
|
2
|
+
import { cn, useControllableState, textareaAdapter, InputTag } from '@particle-academy/react-fancy';
|
|
3
3
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
|
|
5
5
|
// src/components/CodeEditor/CodeEditor.tsx
|
|
@@ -1035,7 +1035,79 @@ var tokenizeGo = (source) => {
|
|
|
1035
1035
|
return tokens;
|
|
1036
1036
|
};
|
|
1037
1037
|
|
|
1038
|
+
// src/engine/tokenizer.ts
|
|
1039
|
+
function tok(type, s, e) {
|
|
1040
|
+
return { type, start: s, end: e };
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// src/engine/tokenizers/markdown.ts
|
|
1044
|
+
function tokenizeMarkdown(source) {
|
|
1045
|
+
const tokens = [];
|
|
1046
|
+
const lines = source.split("\n");
|
|
1047
|
+
let offset = 0;
|
|
1048
|
+
let inFence = false;
|
|
1049
|
+
for (const line of lines) {
|
|
1050
|
+
const base = offset;
|
|
1051
|
+
const len = line.length;
|
|
1052
|
+
offset += len + 1;
|
|
1053
|
+
if (/^\s*(```|~~~)/.test(line)) {
|
|
1054
|
+
tokens.push(tok("string", base, base + len));
|
|
1055
|
+
inFence = !inFence;
|
|
1056
|
+
continue;
|
|
1057
|
+
}
|
|
1058
|
+
if (inFence) {
|
|
1059
|
+
tokens.push(tok("string", base, base + len));
|
|
1060
|
+
continue;
|
|
1061
|
+
}
|
|
1062
|
+
if (/^#{1,6}\s/.test(line)) {
|
|
1063
|
+
tokens.push(tok("keyword", base, base + len));
|
|
1064
|
+
continue;
|
|
1065
|
+
}
|
|
1066
|
+
if (/^\s*>/.test(line)) {
|
|
1067
|
+
tokens.push(tok("comment", base, base + len));
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1070
|
+
const list = line.match(/^(\s*)([-*+]|\d+\.)\s/);
|
|
1071
|
+
if (list) {
|
|
1072
|
+
const start = base + list[1].length;
|
|
1073
|
+
tokens.push(tok("operator", start, start + list[2].length));
|
|
1074
|
+
}
|
|
1075
|
+
pushInline(line, base, tokens);
|
|
1076
|
+
}
|
|
1077
|
+
return tokens;
|
|
1078
|
+
}
|
|
1079
|
+
function pushInline(line, base, out) {
|
|
1080
|
+
const spans = [];
|
|
1081
|
+
const add = (type, start, end) => {
|
|
1082
|
+
spans.push(tok(type, base + start, base + end));
|
|
1083
|
+
};
|
|
1084
|
+
let m;
|
|
1085
|
+
const code = /`[^`]+`/g;
|
|
1086
|
+
while (m = code.exec(line)) add("string", m.index, m.index + m[0].length);
|
|
1087
|
+
const emph = /(\*\*|__)(?=\S)([\s\S]*?\S)\1|(\*|_)(?=\S)([\s\S]*?\S)\3/g;
|
|
1088
|
+
while (m = emph.exec(line)) add("type", m.index, m.index + m[0].length);
|
|
1089
|
+
const link = /!?\[[^\]]*\]\(([^)]+)\)/g;
|
|
1090
|
+
while (m = link.exec(line)) {
|
|
1091
|
+
add("tag", m.index, m.index + m[0].length - m[1].length - 1);
|
|
1092
|
+
const urlStart = m.index + m[0].length - m[1].length - 1;
|
|
1093
|
+
add("attributeValue", urlStart, urlStart + m[1].length);
|
|
1094
|
+
}
|
|
1095
|
+
spans.sort((a, b) => a.start - b.start || b.end - a.end);
|
|
1096
|
+
let lastEnd = -1;
|
|
1097
|
+
for (const s of spans) {
|
|
1098
|
+
if (s.start >= lastEnd) {
|
|
1099
|
+
out.push(s);
|
|
1100
|
+
lastEnd = s.end;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1038
1105
|
// src/languages/builtin.ts
|
|
1106
|
+
registerLanguage({
|
|
1107
|
+
name: "Markdown",
|
|
1108
|
+
aliases: ["md", "markdown", "mkd"],
|
|
1109
|
+
tokenize: tokenizeMarkdown
|
|
1110
|
+
});
|
|
1039
1111
|
registerLanguage({
|
|
1040
1112
|
name: "JavaScript",
|
|
1041
1113
|
aliases: ["js", "javascript", "jsx"],
|
|
@@ -1576,6 +1648,7 @@ function CodeEditorRoot({
|
|
|
1576
1648
|
},
|
|
1577
1649
|
cursorPosition,
|
|
1578
1650
|
selectionLength,
|
|
1651
|
+
textareaRef: engineReturn.textareaRef,
|
|
1579
1652
|
placeholder,
|
|
1580
1653
|
_engineReturn: engineReturn,
|
|
1581
1654
|
_minHeight: minHeight,
|
|
@@ -1605,6 +1678,164 @@ var CodeEditor = Object.assign(CodeEditorRoot, {
|
|
|
1605
1678
|
StatusBar: CodeEditorStatusBar
|
|
1606
1679
|
});
|
|
1607
1680
|
|
|
1608
|
-
|
|
1681
|
+
// src/engine/markdown-render.ts
|
|
1682
|
+
function escapeHtml2(s) {
|
|
1683
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1684
|
+
}
|
|
1685
|
+
function inline(text) {
|
|
1686
|
+
return text.replace(/!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/g, (_m, alt, src, title) => `<img src="${src}" alt="${alt}"${title ? ` title="${title}"` : ""}>`).replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/g, (_m, t, href, title) => `<a href="${href}"${title ? ` title="${title}"` : ""} rel="noopener">${t}</a>`).replace(/`([^`]+)`/g, (_m, c) => `<code>${c}</code>`).replace(/(\*\*|__)(?=\S)([\s\S]*?\S)\1/g, "<strong>$2</strong>").replace(/(\*|_)(?=\S)([\s\S]*?\S)\1/g, "<em>$2</em>");
|
|
1687
|
+
}
|
|
1688
|
+
function renderMarkdown(markdown) {
|
|
1689
|
+
const lines = markdown.replace(/\r\n?/g, "\n").split("\n");
|
|
1690
|
+
const out = [];
|
|
1691
|
+
let i = 0;
|
|
1692
|
+
let para = [];
|
|
1693
|
+
let listType = null;
|
|
1694
|
+
const flushPara = () => {
|
|
1695
|
+
if (para.length) {
|
|
1696
|
+
out.push(`<p>${inline(para.join(" "))}</p>`);
|
|
1697
|
+
para = [];
|
|
1698
|
+
}
|
|
1699
|
+
};
|
|
1700
|
+
const closeList = () => {
|
|
1701
|
+
if (listType) {
|
|
1702
|
+
out.push(`</${listType}>`);
|
|
1703
|
+
listType = null;
|
|
1704
|
+
}
|
|
1705
|
+
};
|
|
1706
|
+
while (i < lines.length) {
|
|
1707
|
+
const line = lines[i];
|
|
1708
|
+
const fence = line.match(/^\s*(```|~~~)(.*)$/);
|
|
1709
|
+
if (fence) {
|
|
1710
|
+
flushPara();
|
|
1711
|
+
closeList();
|
|
1712
|
+
const lang = fence[2].trim();
|
|
1713
|
+
const body = [];
|
|
1714
|
+
i++;
|
|
1715
|
+
while (i < lines.length && !/^\s*(```|~~~)/.test(lines[i])) {
|
|
1716
|
+
body.push(lines[i]);
|
|
1717
|
+
i++;
|
|
1718
|
+
}
|
|
1719
|
+
i++;
|
|
1720
|
+
out.push(`<pre><code${lang ? ` class="language-${escapeHtml2(lang)}"` : ""}>${escapeHtml2(body.join("\n"))}</code></pre>`);
|
|
1721
|
+
continue;
|
|
1722
|
+
}
|
|
1723
|
+
const esc = escapeHtml2(line);
|
|
1724
|
+
if (/^\s*$/.test(line)) {
|
|
1725
|
+
flushPara();
|
|
1726
|
+
closeList();
|
|
1727
|
+
i++;
|
|
1728
|
+
continue;
|
|
1729
|
+
}
|
|
1730
|
+
const heading = esc.match(/^(#{1,6})\s+(.*)$/);
|
|
1731
|
+
if (heading) {
|
|
1732
|
+
flushPara();
|
|
1733
|
+
closeList();
|
|
1734
|
+
const level = heading[1].length;
|
|
1735
|
+
out.push(`<h${level}>${inline(heading[2])}</h${level}>`);
|
|
1736
|
+
i++;
|
|
1737
|
+
continue;
|
|
1738
|
+
}
|
|
1739
|
+
if (/^\s*([-*_])(\s*\1){2,}\s*$/.test(line)) {
|
|
1740
|
+
flushPara();
|
|
1741
|
+
closeList();
|
|
1742
|
+
out.push("<hr>");
|
|
1743
|
+
i++;
|
|
1744
|
+
continue;
|
|
1745
|
+
}
|
|
1746
|
+
const quote = esc.match(/^\s*>\s?(.*)$/);
|
|
1747
|
+
if (quote) {
|
|
1748
|
+
flushPara();
|
|
1749
|
+
closeList();
|
|
1750
|
+
out.push(`<blockquote>${inline(quote[1])}</blockquote>`);
|
|
1751
|
+
i++;
|
|
1752
|
+
continue;
|
|
1753
|
+
}
|
|
1754
|
+
const ol = esc.match(/^\s*\d+\.\s+(.*)$/);
|
|
1755
|
+
const ul = esc.match(/^\s*[-*+]\s+(.*)$/);
|
|
1756
|
+
if (ol || ul) {
|
|
1757
|
+
flushPara();
|
|
1758
|
+
const type = ol ? "ol" : "ul";
|
|
1759
|
+
if (listType !== type) {
|
|
1760
|
+
closeList();
|
|
1761
|
+
out.push(`<${type}>`);
|
|
1762
|
+
listType = type;
|
|
1763
|
+
}
|
|
1764
|
+
out.push(`<li>${inline((ol ?? ul)[1])}</li>`);
|
|
1765
|
+
i++;
|
|
1766
|
+
continue;
|
|
1767
|
+
}
|
|
1768
|
+
closeList();
|
|
1769
|
+
para.push(esc);
|
|
1770
|
+
i++;
|
|
1771
|
+
}
|
|
1772
|
+
flushPara();
|
|
1773
|
+
closeList();
|
|
1774
|
+
return out.join("\n");
|
|
1775
|
+
}
|
|
1776
|
+
function MarkdownEditor({
|
|
1777
|
+
value,
|
|
1778
|
+
defaultValue = "",
|
|
1779
|
+
onValueChange,
|
|
1780
|
+
mode = "split",
|
|
1781
|
+
theme = "auto",
|
|
1782
|
+
readOnly = false,
|
|
1783
|
+
lineNumbers = false,
|
|
1784
|
+
wordWrap = true,
|
|
1785
|
+
placeholder,
|
|
1786
|
+
minHeight,
|
|
1787
|
+
maxHeight,
|
|
1788
|
+
className,
|
|
1789
|
+
renderPreview = renderMarkdown
|
|
1790
|
+
}) {
|
|
1791
|
+
const [content, setContent] = useControllableState(value, defaultValue, onValueChange);
|
|
1792
|
+
const showEditor = mode !== "preview";
|
|
1793
|
+
const showPreview = mode !== "edit";
|
|
1794
|
+
return /* @__PURE__ */ jsxs(
|
|
1795
|
+
"div",
|
|
1796
|
+
{
|
|
1797
|
+
"data-fancy-markdown-editor": "",
|
|
1798
|
+
"data-mode": mode,
|
|
1799
|
+
className: cn("fancy-md-editor", className),
|
|
1800
|
+
children: [
|
|
1801
|
+
showEditor && /* @__PURE__ */ jsx("div", { className: "fancy-md-editor-pane", children: /* @__PURE__ */ jsx(
|
|
1802
|
+
CodeEditor,
|
|
1803
|
+
{
|
|
1804
|
+
value: content,
|
|
1805
|
+
onChange: setContent,
|
|
1806
|
+
language: "markdown",
|
|
1807
|
+
theme,
|
|
1808
|
+
readOnly,
|
|
1809
|
+
lineNumbers,
|
|
1810
|
+
wordWrap,
|
|
1811
|
+
placeholder,
|
|
1812
|
+
minHeight,
|
|
1813
|
+
maxHeight,
|
|
1814
|
+
children: /* @__PURE__ */ jsx(CodeEditor.Panel, {})
|
|
1815
|
+
}
|
|
1816
|
+
) }),
|
|
1817
|
+
showPreview && /* @__PURE__ */ jsx(
|
|
1818
|
+
"div",
|
|
1819
|
+
{
|
|
1820
|
+
"data-fancy-markdown-preview": "",
|
|
1821
|
+
className: "fancy-md-preview",
|
|
1822
|
+
dangerouslySetInnerHTML: { __html: renderPreview(content) }
|
|
1823
|
+
}
|
|
1824
|
+
)
|
|
1825
|
+
]
|
|
1826
|
+
}
|
|
1827
|
+
);
|
|
1828
|
+
}
|
|
1829
|
+
MarkdownEditor.displayName = "MarkdownEditor";
|
|
1830
|
+
function useCodeEditorInputTagAdapter() {
|
|
1831
|
+
const ctx = useCodeEditor();
|
|
1832
|
+
return useMemo(() => textareaAdapter(ctx.textareaRef), [ctx.textareaRef]);
|
|
1833
|
+
}
|
|
1834
|
+
function CodeEditorInputTag(props) {
|
|
1835
|
+
const adapter = useCodeEditorInputTagAdapter();
|
|
1836
|
+
return /* @__PURE__ */ jsx(InputTag, { adapter, ...props });
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
export { CodeEditor, CodeEditorInputTag, MarkdownEditor, getLanguage, getRegisteredLanguages, getRegisteredThemes, getTheme, registerLanguage, registerTheme, renderMarkdown, tokenizeMarkdown, useCodeEditor, useCodeEditorInputTagAdapter };
|
|
1609
1840
|
//# sourceMappingURL=index.js.map
|
|
1610
1841
|
//# sourceMappingURL=index.js.map
|