apostrophe 3.5.0 → 3.8.1

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 (88) hide show
  1. package/.eslintrc +4 -0
  2. package/.scratch.md +2 -0
  3. package/CHANGELOG.md +96 -3
  4. package/README.md +1 -1
  5. package/index.js +108 -3
  6. package/lib/moog-require.js +23 -0
  7. package/lib/moog.js +1 -0
  8. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +30 -14
  9. package/modules/@apostrophecms/area/lib/custom-tags/area.js +1 -1
  10. package/modules/@apostrophecms/area/lib/custom-tags/widget.js +1 -1
  11. package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +2 -2
  12. package/modules/@apostrophecms/asset/index.js +77 -13
  13. package/modules/@apostrophecms/attachment/index.js +1 -0
  14. package/modules/@apostrophecms/db/index.js +5 -6
  15. package/modules/@apostrophecms/doc/index.js +2 -0
  16. package/modules/@apostrophecms/doc-type/index.js +39 -16
  17. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +13 -1
  18. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +3 -0
  19. package/modules/@apostrophecms/i18n/i18n/en.json +19 -6
  20. package/modules/@apostrophecms/i18n/i18n/es.json +382 -0
  21. package/modules/@apostrophecms/i18n/i18n/pt-BR.json +379 -0
  22. package/modules/@apostrophecms/i18n/i18n/sk.json +380 -0
  23. package/modules/@apostrophecms/i18n/index.js +10 -1
  24. package/modules/@apostrophecms/image/index.js +2 -1
  25. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +6 -3
  26. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerEditor.vue +2 -1
  27. package/modules/@apostrophecms/image-widget/index.js +2 -1
  28. package/modules/@apostrophecms/image-widget/views/widget.html +12 -2
  29. package/modules/@apostrophecms/job/index.js +164 -212
  30. package/modules/@apostrophecms/login/index.js +1 -16
  31. package/modules/@apostrophecms/login/ui/apos/components/TheAposLogin.vue +5 -0
  32. package/modules/@apostrophecms/migration/index.js +1 -1
  33. package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +151 -61
  34. package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +6 -2
  35. package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +8 -6
  36. package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocsManagerMixin.js +12 -15
  37. package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +6 -0
  38. package/modules/@apostrophecms/notification/index.js +116 -8
  39. package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +89 -11
  40. package/modules/@apostrophecms/notification/ui/apos/components/TheAposNotifications.vue +1 -1
  41. package/modules/@apostrophecms/page/index.js +37 -30
  42. package/modules/@apostrophecms/permission/index.js +1 -1
  43. package/modules/@apostrophecms/permission/ui/apos/components/AposInputRole.vue +4 -2
  44. package/modules/@apostrophecms/piece-type/index.js +178 -61
  45. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +179 -47
  46. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +1 -3
  47. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerSelectBox.vue +138 -0
  48. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +35 -6
  49. package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Classes.js +1 -3
  50. package/modules/@apostrophecms/schema/index.js +97 -20
  51. package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +1 -0
  52. package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +4 -1
  53. package/modules/@apostrophecms/schema/ui/apos/components/AposInputAttachment.vue +11 -160
  54. package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +24 -2
  55. package/modules/@apostrophecms/schema/ui/apos/components/AposInputSelect.vue +24 -6
  56. package/modules/@apostrophecms/schema/ui/apos/components/AposInputSlug.vue +0 -4
  57. package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +0 -7
  58. package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +25 -3
  59. package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputMixin.js +10 -2
  60. package/modules/@apostrophecms/task/index.js +2 -2
  61. package/modules/@apostrophecms/template/index.js +63 -36
  62. package/modules/@apostrophecms/template/lib/custom-tags/component.js +1 -1
  63. package/modules/@apostrophecms/template/lib/custom-tags/render.js +6 -2
  64. package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +5 -2
  65. package/modules/@apostrophecms/ui/ui/apos/components/AposCellContextMenu.vue +1 -1
  66. package/modules/@apostrophecms/ui/ui/apos/components/AposFile.vue +205 -0
  67. package/modules/@apostrophecms/ui/ui/apos/lib/i18next.js +16 -2
  68. package/modules/@apostrophecms/ui/ui/apos/scss/global/_tables.scss +4 -3
  69. package/modules/@apostrophecms/util/index.js +2 -2
  70. package/modules/@apostrophecms/util/ui/src/http.js +12 -8
  71. package/modules/@apostrophecms/util/ui/src/util.js +15 -0
  72. package/modules/@apostrophecms/widget-type/index.js +1 -1
  73. package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +1 -0
  74. package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +15 -7
  75. package/package.json +3 -3
  76. package/test/extra_node_modules/improve-global/index.js +7 -0
  77. package/test/extra_node_modules/improve-piece-type/index.js +7 -0
  78. package/test/improve-overrides.js +30 -0
  79. package/test/job.js +224 -0
  80. package/test/modules/@apostrophecms/global/index.js +8 -0
  81. package/test/modules/fragment-all/views/aux-test.html +7 -0
  82. package/test/modules/fragment-all/views/fragment.html +5 -0
  83. package/test/package.json +5 -4
  84. package/test/pieces.js +34 -0
  85. package/test/reverse-relationship.js +170 -0
  86. package/test/templates.js +7 -1
  87. package/test-lib/test.js +23 -12
  88. package/test-lib/util.js +33 -0
@@ -18,6 +18,7 @@ const _ = require('lodash');
18
18
  const dayjs = require('dayjs');
19
19
  const tinycolor = require('tinycolor2');
20
20
  const { klona } = require('klona');
21
+ const { stripIndent } = require('common-tags');
21
22
 
22
23
  module.exports = {
23
24
  options: {
@@ -323,7 +324,13 @@ module.exports = {
323
324
  self.addFieldType({
324
325
  name: 'select',
325
326
  async convert(req, field, data, destination) {
326
- destination[field.name] = self.apos.launder.select(data[field.name], field.choices, field.def);
327
+ let choices;
328
+ if ((typeof field.choices) === 'string') {
329
+ choices = await self.apos.modules[field.moduleName][field.choices](req);
330
+ } else {
331
+ choices = field.choices;
332
+ }
333
+ destination[field.name] = self.apos.launder.select(data[field.name], choices, field.def);
327
334
  },
328
335
  index: function (value, field, texts) {
329
336
  const silent = field.silent === undefined ? true : field.silent;
@@ -354,7 +361,9 @@ module.exports = {
354
361
  return self.apos.launder.select(v, field.choices, null);
355
362
  });
356
363
  } else {
357
- value = self.apos.launder.select(value, field.choices, null);
364
+ value = (typeof field.choices) === 'string'
365
+ ? self.apos.launder.string(value)
366
+ : self.apos.launder.select(value, field.choices, null);
358
367
  if (value === null) {
359
368
  return null;
360
369
  }
@@ -362,9 +371,16 @@ module.exports = {
362
371
  }
363
372
  },
364
373
  choices: async function () {
374
+ let allChoices;
365
375
  const values = await query.toDistinct(field.name);
376
+ if ((typeof field.choices) === 'string') {
377
+ const req = self.apos.task.getReq();
378
+ allChoices = await self.apos.modules[field.moduleName][field.choices](req);
379
+ } else {
380
+ allChoices = field.choices;
381
+ }
366
382
  const choices = _.map(values, function (value) {
367
- const choice = _.find(field.choices, { value: value });
383
+ const choice = _.find(allChoices, { value: value });
368
384
  return {
369
385
  value: value,
370
386
  label: choice && (choice.label || value)
@@ -980,6 +996,12 @@ module.exports = {
980
996
  if (field.schema && !Array.isArray(field.schema)) {
981
997
  fail('schema property should be an array if present at this stage');
982
998
  }
999
+ if (field.filters) {
1000
+ fail('"filters" property should be changed to "builders" for 3.x');
1001
+ }
1002
+ if (field.builders && field.builders.projection) {
1003
+ fail('"projection" sub-property should be changed to "project" for 3.x');
1004
+ }
983
1005
  function lintType(type) {
984
1006
  type = self.apos.doc.normalizeType(type);
985
1007
  if (!_.find(self.apos.doc.managers, { name: type })) {
@@ -988,11 +1010,15 @@ module.exports = {
988
1010
  }
989
1011
  },
990
1012
  isEqual(req, field, one, two) {
991
- if (!_.isEqual(one[field.idsStorage], two[field.idsStorage])) {
1013
+ const ids1 = one[field.idsStorage] || [];
1014
+ const ids2 = two[field.idsStorage] || [];
1015
+ if (!_.isEqual(ids1, ids2)) {
992
1016
  return false;
993
1017
  }
994
1018
  if (field.fieldsStorage) {
995
- if (!_.isEqual(one[field.fieldsStorage], two[field.fieldsStorage])) {
1019
+ const fields1 = one[field.fieldsStorage] || {};
1020
+ const fields2 = two[field.fieldsStorage] || {};
1021
+ if (!_.isEqual(fields1, fields2)) {
996
1022
  return false;
997
1023
  }
998
1024
  }
@@ -1098,7 +1124,7 @@ module.exports = {
1098
1124
  // alterFields option should be avoided if your needs can be met
1099
1125
  // via another option.
1100
1126
 
1101
- compose(options) {
1127
+ compose(options, module) {
1102
1128
  let schema = [];
1103
1129
 
1104
1130
  // Useful for finding good unit test cases
@@ -1288,9 +1314,29 @@ module.exports = {
1288
1314
  // like workflow to patch schema fields of various modules
1289
1315
  // without inadvertently impacting other apos instances
1290
1316
  // when running with @apostrophecms/multisite
1291
- return _.map(schema, function (field) {
1317
+ schema = _.map(schema, function (field) {
1292
1318
  return _.clone(field);
1293
1319
  });
1320
+
1321
+ _.each(schema, function(field) {
1322
+ // For use in resolving options like "choices" when they
1323
+ // contain a method name. For bc don't mess with possible
1324
+ // existing usages in custom schema field types predating
1325
+ // this feature
1326
+ self.setModuleName(field, module);
1327
+ });
1328
+ return schema;
1329
+ },
1330
+
1331
+ // Recursively set moduleName property of the field and any subfields,
1332
+ // as might be found in array or object fields. `module` is an actual module
1333
+ setModuleName(field, module) {
1334
+ field.moduleName = field.moduleName || (module && module.__meta.name);
1335
+ if ((field.type === 'array') || (field.type === 'object')) {
1336
+ _.each(field.schema || [], function(subfield) {
1337
+ self.setModuleName(subfield, module);
1338
+ });
1339
+ }
1294
1340
  },
1295
1341
 
1296
1342
  // refine is like compose, but it starts with an existing schema array
@@ -1473,10 +1519,8 @@ module.exports = {
1473
1519
  for (const field of schema) {
1474
1520
  const fieldType = self.fieldTypes[field.type];
1475
1521
  if (!fieldType.isEqual) {
1476
- if ((one[field.name] == null) && (two[field.name] == null)) {
1477
- return true;
1478
- }
1479
- if (!_.isEqual(one[field.name], two[field.name])) {
1522
+ if ((!_.isEqual(one[field.name], two[field.name])) &&
1523
+ !((one[field.name] == null) && (two[field.name] == null))) {
1480
1524
  return false;
1481
1525
  }
1482
1526
  } else {
@@ -1928,6 +1972,7 @@ module.exports = {
1928
1972
  } else if (field.type === 'array') {
1929
1973
  if (doc[field.name]) {
1930
1974
  doc[field.name].forEach(item => {
1975
+ item._id = item._id || self.apos.util.generateId();
1931
1976
  item.metaType = 'arrayItem';
1932
1977
  item.scopedArrayName = field.scopedArrayName;
1933
1978
  forSchema(field.schema, item);
@@ -2179,13 +2224,14 @@ module.exports = {
2179
2224
  // of a `relationship` field, or the `label` property of anything.
2180
2225
 
2181
2226
  validate(schema, options) {
2182
- // Infinite recursion prevention
2183
- if (self.validatedSchemas[options.type + ':' + options.subtype]) {
2184
- return;
2185
- }
2186
- self.validatedSchemas[options.type + ':' + options.subtype] = true;
2187
-
2188
- schema.forEach(field => self.validateField(field, options));
2227
+ schema.forEach(field => {
2228
+ // Infinite recursion prevention
2229
+ const key = `${options.type}:${options.subtype}.${field.name}`;
2230
+ if (!self.validatedSchemas[key]) {
2231
+ self.validatedSchemas[key] = true;
2232
+ self.validateField(field, options);
2233
+ }
2234
+ });
2189
2235
  },
2190
2236
 
2191
2237
  // Validates a single schema field. See `validate`.
@@ -2213,7 +2259,12 @@ module.exports = {
2213
2259
  self.apos.util.error(format(s));
2214
2260
  }
2215
2261
  function format(s) {
2216
- return '\n' + options.type + ' ' + options.subtype + ', field name ' + field.name + ':\n\n' + s + '\n';
2262
+ return stripIndent`
2263
+ ${options.type} ${options.subtype}, ${field.type} field "${field.name}":
2264
+
2265
+ ${s}
2266
+
2267
+ `;
2217
2268
  }
2218
2269
  },
2219
2270
 
@@ -2526,6 +2577,32 @@ module.exports = {
2526
2577
 
2527
2578
  };
2528
2579
  },
2580
+ apiRoutes(self) {
2581
+ return {
2582
+ get: {
2583
+ async choices(req) {
2584
+ const id = self.apos.launder.string(req.query.fieldId);
2585
+ const field = self.getFieldById(id);
2586
+ let choices = [];
2587
+ if (
2588
+ !field ||
2589
+ field.type !== 'select' ||
2590
+ !(field.choices && typeof field.choices === 'string')
2591
+ ) {
2592
+ throw self.apos.error('invalid');
2593
+ }
2594
+ choices = await self.apos.modules[field.moduleName][field.choices](req);
2595
+ if (Array.isArray(choices)) {
2596
+ return {
2597
+ choices
2598
+ };
2599
+ } else {
2600
+ throw self.apos.error('invalid', `The method ${field.choices} from the module ${field.moduleName} did not return an array`);
2601
+ }
2602
+ }
2603
+ }
2604
+ };
2605
+ },
2529
2606
  extendMethods(self) {
2530
2607
  return {
2531
2608
  getBrowserData(_super, req) {
@@ -2540,7 +2617,7 @@ module.exports = {
2540
2617
  }
2541
2618
  fields[name] = component;
2542
2619
  }
2543
-
2620
+ browserOptions.action = self.action;
2544
2621
  browserOptions.components = { fields: fields };
2545
2622
  return browserOptions;
2546
2623
  }
@@ -69,6 +69,7 @@
69
69
  :conditional-fields="conditionalFields()"
70
70
  :value="currentDoc"
71
71
  @input="currentDocUpdate"
72
+ @validate="triggerValidate"
72
73
  :server-errors="currentDocServerErrors"
73
74
  ref="schema"
74
75
  />
@@ -40,7 +40,10 @@ export default {
40
40
  },
41
41
  computed: {
42
42
  editLabel () {
43
- return `Edit ${this.field.label}`;
43
+ return {
44
+ key: 'apostrophe:editType',
45
+ type: this.$t(this.field.label)
46
+ };
44
47
  }
45
48
  },
46
49
  methods: {
@@ -6,44 +6,15 @@
6
6
  >
7
7
  <template #body>
8
8
  <div class="apos-attachment">
9
- <label
10
- class="apos-input-wrapper apos-attachment-dropzone"
11
- :class="{
12
- 'apos-attachment-dropzone--dragover': dragging,
13
- 'apos-is-disabled': disabled || limitReached
14
- }"
15
- @drop.prevent="uploadMedia"
16
- @dragover="dragHandler"
17
- @dragleave="dragging = false"
18
- >
19
- <p class="apos-attachment-instructions">
20
- <template v-if="dragging">
21
- <cloud-upload-icon :size="38" />
22
- </template>
23
- <AposSpinner v-else-if="uploading" />
24
- <template v-else>
25
- <paperclip-icon :size="14" class="apos-attachment-icon" />
26
- {{ messages.primary }}&nbsp;
27
- <span class="apos-attachment-highlight" v-if="messages.highlighted">
28
- {{ messages.highlighted }}
29
- </span>
30
- </template>
31
- </p>
32
- <input
33
- type="file"
34
- class="apos-sr-only"
35
- :disabled="disabled || limitReached"
36
- @input="uploadMedia"
37
- :accept="field.accept"
38
- >
39
- </label>
40
- <div v-if="next && next._id" class="apos-attachment-files">
41
- <AposSlatList
42
- :value="next ? [ next ] : []"
43
- @input="updated"
44
- :disabled="field.readOnly"
45
- />
46
- </div>
9
+ <AposFile
10
+ :allowed-extensions="field.accept"
11
+ :uploading="uploading"
12
+ :disabled="disabled"
13
+ :attachment="next"
14
+ :def="field.def"
15
+ @upload-file="uploadMedia"
16
+ @update="updated"
17
+ />
47
18
  </div>
48
19
  </template>
49
20
  </AposInputWrapper>
@@ -51,13 +22,9 @@
51
22
 
52
23
  <script>
53
24
  import AposInputMixin from 'Modules/@apostrophecms/schema/mixins/AposInputMixin.js';
54
- import AposSlatList from 'Modules/@apostrophecms/ui/components/AposSlatList';
55
25
 
56
26
  export default {
57
27
  name: 'AposInputAttachment',
58
- components: {
59
- AposSlatList
60
- },
61
28
  mixins: [ AposInputMixin ],
62
29
  emits: [ 'upload-started', 'upload-complete' ],
63
30
  data () {
@@ -67,46 +34,13 @@ export default {
67
34
  next: (this.value && (typeof this.value.data === 'object'))
68
35
  ? this.value.data : (this.field.def || null),
69
36
  disabled: false,
70
- dragging: false,
71
- uploading: false,
72
- allowedExtensions: [ '*' ]
37
+ uploading: false
73
38
  };
74
39
  },
75
- computed: {
76
- messages () {
77
- const msgs = {
78
- primary: 'Drop a file here or',
79
- highlighted: 'click to open the file explorer'
80
- };
81
- if (this.disabled) {
82
- msgs.primary = 'Field is disabled';
83
- msgs.highlighted = '';
84
- }
85
- if (this.limitReached) {
86
- msgs.primary = 'Attachment limit reached';
87
- msgs.highlighted = '';
88
- }
89
- return msgs;
90
- },
91
- limitReached () {
92
- return !!(this.value.data && this.value.data._id);
93
- }
94
- },
95
40
  async mounted () {
96
41
  this.disabled = this.field.readOnly;
97
-
98
- const groups = apos.modules['@apostrophecms/attachment'].fileGroups;
99
- const groupInfo = groups.find(group => {
100
- return group.name === this.field.fileGroup;
101
- });
102
- if (groupInfo && groupInfo.extensions) {
103
- this.allowedExtensions = groupInfo.extensions;
104
- }
105
42
  },
106
43
  methods: {
107
- watchNext () {
108
- this.validateAndEmit();
109
- },
110
44
  updated (items) {
111
45
  // NOTE: This is limited to a single item.
112
46
  this.next = items.length > 0 ? items[0] : null;
@@ -118,31 +52,12 @@ export default {
118
52
 
119
53
  return false;
120
54
  },
121
- async uploadMedia (event) {
55
+ async uploadMedia (file) {
122
56
  if (!this.disabled || !this.limitReached) {
123
57
  try {
124
- this.dragging = false;
125
58
  this.disabled = true;
126
59
  this.uploading = true;
127
60
 
128
- const file = event.target.files ? event.target.files[0] : event.dataTransfer.files[0];
129
-
130
- if (!this.checkFileGroup(file.name)) {
131
- const joined = this.allowedExtensions.join(this.$t('apostrophe:listJoiner'));
132
- await apos.notify('apostrophe:fileTypeNotAccepted', {
133
- type: 'warning',
134
- icon: 'alert-circle-icon',
135
- interpolate: {
136
- extensions: joined
137
- }
138
- });
139
-
140
- this.disabled = false;
141
- this.uploading = false;
142
-
143
- return;
144
- }
145
-
146
61
  await apos.notify('apostrophe:uploading', {
147
62
  dismiss: true,
148
63
  icon: 'cloud-upload-icon',
@@ -183,71 +98,7 @@ export default {
183
98
  this.uploading = false;
184
99
  }
185
100
  }
186
- },
187
- checkFileGroup(filename) {
188
- const fileExt = filename.split('.').pop();
189
- return this.allowedExtensions[0] === '*' ||
190
- this.allowedExtensions.includes(fileExt);
191
- },
192
- dragHandler (event) {
193
- event.preventDefault();
194
- if (!this.disabled && !this.dragging) {
195
- this.dragging = true;
196
- }
197
101
  }
198
102
  }
199
103
  };
200
104
  </script>
201
-
202
- <style lang="scss" scoped>
203
- .apos-attachment-dropzone {
204
- @include apos-button-reset();
205
- @include type-base;
206
- display: block;
207
- margin: 10px 0;
208
- padding: 20px;
209
- border: 2px dashed var(--a-base-8);
210
- border-radius: var(--a-border-radius);
211
- transition: all 0.2s ease;
212
- &:hover {
213
- border-color: var(--a-primary);
214
- background-color: var(--a-base-10);
215
- }
216
- &:active,
217
- &:focus {
218
- border: 2px solid var(--a-primary);
219
- }
220
- &.apos-is-disabled {
221
- color: var(--a-base-4);
222
- background-color: var(--a-base-7);
223
- border-color: var(--a-base-4);
224
-
225
- &:hover {
226
- cursor: not-allowed;
227
- }
228
- }
229
- }
230
-
231
- .apos-attachment-dropzone--dragover {
232
- border: 2px dashed var(--a-primary);
233
- background-color: var(--a-base-10);
234
- }
235
-
236
- .apos-attachment-instructions {
237
- display: flex;
238
- flex-wrap: wrap;
239
- align-items: center;
240
- justify-content: center;
241
- pointer-events: none;
242
- // v-html goofiness
243
- & ::v-deep .apos-attachment-highlight {
244
- color: var(--a-primary);
245
- font-weight: var(--a-weight-bold);
246
- }
247
- }
248
-
249
- .apos-attachment-icon {
250
- transform: rotate(45deg);
251
- margin-right: 5px;
252
- }
253
- </style>
@@ -3,6 +3,7 @@
3
3
  :field="field" :error="effectiveError"
4
4
  :uid="uid" :items="next"
5
5
  :display-options="displayOptions"
6
+ :modifiers="modifiers"
6
7
  >
7
8
  <template #additional>
8
9
  <AposMinMaxCount
@@ -14,6 +15,7 @@
14
15
  <div class="apos-input-wrapper apos-input-relationship">
15
16
  <div class="apos-input-relationship__input-wrapper">
16
17
  <input
18
+ v-if="!modifiers.includes('no-search')"
17
19
  class="apos-input apos-input--text apos-input--relationship"
18
20
  v-model="searchTerm" type="text"
19
21
  :placeholder="$t(placeholder)"
@@ -28,7 +30,7 @@
28
30
  class="apos-input-relationship__button"
29
31
  :disabled="field.readOnly || limitReached"
30
32
  :label="browseLabel"
31
- :modifiers="['small']"
33
+ :modifiers="buttonModifiers"
32
34
  type="input"
33
35
  @click="choose"
34
36
  />
@@ -106,6 +108,13 @@ export default {
106
108
  },
107
109
  disableUnpublished() {
108
110
  return apos.modules[this.field.withType].localized;
111
+ },
112
+ buttonModifiers() {
113
+ const modifiers = [ 'small' ];
114
+ if (this.modifiers.includes('no-search')) {
115
+ modifiers.push('block');
116
+ }
117
+ return modifiers;
109
118
  }
110
119
  },
111
120
  watch: {
@@ -225,6 +234,19 @@ export default {
225
234
 
226
235
  .apos-input-relationship__items {
227
236
  padding: relative;
228
- margin-top: 10px;
237
+ margin-top: $spacing-base;
238
+ }
239
+
240
+ .apos-field--small {
241
+ .apos-input-relationship__button {
242
+ padding: $spacing-half;
243
+ }
244
+ }
245
+ .apos-field--no-search {
246
+ .apos-input-relationship__button {
247
+ position: relative;
248
+ width: 100%;
249
+ padding: 0;
250
+ }
229
251
  }
230
252
  </style>
@@ -49,9 +49,27 @@ export default {
49
49
  choices: []
50
50
  };
51
51
  },
52
- mounted() {
52
+ async mounted() {
53
+ let choices;
54
+ if (typeof this.field.choices === 'string') {
55
+ const action = this.options.action;
56
+ const response = await apos.http.get(
57
+ `${action}/choices`,
58
+ {
59
+ qs: {
60
+ fieldId: this.field._id
61
+ },
62
+ busy: true
63
+ }
64
+ );
65
+ if (response.choices) {
66
+ choices = response.choices;
67
+ }
68
+ } else {
69
+ choices = this.field.choices;
70
+ }
53
71
  // Add an null option if there isn't one already
54
- if (!this.field.required && !this.field.choices.find(choice => {
72
+ if (!this.field.required && !choices.find(choice => {
55
73
  return choice.value === null;
56
74
  })) {
57
75
  this.choices.push({
@@ -59,12 +77,12 @@ export default {
59
77
  value: null
60
78
  });
61
79
  }
62
- this.choices = this.choices.concat(this.field.choices);
80
+ this.choices = this.choices.concat(choices);
63
81
  this.$nextTick(() => {
64
82
  // this has to happen on nextTick to avoid emitting before schemaReady is
65
83
  // set in AposSchema
66
- if (this.field.required && (this.next == null) && (this.field.choices[0] != null)) {
67
- this.next = this.field.choices[0].value;
84
+ if (this.field.required && (this.next == null) && (this.choices[0] != null)) {
85
+ this.next = this.choices[0].value;
68
86
  }
69
87
  });
70
88
  },
@@ -74,7 +92,7 @@ export default {
74
92
  return 'required';
75
93
  }
76
94
 
77
- if (value && !this.field.choices.find(choice => choice.value === value)) {
95
+ if (value && !this.choices.find(choice => choice.value === value)) {
78
96
  return 'invalid';
79
97
  }
80
98
 
@@ -73,10 +73,6 @@ export default {
73
73
  icon () {
74
74
  if (this.error) {
75
75
  return 'circle-medium-icon';
76
- } else if (this.field.type === 'date') {
77
- return 'calendar-icon';
78
- } else if (this.field.type === 'time') {
79
- return 'clock-icon';
80
76
  } else if (this.field.icon) {
81
77
  return this.field.icon;
82
78
  } else {
@@ -71,10 +71,6 @@ export default {
71
71
  icon () {
72
72
  if (this.error) {
73
73
  return 'circle-medium-icon';
74
- } else if (this.field.type === 'date') {
75
- return 'calendar-icon';
76
- } else if (this.field.type === 'time') {
77
- return 'clock-icon';
78
74
  } else if (this.field.icon) {
79
75
  return this.field.icon;
80
76
  } else {
@@ -207,9 +203,6 @@ export default {
207
203
  // height of date/time input is slightly larger than others due to the browser spinner ui
208
204
  height: 46px;
209
205
  padding-right: 40px;
210
- &::-webkit-calendar-picker-indicator {
211
- background: none;
212
- }
213
206
  }
214
207
  .apos-input--date {
215
208
  &::-webkit-clear-button {
@@ -10,8 +10,9 @@
10
10
  <component
11
11
  v-show="displayComponent(field.name)"
12
12
  v-model="fieldState[field.name]"
13
- :following-values="followingValues[field.name]"
14
13
  :is="fieldComponentMap[field.type]"
14
+ :following-values="followingValues[field.name]"
15
+ :condition-met="conditionalFields[field.name]"
15
16
  :field="fields[field.name].field"
16
17
  :modifiers="fields[field.name].modifiers"
17
18
  :display-options="getDisplayOptions(field.name)"
@@ -89,7 +90,11 @@ export default {
89
90
  }
90
91
  }
91
92
  },
92
- emits: [ 'input', 'reset' ],
93
+ emits: [
94
+ 'input',
95
+ 'reset',
96
+ 'validate'
97
+ ],
93
98
  data() {
94
99
  return {
95
100
  schemaReady: false,
@@ -112,7 +117,10 @@ export default {
112
117
  data: this.value[item.name]
113
118
  };
114
119
  fields[item.name].serverError = this.serverErrors && this.serverErrors[item.name];
115
- fields[item.name].modifiers = this.modifiers;
120
+ fields[item.name].modifiers = [
121
+ ...(this.modifiers || []),
122
+ ...(item.modifiers || [])
123
+ ];
116
124
  });
117
125
  return fields;
118
126
  }
@@ -142,6 +150,20 @@ export default {
142
150
  this.populateDocData();
143
151
  }
144
152
  }
153
+ },
154
+ conditionalFields(newVal, oldVal) {
155
+ for (const field in oldVal) {
156
+ if (!this.fieldState[field] || (newVal[field] === oldVal[field]) || !this.fieldState[field].ranValidation) {
157
+ continue;
158
+ }
159
+
160
+ if (
161
+ (newVal[field] === false) ||
162
+ (newVal[field] && this.fieldState[field].ranValidation)
163
+ ) {
164
+ this.$emit('validate');
165
+ }
166
+ }
145
167
  }
146
168
  },
147
169
  created() {
@@ -22,6 +22,10 @@ export default {
22
22
  type: Object,
23
23
  required: false
24
24
  },
25
+ conditionMet: {
26
+ type: Boolean,
27
+ required: false
28
+ },
25
29
  triggerValidation: {
26
30
  type: Boolean,
27
31
  default: false
@@ -117,10 +121,14 @@ export default {
117
121
  // You must supply the validate method. It receives the
118
122
  // internal representation used for editing (a string, for instance)
119
123
  validateAndEmit () {
120
- const error = this.validate(this.next);
124
+ // If the field is conditional and isn't shown, disregard any errors.
125
+ const error = this.conditionMet === false ? false
126
+ : this.validate(this.next);
121
127
  this.$emit('input', {
122
128
  data: error ? this.next : this.convert(this.next),
123
- error: this.validate(this.next)
129
+ error,
130
+ ranValidation: this.conditionMet === false ? this.value.ranValidation
131
+ : true
124
132
  });
125
133
  },
126
134
  watchValue () {