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.
- package/.eslintrc +13 -4
- package/CHANGELOG.md +37 -5
- package/defaults.js +2 -1
- package/modules/@apostrophecms/admin-bar/ui/apos/apps/AposAdminBar.js +7 -17
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBar.vue +14 -16
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarLocale.vue +1 -1
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarMenu.vue +22 -15
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarUser.vue +2 -2
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +13 -8
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextModeAndSettings.vue +18 -10
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextTitle.vue +4 -4
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextUndoRedo.vue +14 -8
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposSavingIndicator.vue +2 -1
- package/modules/@apostrophecms/area/ui/apos/apps/AposAreas.js +36 -54
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaContextualMenu.vue +20 -25
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +5 -12
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaExpandedMenu.vue +11 -3
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaMenu.vue +6 -6
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaMenuItem.vue +3 -2
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +31 -44
- package/modules/@apostrophecms/area/ui/apos/components/AposWidgetControls.vue +16 -16
- package/modules/@apostrophecms/asset/index.js +25 -12
- package/modules/@apostrophecms/asset/lib/webpack/apos/webpack.config.js +3 -3
- package/modules/@apostrophecms/asset/lib/webpack/apos/webpack.vue.js +7 -1
- package/modules/@apostrophecms/attachment/index.js +2 -1
- package/modules/@apostrophecms/attachment/public/img/missing-icon.svg +14 -0
- package/modules/@apostrophecms/busy/ui/apos/apps/AposBusy.js +8 -7
- package/modules/@apostrophecms/busy/ui/apos/components/TheAposBusy.vue +1 -1
- package/modules/@apostrophecms/command-menu/ui/apos/apps/AposCommandMenu.js +11 -29
- package/modules/@apostrophecms/command-menu/ui/apos/components/AposCommandMenuKey.vue +1 -1
- package/modules/@apostrophecms/command-menu/ui/apos/components/AposCommandMenuShortcut.vue +6 -6
- package/modules/@apostrophecms/command-menu/ui/apos/components/TheAposCommandMenu.vue +10 -7
- package/modules/@apostrophecms/doc-type/index.js +34 -13
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +3 -3
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +20 -15
- package/modules/@apostrophecms/doc-type/ui/apos/logic/AposDocContextMenu.js +1 -1
- package/modules/@apostrophecms/i18n/i18n/en.json +13 -0
- package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +209 -33
- package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalizeErrors.vue +3 -3
- package/modules/@apostrophecms/image/ui/apos/components/AposImageCropper.vue +5 -5
- package/modules/@apostrophecms/image/ui/apos/components/AposImageRelationshipEditor.vue +6 -6
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +23 -16
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerDisplay.vue +11 -11
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerEditor.vue +28 -21
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerSelections.vue +4 -3
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaUploader.vue +5 -4
- package/modules/@apostrophecms/login/index.js +18 -1
- package/modules/@apostrophecms/login/ui/apos/apps/AposLogin.js +6 -7
- package/modules/@apostrophecms/login/ui/apos/components/AposForgotPasswordForm.vue +3 -3
- package/modules/@apostrophecms/login/ui/apos/components/AposLoginForm.vue +10 -10
- package/modules/@apostrophecms/login/ui/apos/components/AposResetPasswordForm.vue +3 -3
- package/modules/@apostrophecms/login/ui/apos/components/TheAposLogin.vue +2 -2
- package/modules/@apostrophecms/login/ui/apos/logic/AposLoginForm.js +1 -16
- package/modules/@apostrophecms/modal/ui/apos/apps/AposModals.js +60 -87
- package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +15 -11
- package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +17 -11
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalBreadcrumbs.vue +7 -4
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +9 -9
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalLip.vue +2 -2
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalShareDraft.vue +8 -8
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalTabs.vue +4 -3
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalToolbar.vue +7 -7
- package/modules/@apostrophecms/modal/ui/apos/components/TheAposModals.vue +22 -4
- package/modules/@apostrophecms/modal/ui/apos/composables/AposFocus.js +95 -0
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocsManagerMixin.js +2 -3
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +6 -0
- package/modules/@apostrophecms/notification/index.js +4 -4
- package/modules/@apostrophecms/notification/ui/apos/apps/AposNotification.js +6 -9
- package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +12 -8
- package/modules/@apostrophecms/notification/ui/apos/components/TheAposNotifications.vue +4 -2
- package/modules/@apostrophecms/oembed-field/ui/apos/components/AposInputOembed.vue +12 -10
- package/modules/@apostrophecms/page/index.js +1 -0
- package/modules/@apostrophecms/page/ui/apos/components/AposPagesManager.vue +15 -11
- package/modules/@apostrophecms/page/ui/apos/logic/AposPagesManager.js +1 -1
- package/modules/@apostrophecms/permission/ui/apos/components/AposInputRole.vue +3 -3
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +25 -17
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +24 -20
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerSelectBox.vue +9 -5
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposRelationshipEditor.vue +15 -11
- package/modules/@apostrophecms/rich-text-widget/index.js +1 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposImageControlDialog.vue +7 -6
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +31 -30
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapAnchor.vue +12 -10
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapButton.vue +1 -1
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapImage.vue +1 -1
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapLink.vue +9 -8
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapStyles.vue +3 -3
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapTable.vue +3 -3
- package/modules/@apostrophecms/schema/index.js +69 -8
- package/modules/@apostrophecms/schema/lib/addFieldTypes.js +1 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +10 -8
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputArea.vue +5 -3
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +81 -277
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputAttachment.vue +4 -2
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputBoolean.vue +24 -14
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputCheckboxes.vue +7 -6
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputColor.vue +10 -7
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputObject.vue +3 -3
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputPassword.vue +6 -4
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRadio.vue +5 -4
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRange.vue +9 -6
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +15 -12
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSelect.vue +1 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSlug.vue +16 -12
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +19 -11
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +15 -12
- package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +75 -22
- package/modules/@apostrophecms/schema/ui/apos/components/AposSearchList.vue +1 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposSubform.vue +2 -2
- package/modules/@apostrophecms/schema/ui/apos/logic/AposArrayEditor.js +0 -4
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputArea.js +3 -3
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputArray.js +15 -4
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputAttachment.js +3 -3
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputCheckboxes.js +7 -7
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputColor.js +5 -8
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputDateAndTime.js +1 -1
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputObject.js +1 -1
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRadio.js +1 -1
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRelationship.js +12 -9
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputSelect.js +3 -3
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputString.js +7 -9
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputWrapper.js +4 -4
- package/modules/@apostrophecms/schema/ui/apos/logic/AposSchema.js +42 -13
- package/modules/@apostrophecms/schema/ui/apos/logic/AposSubform.js +1 -1
- package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputMixin.js +9 -9
- package/modules/@apostrophecms/schema/ui/apos/scss/AposInputArray.scss +205 -0
- package/modules/@apostrophecms/settings/ui/apos/components/AposSettingsManager.vue +5 -5
- package/modules/@apostrophecms/settings/ui/apos/logic/AposSettingsManager.js +4 -4
- package/modules/@apostrophecms/submitted-draft/ui/apos/components/AposSubmittedDraftIcon.vue +5 -4
- package/modules/@apostrophecms/task/index.js +2 -0
- package/modules/@apostrophecms/translation/index.js +233 -0
- package/modules/@apostrophecms/translation/ui/apos/components/AposTranslationIndicator.vue +84 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposAvatar.vue +2 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +4 -4
- package/modules/@apostrophecms/ui/ui/apos/components/AposButtonGroup.vue +6 -6
- package/modules/@apostrophecms/ui/ui/apos/components/AposButtonSplit.vue +120 -113
- package/modules/@apostrophecms/ui/ui/apos/components/AposCellButton.vue +2 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposCellLabels.vue +49 -5
- package/modules/@apostrophecms/ui/ui/apos/components/AposCheckbox.vue +19 -19
- package/modules/@apostrophecms/ui/ui/apos/components/AposCloudUploadIcon.vue +10 -5
- package/modules/@apostrophecms/ui/ui/apos/components/AposCombo.vue +15 -15
- package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenu.vue +214 -191
- package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenuDialog.vue +77 -65
- package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenuItem.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenuTip.vue +28 -50
- package/modules/@apostrophecms/ui/ui/apos/components/AposEmptyState.vue +3 -3
- package/modules/@apostrophecms/ui/ui/apos/components/AposFile.vue +5 -5
- package/modules/@apostrophecms/ui/ui/apos/components/AposFilterMenu.vue +4 -4
- package/modules/@apostrophecms/ui/ui/apos/components/AposIndicator.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposLabel.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposMinMaxCount.vue +5 -5
- package/modules/@apostrophecms/ui/ui/apos/components/AposPager.vue +14 -8
- package/modules/@apostrophecms/ui/ui/apos/components/AposPagerDots.vue +2 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposSlat.vue +13 -12
- package/modules/@apostrophecms/ui/ui/apos/components/AposSlatList.vue +53 -59
- package/modules/@apostrophecms/ui/ui/apos/components/AposSpinner.vue +2 -2
- package/modules/@apostrophecms/ui/ui/apos/components/AposSubformPreview.vue +2 -2
- package/modules/@apostrophecms/ui/ui/apos/components/AposTag.vue +3 -2
- package/modules/@apostrophecms/ui/ui/apos/components/AposTagApply.vue +40 -35
- package/modules/@apostrophecms/ui/ui/apos/components/AposTagListItem.vue +2 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposToggle.vue +2 -2
- package/modules/@apostrophecms/ui/ui/apos/components/AposTree.vue +9 -11
- package/modules/@apostrophecms/ui/ui/apos/components/AposTreeHeader.vue +5 -3
- package/modules/@apostrophecms/ui/ui/apos/components/AposTreeRows.vue +129 -129
- package/modules/@apostrophecms/ui/ui/apos/composables/AposTheme.js +11 -0
- package/modules/@apostrophecms/ui/ui/apos/lib/click-outside-element.js +4 -4
- package/modules/@apostrophecms/ui/ui/apos/lib/i18next.js +56 -50
- package/modules/@apostrophecms/ui/ui/apos/lib/tooltip.js +191 -0
- package/modules/@apostrophecms/ui/ui/apos/lib/vue.js +19 -10
- package/modules/@apostrophecms/ui/ui/apos/mixins/AposAdvisoryLockMixin.js +1 -1
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_tables.scss +1 -1
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_theme.scss +1 -0
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_tooltips.scss +6 -22
- package/modules/@apostrophecms/ui/ui/apos/scss/shared/_table-rows.scss +1 -1
- package/modules/@apostrophecms/widget-type/index.js +8 -2
- package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +26 -22
- package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +4 -4
- package/package.json +31 -44
- package/test/attachments.js +5 -0
- package/test/schemas.js +138 -0
- package/test/translation.js +538 -0
- package/test-lib/util.js +21 -0
- package/modules/@apostrophecms/ui/ui/apos/lib/localized-v-tooltip.js +0 -63
- 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>
|
|
@@ -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
|
-
|
|
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-
|
|
20
|
+
v-on="href ? {} : {click: click}"
|
|
21
21
|
>
|
|
22
22
|
<transition name="fade">
|
|
23
|
-
<AposSpinner
|
|
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
|
|
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
|
|
85
|
-
.apos-button-group
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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="
|
|
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"
|
|
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"
|
|
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
|
-
|
|
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
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
84
|
+
disabled: {
|
|
85
|
+
type: Boolean,
|
|
86
|
+
default: false
|
|
119
87
|
},
|
|
120
|
-
|
|
121
|
-
|
|
88
|
+
tooltip: {
|
|
89
|
+
type: [ String, Object ],
|
|
90
|
+
default: null
|
|
122
91
|
},
|
|
123
|
-
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
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
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
230
|
+
|
|
231
|
+
:deep(.apos-popover__btn),
|
|
232
|
+
:deep(.trigger),
|
|
233
|
+
:deep(.apos-button__wrapper) {
|
|
234
|
+
height: 100%;
|
|
228
235
|
}
|
|
229
|
-
|
|
236
|
+
:deep(.apos-button) {
|
|
230
237
|
display: flex;
|
|
231
238
|
box-sizing: border-box;
|
|
232
239
|
height: 100%;
|