Skip to content

Commit 1757106

Browse files
committed
improvement(desktop-update): sanitize release-note links and virtualize notes list
1 parent 8868789 commit 1757106

File tree

1 file changed

+93
-2
lines changed

1 file changed

+93
-2
lines changed

frontend/components/DesktopUpdateDialog.vue

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,26 @@
2727

2828
<div class="mt-4 rounded-md border border-gray-200 bg-gray-50 p-3">
2929
<div class="text-xs font-medium text-gray-700">更新内容</div>
30-
<div class="mt-2 text-xs text-gray-700 whitespace-pre-wrap break-words">
31-
{{ info.releaseNotes || '修复了一些已知问题,提升了稳定性。' }}
30+
<div
31+
ref="notesViewportRef"
32+
class="mt-2 max-h-48 overflow-y-auto pr-1 text-xs text-gray-700"
33+
@scroll="onNotesScroll"
34+
>
35+
<div class="relative" :style="{ height: `${virtualTotalHeight}px` }">
36+
<div
37+
class="absolute left-0 right-0 top-0"
38+
:style="{ transform: `translateY(${virtualOffsetTop}px)` }"
39+
>
40+
<div
41+
v-for="item in virtualVisibleItems"
42+
:key="item.key"
43+
class="h-6 leading-6 truncate"
44+
:title="item.text"
45+
>
46+
{{ item.text }}
47+
</div>
48+
</div>
49+
</div>
3250
</div>
3351
</div>
3452

@@ -113,6 +131,79 @@ const props = defineProps({
113131
114132
const emit = defineEmits(["close", "update", "install", "ignore"]);
115133
134+
const DEFAULT_RELEASE_NOTE = "修复了一些已知问题,提升了稳定性。";
135+
const NOTE_ROW_HEIGHT = 24;
136+
const NOTE_OVERSCAN = 6;
137+
const NOTE_FALLBACK_VIEWPORT_HEIGHT = 192; // 8 rows * 24px
138+
139+
const notesViewportRef = ref(null);
140+
const notesScrollTop = ref(0);
141+
142+
const sanitizeReleaseNotes = (input) => {
143+
const raw = String(input || "").replace(/\r\n?/g, "\n");
144+
if (!raw.trim()) return "";
145+
return raw
146+
.replace(/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/gi, "$1")
147+
.replace(/\s*\((https?:\/\/[^)]+)\)/gi, "")
148+
.replace(/<https?:\/\/[^>]+>/gi, "")
149+
.replace(/https?:\/\/\S+/gi, "")
150+
.replace(/[ \t]+$/gm, "")
151+
.replace(/\n{3,}/g, "\n\n")
152+
.trim();
153+
};
154+
155+
const releaseNoteLines = computed(() => {
156+
const sanitized = sanitizeReleaseNotes(props.info?.releaseNotes || "");
157+
const lines = sanitized
158+
.split("\n")
159+
.map((line) => line.trim())
160+
.filter(Boolean)
161+
.filter((line) => !/^更新内容\s*(\(|()/.test(line))
162+
.filter((line) => !/^完整变更[::]?\s*$/.test(line));
163+
if (!lines.length) return [DEFAULT_RELEASE_NOTE];
164+
return lines;
165+
});
166+
167+
const viewportHeight = computed(() => {
168+
const h = Number(notesViewportRef.value?.clientHeight || 0);
169+
return h > 0 ? h : NOTE_FALLBACK_VIEWPORT_HEIGHT;
170+
});
171+
172+
const virtualStartIndex = computed(() => {
173+
const start = Math.floor(notesScrollTop.value / NOTE_ROW_HEIGHT) - NOTE_OVERSCAN;
174+
return Math.max(0, start);
175+
});
176+
177+
const virtualEndIndex = computed(() => {
178+
const count = Math.ceil(viewportHeight.value / NOTE_ROW_HEIGHT) + NOTE_OVERSCAN * 2;
179+
return Math.min(releaseNoteLines.value.length, virtualStartIndex.value + count);
180+
});
181+
182+
const virtualVisibleItems = computed(() => {
183+
const start = virtualStartIndex.value;
184+
return releaseNoteLines.value.slice(start, virtualEndIndex.value).map((text, idx) => ({
185+
key: `${start + idx}-${text}`,
186+
text,
187+
}));
188+
});
189+
190+
const virtualOffsetTop = computed(() => virtualStartIndex.value * NOTE_ROW_HEIGHT);
191+
const virtualTotalHeight = computed(() => releaseNoteLines.value.length * NOTE_ROW_HEIGHT);
192+
193+
const onNotesScroll = (event) => {
194+
notesScrollTop.value = Number(event?.target?.scrollTop || 0);
195+
};
196+
197+
watch(
198+
() => [props.open, props.info?.version, props.info?.releaseNotes],
199+
() => {
200+
notesScrollTop.value = 0;
201+
if (notesViewportRef.value) {
202+
notesViewportRef.value.scrollTop = 0;
203+
}
204+
}
205+
);
206+
116207
const safeProgress = computed(() => {
117208
if (typeof props.progress === "number") return { percent: props.progress };
118209
if (props.progress && typeof props.progress === "object") return props.progress;

0 commit comments

Comments
 (0)