apostrophe 3.48.0 → 3.49.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/CHANGELOG.md +43 -2
- package/index.js +20 -2
- package/lib/locales.js +1 -1
- package/lib/moog-require.js +3 -0
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +12 -2
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +2 -0
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +7 -24
- package/modules/@apostrophecms/asset/index.js +27 -2
- package/modules/@apostrophecms/asset/lib/webpack/apos/webpack.config.js +23 -2
- package/modules/@apostrophecms/asset/lib/webpack/src/webpack.config.js +26 -2
- package/modules/@apostrophecms/doc/index.js +149 -0
- package/modules/@apostrophecms/doc-type/index.js +9 -1
- package/modules/@apostrophecms/global/index.js +4 -15
- package/modules/@apostrophecms/i18n/i18n/en.json +3 -2
- package/modules/@apostrophecms/i18n/index.js +76 -61
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerDisplay.vue +14 -1
- package/modules/@apostrophecms/login/ui/apos/components/AposForgotPasswordForm.vue +3 -60
- package/modules/@apostrophecms/login/ui/apos/components/AposLoginForm.vue +3 -231
- package/modules/@apostrophecms/login/ui/apos/components/AposResetPasswordForm.vue +3 -96
- package/modules/@apostrophecms/login/ui/apos/components/TheAposLogin.vue +2 -99
- package/modules/@apostrophecms/login/ui/apos/logic/AposForgotPasswordForm.js +68 -0
- package/modules/@apostrophecms/login/ui/apos/logic/AposLoginForm.js +239 -0
- package/modules/@apostrophecms/login/ui/apos/logic/AposResetPasswordForm.js +105 -0
- package/modules/@apostrophecms/login/ui/apos/logic/TheAposLogin.js +107 -0
- package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +9 -3
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalToolbar.vue +1 -0
- package/modules/@apostrophecms/page/index.js +63 -1
- package/modules/@apostrophecms/piece-type/index.js +57 -9
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposImageControlDialog.vue +11 -8
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +226 -72
- package/modules/@apostrophecms/schema/index.js +0 -1
- package/modules/@apostrophecms/schema/lib/addFieldTypes.js +35 -7
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSlug.vue +21 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +12 -7
- package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +1 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposCombo.vue +178 -20
- package/modules/@apostrophecms/ui/ui/apos/components/AposFilterMenu.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposPager.vue +4 -6
- package/modules/@apostrophecms/ui/ui/apos/scss/mixins/_theme_mixins.scss +1 -0
- package/modules/@apostrophecms/util/index.js +5 -6
- package/modules/@apostrophecms/util/ui/src/http.js +6 -3
- package/package.json +20 -3
- package/test/change-doc-ids.js +134 -0
- package/test/i18n.js +310 -0
- package/test/static-i18n.js +0 -105
|
@@ -100,6 +100,7 @@ module.exports = {
|
|
|
100
100
|
self.enableBrowserData();
|
|
101
101
|
self.addLegacyMigrations();
|
|
102
102
|
self.addMisreplicatedParkedPagesMigration();
|
|
103
|
+
self.addDuplicateParkedPagesMigration();
|
|
103
104
|
await self.createIndexes();
|
|
104
105
|
},
|
|
105
106
|
restApiRoutes(self) {
|
|
@@ -1959,7 +1960,10 @@ database.`);
|
|
|
1959
1960
|
delete _item._children;
|
|
1960
1961
|
if (!parent) {
|
|
1961
1962
|
// Parking the home page for the first time
|
|
1962
|
-
_item.aposDocId = self.apos.
|
|
1963
|
+
_item.aposDocId = await self.apos.doc.bestAposDocId({
|
|
1964
|
+
level: 0,
|
|
1965
|
+
slug: '/'
|
|
1966
|
+
});
|
|
1963
1967
|
_item.path = _item.aposDocId;
|
|
1964
1968
|
_item.lastPublishedAt = new Date();
|
|
1965
1969
|
return self.apos.doc.insert(req, _item);
|
|
@@ -2365,6 +2369,64 @@ database.`);
|
|
|
2365
2369
|
}
|
|
2366
2370
|
}
|
|
2367
2371
|
});
|
|
2372
|
+
},
|
|
2373
|
+
addDuplicateParkedPagesMigration() {
|
|
2374
|
+
self.apos.migration.add('duplicate-parked-pages', async () => {
|
|
2375
|
+
let parkedPages = await self.apos.doc.db.find({
|
|
2376
|
+
parkedId: {
|
|
2377
|
+
$ne: null
|
|
2378
|
+
}
|
|
2379
|
+
}).toArray();
|
|
2380
|
+
const parkedIds = [ ...new Set(parkedPages.map(page => page.parkedId)) ];
|
|
2381
|
+
const names = Object.keys(self.apos.i18n.locales);
|
|
2382
|
+
const locales = [
|
|
2383
|
+
...names.map(locale => `${locale}:draft`),
|
|
2384
|
+
...names.map(locale => `${locale}:published`),
|
|
2385
|
+
...names.map(locale => `${locale}:previous`)
|
|
2386
|
+
];
|
|
2387
|
+
let changes = 0;
|
|
2388
|
+
const winners = new Map();
|
|
2389
|
+
for (const locale of locales) {
|
|
2390
|
+
for (const parkedId of parkedIds) {
|
|
2391
|
+
let matches = parkedPages.filter(page =>
|
|
2392
|
+
(page.parkedId === parkedId) &&
|
|
2393
|
+
(page.aposLocale === locale)
|
|
2394
|
+
);
|
|
2395
|
+
if (matches.length > 0) {
|
|
2396
|
+
if (!winners.has(parkedId)) {
|
|
2397
|
+
winners.set(parkedId, matches[0].aposDocId);
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
if (matches.length > 1) {
|
|
2401
|
+
matches = matches.sort((a, b) => a.createdAt - b.createdAt);
|
|
2402
|
+
const ids = matches.slice(1).map(page => page._id);
|
|
2403
|
+
await self.apos.doc.db.removeMany({
|
|
2404
|
+
_id: {
|
|
2405
|
+
$in: ids
|
|
2406
|
+
}
|
|
2407
|
+
});
|
|
2408
|
+
parkedPages = parkedPages.filter(page => !ids.includes(page._id));
|
|
2409
|
+
changes++;
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
const idChanges = [];
|
|
2414
|
+
for (const parkedId of parkedIds) {
|
|
2415
|
+
const aposDocId = winners.get(parkedId);
|
|
2416
|
+
const matches = parkedPages.filter(page => page.parkedId === parkedId);
|
|
2417
|
+
for (const match of matches) {
|
|
2418
|
+
if (match.aposDocId !== aposDocId) {
|
|
2419
|
+
idChanges.push([ match._id, match._id.replace(match.aposDocId, aposDocId) ]);
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
if (idChanges.length) {
|
|
2424
|
+
// Also calls self.apos.attachment.recomputeAllDocReferences
|
|
2425
|
+
await self.apos.doc.changeDocIds(idChanges);
|
|
2426
|
+
} else if (changes > 0) {
|
|
2427
|
+
await self.apos.attachment.recomputeAllDocReferences();
|
|
2428
|
+
}
|
|
2429
|
+
});
|
|
2368
2430
|
}
|
|
2369
2431
|
};
|
|
2370
2432
|
},
|
|
@@ -42,15 +42,23 @@ module.exports = {
|
|
|
42
42
|
// visibility: 1
|
|
43
43
|
// }
|
|
44
44
|
},
|
|
45
|
-
fields
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
45
|
+
fields(self) {
|
|
46
|
+
return {
|
|
47
|
+
add: {
|
|
48
|
+
slug: {
|
|
49
|
+
type: 'slug',
|
|
50
|
+
label: 'apostrophe:slug',
|
|
51
|
+
following: [ 'title', 'archived' ],
|
|
52
|
+
required: true
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
remove: self.options.singletonAuto ? [
|
|
56
|
+
'title',
|
|
57
|
+
'slug',
|
|
58
|
+
'archived',
|
|
59
|
+
'visibility'
|
|
60
|
+
] : []
|
|
61
|
+
};
|
|
54
62
|
},
|
|
55
63
|
columns(self) {
|
|
56
64
|
return {
|
|
@@ -1099,6 +1107,31 @@ module.exports = {
|
|
|
1099
1107
|
});
|
|
1100
1108
|
|
|
1101
1109
|
return projection;
|
|
1110
|
+
},
|
|
1111
|
+
async insertIfMissing() {
|
|
1112
|
+
if (!self.options.singletonAuto) {
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
// Insert at startup
|
|
1116
|
+
const req = self.apos.task.getReq();
|
|
1117
|
+
const criteria = {
|
|
1118
|
+
type: self.name
|
|
1119
|
+
};
|
|
1120
|
+
if (self.options.localized) {
|
|
1121
|
+
criteria.aposLocale = {
|
|
1122
|
+
$in: Object.keys(self.apos.i18n.locales).map(locale => [ `${locale}:published`, `${locale}:draft` ]).flat()
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
const existing = await self.apos.doc.db.findOne(criteria, { _id: 1 });
|
|
1126
|
+
if (!existing) {
|
|
1127
|
+
const _new = {
|
|
1128
|
+
...self.newInstance(),
|
|
1129
|
+
aposDocId: await self.apos.doc.bestAposDocId({
|
|
1130
|
+
type: self.name
|
|
1131
|
+
})
|
|
1132
|
+
};
|
|
1133
|
+
await self.insert(req, _new);
|
|
1134
|
+
}
|
|
1102
1135
|
}
|
|
1103
1136
|
};
|
|
1104
1137
|
},
|
|
@@ -1133,6 +1166,21 @@ module.exports = {
|
|
|
1133
1166
|
},
|
|
1134
1167
|
find(_super, req, criteria, projection) {
|
|
1135
1168
|
return _super(req, criteria, projection).defaultSort(self.options.sort || { updatedAt: -1 });
|
|
1169
|
+
},
|
|
1170
|
+
newInstance(_super) {
|
|
1171
|
+
if (!self.options.singletonAuto) {
|
|
1172
|
+
return _super();
|
|
1173
|
+
}
|
|
1174
|
+
const slug = self.apos.util.slugify(self.options.singletonAuto?.slug || self.name);
|
|
1175
|
+
return {
|
|
1176
|
+
..._super(),
|
|
1177
|
+
// These fields are removed from the editable schema of singletons,
|
|
1178
|
+
// but we assign them directly for broader compatibility
|
|
1179
|
+
slug,
|
|
1180
|
+
title: slug,
|
|
1181
|
+
archived: false,
|
|
1182
|
+
visibility: 'public'
|
|
1183
|
+
};
|
|
1136
1184
|
}
|
|
1137
1185
|
};
|
|
1138
1186
|
},
|
package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposImageControlDialog.vue
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
3
|
v-if="active"
|
|
4
|
-
v-click-outside-element="
|
|
4
|
+
v-click-outside-element="cancel"
|
|
5
5
|
class="apos-popover apos-image-control__dialog"
|
|
6
6
|
x-placement="bottom"
|
|
7
7
|
:class="{
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
<footer class="apos-image-control__footer">
|
|
26
26
|
<AposButton
|
|
27
27
|
type="default" label="apostrophe:cancel"
|
|
28
|
-
@click="
|
|
28
|
+
@click="cancel"
|
|
29
29
|
:modifiers="formModifiers"
|
|
30
30
|
/>
|
|
31
31
|
<AposButton
|
|
@@ -54,7 +54,7 @@ export default {
|
|
|
54
54
|
required: true
|
|
55
55
|
}
|
|
56
56
|
},
|
|
57
|
-
emits: [ 'before-commands', '
|
|
57
|
+
emits: [ 'before-commands', 'done', 'cancel' ],
|
|
58
58
|
data() {
|
|
59
59
|
return {
|
|
60
60
|
generation: 1,
|
|
@@ -113,8 +113,11 @@ export default {
|
|
|
113
113
|
}
|
|
114
114
|
},
|
|
115
115
|
methods: {
|
|
116
|
-
|
|
117
|
-
this.$emit('
|
|
116
|
+
cancel() {
|
|
117
|
+
this.$emit('cancel');
|
|
118
|
+
},
|
|
119
|
+
done() {
|
|
120
|
+
this.$emit('done');
|
|
118
121
|
},
|
|
119
122
|
save() {
|
|
120
123
|
this.triggerValidation = true;
|
|
@@ -132,17 +135,17 @@ export default {
|
|
|
132
135
|
style: this.docFields.data.style,
|
|
133
136
|
alt: this.docFields.data.alt
|
|
134
137
|
});
|
|
135
|
-
this.
|
|
138
|
+
this.done();
|
|
136
139
|
});
|
|
137
140
|
},
|
|
138
141
|
keyboardHandler(e) {
|
|
139
142
|
if (e.keyCode === 27) {
|
|
140
|
-
this.
|
|
143
|
+
this.cancel();
|
|
141
144
|
}
|
|
142
145
|
if (e.keyCode === 13) {
|
|
143
146
|
if (this.docFields.data.href || e.metaKey) {
|
|
144
147
|
this.save();
|
|
145
|
-
this.
|
|
148
|
+
this.done();
|
|
146
149
|
}
|
|
147
150
|
e.preventDefault();
|
|
148
151
|
}
|
package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div>
|
|
2
|
+
<div :aria-controls="`insert-menu-${value._id}`" @keydown="handleUIKeydown">
|
|
3
3
|
<bubble-menu
|
|
4
4
|
class="bubble-menu"
|
|
5
5
|
:tippy-options="{ maxWidth: 'none', duration: 100, zIndex: 2000 }"
|
|
@@ -26,44 +26,64 @@
|
|
|
26
26
|
</AposContextMenuDialog>
|
|
27
27
|
</bubble-menu>
|
|
28
28
|
<floating-menu
|
|
29
|
-
class="apos-rich-text-insert-menu" :should-show="showFloatingMenu"
|
|
30
|
-
:editor="editor"
|
|
31
|
-
:tippy-options="{ duration: 100, zIndex: 2000 }"
|
|
32
29
|
v-if="editor"
|
|
30
|
+
class="apos-rich-text-insert-menu"
|
|
31
|
+
:tippy-options="{ duration: 100, zIndex: 2000, placement: 'bottom-start' }"
|
|
32
|
+
:should-show="showFloatingMenu"
|
|
33
|
+
:editor="editor"
|
|
34
|
+
role="listbox"
|
|
35
|
+
tabindex="0"
|
|
36
|
+
ref="insertMenu"
|
|
37
|
+
:id="`insert-menu-${value._id}`"
|
|
38
|
+
:key="insertMenuKey"
|
|
33
39
|
>
|
|
34
40
|
<div class="apos-rich-text-insert-menu-heading">
|
|
35
41
|
{{ $t('apostrophe:richTextInsertMenuHeading') }}
|
|
36
42
|
</div>
|
|
37
43
|
<div
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
class="apos-rich-text-insert-menu-wrapper"
|
|
45
|
+
@keydown.prevent.arrow-up="focusInsertMenuItem(true)"
|
|
46
|
+
@keydown.prevent.arrow-down="focusInsertMenuItem()"
|
|
47
|
+
@keydown="closeInsertMenu"
|
|
41
48
|
>
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
@click="activateInsertMenuItem(item, insertMenu[item])"
|
|
49
|
-
/>
|
|
50
|
-
<component
|
|
51
|
-
v-if="item === activeInsertMenuComponent?.name"
|
|
52
|
-
:is="activeInsertMenuComponent.component"
|
|
53
|
-
:active="true"
|
|
54
|
-
:editor="editor"
|
|
55
|
-
:options="editorOptions"
|
|
56
|
-
@before-commands="removeSlash"
|
|
57
|
-
@close="closeInsertMenuItem"
|
|
58
|
-
@click.stop="$event => null"
|
|
59
|
-
/>
|
|
60
|
-
</div>
|
|
61
|
-
<div
|
|
62
|
-
class="apos-rich-text-insert-menu-label"
|
|
49
|
+
<button
|
|
50
|
+
v-for="(item, index) in insert"
|
|
51
|
+
:key="`${item}-${index}`"
|
|
52
|
+
class="apos-rich-text-insert-menu-item"
|
|
53
|
+
role="option"
|
|
54
|
+
data-insert-menu-item
|
|
63
55
|
@click="activateInsertMenuItem(item, insertMenu[item])"
|
|
64
56
|
>
|
|
65
|
-
<
|
|
66
|
-
|
|
57
|
+
<div class="apos-rich-text-insert-menu-icon">
|
|
58
|
+
<AposIndicator
|
|
59
|
+
:icon="insertMenu[item].icon"
|
|
60
|
+
:icon-size="24"
|
|
61
|
+
class="apos-button__icon"
|
|
62
|
+
fill-color="currentColor"
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="apos-rich-text-insert-menu-label">
|
|
66
|
+
<h4>{{ $t(insertMenu[item].label) }}</h4>
|
|
67
|
+
<p>{{ $t(insertMenu[item].description) }}</p>
|
|
68
|
+
</div>
|
|
69
|
+
</button>
|
|
70
|
+
<div class="apos-rich-text-insert-menu-components">
|
|
71
|
+
<div
|
|
72
|
+
v-for="(item, index) in insert"
|
|
73
|
+
:key="`${item}-${index}-component`"
|
|
74
|
+
>
|
|
75
|
+
<component
|
|
76
|
+
v-if="item === activeInsertMenuComponent?.name"
|
|
77
|
+
:is="activeInsertMenuComponent.component"
|
|
78
|
+
:active="true"
|
|
79
|
+
:editor="editor"
|
|
80
|
+
:options="editorOptions"
|
|
81
|
+
@before-commands="removeSlash"
|
|
82
|
+
@cancel="cancelInsertMenuItem"
|
|
83
|
+
@done="closeInsertMenuItem"
|
|
84
|
+
@close="closeInsertMenuItem"
|
|
85
|
+
/>
|
|
86
|
+
</div>
|
|
67
87
|
</div>
|
|
68
88
|
</div>
|
|
69
89
|
</floating-menu>
|
|
@@ -86,7 +106,23 @@ import {
|
|
|
86
106
|
BubbleMenu,
|
|
87
107
|
FloatingMenu
|
|
88
108
|
} from '@tiptap/vue-2';
|
|
89
|
-
|
|
109
|
+
// Starter Kit extensions
|
|
110
|
+
import BlockQuote from '@tiptap/extension-blockquote';
|
|
111
|
+
import Bold from '@tiptap/extension-bold';
|
|
112
|
+
import BulletList from '@tiptap/extension-bullet-list';
|
|
113
|
+
import Code from '@tiptap/extension-code';
|
|
114
|
+
import CodeBlock from '@tiptap/extension-code-block';
|
|
115
|
+
import Dropcursor from '@tiptap/extension-dropcursor';
|
|
116
|
+
import Gapcursor from '@tiptap/extension-gapcursor';
|
|
117
|
+
import HardBreak from '@tiptap/extension-hard-break';
|
|
118
|
+
import History from '@tiptap/extension-history';
|
|
119
|
+
import HorizontalRule from '@tiptap/extension-horizontal-rule';
|
|
120
|
+
import Italic from '@tiptap/extension-italic';
|
|
121
|
+
import OrderedList from '@tiptap/extension-ordered-list';
|
|
122
|
+
import Paragraph from '@tiptap/extension-paragraph';
|
|
123
|
+
import Strike from '@tiptap/extension-strike';
|
|
124
|
+
import Text from '@tiptap/extension-text';
|
|
125
|
+
// End starter kit extensions
|
|
90
126
|
import TextAlign from '@tiptap/extension-text-align';
|
|
91
127
|
import Highlight from '@tiptap/extension-highlight';
|
|
92
128
|
import Underline from '@tiptap/extension-underline';
|
|
@@ -144,8 +180,11 @@ export default {
|
|
|
144
180
|
},
|
|
145
181
|
pending: null,
|
|
146
182
|
isFocused: null,
|
|
183
|
+
isShowingInsert: false,
|
|
147
184
|
showPlaceholder: null,
|
|
148
|
-
activeInsertMenuComponent: null
|
|
185
|
+
activeInsertMenuComponent: null,
|
|
186
|
+
suppressInsertMenu: false,
|
|
187
|
+
insertMenuKey: null
|
|
149
188
|
};
|
|
150
189
|
},
|
|
151
190
|
computed: {
|
|
@@ -246,17 +285,33 @@ export default {
|
|
|
246
285
|
this.emitWidgetUpdate();
|
|
247
286
|
}
|
|
248
287
|
}
|
|
288
|
+
},
|
|
289
|
+
isShowingInsert(newVal) {
|
|
290
|
+
if (newVal) {
|
|
291
|
+
this.focusInsertMenuItem(false, 0);
|
|
292
|
+
}
|
|
249
293
|
}
|
|
250
294
|
},
|
|
251
295
|
mounted() {
|
|
296
|
+
this.insertMenuKey = this.generateKey();
|
|
252
297
|
// Cleanly namespace it so we don't conflict with other uses and instances
|
|
253
298
|
const CustomPlaceholder = Placeholder.extend();
|
|
254
299
|
const extensions = [
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
300
|
+
BlockQuote,
|
|
301
|
+
Bold,
|
|
302
|
+
BulletList,
|
|
303
|
+
Code,
|
|
304
|
+
CodeBlock,
|
|
305
|
+
Dropcursor,
|
|
306
|
+
Gapcursor,
|
|
307
|
+
HardBreak,
|
|
308
|
+
History,
|
|
309
|
+
HorizontalRule,
|
|
310
|
+
Italic,
|
|
311
|
+
OrderedList,
|
|
312
|
+
Paragraph,
|
|
313
|
+
Strike,
|
|
314
|
+
Text,
|
|
260
315
|
TextAlign.configure({
|
|
261
316
|
types: [ 'heading', 'paragraph', 'defaultNode' ]
|
|
262
317
|
}),
|
|
@@ -273,7 +328,7 @@ export default {
|
|
|
273
328
|
const text = this.$t(this.placeholderText);
|
|
274
329
|
return text;
|
|
275
330
|
},
|
|
276
|
-
emptyNodeClass:
|
|
331
|
+
emptyNodeClass: 'apos-is-empty'
|
|
277
332
|
}),
|
|
278
333
|
FloatingMenu
|
|
279
334
|
]
|
|
@@ -316,6 +371,22 @@ export default {
|
|
|
316
371
|
apos.bus.$off('apos-refreshing', this.onAposRefreshing);
|
|
317
372
|
},
|
|
318
373
|
methods: {
|
|
374
|
+
generateKey() {
|
|
375
|
+
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
376
|
+
},
|
|
377
|
+
handleUIKeydown(e) {
|
|
378
|
+
if (e.key === 'Escape') {
|
|
379
|
+
this.doSuppressInsertMenu();
|
|
380
|
+
} else {
|
|
381
|
+
this.suppressInsertMenu = false;
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
doSuppressInsertMenu() {
|
|
385
|
+
this.suppressInsertMenu = true;
|
|
386
|
+
this.activeInsertMenuComponent = null;
|
|
387
|
+
this.insertMenuKey = this.generateKey();
|
|
388
|
+
this.editor.commands.focus();
|
|
389
|
+
},
|
|
319
390
|
onAposRefreshing(refreshOptions) {
|
|
320
391
|
if (this.activeInsertMenuComponent) {
|
|
321
392
|
refreshOptions.refresh = false;
|
|
@@ -335,6 +406,7 @@ export default {
|
|
|
335
406
|
// least once per second if the user is actively typing
|
|
336
407
|
return;
|
|
337
408
|
}
|
|
409
|
+
|
|
338
410
|
this.pending = setTimeout(() => {
|
|
339
411
|
this.emitWidgetUpdate();
|
|
340
412
|
}, 1000);
|
|
@@ -472,24 +544,34 @@ export default {
|
|
|
472
544
|
types: this.tiptapTypes
|
|
473
545
|
}));
|
|
474
546
|
},
|
|
475
|
-
showFloatingMenu({
|
|
476
|
-
|
|
547
|
+
showFloatingMenu({
|
|
548
|
+
state, oldState
|
|
549
|
+
}) {
|
|
550
|
+
const hasChanges = JSON.stringify(state?.doc.toJSON()) !== JSON.stringify(oldState?.doc.toJSON());
|
|
551
|
+
const { $to } = state.selection;
|
|
552
|
+
|
|
553
|
+
if (
|
|
554
|
+
!this.insertMenu ||
|
|
555
|
+
!this.insert.length ||
|
|
556
|
+
!hasChanges ||
|
|
557
|
+
($to.nodeAfter && $to.nodeAfter.text) ||
|
|
558
|
+
this.suppressInsertMenu
|
|
559
|
+
) {
|
|
560
|
+
this.isShowingInsert = false;
|
|
477
561
|
return false;
|
|
478
562
|
}
|
|
479
|
-
|
|
563
|
+
|
|
480
564
|
if (state.selection.empty) {
|
|
481
565
|
if ($to.nodeBefore && $to.nodeBefore.text) {
|
|
482
566
|
const text = $to.nodeBefore.text;
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
if (text === '/') {
|
|
567
|
+
if (text.slice(-1) === '/') {
|
|
568
|
+
this.isShowingInsert = true;
|
|
486
569
|
return true;
|
|
487
570
|
}
|
|
488
571
|
}
|
|
489
|
-
return false;
|
|
490
|
-
} else if (state.doc.textBetween($from, $to, ' ') === '/') {
|
|
491
|
-
return true;
|
|
492
572
|
}
|
|
573
|
+
|
|
574
|
+
this.isShowingInsert = false;
|
|
493
575
|
return false;
|
|
494
576
|
},
|
|
495
577
|
activateInsertMenuItem(name, info) {
|
|
@@ -523,6 +605,38 @@ export default {
|
|
|
523
605
|
closeInsertMenuItem() {
|
|
524
606
|
this.removeSlash();
|
|
525
607
|
this.activeInsertMenuComponent = null;
|
|
608
|
+
},
|
|
609
|
+
cancelInsertMenuItem() {
|
|
610
|
+
this.doSuppressInsertMenu();
|
|
611
|
+
},
|
|
612
|
+
closeInsertMenu(e) {
|
|
613
|
+
if (
|
|
614
|
+
[ 'ArrowUp', 'ArrowDown', 'Enter', ' ' ].includes(e.key) ||
|
|
615
|
+
this.activeInsertMenuComponent
|
|
616
|
+
) {
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
this.editor.commands.focus();
|
|
620
|
+
this.activeInsertMenuComponent = null;
|
|
621
|
+
// Only insert character keys
|
|
622
|
+
if (e.key.length === 1) {
|
|
623
|
+
this.editor.commands.insertContent(e.key);
|
|
624
|
+
}
|
|
625
|
+
},
|
|
626
|
+
focusInsertMenuItem(prev = false, index) {
|
|
627
|
+
if (this.activeInsertMenuComponent) {
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
const buttons = Array.from(this.$refs.insertMenu.$el.querySelectorAll('[data-insert-menu-item]'));
|
|
631
|
+
const currentIndex = buttons.findIndex(el => el === document.activeElement);
|
|
632
|
+
let targetIndex = prev ? currentIndex - 1 : currentIndex + 1;
|
|
633
|
+
if (targetIndex >= buttons.length) {
|
|
634
|
+
targetIndex = 0;
|
|
635
|
+
}
|
|
636
|
+
if (targetIndex < 0) {
|
|
637
|
+
targetIndex = buttons.length - 1;
|
|
638
|
+
}
|
|
639
|
+
buttons[index || targetIndex]?.focus();
|
|
526
640
|
}
|
|
527
641
|
}
|
|
528
642
|
};
|
|
@@ -569,27 +683,41 @@ function traverseNextNode(node) {
|
|
|
569
683
|
background-color: var(--a-base-9);
|
|
570
684
|
}
|
|
571
685
|
|
|
686
|
+
.apos-rich-text-editor__editor ::v-deep .ProseMirror {
|
|
687
|
+
@include apos-transition();
|
|
688
|
+
}
|
|
689
|
+
|
|
572
690
|
.apos-rich-text-editor__editor ::v-deep .ProseMirror:focus {
|
|
573
691
|
outline: none;
|
|
574
692
|
}
|
|
575
693
|
|
|
576
|
-
.apos-rich-text-editor__editor ::v-deep .ProseMirror
|
|
577
|
-
|
|
694
|
+
.apos-rich-text-editor__editor ::v-deep .ProseMirror {
|
|
695
|
+
padding: 10px 0;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
.apos-rich-text-editor__editor ::v-deep .ProseMirror:focus p.apos-is-empty::after {
|
|
699
|
+
display: block;
|
|
700
|
+
margin: 5px 0 10px;
|
|
701
|
+
color: var(--a-primary-transparent-50);
|
|
702
|
+
font-size: var(--a-type-smaller);
|
|
703
|
+
text-transform: uppercase;
|
|
704
|
+
letter-spacing: 0.5px;
|
|
705
|
+
font-weight: 600;
|
|
706
|
+
border-top: 1px solid var(--a-primary-transparent-50);
|
|
707
|
+
padding-top: 5px;
|
|
578
708
|
content: attr(data-placeholder);
|
|
579
|
-
float: left;
|
|
580
709
|
pointer-events: none;
|
|
581
|
-
height: 0;
|
|
582
|
-
color: var(--a-base-4);
|
|
583
710
|
}
|
|
584
711
|
|
|
585
712
|
.apos-rich-text-editor__editor {
|
|
586
713
|
@include apos-transition();
|
|
587
714
|
position: relative;
|
|
588
715
|
border-radius: var(--a-border-radius);
|
|
589
|
-
|
|
716
|
+
background-color: transparent;
|
|
590
717
|
}
|
|
591
718
|
.apos-rich-text-editor__editor.apos-is-visually-empty {
|
|
592
|
-
|
|
719
|
+
background-color: var(--a-primary-transparent-10);
|
|
720
|
+
min-height: 50px;
|
|
593
721
|
}
|
|
594
722
|
.apos-rich-text-editor__editor_after {
|
|
595
723
|
@include type-small;
|
|
@@ -602,9 +730,7 @@ function traverseNextNode(node) {
|
|
|
602
730
|
width: 200px;
|
|
603
731
|
height: 10px;
|
|
604
732
|
margin: auto;
|
|
605
|
-
|
|
606
|
-
margin-bottom: 7.5px;
|
|
607
|
-
color: var(--a-base-5);
|
|
733
|
+
color: var(--a-primary-transparent-50);
|
|
608
734
|
opacity: 0;
|
|
609
735
|
visibility: hidden;
|
|
610
736
|
pointer-events: none;
|
|
@@ -655,12 +781,9 @@ function traverseNextNode(node) {
|
|
|
655
781
|
}
|
|
656
782
|
|
|
657
783
|
.apos-rich-text-insert-menu {
|
|
658
|
-
display: flex;
|
|
659
|
-
flex-direction: column;
|
|
660
784
|
cursor: pointer;
|
|
661
785
|
user-select: none;
|
|
662
|
-
|
|
663
|
-
padding: 16px;
|
|
786
|
+
min-width: 350px;
|
|
664
787
|
border-radius: var(--a-border-radius);
|
|
665
788
|
box-shadow: var(--a-box-shadow);
|
|
666
789
|
background-color: var(--a-background-primary);
|
|
@@ -670,44 +793,75 @@ function traverseNextNode(node) {
|
|
|
670
793
|
font-size: var(--a-type-base);
|
|
671
794
|
}
|
|
672
795
|
|
|
796
|
+
.apos-rich-text-insert-menu-wrapper {
|
|
797
|
+
display: flex;
|
|
798
|
+
flex-direction: column;
|
|
799
|
+
}
|
|
800
|
+
|
|
673
801
|
.apos-rich-text-insert-menu-item {
|
|
802
|
+
all: unset;
|
|
674
803
|
display: flex;
|
|
675
804
|
flex-direction: row;
|
|
676
|
-
|
|
805
|
+
align-items: center;
|
|
806
|
+
gap: 12px;
|
|
807
|
+
padding: 14px 16px;
|
|
808
|
+
border-bottom: 1px solid var(--a-base-9);
|
|
809
|
+
@include apos-transition();
|
|
810
|
+
&:last-of-type {
|
|
811
|
+
border-bottom: none;
|
|
812
|
+
}
|
|
677
813
|
&:hover {
|
|
678
|
-
color: var(--a-
|
|
814
|
+
background-color: var(--a-primary-transparent-10);
|
|
815
|
+
}
|
|
816
|
+
&:active, &:focus {
|
|
817
|
+
background-color: var(--a-primary);
|
|
818
|
+
color: var(--a-white);
|
|
679
819
|
}
|
|
680
820
|
}
|
|
681
821
|
|
|
682
822
|
.apos-rich-text-insert-menu-label {
|
|
683
823
|
display: flex;
|
|
684
824
|
flex-direction: column;
|
|
825
|
+
gap: 5px;
|
|
685
826
|
h4, p {
|
|
686
|
-
margin:
|
|
827
|
+
margin: 0;
|
|
687
828
|
font-family: var(--a-family-default);
|
|
688
|
-
font-size: var(--a-type-base);
|
|
689
829
|
}
|
|
690
830
|
h4 {
|
|
691
|
-
font-weight:
|
|
831
|
+
font-weight: 500;
|
|
832
|
+
font-size: var(--a-type-large);
|
|
833
|
+
}
|
|
834
|
+
p {
|
|
835
|
+
font-size: var(--a-type-label);
|
|
692
836
|
}
|
|
693
837
|
}
|
|
694
838
|
.apos-rich-text-insert-menu-icon {
|
|
695
|
-
// Positions the popover meaningfully
|
|
696
839
|
position: relative;
|
|
840
|
+
display: flex;
|
|
841
|
+
width: 40px;
|
|
842
|
+
height: 40px;
|
|
843
|
+
align-items: center;
|
|
844
|
+
justify-content: center;
|
|
845
|
+
border: 1px solid var(--a-base-8);
|
|
846
|
+
color: var(--a-text-primary);
|
|
847
|
+
background-color: var(--a-white);
|
|
848
|
+
border-radius: var(--a-border-radius);
|
|
697
849
|
}
|
|
698
850
|
|
|
699
851
|
.apos-rich-text-insert-menu-heading {
|
|
700
|
-
|
|
852
|
+
padding: 12px 16px;
|
|
853
|
+
background-color: var(--a-base-9);
|
|
854
|
+
color: var(--a-base-2);
|
|
855
|
+
font-weight: 500;
|
|
856
|
+
border-bottom: 1px solid var(--a-base-7);
|
|
857
|
+
font-size: var(--a-type-label);
|
|
858
|
+
letter-spacing: 0.25px;
|
|
701
859
|
}
|
|
702
860
|
|
|
703
861
|
::v-deep .ProseMirror {
|
|
704
862
|
> * + * {
|
|
705
863
|
margin-top: 0.75em;
|
|
706
864
|
}
|
|
707
|
-
|
|
708
|
-
> :last-child {
|
|
709
|
-
margin-bottom: 1.75em;
|
|
710
|
-
}
|
|
711
865
|
}
|
|
712
866
|
|
|
713
867
|
::v-deep .ProseMirror-gapcursor {
|