apostrophe 3.7.0 → 3.8.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 +4 -0
- package/.scratch.md +2 -0
- package/CHANGELOG.md +34 -3
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +5 -1
- package/modules/@apostrophecms/asset/index.js +77 -13
- package/modules/@apostrophecms/attachment/index.js +1 -0
- package/modules/@apostrophecms/db/index.js +5 -6
- package/modules/@apostrophecms/doc-type/index.js +23 -3
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +13 -1
- package/modules/@apostrophecms/i18n/i18n/en.json +15 -4
- package/modules/@apostrophecms/i18n/i18n/es.json +0 -1
- package/modules/@apostrophecms/i18n/i18n/pt-BR.json +0 -1
- package/modules/@apostrophecms/i18n/i18n/sk.json +3 -4
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +6 -3
- package/modules/@apostrophecms/image-widget/index.js +2 -1
- package/modules/@apostrophecms/image-widget/views/widget.html +12 -2
- package/modules/@apostrophecms/job/index.js +164 -212
- package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +151 -61
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +8 -6
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocsManagerMixin.js +12 -15
- package/modules/@apostrophecms/notification/index.js +116 -8
- package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +89 -11
- package/modules/@apostrophecms/notification/ui/apos/components/TheAposNotifications.vue +1 -1
- package/modules/@apostrophecms/page/index.js +37 -30
- package/modules/@apostrophecms/piece-type/index.js +178 -61
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +179 -50
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +0 -2
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerSelectBox.vue +138 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputAttachment.vue +11 -160
- package/modules/@apostrophecms/task/index.js +2 -2
- package/modules/@apostrophecms/template/index.js +2 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +5 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposFile.vue +205 -0
- package/modules/@apostrophecms/util/ui/src/util.js +15 -0
- package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +15 -7
- package/package.json +2 -2
- package/test/job.js +224 -0
- package/test/pieces.js +17 -0
- package/test-lib/util.js +32 -0
|
@@ -2,26 +2,46 @@
|
|
|
2
2
|
<AposModalToolbar class-name="apos-manager-toolbar">
|
|
3
3
|
<template #leftControls>
|
|
4
4
|
<AposButton
|
|
5
|
-
v-if="
|
|
6
|
-
label="apostrophe:select"
|
|
7
|
-
|
|
5
|
+
v-if="displayedItems"
|
|
6
|
+
label="apostrophe:select"
|
|
7
|
+
type="outline"
|
|
8
|
+
text-color="var(--a-primary)"
|
|
9
|
+
:icon-only="true"
|
|
10
|
+
:icon="checkboxIcon"
|
|
8
11
|
@click="$emit('select-click')"
|
|
9
12
|
/>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
13
|
+
<div
|
|
14
|
+
v-for="{
|
|
15
|
+
action,
|
|
16
|
+
label,
|
|
17
|
+
icon,
|
|
18
|
+
operations,
|
|
19
|
+
...rest
|
|
20
|
+
} in activeOperations"
|
|
21
|
+
:key="action"
|
|
22
|
+
>
|
|
23
|
+
<AposButton
|
|
24
|
+
v-if="!operations"
|
|
25
|
+
:label="label"
|
|
26
|
+
:icon-only="true"
|
|
27
|
+
:icon="icon"
|
|
28
|
+
:disabled="!checkedCount"
|
|
29
|
+
type="outline"
|
|
30
|
+
@click="confirmOperation({ action, label, ...rest })"
|
|
31
|
+
/>
|
|
32
|
+
<AposContextMenu
|
|
33
|
+
v-else
|
|
34
|
+
:button="{
|
|
35
|
+
label,
|
|
36
|
+
icon,
|
|
37
|
+
iconOnly: true,
|
|
38
|
+
type: 'outline'
|
|
39
|
+
}"
|
|
40
|
+
:disabled="!checkedCount"
|
|
41
|
+
:menu="operations"
|
|
42
|
+
@item-clicked="(a) => beginGroupedOperation(a, operations)"
|
|
43
|
+
/>
|
|
44
|
+
</div>
|
|
25
45
|
</template>
|
|
26
46
|
<template #rightControls>
|
|
27
47
|
<AposPager
|
|
@@ -30,7 +50,7 @@
|
|
|
30
50
|
:total-pages="totalPages" :current-page="currentPage"
|
|
31
51
|
/>
|
|
32
52
|
<AposFilterMenu
|
|
33
|
-
v-if="filters.length
|
|
53
|
+
v-if="filters.length"
|
|
34
54
|
:filters="filters"
|
|
35
55
|
:choices="filterChoices"
|
|
36
56
|
:values="filterValues"
|
|
@@ -57,27 +77,19 @@ export default {
|
|
|
57
77
|
},
|
|
58
78
|
applyTags: {
|
|
59
79
|
type: Array,
|
|
60
|
-
default ()
|
|
61
|
-
return [];
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
filters: {
|
|
65
|
-
type: Array,
|
|
66
|
-
default () {
|
|
67
|
-
return [];
|
|
68
|
-
}
|
|
80
|
+
default: () => []
|
|
69
81
|
},
|
|
70
82
|
filterChoices: {
|
|
71
83
|
type: Object,
|
|
72
|
-
default () {
|
|
73
|
-
return {};
|
|
74
|
-
}
|
|
84
|
+
default: () => ({})
|
|
75
85
|
},
|
|
76
86
|
filterValues: {
|
|
77
87
|
type: Object,
|
|
78
|
-
default () {
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
default: () => ({})
|
|
89
|
+
},
|
|
90
|
+
filters: {
|
|
91
|
+
type: Array,
|
|
92
|
+
default: () => []
|
|
81
93
|
},
|
|
82
94
|
totalPages: {
|
|
83
95
|
type: Number,
|
|
@@ -87,42 +99,40 @@ export default {
|
|
|
87
99
|
type: Number,
|
|
88
100
|
default: 1
|
|
89
101
|
},
|
|
102
|
+
isRelationship: {
|
|
103
|
+
type: Boolean,
|
|
104
|
+
default: false
|
|
105
|
+
},
|
|
90
106
|
labels: {
|
|
91
107
|
type: Object,
|
|
92
|
-
default () {
|
|
93
|
-
return {};
|
|
94
|
-
}
|
|
108
|
+
default: () => ({})
|
|
95
109
|
},
|
|
96
110
|
options: {
|
|
97
111
|
type: Object,
|
|
98
|
-
default () {
|
|
99
|
-
|
|
100
|
-
|
|
112
|
+
default: () => ({})
|
|
113
|
+
},
|
|
114
|
+
batchOperations: {
|
|
115
|
+
type: Array,
|
|
116
|
+
default: () => []
|
|
117
|
+
},
|
|
118
|
+
displayedItems: {
|
|
119
|
+
type: Number,
|
|
120
|
+
required: true
|
|
121
|
+
},
|
|
122
|
+
checkedCount: {
|
|
123
|
+
type: Number,
|
|
124
|
+
required: true
|
|
101
125
|
}
|
|
102
126
|
},
|
|
103
127
|
emits: [
|
|
104
128
|
'select-click',
|
|
105
129
|
'filter',
|
|
106
130
|
'search',
|
|
107
|
-
'page-change'
|
|
131
|
+
'page-change',
|
|
132
|
+
'batch'
|
|
108
133
|
],
|
|
109
134
|
data() {
|
|
110
135
|
return {
|
|
111
|
-
// TODO: Uncomment to return this when batch updates are added.
|
|
112
|
-
// more: {
|
|
113
|
-
// button: {
|
|
114
|
-
// label: 'apostrophe:moreOperations',
|
|
115
|
-
// iconOnly: true,
|
|
116
|
-
// icon: 'dots-vertical-icon',
|
|
117
|
-
// type: 'outline'
|
|
118
|
-
// },
|
|
119
|
-
// menu: [
|
|
120
|
-
// {
|
|
121
|
-
// label: 'Unpublish All',
|
|
122
|
-
// action: 'unpublish-all'
|
|
123
|
-
// }
|
|
124
|
-
// ]
|
|
125
|
-
// },
|
|
126
136
|
searchField: {
|
|
127
137
|
field: {
|
|
128
138
|
name: 'search',
|
|
@@ -135,7 +145,8 @@ export default {
|
|
|
135
145
|
},
|
|
136
146
|
status: {},
|
|
137
147
|
value: { data: '' }
|
|
138
|
-
}
|
|
148
|
+
},
|
|
149
|
+
activeOperations: []
|
|
139
150
|
};
|
|
140
151
|
},
|
|
141
152
|
computed: {
|
|
@@ -149,9 +160,52 @@ export default {
|
|
|
149
160
|
}
|
|
150
161
|
}
|
|
151
162
|
},
|
|
163
|
+
mounted () {
|
|
164
|
+
this.computeActiveOperations();
|
|
165
|
+
},
|
|
152
166
|
methods: {
|
|
167
|
+
computeActiveOperations () {
|
|
168
|
+
if (this.isRelationship) {
|
|
169
|
+
this.activeOperations = [];
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
this.activeOperations = this.batchOperations
|
|
174
|
+
.map(({ operations, ...rest }) => {
|
|
175
|
+
if (!operations) {
|
|
176
|
+
return {
|
|
177
|
+
...rest,
|
|
178
|
+
operations
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
operations: operations.filter((op) => this.isOperationActive(op)),
|
|
184
|
+
...rest
|
|
185
|
+
};
|
|
186
|
+
}).filter((operation) => {
|
|
187
|
+
if (operation.operations && !operation.operations.length) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return this.isOperationActive(operation);
|
|
192
|
+
});
|
|
193
|
+
},
|
|
194
|
+
isOperationActive (operation) {
|
|
195
|
+
return Object.entries(operation.if || {})
|
|
196
|
+
.every(([ filter, val ]) => {
|
|
197
|
+
if (Array.isArray(val)) {
|
|
198
|
+
return val.includes(this.filterValues[filter]);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return this.filterValues[filter] === val;
|
|
202
|
+
});
|
|
203
|
+
},
|
|
153
204
|
filter(filter, value) {
|
|
154
205
|
this.$emit('filter', filter, value.data);
|
|
206
|
+
if (this.filterValues[filter] !== value) {
|
|
207
|
+
this.computeActiveOperations();
|
|
208
|
+
}
|
|
155
209
|
},
|
|
156
210
|
search(value, force) {
|
|
157
211
|
if ((force && !value) || value.data === '') {
|
|
@@ -165,12 +219,48 @@ export default {
|
|
|
165
219
|
|
|
166
220
|
this.$emit('search', value.data);
|
|
167
221
|
},
|
|
168
|
-
managerAction(action) {
|
|
169
|
-
// TODO: flesh this out.
|
|
170
|
-
console.info('ACTION: ', action);
|
|
171
|
-
},
|
|
172
222
|
registerPageChange(pageNum) {
|
|
173
223
|
this.$emit('page-change', pageNum);
|
|
224
|
+
},
|
|
225
|
+
async beginGroupedOperation(action, operations) {
|
|
226
|
+
const operation = operations.find(o => o.action === action);
|
|
227
|
+
|
|
228
|
+
await this.confirmOperation(operation);
|
|
229
|
+
},
|
|
230
|
+
async confirmOperation ({
|
|
231
|
+
modalOptions = {}, action, operations, label, ...rest
|
|
232
|
+
}) {
|
|
233
|
+
const {
|
|
234
|
+
title = label,
|
|
235
|
+
description = '',
|
|
236
|
+
confirmationButton = 'apostrophe:affirmativeLabel',
|
|
237
|
+
form
|
|
238
|
+
} = action && operations
|
|
239
|
+
? (operations.find((op) => op.action === action)).modalOptions
|
|
240
|
+
: modalOptions;
|
|
241
|
+
|
|
242
|
+
const interpolations = {
|
|
243
|
+
count: this.checkedCount,
|
|
244
|
+
type: this.checkedCount === 1
|
|
245
|
+
? this.$t(this.labels.singular)
|
|
246
|
+
: this.$t(this.labels.plural)
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const confirmed = await apos.confirm({
|
|
250
|
+
heading: this.$t(title, interpolations),
|
|
251
|
+
description: this.$t(description, interpolations),
|
|
252
|
+
affirmativeLabel: confirmationButton,
|
|
253
|
+
localize: false,
|
|
254
|
+
...form && form
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
if (confirmed) {
|
|
258
|
+
this.$emit('batch', {
|
|
259
|
+
label,
|
|
260
|
+
action,
|
|
261
|
+
...rest
|
|
262
|
+
});
|
|
263
|
+
}
|
|
174
264
|
}
|
|
175
265
|
}
|
|
176
266
|
};
|
|
@@ -27,7 +27,11 @@
|
|
|
27
27
|
<p v-if="content.description" class="apos-confirm__description">
|
|
28
28
|
{{ localize(content.description) }}
|
|
29
29
|
</p>
|
|
30
|
-
<Component
|
|
30
|
+
<Component
|
|
31
|
+
v-if="content.body"
|
|
32
|
+
:is="content.body.component"
|
|
33
|
+
v-bind="content.body.props"
|
|
34
|
+
/>
|
|
31
35
|
<div v-if="content.form" class="apos-confirm__schema">
|
|
32
36
|
<AposSchema
|
|
33
37
|
v-if="formValues"
|
|
@@ -143,11 +147,9 @@ export default {
|
|
|
143
147
|
this.$emit('modal-result', false);
|
|
144
148
|
},
|
|
145
149
|
localize(s) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return this.$t(s, this.options.interpolate || {});
|
|
150
|
-
}
|
|
150
|
+
return this.options.localize === false
|
|
151
|
+
? s
|
|
152
|
+
: this.$t(s, this.options.interpolate || {});
|
|
151
153
|
}
|
|
152
154
|
}
|
|
153
155
|
};
|
|
@@ -56,25 +56,20 @@ export default {
|
|
|
56
56
|
sort(action) {
|
|
57
57
|
this.$emit('sort', action);
|
|
58
58
|
},
|
|
59
|
-
selectAllValue() {
|
|
60
|
-
return this.checked.length > 0 ? { data: [ 'checked' ] } : { data: [] };
|
|
61
|
-
},
|
|
62
59
|
selectAllChoice() {
|
|
63
60
|
const checkCount = this.checked.length;
|
|
64
61
|
const itemCount = (this.items && this.items.length) || 0;
|
|
65
62
|
|
|
66
|
-
return
|
|
63
|
+
return {
|
|
67
64
|
value: 'checked',
|
|
68
|
-
indeterminate:
|
|
69
|
-
} : {
|
|
70
|
-
value: 'checked'
|
|
65
|
+
indeterminate: checkCount > 0 && checkCount !== itemCount
|
|
71
66
|
};
|
|
72
67
|
},
|
|
73
68
|
selectAllState() {
|
|
74
|
-
if (this.
|
|
69
|
+
if (this.checked.length && !this.selectAllChoice.indeterminate) {
|
|
75
70
|
return 'checked';
|
|
76
71
|
}
|
|
77
|
-
if (this.
|
|
72
|
+
if (this.checked.length && this.selectAllChoice.indeterminate) {
|
|
78
73
|
return 'indeterminate';
|
|
79
74
|
}
|
|
80
75
|
return 'empty';
|
|
@@ -122,22 +117,24 @@ export default {
|
|
|
122
117
|
selectAll() {
|
|
123
118
|
if (!this.checked.length) {
|
|
124
119
|
this.items.forEach((item) => {
|
|
125
|
-
const
|
|
120
|
+
const relationshipsMaxedOrUnpublished = this.relationshipField &&
|
|
121
|
+
(this.maxReached() || !item.lastPublishedAt);
|
|
126
122
|
|
|
127
|
-
if (
|
|
123
|
+
if (relationshipsMaxedOrUnpublished) {
|
|
128
124
|
return;
|
|
129
125
|
}
|
|
130
126
|
|
|
131
127
|
this.checked.push(item._id);
|
|
132
128
|
});
|
|
129
|
+
|
|
133
130
|
return;
|
|
134
131
|
}
|
|
135
132
|
|
|
136
|
-
if (this.
|
|
137
|
-
this.
|
|
138
|
-
this.checked = this.checked.filter(item => item !== id);
|
|
139
|
-
});
|
|
133
|
+
if (this.allPiecesSelection) {
|
|
134
|
+
this.allPiecesSelection.isSelected = false;
|
|
140
135
|
}
|
|
136
|
+
|
|
137
|
+
this.checked = [];
|
|
141
138
|
},
|
|
142
139
|
iconSize(header) {
|
|
143
140
|
if (header.icon) {
|
|
@@ -160,6 +160,40 @@ module.exports = {
|
|
|
160
160
|
return self.db.deleteMany({ _id });
|
|
161
161
|
}
|
|
162
162
|
}),
|
|
163
|
+
apiRoutes(self) {
|
|
164
|
+
return {
|
|
165
|
+
post: {
|
|
166
|
+
// Clear a registered event on a notification to prevent it from
|
|
167
|
+
// emitting twice. Returns `true` if the event was found and cleared.
|
|
168
|
+
// Returns `false` if not found (because it was already cleared).
|
|
169
|
+
':_id/clear-event': async function (req) {
|
|
170
|
+
const lockId = `clear-event-${req.params._id}`;
|
|
171
|
+
|
|
172
|
+
let response;
|
|
173
|
+
try {
|
|
174
|
+
await self.apos.lock.lock(lockId);
|
|
175
|
+
|
|
176
|
+
response = await self.db.updateOne({
|
|
177
|
+
_id: req.params._id,
|
|
178
|
+
event: {
|
|
179
|
+
$ne: null
|
|
180
|
+
}
|
|
181
|
+
}, {
|
|
182
|
+
$set: {
|
|
183
|
+
event: null
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
} catch (error) {
|
|
187
|
+
throw self.apos.error('notfound');
|
|
188
|
+
} finally {
|
|
189
|
+
await self.apos.lock.unlock(lockId);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return response.result.nModified > 0;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
},
|
|
163
197
|
methods(self) {
|
|
164
198
|
return {
|
|
165
199
|
getBrowserData(req) {
|
|
@@ -189,6 +223,19 @@ module.exports = {
|
|
|
189
223
|
// apos bus event of the given `name` with the provided `data` object. Currently
|
|
190
224
|
// `'event'` is the only supported value for `type`.
|
|
191
225
|
//
|
|
226
|
+
// `options.return` will return the notification object. This is not
|
|
227
|
+
// done otherwise to minimize risk of leaking MongoDB metadata to the
|
|
228
|
+
// browser.
|
|
229
|
+
//
|
|
230
|
+
// `options.icon`, set to an active Vue Materials Icons icon name, will
|
|
231
|
+
// set an icon on the notification.
|
|
232
|
+
//
|
|
233
|
+
// `options.job` can be set to an object with properties related to an
|
|
234
|
+
// Apostrophe Job (from the @apostrophecms/job module) for the
|
|
235
|
+
// notification to track the job's progress. These can include the job
|
|
236
|
+
// `_id` and, for the 'completed' stage, the job `action`,
|
|
237
|
+
// e.g., 'archive'.
|
|
238
|
+
//
|
|
192
239
|
// Throws an error if there is no `req.user`.
|
|
193
240
|
//
|
|
194
241
|
// `interpolate` may contain an object with properties to be
|
|
@@ -203,6 +250,8 @@ module.exports = {
|
|
|
203
250
|
// the application, as in a command line task.
|
|
204
251
|
|
|
205
252
|
async trigger(req, message, options = {}, interpolate = {}) {
|
|
253
|
+
const { return: returnId, ...copiedOptions } = options;
|
|
254
|
+
|
|
206
255
|
if (typeof req === 'string') {
|
|
207
256
|
// String was passed, assume it is a user _id
|
|
208
257
|
req = { user: { _id: req } };
|
|
@@ -219,19 +268,23 @@ module.exports = {
|
|
|
219
268
|
createdAt: new Date(),
|
|
220
269
|
userId: req.user._id,
|
|
221
270
|
message,
|
|
271
|
+
icon: options.icon,
|
|
222
272
|
interpolate: interpolate || options.interpolate || {},
|
|
223
273
|
// Defaults to true, otherwise launder as boolean
|
|
224
|
-
localize: has(req.body, 'localize')
|
|
274
|
+
localize: has(req.body, 'localize')
|
|
275
|
+
? self.apos.launder.boolean(req.body.localize) : true,
|
|
276
|
+
job: options.job || null,
|
|
277
|
+
event: options.event
|
|
225
278
|
};
|
|
226
279
|
|
|
227
|
-
if (
|
|
228
|
-
|
|
280
|
+
if (copiedOptions.dismiss === true) {
|
|
281
|
+
copiedOptions.dismiss = 5;
|
|
229
282
|
}
|
|
230
283
|
|
|
231
|
-
Object.assign(notification,
|
|
284
|
+
Object.assign(notification, copiedOptions);
|
|
232
285
|
|
|
233
|
-
// We await here rather than returning because we
|
|
234
|
-
//
|
|
286
|
+
// We await here rather than returning because we expressly do not
|
|
287
|
+
// want to leak mongodb metadata to the browser
|
|
235
288
|
await self.db.updateOne(
|
|
236
289
|
notification,
|
|
237
290
|
{
|
|
@@ -243,8 +296,55 @@ module.exports = {
|
|
|
243
296
|
upsert: true
|
|
244
297
|
}
|
|
245
298
|
);
|
|
299
|
+
|
|
300
|
+
if (returnId) {
|
|
301
|
+
return {
|
|
302
|
+
noteId: notification._id
|
|
303
|
+
};
|
|
304
|
+
}
|
|
246
305
|
},
|
|
247
306
|
|
|
307
|
+
// The dismiss method accepts the following arguments:
|
|
308
|
+
// - req: A valid req.
|
|
309
|
+
// - noteId: The _id of an active notification.
|
|
310
|
+
// - delay: An optional integer of milliseconds to pause before the
|
|
311
|
+
// notification actually dismisses.
|
|
312
|
+
async dismiss (req, noteId, delay) {
|
|
313
|
+
if (!req.user) {
|
|
314
|
+
throw self.apos.error('forbidden');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
await pause(delay);
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
await self.db.updateOne(
|
|
321
|
+
{
|
|
322
|
+
_id: noteId
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
$set: {
|
|
326
|
+
dismissed: true
|
|
327
|
+
},
|
|
328
|
+
$currentDate: {
|
|
329
|
+
updatedAt: true
|
|
330
|
+
}
|
|
331
|
+
}, {
|
|
332
|
+
upsert: true
|
|
333
|
+
}
|
|
334
|
+
);
|
|
335
|
+
} catch (error) {
|
|
336
|
+
// Most likely the ID did not belong to an actual notification.
|
|
337
|
+
throw self.apos.error('invalid');
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function pause (delay) {
|
|
341
|
+
if (!delay) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return new Promise((resolve) => setTimeout(resolve, delay));
|
|
346
|
+
}
|
|
347
|
+
},
|
|
248
348
|
// Resolves with an object with `notifications` and `dismissed`
|
|
249
349
|
// properties.
|
|
250
350
|
//
|
|
@@ -256,8 +356,16 @@ module.exports = {
|
|
|
256
356
|
try {
|
|
257
357
|
const results = await self.db.find({
|
|
258
358
|
userId: req.user._id,
|
|
259
|
-
...(options.modifiedOnOrSince && {
|
|
260
|
-
|
|
359
|
+
...(options.modifiedOnOrSince && {
|
|
360
|
+
updatedAt: {
|
|
361
|
+
$gte: new Date(options.modifiedOnOrSince)
|
|
362
|
+
}
|
|
363
|
+
}),
|
|
364
|
+
...(options.seenIds && {
|
|
365
|
+
_id: {
|
|
366
|
+
$nin: options.seenIds
|
|
367
|
+
}
|
|
368
|
+
})
|
|
261
369
|
}).sort({ createdAt: 1 }).toArray();
|
|
262
370
|
|
|
263
371
|
const notifications = results.filter(result => !result.dismissed);
|