uview-plus 3.8.55 → 3.8.106

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.
Files changed (229) hide show
  1. package/changelog.md +2147 -1828
  2. package/components/u-action-sheet/props.js +3 -1
  3. package/components/u-action-sheet/u-action-sheet.vue +22 -2
  4. package/components/u-album/props.js +3 -1
  5. package/components/u-alert/props.js +4 -2
  6. package/components/u-avatar/props.js +4 -1
  7. package/components/u-avatar-group/props.js +4 -1
  8. package/components/u-back-top/props.js +4 -1
  9. package/components/u-badge/props.js +4 -1
  10. package/components/u-barcode/u-barcode.vue +44 -15
  11. package/components/u-box/props.js +5 -2
  12. package/components/u-button/props.js +4 -1
  13. package/components/u-button/u-button.vue +2 -5
  14. package/components/u-button/vue.scss +3 -3
  15. package/components/u-calendar/calendar.js +8 -0
  16. package/components/u-calendar/props.js +43 -1
  17. package/components/u-calendar/u-calendar.vue +16 -0
  18. package/components/u-calendar-strip/props.js +3 -1
  19. package/components/u-canvas/u-canvas.vue +1028 -423
  20. package/components/u-car-keyboard/props.js +3 -1
  21. package/components/u-card/props.js +3 -1
  22. package/components/u-cell/props.js +4 -1
  23. package/components/u-cell/u-cell.vue +9 -1
  24. package/components/u-cell-group/props.js +4 -1
  25. package/components/u-checkbox/props.js +4 -1
  26. package/components/u-checkbox/u-checkbox.vue +7 -3
  27. package/components/u-checkbox-group/props.js +3 -1
  28. package/components/u-circle-progress/props.js +4 -1
  29. package/components/u-code/props.js +4 -1
  30. package/components/u-code-input/props.js +4 -1
  31. package/components/u-col/props.js +4 -1
  32. package/components/u-collapse/props.js +4 -1
  33. package/components/u-collapse-item/props.js +4 -1
  34. package/components/u-color-picker/u-color-picker.vue +1 -0
  35. package/components/u-column-notice/props.js +4 -1
  36. package/components/u-count-down/props.js +4 -1
  37. package/components/u-count-to/props.js +4 -1
  38. package/components/u-cropper/u-cropper.vue +95 -51
  39. package/components/u-datetime-picker/props.js +4 -1
  40. package/components/u-datetime-picker/u-datetime-picker.vue +53 -4
  41. package/components/u-divider/props.js +4 -1
  42. package/components/u-dragsort/u-dragsort.vue +57 -32
  43. package/components/u-dropdown/props.js +3 -1
  44. package/components/u-dropdown-item/props.js +3 -1
  45. package/components/u-empty/props.js +4 -1
  46. package/components/u-form/props.js +4 -1
  47. package/components/u-form-item/props.js +4 -1
  48. package/components/u-gap/props.js +4 -1
  49. package/components/u-grid/props.js +4 -1
  50. package/components/u-grid-item/props.js +4 -1
  51. package/components/u-guide/props.js +3 -1
  52. package/components/u-icon/props.js +4 -1
  53. package/components/u-icon/u-icon.vue +11 -2
  54. package/components/u-icon/upicon.ttf +0 -0
  55. package/components/u-icon/util.js +103 -10
  56. package/components/u-image/props.js +4 -1
  57. package/components/u-image/u-image.vue +280 -267
  58. package/components/u-index-anchor/props.js +4 -1
  59. package/components/u-index-item/props.js +2 -1
  60. package/components/u-index-item/u-index-item.vue +1 -1
  61. package/components/u-index-list/props.js +4 -1
  62. package/components/u-index-list/u-index-list.vue +1 -1
  63. package/components/u-input/props.js +3 -1
  64. package/components/u-keyboard/props.js +4 -1
  65. package/components/u-line/props.js +4 -1
  66. package/components/u-line-progress/props.js +6 -3
  67. package/components/u-link/props.js +4 -1
  68. package/components/u-list/props.js +4 -1
  69. package/components/u-list-item/props.js +4 -1
  70. package/components/u-loading-icon/props.js +4 -1
  71. package/components/u-loading-page/props.js +3 -1
  72. package/components/u-loadmore/props.js +4 -1
  73. package/components/u-markdown/u-markdown.vue +45 -3
  74. package/components/u-modal/props.js +4 -1
  75. package/components/u-navbar/props.js +4 -2
  76. package/components/u-navbar/u-navbar.vue +15 -9
  77. package/components/u-no-network/props.js +4 -1
  78. package/components/u-notice-bar/props.js +4 -1
  79. package/components/u-notify/props.js +4 -1
  80. package/components/u-novel-reader/content-normalizer.js +80 -0
  81. package/components/u-novel-reader/layout-engine.js +225 -0
  82. package/components/u-novel-reader/measure-adapter.js +69 -0
  83. package/components/u-novel-reader/novelReader.js +35 -0
  84. package/components/u-novel-reader/persistence.js +144 -0
  85. package/components/u-novel-reader/props.js +100 -0
  86. package/components/u-novel-reader/reader-catalog.vue +234 -0
  87. package/components/u-novel-reader/reader-content.vue +226 -0
  88. package/components/u-novel-reader/reader-core.js +151 -0
  89. package/components/u-novel-reader/reader-settings.vue +325 -0
  90. package/components/u-novel-reader/reader-toolbar.vue +313 -0
  91. package/components/u-novel-reader/theme-vars.scss +49 -0
  92. package/components/u-novel-reader/u-novel-reader.vue +836 -0
  93. package/components/u-number-box/props.js +4 -1
  94. package/components/u-number-box/u-number-box.vue +2 -2
  95. package/components/u-number-keyboard/props.js +4 -1
  96. package/components/u-overlay/props.js +4 -1
  97. package/components/u-pagination/u-pagination.vue +60 -39
  98. package/components/u-parse/props.js +4 -1
  99. package/components/u-picker/props.js +4 -1
  100. package/components/u-picker-column/props.js +3 -1
  101. package/components/u-popover/props.js +2 -1
  102. package/components/u-popup/props.js +4 -1
  103. package/components/u-poster/u-poster.vue +246 -214
  104. package/components/u-qrcode/qrcode.js +105 -97
  105. package/components/u-qrcode/u-qrcode.vue +242 -289
  106. package/components/u-radio/props.js +4 -1
  107. package/components/u-radio/u-radio.vue +7 -3
  108. package/components/u-radio-group/props.js +4 -1
  109. package/components/u-rate/props.js +4 -1
  110. package/components/u-read-more/props.js +4 -1
  111. package/components/u-row/props.js +4 -1
  112. package/components/u-row-notice/props.js +4 -1
  113. package/components/u-safe-bottom/props.js +2 -1
  114. package/components/u-scroll-list/props.js +4 -1
  115. package/components/u-scroll-list/scrollWxs.wxs +1 -1
  116. package/components/u-search/props.js +3 -1
  117. package/components/u-skeleton/props.js +4 -1
  118. package/components/u-slider/props.js +3 -1
  119. package/components/u-slider/u-slider.vue +71 -29
  120. package/components/u-status-bar/props.js +4 -1
  121. package/components/u-steps/props.js +4 -1
  122. package/components/u-steps-item/props.js +4 -1
  123. package/components/u-sticky/props.js +4 -1
  124. package/components/u-sticky/u-sticky.vue +11 -0
  125. package/components/u-subsection/props.js +3 -1
  126. package/components/u-swipe-action/props.js +4 -1
  127. package/components/u-swipe-action-item/index.wxs +43 -25
  128. package/components/u-swipe-action-item/nvue.js +9 -0
  129. package/components/u-swipe-action-item/other.js +32 -21
  130. package/components/u-swipe-action-item/props.js +9 -1
  131. package/components/u-swipe-action-item/swipeActionItem.js +1 -0
  132. package/components/u-swipe-action-item/u-swipe-action-item.vue +42 -4
  133. package/components/u-swiper/props.js +9 -1
  134. package/components/u-swiper/swiper.js +1 -0
  135. package/components/u-swiper/u-swiper.vue +2 -0
  136. package/components/u-swiper-indicator/props.js +4 -1
  137. package/components/u-switch/props.js +4 -1
  138. package/components/u-switch/u-switch.vue +1 -1
  139. package/components/u-tabbar/props.js +3 -1
  140. package/components/u-tabbar/u-tabbar.vue +2 -1
  141. package/components/u-tabbar-item/props.js +4 -1
  142. package/components/u-tabbar-item/u-tabbar-item.vue +64 -21
  143. package/components/u-table/props.js +3 -1
  144. package/components/u-table2/tableRow.vue +155 -69
  145. package/components/u-table2/u-table2.vue +327 -137
  146. package/components/u-tabs/props.js +4 -1
  147. package/components/u-tabs/u-tabs.vue +11 -8
  148. package/components/u-tabs-item/props.js +3 -1
  149. package/components/u-tabs-pro/u-tabs-pro.vue +212 -0
  150. package/components/u-tag/props.js +4 -1
  151. package/components/u-td/props.js +3 -1
  152. package/components/u-text/props.js +4 -1
  153. package/components/u-text/text.js +1 -1
  154. package/components/u-text/u-text.vue +18 -6
  155. package/components/u-textarea/props.js +3 -1
  156. package/components/u-textarea/u-textarea.vue +16 -5
  157. package/components/u-th/props.js +3 -1
  158. package/components/u-toolbar/props.js +4 -1
  159. package/components/u-tooltip/props.js +10 -2
  160. package/components/u-tooltip/tooltip.js +2 -1
  161. package/components/u-tooltip/u-tooltip.vue +31 -3
  162. package/components/u-tr/props.js +3 -1
  163. package/components/u-transition/nvue-ani-map.js +75 -68
  164. package/components/u-transition/props.js +4 -1
  165. package/components/u-transition/transitionMixin.js +164 -163
  166. package/components/u-upload/props.js +4 -1
  167. package/components/u-upload/u-upload.vue +48 -41
  168. package/components/u-waterfall/u-waterfall.vue +162 -110
  169. package/index.js +65 -8
  170. package/libs/config/config.js +1 -1
  171. package/libs/config/props.js +180 -204
  172. package/libs/css/components.scss +3 -0
  173. package/libs/function/index.js +66 -0
  174. package/libs/function/uni-api-shims.js +29 -0
  175. package/libs/i18n/index.js +71 -36
  176. package/libs/i18n/locale-packs.js +11 -0
  177. package/libs/i18n/locales/all.js +23 -0
  178. package/libs/i18n/locales/{de.json → de.js} +1 -1
  179. package/libs/i18n/locales/{en.json → en.js} +2 -2
  180. package/libs/i18n/locales/{es.json → es.js} +1 -1
  181. package/libs/i18n/locales/{fr.json → fr.js} +2 -2
  182. package/libs/i18n/locales/{ja.json → ja.js} +1 -1
  183. package/libs/i18n/locales/{ko.json → ko.js} +1 -1
  184. package/libs/i18n/locales/{ru.json → ru.js} +1 -1
  185. package/libs/i18n/locales/{th.json → th.js} +1 -1
  186. package/libs/i18n/locales/{zh-Hans.json → zh-Hans.js} +1 -1
  187. package/libs/i18n/locales/{zh-Hant.json → zh-Hant.js} +1 -1
  188. package/libs/mixin/mixin.js +18 -54
  189. package/libs/root/index.js +78 -5
  190. package/libs/root/page.js +7 -7
  191. package/libs/root/root.js +73 -3
  192. package/libs/util/app-nvue-webview-canvas.js +486 -0
  193. package/libs/util/gcanvas/bridge/bridge-weex.js +10 -7
  194. package/libs/util/gcanvas/context-2d/FillStyleLinearGradient.js +0 -0
  195. package/libs/util/gcanvas/context-2d/FillStylePattern.js +0 -0
  196. package/libs/util/gcanvas/context-2d/FillStyleRadialGradient.js +0 -0
  197. package/libs/util/gcanvas/context-2d/RenderingContext.js +0 -0
  198. package/libs/util/gcanvas/context-webgl/ActiveInfo.js +0 -0
  199. package/libs/util/gcanvas/context-webgl/Buffer.js +0 -0
  200. package/libs/util/gcanvas/context-webgl/Framebuffer.js +0 -0
  201. package/libs/util/gcanvas/context-webgl/GLenum.js +0 -0
  202. package/libs/util/gcanvas/context-webgl/GLmethod.js +0 -0
  203. package/libs/util/gcanvas/context-webgl/GLtype.js +0 -0
  204. package/libs/util/gcanvas/context-webgl/Program.js +0 -0
  205. package/libs/util/gcanvas/context-webgl/Renderbuffer.js +0 -0
  206. package/libs/util/gcanvas/context-webgl/RenderingContext.js +0 -0
  207. package/libs/util/gcanvas/context-webgl/Shader.js +0 -0
  208. package/libs/util/gcanvas/context-webgl/ShaderPrecisionFormat.js +0 -0
  209. package/libs/util/gcanvas/context-webgl/Texture.js +0 -0
  210. package/libs/util/gcanvas/context-webgl/UniformLocation.js +0 -0
  211. package/libs/util/gcanvas/context-webgl/classUtils.js +0 -0
  212. package/libs/util/gcanvas/env/canvas.js +0 -0
  213. package/libs/util/gcanvas/env/image.js +0 -0
  214. package/libs/util/gcanvas/env/tool.js +0 -0
  215. package/libs/util/gcanvas/index.js +3 -3
  216. package/package.json +4 -5
  217. package/tsconfig.json +18 -0
  218. package/types/comps/calendar.d.ts +38 -0
  219. package/types/comps/checkbox.d.ts +12 -0
  220. package/types/comps/lineProgress.d.ts +5 -0
  221. package/types/comps/navbar.d.ts +3 -3
  222. package/types/comps/swipeActionItem.d.ts +13 -0
  223. package/types/comps/swiper.d.ts +5 -0
  224. package/types/comps/tabs.d.ts +2 -1
  225. package/types/comps/text.d.ts +1 -1
  226. package/types/comps/tooltip.d.ts +36 -1
  227. package/types/func.d.ts +23 -0
  228. package/types/index.d.ts +1 -0
  229. /package/components/u-markdown/{marked.esm.js → marked.esm.mjs} +0 -0
@@ -0,0 +1,836 @@
1
+ <template>
2
+ <view
3
+ class="up-novel-reader"
4
+ :class="`theme-${resolvedSettings.theme}`"
5
+ :style="readerStyle"
6
+ >
7
+ <view class="up-novel-reader__viewport">
8
+ <reader-content
9
+ :mode="resolvedMode"
10
+ :paragraphs="normalizedContent.paragraphs"
11
+ :pages="layout.pages"
12
+ :page-index="pageIndex"
13
+ :scroll-top="scrollTop"
14
+ :settings="resolvedSettings"
15
+ :theme-tokens="themeTokens"
16
+ :loading="loading"
17
+ :error="error"
18
+ :page-animation="effectiveAnimation"
19
+ @content-scroll="handleContentScroll"
20
+ @page-change="handlePageChange"
21
+ @tap-zone="handleTapZone"
22
+ @retry="handleRetry"
23
+ >
24
+ <template #loading>
25
+ <slot name="loading"></slot>
26
+ </template>
27
+ <template #error="{ error: slotError, retry }">
28
+ <slot name="error" :error="slotError" :retry="retry"></slot>
29
+ </template>
30
+ <template #empty>
31
+ <slot name="empty"></slot>
32
+ </template>
33
+ </reader-content>
34
+ </view>
35
+ <view v-if="controlsVisible" class="up-novel-reader__controls">
36
+ <view class="up-novel-reader__top-toolbar">
37
+ <u-status-bar
38
+ v-if="safeAreaInsetTop"
39
+ :bg-color="themeTokens.toolbar"
40
+ ></u-status-bar>
41
+ <reader-toolbar
42
+ position="top"
43
+ :title="currentChapter && currentChapter.title"
44
+ :show-back="showBack"
45
+ :back-icon="backIcon"
46
+ :is-bookmarked="isCurrentBookmarked"
47
+ :theme-tokens="themeTokens"
48
+ @back="handleBack"
49
+ @toggle-catalog="catalogVisible = true"
50
+ @toggle-bookmark="toggleBookmark"
51
+ @toggle-controls="hideControls"
52
+ >
53
+ <template #top>
54
+ <slot name="top"></slot>
55
+ </template>
56
+ <template #toolbar-extra>
57
+ <slot name="toolbar-extra"></slot>
58
+ </template>
59
+ </reader-toolbar>
60
+ </view>
61
+ <view class="up-novel-reader__bottom-toolbar">
62
+ <reader-toolbar
63
+ position="bottom"
64
+ :progress="currentProgress"
65
+ :page-count="layout.pageCount"
66
+ :current-chapter-index="currentChapterIndex"
67
+ :chapter-count="chapters.length"
68
+ :has-previous="hasPreviousChapter"
69
+ :has-next="hasNextChapter"
70
+ :theme-tokens="themeTokens"
71
+ @previous="requestChapter('previous')"
72
+ @next="requestChapter('next')"
73
+ @toggle-settings="settingsVisible = true"
74
+ @toggle-controls="hideControls"
75
+ >
76
+ <template #bottom>
77
+ <slot name="bottom"></slot>
78
+ </template>
79
+ </reader-toolbar>
80
+ <u-safe-bottom
81
+ v-if="safeAreaInsetBottom"
82
+ :style="{ backgroundColor: themeTokens.toolbar }"
83
+ ></u-safe-bottom>
84
+ </view>
85
+ </view>
86
+ <u-popup
87
+ v-model:show="catalogVisible"
88
+ mode="left"
89
+ round="0"
90
+ :duration="effectiveAnimation ? 300 : 0"
91
+ :overlay-duration="effectiveAnimation ? 350 : 0"
92
+ :safe-area-inset-bottom="safeAreaInsetBottom"
93
+ :custom-style="catalogPopupStyle"
94
+ >
95
+ <reader-catalog
96
+ :chapters="chapters"
97
+ :current-chapter="currentChapter"
98
+ :bookmarks="resolvedBookmarks"
99
+ :progress="currentProgress"
100
+ :theme-tokens="themeTokens"
101
+ @chapter-select="handleChapterSelect"
102
+ @bookmark-select="handleBookmarkSelect"
103
+ >
104
+ <template #default>
105
+ <slot name="catalog"></slot>
106
+ </template>
107
+ </reader-catalog>
108
+ </u-popup>
109
+ <u-popup
110
+ v-model:show="settingsVisible"
111
+ mode="bottom"
112
+ round="18"
113
+ :duration="effectiveAnimation ? 300 : 0"
114
+ :overlay-duration="effectiveAnimation ? 350 : 0"
115
+ :safe-area-inset-bottom="safeAreaInsetBottom"
116
+ :custom-style="settingsPopupStyle"
117
+ >
118
+ <reader-settings
119
+ :settings="resolvedSettings"
120
+ :theme-tokens="themeTokens"
121
+ @update-settings="handleSettingsUpdate"
122
+ @close="settingsVisible = false"
123
+ >
124
+ <template #default>
125
+ <slot name="settings"></slot>
126
+ </template>
127
+ </reader-settings>
128
+ </u-popup>
129
+ </view>
130
+ </template>
131
+
132
+ <script>
133
+ import { nextTick } from 'vue'
134
+ import { getWindowInfo } from '../../libs/function/index'
135
+ import { mixin } from '../../libs/mixin/mixin'
136
+ import { mpMixin } from '../../libs/mixin/mpMixin'
137
+ import { measureContainer, measureTextWidth } from './measure-adapter'
138
+ import { normalizeContent, normalizeProgress } from './content-normalizer'
139
+ import {
140
+ createBookmark,
141
+ mergeReaderSettings,
142
+ normalizeMode
143
+ } from './reader-core'
144
+ import { paginateParagraphs, resolveAnchor } from './layout-engine'
145
+ import {
146
+ createStorageKey,
147
+ readPersistedState,
148
+ writePersistedState
149
+ } from './persistence'
150
+ import props from './props'
151
+ import ReaderContent from './reader-content.vue'
152
+ import ReaderToolbar from './reader-toolbar.vue'
153
+ import ReaderCatalog from './reader-catalog.vue'
154
+ import ReaderSettings from './reader-settings.vue'
155
+
156
+ const THEME_TOKENS = {
157
+ day: {
158
+ background: '#f7f8fa',
159
+ text: '#303133',
160
+ muted: '#909399',
161
+ toolbar: '#ffffff',
162
+ border: 'rgba(48, 49, 51, 0.12)',
163
+ active: '#2979ff',
164
+ disabled: '#c8c9cc'
165
+ },
166
+ paper: {
167
+ background: '#f3ead7',
168
+ text: '#51483d',
169
+ muted: '#8f806d',
170
+ toolbar: '#f7efdf',
171
+ border: 'rgba(81, 72, 61, 0.16)',
172
+ active: '#9b7653',
173
+ disabled: '#c7b9a3'
174
+ },
175
+ green: {
176
+ background: '#e7f1e4',
177
+ text: '#3f5140',
178
+ muted: '#708371',
179
+ toolbar: '#eef6eb',
180
+ border: 'rgba(63, 81, 64, 0.16)',
181
+ active: '#4d8b55',
182
+ disabled: '#b6c7b4'
183
+ },
184
+ night: {
185
+ background: '#202124',
186
+ text: '#d6d7da',
187
+ muted: '#9ca0a8',
188
+ toolbar: '#292b30',
189
+ border: 'rgba(214, 215, 218, 0.16)',
190
+ active: '#7da7ff',
191
+ disabled: '#62656d'
192
+ },
193
+ dark: {
194
+ background: '#111214',
195
+ text: '#e5e7eb',
196
+ muted: '#9ca3af',
197
+ toolbar: '#1b1d21',
198
+ border: 'rgba(229, 231, 235, 0.16)',
199
+ active: '#8ab4ff',
200
+ disabled: '#5f6368'
201
+ }
202
+ }
203
+
204
+ export default {
205
+ name: 'up-novel-reader',
206
+ components: {
207
+ ReaderContent,
208
+ ReaderToolbar,
209
+ ReaderCatalog,
210
+ ReaderSettings
211
+ },
212
+ mixins: [mpMixin, mixin, props],
213
+ emits: [
214
+ 'chapter-request',
215
+ 'chapter-prefetch',
216
+ 'progress-change',
217
+ 'settings-change',
218
+ 'bookmark-change',
219
+ 'reading-time-change',
220
+ 'back',
221
+ 'mode-change',
222
+ 'toolbar-change',
223
+ 'layout-ready',
224
+ 'retry'
225
+ ],
226
+ data() {
227
+ return {
228
+ controlsVisible: false,
229
+ normalizedContent: {
230
+ paragraphs: [],
231
+ text: '',
232
+ length: 0
233
+ },
234
+ layout: {
235
+ pages: [],
236
+ pageCount: 0,
237
+ charOffsetToPage: []
238
+ },
239
+ pageIndex: 0,
240
+ scrollTop: 0,
241
+ viewport: {
242
+ width: 0,
243
+ height: 0
244
+ },
245
+ refreshToken: 0,
246
+ pendingChapter: null,
247
+ localSettings: null,
248
+ localProgress: null,
249
+ localBookmarks: [],
250
+ persistedState: null,
251
+ readerStorageKey: '',
252
+ persistTimer: null,
253
+ prefetchedTargets: {},
254
+ readingTime: 0,
255
+ readingActive: false,
256
+ readingLastActiveAt: 0,
257
+ scrollHeight: 0,
258
+ catalogVisible: false,
259
+ settingsVisible: false,
260
+ controlsHideTimer: null
261
+ }
262
+ },
263
+ created() {
264
+ this.initializeReaderState()
265
+ },
266
+ computed: {
267
+ resolvedSettings() {
268
+ return mergeReaderSettings(
269
+ this.defaultSettings,
270
+ this.localSettings,
271
+ this.settings
272
+ )
273
+ },
274
+ resolvedMode() {
275
+ return normalizeMode(this.mode)
276
+ },
277
+ themeTokens() {
278
+ const theme = THEME_TOKENS[this.resolvedSettings.theme]
279
+ ? this.resolvedSettings.theme
280
+ : 'day'
281
+ return {
282
+ ...THEME_TOKENS[theme],
283
+ theme
284
+ }
285
+ },
286
+ effectiveAnimation() {
287
+ return this.pageAnimation && this.resolvedSettings.animation
288
+ },
289
+ hasPreviousChapter() {
290
+ return this.chapters.some((chapter) => (
291
+ chapter &&
292
+ Number(chapter.index) < this.currentChapterIndex &&
293
+ !chapter.isLocked
294
+ ))
295
+ },
296
+ hasNextChapter() {
297
+ return this.chapters.some((chapter) => (
298
+ chapter &&
299
+ Number(chapter.index) > this.currentChapterIndex &&
300
+ !chapter.isLocked
301
+ ))
302
+ },
303
+ resolvedBookmarks() {
304
+ return Array.isArray(this.bookmarks)
305
+ ? this.bookmarks
306
+ : this.localBookmarks
307
+ },
308
+ isCurrentBookmarked() {
309
+ const chapterId = this.currentChapter && this.currentChapter.id
310
+ const charOffset = Number(this.currentProgress.charOffset) || 0
311
+ return this.resolvedBookmarks.some((bookmark) => (
312
+ bookmark &&
313
+ bookmark.chapterId === chapterId &&
314
+ Number(bookmark.charOffset) === charOffset
315
+ ))
316
+ },
317
+ readerStyle() {
318
+ return {
319
+ backgroundColor: this.themeTokens.background,
320
+ color: this.themeTokens.text,
321
+ '--up-novel-reader-background': this.themeTokens.background,
322
+ '--up-novel-reader-text': this.themeTokens.text,
323
+ '--up-novel-reader-muted': this.themeTokens.muted,
324
+ '--up-novel-reader-toolbar': this.themeTokens.toolbar,
325
+ '--up-novel-reader-active': this.themeTokens.active || '#2979ff',
326
+ '--up-novel-reader-disabled': this.themeTokens.disabled || '#c8c9cc',
327
+ '--up-novel-reader-border': this.themeTokens.border || 'rgba(48, 49, 51, 0.12)'
328
+ }
329
+ },
330
+ catalogPopupStyle() {
331
+ return {
332
+ width: '82vw',
333
+ maxWidth: '360px',
334
+ backgroundColor: this.themeTokens.background
335
+ }
336
+ },
337
+ settingsPopupStyle() {
338
+ return {
339
+ backgroundColor: this.themeTokens.background
340
+ }
341
+ },
342
+ currentChapterIndex() {
343
+ const index = Number(this.currentChapter && this.currentChapter.index)
344
+ if (Number.isFinite(index)) return index
345
+ return this.chapters.findIndex((item) => (
346
+ item && this.currentChapter && item.id === this.currentChapter.id
347
+ ))
348
+ },
349
+ currentProgress() {
350
+ const source = this.progress != null
351
+ ? this.progress
352
+ : (this.localProgress || this.initialProgress)
353
+ return normalizeProgress(source, this.currentChapter)
354
+ }
355
+ },
356
+ watch: {
357
+ currentChapter: {
358
+ handler(chapter) {
359
+ if (
360
+ this.pendingChapter &&
361
+ chapter &&
362
+ chapter.id === this.pendingChapter.targetId
363
+ ) {
364
+ this.pendingChapter = null
365
+ }
366
+ this.prefetchedTargets = {}
367
+ this.refreshContent()
368
+ },
369
+ deep: true,
370
+ immediate: true
371
+ },
372
+ mode() {
373
+ this.refreshLayout()
374
+ this.$emit('mode-change', this.resolvedMode)
375
+ },
376
+ settings: {
377
+ handler() {
378
+ this.refreshLayout()
379
+ this.queuePersist()
380
+ },
381
+ deep: true
382
+ },
383
+ progress: {
384
+ handler() {
385
+ this.syncProgress()
386
+ this.queuePersist()
387
+ },
388
+ deep: true
389
+ },
390
+ bookmarks: {
391
+ handler() {
392
+ this.queuePersist()
393
+ },
394
+ deep: true
395
+ },
396
+ loading(value) {
397
+ if (value) this.pauseReading()
398
+ },
399
+ error(value) {
400
+ if (value) this.pauseReading()
401
+ },
402
+ bookId() {
403
+ this.initializeReaderState()
404
+ },
405
+ storageKey() {
406
+ this.initializeReaderState()
407
+ }
408
+ },
409
+ mounted() {
410
+ this.refreshLayout()
411
+ if (typeof uni !== 'undefined' && typeof uni.onWindowResize === 'function') {
412
+ uni.onWindowResize(this.handleWindowResize)
413
+ }
414
+ },
415
+ beforeUnmount() {
416
+ this.pauseReading()
417
+ this.flushPersistence()
418
+ this.clearControlsHideTimer()
419
+ if (typeof uni !== 'undefined' && typeof uni.offWindowResize === 'function') {
420
+ uni.offWindowResize(this.handleWindowResize)
421
+ }
422
+ },
423
+ onHide() {
424
+ this.pauseReading()
425
+ this.flushPersistence()
426
+ },
427
+ methods: {
428
+ async refreshContent() {
429
+ this.normalizedContent = normalizeContent(
430
+ this.currentChapter && this.currentChapter.content
431
+ )
432
+ const progress = this.currentProgress
433
+ this.pageIndex = progress.pageIndex
434
+ this.scrollTop = progress.scrollTop
435
+ await nextTick()
436
+ this.refreshLayout()
437
+ },
438
+ async refreshLayout() {
439
+ const token = ++this.refreshToken
440
+ const rect = await measureContainer('.up-novel-reader__viewport', this)
441
+ if (token !== this.refreshToken) return
442
+ const windowInfo = getWindowInfo()
443
+ const width = Number(rect && rect.width) || Number(windowInfo.windowWidth) || 375
444
+ const height = Number(rect && rect.height) || Number(windowInfo.windowHeight) || 667
445
+ this.viewport = { width, height }
446
+ if (this.resolvedMode !== 'page') {
447
+ this.$emit('layout-ready', {
448
+ mode: this.resolvedMode,
449
+ width,
450
+ height,
451
+ pageCount: 0
452
+ })
453
+ return
454
+ }
455
+ const contentWidth = this.resolveContentWidth(width)
456
+ const layout = paginateParagraphs(this.normalizedContent.paragraphs, {
457
+ width: contentWidth,
458
+ height: Math.max(1, height - 64),
459
+ fontSize: this.resolvedSettings.fontSize,
460
+ lineHeight: this.resolvedSettings.lineHeight,
461
+ paragraphSpacing: this.resolvedSettings.paragraphSpacing,
462
+ fontFamily: this.resolvedSettings.fontFamily,
463
+ fontWeight: this.resolvedSettings.fontWeight,
464
+ measureText: (text) => measureTextWidth(text, this.resolvedSettings)
465
+ })
466
+ this.layout = layout
467
+ const anchor = resolveAnchor(layout.pages, this.currentProgress.charOffset)
468
+ this.pageIndex = anchor.pageIndex
469
+ this.$emit('layout-ready', {
470
+ mode: this.resolvedMode,
471
+ width,
472
+ height,
473
+ pageCount: layout.pageCount
474
+ })
475
+ },
476
+ resolveContentWidth(width) {
477
+ const value = this.resolvedSettings.contentWidth
478
+ if (typeof value === 'string' && value.endsWith('%')) {
479
+ return width * Math.min(100, Math.max(40, Number.parseFloat(value))) / 100
480
+ }
481
+ return Math.min(width, Math.max(1, Number.parseFloat(value) || width))
482
+ },
483
+ handleWindowResize() {
484
+ this.refreshLayout()
485
+ },
486
+ handleContentScroll(event) {
487
+ this.activateReading()
488
+ this.scrollTop = Math.max(0, Number(event && event.detail && event.detail.scrollTop) || 0)
489
+ this.scrollHeight = Math.max(
490
+ 0,
491
+ Number(event && event.detail && event.detail.scrollHeight) || this.scrollHeight
492
+ )
493
+ this.emitProgress({
494
+ scrollTop: this.scrollTop
495
+ })
496
+ },
497
+ handlePageChange(payload) {
498
+ this.activateReading()
499
+ this.pageIndex = Math.max(0, Number(payload && payload.pageIndex) || 0)
500
+ this.emitProgress({
501
+ pageIndex: this.pageIndex,
502
+ charOffset: this.layout.pages[this.pageIndex]
503
+ ? this.layout.pages[this.pageIndex].startOffset
504
+ : 0
505
+ })
506
+ },
507
+ handleTapZone(zone) {
508
+ this.activateReading()
509
+ if (zone === 'center') {
510
+ this.toggleControls('tap-center')
511
+ return
512
+ }
513
+ if (this.resolvedMode !== 'page') return
514
+ this.movePage(zone === 'left' ? -1 : 1)
515
+ },
516
+ toggleControls(reason = 'manual') {
517
+ this.controlsVisible = !this.controlsVisible
518
+ this.$emit('toolbar-change', {
519
+ visible: this.controlsVisible,
520
+ reason
521
+ })
522
+ if (this.controlsVisible) this.scheduleControlsHide()
523
+ else this.clearControlsHideTimer()
524
+ },
525
+ hideControls() {
526
+ if (!this.controlsVisible) return
527
+ this.controlsVisible = false
528
+ this.clearControlsHideTimer()
529
+ this.$emit('toolbar-change', {
530
+ visible: false,
531
+ reason: 'toolbar-action'
532
+ })
533
+ },
534
+ scheduleControlsHide() {
535
+ this.clearControlsHideTimer()
536
+ if (!this.controlsAutoHide) return
537
+ this.controlsHideTimer = setTimeout(() => {
538
+ this.hideControls()
539
+ }, Math.max(0, Number(this.controlsAutoHide)))
540
+ },
541
+ clearControlsHideTimer() {
542
+ if (this.controlsHideTimer) {
543
+ clearTimeout(this.controlsHideTimer)
544
+ this.controlsHideTimer = null
545
+ }
546
+ },
547
+ handleBack() {
548
+ this.$emit('back')
549
+ if (
550
+ this.autoBack &&
551
+ typeof uni !== 'undefined' &&
552
+ typeof uni.navigateBack === 'function'
553
+ ) {
554
+ uni.navigateBack()
555
+ }
556
+ },
557
+ handleChapterSelect(chapter) {
558
+ if (!chapter) return
559
+ this.catalogVisible = false
560
+ this.requestChapter('catalog', chapter.index)
561
+ },
562
+ handleBookmarkSelect(bookmark) {
563
+ if (!bookmark) return
564
+ this.catalogVisible = false
565
+ if (
566
+ this.currentChapter &&
567
+ bookmark.chapterId === this.currentChapter.id
568
+ ) {
569
+ this.pageIndex = Math.max(0, Number(bookmark.pageIndex) || 0)
570
+ this.scrollTop = Math.max(0, Number(bookmark.scrollTop) || 0)
571
+ this.emitProgress({
572
+ charOffset: Math.max(0, Number(bookmark.charOffset) || 0)
573
+ })
574
+ return
575
+ }
576
+ this.requestChapter('catalog', bookmark.chapterIndex)
577
+ },
578
+ handleSettingsUpdate(settings) {
579
+ this.localSettings = { ...settings }
580
+ this.settingsVisible = false
581
+ this.$emit('settings-change', {
582
+ mode: this.resolvedMode,
583
+ ...mergeReaderSettings(settings)
584
+ })
585
+ this.queuePersist()
586
+ this.refreshLayout()
587
+ },
588
+ toggleBookmark() {
589
+ const chapterId = this.currentChapter && this.currentChapter.id
590
+ if (!chapterId) return
591
+ const bookmark = createBookmark({
592
+ chapterId,
593
+ chapterIndex: this.currentChapterIndex,
594
+ charOffset: this.currentProgress.charOffset || 0,
595
+ pageIndex: this.pageIndex,
596
+ scrollTop: this.scrollTop,
597
+ excerpt: this.getBookmarkExcerpt()
598
+ })
599
+ const list = this.isCurrentBookmarked
600
+ ? this.resolvedBookmarks.filter((item) => item.id !== bookmark.id)
601
+ : [...this.resolvedBookmarks, bookmark]
602
+ if (!Array.isArray(this.bookmarks)) this.localBookmarks = list
603
+ this.$emit('bookmark-change', list)
604
+ this.queuePersist()
605
+ },
606
+ getBookmarkExcerpt() {
607
+ const text = this.normalizedContent.text || ''
608
+ const offset = Math.max(0, Number(this.currentProgress.charOffset) || 0)
609
+ return text.slice(Math.max(0, offset - 12), offset + 28).trim()
610
+ },
611
+ movePage(offset) {
612
+ const nextPageIndex = this.pageIndex + offset
613
+ if (nextPageIndex >= 0 && nextPageIndex < this.layout.pageCount) {
614
+ this.pageIndex = nextPageIndex
615
+ this.emitProgress({
616
+ pageIndex: nextPageIndex,
617
+ charOffset: this.layout.pages[nextPageIndex].startOffset
618
+ })
619
+ return
620
+ }
621
+ this.requestChapter(offset < 0 ? 'previous' : 'next')
622
+ },
623
+ requestChapter(direction, targetIndex) {
624
+ if (this.loading || this.pendingChapter) return false
625
+ const currentIndex = this.currentChapterIndex
626
+ const nextIndex = Number.isFinite(targetIndex)
627
+ ? targetIndex
628
+ : currentIndex + (direction === 'previous' ? -1 : 1)
629
+ const target = this.resolveChapter(nextIndex)
630
+ if (!target || target.isLocked) return false
631
+ const payload = {
632
+ targetIndex: nextIndex,
633
+ targetId: target.id,
634
+ direction,
635
+ requestId: `${Date.now()}-${this.refreshToken}`
636
+ }
637
+ this.pendingChapter = payload
638
+ this.flushPersistence()
639
+ this.$emit('chapter-request', payload)
640
+ return true
641
+ },
642
+ handleRetry() {
643
+ const retryPayload = this.pendingChapter
644
+ this.pendingChapter = null
645
+ this.$emit('retry', retryPayload)
646
+ },
647
+ syncProgress() {
648
+ if (!this.progress || !this.currentChapter) return
649
+ if (
650
+ this.progress.chapterId != null &&
651
+ this.progress.chapterId !== this.currentChapter.id
652
+ ) {
653
+ return
654
+ }
655
+ this.pageIndex = Math.max(0, Number(this.progress.pageIndex) || 0)
656
+ this.scrollTop = Math.max(0, Number(this.progress.scrollTop) || 0)
657
+ },
658
+ emitProgress(extra = {}) {
659
+ const page = this.layout.pages[this.pageIndex]
660
+ const pageCount = this.layout.pageCount
661
+ const scrollProgress = this.resolveScrollProgress()
662
+ const chapterProgress = pageCount
663
+ ? (this.pageIndex + 1) / pageCount
664
+ : scrollProgress
665
+ const charOffset = page
666
+ ? page.startOffset
667
+ : Math.round(this.normalizedContent.length * scrollProgress)
668
+ const progress = {
669
+ chapterId: this.currentChapter && this.currentChapter.id,
670
+ chapterIndex: this.currentChapterIndex,
671
+ pageIndex: this.pageIndex,
672
+ pageCount,
673
+ charOffset,
674
+ chapterProgress,
675
+ totalProgress: this.chapters.length
676
+ ? (this.currentChapterIndex + chapterProgress) / this.chapters.length
677
+ : 0,
678
+ scrollTop: this.scrollTop,
679
+ updatedAt: Date.now(),
680
+ ...extra
681
+ }
682
+ if (this.progress == null) this.localProgress = progress
683
+ this.$emit('progress-change', progress)
684
+ this.queuePersist()
685
+ this.emitPrefetchIfNeeded()
686
+ },
687
+ initializeReaderState() {
688
+ this.readerStorageKey = createStorageKey({
689
+ storageKey: this.storageKey,
690
+ bookId: this.bookId
691
+ })
692
+ this.persistedState = this.persist
693
+ ? readPersistedState(this.readerStorageKey)
694
+ : null
695
+ if (this.progress == null) {
696
+ this.localProgress = this.initialProgress || (
697
+ this.persistedState && this.persistedState.progress
698
+ ) || null
699
+ }
700
+ if (!Array.isArray(this.bookmarks)) {
701
+ this.localBookmarks = (
702
+ this.initialBookmarks && this.initialBookmarks.length
703
+ ? this.initialBookmarks
704
+ : (this.persistedState && this.persistedState.bookmarks) || []
705
+ ).slice()
706
+ }
707
+ if (this.settings == null) {
708
+ this.localSettings = (
709
+ this.persistedState && this.persistedState.settings
710
+ ) || null
711
+ }
712
+ this.readingTime = this.persistedState
713
+ ? this.persistedState.readingTime
714
+ : 0
715
+ },
716
+ resolveChapter(index) {
717
+ const numericIndex = Number(index)
718
+ return this.chapters.find((chapter) => (
719
+ chapter && Number(chapter.index) === numericIndex
720
+ )) || this.chapters[numericIndex]
721
+ },
722
+ resolveScrollProgress() {
723
+ if (this.scrollHeight <= this.viewport.height) return 0
724
+ const available = Math.max(1, this.scrollHeight - this.viewport.height)
725
+ return Math.min(1, Math.max(0, this.scrollTop / available))
726
+ },
727
+ emitPrefetchIfNeeded() {
728
+ if (
729
+ this.resolvedMode !== 'page' ||
730
+ !this.layout.pageCount ||
731
+ this.layout.pageCount - this.pageIndex > Math.max(0, this.preloadThreshold)
732
+ ) {
733
+ return
734
+ }
735
+ const target = this.chapters.find((chapter) => (
736
+ chapter &&
737
+ Number(chapter.index) > this.currentChapterIndex &&
738
+ !chapter.isLocked
739
+ ))
740
+ if (!target || this.prefetchedTargets[target.id]) return
741
+ this.prefetchedTargets[target.id] = true
742
+ this.$emit('chapter-prefetch', {
743
+ targetIndex: Number(target.index),
744
+ targetId: target.id,
745
+ direction: 'next'
746
+ })
747
+ },
748
+ queuePersist() {
749
+ if (!this.persist || !this.readerStorageKey) return
750
+ if (this.persistTimer) return
751
+ this.persistTimer = setTimeout(() => {
752
+ this.persistTimer = null
753
+ this.flushPersistence()
754
+ }, 180)
755
+ },
756
+ flushPersistence() {
757
+ if (this.persistTimer) {
758
+ clearTimeout(this.persistTimer)
759
+ this.persistTimer = null
760
+ }
761
+ if (!this.persist || !this.readerStorageKey) return false
762
+ return writePersistedState(this.readerStorageKey, {
763
+ progress: this.currentProgress,
764
+ settings: this.resolvedSettings,
765
+ bookmarks: this.resolvedBookmarks,
766
+ readingTime: this.readingTime
767
+ })
768
+ },
769
+ activateReading() {
770
+ if (this.loading || this.error || !this.currentChapter) return
771
+ if (!this.readingActive) {
772
+ this.readingActive = true
773
+ this.readingLastActiveAt = Date.now()
774
+ }
775
+ },
776
+ pauseReading() {
777
+ if (!this.readingActive) return
778
+ const now = Date.now()
779
+ const delta = Math.max(0, now - this.readingLastActiveAt)
780
+ this.readingTime += delta
781
+ this.readingActive = false
782
+ this.readingLastActiveAt = 0
783
+ this.$emit('reading-time-change', {
784
+ readingTime: this.readingTime,
785
+ delta,
786
+ updatedAt: now
787
+ })
788
+ this.queuePersist()
789
+ }
790
+ }
791
+ }
792
+ </script>
793
+
794
+ <style lang="scss" scoped>
795
+ @import "./theme-vars.scss";
796
+
797
+ .up-novel-reader {
798
+ position: relative;
799
+ width: 100%;
800
+ height: 100%;
801
+ overflow: hidden;
802
+ }
803
+
804
+ .up-novel-reader__controls,
805
+ .up-novel-reader__top-toolbar,
806
+ .up-novel-reader__bottom-toolbar {
807
+ position: absolute;
808
+ right: 0;
809
+ left: 0;
810
+ z-index: 10;
811
+ }
812
+
813
+ .up-novel-reader__controls {
814
+ top: 0;
815
+ bottom: 0;
816
+ pointer-events: none;
817
+ }
818
+
819
+ .up-novel-reader__top-toolbar,
820
+ .up-novel-reader__bottom-toolbar {
821
+ pointer-events: auto;
822
+ }
823
+
824
+ .up-novel-reader__top-toolbar {
825
+ top: 0;
826
+ }
827
+
828
+ .up-novel-reader__bottom-toolbar {
829
+ bottom: 0;
830
+ }
831
+
832
+ .up-novel-reader__viewport {
833
+ width: 100%;
834
+ height: 100%;
835
+ }
836
+ </style>