uview-plus 3.8.86 → 3.8.108

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 (61) hide show
  1. package/changelog.md +173 -3
  2. package/components/u-action-sheet/u-action-sheet.vue +21 -2
  3. package/components/u-barcode/u-barcode.vue +0 -2
  4. package/components/u-button/u-button.vue +2 -5
  5. package/components/u-canvas/u-canvas.vue +430 -77
  6. package/components/u-cell/u-cell.vue +8 -0
  7. package/components/u-datetime-picker/u-datetime-picker.vue +53 -4
  8. package/components/u-dragsort/u-dragsort.vue +55 -31
  9. package/components/u-icon/u-icon.vue +12 -3
  10. package/components/u-icon/util.js +81 -10
  11. package/components/u-index-item/u-index-item.vue +1 -1
  12. package/components/u-index-list/u-index-list.vue +1 -1
  13. package/components/u-novel-reader/content-normalizer.js +80 -0
  14. package/components/u-novel-reader/layout-engine.js +225 -0
  15. package/components/u-novel-reader/measure-adapter.js +69 -0
  16. package/components/u-novel-reader/novelReader.js +35 -0
  17. package/components/u-novel-reader/persistence.js +144 -0
  18. package/components/u-novel-reader/props.js +100 -0
  19. package/components/u-novel-reader/reader-catalog.vue +234 -0
  20. package/components/u-novel-reader/reader-content.vue +226 -0
  21. package/components/u-novel-reader/reader-core.js +151 -0
  22. package/components/u-novel-reader/reader-settings.vue +325 -0
  23. package/components/u-novel-reader/reader-toolbar.vue +313 -0
  24. package/components/u-novel-reader/theme-vars.scss +49 -0
  25. package/components/u-novel-reader/u-novel-reader.vue +836 -0
  26. package/components/u-poster/u-poster.vue +272 -222
  27. package/components/u-qrcode/qrcode.js +56 -49
  28. package/components/u-qrcode/u-qrcode.vue +159 -88
  29. package/components/u-scroll-list/scrollWxs.wxs +1 -1
  30. package/components/u-slider/u-slider.vue +1 -1
  31. package/components/u-swipe-action-item/index.wxs +43 -25
  32. package/components/u-swipe-action-item/nvue.js +9 -0
  33. package/components/u-swipe-action-item/other.js +32 -21
  34. package/components/u-swipe-action-item/props.js +5 -0
  35. package/components/u-swipe-action-item/swipeActionItem.js +1 -0
  36. package/components/u-swipe-action-item/u-swipe-action-item.vue +28 -4
  37. package/components/u-switch/u-switch.vue +1 -1
  38. package/components/u-tabbar/u-tabbar.vue +2 -1
  39. package/components/u-tabbar-item/u-tabbar-item.vue +64 -21
  40. package/components/u-tabs/u-tabs.vue +11 -8
  41. package/components/u-tabs-pro/u-tabs-pro.vue +212 -0
  42. package/components/u-text/text.js +1 -1
  43. package/components/u-text/u-text.vue +18 -6
  44. package/components/u-upload/u-upload.vue +1 -1
  45. package/components/u-waterfall/u-waterfall.vue +118 -65
  46. package/libs/config/config.js +1 -1
  47. package/libs/config/props.js +1 -0
  48. package/libs/function/index.js +66 -0
  49. package/libs/mixin/mixin.js +4 -50
  50. package/libs/root/index.js +78 -5
  51. package/libs/root/page.js +7 -7
  52. package/libs/root/root.js +73 -3
  53. package/libs/util/app-nvue-webview-canvas.js +486 -0
  54. package/libs/util/gcanvas/bridge/bridge-weex.js +0 -2
  55. package/libs/util/gcanvas/index.js +3 -3
  56. package/package.json +2 -2
  57. package/types/comps/swipeActionItem.d.ts +13 -0
  58. package/types/comps/tabs.d.ts +2 -1
  59. package/types/comps/text.d.ts +1 -1
  60. package/types/func.d.ts +23 -0
  61. 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