apostrophe 3.53.0 → 3.55.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/CHANGELOG.md +58 -1
- package/defaults.js +1 -0
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextModeAndSettings.vue +5 -2
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextTitle.vue +28 -19
- package/modules/@apostrophecms/any-doc-type/index.js +2 -2
- package/modules/@apostrophecms/any-page-type/index.js +2 -2
- package/modules/@apostrophecms/doc/index.js +55 -29
- package/modules/@apostrophecms/doc-type/index.js +11 -6
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +4 -440
- package/modules/@apostrophecms/doc-type/ui/apos/logic/AposDocContextMenu.js +445 -0
- package/modules/@apostrophecms/i18n/i18n/de.json +113 -105
- package/modules/@apostrophecms/i18n/i18n/es.json +10 -0
- package/modules/@apostrophecms/i18n/i18n/fr.json +8 -0
- package/modules/@apostrophecms/i18n/i18n/pt-BR.json +10 -0
- package/modules/@apostrophecms/i18n/i18n/sk.json +8 -0
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +1 -0
- package/modules/@apostrophecms/log/index.js +429 -0
- package/modules/@apostrophecms/login/index.js +47 -4
- package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +14 -1
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +1 -1
- package/modules/@apostrophecms/module/index.js +32 -6
- package/modules/@apostrophecms/module/lib/log.js +68 -0
- package/modules/@apostrophecms/page/index.js +71 -19
- package/modules/@apostrophecms/page/lib/legacy-migrations.js +0 -57
- package/modules/@apostrophecms/page/ui/apos/components/AposPagesManager.vue +8 -285
- package/modules/@apostrophecms/page/ui/apos/logic/AposPagesManager.js +291 -0
- package/modules/@apostrophecms/page-type/index.js +39 -26
- package/modules/@apostrophecms/piece-type/index.js +19 -11
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +1 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +2 -357
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputArea.vue +2 -86
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +2 -254
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputAttachment.vue +2 -77
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputBoolean.vue +2 -44
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputCheckboxes.vue +2 -64
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputColor.vue +2 -94
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputDateAndTime.vue +3 -47
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputObject.vue +2 -82
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputPassword.vue +2 -37
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRadio.vue +2 -26
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRange.vue +2 -57
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +2 -259
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSelect.vue +2 -38
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSlug.vue +2 -275
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +2 -167
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +2 -115
- package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +3 -279
- package/modules/@apostrophecms/schema/ui/apos/components/AposSearchList.vue +2 -83
- package/modules/@apostrophecms/schema/ui/apos/lib/detectChange.js +10 -1
- package/modules/@apostrophecms/schema/ui/apos/logic/AposArrayEditor.js +361 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputArea.js +89 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputArray.js +257 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputAttachment.js +81 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputBoolean.js +48 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputCheckboxes.js +68 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputColor.js +98 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputDateAndTime.js +49 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputObject.js +86 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputPassword.js +41 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRadio.js +29 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRange.js +60 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRelationship.js +262 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputSelect.js +41 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputSlug.js +278 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputString.js +170 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputWrapper.js +118 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposSchema.js +281 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposSearchList.js +85 -0
- package/modules/@apostrophecms/template/index.js +1 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposTreeHeader.vue +2 -2
- package/modules/@apostrophecms/util/index.js +83 -13
- package/modules/@apostrophecms/util/lib/logger.js +19 -17
- package/package.json +1 -1
- package/test/docs.js +35 -2
- package/test/log.js +1765 -0
- package/test/pages.js +57 -0
- package/test-lib/util.js +1 -1
|
@@ -35,283 +35,10 @@
|
|
|
35
35
|
</template>
|
|
36
36
|
|
|
37
37
|
<script>
|
|
38
|
-
|
|
39
|
-
// already have `type: 'slug'` fields, so this is needed to avoid distracting
|
|
40
|
-
// errors.
|
|
41
|
-
import AposInputMixin from 'Modules/@apostrophecms/schema/mixins/AposInputMixin';
|
|
42
|
-
import sluggo from 'sluggo';
|
|
43
|
-
import debounce from 'debounce-async';
|
|
44
|
-
import { klona } from 'klona';
|
|
45
|
-
|
|
38
|
+
import AposInputSlugLogic from '../logic/AposInputSlug';
|
|
46
39
|
export default {
|
|
47
40
|
name: 'AposInputSlug',
|
|
48
|
-
mixins: [
|
|
49
|
-
emits: [ 'return' ],
|
|
50
|
-
data() {
|
|
51
|
-
return {
|
|
52
|
-
conflict: false,
|
|
53
|
-
isArchived: null,
|
|
54
|
-
originalSlugPartsLength: null
|
|
55
|
-
};
|
|
56
|
-
},
|
|
57
|
-
computed: {
|
|
58
|
-
tabindex () {
|
|
59
|
-
return this.field.disableFocus ? '-1' : '0';
|
|
60
|
-
},
|
|
61
|
-
type () {
|
|
62
|
-
if (this.field.type) {
|
|
63
|
-
return this.field.type;
|
|
64
|
-
} else {
|
|
65
|
-
return 'text';
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
classes () {
|
|
69
|
-
return [ 'apos-input', 'apos-input--text', 'apos-input--slug' ];
|
|
70
|
-
},
|
|
71
|
-
wrapperClasses () {
|
|
72
|
-
return [ 'apos-input-wrapper' ].concat(this.localePrefix ? [ 'apos-input-wrapper--with-prefix' ] : []);
|
|
73
|
-
},
|
|
74
|
-
icon () {
|
|
75
|
-
if (this.error) {
|
|
76
|
-
return 'circle-medium-icon';
|
|
77
|
-
} else if (this.field.icon) {
|
|
78
|
-
return this.field.icon;
|
|
79
|
-
} else {
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
prefix () {
|
|
84
|
-
return this.field.prefix || '';
|
|
85
|
-
},
|
|
86
|
-
localePrefix() {
|
|
87
|
-
return this.field.page && apos.i18n.locales[apos.i18n.locale].prefix;
|
|
88
|
-
}
|
|
89
|
-
},
|
|
90
|
-
watch: {
|
|
91
|
-
followingValues: {
|
|
92
|
-
// We are usually interested in followingValue.title, but a
|
|
93
|
-
// secondary slug field could be configured to watch
|
|
94
|
-
// one or more other fields
|
|
95
|
-
deep: true,
|
|
96
|
-
handler(newValue, oldValue) {
|
|
97
|
-
const newClone = klona(newValue);
|
|
98
|
-
const oldClone = klona(oldValue);
|
|
99
|
-
|
|
100
|
-
// Track whether the slug is archived for prefixing.
|
|
101
|
-
this.isArchived = newValue.archived;
|
|
102
|
-
// We only want the string properties to build the slug itself.
|
|
103
|
-
delete newClone.archived;
|
|
104
|
-
delete oldClone.archived;
|
|
105
|
-
|
|
106
|
-
oldValue = Object.values(oldClone).join(' ');
|
|
107
|
-
newValue = Object.values(newClone).join(' ');
|
|
108
|
-
|
|
109
|
-
if (this.compatible(oldValue, this.next) && !newValue.archived) {
|
|
110
|
-
// If this is a page slug, we only replace the last section of the slug.
|
|
111
|
-
if (this.field.page) {
|
|
112
|
-
let parts = this.next.split('/');
|
|
113
|
-
parts = parts.filter(part => part.length > 0);
|
|
114
|
-
if ((!this.originalSlugPartsLength && parts.length) || (this.originalSlugPartsLength && parts.length === (this.originalSlugPartsLength - 1))) {
|
|
115
|
-
// Remove last path component so we can replace it
|
|
116
|
-
parts.pop();
|
|
117
|
-
}
|
|
118
|
-
parts.push(this.slugify(newValue, { componentOnly: true }));
|
|
119
|
-
if (parts[0].length) {
|
|
120
|
-
// TODO: handle page archives.
|
|
121
|
-
this.next = `/${parts.join('/')}`;
|
|
122
|
-
}
|
|
123
|
-
} else {
|
|
124
|
-
this.next = this.slugify(newValue);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
},
|
|
130
|
-
async mounted() {
|
|
131
|
-
this.debouncedCheckConflict = debounce(() => this.checkConflict(), 250);
|
|
132
|
-
if (this.next.length) {
|
|
133
|
-
await this.debouncedCheckConflict();
|
|
134
|
-
}
|
|
135
|
-
this.originalSlugPartsLength = this.next.split('/').length;
|
|
136
|
-
},
|
|
137
|
-
methods: {
|
|
138
|
-
async watchNext() {
|
|
139
|
-
this.next = this.slugify(this.next);
|
|
140
|
-
this.validateAndEmit();
|
|
141
|
-
try {
|
|
142
|
-
await this.debouncedCheckConflict();
|
|
143
|
-
} catch (e) {
|
|
144
|
-
if (e === 'canceled') {
|
|
145
|
-
// That's fine
|
|
146
|
-
} else {
|
|
147
|
-
throw e;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
},
|
|
151
|
-
validate(value) {
|
|
152
|
-
if (this.conflict) {
|
|
153
|
-
return {
|
|
154
|
-
name: 'conflict',
|
|
155
|
-
message: 'apostrophe:slugInUse'
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
if (this.field.required) {
|
|
159
|
-
if (!value.length) {
|
|
160
|
-
return 'required';
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
if (this.field.min) {
|
|
164
|
-
if (value.length && (value.length < this.field.min)) {
|
|
165
|
-
return 'min';
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
if (this.field.max) {
|
|
169
|
-
if (value.length && (value.length > this.field.max)) {
|
|
170
|
-
return 'max';
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
return false;
|
|
174
|
-
},
|
|
175
|
-
compatible(title, slug) {
|
|
176
|
-
if ((typeof title) !== 'string') {
|
|
177
|
-
title = '';
|
|
178
|
-
}
|
|
179
|
-
if (this.field.page) {
|
|
180
|
-
const matches = slug.match(/[^/]+$/);
|
|
181
|
-
slug = (matches && matches[0]) || '';
|
|
182
|
-
}
|
|
183
|
-
return ((title === '') && (slug === `${this.prefix}`)) ||
|
|
184
|
-
this.slugify(title) === this.slugify(slug);
|
|
185
|
-
},
|
|
186
|
-
// if componentOnly is true, we are slugifying just one component of
|
|
187
|
-
// a slug as part of following the title field, and so we do *not*
|
|
188
|
-
// want to allow slashes (when editing a page) or set a prefix.
|
|
189
|
-
slugify(s, { componentOnly = false } = {}) {
|
|
190
|
-
const options = {
|
|
191
|
-
def: ''
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
if (this.field.page && !componentOnly) {
|
|
195
|
-
options.allow = '/';
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
let preserveDash = false;
|
|
199
|
-
// When you are typing a slug it feels wrong for hyphens you typed
|
|
200
|
-
// to disappear as you go, so if the last character is not valid in a slug,
|
|
201
|
-
// restore it after we call sluggo for the full string
|
|
202
|
-
if (this.focus && s.length && (sluggo(s.charAt(s.length - 1), options) === '')) {
|
|
203
|
-
preserveDash = true;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
s = sluggo(s, options);
|
|
207
|
-
if (preserveDash) {
|
|
208
|
-
s += '-';
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (this.field.page && !componentOnly) {
|
|
212
|
-
if (!this.followingValues?.title) {
|
|
213
|
-
const nextParts = this.next.split('/');
|
|
214
|
-
if (s === nextParts[nextParts.length - 1]) {
|
|
215
|
-
s = '';
|
|
216
|
-
if (this.originalSlugPartsLength === nextParts.length) {
|
|
217
|
-
nextParts.pop();
|
|
218
|
-
}
|
|
219
|
-
this.next = nextParts.join('/');
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
if (!s.charAt(0) !== '/') {
|
|
223
|
-
s = `/${s}`;
|
|
224
|
-
}
|
|
225
|
-
s = s.replace(/\/+/g, '/');
|
|
226
|
-
if (s !== '/') {
|
|
227
|
-
s = s.replace(/\/$/, '');
|
|
228
|
-
}
|
|
229
|
-
if (!this.followingValues?.title && s.length) {
|
|
230
|
-
s += '/';
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (!componentOnly) {
|
|
235
|
-
s = this.setPrefix(s);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return s;
|
|
239
|
-
},
|
|
240
|
-
setPrefix (slug) {
|
|
241
|
-
// Get a fresh clone of the slug.
|
|
242
|
-
let updated = slug;
|
|
243
|
-
const archivedRegexp = new RegExp(`^deduplicate-[a-z0-9]+-${this.prefix}`);
|
|
244
|
-
|
|
245
|
-
// Prefix if the slug doesn't start with the prefix OR if its archived
|
|
246
|
-
// and it doesn't start with the dedupe+prefix pattern.
|
|
247
|
-
if (
|
|
248
|
-
!updated.startsWith(this.prefix) ||
|
|
249
|
-
(this.isArchived && !updated.match(archivedRegexp))
|
|
250
|
-
) {
|
|
251
|
-
let archivePrefix = '';
|
|
252
|
-
// If archived, remove the dedupe pattern to add again later.
|
|
253
|
-
if (this.isArchived) {
|
|
254
|
-
archivePrefix = updated.match(/^deduplicate-[a-z0-9]+-/);
|
|
255
|
-
updated = updated.replace(archivePrefix, '');
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (this.prefix.startsWith(updated)) {
|
|
259
|
-
// If they delete the `-`, and the prefix is `recipe-`,
|
|
260
|
-
// we want to restore `recipe-`, not set it to `recipe-recipe`
|
|
261
|
-
updated = this.prefix;
|
|
262
|
-
} else {
|
|
263
|
-
// Make sure we're not double prefixing archived slugs.
|
|
264
|
-
updated = updated.startsWith(this.prefix) ? updated : this.prefix + updated;
|
|
265
|
-
}
|
|
266
|
-
// Reapply the dedupe pattern if archived. If being restored from the
|
|
267
|
-
// doc editor modal it will momentarily be tracked as archived but
|
|
268
|
-
// without not have the archive prefix, so check that too.
|
|
269
|
-
updated = this.isArchived && archivePrefix ? `${archivePrefix}${updated}` : updated;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return updated;
|
|
273
|
-
},
|
|
274
|
-
async checkConflict() {
|
|
275
|
-
let slug;
|
|
276
|
-
try {
|
|
277
|
-
slug = this.next;
|
|
278
|
-
if (slug.length) {
|
|
279
|
-
await apos.http.post(`${apos.doc.action}/slug-taken`, {
|
|
280
|
-
body: {
|
|
281
|
-
slug,
|
|
282
|
-
_id: this.docId
|
|
283
|
-
},
|
|
284
|
-
draft: true
|
|
285
|
-
});
|
|
286
|
-
// Still relevant?
|
|
287
|
-
if (slug === this.next) {
|
|
288
|
-
this.conflict = false;
|
|
289
|
-
this.validateAndEmit();
|
|
290
|
-
} else {
|
|
291
|
-
// Can ignore it, another request
|
|
292
|
-
// probably already in-flight
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
} catch (e) {
|
|
296
|
-
// 409: Conflict (slug in use)
|
|
297
|
-
if (e.status === 409) {
|
|
298
|
-
// Still relevant?
|
|
299
|
-
if (slug === this.next) {
|
|
300
|
-
this.conflict = true;
|
|
301
|
-
this.validateAndEmit();
|
|
302
|
-
} else {
|
|
303
|
-
// Can ignore it, another request
|
|
304
|
-
// probably already in-flight
|
|
305
|
-
}
|
|
306
|
-
} else {
|
|
307
|
-
throw e;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
},
|
|
311
|
-
passFocus() {
|
|
312
|
-
this.$refs.input.focus();
|
|
313
|
-
}
|
|
314
|
-
}
|
|
41
|
+
mixins: [ AposInputSlugLogic ]
|
|
315
42
|
};
|
|
316
43
|
</script>
|
|
317
44
|
|
|
@@ -37,175 +37,10 @@
|
|
|
37
37
|
</template>
|
|
38
38
|
|
|
39
39
|
<script>
|
|
40
|
-
import
|
|
41
|
-
|
|
40
|
+
import AposInputStringLogic from '../logic/AposInputString';
|
|
42
41
|
export default {
|
|
43
42
|
name: 'AposInputString',
|
|
44
|
-
mixins: [
|
|
45
|
-
emits: [ 'return' ],
|
|
46
|
-
data () {
|
|
47
|
-
return {
|
|
48
|
-
step: undefined,
|
|
49
|
-
wasPopulated: false
|
|
50
|
-
};
|
|
51
|
-
},
|
|
52
|
-
computed: {
|
|
53
|
-
tabindex () {
|
|
54
|
-
return this.field.disableFocus ? '-1' : '0';
|
|
55
|
-
},
|
|
56
|
-
type () {
|
|
57
|
-
if (this.field.type) {
|
|
58
|
-
if (this.field.type === 'float' || this.field.type === 'integer') {
|
|
59
|
-
return 'number';
|
|
60
|
-
}
|
|
61
|
-
if (this.field.type === 'string' || this.field.type === 'slug') {
|
|
62
|
-
return 'text';
|
|
63
|
-
}
|
|
64
|
-
return this.field.type;
|
|
65
|
-
} else {
|
|
66
|
-
return 'text';
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
classes () {
|
|
70
|
-
return [ 'apos-input', `apos-input--${this.type}`, this.value.duplicate && 'apos-input--error' ];
|
|
71
|
-
},
|
|
72
|
-
icon () {
|
|
73
|
-
if (this.error) {
|
|
74
|
-
return 'circle-medium-icon';
|
|
75
|
-
} else if (this.field.icon) {
|
|
76
|
-
return this.field.icon;
|
|
77
|
-
} else {
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
},
|
|
82
|
-
watch: {
|
|
83
|
-
followingValues: {
|
|
84
|
-
// We may be following multiple fields, like firstName and lastName,
|
|
85
|
-
// or none at all, depending
|
|
86
|
-
deep: true,
|
|
87
|
-
handler(newValue, oldValue) {
|
|
88
|
-
// Follow the value of the other field(s), but only if our
|
|
89
|
-
// previous value matched the previous value of the other field(s)
|
|
90
|
-
oldValue = Object.values(oldValue).join(' ').trim();
|
|
91
|
-
newValue = Object.values(newValue).join(' ').trim();
|
|
92
|
-
if ((!this.wasPopulated && ((this.next == null) || (!this.next.length))) || (this.next === oldValue)) {
|
|
93
|
-
this.next = newValue;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
|
-
next() {
|
|
98
|
-
if (this.next && this.next.length) {
|
|
99
|
-
this.wasPopulated = true;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
mounted() {
|
|
104
|
-
this.defineStep();
|
|
105
|
-
this.wasPopulated = this.next && this.next.length;
|
|
106
|
-
},
|
|
107
|
-
methods: {
|
|
108
|
-
enterEmit() {
|
|
109
|
-
if (this.field.enterSubmittable) {
|
|
110
|
-
// Include the validated results in cases where an Enter keydown should
|
|
111
|
-
// act as submitting a form.
|
|
112
|
-
this.$emit('return', {
|
|
113
|
-
data: this.next,
|
|
114
|
-
error: this.validate(this.next)
|
|
115
|
-
});
|
|
116
|
-
} else {
|
|
117
|
-
this.$emit('return');
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
validate(value) {
|
|
121
|
-
if (value == null) {
|
|
122
|
-
value = '';
|
|
123
|
-
}
|
|
124
|
-
if (typeof value === 'string' && !value.length) {
|
|
125
|
-
// Also correct for float and integer because Vue coerces
|
|
126
|
-
// number fields to either a number or the empty string
|
|
127
|
-
return this.field.required ? 'required' : false;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const minMaxFields = [
|
|
131
|
-
'integer',
|
|
132
|
-
'float',
|
|
133
|
-
'string',
|
|
134
|
-
'date',
|
|
135
|
-
'password'
|
|
136
|
-
];
|
|
137
|
-
|
|
138
|
-
if (typeof value === 'string' && this.field.pattern) {
|
|
139
|
-
const regex = new RegExp(this.field.pattern);
|
|
140
|
-
if (!regex.test(value)) {
|
|
141
|
-
return 'invalid';
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (this.field.min && minMaxFields.includes(this.field.type)) {
|
|
146
|
-
if ((value != null) && value.length && (this.minMaxComparable(value) < this.field.min)) {
|
|
147
|
-
return 'min';
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
if (this.field.max && minMaxFields.includes(this.field.type)) {
|
|
151
|
-
if ((value != null) && value.length && (this.minMaxComparable(value) > this.field.max)) {
|
|
152
|
-
return 'max';
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
if (this.field.type === 'email' && value) {
|
|
156
|
-
// regex source: https://emailregex.com/
|
|
157
|
-
const matches = value.match(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/);
|
|
158
|
-
if (!matches) {
|
|
159
|
-
return 'invalid';
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return false;
|
|
163
|
-
},
|
|
164
|
-
defineStep() {
|
|
165
|
-
if (this.type === 'number') {
|
|
166
|
-
this.step = this.field.type === 'float' ? 'any' : 1;
|
|
167
|
-
}
|
|
168
|
-
},
|
|
169
|
-
convert(s) {
|
|
170
|
-
if (this.field.type === 'integer') {
|
|
171
|
-
if ((s == null) || (s === '')) {
|
|
172
|
-
return s;
|
|
173
|
-
} else {
|
|
174
|
-
return parseInt(s);
|
|
175
|
-
}
|
|
176
|
-
} else if (this.field.type === 'float') {
|
|
177
|
-
if ((s == null) || (s === '')) {
|
|
178
|
-
return s;
|
|
179
|
-
} else {
|
|
180
|
-
// The native parse float converts 3.0 to 3 and makes
|
|
181
|
-
// next to become integer. In theory we don't need parseFloat
|
|
182
|
-
// as the value is natively guarded by the browser 'number' type.
|
|
183
|
-
// However we need a float value sent to the backend
|
|
184
|
-
// and we force that when focus is lost.
|
|
185
|
-
if (this.focus && `${s}`.match(/\.[0]*$/)) {
|
|
186
|
-
return s;
|
|
187
|
-
}
|
|
188
|
-
return parseFloat(s);
|
|
189
|
-
}
|
|
190
|
-
} else {
|
|
191
|
-
if (s == null) {
|
|
192
|
-
return '';
|
|
193
|
-
} else {
|
|
194
|
-
return s.toString();
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
minMaxComparable(s) {
|
|
199
|
-
const converted = this.convert(s);
|
|
200
|
-
if ([ 'integer', 'float', 'date', 'range', 'time' ].includes(this.field.type)) {
|
|
201
|
-
// Compare the actual values for these types
|
|
202
|
-
return converted;
|
|
203
|
-
} else {
|
|
204
|
-
// Compare the length for other types, like string or password or url
|
|
205
|
-
return converted.length;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
43
|
+
mixins: [ AposInputStringLogic ]
|
|
209
44
|
};
|
|
210
45
|
</script>
|
|
211
46
|
|
|
@@ -57,123 +57,10 @@
|
|
|
57
57
|
</template>
|
|
58
58
|
|
|
59
59
|
<script>
|
|
60
|
-
|
|
61
|
-
// friends, which override the `body` slot
|
|
60
|
+
import AposInputWrapperLogic from '../logic/AposInputWrapper';
|
|
62
61
|
export default {
|
|
63
62
|
name: 'AposInputWrapper',
|
|
64
|
-
|
|
65
|
-
originalDoc: {
|
|
66
|
-
default: () => ({
|
|
67
|
-
ref: null
|
|
68
|
-
})
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
props: {
|
|
72
|
-
field: {
|
|
73
|
-
type: Object,
|
|
74
|
-
required: true
|
|
75
|
-
},
|
|
76
|
-
error: {
|
|
77
|
-
type: [ String, Boolean, Object ],
|
|
78
|
-
default: null
|
|
79
|
-
},
|
|
80
|
-
uid: {
|
|
81
|
-
type: Number,
|
|
82
|
-
required: true
|
|
83
|
-
},
|
|
84
|
-
modifiers: {
|
|
85
|
-
type: Array,
|
|
86
|
-
default() {
|
|
87
|
-
return [];
|
|
88
|
-
}
|
|
89
|
-
},
|
|
90
|
-
items: {
|
|
91
|
-
type: Array,
|
|
92
|
-
default() {
|
|
93
|
-
return [];
|
|
94
|
-
}
|
|
95
|
-
},
|
|
96
|
-
displayOptions: {
|
|
97
|
-
type: Object,
|
|
98
|
-
default() {
|
|
99
|
-
return {};
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
data () {
|
|
104
|
-
return {
|
|
105
|
-
wrapEl: 'div',
|
|
106
|
-
labelEl: 'label'
|
|
107
|
-
};
|
|
108
|
-
},
|
|
109
|
-
computed: {
|
|
110
|
-
label () {
|
|
111
|
-
const { label, publishedLabel } = this.field;
|
|
112
|
-
|
|
113
|
-
if (
|
|
114
|
-
this.originalDoc.ref &&
|
|
115
|
-
this.originalDoc.ref.lastPublishedAt &&
|
|
116
|
-
publishedLabel
|
|
117
|
-
) {
|
|
118
|
-
return publishedLabel;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return label;
|
|
122
|
-
},
|
|
123
|
-
classList: function () {
|
|
124
|
-
const classes = [
|
|
125
|
-
'apos-field',
|
|
126
|
-
`apos-field--${this.field.type}`,
|
|
127
|
-
`apos-field--${this.field.name}`
|
|
128
|
-
];
|
|
129
|
-
if (this.field.classes) {
|
|
130
|
-
classes.push(this.field.classes);
|
|
131
|
-
}
|
|
132
|
-
if (this.errorClasses) {
|
|
133
|
-
classes.push(this.errorClasses);
|
|
134
|
-
}
|
|
135
|
-
if (this.modifiers) {
|
|
136
|
-
this.modifiers.forEach((m) => {
|
|
137
|
-
classes.push(`apos-field--${m}`);
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
return classes;
|
|
141
|
-
},
|
|
142
|
-
errorClasses: function () {
|
|
143
|
-
if (!this.error) {
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
let error = 'unknown';
|
|
148
|
-
|
|
149
|
-
if (typeof this.error === 'string') {
|
|
150
|
-
error = this.error;
|
|
151
|
-
} else if (this.error.name) {
|
|
152
|
-
error = this.error.name;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return `apos-field--error apos-field--error-${error}`;
|
|
156
|
-
},
|
|
157
|
-
errorMessage () {
|
|
158
|
-
if (this.error) {
|
|
159
|
-
if (typeof this.error === 'string') {
|
|
160
|
-
return this.error;
|
|
161
|
-
} else if (this.error.message) {
|
|
162
|
-
return this.error.message;
|
|
163
|
-
} else {
|
|
164
|
-
return 'Error';
|
|
165
|
-
}
|
|
166
|
-
} else {
|
|
167
|
-
return false;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
mounted: function () {
|
|
172
|
-
if (this.field.type === 'radio' || this.field.type === 'checkbox') {
|
|
173
|
-
this.wrapEl = 'fieldset';
|
|
174
|
-
this.labelEl = 'legend';
|
|
175
|
-
}
|
|
176
|
-
}
|
|
63
|
+
mixins: [ AposInputWrapperLogic ]
|
|
177
64
|
};
|
|
178
65
|
</script>
|
|
179
66
|
|