uview-plus 3.8.86 → 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.
- package/changelog.md +150 -3
- package/components/u-action-sheet/u-action-sheet.vue +21 -2
- package/components/u-barcode/u-barcode.vue +0 -2
- package/components/u-button/u-button.vue +2 -5
- package/components/u-canvas/u-canvas.vue +368 -74
- package/components/u-cell/u-cell.vue +8 -0
- package/components/u-datetime-picker/u-datetime-picker.vue +53 -4
- package/components/u-dragsort/u-dragsort.vue +55 -31
- package/components/u-icon/u-icon.vue +12 -3
- package/components/u-icon/util.js +81 -10
- package/components/u-index-item/u-index-item.vue +1 -1
- package/components/u-index-list/u-index-list.vue +1 -1
- package/components/u-novel-reader/content-normalizer.js +80 -0
- package/components/u-novel-reader/layout-engine.js +225 -0
- package/components/u-novel-reader/measure-adapter.js +69 -0
- package/components/u-novel-reader/novelReader.js +35 -0
- package/components/u-novel-reader/persistence.js +144 -0
- package/components/u-novel-reader/props.js +100 -0
- package/components/u-novel-reader/reader-catalog.vue +234 -0
- package/components/u-novel-reader/reader-content.vue +226 -0
- package/components/u-novel-reader/reader-core.js +151 -0
- package/components/u-novel-reader/reader-settings.vue +325 -0
- package/components/u-novel-reader/reader-toolbar.vue +313 -0
- package/components/u-novel-reader/theme-vars.scss +49 -0
- package/components/u-novel-reader/u-novel-reader.vue +836 -0
- package/components/u-poster/u-poster.vue +236 -219
- package/components/u-qrcode/qrcode.js +56 -49
- package/components/u-qrcode/u-qrcode.vue +159 -88
- package/components/u-scroll-list/scrollWxs.wxs +1 -1
- package/components/u-slider/u-slider.vue +1 -1
- package/components/u-swipe-action-item/index.wxs +43 -25
- package/components/u-swipe-action-item/nvue.js +9 -0
- package/components/u-swipe-action-item/other.js +32 -21
- package/components/u-swipe-action-item/props.js +5 -0
- package/components/u-swipe-action-item/swipeActionItem.js +1 -0
- package/components/u-swipe-action-item/u-swipe-action-item.vue +28 -4
- package/components/u-switch/u-switch.vue +1 -1
- package/components/u-tabbar/u-tabbar.vue +2 -1
- package/components/u-tabbar-item/u-tabbar-item.vue +64 -21
- package/components/u-tabs/u-tabs.vue +11 -8
- package/components/u-tabs-pro/u-tabs-pro.vue +212 -0
- package/components/u-text/text.js +1 -1
- package/components/u-text/u-text.vue +18 -6
- package/components/u-upload/u-upload.vue +1 -1
- package/components/u-waterfall/u-waterfall.vue +118 -65
- package/libs/config/config.js +1 -1
- package/libs/config/props.js +1 -0
- package/libs/function/index.js +66 -0
- package/libs/mixin/mixin.js +4 -50
- package/libs/root/index.js +78 -5
- package/libs/root/page.js +7 -7
- package/libs/root/root.js +73 -3
- package/libs/util/app-nvue-webview-canvas.js +486 -0
- package/libs/util/gcanvas/bridge/bridge-weex.js +0 -2
- package/libs/util/gcanvas/context-2d/FillStyleLinearGradient.js +0 -0
- package/libs/util/gcanvas/context-2d/FillStylePattern.js +0 -0
- package/libs/util/gcanvas/context-2d/FillStyleRadialGradient.js +0 -0
- package/libs/util/gcanvas/context-2d/RenderingContext.js +0 -0
- package/libs/util/gcanvas/context-webgl/ActiveInfo.js +0 -0
- package/libs/util/gcanvas/context-webgl/Buffer.js +0 -0
- package/libs/util/gcanvas/context-webgl/Framebuffer.js +0 -0
- package/libs/util/gcanvas/context-webgl/GLenum.js +0 -0
- package/libs/util/gcanvas/context-webgl/GLmethod.js +0 -0
- package/libs/util/gcanvas/context-webgl/GLtype.js +0 -0
- package/libs/util/gcanvas/context-webgl/Program.js +0 -0
- package/libs/util/gcanvas/context-webgl/Renderbuffer.js +0 -0
- package/libs/util/gcanvas/context-webgl/RenderingContext.js +0 -0
- package/libs/util/gcanvas/context-webgl/Shader.js +0 -0
- package/libs/util/gcanvas/context-webgl/ShaderPrecisionFormat.js +0 -0
- package/libs/util/gcanvas/context-webgl/Texture.js +0 -0
- package/libs/util/gcanvas/context-webgl/UniformLocation.js +0 -0
- package/libs/util/gcanvas/context-webgl/classUtils.js +0 -0
- package/libs/util/gcanvas/env/canvas.js +0 -0
- package/libs/util/gcanvas/env/image.js +0 -0
- package/libs/util/gcanvas/env/tool.js +0 -0
- package/libs/util/gcanvas/index.js +3 -3
- package/package.json +1 -1
- package/types/comps/swipeActionItem.d.ts +13 -0
- package/types/comps/tabs.d.ts +2 -1
- package/types/comps/text.d.ts +1 -1
- package/types/func.d.ts +23 -0
- package/types/index.d.ts +1 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { measureTextWidth } from './measure-adapter'
|
|
2
|
+
|
|
3
|
+
const CJK_PATTERN = /[\u3400-\u9fff\u3040-\u30ff\uff00-\uffef]/
|
|
4
|
+
|
|
5
|
+
function createUnit(text, startOffset, endOffset) {
|
|
6
|
+
return {
|
|
7
|
+
text,
|
|
8
|
+
startOffset,
|
|
9
|
+
endOffset
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function tokenizeText(text) {
|
|
14
|
+
const units = []
|
|
15
|
+
let token = ''
|
|
16
|
+
let tokenStart = 0
|
|
17
|
+
let offset = 0
|
|
18
|
+
|
|
19
|
+
const flushToken = () => {
|
|
20
|
+
if (token) {
|
|
21
|
+
units.push(createUnit(token, tokenStart, offset))
|
|
22
|
+
token = ''
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
Array.from(String(text)).forEach((character) => {
|
|
27
|
+
const startOffset = offset
|
|
28
|
+
offset += character.length
|
|
29
|
+
if (CJK_PATTERN.test(character) || /\s/.test(character)) {
|
|
30
|
+
flushToken()
|
|
31
|
+
units.push(createUnit(character, startOffset, offset))
|
|
32
|
+
tokenStart = offset
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
if (!token) tokenStart = startOffset
|
|
36
|
+
token += character
|
|
37
|
+
})
|
|
38
|
+
flushToken()
|
|
39
|
+
return units
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function splitUnit(unit) {
|
|
43
|
+
if (unit.text.length <= 1) return [unit]
|
|
44
|
+
const result = []
|
|
45
|
+
let offset = unit.startOffset
|
|
46
|
+
Array.from(unit.text).forEach((character) => {
|
|
47
|
+
const nextOffset = offset + character.length
|
|
48
|
+
result.push(createUnit(character, offset, nextOffset))
|
|
49
|
+
offset = nextOffset
|
|
50
|
+
})
|
|
51
|
+
return result
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function lineFromUnits(units) {
|
|
55
|
+
return {
|
|
56
|
+
text: units.map((unit) => unit.text).join(''),
|
|
57
|
+
startOffset: units[0].startOffset,
|
|
58
|
+
endOffset: units[units.length - 1].endOffset
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function createLayoutKey({
|
|
63
|
+
chapterId = '',
|
|
64
|
+
settings = {},
|
|
65
|
+
width = 0,
|
|
66
|
+
height = 0
|
|
67
|
+
} = {}) {
|
|
68
|
+
return JSON.stringify({
|
|
69
|
+
chapterId,
|
|
70
|
+
width,
|
|
71
|
+
height,
|
|
72
|
+
fontSize: settings.fontSize,
|
|
73
|
+
lineHeight: settings.lineHeight,
|
|
74
|
+
paragraphSpacing: settings.paragraphSpacing,
|
|
75
|
+
contentWidth: settings.contentWidth,
|
|
76
|
+
fontFamily: settings.fontFamily,
|
|
77
|
+
fontWeight: settings.fontWeight
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function wrapText(text, width, measureText = measureTextWidth) {
|
|
82
|
+
const source = String(text == null ? '' : text)
|
|
83
|
+
if (!source) {
|
|
84
|
+
return [{
|
|
85
|
+
text: '',
|
|
86
|
+
startOffset: 0,
|
|
87
|
+
endOffset: 0
|
|
88
|
+
}]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const units = tokenizeText(source).reduce((result, unit) => {
|
|
92
|
+
const unitWidth = measureText(unit.text)
|
|
93
|
+
if (unitWidth > width && unit.text.length > 1) {
|
|
94
|
+
return result.concat(splitUnit(unit))
|
|
95
|
+
}
|
|
96
|
+
result.push(unit)
|
|
97
|
+
return result
|
|
98
|
+
}, [])
|
|
99
|
+
const lines = []
|
|
100
|
+
let currentUnits = []
|
|
101
|
+
|
|
102
|
+
units.forEach((unit) => {
|
|
103
|
+
const candidateUnits = currentUnits.concat(unit)
|
|
104
|
+
const candidateText = candidateUnits.map((item) => item.text).join('')
|
|
105
|
+
if (
|
|
106
|
+
currentUnits.length &&
|
|
107
|
+
measureText(candidateText) > width
|
|
108
|
+
) {
|
|
109
|
+
lines.push(lineFromUnits(currentUnits))
|
|
110
|
+
currentUnits = [unit]
|
|
111
|
+
} else {
|
|
112
|
+
currentUnits = candidateUnits
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
if (currentUnits.length) {
|
|
117
|
+
lines.push(lineFromUnits(currentUnits))
|
|
118
|
+
}
|
|
119
|
+
return lines
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function getLineHeight(layout) {
|
|
123
|
+
const fontSize = Number(layout.fontSize) || 18
|
|
124
|
+
const lineHeight = Number(layout.lineHeight)
|
|
125
|
+
if (!Number.isFinite(lineHeight)) return fontSize * 1.8
|
|
126
|
+
return lineHeight <= 4 ? fontSize * lineHeight : lineHeight
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function createPage(lines, index) {
|
|
130
|
+
if (!lines.length) return null
|
|
131
|
+
return {
|
|
132
|
+
index,
|
|
133
|
+
text: lines.map((line) => line.text).join('\n'),
|
|
134
|
+
lines,
|
|
135
|
+
startOffset: lines[0].startOffset,
|
|
136
|
+
endOffset: lines[lines.length - 1].endOffset
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function paginateParagraphs(paragraphs = [], layout = {}) {
|
|
141
|
+
const width = Math.max(1, Number(layout.width) || 320)
|
|
142
|
+
const height = Math.max(1, Number(layout.height) || 500)
|
|
143
|
+
const lineHeight = Math.max(1, getLineHeight(layout))
|
|
144
|
+
const paragraphSpacing = Math.max(0, Number(layout.paragraphSpacing) || 0)
|
|
145
|
+
const measureText = typeof layout.measureText === 'function'
|
|
146
|
+
? layout.measureText
|
|
147
|
+
: (text) => measureTextWidth(text, layout)
|
|
148
|
+
const pages = []
|
|
149
|
+
let lines = []
|
|
150
|
+
let usedHeight = 0
|
|
151
|
+
|
|
152
|
+
const flushPage = () => {
|
|
153
|
+
const page = createPage(lines, pages.length)
|
|
154
|
+
if (page) pages.push(page)
|
|
155
|
+
lines = []
|
|
156
|
+
usedHeight = 0
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
paragraphs.forEach((paragraph, paragraphIndex) => {
|
|
160
|
+
const normalizedParagraph = typeof paragraph === 'string'
|
|
161
|
+
? {
|
|
162
|
+
text: paragraph,
|
|
163
|
+
startOffset: 0,
|
|
164
|
+
endOffset: paragraph.length
|
|
165
|
+
}
|
|
166
|
+
: paragraph
|
|
167
|
+
const paragraphLines = wrapText(
|
|
168
|
+
normalizedParagraph && normalizedParagraph.text,
|
|
169
|
+
width,
|
|
170
|
+
measureText
|
|
171
|
+
).map((line) => ({
|
|
172
|
+
...line,
|
|
173
|
+
startOffset: line.startOffset + (normalizedParagraph.startOffset || 0),
|
|
174
|
+
endOffset: line.endOffset + (normalizedParagraph.startOffset || 0),
|
|
175
|
+
paragraphIndex
|
|
176
|
+
}))
|
|
177
|
+
|
|
178
|
+
if (paragraphIndex > 0 && lines.length && usedHeight + paragraphSpacing + lineHeight > height) {
|
|
179
|
+
flushPage()
|
|
180
|
+
} else if (paragraphIndex > 0 && lines.length) {
|
|
181
|
+
usedHeight += paragraphSpacing
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
paragraphLines.forEach((line) => {
|
|
185
|
+
if (lines.length && usedHeight + lineHeight > height) {
|
|
186
|
+
flushPage()
|
|
187
|
+
}
|
|
188
|
+
lines.push(line)
|
|
189
|
+
usedHeight += lineHeight
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
flushPage()
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
pages,
|
|
196
|
+
pageCount: pages.length,
|
|
197
|
+
charOffsetToPage: pages.map((page) => ({
|
|
198
|
+
pageIndex: page.index,
|
|
199
|
+
startOffset: page.startOffset,
|
|
200
|
+
endOffset: page.endOffset
|
|
201
|
+
}))
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function resolveAnchor(pages = [], charOffset = 0) {
|
|
206
|
+
if (!pages.length) {
|
|
207
|
+
return {
|
|
208
|
+
pageIndex: 0,
|
|
209
|
+
localOffset: 0
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const offset = Math.max(0, Number(charOffset) || 0)
|
|
213
|
+
const page = pages.find((item) => offset <= item.endOffset) || pages[pages.length - 1]
|
|
214
|
+
return {
|
|
215
|
+
pageIndex: page.index,
|
|
216
|
+
localOffset: Math.max(0, Math.min(page.text.length, offset - page.startOffset))
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export default {
|
|
221
|
+
createLayoutKey,
|
|
222
|
+
wrapText,
|
|
223
|
+
paginateParagraphs,
|
|
224
|
+
resolveAnchor
|
|
225
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
function toNumber(value, fallback) {
|
|
2
|
+
const number = Number.parseFloat(value)
|
|
3
|
+
return Number.isFinite(number) ? number : fallback
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function getFontSize(style = {}) {
|
|
7
|
+
return toNumber(style.fontSize, 18)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function getCharacterWidth(character, fontSize) {
|
|
11
|
+
if (/[\u3400-\u9fff\u3040-\u30ff\uff00-\uffef]/.test(character)) {
|
|
12
|
+
return fontSize
|
|
13
|
+
}
|
|
14
|
+
if (/\s/.test(character)) {
|
|
15
|
+
return fontSize * 0.28
|
|
16
|
+
}
|
|
17
|
+
return fontSize * 0.56
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function normalizeMeasureResult(value) {
|
|
21
|
+
if (typeof value === 'number') return value
|
|
22
|
+
if (value && typeof value.width === 'number') return value.width
|
|
23
|
+
return 0
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function measureTextWidth(text, style = {}, measureText) {
|
|
27
|
+
if (typeof measureText === 'function') {
|
|
28
|
+
return normalizeMeasureResult(measureText(String(text), style))
|
|
29
|
+
}
|
|
30
|
+
const fontSize = getFontSize(style)
|
|
31
|
+
return Array.from(String(text)).reduce(
|
|
32
|
+
(width, character) => width + getCharacterWidth(character, fontSize),
|
|
33
|
+
0
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function createMeasureText({ canvasContext, style = {}, measureText } = {}) {
|
|
38
|
+
if (typeof measureText === 'function') {
|
|
39
|
+
return (text) => measureTextWidth(text, style, measureText)
|
|
40
|
+
}
|
|
41
|
+
if (canvasContext && typeof canvasContext.measureText === 'function') {
|
|
42
|
+
const fontSize = getFontSize(style)
|
|
43
|
+
const fontWeight = style.fontWeight || 400
|
|
44
|
+
const fontFamily = style.fontFamily || 'sans-serif'
|
|
45
|
+
canvasContext.font = `${fontWeight} ${fontSize}px ${fontFamily}`
|
|
46
|
+
return (text) => normalizeMeasureResult(canvasContext.measureText(String(text)))
|
|
47
|
+
}
|
|
48
|
+
return (text) => measureTextWidth(text, style)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function measureContainer(selector, vm) {
|
|
52
|
+
return new Promise((resolve) => {
|
|
53
|
+
if (typeof uni === 'undefined' || typeof uni.createSelectorQuery !== 'function') {
|
|
54
|
+
resolve(null)
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
const query = vm
|
|
58
|
+
? uni.createSelectorQuery().in(vm)
|
|
59
|
+
: uni.createSelectorQuery()
|
|
60
|
+
query.select(selector).boundingClientRect((rect) => resolve(rect || null)).exec()
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default {
|
|
65
|
+
normalizeMeasureResult,
|
|
66
|
+
measureTextWidth,
|
|
67
|
+
createMeasureText,
|
|
68
|
+
measureContainer
|
|
69
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
novelReader: {
|
|
3
|
+
chapters: [],
|
|
4
|
+
currentChapter: null,
|
|
5
|
+
loading: false,
|
|
6
|
+
error: null,
|
|
7
|
+
bookId: '',
|
|
8
|
+
storageKey: '',
|
|
9
|
+
persist: true,
|
|
10
|
+
initialProgress: null,
|
|
11
|
+
progress: null,
|
|
12
|
+
initialBookmarks: [],
|
|
13
|
+
bookmarks: null,
|
|
14
|
+
defaultSettings: {
|
|
15
|
+
theme: 'day',
|
|
16
|
+
fontSize: 18,
|
|
17
|
+
lineHeight: 1.8,
|
|
18
|
+
paragraphSpacing: 16,
|
|
19
|
+
contentWidth: '92%',
|
|
20
|
+
fontFamily: 'system',
|
|
21
|
+
fontWeight: 400,
|
|
22
|
+
animation: true
|
|
23
|
+
},
|
|
24
|
+
settings: null,
|
|
25
|
+
mode: 'scroll',
|
|
26
|
+
showBack: true,
|
|
27
|
+
autoBack: false,
|
|
28
|
+
backIcon: 'arrow-left',
|
|
29
|
+
safeAreaInsetTop: true,
|
|
30
|
+
safeAreaInsetBottom: true,
|
|
31
|
+
preloadThreshold: 2,
|
|
32
|
+
pageAnimation: true,
|
|
33
|
+
controlsAutoHide: 0
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
export const STORAGE_VERSION = 1
|
|
2
|
+
export const DEFAULT_STORAGE_PREFIX = 'uview-plus:novel-reader:'
|
|
3
|
+
|
|
4
|
+
function isObject(value) {
|
|
5
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function getStorageValue(key) {
|
|
9
|
+
if (!key || typeof uni === 'undefined' || typeof uni.getStorageSync !== 'function') {
|
|
10
|
+
return null
|
|
11
|
+
}
|
|
12
|
+
return uni.getStorageSync(key)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeProgress(value) {
|
|
16
|
+
if (value == null) return null
|
|
17
|
+
if (!isObject(value)) return null
|
|
18
|
+
const numericFields = ['chapterIndex', 'pageIndex', 'pageCount', 'charOffset', 'scrollTop']
|
|
19
|
+
for (const field of numericFields) {
|
|
20
|
+
if (value[field] !== undefined && (
|
|
21
|
+
!Number.isFinite(Number(value[field])) ||
|
|
22
|
+
Number(value[field]) < 0
|
|
23
|
+
)) {
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
for (const field of ['chapterProgress', 'totalProgress']) {
|
|
28
|
+
if (value[field] !== undefined && (
|
|
29
|
+
!Number.isFinite(Number(value[field])) ||
|
|
30
|
+
Number(value[field]) < 0 ||
|
|
31
|
+
Number(value[field]) > 1
|
|
32
|
+
)) {
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
...value,
|
|
38
|
+
pageIndex: Math.max(0, Number(value.pageIndex) || 0),
|
|
39
|
+
pageCount: Math.max(0, Number(value.pageCount) || 0),
|
|
40
|
+
charOffset: Math.max(0, Number(value.charOffset) || 0),
|
|
41
|
+
scrollTop: Math.max(0, Number(value.scrollTop) || 0)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function normalizeSettings(value) {
|
|
46
|
+
if (!isObject(value)) return {}
|
|
47
|
+
return { ...value }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizeBookmarks(value) {
|
|
51
|
+
if (!Array.isArray(value)) return []
|
|
52
|
+
return value.filter((bookmark) => (
|
|
53
|
+
isObject(bookmark) &&
|
|
54
|
+
bookmark.id != null &&
|
|
55
|
+
bookmark.chapterId != null &&
|
|
56
|
+
Number.isFinite(Number(bookmark.charOffset)) &&
|
|
57
|
+
Number(bookmark.charOffset) >= 0
|
|
58
|
+
))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function createStorageKey({ storageKey = '', bookId = '' } = {}) {
|
|
62
|
+
if (storageKey) return String(storageKey)
|
|
63
|
+
if (bookId === '' || bookId == null) return ''
|
|
64
|
+
return `${DEFAULT_STORAGE_PREFIX}${bookId}`
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function normalizePersistedState(value) {
|
|
68
|
+
if (!isObject(value) || value.version !== STORAGE_VERSION) return null
|
|
69
|
+
const progress = normalizeProgress(value.progress)
|
|
70
|
+
const readingTime = Number(value.readingTime)
|
|
71
|
+
const updatedAt = Number(value.updatedAt)
|
|
72
|
+
if (
|
|
73
|
+
!Number.isFinite(readingTime) ||
|
|
74
|
+
readingTime < 0 ||
|
|
75
|
+
!Number.isFinite(updatedAt) ||
|
|
76
|
+
updatedAt < 0
|
|
77
|
+
) {
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
version: STORAGE_VERSION,
|
|
82
|
+
progress,
|
|
83
|
+
settings: normalizeSettings(value.settings),
|
|
84
|
+
bookmarks: normalizeBookmarks(value.bookmarks),
|
|
85
|
+
readingTime,
|
|
86
|
+
updatedAt
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function readPersistedState(key) {
|
|
91
|
+
if (!key) return null
|
|
92
|
+
try {
|
|
93
|
+
const value = getStorageValue(key)
|
|
94
|
+
const parsed = typeof value === 'string' ? JSON.parse(value) : value
|
|
95
|
+
const normalized = normalizePersistedState(parsed)
|
|
96
|
+
if (
|
|
97
|
+
parsed != null &&
|
|
98
|
+
normalized == null &&
|
|
99
|
+
typeof uni !== 'undefined' &&
|
|
100
|
+
typeof uni.removeStorageSync === 'function'
|
|
101
|
+
) {
|
|
102
|
+
uni.removeStorageSync(key)
|
|
103
|
+
}
|
|
104
|
+
return normalized
|
|
105
|
+
} catch (error) {
|
|
106
|
+
try {
|
|
107
|
+
if (typeof uni !== 'undefined' && typeof uni.removeStorageSync === 'function') {
|
|
108
|
+
uni.removeStorageSync(key)
|
|
109
|
+
}
|
|
110
|
+
} catch (removeError) {
|
|
111
|
+
return null
|
|
112
|
+
}
|
|
113
|
+
return null
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function writePersistedState(key, state) {
|
|
118
|
+
if (!key || typeof uni === 'undefined' || typeof uni.setStorageSync !== 'function') {
|
|
119
|
+
return false
|
|
120
|
+
}
|
|
121
|
+
const payload = {
|
|
122
|
+
version: STORAGE_VERSION,
|
|
123
|
+
progress: isObject(state && state.progress) ? state.progress : null,
|
|
124
|
+
settings: isObject(state && state.settings) ? state.settings : {},
|
|
125
|
+
bookmarks: Array.isArray(state && state.bookmarks) ? state.bookmarks : [],
|
|
126
|
+
readingTime: Math.max(0, Number(state && state.readingTime) || 0),
|
|
127
|
+
updatedAt: Date.now()
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
uni.setStorageSync(key, payload)
|
|
131
|
+
return true
|
|
132
|
+
} catch (error) {
|
|
133
|
+
return false
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export default {
|
|
138
|
+
STORAGE_VERSION,
|
|
139
|
+
DEFAULT_STORAGE_PREFIX,
|
|
140
|
+
createStorageKey,
|
|
141
|
+
normalizePersistedState,
|
|
142
|
+
readPersistedState,
|
|
143
|
+
writePersistedState
|
|
144
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { defineMixin } from '../../libs/vue'
|
|
2
|
+
import { registerComponentProps } from '../../libs/config/props.js'
|
|
3
|
+
import NovelReaderDefaultProps from './novelReader'
|
|
4
|
+
|
|
5
|
+
const defProps = registerComponentProps(NovelReaderDefaultProps)
|
|
6
|
+
|
|
7
|
+
export const props = defineMixin({
|
|
8
|
+
props: {
|
|
9
|
+
chapters: {
|
|
10
|
+
type: Array,
|
|
11
|
+
default: () => defProps.novelReader.chapters
|
|
12
|
+
},
|
|
13
|
+
currentChapter: {
|
|
14
|
+
type: Object,
|
|
15
|
+
default: () => defProps.novelReader.currentChapter
|
|
16
|
+
},
|
|
17
|
+
loading: {
|
|
18
|
+
type: Boolean,
|
|
19
|
+
default: () => defProps.novelReader.loading
|
|
20
|
+
},
|
|
21
|
+
error: {
|
|
22
|
+
type: Object,
|
|
23
|
+
default: () => defProps.novelReader.error
|
|
24
|
+
},
|
|
25
|
+
bookId: {
|
|
26
|
+
type: [String, Number],
|
|
27
|
+
default: () => defProps.novelReader.bookId
|
|
28
|
+
},
|
|
29
|
+
storageKey: {
|
|
30
|
+
type: String,
|
|
31
|
+
default: () => defProps.novelReader.storageKey
|
|
32
|
+
},
|
|
33
|
+
persist: {
|
|
34
|
+
type: Boolean,
|
|
35
|
+
default: () => defProps.novelReader.persist
|
|
36
|
+
},
|
|
37
|
+
initialProgress: {
|
|
38
|
+
type: Object,
|
|
39
|
+
default: () => defProps.novelReader.initialProgress
|
|
40
|
+
},
|
|
41
|
+
progress: {
|
|
42
|
+
type: Object,
|
|
43
|
+
default: () => defProps.novelReader.progress
|
|
44
|
+
},
|
|
45
|
+
initialBookmarks: {
|
|
46
|
+
type: Array,
|
|
47
|
+
default: () => defProps.novelReader.initialBookmarks
|
|
48
|
+
},
|
|
49
|
+
bookmarks: {
|
|
50
|
+
type: Array,
|
|
51
|
+
default: () => defProps.novelReader.bookmarks
|
|
52
|
+
},
|
|
53
|
+
defaultSettings: {
|
|
54
|
+
type: Object,
|
|
55
|
+
default: () => ({ ...defProps.novelReader.defaultSettings })
|
|
56
|
+
},
|
|
57
|
+
settings: {
|
|
58
|
+
type: Object,
|
|
59
|
+
default: () => defProps.novelReader.settings
|
|
60
|
+
},
|
|
61
|
+
mode: {
|
|
62
|
+
type: String,
|
|
63
|
+
default: () => defProps.novelReader.mode
|
|
64
|
+
},
|
|
65
|
+
showBack: {
|
|
66
|
+
type: Boolean,
|
|
67
|
+
default: () => defProps.novelReader.showBack
|
|
68
|
+
},
|
|
69
|
+
autoBack: {
|
|
70
|
+
type: Boolean,
|
|
71
|
+
default: () => defProps.novelReader.autoBack
|
|
72
|
+
},
|
|
73
|
+
backIcon: {
|
|
74
|
+
type: String,
|
|
75
|
+
default: () => defProps.novelReader.backIcon
|
|
76
|
+
},
|
|
77
|
+
safeAreaInsetTop: {
|
|
78
|
+
type: Boolean,
|
|
79
|
+
default: () => defProps.novelReader.safeAreaInsetTop
|
|
80
|
+
},
|
|
81
|
+
safeAreaInsetBottom: {
|
|
82
|
+
type: Boolean,
|
|
83
|
+
default: () => defProps.novelReader.safeAreaInsetBottom
|
|
84
|
+
},
|
|
85
|
+
preloadThreshold: {
|
|
86
|
+
type: Number,
|
|
87
|
+
default: () => defProps.novelReader.preloadThreshold
|
|
88
|
+
},
|
|
89
|
+
pageAnimation: {
|
|
90
|
+
type: Boolean,
|
|
91
|
+
default: () => defProps.novelReader.pageAnimation
|
|
92
|
+
},
|
|
93
|
+
controlsAutoHide: {
|
|
94
|
+
type: Number,
|
|
95
|
+
default: () => defProps.novelReader.controlsAutoHide
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
export default props
|