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
package/CHANGELOG.md CHANGED
@@ -1,6 +1,37 @@
1
1
  # Changelog
2
2
 
3
- ## 4.28.0
3
+ ## 4.29.0 (2026-04-15)
4
+
5
+ ### Adds
6
+
7
+ - Added support for pretty URL file attachments in the static build metadata pipeline. When `@apostrophecms/file` has `options.prettyUrls` enabled, the `getAllUrlMetadata` API now annotates affected attachments properly. The backend streaming proxy route was also fixed to correctly resolve relative uploadfs URLs during static builds.
8
+ - Introduced Recently Edited manager as Admin Bar action, next to the existing Submitted Drafts. Allows modules to contribute filter choices.
9
+ - Fix batch operations executed in a modal in a different locale causing wrong browser URL rewrite
10
+ - Add background preset to the Styles Editor, supporting image, color, and gradient background CSS generation.
11
+
12
+ ### Fixes
13
+
14
+ - Fix a focus trap bug where in the context menu focus would jump back to the first element when reaching the last one.
15
+ - Bug fix: the "pretty URLs" feature of `@apostrophecms/file` is now compatible with locale prefixes.
16
+ - Removed misleading return from `pruneDataForExternalFront`, a method intended to be overridden to modify data "in place" before it is sent to Astro or a similar frontend.
17
+ - Fix layout column breadcrumb operations leaking in layout edit mode.
18
+ - Fix edge case where widgets having styles and fields at the same time would show "Ungrouped" tab. Add `hideSingleTab` option that can be enabled in any widget to hide tabs from the widget editor when there is only one tab containing fields. This option can also be enabled globally in `@apostrophecms/widget-type` options.
19
+ - Add background preset, supporting image, color and gradient background CSS generation.
20
+
21
+ ### Changes
22
+
23
+ - Combine Styles and Column configuration in a single Styles Editor experience.
24
+ - Use shorter placeholder text for relationship inputs in small/micro contexts.
25
+
26
+ ### Security
27
+
28
+ - Fix an XSS vulnerability allowing arbitrary markup to be inserted via the "SEO Title" or "Meta Description" fields provided by the `@apostrophecms/seo` module. The fix requires upgrading BOTH `apostrophe` and `@apostrophecms/seo`. A new mechanism for safely emitting JSON nodes has been introduced to make this type of vulnerability unlikely in the future. Thanks to [K Shanmukha Srinivasulu Royal](https://github.com/Chittu13) for reporting the vulnerability.
29
+ - Fixed a security hole in the `.choices()` and `.counts()` query builders: formerly, these query builders could be used by the public to exfiltrate schema fields not included in the `publicApiProjection`, or fields locked down with a `viewPermission` property. Thanks to [offset](https://github.com/offset) for reporting this issue, which was not made public prior to the release of the fix.
30
+ - Fixed an XSS vulnerability in color fields, which formerly accepted `-` followed by anything, including `</style>`, which could be used to inject other markup. Thanks to [restriction](https://github.com/restriction) for reporting the issue and proposing the fix.
31
+ - Resolved a `publicApiProjection` bypass vulnerability for piece types. Thanks to [restriction](https://github.com/restriction) for reporting the issue and proposing the fix.
32
+ - Ensured a minimum 2-second delay in the password reset flow to avoid disclosing whether the email or username was valid or not. Thanks to [restriction](https://github.com/restriction) for reporting the issue and proposing the fix.
33
+
34
+ ## 4.28.0 (2026-03-19)
4
35
 
5
36
  ### Adds
6
37
 
@@ -8,7 +39,6 @@
8
39
  - Adds widget graph store, accessible in Admin UI.
9
40
  - Support for the new `prettyUrls: true` option for @apostrophecms/file, which enables "pretty URLs" for PDFs and other items in the file library, in exchange for a small performance impact. Edit the slug field to adjust the pretty URL
10
41
 
11
-
12
42
  ### Fixes
13
43
 
14
44
  - Fix a bug when rich text link open in new tab checkbox can't be cleared
@@ -27,7 +57,7 @@
27
57
  - Improve re-rendering UX while keeping the performance optimization
28
58
  - raise the user's widget z-index context only when focused
29
59
  - Hide add content buttons on rich text editing, like widget controls
30
- - Refine in-context focus states for calmer UX
60
+ - Refine in-context focus states for calmer UX
31
61
  - Simplifies some in-context UI rendering checks
32
62
  - Updated dependencies
33
63
 
package/README.md ADDED
@@ -0,0 +1,142 @@
1
+ <div align="center">
2
+ <a href="https://github.com/apostrophecms/apostrophe">
3
+ <img src="https://static.apostrophecms.com/apostrophecms/apostrophe/logo.svg" alt="ApostropheCMS logo" width="80" height="80">
4
+ </a>
5
+
6
+ <h1>ApostropheCMS</h1>
7
+
8
+ <p>
9
+ <a aria-label="Join the community on Discord" href="http://chat.apostrophecms.org">
10
+ <img alt="" src="https://img.shields.io/discord/517772094482677790?color=5865f2&label=Join%20the%20Discord&logo=discord&logoColor=fff&labelColor=000&style=for-the-badge&logoWidth=20" />
11
+ </a>
12
+ <a aria-label="License" href="https://github.com/apostrophecms/apostrophe/blob/main/LICENSE.md">
13
+ <img alt="" src="https://img.shields.io/static/v1?style=for-the-badge&labelColor=000000&label=License&message=MIT&color=3DA639" />
14
+ </a>
15
+ </p>
16
+
17
+ <p>
18
+ <strong>Full-stack CMS for developers and content teams</strong><br />
19
+ Build websites with in-context editing and headless flexibility using Node.js and MongoDB.
20
+ <br />
21
+ <a href="https://docs.apostrophecms.org/"><strong>Documentation »</strong></a>
22
+ <br />
23
+ <br />
24
+ <a href="http://demo.apostrophecms.com">Demo</a>
25
+ ·
26
+ <a href="https://roadmap.apostrophecms.com/roadmap">Roadmap</a>
27
+ ·
28
+ <a href="https://github.com/apostrophecms/apostrophe/issues/new?assignees=&labels=bug,3.0&template=bug_report.md&title=">Report Bug</a>
29
+ </p>
30
+ </div>
31
+
32
+ ## About
33
+
34
+ ApostropheCMS is a full-stack content management system built with Node.js and MongoDB. Content creators can edit directly on live pages without switching between admin interfaces, while developers can build with modern JavaScript or use it headlessly with any frontend framework.
35
+
36
+ ### Key Features
37
+
38
+ - **🎯 In-Context Editing** - Content creators edit directly on the live page, seeing changes instantly
39
+ - **⚡ Headless-Ready** - Use any frontend framework while keeping the powerful admin experience
40
+ - **🛠️ Developer-First** - Built with Node.js and MongoDB for full-stack JavaScript development
41
+ - **📈 Scales Beautifully** - From small sites to enterprise applications handling millions of pages
42
+ - **🔐 Enterprise Features** - Advanced permissions, workflow management, automated translations, and more
43
+
44
+ ## System Requirements
45
+
46
+ | Requirement | Version | Installation Notes |
47
+ |-------------|---------|-------------------|
48
+ | **Node.js** | 20.x+ | Use [NVM](https://github.com/nvm-sh/nvm) for version management |
49
+ | **MongoDB** | 6.0+ | [MongoDB Atlas](https://www.mongodb.com/atlas) (cloud) or local install |
50
+ | **npm** | 10.x+ | Included with Node.js |
51
+
52
+ See our [setup guides](https://docs.apostrophecms.org/guide/development-setup.html) for installation instructions.
53
+
54
+ ## Quick Start
55
+
56
+ Get ApostropheCMS running locally in minutes:
57
+
58
+ ```bash
59
+ # Option 1: Install CLI globally (recommended for multiple projects)
60
+ npm install -g @apostrophecms/cli
61
+ apos create my-website
62
+ cd my-website
63
+ npm run dev
64
+
65
+ # Option 2: Use npx for one-time project creation
66
+ npx @apostrophecms/cli create my-website
67
+ cd my-website
68
+ npm run dev
69
+ ```
70
+
71
+ Your new ApostropheCMS site will be available at `http://localhost:3000` with a powerful admin interface at `/login`.
72
+
73
+ ### Prefer to Go Headless?
74
+
75
+ **Get started with Astro integration** - the easiest way to build headless sites while keeping visual editing:
76
+
77
+ - **[Apollo Starter Kit (Astro)](https://apostrophecms.com/starter-kits/apollo-starter-kit-for-astro-cms)** - Production-ready foundation with beautiful design system and rich content features
78
+ - **[Essentials Starter Kit (Astro)](git clone https://github.com/apostrophecms/starter-kit-astro-essentials)** - Minimal, clean foundation for building custom designs from scratch
79
+
80
+ Both starter kits provide headless CMS power with in-context editing, letting content creators edit directly on the live site while you build with modern frontend tools. Our Astro integration handles all the content fetching automatically—no REST API calls to write.
81
+
82
+ **Desire a different frontend framework?** Use our REST APIs with React, Vue, Svelte, or any other framework:
83
+
84
+ - **[REST API Documentation](https://docs.apostrophecms.org/reference/api/pieces.html)** - Complete API reference
85
+ - **[Headless CMS Guide](https://docs.apostrophecms.org/guide/headless-cms.html)** - Integration walkthrough for any framework
86
+
87
+ ### Hosting & Deployment
88
+
89
+ Choose [ApostropheCMS hosting](https://apostrophecms.com/hosting) for turnkey solutions with optimized performance and dedicated support, or deploy to [any platform where Node.js runs](https://docs.apostrophecms.org/guide/hosting.html).
90
+
91
+ ## Built With Modern Tech
92
+
93
+ - **[Node.js](https://nodejs.org/)** - JavaScript runtime for server-side development
94
+ - **[MongoDB](https://www.mongodb.com/)** - Flexible document database for content storage
95
+ - **ESM Modules** - Native ES6 module support for modern JavaScript
96
+ - **Vite** - Lightning-fast build tool and development server
97
+ - **Modern JavaScript** - ES6+, async/await, and contemporary development patterns
98
+
99
+ ## Community & Support
100
+
101
+ **Join other developers and content creators using ApostropheCMS:**
102
+
103
+ - **[Discord](https://discord.com/invite/XkbRNq7)** - Get help, share projects, and connect with other users
104
+ - **[GitHub Discussions](https://github.com/apostrophecms/apostrophe/discussions)** - Feature requests, technical discussions, and community support
105
+ - **[Documentation](https://docs.apostrophecms.org/)** - Comprehensive guides, tutorials, and API references
106
+
107
+ ## Contributing
108
+
109
+ We welcome contributions from the community! Whether you're fixing bugs, adding features, or improving documentation, your help makes ApostropheCMS better for everyone.
110
+
111
+ - **[Contribution Guide](https://github.com/apostrophecms/apostrophe/blob/main/CONTRIBUTING.md)** - How to contribute code, documentation, and feedback
112
+ - **[Good First Issues](https://github.com/apostrophecms/apostrophe/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)** - Perfect starting points for new contributors
113
+
114
+
115
+ ## Pro Features
116
+
117
+ **For teams and organizations requiring additional features:**
118
+
119
+ - **🔐 Advanced User Management** - Granular permissions, user groups, and access controls
120
+ - **🌍 Automated Translation** - AI-powered translation with DeepL, Google Translate, and Azure
121
+ - **📊 Analytics & SEO** - Built-in SEO optimization and content analytics
122
+ - **⚡ Performance Optimization** - Advanced caching, CDN integration, and performance monitoring
123
+ - **🏢 Multisite Management** - Manage multiple sites from a single dashboard with shared resources
124
+ - **💼 Professional Support** - Dedicated support, training, and consultation services
125
+
126
+ [Explore all the pro extensions](https://apostrophecms.com/extensions?autocomplete=&license=assembly&license=pro) and [sign up](https://app.apostrophecms.com/login) for a Pro license in our self-service Apostrophe Workspaces, or [contact us](https://apostrophecms.com/contact-us) to learn about licensing and support options.
127
+
128
+ ## License
129
+
130
+ ApostropheCMS is open source software licensed under the [MIT License](https://github.com/apostrophecms/apostrophe/blob/main/LICENSE.md). This means you're free to use, modify, and distribute it for both personal and commercial projects.
131
+
132
+ ---
133
+
134
+ <div align="center">
135
+ <p>
136
+ <strong>Ready to build something amazing?</strong><br>
137
+ <a href="https://docs.apostrophecms.org/">Get started with our documentation</a> or <a href="https://apostrophecms.com/contact-us">talk to our team</a>
138
+ </p>
139
+ <p>
140
+ <em>Built with ❤️ by the <a href="https://apostrophecms.com">ApostropheCMS team</a></em>
141
+ </p>
142
+ </div>
package/defaults.js CHANGED
@@ -61,6 +61,7 @@ module.exports = {
61
61
  '@apostrophecms/file-tag': {},
62
62
  '@apostrophecms/soft-redirect': {},
63
63
  '@apostrophecms/submitted-draft': {},
64
+ '@apostrophecms/recently-edited': {},
64
65
  '@apostrophecms/command-menu': {},
65
66
  '@apostrophecms/translation': {}
66
67
  }
@@ -0,0 +1,27 @@
1
+ // Serialize `data` to a JSON string that is safe to embed inside an HTML
2
+ // `<script>` element. `JSON.stringify` on its own does NOT escape the
3
+ // sequences `</script>`, `<!--` or `<![CDATA[`, so untrusted data (e.g.
4
+ // editor-provided SEO fields) in a JSON body could otherwise break out of
5
+ // the surrounding script tag and inject arbitrary HTML/JS (stored XSS).
6
+ // Escaping `<` as its `\u003c` form keeps the JSON valid while neutralizing
7
+ // all of those sequences. Line and paragraph separators are also escaped
8
+ // since they are valid in JSON but illegal in some JavaScript parsers.
9
+ //
10
+ // This is the single source of truth for that escaping. The template
11
+ // `renderNodes` helper uses it to render `{ json: ... }` node bodies, so in
12
+ // most cases you should just build a node like:
13
+ //
14
+ // {
15
+ // name: 'script',
16
+ // attrs: { type: 'application/ld+json' },
17
+ // body: [ { json: data } ]
18
+ // }
19
+ //
20
+ // and let `renderNodes` do the right thing.
21
+
22
+ module.exports = function safeJsonForScript(data) {
23
+ return JSON.stringify(data, null, 2)
24
+ .replace(/</g, '\\u003c')
25
+ .replace(/\u2028/g, '\\u2028')
26
+ .replace(/\u2029/g, '\\u2029');
27
+ };
@@ -123,7 +123,7 @@ export default {
123
123
  itemName: '@apostrophecms/i18n:localize',
124
124
  props: {
125
125
  doc: apos.adminBar.context,
126
- locale
126
+ targetLocale: locale
127
127
  }
128
128
  });
129
129
  } else {
@@ -579,6 +579,7 @@ export default {
579
579
  doc = await apos.http.get(`${action}/${this.context.aposDocId}`, {
580
580
  qs: {
581
581
  aposMode: this.draftMode,
582
+ aposLocale: apos.i18n.locale,
582
583
  project: { _url: 1 }
583
584
  }
584
585
  });
@@ -83,6 +83,7 @@
83
83
  :tiny-screen="tinyScreen"
84
84
  :widget="widget"
85
85
  :options="options"
86
+ :breadcrumb-operations="widgetBreadcrumbOperations"
86
87
  :disabled="disabled"
87
88
  :is-focused="isFocused"
88
89
  @widget-focus="getFocus"
@@ -304,10 +305,6 @@ export default {
304
305
  type: Boolean,
305
306
  default: false
306
307
  },
307
- breadcrumbDisabled: {
308
- type: Boolean,
309
- default: false
310
- },
311
308
  generation: {
312
309
  type: Number,
313
310
  required: false,
@@ -426,7 +423,8 @@ export default {
426
423
  return apos.modules[this.moduleOptions?.widgetManagers[this.widget?.type]] ?? {};
427
424
  },
428
425
  widgetBreadcrumbOperations() {
429
- return (this.widgetModuleOptions.widgetBreadcrumbOperations || []);
426
+ return (this.widgetModuleOptions.widgetBreadcrumbOperations || [])
427
+ .filter(op => op.hidden !== true);
430
428
  },
431
429
  shouldSkipEdit() {
432
430
  return !this.widgetModuleOptions.widgetOperations
@@ -75,6 +75,15 @@ export default {
75
75
  return {};
76
76
  }
77
77
  },
78
+ // Override module breadcrumb operations.
79
+ // If not provided (undefined or null), operations will be pulled from the
80
+ // widget's module options.
81
+ breadcrumbOperations: {
82
+ type: Array,
83
+ default() {
84
+ return null;
85
+ }
86
+ },
78
87
  isFocused: {
79
88
  type: Boolean,
80
89
  default: false
@@ -130,7 +139,10 @@ export default {
130
139
  return apos.modules[this.moduleOptions?.widgetManagers[this.widget?.type]] ?? {};
131
140
  },
132
141
  widgetBreadcrumbOperations() {
133
- return (this.widgetModuleOptions.widgetBreadcrumbOperations || [])
142
+ return (
143
+ this.breadcrumbOperations ||
144
+ this.widgetModuleOptions.widgetBreadcrumbOperations || []
145
+ )
134
146
  .map((operation) => ({
135
147
  component: this.getOperationComponent(operation),
136
148
  props: this.getOperationProps(operation),
@@ -23,6 +23,7 @@ module.exports = {
23
23
  'arrow-expand-vertical-icon': 'ArrowExpandVertical',
24
24
  'arrow-left-icon': 'ArrowLeft',
25
25
  'arrow-right-icon': 'ArrowRight',
26
+ 'arrow-top-right-icon': 'ArrowTopRight',
26
27
  'arrow-up-icon': 'ArrowUp',
27
28
  'binoculars-icon': 'Binoculars',
28
29
  'calendar-icon': 'Calendar',
@@ -109,6 +110,7 @@ module.exports = {
109
110
  'keyboard-tab': 'KeyboardTab',
110
111
  'label-icon': 'Label',
111
112
  'lightbulb-on-icon': 'LightbulbOn',
113
+ 'link-external-icon': 'OpenInNew',
112
114
  'link-icon': 'Link',
113
115
  'list-status-icon': 'ListStatus',
114
116
  'lock-icon': 'Lock',
@@ -124,6 +126,7 @@ module.exports = {
124
126
  'play-box-icon': 'PlayBox',
125
127
  'playlist-edit-icon': 'PlaylistEdit',
126
128
  'plus-icon': 'Plus',
129
+ 'recently-edited-icon': 'ClockOutline',
127
130
  'redo-icon': 'RedoVariant',
128
131
  'refresh-icon': 'Refresh',
129
132
  'resize-bottom-right-icon': 'ResizeBottomRight',
@@ -217,6 +217,11 @@ module.exports = {
217
217
 
218
218
  await self.crop(req, _id, sanitizedCrop);
219
219
 
220
+ if (req.body.annotate) {
221
+ const attachment = await self.db.findOne({ _id });
222
+ return self.annotateAttachment(attachment, sanitizedCrop);
223
+ }
224
+
220
225
  return true;
221
226
  }
222
227
  ]
@@ -611,6 +616,42 @@ module.exports = {
611
616
  height: sanitizeInteger(crop.height, 0, 0, 10000)
612
617
  };
613
618
  },
619
+ // Given an attachment object and an optional crop,
620
+ // return a clone with `_urls` fully populated for all
621
+ // configured image sizes. For non-image attachments
622
+ // a single `_url` is set instead.
623
+ annotateAttachment(attachment, crop) {
624
+ const result = { ...attachment };
625
+ result._isCroppable = self.isCroppable(result);
626
+ if (crop && crop.width) {
627
+ result._crop = _.pick(crop, 'width', 'height', 'top', 'left');
628
+ }
629
+ if (result.group === 'images') {
630
+ result._urls = {};
631
+ if (result._crop) {
632
+ result._urls.uncropped = {};
633
+ }
634
+ for (const size of self.imageSizes) {
635
+ result._urls[size.name] = self.url(result, { size: size.name });
636
+ if (result._crop) {
637
+ result._urls.uncropped[size.name] = self.url(result, {
638
+ size: size.name,
639
+ crop: false
640
+ });
641
+ }
642
+ }
643
+ result._urls.original = self.url(result, { size: 'original' });
644
+ if (result._crop) {
645
+ result._urls.uncropped.original = self.url(result, {
646
+ size: 'original',
647
+ crop: false
648
+ });
649
+ }
650
+ } else {
651
+ result._url = self.url(result);
652
+ }
653
+ return result;
654
+ },
614
655
  // This method return a default icon url if an attachment is missing
615
656
  // to avoid template errors
616
657
  getMissingAttachmentUrl() {
@@ -1459,7 +1500,8 @@ module.exports = {
1459
1500
  uploadsUrl: self.uploadfs.getUrl(),
1460
1501
  croppable: self.croppable,
1461
1502
  sized: self.sized,
1462
- maxSize: self.options.maxSize
1503
+ maxSize: self.options.maxSize,
1504
+ imageSizes: self.imageSizes
1463
1505
  };
1464
1506
  },
1465
1507
  // Middleware method used when only those with attachment privileges
@@ -32,9 +32,15 @@ module.exports = {
32
32
  throw self.apos.error('required');
33
33
  }
34
34
 
35
+ const isVariable = destination[field.name].startsWith('--');
35
36
  const test = new TinyColor(destination[field.name]);
36
- if (!test.isValid && !destination[field.name].startsWith('--')) {
37
+ if (!test.isValid && !isVariable) {
37
38
  destination[field.name] = null;
39
+ } else if (isVariable) {
40
+ // CSS custom property names: only allow alphanumeric, hyphens, underscores
41
+ if (!/^--[a-zA-Z0-9_-]+$/.test(destination[field.name])) {
42
+ destination[field.name] = null;
43
+ }
38
44
  }
39
45
  },
40
46
  isEmpty: function (field, value) {
@@ -1553,7 +1553,8 @@ module.exports = {
1553
1553
  ];
1554
1554
 
1555
1555
  function validate ({
1556
- action, context, type = 'modal', label, modal, conditions, if: ifProps
1556
+ action, context, type = 'modal', label, modal, conditions, if: ifProps,
1557
+ crossLocale
1557
1558
  }) {
1558
1559
  const allowedConditions = [
1559
1560
  'canPublish',
@@ -1596,6 +1597,15 @@ module.exports = {
1596
1597
  'invalid', 'The if property in addContextOperation must be an object containing properties and values that will be checked against the current document in order to show or not the context operation.'
1597
1598
  );
1598
1599
  }
1600
+
1601
+ if (
1602
+ crossLocale !== undefined &&
1603
+ typeof crossLocale !== 'boolean'
1604
+ ) {
1605
+ throw self.apos.error(
1606
+ 'invalid', 'The crossLocale property in addContextOperation must be a boolean.'
1607
+ );
1608
+ }
1599
1609
  }
1600
1610
  },
1601
1611
  getBrowserData(req) {