quasar 2.21.3 → 2.22.0
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/README.md +1 -1
- package/dist/api/Lang.json +1 -1
- package/dist/api/QUploader.json +1 -1
- package/dist/icon-set/bootstrap-icons.umd.prod.js +1 -1
- package/dist/icon-set/eva-icons.umd.prod.js +1 -1
- package/dist/icon-set/fontawesome-v5-pro.umd.prod.js +1 -1
- package/dist/icon-set/fontawesome-v5.umd.prod.js +1 -1
- package/dist/icon-set/fontawesome-v6-pro.umd.prod.js +1 -1
- package/dist/icon-set/fontawesome-v6.umd.prod.js +1 -1
- package/dist/icon-set/fontawesome-v7-pro.umd.prod.js +1 -1
- package/dist/icon-set/fontawesome-v7.umd.prod.js +1 -1
- package/dist/icon-set/ionicons-v4.umd.prod.js +1 -1
- package/dist/icon-set/line-awesome.umd.prod.js +1 -1
- package/dist/icon-set/material-icons-outlined.umd.prod.js +1 -1
- package/dist/icon-set/material-icons-round.umd.prod.js +1 -1
- package/dist/icon-set/material-icons-sharp.umd.prod.js +1 -1
- package/dist/icon-set/material-icons.umd.prod.js +1 -1
- package/dist/icon-set/material-symbols-outlined.umd.prod.js +1 -1
- package/dist/icon-set/material-symbols-rounded.umd.prod.js +1 -1
- package/dist/icon-set/material-symbols-sharp.umd.prod.js +1 -1
- package/dist/icon-set/mdi-v3.umd.prod.js +1 -1
- package/dist/icon-set/mdi-v4.umd.prod.js +1 -1
- package/dist/icon-set/mdi-v5.umd.prod.js +1 -1
- package/dist/icon-set/mdi-v6.umd.prod.js +1 -1
- package/dist/icon-set/mdi-v7.umd.prod.js +1 -1
- package/dist/icon-set/svg-bootstrap-icons.umd.prod.js +1 -1
- package/dist/icon-set/svg-eva-icons.umd.prod.js +1 -1
- package/dist/icon-set/svg-fontawesome-v5.umd.prod.js +1 -1
- package/dist/icon-set/svg-fontawesome-v6.umd.prod.js +1 -1
- package/dist/icon-set/svg-fontawesome-v7.umd.prod.js +1 -1
- package/dist/icon-set/svg-ionicons-v4.umd.prod.js +1 -1
- package/dist/icon-set/svg-ionicons-v5.umd.prod.js +1 -1
- package/dist/icon-set/svg-ionicons-v6.umd.prod.js +1 -1
- package/dist/icon-set/svg-ionicons-v7.umd.prod.js +1 -1
- package/dist/icon-set/svg-ionicons-v8.umd.prod.js +1 -1
- package/dist/icon-set/svg-line-awesome.umd.prod.js +1 -1
- package/dist/icon-set/svg-material-icons-outlined.umd.prod.js +1 -1
- package/dist/icon-set/svg-material-icons-round.umd.prod.js +1 -1
- package/dist/icon-set/svg-material-icons-sharp.umd.prod.js +1 -1
- package/dist/icon-set/svg-material-icons.umd.prod.js +1 -1
- package/dist/icon-set/svg-material-symbols-outlined.umd.prod.js +1 -1
- package/dist/icon-set/svg-material-symbols-rounded.umd.prod.js +1 -1
- package/dist/icon-set/svg-material-symbols-sharp.umd.prod.js +1 -1
- package/dist/icon-set/svg-mdi-v6.umd.prod.js +1 -1
- package/dist/icon-set/svg-mdi-v7.umd.prod.js +1 -1
- package/dist/icon-set/svg-themify.umd.prod.js +1 -1
- package/dist/icon-set/themify.umd.prod.js +1 -1
- package/dist/lang/ar-TN.umd.prod.js +1 -1
- package/dist/lang/ar.umd.prod.js +1 -1
- package/dist/lang/az-Latn.umd.prod.js +2 -2
- package/dist/lang/bg.umd.prod.js +2 -2
- package/dist/lang/bn.umd.prod.js +2 -2
- package/dist/lang/bs-BA.umd.prod.js +2 -2
- package/dist/lang/ca.umd.prod.js +2 -2
- package/dist/lang/cs.umd.prod.js +2 -2
- package/dist/lang/da.umd.prod.js +2 -2
- package/dist/lang/de-CH.umd.prod.js +2 -2
- package/dist/lang/de-DE.umd.prod.js +2 -2
- package/dist/lang/de.umd.prod.js +2 -2
- package/dist/lang/el.umd.prod.js +2 -2
- package/dist/lang/en-GB.umd.prod.js +2 -2
- package/dist/lang/en-US.umd.prod.js +2 -2
- package/dist/lang/eo.umd.prod.js +2 -2
- package/dist/lang/es.umd.prod.js +2 -2
- package/dist/lang/et.umd.prod.js +2 -2
- package/dist/lang/eu.umd.prod.js +2 -2
- package/dist/lang/fa-IR.umd.prod.js +1 -1
- package/dist/lang/fa.umd.prod.js +1 -1
- package/dist/lang/fi.umd.prod.js +2 -2
- package/dist/lang/fr.umd.prod.js +2 -2
- package/dist/lang/gn.umd.prod.js +2 -2
- package/dist/lang/he.umd.prod.js +1 -1
- package/dist/lang/hi.umd.prod.js +2 -2
- package/dist/lang/hr.umd.prod.js +2 -2
- package/dist/lang/hu.umd.prod.js +2 -2
- package/dist/lang/id.umd.prod.js +2 -2
- package/dist/lang/is.umd.prod.js +2 -2
- package/dist/lang/it.umd.prod.js +2 -2
- package/dist/lang/ja.umd.prod.js +2 -2
- package/dist/lang/kk.umd.prod.js +2 -2
- package/dist/lang/km.umd.prod.js +2 -2
- package/dist/lang/ko-KR.umd.prod.js +1 -1
- package/dist/lang/kur-CKB.umd.prod.js +1 -1
- package/dist/lang/lb.umd.prod.js +2 -2
- package/dist/lang/lt.umd.prod.js +2 -2
- package/dist/lang/lu.umd.prod.js +2 -2
- package/dist/lang/lv.umd.prod.js +2 -2
- package/dist/lang/mk.umd.prod.js +2 -2
- package/dist/lang/ml.umd.prod.js +2 -2
- package/dist/lang/mm.umd.prod.js +2 -2
- package/dist/lang/ms-MY.umd.prod.js +2 -2
- package/dist/lang/ms.umd.prod.js +2 -2
- package/dist/lang/my.umd.prod.js +1 -1
- package/dist/lang/nb-NO.umd.prod.js +2 -2
- package/dist/lang/nl.umd.prod.js +2 -2
- package/dist/lang/pl.umd.prod.js +2 -2
- package/dist/lang/pt-BR.umd.prod.js +2 -2
- package/dist/lang/pt.umd.prod.js +2 -2
- package/dist/lang/ro.umd.prod.js +2 -2
- package/dist/lang/ru.umd.prod.js +2 -2
- package/dist/lang/sk.umd.prod.js +2 -2
- package/dist/lang/sl.umd.prod.js +2 -2
- package/dist/lang/sm.umd.prod.js +2 -2
- package/dist/lang/sq.umd.prod.js +2 -2
- package/dist/lang/sr-CYR.umd.prod.js +2 -2
- package/dist/lang/sr.umd.prod.js +2 -2
- package/dist/lang/sv.umd.prod.js +2 -2
- package/dist/lang/ta.umd.prod.js +2 -2
- package/dist/lang/th.umd.prod.js +2 -2
- package/dist/lang/tl.umd.prod.js +2 -2
- package/dist/lang/tr.umd.prod.js +2 -2
- package/dist/lang/ug.umd.prod.js +1 -1
- package/dist/lang/uk.umd.prod.js +2 -2
- package/dist/lang/ur-PK.umd.prod.js +1 -1
- package/dist/lang/uz-Cyrl.umd.prod.js +2 -2
- package/dist/lang/uz-Latn.umd.prod.js +2 -2
- package/dist/lang/vi.umd.prod.js +2 -2
- package/dist/lang/zh-CN.umd.prod.js +2 -2
- package/dist/lang/zh-TW.umd.prod.js +2 -2
- package/dist/quasar.client.js +573 -188
- package/dist/quasar.css +4 -0
- package/dist/quasar.prod.css +1 -1
- package/dist/quasar.rtl.css +5 -0
- package/dist/quasar.rtl.prod.css +1 -1
- package/dist/quasar.sass +4 -1
- package/dist/quasar.server.prod.cjs +35 -35
- package/dist/quasar.server.prod.js +25 -25
- package/dist/quasar.umd.js +573 -188
- package/dist/quasar.umd.prod.js +30 -30
- package/dist/types/composables.d.ts +10 -5
- package/dist/types/index.d.ts +4 -4
- package/dist/web-types/web-types.json +1 -1
- package/lang/az-Latn.js +1 -1
- package/lang/bg.js +1 -1
- package/lang/bn.js +1 -1
- package/lang/bs-BA.js +1 -1
- package/lang/ca.js +1 -1
- package/lang/cs.js +1 -1
- package/lang/da.js +1 -1
- package/lang/de-CH.js +1 -1
- package/lang/de-DE.js +1 -1
- package/lang/de.js +1 -1
- package/lang/el.js +1 -1
- package/lang/en-GB.js +1 -1
- package/lang/en-US.js +1 -1
- package/lang/eo.js +1 -1
- package/lang/es.js +1 -1
- package/lang/et.js +1 -1
- package/lang/eu.js +1 -1
- package/lang/fi.js +1 -1
- package/lang/fr.js +1 -1
- package/lang/gn.js +1 -1
- package/lang/hi.js +1 -1
- package/lang/hr.js +1 -1
- package/lang/hu.js +1 -1
- package/lang/id.js +1 -1
- package/lang/is.js +1 -1
- package/lang/it.js +1 -1
- package/lang/ja.js +1 -1
- package/lang/kk.js +1 -1
- package/lang/km.js +1 -1
- package/lang/lb.js +1 -1
- package/lang/lt.js +1 -1
- package/lang/lu.js +1 -1
- package/lang/lv.js +1 -1
- package/lang/mk.js +1 -1
- package/lang/ml.js +1 -1
- package/lang/mm.js +1 -1
- package/lang/ms-MY.js +1 -1
- package/lang/ms.js +1 -1
- package/lang/nb-NO.js +1 -1
- package/lang/nl.js +1 -1
- package/lang/pl.js +1 -1
- package/lang/pt-BR.js +1 -1
- package/lang/pt.js +1 -1
- package/lang/ro.js +1 -1
- package/lang/ru.js +1 -1
- package/lang/sk.js +1 -1
- package/lang/sl.js +1 -1
- package/lang/sm.js +1 -1
- package/lang/sq.js +1 -1
- package/lang/sr-CYR.js +1 -1
- package/lang/sr.js +1 -1
- package/lang/sv.js +1 -1
- package/lang/ta.js +1 -1
- package/lang/th.js +1 -1
- package/lang/tl.js +1 -1
- package/lang/tr.js +1 -1
- package/lang/uk.js +1 -1
- package/lang/uz-Cyrl.js +1 -1
- package/lang/uz-Latn.js +1 -1
- package/lang/vi.js +1 -1
- package/lang/zh-CN.js +1 -1
- package/lang/zh-TW.js +1 -1
- package/package.json +6 -6
- package/src/components/carousel/QCarousel.js +2 -0
- package/src/components/chip/QChip.js +20 -4
- package/src/components/date/QDate.js +9 -2
- package/src/components/dialog/QDialog.js +9 -2
- package/src/components/editor/QEditor.js +18 -3
- package/src/components/expansion-item/QExpansionItem.js +6 -1
- package/src/components/input/QInput.js +42 -1
- package/src/components/input/QInput.json +7 -0
- package/src/components/input/use-mask.js +12 -4
- package/src/components/item/QItem.js +10 -1
- package/src/components/menu/QMenu.js +9 -2
- package/src/components/range/QRange.js +2 -1
- package/src/components/rating/QRating.js +27 -8
- package/src/components/rating/QRating.sass +4 -0
- package/src/components/resize-observer/QResizeObserver.js +14 -3
- package/src/components/scroll-area/QScrollArea.js +69 -17
- package/src/components/slide-item/QSlideItem.js +3 -2
- package/src/components/slider/use-slider.js +7 -3
- package/src/components/stepper/StepHeader.js +11 -2
- package/src/components/table/QTable.js +11 -9
- package/src/components/table/QTh.js +20 -1
- package/src/components/table/table-column-selection.js +9 -1
- package/src/components/table/table-pagination.js +5 -2
- package/src/components/table/table-row-selection.js +10 -10
- package/src/components/tabs/QTabs.js +5 -6
- package/src/components/time/QTime.js +35 -10
- package/src/components/tooltip/QTooltip.js +112 -6
- package/src/components/tree/QTree.js +124 -4
- package/src/components/uploader/uploader-core.js +19 -4
- package/src/components/uploader/xhr-uploader-plugin.js +93 -43
- package/src/components/uploader/xhr-uploader-plugin.json +4 -4
- package/src/composables/private.use-field/use-field.js +2 -0
- package/src/composables/private.use-file/use-file.js +21 -12
- package/src/composables/private.use-fullscreen/use-fullscreen.js +8 -3
- package/src/composables/private.use-model-toggle/use-model-toggle.js +2 -0
- package/src/composables/private.use-panel/use-panel.js +6 -2
- package/src/composables/use-interval/use-interval.js +8 -0
- package/src/composables/use-render-cache/use-render-cache.js +15 -10
- package/src/composables/use-timeout/use-timeout.js +8 -0
- package/src/directives/intersection/Intersection.js +2 -2
- package/src/directives/touch-repeat/TouchRepeat.js +3 -1
- package/src/plugins/cookies/Cookies.js +29 -13
- package/src/plugins/lang/Lang.json +4 -4
- package/src/plugins/lang/Lang.test.js +1 -1
- package/src/plugins/meta/Meta.js +57 -9
- package/src/utils/extend/extend.js +4 -0
- package/src/utils/extend/extend.test.js +29 -0
- package/src/utils/scroll/prevent-scroll.js +4 -4
|
@@ -105,7 +105,9 @@ export default function useFile({
|
|
|
105
105
|
files = filterFiles(files, rejectedFiles, 'accept', file =>
|
|
106
106
|
extensions.value.some(
|
|
107
107
|
ext =>
|
|
108
|
-
|
|
108
|
+
(ext.endsWith('/')
|
|
109
|
+
? file.type.toUpperCase().startsWith(ext)
|
|
110
|
+
: file.type.toUpperCase() === ext) ||
|
|
109
111
|
file.name.toUpperCase().endsWith(ext)
|
|
110
112
|
)
|
|
111
113
|
)
|
|
@@ -137,19 +139,23 @@ export default function useFile({
|
|
|
137
139
|
|
|
138
140
|
// Compute key to use for each file
|
|
139
141
|
files.forEach(file => {
|
|
140
|
-
file.__key =
|
|
141
|
-
file.webkitRelativePath
|
|
142
|
+
file.__key = JSON.stringify([
|
|
143
|
+
file.webkitRelativePath,
|
|
144
|
+
file.lastModified,
|
|
145
|
+
file.name,
|
|
146
|
+
file.size
|
|
147
|
+
])
|
|
142
148
|
})
|
|
143
149
|
|
|
144
150
|
if (append) {
|
|
145
151
|
// Avoid duplicate files
|
|
146
|
-
const
|
|
147
|
-
files = filterFiles(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
)
|
|
152
|
+
const filenameSet = new Set(currentFileList.map(entry => entry.__key))
|
|
153
|
+
files = filterFiles(files, rejectedFiles, 'duplicate', file => {
|
|
154
|
+
if (filenameSet.has(file.__key)) return false
|
|
155
|
+
|
|
156
|
+
filenameSet.add(file.__key)
|
|
157
|
+
return true
|
|
158
|
+
})
|
|
153
159
|
}
|
|
154
160
|
|
|
155
161
|
if (files.length === 0) {
|
|
@@ -162,8 +168,11 @@ export default function useFile({
|
|
|
162
168
|
: 0
|
|
163
169
|
|
|
164
170
|
files = filterFiles(files, rejectedFiles, 'max-total-size', file => {
|
|
165
|
-
size
|
|
166
|
-
|
|
171
|
+
const newSize = size + file.size
|
|
172
|
+
if (newSize > maxTotalSizeNumber.value) return false
|
|
173
|
+
|
|
174
|
+
size = newSize
|
|
175
|
+
return true
|
|
167
176
|
})
|
|
168
177
|
|
|
169
178
|
if (files.length === 0) {
|
|
@@ -23,7 +23,9 @@ export default function useFullscreen() {
|
|
|
23
23
|
const vm = getCurrentInstance()
|
|
24
24
|
const { props, emit, proxy } = vm
|
|
25
25
|
|
|
26
|
-
let historyEntry,
|
|
26
|
+
let historyEntry,
|
|
27
|
+
fullscreenFillerNode,
|
|
28
|
+
isUnmounting = false
|
|
27
29
|
const inFullscreen = ref(false)
|
|
28
30
|
|
|
29
31
|
if (vmHasRouter(vm)) {
|
|
@@ -89,7 +91,7 @@ export default function useFullscreen() {
|
|
|
89
91
|
if (counter === 0) {
|
|
90
92
|
document.body.classList.remove('q-body--fullscreen-mixin')
|
|
91
93
|
|
|
92
|
-
if (proxy.$el.scrollIntoView !== void 0) {
|
|
94
|
+
if (!isUnmounting && proxy.$el.scrollIntoView !== void 0) {
|
|
93
95
|
setTimeout(() => {
|
|
94
96
|
proxy.$el.scrollIntoView()
|
|
95
97
|
}, 0)
|
|
@@ -105,7 +107,10 @@ export default function useFullscreen() {
|
|
|
105
107
|
if (props.fullscreen) setFullscreen()
|
|
106
108
|
})
|
|
107
109
|
|
|
108
|
-
onBeforeUnmount(
|
|
110
|
+
onBeforeUnmount(() => {
|
|
111
|
+
isUnmounting = true
|
|
112
|
+
exitFullscreen()
|
|
113
|
+
})
|
|
109
114
|
|
|
110
115
|
// expose public methods
|
|
111
116
|
Object.assign(proxy, {
|
|
@@ -21,6 +21,7 @@ export default function useModelToggle({
|
|
|
21
21
|
hideOnRouteChange, // optional
|
|
22
22
|
handleShow, // optional
|
|
23
23
|
handleHide, // optional
|
|
24
|
+
handleRouteChange, // optional
|
|
24
25
|
processOnMount // optional
|
|
25
26
|
}) {
|
|
26
27
|
const vm = getCurrentInstance()
|
|
@@ -127,6 +128,7 @@ export default function useModelToggle({
|
|
|
127
128
|
() => proxy.$route.fullPath,
|
|
128
129
|
() => {
|
|
129
130
|
if (hideOnRouteChange.value && showing.value) {
|
|
131
|
+
handleRouteChange?.()
|
|
130
132
|
hide()
|
|
131
133
|
}
|
|
132
134
|
}
|
|
@@ -108,11 +108,15 @@ export default function usePanel() {
|
|
|
108
108
|
])
|
|
109
109
|
|
|
110
110
|
const transitionPrev = computed(
|
|
111
|
-
() =>
|
|
111
|
+
() =>
|
|
112
|
+
props.transitionPrev ||
|
|
113
|
+
`slide-${props.vertical ? 'down' : proxy.$q.lang.rtl ? 'left' : 'right'}`
|
|
112
114
|
)
|
|
113
115
|
|
|
114
116
|
const transitionNext = computed(
|
|
115
|
-
() =>
|
|
117
|
+
() =>
|
|
118
|
+
props.transitionNext ||
|
|
119
|
+
`slide-${props.vertical ? 'up' : proxy.$q.lang.rtl ? 'right' : 'left'}`
|
|
116
120
|
)
|
|
117
121
|
|
|
118
122
|
const transitionStyle = computed(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getCurrentInstance, onBeforeUnmount, onDeactivated } from 'vue'
|
|
2
2
|
|
|
3
3
|
import { vmIsDestroyed } from '../../utils/private.vm/vm.js'
|
|
4
|
+
import { noop } from '../../utils/event/event.js'
|
|
4
5
|
|
|
5
6
|
/*
|
|
6
7
|
* Usage:
|
|
@@ -9,6 +10,13 @@ import { vmIsDestroyed } from '../../utils/private.vm/vm.js'
|
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
export default function useInterval() {
|
|
13
|
+
if (__QUASAR_SSR_SERVER__) {
|
|
14
|
+
return {
|
|
15
|
+
removeInterval: noop,
|
|
16
|
+
registerInterval: noop
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
let timer = null
|
|
13
21
|
const vm = getCurrentInstance()
|
|
14
22
|
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
export default function useRenderCache() {
|
|
2
|
+
if (__QUASAR_SSR_SERVER__) {
|
|
3
|
+
return {
|
|
4
|
+
getCache: (_, defaultValue) =>
|
|
5
|
+
typeof defaultValue === 'function' ? defaultValue() : defaultValue,
|
|
6
|
+
setCache() {},
|
|
7
|
+
hasCache: () => false,
|
|
8
|
+
clearCache() {}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
2
12
|
let cache = Object.create(null)
|
|
3
13
|
|
|
4
14
|
return {
|
|
5
|
-
getCache:
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
? (cache[key] =
|
|
11
|
-
typeof defaultValue === 'function'
|
|
12
|
-
? defaultValue()
|
|
13
|
-
: defaultValue)
|
|
14
|
-
: cache[key],
|
|
15
|
+
getCache: (key, defaultValue) =>
|
|
16
|
+
Object.hasOwn(cache, key)
|
|
17
|
+
? cache[key]
|
|
18
|
+
: (cache[key] =
|
|
19
|
+
typeof defaultValue === 'function' ? defaultValue() : defaultValue),
|
|
15
20
|
|
|
16
21
|
setCache(key, obj) {
|
|
17
22
|
cache[key] = obj
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getCurrentInstance, onBeforeUnmount, onDeactivated } from 'vue'
|
|
2
2
|
|
|
3
3
|
import { vmIsDestroyed } from '../../utils/private.vm/vm.js'
|
|
4
|
+
import { noop } from '../../utils/event/event.js'
|
|
4
5
|
|
|
5
6
|
/*
|
|
6
7
|
* Usage:
|
|
@@ -9,6 +10,13 @@ import { vmIsDestroyed } from '../../utils/private.vm/vm.js'
|
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
export default function useTimeout() {
|
|
13
|
+
if (__QUASAR_SSR_SERVER__) {
|
|
14
|
+
return {
|
|
15
|
+
removeTimeout: noop,
|
|
16
|
+
registerTimeout: noop
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
let timer = null
|
|
13
21
|
const vm = getCurrentInstance()
|
|
14
22
|
|
|
@@ -27,7 +27,7 @@ function update(el, ctx, value) {
|
|
|
27
27
|
|
|
28
28
|
if (changed) {
|
|
29
29
|
ctx.cfg = cfg
|
|
30
|
-
ctx.observer?.
|
|
30
|
+
ctx.observer?.disconnect()
|
|
31
31
|
|
|
32
32
|
ctx.observer = new IntersectionObserver(([entry]) => {
|
|
33
33
|
if (typeof ctx.handler === 'function') {
|
|
@@ -55,7 +55,7 @@ function destroy(el) {
|
|
|
55
55
|
const ctx = el.__qvisible
|
|
56
56
|
|
|
57
57
|
if (ctx !== void 0) {
|
|
58
|
-
ctx.observer?.
|
|
58
|
+
ctx.observer?.disconnect()
|
|
59
59
|
delete el.__qvisible
|
|
60
60
|
}
|
|
61
61
|
}
|
|
@@ -20,7 +20,11 @@ function read(string) {
|
|
|
20
20
|
// Replace server-side written pluses with spaces.
|
|
21
21
|
// If we can't decode the cookie, ignore it, it's unusable.
|
|
22
22
|
// If we can't parse the cookie, ignore it, it's unusable.
|
|
23
|
-
|
|
23
|
+
try {
|
|
24
|
+
string = decodeURIComponent(string.replaceAll('+', ' '))
|
|
25
|
+
} catch {
|
|
26
|
+
return
|
|
27
|
+
}
|
|
24
28
|
|
|
25
29
|
try {
|
|
26
30
|
const parsed = JSON.parse(string)
|
|
@@ -56,7 +60,12 @@ function parseExpireToSeconds(str) {
|
|
|
56
60
|
totalSeconds += value * numberUnitMultiplierMap[unit]
|
|
57
61
|
}
|
|
58
62
|
|
|
59
|
-
|
|
63
|
+
if (hasMatch) return totalSeconds
|
|
64
|
+
|
|
65
|
+
const timestamp = Date.parse(str)
|
|
66
|
+
return Number.isNaN(timestamp)
|
|
67
|
+
? void 0
|
|
68
|
+
: Math.round((timestamp - Date.now()) / 1000)
|
|
60
69
|
}
|
|
61
70
|
|
|
62
71
|
// oxlint-disable-next-line default-param-last
|
|
@@ -69,9 +78,12 @@ function set(key, val, opts = {}, ssr) {
|
|
|
69
78
|
if (Number.isFinite(opts.expires)) {
|
|
70
79
|
maxAge = Math.round(opts.expires * 86_400) // 86400 seconds in a day
|
|
71
80
|
} else if (opts.expires instanceof Date) {
|
|
72
|
-
|
|
81
|
+
const timestamp = opts.expires.getTime()
|
|
82
|
+
if (Number.isFinite(timestamp)) {
|
|
83
|
+
maxAge = Math.round((timestamp - Date.now()) / 1000)
|
|
84
|
+
}
|
|
73
85
|
}
|
|
74
|
-
// String check (eg. "15m", "1h"
|
|
86
|
+
// String check (eg. "15m", "1h", or a date string)
|
|
75
87
|
else if (typeof opts.expires === 'string') {
|
|
76
88
|
maxAge = parseExpireToSeconds(opts.expires)
|
|
77
89
|
}
|
|
@@ -104,13 +116,11 @@ function set(key, val, opts = {}, ssr) {
|
|
|
104
116
|
let all = ssr.req.headers.cookie || ''
|
|
105
117
|
|
|
106
118
|
if (maxAge !== void 0 && isDeletion) {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
.replace(`${key}=${localVal}`, '')
|
|
113
|
-
}
|
|
119
|
+
const encodedKey = encodeURIComponent(key)
|
|
120
|
+
all = all
|
|
121
|
+
.split('; ')
|
|
122
|
+
.filter(entry => entry.split('=', 1)[0] !== encodedKey)
|
|
123
|
+
.join('; ')
|
|
114
124
|
} else {
|
|
115
125
|
all = all ? `${keyValue}; ${all}` : keyValue
|
|
116
126
|
}
|
|
@@ -135,13 +145,19 @@ function get(key, ssr) {
|
|
|
135
145
|
|
|
136
146
|
for (; i < l; i++) {
|
|
137
147
|
parts = cookies[i].split('=')
|
|
138
|
-
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
name = decodeURIComponent(parts.shift())
|
|
151
|
+
} catch {
|
|
152
|
+
continue
|
|
153
|
+
}
|
|
154
|
+
|
|
139
155
|
cookie = parts.join('=')
|
|
140
156
|
|
|
141
157
|
if (!key) {
|
|
142
158
|
result[name] = cookie
|
|
143
159
|
} else if (key === name) {
|
|
144
|
-
result = read(cookie)
|
|
160
|
+
result = read(cookie) ?? null
|
|
145
161
|
break
|
|
146
162
|
}
|
|
147
163
|
}
|
|
@@ -263,10 +263,10 @@
|
|
|
263
263
|
"returns": {
|
|
264
264
|
"type": "String",
|
|
265
265
|
"desc": "Label",
|
|
266
|
-
"examples": ["'5
|
|
266
|
+
"examples": ["'5–10 of 50'"]
|
|
267
267
|
},
|
|
268
268
|
"examples": [
|
|
269
|
-
"(start, end, total) => start + '
|
|
269
|
+
"(start, end, total) => start + '–' + end + ' of ' + total"
|
|
270
270
|
]
|
|
271
271
|
},
|
|
272
272
|
"columns": {
|
|
@@ -804,10 +804,10 @@
|
|
|
804
804
|
"returns": {
|
|
805
805
|
"type": "String",
|
|
806
806
|
"desc": "Label",
|
|
807
|
-
"examples": ["'5
|
|
807
|
+
"examples": ["'5–10 of 50'"]
|
|
808
808
|
},
|
|
809
809
|
"examples": [
|
|
810
|
-
"(start, end, total) => start + '
|
|
810
|
+
"(start, end, total) => start + '–' + end + ' of ' + total"
|
|
811
811
|
]
|
|
812
812
|
},
|
|
813
813
|
"columns": {
|
|
@@ -227,7 +227,7 @@ describe('[Lang API]', () => {
|
|
|
227
227
|
recordsPerPage: 'Records per page:',
|
|
228
228
|
allRows: 'All',
|
|
229
229
|
pagination: (start, end, total) =>
|
|
230
|
-
start + '
|
|
230
|
+
start + '–' + end + ' of ' + total,
|
|
231
231
|
columns: 'Columns'
|
|
232
232
|
},
|
|
233
233
|
pagination: {
|
package/src/plugins/meta/Meta.js
CHANGED
|
@@ -4,6 +4,48 @@ import extend from '../../utils/extend/extend.js'
|
|
|
4
4
|
let updateId = null,
|
|
5
5
|
currentClientMeta
|
|
6
6
|
|
|
7
|
+
const encodeHtmlChars = {
|
|
8
|
+
'&': '&',
|
|
9
|
+
'<': '<',
|
|
10
|
+
'>': '>',
|
|
11
|
+
'"': '"',
|
|
12
|
+
"'": '''
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const encodeJsonChars = {
|
|
16
|
+
'<': String.raw`\u003C`,
|
|
17
|
+
'>': String.raw`\u003E`,
|
|
18
|
+
'&': String.raw`\u0026`,
|
|
19
|
+
'\u2028': String.raw`\u2028`,
|
|
20
|
+
'\u2029': String.raw`\u2029`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const validAttrName = /^[^\s"'>/=]+$/
|
|
24
|
+
|
|
25
|
+
function isValidAttrName(name) {
|
|
26
|
+
return (
|
|
27
|
+
validAttrName.test(name) && name.includes(String.fromCodePoint(0)) === false
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function encodeHtml(value) {
|
|
32
|
+
return String(value).replaceAll(/[&<>"']/g, char => encodeHtmlChars[char])
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function encodeJson(value) {
|
|
36
|
+
return JSON.stringify(value).replaceAll(
|
|
37
|
+
/[<>&\u2028\u2029]/g,
|
|
38
|
+
char => encodeJsonChars[char]
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function protectRawText(value, tagName) {
|
|
43
|
+
return String(value).replaceAll(
|
|
44
|
+
new RegExp(`</${tagName}`, 'gi'),
|
|
45
|
+
`<\\/${tagName}`
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
7
49
|
export const clientList = []
|
|
8
50
|
|
|
9
51
|
function normalize(meta) {
|
|
@@ -148,15 +190,17 @@ function apply({ add, remove }) {
|
|
|
148
190
|
|
|
149
191
|
function getAttr(seed) {
|
|
150
192
|
return att => {
|
|
193
|
+
if (isValidAttrName(att) === false) return
|
|
194
|
+
|
|
151
195
|
const val = seed[att]
|
|
152
|
-
return att + (val !== true && val !== void 0 ? `="${val}"` : '')
|
|
196
|
+
return att + (val !== true && val !== void 0 ? `="${encodeHtml(val)}"` : '')
|
|
153
197
|
}
|
|
154
198
|
}
|
|
155
199
|
|
|
156
200
|
function getHead(meta) {
|
|
157
201
|
let output = ''
|
|
158
202
|
if (meta.title) {
|
|
159
|
-
output += `<title>${meta.title}</title>`
|
|
203
|
+
output += `<title>${encodeHtml(meta.title)}</title>`
|
|
160
204
|
}
|
|
161
205
|
;['meta', 'link', 'script'].forEach(type => {
|
|
162
206
|
const metaType = meta[type]
|
|
@@ -165,10 +209,12 @@ function getHead(meta) {
|
|
|
165
209
|
const attrs = Object.keys(metaType[att])
|
|
166
210
|
.filter(item => item !== 'innerHTML')
|
|
167
211
|
.map(getAttr(metaType[att]))
|
|
212
|
+
.filter(Boolean)
|
|
168
213
|
|
|
169
|
-
output += `<${type} ${attrs.join(' ')} data-qmeta="${att}">`
|
|
214
|
+
output += `<${type} ${attrs.join(' ')} data-qmeta="${encodeHtml(att)}">`
|
|
170
215
|
if (type === 'script') {
|
|
171
|
-
output +=
|
|
216
|
+
output +=
|
|
217
|
+
protectRawText(metaType[att].innerHTML || '', 'script') + '</script>'
|
|
172
218
|
}
|
|
173
219
|
}
|
|
174
220
|
})
|
|
@@ -195,7 +241,9 @@ function injectServerMeta(ssrContext) {
|
|
|
195
241
|
normalize(data)
|
|
196
242
|
|
|
197
243
|
const nonce =
|
|
198
|
-
ssrContext.nonce !== void 0
|
|
244
|
+
ssrContext.nonce !== void 0
|
|
245
|
+
? ` nonce="${encodeHtml(ssrContext.nonce)}"`
|
|
246
|
+
: ''
|
|
199
247
|
|
|
200
248
|
const ctx = ssrContext._meta
|
|
201
249
|
|
|
@@ -204,7 +252,7 @@ function injectServerMeta(ssrContext) {
|
|
|
204
252
|
if (htmlAttr.length !== 0) {
|
|
205
253
|
ctx.htmlAttrs +=
|
|
206
254
|
(ctx.htmlAttrs.length !== 0 ? ' ' : '') +
|
|
207
|
-
htmlAttr.map(getAttr(data.htmlAttr)).join(' ')
|
|
255
|
+
htmlAttr.map(getAttr(data.htmlAttr)).filter(Boolean).join(' ')
|
|
208
256
|
}
|
|
209
257
|
|
|
210
258
|
ctx.headTags += getHead(data)
|
|
@@ -214,17 +262,17 @@ function injectServerMeta(ssrContext) {
|
|
|
214
262
|
if (bodyAttr.length !== 0) {
|
|
215
263
|
ctx.bodyAttrs +=
|
|
216
264
|
(ctx.bodyAttrs.length !== 0 ? ' ' : '') +
|
|
217
|
-
bodyAttr.map(getAttr(data.bodyAttr)).join(' ')
|
|
265
|
+
bodyAttr.map(getAttr(data.bodyAttr)).filter(Boolean).join(' ')
|
|
218
266
|
}
|
|
219
267
|
|
|
220
268
|
ctx.bodyTags +=
|
|
221
269
|
Object.keys(data.noscript)
|
|
222
270
|
.map(
|
|
223
271
|
name =>
|
|
224
|
-
`<noscript data-qmeta="${name}">${data.noscript[name]}</noscript>`
|
|
272
|
+
`<noscript data-qmeta="${encodeHtml(name)}">${protectRawText(data.noscript[name], 'noscript')}</noscript>`
|
|
225
273
|
)
|
|
226
274
|
.join('') +
|
|
227
|
-
`<script${nonce} id="qmeta-init">window.__Q_META__=${delete data.noscript &&
|
|
275
|
+
`<script${nonce} id="qmeta-init">window.__Q_META__=${delete data.noscript && encodeJson(data)}</script>`
|
|
228
276
|
}
|
|
229
277
|
|
|
230
278
|
function updateClientMeta() {
|
|
@@ -82,6 +82,35 @@ describe('[extend API]', () => {
|
|
|
82
82
|
|
|
83
83
|
expect(result.a()).toBe(6)
|
|
84
84
|
})
|
|
85
|
+
|
|
86
|
+
test.each([true, false])('does not merge __proto__ (deep: %s)', deep => {
|
|
87
|
+
const target = {}
|
|
88
|
+
const source = JSON.parse(
|
|
89
|
+
'{"__proto__":{"polluted":"prototype pollution"}}'
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
extend(deep, target, source)
|
|
93
|
+
|
|
94
|
+
expect(Object.getPrototypeOf(target)).toBe(Object.prototype)
|
|
95
|
+
expect(Object.hasOwn(target, '__proto__')).toBe(false)
|
|
96
|
+
expect(Object.prototype.polluted).toBeUndefined()
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('preserves constructor and prototype data properties', () => {
|
|
100
|
+
const result = extend(
|
|
101
|
+
true,
|
|
102
|
+
{},
|
|
103
|
+
{
|
|
104
|
+
constructor: { name: 'custom' },
|
|
105
|
+
prototype: { type: 'custom' }
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
expect(Object.hasOwn(result, 'constructor')).toBe(true)
|
|
110
|
+
expect(Object.hasOwn(result, 'prototype')).toBe(true)
|
|
111
|
+
expect(result.constructor).toStrictEqual({ name: 'custom' })
|
|
112
|
+
expect(result.prototype).toStrictEqual({ type: 'custom' })
|
|
113
|
+
})
|
|
85
114
|
})
|
|
86
115
|
})
|
|
87
116
|
})
|
|
@@ -12,7 +12,7 @@ let registered = 0,
|
|
|
12
12
|
vpPendingUpdate = false,
|
|
13
13
|
bodyLeft,
|
|
14
14
|
bodyTop,
|
|
15
|
-
|
|
15
|
+
routePath,
|
|
16
16
|
closeTimer = null
|
|
17
17
|
|
|
18
18
|
function onAppleScroll(e) {
|
|
@@ -59,7 +59,7 @@ function apply(action) {
|
|
|
59
59
|
bodyLeft = body.style.left
|
|
60
60
|
bodyTop = body.style.top
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
routePath = window.location.pathname
|
|
63
63
|
|
|
64
64
|
body.style.left = `-${scrollPositionX}px`
|
|
65
65
|
body.style.top = `-${scrollPositionY}px`
|
|
@@ -137,8 +137,8 @@ function apply(action) {
|
|
|
137
137
|
body.style.left = bodyLeft
|
|
138
138
|
body.style.top = bodyTop
|
|
139
139
|
|
|
140
|
-
// scroll back only if route has not changed
|
|
141
|
-
if (window.location.
|
|
140
|
+
// scroll back only if route path has not changed
|
|
141
|
+
if (window.location.pathname === routePath) {
|
|
142
142
|
window.scrollTo(scrollPositionX, scrollPositionY)
|
|
143
143
|
}
|
|
144
144
|
|