apostrophe 3.58.0 → 3.59.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 (41) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/DEVELOPMENT.md +0 -12
  3. package/modules/@apostrophecms/area/index.js +7 -0
  4. package/modules/@apostrophecms/asset/index.js +0 -7
  5. package/modules/@apostrophecms/asset/lib/webpack/apos/webpack.config.js +4 -8
  6. package/modules/@apostrophecms/asset/lib/webpack/apos/webpack.vue.js +1 -1
  7. package/modules/@apostrophecms/asset/lib/webpack/src/webpack.config.js +1 -7
  8. package/modules/@apostrophecms/doc-type/index.js +3 -1
  9. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +11 -4
  10. package/modules/@apostrophecms/express/index.js +28 -0
  11. package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocErrorsMixin.js +5 -4
  12. package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +11 -8
  13. package/modules/@apostrophecms/modal/ui/apos/mixins/AposModalTabsMixin.js +4 -3
  14. package/modules/@apostrophecms/module/index.js +6 -0
  15. package/modules/@apostrophecms/piece-page-type/index.js +9 -3
  16. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposImageControlDialog.vue +10 -3
  17. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapAnchor.vue +6 -1
  18. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapLink.vue +12 -3
  19. package/modules/@apostrophecms/schema/index.js +105 -51
  20. package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +3 -3
  21. package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +10 -6
  22. package/modules/@apostrophecms/schema/ui/apos/components/AposInputObject.vue +5 -3
  23. package/modules/@apostrophecms/schema/ui/apos/components/AposInputRadio.vue +16 -11
  24. package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +2 -1
  25. package/modules/@apostrophecms/schema/ui/apos/components/AposSubform.vue +3 -3
  26. package/modules/@apostrophecms/schema/ui/apos/lib/conditionalFields.js +76 -71
  27. package/modules/@apostrophecms/schema/ui/apos/logic/AposArrayEditor.js +9 -1
  28. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputArray.js +48 -20
  29. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputDateAndTime.js +10 -1
  30. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputObject.js +8 -3
  31. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRadio.js +3 -2
  32. package/modules/@apostrophecms/schema/ui/apos/logic/AposSchema.js +45 -25
  33. package/modules/@apostrophecms/schema/ui/apos/logic/AposSubform.js +4 -1
  34. package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputConditionalFieldsMixin.js +11 -4
  35. package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputFollowingMixin.js +1 -1
  36. package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputMixin.js +4 -6
  37. package/modules/@apostrophecms/template/index.js +99 -31
  38. package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +5 -2
  39. package/package.json +3 -4
  40. package/test/pieces.js +15 -0
  41. package/test/schemas.js +1700 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,53 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.59.0 (2023-11-03)
4
+
5
+ ### Changes
6
+
7
+ * Webpack warnings about package size during the admin UI build process have been turned off by default. Warnings are still enabled for the public build, where a large bundle can be problematic for SEO.
8
+
9
+ ### Fixes
10
+
11
+ * Apostrophe warns you if you have more than one piece page for the same piece type and you have not overridden `chooseParentPage`
12
+ to help Apostrophe decide which page is suitable as the `_url` of each piece. Beginning with this release, Apostrophe can recognize
13
+ when you have chosen to do this via `extendMethods`, so that you can call `_super()` to fall back to the default implementation without
14
+ receiving this warning. The default implementation still just returns the first page found, but always following the
15
+ `_super()` pattern here opens the door to npm modules that `improve` `@apostrophecms/piece-page` to do something more
16
+ sophisticated by default.
17
+ * `newInstance` always returns a reasonable non-null empty value for area and
18
+ object fields in case the document is inserted without being passed through
19
+ the editor, e.g. in a parked page like the home page. This simplifies
20
+ the new external front feature.
21
+
22
+ ### Adds
23
+
24
+ * An adapter for Astro is under development with support from Michelin.
25
+ Starting with this release, adapters for external fronts, i.e. "back for front"
26
+ frameworks such as Astro, may now be implemented more easily. Apostrophe recognizes the
27
+ `x-requested-with: AposExternalFront` header and the `apos-external-front-key` header.
28
+ If both are present and `apos-external-front-key` matches the `APOS_EXTERNAL_FRONT_KEY`
29
+ environment variable, then Apostrophe returns JSON in place of a normal page response.
30
+ This mechanism is also available for the `render-widget` route.
31
+ * Like `type`, `metaType` is always included in projections. This helps
32
+ ensure that `apos.util.getManagerOf()` can be used on any object returned
33
+ by the Apostrophe APIs.
34
+
35
+ ## 3.58.1 (2023-10-18)
36
+
37
+ ### Security
38
+
39
+ * Update `uploadfs` to guarantee users get a fix for a [potential security vulnerability in `sharp`](https://security.snyk.io/vuln/SNYK-JS-SHARP-5922108).
40
+ This was theoretically exploitable only by users with permission to upload media to Apostrophe
41
+ * Remove the webpack bundle analyzer feature, which had been nonfunctional for some time, to address a harmless npm audit warning
42
+ * Note: there is one remaining `npm audit` warning regarding `postcss`. This is not a true vulnerability because only developers
43
+ with access to the entire codebase can modify styles passed to `postcss` by Apostrophe, but we are working with upstream
44
+ developers to determine the best steps to clear the warning
45
+
46
+ ### Fixes
47
+
48
+ * Automatically add `type` to the projection only if there are no exclusions in the projection. Needed to prevent `Cannot do
49
+ exclusion on field in inclusion projection` error.
50
+
3
51
  ## 3.58.0 (2023-10-12)
4
52
 
5
53
  ### Fixes
package/DEVELOPMENT.md CHANGED
@@ -10,15 +10,3 @@ We generally aim to follow [Vue best practices](https://vuejs.org/v2/style-guide
10
10
  ### UI component styles
11
11
 
12
12
  As a rule, all user interface components should have their styles scoped (using the `scoped` attribute). This helps us write simpler CSS selectors and avoide a certain amount of style "bleed" across components. Global styles, and styles for top level Vue apps (e.g., `TheAposNotifications`), should be in `.scss` files and imported into the import file: `/modules/@apostrophecms/ui/ui/apos/scss/imports.scss`.
13
-
14
- ## Analyzing bundle size
15
-
16
- It is possible to analyze the size of the admin UI webpack bundle:
17
-
18
- ```
19
- APOS_BUNDLE_ANALYZER=1 node app @apostrophecms/asset:build
20
- ```
21
-
22
- This will display a visualization in your browser.
23
-
24
- As of this writing, we are not optimizing the webpack build for production, so expect to see big numbers.
@@ -68,6 +68,13 @@ module.exports = {
68
68
  return manager.loadIfSuitable(req, [ widget ]);
69
69
  }
70
70
  async function render() {
71
+ if (req.aposExternalFront) {
72
+ const result = {
73
+ ...req.data,
74
+ widget
75
+ };
76
+ return result;
77
+ }
71
78
  return self.renderWidget(req, type, widget, options);
72
79
  }
73
80
  }
@@ -253,13 +253,6 @@ module.exports = {
253
253
 
254
254
  await deploy(deployFiles);
255
255
 
256
- if (process.env.APOS_BUNDLE_ANALYZER) {
257
- return new Promise((resolve, reject) => {
258
- // Intentionally never resolve it, so the task never exits
259
- // and the UI stays up
260
- });
261
- }
262
-
263
256
  async function moduleOverrides(modulesDir, source, pnpmPaths) {
264
257
  await fs.remove(modulesDir);
265
258
  await fs.mkdirp(modulesDir);
@@ -4,12 +4,6 @@ const scss = require('./webpack.scss');
4
4
  const vue = require('./webpack.vue');
5
5
  const js = require('./webpack.js');
6
6
 
7
- let BundleAnalyzerPlugin;
8
-
9
- if (process.env.APOS_BUNDLE_ANALYZER) {
10
- BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
11
- }
12
-
13
7
  module.exports = ({
14
8
  importFile,
15
9
  modulesDir,
@@ -30,6 +24,9 @@ module.exports = ({
30
24
 
31
25
  const pnpmModulePath = apos.isPnpm ? [ path.join(apos.selfDir, '../') ] : [];
32
26
  const config = {
27
+ performance: {
28
+ hints: false
29
+ },
33
30
  entry: importFile,
34
31
  // Ensure that the correct version of vue-loader is found
35
32
  context: __dirname,
@@ -84,8 +81,7 @@ module.exports = ({
84
81
  ],
85
82
  symlinks: false
86
83
  },
87
- stats: 'verbose',
88
- plugins: process.env.APOS_BUNDLE_ANALYZER ? [ new BundleAnalyzerPlugin() ] : []
84
+ stats: 'verbose'
89
85
  };
90
86
 
91
87
  return merge(config, ...tasks);
@@ -1,4 +1,4 @@
1
- const VueLoaderPlugin = require('vue-loader/lib/plugin');
1
+ const { VueLoaderPlugin } = require('vue-loader');
2
2
 
3
3
  module.exports = (options, apos) => {
4
4
  return {
@@ -3,12 +3,6 @@ const merge = require('webpack-merge').merge;
3
3
  const scssTask = require('./webpack.scss');
4
4
  const srcBuildNames = [ 'src-build', 'src-es5-build' ];
5
5
 
6
- let BundleAnalyzerPlugin;
7
-
8
- if (process.env.APOS_BUNDLE_ANALYZER) {
9
- BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
10
- }
11
-
12
6
  module.exports = ({
13
7
  importFile,
14
8
  modulesDir,
@@ -99,7 +93,7 @@ module.exports = ({
99
93
  symlinks: false
100
94
  },
101
95
  stats: 'verbose',
102
- plugins: process.env.APOS_BUNDLE_ANALYZER ? [ new BundleAnalyzerPlugin() ] : []
96
+ plugins: []
103
97
  };
104
98
 
105
99
  if (es5) {
@@ -1626,8 +1626,10 @@ module.exports = {
1626
1626
  const remove = [];
1627
1627
 
1628
1628
  // Add type in projection by default
1629
- if (!_.isEmpty(projection)) {
1629
+ const hasExclusion = Object.values(projection).some(value => !value);
1630
+ if (!_.isEmpty(projection) && !hasExclusion) {
1630
1631
  add.push('type');
1632
+ add.push('metaType');
1631
1633
  }
1632
1634
 
1633
1635
  for (const [ key, val ] of Object.entries(projection)) {
@@ -64,7 +64,7 @@
64
64
  :trigger-validation="triggerValidation"
65
65
  :utility-rail="false"
66
66
  :following-values="followingValues('other')"
67
- :conditional-fields="conditionalFields('other')"
67
+ :conditional-fields="conditionalFields"
68
68
  :doc-id="docId"
69
69
  :value="docFields"
70
70
  :server-errors="serverErrors"
@@ -89,7 +89,7 @@
89
89
  :trigger-validation="triggerValidation"
90
90
  :utility-rail="true"
91
91
  :following-values="followingUtils"
92
- :conditional-fields="conditionalFields('utility')"
92
+ :conditional-fields="conditionalFields"
93
93
  :doc-id="docId"
94
94
  :value="docFields"
95
95
  @input="updateDocFields"
@@ -321,7 +321,7 @@ export default {
321
321
  },
322
322
  watch: {
323
323
  'docFields.data.type': {
324
- handler(newVal, oldVal) {
324
+ handler(newVal) {
325
325
  if (this.moduleName !== '@apostrophecms/page') {
326
326
  return;
327
327
  }
@@ -342,6 +342,7 @@ export default {
342
342
  },
343
343
  async mounted() {
344
344
  this.modal.active = true;
345
+ await this.evaluateExternalConditions();
345
346
  // After computed properties become available
346
347
  this.saveMenu = this.computeSaveMenu();
347
348
  this.cancelDescription = {
@@ -349,6 +350,7 @@ export default {
349
350
  type: this.$t(this.moduleOptions.label)
350
351
  };
351
352
  if (this.docId) {
353
+ this.evaluateConditions();
352
354
  await this.loadDoc();
353
355
  try {
354
356
  if (this.manuallyPublished) {
@@ -372,6 +374,8 @@ export default {
372
374
  }
373
375
  this.modal.triggerFocusRefresh++;
374
376
  } else if (this.copyOfId) {
377
+ this.evaluateConditions();
378
+
375
379
  // Because the page or piece manager might give us just a projected,
376
380
  // minimum number of properties otherwise, and because we need to
377
381
  // make sure we use our preferred module to fetch the content
@@ -405,6 +409,7 @@ export default {
405
409
  } else {
406
410
  this.$nextTick(async () => {
407
411
  await this.loadNewInstance();
412
+ this.evaluateConditions();
408
413
  this.modal.triggerFocusRefresh++;
409
414
  });
410
415
  }
@@ -452,7 +457,7 @@ export default {
452
457
  const canEdit = docData._edit || this.moduleOptions.canEdit;
453
458
  this.readOnly = canEdit === false;
454
459
  if (canEdit && !await this.lock(this.getOnePath, this.docId)) {
455
- await this.lockNotAvailable();
460
+ this.lockNotAvailable();
456
461
  return;
457
462
  }
458
463
  } catch {
@@ -679,6 +684,8 @@ export default {
679
684
  ...this.docFields.data,
680
685
  ...value.data
681
686
  };
687
+
688
+ this.evaluateConditions();
682
689
  },
683
690
  getAposSchema(field) {
684
691
  if (field.group.name === 'utility') {
@@ -162,6 +162,7 @@ module.exports = {
162
162
  self.createApp();
163
163
  self.prefix();
164
164
  self.trustProxy();
165
+ self.options.externalFrontKey = process.env.APOS_EXTERNAL_FRONT_KEY || self.options.externalFrontKey;
165
166
  if (self.options.baseUrl && !self.apos.baseUrl) {
166
167
  self.apos.util.error('WARNING: you have baseUrl set as an option to the `@apostrophecms/express` module.');
167
168
  self.apos.util.error('Set it as a global option (a property of the main object passed to apostrophe).');
@@ -249,6 +250,33 @@ module.exports = {
249
250
  url: '/api/v1',
250
251
  middleware: cors()
251
252
  },
253
+ externalFront(req, res, next) {
254
+ if (req.headers['x-requested-with'] !== 'AposExternalFront') {
255
+ return next();
256
+ }
257
+ if ((!self.options.externalFrontKey) || (req.headers['apos-external-front-key'] !== self.options.externalFrontKey)) {
258
+ if (!self.options.externalFrontKey) {
259
+ self.logError('externalFrontNotEnabled', 'An attempt was made to integrate an external front but the externalFrontKey option has not been set on the @apostrophecms/express module');
260
+ } else {
261
+ self.logError('externalFrontKeyInvalid', 'An attempt was made to integrate an external front but the apos-external-front-key header was missing or did not match the externalFrontKey option set on the @apostrophecms/express module');
262
+ }
263
+ return res.status(403).send('forbidden');
264
+ }
265
+ req.aposExternalFront = true;
266
+ res.redirect = function(...args) {
267
+ // The external front end needs to issue the actual redirect,
268
+ // not us
269
+ // Per Express handling of 1 arg versus 2
270
+ const status = args.length > 1 ? args[0] : 302;
271
+ const url = args[args.length - 1];
272
+ return res.send({
273
+ redirect: true,
274
+ url,
275
+ status
276
+ });
277
+ };
278
+ return next();
279
+ },
252
280
  attachUtilityMethods(req, res, next) {
253
281
  // We apply the super pattern variously to res.redirect,
254
282
  // make sure the original version is always available
@@ -4,7 +4,6 @@ export default {
4
4
  data: () => ({
5
5
  fieldErrors: {},
6
6
  errorCount: 0
7
-
8
7
  }),
9
8
  mounted () {
10
9
  this.prepErrors();
@@ -28,17 +27,19 @@ export default {
28
27
  }
29
28
  });
30
29
  }
30
+
31
31
  this.updateErrorCount();
32
32
  },
33
33
  updateErrorCount() {
34
34
  let count = 0;
35
- for (const key in this.fieldErrors) {
36
- for (const errKey in this.fieldErrors[key]) {
37
- if (this.fieldErrors[key][errKey]) {
35
+ for (const group in this.fieldErrors) {
36
+ for (const field in this.fieldErrors[group]) {
37
+ if (this.fieldErrors[group][field]) {
38
38
  count++;
39
39
  }
40
40
  }
41
41
  }
42
+
42
43
  this.errorCount = count;
43
44
  },
44
45
  prepErrors() {
@@ -15,7 +15,9 @@
15
15
  */
16
16
 
17
17
  import { klona } from 'klona';
18
- import { evaluateExternalConditions, conditionalFields } from 'Modules/@apostrophecms/schema/lib/conditionalFields.js';
18
+ import {
19
+ evaluateExternalConditions, getConditionalFields, getConditionTypesObject
20
+ } from 'Modules/@apostrophecms/schema/lib/conditionalFields.js';
19
21
 
20
22
  export default {
21
23
  data() {
@@ -27,7 +29,8 @@ export default {
27
29
  restoreOnly: false,
28
30
  readOnly: false,
29
31
  changed: [],
30
- externalConditionsResults: {}
32
+ externalConditionsResults: getConditionTypesObject(),
33
+ conditionalFields: getConditionTypesObject()
31
34
  };
32
35
  },
33
36
 
@@ -57,10 +60,6 @@ export default {
57
60
  }
58
61
  },
59
62
 
60
- async created() {
61
- await this.evaluateExternalConditions();
62
- },
63
-
64
63
  methods: {
65
64
  // Evaluate the external conditions found in each field
66
65
  // via API calls -made in parallel for performance-
@@ -129,8 +128,8 @@ export default {
129
128
  // the returned object will contain properties only for conditional fields
130
129
  // in that category, although they may be conditional upon fields in either
131
130
  // category.
132
- conditionalFields(followedByCategory) {
133
- return conditionalFields(
131
+ getConditionalFields(followedByCategory) {
132
+ return getConditionalFields(
134
133
  this.schema,
135
134
  this.getFieldsByCategory(followedByCategory),
136
135
  // currentDoc for arrays, docFields for all other editors
@@ -139,6 +138,10 @@ export default {
139
138
  );
140
139
  },
141
140
 
141
+ evaluateConditions() {
142
+ this.conditionalFields = this.getConditionalFields();
143
+ },
144
+
142
145
  // Overridden by components that split the fields into several AposSchemas
143
146
  getFieldValue(name) {
144
147
  return this.docFields.data[name];
@@ -46,14 +46,15 @@ export default {
46
46
  const tabs = [];
47
47
  for (const key in this.groups) {
48
48
  if (key !== 'utility') {
49
- // AposRelationshipEditor does not implement AposEditorMixin with the function conditionalFields
50
- const conditionalFields = this.conditionalFields?.('other') || [];
49
+ // AposRelationshipEditor does not implement AposEditorMixin with the function getConditionalFields
51
50
  const fields = this.groups[key].fields;
52
51
  tabs.push({
53
52
  name: key,
54
53
  label: this.groups[key].label,
55
54
  fields,
56
- isVisible: fields.some(field => conditionalFields[field] !== false)
55
+ isVisible: this.conditionalFields?.if
56
+ ? fields.some(field => this.conditionalFields.if[field] !== false)
57
+ : true
57
58
  });
58
59
  }
59
60
  }
@@ -423,6 +423,12 @@ module.exports = {
423
423
  // for this part of the behavior of sendPage.
424
424
 
425
425
  async renderPage(req, template, data) {
426
+ if (req.aposExternalFront) {
427
+ await self.apos.template.annotateDataForExternalFront(req, template, data);
428
+ self.apos.template.pruneDataForExternalFront(req, template, data);
429
+ // Reply with JSON
430
+ return data;
431
+ }
426
432
  return self.apos.template.renderPageForModule(req, template, data, self);
427
433
  },
428
434
 
@@ -40,7 +40,7 @@ module.exports = {
40
40
  self.enableAddUrlsToPieces();
41
41
  },
42
42
  methods(self) {
43
- return {
43
+ const methods = {
44
44
 
45
45
  // Extend this method for your piece type to call additional
46
46
  // query builders by default.
@@ -212,8 +212,11 @@ module.exports = {
212
212
  // for their design.
213
213
 
214
214
  chooseParentPage(pages, piece) {
215
- if (pages.length > 1) {
216
- self.apos.util.warnDevOnce(`${self.__meta.name}/chooseParentPage`, `Your site has more than one ${self.name} page, but does not override the chooseParentPage method in ${self.__meta.name} to choose the right one for individual ${self.pieces.name}. You should also override filterByIndexPage. for ${self.pieces.name} will point to an arbitrarily chosen page.`);
215
+ // Complain if this method is called with more than one page without an
216
+ // extension to make it smart enough to presumably do something intelligent
217
+ // in that situation. Don't complain though if this is just a call to _super
218
+ if ((self.originalChooseParentPage === self.chooseParentPage) && (pages.length > 1)) {
219
+ self.apos.util.warnDevOnce(`${self.__meta.name}/chooseParentPage`, `Your site has more than one ${self.name} page, but does not extend the chooseParentPage\nmethod in ${self.__meta.name} to choose the right one for individual ${self.pieces.name}. You should also extend filterByIndexPage.\nOtherwise URLs for each ${self.pieces.name} will point to an arbitrarily chosen page.`);
217
220
  }
218
221
  return pages[0];
219
222
  },
@@ -309,6 +312,9 @@ module.exports = {
309
312
  }
310
313
  }
311
314
  };
315
+
316
+ self.originalChooseParentPage = methods.chooseParentPage;
317
+ return methods;
312
318
  },
313
319
  extendMethods(self) {
314
320
  return {
@@ -20,7 +20,8 @@
20
20
  :key="lastSelectionTime"
21
21
  :generation="generation"
22
22
  :following-values="followingValues()"
23
- :conditional-fields="conditionalFields()"
23
+ :conditional-fields="conditionalFields"
24
+ @input="evaluateConditions()"
24
25
  />
25
26
  <footer class="apos-image-control__footer">
26
27
  <AposButton
@@ -29,9 +30,11 @@
29
30
  :modifiers="formModifiers"
30
31
  />
31
32
  <AposButton
32
- type="primary" label="apostrophe:save"
33
- @click="save"
33
+ type="primary"
34
+ label="apostrophe:save"
34
35
  :modifiers="formModifiers"
36
+ :disabled="docFields.hasErrors"
37
+ @click="save"
35
38
  />
36
39
  </footer>
37
40
  </AposContextMenuDialog>
@@ -112,6 +115,10 @@ export default {
112
115
  }
113
116
  }
114
117
  },
118
+ async mounted() {
119
+ await this.evaluateExternalConditions();
120
+ this.evaluateConditions();
121
+ },
115
122
  methods: {
116
123
  cancel() {
117
124
  this.$emit('cancel');
@@ -42,7 +42,8 @@
42
42
  :key="lastSelectionTime"
43
43
  :generation="generation"
44
44
  :following-values="followingValues()"
45
- :conditional-fields="conditionalFields()"
45
+ :conditional-fields="conditionalFields"
46
+ @input="evaluateConditions()"
46
47
  />
47
48
  <footer class="apos-anchor-control__footer">
48
49
  <AposButton
@@ -139,6 +140,10 @@ export default {
139
140
  }
140
141
  }
141
142
  },
143
+ async mounted() {
144
+ await this.evaluateExternalConditions();
145
+ this.evaluateConditions();
146
+ },
142
147
  methods: {
143
148
  removeAnchor() {
144
149
  this.docFields.data = {};
@@ -43,7 +43,8 @@
43
43
  :key="lastSelectionTime"
44
44
  :generation="generation"
45
45
  :following-values="followingValues()"
46
- :conditional-fields="conditionalFields()"
46
+ :conditional-fields="conditionalFields"
47
+ @input="evaluateConditions()"
47
48
  />
48
49
  <footer class="apos-link-control__footer">
49
50
  <AposButton
@@ -52,9 +53,11 @@
52
53
  :modifiers="formModifiers"
53
54
  />
54
55
  <AposButton
55
- type="primary" label="apostrophe:save"
56
- @click="save"
56
+ type="primary"
57
+ label="apostrophe:save"
57
58
  :modifiers="formModifiers"
59
+ :disabled="docFields.hasErrors"
60
+ @click="save"
58
61
  />
59
62
  </footer>
60
63
  </AposContextMenuDialog>
@@ -145,6 +148,7 @@ export default {
145
148
  name: 'href',
146
149
  label: this.$t('apostrophe:url'),
147
150
  type: 'string',
151
+ required: true,
148
152
  if: {
149
153
  linkTo: '_url'
150
154
  }
@@ -201,6 +205,10 @@ export default {
201
205
  }
202
206
  }
203
207
  },
208
+ async mounted() {
209
+ await this.evaluateExternalConditions();
210
+ this.evaluateConditions();
211
+ },
204
212
  methods: {
205
213
  removeLink() {
206
214
  this.docFields.data = {};
@@ -211,6 +219,7 @@ export default {
211
219
  if (this.hasSelection) {
212
220
  this.active = !this.active;
213
221
  this.populateFields();
222
+ this.evaluateConditions();
214
223
  }
215
224
  },
216
225
  close() {