quasar-ui-danx 0.4.94 → 0.4.99
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/dist/danx.es.js +24432 -22819
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +130 -119
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/Utility/Buttons/ActionButton.vue +11 -3
- package/src/components/Utility/Code/CodeViewer.vue +219 -0
- package/src/components/Utility/Code/CodeViewerCollapsed.vue +34 -0
- package/src/components/Utility/Code/CodeViewerFooter.vue +53 -0
- package/src/components/Utility/Code/LanguageBadge.vue +122 -0
- package/src/components/Utility/Code/MarkdownContent.vue +251 -0
- package/src/components/Utility/Code/index.ts +5 -0
- package/src/components/Utility/Dialogs/FullscreenCarouselDialog.vue +134 -38
- package/src/components/Utility/Files/CarouselHeader.vue +24 -0
- package/src/components/Utility/Files/FileMetadataDialog.vue +69 -0
- package/src/components/Utility/Files/FilePreview.vue +124 -162
- package/src/components/Utility/Files/index.ts +1 -0
- package/src/components/Utility/index.ts +1 -0
- package/src/composables/index.ts +5 -0
- package/src/composables/useCodeFormat.ts +199 -0
- package/src/composables/useCodeViewerCollapse.ts +125 -0
- package/src/composables/useCodeViewerEditor.ts +420 -0
- package/src/composables/useFilePreview.ts +119 -0
- package/src/composables/useTranscodeLoader.ts +68 -0
- package/src/helpers/filePreviewHelpers.ts +31 -0
- package/src/helpers/formats/highlightSyntax.ts +327 -0
- package/src/helpers/formats/index.ts +3 -1
- package/src/helpers/formats/renderMarkdown.ts +338 -0
- package/src/helpers/objectStore.ts +10 -2
- package/src/styles/danx.scss +3 -0
- package/src/styles/themes/danx/code.scss +158 -0
- package/src/styles/themes/danx/index.scss +2 -0
- package/src/styles/themes/danx/markdown.scss +145 -0
- package/src/styles/themes/danx/scrollbar.scss +125 -0
- package/src/svg/GoogleDocsIcon.vue +88 -0
- package/src/svg/index.ts +1 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight markdown to HTML renderer
|
|
3
|
+
* Zero external dependencies, XSS-safe by default
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface MarkdownRenderOptions {
|
|
7
|
+
sanitize?: boolean; // XSS protection (default: true)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Escape HTML entities to prevent XSS
|
|
12
|
+
*/
|
|
13
|
+
function escapeHtml(text: string): string {
|
|
14
|
+
return text
|
|
15
|
+
.replace(/&/g, "&")
|
|
16
|
+
.replace(/</g, "<")
|
|
17
|
+
.replace(/>/g, ">")
|
|
18
|
+
.replace(/"/g, """)
|
|
19
|
+
.replace(/'/g, "'");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Token types for block-level parsing
|
|
24
|
+
*/
|
|
25
|
+
export type BlockToken =
|
|
26
|
+
| { type: "heading"; level: number; content: string }
|
|
27
|
+
| { type: "code_block"; language: string; content: string }
|
|
28
|
+
| { type: "blockquote"; content: string }
|
|
29
|
+
| { type: "ul"; items: string[] }
|
|
30
|
+
| { type: "ol"; items: string[]; start: number }
|
|
31
|
+
| { type: "hr" }
|
|
32
|
+
| { type: "paragraph"; content: string };
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Parse inline markdown elements within text
|
|
36
|
+
* Order matters: more specific patterns first
|
|
37
|
+
*/
|
|
38
|
+
export function parseInline(text: string, sanitize: boolean = true): string {
|
|
39
|
+
if (!text) return "";
|
|
40
|
+
|
|
41
|
+
// Escape HTML if sanitizing (before applying markdown)
|
|
42
|
+
let result = sanitize ? escapeHtml(text) : text;
|
|
43
|
+
|
|
44
|
+
// Images:  - must be before links
|
|
45
|
+
result = result.replace(
|
|
46
|
+
/!\[([^\]]*)\]\(([^)]+)\)/g,
|
|
47
|
+
'<img src="$2" alt="$1" />'
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// Links: [text](url)
|
|
51
|
+
result = result.replace(
|
|
52
|
+
/\[([^\]]+)\]\(([^)]+)\)/g,
|
|
53
|
+
'<a href="$2">$1</a>'
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// Inline code: `code` (escape the content again if already escaped)
|
|
57
|
+
result = result.replace(/`([^`]+)`/g, "<code>$1</code>");
|
|
58
|
+
|
|
59
|
+
// Bold + Italic: ***text*** or ___text___
|
|
60
|
+
result = result.replace(/\*\*\*([^*]+)\*\*\*/g, "<strong><em>$1</em></strong>");
|
|
61
|
+
result = result.replace(/___([^_]+)___/g, "<strong><em>$1</em></strong>");
|
|
62
|
+
|
|
63
|
+
// Bold: **text** or __text__
|
|
64
|
+
result = result.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
|
|
65
|
+
result = result.replace(/__([^_]+)__/g, "<strong>$1</strong>");
|
|
66
|
+
|
|
67
|
+
// Italic: *text* or _text_ (but not inside words for underscores)
|
|
68
|
+
// For asterisks, match any single asterisk pairs
|
|
69
|
+
result = result.replace(/\*([^*]+)\*/g, "<em>$1</em>");
|
|
70
|
+
// For underscores, only match at word boundaries
|
|
71
|
+
result = result.replace(/(^|[^a-zA-Z0-9])_([^_]+)_([^a-zA-Z0-9]|$)/g, "$1<em>$2</em>$3");
|
|
72
|
+
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Tokenize markdown into block-level elements
|
|
78
|
+
*/
|
|
79
|
+
export function tokenizeBlocks(markdown: string): BlockToken[] {
|
|
80
|
+
const tokens: BlockToken[] = [];
|
|
81
|
+
const lines = markdown.split("\n");
|
|
82
|
+
let i = 0;
|
|
83
|
+
|
|
84
|
+
while (i < lines.length) {
|
|
85
|
+
const line = lines[i];
|
|
86
|
+
const trimmedLine = line.trim();
|
|
87
|
+
|
|
88
|
+
// Skip empty lines between blocks
|
|
89
|
+
if (!trimmedLine) {
|
|
90
|
+
i++;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Code blocks: ```language ... ```
|
|
95
|
+
if (trimmedLine.startsWith("```")) {
|
|
96
|
+
const language = trimmedLine.slice(3).trim();
|
|
97
|
+
const contentLines: string[] = [];
|
|
98
|
+
i++;
|
|
99
|
+
|
|
100
|
+
while (i < lines.length && !lines[i].trim().startsWith("```")) {
|
|
101
|
+
contentLines.push(lines[i]);
|
|
102
|
+
i++;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
tokens.push({
|
|
106
|
+
type: "code_block",
|
|
107
|
+
language,
|
|
108
|
+
content: contentLines.join("\n")
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Skip closing ```
|
|
112
|
+
if (i < lines.length) i++;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Headings: # through ######
|
|
117
|
+
const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
|
|
118
|
+
if (headingMatch) {
|
|
119
|
+
tokens.push({
|
|
120
|
+
type: "heading",
|
|
121
|
+
level: headingMatch[1].length,
|
|
122
|
+
content: headingMatch[2]
|
|
123
|
+
});
|
|
124
|
+
i++;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Horizontal rules: ---, ***, ___
|
|
129
|
+
if (/^(-{3,}|\*{3,}|_{3,})$/.test(trimmedLine)) {
|
|
130
|
+
tokens.push({ type: "hr" });
|
|
131
|
+
i++;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Blockquotes: > text
|
|
136
|
+
if (trimmedLine.startsWith(">")) {
|
|
137
|
+
const quoteLines: string[] = [];
|
|
138
|
+
|
|
139
|
+
while (i < lines.length && lines[i].trim().startsWith(">")) {
|
|
140
|
+
// Remove the leading > and optional space
|
|
141
|
+
quoteLines.push(lines[i].trim().replace(/^>\s?/, ""));
|
|
142
|
+
i++;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
tokens.push({
|
|
146
|
+
type: "blockquote",
|
|
147
|
+
content: quoteLines.join("\n")
|
|
148
|
+
});
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Unordered lists: -, *, +
|
|
153
|
+
if (/^[-*+]\s+/.test(trimmedLine)) {
|
|
154
|
+
const items: string[] = [];
|
|
155
|
+
|
|
156
|
+
while (i < lines.length) {
|
|
157
|
+
const listLine = lines[i].trim();
|
|
158
|
+
const listMatch = listLine.match(/^[-*+]\s+(.+)$/);
|
|
159
|
+
|
|
160
|
+
if (listMatch) {
|
|
161
|
+
items.push(listMatch[1]);
|
|
162
|
+
i++;
|
|
163
|
+
} else if (listLine === "") {
|
|
164
|
+
// Empty line might end the list or just be spacing
|
|
165
|
+
i++;
|
|
166
|
+
// Check if next non-empty line continues the list
|
|
167
|
+
const nextNonEmpty = lines.slice(i).find((l) => l.trim() !== "");
|
|
168
|
+
if (!nextNonEmpty || !/^[-*+]\s+/.test(nextNonEmpty.trim())) {
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
tokens.push({ type: "ul", items });
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Ordered lists: 1., 2., etc.
|
|
181
|
+
const orderedMatch = trimmedLine.match(/^(\d+)\.\s+(.+)$/);
|
|
182
|
+
if (orderedMatch) {
|
|
183
|
+
const items: string[] = [];
|
|
184
|
+
const startNum = parseInt(orderedMatch[1], 10);
|
|
185
|
+
|
|
186
|
+
while (i < lines.length) {
|
|
187
|
+
const listLine = lines[i].trim();
|
|
188
|
+
const listMatch = listLine.match(/^\d+\.\s+(.+)$/);
|
|
189
|
+
|
|
190
|
+
if (listMatch) {
|
|
191
|
+
items.push(listMatch[1]);
|
|
192
|
+
i++;
|
|
193
|
+
} else if (listLine === "") {
|
|
194
|
+
i++;
|
|
195
|
+
// Check if next non-empty line continues the list
|
|
196
|
+
const nextNonEmpty = lines.slice(i).find((l) => l.trim() !== "");
|
|
197
|
+
if (!nextNonEmpty || !/^\d+\.\s+/.test(nextNonEmpty.trim())) {
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
tokens.push({ type: "ol", items, start: startNum });
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Paragraph: collect consecutive non-empty lines
|
|
210
|
+
const paragraphLines: string[] = [];
|
|
211
|
+
|
|
212
|
+
while (i < lines.length) {
|
|
213
|
+
const pLine = lines[i];
|
|
214
|
+
const pTrimmed = pLine.trim();
|
|
215
|
+
|
|
216
|
+
// Stop on empty line or block-level element
|
|
217
|
+
if (!pTrimmed) {
|
|
218
|
+
i++;
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Check for block-level starters
|
|
223
|
+
if (
|
|
224
|
+
pTrimmed.startsWith("#") ||
|
|
225
|
+
pTrimmed.startsWith("```") ||
|
|
226
|
+
pTrimmed.startsWith(">") ||
|
|
227
|
+
/^[-*+]\s+/.test(pTrimmed) ||
|
|
228
|
+
/^\d+\.\s+/.test(pTrimmed) ||
|
|
229
|
+
/^(-{3,}|\*{3,}|_{3,})$/.test(pTrimmed)
|
|
230
|
+
) {
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
paragraphLines.push(pLine);
|
|
235
|
+
i++;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (paragraphLines.length > 0) {
|
|
239
|
+
tokens.push({
|
|
240
|
+
type: "paragraph",
|
|
241
|
+
content: paragraphLines.join("\n")
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return tokens;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Render tokens to HTML
|
|
251
|
+
*/
|
|
252
|
+
function renderTokens(tokens: BlockToken[], sanitize: boolean): string {
|
|
253
|
+
const htmlParts: string[] = [];
|
|
254
|
+
|
|
255
|
+
for (const token of tokens) {
|
|
256
|
+
switch (token.type) {
|
|
257
|
+
case "heading": {
|
|
258
|
+
const content = parseInline(token.content, sanitize);
|
|
259
|
+
htmlParts.push(`<h${token.level}>${content}</h${token.level}>`);
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
case "code_block": {
|
|
264
|
+
// Always escape code block content for safety
|
|
265
|
+
const escapedContent = escapeHtml(token.content);
|
|
266
|
+
const langAttr = token.language ? ` class="language-${escapeHtml(token.language)}"` : "";
|
|
267
|
+
htmlParts.push(`<pre><code${langAttr}>${escapedContent}</code></pre>`);
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
case "blockquote": {
|
|
272
|
+
// Recursively parse blockquote content
|
|
273
|
+
const innerTokens = tokenizeBlocks(token.content);
|
|
274
|
+
const innerHtml = renderTokens(innerTokens, sanitize);
|
|
275
|
+
htmlParts.push(`<blockquote>${innerHtml}</blockquote>`);
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
case "ul": {
|
|
280
|
+
const items = token.items
|
|
281
|
+
.map((item) => `<li>${parseInline(item, sanitize)}</li>`)
|
|
282
|
+
.join("");
|
|
283
|
+
htmlParts.push(`<ul>${items}</ul>`);
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
case "ol": {
|
|
288
|
+
const items = token.items
|
|
289
|
+
.map((item) => `<li>${parseInline(item, sanitize)}</li>`)
|
|
290
|
+
.join("");
|
|
291
|
+
const startAttr = token.start !== 1 ? ` start="${token.start}"` : "";
|
|
292
|
+
htmlParts.push(`<ol${startAttr}>${items}</ol>`);
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
case "hr": {
|
|
297
|
+
htmlParts.push("<hr />");
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
case "paragraph": {
|
|
302
|
+
const content = parseInline(token.content, sanitize);
|
|
303
|
+
// Convert single newlines to <br> within paragraphs
|
|
304
|
+
const withBreaks = content.replace(/\n/g, "<br />");
|
|
305
|
+
htmlParts.push(`<p>${withBreaks}</p>`);
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return htmlParts.join("\n");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Convert markdown text to HTML
|
|
316
|
+
*
|
|
317
|
+
* Supports:
|
|
318
|
+
* - Headings (# through ######)
|
|
319
|
+
* - Paragraphs (double newlines)
|
|
320
|
+
* - Code blocks (```language ... ```)
|
|
321
|
+
* - Blockquotes (> text)
|
|
322
|
+
* - Unordered lists (-, *, +)
|
|
323
|
+
* - Ordered lists (1., 2., etc.)
|
|
324
|
+
* - Horizontal rules (---, ***, ___)
|
|
325
|
+
* - Bold (**text** or __text__)
|
|
326
|
+
* - Italic (*text* or _text_)
|
|
327
|
+
* - Bold+Italic (***text***)
|
|
328
|
+
* - Inline code (`code`)
|
|
329
|
+
* - Links [text](url)
|
|
330
|
+
* - Images 
|
|
331
|
+
*/
|
|
332
|
+
export function renderMarkdown(markdown: string, options?: MarkdownRenderOptions): string {
|
|
333
|
+
if (!markdown) return "";
|
|
334
|
+
|
|
335
|
+
const sanitize = options?.sanitize ?? true;
|
|
336
|
+
const tokens = tokenizeBlocks(markdown);
|
|
337
|
+
return renderTokens(tokens, sanitize);
|
|
338
|
+
}
|
|
@@ -25,7 +25,12 @@ export function storeObject<T extends TypedObject>(newObject: T, recentlyStoredO
|
|
|
25
25
|
|
|
26
26
|
const id = newObject?.id || newObject?.name;
|
|
27
27
|
const type = newObject?.__type;
|
|
28
|
-
if (!id || !type)
|
|
28
|
+
if (!id || !type) {
|
|
29
|
+
// Still process children to store any nested TypedObjects
|
|
30
|
+
const reactiveObject = shallowReactive(newObject);
|
|
31
|
+
storeObjectChildren(newObject, recentlyStoredObjects, reactiveObject);
|
|
32
|
+
return reactiveObject;
|
|
33
|
+
}
|
|
29
34
|
|
|
30
35
|
if (!newObject.__id) {
|
|
31
36
|
newObject.__id = uid();
|
|
@@ -88,7 +93,7 @@ function storeObjectChildren<T extends TypedObject>(object: T, recentlyStoredObj
|
|
|
88
93
|
applyToObject = applyToObject || object;
|
|
89
94
|
for (const key of Object.keys(object)) {
|
|
90
95
|
const value = object[key];
|
|
91
|
-
if (Array.isArray(value)
|
|
96
|
+
if (Array.isArray(value)) {
|
|
92
97
|
for (const index in value) {
|
|
93
98
|
if (value[index] && typeof value[index] === "object") {
|
|
94
99
|
if (!applyToObject[key]) {
|
|
@@ -101,6 +106,9 @@ function storeObjectChildren<T extends TypedObject>(object: T, recentlyStoredObj
|
|
|
101
106
|
} else if (value?.__type) {
|
|
102
107
|
// @ts-expect-error __type is guaranteed to be set in this case
|
|
103
108
|
applyToObject[key] = storeObject(value as TypedObject, recentlyStoredObjects);
|
|
109
|
+
} else if (value && typeof value === "object") {
|
|
110
|
+
// Handle plain objects/dictionaries - recurse to find nested TypedObjects at any depth
|
|
111
|
+
storeObjectChildren(value, recentlyStoredObjects, applyToObject[key]);
|
|
104
112
|
}
|
|
105
113
|
}
|
|
106
114
|
}
|
package/src/styles/danx.scss
CHANGED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// Code Viewer Theme
|
|
2
|
+
// Dark theme with syntax highlighting for JSON/YAML
|
|
3
|
+
|
|
4
|
+
.dx-code-viewer {
|
|
5
|
+
width: 100%;
|
|
6
|
+
height: 100%;
|
|
7
|
+
|
|
8
|
+
// ==========================================
|
|
9
|
+
// Collapsible mode
|
|
10
|
+
// ==========================================
|
|
11
|
+
&.is-collapsed {
|
|
12
|
+
height: auto;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Collapsed view - inline preview
|
|
16
|
+
.code-collapsed {
|
|
17
|
+
background-color: #1e1e1e;
|
|
18
|
+
border-radius: 0.375rem;
|
|
19
|
+
padding: 0.5rem 0.75rem;
|
|
20
|
+
font-family: 'Consolas', 'Monaco', 'Menlo', 'Ubuntu Mono', 'source-code-pro', monospace;
|
|
21
|
+
font-size: 0.875rem;
|
|
22
|
+
transition: background-color 0.15s ease;
|
|
23
|
+
|
|
24
|
+
&:hover {
|
|
25
|
+
background-color: #252526;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.code-collapsed-preview {
|
|
29
|
+
color: #d4d4d4;
|
|
30
|
+
white-space: nowrap;
|
|
31
|
+
overflow: hidden;
|
|
32
|
+
text-overflow: ellipsis;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Collapse toggle button in expanded view
|
|
37
|
+
.collapse-toggle {
|
|
38
|
+
transition: color 0.15s ease;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Clickable header area to collapse when expanded
|
|
42
|
+
.collapse-header {
|
|
43
|
+
position: absolute;
|
|
44
|
+
top: 0;
|
|
45
|
+
left: 0;
|
|
46
|
+
right: 0;
|
|
47
|
+
height: 1.5rem;
|
|
48
|
+
cursor: pointer;
|
|
49
|
+
z-index: 5;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Code content area - the main display
|
|
53
|
+
.code-content {
|
|
54
|
+
// When collapsible, add padding for the collapse toggle button
|
|
55
|
+
&.is-collapsible {
|
|
56
|
+
padding-top: 1rem;
|
|
57
|
+
padding-left: 1rem;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
background-color: #1e1e1e;
|
|
61
|
+
color: #d4d4d4;
|
|
62
|
+
width: 100%;
|
|
63
|
+
min-height: 0;
|
|
64
|
+
font-family: 'Consolas', 'Monaco', 'Menlo', 'Ubuntu Mono', 'source-code-pro', monospace;
|
|
65
|
+
font-size: 0.875rem;
|
|
66
|
+
line-height: 1.6;
|
|
67
|
+
border-radius: 0.375rem 0.375rem 0 0;
|
|
68
|
+
padding: 1rem;
|
|
69
|
+
overflow: auto;
|
|
70
|
+
margin: 0;
|
|
71
|
+
box-sizing: border-box;
|
|
72
|
+
|
|
73
|
+
code {
|
|
74
|
+
white-space: pre-wrap;
|
|
75
|
+
word-break: break-word;
|
|
76
|
+
background: transparent;
|
|
77
|
+
color: inherit;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Footer area
|
|
82
|
+
.code-footer {
|
|
83
|
+
background-color: #252526;
|
|
84
|
+
border-radius: 0 0 0.375rem 0.375rem;
|
|
85
|
+
padding: 0.25rem 0.5rem;
|
|
86
|
+
transition: background-color 0.2s ease;
|
|
87
|
+
|
|
88
|
+
&.has-error {
|
|
89
|
+
background-color: #5a1d1d;
|
|
90
|
+
border-top: 1px solid #be1100;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Language badge
|
|
95
|
+
.language-badge {
|
|
96
|
+
background-color: #333333;
|
|
97
|
+
color: #808080;
|
|
98
|
+
padding: 0.25rem 0.5rem;
|
|
99
|
+
font-size: 0.7em;
|
|
100
|
+
text-transform: uppercase;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ==========================================
|
|
104
|
+
// Syntax highlighting classes
|
|
105
|
+
// ==========================================
|
|
106
|
+
|
|
107
|
+
// Keys (property names) - Light blue
|
|
108
|
+
.syntax-key {
|
|
109
|
+
color: #9cdcfe;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Strings - Soft green
|
|
113
|
+
.syntax-string {
|
|
114
|
+
color: #9ae6b4;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Numbers - Orange/salmon
|
|
118
|
+
.syntax-number {
|
|
119
|
+
color: #ce9178;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Booleans - Blue
|
|
123
|
+
.syntax-boolean {
|
|
124
|
+
color: #569cd6;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Null - Blue
|
|
128
|
+
.syntax-null {
|
|
129
|
+
color: #569cd6;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Punctuation - Gray
|
|
133
|
+
.syntax-punctuation {
|
|
134
|
+
color: #808080;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
// ==========================================
|
|
139
|
+
// Editable mode (contenteditable)
|
|
140
|
+
// ==========================================
|
|
141
|
+
.code-content.is-editable {
|
|
142
|
+
cursor: text;
|
|
143
|
+
outline: none;
|
|
144
|
+
border: 2px solid transparent;
|
|
145
|
+
transition: border-color 0.2s ease;
|
|
146
|
+
|
|
147
|
+
&:focus {
|
|
148
|
+
border-color: rgba(86, 156, 214, 0.6);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
&:hover:not(:focus) {
|
|
152
|
+
border-color: rgba(86, 156, 214, 0.3);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Caret color
|
|
156
|
+
caret-color: #d4d4d4;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// Markdown Content Theme
|
|
2
|
+
// Styles for rendered markdown content
|
|
3
|
+
|
|
4
|
+
.dx-markdown-content {
|
|
5
|
+
// Base styling
|
|
6
|
+
line-height: 1.6;
|
|
7
|
+
color: inherit;
|
|
8
|
+
|
|
9
|
+
// Headings
|
|
10
|
+
h1, h2, h3, h4, h5, h6 {
|
|
11
|
+
margin-top: 1.5em;
|
|
12
|
+
margin-bottom: 0.5em;
|
|
13
|
+
font-weight: 600;
|
|
14
|
+
line-height: 1.25;
|
|
15
|
+
|
|
16
|
+
&:first-child {
|
|
17
|
+
margin-top: 0;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
h1 { font-size: 2em; }
|
|
22
|
+
h2 { font-size: 1.5em; }
|
|
23
|
+
h3 { font-size: 1.25em; }
|
|
24
|
+
h4 { font-size: 1em; }
|
|
25
|
+
h5 { font-size: 0.875em; }
|
|
26
|
+
h6 { font-size: 0.85em; }
|
|
27
|
+
|
|
28
|
+
// Paragraphs
|
|
29
|
+
p {
|
|
30
|
+
margin: 1em 0;
|
|
31
|
+
|
|
32
|
+
&:first-child { margin-top: 0; }
|
|
33
|
+
&:last-child { margin-bottom: 0; }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Inline code
|
|
37
|
+
code {
|
|
38
|
+
background: rgba(255, 255, 255, 0.1);
|
|
39
|
+
padding: 0.2em 0.4em;
|
|
40
|
+
border-radius: 3px;
|
|
41
|
+
font-size: 0.9em;
|
|
42
|
+
font-family: 'Fira Code', 'Monaco', monospace;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Code blocks
|
|
46
|
+
pre {
|
|
47
|
+
background: rgba(0, 0, 0, 0.3);
|
|
48
|
+
padding: 1em;
|
|
49
|
+
border-radius: 6px;
|
|
50
|
+
overflow-x: auto;
|
|
51
|
+
margin: 1em 0;
|
|
52
|
+
|
|
53
|
+
code {
|
|
54
|
+
background: transparent;
|
|
55
|
+
padding: 0;
|
|
56
|
+
font-size: 0.875em;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Blockquotes
|
|
61
|
+
blockquote {
|
|
62
|
+
border-left: 4px solid rgba(255, 255, 255, 0.3);
|
|
63
|
+
margin: 1em 0;
|
|
64
|
+
padding: 0.5em 1em;
|
|
65
|
+
color: rgba(255, 255, 255, 0.7);
|
|
66
|
+
|
|
67
|
+
p:first-child { margin-top: 0; }
|
|
68
|
+
p:last-child { margin-bottom: 0; }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Lists
|
|
72
|
+
ul, ol {
|
|
73
|
+
margin: 1em 0;
|
|
74
|
+
padding-left: 2em;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
li {
|
|
78
|
+
margin: 0.25em 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
ul {
|
|
82
|
+
list-style-type: disc;
|
|
83
|
+
|
|
84
|
+
ul {
|
|
85
|
+
list-style-type: circle;
|
|
86
|
+
|
|
87
|
+
ul {
|
|
88
|
+
list-style-type: square;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
ol {
|
|
94
|
+
list-style-type: decimal;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Links
|
|
98
|
+
a {
|
|
99
|
+
color: #60a5fa; // blue-400
|
|
100
|
+
text-decoration: none;
|
|
101
|
+
|
|
102
|
+
&:hover {
|
|
103
|
+
text-decoration: underline;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Images
|
|
108
|
+
img {
|
|
109
|
+
max-width: 100%;
|
|
110
|
+
height: auto;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Horizontal rules
|
|
114
|
+
hr {
|
|
115
|
+
border: none;
|
|
116
|
+
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
|
117
|
+
margin: 2em 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Bold and italic
|
|
121
|
+
strong { font-weight: 600; }
|
|
122
|
+
em { font-style: italic; }
|
|
123
|
+
|
|
124
|
+
// Tables
|
|
125
|
+
table {
|
|
126
|
+
border-collapse: collapse;
|
|
127
|
+
width: 100%;
|
|
128
|
+
margin: 1em 0;
|
|
129
|
+
|
|
130
|
+
th, td {
|
|
131
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
132
|
+
padding: 0.5em 1em;
|
|
133
|
+
text-align: left;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
th {
|
|
137
|
+
background: rgba(255, 255, 255, 0.1);
|
|
138
|
+
font-weight: 600;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
tr:nth-child(even) {
|
|
142
|
+
background: rgba(255, 255, 255, 0.05);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|