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
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import AposInputMixin from 'Modules/@apostrophecms/schema/mixins/AposInputMixin';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
name: 'AposInputString',
|
|
5
|
+
mixins: [ AposInputMixin ],
|
|
6
|
+
emits: [ 'return' ],
|
|
7
|
+
data () {
|
|
8
|
+
return {
|
|
9
|
+
step: undefined,
|
|
10
|
+
wasPopulated: false
|
|
11
|
+
};
|
|
12
|
+
},
|
|
13
|
+
computed: {
|
|
14
|
+
tabindex () {
|
|
15
|
+
return this.field.disableFocus ? '-1' : '0';
|
|
16
|
+
},
|
|
17
|
+
type () {
|
|
18
|
+
if (this.field.type) {
|
|
19
|
+
if (this.field.type === 'float' || this.field.type === 'integer') {
|
|
20
|
+
return 'number';
|
|
21
|
+
}
|
|
22
|
+
if (this.field.type === 'string' || this.field.type === 'slug') {
|
|
23
|
+
return 'text';
|
|
24
|
+
}
|
|
25
|
+
return this.field.type;
|
|
26
|
+
} else {
|
|
27
|
+
return 'text';
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
classes () {
|
|
31
|
+
return [ 'apos-input', `apos-input--${this.type}`, this.value.duplicate && 'apos-input--error' ];
|
|
32
|
+
},
|
|
33
|
+
icon () {
|
|
34
|
+
if (this.error) {
|
|
35
|
+
return 'circle-medium-icon';
|
|
36
|
+
} else if (this.field.icon) {
|
|
37
|
+
return this.field.icon;
|
|
38
|
+
} else {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
watch: {
|
|
44
|
+
followingValues: {
|
|
45
|
+
// We may be following multiple fields, like firstName and lastName,
|
|
46
|
+
// or none at all, depending
|
|
47
|
+
deep: true,
|
|
48
|
+
handler(newValue, oldValue) {
|
|
49
|
+
// Follow the value of the other field(s), but only if our
|
|
50
|
+
// previous value matched the previous value of the other field(s)
|
|
51
|
+
oldValue = Object.values(oldValue).join(' ').trim();
|
|
52
|
+
newValue = Object.values(newValue).join(' ').trim();
|
|
53
|
+
if ((!this.wasPopulated && ((this.next == null) || (!this.next.length))) || (this.next === oldValue)) {
|
|
54
|
+
this.next = newValue;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
next() {
|
|
59
|
+
if (this.next && this.next.length) {
|
|
60
|
+
this.wasPopulated = true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
mounted() {
|
|
65
|
+
this.defineStep();
|
|
66
|
+
this.wasPopulated = this.next && this.next.length;
|
|
67
|
+
},
|
|
68
|
+
methods: {
|
|
69
|
+
enterEmit() {
|
|
70
|
+
if (this.field.enterSubmittable) {
|
|
71
|
+
// Include the validated results in cases where an Enter keydown should
|
|
72
|
+
// act as submitting a form.
|
|
73
|
+
this.$emit('return', {
|
|
74
|
+
data: this.next,
|
|
75
|
+
error: this.validate(this.next)
|
|
76
|
+
});
|
|
77
|
+
} else {
|
|
78
|
+
this.$emit('return');
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
validate(value) {
|
|
82
|
+
if (value == null) {
|
|
83
|
+
value = '';
|
|
84
|
+
}
|
|
85
|
+
if (typeof value === 'string' && !value.length) {
|
|
86
|
+
// Also correct for float and integer because Vue coerces
|
|
87
|
+
// number fields to either a number or the empty string
|
|
88
|
+
return this.field.required ? 'required' : false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const minMaxFields = [
|
|
92
|
+
'integer',
|
|
93
|
+
'float',
|
|
94
|
+
'string',
|
|
95
|
+
'date',
|
|
96
|
+
'password'
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
if (typeof value === 'string' && this.field.pattern) {
|
|
100
|
+
const regex = new RegExp(this.field.pattern);
|
|
101
|
+
if (!regex.test(value)) {
|
|
102
|
+
return 'invalid';
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (this.field.min && minMaxFields.includes(this.field.type)) {
|
|
107
|
+
if ((value != null) && value.length && (this.minMaxComparable(value) < this.field.min)) {
|
|
108
|
+
return 'min';
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (this.field.max && minMaxFields.includes(this.field.type)) {
|
|
112
|
+
if ((value != null) && value.length && (this.minMaxComparable(value) > this.field.max)) {
|
|
113
|
+
return 'max';
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (this.field.type === 'email' && value) {
|
|
117
|
+
// regex source: https://emailregex.com/
|
|
118
|
+
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,}))$/);
|
|
119
|
+
if (!matches) {
|
|
120
|
+
return 'invalid';
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
},
|
|
125
|
+
defineStep() {
|
|
126
|
+
if (this.type === 'number') {
|
|
127
|
+
this.step = this.field.type === 'float' ? 'any' : 1;
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
convert(s) {
|
|
131
|
+
if (this.field.type === 'integer') {
|
|
132
|
+
if ((s == null) || (s === '')) {
|
|
133
|
+
return s;
|
|
134
|
+
} else {
|
|
135
|
+
return parseInt(s);
|
|
136
|
+
}
|
|
137
|
+
} else if (this.field.type === 'float') {
|
|
138
|
+
if ((s == null) || (s === '')) {
|
|
139
|
+
return s;
|
|
140
|
+
} else {
|
|
141
|
+
// The native parse float converts 3.0 to 3 and makes
|
|
142
|
+
// next to become integer. In theory we don't need parseFloat
|
|
143
|
+
// as the value is natively guarded by the browser 'number' type.
|
|
144
|
+
// However we need a float value sent to the backend
|
|
145
|
+
// and we force that when focus is lost.
|
|
146
|
+
if (this.focus && `${s}`.match(/\.[0]*$/)) {
|
|
147
|
+
return s;
|
|
148
|
+
}
|
|
149
|
+
return parseFloat(s);
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
if (s == null) {
|
|
153
|
+
return '';
|
|
154
|
+
} else {
|
|
155
|
+
return s.toString();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
minMaxComparable(s) {
|
|
160
|
+
const converted = this.convert(s);
|
|
161
|
+
if ([ 'integer', 'float', 'date', 'range', 'time' ].includes(this.field.type)) {
|
|
162
|
+
// Compare the actual values for these types
|
|
163
|
+
return converted;
|
|
164
|
+
} else {
|
|
165
|
+
// Compare the length for other types, like string or password or url
|
|
166
|
+
return converted.length;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// A component designed to be used as a scaffold for AposInputString and
|
|
2
|
+
// friends, which override the `body` slot
|
|
3
|
+
export default {
|
|
4
|
+
name: 'AposInputWrapper',
|
|
5
|
+
inject: {
|
|
6
|
+
originalDoc: {
|
|
7
|
+
default: () => ({
|
|
8
|
+
ref: null
|
|
9
|
+
})
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
props: {
|
|
13
|
+
field: {
|
|
14
|
+
type: Object,
|
|
15
|
+
required: true
|
|
16
|
+
},
|
|
17
|
+
error: {
|
|
18
|
+
type: [ String, Boolean, Object ],
|
|
19
|
+
default: null
|
|
20
|
+
},
|
|
21
|
+
uid: {
|
|
22
|
+
type: Number,
|
|
23
|
+
required: true
|
|
24
|
+
},
|
|
25
|
+
modifiers: {
|
|
26
|
+
type: Array,
|
|
27
|
+
default() {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
items: {
|
|
32
|
+
type: Array,
|
|
33
|
+
default() {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
displayOptions: {
|
|
38
|
+
type: Object,
|
|
39
|
+
default() {
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
data () {
|
|
45
|
+
return {
|
|
46
|
+
wrapEl: 'div',
|
|
47
|
+
labelEl: 'label'
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
computed: {
|
|
51
|
+
label () {
|
|
52
|
+
const { label, publishedLabel } = this.field;
|
|
53
|
+
|
|
54
|
+
if (
|
|
55
|
+
this.originalDoc.ref &&
|
|
56
|
+
this.originalDoc.ref.lastPublishedAt &&
|
|
57
|
+
publishedLabel
|
|
58
|
+
) {
|
|
59
|
+
return publishedLabel;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return label;
|
|
63
|
+
},
|
|
64
|
+
classList: function () {
|
|
65
|
+
const classes = [
|
|
66
|
+
'apos-field',
|
|
67
|
+
`apos-field--${this.field.type}`,
|
|
68
|
+
`apos-field--${this.field.name}`
|
|
69
|
+
];
|
|
70
|
+
if (this.field.classes) {
|
|
71
|
+
classes.push(this.field.classes);
|
|
72
|
+
}
|
|
73
|
+
if (this.errorClasses) {
|
|
74
|
+
classes.push(this.errorClasses);
|
|
75
|
+
}
|
|
76
|
+
if (this.modifiers) {
|
|
77
|
+
this.modifiers.forEach((m) => {
|
|
78
|
+
classes.push(`apos-field--${m}`);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return classes;
|
|
82
|
+
},
|
|
83
|
+
errorClasses: function () {
|
|
84
|
+
if (!this.error) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let error = 'unknown';
|
|
89
|
+
|
|
90
|
+
if (typeof this.error === 'string') {
|
|
91
|
+
error = this.error;
|
|
92
|
+
} else if (this.error.name) {
|
|
93
|
+
error = this.error.name;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return `apos-field--error apos-field--error-${error}`;
|
|
97
|
+
},
|
|
98
|
+
errorMessage () {
|
|
99
|
+
if (this.error) {
|
|
100
|
+
if (typeof this.error === 'string') {
|
|
101
|
+
return this.error;
|
|
102
|
+
} else if (this.error.message) {
|
|
103
|
+
return this.error.message;
|
|
104
|
+
} else {
|
|
105
|
+
return 'Error';
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
mounted: function () {
|
|
113
|
+
if (this.field.type === 'radio' || this.field.type === 'checkbox') {
|
|
114
|
+
this.wrapEl = 'fieldset';
|
|
115
|
+
this.labelEl = 'legend';
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { detectFieldChange } from 'Modules/@apostrophecms/schema/lib/detectChange';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
name: 'AposSchema',
|
|
5
|
+
props: {
|
|
6
|
+
value: {
|
|
7
|
+
type: Object,
|
|
8
|
+
required: true
|
|
9
|
+
},
|
|
10
|
+
generation: {
|
|
11
|
+
type: Number,
|
|
12
|
+
required: false,
|
|
13
|
+
default() {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
schema: {
|
|
18
|
+
type: Array,
|
|
19
|
+
required: true
|
|
20
|
+
},
|
|
21
|
+
fieldStyle: {
|
|
22
|
+
type: String,
|
|
23
|
+
required: false,
|
|
24
|
+
default: ''
|
|
25
|
+
},
|
|
26
|
+
currentFields: {
|
|
27
|
+
type: Array,
|
|
28
|
+
default() {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
followingValues: {
|
|
33
|
+
type: Object,
|
|
34
|
+
default() {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
conditionalFields: {
|
|
39
|
+
type: Object,
|
|
40
|
+
default() {
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
modifiers: {
|
|
45
|
+
type: Array,
|
|
46
|
+
default() {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
triggerValidation: Boolean,
|
|
51
|
+
utilityRail: {
|
|
52
|
+
type: Boolean,
|
|
53
|
+
default() {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
docId: {
|
|
58
|
+
type: String,
|
|
59
|
+
default() {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
serverErrors: {
|
|
64
|
+
type: Object,
|
|
65
|
+
default() {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
displayOptions: {
|
|
70
|
+
type: Object,
|
|
71
|
+
default() {
|
|
72
|
+
return {};
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
changed: {
|
|
76
|
+
type: Array,
|
|
77
|
+
default() {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
emits: [
|
|
83
|
+
'input',
|
|
84
|
+
'reset',
|
|
85
|
+
'validate',
|
|
86
|
+
'update-doc-data'
|
|
87
|
+
],
|
|
88
|
+
data() {
|
|
89
|
+
return {
|
|
90
|
+
schemaReady: false,
|
|
91
|
+
next: {
|
|
92
|
+
hasErrors: false,
|
|
93
|
+
data: {},
|
|
94
|
+
fieldErrors: {}
|
|
95
|
+
},
|
|
96
|
+
fieldState: {},
|
|
97
|
+
fieldComponentMap: window.apos.schema.components.fields || {}
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
computed: {
|
|
101
|
+
fields() {
|
|
102
|
+
const fields = {};
|
|
103
|
+
this.schema.forEach(item => {
|
|
104
|
+
fields[item.name] = {};
|
|
105
|
+
fields[item.name].field = item;
|
|
106
|
+
fields[item.name].value = {
|
|
107
|
+
data: this.value[item.name]
|
|
108
|
+
};
|
|
109
|
+
fields[item.name].serverError = this.serverErrors && this.serverErrors[item.name];
|
|
110
|
+
fields[item.name].modifiers = [
|
|
111
|
+
...(this.modifiers || []),
|
|
112
|
+
...(item.modifiers || [])
|
|
113
|
+
];
|
|
114
|
+
});
|
|
115
|
+
return fields;
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
watch: {
|
|
119
|
+
fieldState: {
|
|
120
|
+
deep: true,
|
|
121
|
+
handler() {
|
|
122
|
+
this.updateNextAndEmit();
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
schema() {
|
|
126
|
+
this.populateDocData();
|
|
127
|
+
},
|
|
128
|
+
'value.data._id'(_id) {
|
|
129
|
+
// The doc might be swapped out completely in cases such as the media
|
|
130
|
+
// library editor. Repopulate the fields if that happens.
|
|
131
|
+
if (
|
|
132
|
+
// If the fieldState had been cleared and there's new populated data
|
|
133
|
+
(!this.fieldState._id && _id) ||
|
|
134
|
+
// or if there *is* active fieldState, but the new data is a new doc
|
|
135
|
+
(this.fieldState._id && _id !== this.fieldState._id.data)
|
|
136
|
+
) {
|
|
137
|
+
// repopulate the schema.
|
|
138
|
+
this.populateDocData();
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
generation() {
|
|
142
|
+
// repopulate the schema.
|
|
143
|
+
this.populateDocData();
|
|
144
|
+
},
|
|
145
|
+
conditionalFields(newVal, oldVal) {
|
|
146
|
+
for (const field in oldVal) {
|
|
147
|
+
if (!this.fieldState[field] || (newVal[field] === oldVal[field]) || !this.fieldState[field].ranValidation) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (
|
|
152
|
+
(newVal[field] === false) ||
|
|
153
|
+
(newVal[field] && this.fieldState[field].ranValidation)
|
|
154
|
+
) {
|
|
155
|
+
this.$emit('validate');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
created() {
|
|
161
|
+
this.populateDocData();
|
|
162
|
+
},
|
|
163
|
+
methods: {
|
|
164
|
+
getDisplayOptions(fieldName) {
|
|
165
|
+
let options = {};
|
|
166
|
+
if (this.displayOptions) {
|
|
167
|
+
options = { ...this.displayOptions };
|
|
168
|
+
}
|
|
169
|
+
if (this.changed && this.changed.includes(fieldName)) {
|
|
170
|
+
options.changed = true;
|
|
171
|
+
}
|
|
172
|
+
return options;
|
|
173
|
+
},
|
|
174
|
+
populateDocData() {
|
|
175
|
+
this.schemaReady = false;
|
|
176
|
+
const next = {
|
|
177
|
+
hasErrors: false,
|
|
178
|
+
data: {}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const fieldState = {};
|
|
182
|
+
|
|
183
|
+
// Though not in the schema, keep track of the _id field.
|
|
184
|
+
if (this.value.data._id) {
|
|
185
|
+
next.data._id = this.value.data._id;
|
|
186
|
+
fieldState._id = { data: this.value.data._id };
|
|
187
|
+
}
|
|
188
|
+
// Though not *always* in the schema, keep track of the archived status.
|
|
189
|
+
if (this.value.data.archived !== undefined) {
|
|
190
|
+
next.data.archived = this.value.data.archived;
|
|
191
|
+
fieldState.archived = { data: this.value.data.archived };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
this.schema.forEach(field => {
|
|
195
|
+
const value = this.value.data[field.name];
|
|
196
|
+
fieldState[field.name] = {
|
|
197
|
+
error: false,
|
|
198
|
+
data: (value === undefined) ? field.def : value
|
|
199
|
+
};
|
|
200
|
+
next.data[field.name] = fieldState[field.name].data;
|
|
201
|
+
});
|
|
202
|
+
this.next = next;
|
|
203
|
+
this.fieldState = fieldState;
|
|
204
|
+
|
|
205
|
+
// Wait until the next tick so the parent editor component is done
|
|
206
|
+
// updating. This is only really a concern in editors that can swap
|
|
207
|
+
// the active doc/object without unmounting AposSchema.
|
|
208
|
+
this.$nextTick(() => {
|
|
209
|
+
this.schemaReady = true;
|
|
210
|
+
// Signal that the schema data is ready to be tracked.
|
|
211
|
+
this.$emit('reset');
|
|
212
|
+
});
|
|
213
|
+
},
|
|
214
|
+
updateNextAndEmit() {
|
|
215
|
+
if (!this.schemaReady) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const oldHasErrors = this.next.hasErrors;
|
|
219
|
+
// destructure these for non-linked comparison
|
|
220
|
+
const oldFieldState = { ...this.next.fieldState };
|
|
221
|
+
const newFieldState = { ...this.fieldState };
|
|
222
|
+
|
|
223
|
+
let changeFound = false;
|
|
224
|
+
|
|
225
|
+
this.next.hasErrors = false;
|
|
226
|
+
this.next.fieldState = { ...this.fieldState };
|
|
227
|
+
|
|
228
|
+
this.schema.filter(field => this.displayComponent(field)).forEach(field => {
|
|
229
|
+
if (this.fieldState[field.name].error) {
|
|
230
|
+
this.next.hasErrors = true;
|
|
231
|
+
}
|
|
232
|
+
if (
|
|
233
|
+
this.fieldState[field.name].data !== undefined &&
|
|
234
|
+
detectFieldChange(field, this.next.data[field.name], this.fieldState[field.name].data)
|
|
235
|
+
) {
|
|
236
|
+
changeFound = true;
|
|
237
|
+
this.next.data[field.name] = this.fieldState[field.name].data;
|
|
238
|
+
} else {
|
|
239
|
+
this.next.data[field.name] = this.value.data[field.name];
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
if (
|
|
243
|
+
oldHasErrors !== this.next.hasErrors ||
|
|
244
|
+
oldFieldState !== newFieldState
|
|
245
|
+
) {
|
|
246
|
+
// Otherwise the save button may never unlock
|
|
247
|
+
changeFound = true;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (changeFound) {
|
|
251
|
+
// ... removes need for deep watch at parent level
|
|
252
|
+
this.$emit('input', { ...this.next });
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
displayComponent({ name, hidden = false }) {
|
|
256
|
+
if (hidden === true) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (this.currentFields && !this.currentFields.includes(name)) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Might not be a conditional field at all, so test explicitly for false
|
|
265
|
+
if (this.conditionalFields[name] === false) {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return true;
|
|
270
|
+
},
|
|
271
|
+
scrollFieldIntoView(fieldName) {
|
|
272
|
+
// The refs for a name are an array if that ref was assigned
|
|
273
|
+
// in a v-for. We know there is only one in this case
|
|
274
|
+
// https://forum.vuejs.org/t/this-refs-theid-returns-an-array/31995/9
|
|
275
|
+
this.$refs[fieldName][0].$el.scrollIntoView();
|
|
276
|
+
},
|
|
277
|
+
onUpdateDocData(data) {
|
|
278
|
+
this.$emit('update-doc-data', data);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
name: 'AposSearchList',
|
|
3
|
+
props: {
|
|
4
|
+
list: {
|
|
5
|
+
type: Array,
|
|
6
|
+
default() {
|
|
7
|
+
return [];
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
customFields: {
|
|
11
|
+
type: Array,
|
|
12
|
+
default() {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
selectedItems: {
|
|
17
|
+
type: Array,
|
|
18
|
+
default() {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
disabledTooltip: {
|
|
23
|
+
type: String,
|
|
24
|
+
default: null
|
|
25
|
+
},
|
|
26
|
+
label: {
|
|
27
|
+
type: String,
|
|
28
|
+
default: ''
|
|
29
|
+
},
|
|
30
|
+
help: {
|
|
31
|
+
type: [ String, Object ],
|
|
32
|
+
default: ''
|
|
33
|
+
},
|
|
34
|
+
icon: {
|
|
35
|
+
type: String,
|
|
36
|
+
default: 'text-box-icon'
|
|
37
|
+
},
|
|
38
|
+
iconSize: {
|
|
39
|
+
type: Number,
|
|
40
|
+
default: 20
|
|
41
|
+
},
|
|
42
|
+
fields: {
|
|
43
|
+
type: Array,
|
|
44
|
+
default() {
|
|
45
|
+
return [ 'slug' ];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
emits: [ 'select' ],
|
|
50
|
+
methods: {
|
|
51
|
+
select(item, $event) {
|
|
52
|
+
if (item.disabled) {
|
|
53
|
+
$event.stopPropagation();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const selectedItems = this.selectedItems;
|
|
57
|
+
if (!selectedItems.some(selectedItem => selectedItem._id === item._id)) {
|
|
58
|
+
// Never modify a prop
|
|
59
|
+
this.$emit('select', [ ...selectedItems, item ]);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
getClasses(item) {
|
|
63
|
+
const classes = {
|
|
64
|
+
'apos-search__item': true
|
|
65
|
+
};
|
|
66
|
+
if (item.disabled) {
|
|
67
|
+
classes['apos-search__item--disabled'] = true;
|
|
68
|
+
}
|
|
69
|
+
item.classes && Array.isArray(item.classes) && item.classes.forEach(suffix => {
|
|
70
|
+
classes[`apos-search__item--${suffix}`] = true;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return classes;
|
|
74
|
+
},
|
|
75
|
+
getTooltip(item) {
|
|
76
|
+
return item.disabled && item.tooltip !== false ? this.disabledTooltip : null;
|
|
77
|
+
},
|
|
78
|
+
getIcon(item) {
|
|
79
|
+
return {
|
|
80
|
+
icon: item.icon ?? this.icon,
|
|
81
|
+
iconSize: item.iconSize || this.iconSize
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
@@ -96,8 +96,8 @@ export default {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
// Set the column width to the spacer width plus 15 for extra wiggle
|
|
99
|
-
// room.
|
|
100
|
-
colWidths[col.property] = ref.clientWidth + 15;
|
|
99
|
+
// room. Add additional width if specified.
|
|
100
|
+
colWidths[col.property] = ref.clientWidth + 15 + (col.extraWidth ?? 0);
|
|
101
101
|
});
|
|
102
102
|
this.$emit('calculated', colWidths);
|
|
103
103
|
},
|