apostrophe 3.4.1 → 3.8.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.
Files changed (111) hide show
  1. package/.eslintrc +4 -0
  2. package/.scratch.md +2 -0
  3. package/CHANGELOG.md +114 -2
  4. package/README.md +1 -1
  5. package/deploy-test-count +1 -1
  6. package/index.js +125 -5
  7. package/lib/moog-require.js +41 -3
  8. package/lib/moog.js +20 -8
  9. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarLocale.vue +42 -23
  10. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +30 -14
  11. package/modules/@apostrophecms/area/index.js +9 -0
  12. package/modules/@apostrophecms/area/lib/custom-tags/area.js +1 -1
  13. package/modules/@apostrophecms/area/lib/custom-tags/widget.js +1 -1
  14. package/modules/@apostrophecms/area/ui/apos/apps/AposAreas.js +3 -0
  15. package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +6 -6
  16. package/modules/@apostrophecms/asset/index.js +85 -21
  17. package/modules/@apostrophecms/asset/lib/globalIcons.js +2 -0
  18. package/modules/@apostrophecms/asset/lib/webpack/src/webpack.scss.js +5 -2
  19. package/modules/@apostrophecms/attachment/index.js +1 -0
  20. package/modules/@apostrophecms/db/index.js +5 -6
  21. package/modules/@apostrophecms/doc/index.js +13 -3
  22. package/modules/@apostrophecms/doc-type/index.js +24 -4
  23. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +13 -1
  24. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +3 -0
  25. package/modules/@apostrophecms/i18n/i18n/en.json +26 -6
  26. package/modules/@apostrophecms/i18n/i18n/es.json +382 -0
  27. package/modules/@apostrophecms/i18n/i18n/pt-BR.json +379 -0
  28. package/modules/@apostrophecms/i18n/i18n/sk.json +380 -0
  29. package/modules/@apostrophecms/i18n/index.js +10 -1
  30. package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +153 -121
  31. package/modules/@apostrophecms/image/index.js +2 -1
  32. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +6 -3
  33. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerEditor.vue +24 -13
  34. package/modules/@apostrophecms/image-widget/index.js +2 -1
  35. package/modules/@apostrophecms/image-widget/views/widget.html +12 -2
  36. package/modules/@apostrophecms/job/index.js +164 -212
  37. package/modules/@apostrophecms/login/index.js +36 -17
  38. package/modules/@apostrophecms/login/ui/apos/components/TheAposLogin.vue +8 -0
  39. package/modules/@apostrophecms/migration/index.js +1 -1
  40. package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +151 -61
  41. package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +6 -2
  42. package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +9 -7
  43. package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocsManagerMixin.js +12 -15
  44. package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +6 -0
  45. package/modules/@apostrophecms/module/index.js +1 -1
  46. package/modules/@apostrophecms/notification/index.js +116 -8
  47. package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +89 -11
  48. package/modules/@apostrophecms/notification/ui/apos/components/TheAposNotifications.vue +1 -1
  49. package/modules/@apostrophecms/page/index.js +37 -30
  50. package/modules/@apostrophecms/permission/index.js +1 -1
  51. package/modules/@apostrophecms/permission/ui/apos/components/AposInputRole.vue +4 -2
  52. package/modules/@apostrophecms/piece-type/index.js +178 -61
  53. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +179 -47
  54. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +1 -3
  55. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerSelectBox.vue +138 -0
  56. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +42 -10
  57. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapStyles.vue +3 -0
  58. package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Classes.js +6 -10
  59. package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Default.js +64 -0
  60. package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Document.js +15 -0
  61. package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Heading.js +23 -0
  62. package/modules/@apostrophecms/schema/index.js +97 -20
  63. package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +1 -0
  64. package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +4 -1
  65. package/modules/@apostrophecms/schema/ui/apos/components/AposInputAttachment.vue +11 -160
  66. package/modules/@apostrophecms/schema/ui/apos/components/AposInputRadio.vue +8 -5
  67. package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +24 -2
  68. package/modules/@apostrophecms/schema/ui/apos/components/AposInputSelect.vue +24 -6
  69. package/modules/@apostrophecms/schema/ui/apos/components/AposInputSlug.vue +0 -4
  70. package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +0 -7
  71. package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +25 -3
  72. package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputMixin.js +10 -2
  73. package/modules/@apostrophecms/task/index.js +2 -2
  74. package/modules/@apostrophecms/template/index.js +63 -36
  75. package/modules/@apostrophecms/template/lib/custom-tags/component.js +1 -1
  76. package/modules/@apostrophecms/template/lib/custom-tags/render.js +6 -2
  77. package/modules/@apostrophecms/ui/index.js +6 -2
  78. package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +21 -3
  79. package/modules/@apostrophecms/ui/ui/apos/components/AposCellContextMenu.vue +1 -1
  80. package/modules/@apostrophecms/ui/ui/apos/components/AposFile.vue +205 -0
  81. package/modules/@apostrophecms/ui/ui/apos/components/AposIndicator.vue +5 -0
  82. package/modules/@apostrophecms/ui/ui/apos/lib/i18next.js +16 -2
  83. package/modules/@apostrophecms/ui/ui/apos/scss/global/_tables.scss +4 -3
  84. package/modules/@apostrophecms/ui/ui/apos/scss/global/_theme.scss +3 -0
  85. package/modules/@apostrophecms/ui/ui/apos/scss/global/_widgets.scss +3 -0
  86. package/modules/@apostrophecms/ui/ui/apos/scss/global/import-all.scss +2 -1
  87. package/modules/@apostrophecms/user/index.js +21 -0
  88. package/modules/@apostrophecms/util/index.js +2 -2
  89. package/modules/@apostrophecms/util/ui/src/http.js +12 -8
  90. package/modules/@apostrophecms/util/ui/src/util.js +15 -0
  91. package/modules/@apostrophecms/widget-type/index.js +1 -1
  92. package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +1 -0
  93. package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +15 -7
  94. package/package.json +4 -4
  95. package/test/extra_node_modules/improve-global/index.js +7 -0
  96. package/test/extra_node_modules/improve-piece-type/index.js +7 -0
  97. package/test/improve-overrides.js +30 -0
  98. package/test/job.js +224 -0
  99. package/test/login.js +183 -0
  100. package/test/modules/@apostrophecms/global/index.js +8 -0
  101. package/test/modules/fragment-all/views/aux-test.html +7 -0
  102. package/test/modules/fragment-all/views/fragment.html +5 -0
  103. package/test/moog.js +47 -0
  104. package/test/package.json +5 -4
  105. package/test/pieces.js +17 -0
  106. package/test/reverse-relationship.js +170 -0
  107. package/test/subdir-project/app.js +3 -0
  108. package/test/subdir-project.js +26 -0
  109. package/test/templates.js +7 -1
  110. package/test-lib/test.js +23 -12
  111. package/test-lib/util.js +33 -0
@@ -0,0 +1,64 @@
1
+ // If a style has been marked `def`, we need to make sure it
2
+ // and it's attributes are prioritized in our editor.
3
+ // It's easier to create a new Node with our specifications and put it
4
+ // at the front of the line than try to infer between editor instantiation
5
+ // and the editor-user setting styles via the toolbar to know when its needed.
6
+
7
+ import { Node } from '@tiptap/core';
8
+ import Heading from '@tiptap/extension-heading';
9
+ import Paragraph from '@tiptap/extension-paragraph';
10
+
11
+ const nodeMap = {
12
+ heading: Heading,
13
+ paragraph: Paragraph
14
+ };
15
+
16
+ export default (options) => {
17
+ const def = options.styles.filter(style => style.def)[0];
18
+
19
+ // Configuration has a default style
20
+ if (def) {
21
+ const nodeName = 'defaultNode';
22
+ const attrs = {
23
+ class: def.options.class || null
24
+ };
25
+
26
+ if (def.type === 'heading' || def.type === 'paragraph') {
27
+ return nodeMap[def.type].extend({
28
+ name: nodeName,
29
+ defaultOptions: {
30
+ HTMLAttributes: attrs,
31
+ levels: def.options.level ? [ def.options.level ] : null
32
+ }
33
+ });
34
+ }
35
+
36
+ if (def.type === 'textStyle') {
37
+ return Node.create({
38
+ group: 'block',
39
+ content: 'text*',
40
+ name: nodeName,
41
+ defaultOptions: {
42
+ HTMLAttributes: attrs
43
+ },
44
+ renderHTML: () => {
45
+ return [ 'span', attrs, 0 ];
46
+ },
47
+ parseHTML() {
48
+ return [
49
+ {
50
+ tag: 'span',
51
+ getAttrs: element => {
52
+ const hasStyles = element.hasAttribute('style');
53
+ if (!hasStyles) {
54
+ return false;
55
+ }
56
+ return {};
57
+ }
58
+ }
59
+ ];
60
+ }
61
+ });
62
+ }
63
+ }
64
+ };
@@ -0,0 +1,15 @@
1
+ // Acts as a custom Document extension
2
+ import { Node } from '@tiptap/core';
3
+ export default (options) => {
4
+ const def = options.styles.filter(style => style.def)[0];
5
+ let content = 'block+'; // one or more block nodes (default Document setting)
6
+ if (def) {
7
+ // one/more defaultNodes (created in ./Default) or one/more other block nodes
8
+ content = '(defaultNode|block)+';
9
+ }
10
+ return Node.create({
11
+ name: 'doc',
12
+ topNode: true,
13
+ content
14
+ });
15
+ };
@@ -0,0 +1,23 @@
1
+ // Lock Heading levels down to just those provided via configuration
2
+ import Heading from '@tiptap/extension-heading';
3
+
4
+ export default (options) => {
5
+ const headings = options.styles.filter(style => style.type === 'heading');
6
+ const levels = headings.map(heading => heading.options.level);
7
+ const defaultLevel = headings.filter(heading => heading.def).length
8
+ ? headings.filter(heading => heading.def)[0].options.level
9
+ : levels[0];
10
+ return Heading.extend({
11
+ defaultOptions: {
12
+ levels
13
+ },
14
+ addAttributes() {
15
+ return {
16
+ level: {
17
+ default: defaultLevel,
18
+ rendered: false
19
+ }
20
+ };
21
+ }
22
+ });
23
+ };
@@ -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>
@@ -26,11 +26,14 @@
26
26
  :size="8" v-if="next === choice.value"
27
27
  />
28
28
  </span>
29
- <span class="apos-choice-label-text" v-apos-tooltip.top="choice.tooltip">
29
+ <span class="apos-choice-label-text">
30
30
  {{ $t(choice.label) }}
31
- <InformationIcon
31
+ <AposIndicator
32
32
  v-if="choice.tooltip"
33
- :size="14"
33
+ class="apos-choice-label-info"
34
+ :tooltip="choice.tooltip"
35
+ :icon-size="14"
36
+ icon="information-icon"
34
37
  />
35
38
  </span>
36
39
  </label>
@@ -75,8 +78,8 @@ export default {
75
78
  border-radius: 50%;
76
79
  }
77
80
  }
78
- .information-icon {
81
+ .apos-choice-label-info {
79
82
  position: relative;
80
- top: 2px;
83
+ top: 3px;
81
84
  }
82
85
  </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>