apostrophe 3.22.1 → 3.24.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 +17 -17
- package/CHANGELOG.md +27 -0
- package/index.js +1 -1
- package/lib/locales.js +43 -0
- package/lib/moog-require.js +1 -1
- package/modules/@apostrophecms/doc-type/index.js +101 -10
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +11 -7
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +8 -109
- package/modules/@apostrophecms/i18n/i18n/en.json +7 -1
- package/modules/@apostrophecms/i18n/i18n/es.json +7 -1
- package/modules/@apostrophecms/i18n/i18n/fr.json +7 -1
- package/modules/@apostrophecms/i18n/i18n/pt-BR.json +7 -1
- package/modules/@apostrophecms/i18n/i18n/sk.json +7 -1
- package/modules/@apostrophecms/i18n/index.js +16 -30
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +3 -1
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerEditor.vue +37 -2
- package/modules/@apostrophecms/login/index.js +10 -0
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalShareDraft.vue +323 -0
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocErrorsMixin.js +79 -0
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +2 -1
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposModalTabsMixin.js +58 -0
- package/modules/@apostrophecms/module/index.js +11 -0
- package/modules/@apostrophecms/page/index.js +39 -5
- package/modules/@apostrophecms/permission/index.js +26 -1
- package/modules/@apostrophecms/piece-type/index.js +22 -0
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposRelationshipEditor.vue +53 -10
- package/modules/@apostrophecms/polymorphic-type/index.js +4 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +13 -3
- package/modules/@apostrophecms/soft-redirect/index.js +10 -1
- package/modules/@apostrophecms/task/index.js +3 -1
- package/modules/@apostrophecms/template/index.js +0 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposSlat.vue +3 -3
- package/package.json +2 -2
- package/test/caches.js +20 -0
- package/test/docs.js +4 -4
- package/test/modules/test-page/views/page.html +10 -5
- package/test/pages.js +147 -0
- package/test/pieces.js +151 -0
- package/test/static-i18n.js +14 -0
- package/test-lib/util.js +3 -2
|
@@ -11,6 +11,7 @@ const _ = require('lodash');
|
|
|
11
11
|
const { stripIndent } = require('common-tags');
|
|
12
12
|
const ExpressSessionCookie = require('express-session/session/cookie');
|
|
13
13
|
const path = require('path');
|
|
14
|
+
const { verifyLocales } = require('../../../lib/locales');
|
|
14
15
|
|
|
15
16
|
const apostropheI18nDebugPlugin = {
|
|
16
17
|
type: 'postProcessor',
|
|
@@ -218,7 +219,8 @@ module.exports = {
|
|
|
218
219
|
} else {
|
|
219
220
|
locale = self.matchLocale(req);
|
|
220
221
|
}
|
|
221
|
-
const
|
|
222
|
+
const locales = self.filterPrivateLocales(req, self.locales);
|
|
223
|
+
const localeOptions = locales[locale];
|
|
222
224
|
if (localeOptions.prefix) {
|
|
223
225
|
// Remove locale prefix so URL parsing can proceed normally from here
|
|
224
226
|
if (req.path === localeOptions.prefix) {
|
|
@@ -464,8 +466,9 @@ module.exports = {
|
|
|
464
466
|
// possible the default locale is returned.
|
|
465
467
|
matchLocale(req) {
|
|
466
468
|
const hostname = req.hostname;
|
|
469
|
+
const locales = self.filterPrivateLocales(req, self.locales);
|
|
467
470
|
let best = false;
|
|
468
|
-
for (const [ name, options ] of Object.entries(
|
|
471
|
+
for (const [ name, options ] of Object.entries(locales)) {
|
|
469
472
|
const matchedHostname = options.hostname
|
|
470
473
|
? (hostname === options.hostname.split(':')[0]) : null;
|
|
471
474
|
const matchedPrefix = options.prefix
|
|
@@ -568,34 +571,7 @@ module.exports = {
|
|
|
568
571
|
label: 'English'
|
|
569
572
|
}
|
|
570
573
|
};
|
|
571
|
-
|
|
572
|
-
let hostnamesCount = 0;
|
|
573
|
-
for (const [ name, options ] of Object.entries(locales)) {
|
|
574
|
-
const key = (options.hostname || '__none') + ':' + (options.prefix || '__none');
|
|
575
|
-
hostnamesCount += (options.hostname ? 1 : 0);
|
|
576
|
-
if (taken[key]) {
|
|
577
|
-
throw new Error(stripIndent`
|
|
578
|
-
@apostrophecms/i18n: the locale ${name} cannot be distinguished from
|
|
579
|
-
earlier locales. Make sure it is uniquely distinguished by its hostname
|
|
580
|
-
option, prefix option or a combination of the two. One locale per site
|
|
581
|
-
may be a default with neither hostname nor prefix, and one locale per
|
|
582
|
-
hostname may be a default for that hostname without a prefix.
|
|
583
|
-
`);
|
|
584
|
-
}
|
|
585
|
-
taken[key] = true;
|
|
586
|
-
}
|
|
587
|
-
if ((hostnamesCount > 0) && (hostnamesCount < Object.keys(locales).length) && (!self.apos.options.baseUrl)) {
|
|
588
|
-
throw new Error(stripIndent`
|
|
589
|
-
If some of your locales have hostnames, then they all must have
|
|
590
|
-
hostnames, or your top-level baseUrl option must be set.
|
|
591
|
-
|
|
592
|
-
In development, you can set baseUrl to http://localhost:3000
|
|
593
|
-
for testing purposes. In production it should always be set
|
|
594
|
-
to a real base URL for the site.
|
|
595
|
-
`);
|
|
596
|
-
}
|
|
597
|
-
// Make sure they are adequately distinguished by
|
|
598
|
-
// hostname and prefix
|
|
574
|
+
verifyLocales(locales, self.apos.options.baseUrl);
|
|
599
575
|
return locales;
|
|
600
576
|
},
|
|
601
577
|
sanitizeLocaleName(locale) {
|
|
@@ -656,6 +632,16 @@ module.exports = {
|
|
|
656
632
|
}
|
|
657
633
|
return res.redirect(corresponding._url);
|
|
658
634
|
};
|
|
635
|
+
},
|
|
636
|
+
// Exclude private locales when logged out
|
|
637
|
+
filterPrivateLocales(req, locales) {
|
|
638
|
+
return req.user
|
|
639
|
+
? locales
|
|
640
|
+
: Object.fromEntries(
|
|
641
|
+
Object
|
|
642
|
+
.entries(locales)
|
|
643
|
+
.filter(([ name, options ]) => options.private !== true)
|
|
644
|
+
);
|
|
659
645
|
}
|
|
660
646
|
};
|
|
661
647
|
}
|
|
@@ -90,7 +90,9 @@
|
|
|
90
90
|
>
|
|
91
91
|
<AposMediaManagerEditor
|
|
92
92
|
v-show="editing"
|
|
93
|
-
:media="editing"
|
|
93
|
+
:media="editing"
|
|
94
|
+
:selected="selected"
|
|
95
|
+
:is-modified="isModified"
|
|
94
96
|
:module-labels="moduleLabels"
|
|
95
97
|
@back="updateEditing(null)" @saved="updateMedia"
|
|
96
98
|
@modified="editorModified"
|
|
@@ -100,6 +100,7 @@
|
|
|
100
100
|
<script>
|
|
101
101
|
import AposEditorMixin from 'Modules/@apostrophecms/modal/mixins/AposEditorMixin';
|
|
102
102
|
import AposAdvisoryLockMixin from 'Modules/@apostrophecms/ui/mixins/AposAdvisoryLockMixin';
|
|
103
|
+
import AposModifiedMixin from 'Modules/@apostrophecms/ui/mixins/AposModifiedMixin';
|
|
103
104
|
import { detectDocChange } from 'Modules/@apostrophecms/schema/lib/detectChange';
|
|
104
105
|
import { klona } from 'klona';
|
|
105
106
|
import dayjs from 'dayjs';
|
|
@@ -110,7 +111,7 @@ import cuid from 'cuid';
|
|
|
110
111
|
dayjs.extend(advancedFormat);
|
|
111
112
|
|
|
112
113
|
export default {
|
|
113
|
-
mixins: [ AposEditorMixin, AposAdvisoryLockMixin ],
|
|
114
|
+
mixins: [ AposEditorMixin, AposAdvisoryLockMixin, AposModifiedMixin ],
|
|
114
115
|
props: {
|
|
115
116
|
media: {
|
|
116
117
|
type: Object,
|
|
@@ -124,6 +125,10 @@ export default {
|
|
|
124
125
|
return [];
|
|
125
126
|
}
|
|
126
127
|
},
|
|
128
|
+
isModified: {
|
|
129
|
+
type: Boolean,
|
|
130
|
+
default: false
|
|
131
|
+
},
|
|
127
132
|
moduleLabels: {
|
|
128
133
|
type: Object,
|
|
129
134
|
default() {
|
|
@@ -152,11 +157,20 @@ export default {
|
|
|
152
157
|
moduleOptions() {
|
|
153
158
|
return window.apos.modules[this.activeMedia.type] || {};
|
|
154
159
|
},
|
|
160
|
+
canLocalize() {
|
|
161
|
+
return (Object.keys(apos.i18n.locales).length > 1) && this.moduleOptions.localized && this.activeMedia._id;
|
|
162
|
+
},
|
|
155
163
|
moreMenu() {
|
|
156
164
|
const menu = [ {
|
|
157
165
|
label: 'apostrophe:discardChanges',
|
|
158
166
|
action: 'cancel'
|
|
159
167
|
} ];
|
|
168
|
+
if (this.canLocalize) {
|
|
169
|
+
menu.push({
|
|
170
|
+
label: 'apostrophe:localize',
|
|
171
|
+
action: 'localize'
|
|
172
|
+
});
|
|
173
|
+
}
|
|
160
174
|
if (this.activeMedia._id && !this.restoreOnly) {
|
|
161
175
|
menu.push({
|
|
162
176
|
label: 'apostrophe:archiveImage',
|
|
@@ -330,7 +344,7 @@ export default {
|
|
|
330
344
|
this.$emit('back');
|
|
331
345
|
},
|
|
332
346
|
lockNotAvailable() {
|
|
333
|
-
this
|
|
347
|
+
this.$emit('modified', false);
|
|
334
348
|
this.cancel();
|
|
335
349
|
},
|
|
336
350
|
updateActiveAttachment(attachment) {
|
|
@@ -338,6 +352,27 @@ export default {
|
|
|
338
352
|
},
|
|
339
353
|
viewMedia () {
|
|
340
354
|
window.open(this.activeMedia.attachment._urls.original, '_blank');
|
|
355
|
+
},
|
|
356
|
+
async localize(media) {
|
|
357
|
+
// If there are changes warn the user before discarding them before
|
|
358
|
+
// the localize operation
|
|
359
|
+
if (this.isModified) {
|
|
360
|
+
if (!await this.confirmAndCancel()) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
await this.cancel();
|
|
365
|
+
this.updateActiveDoc(this.activeMedia);
|
|
366
|
+
}
|
|
367
|
+
apos.bus.$emit('admin-menu-click', {
|
|
368
|
+
itemName: '@apostrophecms/i18n:localize',
|
|
369
|
+
props: {
|
|
370
|
+
doc: this.activeMedia
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
},
|
|
374
|
+
async close() {
|
|
375
|
+
await this.cancel();
|
|
341
376
|
}
|
|
342
377
|
}
|
|
343
378
|
};
|
|
@@ -806,6 +806,16 @@ module.exports = {
|
|
|
806
806
|
return (req, res, next) => req.user ? next() : passportSession(req, res, next);
|
|
807
807
|
})()
|
|
808
808
|
},
|
|
809
|
+
removeUserForDraftSharing: {
|
|
810
|
+
before: '@apostrophecms/i18n',
|
|
811
|
+
middleware(req, res, next) {
|
|
812
|
+
// Remove user to hide the admin UI, in order to simulate a logged-out page view
|
|
813
|
+
if (self.isShareDraftRequest(req)) {
|
|
814
|
+
delete req.user;
|
|
815
|
+
}
|
|
816
|
+
return next();
|
|
817
|
+
}
|
|
818
|
+
},
|
|
809
819
|
honorLoginInvalidBefore: {
|
|
810
820
|
before: '@apostrophecms/i18n',
|
|
811
821
|
middleware(req, res, next) {
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<AposModal
|
|
3
|
+
:modal="modal"
|
|
4
|
+
class="apos-share-draft"
|
|
5
|
+
data-apos-test="share-draft-modal"
|
|
6
|
+
v-on="{ esc: close }"
|
|
7
|
+
@no-modal="$emit('safe-close')"
|
|
8
|
+
@inactive="modal.active = false"
|
|
9
|
+
@show-modal="modal.showModal = true"
|
|
10
|
+
>
|
|
11
|
+
<template #main>
|
|
12
|
+
<AposModalBody>
|
|
13
|
+
<template #bodyMain>
|
|
14
|
+
<div class="apos-share-draft__header">
|
|
15
|
+
<h2 class="apos-share-draft__heading">
|
|
16
|
+
{{ $t('apostrophe:shareDraftHeader') }}
|
|
17
|
+
</h2>
|
|
18
|
+
<Close
|
|
19
|
+
class="apos-share-draft__close"
|
|
20
|
+
:title="$t('apostrophe:close')"
|
|
21
|
+
:size="18"
|
|
22
|
+
@click.prevent="close"
|
|
23
|
+
/>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="apos-share-draft__content">
|
|
26
|
+
<div class="apos-share-draft__toggle-wrapper">
|
|
27
|
+
<AposToggle
|
|
28
|
+
v-model="disabled"
|
|
29
|
+
class="apos-share-draft__toggle"
|
|
30
|
+
@toggle="toggle"
|
|
31
|
+
/>
|
|
32
|
+
<p class="apos-share-draft__toggle-label">
|
|
33
|
+
{{ $t('apostrophe:shareDraftEnable') }}
|
|
34
|
+
</p>
|
|
35
|
+
</div>
|
|
36
|
+
<p class="apos-share-draft__description">
|
|
37
|
+
{{ $t('apostrophe:shareDraftDescription') }}
|
|
38
|
+
</p>
|
|
39
|
+
<transition
|
|
40
|
+
name="collapse"
|
|
41
|
+
:duration="200"
|
|
42
|
+
>
|
|
43
|
+
<div
|
|
44
|
+
class="apos-share-draft__url-block"
|
|
45
|
+
v-show="!disabled"
|
|
46
|
+
>
|
|
47
|
+
<input
|
|
48
|
+
v-model="shareUrl"
|
|
49
|
+
type="text"
|
|
50
|
+
disabled
|
|
51
|
+
class="apos-share-draft__url"
|
|
52
|
+
>
|
|
53
|
+
<a
|
|
54
|
+
href=""
|
|
55
|
+
class="apos-share-draft__link-copy"
|
|
56
|
+
@click.prevent="copy"
|
|
57
|
+
>
|
|
58
|
+
<LinkVariant
|
|
59
|
+
class="apos-share-draft__link-icon"
|
|
60
|
+
:title="$t('apostrophe:shareDraftCopyLink')"
|
|
61
|
+
:size="16"
|
|
62
|
+
/>
|
|
63
|
+
{{ $t('apostrophe:shareDraftCopyLink') }}
|
|
64
|
+
</a>
|
|
65
|
+
</div>
|
|
66
|
+
</transition>
|
|
67
|
+
</div>
|
|
68
|
+
</template>
|
|
69
|
+
</AposModalBody>
|
|
70
|
+
</template>
|
|
71
|
+
</AposModal>
|
|
72
|
+
</template>
|
|
73
|
+
|
|
74
|
+
<script>
|
|
75
|
+
import Close from 'vue-material-design-icons/Close.vue';
|
|
76
|
+
import LinkVariant from 'vue-material-design-icons/LinkVariant.vue';
|
|
77
|
+
|
|
78
|
+
export default {
|
|
79
|
+
components: {
|
|
80
|
+
Close,
|
|
81
|
+
LinkVariant
|
|
82
|
+
},
|
|
83
|
+
props: {
|
|
84
|
+
doc: {
|
|
85
|
+
type: Object,
|
|
86
|
+
required: true
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
emits: [ 'safe-close' ],
|
|
90
|
+
data() {
|
|
91
|
+
return {
|
|
92
|
+
modal: {
|
|
93
|
+
active: false,
|
|
94
|
+
type: 'overlay',
|
|
95
|
+
showModal: false,
|
|
96
|
+
disableHeader: true,
|
|
97
|
+
trapFocus: true
|
|
98
|
+
},
|
|
99
|
+
shareUrl: '',
|
|
100
|
+
disabled: true
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
async mounted() {
|
|
104
|
+
this.modal.active = true;
|
|
105
|
+
await this.checkUrlProp();
|
|
106
|
+
await this.getAposShareKey();
|
|
107
|
+
},
|
|
108
|
+
methods: {
|
|
109
|
+
async copy() {
|
|
110
|
+
await navigator.clipboard.writeText(this.shareUrl);
|
|
111
|
+
},
|
|
112
|
+
async toggle() {
|
|
113
|
+
this.disabled = !this.disabled;
|
|
114
|
+
|
|
115
|
+
await this.setShareUrl();
|
|
116
|
+
},
|
|
117
|
+
async setShareUrl() {
|
|
118
|
+
try {
|
|
119
|
+
const { aposShareKey } = await apos.http.post(
|
|
120
|
+
`${apos.modules[this.doc.type].action}/${this.doc._id}/share`, {
|
|
121
|
+
busy: true,
|
|
122
|
+
body: {
|
|
123
|
+
share: !this.disabled
|
|
124
|
+
},
|
|
125
|
+
draft: true
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
if (this.disabled) {
|
|
130
|
+
setTimeout(() => {
|
|
131
|
+
this.shareUrl = '';
|
|
132
|
+
}, 200);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!aposShareKey) {
|
|
137
|
+
return this.showError();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
this.shareUrl = this.generateShareUrl(aposShareKey);
|
|
141
|
+
} catch {
|
|
142
|
+
if (this.disabled) {
|
|
143
|
+
this.shareUrl = '';
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
await this.showError();
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
close() {
|
|
150
|
+
this.modal.showModal = false;
|
|
151
|
+
},
|
|
152
|
+
async checkUrlProp() {
|
|
153
|
+
if (!this.doc._url) {
|
|
154
|
+
await this.showError();
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
async getAposShareKey() {
|
|
158
|
+
try {
|
|
159
|
+
const { aposShareKey } = await apos.http.get(
|
|
160
|
+
`${apos.modules[this.doc.type].action}/${this.doc._id}`, {}
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
if (aposShareKey) {
|
|
164
|
+
this.disabled = false;
|
|
165
|
+
this.shareUrl = this.generateShareUrl(aposShareKey);
|
|
166
|
+
}
|
|
167
|
+
} catch {
|
|
168
|
+
await this.showError();
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
async showError() {
|
|
172
|
+
await apos.notify('apostrophe:shareDraftError', {
|
|
173
|
+
type: 'danger',
|
|
174
|
+
icon: 'alert-circle-icon',
|
|
175
|
+
dismiss: true
|
|
176
|
+
});
|
|
177
|
+
},
|
|
178
|
+
generateShareUrl(aposShareKey) {
|
|
179
|
+
const regex = /^https?:\/\//;
|
|
180
|
+
const docUrl = regex.test(this.doc._url)
|
|
181
|
+
? this.doc._url
|
|
182
|
+
: `${location.origin}${this.doc._url}`;
|
|
183
|
+
|
|
184
|
+
const url = new URL(docUrl);
|
|
185
|
+
|
|
186
|
+
const urlInfo = {
|
|
187
|
+
url: url.href
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
apos.bus.$emit('shared-draft-link', urlInfo);
|
|
191
|
+
|
|
192
|
+
return apos.http.addQueryToUrl(urlInfo.url, {
|
|
193
|
+
...(url.search ? apos.http.parseQuery(url.search) : {}),
|
|
194
|
+
aposShareKey,
|
|
195
|
+
aposShareId: this.doc._id
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
</script>
|
|
201
|
+
|
|
202
|
+
<style lang="scss" scoped>
|
|
203
|
+
.apos-share-draft {
|
|
204
|
+
z-index: $z-index-modal;
|
|
205
|
+
position: fixed;
|
|
206
|
+
top: 0;
|
|
207
|
+
right: 0;
|
|
208
|
+
bottom: 0;
|
|
209
|
+
left: 0;
|
|
210
|
+
display: flex;
|
|
211
|
+
align-items: center;
|
|
212
|
+
justify-content: center;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
::v-deep .apos-modal__inner {
|
|
216
|
+
top: auto;
|
|
217
|
+
right: auto;
|
|
218
|
+
bottom: auto;
|
|
219
|
+
left: auto;
|
|
220
|
+
max-width: 700px;
|
|
221
|
+
height: auto;
|
|
222
|
+
border-radius: 15px;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
::v-deep .apos-modal__overlay {
|
|
226
|
+
.apos-modal + .apos-share-draft & {
|
|
227
|
+
display: block;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
::v-deep .apos-modal__body {
|
|
232
|
+
padding: 20px;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
::v-deep .apos-modal__body-main {
|
|
236
|
+
display: flex;
|
|
237
|
+
flex-direction: column;
|
|
238
|
+
align-items: center;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.apos-share-draft__header {
|
|
242
|
+
display: flex;
|
|
243
|
+
justify-content: space-between;
|
|
244
|
+
width: 100%;
|
|
245
|
+
border-bottom: 1px solid var(--a-base-8);
|
|
246
|
+
padding: 0 20px 20px;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.apos-share-draft__heading {
|
|
250
|
+
@include type-title;
|
|
251
|
+
line-height: var(--a-line-tall);
|
|
252
|
+
margin: 0;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.apos-share-draft__close {
|
|
256
|
+
height: 18px;
|
|
257
|
+
align-self: center;
|
|
258
|
+
cursor: pointer;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.apos-share-draft__content {
|
|
262
|
+
margin-top: $spacing-double;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.apos-share-draft__toggle-wrapper {
|
|
266
|
+
display: flex;
|
|
267
|
+
align-items: center;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.apos-share-draft__toggle {
|
|
271
|
+
margin-right: 12px;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.apos-share-draft__toggle-label {
|
|
275
|
+
@include type-base;
|
|
276
|
+
max-width: 370px;
|
|
277
|
+
line-height: var(--a-line-tallest);
|
|
278
|
+
margin: 0;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.apos-share-draft__description {
|
|
282
|
+
@include type-small;
|
|
283
|
+
line-height: var(--a-line-tall);
|
|
284
|
+
max-width: 355px;
|
|
285
|
+
color: var(--a-base-2);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.apos-share-draft__url-block {
|
|
289
|
+
overflow: hidden;
|
|
290
|
+
height: 63px;
|
|
291
|
+
transition: height 200ms linear;
|
|
292
|
+
|
|
293
|
+
&.collapse-enter,
|
|
294
|
+
&.collapse-leave-to {
|
|
295
|
+
height: 0;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.apos-share-draft__url {
|
|
300
|
+
@include type-base;
|
|
301
|
+
width: 100%;
|
|
302
|
+
padding: 5px;
|
|
303
|
+
border-radius: 5px;
|
|
304
|
+
background-color: var(--a-base-9);
|
|
305
|
+
border: 1px solid var(--a-base-8);
|
|
306
|
+
box-sizing: border-box;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.apos-share-draft__link-icon {
|
|
310
|
+
height: 16px;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.apos-share-draft__link-copy {
|
|
314
|
+
@include type-base;
|
|
315
|
+
display: flex;
|
|
316
|
+
justify-content: flex-end;
|
|
317
|
+
align-items: center;
|
|
318
|
+
margin-top: $spacing-double;
|
|
319
|
+
text-decoration: none;
|
|
320
|
+
color: var(--a-primary);
|
|
321
|
+
font-weight: var(--a-weight-bold);;
|
|
322
|
+
}
|
|
323
|
+
</style>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import cuid from 'cuid';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
data: () => ({
|
|
5
|
+
fieldErrors: {},
|
|
6
|
+
errorCount: 0
|
|
7
|
+
|
|
8
|
+
}),
|
|
9
|
+
mounted () {
|
|
10
|
+
this.prepErrors();
|
|
11
|
+
},
|
|
12
|
+
computed: {
|
|
13
|
+
errorTooltip() {
|
|
14
|
+
return this.errorCount
|
|
15
|
+
? {
|
|
16
|
+
key: 'apostrophe:errorCount',
|
|
17
|
+
count: this.errorCount
|
|
18
|
+
} : null;
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
methods: {
|
|
22
|
+
updateFieldErrors(fieldState) {
|
|
23
|
+
this.tabKey = cuid();
|
|
24
|
+
for (const key in this.groups) {
|
|
25
|
+
this.groups[key].fields.forEach(field => {
|
|
26
|
+
if (fieldState[field]) {
|
|
27
|
+
this.fieldErrors[key][field] = fieldState[field].error;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
this.updateErrorCount();
|
|
32
|
+
},
|
|
33
|
+
updateErrorCount() {
|
|
34
|
+
let count = 0;
|
|
35
|
+
for (const key in this.fieldErrors) {
|
|
36
|
+
for (const errKey in this.fieldErrors[key]) {
|
|
37
|
+
if (this.fieldErrors[key][errKey]) {
|
|
38
|
+
count++;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
this.errorCount = count;
|
|
43
|
+
},
|
|
44
|
+
prepErrors() {
|
|
45
|
+
this.fieldErrors = Object.keys(this.groups).reduce((acc, name) => {
|
|
46
|
+
return {
|
|
47
|
+
...acc,
|
|
48
|
+
[name]: {}
|
|
49
|
+
};
|
|
50
|
+
}, {});
|
|
51
|
+
},
|
|
52
|
+
focusNextError() {
|
|
53
|
+
let field;
|
|
54
|
+
for (const key in this.fieldErrors) {
|
|
55
|
+
for (const errKey in this.fieldErrors[key]) {
|
|
56
|
+
if (this.fieldErrors[key][errKey] && !field) {
|
|
57
|
+
field = this.schema.filter(item => {
|
|
58
|
+
return item.name === errKey;
|
|
59
|
+
})[0];
|
|
60
|
+
|
|
61
|
+
if (field.group.name !== 'utility') {
|
|
62
|
+
this.switchPane(field.group.name);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
this.getAposSchema(field).scrollFieldIntoView(field.name);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
getAposSchema(field) {
|
|
72
|
+
if (field.group.name === 'utility') {
|
|
73
|
+
return this.$refs.utilitySchema;
|
|
74
|
+
} else {
|
|
75
|
+
return this.$refs[field.group.name][0];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
@@ -73,7 +73,8 @@ export default {
|
|
|
73
73
|
getFieldsByCategory(followedByCategory) {
|
|
74
74
|
if (followedByCategory) {
|
|
75
75
|
return (followedByCategory === 'other')
|
|
76
|
-
? this.schema.filter(field => !this.utilityFields.includes(field.name))
|
|
76
|
+
? this.schema.filter(field => !this.utilityFields.includes(field.name))
|
|
77
|
+
: this.schema.filter(field => this.utilityFields.includes(field.name));
|
|
77
78
|
} else {
|
|
78
79
|
return this.schema;
|
|
79
80
|
}
|
|
@@ -1,12 +1,70 @@
|
|
|
1
|
+
import cuid from 'cuid';
|
|
2
|
+
|
|
1
3
|
// Provide basic bridging functionality between tabs
|
|
2
4
|
// and the modal body.
|
|
3
5
|
|
|
4
6
|
export default {
|
|
5
7
|
data() {
|
|
6
8
|
return {
|
|
9
|
+
tabKey: cuid(),
|
|
7
10
|
currentTab: null
|
|
8
11
|
};
|
|
9
12
|
},
|
|
13
|
+
computed: {
|
|
14
|
+
groups() {
|
|
15
|
+
const groupSet = {};
|
|
16
|
+
|
|
17
|
+
this.schema.forEach(field => {
|
|
18
|
+
if (
|
|
19
|
+
this.filterOutParkedFields &&
|
|
20
|
+
!this.filterOutParkedFields([ field.name ]).length
|
|
21
|
+
) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (field.group && !groupSet[field.group.name]) {
|
|
25
|
+
groupSet[field.group.name] = {
|
|
26
|
+
label: field.group.label,
|
|
27
|
+
fields: [ field.name ],
|
|
28
|
+
schema: [ field ]
|
|
29
|
+
};
|
|
30
|
+
} else if (field.group) {
|
|
31
|
+
groupSet[field.group.name].fields.push(field.name);
|
|
32
|
+
groupSet[field.group.name].schema.push(field);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
if (!groupSet.utility) {
|
|
36
|
+
groupSet.utility = {
|
|
37
|
+
label: 'apostrophe:utility',
|
|
38
|
+
fields: [],
|
|
39
|
+
schema: []
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return groupSet;
|
|
44
|
+
},
|
|
45
|
+
tabs() {
|
|
46
|
+
const tabs = [];
|
|
47
|
+
for (const key in this.groups) {
|
|
48
|
+
if (key !== 'utility') {
|
|
49
|
+
tabs.push({
|
|
50
|
+
name: key,
|
|
51
|
+
label: this.groups[key].label
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return tabs;
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
watch: {
|
|
61
|
+
tabs() {
|
|
62
|
+
if ((!this.currentTab) || (!this.tabs.find(tab => tab.name === this.currentTab))) {
|
|
63
|
+
this.currentTab = this.tabs[0] && this.tabs[0].name;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
|
|
10
68
|
mounted() {
|
|
11
69
|
this.currentTab = this.tabs[0] ? this.tabs[0].name : null;
|
|
12
70
|
},
|
|
@@ -746,6 +746,17 @@ module.exports = {
|
|
|
746
746
|
}
|
|
747
747
|
},
|
|
748
748
|
|
|
749
|
+
isShareDraftRequest(req) {
|
|
750
|
+
const { aposShareId, aposShareKey } = req.query;
|
|
751
|
+
|
|
752
|
+
return (
|
|
753
|
+
typeof aposShareId === 'string' &&
|
|
754
|
+
aposShareId.length &&
|
|
755
|
+
typeof aposShareKey === 'string' &&
|
|
756
|
+
aposShareKey.length
|
|
757
|
+
);
|
|
758
|
+
},
|
|
759
|
+
|
|
749
760
|
// Merge in the event emitter / responder capabilities
|
|
750
761
|
...require('./lib/events.js')(self)
|
|
751
762
|
};
|