apostrophe 3.63.3 → 4.1.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 (184) hide show
  1. package/.eslintrc +13 -4
  2. package/CHANGELOG.md +37 -5
  3. package/defaults.js +2 -1
  4. package/modules/@apostrophecms/admin-bar/ui/apos/apps/AposAdminBar.js +7 -17
  5. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBar.vue +14 -16
  6. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarLocale.vue +1 -1
  7. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarMenu.vue +22 -15
  8. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarUser.vue +2 -2
  9. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +13 -8
  10. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextModeAndSettings.vue +18 -10
  11. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextTitle.vue +4 -4
  12. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextUndoRedo.vue +14 -8
  13. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposSavingIndicator.vue +2 -1
  14. package/modules/@apostrophecms/area/ui/apos/apps/AposAreas.js +36 -54
  15. package/modules/@apostrophecms/area/ui/apos/components/AposAreaContextualMenu.vue +20 -25
  16. package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +5 -12
  17. package/modules/@apostrophecms/area/ui/apos/components/AposAreaExpandedMenu.vue +11 -3
  18. package/modules/@apostrophecms/area/ui/apos/components/AposAreaMenu.vue +6 -6
  19. package/modules/@apostrophecms/area/ui/apos/components/AposAreaMenuItem.vue +3 -2
  20. package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +31 -44
  21. package/modules/@apostrophecms/area/ui/apos/components/AposWidgetControls.vue +16 -16
  22. package/modules/@apostrophecms/asset/index.js +25 -12
  23. package/modules/@apostrophecms/asset/lib/webpack/apos/webpack.config.js +3 -3
  24. package/modules/@apostrophecms/asset/lib/webpack/apos/webpack.vue.js +7 -1
  25. package/modules/@apostrophecms/attachment/index.js +2 -1
  26. package/modules/@apostrophecms/attachment/public/img/missing-icon.svg +14 -0
  27. package/modules/@apostrophecms/busy/ui/apos/apps/AposBusy.js +8 -7
  28. package/modules/@apostrophecms/busy/ui/apos/components/TheAposBusy.vue +1 -1
  29. package/modules/@apostrophecms/command-menu/ui/apos/apps/AposCommandMenu.js +11 -29
  30. package/modules/@apostrophecms/command-menu/ui/apos/components/AposCommandMenuKey.vue +1 -1
  31. package/modules/@apostrophecms/command-menu/ui/apos/components/AposCommandMenuShortcut.vue +6 -6
  32. package/modules/@apostrophecms/command-menu/ui/apos/components/TheAposCommandMenu.vue +10 -7
  33. package/modules/@apostrophecms/doc-type/index.js +34 -13
  34. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +3 -3
  35. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +20 -15
  36. package/modules/@apostrophecms/doc-type/ui/apos/logic/AposDocContextMenu.js +1 -1
  37. package/modules/@apostrophecms/i18n/i18n/en.json +13 -0
  38. package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +209 -33
  39. package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalizeErrors.vue +3 -3
  40. package/modules/@apostrophecms/image/ui/apos/components/AposImageCropper.vue +5 -5
  41. package/modules/@apostrophecms/image/ui/apos/components/AposImageRelationshipEditor.vue +6 -6
  42. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +23 -16
  43. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerDisplay.vue +11 -11
  44. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerEditor.vue +28 -21
  45. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerSelections.vue +4 -3
  46. package/modules/@apostrophecms/image/ui/apos/components/AposMediaUploader.vue +5 -4
  47. package/modules/@apostrophecms/login/index.js +18 -1
  48. package/modules/@apostrophecms/login/ui/apos/apps/AposLogin.js +6 -7
  49. package/modules/@apostrophecms/login/ui/apos/components/AposForgotPasswordForm.vue +3 -3
  50. package/modules/@apostrophecms/login/ui/apos/components/AposLoginForm.vue +10 -10
  51. package/modules/@apostrophecms/login/ui/apos/components/AposResetPasswordForm.vue +3 -3
  52. package/modules/@apostrophecms/login/ui/apos/components/TheAposLogin.vue +2 -2
  53. package/modules/@apostrophecms/login/ui/apos/logic/AposLoginForm.js +1 -16
  54. package/modules/@apostrophecms/modal/ui/apos/apps/AposModals.js +60 -87
  55. package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +15 -11
  56. package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +17 -11
  57. package/modules/@apostrophecms/modal/ui/apos/components/AposModalBreadcrumbs.vue +7 -4
  58. package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +9 -9
  59. package/modules/@apostrophecms/modal/ui/apos/components/AposModalLip.vue +2 -2
  60. package/modules/@apostrophecms/modal/ui/apos/components/AposModalShareDraft.vue +8 -8
  61. package/modules/@apostrophecms/modal/ui/apos/components/AposModalTabs.vue +4 -3
  62. package/modules/@apostrophecms/modal/ui/apos/components/AposModalToolbar.vue +7 -7
  63. package/modules/@apostrophecms/modal/ui/apos/components/TheAposModals.vue +22 -4
  64. package/modules/@apostrophecms/modal/ui/apos/composables/AposFocus.js +95 -0
  65. package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocsManagerMixin.js +2 -3
  66. package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +6 -0
  67. package/modules/@apostrophecms/notification/index.js +4 -4
  68. package/modules/@apostrophecms/notification/ui/apos/apps/AposNotification.js +6 -9
  69. package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +12 -8
  70. package/modules/@apostrophecms/notification/ui/apos/components/TheAposNotifications.vue +4 -2
  71. package/modules/@apostrophecms/oembed-field/ui/apos/components/AposInputOembed.vue +12 -10
  72. package/modules/@apostrophecms/page/index.js +1 -0
  73. package/modules/@apostrophecms/page/ui/apos/components/AposPagesManager.vue +15 -11
  74. package/modules/@apostrophecms/page/ui/apos/logic/AposPagesManager.js +1 -1
  75. package/modules/@apostrophecms/permission/ui/apos/components/AposInputRole.vue +3 -3
  76. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +25 -17
  77. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +24 -20
  78. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerSelectBox.vue +9 -5
  79. package/modules/@apostrophecms/piece-type/ui/apos/components/AposRelationshipEditor.vue +15 -11
  80. package/modules/@apostrophecms/rich-text-widget/index.js +1 -0
  81. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposImageControlDialog.vue +7 -6
  82. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +31 -30
  83. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapAnchor.vue +12 -10
  84. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapButton.vue +1 -1
  85. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapImage.vue +1 -1
  86. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapLink.vue +9 -8
  87. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapStyles.vue +3 -3
  88. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapTable.vue +3 -3
  89. package/modules/@apostrophecms/schema/index.js +69 -8
  90. package/modules/@apostrophecms/schema/lib/addFieldTypes.js +1 -1
  91. package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +10 -8
  92. package/modules/@apostrophecms/schema/ui/apos/components/AposInputArea.vue +5 -3
  93. package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +81 -277
  94. package/modules/@apostrophecms/schema/ui/apos/components/AposInputAttachment.vue +4 -2
  95. package/modules/@apostrophecms/schema/ui/apos/components/AposInputBoolean.vue +24 -14
  96. package/modules/@apostrophecms/schema/ui/apos/components/AposInputCheckboxes.vue +7 -6
  97. package/modules/@apostrophecms/schema/ui/apos/components/AposInputColor.vue +10 -7
  98. package/modules/@apostrophecms/schema/ui/apos/components/AposInputObject.vue +3 -3
  99. package/modules/@apostrophecms/schema/ui/apos/components/AposInputPassword.vue +6 -4
  100. package/modules/@apostrophecms/schema/ui/apos/components/AposInputRadio.vue +5 -4
  101. package/modules/@apostrophecms/schema/ui/apos/components/AposInputRange.vue +9 -6
  102. package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +15 -12
  103. package/modules/@apostrophecms/schema/ui/apos/components/AposInputSelect.vue +1 -1
  104. package/modules/@apostrophecms/schema/ui/apos/components/AposInputSlug.vue +16 -12
  105. package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +19 -11
  106. package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +15 -12
  107. package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +75 -22
  108. package/modules/@apostrophecms/schema/ui/apos/components/AposSearchList.vue +1 -1
  109. package/modules/@apostrophecms/schema/ui/apos/components/AposSubform.vue +2 -2
  110. package/modules/@apostrophecms/schema/ui/apos/logic/AposArrayEditor.js +0 -4
  111. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputArea.js +3 -3
  112. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputArray.js +15 -4
  113. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputAttachment.js +3 -3
  114. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputCheckboxes.js +7 -7
  115. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputColor.js +5 -8
  116. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputDateAndTime.js +1 -1
  117. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputObject.js +1 -1
  118. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRadio.js +1 -1
  119. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRelationship.js +12 -9
  120. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputSelect.js +3 -3
  121. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputString.js +7 -9
  122. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputWrapper.js +4 -4
  123. package/modules/@apostrophecms/schema/ui/apos/logic/AposSchema.js +42 -13
  124. package/modules/@apostrophecms/schema/ui/apos/logic/AposSubform.js +1 -1
  125. package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputMixin.js +9 -9
  126. package/modules/@apostrophecms/schema/ui/apos/scss/AposInputArray.scss +205 -0
  127. package/modules/@apostrophecms/settings/ui/apos/components/AposSettingsManager.vue +5 -5
  128. package/modules/@apostrophecms/settings/ui/apos/logic/AposSettingsManager.js +4 -4
  129. package/modules/@apostrophecms/submitted-draft/ui/apos/components/AposSubmittedDraftIcon.vue +5 -4
  130. package/modules/@apostrophecms/task/index.js +2 -0
  131. package/modules/@apostrophecms/translation/index.js +233 -0
  132. package/modules/@apostrophecms/translation/ui/apos/components/AposTranslationIndicator.vue +84 -0
  133. package/modules/@apostrophecms/ui/ui/apos/components/AposAvatar.vue +2 -1
  134. package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +4 -4
  135. package/modules/@apostrophecms/ui/ui/apos/components/AposButtonGroup.vue +6 -6
  136. package/modules/@apostrophecms/ui/ui/apos/components/AposButtonSplit.vue +120 -113
  137. package/modules/@apostrophecms/ui/ui/apos/components/AposCellButton.vue +2 -1
  138. package/modules/@apostrophecms/ui/ui/apos/components/AposCellLabels.vue +49 -5
  139. package/modules/@apostrophecms/ui/ui/apos/components/AposCheckbox.vue +19 -19
  140. package/modules/@apostrophecms/ui/ui/apos/components/AposCloudUploadIcon.vue +10 -5
  141. package/modules/@apostrophecms/ui/ui/apos/components/AposCombo.vue +15 -15
  142. package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenu.vue +214 -191
  143. package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenuDialog.vue +77 -65
  144. package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenuItem.vue +1 -1
  145. package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenuTip.vue +28 -50
  146. package/modules/@apostrophecms/ui/ui/apos/components/AposEmptyState.vue +3 -3
  147. package/modules/@apostrophecms/ui/ui/apos/components/AposFile.vue +5 -5
  148. package/modules/@apostrophecms/ui/ui/apos/components/AposFilterMenu.vue +4 -4
  149. package/modules/@apostrophecms/ui/ui/apos/components/AposIndicator.vue +1 -1
  150. package/modules/@apostrophecms/ui/ui/apos/components/AposLabel.vue +1 -1
  151. package/modules/@apostrophecms/ui/ui/apos/components/AposMinMaxCount.vue +5 -5
  152. package/modules/@apostrophecms/ui/ui/apos/components/AposPager.vue +14 -8
  153. package/modules/@apostrophecms/ui/ui/apos/components/AposPagerDots.vue +2 -1
  154. package/modules/@apostrophecms/ui/ui/apos/components/AposSlat.vue +13 -12
  155. package/modules/@apostrophecms/ui/ui/apos/components/AposSlatList.vue +53 -59
  156. package/modules/@apostrophecms/ui/ui/apos/components/AposSpinner.vue +2 -2
  157. package/modules/@apostrophecms/ui/ui/apos/components/AposSubformPreview.vue +2 -2
  158. package/modules/@apostrophecms/ui/ui/apos/components/AposTag.vue +3 -2
  159. package/modules/@apostrophecms/ui/ui/apos/components/AposTagApply.vue +40 -35
  160. package/modules/@apostrophecms/ui/ui/apos/components/AposTagListItem.vue +2 -1
  161. package/modules/@apostrophecms/ui/ui/apos/components/AposToggle.vue +2 -2
  162. package/modules/@apostrophecms/ui/ui/apos/components/AposTree.vue +9 -11
  163. package/modules/@apostrophecms/ui/ui/apos/components/AposTreeHeader.vue +5 -3
  164. package/modules/@apostrophecms/ui/ui/apos/components/AposTreeRows.vue +129 -129
  165. package/modules/@apostrophecms/ui/ui/apos/composables/AposTheme.js +11 -0
  166. package/modules/@apostrophecms/ui/ui/apos/lib/click-outside-element.js +4 -4
  167. package/modules/@apostrophecms/ui/ui/apos/lib/i18next.js +56 -50
  168. package/modules/@apostrophecms/ui/ui/apos/lib/tooltip.js +191 -0
  169. package/modules/@apostrophecms/ui/ui/apos/lib/vue.js +19 -10
  170. package/modules/@apostrophecms/ui/ui/apos/mixins/AposAdvisoryLockMixin.js +1 -1
  171. package/modules/@apostrophecms/ui/ui/apos/scss/global/_tables.scss +1 -1
  172. package/modules/@apostrophecms/ui/ui/apos/scss/global/_theme.scss +1 -0
  173. package/modules/@apostrophecms/ui/ui/apos/scss/global/_tooltips.scss +6 -22
  174. package/modules/@apostrophecms/ui/ui/apos/scss/shared/_table-rows.scss +1 -1
  175. package/modules/@apostrophecms/widget-type/index.js +8 -2
  176. package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +26 -22
  177. package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +4 -4
  178. package/package.json +31 -44
  179. package/test/attachments.js +5 -0
  180. package/test/schemas.js +138 -0
  181. package/test/translation.js +538 -0
  182. package/test-lib/util.js +21 -0
  183. package/modules/@apostrophecms/ui/ui/apos/lib/localized-v-tooltip.js +0 -63
  184. package/modules/@apostrophecms/ui/ui/apos/lib/tooltip-options.js +0 -13
@@ -0,0 +1,233 @@
1
+ // ⚠️ The presence of this module in core may be temporary.
2
+ // Its presence should not be relied upon in project development.
3
+ // It is an implementation detail supporting @apostrophecms-pro/automatic-translation
4
+
5
+ module.exports = {
6
+ extend: '@apostrophecms/module',
7
+ options: {
8
+ enabled: true,
9
+ alias: 'translation'
10
+ },
11
+ init(self) {
12
+ self.providers = [];
13
+ self.enableBrowserData();
14
+ },
15
+ handlers(self) {
16
+ return {
17
+ '@apostrophecms/doc-type:beforeLocalize': {
18
+ // Translate the document using the first available provider.
19
+ async translate(req, doc, {
20
+ source, target, existing
21
+ }) {
22
+ if (!self.isEnabled()) {
23
+ return;
24
+ }
25
+ const targets = req.query.aposTranslateTargets || [];
26
+ const aposProvider = self.apos.launder.string(req.query.aposTranslateProvider);
27
+
28
+ if (!Array.isArray(targets)) {
29
+ throw self.apos.error('invalid', 'apostrophe:automaticTranslationErrorTargets');
30
+ }
31
+
32
+ if (!targets.includes(target)) {
33
+ return;
34
+ }
35
+
36
+ // We don't support multiple providers yet, so just use the first one
37
+ // when no provider is specified.
38
+ const manager = self.getProviderModule(aposProvider);
39
+ if (!manager) {
40
+ const name = aposProvider || 'apostrophe:notAvailable';
41
+
42
+ self.logError('before-localize-translate', 'Provider not found.', {
43
+ _id: doc._id,
44
+ title: doc.title,
45
+ provider: name
46
+ });
47
+ return self.apos.notify(
48
+ req,
49
+ req.t(
50
+ 'apostrophe:automaticTranslationErrorNoProvider',
51
+ {
52
+ title: doc.title,
53
+ name: req.t(name)
54
+ }
55
+ ),
56
+ {
57
+ type: 'danger',
58
+ dismiss: true
59
+ }
60
+ );
61
+ }
62
+
63
+ // Explicitly resolve the provider name to avoid errors as consequence of
64
+ // internal logic (empty argument vs non-existent provider).
65
+ const providerName = self.getProvider(aposProvider)?.name ?? '';
66
+
67
+ return manager.translate(req, providerName, doc, source, target, {
68
+ existing
69
+ });
70
+ }
71
+ }
72
+ };
73
+ },
74
+ apiRoutes: (self) => ({
75
+ get: {
76
+ // Get the list of supported languages for the given provider.
77
+ // If no provider is specified, the first available provider is used.
78
+ // query parameters:
79
+ // - provider: The name of the provider
80
+ // - source: (optional) Array of language codes to translate from
81
+ // - target: (optional) Array of language codes to translate to
82
+ // Example response:
83
+ // ```json
84
+ // {
85
+ // source: [
86
+ // { code: 'en', supported: true },
87
+ // { code: 'de', supported: true }
88
+ // ],
89
+ // target: [
90
+ // { code: 'en', supported: true },
91
+ // { code: 'de', supported: true }
92
+ // ]
93
+ // }
94
+ // ```
95
+ // If no `source` or `target` parameters are provided, the response should
96
+ // contain all supported languages for the source/target.
97
+ async languages(req) {
98
+ if (!self.isEnabled() || !self.canAccessApi(req)) {
99
+ throw self.apos.error('notfound');
100
+ }
101
+
102
+ const name = self.getProvider(req.query.provider)?.name;
103
+ const manager = self.getProviderModule(name);
104
+
105
+ if (!name || !manager) {
106
+ throw self.apos.error(
107
+ 'invalid',
108
+ req.t('apostrophe:automaticTranslationLngCheckNoProvider')
109
+ );
110
+ }
111
+
112
+ const source = Array.isArray(req.query.source)
113
+ ? req.query.source.map(self.apos.launder.string)
114
+ : undefined;
115
+
116
+ const target = Array.isArray(req.query.target)
117
+ ? req.query.target.map(self.apos.launder.string)
118
+ : undefined;
119
+
120
+ return manager.getSupportedLanguages(
121
+ req,
122
+ {
123
+ provider: name,
124
+ source,
125
+ target
126
+ }
127
+ );
128
+ }
129
+ }
130
+ }),
131
+ methods(self, options) {
132
+ return {
133
+ isEnabled() {
134
+ return options.enabled && self.providers.length > 0;
135
+ },
136
+ // Add a translation provider to the list of available providers.
137
+ // `aposModule` should be the module object (self), not its name.
138
+ // `name` should be a unique name for the provider.
139
+ // `label` should be a human-readable label for the provider.
140
+ //
141
+ // The provider module must implement a `translate` method that takes
142
+ // the following arguments:
143
+ // - `req` (Object): The request object
144
+ // - `provider` (String): The provider name
145
+ // - `draft` (Object): The document to translate
146
+ // - `source` (String): The language code of the original document
147
+ // - `target` (String): The language code to translate the document to
148
+ // - `options` (Object): Additional options as passed from the beforeLocalize
149
+ // event.
150
+ // The `translate` method should directly modify the `draft` object.
151
+ //
152
+ // The provider module must also implement a `getSupportedLanguages` method
153
+ // that takes the following arguments:
154
+ // - `req` (Object): The request object
155
+ // - `query` (Object): An object with the following properties:
156
+ // - `provider` (String): Requried, the provider name
157
+ // - `source` (Array): Optional, an array of language codes to translate from
158
+ // - `target` (Array): Optional, an array of language codes to translate to
159
+ // The method should return an object with `source` and `target` properties,
160
+ // each containing an array of objects with `code` and `supported` properties.
161
+ // The `code` property should be the language code and the `supported` property
162
+ // should be a boolean indicating whether the provider supports the language.
163
+ // If no `source` or `target` parameters are provided, the method should return
164
+ // all supported languages for any source or target language.
165
+ //
166
+ // Example:
167
+ // ```js
168
+ // // within a module's `init` method
169
+ // self.apos.translation.addProvider(self, {
170
+ // name: 'google',
171
+ // label: 'Google Translate'
172
+ // });
173
+ // ```
174
+ addProvider(aposModule, { name, label } = {}) {
175
+ if (!aposModule?.__meta?.name) {
176
+ throw new Error(
177
+ 'Apostrophe module not provided.',
178
+ { cause: 'invalidArguments' }
179
+ );
180
+ }
181
+ if (typeof aposModule.translate !== 'function') {
182
+ throw new Error(
183
+ `The translation provider module "${aposModule.__meta.name}" has to implement "translate" method.`,
184
+ { cause: 'interfaceNotImplemented' }
185
+ );
186
+ }
187
+ if (typeof aposModule.getSupportedLanguages !== 'function') {
188
+ throw new Error(
189
+ `The translation provider module "${aposModule.__meta.name}" has to implement "getSupportedLanguages" method.`,
190
+ { cause: 'interfaceNotImplemented' }
191
+ );
192
+ }
193
+
194
+ if (self.providers.some((provider) => provider.name === name)) {
195
+ throw new Error(
196
+ `The translation provider "${name}" is already registered.`,
197
+ { cause: 'duplicate' }
198
+ );
199
+ }
200
+
201
+ self.providers.push({
202
+ name,
203
+ label,
204
+ module: aposModule.__meta.name
205
+ });
206
+ },
207
+ // Get the first available provider or the provider with the given name.
208
+ // If no name is provided, the first available provider is returned.
209
+ // If name is provided and no provider with the given name is found,
210
+ // `undefined` is returned.
211
+ getProvider(name) {
212
+ if (!name) {
213
+ return self.providers[0];
214
+ }
215
+
216
+ return self.providers.find((provider) => provider.name === name);
217
+ },
218
+ getProviderModule(name) {
219
+ return self.apos.modules[self.getProvider(name)?.module];
220
+ },
221
+ getBrowserData(req) {
222
+ return {
223
+ action: self.action,
224
+ enabled: self.isEnabled(),
225
+ providers: self.providers.map(({ name, label }) => ({
226
+ name,
227
+ label
228
+ }))
229
+ };
230
+ }
231
+ };
232
+ }
233
+ };
@@ -0,0 +1,84 @@
1
+ <template>
2
+ <span
3
+ class="apos-translation-indicator"
4
+ data-apos-test="translationIndicator"
5
+ >
6
+ <svg
7
+ :width="size"
8
+ :height="size"
9
+ viewBox="0 0 24 24"
10
+ >
11
+ <defs>
12
+ <linearGradient
13
+ id="apos-translation-gradient"
14
+ x1="0"
15
+ x2="0"
16
+ y1="0"
17
+ y2="1"
18
+ >
19
+ <stop class="stop1" offset="0%" />
20
+ <stop class="stop2" offset="100%" />
21
+ </linearGradient>
22
+ </defs>
23
+ <path
24
+ id="svg-path"
25
+ d="M19,1L17.74,3.75L15,5L17.74,6.26L19,9L20.25,6.26L23,5L20.25,3.75M9,4L6.5,9.5L1,12L6.5,14.5L9,20L11.5,14.5L17,12L11.5,9.5M19,15L17.74,17.74L15,19L17.74,20.25L19,23L20.25,20.25L23,19L20.25,17.74"
26
+ />
27
+ </svg>
28
+ <p v-if="label" class="apos-translation-indicator__text">
29
+ {{ $t(label) }}
30
+ </p>
31
+ </span>
32
+ </template>
33
+ <script>
34
+
35
+ export default {
36
+ name: 'AposTranslationIndicator',
37
+ props: {
38
+ label: {
39
+ type: String,
40
+ default: null
41
+ },
42
+ size: {
43
+ type: Number,
44
+ default: 16
45
+ }
46
+ }
47
+ };
48
+
49
+ </script>
50
+
51
+ <style lang="scss">
52
+ $border: #8EFF92;
53
+ $color: #035204;
54
+ $background: #F7FEF4;
55
+
56
+ .apos-translation-indicator {
57
+ display: inline-flex;
58
+ align-items: center;
59
+ padding: 2px;
60
+ border: 1px solid $border;
61
+ border-radius: 6px;
62
+ color: $color;
63
+ background-color: $background;
64
+ }
65
+
66
+ .apos-translation-indicator__text {
67
+ @include type-label;
68
+
69
+ margin: 0 0 0 4px;
70
+ color: $color;
71
+ font-size: var(--a-type-tiny);
72
+ }
73
+
74
+ #svg-path {
75
+ fill: url(#apos-translation-gradient);
76
+ }
77
+
78
+ .stop1 {
79
+ stop-color: $border;
80
+ }
81
+ .stop2 {
82
+ stop-color: $color;
83
+ }
84
+ </style>
@@ -2,7 +2,8 @@
2
2
  <template>
3
3
  <div
4
4
  class="apos-avatar"
5
- :style="style" :alt="alt"
5
+ :style="style"
6
+ :alt="alt"
6
7
  >
7
8
  <span>{{ initials }}</span>
8
9
  </div>
@@ -6,8 +6,9 @@
6
6
  :data-apos-test="actionTestLabel"
7
7
  >
8
8
  <component
9
+ v-bind="attrs"
9
10
  :is="href ? 'a' : 'button'"
10
- v-on="href ? {} : {click: click}"
11
+ :id="attrs.id ? attrs.id : id"
11
12
  :href="href.length ? href : false"
12
13
  class="apos-button"
13
14
  :class="modifierClass"
@@ -15,12 +16,11 @@
15
16
  :disabled="isDisabled"
16
17
  :type="buttonType"
17
18
  :role="role"
18
- :id="attrs.id ? attrs.id : id"
19
19
  :style="{color: textColor}"
20
- v-bind="attrs"
20
+ v-on="href ? {} : {click: click}"
21
21
  >
22
22
  <transition name="fade">
23
- <AposSpinner :color="spinnerColor" v-if="busy" />
23
+ <AposSpinner v-if="busy" :color="spinnerColor" />
24
24
  </transition>
25
25
  <span
26
26
  v-if="colorStyle"
@@ -52,7 +52,7 @@ export default {
52
52
  display: inline-flex;
53
53
  }
54
54
 
55
- .apos-button-group ::v-deep .apos-button {
55
+ .apos-button-group :deep(.apos-button) {
56
56
  background-color: var(--a-background-primary);
57
57
  border: none;
58
58
  &:hover {
@@ -81,13 +81,13 @@ export default {
81
81
  // group-specific style overrides
82
82
 
83
83
  // transform weirds this out
84
- .apos-button-group ::v-deep .apos-button:hover,
85
- .apos-button-group ::v-deep .apos-button:focus {
84
+ .apos-button-group :deep(.apos-button:hover,)
85
+ .apos-button-group :deep(.apos-button:focus) {
86
86
  transform: none;
87
87
  }
88
88
 
89
89
  // border throws off bounding shell
90
- .apos-button-group ::v-deep .apos-button:focus {
90
+ .apos-button-group :deep(.apos-button:focus) {
91
91
  border: none;
92
92
  }
93
93
 
@@ -97,7 +97,7 @@ export default {
97
97
  background-color: var(--a-background-inverted);
98
98
  color: var(--a-text-inverted);
99
99
  }
100
- & ::v-deep .apos-button {
100
+ &:deep(.apos-button) {
101
101
  border: none;
102
102
  background-color: var(--a-background-inverted);
103
103
  color: var(--a-text-inverted);
@@ -116,7 +116,7 @@ export default {
116
116
  border: 1px solid var(--a-primary-dark-10);
117
117
  color: var(--a-primary-dark-10);
118
118
  }
119
- & ::v-deep .apos-button {
119
+ &:deep(.apos-button) {
120
120
  background-color: var(--a-background-primary);
121
121
  color: var(--a-primary-dark-10);
122
122
  &:hover {
@@ -6,32 +6,34 @@
6
6
  :label="label"
7
7
  :disabled="disabled"
8
8
  :tooltip="tooltip"
9
- @click="$emit('click', action)"
9
+ @click="emit('click', action)"
10
10
  />
11
11
  <AposContextMenu
12
+ ref="contextMenu"
12
13
  class="apos-button-split__menu"
13
14
  :menu="menu"
14
15
  :button="contextMenuButton"
15
16
  :disabled="disabled"
16
17
  menu-offset="1, 10"
17
18
  menu-placement="bottom-end"
18
- ref="contextMenu"
19
19
  @open="menuOpen"
20
20
  @close="menuClose"
21
21
  >
22
22
  <dl
23
- class="apos-button-split__menu__dialog" role="menu"
23
+ class="apos-button-split__menu__dialog"
24
+ role="menu"
24
25
  :aria-label="menuLabel"
25
26
  >
26
27
  <button
27
- v-for="item in menu" :key="item.action"
28
+ v-for="item in menu"
29
+ :key="item.action"
30
+ ref="choices"
28
31
  class="apos-button-split__menu__dialog-item"
29
32
  :class="{ 'apos-is-selected': item.action === action }"
30
- @click="selectionHandler(item.action)"
31
33
  :aria-checked="item.action === action ? 'true' : 'false'"
32
34
  role="menuitemradio"
33
35
  :value="item.action"
34
- ref="choices"
36
+ @click="selectionHandler(item.action)"
35
37
  @keydown="cycleElementsToFocus"
36
38
  >
37
39
  <AposIndicator
@@ -53,111 +55,117 @@
53
55
  </div>
54
56
  </template>
55
57
 
56
- <script>
57
- import AposFocusMixin from 'Modules/@apostrophecms/modal/mixins/AposFocusMixin';
58
-
59
- export default {
60
- name: 'AposButtonSplit',
61
- mixins: [
62
- AposFocusMixin
63
- ],
64
- props: {
65
- menu: {
66
- type: Array,
67
- required: true
68
- },
69
- menuLabel: {
70
- type: String,
71
- required: true
72
- },
73
- type: {
74
- type: String,
75
- default: 'primary'
76
- },
77
- disabled: {
78
- type: Boolean,
79
- default: false
80
- },
81
- tooltip: {
82
- type: [ String, Object ],
83
- default: null
84
- },
85
- selected: {
86
- // corresponds to a menu item action
87
- type: String,
88
- default: null
89
- }
58
+ <script setup>
59
+ import {
60
+ ref, computed, watch, nextTick
61
+ } from 'vue';
62
+ import { useAposFocus } from 'Modules/@apostrophecms/modal/composables/AposFocus';
63
+
64
+ const {
65
+ elementsToFocus,
66
+ cycleElementsToFocus,
67
+ focusElement,
68
+ focuslastmodalfocusedelement
69
+ } = useAposFocus();
70
+
71
+ const props = defineProps({
72
+ menu: {
73
+ type: Array,
74
+ required: true
90
75
  },
91
- emits: [ 'click' ],
92
- data() {
93
- return {
94
- label: null,
95
- action: null,
96
- button: {
97
- type: this.type,
98
- modifiers: [ 'no-motion' ]
99
- },
100
- contextMenuButton: {
101
- iconOnly: true,
102
- icon: 'chevron-down-icon',
103
- modifiers: [ 'no-motion' ],
104
- type: this.type
105
- }
106
- };
76
+ menuLabel: {
77
+ type: String,
78
+ required: true
107
79
  },
108
- computed: {
109
- modifiers() {
110
- const classes = [];
111
- classes.push(`apos-button-split--type-${this.button.type}`);
112
- return classes;
113
- }
80
+ type: {
81
+ type: String,
82
+ default: 'primary'
114
83
  },
115
- watch: {
116
- menu() {
117
- this.initialize();
118
- }
84
+ disabled: {
85
+ type: Boolean,
86
+ default: false
119
87
  },
120
- mounted() {
121
- this.initialize();
88
+ tooltip: {
89
+ type: [ String, Object ],
90
+ default: null
122
91
  },
123
- methods: {
124
- // sets the label and emitted action of the button
125
- setButton(action) {
126
- this.action = action;
127
- this.label = this.menu.find(i => i.action === action).label;
128
- },
129
- selectionHandler(action) {
130
- this.setButton(action);
131
- this.$refs.contextMenu.hide();
132
- },
133
- initialize() {
134
- let initial = this.menu[0].action || null;
135
- if (this.selected && this.menu.find(i => i.action === this.selected)) {
136
- initial = this.selected;
137
- } else if (this.menu.find(i => i.def)) {
138
- initial = this.menu.find(i => i.def).action;
139
- }
140
- this.setButton(initial);
141
- },
142
- trapFocus() {
143
- const selectedElementIndex = this.menu.findIndex(i => i.action === this.action) || 0;
144
-
145
- // use map to keep items order:
146
- this.elementsToFocus = this.menu.map(
147
- i => this.$refs.choices.find(choice => choice.value === i.action)
148
- );
149
-
150
- this.focusElement(this.elementsToFocus[selectedElementIndex]);
151
- },
152
- menuOpen() {
153
- // TODO: find another way to wait for elements to be visible
154
- setTimeout(this.trapFocus, 200);
155
- },
156
- menuClose() {
157
- this.focusLastModalFocusedElement();
158
- }
92
+ selected: {
93
+ // corresponds to a menu item action
94
+ type: String,
95
+ default: null
159
96
  }
160
- };
97
+ });
98
+
99
+ const emit = defineEmits([ 'click' ]);
100
+
101
+ const label = ref(null);
102
+ const action = ref(null);
103
+ const button = ref({
104
+ type: props.type,
105
+ modifiers: [ 'no-motion' ]
106
+ });
107
+ const contextMenu = ref();
108
+ const choices = ref([]);
109
+ const contextMenuButton = ref({
110
+ iconOnly: true,
111
+ icon: 'chevron-down-icon',
112
+ modifiers: [ 'no-motion' ],
113
+ type: props.type
114
+ });
115
+
116
+ const modifiers = computed(() => {
117
+ const classes = [];
118
+ classes.push(`apos-button-split--type-${button.value.type}`);
119
+ return classes;
120
+ });
121
+
122
+ watch(() => props.menu, () => {
123
+ initialize();
124
+ }, { immediate: true });
125
+
126
+ // sets the label and emitted action of the button
127
+ function setButton(btnAction) {
128
+ action.value = btnAction;
129
+ label.value = props.menu.find(i => i.action === btnAction).label;
130
+ }
131
+
132
+ function selectionHandler(btnAction) {
133
+ setButton(btnAction);
134
+ contextMenu.value.hide();
135
+ }
136
+
137
+ function initialize() {
138
+ let initial = props.menu[0].action || null;
139
+ if (props.selected && props.menu.find(i => i.action === props.selected)) {
140
+ initial = props.selected;
141
+ } else if (props.menu.find(i => i.def)) {
142
+ initial = props.menu.find(i => i.def).action;
143
+ }
144
+
145
+ setButton(initial);
146
+ }
147
+
148
+ function trapFocus() {
149
+ const selectedElementIndex = props.menu.findIndex(i => i.action === action.value) || 0;
150
+
151
+ // use map to keep items order:
152
+ elementsToFocus.value = props.menu.map(
153
+ ({ action }) => choices.value.find(choice => {
154
+ return choice.value === action;
155
+ })
156
+ );
157
+
158
+ focusElement(elementsToFocus.value[selectedElementIndex]);
159
+ }
160
+ function menuOpen() {
161
+ nextTick(() => {
162
+ trapFocus();
163
+ });
164
+ }
165
+
166
+ function menuClose() {
167
+ focuslastmodalfocusedelement();
168
+ }
161
169
  </script>
162
170
  <style lang="scss" scoped>
163
171
  .apos-button-split {
@@ -208,7 +216,7 @@ export default {
208
216
  font-size: var(--a-type-base);
209
217
  }
210
218
 
211
- .apos-button-split__button ::v-deep .apos-button {
219
+ .apos-button-split__button :deep(.apos-button) {
212
220
  padding-right: $spacing-quadruple + $spacing-base;
213
221
  margin-top: 0;
214
222
  margin-bottom: 0;
@@ -219,14 +227,13 @@ export default {
219
227
  top: 0;
220
228
  right: 0;
221
229
  height: 100%;
222
- ::v-deep {
223
- .v-popover,
224
- .trigger,
225
- .apos-button__wrapper {
226
- height: 100%;
227
- }
230
+
231
+ :deep(.apos-popover__btn),
232
+ :deep(.trigger),
233
+ :deep(.apos-button__wrapper) {
234
+ height: 100%;
228
235
  }
229
- ::v-deep .apos-button {
236
+ :deep(.apos-button) {
230
237
  display: flex;
231
238
  box-sizing: border-box;
232
239
  height: 100%;
@@ -1,6 +1,7 @@
1
1
  <template>
2
2
  <button
3
- class="apos-table__cell-field" type="button"
3
+ class="apos-table__cell-field"
4
+ type="button"
4
5
  :class="`apos-table__cell-field--${header.name}`"
5
6
  >
6
7
  {{ get(header.name) }}