apostrophe 3.17.0 → 3.18.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 (97) hide show
  1. package/.editorconfig +3 -0
  2. package/.eslintrc +4 -3
  3. package/.github/workflows/main.yml +2 -2
  4. package/.stylelintrc +12 -2
  5. package/CHANGELOG.md +34 -2
  6. package/defaults.js +2 -2
  7. package/index.js +124 -33
  8. package/lib/escape-host.js +8 -0
  9. package/lib/mongodb-connect.js +55 -0
  10. package/lib/opentelemetry.js +144 -0
  11. package/modules/@apostrophecms/area/ui/apos/apps/AposAreas.js +2 -0
  12. package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +20 -8
  13. package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +10 -0
  14. package/modules/@apostrophecms/asset/lib/globalIcons.js +1 -0
  15. package/modules/@apostrophecms/attachment/index.js +81 -29
  16. package/modules/@apostrophecms/db/index.js +7 -10
  17. package/modules/@apostrophecms/doc/index.js +138 -23
  18. package/modules/@apostrophecms/doc-type/index.js +162 -63
  19. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +39 -1
  20. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +11 -1
  21. package/modules/@apostrophecms/email/index.js +1 -1
  22. package/modules/@apostrophecms/express/index.js +2 -2
  23. package/modules/@apostrophecms/http/index.js +2 -1
  24. package/modules/@apostrophecms/i18n/i18n/en.json +10 -0
  25. package/modules/@apostrophecms/i18n/i18n/es.json +7 -0
  26. package/modules/@apostrophecms/i18n/i18n/pt-BR.json +7 -0
  27. package/modules/@apostrophecms/i18n/i18n/sk.json +7 -0
  28. package/modules/@apostrophecms/image/index.js +182 -1
  29. package/modules/@apostrophecms/image/ui/apos/apps/AposImageRelationshipQueryFilter.js +13 -0
  30. package/modules/@apostrophecms/image/ui/apos/components/AposImageCropper.vue +460 -0
  31. package/modules/@apostrophecms/image/ui/apos/components/AposImageRelationshipEditor.vue +510 -0
  32. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +5 -1
  33. package/modules/@apostrophecms/image/ui/apos/lib/aspectRatios.js +26 -0
  34. package/modules/@apostrophecms/image-widget/views/widget.html +5 -2
  35. package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +45 -1
  36. package/modules/@apostrophecms/module/index.js +98 -17
  37. package/modules/@apostrophecms/module/lib/events.js +46 -11
  38. package/modules/@apostrophecms/page/index.js +55 -22
  39. package/modules/@apostrophecms/piece-page-type/index.js +1 -0
  40. package/modules/@apostrophecms/piece-type/index.js +13 -4
  41. package/modules/@apostrophecms/piece-type/ui/apos/components/AposRelationshipEditor.vue +2 -2
  42. package/modules/@apostrophecms/rich-text-widget/index.js +1 -3
  43. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +4 -0
  44. package/modules/@apostrophecms/schema/index.js +79 -73
  45. package/modules/@apostrophecms/schema/ui/apos/components/AposInputArea.vue +10 -0
  46. package/modules/@apostrophecms/schema/ui/apos/components/AposInputObject.vue +22 -3
  47. package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +72 -36
  48. package/modules/@apostrophecms/schema/ui/apos/components/AposInputSelect.vue +7 -26
  49. package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +8 -0
  50. package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +45 -15
  51. package/modules/@apostrophecms/task/index.js +106 -52
  52. package/modules/@apostrophecms/template/index.js +111 -76
  53. package/modules/@apostrophecms/template/lib/custom-tags/component.js +42 -22
  54. package/modules/@apostrophecms/ui/ui/apos/components/AposSelect.vue +61 -0
  55. package/modules/@apostrophecms/ui/ui/apos/components/AposSlat.vue +46 -11
  56. package/modules/@apostrophecms/ui/ui/apos/components/AposSlatList.vue +10 -0
  57. package/modules/@apostrophecms/ui/ui/apos/components/AposTreeHeader.vue +2 -22
  58. package/modules/@apostrophecms/ui/ui/apos/utils/index.js +9 -0
  59. package/modules/@apostrophecms/widget-type/index.js +2 -23
  60. package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidget.vue +1 -1
  61. package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +20 -1
  62. package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +0 -9
  63. package/package.json +16 -12
  64. package/scripts/lint-i18n.js +2 -2
  65. package/test/assets.js +2 -1
  66. package/test/attachments.js +119 -26
  67. package/test/bundle.js +1 -1
  68. package/test/content-i18n.js +6 -6
  69. package/test/docs.js +244 -4
  70. package/test/draft-published.js +41 -41
  71. package/test/express.js +1 -1
  72. package/test/http.js +2 -2
  73. package/test/images.js +94 -4
  74. package/test/job.js +1 -1
  75. package/test/locks.js +1 -1
  76. package/test/middleware-and-route-order.js +3 -3
  77. package/test/pages-public-api.js +48 -4
  78. package/test/pages-rest.js +20 -20
  79. package/test/pages.js +377 -11
  80. package/test/parked-pages.js +1 -1
  81. package/test/permissions.js +10 -10
  82. package/test/pieces-public-api.js +130 -6
  83. package/test/pieces.js +247 -60
  84. package/test/recursionGuard.js +6 -6
  85. package/test/restApiRoutes.js +6 -6
  86. package/test/schemaBuilders.js +7 -7
  87. package/test/schemas.js +59 -59
  88. package/test/search.js +3 -3
  89. package/test/soft-redirects.js +13 -13
  90. package/test/static-i18n.js +1 -1
  91. package/test/templates.js +10 -10
  92. package/test/urls.js +2 -2
  93. package/test/users.js +21 -21
  94. package/test/utils.js +13 -13
  95. package/test/widgets.js +2 -2
  96. package/test-lib/util.js +2 -5
  97. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidget.vue +0 -26
@@ -25,6 +25,7 @@
25
25
  // available in the browser for use in the Vue-based admin UI when a user is
26
26
  // logged in.
27
27
 
28
+ const { SemanticAttributes } = require('@opentelemetry/semantic-conventions');
28
29
  const _ = require('lodash');
29
30
 
30
31
  module.exports = {
@@ -444,10 +445,42 @@ module.exports = {
444
445
  // that point.
445
446
 
446
447
  async sendPage(req, template, data) {
447
- await self.apos.page.emit('beforeSend', req);
448
- await self.apos.area.loadDeferredWidgets(req);
449
- req.res.send(
450
- await self.apos.template.renderPageForModule(req, template, data, self)
448
+ const telemetry = self.apos.telemetry;
449
+ const spanName = `${self.__meta.name}:sendPage`;
450
+ await telemetry.startActiveSpan(spanName, async (span) => {
451
+ span.setAttribute(SemanticAttributes.CODE_FUNCTION, 'sendPage');
452
+ span.setAttribute(SemanticAttributes.CODE_NAMESPACE, self.__meta.name);
453
+ span.setAttribute(telemetry.Attributes.TEMPLATE, template);
454
+
455
+ try {
456
+ await self.apos.page.emit('beforeSend', req);
457
+ await self.apos.area.loadDeferredWidgets(req);
458
+ req.res.send(
459
+ await self.apos.template.renderPageForModule(req, template, data, self)
460
+ );
461
+ span.setStatus({ code: telemetry.api.SpanStatusCode.OK });
462
+ } catch (err) {
463
+ telemetry.handleError(span, err);
464
+ throw err;
465
+ } finally {
466
+ span.end();
467
+ }
468
+ });
469
+ },
470
+
471
+ // A cookie in session doesn't mean we can't cache, nor an empty flash or passport object.
472
+ // Other session properties must be assumed to be specific to the user, with a possible
473
+ // impact on the response, and thus mean this request must not be cached.
474
+ // Same rule as in [express-cache-on-demand](https://github.com/apostrophecms/express-cache-on-demand/blob/master/index.js#L102)
475
+ isSafeToCache(req) {
476
+ if (req.user) {
477
+ return false;
478
+ }
479
+
480
+ return Object.entries(req.session).every(([ key, val ]) =>
481
+ key === 'cookie' || (
482
+ (key === 'flash' || key === 'passport') && _.isEmpty(val)
483
+ )
451
484
  );
452
485
  },
453
486
 
@@ -457,21 +490,50 @@ module.exports = {
457
490
  return;
458
491
  }
459
492
 
460
- // A cookie in session doesn't mean we can't cache, nor an empty flash or passport object.
461
- // Other session properties must be assumed to be specific to the user, with a possible
462
- // impact on the response, and thus mean this request must not be cached.
463
- // Same rule as in [express-cache-on-demand](https://github.com/apostrophecms/express-cache-on-demand/blob/master/index.js#L102)
464
- const isSessionClearForCaching = Object.entries(req.session).every(([ key, val ]) =>
465
- key === 'cookie' || (
466
- (key === 'flash' || key === 'passport') && _.isEmpty(val)
467
- )
468
- );
469
- const isSafeToCache = !req.user && isSessionClearForCaching;
470
- const cacheControlValue = isSafeToCache ? `max-age=${maxAge}` : 'no-store';
493
+ const cacheControlValue = self.isSafeToCache(req) ? `max-age=${maxAge}` : 'no-store';
471
494
 
472
495
  req.res.header('Cache-Control', cacheControlValue);
473
496
  },
474
497
 
498
+ generateETagParts(req, doc) {
499
+ const context = doc || req.data.piece || req.data.page;
500
+
501
+ if (!context || !context.cacheInvalidatedAt) {
502
+ return null;
503
+ }
504
+
505
+ const releaseId = self.apos.asset.getReleaseId();
506
+ const cacheInvalidatedAtTimestamp = (new Date(context.cacheInvalidatedAt)).getTime().toString();
507
+
508
+ return [ releaseId, cacheInvalidatedAtTimestamp ];
509
+ },
510
+
511
+ setETag(req, eTagParts) {
512
+ req.res.header('ETag', eTagParts.join(':'));
513
+ },
514
+
515
+ checkETag(req, doc, maxAge) {
516
+ const eTagParts = self.generateETagParts(req, doc);
517
+
518
+ if (!eTagParts || !self.isSafeToCache(req)) {
519
+ return false;
520
+ }
521
+
522
+ const clientETagParts = req.headers['if-none-match'] ? req.headers['if-none-match'].split(':') : [];
523
+ const doesETagMatch = clientETagParts[0] === eTagParts[0] && clientETagParts[1] === eTagParts[1];
524
+
525
+ const now = Date.now();
526
+ const clientETagAge = (now - clientETagParts[2]) / 1000;
527
+
528
+ if (!doesETagMatch || clientETagAge > maxAge) {
529
+ self.setETag(req, [ ...eTagParts, now ]);
530
+ return false;
531
+ }
532
+
533
+ self.setETag(req, clientETagParts);
534
+ return true;
535
+ },
536
+
475
537
  // Call from init once if this module implements the `getBrowserData` method.
476
538
  // The data returned by `getBrowserData(req)` will then be available on
477
539
  // `apos.modules['your-module-name']` in the browser.
@@ -649,10 +711,29 @@ module.exports = {
649
711
  // @apostrophecms/db:reset which
650
712
  // must run before most modules are awake
651
713
  if (self.apos.argv._[0] === `${self.__meta.name}:${name}`) {
652
- await info.task(self.apos.argv);
714
+ const telemetry = self.apos.telemetry;
715
+ const spanName = `task:${self.__meta.name}:${name}`;
716
+ // only this span can be sent to the backend, attach to the ROOT
717
+ await telemetry.startActiveSpan(
718
+ spanName,
719
+ async (span) => {
720
+ span.setAttribute(SemanticAttributes.CODE_FUNCTION, 'executeAfterModuleInitTask');
721
+ span.setAttribute(SemanticAttributes.CODE_NAMESPACE, '@apostrophecms/module');
722
+ span.setAttribute(telemetry.Attributes.TARGET_NAMESPACE, self.__meta.name);
723
+ span.setAttribute(telemetry.Attributes.TARGET_FUNCTION, name);
724
+ try {
725
+ await info.task(self.apos.argv);
726
+ span.setStatus({ code: telemetry.api.SpanStatusCode.OK });
727
+ } catch (err) {
728
+ telemetry.handleError(span, err);
729
+ throw err;
730
+ } finally {
731
+ span.end();
732
+ }
733
+ });
653
734
  // In most cases we exit after running a task
654
735
  if (info.exitAfter !== false) {
655
- process.exit(0);
736
+ await self.apos._exit();
656
737
  } else {
657
738
  // Provision for @apostrophecms/db:reset which should be
658
739
  // followed by normal initialization so all the collections
@@ -1,4 +1,5 @@
1
1
  const _ = require('lodash');
2
+ const { SemanticAttributes } = require('@opentelemetry/semantic-conventions');
2
3
 
3
4
  module.exports = function(self) {
4
5
 
@@ -38,18 +39,52 @@ module.exports = function(self) {
38
39
  }
39
40
  ];
40
41
 
41
- for (const entry of chain) {
42
- const handlers = self.apos.eventHandlers[entry.name] && self.apos.eventHandlers[entry.name][name];
43
- if (handlers) {
44
- for (const handler of handlers) {
45
- const module = self.apos.modules[handler.moduleName];
46
- const fn = module.compiledHandlers[entry.name][name][handler.handlerName];
47
- // Although we have `self` it can't hurt to
48
- // supply the correct `this`
49
- await fn.apply(module, args);
42
+ const telemetry = self.apos.telemetry;
43
+
44
+ // Create the "outer" span
45
+ const moduleName = (self.__meta && self.__meta.name) || 'apostrophe';
46
+ const spanEmitName = `event:${moduleName}:${name}`;
47
+ await telemetry.startActiveSpan(spanEmitName, async (spanEmit) => {
48
+ spanEmit.setAttribute(SemanticAttributes.CODE_FUNCTION, 'emit');
49
+ spanEmit.setAttribute(SemanticAttributes.CODE_NAMESPACE, moduleName);
50
+ spanEmit.setAttribute(telemetry.Attributes.EVENT_MODULE, moduleName);
51
+ spanEmit.setAttribute(telemetry.Attributes.EVENT_NAME, name);
52
+
53
+ for (const entry of chain) {
54
+ const handlers = self.apos.eventHandlers[entry.name] && self.apos.eventHandlers[entry.name][name];
55
+ if (handlers) {
56
+ for (const handler of handlers) {
57
+
58
+ // Create an active "inner" span for each handler using the parent as a context
59
+ const spanHandlerName = spanEmitName + `:handler:${handler.moduleName}:${handler.handlerName}`;
60
+ await telemetry.startActiveSpan(spanHandlerName, async (spanHandler) => {
61
+ spanHandler.setAttribute(SemanticAttributes.CODE_FUNCTION, handler.handlerName);
62
+ spanHandler.setAttribute(SemanticAttributes.CODE_NAMESPACE, handler.moduleName);
63
+ spanHandler.setAttribute(telemetry.Attributes.EVENT_MODULE, moduleName);
64
+ spanHandler.setAttribute(telemetry.Attributes.EVENT_NAME, name);
65
+
66
+ try {
67
+ const module = self.apos.modules[handler.moduleName];
68
+ const fn = module.compiledHandlers[entry.name][name][handler.handlerName];
69
+ // Although we have `self` it can't hurt to
70
+ // supply the correct `this`
71
+ await fn.apply(module, args);
72
+ spanHandler.setStatus({ code: telemetry.api.SpanStatusCode.OK });
73
+ spanHandler.end();
74
+ } catch (err) {
75
+ telemetry.handleError(spanHandler, err);
76
+ // Be sure to close the parent span as well
77
+ spanHandler.end();
78
+ spanEmit.end();
79
+ throw err;
80
+ }
81
+ }, spanEmit);
82
+ }
50
83
  }
51
84
  }
52
- }
85
+
86
+ spanEmit.end();
87
+ });
53
88
  },
54
89
 
55
90
  // You don't need to call this. It is called for you
@@ -95,7 +130,7 @@ module.exports = function(self) {
95
130
  self.apos.eventHandlers[moduleName] = self.apos.eventHandlers[moduleName] || {};
96
131
  const eh = self.apos.eventHandlers[moduleName];
97
132
  eh[eventName] = eh[eventName] || [];
98
- if (_.find(eh[eventName], function(item) {
133
+ if (_.find(eh[eventName], function (item) {
99
134
  return (item.moduleName === self.__meta.name) && (item.handlerName === handlerName);
100
135
  })) {
101
136
  // The "event name and method name must differ" rule helps
@@ -1,6 +1,7 @@
1
1
  const _ = require('lodash');
2
2
  const path = require('path');
3
3
  const { klona } = require('klona');
4
+ const { SemanticAttributes } = require('@opentelemetry/semantic-conventions');
4
5
  const expressCacheOnDemand = require('express-cache-on-demand')();
5
6
 
6
7
  module.exports = {
@@ -119,7 +120,7 @@ module.exports = {
119
120
  project: self.getAllProjection()
120
121
  }).toObject();
121
122
 
122
- if (self.options.cache && self.options.cache.api) {
123
+ if (self.options.cache && self.options.cache.api && self.options.cache.api.maxAge) {
123
124
  self.setMaxAge(req, self.options.cache.api.maxAge);
124
125
  }
125
126
 
@@ -142,7 +143,7 @@ module.exports = {
142
143
  } else {
143
144
  const result = await self.getRestQuery(req).and({ level: 0 }).toObject();
144
145
 
145
- if (self.options.cache && self.options.cache.api) {
146
+ if (self.options.cache && self.options.cache.api && self.options.cache.api.maxAge) {
146
147
  self.setMaxAge(req, self.options.cache.api.maxAge);
147
148
  }
148
149
 
@@ -178,8 +179,14 @@ module.exports = {
178
179
  const criteria = self.getIdCriteria(_id);
179
180
  const result = await self.getRestQuery(req).and(criteria).toObject();
180
181
 
181
- if (self.options.cache && self.options.cache.api) {
182
- self.setMaxAge(req, self.options.cache.api.maxAge);
182
+ if (self.options.cache && self.options.cache.api && self.options.cache.api.maxAge) {
183
+ const { maxAge } = self.options.cache.api;
184
+
185
+ if (!self.options.cache.api.etags) {
186
+ self.setMaxAge(req, maxAge);
187
+ } else if (self.checkETag(req, result, maxAge)) {
188
+ return {};
189
+ }
183
190
  }
184
191
 
185
192
  if (!result) {
@@ -1404,6 +1411,18 @@ database.`);
1404
1411
  } catch (err) {
1405
1412
  return await self.serve500Error(req, err);
1406
1413
  }
1414
+
1415
+ if (self.options.cache && self.options.cache.page && self.options.cache.page.maxAge) {
1416
+ const { maxAge } = self.options.cache.page;
1417
+
1418
+ if (!self.options.cache.page.etags) {
1419
+ self.setMaxAge(req, maxAge);
1420
+ } else if (self.checkETag(req, undefined, maxAge)) {
1421
+ // Stop there and send a 304 status code; the cached response will be used
1422
+ return res.sendStatus(304);
1423
+ }
1424
+ }
1425
+
1407
1426
  try {
1408
1427
  await self.serveDeliver(req, null);
1409
1428
  } catch (err) {
@@ -1415,24 +1434,35 @@ database.`);
1415
1434
  // of course wins, followed by the parent "folder," and so on up to the
1416
1435
  // home page.
1417
1436
  async serveGetPage(req) {
1418
- req.slug = req.params[0];
1419
- self.normalizeSlug(req);
1420
- // Had to change the URL, so redirect to it. TODO: this
1421
- // contains an assumption that we are mounted at /
1422
- if (req.slug !== req.params[0]) {
1423
- req.redirect = req.slug;
1424
- }
1425
- const builders = self.getServePageBuilders();
1426
- const query = self.find(req);
1427
- query.applyBuilders(builders);
1428
- self.matchPageAndPrefixes(query, req.slug);
1429
- await self.emit('serveQuery', query);
1430
- req.data.bestPage = await query.toObject();
1431
- self.evaluatePageMatch(req);
1437
+ const spanName = `${self.__meta.name}:serveGetPage`;
1438
+ await self.apos.telemetry.startActiveSpan(spanName, async (span) => {
1439
+ span.setAttribute(SemanticAttributes.CODE_FUNCTION, 'serveGetPage');
1440
+ span.setAttribute(SemanticAttributes.CODE_NAMESPACE, self.__meta.name);
1432
1441
 
1433
- if (self.options.cache && self.options.cache.page) {
1434
- self.setMaxAge(req, self.options.cache.page.maxAge);
1435
- }
1442
+ try {
1443
+ req.slug = req.params[0];
1444
+ self.normalizeSlug(req);
1445
+ // Had to change the URL, so redirect to it. TODO: this
1446
+ // contains an assumption that we are mounted at /
1447
+ if (req.slug !== req.params[0]) {
1448
+ req.redirect = req.slug;
1449
+ }
1450
+ const builders = self.getServePageBuilders();
1451
+ const query = self.find(req);
1452
+ query.applyBuilders(builders);
1453
+ self.matchPageAndPrefixes(query, req.slug);
1454
+ await self.emit('serveQuery', query);
1455
+ req.data.bestPage = await query.toObject();
1456
+ self.evaluatePageMatch(req);
1457
+
1458
+ span.setStatus({ code: self.apos.telemetry.api.SpanStatusCode.OK });
1459
+ } catch (err) {
1460
+ self.apos.telemetry.handleError(span, err);
1461
+ throw err;
1462
+ } finally {
1463
+ span.end();
1464
+ }
1465
+ });
1436
1466
  },
1437
1467
  // Normalize req.slug to account for unneeded trailing whitespace,
1438
1468
  // trailing slashes other than the root, and double slash based open
@@ -2108,7 +2138,10 @@ database.`);
2108
2138
  _id: null
2109
2139
  });
2110
2140
  } else {
2111
- query.project(self.options.publicApiProjection);
2141
+ query.project({
2142
+ ...self.options.publicApiProjection,
2143
+ cacheInvalidatedAt: 1
2144
+ });
2112
2145
  }
2113
2146
  }
2114
2147
  return query;
@@ -241,6 +241,7 @@ module.exports = {
241
241
  if (parentPage) {
242
242
  piece._url = self.buildUrl(req, parentPage, piece);
243
243
  piece._parentUrl = parentPage._url;
244
+ piece._parentSlug = parentPage.slug;
244
245
  }
245
246
  });
246
247
  },
@@ -201,7 +201,7 @@ module.exports = {
201
201
  result.counts = query.get('countsResults');
202
202
  }
203
203
 
204
- if (self.options.cache && self.options.cache.api) {
204
+ if (self.options.cache && self.options.cache.api && self.options.cache.api.maxAge) {
205
205
  self.setMaxAge(req, self.options.cache.api.maxAge);
206
206
  }
207
207
 
@@ -215,8 +215,14 @@ module.exports = {
215
215
  self.publicApiCheck(req);
216
216
  const doc = await self.getRestQuery(req).and({ _id }).toObject();
217
217
 
218
- if (self.options.cache && self.options.cache.api) {
219
- self.setMaxAge(req, self.options.cache.api.maxAge);
218
+ if (self.options.cache && self.options.cache.api && self.options.cache.api.maxAge) {
219
+ const { maxAge } = self.options.cache.api;
220
+
221
+ if (!self.options.cache.api.etags) {
222
+ self.setMaxAge(req, maxAge);
223
+ } else if (self.checkETag(req, doc, maxAge)) {
224
+ return {};
225
+ }
220
226
  }
221
227
 
222
228
  if (!doc) {
@@ -917,7 +923,10 @@ module.exports = {
917
923
  _id: null
918
924
  });
919
925
  } else if (!query.state.project) {
920
- query.project(self.options.publicApiProjection);
926
+ query.project({
927
+ ...self.options.publicApiProjection,
928
+ cacheInvalidatedAt: 1
929
+ });
921
930
  }
922
931
  }
923
932
  return query;
@@ -72,8 +72,8 @@ export default {
72
72
  original: this.value,
73
73
  docFields: {
74
74
  data: {
75
- ...((this.value != null) ? this.value :
76
- Object.fromEntries(
75
+ ...((this.value != null) ? this.value
76
+ : Object.fromEntries(
77
77
  this.schema.map(field =>
78
78
  [ field.name, (field.def !== undefined) ? klona(field.def) : null ]
79
79
  )
@@ -44,8 +44,7 @@ module.exports = {
44
44
  },
45
45
  defaultOptions: {},
46
46
  components: {
47
- widgetEditor: 'AposRichTextWidgetEditor',
48
- widget: 'AposRichTextWidget'
47
+ widgetEditor: 'AposRichTextWidgetEditor'
49
48
  },
50
49
  editorTools: {
51
50
  styles: {
@@ -415,7 +414,6 @@ module.exports = {
415
414
 
416
415
  const finalData = {
417
416
  ...initialData,
418
- components: self.options.components,
419
417
  tools: self.options.editorTools,
420
418
  defaultOptions: self.options.defaultOptions,
421
419
  tiptapTextCommands: self.options.tiptapTextCommands,
@@ -72,6 +72,10 @@ export default {
72
72
  default() {
73
73
  return null;
74
74
  }
75
+ },
76
+ focused: {
77
+ type: Boolean,
78
+ default: false
75
79
  }
76
80
  },
77
81
  emits: [ 'update' ],