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,538 @@
1
+ const t = require('../test-lib/test.js');
2
+ const assert = require('assert/strict');
3
+
4
+ describe('Translation', function () {
5
+ let apos = null;
6
+
7
+ this.timeout(t.timeout);
8
+
9
+ const boot = async function () {
10
+ apos = await t.create({
11
+ root: module,
12
+ modules: {
13
+ 'fake-translation-provider': {
14
+ options: {
15
+ alias: 'fakeProvider'
16
+ },
17
+ methods: (self) => ({
18
+ async translate(req, provider, doc, source, target) {
19
+ doc.title = `${provider}-${source}-${target}-${doc.title}-translated`;
20
+ return doc;
21
+ },
22
+ async getSupportedLanguages(req, {
23
+ provider, source, target
24
+ } = {}) {
25
+ const supported = [ 'en', 'es', 'fr' ];
26
+ const sourceRespone = source?.length
27
+ ? source.map((code) => ({
28
+ code,
29
+ supported: supported.includes(code)
30
+ }))
31
+ : supported.map((code) => ({
32
+ code,
33
+ supported: true
34
+ }));
35
+ const targetResponse = target?.length
36
+ ? target.map((code) => ({
37
+ code,
38
+ supported: supported.includes(code)
39
+ }))
40
+ : supported.map((code) => ({
41
+ code,
42
+ supported: true
43
+ }));
44
+
45
+ return {
46
+ source: sourceRespone,
47
+ target: targetResponse
48
+ };
49
+ }
50
+ })
51
+ },
52
+ 'default-page': {
53
+ extend: '@apostrophecms/page-type',
54
+ options: {
55
+ label: 'Default'
56
+ }
57
+ }
58
+ }
59
+ });
60
+ };
61
+
62
+ before(async function () {
63
+ await boot();
64
+ });
65
+
66
+ after(function () {
67
+ return t.destroy(apos);
68
+ });
69
+
70
+ it('should be disabled if no providers', async function () {
71
+ const translation = apos.modules['@apostrophecms/translation'];
72
+ assert.equal(translation.isEnabled(), false);
73
+ });
74
+
75
+ it('should add a translation provider', async function () {
76
+ const translation = apos.modules['@apostrophecms/translation'];
77
+ const provider = apos.fakeProvider;
78
+
79
+ translation.addProvider(provider, {
80
+ label: 'Fake Provider',
81
+ name: 'fake'
82
+ });
83
+ assert.equal(translation.providers.length, 1);
84
+ assert.equal(translation.providers[0].name, 'fake');
85
+ assert.equal(translation.isEnabled(), true);
86
+ });
87
+
88
+ it('should validate the provider interface', async function () {
89
+ const translation = apos.modules['@apostrophecms/translation'];
90
+
91
+ assert.throws(() => translation.addProvider(), {
92
+ cause: 'invalidArguments'
93
+ });
94
+ assert.throws(() => translation.addProvider({}, { name: 'anotherFake' }), {
95
+ cause: 'invalidArguments'
96
+ });
97
+ assert.throws(
98
+ () => translation.addProvider(
99
+ { __meta: { name: 'another-translation-provider' } },
100
+ { name: 'anotherFake' }
101
+ ),
102
+ { cause: 'interfaceNotImplemented' }
103
+ );
104
+ assert.throws(
105
+ () => translation.addProvider(
106
+ {
107
+ __meta: { name: 'another-translation-provider' },
108
+ translate() {}
109
+ },
110
+ { name: 'anotherFake' }
111
+ ),
112
+ { cause: 'interfaceNotImplemented' }
113
+ );
114
+ assert.throws(
115
+ () => translation.addProvider(
116
+ {
117
+ __meta: { name: 'another-translation-provider' },
118
+ getSupportedLanguages() {}
119
+ },
120
+ { name: 'anotherFake' }
121
+ ),
122
+ { cause: 'interfaceNotImplemented' }
123
+ );
124
+ assert.throws(
125
+ () => translation.addProvider(
126
+ {
127
+ __meta: { name: 'another-translation-provider' },
128
+ translate() {},
129
+ getSupportedLanguages() {}
130
+ },
131
+ { name: 'fake' }
132
+ ),
133
+ { cause: 'duplicate' }
134
+ );
135
+ });
136
+
137
+ it('should get a translation provider by its name', async function () {
138
+ const translation = apos.modules['@apostrophecms/translation'];
139
+
140
+ assert.deepEqual(translation.getProvider('fake'), {
141
+ name: 'fake',
142
+ label: 'Fake Provider',
143
+ module: 'fake-translation-provider'
144
+ });
145
+ assert.equal(translation.getProvider('anotherFake'), undefined);
146
+ });
147
+
148
+ it('should get the first provider if no name is provided', async function () {
149
+ const translation = apos.modules['@apostrophecms/translation'];
150
+
151
+ assert.deepEqual(translation.getProvider(), {
152
+ name: 'fake',
153
+ label: 'Fake Provider',
154
+ module: 'fake-translation-provider'
155
+ });
156
+ });
157
+
158
+ it('should get the provider module by its name', async function () {
159
+ const translation = apos.modules['@apostrophecms/translation'];
160
+
161
+ assert.deepEqual(translation.getProviderModule('fake'), apos.fakeProvider);
162
+ assert.equal(translation.getProviderModule('anotherFake'), undefined);
163
+ });
164
+
165
+ it('should get the first provider module if no name is provided', async function () {
166
+ const translation = apos.modules['@apostrophecms/translation'];
167
+
168
+ assert.deepEqual(translation.getProviderModule(), apos.fakeProvider);
169
+ });
170
+
171
+ it('should get browser data', async function () {
172
+ const translation = apos.modules['@apostrophecms/translation'];
173
+ const req = apos.task.getReq();
174
+
175
+ assert.deepEqual(translation.getBrowserData(req), {
176
+ action: '/api/v1/@apostrophecms/translation',
177
+ enabled: true,
178
+ providers: [
179
+ {
180
+ name: 'fake',
181
+ label: 'Fake Provider'
182
+ }
183
+ ]
184
+ });
185
+ });
186
+
187
+ it('should invoke translation on beforeLocalize', async function () {
188
+ const translation = apos.modules['@apostrophecms/translation'];
189
+ const handler = translation.handlers['@apostrophecms/doc-type:beforeLocalize']
190
+ .translate;
191
+
192
+ assert(handler);
193
+
194
+ const req = apos.task.getReq({
195
+ query: {
196
+ aposTranslateTargets: [ 'es', 'fr' ]
197
+ }
198
+ });
199
+ const doc = {
200
+ _id: '123',
201
+ title: 'Hello World'
202
+ };
203
+ const source = 'en';
204
+ const target = 'es';
205
+ const existing = false;
206
+
207
+ await handler(req, doc, {
208
+ source,
209
+ target,
210
+ existing
211
+ });
212
+ assert.equal(doc.title, 'fake-en-es-Hello World-translated');
213
+ });
214
+
215
+ it('should skip translation on beforeLocalize if no providers', async function () {
216
+ const translation = apos.modules['@apostrophecms/translation'];
217
+ const providers = translation.providers;
218
+ translation.providers = [];
219
+ const handler = translation.handlers['@apostrophecms/doc-type:beforeLocalize']
220
+ .translate;
221
+
222
+ const savedNotify = apos.notify;
223
+ let notifyCallArgs = null;
224
+ apos.notify = function (...args) {
225
+ notifyCallArgs = args;
226
+ };
227
+
228
+ assert(handler);
229
+
230
+ const req = apos.task.getReq({
231
+ query: {
232
+ aposTranslateTargets: [ 'es', 'fr' ]
233
+ }
234
+ });
235
+ const doc = {
236
+ _id: '123',
237
+ title: 'Hello World'
238
+ };
239
+ const source = 'en';
240
+ const target = 'es';
241
+ const existing = false;
242
+
243
+ await handler(req, doc, {
244
+ source,
245
+ target,
246
+ existing
247
+ });
248
+ translation.providers = providers;
249
+ apos.notify = savedNotify;
250
+
251
+ assert.equal(doc.title, 'Hello World');
252
+ assert.equal(notifyCallArgs, null);
253
+ });
254
+
255
+ it('should skip translation on beforeLocalize if disabled', async function () {
256
+ const translation = apos.modules['@apostrophecms/translation'];
257
+ translation.options.enabled = false;
258
+ const handler = translation.handlers['@apostrophecms/doc-type:beforeLocalize']
259
+ .translate;
260
+
261
+ const savedNotify = apos.notify;
262
+ let notifyCallArgs = null;
263
+ apos.notify = function (...args) {
264
+ notifyCallArgs = args;
265
+ };
266
+
267
+ assert(handler);
268
+
269
+ const req = apos.task.getReq({
270
+ query: {
271
+ aposTranslateTargets: [ 'es', 'fr' ]
272
+ }
273
+ });
274
+ const doc = {
275
+ _id: '123',
276
+ title: 'Hello World'
277
+ };
278
+ const source = 'en';
279
+ const target = 'es';
280
+ const existing = false;
281
+
282
+ await handler(req, doc, {
283
+ source,
284
+ target,
285
+ existing
286
+ });
287
+ translation.options.enabled = true;
288
+ apos.notify = savedNotify;
289
+
290
+ assert.equal(doc.title, 'Hello World');
291
+ assert.equal(notifyCallArgs, null);
292
+ });
293
+
294
+ it('should skip translation on beforeLocalize if bad provider', async function () {
295
+ const translation = apos.modules['@apostrophecms/translation'];
296
+ const handler = translation.handlers['@apostrophecms/doc-type:beforeLocalize']
297
+ .translate;
298
+
299
+ const savedNotify = apos.notify;
300
+ let notifyCallArgs = null;
301
+ apos.notify = function (...args) {
302
+ notifyCallArgs = args;
303
+ };
304
+
305
+ assert(handler);
306
+
307
+ const req = apos.task.getReq({
308
+ query: {
309
+ aposTranslateTargets: [ 'es', 'fr' ],
310
+ aposTranslateProvider: 'badProvider'
311
+ }
312
+ });
313
+ const doc = {
314
+ _id: '123',
315
+ title: 'Hello World'
316
+ };
317
+ const source = 'en';
318
+ const target = 'es';
319
+ const existing = false;
320
+
321
+ await handler(req, doc, {
322
+ source,
323
+ target,
324
+ existing
325
+ });
326
+ apos.notify = savedNotify;
327
+
328
+ assert.equal(doc.title, 'Hello World');
329
+ assert.deepEqual(notifyCallArgs, [
330
+ req,
331
+ 'Translation provider not found. Page "Hello World" was not translated.',
332
+ {
333
+ type: 'danger',
334
+ dismiss: true
335
+ }
336
+ ]);
337
+ });
338
+
339
+ it('should skip translation on beforeLocalize if no target match', async function () {
340
+ const translation = apos.modules['@apostrophecms/translation'];
341
+ const handler = translation.handlers['@apostrophecms/doc-type:beforeLocalize']
342
+ .translate;
343
+ const savedNotify = apos.notify;
344
+ let notifyCallArgs = null;
345
+ apos.notify = function (...args) {
346
+ notifyCallArgs = args;
347
+ };
348
+
349
+ assert(handler);
350
+
351
+ const req = apos.task.getReq({
352
+ query: {
353
+ aposTranslateTargets: [ 'fr' ],
354
+ aposTranslateProvider: 'fake'
355
+ }
356
+ });
357
+ const doc = {
358
+ _id: '123',
359
+ title: 'Hello World'
360
+ };
361
+ const source = 'en';
362
+ const target = 'es';
363
+ const options = {};
364
+
365
+ await handler(req, doc, source, target, options);
366
+ apos.notify = savedNotify;
367
+
368
+ assert.equal(doc.title, 'Hello World');
369
+ assert.equal(notifyCallArgs, null);
370
+ });
371
+
372
+ describe('API', function () {
373
+ let jar = null;
374
+
375
+ before(async function () {
376
+ jar = apos.http.jar();
377
+ await t.createAdmin(apos, {
378
+ username: 'admin',
379
+ password: 'admin'
380
+ });
381
+ await t.createUser(apos, 'editor', {
382
+ username: 'editor',
383
+ password: 'editor'
384
+ });
385
+ await t.createUser(apos, 'guest', {
386
+ username: 'guest',
387
+ password: 'guest'
388
+ });
389
+ });
390
+
391
+ async function login(role = 'admin') {
392
+ jar = await t.loginAs(apos, role);
393
+ const page = await apos.http.get('/', {
394
+ jar
395
+ });
396
+ assert(page.match(/logged in/));
397
+ }
398
+
399
+ it('should not get supported languages when not authorized', async function () {
400
+ jar = apos.http.jar();
401
+ await assert.rejects(
402
+ async () => apos.http.get(
403
+ '/api/v1/@apostrophecms/translation/languages',
404
+ {
405
+ jar,
406
+ qs: {
407
+ provider: 'fake',
408
+ source: [ 'en' ],
409
+ target: [ 'es' ]
410
+ }
411
+ }
412
+ ),
413
+ {
414
+ status: 404
415
+ }
416
+ );
417
+
418
+ await login('guest');
419
+ await assert.rejects(
420
+ async () => apos.http.get(
421
+ '/api/v1/@apostrophecms/translation/languages',
422
+ {
423
+ jar,
424
+ qs: {
425
+ provider: 'fake',
426
+ source: [ 'en' ],
427
+ target: [ 'es' ]
428
+ }
429
+ }
430
+ ),
431
+ {
432
+ status: 404
433
+ }
434
+ );
435
+ await t.logout(apos, 'guest', 'guest', jar);
436
+ jar = apos.http.jar();
437
+ });
438
+
439
+ it('should get supported languages when authorized', async function () {
440
+ await login('editor');
441
+
442
+ {
443
+ const res = await apos.http.get(
444
+ '/api/v1/@apostrophecms/translation/languages',
445
+ {
446
+ jar,
447
+ qs: {
448
+ // Test without provider to get the first available
449
+ source: [ 'en' ],
450
+ // Don't get confused, `et` stands for
451
+ // Extra- Terrestrial(E.T.)!
452
+ target: [ 'es', 'et' ]
453
+ }
454
+ }
455
+ );
456
+
457
+ assert.deepEqual(res, {
458
+ source: [
459
+ {
460
+ code: 'en',
461
+ supported: true
462
+ }
463
+ ],
464
+ target: [
465
+ {
466
+ code: 'es',
467
+ supported: true
468
+ },
469
+ {
470
+ code: 'et',
471
+ supported: false
472
+ }
473
+ ]
474
+ });
475
+ }
476
+
477
+ await t.logout(apos, 'editor', 'editor', jar);
478
+ await login('admin');
479
+ {
480
+ const res = await apos.http.get(
481
+ '/api/v1/@apostrophecms/translation/languages',
482
+ {
483
+ jar,
484
+ qs: {
485
+ provider: 'fake',
486
+ source: [ 'en' ],
487
+ // Don't get confused, `et` stands for
488
+ // Extra- Terrestrial(E.T.)!
489
+ target: [ 'es', 'et' ]
490
+ }
491
+ }
492
+ );
493
+
494
+ assert.deepEqual(res, {
495
+ source: [
496
+ {
497
+ code: 'en',
498
+ supported: true
499
+ }
500
+ ],
501
+ target: [
502
+ {
503
+ code: 'es',
504
+ supported: true
505
+ },
506
+ {
507
+ code: 'et',
508
+ supported: false
509
+ }
510
+ ]
511
+ });
512
+
513
+ await t.logout(apos, 'admin', 'admin', jar);
514
+ jar = apos.http.jar();
515
+ }
516
+ });
517
+
518
+ it('should validate the provider name', async function () {
519
+ await login('admin');
520
+ await assert.rejects(
521
+ async () => apos.http.get(
522
+ '/api/v1/@apostrophecms/translation/languages',
523
+ {
524
+ jar,
525
+ qs: {
526
+ provider: 'badProvider',
527
+ source: [ 'en' ],
528
+ target: [ 'es' ]
529
+ }
530
+ }
531
+ ),
532
+ {
533
+ status: 400
534
+ }
535
+ );
536
+ });
537
+ });
538
+ });
package/test-lib/util.js CHANGED
@@ -118,6 +118,26 @@ async function loginAs(apos, username, password) {
118
118
  return jar;
119
119
  };
120
120
 
121
+ async function logout(apos, username, password, jar) {
122
+ await apos.http.post(
123
+ '/api/v1/@apostrophecms/login/logout',
124
+ {
125
+ body: {
126
+ username,
127
+ password,
128
+ session: true
129
+ },
130
+ jar
131
+ }
132
+ );
133
+ await apos.http.get(
134
+ '/',
135
+ {
136
+ jar
137
+ }
138
+ );
139
+ };
140
+
121
141
  // Deprecated legacy wrapper for loginAs.
122
142
  function getUserJar(apos, { username = 'admin', password } = {}) {
123
143
  return loginAs(apos, username, password);
@@ -129,6 +149,7 @@ module.exports = {
129
149
  createAdmin,
130
150
  createUser,
131
151
  loginAs,
152
+ logout,
132
153
  getUserJar,
133
154
  timeout: (process.env.TEST_TIMEOUT && parseInt(process.env.TEST_TIMEOUT)) || 20000
134
155
  };
@@ -1,63 +0,0 @@
1
- // Vue plugin. Create a new directive with i18n support by applying the decorator
2
- // pattern to VTooltip, then add it to the Vue instance
3
-
4
- import { VTooltip } from 'v-tooltip';
5
-
6
- export default {
7
- install(Vue, options) {
8
-
9
- const directive = {};
10
-
11
- Object.assign(VTooltip.options, options);
12
- let instance;
13
-
14
- // Right now VTooltip only uses bind, but be forwards-compatible
15
- extendHandler('bind');
16
- extendHandler('inserted');
17
- extendHandler('update');
18
- extendHandler('componentUpdated');
19
- extendHandler('unbind');
20
-
21
- Vue.directive('apos-tooltip', directive);
22
-
23
- function extendHandler(name) {
24
- if (VTooltip[name]) {
25
- directive[name] = (el, binding, vnode, oldVnode) => {
26
- return VTooltip[name](el, {
27
- ...binding,
28
- value: localize(binding.value)
29
- }, vnode, oldVnode);
30
- };
31
- }
32
- }
33
-
34
- function localize(value) {
35
- if (!value) {
36
- return;
37
- }
38
- if (!instance) {
39
- // A headless Vue instance to call $t on. We do this late so we
40
- // know $t is ready
41
- instance = new Vue();
42
- }
43
- // Something stringable
44
- if (!value) {
45
- // VTooltip will be confused if $t converts a falsy value to the string "false"
46
- return value;
47
- }
48
- if (value.content) {
49
- // Object with a content property
50
- if (value && value.content) {
51
- return {
52
- ...value,
53
- content: (value.localize === false) ? value.content : instance.$t(value.content)
54
- };
55
- } else {
56
- return value;
57
- }
58
- } else {
59
- return instance.$t(value);
60
- }
61
- }
62
- }
63
- };
@@ -1,13 +0,0 @@
1
- export default {
2
- defaultOffset: '11',
3
- defaultPlacement: 'bottom-end',
4
- defaultTemplate: `
5
- <div class="apos-tooltip" role="tooltip">
6
- <div class="apos-tooltip__wrapper">
7
- <div class="apos-tooltip__arrow"></div>
8
- <div class="apos-tooltip__inner"></div>
9
- </div>
10
- </div>`,
11
- defaultArrowSelector: '.apos-tooltip__arrow',
12
- defaultInnerSelector: '.apos-tooltip__inner'
13
- };