apostrophe 3.8.0 → 3.10.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/.github/workflows/main.yml +45 -0
- package/CHANGELOG.md +52 -0
- package/README.md +1 -2
- package/lib/moog.js +26 -1
- package/modules/@apostrophecms/admin-bar/ui/apos/apps/AposAdminBar.js +7 -1
- package/modules/@apostrophecms/any-page-type/index.js +2 -0
- package/modules/@apostrophecms/area/ui/apos/apps/AposAreas.js +28 -11
- package/modules/@apostrophecms/asset/index.js +35 -4
- package/modules/@apostrophecms/asset/lib/webpack/apos/webpack.config.js +2 -2
- package/modules/@apostrophecms/asset/lib/webpack/src/webpack.config.js +1 -1
- package/modules/@apostrophecms/attachment/index.js +0 -4
- package/modules/@apostrophecms/busy/ui/apos/apps/AposBusy.js +3 -1
- package/modules/@apostrophecms/doc-type/index.js +27 -20
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +0 -1
- package/modules/@apostrophecms/i18n/i18n/en.json +8 -0
- package/modules/@apostrophecms/i18n/i18n/es.json +1 -1
- package/modules/@apostrophecms/i18n/index.js +26 -5
- package/modules/@apostrophecms/job/index.js +10 -17
- package/modules/@apostrophecms/login/ui/apos/apps/AposLogin.js +3 -2
- package/modules/@apostrophecms/modal/ui/apos/apps/AposModals.js +8 -5
- package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +1 -1
- package/modules/@apostrophecms/module/index.js +1 -4
- package/modules/@apostrophecms/notification/ui/apos/apps/AposNotification.js +3 -1
- package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +1 -1
- package/modules/@apostrophecms/page/index.js +47 -22
- package/modules/@apostrophecms/page-type/index.js +5 -1
- package/modules/@apostrophecms/piece-type/index.js +5 -0
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +2 -1
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerSelectBox.vue +27 -24
- package/modules/@apostrophecms/rich-text-widget/index.js +2 -1
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +9 -2
- package/modules/@apostrophecms/schema/index.js +7 -19
- package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +9 -3
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputCheckboxes.vue +3 -3
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputPassword.vue +11 -4
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRange.vue +2 -2
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +0 -2
- package/modules/@apostrophecms/schema/ui/apos/components/AposLogo.vue +1 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposLogoIcon.vue +1 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposLogoPadless.vue +1 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposSearchList.vue +0 -1
- package/modules/@apostrophecms/search/index.js +53 -33
- package/modules/@apostrophecms/task/index.js +5 -1
- package/modules/@apostrophecms/template/index.js +5 -11
- package/modules/@apostrophecms/ui/ui/apos/components/AposMinMaxCount.vue +9 -3
- package/modules/@apostrophecms/ui/ui/apos/mixins/AposPublishMixin.js +3 -2
- package/modules/@apostrophecms/widget-type/index.js +9 -0
- package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +5 -27
- package/package.json +3 -3
- package/test/moog.js +48 -0
- package/test/pieces.js +17 -0
- package/.circleci/config.yml +0 -94
- package/.scratch.md +0 -2
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# This is a basic workflow to help you get started with Actions
|
|
2
|
+
|
|
3
|
+
name: Tests
|
|
4
|
+
|
|
5
|
+
# Controls when the action will run.
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [ '*' ]
|
|
9
|
+
pull_request:
|
|
10
|
+
branches: [ '*' ]
|
|
11
|
+
|
|
12
|
+
# Allows you to run this workflow manually from the Actions tab
|
|
13
|
+
workflow_dispatch:
|
|
14
|
+
|
|
15
|
+
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
|
16
|
+
jobs:
|
|
17
|
+
# This workflow contains a single job called "build"
|
|
18
|
+
build:
|
|
19
|
+
# The type of runner that the job will run on
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
strategy:
|
|
22
|
+
matrix:
|
|
23
|
+
node-version: [12, 14, 16]
|
|
24
|
+
mongodb-version: [4.2, 4.4, 5.0]
|
|
25
|
+
|
|
26
|
+
# Steps represent a sequence of tasks that will be executed as part of the job
|
|
27
|
+
steps:
|
|
28
|
+
- name: Git checkout
|
|
29
|
+
uses: actions/checkout@v2
|
|
30
|
+
|
|
31
|
+
- name: Use Node.js ${{ matrix.node-version }}
|
|
32
|
+
uses: actions/setup-node@v1
|
|
33
|
+
with:
|
|
34
|
+
node-version: ${{ matrix.node-version }}
|
|
35
|
+
|
|
36
|
+
- name: Start MongoDB
|
|
37
|
+
uses: supercharge/mongodb-github-action@1.3.0
|
|
38
|
+
with:
|
|
39
|
+
mongodb-version: ${{ matrix.mongodb-version }}
|
|
40
|
+
|
|
41
|
+
- run: npm install
|
|
42
|
+
|
|
43
|
+
- run: npm test
|
|
44
|
+
env:
|
|
45
|
+
CI: true
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,57 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.10.0 - 2021-12-22
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
|
|
7
|
+
* `slug` type fields can now have an empty string or `null` as their `def` value without the string `'none'` populating automatically.
|
|
8
|
+
* The `underline` feature works properly in tiptap toolbar configuration.
|
|
9
|
+
* Required checkbox fields now properly prevent editor submission when empty.
|
|
10
|
+
* Pins `vue-click-outside-element` to a version that does not attempt to use `eval` in its distribution build, which is incompatible with a strict Content Security Policy.
|
|
11
|
+
|
|
12
|
+
### Adds
|
|
13
|
+
|
|
14
|
+
* Adds a `last` option to fields. Setting `last: true` on a field puts that field at the end of the field's widget order. If more than one field has that option active the true last item will depend on general field registration order. If the field is ordered with the `fields.order` array or field group ordering, those specified orders will take precedence.
|
|
15
|
+
|
|
16
|
+
### Changes
|
|
17
|
+
|
|
18
|
+
* Adds deprecation notes to the widget class methods `getWidgetWrapperClasses` and `getWidgetClasses` from A2.
|
|
19
|
+
* Adds a deprecation note to the `reorganize` query builder for the next major version.
|
|
20
|
+
* Uses the runtime build of Vue. This has major performance and bundle size benefits, however it does require changes to Apostrophe admin UI apps that use a `template` property (components should require no changes, just apps require an update). These apps must now use a `render` function instead. Since custom admin UI apps are not yet a documented feature we do not regard this as a bc break.
|
|
21
|
+
* Compatible with the `@apostrophecms/security-headers` module, which supports a strict `Content-Security-Policy`.
|
|
22
|
+
* Adds a deprecation note to the `addLateCriteria` query builder.
|
|
23
|
+
* Updates the `toCount` doc type query method to use Math.ceil rather than Math.floor plus an additional step.
|
|
24
|
+
|
|
25
|
+
## 3.9.0 - 2021-12-08
|
|
26
|
+
|
|
27
|
+
### Adds
|
|
28
|
+
|
|
29
|
+
* Developers can now override any Vue component of the ApostropheCMS admin UI by providing a component of the same name in the `ui/apos/components` folder of their own module. This is not always the best approach, see the documentation for details.
|
|
30
|
+
* When running a job, we now trigger the notification before to run the job, this way the progress notification ID is available from the job and the notification can be dismissed if needed.
|
|
31
|
+
* Adds `maxUi`, `maxLabel`, `minUi`, and `minLabel` localization strings for array input and other UI.
|
|
32
|
+
|
|
33
|
+
### Fixes
|
|
34
|
+
|
|
35
|
+
* Fully removes references to the A2 `self.partial` module method. It appeared only once outside of comments, but was not actually used by the UI. The `self.render` method should be used for simple template rendering.
|
|
36
|
+
* Fixes string interpolation for the confirmation modal when publishing a page that has an unpublished parent page.
|
|
37
|
+
* No more "cannot set headers after they are sent to the client" and "req.res.redirect not defined" messages when handling URLs with extra trailing slashes.
|
|
38
|
+
* The `apos.util.runPlayers` method is not called until all of the widgets in a particular tree of areas and sub-areas have been added to the DOM. This means a parent area widget player will see the expected markup for any sub-widgets when the "Edit" button is clicked.
|
|
39
|
+
* Properly activates the `apostropheI18nDebugPlugin` i18next debugging plugin when using the `APOS_SHOW_I18N` environment variable. The full set of l10n emoji indicators previously available for the UI is now available for template and server-side strings.
|
|
40
|
+
* Actually registers piece types for site search unless the `searchable` option is `false`.
|
|
41
|
+
* Fixes the methods required for the search `index` task.
|
|
42
|
+
|
|
43
|
+
### Changes
|
|
44
|
+
|
|
45
|
+
* Adds localization keys for the password field component's min and max error messages.
|
|
46
|
+
|
|
47
|
+
## 3.8.1 - 2021-11-23
|
|
48
|
+
|
|
49
|
+
### Fixes
|
|
50
|
+
|
|
51
|
+
* The search field of the pieces manager modal works properly. Thanks to [Miro Yovchev](https://github.com/myovchev) for pointing out the issue and providing a solution.
|
|
52
|
+
* Fixes a bug in `AposRichTextWidgetEditor.vue` when a rich text widget was specifically configured with an empty array as the `styles` option. In that case a new empty rich text widget will initiate with an empty paragraph tag.
|
|
53
|
+
* The`fieldsPresent` method that is used with the `presentFieldsOnly` option in doc-type was broken, looking for properties in strings and wasn't returning anything.
|
|
54
|
+
|
|
3
55
|
## 3.8.0 - 2021-11-15
|
|
4
56
|
|
|
5
57
|
### Adds
|
package/README.md
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
[](https://circleci.com/gh/apostrophecms/apostrophe/tree/main)
|
|
1
|
+

|
|
3
2
|
[](https://chat.apostrophecms.org)
|
|
4
3
|
|
|
5
4
|
<p align="center">
|
package/lib/moog.js
CHANGED
|
@@ -179,17 +179,42 @@ module.exports = function(options) {
|
|
|
179
179
|
...properties.add
|
|
180
180
|
};
|
|
181
181
|
}
|
|
182
|
+
|
|
183
|
+
const lastFields = Object.entries(that[cascade])
|
|
184
|
+
.filter(([ field, val ]) => val.last === true)
|
|
185
|
+
.map(([ field, val ]) => field);
|
|
186
|
+
|
|
182
187
|
if (properties.remove) {
|
|
183
188
|
for (const field of properties.remove) {
|
|
184
189
|
delete that[cascade][field];
|
|
185
190
|
}
|
|
186
191
|
}
|
|
187
192
|
if (properties.order) {
|
|
193
|
+
// 1. Ordered fields
|
|
194
|
+
// 2. Other fields not marked as last
|
|
195
|
+
// 3. Fields marked as last
|
|
188
196
|
that[cascade] = Object.fromEntries([
|
|
189
197
|
...properties.order.map(field => [ field, that[cascade][field] ]),
|
|
190
|
-
...Object.keys(that[cascade])
|
|
198
|
+
...Object.keys(that[cascade])
|
|
199
|
+
.filter(field => {
|
|
200
|
+
return !properties.order.includes(field) &&
|
|
201
|
+
!lastFields.includes(field);
|
|
202
|
+
})
|
|
203
|
+
.map(field => [ field, that[cascade][field] ]),
|
|
204
|
+
...lastFields.filter(field => !properties.order.includes(field))
|
|
205
|
+
.map(field => [ field, that[cascade][field] ])
|
|
206
|
+
]);
|
|
207
|
+
} else if (lastFields.length > 0) {
|
|
208
|
+
// 1. Fields not marked as last
|
|
209
|
+
// 2. Fields marked as last
|
|
210
|
+
that[cascade] = Object.fromEntries([
|
|
211
|
+
...Object.keys(that[cascade])
|
|
212
|
+
.filter(field => !lastFields.includes(field))
|
|
213
|
+
.map(field => [ field, that[cascade][field] ]),
|
|
214
|
+
...lastFields.map(field => [ field, that[cascade][field] ])
|
|
191
215
|
]);
|
|
192
216
|
}
|
|
217
|
+
|
|
193
218
|
if (properties.group) {
|
|
194
219
|
const groups = klona(that[`${cascade}Groups`]);
|
|
195
220
|
for (const value of Object.values(properties.group)) {
|
|
@@ -10,7 +10,13 @@ export default function() {
|
|
|
10
10
|
return window.apos;
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
|
-
|
|
13
|
+
render: function (h) {
|
|
14
|
+
return h('TheAposAdminBar', {
|
|
15
|
+
props: {
|
|
16
|
+
items: apos.adminBar.items
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
14
20
|
});
|
|
15
21
|
}
|
|
16
22
|
};
|
|
@@ -287,6 +287,8 @@ module.exports = {
|
|
|
287
287
|
// are suitable for display in the reorganize view.
|
|
288
288
|
// The only pages excluded are those with a `reorganize`
|
|
289
289
|
// property explicitly set to `false`.
|
|
290
|
+
// NOTE: This query builder is deprecated and will be removed in the
|
|
291
|
+
// next major version.
|
|
290
292
|
reorganize: {
|
|
291
293
|
def: null,
|
|
292
294
|
finalize() {
|
|
@@ -3,27 +3,32 @@ import { klona } from 'klona';
|
|
|
3
3
|
|
|
4
4
|
export default function() {
|
|
5
5
|
|
|
6
|
+
let widgetsRendering = 0;
|
|
7
|
+
|
|
6
8
|
createWidgetClipboardApp();
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
createAreaApps();
|
|
9
11
|
|
|
10
12
|
document.documentElement.style.setProperty('--a-widget-margin', apos.ui.widgetMargin);
|
|
11
13
|
|
|
14
|
+
apos.bus.$on('widget-rendering', function() {
|
|
15
|
+
widgetsRendering++;
|
|
16
|
+
});
|
|
17
|
+
|
|
12
18
|
apos.bus.$on('widget-rendered', function() {
|
|
13
|
-
|
|
19
|
+
widgetsRendering--;
|
|
20
|
+
createAreaAppsAndRunPlayersIfDone();
|
|
14
21
|
});
|
|
22
|
+
|
|
15
23
|
apos.bus.$on('refreshed', function() {
|
|
16
|
-
|
|
24
|
+
createAreaAppsAndRunPlayersIfDone();
|
|
17
25
|
});
|
|
18
26
|
|
|
19
|
-
function
|
|
20
|
-
// Doing this first allows markup to be captured for the editor
|
|
21
|
-
// before players alter it
|
|
27
|
+
function createAreaAppsAndRunPlayersIfDone() {
|
|
22
28
|
createAreaApps();
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
apos.util.runPlayers();
|
|
29
|
+
if (widgetsRendering === 0) {
|
|
30
|
+
apos.util.runPlayers();
|
|
31
|
+
}
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
function createAreaApps() {
|
|
@@ -106,7 +111,19 @@ export default function() {
|
|
|
106
111
|
renderings
|
|
107
112
|
};
|
|
108
113
|
},
|
|
109
|
-
|
|
114
|
+
render(h) {
|
|
115
|
+
return h(component, {
|
|
116
|
+
props: {
|
|
117
|
+
options: this.options,
|
|
118
|
+
items: this.items,
|
|
119
|
+
choices: this.choices,
|
|
120
|
+
id: this.id,
|
|
121
|
+
docId: this.docId,
|
|
122
|
+
fieldId: this.fieldId,
|
|
123
|
+
renderings: this.renderings
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
110
127
|
});
|
|
111
128
|
}
|
|
112
129
|
}
|
|
@@ -192,7 +192,10 @@ module.exports = {
|
|
|
192
192
|
let iconImports, componentImports, tiptapExtensionImports, appImports, indexJsImports, indexSassImports;
|
|
193
193
|
if (options.apos) {
|
|
194
194
|
iconImports = getIcons();
|
|
195
|
-
componentImports = getImports(`${source}/components`, '*.vue', {
|
|
195
|
+
componentImports = getImports(`${source}/components`, '*.vue', {
|
|
196
|
+
registerComponents: true,
|
|
197
|
+
importLastVersion: true
|
|
198
|
+
});
|
|
196
199
|
tiptapExtensionImports = getImports(`${source}/tiptap-extensions`, '*.js', { registerTiptapExtensions: true });
|
|
197
200
|
appImports = getImports(`${source}/apps`, '*.js', {
|
|
198
201
|
invokeApps: true,
|
|
@@ -441,6 +444,25 @@ module.exports = {
|
|
|
441
444
|
paths: []
|
|
442
445
|
};
|
|
443
446
|
|
|
447
|
+
if (options.importLastVersion) {
|
|
448
|
+
// Reverse the list so we can easily find the last configured import
|
|
449
|
+
// of a given component, allowing "improve" modules to win over
|
|
450
|
+
// the originals when shipping an override of a Vue component
|
|
451
|
+
// with the same name, and filter out earlier versions
|
|
452
|
+
components.reverse();
|
|
453
|
+
const seen = new Set();
|
|
454
|
+
components = components.filter(component => {
|
|
455
|
+
const name = getComponentName(component, options);
|
|
456
|
+
if (seen.has(name)) {
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
seen.add(name);
|
|
460
|
+
return true;
|
|
461
|
+
});
|
|
462
|
+
// Put the components back in their original order
|
|
463
|
+
components.reverse();
|
|
464
|
+
}
|
|
465
|
+
|
|
444
466
|
components.forEach((component, i) => {
|
|
445
467
|
if (options.requireDefaultExport) {
|
|
446
468
|
if (!fs.readFileSync(component, 'utf8').match(/export[\s\n]+default/)) {
|
|
@@ -453,7 +475,7 @@ module.exports = {
|
|
|
453
475
|
}
|
|
454
476
|
}
|
|
455
477
|
const jsFilename = JSON.stringify(component);
|
|
456
|
-
const name =
|
|
478
|
+
const name = getComponentName(component, options, i);
|
|
457
479
|
const jsName = JSON.stringify(name);
|
|
458
480
|
output.paths.push(component);
|
|
459
481
|
const importCode = `
|
|
@@ -492,6 +514,10 @@ module.exports = {
|
|
|
492
514
|
|
|
493
515
|
return pkgTimestamp > parseInt(timestamp);
|
|
494
516
|
}
|
|
517
|
+
|
|
518
|
+
function getComponentName(component, options, i) {
|
|
519
|
+
return require('path').basename(component).replace(/\.\w+/, '') + (options.enumerateImports ? `_${i}` : '');
|
|
520
|
+
}
|
|
495
521
|
}
|
|
496
522
|
}
|
|
497
523
|
};
|
|
@@ -726,8 +752,7 @@ module.exports = {
|
|
|
726
752
|
if (!self.shouldRefreshOnRestart()) {
|
|
727
753
|
return '';
|
|
728
754
|
}
|
|
729
|
-
|
|
730
|
-
return self.apos.template.safe(`<script data-apos-refresh-on-restart="${self.action}/restart-id">\n${script}</script>`);
|
|
755
|
+
return self.apos.template.safe(`<script data-apos-refresh-on-restart="${self.action}/restart-id" src="${self.action}/refresh-on-restart"></script>`);
|
|
731
756
|
},
|
|
732
757
|
url(path) {
|
|
733
758
|
return `${self.getAssetBaseUrl()}${path}`;
|
|
@@ -739,6 +764,12 @@ module.exports = {
|
|
|
739
764
|
return;
|
|
740
765
|
}
|
|
741
766
|
return {
|
|
767
|
+
get: {
|
|
768
|
+
refreshOnRestart(req) {
|
|
769
|
+
req.res.setHeader('content-type', 'text/javascript');
|
|
770
|
+
return fs.readFileSync(path.join(__dirname, '/lib/refresh-on-restart.js'), 'utf8');
|
|
771
|
+
}
|
|
772
|
+
},
|
|
742
773
|
// Use a POST route so IE11 doesn't cache it
|
|
743
774
|
post: {
|
|
744
775
|
async restartId(req) {
|
|
@@ -28,7 +28,7 @@ module.exports = ({
|
|
|
28
28
|
optimization: {
|
|
29
29
|
minimize: process.env.NODE_ENV === 'production'
|
|
30
30
|
},
|
|
31
|
-
devtool: '
|
|
31
|
+
devtool: 'source-map',
|
|
32
32
|
output: {
|
|
33
33
|
path: outputPath,
|
|
34
34
|
filename: outputFilename
|
|
@@ -42,7 +42,7 @@ module.exports = ({
|
|
|
42
42
|
resolve: {
|
|
43
43
|
extensions: [ '*', '.js', '.vue', '.json' ],
|
|
44
44
|
alias: {
|
|
45
|
-
vue$: 'vue/dist/vue.esm.js',
|
|
45
|
+
vue$: 'vue/dist/vue.runtime.esm.js',
|
|
46
46
|
// resolve apostrophe modules
|
|
47
47
|
Modules: path.resolve(modulesDir)
|
|
48
48
|
},
|
|
@@ -230,7 +230,6 @@ module.exports = {
|
|
|
230
230
|
addFieldType() {
|
|
231
231
|
self.apos.schema.addFieldType({
|
|
232
232
|
name: self.name,
|
|
233
|
-
partial: self.fieldTypePartial,
|
|
234
233
|
convert: self.convert,
|
|
235
234
|
index: self.index,
|
|
236
235
|
register: self.register
|
|
@@ -274,9 +273,6 @@ module.exports = {
|
|
|
274
273
|
await self.db.replaceOne({ _id: info._id }, info);
|
|
275
274
|
object[field.name] = info;
|
|
276
275
|
},
|
|
277
|
-
fieldTypePartial(data) {
|
|
278
|
-
return self.partial('attachment', data);
|
|
279
|
-
},
|
|
280
276
|
index(value, field, texts) {
|
|
281
277
|
const silent = field.silent === undefined ? true : field.silent;
|
|
282
278
|
texts.push({
|
|
@@ -545,8 +545,11 @@ module.exports = {
|
|
|
545
545
|
|
|
546
546
|
await self.apos.schema.convert(req, schema, input, doc);
|
|
547
547
|
|
|
548
|
-
doc.copyOfId = copyOf && copyOf._id;
|
|
549
548
|
if (copyOf) {
|
|
549
|
+
if (copyOf._id) {
|
|
550
|
+
doc.copyOfId = copyOf._id;
|
|
551
|
+
}
|
|
552
|
+
|
|
550
553
|
self.apos.schema.regenerateIds(req, fullSchema, doc);
|
|
551
554
|
}
|
|
552
555
|
},
|
|
@@ -554,17 +557,9 @@ module.exports = {
|
|
|
554
557
|
// taking into account issues like relationship fields keeping their data in
|
|
555
558
|
// a separate ids property, etc.
|
|
556
559
|
fieldsPresent(input) {
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
if (field.type.name.substring(0, 5) === '_relationship') {
|
|
561
|
-
if (_.has(input, field.idsStorage)) {
|
|
562
|
-
output.push(field.name);
|
|
563
|
-
}
|
|
564
|
-
} else {
|
|
565
|
-
output.push(field.name);
|
|
566
|
-
}
|
|
567
|
-
}
|
|
560
|
+
return self.schema
|
|
561
|
+
.filter((field) => _.has(input, field.name))
|
|
562
|
+
.map((field) => field.name);
|
|
568
563
|
},
|
|
569
564
|
// Returns a query that finds docs the current user can edit. Unlike
|
|
570
565
|
// find(), this query defaults to including docs in the archive. Subclasses
|
|
@@ -1157,8 +1152,10 @@ module.exports = {
|
|
|
1157
1152
|
|
|
1158
1153
|
// `.addLateCriteria({...})` provides an object to be merged directly into the final
|
|
1159
1154
|
// criteria object that will go to MongoDB. This is to be used only
|
|
1160
|
-
// in cases where MongoDB forbids the use of an operator inside
|
|
1161
|
-
//
|
|
1155
|
+
// in cases where MongoDB forbids the use of an operator inside `$and`.
|
|
1156
|
+
//
|
|
1157
|
+
// TODO: Since `$near` can now be used in `$and` operators, this query
|
|
1158
|
+
// builder is deprecated and should be removed in the 4.x major version.
|
|
1162
1159
|
addLateCriteria: {
|
|
1163
1160
|
set(c) {
|
|
1164
1161
|
let lateCriteria = query.get('lateCriteria');
|
|
@@ -1274,6 +1271,14 @@ module.exports = {
|
|
|
1274
1271
|
if (query.get('search')) {
|
|
1275
1272
|
// MongoDB mandates this if we want to sort on search result quality
|
|
1276
1273
|
projection.textScore = { $meta: 'textScore' };
|
|
1274
|
+
} else if (projection.textScore) {
|
|
1275
|
+
// Gracefully elide the textScore projection when it is not useful and
|
|
1276
|
+
// would cause an error anyway.
|
|
1277
|
+
//
|
|
1278
|
+
// This allows the reuse of the `project()` value passed to one query
|
|
1279
|
+
// in a second query without worrying about whether the second query
|
|
1280
|
+
// contains a search or not
|
|
1281
|
+
delete projection.textScore;
|
|
1277
1282
|
}
|
|
1278
1283
|
query.set('project', projection);
|
|
1279
1284
|
}
|
|
@@ -1417,7 +1422,7 @@ module.exports = {
|
|
|
1417
1422
|
}
|
|
1418
1423
|
},
|
|
1419
1424
|
|
|
1420
|
-
// `.permission('
|
|
1425
|
+
// `.permission('edit')` would limit the returned docs to those for which the
|
|
1421
1426
|
// user associated with the query's `req` has the named permission.
|
|
1422
1427
|
// By default, `view` is checked for. You might want to specify
|
|
1423
1428
|
// `edit`.
|
|
@@ -1655,7 +1660,11 @@ module.exports = {
|
|
|
1655
1660
|
const _req = query.req.clone({
|
|
1656
1661
|
mode: 'published'
|
|
1657
1662
|
});
|
|
1658
|
-
const publishedDocs = await self.find(_req)
|
|
1663
|
+
const publishedDocs = await self.find(_req)
|
|
1664
|
+
._ids(results.map(result => {
|
|
1665
|
+
return result._id.replace(':draft', ':published');
|
|
1666
|
+
})).project(query.get('project')).toArray();
|
|
1667
|
+
|
|
1659
1668
|
for (const doc of results) {
|
|
1660
1669
|
const publishedDoc = publishedDocs.find(publishedDoc => doc.aposDocId === publishedDoc.aposDocId);
|
|
1661
1670
|
doc._publishedDoc = publishedDoc;
|
|
@@ -2145,10 +2154,8 @@ module.exports = {
|
|
|
2145
2154
|
const count = await mongo.count();
|
|
2146
2155
|
if (query.get('perPage')) {
|
|
2147
2156
|
const perPage = query.get('perPage');
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
totalPages++;
|
|
2151
|
-
}
|
|
2157
|
+
const totalPages = Math.ceil(count / perPage);
|
|
2158
|
+
|
|
2152
2159
|
query.set('totalPages', totalPages);
|
|
2153
2160
|
}
|
|
2154
2161
|
return count;
|
|
@@ -124,6 +124,7 @@
|
|
|
124
124
|
"errorPageMessage": "An error has occurred",
|
|
125
125
|
"errorPageStatusCode": "500",
|
|
126
126
|
"errorPageTitle": "An error has occurred",
|
|
127
|
+
"errorBatchOperationNoti": "Batch operation {{ operation }} failed.",
|
|
127
128
|
"everythingElse": "Everything Else",
|
|
128
129
|
"exit": "Exit",
|
|
129
130
|
"fetchPublishedVersionFailed": "An error occurred fetching the published version of the document.",
|
|
@@ -176,6 +177,8 @@
|
|
|
176
177
|
"manageDocType": "Manage {{ type }}",
|
|
177
178
|
"manageDraftSubmissions": "Manage Draft Submissions",
|
|
178
179
|
"managePages": "Manage Pages",
|
|
180
|
+
"maxLabel": "Max:",
|
|
181
|
+
"maxUi": "Max: {{ number }}",
|
|
179
182
|
"mediaCreatedDate": "Uploaded: {{ createdDate }}",
|
|
180
183
|
"mediaDimensions": "Dimensions: {{ width }} 𝗑 {{ height }}",
|
|
181
184
|
"mediaFileSize": "File Size: {{ fileSize }}",
|
|
@@ -183,6 +186,8 @@
|
|
|
183
186
|
"mediaMB": "{{ size }}MB",
|
|
184
187
|
"mediaUploadViaDrop": "Drop ’em when you’re ready",
|
|
185
188
|
"mediaUploadViaExplorer": "Or click to open the file explorer",
|
|
189
|
+
"minLabel": "Min:",
|
|
190
|
+
"minUi": "Min: {{ number }}",
|
|
186
191
|
"modify": "Modify",
|
|
187
192
|
"modifyOrDelete": "Modify / Delete",
|
|
188
193
|
"moreOptions": "More Options",
|
|
@@ -213,6 +218,7 @@
|
|
|
213
218
|
"notYetPublished": "This document hasn't been published yet.",
|
|
214
219
|
"nudgeDown": "Nudge Down",
|
|
215
220
|
"nudgeUp": "Nudge Up",
|
|
221
|
+
"numberAdded": "{{ count }} Added",
|
|
216
222
|
"office": "Office",
|
|
217
223
|
"openGlobal": "Open Global Site Settings",
|
|
218
224
|
"page": "Page",
|
|
@@ -248,6 +254,8 @@
|
|
|
248
254
|
"richTextUndo": "Undo",
|
|
249
255
|
"richTextStyleConfigWarning": "Misconfigured rich text style: label: {{ label }}, {{ tag }}",
|
|
250
256
|
"password": "Password",
|
|
257
|
+
"passwordErrorMin": "Minimum of {{ min }} characters",
|
|
258
|
+
"passwordErrorMax": "Maximum of {{ max }} characters",
|
|
251
259
|
"passwordResetRequest": "Your request to reset your password on {{ site }}",
|
|
252
260
|
"pasteWidget": "Paste {{ widget }}",
|
|
253
261
|
"pending": "Pending",
|
|
@@ -204,7 +204,7 @@
|
|
|
204
204
|
"notFoundPageStatusCode": "404",
|
|
205
205
|
"notFoundPageTitle": "404 - Página no encontrada",
|
|
206
206
|
"notInLocale": "La página actual no existe en {{ label }}. ¿Traducir la versión desde la configuración regional {{ currentLocale }}?",
|
|
207
|
-
"noTypeFound": "Ningún {{ type }}
|
|
207
|
+
"noTypeFound": "Ningún {{ type }} Encontrado",
|
|
208
208
|
"parentNotLocalized": "Primero traduzca la configuración regional de la página principal",
|
|
209
209
|
"notYetPublished": "Este documento aún no ha sido publicado.",
|
|
210
210
|
"nudgeDown": "Mover Hacia Arriba",
|
|
@@ -13,11 +13,27 @@ const ExpressSessionCookie = require('express-session/session/cookie');
|
|
|
13
13
|
|
|
14
14
|
const apostropheI18nDebugPlugin = {
|
|
15
15
|
type: 'postProcessor',
|
|
16
|
-
name: '
|
|
16
|
+
name: 'apostropheI18nDebugPlugin',
|
|
17
17
|
process(value, key, options, translator) {
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
|
|
18
|
+
// The key is passed as an array (theoretically to include multiple keys).
|
|
19
|
+
// We confirm that and grab the primary one for comparison.
|
|
20
|
+
const l10nKey = Array.isArray(key) ? key[0] : key;
|
|
21
|
+
|
|
22
|
+
if (value === l10nKey) {
|
|
23
|
+
if (l10nKey.match(/^\S+:/)) {
|
|
24
|
+
// The l10n key does not have a value assigned (or the key is
|
|
25
|
+
// actually the same as the phrase). The key seems to have a
|
|
26
|
+
// namespace, so might be from the Apostrophe UI.
|
|
27
|
+
return `❌ ${value}`;
|
|
28
|
+
} else {
|
|
29
|
+
// The l10n key does not have a value assigned (or the key is
|
|
30
|
+
// actually the same as the phrase). It is in the default namespace.
|
|
31
|
+
return `🕳 ${value}`;
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
// The phrase is fully localized.
|
|
35
|
+
return `🌍 ${value}`;
|
|
36
|
+
}
|
|
21
37
|
}
|
|
22
38
|
};
|
|
23
39
|
|
|
@@ -73,7 +89,12 @@ module.exports = {
|
|
|
73
89
|
if (self.show) {
|
|
74
90
|
self.i18next.use(apostropheI18nDebugPlugin);
|
|
75
91
|
}
|
|
76
|
-
|
|
92
|
+
|
|
93
|
+
const i18nextOptions = self.show ? {
|
|
94
|
+
postProcess: 'apostropheI18nDebugPlugin'
|
|
95
|
+
} : {};
|
|
96
|
+
|
|
97
|
+
await self.i18next.init(i18nextOptions);
|
|
77
98
|
self.addInitialResources();
|
|
78
99
|
self.enableBrowserData();
|
|
79
100
|
},
|
|
@@ -189,38 +189,31 @@ module.exports = {
|
|
|
189
189
|
async run(req, doTheWork, options = {}) {
|
|
190
190
|
const res = req.res;
|
|
191
191
|
let job;
|
|
192
|
-
let notification;
|
|
193
192
|
let total;
|
|
194
193
|
|
|
195
194
|
try {
|
|
196
195
|
job = await self.start(options);
|
|
197
|
-
run();
|
|
198
196
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
// It's only relevant to pass a job ID to the notification if
|
|
202
|
-
// the notification will show progress. Without a total number we
|
|
203
|
-
// can't show progress.
|
|
204
|
-
jobId: total && job._id,
|
|
205
|
-
count: total
|
|
197
|
+
const notification = await self.triggerNotification(req, 'progress', {
|
|
198
|
+
jobId: job._id
|
|
206
199
|
});
|
|
207
200
|
|
|
201
|
+
run({ notificationId: notification.noteId });
|
|
202
|
+
|
|
208
203
|
return {
|
|
209
204
|
jobId: job._id
|
|
210
205
|
};
|
|
211
206
|
} catch (err) {
|
|
212
207
|
self.apos.util.error(err);
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
// stopped talking to the user
|
|
216
|
-
self.apos.util.error(err);
|
|
217
|
-
} else {
|
|
208
|
+
|
|
209
|
+
if (!job) {
|
|
218
210
|
// If we never made a job, then we never responded
|
|
211
|
+
// otherwise we already stopped talking to the user
|
|
219
212
|
return res.status(500).send('error');
|
|
220
213
|
}
|
|
221
214
|
}
|
|
222
215
|
|
|
223
|
-
async function run() {
|
|
216
|
+
async function run(info) {
|
|
224
217
|
let results;
|
|
225
218
|
let good = false;
|
|
226
219
|
try {
|
|
@@ -240,7 +233,7 @@ module.exports = {
|
|
|
240
233
|
setResults (_results) {
|
|
241
234
|
results = _results;
|
|
242
235
|
}
|
|
243
|
-
});
|
|
236
|
+
}, info);
|
|
244
237
|
good = true;
|
|
245
238
|
} finally {
|
|
246
239
|
await self.end(job, good, results);
|
|
@@ -253,7 +246,7 @@ module.exports = {
|
|
|
253
246
|
// Dismiss the progress notification. It will delay 4 seconds
|
|
254
247
|
// because "completed" notification will dismiss in 5 and we want
|
|
255
248
|
// to maintain the feeling of process order for users.
|
|
256
|
-
await self.apos.notification.dismiss(req,
|
|
249
|
+
await self.apos.notification.dismiss(req, info.notificationId, 4000);
|
|
257
250
|
}
|
|
258
251
|
}
|
|
259
252
|
},
|