@djangocfg/widget-code 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +38 -0
- package/package.json +119 -0
- package/src/diff-viewer/DiffViewer.tsx +168 -0
- package/src/diff-viewer/README.md +33 -0
- package/src/diff-viewer/components/CopyButton.tsx +49 -0
- package/src/diff-viewer/components/DiffLineContent.tsx +48 -0
- package/src/diff-viewer/components/SplitView.tsx +270 -0
- package/src/diff-viewer/components/UnifiedView.tsx +189 -0
- package/src/diff-viewer/diff-viewer.stories.tsx +168 -0
- package/src/diff-viewer/hooks/useDiff.ts +47 -0
- package/src/diff-viewer/hooks/useHighlighter.ts +54 -0
- package/src/diff-viewer/index.ts +22 -0
- package/src/diff-viewer/lazy.tsx +25 -0
- package/src/diff-viewer/types.ts +125 -0
- package/src/diff-viewer/utils/computeDiff.ts +171 -0
- package/src/editor/README.md +187 -0
- package/src/editor/components/DiffEditor.tsx +158 -0
- package/src/editor/components/Editor.tsx +293 -0
- package/src/editor/components/index.ts +2 -0
- package/src/editor/context/EditorProvider.tsx +212 -0
- package/src/editor/context/index.ts +1 -0
- package/src/editor/editor.stories.tsx +397 -0
- package/src/editor/hooks/index.ts +4 -0
- package/src/editor/hooks/useEditor.ts +36 -0
- package/src/editor/hooks/useEditorTheme.ts +91 -0
- package/src/editor/hooks/useLanguage.ts +29 -0
- package/src/editor/hooks/useMonaco.ts +79 -0
- package/src/editor/index.ts +16 -0
- package/src/editor/lazy.tsx +76 -0
- package/src/editor/lib/index.ts +2 -0
- package/src/editor/lib/languages.ts +227 -0
- package/src/editor/lib/themes.ts +42 -0
- package/src/editor/types/index.ts +137 -0
- package/src/editor/workers/index.ts +1 -0
- package/src/editor/workers/setup.ts +58 -0
- package/src/index.ts +10 -0
- package/src/json-editor/JsonEditor.tsx +115 -0
- package/src/json-editor/index.ts +1 -0
- package/src/json-editor/lazy.tsx +24 -0
- package/src/markdown-message/ActionRow.tsx +53 -0
- package/src/markdown-message/ChatMessageRow.tsx +103 -0
- package/src/markdown-message/CodeBlock.tsx +137 -0
- package/src/markdown-message/CollapseToggle.tsx +69 -0
- package/src/markdown-message/MarkdownMessage.tsx +376 -0
- package/src/markdown-message/README.md +221 -0
- package/src/markdown-message/builtinRules.tsx +92 -0
- package/src/markdown-message/components.tsx +224 -0
- package/src/markdown-message/index.ts +22 -0
- package/src/markdown-message/linkRules.ts +83 -0
- package/src/markdown-message/plainText.ts +83 -0
- package/src/markdown-message/sanitize.ts +88 -0
- package/src/markdown-message/streamingBlocks.ts +168 -0
- package/src/markdown-message/types.ts +168 -0
- package/src/panel-header/CodePanelHeader.tsx +121 -0
- package/src/panel-header/index.ts +1 -0
- package/src/pretty-code/PrettyCode.client.tsx +402 -0
- package/src/pretty-code/README.md +81 -0
- package/src/pretty-code/index.tsx +72 -0
- package/src/pretty-code/lazy.tsx +69 -0
- package/src/pretty-code/pretty-code.stories.tsx +353 -0
- package/src/pretty-code/registerPrismLanguages.ts +116 -0
- package/src/role-tokens.ts +39 -0
- package/src/use-collapsible-content.ts +236 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `@djangocfg/widget-code/editor` subpath entrypoint.
|
|
5
|
+
*
|
|
6
|
+
* Monaco itself weighs ~550 KB minified — we must never pay that cost
|
|
7
|
+
* unless the editor actually mounts. The `Lazy*` wrappers here dynamically
|
|
8
|
+
* import the `Editor` and `DiffEditor` components, which transitively pull
|
|
9
|
+
* in `monaco-editor` and the worker setup.
|
|
10
|
+
*
|
|
11
|
+
* The rest of the surface (types, EditorProvider, useMonaco hook, helpers)
|
|
12
|
+
* is light:
|
|
13
|
+
* - `monaco-editor` is referenced only as a TS type (`type *`) — erased
|
|
14
|
+
* at compile time.
|
|
15
|
+
* - `useMonaco` performs a dynamic `import('monaco-editor')` itself, so
|
|
16
|
+
* importing the hook is free until the caller actually invokes it.
|
|
17
|
+
*
|
|
18
|
+
* That means consumers can wire up keyboard shortcuts, theme toggles, or
|
|
19
|
+
* file-state hooks at the top of their tree without paying Monaco's cost,
|
|
20
|
+
* and only render `<LazyEditor>` lower in the tree where it's needed.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { createLazyComponent, LoadingFallback } from '@djangocfg/widget-kit/lazy';
|
|
24
|
+
import type { EditorProps, DiffEditorProps } from './types';
|
|
25
|
+
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// Lazy components
|
|
28
|
+
// ============================================================================
|
|
29
|
+
|
|
30
|
+
export const LazyEditor = createLazyComponent<EditorProps>(
|
|
31
|
+
() => import('./components/Editor').then((m) => ({ default: m.Editor })),
|
|
32
|
+
{
|
|
33
|
+
displayName: 'LazyEditor',
|
|
34
|
+
fallback: <LoadingFallback minHeight={320} text="Loading editor…" />,
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
export const LazyDiffEditor = createLazyComponent<DiffEditorProps>(
|
|
39
|
+
() => import('./components/DiffEditor').then((m) => ({ default: m.DiffEditor })),
|
|
40
|
+
{
|
|
41
|
+
displayName: 'LazyDiffEditor',
|
|
42
|
+
fallback: <LoadingFallback minHeight={320} text="Loading diff editor…" />,
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// `Editor` / `DiffEditor` are the plain named exports — same lazy components,
|
|
47
|
+
// kept so callers can `import { Editor } from '@djangocfg/widget-code/editor'`
|
|
48
|
+
// without knowing about the `Lazy*` naming.
|
|
49
|
+
export { LazyEditor as Editor };
|
|
50
|
+
export { LazyDiffEditor as DiffEditor };
|
|
51
|
+
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// Light surface
|
|
54
|
+
// ============================================================================
|
|
55
|
+
|
|
56
|
+
// Hooks — `useMonaco` does its own dynamic import; the others are pure.
|
|
57
|
+
export {
|
|
58
|
+
useMonaco,
|
|
59
|
+
useEditor,
|
|
60
|
+
useLanguage,
|
|
61
|
+
useEditorTheme,
|
|
62
|
+
} from './hooks';
|
|
63
|
+
|
|
64
|
+
// Provider + context hook
|
|
65
|
+
export { EditorProvider, useEditorContext } from './context';
|
|
66
|
+
|
|
67
|
+
// All types
|
|
68
|
+
export type {
|
|
69
|
+
EditorFile,
|
|
70
|
+
EditorOptions,
|
|
71
|
+
EditorProps,
|
|
72
|
+
EditorContextValue,
|
|
73
|
+
UseEditorReturn,
|
|
74
|
+
UseMonacoReturn,
|
|
75
|
+
DiffEditorProps,
|
|
76
|
+
} from './types';
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language Detection & Mapping
|
|
3
|
+
*
|
|
4
|
+
* Maps file extensions to Monaco Editor language IDs.
|
|
5
|
+
* Monaco supports 80+ languages out of the box.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* File extension to Monaco language ID mapping
|
|
10
|
+
*/
|
|
11
|
+
export const LANGUAGE_MAP: Record<string, string> = {
|
|
12
|
+
// Web
|
|
13
|
+
'.html': 'html',
|
|
14
|
+
'.htm': 'html',
|
|
15
|
+
'.xhtml': 'html',
|
|
16
|
+
'.vue': 'html',
|
|
17
|
+
'.svelte': 'html',
|
|
18
|
+
|
|
19
|
+
// CSS
|
|
20
|
+
'.css': 'css',
|
|
21
|
+
'.scss': 'scss',
|
|
22
|
+
'.sass': 'scss',
|
|
23
|
+
'.less': 'less',
|
|
24
|
+
|
|
25
|
+
// JavaScript/TypeScript
|
|
26
|
+
'.js': 'javascript',
|
|
27
|
+
'.mjs': 'javascript',
|
|
28
|
+
'.cjs': 'javascript',
|
|
29
|
+
'.jsx': 'javascript',
|
|
30
|
+
'.ts': 'typescript',
|
|
31
|
+
'.tsx': 'typescript',
|
|
32
|
+
'.mts': 'typescript',
|
|
33
|
+
'.cts': 'typescript',
|
|
34
|
+
|
|
35
|
+
// Data formats
|
|
36
|
+
'.json': 'json',
|
|
37
|
+
'.jsonc': 'json',
|
|
38
|
+
'.json5': 'json',
|
|
39
|
+
'.yaml': 'yaml',
|
|
40
|
+
'.yml': 'yaml',
|
|
41
|
+
'.toml': 'ini',
|
|
42
|
+
'.xml': 'xml',
|
|
43
|
+
'.svg': 'xml',
|
|
44
|
+
'.xsl': 'xml',
|
|
45
|
+
'.xsd': 'xml',
|
|
46
|
+
|
|
47
|
+
// Markdown & Documentation
|
|
48
|
+
'.md': 'markdown',
|
|
49
|
+
'.mdx': 'markdown',
|
|
50
|
+
'.markdown': 'markdown',
|
|
51
|
+
'.rst': 'restructuredtext',
|
|
52
|
+
'.txt': 'plaintext',
|
|
53
|
+
'.text': 'plaintext',
|
|
54
|
+
|
|
55
|
+
// Programming languages
|
|
56
|
+
'.py': 'python',
|
|
57
|
+
'.pyw': 'python',
|
|
58
|
+
'.pyi': 'python',
|
|
59
|
+
'.rb': 'ruby',
|
|
60
|
+
'.rake': 'ruby',
|
|
61
|
+
'.gemspec': 'ruby',
|
|
62
|
+
'.php': 'php',
|
|
63
|
+
'.phtml': 'php',
|
|
64
|
+
'.java': 'java',
|
|
65
|
+
'.kt': 'kotlin',
|
|
66
|
+
'.kts': 'kotlin',
|
|
67
|
+
'.scala': 'scala',
|
|
68
|
+
'.go': 'go',
|
|
69
|
+
'.rs': 'rust',
|
|
70
|
+
'.swift': 'swift',
|
|
71
|
+
'.c': 'c',
|
|
72
|
+
'.h': 'c',
|
|
73
|
+
'.cpp': 'cpp',
|
|
74
|
+
'.cc': 'cpp',
|
|
75
|
+
'.cxx': 'cpp',
|
|
76
|
+
'.hpp': 'cpp',
|
|
77
|
+
'.hxx': 'cpp',
|
|
78
|
+
'.cs': 'csharp',
|
|
79
|
+
'.fs': 'fsharp',
|
|
80
|
+
'.fsx': 'fsharp',
|
|
81
|
+
'.vb': 'vb',
|
|
82
|
+
'.lua': 'lua',
|
|
83
|
+
'.r': 'r',
|
|
84
|
+
'.R': 'r',
|
|
85
|
+
'.m': 'objective-c',
|
|
86
|
+
'.mm': 'objective-c',
|
|
87
|
+
'.pl': 'perl',
|
|
88
|
+
'.pm': 'perl',
|
|
89
|
+
'.ex': 'elixir',
|
|
90
|
+
'.exs': 'elixir',
|
|
91
|
+
'.erl': 'erlang',
|
|
92
|
+
'.hrl': 'erlang',
|
|
93
|
+
'.clj': 'clojure',
|
|
94
|
+
'.cljs': 'clojure',
|
|
95
|
+
'.cljc': 'clojure',
|
|
96
|
+
'.hs': 'haskell',
|
|
97
|
+
'.lhs': 'haskell',
|
|
98
|
+
'.ml': 'fsharp',
|
|
99
|
+
'.mli': 'fsharp',
|
|
100
|
+
'.dart': 'dart',
|
|
101
|
+
'.groovy': 'groovy',
|
|
102
|
+
'.gradle': 'groovy',
|
|
103
|
+
'.jl': 'julia',
|
|
104
|
+
|
|
105
|
+
// Shell & Scripts
|
|
106
|
+
'.sh': 'shell',
|
|
107
|
+
'.bash': 'shell',
|
|
108
|
+
'.zsh': 'shell',
|
|
109
|
+
'.fish': 'shell',
|
|
110
|
+
'.ps1': 'powershell',
|
|
111
|
+
'.psm1': 'powershell',
|
|
112
|
+
'.psd1': 'powershell',
|
|
113
|
+
'.bat': 'bat',
|
|
114
|
+
'.cmd': 'bat',
|
|
115
|
+
|
|
116
|
+
// Config files
|
|
117
|
+
'.ini': 'ini',
|
|
118
|
+
'.cfg': 'ini',
|
|
119
|
+
'.conf': 'ini',
|
|
120
|
+
'.properties': 'ini',
|
|
121
|
+
'.env': 'ini',
|
|
122
|
+
'.gitignore': 'ini',
|
|
123
|
+
'.gitattributes': 'ini',
|
|
124
|
+
'.editorconfig': 'ini',
|
|
125
|
+
'.npmrc': 'ini',
|
|
126
|
+
|
|
127
|
+
// Database
|
|
128
|
+
'.sql': 'sql',
|
|
129
|
+
'.mysql': 'mysql',
|
|
130
|
+
'.pgsql': 'pgsql',
|
|
131
|
+
'.plsql': 'plsql',
|
|
132
|
+
'.redis': 'redis',
|
|
133
|
+
|
|
134
|
+
// Templates
|
|
135
|
+
'.hbs': 'handlebars',
|
|
136
|
+
'.handlebars': 'handlebars',
|
|
137
|
+
'.mustache': 'handlebars',
|
|
138
|
+
'.ejs': 'html',
|
|
139
|
+
'.pug': 'pug',
|
|
140
|
+
'.jade': 'pug',
|
|
141
|
+
'.twig': 'twig',
|
|
142
|
+
'.liquid': 'liquid',
|
|
143
|
+
|
|
144
|
+
// GraphQL
|
|
145
|
+
'.graphql': 'graphql',
|
|
146
|
+
'.gql': 'graphql',
|
|
147
|
+
|
|
148
|
+
// Docker
|
|
149
|
+
'.dockerfile': 'dockerfile',
|
|
150
|
+
|
|
151
|
+
// Other
|
|
152
|
+
'.diff': 'diff',
|
|
153
|
+
'.patch': 'diff',
|
|
154
|
+
'.log': 'log',
|
|
155
|
+
'.tex': 'latex',
|
|
156
|
+
'.cls': 'latex',
|
|
157
|
+
'.sty': 'latex',
|
|
158
|
+
'.proto': 'protobuf',
|
|
159
|
+
'.sol': 'sol',
|
|
160
|
+
'.asm': 'mips',
|
|
161
|
+
'.s': 'mips',
|
|
162
|
+
'.wasm': 'wasm',
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Special filename mappings (without extension)
|
|
167
|
+
*/
|
|
168
|
+
const FILENAME_MAP: Record<string, string> = {
|
|
169
|
+
Dockerfile: 'dockerfile',
|
|
170
|
+
'docker-compose.yml': 'yaml',
|
|
171
|
+
'docker-compose.yaml': 'yaml',
|
|
172
|
+
Makefile: 'makefile',
|
|
173
|
+
makefile: 'makefile',
|
|
174
|
+
Gemfile: 'ruby',
|
|
175
|
+
Rakefile: 'ruby',
|
|
176
|
+
Jenkinsfile: 'groovy',
|
|
177
|
+
Vagrantfile: 'ruby',
|
|
178
|
+
'.bashrc': 'shell',
|
|
179
|
+
'.bash_profile': 'shell',
|
|
180
|
+
'.zshrc': 'shell',
|
|
181
|
+
'.profile': 'shell',
|
|
182
|
+
'.vimrc': 'plaintext',
|
|
183
|
+
'.gitconfig': 'ini',
|
|
184
|
+
'.htaccess': 'ini',
|
|
185
|
+
'nginx.conf': 'ini',
|
|
186
|
+
'package.json': 'json',
|
|
187
|
+
'tsconfig.json': 'json',
|
|
188
|
+
'jsconfig.json': 'json',
|
|
189
|
+
'.prettierrc': 'json',
|
|
190
|
+
'.eslintrc': 'json',
|
|
191
|
+
'composer.json': 'json',
|
|
192
|
+
'Cargo.toml': 'ini',
|
|
193
|
+
'go.mod': 'go',
|
|
194
|
+
'go.sum': 'plaintext',
|
|
195
|
+
'requirements.txt': 'plaintext',
|
|
196
|
+
'pyproject.toml': 'ini',
|
|
197
|
+
'setup.py': 'python',
|
|
198
|
+
'setup.cfg': 'ini',
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get Monaco language ID from file extension
|
|
203
|
+
*/
|
|
204
|
+
export function getLanguageByExtension(extension: string): string {
|
|
205
|
+
const ext = extension.startsWith('.') ? extension.toLowerCase() : `.${extension.toLowerCase()}`;
|
|
206
|
+
return LANGUAGE_MAP[ext] || 'plaintext';
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Get Monaco language ID from filename
|
|
211
|
+
* Checks special filenames first, then falls back to extension
|
|
212
|
+
*/
|
|
213
|
+
export function getLanguageByFilename(filename: string): string {
|
|
214
|
+
// Check special filenames first
|
|
215
|
+
if (FILENAME_MAP[filename]) {
|
|
216
|
+
return FILENAME_MAP[filename];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Extract extension
|
|
220
|
+
const lastDot = filename.lastIndexOf('.');
|
|
221
|
+
if (lastDot === -1) {
|
|
222
|
+
return 'plaintext';
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const extension = filename.slice(lastDot).toLowerCase();
|
|
226
|
+
return LANGUAGE_MAP[extension] || 'plaintext';
|
|
227
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Editor Themes
|
|
3
|
+
*
|
|
4
|
+
* The editor is always dark (devtool-style), so only dark themes are exposed.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type EditorTheme = 'vs-dark' | 'hc-black' | 'cmdop-dark';
|
|
8
|
+
|
|
9
|
+
export const EDITOR_THEMES = {
|
|
10
|
+
// Built-in themes
|
|
11
|
+
'vs-dark': 'Dark (VS Code)',
|
|
12
|
+
'hc-black': 'High Contrast Dark',
|
|
13
|
+
// Custom themes
|
|
14
|
+
'cmdop-dark': 'CMDOP Dark',
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* CMDOP Dark Theme Definition
|
|
19
|
+
* Can be registered with monaco.editor.defineTheme()
|
|
20
|
+
*/
|
|
21
|
+
export const cmdopDarkTheme = {
|
|
22
|
+
base: 'vs-dark' as const,
|
|
23
|
+
inherit: true,
|
|
24
|
+
rules: [
|
|
25
|
+
{ token: 'comment', foreground: '6A9955', fontStyle: 'italic' },
|
|
26
|
+
{ token: 'keyword', foreground: 'C586C0' },
|
|
27
|
+
{ token: 'string', foreground: 'CE9178' },
|
|
28
|
+
{ token: 'number', foreground: 'B5CEA8' },
|
|
29
|
+
{ token: 'type', foreground: '4EC9B0' },
|
|
30
|
+
{ token: 'function', foreground: 'DCDCAA' },
|
|
31
|
+
{ token: 'variable', foreground: '9CDCFE' },
|
|
32
|
+
],
|
|
33
|
+
colors: {
|
|
34
|
+
'editor.background': '#1a1a1a',
|
|
35
|
+
'editor.foreground': '#D4D4D4',
|
|
36
|
+
'editor.lineHighlightBackground': '#2d2d2d',
|
|
37
|
+
'editor.selectionBackground': '#264F78',
|
|
38
|
+
'editorCursor.foreground': '#AEAFAD',
|
|
39
|
+
'editorLineNumber.foreground': '#858585',
|
|
40
|
+
'editorLineNumber.activeForeground': '#C6C6C6',
|
|
41
|
+
},
|
|
42
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type * as monaco from 'monaco-editor';
|
|
2
|
+
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// Editor Types
|
|
5
|
+
// ============================================================================
|
|
6
|
+
|
|
7
|
+
export interface EditorFile {
|
|
8
|
+
/** Unique file path */
|
|
9
|
+
path: string;
|
|
10
|
+
/** File content */
|
|
11
|
+
content: string;
|
|
12
|
+
/** Detected or specified language */
|
|
13
|
+
language: string;
|
|
14
|
+
/** Whether file has unsaved changes */
|
|
15
|
+
isDirty: boolean;
|
|
16
|
+
/** Monaco model reference */
|
|
17
|
+
model?: monaco.editor.ITextModel;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface EditorOptions {
|
|
21
|
+
/** Editor theme: 'vs' | 'vs-dark' | 'hc-black' | custom */
|
|
22
|
+
theme?: string;
|
|
23
|
+
/** Font size in pixels */
|
|
24
|
+
fontSize?: number;
|
|
25
|
+
/** Font family */
|
|
26
|
+
fontFamily?: string;
|
|
27
|
+
/** Tab size */
|
|
28
|
+
tabSize?: number;
|
|
29
|
+
/** Insert spaces instead of tabs */
|
|
30
|
+
insertSpaces?: boolean;
|
|
31
|
+
/** Word wrap mode */
|
|
32
|
+
wordWrap?: 'off' | 'on' | 'wordWrapColumn' | 'bounded';
|
|
33
|
+
/** Show minimap */
|
|
34
|
+
minimap?: boolean;
|
|
35
|
+
/** Show line numbers */
|
|
36
|
+
lineNumbers?: 'on' | 'off' | 'relative' | 'interval';
|
|
37
|
+
/** Read-only mode */
|
|
38
|
+
readOnly?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface EditorProps {
|
|
42
|
+
/** File content */
|
|
43
|
+
value?: string;
|
|
44
|
+
/** Programming language for syntax highlighting */
|
|
45
|
+
language?: string;
|
|
46
|
+
/** Called when content changes */
|
|
47
|
+
onChange?: (value: string) => void;
|
|
48
|
+
/** Called when editor is mounted */
|
|
49
|
+
onMount?: (editor: monaco.editor.IStandaloneCodeEditor) => void;
|
|
50
|
+
/** Editor options */
|
|
51
|
+
options?: EditorOptions;
|
|
52
|
+
/** CSS class name */
|
|
53
|
+
className?: string;
|
|
54
|
+
/** Height — fixed px, CSS string, or '100%' (default: '100%'). Ignored when autoHeight=true */
|
|
55
|
+
height?: string | number;
|
|
56
|
+
/** Width (default: '100%') */
|
|
57
|
+
width?: string | number;
|
|
58
|
+
/** Auto-resize height to fit content. Editor grows/shrinks with content lines */
|
|
59
|
+
autoHeight?: boolean;
|
|
60
|
+
/** Min height in px when autoHeight is enabled (default: 100) */
|
|
61
|
+
minHeight?: number;
|
|
62
|
+
/** Max height in px when autoHeight is enabled (default: 600) */
|
|
63
|
+
maxHeight?: number;
|
|
64
|
+
/** Focus the editor once Monaco mounts. Pair with `key={path}` upstream
|
|
65
|
+
* for per-file focus reset. */
|
|
66
|
+
autoFocus?: boolean;
|
|
67
|
+
/** Bound to Cmd/Ctrl+S inside the editor via Monaco's command palette.
|
|
68
|
+
* Receives the current value. The browser default is suppressed by
|
|
69
|
+
* Monaco when a command is registered for that chord. */
|
|
70
|
+
onSave?: (value: string) => void;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface DiffEditorProps {
|
|
74
|
+
/** Original content (left side) */
|
|
75
|
+
original: string;
|
|
76
|
+
/** Modified content (right side) */
|
|
77
|
+
modified: string;
|
|
78
|
+
/** Programming language */
|
|
79
|
+
language?: string;
|
|
80
|
+
/** Editor options */
|
|
81
|
+
options?: EditorOptions;
|
|
82
|
+
/** CSS class name */
|
|
83
|
+
className?: string;
|
|
84
|
+
/** Height (default: '100%') */
|
|
85
|
+
height?: string | number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ============================================================================
|
|
89
|
+
// Context Types
|
|
90
|
+
// ============================================================================
|
|
91
|
+
|
|
92
|
+
export interface EditorContextValue {
|
|
93
|
+
/** Currently open files */
|
|
94
|
+
openFiles: EditorFile[];
|
|
95
|
+
/** Currently active file */
|
|
96
|
+
activeFile: EditorFile | null;
|
|
97
|
+
/** Monaco instance (null during SSR) */
|
|
98
|
+
monaco: typeof monaco | null;
|
|
99
|
+
/** Editor instance */
|
|
100
|
+
editor: monaco.editor.IStandaloneCodeEditor | null;
|
|
101
|
+
/** Whether editor is ready */
|
|
102
|
+
isReady: boolean;
|
|
103
|
+
|
|
104
|
+
// File operations
|
|
105
|
+
openFile: (path: string, content: string, language?: string) => void;
|
|
106
|
+
closeFile: (path: string) => void;
|
|
107
|
+
setActiveFile: (path: string) => void;
|
|
108
|
+
updateContent: (path: string, content: string) => void;
|
|
109
|
+
saveFile: (path: string) => Promise<void>;
|
|
110
|
+
|
|
111
|
+
// State queries
|
|
112
|
+
isDirty: (path: string) => boolean;
|
|
113
|
+
getContent: (path: string) => string | null;
|
|
114
|
+
getFile: (path: string) => EditorFile | null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ============================================================================
|
|
118
|
+
// Hook Return Types
|
|
119
|
+
// ============================================================================
|
|
120
|
+
|
|
121
|
+
export interface UseEditorReturn {
|
|
122
|
+
/** Editor instance */
|
|
123
|
+
editor: monaco.editor.IStandaloneCodeEditor | null;
|
|
124
|
+
/** Whether editor is mounted and ready */
|
|
125
|
+
isReady: boolean;
|
|
126
|
+
/** Set editor reference */
|
|
127
|
+
setEditor: (editor: monaco.editor.IStandaloneCodeEditor | null) => void;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface UseMonacoReturn {
|
|
131
|
+
/** Monaco namespace */
|
|
132
|
+
monaco: typeof monaco | null;
|
|
133
|
+
/** Whether Monaco is loaded */
|
|
134
|
+
isLoading: boolean;
|
|
135
|
+
/** Loading error if any */
|
|
136
|
+
error: Error | null;
|
|
137
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { setupMonacoWorkers } from './setup';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Monaco Editor Web Worker Setup
|
|
5
|
+
*
|
|
6
|
+
* Workers improve performance for language services (TypeScript type-checking,
|
|
7
|
+
* JSON validation, etc.) but require bundler-specific configuration:
|
|
8
|
+
*
|
|
9
|
+
* **Vite**: Use `?worker` imports (see example below)
|
|
10
|
+
* **Next.js/Webpack**: Use `monaco-editor-webpack-plugin`
|
|
11
|
+
* **No config**: Monaco falls back to main-thread execution (fully functional)
|
|
12
|
+
*
|
|
13
|
+
* @example Vite setup (call once in app entry):
|
|
14
|
+
* ```ts
|
|
15
|
+
* import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
|
|
16
|
+
* import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
|
|
17
|
+
* import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
|
|
18
|
+
* import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'
|
|
19
|
+
* import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker'
|
|
20
|
+
*
|
|
21
|
+
* setupMonacoWorkers((label) => {
|
|
22
|
+
* switch (label) {
|
|
23
|
+
* case 'typescript': case 'javascript': return new tsWorker()
|
|
24
|
+
* case 'json': return new jsonWorker()
|
|
25
|
+
* case 'css': case 'scss': case 'less': return new cssWorker()
|
|
26
|
+
* case 'html': case 'handlebars': case 'razor': return new htmlWorker()
|
|
27
|
+
* default: return new editorWorker()
|
|
28
|
+
* }
|
|
29
|
+
* })
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @example Next.js setup:
|
|
33
|
+
* ```ts
|
|
34
|
+
* // next.config.js — add monaco-editor-webpack-plugin
|
|
35
|
+
* // Then just call setupMonacoWorkers() without arguments
|
|
36
|
+
* setupMonacoWorkers()
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
type GetWorkerFn = (label: string) => Worker;
|
|
41
|
+
|
|
42
|
+
let isSetup = false;
|
|
43
|
+
|
|
44
|
+
export function setupMonacoWorkers(getWorker?: GetWorkerFn): void {
|
|
45
|
+
if (isSetup || typeof window === 'undefined') return;
|
|
46
|
+
|
|
47
|
+
if (getWorker) {
|
|
48
|
+
// App provides bundler-specific worker factory
|
|
49
|
+
(self as unknown as { MonacoEnvironment: object }).MonacoEnvironment = {
|
|
50
|
+
getWorker: (_workerId: string, label: string) => getWorker(label),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// else: don't set MonacoEnvironment at all.
|
|
54
|
+
// - If webpack plugin is installed, it sets MonacoEnvironment automatically.
|
|
55
|
+
// - If nothing sets it, Monaco falls back to main-thread execution.
|
|
56
|
+
|
|
57
|
+
isSetup = true;
|
|
58
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code surfaces: an editor, a diff, a highlighted listing, a JSON editor and
|
|
3
|
+
* the markdown renderer that embeds them.
|
|
4
|
+
*
|
|
5
|
+
* No root barrel worth importing — Monaco alone is ~550 KB and `json-edit-react`
|
|
6
|
+
* and Prism sit behind their own subpaths. Import the one you want:
|
|
7
|
+
* `@djangocfg/widget-code/editor`, `/pretty-code`, `/diff-viewer`,
|
|
8
|
+
* `/json-editor`, `/markdown-message`.
|
|
9
|
+
*/
|
|
10
|
+
export { ANCHOR, TOGGLE } from './role-tokens';
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* JsonEditor — editable JSON tree.
|
|
5
|
+
*
|
|
6
|
+
* Thin wrapper over `json-edit-react` (MIT, CarlosNZ). The library
|
|
7
|
+
* handles the hard parts (inline editing, drag-reorder, JSON-Schema
|
|
8
|
+
* validation, undo/redo, keyboard nav, copy-to-clipboard) — we apply
|
|
9
|
+
* our theme tokens and a sane default surface, nothing more.
|
|
10
|
+
*
|
|
11
|
+
* When to use:
|
|
12
|
+
* - User is *editing* JSON live (debug Store panel, config UI).
|
|
13
|
+
* - You need schema validation or controlled mutations with
|
|
14
|
+
* `onUpdate`.
|
|
15
|
+
*
|
|
16
|
+
* When NOT to use:
|
|
17
|
+
* - Read-only display → use `@djangocfg/widget-data/json-tree` (≈ 10×
|
|
18
|
+
* lighter bundle, search + copy-path included).
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { JsonEditor as RawEditor, type JsonEditorProps as RawProps } from 'json-edit-react';
|
|
22
|
+
import { useMemo } from 'react';
|
|
23
|
+
import { useResolvedTheme } from '@djangocfg/ui-core/hooks';
|
|
24
|
+
import { cn } from '@djangocfg/ui-core/lib';
|
|
25
|
+
|
|
26
|
+
export interface JsonEditorProps extends Omit<RawProps, 'theme'> {
|
|
27
|
+
/** Extra class on the root container. */
|
|
28
|
+
className?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Hide chrome the host already provides. When `false`, no border /
|
|
31
|
+
* background — the editor sits flush in the surrounding pane.
|
|
32
|
+
* Default `true`.
|
|
33
|
+
*/
|
|
34
|
+
bordered?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Hand-rolled theme bridge: maps our semantic CSS vars onto
|
|
39
|
+
* json-edit-react's theme slots. Light/dark resolution lives in our
|
|
40
|
+
* `useResolvedTheme`; the library only sees concrete colours.
|
|
41
|
+
*
|
|
42
|
+
* We keep this minimal — most slots inherit `currentColor`, only the
|
|
43
|
+
* data-typed tokens get explicit Tailwind palette values so the
|
|
44
|
+
* keys/strings/numbers stay legible on every host surface.
|
|
45
|
+
*/
|
|
46
|
+
function useEditorTheme(): RawProps['theme'] {
|
|
47
|
+
const resolved = useResolvedTheme();
|
|
48
|
+
return useMemo(
|
|
49
|
+
() => ({
|
|
50
|
+
displayName: 'djangocfg',
|
|
51
|
+
styles: {
|
|
52
|
+
container: {
|
|
53
|
+
backgroundColor: 'transparent',
|
|
54
|
+
fontFamily:
|
|
55
|
+
"'SF Mono', ui-monospace, 'JetBrains Mono', Menlo, Consolas, monospace",
|
|
56
|
+
fontSize: '12px',
|
|
57
|
+
},
|
|
58
|
+
property:
|
|
59
|
+
resolved === 'dark' ? 'rgb(167 139 250)' : 'rgb(124 58 237)', // violet-400 / violet-600
|
|
60
|
+
bracket:
|
|
61
|
+
resolved === 'dark' ? 'rgb(148 163 184)' : 'rgb(100 116 139)', // slate-400 / slate-500
|
|
62
|
+
itemCount:
|
|
63
|
+
resolved === 'dark' ? 'rgb(100 116 139)' : 'rgb(148 163 184)',
|
|
64
|
+
string:
|
|
65
|
+
resolved === 'dark' ? 'rgb(52 211 153)' : 'rgb(5 150 105)', // emerald-400 / emerald-600
|
|
66
|
+
number:
|
|
67
|
+
resolved === 'dark' ? 'rgb(56 189 248)' : 'rgb(2 132 199)', // sky-400 / sky-600
|
|
68
|
+
boolean:
|
|
69
|
+
resolved === 'dark' ? 'rgb(251 191 36)' : 'rgb(217 119 6)', // amber-400 / amber-600
|
|
70
|
+
null: {
|
|
71
|
+
color: resolved === 'dark' ? 'rgb(100 116 139)' : 'rgb(148 163 184)',
|
|
72
|
+
fontStyle: 'italic',
|
|
73
|
+
},
|
|
74
|
+
input: {
|
|
75
|
+
backgroundColor:
|
|
76
|
+
resolved === 'dark' ? 'rgb(30 41 59)' : 'rgb(241 245 249)',
|
|
77
|
+
color: resolved === 'dark' ? 'white' : 'rgb(15 23 42)',
|
|
78
|
+
borderRadius: 4,
|
|
79
|
+
padding: '2px 6px',
|
|
80
|
+
},
|
|
81
|
+
inputHighlight:
|
|
82
|
+
resolved === 'dark' ? 'rgba(96, 165, 250, 0.25)' : 'rgba(59, 130, 246, 0.15)',
|
|
83
|
+
iconCollection:
|
|
84
|
+
resolved === 'dark' ? 'rgb(148 163 184)' : 'rgb(100 116 139)',
|
|
85
|
+
iconEdit: 'currentColor',
|
|
86
|
+
iconDelete: 'rgb(239 68 68)',
|
|
87
|
+
iconAdd: 'rgb(34 197 94)',
|
|
88
|
+
iconCopy: 'currentColor',
|
|
89
|
+
iconOk: 'rgb(34 197 94)',
|
|
90
|
+
iconCancel: 'rgb(239 68 68)',
|
|
91
|
+
},
|
|
92
|
+
}),
|
|
93
|
+
[resolved],
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function JsonEditor({
|
|
98
|
+
className,
|
|
99
|
+
bordered = true,
|
|
100
|
+
...props
|
|
101
|
+
}: JsonEditorProps) {
|
|
102
|
+
const theme = useEditorTheme();
|
|
103
|
+
return (
|
|
104
|
+
<div
|
|
105
|
+
className={cn(
|
|
106
|
+
'json-editor-host w-full',
|
|
107
|
+
bordered &&
|
|
108
|
+
'overflow-hidden rounded-md border border-border/60 bg-card px-3 py-2',
|
|
109
|
+
className,
|
|
110
|
+
)}
|
|
111
|
+
>
|
|
112
|
+
<RawEditor theme={theme} {...props} />
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lazy';
|