@deckbuilderkit/editor 0.0.0-stage → 0.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.
Files changed (56) hide show
  1. package/package.json +53 -4
  2. package/src/ArchiveView.tsx +111 -0
  3. package/src/CodeEditor.tsx +265 -0
  4. package/src/CodeView.tsx +67 -0
  5. package/src/DiffView.tsx +139 -0
  6. package/src/EnvView.tsx +42 -0
  7. package/src/FilePreview.tsx +108 -0
  8. package/src/FontView.tsx +48 -0
  9. package/src/HarView.tsx +70 -0
  10. package/src/HexView.tsx +28 -0
  11. package/src/HtmlPreview.tsx +23 -0
  12. package/src/MarkdownEditor.tsx +90 -0
  13. package/src/MarkdownToc.tsx +135 -0
  14. package/src/MarkdownView.tsx +84 -0
  15. package/src/MediaView.tsx +17 -0
  16. package/src/NotebookView.tsx +74 -0
  17. package/src/PdfView.tsx +289 -0
  18. package/src/RequestEditor.tsx +419 -0
  19. package/src/ResponseView.tsx +89 -0
  20. package/src/SqlGraph.tsx +322 -0
  21. package/src/SqlSteps.tsx +487 -0
  22. package/src/SqliteView.tsx +68 -0
  23. package/src/SvgView.tsx +231 -0
  24. package/src/TableView.tsx +85 -0
  25. package/src/VarSuggest.tsx +125 -0
  26. package/src/curl-file.ts +70 -0
  27. package/src/editorOptions.ts +83 -0
  28. package/src/editorOptionsContext.ts +4 -0
  29. package/src/fileKinds.ts +69 -0
  30. package/src/http-client.ts +9 -0
  31. package/src/http-import.ts +594 -0
  32. package/src/http.ts +251 -0
  33. package/src/import-warning.ts +8 -0
  34. package/src/index.ts +56 -0
  35. package/src/languages.ts +107 -0
  36. package/src/markdown.ts +123 -0
  37. package/src/markdownLive.ts +289 -0
  38. package/src/options.ts +3 -0
  39. package/src/pdf-url.d.ts +4 -0
  40. package/src/sql/format.ts +637 -0
  41. package/src/sql/formatOptions.ts +71 -0
  42. package/src/sql/lineage.ts +365 -0
  43. package/src/sql/lint.ts +951 -0
  44. package/src/sql/markerHighlight.ts +50 -0
  45. package/src/sql/markers.ts +138 -0
  46. package/src/sql/outline.ts +662 -0
  47. package/src/sql/statements.ts +83 -0
  48. package/src/sql/tokens.ts +156 -0
  49. package/src/sql/xmlQuery.ts +249 -0
  50. package/src/sqlProblems.ts +40 -0
  51. package/src/table.ts +89 -0
  52. package/src/text-save.ts +28 -0
  53. package/src/theme.ts +86 -0
  54. package/src/toc.ts +11 -0
  55. package/src/variables.ts +123 -0
  56. package/README.md +0 -3
@@ -0,0 +1,289 @@
1
+ import { syntaxTree } from '@codemirror/language';
2
+ import type { SyntaxNode } from '@lezer/common';
3
+ import { type EditorState, type Extension, type Range, StateField } from '@codemirror/state';
4
+ import { Decoration, type DecorationSet, EditorView, WidgetType } from '@codemirror/view';
5
+ import { highlightTree } from '@lezer/highlight';
6
+ import { langOfFile, parserOf } from './languages';
7
+ import { frontmatter, frontmatterEntries, frontmatterTable, imagePolicy, renderMarkdown } from './markdown';
8
+ import { highlightStyle } from './theme';
9
+
10
+ const hidden = Decoration.replace({});
11
+ const lineDeco = (cls: string) => Decoration.line({ class: cls });
12
+ const mark = (cls: string) => Decoration.mark({ class: cls });
13
+
14
+ const HEADING = /^ATXHeading([1-6])$/;
15
+ const HIDDEN_MARKS = new Set(['HeaderMark', 'EmphasisMark', 'CodeMark', 'QuoteMark', 'LinkMark', 'URL']);
16
+ const IMAGE = /^!\[([^\]]*)\]\(\s*<?([^)\s>]+)/;
17
+
18
+ class TableWidget extends WidgetType {
19
+ constructor(readonly source: string, readonly from: number) {
20
+ super();
21
+ }
22
+ override eq(other: TableWidget) {
23
+ return other.source === this.source && other.from === this.from;
24
+ }
25
+ override toDOM(view: EditorView) {
26
+ const el = document.createElement('div');
27
+ el.className = 'markdown cm-md-table';
28
+ el.innerHTML = renderMarkdown(this.source);
29
+ // Clicking the rendered table puts the cursor in it, which shows its source.
30
+ el.addEventListener('mousedown', (e) => {
31
+ e.preventDefault();
32
+ view.dispatch({ selection: { anchor: this.from }, scrollIntoView: true });
33
+ view.focus();
34
+ });
35
+ return el;
36
+ }
37
+ override ignoreEvent() {
38
+ return true;
39
+ }
40
+ }
41
+
42
+ class FrontmatterWidget extends WidgetType {
43
+ constructor(readonly entries: [string, string][], readonly source: string) {
44
+ super();
45
+ }
46
+ override eq(other: FrontmatterWidget) {
47
+ return other.source === this.source;
48
+ }
49
+ override toDOM(view: EditorView) {
50
+ const el = document.createElement('div');
51
+ el.className = 'markdown cm-md-table';
52
+ el.append(frontmatterTable(this.entries));
53
+ el.addEventListener('mousedown', (e) => {
54
+ e.preventDefault();
55
+ view.dispatch({ selection: { anchor: 0 }, scrollIntoView: true });
56
+ view.focus();
57
+ });
58
+ return el;
59
+ }
60
+ override ignoreEvent() {
61
+ return true;
62
+ }
63
+ }
64
+
65
+ class ImageWidget extends WidgetType {
66
+ constructor(readonly alt: string, readonly src: string) {
67
+ super();
68
+ }
69
+ override eq(other: ImageWidget) {
70
+ return other.alt === this.alt && other.src === this.src;
71
+ }
72
+ override toDOM() {
73
+ if (this.src === 'blocked') {
74
+ const span = document.createElement('span');
75
+ span.className = 'cm-md-code';
76
+ span.textContent = this.alt || 'image';
77
+ return span;
78
+ }
79
+ const img = document.createElement('img');
80
+ img.className = 'cm-md-img';
81
+ img.src = this.src;
82
+ img.alt = this.alt;
83
+ return img;
84
+ }
85
+ }
86
+
87
+ class BulletWidget extends WidgetType {
88
+ override eq() {
89
+ return true;
90
+ }
91
+ override toDOM() {
92
+ const span = document.createElement('span');
93
+ span.className = 'cm-md-bullet';
94
+ span.textContent = '•';
95
+ return span;
96
+ }
97
+ }
98
+
99
+ class FenceLabel extends WidgetType {
100
+ constructor(readonly text: string) {
101
+ super();
102
+ }
103
+ override eq(other: FenceLabel) {
104
+ return other.text === this.text;
105
+ }
106
+ override toDOM() {
107
+ const span = document.createElement('span');
108
+ span.className = 'cm-md-fence-label';
109
+ span.textContent = this.text;
110
+ return span;
111
+ }
112
+ }
113
+
114
+ /** Does any selection range touch a line of the span `from..to`? */
115
+ function touched(state: EditorState, from: number, to: number): boolean {
116
+ const first = state.doc.lineAt(from).from;
117
+ const last = state.doc.lineAt(to).to;
118
+ return state.selection.ranges.some((r) => r.to >= first && r.from <= last);
119
+ }
120
+
121
+ function decorate(state: EditorState, resolveImage?: (src: string) => string | null): DecorationSet {
122
+ const out: Range<Decoration>[] = [];
123
+ const lined = new Set<string>();
124
+ const addLine = (pos: number, cls: string) => {
125
+ const from = state.doc.lineAt(pos).from;
126
+ if (lined.has(`${from}:${cls}`)) return;
127
+ lined.add(`${from}:${cls}`);
128
+ out.push(lineDeco(cls).range(from));
129
+ // CodeMirror sizes every unmeasured line from the first short plain line it draws; a heading or
130
+ // code line would skew that, so a mark over its text keeps it out of the sample.
131
+ const to = state.doc.lineAt(pos).to;
132
+ if (to > from && !lined.has(`${from}:mark`)) {
133
+ lined.add(`${from}:mark`);
134
+ out.push(mark('cm-md-nosample').range(from, to));
135
+ }
136
+ };
137
+ const addLines = (from: number, to: number, cls: string) => {
138
+ for (let p = from; p <= to;) {
139
+ const l = state.doc.lineAt(p);
140
+ addLine(l.from, cls);
141
+ p = l.to + 1;
142
+ }
143
+ };
144
+
145
+ const fenced = (code: SyntaxNode) => {
146
+ const first = state.doc.lineAt(code.from);
147
+ const last = state.doc.lineAt(code.to);
148
+ const marks = code.getChildren('CodeMark');
149
+ const closed = marks.length > 1 && marks[marks.length - 1].from >= first.to;
150
+ const raw = touched(state, code.from, code.to);
151
+ addLines(code.from, code.to, 'cm-md-fence');
152
+ addLine(code.from, 'cm-md-fence-edge');
153
+ if (closed) addLine(last.from, 'cm-md-fence-edge');
154
+ const info = code.getChild('CodeInfo');
155
+ const lang = info ? state.doc.sliceString(info.from, info.to).trim() : '';
156
+ if (!raw) {
157
+ if (first.to > first.from) out.push(Decoration.replace({ widget: new FenceLabel(lang) }).range(first.from, first.to));
158
+ if (closed && last.to > last.from) out.push(hidden.range(last.from, last.to));
159
+ }
160
+ const body = code.getChild('CodeText');
161
+ const parser = lang ? parserOf(lang.toLowerCase() === 'shell' ? 'shell' : langOfFile('x.' + lang.toLowerCase())) : null;
162
+ if (!body || !parser) return;
163
+ const text = state.doc.sliceString(body.from, body.to);
164
+ highlightTree(parser.parse(text), highlightStyle, (from, to, classes) => {
165
+ out.push(mark(classes).range(body.from + from, body.from + to));
166
+ });
167
+ };
168
+
169
+ const fm = frontmatter(state.doc.toString());
170
+ const fmEnd = fm?.length ?? 0;
171
+ if (fm) {
172
+ const last = state.doc.lineAt(Math.max(0, fmEnd - 1)).number;
173
+ const entries = frontmatterEntries(fm.yaml);
174
+ if (entries && !touched(state, 0, state.doc.line(last).to)) {
175
+ const source = state.doc.sliceString(0, state.doc.line(last).to);
176
+ out.push(Decoration.replace({ widget: new FrontmatterWidget(entries, source), block: true }).range(0, state.doc.line(last).to));
177
+ } else {
178
+ for (let n = 1; n <= last; n++) addLine(state.doc.line(n).from, n === 1 || n === last ? 'cm-md-fence cm-md-fence-edge' : 'cm-md-fence');
179
+ const offset = state.doc.line(2).from;
180
+ const parser = parserOf('yaml');
181
+ if (parser) {
182
+ highlightTree(parser.parse(state.doc.sliceString(offset, state.doc.line(last).from)), highlightStyle, (from, to, classes) => {
183
+ out.push(mark(classes).range(offset + from, offset + to));
184
+ });
185
+ }
186
+ }
187
+ }
188
+
189
+ syntaxTree(state).iterate({
190
+ enter: (node) => {
191
+ if (node.from < fmEnd && node.name !== 'Document') return false;
192
+ const name = node.name;
193
+ const heading = HEADING.exec(name);
194
+ if (heading) {
195
+ addLine(node.from, `cm-md-h cm-md-h${heading[1]}`);
196
+ return;
197
+ }
198
+ switch (name) {
199
+ case 'Table': {
200
+ if (touched(state, node.from, node.to)) {
201
+ addLines(node.from, node.to, 'cm-md-fence');
202
+ return false;
203
+ }
204
+ const source = state.doc.sliceString(node.from, node.to);
205
+ out.push(Decoration.replace({ widget: new TableWidget(source, node.from), block: true }).range(node.from, node.to));
206
+ return false;
207
+ }
208
+ case 'Image': {
209
+ if (touched(state, node.from, node.to)) return false;
210
+ const m = IMAGE.exec(state.doc.sliceString(node.from, node.to));
211
+ if (!m) return false;
212
+ out.push(Decoration.replace({ widget: new ImageWidget(m[1], imagePolicy(m[2], resolveImage)) }).range(node.from, node.to));
213
+ return false;
214
+ }
215
+ case 'StrongEmphasis': out.push(mark('cm-md-strong').range(node.from, node.to)); return;
216
+ case 'Emphasis': out.push(mark('cm-md-em').range(node.from, node.to)); return;
217
+ case 'Strikethrough': out.push(mark('cm-md-strike').range(node.from, node.to)); return;
218
+ case 'InlineCode': out.push(mark('cm-md-code').range(node.from, node.to)); return;
219
+ case 'Link': out.push(mark('cm-md-link').range(node.from, node.to)); return;
220
+ case 'Blockquote': addLines(node.from, node.to, 'cm-md-quote'); return;
221
+ case 'HorizontalRule': addLine(node.from, 'cm-md-hr'); return;
222
+ case 'FencedCode': {
223
+ fenced(node.node);
224
+ return false;
225
+ }
226
+ case 'ListMark': {
227
+ const list = node.node.parent?.parent;
228
+ const text = state.doc.sliceString(node.from, node.to);
229
+ if (list?.name === 'BulletList' && /^[-*+]$/.test(text) && !touched(state, node.from, node.to)) {
230
+ out.push(Decoration.replace({ widget: new BulletWidget() }).range(node.from, node.to));
231
+ }
232
+ return;
233
+ }
234
+ case 'TaskMarker': out.push(mark('cm-md-code').range(node.from, node.to)); return;
235
+ }
236
+ if (!HIDDEN_MARKS.has(name) || node.from === node.to) return;
237
+ const parent = node.node.parent;
238
+ if (!parent || parent.name === 'FencedCode' || parent.name === 'CodeBlock') return;
239
+ if (name === 'URL' && parent.name !== 'Link') return;
240
+ if (touched(state, parent.from, parent.to)) return;
241
+ // A heading or quote mark takes its trailing space with it.
242
+ const spaced = (name === 'HeaderMark' || name === 'QuoteMark') && state.doc.sliceString(node.to, node.to + 1) === ' ';
243
+ out.push(hidden.range(node.from, spaced ? node.to + 1 : node.to));
244
+ },
245
+ });
246
+ return Decoration.set(out, true);
247
+ }
248
+
249
+ // A state field, not a view plugin: block widgets (tables) can't come from a plugin.
250
+ const live = (resolveImage?: (src: string) => string | null) => StateField.define<DecorationSet>({
251
+ create: (state) => decorate(state, resolveImage),
252
+ update: (value, tr) =>
253
+ tr.docChanged || tr.selection || syntaxTree(tr.startState) !== syntaxTree(tr.state) ? decorate(tr.state, resolveImage) : value,
254
+ provide: (f) => EditorView.decorations.from(f),
255
+ });
256
+
257
+ const theme = EditorView.theme({
258
+ // Mirrors `.markdown` in the UI styles, so Live reads like Preview.
259
+ '&': { backgroundColor: 'var(--bg)', fontSize: '14px' },
260
+ '&.cm-editor .cm-scroller': { fontFamily: "'Geist', 'Segoe UI', system-ui, sans-serif", fontSize: '14px', lineHeight: '1.6', padding: '4px 12px' },
261
+ '.cm-content': { maxWidth: 'var(--md-width, 860px)', margin: 'var(--md-margin, 0 auto)', padding: '0' },
262
+ '.cm-md-h span': { color: 'inherit' },
263
+ '.cm-md-h': { fontWeight: '600', lineHeight: '1.25', letterSpacing: '-.01em' },
264
+ // No margins on lines: CodeMirror's height map ignores them, so clicks would land on a later line.
265
+ // The underline is a background 8px above the padded bottom instead of a border plus margin.
266
+ '.cm-md-h1': { fontSize: '24px', paddingTop: '16px', paddingBottom: '15px', background: 'linear-gradient(var(--border-soft), var(--border-soft)) left bottom 8px / 100% 1px no-repeat' },
267
+ '.cm-md-h2': { fontSize: '19px', paddingTop: '14px', paddingBottom: '13px', background: 'linear-gradient(var(--border-soft), var(--border-soft)) left bottom 8px / 100% 1px no-repeat' },
268
+ '.cm-md-h3': { fontSize: '16px', paddingTop: '12px' },
269
+ '.cm-md-h4, .cm-md-h5, .cm-md-h6': { fontSize: '14px', paddingTop: '10px', color: 'var(--text-2)' },
270
+ '.cm-md-strong': { fontWeight: '700' },
271
+ '.cm-md-em': { fontStyle: 'italic' },
272
+ '.cm-md-strike': { textDecoration: 'line-through' },
273
+ '.cm-md-code': { fontFamily: 'var(--font-mono)', fontSize: '12.5px', backgroundColor: 'var(--bg-sunken)', borderRadius: '4px', padding: '1px 5px' },
274
+ '.cm-md-link, .cm-md-link span': { color: 'var(--accent)' },
275
+ '.cm-md-quote': { borderLeft: '3px solid var(--border)', paddingLeft: '12px', color: 'var(--text-2)' },
276
+ '.cm-md-quote span': { color: 'inherit' },
277
+ '.cm-md-hr': { paddingBottom: '19px', background: 'linear-gradient(var(--border), var(--border)) left bottom 9px / 100% 1px no-repeat' },
278
+ '.cm-md-fence': { fontFamily: 'var(--font-mono)', fontSize: '12.5px', lineHeight: '1.55', backgroundColor: 'var(--bg-sunken)', padding: '0 12px', borderLeft: '1px solid var(--border-soft)', borderRight: '1px solid var(--border-soft)' },
279
+ '.cm-md-fence-edge': { padding: '5px 12px', lineHeight: '1.3', color: 'var(--text-3)' },
280
+ '.cm-md-fence-label': { color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: '.04em' },
281
+ '.cm-md-table': { padding: '4px 0', whiteSpace: 'normal', lineHeight: '1.5' },
282
+ '.cm-md-img': { maxWidth: '100%', verticalAlign: 'middle' },
283
+ '.cm-md-bullet': { color: 'var(--text-2)', padding: '0 2px' },
284
+ '.cm-gutters': { display: 'none' },
285
+ '.cm-activeLine, .cm-activeLineGutter': { backgroundColor: 'transparent' },
286
+ });
287
+
288
+ /** Live preview for markdown: formatted inline (tables and images as rendered widgets), raw syntax only where the cursor is. */
289
+ export const markdownLive = (resolveImage?: (src: string) => string | null): Extension => [live(resolveImage), theme];
package/src/options.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { EditorOptionsContext } from './editorOptionsContext';
2
+ export { DEFAULT_FORMAT_OPTIONS, FORMAT_OPTION_META, type FormatOptionMeta, type SqlFormatOptions } from './sql/formatOptions';
3
+ export { previewKind, type PreviewKind } from './fileKinds';
@@ -0,0 +1,4 @@
1
+ declare module '*?url' {
2
+ const url: string;
3
+ export default url;
4
+ }