apostrophe 4.28.0 → 4.29.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 (88) hide show
  1. package/CHANGELOG.md +33 -3
  2. package/README.md +142 -0
  3. package/defaults.js +1 -0
  4. package/lib/safe-json-script.js +27 -0
  5. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarLocale.vue +1 -1
  6. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +1 -0
  7. package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +3 -5
  8. package/modules/@apostrophecms/area/ui/apos/components/AposBreadcrumbOperations.vue +13 -1
  9. package/modules/@apostrophecms/asset/lib/globalIcons.js +3 -0
  10. package/modules/@apostrophecms/attachment/index.js +43 -1
  11. package/modules/@apostrophecms/color-field/index.js +7 -1
  12. package/modules/@apostrophecms/doc/index.js +11 -1
  13. package/modules/@apostrophecms/doc-type/index.js +165 -32
  14. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +1 -1
  15. package/modules/@apostrophecms/doc-type/ui/apos/logic/AposDocContextMenu.js +104 -59
  16. package/modules/@apostrophecms/file/index.js +109 -9
  17. package/modules/@apostrophecms/i18n/i18n/de.json +0 -2
  18. package/modules/@apostrophecms/i18n/i18n/en.json +40 -1
  19. package/modules/@apostrophecms/i18n/i18n/es.json +0 -1
  20. package/modules/@apostrophecms/i18n/i18n/fr.json +0 -1
  21. package/modules/@apostrophecms/i18n/i18n/it.json +0 -1
  22. package/modules/@apostrophecms/i18n/i18n/pt-BR.json +0 -1
  23. package/modules/@apostrophecms/i18n/i18n/sk.json +0 -1
  24. package/modules/@apostrophecms/i18n/ui/apos/apps/AposI18nBatchReporting.js +18 -1
  25. package/modules/@apostrophecms/i18n/ui/apos/apps/AposI18nLocalizeActions.js +50 -0
  26. package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +56 -13
  27. package/modules/@apostrophecms/image/ui/apos/components/AposImageRelationshipEditor.vue +8 -2
  28. package/modules/@apostrophecms/layout-column-widget/index.js +156 -163
  29. package/modules/@apostrophecms/layout-widget/index.js +7 -2
  30. package/modules/@apostrophecms/layout-widget/ui/apos/components/AposAreaLayoutEditor.vue +6 -11
  31. package/modules/@apostrophecms/layout-widget/ui/apos/components/AposGridColumn.vue +3 -5
  32. package/modules/@apostrophecms/layout-widget/ui/apos/components/AposGridLayout.vue +4 -4
  33. package/modules/@apostrophecms/layout-widget/ui/apos/components/AposGridManager.vue +0 -16
  34. package/modules/@apostrophecms/layout-widget/ui/apos/lib/grid-state.mjs +7 -27
  35. package/modules/@apostrophecms/layout-widget/views/column.html +7 -9
  36. package/modules/@apostrophecms/login/index.js +39 -40
  37. package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +17 -2
  38. package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +3 -2
  39. package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +1 -0
  40. package/modules/@apostrophecms/page/index.js +2 -0
  41. package/modules/@apostrophecms/piece-type/index.js +3 -1
  42. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +1 -0
  43. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +5 -0
  44. package/modules/@apostrophecms/recently-edited/index.js +831 -0
  45. package/modules/@apostrophecms/recently-edited/ui/apos/components/AposCellTitle.vue +54 -0
  46. package/modules/@apostrophecms/recently-edited/ui/apos/components/AposRecentlyEditedCombo.vue +454 -0
  47. package/modules/@apostrophecms/recently-edited/ui/apos/components/AposRecentlyEditedFilterTag.vue +75 -0
  48. package/modules/@apostrophecms/recently-edited/ui/apos/components/AposRecentlyEditedFilters.vue +287 -0
  49. package/modules/@apostrophecms/recently-edited/ui/apos/components/AposRecentlyEditedIcon.vue +16 -0
  50. package/modules/@apostrophecms/recently-edited/ui/apos/components/AposRecentlyEditedManager.vue +346 -0
  51. package/modules/@apostrophecms/recently-edited/ui/apos/composables/useRecentlyEditedBatch.js +193 -0
  52. package/modules/@apostrophecms/recently-edited/ui/apos/composables/useRecentlyEditedData.js +276 -0
  53. package/modules/@apostrophecms/recently-edited/ui/apos/composables/useRecentlyEditedFetch.js +199 -0
  54. package/modules/@apostrophecms/recently-edited/ui/apos/composables/useRecentlyEditedFilters.js +100 -0
  55. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRelationship.js +8 -4
  56. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputWrapper.js +1 -1
  57. package/modules/@apostrophecms/styles/index.js +10 -0
  58. package/modules/@apostrophecms/styles/lib/apiRoutes.js +6 -0
  59. package/modules/@apostrophecms/styles/lib/handlers.js +5 -0
  60. package/modules/@apostrophecms/styles/lib/methods.js +9 -3
  61. package/modules/@apostrophecms/styles/lib/presets.js +119 -0
  62. package/modules/@apostrophecms/styles/ui/apos/components/TheAposStyles.vue +3 -8
  63. package/modules/@apostrophecms/styles/ui/apos/composables/AposStyles.js +1 -3
  64. package/modules/@apostrophecms/styles/ui/apos/render-factory.js +29 -0
  65. package/modules/@apostrophecms/styles/ui/apos/universal/backgroundHelpers.mjs +140 -0
  66. package/modules/@apostrophecms/styles/ui/apos/universal/customRules.mjs +105 -0
  67. package/modules/@apostrophecms/styles/ui/apos/universal/render.mjs +195 -15
  68. package/modules/@apostrophecms/template/index.js +22 -6
  69. package/modules/@apostrophecms/ui/ui/apos/components/AposCellContextMenu.vue +2 -0
  70. package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenu.vue +18 -4
  71. package/modules/@apostrophecms/ui/ui/apos/composables/useInfiniteScroll.js +91 -0
  72. package/modules/@apostrophecms/ui/ui/apos/scss/global/_theme.scss +1 -0
  73. package/modules/@apostrophecms/ui/ui/apos/stores/modal.js +5 -2
  74. package/modules/@apostrophecms/ui/ui/apos/utils/index.js +9 -0
  75. package/modules/@apostrophecms/url/index.js +38 -4
  76. package/modules/@apostrophecms/widget-type/index.js +22 -6
  77. package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +8 -4
  78. package/package.json +19 -19
  79. package/test/files.js +129 -0
  80. package/test/layout-widget-migration.js +719 -0
  81. package/test/login-requirements.js +1 -1
  82. package/test/pieces-public-api.js +80 -0
  83. package/test/pieces.js +25 -0
  84. package/test/recently-edited.js +2311 -0
  85. package/test/schemas.js +39 -3
  86. package/test/static-build.js +642 -0
  87. package/test/styles.js +2569 -0
  88. package/modules/@apostrophecms/layout-widget/ui/apos/components/AposLayoutColControlDialog.vue +0 -171
@@ -637,7 +637,7 @@ describe('Expired Token Deletion', function() {
637
637
  const jar = apos.http.jar();
638
638
 
639
639
  // establish session
640
- let page = await apos.http.get(
640
+ await apos.http.get(
641
641
  '/',
642
642
  {
643
643
  jar
@@ -118,6 +118,86 @@ describe('Pieces Public API', function() {
118
118
  assert(!response.results[0].foo);
119
119
  });
120
120
 
121
+ it('should not leak distinct values of non-projected fields via ?choices=', async function() {
122
+ apos.thing.options.publicApiProjection = {
123
+ title: 1,
124
+ _url: 1
125
+ };
126
+ const response = await apos.http.get('/api/v1/thing?choices=title,foo');
127
+ assert(response);
128
+ assert(response.choices);
129
+ // title IS in the public API projection, so its choices are allowed
130
+ assert(response.choices.title);
131
+ assert(response.choices.title.some(c => c.label === 'hello'));
132
+ // foo is NOT in the public API projection: its distinct values
133
+ // must not be exposed to anonymous callers
134
+ assert(
135
+ !response.choices.foo || response.choices.foo.length === 0,
136
+ 'choices for non-projected field "foo" must not be exposed publicly'
137
+ );
138
+ });
139
+
140
+ it('should not leak distinct values of non-projected fields via ?counts=', async function() {
141
+ apos.thing.options.publicApiProjection = {
142
+ title: 1,
143
+ _url: 1
144
+ };
145
+ const response = await apos.http.get('/api/v1/thing?counts=title,foo');
146
+ assert(response);
147
+ assert(response.counts);
148
+ assert(response.counts.title);
149
+ assert(
150
+ !response.counts.foo || response.counts.foo.length === 0,
151
+ 'counts for non-projected field "foo" must not be exposed publicly'
152
+ );
153
+ });
154
+
155
+ it('should not leak non-projected fields via dot-notated ?choices= filter names', async function() {
156
+ apos.thing.options.publicApiProjection = {
157
+ title: 1,
158
+ _url: 1
159
+ };
160
+ // A dot-notated filter whose top-level segment is not projected
161
+ // must be blocked the same way the plain field name is.
162
+ const response = await apos.http.get('/api/v1/thing?choices=foo.bar');
163
+ assert(response);
164
+ assert(
165
+ !response.choices || !response.choices['foo.bar'] || response.choices['foo.bar'].length === 0,
166
+ 'choices for dot-notated non-projected field "foo.bar" must not be exposed publicly'
167
+ );
168
+ });
169
+
170
+ it('should still expose choices for non-projected fields to authenticated API users', async function() {
171
+ apos.thing.options.publicApiProjection = {
172
+ title: 1,
173
+ _url: 1
174
+ };
175
+ const req = apos.task.getReq();
176
+ // Simulate a full-API caller: canAccessApi is true, no projection applied
177
+ const query = apos.thing.find(req).choices([ 'foo' ]);
178
+ await query.toArray();
179
+ const choices = query.get('choicesResults');
180
+ assert(choices);
181
+ assert(choices.foo);
182
+ assert(choices.foo.some(c => c.value === 'bar'));
183
+ });
184
+
185
+ it('should not restrict choices when an authenticated user voluntarily sets a projection', async function() {
186
+ apos.thing.options.publicApiProjection = {
187
+ title: 1,
188
+ _url: 1
189
+ };
190
+ const req = apos.task.getReq();
191
+ // Authenticated user narrows their own query via project() — this is
192
+ // voluntary and must not block choices for fields outside that projection
193
+ const query = apos.thing.find(req).project({ title: 1 }).choices([ 'foo' ]);
194
+ await query.toArray();
195
+ const choices = query.get('choicesResults');
196
+ assert(choices);
197
+ assert(choices.foo);
198
+ assert(choices.foo.some(c => c.value === 'bar'));
199
+ });
200
+
121
201
  it('should not set a "max-age" cache-control value when retrieving pieces, when cache option is not set, with a public API projection', async function() {
122
202
  apos.thing.options.publicApiProjection = {
123
203
  title: 1,
package/test/pieces.js CHANGED
@@ -2364,6 +2364,31 @@ describe('Pieces', function() {
2364
2364
  assert.deepEqual(actual, expected);
2365
2365
  });
2366
2366
 
2367
+ it('should not leak viewPermission-protected fields via ?choices=', async function() {
2368
+ const jar = await t.loginAs(apos, 'editor');
2369
+
2370
+ const req = apos.task.getReq();
2371
+ const candidate = {
2372
+ ...apos.modules.board.newInstance(),
2373
+ title: 'Icarus',
2374
+ slug: 'icarus',
2375
+ discontinued: 'April 2077'
2376
+ };
2377
+ await apos.modules.board.insert(req, candidate);
2378
+
2379
+ // Editor does not have 'publish' permission on 'board', so
2380
+ // 'discontinued' (viewPermission: { action: 'publish', type: 'board' })
2381
+ // must not appear in choices
2382
+ const response = await apos.http.get('/api/v1/board?choices=title,discontinued', { jar });
2383
+ assert(response);
2384
+ assert(response.choices);
2385
+ assert(response.choices.title);
2386
+ assert(
2387
+ !response.choices.discontinued || response.choices.discontinued.length === 0,
2388
+ 'choices for viewPermission-protected field "discontinued" must not be exposed to editors'
2389
+ );
2390
+ });
2391
+
2367
2392
  it('should be able to edit fields with editPermission when having appropriate credentials on rest API', async function() {
2368
2393
  const jar = await t.loginAs(apos, 'editor');
2369
2394