aegon-dangerious 1.0.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.
- package/build-report/components/AiPromptGenerateDialog.vue +349 -0
- package/build-report/components/CapsuleScrollbar.vue +145 -0
- package/build-report/components/ChapterTitleScroll.vue +292 -0
- package/build-report/components/PromptField.vue +314 -0
- package/build-report/components/RecentReportsTable.vue +408 -0
- package/build-report/components/ReportChapterPreview.vue +437 -0
- package/build-report/components/ReportChapterWorkspace.vue +923 -0
- package/build-report/components/ReportCollapsibleSection.vue +132 -0
- package/build-report/components/ReportConfigPanel.vue +609 -0
- package/build-report/components/ReportGenerateSettingsPanel.vue +403 -0
- package/build-report/components/ReportGeneratingPanel.vue +908 -0
- package/build-report/components/ReportHistoryList.vue +157 -0
- package/build-report/components/ReportHistoryPanel.vue +140 -0
- package/build-report/components/ReportPromptPanel.vue +445 -0
- package/build-report/components/ReportSectionConfirmPopover.vue +232 -0
- package/build-report/components/ReportSourceFileSummary.vue +544 -0
- package/build-report/components/ReportSourcesMoreOverlay.vue +279 -0
- package/build-report/components/ReportSourcesPanel.vue +1055 -0
- package/build-report/components/ReportTemplateDetail.vue +176 -0
- package/build-report/components/ReportTemplateEditorFormPanel.vue +602 -0
- package/build-report/components/ReportTemplatePicker.vue +802 -0
- package/build-report/components/ReportTemplatePromptField.vue +314 -0
- package/build-report/components/ReportWorkflowSidebar.vue +104 -0
- package/build-report/components/reportEdit.vue +334 -0
- package/build-report/index.vue +646 -0
- package/build-report/report-list.vue +407 -0
- package/build-report/report-template-data.ts +248 -0
- package/build-report/styles/report-workflow-dialog.css +33 -0
- package/build-report/useReportWorkflow.ts +343 -0
- package/build-report/utils/report-edit-route.ts +109 -0
- package/build-report/utils/scroll.ts +361 -0
- package/package.json +12 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
|
|
2
|
+
|
|
3
|
+
export const CAPSULE_SCROLL_CONTAINER_CLASS = 'capsule-scroll-container'
|
|
4
|
+
|
|
5
|
+
export interface CapsuleScrollbarTheme {
|
|
6
|
+
thumbColor?: string
|
|
7
|
+
thumbHoverColor?: string
|
|
8
|
+
thumbActiveColor?: string
|
|
9
|
+
trackColor?: string
|
|
10
|
+
thumbWidthPx?: number
|
|
11
|
+
classPrefix?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface CapsuleScrollbarOptions extends CapsuleScrollbarTheme {
|
|
15
|
+
scrollEl: { value: HTMLElement | null }
|
|
16
|
+
anchorEl?: { value: HTMLElement | null }
|
|
17
|
+
active: { value: boolean }
|
|
18
|
+
gutterPx?: number
|
|
19
|
+
thumbWidthPx?: number
|
|
20
|
+
minThumbPx?: number
|
|
21
|
+
visibleRatio?: number
|
|
22
|
+
alwaysVisible?: boolean
|
|
23
|
+
autoHideAfterMs?: number
|
|
24
|
+
rightOffsetPx?: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CapsuleScrollbarBindings {
|
|
28
|
+
canScroll: { value: boolean }
|
|
29
|
+
shown: { value: boolean }
|
|
30
|
+
visible: { value: boolean }
|
|
31
|
+
rootStyle: { value: { [key: string]: string } }
|
|
32
|
+
thumbStyle: { value: { [key: string]: string } }
|
|
33
|
+
rootClass: string
|
|
34
|
+
thumbClass: string
|
|
35
|
+
onThumbMouseDown: (event: MouseEvent) => void
|
|
36
|
+
refresh: () => void
|
|
37
|
+
bind: () => void
|
|
38
|
+
unbind: () => void
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const DEFAULT_CLASS_PREFIX = 'capsule-scrollbar'
|
|
42
|
+
|
|
43
|
+
interface NativeHideSnapshot {
|
|
44
|
+
paddingRight: string
|
|
45
|
+
marginRight: string
|
|
46
|
+
width: string
|
|
47
|
+
boxSizing: string
|
|
48
|
+
scrollbarWidth: string
|
|
49
|
+
msOverflowStyle: string
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function markCapsuleScrollContainer(el: HTMLElement): () => void {
|
|
53
|
+
const computed = getComputedStyle(el)
|
|
54
|
+
const snapshot: NativeHideSnapshot = {
|
|
55
|
+
paddingRight: el.style.paddingRight,
|
|
56
|
+
marginRight: el.style.marginRight,
|
|
57
|
+
width: el.style.width,
|
|
58
|
+
boxSizing: el.style.boxSizing,
|
|
59
|
+
scrollbarWidth: el.style.scrollbarWidth,
|
|
60
|
+
msOverflowStyle: (el.style as CSSStyleDeclaration & { msOverflowStyle?: string }).msOverflowStyle ?? '',
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
el.style.setProperty('--capsule-scroll-padding-right', computed.paddingRight)
|
|
64
|
+
el.classList.add(CAPSULE_SCROLL_CONTAINER_CLASS)
|
|
65
|
+
el.style.scrollbarWidth = 'none'
|
|
66
|
+
;(el.style as CSSStyleDeclaration & { msOverflowStyle?: string }).msOverflowStyle = 'none'
|
|
67
|
+
|
|
68
|
+
return () => {
|
|
69
|
+
el.classList.remove(CAPSULE_SCROLL_CONTAINER_CLASS)
|
|
70
|
+
el.style.removeProperty('--capsule-scroll-padding-right')
|
|
71
|
+
el.style.paddingRight = snapshot.paddingRight
|
|
72
|
+
el.style.marginRight = snapshot.marginRight
|
|
73
|
+
el.style.width = snapshot.width
|
|
74
|
+
el.style.boxSizing = snapshot.boxSizing
|
|
75
|
+
el.style.scrollbarWidth = snapshot.scrollbarWidth
|
|
76
|
+
;(el.style as CSSStyleDeclaration & { msOverflowStyle?: string }).msOverflowStyle =
|
|
77
|
+
snapshot.msOverflowStyle
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function useCapsuleScrollbar(options: CapsuleScrollbarOptions): CapsuleScrollbarBindings {
|
|
82
|
+
const thumbWidthPx = options.thumbWidthPx ?? 10
|
|
83
|
+
const gutterPx = options.gutterPx ?? thumbWidthPx
|
|
84
|
+
const minThumbPx = options.minThumbPx ?? 48
|
|
85
|
+
const visibleRatio = options.visibleRatio ?? 1
|
|
86
|
+
const rightOffsetPx = options.rightOffsetPx ?? 1
|
|
87
|
+
const autoHideAfterMs = options.autoHideAfterMs ?? 3000
|
|
88
|
+
const alwaysVisible = options.alwaysVisible ?? autoHideAfterMs <= 0
|
|
89
|
+
const classPrefix = options.classPrefix ?? DEFAULT_CLASS_PREFIX
|
|
90
|
+
|
|
91
|
+
const canScroll = ref(false)
|
|
92
|
+
const shown = ref(false)
|
|
93
|
+
const zoneTop = ref(0)
|
|
94
|
+
const zoneRight = ref(rightOffsetPx)
|
|
95
|
+
const zoneHeight = ref(0)
|
|
96
|
+
const thumbHeight = ref(minThumbPx)
|
|
97
|
+
const thumbOffset = ref(0)
|
|
98
|
+
|
|
99
|
+
const visible = computed(() => canScroll.value && (alwaysVisible || shown.value))
|
|
100
|
+
|
|
101
|
+
let resizeObserver: ResizeObserver | undefined
|
|
102
|
+
let thumbDragCleanup: (() => void) | undefined
|
|
103
|
+
let removeContainerMark: (() => void) | undefined
|
|
104
|
+
let hideTimer: number | undefined
|
|
105
|
+
let rafId = 0
|
|
106
|
+
|
|
107
|
+
const rootStyle = computed(() => ({
|
|
108
|
+
top: `${zoneTop.value + thumbOffset.value}px`,
|
|
109
|
+
right: `${zoneRight.value}px`,
|
|
110
|
+
width: `${gutterPx}px`,
|
|
111
|
+
height: `${Math.round(thumbHeight.value)}px`,
|
|
112
|
+
}))
|
|
113
|
+
|
|
114
|
+
const thumbStyle = computed(() => ({
|
|
115
|
+
width: `${thumbWidthPx}px`,
|
|
116
|
+
height: '100%',
|
|
117
|
+
}))
|
|
118
|
+
|
|
119
|
+
function clearHideTimer() {
|
|
120
|
+
if (hideTimer !== undefined) {
|
|
121
|
+
clearTimeout(hideTimer)
|
|
122
|
+
hideTimer = undefined
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function scheduleHide() {
|
|
127
|
+
clearHideTimer()
|
|
128
|
+
if (alwaysVisible || autoHideAfterMs <= 0) return
|
|
129
|
+
hideTimer = window.setTimeout(() => {
|
|
130
|
+
shown.value = false
|
|
131
|
+
hideTimer = undefined
|
|
132
|
+
}, autoHideAfterMs)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function revealBar() {
|
|
136
|
+
if (!canScroll.value) return
|
|
137
|
+
shown.value = true
|
|
138
|
+
scheduleHide()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function refreshLayout() {
|
|
142
|
+
const scrollEl = options.scrollEl.value
|
|
143
|
+
const anchorEl = options.anchorEl?.value ?? scrollEl
|
|
144
|
+
if (!options.active.value || !scrollEl || !anchorEl) {
|
|
145
|
+
canScroll.value = false
|
|
146
|
+
shown.value = false
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const { scrollTop, scrollHeight, clientHeight } = scrollEl
|
|
151
|
+
const scrollable = scrollHeight > clientHeight + 1
|
|
152
|
+
canScroll.value = scrollable
|
|
153
|
+
if (!scrollable) {
|
|
154
|
+
shown.value = false
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const anchorRect = anchorEl.getBoundingClientRect()
|
|
159
|
+
const shrinkInset = (anchorRect.height * (1 - visibleRatio)) / 2
|
|
160
|
+
|
|
161
|
+
zoneTop.value = Math.round(anchorRect.top + shrinkInset)
|
|
162
|
+
zoneRight.value = rightOffsetPx
|
|
163
|
+
zoneHeight.value = Math.round(anchorRect.height * visibleRatio)
|
|
164
|
+
|
|
165
|
+
const trackH = zoneHeight.value
|
|
166
|
+
thumbHeight.value = Math.max(minThumbPx, (clientHeight / scrollHeight) * trackH)
|
|
167
|
+
const maxThumbTravel = Math.max(0, trackH - thumbHeight.value)
|
|
168
|
+
const scrollMax = scrollHeight - clientHeight
|
|
169
|
+
thumbOffset.value = scrollMax > 0 ? Math.round((scrollTop / scrollMax) * maxThumbTravel) : 0
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function scheduleRefresh() {
|
|
173
|
+
cancelAnimationFrame(rafId)
|
|
174
|
+
rafId = requestAnimationFrame(refreshLayout)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function onScroll() {
|
|
178
|
+
revealBar()
|
|
179
|
+
scheduleRefresh()
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function bind() {
|
|
183
|
+
unbind()
|
|
184
|
+
const scrollEl = options.scrollEl.value
|
|
185
|
+
if (!scrollEl) return
|
|
186
|
+
|
|
187
|
+
removeContainerMark = markCapsuleScrollContainer(scrollEl)
|
|
188
|
+
refreshLayout()
|
|
189
|
+
scrollEl.addEventListener('scroll', onScroll, { passive: true })
|
|
190
|
+
resizeObserver = new ResizeObserver(() => scheduleRefresh())
|
|
191
|
+
resizeObserver.observe(scrollEl)
|
|
192
|
+
if (options.anchorEl?.value) resizeObserver.observe(options.anchorEl.value)
|
|
193
|
+
window.addEventListener('resize', scheduleRefresh, { passive: true })
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function unbind() {
|
|
197
|
+
cancelAnimationFrame(rafId)
|
|
198
|
+
clearHideTimer()
|
|
199
|
+
window.removeEventListener('resize', scheduleRefresh)
|
|
200
|
+
resizeObserver?.disconnect()
|
|
201
|
+
resizeObserver = undefined
|
|
202
|
+
thumbDragCleanup?.()
|
|
203
|
+
thumbDragCleanup = undefined
|
|
204
|
+
options.scrollEl.value?.removeEventListener('scroll', onScroll)
|
|
205
|
+
removeContainerMark?.()
|
|
206
|
+
removeContainerMark = undefined
|
|
207
|
+
canScroll.value = false
|
|
208
|
+
shown.value = false
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function onThumbMouseDown(event: MouseEvent) {
|
|
212
|
+
const scrollEl = options.scrollEl.value
|
|
213
|
+
if (!scrollEl) return
|
|
214
|
+
|
|
215
|
+
event.preventDefault()
|
|
216
|
+
revealBar()
|
|
217
|
+
clearHideTimer()
|
|
218
|
+
|
|
219
|
+
const startY = event.clientY
|
|
220
|
+
const startScrollTop = scrollEl.scrollTop
|
|
221
|
+
const trackH = zoneHeight.value
|
|
222
|
+
const scrollMax = scrollEl.scrollHeight - scrollEl.clientHeight
|
|
223
|
+
const maxThumbTravel = Math.max(0, trackH - thumbHeight.value)
|
|
224
|
+
|
|
225
|
+
const onMove = (ev: MouseEvent) => {
|
|
226
|
+
if (maxThumbTravel <= 0 || scrollMax <= 0) return
|
|
227
|
+
scrollEl.scrollTop = startScrollTop + ((ev.clientY - startY) / maxThumbTravel) * scrollMax
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const onUp = () => {
|
|
231
|
+
document.removeEventListener('mousemove', onMove)
|
|
232
|
+
document.removeEventListener('mouseup', onUp)
|
|
233
|
+
thumbDragCleanup = undefined
|
|
234
|
+
scheduleHide()
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
document.addEventListener('mousemove', onMove)
|
|
238
|
+
document.addEventListener('mouseup', onUp)
|
|
239
|
+
thumbDragCleanup = onUp
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
watch(
|
|
243
|
+
() => options.active.value,
|
|
244
|
+
(active) => {
|
|
245
|
+
if (active) {
|
|
246
|
+
void nextTick(() => {
|
|
247
|
+
bind()
|
|
248
|
+
void nextTick(refreshLayout)
|
|
249
|
+
})
|
|
250
|
+
return
|
|
251
|
+
}
|
|
252
|
+
unbind()
|
|
253
|
+
},
|
|
254
|
+
{ immediate: true },
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
onUnmounted(unbind)
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
canScroll,
|
|
261
|
+
shown,
|
|
262
|
+
visible,
|
|
263
|
+
rootStyle,
|
|
264
|
+
thumbStyle,
|
|
265
|
+
rootClass: classPrefix,
|
|
266
|
+
thumbClass: `${classPrefix}__thumb`,
|
|
267
|
+
onThumbMouseDown,
|
|
268
|
+
refresh: refreshLayout,
|
|
269
|
+
bind,
|
|
270
|
+
unbind,
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// --- 頁面滿屏滾動鎖定 ---
|
|
275
|
+
|
|
276
|
+
/** 鎖定 document 滿屏滾動,返回 cleanup 恢復原 overflow */
|
|
277
|
+
export function lockDocumentScroll(): () => void {
|
|
278
|
+
const html = document.documentElement
|
|
279
|
+
const body = document.body
|
|
280
|
+
const prevHtml = html.style.overflow
|
|
281
|
+
const prevBody = body.style.overflow
|
|
282
|
+
|
|
283
|
+
html.style.overflow = 'hidden'
|
|
284
|
+
body.style.overflow = 'hidden'
|
|
285
|
+
|
|
286
|
+
return () => {
|
|
287
|
+
html.style.overflow = prevHtml
|
|
288
|
+
body.style.overflow = prevBody
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// --- 視口主內容區高度 ---
|
|
293
|
+
|
|
294
|
+
export interface ViewportBodyHeightOptions {
|
|
295
|
+
/** 從視口高度中扣除的固定像素 */
|
|
296
|
+
subtract?: number
|
|
297
|
+
/** 按選擇器測量需扣除的高度(如 PageHeader) */
|
|
298
|
+
subtractSelector?: string
|
|
299
|
+
/** 測量 subtractSelector 時的父級容器,默認取 element 的 offsetParent 或 document */
|
|
300
|
+
subtractRoot?: HTMLElement
|
|
301
|
+
/** 是否監聽 resize,默認 true */
|
|
302
|
+
listenResize?: boolean
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** 當前視口高度(px) */
|
|
306
|
+
export function getViewportHeight(): number {
|
|
307
|
+
return window.innerHeight
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function resolveSubtract(el: HTMLElement, options?: ViewportBodyHeightOptions): number {
|
|
311
|
+
if (options?.subtract !== undefined) return options.subtract
|
|
312
|
+
|
|
313
|
+
if (options?.subtractSelector) {
|
|
314
|
+
const root = options.subtractRoot ?? el.parentElement ?? document.body
|
|
315
|
+
const node = root.querySelector(options.subtractSelector) as HTMLElement | null
|
|
316
|
+
if (node) return node.getBoundingClientRect().height
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return 0
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** 計算主內容區可用高度(視口高度 − 扣除項) */
|
|
323
|
+
export function getViewportBodyHeight(
|
|
324
|
+
el: HTMLElement,
|
|
325
|
+
options?: ViewportBodyHeightOptions,
|
|
326
|
+
): number {
|
|
327
|
+
const subtract = resolveSubtract(el, options)
|
|
328
|
+
return Math.max(0, getViewportHeight() - subtract)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* 將元素高度綁定為當前視口可用高度;返回 cleanup(移除監聽並清空 inline height)
|
|
333
|
+
*/
|
|
334
|
+
export function bindViewportBodyHeight(
|
|
335
|
+
el: HTMLElement,
|
|
336
|
+
options?: ViewportBodyHeightOptions,
|
|
337
|
+
): () => void {
|
|
338
|
+
const listenResize = options?.listenResize ?? true
|
|
339
|
+
|
|
340
|
+
const update = () => {
|
|
341
|
+
const height = getViewportBodyHeight(el, options)
|
|
342
|
+
el.style.height = `${height}px`
|
|
343
|
+
el.style.maxHeight = `${height}px`
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
update()
|
|
347
|
+
|
|
348
|
+
if (!listenResize) {
|
|
349
|
+
return () => {
|
|
350
|
+
el.style.height = ''
|
|
351
|
+
el.style.maxHeight = ''
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
window.addEventListener('resize', update)
|
|
356
|
+
return () => {
|
|
357
|
+
window.removeEventListener('resize', update)
|
|
358
|
+
el.style.height = ''
|
|
359
|
+
el.style.maxHeight = ''
|
|
360
|
+
}
|
|
361
|
+
}
|
package/package.json
ADDED