@hanology/cham-browser 0.3.8 → 0.4.1

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.
@@ -1,11 +1,12 @@
1
1
  <script setup lang="ts">
2
- import { computed, ref } from 'vue'
2
+ import { computed, ref, inject } from 'vue'
3
3
  import { useRouter } from 'vue-router'
4
4
  import { useLibrary } from '../composables/useLibrary'
5
5
  import { useBook } from '../composables/useBook'
6
6
  import { useTitle } from '../composables/useTitle'
7
7
  import { useReadingMode } from '../composables/useReadingMode'
8
8
  import { useHorizontalScroll } from '../composables/useHorizontalScroll'
9
+ import { useI18n } from '../composables/useI18n'
9
10
  import BookCard from '../components/BookCard.vue'
10
11
  import SideNav from '../components/SideNav.vue'
11
12
  import ReadingToolbar from '../components/ReadingToolbar.vue'
@@ -13,10 +14,14 @@ import BackToTop from '../components/BackToTop.vue'
13
14
  import { useSiteConfig } from '../composables/useSiteConfig'
14
15
  import type { BookMeta } from '../types'
15
16
 
17
+ const aboutPane = inject<{ toggleAbout: () => void; closeAbout: () => void }>('aboutPane')
18
+
16
19
  const { scale, books, singleBook, loadLibrary } = useLibrary()
17
20
  await loadLibrary()
18
21
 
19
- useTitle('古典詩文圖書館')
22
+ const { siteTitle, siteSubtitle, aboutHtml, logoUrl } = useSiteConfig()
23
+ const displayTitle = siteTitle || 'CHAM'
24
+ useTitle(displayTitle)
20
25
 
21
26
  // Single-book: redirect to book home
22
27
  if (scale.value === 'single-book' && singleBook.value) {
@@ -37,27 +42,20 @@ if (scale.value === 'single-piece' && singleBook.value) {
37
42
 
38
43
  const router = useRouter()
39
44
  const { layout } = useReadingMode()
40
- const { logoUrl } = useSiteConfig()
41
45
  const isVertical = computed(() => layout.value === 'vertical')
42
46
  const vPageRef = ref<HTMLElement | null>(null)
43
47
  const vScroll = useHorizontalScroll(vPageRef)
44
-
45
- const genreLabel: Record<string, string> = {
46
- poetry: '詩歌',
47
- prose: '散文',
48
- mixed: '綜合',
49
- drama: '戲曲',
50
- }
48
+ const { t } = useI18n()
51
49
 
52
50
  function bookCategory(book: BookMeta): string {
53
- if (book.id.startsWith('skqs-')) return '四庫全書'
54
- if (book.id === 'primary' || book.id === 'primary-culture' || book.id === 'secondary' || book.id === 'nss') return '教材'
55
- return '古典文本'
51
+ if (book.id.startsWith('skqs-')) return t('genre.fourTreasuries')
52
+ if (book.id === 'primary' || book.id === 'primary-culture' || book.id === 'secondary' || book.id === 'nss') return t('genre.textbooks')
53
+ return t('genre.classicalText')
56
54
  }
57
55
 
58
56
  const groupedBooks = computed(() => {
59
57
  const groups = new Map<string, BookMeta[]>()
60
- const order = ['教材', '古典文本', '四庫全書']
58
+ const order = [t('genre.textbooks'), t('genre.classicalText'), t('genre.fourTreasuries')]
61
59
  for (const book of books.value) {
62
60
  const cat = bookCategory(book)
63
61
  if (!groups.has(cat)) groups.set(cat, [])
@@ -70,23 +68,29 @@ const groupedBooks = computed(() => {
70
68
 
71
69
  const totalPieces = computed(() => books.value.reduce((sum, b) => sum + b.count, 0))
72
70
 
71
+ const spacedTitle = computed(() => displayTitle.split('').join(' '))
72
+
73
73
  function openBook(bookId: string) {
74
74
  router.push(`/${bookId}`)
75
75
  }
76
76
  </script>
77
77
 
78
78
  <template>
79
- <div v-if="scale === 'library'">
79
+ <div v-if="scale !== 'library'" class="page-loading">
80
+ <img v-if="logoUrl" :src="logoUrl" alt="" class="page-loading-logo" />
81
+ <div v-else class="page-loading-seal">文</div>
82
+ </div>
83
+ <div v-else>
80
84
  <!-- ═══════ 直排模式 ═══════ -->
81
85
  <div v-if="isVertical" class="v-root">
82
86
  <SideNav @home="router.push('/')" @back="router.push('/')" />
83
87
  <div ref="vPageRef" class="v-page">
84
- <div class="v-about-col">
85
- <router-link to="/about" class="v-about-link">關 於</router-link>
88
+ <div v-if="aboutHtml" class="v-about-col">
89
+ <button class="v-about-link" @click="aboutPane?.toggleAbout()">{{ t('nav.about') }}</button>
86
90
  </div>
87
91
  <section class="v-hero">
88
- <h1 class="v-title">古 詩 文 圖 書 館</h1>
89
- <p class="v-subtitle">Classical Chinese Text Library</p>
92
+ <h1 class="v-title">{{ spacedTitle }}</h1>
93
+ <p v-if="siteSubtitle" class="v-subtitle">{{ siteSubtitle }}</p>
90
94
  <div class="v-divider"></div>
91
95
  </section>
92
96
 
@@ -101,7 +105,7 @@ function openBook(bookId: string) {
101
105
  <h2 class="v-book-title">{{ book.title }}</h2>
102
106
  <p v-if="book.subtitle" class="v-book-sub">{{ book.subtitle }}</p>
103
107
  <div class="v-book-stats">
104
- <span class="v-book-count">{{ book.count }} 篇</span>
108
+ <span class="v-book-count">{{ t('stat.pieceCount', { count: book.count }) }}</span>
105
109
  </div>
106
110
  </div>
107
111
  </section>
@@ -112,15 +116,14 @@ function openBook(bookId: string) {
112
116
  <div v-else class="lib-root">
113
117
  <header class="lib-hero">
114
118
  <img v-if="logoUrl" :src="logoUrl" alt="" class="lib-logo" />
115
- <div v-else class="lib-seal">漢流</div>
116
- <h1>古典詩文圖書館</h1>
117
- <p class="lib-subtitle">Classical Chinese Text Library</p>
119
+ <div v-else class="lib-seal">{{ displayTitle.slice(0, 2) }}</div>
120
+ <h1>{{ displayTitle }} <button v-if="aboutHtml" class="lib-about-link" @click="aboutPane?.toggleAbout()">{{ t('nav.about') }}</button></h1>
121
+ <p v-if="siteSubtitle" class="lib-subtitle">{{ siteSubtitle }}</p>
118
122
  <div class="lib-stats-bar">
119
- <span class="lib-stat">{{ books.length }} 部</span>
123
+ <span class="lib-stat">{{ books.length }} {{ t('stat.books') }}</span>
120
124
  <span class="lib-stat-sep">·</span>
121
- <span class="lib-stat">{{ totalPieces }} 篇</span>
125
+ <span class="lib-stat">{{ totalPieces }} {{ t('stat.pieces') }}</span>
122
126
  </div>
123
- <router-link to="/about" class="lib-about-link">關於</router-link>
124
127
  </header>
125
128
  <div v-for="group in groupedBooks" :key="group.category" class="lib-group">
126
129
  <h2 class="lib-group-title">{{ group.category }}</h2>
@@ -136,11 +139,11 @@ function openBook(bookId: string) {
136
139
  <div class="lib-card-body">
137
140
  <div class="lib-card-top">
138
141
  <h3 class="lib-card-title">{{ book.title }}</h3>
139
- <span class="lib-card-genre">{{ genreLabel[book.genre] || book.genre }}</span>
142
+ <span class="lib-card-genre">{{ bookCategory(book) }}</span>
140
143
  </div>
141
144
  <p v-if="book.subtitle" class="lib-card-sub">{{ book.subtitle }}</p>
142
145
  <div class="lib-card-stats">
143
- <span class="lib-card-count">{{ book.count }} 篇</span>
146
+ <span class="lib-card-count">{{ t('stat.pieceCount', { count: book.count }) }}</span>
144
147
  </div>
145
148
  </div>
146
149
  </div>
@@ -156,20 +159,9 @@ function openBook(bookId: string) {
156
159
  /* ═══════ 直排模式 ═══════ */
157
160
 
158
161
  .v-page {
159
- height: 100vh;
160
- display: flex;
161
- flex-direction: row-reverse;
162
- overflow-x: auto;
163
- overflow-y: hidden;
164
- margin-right: var(--nav-width, 56px);
165
162
  padding: 0 32px;
166
163
  background: linear-gradient(90deg, var(--paper) 0%, var(--paper-warm) 100%);
167
- scrollbar-width: thin;
168
- scrollbar-color: var(--gold) transparent;
169
- scroll-snap-type: x proximity;
170
164
  }
171
- .v-page::-webkit-scrollbar { height: 4px; }
172
- .v-page::-webkit-scrollbar-thumb { background: var(--gold); border-radius: 2px; }
173
165
 
174
166
  .v-hero {
175
167
  writing-mode: vertical-rl;
@@ -201,6 +193,8 @@ function openBook(bookId: string) {
201
193
  padding: 12px 8px;
202
194
  border: 1px solid var(--border-light);
203
195
  border-radius: 2px;
196
+ background: none;
197
+ cursor: pointer;
204
198
  transition: all 0.2s;
205
199
  }
206
200
  .v-about-link:hover {
@@ -353,16 +347,17 @@ function openBook(bookId: string) {
353
347
 
354
348
  .lib-about-link {
355
349
  display: inline-block;
356
- margin-top: 16px;
357
350
  font-family: var(--sans);
358
351
  font-size: 13px;
359
352
  color: var(--ink-faint);
360
353
  letter-spacing: 2px;
361
- text-decoration: none;
362
354
  padding: 4px 12px;
363
355
  border: 1px solid var(--border-light);
364
356
  border-radius: 4px;
357
+ background: none;
358
+ cursor: pointer;
365
359
  transition: all 0.2s;
360
+ vertical-align: middle;
366
361
  }
367
362
  .lib-about-link:hover {
368
363
  color: var(--ink);
@@ -455,12 +450,15 @@ function openBook(bookId: string) {
455
450
  .v-page { padding: 0 16px; }
456
451
  .v-title { font-size: 36px; letter-spacing: 10px; }
457
452
  .lib-root { padding: 40px 16px 80px; }
453
+ .lib-hero { margin-bottom: 32px; }
458
454
  .lib-hero h1 { font-size: 28px; letter-spacing: 4px; }
455
+ .lib-logo { height: 48px; margin-bottom: 16px; }
459
456
  .lib-grid {
460
457
  grid-template-columns: 1fr 1fr;
461
458
  gap: 8px;
462
459
  }
463
460
  .lib-card { padding: 14px; }
461
+ .lib-card:active { transform: scale(0.98); }
464
462
  .lib-card-title { font-size: 18px; letter-spacing: 2px; }
465
463
  .lib-card-genre { display: none; }
466
464
  .lib-card-sub { font-size: 12px; margin-bottom: 8px; }