@hanology/cham-browser 0.4.22 → 0.4.24
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/package.json
CHANGED
|
@@ -21,6 +21,71 @@ const emit = defineEmits<{
|
|
|
21
21
|
|
|
22
22
|
const bodyRef = ref<HTMLElement | null>(null)
|
|
23
23
|
|
|
24
|
+
// ─── Resize ───
|
|
25
|
+
const paneWidth = ref(320)
|
|
26
|
+
const MIN_W = 180
|
|
27
|
+
const MAX_W = 500
|
|
28
|
+
const RESIZE_KEY = 'ann-pane-width'
|
|
29
|
+
|
|
30
|
+
function initWidth() {
|
|
31
|
+
try {
|
|
32
|
+
const saved = localStorage.getItem(RESIZE_KEY)
|
|
33
|
+
if (saved) {
|
|
34
|
+
const w = parseInt(saved, 10)
|
|
35
|
+
if (w >= MIN_W && w <= MAX_W) paneWidth.value = w
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
} catch {}
|
|
39
|
+
paneWidth.value = props.vertical ? 240 : 320
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let resizing = false
|
|
43
|
+
let resizeStartX = 0
|
|
44
|
+
let resizeStartW = 0
|
|
45
|
+
let hasMoved = false
|
|
46
|
+
let moveFn: ((e: MouseEvent | TouchEvent) => void) | null = null
|
|
47
|
+
let endFn: (() => void) | null = null
|
|
48
|
+
|
|
49
|
+
function onHandleStart(e: MouseEvent | TouchEvent) {
|
|
50
|
+
resizing = true
|
|
51
|
+
hasMoved = false
|
|
52
|
+
resizeStartX = 'touches' in e ? e.touches[0].clientX : (e as MouseEvent).clientX
|
|
53
|
+
resizeStartW = paneWidth.value
|
|
54
|
+
|
|
55
|
+
moveFn = (ev: MouseEvent | TouchEvent) => {
|
|
56
|
+
if (!resizing) return
|
|
57
|
+
const x = 'touches' in ev ? ev.touches[0].clientX : (ev as MouseEvent).clientX
|
|
58
|
+
const dx = x - resizeStartX
|
|
59
|
+
if (Math.abs(dx) > 2) hasMoved = true
|
|
60
|
+
paneWidth.value = Math.max(MIN_W, Math.min(MAX_W, resizeStartW + dx))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
endFn = () => {
|
|
64
|
+
resizing = false
|
|
65
|
+
if (!hasMoved) {
|
|
66
|
+
emit('close')
|
|
67
|
+
} else {
|
|
68
|
+
try { localStorage.setItem(RESIZE_KEY, String(paneWidth.value)) } catch {}
|
|
69
|
+
}
|
|
70
|
+
if (moveFn) {
|
|
71
|
+
document.removeEventListener('mousemove', moveFn)
|
|
72
|
+
document.removeEventListener('touchmove', moveFn)
|
|
73
|
+
}
|
|
74
|
+
if (endFn) {
|
|
75
|
+
document.removeEventListener('mouseup', endFn)
|
|
76
|
+
document.removeEventListener('touchend', endFn)
|
|
77
|
+
}
|
|
78
|
+
moveFn = null
|
|
79
|
+
endFn = null
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
document.addEventListener('mousemove', moveFn)
|
|
83
|
+
document.addEventListener('mouseup', endFn)
|
|
84
|
+
document.addEventListener('touchmove', moveFn, { passive: false })
|
|
85
|
+
document.addEventListener('touchend', endFn)
|
|
86
|
+
e.preventDefault()
|
|
87
|
+
}
|
|
88
|
+
|
|
24
89
|
function getSegment(ann: Annotation) {
|
|
25
90
|
return annotationToPronSegment(ann)
|
|
26
91
|
}
|
|
@@ -69,14 +134,33 @@ watch(() => props.activeId, async (id) => {
|
|
|
69
134
|
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
|
|
70
135
|
})
|
|
71
136
|
|
|
72
|
-
onMounted(() =>
|
|
73
|
-
|
|
137
|
+
onMounted(() => {
|
|
138
|
+
initWidth()
|
|
139
|
+
document.addEventListener('keydown', onKeydown)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
onBeforeUnmount(() => {
|
|
143
|
+
document.removeEventListener('keydown', onKeydown)
|
|
144
|
+
if (moveFn) {
|
|
145
|
+
document.removeEventListener('mousemove', moveFn)
|
|
146
|
+
document.removeEventListener('touchmove', moveFn)
|
|
147
|
+
}
|
|
148
|
+
if (endFn) {
|
|
149
|
+
document.removeEventListener('mouseup', endFn)
|
|
150
|
+
document.removeEventListener('touchend', endFn)
|
|
151
|
+
}
|
|
152
|
+
})
|
|
74
153
|
</script>
|
|
75
154
|
|
|
76
155
|
<template>
|
|
77
156
|
<Teleport to="body">
|
|
78
157
|
<Transition name="ann-pane">
|
|
79
|
-
<div
|
|
158
|
+
<div
|
|
159
|
+
v-if="visible && annotations.length"
|
|
160
|
+
class="ann-pane"
|
|
161
|
+
:class="{ vertical }"
|
|
162
|
+
:style="{ width: paneWidth + 'px' }"
|
|
163
|
+
>
|
|
80
164
|
<div class="ann-pane-header">
|
|
81
165
|
<span class="ann-pane-title">注釋</span>
|
|
82
166
|
<span class="ann-pane-count">{{ annotations.length }}</span>
|
|
@@ -93,18 +177,29 @@ onBeforeUnmount(() => document.removeEventListener('keydown', onKeydown))
|
|
|
93
177
|
:class="{ active: activeId === ann.id, [ann.kind]: true }"
|
|
94
178
|
@click="emit('select', ann)"
|
|
95
179
|
>
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
<span
|
|
99
|
-
<span class="ann-pane-
|
|
100
|
-
<span v-if="layerLabel(ann)" class="ann-pane-layer">{{ layerLabel(ann) }}</span>
|
|
180
|
+
<!-- Vertical: headword column on the right side -->
|
|
181
|
+
<div v-if="vertical && headword(ann)" class="ann-pane-v-word">
|
|
182
|
+
<span class="ann-pane-word-v">{{ headword(ann) }}</span>
|
|
183
|
+
<span class="ann-pane-idx-v">{{ toChineseNumber(idx + 1) }}</span>
|
|
101
184
|
</div>
|
|
102
|
-
<div class="ann-pane-entry-
|
|
103
|
-
<
|
|
104
|
-
|
|
185
|
+
<div class="ann-pane-entry-main">
|
|
186
|
+
<div class="ann-pane-entry-head">
|
|
187
|
+
<span v-if="!vertical" class="ann-pane-idx">{{ toChineseNumber(idx + 1) }}</span>
|
|
188
|
+
<span v-if="!vertical && headword(ann)" class="ann-pane-word">{{ headword(ann) }}</span>
|
|
189
|
+
<span class="ann-pane-kind" :class="ann.kind">{{ kindLabel(ann) }}</span>
|
|
190
|
+
<span v-if="layerLabel(ann)" class="ann-pane-layer">{{ layerLabel(ann) }}</span>
|
|
191
|
+
</div>
|
|
192
|
+
<div class="ann-pane-entry-body">
|
|
193
|
+
<PronunciationGroup v-if="getSegment(ann)" :segment="getSegment(ann)!" />
|
|
194
|
+
<div v-if="ann.text && ann.kind !== 'pronunciation'" class="ann-pane-text">{{ ann.text }}</div>
|
|
195
|
+
</div>
|
|
105
196
|
</div>
|
|
106
197
|
</div>
|
|
107
198
|
</div>
|
|
199
|
+
<!-- Resize / close handle on right edge -->
|
|
200
|
+
<div class="ann-pane-handle" @mousedown="onHandleStart" @touchstart.prevent="onHandleStart">
|
|
201
|
+
<span class="ann-handle-grip" />
|
|
202
|
+
</div>
|
|
108
203
|
</div>
|
|
109
204
|
</Transition>
|
|
110
205
|
</Teleport>
|
|
@@ -115,7 +210,6 @@ onBeforeUnmount(() => document.removeEventListener('keydown', onKeydown))
|
|
|
115
210
|
position: fixed;
|
|
116
211
|
left: 0;
|
|
117
212
|
top: 0;
|
|
118
|
-
width: 320px;
|
|
119
213
|
height: 100vh;
|
|
120
214
|
background: var(--surface-warm);
|
|
121
215
|
border-right: 1px solid var(--border);
|
|
@@ -276,7 +370,35 @@ onBeforeUnmount(() => document.removeEventListener('keydown', onKeydown))
|
|
|
276
370
|
white-space: pre-line;
|
|
277
371
|
}
|
|
278
372
|
|
|
279
|
-
/*
|
|
373
|
+
/* ─── Resize / close handle ─── */
|
|
374
|
+
.ann-pane-handle {
|
|
375
|
+
position: absolute;
|
|
376
|
+
top: 0;
|
|
377
|
+
right: -6px;
|
|
378
|
+
width: 14px;
|
|
379
|
+
height: 100%;
|
|
380
|
+
cursor: col-resize;
|
|
381
|
+
z-index: 2;
|
|
382
|
+
display: flex;
|
|
383
|
+
align-items: center;
|
|
384
|
+
justify-content: center;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.ann-handle-grip {
|
|
388
|
+
display: block;
|
|
389
|
+
width: 3px;
|
|
390
|
+
height: 32px;
|
|
391
|
+
border-radius: 2px;
|
|
392
|
+
background: var(--border);
|
|
393
|
+
transition: all 0.2s;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.ann-pane-handle:hover .ann-handle-grip {
|
|
397
|
+
background: var(--vermillion);
|
|
398
|
+
height: 48px;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/* ─── Transition ─── */
|
|
280
402
|
.ann-pane-enter-active {
|
|
281
403
|
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
282
404
|
}
|
|
@@ -290,17 +412,19 @@ onBeforeUnmount(() => document.removeEventListener('keydown', onKeydown))
|
|
|
290
412
|
|
|
291
413
|
@media (max-width: 768px) {
|
|
292
414
|
.ann-pane {
|
|
293
|
-
width: 100
|
|
415
|
+
width: 100% !important;
|
|
294
416
|
height: auto;
|
|
295
417
|
max-height: 55vh;
|
|
296
418
|
top: auto;
|
|
297
419
|
bottom: 0;
|
|
298
|
-
left: 0;
|
|
299
420
|
border-right: none;
|
|
300
421
|
border-top: 1px solid var(--border);
|
|
301
422
|
border-radius: 14px 14px 0 0;
|
|
302
423
|
box-shadow: 0 -4px 24px rgba(var(--shadow-rgb), 0.08);
|
|
303
424
|
}
|
|
425
|
+
.ann-pane-handle {
|
|
426
|
+
display: none;
|
|
427
|
+
}
|
|
304
428
|
.ann-pane-enter-from,
|
|
305
429
|
.ann-pane-leave-to {
|
|
306
430
|
transform: translateY(100%);
|
|
@@ -311,7 +435,6 @@ onBeforeUnmount(() => document.removeEventListener('keydown', onKeydown))
|
|
|
311
435
|
.ann-pane.vertical {
|
|
312
436
|
writing-mode: vertical-rl;
|
|
313
437
|
text-orientation: mixed;
|
|
314
|
-
width: 220px;
|
|
315
438
|
}
|
|
316
439
|
|
|
317
440
|
.ann-pane.vertical .ann-pane-body {
|
|
@@ -322,6 +445,8 @@ onBeforeUnmount(() => document.removeEventListener('keydown', onKeydown))
|
|
|
322
445
|
}
|
|
323
446
|
|
|
324
447
|
.ann-pane.vertical .ann-pane-entry {
|
|
448
|
+
display: flex;
|
|
449
|
+
flex-direction: column;
|
|
325
450
|
padding: 10px 6px;
|
|
326
451
|
border-bottom: none;
|
|
327
452
|
border-right: 3px solid transparent;
|
|
@@ -339,6 +464,37 @@ onBeforeUnmount(() => document.removeEventListener('keydown', onKeydown))
|
|
|
339
464
|
border-right-color: var(--jade);
|
|
340
465
|
}
|
|
341
466
|
|
|
467
|
+
/* Vertical headword on the right (first in vertical-rl reading order) */
|
|
468
|
+
.ann-pane-v-word {
|
|
469
|
+
writing-mode: vertical-rl;
|
|
470
|
+
text-orientation: upright;
|
|
471
|
+
display: flex;
|
|
472
|
+
flex-direction: column;
|
|
473
|
+
align-items: center;
|
|
474
|
+
gap: 6px;
|
|
475
|
+
padding: 6px 8px;
|
|
476
|
+
border-left: 1px solid var(--border-light);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.ann-pane-word-v {
|
|
480
|
+
font-family: var(--serif);
|
|
481
|
+
font-size: 20px;
|
|
482
|
+
font-weight: 900;
|
|
483
|
+
letter-spacing: 6px;
|
|
484
|
+
color: var(--ink);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.ann-pane-idx-v {
|
|
488
|
+
font-family: var(--serif);
|
|
489
|
+
font-size: 12px;
|
|
490
|
+
font-weight: 700;
|
|
491
|
+
color: var(--vermillion);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.ann-pane-entry.active.pronunciation .ann-pane-idx-v {
|
|
495
|
+
color: var(--jade);
|
|
496
|
+
}
|
|
497
|
+
|
|
342
498
|
.ann-pane.vertical .ann-pane-entry-head {
|
|
343
499
|
flex-direction: row;
|
|
344
500
|
gap: 4px;
|
|
@@ -356,4 +512,14 @@ onBeforeUnmount(() => document.removeEventListener('keydown', onKeydown))
|
|
|
356
512
|
.ann-pane.vertical .ann-pane-close {
|
|
357
513
|
writing-mode: horizontal-tb;
|
|
358
514
|
}
|
|
515
|
+
|
|
516
|
+
.ann-pane.vertical .ann-pane-handle {
|
|
517
|
+
writing-mode: horizontal-tb;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
@media (max-width: 768px) {
|
|
521
|
+
.ann-pane.vertical .ann-pane-handle {
|
|
522
|
+
display: none;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
359
525
|
</style>
|