Skip to content

Commit 7d9733b

Browse files
committed
Add diagram lightbox
1 parent d220ff2 commit 7d9733b

2 files changed

Lines changed: 186 additions & 24 deletions

File tree

docs/.vitepress/theme/custom.css

Lines changed: 110 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -129,51 +129,138 @@
129129
}
130130
}
131131

132-
/* Diagrams are evidence, not decoration: keep them readable on desktop and scrollable on mobile. */
132+
/* Diagrams stay in-flow by default; click them to inspect in a full-screen lightbox. */
133+
.vp-doc .ac-mermaid,
134+
.vp-doc img[src$="harness-engine-shift.svg"] {
135+
cursor: zoom-in;
136+
}
137+
133138
.vp-doc .ac-mermaid {
134-
margin: 2rem calc((min(100vw, 1280px) - 100%) / -2);
135-
padding: 1.25rem;
139+
position: relative;
140+
margin: 1.5rem 0 2rem;
141+
padding: 1rem;
142+
overflow: hidden;
143+
}
144+
145+
.vp-doc .ac-mermaid::after,
146+
.vp-doc p:has(> img[src$="harness-engine-shift.svg"])::after {
147+
content: "点击放大";
148+
position: absolute;
149+
right: 0.9rem;
150+
top: 0.8rem;
151+
border: 1px solid var(--vp-c-divider);
152+
border-radius: 999px;
153+
padding: 0.2rem 0.55rem;
154+
background: color-mix(in srgb, var(--vp-c-bg) 88%, transparent);
155+
color: var(--vp-c-text-2);
156+
font-size: 0.74rem;
157+
font-weight: 700;
158+
pointer-events: none;
136159
}
137160

138161
.vp-doc .ac-mermaid__canvas {
139-
min-width: 1180px;
162+
min-width: 0;
140163
}
141164

142165
.vp-doc .ac-mermaid__canvas svg {
143166
display: block;
144-
width: auto;
145-
min-width: 1500px;
146-
max-width: none;
147-
margin: 0 auto;
167+
width: 100%;
168+
min-width: 0;
169+
max-width: 100%;
170+
height: auto;
171+
}
172+
173+
.vp-doc p:has(> img[src$="harness-engine-shift.svg"]) {
174+
position: relative;
148175
}
149176

150177
.vp-doc img[src$="harness-engine-shift.svg"] {
151178
display: block;
152-
width: min(1180px, 100%);
153-
max-width: 1180px;
179+
width: 100%;
180+
max-width: 100%;
154181
margin: 1.5rem auto 2rem;
155182
border: 1px solid var(--vp-c-divider);
156183
border-radius: 20px;
157184
background: var(--vp-c-bg-soft);
158185
}
159186

187+
.ac-lightbox {
188+
position: fixed;
189+
inset: 0;
190+
z-index: 9999;
191+
display: flex;
192+
align-items: center;
193+
justify-content: center;
194+
padding: 3rem;
195+
background: rgba(9, 14, 24, 0.86);
196+
backdrop-filter: blur(10px);
197+
}
198+
199+
.ac-lightbox[hidden] {
200+
display: none;
201+
}
202+
203+
.ac-lightbox__stage {
204+
max-width: min(96vw, 1600px);
205+
max-height: 88vh;
206+
overflow: auto;
207+
border-radius: 22px;
208+
background: var(--vp-c-bg);
209+
box-shadow: 0 28px 80px rgba(0, 0, 0, 0.38);
210+
}
211+
212+
.ac-lightbox__stage svg,
213+
.ac-lightbox__stage img {
214+
display: block;
215+
width: auto;
216+
max-width: none;
217+
height: auto;
218+
}
219+
220+
.ac-lightbox__stage svg {
221+
min-width: 1600px;
222+
}
223+
224+
.ac-lightbox__stage img {
225+
min-width: min(1600px, 92vw);
226+
}
227+
228+
.ac-lightbox__close {
229+
position: fixed;
230+
top: 1.2rem;
231+
right: 1.2rem;
232+
border: 1px solid rgba(255, 255, 255, 0.34);
233+
border-radius: 999px;
234+
padding: 0.45rem 0.8rem;
235+
background: rgba(255, 255, 255, 0.12);
236+
color: #fff;
237+
font-weight: 800;
238+
cursor: pointer;
239+
}
240+
241+
.ac-lightbox__hint {
242+
position: fixed;
243+
left: 50%;
244+
bottom: 1.2rem;
245+
transform: translateX(-50%);
246+
border-radius: 999px;
247+
padding: 0.38rem 0.8rem;
248+
background: rgba(255, 255, 255, 0.12);
249+
color: rgba(255, 255, 255, 0.82);
250+
font-size: 0.82rem;
251+
}
252+
160253
@media (max-width: 760px) {
161-
.vp-doc .ac-mermaid {
162-
margin-left: -1rem;
163-
margin-right: -1rem;
164-
border-radius: 0;
254+
.ac-lightbox {
255+
padding: 1rem;
165256
}
166257

167-
.vp-doc .ac-mermaid__canvas {
168-
min-width: 960px;
258+
.ac-lightbox__stage {
259+
max-width: 96vw;
260+
max-height: 84vh;
169261
}
170262

171-
.vp-doc .ac-mermaid__canvas svg {
172-
min-width: 760px;
173-
width: auto;
263+
.ac-lightbox__stage svg {
264+
min-width: 1200px;
174265
}
175266
}
176-
177-
.vp-doc .ac-mermaid__canvas svg[viewBox] {
178-
min-width: 1500px;
179-
}

docs/.vitepress/theme/index.ts

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import DefaultTheme from 'vitepress/theme'
2-
import { defineComponent, h, onMounted, watch } from 'vue'
2+
import { defineComponent, h, nextTick, onMounted, watch } from 'vue'
33
import type { Theme } from 'vitepress'
44
import { useRoute } from 'vitepress'
55
import MermaidDiagram from './MermaidDiagram.vue'
@@ -61,6 +61,75 @@ async function writeClipboard(value: string) {
6161
}
6262
}
6363

64+
65+
function openDiagramLightbox(source: Element) {
66+
const existing = document.querySelector('.ac-lightbox')
67+
if (existing) existing.remove()
68+
69+
const overlay = document.createElement('div')
70+
overlay.className = 'ac-lightbox'
71+
overlay.setAttribute('role', 'dialog')
72+
overlay.setAttribute('aria-modal', 'true')
73+
74+
const stage = document.createElement('div')
75+
stage.className = 'ac-lightbox__stage'
76+
77+
const close = document.createElement('button')
78+
close.className = 'ac-lightbox__close'
79+
close.type = 'button'
80+
close.textContent = '关闭'
81+
82+
const hint = document.createElement('div')
83+
hint.className = 'ac-lightbox__hint'
84+
hint.textContent = '滚动查看大图,按 Esc 关闭'
85+
86+
const clone = source.cloneNode(true) as Element
87+
clone.removeAttribute('id')
88+
stage.appendChild(clone)
89+
overlay.append(close, stage, hint)
90+
document.body.appendChild(overlay)
91+
document.body.style.overflow = 'hidden'
92+
93+
const cleanup = () => {
94+
document.body.style.overflow = ''
95+
document.removeEventListener('keydown', onKeydown)
96+
overlay.remove()
97+
}
98+
99+
const onKeydown = (event: KeyboardEvent) => {
100+
if (event.key === 'Escape') cleanup()
101+
}
102+
103+
close.addEventListener('click', cleanup)
104+
overlay.addEventListener('click', (event) => {
105+
if (event.target === overlay) cleanup()
106+
})
107+
document.addEventListener('keydown', onKeydown)
108+
}
109+
110+
function installDiagramLightbox() {
111+
const targets = [
112+
...document.querySelectorAll('.ac-mermaid__canvas svg'),
113+
...document.querySelectorAll('img[src$="harness-engine-shift.svg"]')
114+
]
115+
116+
for (const target of targets) {
117+
const element = target as HTMLElement
118+
if (element.dataset.lightboxReady === 'true') continue
119+
element.dataset.lightboxReady = 'true'
120+
element.tabIndex = 0
121+
element.setAttribute('role', 'button')
122+
element.setAttribute('aria-label', '点击放大图表')
123+
element.addEventListener('click', () => openDiagramLightbox(element))
124+
element.addEventListener('keydown', (event) => {
125+
if (event.key === 'Enter' || event.key === ' ') {
126+
event.preventDefault()
127+
openDiagramLightbox(element)
128+
}
129+
})
130+
}
131+
}
132+
64133
const CopyMarkdownButton = defineComponent({
65134
name: 'CopyMarkdownButton',
66135
setup() {
@@ -70,6 +139,12 @@ const CopyMarkdownButton = defineComponent({
70139
const update = () => {
71140
const existing = document.querySelector<HTMLButtonElement>('.ac-copy-md')
72141
if (existing) existing.dataset.path = route.path
142+
const scheduleInstall = () => {
143+
installDiagramLightbox()
144+
window.setTimeout(installDiagramLightbox, 300)
145+
window.setTimeout(installDiagramLightbox, 1000)
146+
}
147+
nextTick(scheduleInstall)
73148
}
74149
update()
75150
watch(() => route.path, update)

0 commit comments

Comments
 (0)