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.
Files changed (37) hide show
  1. package/dist/danx.es.js +24432 -22819
  2. package/dist/danx.es.js.map +1 -1
  3. package/dist/danx.umd.js +130 -119
  4. package/dist/danx.umd.js.map +1 -1
  5. package/dist/style.css +1 -1
  6. package/package.json +1 -1
  7. package/src/components/Utility/Buttons/ActionButton.vue +11 -3
  8. package/src/components/Utility/Code/CodeViewer.vue +219 -0
  9. package/src/components/Utility/Code/CodeViewerCollapsed.vue +34 -0
  10. package/src/components/Utility/Code/CodeViewerFooter.vue +53 -0
  11. package/src/components/Utility/Code/LanguageBadge.vue +122 -0
  12. package/src/components/Utility/Code/MarkdownContent.vue +251 -0
  13. package/src/components/Utility/Code/index.ts +5 -0
  14. package/src/components/Utility/Dialogs/FullscreenCarouselDialog.vue +134 -38
  15. package/src/components/Utility/Files/CarouselHeader.vue +24 -0
  16. package/src/components/Utility/Files/FileMetadataDialog.vue +69 -0
  17. package/src/components/Utility/Files/FilePreview.vue +124 -162
  18. package/src/components/Utility/Files/index.ts +1 -0
  19. package/src/components/Utility/index.ts +1 -0
  20. package/src/composables/index.ts +5 -0
  21. package/src/composables/useCodeFormat.ts +199 -0
  22. package/src/composables/useCodeViewerCollapse.ts +125 -0
  23. package/src/composables/useCodeViewerEditor.ts +420 -0
  24. package/src/composables/useFilePreview.ts +119 -0
  25. package/src/composables/useTranscodeLoader.ts +68 -0
  26. package/src/helpers/filePreviewHelpers.ts +31 -0
  27. package/src/helpers/formats/highlightSyntax.ts +327 -0
  28. package/src/helpers/formats/index.ts +3 -1
  29. package/src/helpers/formats/renderMarkdown.ts +338 -0
  30. package/src/helpers/objectStore.ts +10 -2
  31. package/src/styles/danx.scss +3 -0
  32. package/src/styles/themes/danx/code.scss +158 -0
  33. package/src/styles/themes/danx/index.scss +2 -0
  34. package/src/styles/themes/danx/markdown.scss +145 -0
  35. package/src/styles/themes/danx/scrollbar.scss +125 -0
  36. package/src/svg/GoogleDocsIcon.vue +88 -0
  37. 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, "&lt;")
17
+ .replace(/>/g, "&gt;")
18
+ .replace(/"/g, "&quot;")
19
+ .replace(/'/g, "&#039;");
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: ![alt](url) - 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 ![alt](url)
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) return shallowReactive(newObject);
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) && value.length > 0) {
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
  }
@@ -1,3 +1,6 @@
1
+ @import "themes/danx/scrollbar";
2
+ @import "themes/danx/code";
3
+
1
4
  .dx-action-table {
2
5
  .dx-column-shrink {
3
6
  width: 1px;
@@ -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
+ }
@@ -1,8 +1,10 @@
1
1
  @import "action-table";
2
2
  @import "buttons";
3
3
  @import "carousels";
4
+ @import "code";
4
5
  @import "dialogs";
5
6
  @import "forms";
7
+ @import "markdown";
6
8
  @import "panels";
7
9
  @import "sidebar";
8
10
  @import "toolbar";
@@ -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
+ }