Skip to content

Commit 5e7fdd1

Browse files
committed
Revamp Help/Changelog modals; fix empty code line
Replace raw-markdown rendering in Help and Changelog modals with structured templates and data: - ModalChangelog: add parseChangelog to parse changelog.md (raw) into dated entries, sections and items; convert markdown links and inline code to safe HTML; add badgeClass mapping and a styled list layout. - ModalHelp: replace runtime-markdown with a static structured sections array (text, images, tables) for richer, consistent rendering. - CodeLine: fix empty-line rendering by using v-text '\n' for proper newline display. - Page: remove an extra gap class from the editor container to adjust layout spacing. These changes remove dependency on runtime-marked rendering and provide more robust, styled UI for help and changelog content.
1 parent d5fa71c commit 5e7fdd1

4 files changed

Lines changed: 207 additions & 16 deletions

File tree

app/components/CodeLine.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
>
1515
{{ added ? '+' : removed ? '-' : number + 1 }}
1616
</span>
17-
<span v-if="line.length === 0">&#10;</span>
17+
<span v-if="line.length === 0" v-text="'\n'"></span>
1818
<span
1919
v-for="(token, tokenIndex) in line"
2020
:key="`token-${tokenIndex}`"

app/components/ModalChangelog.vue

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,45 @@
88
</DialogHeader>
99

1010
<ScrollArea class="flex-1 overflow-auto">
11-
<div class="prose dark:prose-invert prose-sm max-w-none p-5" v-html="content" />
11+
<div class="space-y-4 p-5">
12+
<div
13+
v-for="(entry, index) in entries"
14+
:key="index"
15+
class="rounded-lg border border-zinc-200 dark:border-zinc-800"
16+
>
17+
<div
18+
class="rounded-t-lg border-b border-zinc-200 bg-zinc-50 px-4 py-2.5 dark:border-zinc-800 dark:bg-zinc-900"
19+
>
20+
<span class="text-sm font-semibold">{{ entry.date }}</span>
21+
</div>
22+
23+
<div class="divide-y divide-zinc-100 dark:divide-zinc-800/50">
24+
<div
25+
v-for="(section, sIndex) in entry.sections"
26+
:key="sIndex"
27+
class="px-4 py-3"
28+
>
29+
<span
30+
class="mb-2 inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium"
31+
:class="badgeClass(section.type)"
32+
>
33+
{{ section.type }}
34+
</span>
35+
36+
<ul class="mt-1.5 space-y-1">
37+
<li
38+
v-for="(item, iIndex) in section.items"
39+
:key="iIndex"
40+
class="flex gap-2 text-sm text-zinc-700 dark:text-zinc-300"
41+
>
42+
<span class="mt-1.5 h-1 w-1 shrink-0 rounded-full bg-zinc-400 dark:bg-zinc-500" />
43+
<span v-html="item" />
44+
</li>
45+
</ul>
46+
</div>
47+
</div>
48+
</div>
49+
</div>
1250
</ScrollArea>
1351
</DialogContent>
1452
</Dialog>
@@ -20,11 +58,55 @@ import { ref, onMounted } from 'vue';
2058
defineProps({ modelValue: { type: [Boolean, Object], default: false } });
2159
defineEmits(['update:modelValue']);
2260
23-
const content = ref('');
61+
const entries = ref([]);
62+
63+
const badgeClass = (type) => ({
64+
'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-400': type === 'Added',
65+
'bg-sky-100 text-sky-700 dark:bg-sky-900/40 dark:text-sky-400': type === 'Changed',
66+
'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-400': type === 'Fixed',
67+
});
68+
69+
function parseChangelog(raw) {
70+
const result = [];
71+
let current = null;
72+
73+
for (const line of raw.split('\n')) {
74+
const dateMatch = line.match(/^## (.+)/);
75+
76+
if (dateMatch) {
77+
current = { date: dateMatch[1], sections: [] };
78+
result.push(current);
79+
continue;
80+
}
81+
82+
const typeMatch = line.match(/^\*\*(\w+)\*\*/);
83+
84+
if (typeMatch && current) {
85+
current.sections.push({ type: typeMatch[1], items: [] });
86+
continue;
87+
}
88+
89+
const itemMatch = line.match(/^- (.+)/);
90+
91+
if (itemMatch && current?.sections.length) {
92+
// Convert markdown links to HTML
93+
const html = itemMatch[1].replace(
94+
/\[([^\]]+)\]\(([^)]+)\)/g,
95+
'<a href="$2" target="_blank" class="underline hover:text-zinc-900 dark:hover:text-zinc-100">$1</a>'
96+
).replace(
97+
/`([^`]+)`/g,
98+
'<code class="rounded bg-zinc-100 px-1 py-0.5 text-xs dark:bg-zinc-800">$1</code>'
99+
);
100+
101+
current.sections.at(-1).items.push(html);
102+
}
103+
}
104+
105+
return result;
106+
}
24107
25108
onMounted(async () => {
26109
const md = await import('@/content/changelog.md?raw');
27-
const { marked } = await import('marked');
28-
content.value = marked(md.default);
110+
entries.value = parseChangelog(md.default);
29111
});
30112
</script>

app/components/ModalHelp.vue

Lines changed: 119 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,132 @@
88
</DialogHeader>
99

1010
<ScrollArea class="flex-1 overflow-auto">
11-
<div class="prose dark:prose-invert prose-sm max-w-none p-5" v-html="content" />
11+
<div class="space-y-4 p-5">
12+
<div
13+
v-for="(section, index) in sections"
14+
:key="index"
15+
class="rounded-lg border border-zinc-200 dark:border-zinc-800"
16+
>
17+
<div
18+
class="rounded-t-lg border-b border-zinc-200 bg-zinc-50 px-4 py-2.5 dark:border-zinc-800 dark:bg-zinc-900"
19+
>
20+
<span class="text-sm font-semibold">{{ section.title }}</span>
21+
</div>
22+
23+
<div class="space-y-4 px-4 py-4">
24+
<template v-for="(block, bIndex) in section.blocks" :key="bIndex">
25+
<p
26+
v-if="block.type === 'text'"
27+
class="text-sm leading-relaxed text-zinc-700 dark:text-zinc-300"
28+
>
29+
{{ block.content }}
30+
</p>
31+
32+
<div
33+
v-else-if="block.type === 'image'"
34+
class="flex justify-center"
35+
>
36+
<img
37+
:src="block.src"
38+
:width="block.width"
39+
class="rounded-lg border border-zinc-200 dark:border-zinc-700"
40+
/>
41+
</div>
42+
43+
<div
44+
v-else-if="block.type === 'table'"
45+
class="overflow-hidden rounded-lg border border-zinc-200 dark:border-zinc-800"
46+
>
47+
<table class="w-full text-sm">
48+
<thead>
49+
<tr
50+
class="border-b border-zinc-200 bg-zinc-50 dark:border-zinc-800 dark:bg-zinc-900"
51+
>
52+
<th
53+
v-for="(header, hIndex) in block.headers"
54+
:key="hIndex"
55+
class="px-4 py-2 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400"
56+
>
57+
{{ header }}
58+
</th>
59+
</tr>
60+
</thead>
61+
<tbody
62+
class="divide-y divide-zinc-100 dark:divide-zinc-800/50"
63+
>
64+
<tr v-for="(row, rIndex) in block.rows" :key="rIndex">
65+
<td
66+
class="px-4 py-2.5 font-medium text-zinc-700 dark:text-zinc-300"
67+
>
68+
{{ row.label }}
69+
</td>
70+
<td class="px-4 py-2.5">
71+
<div class="flex items-center gap-1">
72+
<kbd
73+
v-for="(key, kIndex) in row.keys"
74+
:key="kIndex"
75+
class="rounded-md border border-zinc-300 bg-zinc-100 px-2 py-0.5 text-xs font-medium text-zinc-600 shadow-sm dark:border-zinc-600 dark:bg-zinc-800 dark:text-zinc-300"
76+
>
77+
{{ key }}
78+
</kbd>
79+
</div>
80+
</td>
81+
</tr>
82+
</tbody>
83+
</table>
84+
</div>
85+
</template>
86+
</div>
87+
</div>
88+
</div>
1289
</ScrollArea>
1390
</DialogContent>
1491
</Dialog>
1592
</template>
1693

1794
<script setup>
18-
import { ref, onMounted } from 'vue';
19-
2095
defineProps({ modelValue: { type: [Boolean, Object], default: false } });
2196
defineEmits(['update:modelValue']);
2297
23-
const content = ref('');
24-
25-
onMounted(async () => {
26-
const md = await import('@/content/help.md?raw');
27-
const { marked } = await import('marked');
28-
content.value = marked(md.default);
29-
});
98+
const sections = [
99+
{
100+
title: 'Code Highlighting',
101+
blocks: [
102+
{
103+
type: 'text',
104+
content:
105+
'To highlight portions of your code to display an added, removed, or focused line, you can right click in the code editor and select from the context menu:',
106+
},
107+
{
108+
type: 'image',
109+
src: 'https://user-images.githubusercontent.com/6421846/175950540-c22c5868-eca0-4608-9f43-44681dcc3aee.png',
110+
width: 400,
111+
},
112+
{
113+
type: 'text',
114+
content:
115+
'Once a line has been highlighted, colored dots will be displayed indicating their highlight:',
116+
},
117+
{
118+
type: 'image',
119+
src: 'https://user-images.githubusercontent.com/6421846/175952152-acd7c0ae-719b-4fb8-89c8-5c6f8f51df19.png',
120+
width: 400,
121+
},
122+
{
123+
type: 'text',
124+
content:
125+
'Highlights can also be toggled in the editor using the keyboard shortcuts below:',
126+
},
127+
{
128+
type: 'table',
129+
headers: ['Style', 'Keyboard Shortcut'],
130+
rows: [
131+
{ label: 'Added Line', keys: ['⌘/Ctrl', 'Shift', 'A'] },
132+
{ label: 'Removed Line', keys: ['⌘/Ctrl', 'Shift', 'R'] },
133+
{ label: 'Focused Line', keys: ['⌘/Ctrl', 'Shift', 'F'] },
134+
],
135+
},
136+
],
137+
},
138+
];
30139
</script>

app/components/Page.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
>
1111
<div
1212
ref="editorContainerRef"
13-
class="flex h-full w-full gap-1 overflow-hidden"
13+
class="flex h-full w-full overflow-hidden"
1414
:class="{
1515
'flex-col': ['left', 'right'].includes(orientation),
1616
'flex-row': ['top', 'bottom'].includes(orientation),

0 commit comments

Comments
 (0)