apostrophe 4.6.1 → 4.7.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 +1 -1
- package/CHANGELOG.md +39 -1
- package/lib/big-upload-client.js +100 -0
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBar.vue +5 -3
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarLocale.vue +6 -3
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarUser.vue +4 -1
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +24 -16
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextTitle.vue +1 -0
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposSavingIndicator.vue +7 -5
- package/modules/@apostrophecms/area/index.js +5 -2
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaContextualMenu.vue +20 -12
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaExpandedMenu.vue +11 -7
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaMenu.vue +20 -12
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaMenuItem.vue +3 -1
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +15 -11
- package/modules/@apostrophecms/attachment/index.js +4 -2
- package/modules/@apostrophecms/command-menu/ui/apos/components/AposCommandMenuKey.vue +28 -24
- package/modules/@apostrophecms/command-menu/ui/apos/components/AposCommandMenuShortcut.vue +17 -11
- package/modules/@apostrophecms/doc/index.js +22 -19
- package/modules/@apostrophecms/doc-type/index.js +6 -2
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +9 -5
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocLocalePicker.vue +10 -5
- package/modules/@apostrophecms/doc-type/ui/apos/logic/AposDocContextMenu.js +12 -0
- package/modules/@apostrophecms/http/index.js +19 -3
- package/modules/@apostrophecms/http/lib/big-upload-middleware.js +251 -0
- package/modules/@apostrophecms/i18n/i18n/de.json +1 -1
- package/modules/@apostrophecms/i18n/i18n/en.json +9 -1
- package/modules/@apostrophecms/i18n/i18n/es.json +1 -1
- package/modules/@apostrophecms/i18n/i18n/fr.json +1 -1
- package/modules/@apostrophecms/i18n/i18n/it.json +1 -1
- package/modules/@apostrophecms/i18n/i18n/pt-BR.json +1 -1
- package/modules/@apostrophecms/i18n/i18n/sk.json +1 -1
- package/modules/@apostrophecms/i18n/index.js +3 -0
- package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +30 -16
- package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalizeErrors.vue +7 -5
- package/modules/@apostrophecms/image/ui/apos/components/AposImageCropper.vue +5 -1
- package/modules/@apostrophecms/image/ui/apos/components/AposImageRelationshipEditor.vue +10 -6
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +40 -18
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerDisplay.vue +35 -25
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerEditor.vue +11 -5
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerSelections.vue +15 -9
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaUploader.vue +39 -31
- package/modules/@apostrophecms/job/index.js +1 -1
- package/modules/@apostrophecms/login/ui/apos/components/AposLoginForm.vue +9 -7
- package/modules/@apostrophecms/login/ui/apos/components/TheAposLogin.vue +17 -13
- package/modules/@apostrophecms/login/ui/apos/components/TheAposLoginHeader.vue +30 -20
- package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +5 -0
- package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +4 -1
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalBreadcrumbs.vue +8 -4
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +14 -8
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalShareDraft.vue +32 -22
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalTabs.vue +16 -14
- package/modules/@apostrophecms/modal/ui/apos/components/AposWidgetModalTabs.vue +16 -14
- package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +93 -91
- package/modules/@apostrophecms/page/index.js +482 -13
- package/modules/@apostrophecms/page/ui/apos/components/AposPagesManager.vue +43 -23
- package/modules/@apostrophecms/page/ui/apos/logic/AposPagesManager.js +248 -156
- package/modules/@apostrophecms/permission/ui/apos/components/AposPermissionGrid.vue +9 -5
- package/modules/@apostrophecms/piece-type/index.js +7 -7
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +92 -36
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +30 -24
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapDivider.vue +4 -2
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapLink.vue +2 -1
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapMarks.vue +5 -3
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapStyles.vue +5 -3
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapTable.vue +5 -2
- package/modules/@apostrophecms/schema/index.js +26 -5
- package/modules/@apostrophecms/schema/lib/addFieldTypes.js +42 -9
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputColor.vue +4 -2
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRange.vue +8 -4
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +6 -4
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSlug.vue +5 -3
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +19 -13
- package/modules/@apostrophecms/schema/ui/apos/components/AposSearchList.vue +6 -2
- package/modules/@apostrophecms/schema/ui/apos/components/AposSubform.vue +6 -4
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputSlug.js +28 -25
- package/modules/@apostrophecms/schema/ui/apos/scss/AposInputArray.scss +13 -7
- package/modules/@apostrophecms/settings/ui/apos/components/AposSettingsManager.vue +11 -6
- package/modules/@apostrophecms/translation/ui/apos/components/AposTranslationIndicator.vue +5 -3
- package/modules/@apostrophecms/ui/ui/apos/components/AposAvatar.vue +14 -12
- package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +14 -11
- package/modules/@apostrophecms/ui/ui/apos/components/AposButtonSplit.vue +7 -3
- package/modules/@apostrophecms/ui/ui/apos/components/AposCellContextMenu.vue +4 -2
- package/modules/@apostrophecms/ui/ui/apos/components/AposCombo.vue +23 -17
- package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenu.vue +25 -10
- package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenuDialog.vue +7 -5
- package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenuItem.vue +10 -8
- package/modules/@apostrophecms/ui/ui/apos/components/AposEmptyState.vue +9 -5
- package/modules/@apostrophecms/ui/ui/apos/components/AposFile.vue +9 -6
- package/modules/@apostrophecms/ui/ui/apos/components/AposIndicator.vue +5 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposLoadingBlock.vue +3 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposLocale.vue +3 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposLocalePicker.vue +11 -9
- package/modules/@apostrophecms/ui/ui/apos/components/AposMinMaxCount.vue +5 -3
- package/modules/@apostrophecms/ui/ui/apos/components/AposPager.vue +4 -2
- package/modules/@apostrophecms/ui/ui/apos/components/AposPagerDots.vue +8 -6
- package/modules/@apostrophecms/ui/ui/apos/components/AposSlat.vue +25 -17
- package/modules/@apostrophecms/ui/ui/apos/components/AposSlatList.vue +5 -9
- package/modules/@apostrophecms/ui/ui/apos/components/AposSubformPreview.vue +10 -6
- package/modules/@apostrophecms/ui/ui/apos/components/AposTag.vue +9 -7
- package/modules/@apostrophecms/ui/ui/apos/components/AposTagApply.vue +8 -4
- package/modules/@apostrophecms/ui/ui/apos/components/AposTagList.vue +4 -2
- package/modules/@apostrophecms/ui/ui/apos/components/AposTagListItem.vue +7 -5
- package/modules/@apostrophecms/ui/ui/apos/components/AposToggle.vue +16 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposTree.vue +3 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposTreeRows.vue +11 -9
- package/modules/@apostrophecms/ui/ui/apos/mixins/AposArchiveMixin.js +2 -2
- package/modules/@apostrophecms/ui/ui/apos/mixins/AposPublishMixin.js +6 -6
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_inputs.scss +30 -22
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_theme.scss +22 -18
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_tooltips.scss +18 -15
- package/modules/@apostrophecms/ui/ui/apos/scss/mixins/_input_mixins.scss +8 -6
- package/modules/@apostrophecms/ui/ui/apos/scss/mixins/_mixins.scss +3 -1
- package/modules/@apostrophecms/ui/ui/apos/scss/mixins/_theme_mixins.scss +34 -19
- package/modules/@apostrophecms/ui/ui/apos/scss/mixins/_type_mixins.scss +3 -1
- package/modules/@apostrophecms/ui/ui/apos/utils/index.js +140 -51
- package/modules/@apostrophecms/widget-type/index.js +3 -2
- package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +5 -1
- package/package.json +5 -6
- package/test/big-upload.js +111 -0
- package/test/change-doc-ids.js +60 -1
- package/test/pages.js +488 -0
- package/test/schemas.js +327 -0
- package/test/utils.js +266 -5
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
line-height: var(--a-line-tall);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
ul,
|
|
19
|
+
ul,
|
|
20
|
+
ol {
|
|
20
21
|
padding-left: $spacing-base;
|
|
21
22
|
}
|
|
22
23
|
|
|
@@ -30,7 +31,7 @@
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
ul li::before {
|
|
33
|
-
content:
|
|
34
|
+
content: "-";
|
|
34
35
|
position: relative;
|
|
35
36
|
left: -$spacing-half;
|
|
36
37
|
}
|
|
@@ -39,13 +40,15 @@
|
|
|
39
40
|
.apos-tooltip__inner {
|
|
40
41
|
@include type-small;
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
& {
|
|
44
|
+
z-index: $z-index-default;
|
|
45
|
+
position: relative;
|
|
46
|
+
padding: 8px 10px;
|
|
47
|
+
border-radius: 3px;
|
|
48
|
+
border-width: 0;
|
|
49
|
+
color: var(--a-text-inverted);
|
|
50
|
+
background: var(--a-background-inverted);
|
|
51
|
+
}
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
.apos-tooltip__arrow {
|
|
@@ -61,31 +64,31 @@
|
|
|
61
64
|
transform: rotate(45deg);
|
|
62
65
|
}
|
|
63
66
|
|
|
64
|
-
&[x-placement^=
|
|
67
|
+
&[x-placement^="top"] {
|
|
65
68
|
.apos-tooltip__arrow {
|
|
66
69
|
bottom: -4px;
|
|
67
70
|
}
|
|
68
71
|
}
|
|
69
72
|
|
|
70
|
-
&[x-placement^=
|
|
73
|
+
&[x-placement^="bottom"] {
|
|
71
74
|
.apos-tooltip__arrow {
|
|
72
75
|
top: -3px;
|
|
73
76
|
}
|
|
74
77
|
}
|
|
75
78
|
|
|
76
|
-
&[x-placement^=
|
|
79
|
+
&[x-placement^="right"] {
|
|
77
80
|
.apos-tooltip__arrow {
|
|
78
81
|
left: -3px;
|
|
79
82
|
}
|
|
80
83
|
}
|
|
81
84
|
|
|
82
|
-
&[x-placement^=
|
|
85
|
+
&[x-placement^="left"] {
|
|
83
86
|
.apos-tooltip__arrow {
|
|
84
87
|
right: -3px;
|
|
85
88
|
}
|
|
86
89
|
}
|
|
87
90
|
|
|
88
|
-
&[aria-hidden=
|
|
91
|
+
&[aria-hidden="true"] {
|
|
89
92
|
display: none;
|
|
90
93
|
visibility: hidden;
|
|
91
94
|
opacity: 0;
|
|
@@ -96,7 +99,7 @@
|
|
|
96
99
|
}
|
|
97
100
|
}
|
|
98
101
|
|
|
99
|
-
&[aria-hidden=
|
|
102
|
+
&[aria-hidden="false"] {
|
|
100
103
|
display: initial;
|
|
101
104
|
visibility: visible;
|
|
102
105
|
opacity: 1;
|
|
@@ -5,12 +5,14 @@ $box-width: 12px;
|
|
|
5
5
|
@include type-base;
|
|
6
6
|
@include apos-transition(all);
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
& {
|
|
9
|
+
box-sizing: border-box;
|
|
10
|
+
width: 100%;
|
|
11
|
+
border: 1px solid var(--a-base-8);
|
|
12
|
+
color: var(--a-text-primary);
|
|
13
|
+
border-radius: var(--a-border-radius);
|
|
14
|
+
background-color: var(--a-base-9);
|
|
15
|
+
}
|
|
14
16
|
|
|
15
17
|
&::placeholder {
|
|
16
18
|
color: var(--a-base-4);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
@use
|
|
1
|
+
@use "sass:math";
|
|
2
2
|
|
|
3
3
|
$brand-red: #ea433a;
|
|
4
4
|
$brand-gold: #cc9300;
|
|
@@ -27,57 +27,72 @@ $input-color-disabled: var(--a-base-4);
|
|
|
27
27
|
--a-primary-transparent-10: #{transparentize($color, 0.9)};
|
|
28
28
|
--a-primary-dark-10: #{mix(#000, $color, 10%)};
|
|
29
29
|
--a-primary-dark-15: #{mix(#000, $color, 15%)};
|
|
30
|
-
--a-primary-light-40: #{mix(#
|
|
30
|
+
--a-primary-light-40: #{mix(#fff, $color, 40%)};
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
//Application Styles
|
|
34
34
|
|
|
35
35
|
@mixin type-base {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
& {
|
|
37
|
+
color: var(--a-text-primary);
|
|
38
|
+
font-family: var(--a-family-default);
|
|
39
|
+
font-size: var(--a-type-base);
|
|
40
|
+
font-weight: var(--a-weight-base);
|
|
41
|
+
letter-spacing: var(--a-letter-base);
|
|
42
|
+
line-height: var(--a-line-base);
|
|
43
|
+
}
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
@mixin type-help {
|
|
45
47
|
@include type-base;
|
|
46
48
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
& {
|
|
50
|
+
font-size: var(--a-type-small);
|
|
51
|
+
text-transform: uppercase;
|
|
52
|
+
}
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
@mixin type-small {
|
|
52
56
|
@include type-base;
|
|
53
57
|
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
& {
|
|
59
|
+
font-size: var(--a-type-small);
|
|
60
|
+
line-height: var(--a-line-tall);
|
|
61
|
+
}
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
@mixin type-label {
|
|
59
65
|
@include type-base;
|
|
60
66
|
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
& {
|
|
68
|
+
font-size: var(--a-type-label);
|
|
69
|
+
line-height: var(--a-line-tall);
|
|
70
|
+
}
|
|
63
71
|
}
|
|
64
72
|
|
|
65
73
|
@mixin type-large {
|
|
66
74
|
@include type-base;
|
|
67
75
|
|
|
68
|
-
|
|
69
|
-
|
|
76
|
+
& {
|
|
77
|
+
font-size: var(--a-type-large);
|
|
78
|
+
line-height: var(--a-line-tall);
|
|
79
|
+
}
|
|
80
|
+
|
|
70
81
|
}
|
|
71
82
|
|
|
72
83
|
@mixin type-title {
|
|
73
84
|
@include type-base;
|
|
74
85
|
|
|
75
|
-
|
|
76
|
-
|
|
86
|
+
& {
|
|
87
|
+
font-size: var(--a-type-heading);
|
|
88
|
+
line-height: var(--a-line-tall);
|
|
89
|
+
}
|
|
77
90
|
}
|
|
78
91
|
|
|
79
92
|
@mixin type-display {
|
|
80
93
|
@include type-base;
|
|
81
94
|
|
|
82
|
-
|
|
95
|
+
& {
|
|
96
|
+
font-size: var(--a-type-display);
|
|
97
|
+
}
|
|
83
98
|
}
|
|
@@ -1,53 +1,142 @@
|
|
|
1
1
|
module.exports = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const setTimer = (res, rej, args, delay) => {
|
|
7
|
-
return setTimeout(() => {
|
|
8
|
-
if (!previousDone) {
|
|
9
|
-
clearTimeout(timer);
|
|
10
|
-
timer = setTimer(res, rej, args, delay);
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
previousDone = false;
|
|
15
|
-
const returned = fn.apply(this, args);
|
|
16
|
-
if (returned instanceof Promise) {
|
|
17
|
-
return returned
|
|
18
|
-
.then(res)
|
|
19
|
-
.catch(rej)
|
|
20
|
-
.finally(() => {
|
|
21
|
-
previousDone = true;
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
previousDone = true;
|
|
26
|
-
res(returned);
|
|
27
|
-
}, delay);
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
return (...args) => {
|
|
31
|
-
return new Promise((resolve, reject) => {
|
|
32
|
-
clearTimeout(timer);
|
|
33
|
-
timer = setTimer(resolve, reject, args, delay);
|
|
34
|
-
});
|
|
35
|
-
};
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
throttle(fn, delay) {
|
|
39
|
-
let inThrottle;
|
|
40
|
-
|
|
41
|
-
return (...args) => {
|
|
42
|
-
return new Promise((resolve) => {
|
|
43
|
-
if (!inThrottle) {
|
|
44
|
-
inThrottle = true;
|
|
45
|
-
setTimeout(() => {
|
|
46
|
-
inThrottle = false;
|
|
47
|
-
resolve(fn.apply(this, args));
|
|
48
|
-
}, delay);
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
};
|
|
52
|
-
}
|
|
2
|
+
debounceAsync,
|
|
3
|
+
// BC alias
|
|
4
|
+
debounce: debounceAsync,
|
|
5
|
+
throttle
|
|
53
6
|
};
|
|
7
|
+
|
|
8
|
+
// Debounce the async function "fn". For synchronous functions, use "lodash/debounce", not this function.
|
|
9
|
+
//
|
|
10
|
+
// Returns a debounced function that invokes "fn", but no more frequently than every "delay"
|
|
11
|
+
// milliseconds and never while "fn" is already in progress.
|
|
12
|
+
//
|
|
13
|
+
// As always when debouncing, extra calls are discarded, however the most recent call
|
|
14
|
+
// is guaranteed to result in a final invocation if not preempted by a new call, so you may
|
|
15
|
+
// trust that the user's most recent input will eventually be sent etc.
|
|
16
|
+
//
|
|
17
|
+
// ### Avoiding race conditions with onSuccess
|
|
18
|
+
//
|
|
19
|
+
// Race conditions are a challenge. To avoid them, "fn" should have no side effects, and you should
|
|
20
|
+
// pass a synchronous function that accepts the return value of "fn" as the "onSuccess" option.
|
|
21
|
+
// The "onSuccess" function should then implement all needed side effects (e.g. changes to component
|
|
22
|
+
// state, etc) when invoked. The debounced function has no return value when "onSuccess" is used.
|
|
23
|
+
//
|
|
24
|
+
// You can cancel the debounced function and cause all ongoing and any further invocations to be rejected by
|
|
25
|
+
// calling the "cancel()" method attached to it.
|
|
26
|
+
//
|
|
27
|
+
// You can skip the initial delay for a particular invocation by calling the "skipDelay()" method
|
|
28
|
+
// attached to the debounced function, passing any appropriate arguments for "fn" to "skipDelay" instead.
|
|
29
|
+
// This is useful when immediate action is sometimes needed but you still want to use "onSuccess" in
|
|
30
|
+
// a consistent manner and prevent race conditions.
|
|
31
|
+
//
|
|
32
|
+
// ### Detecting Cancellations
|
|
33
|
+
//
|
|
34
|
+
// If "onSuccess" is not provided, then after cancellation any invocations will be rejected
|
|
35
|
+
// with an error such that "e.name === 'debounce.canceled'". Any other errors are passed through
|
|
36
|
+
// as errors in the debounced function.
|
|
37
|
+
//
|
|
38
|
+
// If "onSuccess" is provided then all invocations of the debounced function resolve with "null".
|
|
39
|
+
// If a rejection due to cancelation is detected ("e.name === 'debounce.canceled'") then
|
|
40
|
+
// it will be "muted" internally. Howeever, if any other type of error occurs, it will be passed through,
|
|
41
|
+
// resulting in a rejection of the debounced function.
|
|
42
|
+
|
|
43
|
+
function debounceAsync(fn, delay, options = {}) {
|
|
44
|
+
const canceledRejection = new Error('debounce:canceled');
|
|
45
|
+
canceledRejection.name = 'debounce.canceled';
|
|
46
|
+
let timer;
|
|
47
|
+
let canceled = false;
|
|
48
|
+
let previousDone = true;
|
|
49
|
+
let skipNextDelay = false;
|
|
50
|
+
|
|
51
|
+
const setTimer = (res, rej, args, delay) => {
|
|
52
|
+
function body() {
|
|
53
|
+
if (canceled) {
|
|
54
|
+
return rej(canceledRejection);
|
|
55
|
+
}
|
|
56
|
+
if (!previousDone) {
|
|
57
|
+
clearTimeout(timer);
|
|
58
|
+
// At least 100ms delay to let current invocation finish
|
|
59
|
+
timer = setTimer(res, rej, args, delay || 100);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
previousDone = false;
|
|
64
|
+
const returned = fn.apply(this, args);
|
|
65
|
+
if (returned instanceof Promise) {
|
|
66
|
+
return returned
|
|
67
|
+
.then((result) => {
|
|
68
|
+
if (canceled) {
|
|
69
|
+
return rej(canceledRejection);
|
|
70
|
+
}
|
|
71
|
+
res(result);
|
|
72
|
+
})
|
|
73
|
+
.catch(rej)
|
|
74
|
+
.finally(() => {
|
|
75
|
+
previousDone = true;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
previousDone = true;
|
|
80
|
+
res(returned);
|
|
81
|
+
}
|
|
82
|
+
return setTimeout(body, delay);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const wrapper = async (...args) => {
|
|
86
|
+
const promise = new Promise((resolve, reject) => {
|
|
87
|
+
if (canceled) {
|
|
88
|
+
return reject(canceledRejection);
|
|
89
|
+
}
|
|
90
|
+
if (skipNextDelay) {
|
|
91
|
+
skipNextDelay = false;
|
|
92
|
+
timer = setTimer(resolve, reject, args, 0);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
clearTimeout(timer);
|
|
96
|
+
timer = setTimer(resolve, reject, args, delay);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const result = await promise;
|
|
101
|
+
if (options.onSuccess) {
|
|
102
|
+
await options.onSuccess(result);
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
return promise;
|
|
106
|
+
} catch (e) {
|
|
107
|
+
if (e.name !== 'debounce.canceled' || !options.onSuccess) {
|
|
108
|
+
throw e;
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
wrapper.cancel = () => {
|
|
115
|
+
canceled = true;
|
|
116
|
+
clearTimeout(timer);
|
|
117
|
+
timer = null;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
wrapper.skipDelay = (...args) => {
|
|
121
|
+
skipNextDelay = true;
|
|
122
|
+
return wrapper(...args);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
return wrapper;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function throttle(fn, delay) {
|
|
129
|
+
let inThrottle;
|
|
130
|
+
|
|
131
|
+
return (...args) => {
|
|
132
|
+
return new Promise((resolve) => {
|
|
133
|
+
if (!inThrottle) {
|
|
134
|
+
inThrottle = true;
|
|
135
|
+
setTimeout(() => {
|
|
136
|
+
inThrottle = false;
|
|
137
|
+
resolve(fn.apply(this, args));
|
|
138
|
+
}, delay);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -311,7 +311,8 @@ module.exports = {
|
|
|
311
311
|
//
|
|
312
312
|
// Returns a new, sanitized widget object.
|
|
313
313
|
|
|
314
|
-
async sanitize(req, input, options) {
|
|
314
|
+
async sanitize(req, input, options, { fetchRelationships = true } = {}) {
|
|
315
|
+
const convertOptions = { fetchRelationships };
|
|
315
316
|
if (!input || typeof input !== 'object') {
|
|
316
317
|
// Do not crash
|
|
317
318
|
input = {};
|
|
@@ -325,7 +326,7 @@ module.exports = {
|
|
|
325
326
|
output.aposPlaceholder = self.apos.launder.boolean(input.aposPlaceholder);
|
|
326
327
|
if (!output.aposPlaceholder) {
|
|
327
328
|
const schema = self.allowedSchema(req);
|
|
328
|
-
await self.apos.schema.convert(req, schema, input, output);
|
|
329
|
+
await self.apos.schema.convert(req, schema, input, output, convertOptions);
|
|
329
330
|
}
|
|
330
331
|
return output;
|
|
331
332
|
},
|
|
@@ -7,6 +7,10 @@ export default {
|
|
|
7
7
|
type: String,
|
|
8
8
|
areaFieldId: String,
|
|
9
9
|
modelValue: Object,
|
|
10
|
+
mode: {
|
|
11
|
+
type: String,
|
|
12
|
+
default: 'draft'
|
|
13
|
+
},
|
|
10
14
|
meta: {
|
|
11
15
|
type: Object,
|
|
12
16
|
default() {
|
|
@@ -59,7 +63,7 @@ export default {
|
|
|
59
63
|
this.rendered = this.rendering.html;
|
|
60
64
|
} else {
|
|
61
65
|
this.rendered = '...';
|
|
62
|
-
this.rendered = await apos.http.post(`${apos.area.action}/render-widget?aposEdit=1&aposMode
|
|
66
|
+
this.rendered = await apos.http.post(`${apos.area.action}/render-widget?aposEdit=1&aposMode=${this.mode}`, {
|
|
63
67
|
busy: true,
|
|
64
68
|
body: parameters
|
|
65
69
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apostrophe",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.7.0",
|
|
4
4
|
"description": "The Apostrophe Content Management System.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -69,7 +69,6 @@
|
|
|
69
69
|
"cors": "^2.8.5",
|
|
70
70
|
"css-loader": "^5.2.4",
|
|
71
71
|
"dayjs": "^1.9.8",
|
|
72
|
-
"debounce-async": "0.0.2",
|
|
73
72
|
"dompurify": "^2.3.1",
|
|
74
73
|
"express": "^4.16.4",
|
|
75
74
|
"express-bearer-token": "^2.4.0",
|
|
@@ -77,7 +76,7 @@
|
|
|
77
76
|
"express-session": "^1.17.1",
|
|
78
77
|
"form-data": "^4.0.0",
|
|
79
78
|
"fs-extra": "^7.0.1",
|
|
80
|
-
"glob": "^10.
|
|
79
|
+
"glob": "^10.0.0",
|
|
81
80
|
"he": "^1.2.0",
|
|
82
81
|
"html-to-text": "^9.0.5",
|
|
83
82
|
"i18next": "^20.3.2",
|
|
@@ -110,8 +109,8 @@
|
|
|
110
109
|
"resolve": "^1.19.0",
|
|
111
110
|
"resolve-from": "^5.0.0",
|
|
112
111
|
"sanitize-html": "^2.12.1",
|
|
113
|
-
"sass": "^1.
|
|
114
|
-
"sass-loader": "^
|
|
112
|
+
"sass": "^1.77.8",
|
|
113
|
+
"sass-loader": "^16.0.0",
|
|
115
114
|
"server-destroy": "^1.0.1",
|
|
116
115
|
"sluggo": "^1.0.0",
|
|
117
116
|
"sortablejs": "^1.15.0",
|
|
@@ -120,7 +119,7 @@
|
|
|
120
119
|
"tinycolor2": "^1.4.2",
|
|
121
120
|
"tough-cookie": "^4.0.0",
|
|
122
121
|
"underscore.string": "^3.3.4",
|
|
123
|
-
"uploadfs": "^1.22.
|
|
122
|
+
"uploadfs": "^1.22.6",
|
|
124
123
|
"vue": "^3.3.8",
|
|
125
124
|
"vue-advanced-cropper": "^2.8.8",
|
|
126
125
|
"vue-loader": "^17.1.0",
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const qs = require('qs');
|
|
5
|
+
|
|
6
|
+
const t = require('../test-lib/test.js');
|
|
7
|
+
const bigUpload = require('../lib/big-upload-client.js');
|
|
8
|
+
|
|
9
|
+
const buffer = fs.readFileSync(path.resolve(__dirname, 'data/upload_tests/crop_image.png'));
|
|
10
|
+
|
|
11
|
+
describe('Big Upload', function() {
|
|
12
|
+
|
|
13
|
+
let apos;
|
|
14
|
+
let received = false;
|
|
15
|
+
|
|
16
|
+
after(async function () {
|
|
17
|
+
return t.destroy(apos);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
this.timeout(t.timeout);
|
|
21
|
+
|
|
22
|
+
it('init apos', async function() {
|
|
23
|
+
apos = await t.create({
|
|
24
|
+
root: module,
|
|
25
|
+
modules: {
|
|
26
|
+
test: {
|
|
27
|
+
apiRoutes: (self) => ({
|
|
28
|
+
post: {
|
|
29
|
+
'big-upload-test': [
|
|
30
|
+
self.apos.http.bigUploadMiddleware(),
|
|
31
|
+
(req) => {
|
|
32
|
+
try {
|
|
33
|
+
received = true;
|
|
34
|
+
assert(req.files);
|
|
35
|
+
assert(req.files.file);
|
|
36
|
+
assert.strictEqual(req.files.file.name, 'crop_image.png');
|
|
37
|
+
const buffer2 = fs.readFileSync(req.files.file.path);
|
|
38
|
+
assert(buffer.equals(buffer2));
|
|
39
|
+
return {
|
|
40
|
+
ok: true
|
|
41
|
+
};
|
|
42
|
+
} finally {
|
|
43
|
+
fs.unlinkSync(req.files.file.path);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
assert(apos.http);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should be able to make a big upload request', async function() {
|
|
57
|
+
|
|
58
|
+
// Must have the same shape as a browser-side File object and be sliceable,
|
|
59
|
+
// returning a Blob
|
|
60
|
+
|
|
61
|
+
const file = {
|
|
62
|
+
size: buffer.byteLength,
|
|
63
|
+
name: 'crop_image.png',
|
|
64
|
+
slice(from, to) {
|
|
65
|
+
return new Blob([ buffer.subarray(from, to) ]);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Emulate the browser-side apos.http object just barely well enough to test
|
|
70
|
+
// big-upload-client server-side.
|
|
71
|
+
//
|
|
72
|
+
// self.apos.http won't work for this task because it is based on node-fetch 2
|
|
73
|
+
// which doesn't have a 100% browser-compatible API for blobs
|
|
74
|
+
|
|
75
|
+
const http = {
|
|
76
|
+
async post(url, options) {
|
|
77
|
+
if (options.qs) {
|
|
78
|
+
url += '?' + qs.stringify(options.qs);
|
|
79
|
+
}
|
|
80
|
+
const isFormData = options.body instanceof FormData;
|
|
81
|
+
const body = isFormData ? options.body : JSON.stringify(options.body);
|
|
82
|
+
const headers = {};
|
|
83
|
+
headers.cookie = `${apos.csrfCookieName}=csrf`;
|
|
84
|
+
if (!isFormData) {
|
|
85
|
+
headers['content-type'] = 'application/json';
|
|
86
|
+
}
|
|
87
|
+
const base = apos.http.getBase();
|
|
88
|
+
const result = await fetch(`${base}${url}`, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
headers,
|
|
91
|
+
body
|
|
92
|
+
});
|
|
93
|
+
if (result.status >= 400) {
|
|
94
|
+
throw new Error(result.status);
|
|
95
|
+
}
|
|
96
|
+
return result.json();
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const result = await bigUpload('/api/v1/test/big-upload-test', {
|
|
101
|
+
files: {
|
|
102
|
+
file
|
|
103
|
+
},
|
|
104
|
+
http,
|
|
105
|
+
chunkSize: 1024
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
assert(received);
|
|
109
|
+
assert.strictEqual(result.ok, true);
|
|
110
|
+
});
|
|
111
|
+
});
|
package/test/change-doc-ids.js
CHANGED
|
@@ -104,6 +104,61 @@ describe('change-doc-ids', function() {
|
|
|
104
104
|
await sanityCheck(newPageId, newCategoryId);
|
|
105
105
|
});
|
|
106
106
|
|
|
107
|
+
it('changeDocIds should skipReplace', async function() {
|
|
108
|
+
|
|
109
|
+
await sanityCheck();
|
|
110
|
+
const req = apos.task.getReq();
|
|
111
|
+
const pages = await apos.page.find(req, {}).toArray();
|
|
112
|
+
const categories = await apos.category.find(req, {}).toArray();
|
|
113
|
+
const category = categories.find(category => category._id.startsWith('new-test-category-id'));
|
|
114
|
+
const articles = await await apos.article.find(req, {}).toArray();
|
|
115
|
+
const test = pages.find(page => page.slug === '/test');
|
|
116
|
+
const existingPageId = `new-test-page-id:${test.aposLocale}`;
|
|
117
|
+
const oldPageId = `old-test-page-id:${test.aposLocale}`;
|
|
118
|
+
const existingCategoryId = `new-test-category-id:${category.aposLocale}`;
|
|
119
|
+
const oldCategoryId = `old-test-category-id:${category.aposLocale}`;
|
|
120
|
+
|
|
121
|
+
// Update articles to include the OLD category
|
|
122
|
+
for (const article of articles) {
|
|
123
|
+
if (article.categoriesIds.includes('new-test-category-id')) {
|
|
124
|
+
const newIds = article.categoriesIds
|
|
125
|
+
.filter(id => id !== 'new-test-category-id')
|
|
126
|
+
.concat('old-test-category-id');
|
|
127
|
+
await apos.doc.db.updateMany({
|
|
128
|
+
aposDocId: article.aposDocId
|
|
129
|
+
}, {
|
|
130
|
+
$set: {
|
|
131
|
+
categoriesIds: newIds
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Sanity check - existingCategoryId should not be present in articles
|
|
137
|
+
const articlesBefore = await apos.article.find(req, {}).toArray();
|
|
138
|
+
assert.strictEqual(
|
|
139
|
+
articlesBefore.every(article => !article.categoriesIds.includes('new-test-category-id')),
|
|
140
|
+
true,
|
|
141
|
+
'new-test-category-id should not be present in any articles'
|
|
142
|
+
);
|
|
143
|
+
assert.strictEqual(test._id, existingPageId);
|
|
144
|
+
assert.strictEqual(category._id, existingCategoryId);
|
|
145
|
+
const pairs = [
|
|
146
|
+
[ oldPageId, existingPageId ],
|
|
147
|
+
[ oldCategoryId, existingCategoryId ]
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
await apos.doc.changeDocIds(pairs, { skipReplace: true });
|
|
151
|
+
|
|
152
|
+
const articlesAfter = await apos.article.find(req, {}).toArray();
|
|
153
|
+
assert.strictEqual(
|
|
154
|
+
articlesAfter.every(article => !article.categoriesIds.includes('old-test-category-id')),
|
|
155
|
+
true,
|
|
156
|
+
'old-test-category-id should not be present in any articles'
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
await sanityCheck(existingPageId, existingCategoryId);
|
|
160
|
+
});
|
|
161
|
+
|
|
107
162
|
async function sanityCheck(newPageId, newCategoryId) {
|
|
108
163
|
const pages = await apos.page.find(apos.task.getReq(), {}).children(true).toArray();
|
|
109
164
|
const test = pages.find(page => page.slug === '/test');
|
|
@@ -125,7 +180,11 @@ describe('change-doc-ids', function() {
|
|
|
125
180
|
assert(articles[0]._categories.find(category => category.slug === 'category-0'));
|
|
126
181
|
assert(articles[0]._categories.find(category => category.slug === 'category-1'));
|
|
127
182
|
if (newCategoryId) {
|
|
128
|
-
assert.strictEqual(
|
|
183
|
+
assert.strictEqual(
|
|
184
|
+
articles[0]._categories.some(obj => obj._id === newCategoryId),
|
|
185
|
+
true,
|
|
186
|
+
'newCategoryId not found'
|
|
187
|
+
);
|
|
129
188
|
const newCategory = articles[0]._categories.find(category => category._id === newCategoryId);
|
|
130
189
|
assert(newCategory);
|
|
131
190
|
assert.strictEqual(newCategory._id, newCategoryId);
|