apostrophe 3.56.0 → 3.58.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 +2 -7
- package/.github/workflows/outdated-dependencies.yml +43 -0
- package/CHANGELOG.md +37 -0
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBar.vue +0 -5
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +12 -2
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +96 -129
- package/modules/@apostrophecms/attachment/index.js +59 -6
- package/modules/@apostrophecms/doc-type/index.js +7 -1
- package/modules/@apostrophecms/http/index.js +4 -0
- package/modules/@apostrophecms/i18n/i18n/it.json +517 -0
- package/modules/@apostrophecms/i18n/index.js +8 -0
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +1 -0
- package/modules/@apostrophecms/job/index.js +10 -2
- package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +5 -1
- package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +1 -0
- package/modules/@apostrophecms/permission/index.js +7 -2
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +1 -0
- package/modules/@apostrophecms/schema/lib/addFieldTypes.js +5 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputArea.vue +1 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposCellDate.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_theme.scss +1 -0
- package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +4 -0
- package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +6 -0
- package/package.json +3 -3
- package/test/pages.js +0 -1
- package/test/pieces.js +3 -2
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
# This is a basic workflow to help you get started with Actions
|
|
2
|
-
|
|
3
1
|
name: Tests
|
|
4
2
|
|
|
5
3
|
# Controls when the action will run.
|
|
@@ -20,8 +18,8 @@ jobs:
|
|
|
20
18
|
runs-on: ubuntu-latest
|
|
21
19
|
strategy:
|
|
22
20
|
matrix:
|
|
23
|
-
node-version: [
|
|
24
|
-
mongodb-version: [4.4, 5.0, 6.0]
|
|
21
|
+
node-version: [18, 20]
|
|
22
|
+
mongodb-version: [4.4, 5.0, 6.0, 7.0]
|
|
25
23
|
|
|
26
24
|
# Steps represent a sequence of tasks that will be executed as part of the job
|
|
27
25
|
steps:
|
|
@@ -38,9 +36,6 @@ jobs:
|
|
|
38
36
|
with:
|
|
39
37
|
mongodb-version: ${{ matrix.mongodb-version }}
|
|
40
38
|
|
|
41
|
-
# Exit status must be succesful in the end
|
|
42
|
-
- run: ( ( node --version | grep v14 ) && npm install -g npm@8 ) || echo "npm OK"
|
|
43
|
-
|
|
44
39
|
- run: npm install
|
|
45
40
|
|
|
46
41
|
- run: npm test
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: Check outdated dependencies
|
|
2
|
+
on:
|
|
3
|
+
schedule:
|
|
4
|
+
# Runs every Monday at 8:00
|
|
5
|
+
- cron: "0 8 * * MON"
|
|
6
|
+
# Allows you to run this workflow manually from the Actions tab
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
check_outdated_dependencies:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- name: Git checkout
|
|
14
|
+
uses: actions/checkout@v2
|
|
15
|
+
- name: Use Node.js 18
|
|
16
|
+
uses: actions/setup-node@v1
|
|
17
|
+
with:
|
|
18
|
+
node-version: 18
|
|
19
|
+
- name: Install dependencies
|
|
20
|
+
run: npm install
|
|
21
|
+
- name: Check outdated dependencies
|
|
22
|
+
run: |
|
|
23
|
+
echo "$(npm outdated)" > output
|
|
24
|
+
npm outdated
|
|
25
|
+
- name: Report Status
|
|
26
|
+
if: failure()
|
|
27
|
+
run: |
|
|
28
|
+
outdated_dependencies=$(cat output)
|
|
29
|
+
|
|
30
|
+
repo="${{ github.repository }}"
|
|
31
|
+
repo_url="${{ github.server_url }}/$repo"
|
|
32
|
+
run_url="$repo_url/actions/runs/${{ github.run_id }}"
|
|
33
|
+
|
|
34
|
+
text="ℹ️ The <$repo_url|$repo> project has outdated dependencies:
|
|
35
|
+
\`\`\`
|
|
36
|
+
$outdated_dependencies
|
|
37
|
+
\`\`\`
|
|
38
|
+
<$run_url|View Run>"
|
|
39
|
+
|
|
40
|
+
payload="{\"text\": \"$text\"}"
|
|
41
|
+
curl -X POST -H 'Content-type: application/json' --data "$payload" $SLACK_WEBHOOK_URL
|
|
42
|
+
env:
|
|
43
|
+
SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.58.0 (2023-10-12)
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
|
|
7
|
+
* Ensure Apostrophe can make appropriate checks by always including `type` in the projection even if it is not explicitly listed.
|
|
8
|
+
* Never try to annotate a widget with permissions the way we annotate a document, even if the widget is simulating a document.
|
|
9
|
+
* The `areas` query builder now works properly when an array of area names has been specified.
|
|
10
|
+
|
|
11
|
+
### Adds
|
|
12
|
+
|
|
13
|
+
* Widget schema can now follow the parent schema via the similar to introduced in the `array` field type syntax (`<` prefix). In order a parent followed field to be available to the widget schema, the area field should follow it. For example, if area follows the root schema `title` field via `following: ['title']`, any field from a widget schema inside that area can do `following: ['<title']`.
|
|
14
|
+
* The values of fields followed by an `area` field are now available in custom widget preview Vue components (registered with widget option `options.widget = 'MyComponentPreview'`). Those components will also receive additional `areaField` prop (the parent area field definition object).
|
|
15
|
+
* Allows to insert attachments with a given ID, as well as with `docIds` and `archivedDocIds` to preserve related docs.
|
|
16
|
+
* Adds an `update` method to the attachment module, that updates the mongoDB doc and the associated file.
|
|
17
|
+
* Adds an option to the `http` `remote` method to allow receiving the original response from `node-fetch` that is a stream.
|
|
18
|
+
|
|
19
|
+
## 3.57.0 2023-09-27
|
|
20
|
+
|
|
21
|
+
### Changes
|
|
22
|
+
* Removes a 25px gap used to prevent in-context widget UI from overlapping with the admin bar
|
|
23
|
+
* Simplifies the way in-context widget state is rendered via modifier classes
|
|
24
|
+
### Adds
|
|
25
|
+
|
|
26
|
+
* Widgets detect whether or not their in-context editing UI will collide with the admin bar and adjust it appropriately.
|
|
27
|
+
* Italian translation i18n file created for the Apostrophe Admin-UI. Thanks to [Antonello Zanini](https://github.com/Tonel) for this contribution.
|
|
28
|
+
* Fixed date in piece type being displayed as current date in column when set as undefined and without default value. Thanks to [TheSaddestBread](https://github.com/AllanKoder) for this contribution.
|
|
29
|
+
|
|
30
|
+
### Fixes
|
|
31
|
+
|
|
32
|
+
* Bumped dependency on `oembetter` to ensure Vimeo starts working again
|
|
33
|
+
for everyone with this release. This is necessary because Vimeo stopped
|
|
34
|
+
offering oembed discovery meta tags on their video pages.
|
|
35
|
+
|
|
36
|
+
### Fixes
|
|
37
|
+
|
|
38
|
+
* The `118n` module now ignores non-JSON files within the i18n folder of any module and does not crash the build process.
|
|
39
|
+
|
|
3
40
|
## 3.56.0 (2023-09-13)
|
|
4
41
|
|
|
5
42
|
### Adds
|
|
@@ -43,9 +43,11 @@
|
|
|
43
43
|
:i="i"
|
|
44
44
|
:options="options"
|
|
45
45
|
:next="next"
|
|
46
|
+
:following-values="followingValues"
|
|
46
47
|
:doc-id="docId"
|
|
47
48
|
:context-menu-options="contextMenuOptions"
|
|
48
49
|
:field-id="fieldId"
|
|
50
|
+
:field="field"
|
|
49
51
|
:disabled="field && field.readOnly"
|
|
50
52
|
:widget-hovered="hoveredWidget"
|
|
51
53
|
:non-foreign-widget-hovered="hoveredNonForeignWidget"
|
|
@@ -109,6 +111,12 @@ export default {
|
|
|
109
111
|
return [];
|
|
110
112
|
}
|
|
111
113
|
},
|
|
114
|
+
followingValues: {
|
|
115
|
+
type: Object,
|
|
116
|
+
default() {
|
|
117
|
+
return {};
|
|
118
|
+
}
|
|
119
|
+
},
|
|
112
120
|
choices: {
|
|
113
121
|
type: Array,
|
|
114
122
|
required: true
|
|
@@ -370,7 +378,8 @@ export default {
|
|
|
370
378
|
value: widget,
|
|
371
379
|
options: this.widgetOptionsByType(widget.type),
|
|
372
380
|
type: widget.type,
|
|
373
|
-
docId: this.docId
|
|
381
|
+
docId: this.docId,
|
|
382
|
+
parentFollowingValues: this.followingValues
|
|
374
383
|
});
|
|
375
384
|
apos.area.activeEditor = null;
|
|
376
385
|
apos.bus.$off('apos-refreshing', cancelRefresh);
|
|
@@ -467,7 +476,8 @@ export default {
|
|
|
467
476
|
value: null,
|
|
468
477
|
options: this.widgetOptionsByType(name),
|
|
469
478
|
type: name,
|
|
470
|
-
docId: this.docId
|
|
479
|
+
docId: this.docId,
|
|
480
|
+
parentFollowingValues: this.followingValues
|
|
471
481
|
});
|
|
472
482
|
apos.area.activeEditor = null;
|
|
473
483
|
if (widget) {
|
|
@@ -7,17 +7,19 @@
|
|
|
7
7
|
:data-area-label="widgetLabel"
|
|
8
8
|
:data-apos-widget-foreign="foreign ? 1 : 0"
|
|
9
9
|
:data-apos-widget-id="widget._id"
|
|
10
|
+
ref="widget"
|
|
10
11
|
>
|
|
11
12
|
<div
|
|
12
13
|
class="apos-area-widget-inner"
|
|
13
|
-
:class="
|
|
14
|
+
:class="containerClasses"
|
|
14
15
|
@mouseover="mouseover($event)"
|
|
15
16
|
@mouseleave="mouseleave"
|
|
16
17
|
@click="getFocus($event, widget._id)"
|
|
17
18
|
>
|
|
18
19
|
<div
|
|
19
20
|
class="apos-area-widget-controls apos-area-widget__label"
|
|
20
|
-
|
|
21
|
+
ref="label"
|
|
22
|
+
:class="labelsClasses"
|
|
21
23
|
>
|
|
22
24
|
<ol class="apos-area-widget__breadcrumbs">
|
|
23
25
|
<li class="apos-area-widget__breadcrumb apos-area-widget__breadcrumb--widget-icon">
|
|
@@ -56,7 +58,7 @@
|
|
|
56
58
|
</div>
|
|
57
59
|
<div
|
|
58
60
|
class="apos-area-widget-controls apos-area-widget-controls--add apos-area-widget-controls--add--top"
|
|
59
|
-
:class="
|
|
61
|
+
:class="addClasses"
|
|
60
62
|
>
|
|
61
63
|
<AposAreaMenu
|
|
62
64
|
v-if="!foreign"
|
|
@@ -73,7 +75,7 @@
|
|
|
73
75
|
</div>
|
|
74
76
|
<div
|
|
75
77
|
class="apos-area-widget-controls apos-area-widget-controls--modify"
|
|
76
|
-
:class="
|
|
78
|
+
:class="controlsClasses"
|
|
77
79
|
>
|
|
78
80
|
<AposWidgetControls
|
|
79
81
|
v-if="!foreign"
|
|
@@ -98,7 +100,7 @@
|
|
|
98
100
|
-->
|
|
99
101
|
<div
|
|
100
102
|
class="apos-area-widget-guard"
|
|
101
|
-
:class="{'apos-is-disabled':
|
|
103
|
+
:class="{'apos-is-disabled': isFocused}"
|
|
102
104
|
/>
|
|
103
105
|
<!-- Still used for contextual editing components -->
|
|
104
106
|
<component
|
|
@@ -109,7 +111,7 @@
|
|
|
109
111
|
:value="widget"
|
|
110
112
|
@update="$emit('update', $event)"
|
|
111
113
|
:doc-id="docId"
|
|
112
|
-
:focused="
|
|
114
|
+
:focused="isFocused"
|
|
113
115
|
:key="generation"
|
|
114
116
|
/>
|
|
115
117
|
<component
|
|
@@ -119,16 +121,18 @@
|
|
|
119
121
|
:type="widget.type"
|
|
120
122
|
:id="widget._id"
|
|
121
123
|
:area-field-id="fieldId"
|
|
124
|
+
:area-field="field"
|
|
125
|
+
:following-values="followingValuesWithParent"
|
|
122
126
|
:value="widget"
|
|
123
127
|
:foreign="foreign"
|
|
124
128
|
@edit="$emit('edit', i);"
|
|
125
129
|
:doc-id="docId"
|
|
126
130
|
:rendering="rendering"
|
|
127
|
-
:key="generation"
|
|
131
|
+
:key="`${generation}-preview`"
|
|
128
132
|
/>
|
|
129
133
|
<div
|
|
130
134
|
class="apos-area-widget-controls apos-area-widget-controls--add apos-area-widget-controls--add--bottom"
|
|
131
|
-
:class="
|
|
135
|
+
:class="addClasses"
|
|
132
136
|
>
|
|
133
137
|
<AposAreaMenu
|
|
134
138
|
v-if="!foreign"
|
|
@@ -148,7 +152,6 @@
|
|
|
148
152
|
</template>
|
|
149
153
|
|
|
150
154
|
<script>
|
|
151
|
-
import { klona } from 'klona';
|
|
152
155
|
import AposIndicator from '../../../../ui/ui/apos/components/AposIndicator.vue';
|
|
153
156
|
|
|
154
157
|
export default {
|
|
@@ -190,10 +193,20 @@ export default {
|
|
|
190
193
|
return {};
|
|
191
194
|
}
|
|
192
195
|
},
|
|
196
|
+
followingValues: {
|
|
197
|
+
type: Object,
|
|
198
|
+
default() {
|
|
199
|
+
return {};
|
|
200
|
+
}
|
|
201
|
+
},
|
|
193
202
|
next: {
|
|
194
203
|
type: Array,
|
|
195
204
|
required: true
|
|
196
205
|
},
|
|
206
|
+
field: {
|
|
207
|
+
type: Object,
|
|
208
|
+
required: true
|
|
209
|
+
},
|
|
197
210
|
fieldId: {
|
|
198
211
|
type: String,
|
|
199
212
|
required: true
|
|
@@ -225,38 +238,15 @@ export default {
|
|
|
225
238
|
},
|
|
226
239
|
emits: [ 'clone', 'up', 'down', 'remove', 'edit', 'cut', 'copy', 'update', 'add', 'changed' ],
|
|
227
240
|
data() {
|
|
228
|
-
const initialState = {
|
|
229
|
-
controls: {
|
|
230
|
-
show: false
|
|
231
|
-
},
|
|
232
|
-
container: {
|
|
233
|
-
highlight: false,
|
|
234
|
-
focus: false
|
|
235
|
-
},
|
|
236
|
-
add: {
|
|
237
|
-
bottom: {
|
|
238
|
-
show: false,
|
|
239
|
-
focus: false
|
|
240
|
-
},
|
|
241
|
-
top: {
|
|
242
|
-
show: false,
|
|
243
|
-
focus: false
|
|
244
|
-
}
|
|
245
|
-
},
|
|
246
|
-
labels: {
|
|
247
|
-
show: false
|
|
248
|
-
}
|
|
249
|
-
};
|
|
250
241
|
return {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
highlightable: false,
|
|
254
|
-
focused: false,
|
|
242
|
+
mounted: false, // hack around needing DOM to be rendered for computed classes
|
|
243
|
+
isSuppressed: false,
|
|
255
244
|
classes: {
|
|
256
245
|
show: 'apos-is-visible',
|
|
257
246
|
open: 'apos-is-open',
|
|
258
247
|
focus: 'apos-is-focused',
|
|
259
|
-
highlight: 'apos-is-highlighted'
|
|
248
|
+
highlight: 'apos-is-highlighted',
|
|
249
|
+
adjust: 'apos-is-ui-adjusted'
|
|
260
250
|
},
|
|
261
251
|
breadcrumbs: {
|
|
262
252
|
$lastEl: null,
|
|
@@ -266,6 +256,14 @@ export default {
|
|
|
266
256
|
};
|
|
267
257
|
},
|
|
268
258
|
computed: {
|
|
259
|
+
// Passed only to the preview layer (custom preview components).
|
|
260
|
+
followingValuesWithParent() {
|
|
261
|
+
return Object.entries(this.followingValues || {})
|
|
262
|
+
.reduce((acc, [ key, value ]) => {
|
|
263
|
+
acc[`<${key}`] = value;
|
|
264
|
+
return acc;
|
|
265
|
+
}, {});
|
|
266
|
+
},
|
|
269
267
|
bottomContextMenuOptions() {
|
|
270
268
|
return {
|
|
271
269
|
...this.contextMenuOptions,
|
|
@@ -294,73 +292,56 @@ export default {
|
|
|
294
292
|
moduleOptions() {
|
|
295
293
|
return window.apos.area;
|
|
296
294
|
},
|
|
297
|
-
|
|
298
|
-
if (this.
|
|
299
|
-
return false;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
if (this.highlightable) {
|
|
295
|
+
isFocused() {
|
|
296
|
+
if (this.isSuppressed) {
|
|
303
297
|
return false;
|
|
298
|
+
} else {
|
|
299
|
+
if (this.widgetFocused === this.widget._id) {
|
|
300
|
+
document.addEventListener('click', this.unfocus);
|
|
301
|
+
}
|
|
302
|
+
return this.widgetFocused === this.widget._id;
|
|
304
303
|
}
|
|
305
|
-
|
|
306
|
-
return !(this.hovered || this.nonForeignHovered);
|
|
307
304
|
},
|
|
308
|
-
|
|
305
|
+
isHovered() {
|
|
309
306
|
return this.widgetHovered === this.widget._id;
|
|
310
307
|
},
|
|
308
|
+
isHighlighted() {
|
|
309
|
+
const $parent = this.getParent();
|
|
310
|
+
return $parent && $parent.dataset.areaWidget === this.widgetFocused;
|
|
311
|
+
},
|
|
311
312
|
nonForeignHovered() {
|
|
312
313
|
return this.nonForeignWidgetHovered === this.widget._id;
|
|
313
314
|
},
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
ui() {
|
|
318
|
-
const state = {
|
|
319
|
-
controls: this.state.controls.show ? this.classes.show : null,
|
|
320
|
-
labels: this.state.labels.show ? this.classes.show : null,
|
|
321
|
-
container: this.state.container.focus ? this.classes.focus
|
|
322
|
-
: ((this.state.container.highlight || this.nonForeignHovered) ? this.classes.highlight : null),
|
|
323
|
-
addTop: this.state.add.top.focus ? this.classes.focus
|
|
324
|
-
: ((this.state.add.top.show || this.nonForeignHovered) ? this.classes.show : null),
|
|
325
|
-
addBottom: this.state.add.bottom.focus ? this.classes.focus
|
|
326
|
-
: ((this.state.add.bottom.show || this.nonForeignHovered) ? this.classes.show : null)
|
|
315
|
+
controlsClasses() {
|
|
316
|
+
return {
|
|
317
|
+
[this.classes.show]: this.isFocused
|
|
327
318
|
};
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
319
|
+
},
|
|
320
|
+
containerClasses() {
|
|
321
|
+
const classes = {
|
|
322
|
+
[this.classes.highlight]: this.isHighlighted || this.isHovered,
|
|
323
|
+
[this.classes.focus]: this.isFocused
|
|
324
|
+
};
|
|
325
|
+
if (this.mounted) {
|
|
326
|
+
classes[this.classes.adjust] = this.adjustUi();
|
|
332
327
|
}
|
|
333
|
-
|
|
334
|
-
|
|
328
|
+
return classes;
|
|
329
|
+
},
|
|
330
|
+
labelsClasses() {
|
|
331
|
+
return {
|
|
332
|
+
[this.classes.show]: this.isHovered || this.isFocused
|
|
333
|
+
};
|
|
334
|
+
},
|
|
335
|
+
addClasses() {
|
|
336
|
+
return {
|
|
337
|
+
[this.classes.show]: this.isHovered || this.isFocused
|
|
338
|
+
};
|
|
335
339
|
},
|
|
336
340
|
foreign() {
|
|
337
341
|
// Cast to boolean is necessary to satisfy prop typing
|
|
338
342
|
return !!(this.docId && (window.apos.adminBar.contextId !== this.docId));
|
|
339
343
|
}
|
|
340
344
|
},
|
|
341
|
-
watch: {
|
|
342
|
-
widgetFocused (newVal) {
|
|
343
|
-
if (newVal === this.widget._id) {
|
|
344
|
-
this.focus();
|
|
345
|
-
} else {
|
|
346
|
-
// reset everything
|
|
347
|
-
this.resetState();
|
|
348
|
-
this.focused = false;
|
|
349
|
-
}
|
|
350
|
-
const $parent = this.getParent();
|
|
351
|
-
if (
|
|
352
|
-
$parent &&
|
|
353
|
-
$parent.dataset.areaWidget === newVal
|
|
354
|
-
) {
|
|
355
|
-
// Our parent was focused
|
|
356
|
-
this.resetState();
|
|
357
|
-
this.state.container.highlight = true;
|
|
358
|
-
this.highlightable = true;
|
|
359
|
-
} else {
|
|
360
|
-
this.highlightable = false;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
},
|
|
364
345
|
created() {
|
|
365
346
|
if (this.options.groups) {
|
|
366
347
|
for (const group of Object.keys(this.options.groups)) {
|
|
@@ -372,6 +353,7 @@ export default {
|
|
|
372
353
|
}
|
|
373
354
|
},
|
|
374
355
|
mounted() {
|
|
356
|
+
this.mounted = true;
|
|
375
357
|
// AposAreaEditor is listening for keyboard input that triggers
|
|
376
358
|
// a 'focus my parent' plea
|
|
377
359
|
apos.bus.$on('widget-focus-parent', this.focusParent);
|
|
@@ -392,11 +374,21 @@ export default {
|
|
|
392
374
|
apos.bus.$off('widget-focus-parent', this.focusParent);
|
|
393
375
|
},
|
|
394
376
|
methods: {
|
|
377
|
+
|
|
378
|
+
// Determine whether or not we should adjust the label based on its position to the admin bar
|
|
379
|
+
adjustUi() {
|
|
380
|
+
const { height: labelHeight } = this.$refs.label.getBoundingClientRect();
|
|
381
|
+
const { top: widgetTop } = this.$refs.widget.getBoundingClientRect();
|
|
382
|
+
const adminBarHeight = window.apos.modules['@apostrophecms/admin-bar'].height;
|
|
383
|
+
const offsetTop = widgetTop + window.scrollY;
|
|
384
|
+
return offsetTop - labelHeight < adminBarHeight;
|
|
385
|
+
},
|
|
386
|
+
|
|
395
387
|
// Focus parent, useful for obtrusive UI
|
|
396
388
|
focusParent() {
|
|
397
389
|
// Something above us asked the focused widget to try and focus its parent
|
|
398
390
|
// We only care about this if we're focused ...
|
|
399
|
-
if (this.
|
|
391
|
+
if (this.isFocused) {
|
|
400
392
|
const $parent = this.getParent();
|
|
401
393
|
// .. And have a parent
|
|
402
394
|
if ($parent) {
|
|
@@ -408,6 +400,7 @@ export default {
|
|
|
408
400
|
// Ask the parent AposAreaEditor to make us focused
|
|
409
401
|
getFocus(e, id) {
|
|
410
402
|
e.stopPropagation();
|
|
403
|
+
this.isSuppressed = false;
|
|
411
404
|
apos.bus.$emit('widget-focus', id);
|
|
412
405
|
},
|
|
413
406
|
|
|
@@ -416,15 +409,6 @@ export default {
|
|
|
416
409
|
if (e) {
|
|
417
410
|
e.stopPropagation();
|
|
418
411
|
}
|
|
419
|
-
if (this.focused) {
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
if (!this.foreign) {
|
|
423
|
-
this.state.add.top.show = true;
|
|
424
|
-
this.state.add.bottom.show = true;
|
|
425
|
-
}
|
|
426
|
-
this.state.container.highlight = true;
|
|
427
|
-
this.state.labels.show = true;
|
|
428
412
|
const closest = this.foreign && this.$el.closest('[data-apos-widget-foreign="0"]');
|
|
429
413
|
const closestId = closest && closest.getAttribute('data-apos-widget-id');
|
|
430
414
|
apos.bus.$emit('widget-hover', {
|
|
@@ -434,41 +418,16 @@ export default {
|
|
|
434
418
|
},
|
|
435
419
|
|
|
436
420
|
mouseleave() {
|
|
437
|
-
if (
|
|
438
|
-
// Force highlight when a parent has been focused
|
|
439
|
-
this.state.container.highlight = false;
|
|
440
|
-
}
|
|
441
|
-
if (!this.focused) {
|
|
442
|
-
this.state.labels.show = false;
|
|
443
|
-
this.state.add.top.show = false;
|
|
444
|
-
this.state.add.bottom.show = false;
|
|
445
|
-
}
|
|
446
|
-
if (this.hovered) {
|
|
421
|
+
if (this.isHovered) {
|
|
447
422
|
apos.bus.$emit('widget-hover', {
|
|
448
423
|
_id: null,
|
|
449
424
|
nonForeignId: null
|
|
450
425
|
});
|
|
451
426
|
}
|
|
452
427
|
},
|
|
453
|
-
|
|
454
|
-
focus(e) {
|
|
455
|
-
if (e) {
|
|
456
|
-
e.stopPropagation();
|
|
457
|
-
}
|
|
458
|
-
this.focused = true;
|
|
459
|
-
this.state.container.focus = true;
|
|
460
|
-
this.state.controls.show = true;
|
|
461
|
-
this.state.add.top.show = true;
|
|
462
|
-
this.state.add.bottom.show = true;
|
|
463
|
-
this.state.labels.show = true;
|
|
464
|
-
document.addEventListener('click', this.unfocus);
|
|
465
|
-
},
|
|
466
|
-
|
|
467
428
|
unfocus(event) {
|
|
468
429
|
if (!this.$el.contains(event.target)) {
|
|
469
|
-
this.
|
|
470
|
-
this.resetState();
|
|
471
|
-
this.highlightable = false;
|
|
430
|
+
this.isSuppressed = true;
|
|
472
431
|
document.removeEventListener('click', this.unfocus);
|
|
473
432
|
apos.bus.$emit('widget-focus', null);
|
|
474
433
|
}
|
|
@@ -485,12 +444,11 @@ export default {
|
|
|
485
444
|
}
|
|
486
445
|
},
|
|
487
446
|
|
|
488
|
-
resetState() {
|
|
489
|
-
this.state = klona(this.blankState);
|
|
490
|
-
},
|
|
491
|
-
|
|
492
447
|
getParent() {
|
|
493
|
-
|
|
448
|
+
if (!this.mounted) {
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
return this.$el.parentNode ? apos.util.closest(this.$el.parentNode, '[data-area-widget]') : false;
|
|
494
452
|
},
|
|
495
453
|
|
|
496
454
|
// Hacky way to get the parents tree of a widget
|
|
@@ -555,6 +513,14 @@ export default {
|
|
|
555
513
|
box-shadow: none;
|
|
556
514
|
}
|
|
557
515
|
}
|
|
516
|
+
&.apos-is-ui-adjusted {
|
|
517
|
+
.apos-area-widget-controls--modify {
|
|
518
|
+
transform: translate3d(-10px, 50px, 0);
|
|
519
|
+
}
|
|
520
|
+
.apos-area-widget__label {
|
|
521
|
+
transform: translate(-10px, 10px);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
558
524
|
|
|
559
525
|
.apos-area-widget-inner &:after {
|
|
560
526
|
display: none;
|
|
@@ -704,6 +670,7 @@ export default {
|
|
|
704
670
|
right: 0;
|
|
705
671
|
display: flex;
|
|
706
672
|
transform: translateY(-100%);
|
|
673
|
+
transition: opacity 0.3s ease;
|
|
707
674
|
}
|
|
708
675
|
|
|
709
676
|
.apos-area-widget-inner .apos-area-widget-inner .apos-area-widget__label {
|
|
@@ -386,8 +386,7 @@ module.exports = {
|
|
|
386
386
|
// This method returns `attachment` where `attachment` is an attachment
|
|
387
387
|
// object, suitable for passing to the `url` API and for use as the value
|
|
388
388
|
// of a `type: 'attachment'` schema field.
|
|
389
|
-
async insert(req, file, options) {
|
|
390
|
-
options = options || {};
|
|
389
|
+
async insert(req, file, options = {}) {
|
|
391
390
|
let extension = path.extname(file.name);
|
|
392
391
|
if (extension && extension.length) {
|
|
393
392
|
extension = extension.substr(1);
|
|
@@ -402,16 +401,21 @@ module.exports = {
|
|
|
402
401
|
extensions: accepted.join(req.t('apostrophe:listJoiner'))
|
|
403
402
|
}));
|
|
404
403
|
}
|
|
404
|
+
|
|
405
|
+
if (options.attachmentId && await self.apos.attachment.db.findOne({ _id: options.attachmentId })) {
|
|
406
|
+
throw self.apos.error('invalid', 'duplicate');
|
|
407
|
+
}
|
|
408
|
+
|
|
405
409
|
const info = {
|
|
406
|
-
_id: self.apos.util.generateId(),
|
|
410
|
+
_id: options.attachmentId ?? self.apos.util.generateId(),
|
|
407
411
|
group: group.name,
|
|
408
412
|
createdAt: new Date(),
|
|
409
413
|
name: self.apos.util.slugify(path.basename(file.name, path.extname(file.name))),
|
|
410
414
|
title: self.apos.util.sortify(path.basename(file.name, path.extname(file.name))),
|
|
411
415
|
extension: extension,
|
|
412
416
|
type: 'attachment',
|
|
413
|
-
docIds: [],
|
|
414
|
-
archivedDocIds: []
|
|
417
|
+
docIds: options.docIds ?? [],
|
|
418
|
+
archivedDocIds: options.archivedDocIds ?? []
|
|
415
419
|
};
|
|
416
420
|
if (!(options.permissions === false)) {
|
|
417
421
|
if (!self.apos.permission.can(req, 'upload-attachment')) {
|
|
@@ -432,7 +436,11 @@ module.exports = {
|
|
|
432
436
|
}
|
|
433
437
|
if (self.isSized(extension)) {
|
|
434
438
|
// For images we correct automatically for common file extension mistakes
|
|
435
|
-
const result = await Promise.promisify(self.uploadfs.copyImageIn)(
|
|
439
|
+
const result = await Promise.promisify(self.uploadfs.copyImageIn)(
|
|
440
|
+
file.path,
|
|
441
|
+
'/attachments/' + info._id + '-' + info.name,
|
|
442
|
+
{ sizes: self.imageSizes }
|
|
443
|
+
);
|
|
436
444
|
info.extension = result.extension;
|
|
437
445
|
info.width = result.width;
|
|
438
446
|
info.height = result.height;
|
|
@@ -454,6 +462,51 @@ module.exports = {
|
|
|
454
462
|
await self.db.insertOne(info);
|
|
455
463
|
return info;
|
|
456
464
|
},
|
|
465
|
+
|
|
466
|
+
async update(req, file, attachment) {
|
|
467
|
+
const existing = await self.db.findOne({ _id: attachment._id });
|
|
468
|
+
if (!existing) {
|
|
469
|
+
throw self.apos.error('notfound');
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const projection = {
|
|
473
|
+
_id: 1,
|
|
474
|
+
archived: 1
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
const existingRelatedDocs = await self.apos.doc.db
|
|
478
|
+
.find({
|
|
479
|
+
_id: {
|
|
480
|
+
$in: [
|
|
481
|
+
...existing.docIds,
|
|
482
|
+
...existing.archivedDocIds,
|
|
483
|
+
...attachment.docIds,
|
|
484
|
+
...attachment.archivedDocIds
|
|
485
|
+
]
|
|
486
|
+
}
|
|
487
|
+
}, { projection })
|
|
488
|
+
.toArray();
|
|
489
|
+
|
|
490
|
+
const { docIds, archivedDocIds } = existingRelatedDocs
|
|
491
|
+
.reduce(({ docIds, archivedDocIds }, doc) => {
|
|
492
|
+
return {
|
|
493
|
+
docIds: [ ...docIds, ...!doc.archived ? [ doc._id ] : [] ],
|
|
494
|
+
archivedDocIds: [ ...archivedDocIds, ...doc.archived ? [ doc._id ] : [] ]
|
|
495
|
+
};
|
|
496
|
+
}, {
|
|
497
|
+
docIds: [],
|
|
498
|
+
archivedDocIds: []
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
await self.alterAttachment(existing, 'remove');
|
|
502
|
+
await self.db.deleteOne({ _id: existing._id });
|
|
503
|
+
await self.insert(req, file, {
|
|
504
|
+
attachmentId: attachment._id,
|
|
505
|
+
docIds: _.uniq([ ...docIds, ...existing.docIds || [] ]),
|
|
506
|
+
archivedDocIds: _.uniq([ ...archivedDocIds, ...existing.archivedDocIds || [] ])
|
|
507
|
+
});
|
|
508
|
+
},
|
|
509
|
+
|
|
457
510
|
// Given a path to a local svg file, sanitize any XSS attack vectors that
|
|
458
511
|
// may be present in the file. The caller is responsible for catching any
|
|
459
512
|
// exception thrown and treating that as an invalid file but there is no
|
|
@@ -1624,6 +1624,12 @@ module.exports = {
|
|
|
1624
1624
|
// to the projection instead.
|
|
1625
1625
|
const add = [];
|
|
1626
1626
|
const remove = [];
|
|
1627
|
+
|
|
1628
|
+
// Add type in projection by default
|
|
1629
|
+
if (!_.isEmpty(projection)) {
|
|
1630
|
+
add.push('type');
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1627
1633
|
for (const [ key, val ] of Object.entries(projection)) {
|
|
1628
1634
|
if (!val) {
|
|
1629
1635
|
// For a negative projection this is just
|
|
@@ -2228,7 +2234,7 @@ module.exports = {
|
|
|
2228
2234
|
const dotPath = info.dotPath;
|
|
2229
2235
|
if (setting && Array.isArray(setting)) {
|
|
2230
2236
|
if (!_.includes(setting, dotPath)) {
|
|
2231
|
-
|
|
2237
|
+
continue;
|
|
2232
2238
|
}
|
|
2233
2239
|
}
|
|
2234
2240
|
if (doc._edit) {
|