apostrophe 4.27.1 → 4.28.1
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/.claude/settings.local.json +15 -0
- package/CHANGELOG.md +40 -0
- package/README.md +142 -0
- package/index.js +3 -0
- package/lib/stream-proxy.js +49 -0
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextTitle.vue +2 -11
- package/modules/@apostrophecms/area/ui/apos/apps/AposAreas.js +38 -6
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +12 -1
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +111 -41
- package/modules/@apostrophecms/area/ui/apos/components/AposBreadcrumbOperations.vue +1 -0
- package/modules/@apostrophecms/area/ui/apos/components/AposWidgetControls.vue +22 -10
- package/modules/@apostrophecms/area/ui/apos/logic/AposAreaEditor.js +40 -0
- package/modules/@apostrophecms/asset/index.js +3 -2
- package/modules/@apostrophecms/attachment/index.js +270 -0
- package/modules/@apostrophecms/doc/index.js +8 -2
- package/modules/@apostrophecms/doc-type/index.js +81 -1
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +18 -2
- package/modules/@apostrophecms/express/index.js +30 -1
- package/modules/@apostrophecms/file/index.js +70 -6
- package/modules/@apostrophecms/i18n/index.js +20 -1
- package/modules/@apostrophecms/image/index.js +11 -0
- package/modules/@apostrophecms/layout-widget/ui/apos/components/AposAreaLayoutEditor.vue +31 -6
- package/modules/@apostrophecms/layout-widget/ui/apos/components/AposGridLayout.vue +12 -10
- package/modules/@apostrophecms/login/index.js +43 -11
- package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +2 -1
- package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +5 -0
- package/modules/@apostrophecms/page/index.js +9 -11
- package/modules/@apostrophecms/page-type/index.js +6 -1
- package/modules/@apostrophecms/piece-page-type/index.js +100 -13
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposImageControlDialog.vue +1 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +28 -12
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapLink.vue +1 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposSearchList.vue +1 -1
- package/modules/@apostrophecms/styles/lib/apiRoutes.js +25 -5
- package/modules/@apostrophecms/styles/lib/handlers.js +19 -0
- package/modules/@apostrophecms/styles/lib/methods.js +35 -12
- package/modules/@apostrophecms/styles/ui/apos/components/TheAposStyles.vue +7 -2
- package/modules/@apostrophecms/task/index.js +9 -1
- package/modules/@apostrophecms/template/views/outerLayoutBase.html +3 -0
- package/modules/@apostrophecms/ui/index.js +2 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposButtonGroup.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenu.vue +5 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenuDialog.vue +5 -0
- package/modules/@apostrophecms/ui/ui/apos/lib/vue.js +2 -0
- package/modules/@apostrophecms/ui/ui/apos/stores/widget.js +12 -7
- package/modules/@apostrophecms/ui/ui/apos/stores/widgetGraph.js +461 -0
- package/modules/@apostrophecms/ui/ui/apos/universal/graph.js +452 -0
- package/modules/@apostrophecms/ui/ui/apos/universal/widgetGraph.js +10 -0
- package/modules/@apostrophecms/uploadfs/index.js +15 -1
- package/modules/@apostrophecms/url/index.js +419 -1
- package/package.json +5 -5
- package/test/add-missing-schema-fields-project/node_modules/.package-lock.json +131 -0
- package/test/external-front.js +1 -0
- package/test/files.js +264 -0
- package/test/login-requirements.js +145 -3
- package/test/static-build.js +2701 -0
- package/test/universal-graph.js +1135 -0
package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue
CHANGED
|
@@ -10,16 +10,7 @@
|
|
|
10
10
|
v-if="editor"
|
|
11
11
|
plugin-key="richTextMenu"
|
|
12
12
|
class="bubble-menu"
|
|
13
|
-
:tippy-options="
|
|
14
|
-
maxWidth: 'none',
|
|
15
|
-
duration: 300,
|
|
16
|
-
zIndex: 999,
|
|
17
|
-
animation: 'fade',
|
|
18
|
-
inertia: true,
|
|
19
|
-
placement: 'bottom',
|
|
20
|
-
hideOnClick: false,
|
|
21
|
-
onHide: onBubbleHide
|
|
22
|
-
}"
|
|
13
|
+
:tippy-options="bubbleMenuTippyOptions"
|
|
23
14
|
:editor="editor"
|
|
24
15
|
>
|
|
25
16
|
<AposContextMenuDialog
|
|
@@ -122,6 +113,7 @@ import {
|
|
|
122
113
|
BubbleMenu,
|
|
123
114
|
FloatingMenu
|
|
124
115
|
} from '@tiptap/vue-3';
|
|
116
|
+
|
|
125
117
|
import AposTiptapTableControls from './AposTiptapTableControls.vue';
|
|
126
118
|
// Starter Kit extensions
|
|
127
119
|
import BlockQuote from '@tiptap/extension-blockquote';
|
|
@@ -155,6 +147,7 @@ import newInstance from 'apostrophe/modules/@apostrophecms/schema/lib/newInstanc
|
|
|
155
147
|
import merge from 'lodash/merge';
|
|
156
148
|
import { useAposStyles } from 'Modules/@apostrophecms/styles/composables/AposStyles.js';
|
|
157
149
|
import { useModalStore } from 'Modules/@apostrophecms/ui/stores/modal';
|
|
150
|
+
import { useWidgetStore } from 'Modules/@apostrophecms/ui/stores/widget';
|
|
158
151
|
|
|
159
152
|
export default {
|
|
160
153
|
name: 'AposRichTextWidgetEditor',
|
|
@@ -199,7 +192,7 @@ export default {
|
|
|
199
192
|
default: false
|
|
200
193
|
}
|
|
201
194
|
},
|
|
202
|
-
emits: [ 'update', 'suppressWidgetControls' ],
|
|
195
|
+
emits: [ 'update', 'suppressWidgetControls', 'suppressAddContentButtons' ],
|
|
203
196
|
setup() {
|
|
204
197
|
return useAposStyles();
|
|
205
198
|
},
|
|
@@ -219,12 +212,26 @@ export default {
|
|
|
219
212
|
activeInsertMenuComponent: false,
|
|
220
213
|
suppressInsertMenu: false,
|
|
221
214
|
suppressWidgetControls: false,
|
|
215
|
+
suppressAddContentButtons: false,
|
|
222
216
|
hasSelection: false,
|
|
223
217
|
openedPopover: false
|
|
224
218
|
};
|
|
225
219
|
},
|
|
226
220
|
computed: {
|
|
227
221
|
...mapState(useModalStore, [ 'getAdminDirectionClass' ]),
|
|
222
|
+
...mapState(useWidgetStore, [ 'focusedWidget' ]),
|
|
223
|
+
bubbleMenuTippyOptions() {
|
|
224
|
+
return {
|
|
225
|
+
maxWidth: 'none',
|
|
226
|
+
duration: 300,
|
|
227
|
+
zIndex: 999,
|
|
228
|
+
animation: 'fade',
|
|
229
|
+
inertia: true,
|
|
230
|
+
placement: 'bottom',
|
|
231
|
+
hideOnClick: false,
|
|
232
|
+
onHide: this.onBubbleHide
|
|
233
|
+
};
|
|
234
|
+
},
|
|
228
235
|
// Note that context menu class-list expects a string
|
|
229
236
|
contextMenuClasses() {
|
|
230
237
|
const directionClass = this.getAdminDirectionClass();
|
|
@@ -374,9 +381,15 @@ export default {
|
|
|
374
381
|
this.$emit('suppressWidgetControls');
|
|
375
382
|
}
|
|
376
383
|
},
|
|
384
|
+
suppressAddContentButtons(newVal) {
|
|
385
|
+
if (newVal) {
|
|
386
|
+
this.$emit('suppressAddContentButtons');
|
|
387
|
+
}
|
|
388
|
+
},
|
|
377
389
|
isFocused(newVal) {
|
|
378
390
|
if (!newVal) {
|
|
379
391
|
this.suppressWidgetControls = false;
|
|
392
|
+
this.suppressAddContentButtons = false;
|
|
380
393
|
if (this.pending) {
|
|
381
394
|
this.emitWidgetUpdate();
|
|
382
395
|
}
|
|
@@ -482,9 +495,11 @@ export default {
|
|
|
482
495
|
});
|
|
483
496
|
},
|
|
484
497
|
onSelectionUpdate: ({ editor }) => {
|
|
498
|
+
this.hasSelection = !editor.view.state.selection.empty;
|
|
485
499
|
this.$nextTick(() => {
|
|
486
|
-
if (
|
|
500
|
+
if (this.hasSelection) {
|
|
487
501
|
this.suppressWidgetControls = true;
|
|
502
|
+
this.suppressAddContentButtons = true;
|
|
488
503
|
}
|
|
489
504
|
});
|
|
490
505
|
}
|
|
@@ -579,6 +594,7 @@ export default {
|
|
|
579
594
|
this.suppressInsertMenu = false;
|
|
580
595
|
}
|
|
581
596
|
this.suppressWidgetControls = true;
|
|
597
|
+
this.suppressAddContentButtons = true;
|
|
582
598
|
},
|
|
583
599
|
doSuppressInsertMenu() {
|
|
584
600
|
this.suppressInsertMenu = true;
|
|
@@ -179,6 +179,7 @@ export default {
|
|
|
179
179
|
const attrs = this.schemaHtmlAttributes.reduce((acc, field) => {
|
|
180
180
|
const value = this.docFields.data[field.name];
|
|
181
181
|
if (field.type === 'checkboxes' && !value?.[0]) {
|
|
182
|
+
acc[field.htmlAttribute] = null;
|
|
182
183
|
return acc;
|
|
183
184
|
}
|
|
184
185
|
if (field.type === 'boolean') {
|
|
@@ -2,12 +2,32 @@ module.exports = self => {
|
|
|
2
2
|
return {
|
|
3
3
|
get: {
|
|
4
4
|
// This route serves the existing styles stylesheet,
|
|
5
|
-
// constructed from the global object
|
|
6
|
-
// We do it this way so the browser can cache the styles as often as possible
|
|
5
|
+
// constructed from the global object.
|
|
6
|
+
// We do it this way so the browser can cache the styles as often as possible.
|
|
7
|
+
//
|
|
8
|
+
// The locale-qualified alias (`stylesheet/locale/:locale/:mode`)
|
|
9
|
+
// produces a distinct path per locale so that static-build
|
|
10
|
+
// tools that key on the URL path (e.g. Astro) don't
|
|
11
|
+
// overwrite one locale's stylesheet with another's.
|
|
7
12
|
stylesheet(req) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
return self.serveStylesheet(req);
|
|
14
|
+
},
|
|
15
|
+
'stylesheet/locale/:locale/:mode': async function(req) {
|
|
16
|
+
const { locale, mode } = req.params;
|
|
17
|
+
if (!locale || !self.apos.i18n.isValidLocale(locale)) {
|
|
18
|
+
throw self.apos.error('invalid');
|
|
19
|
+
}
|
|
20
|
+
const validModes = [ 'published', 'draft' ];
|
|
21
|
+
const safeMode = validModes.includes(mode) ? mode : 'published';
|
|
22
|
+
if (safeMode === 'draft' && !self.apos.permission.can(req, 'view-draft')) {
|
|
23
|
+
throw self.apos.error('forbidden');
|
|
24
|
+
}
|
|
25
|
+
req.locale = locale;
|
|
26
|
+
req.mode = safeMode;
|
|
27
|
+
// Force re-fetch global for the correct locale
|
|
28
|
+
delete req.data.global;
|
|
29
|
+
await self.apos.global.addGlobalToData(req);
|
|
30
|
+
return self.serveStylesheet(req);
|
|
11
31
|
}
|
|
12
32
|
},
|
|
13
33
|
post: {
|
|
@@ -45,6 +45,25 @@ node app @apostrophecms-pro/palette:migrate-to-styles
|
|
|
45
45
|
}
|
|
46
46
|
self.apos.template.addBodyClass(req, classes.join(' '));
|
|
47
47
|
}
|
|
48
|
+
},
|
|
49
|
+
'@apostrophecms/url:getAllUrlMetadata': {
|
|
50
|
+
// Provide a literal content entry so static builds
|
|
51
|
+
// can include the dynamically generated stylesheet.
|
|
52
|
+
// Uses the locale-path URL so each locale produces a
|
|
53
|
+
// distinct path for static-build tools.
|
|
54
|
+
// The URL must be relative and prefix-free — the
|
|
55
|
+
// consumer prepends the prefix when fetching.
|
|
56
|
+
addStylesheetUrl(req, results) {
|
|
57
|
+
if (!req.data.global?.stylesStylesheet) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
results.push({
|
|
61
|
+
url: self.getStylesheetUrl(req, { relative: true }),
|
|
62
|
+
contentType: 'text/css',
|
|
63
|
+
i18nId: '@apostrophecms/styles:stylesheet',
|
|
64
|
+
sitemap: false
|
|
65
|
+
});
|
|
66
|
+
}
|
|
48
67
|
}
|
|
49
68
|
};
|
|
50
69
|
};
|
|
@@ -82,6 +82,27 @@ module.exports = (self, options) => {
|
|
|
82
82
|
|
|
83
83
|
return expanded;
|
|
84
84
|
},
|
|
85
|
+
// Returns the locale-qualified stylesheet URL for the given
|
|
86
|
+
// request. The path encodes the locale so that static-build
|
|
87
|
+
// tools produce a distinct file per locale.
|
|
88
|
+
//
|
|
89
|
+
// Options:
|
|
90
|
+
// `relative` - if true, return a prefix-free path suitable
|
|
91
|
+
// for route-relative contexts (e.g. `getAllUrlMetadata`).
|
|
92
|
+
// Defaults to `false`, which prepends `apos.prefix` so the
|
|
93
|
+
// URL works in rendered HTML (`<link>` tags, etc.).
|
|
94
|
+
getStylesheetUrl(req, { relative = false } = {}) {
|
|
95
|
+
const prefix = relative ? '' : (self.apos.prefix || '');
|
|
96
|
+
const version = req.data.global?.stylesStylesheetVersion;
|
|
97
|
+
return `${prefix}${self.action}/stylesheet/locale/${req.locale}/${req.mode}${version ? `?version=${version}` : ''}`;
|
|
98
|
+
},
|
|
99
|
+
// Shared implementation for the stylesheet API routes.
|
|
100
|
+
// Sets cache headers and returns the raw CSS string.
|
|
101
|
+
serveStylesheet(req) {
|
|
102
|
+
req.res.setHeader('Content-Type', 'text/css');
|
|
103
|
+
req.res.setHeader('Cache-Control', 'public, max-age=31557600');
|
|
104
|
+
return req.data.global?.stylesStylesheet || '';
|
|
105
|
+
},
|
|
85
106
|
stylesheet(req) {
|
|
86
107
|
// Stylesheet node should be created only for logged in users.
|
|
87
108
|
if (!req.data.global) {
|
|
@@ -94,7 +115,7 @@ module.exports = (self, options) => {
|
|
|
94
115
|
// and similar features from those who should just get the link tag
|
|
95
116
|
const hasLink = !self.apos.permission.can(req, 'view-draft');
|
|
96
117
|
if (req.data.global.stylesStylesheet && hasLink) {
|
|
97
|
-
const href =
|
|
118
|
+
const href = self.getStylesheetUrl(req);
|
|
98
119
|
nodes.push({
|
|
99
120
|
name: 'link',
|
|
100
121
|
attrs: {
|
|
@@ -103,17 +124,19 @@ module.exports = (self, options) => {
|
|
|
103
124
|
}
|
|
104
125
|
});
|
|
105
126
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
127
|
+
if (!hasLink) {
|
|
128
|
+
nodes.push({
|
|
129
|
+
name: 'style',
|
|
130
|
+
attrs: {
|
|
131
|
+
id: 'apos-styles-stylesheet'
|
|
132
|
+
},
|
|
133
|
+
body: [
|
|
134
|
+
{
|
|
135
|
+
raw: req.data.global.stylesStylesheet || ''
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
});
|
|
139
|
+
}
|
|
117
140
|
return nodes;
|
|
118
141
|
},
|
|
119
142
|
ui(req) {
|
|
@@ -431,6 +431,7 @@ export default {
|
|
|
431
431
|
);
|
|
432
432
|
},
|
|
433
433
|
async setStyleMarkup({ css, classes }) {
|
|
434
|
+
const stylesheetEl = document.querySelector('#apos-styles-stylesheet');
|
|
434
435
|
this.setBodyClasses(classes);
|
|
435
436
|
if (apos.adminBar.breakpointPreviewMode?.enable) {
|
|
436
437
|
const processed = breakpointPreviewTransformer(css, {
|
|
@@ -438,11 +439,15 @@ export default {
|
|
|
438
439
|
debug: apos.adminBar.breakpointPreviewMode?.debug === true,
|
|
439
440
|
transform: apos.adminBar.breakpointPreviewMode?.transform || null
|
|
440
441
|
});
|
|
441
|
-
|
|
442
|
+
if (stylesheetEl) {
|
|
443
|
+
stylesheetEl.textContent = processed;
|
|
444
|
+
}
|
|
442
445
|
return;
|
|
443
446
|
}
|
|
444
447
|
|
|
445
|
-
|
|
448
|
+
if (stylesheetEl) {
|
|
449
|
+
stylesheetEl.textContent = css;
|
|
450
|
+
}
|
|
446
451
|
},
|
|
447
452
|
setBodyClasses(classes) {
|
|
448
453
|
const previousClasses = document.body
|
|
@@ -300,7 +300,15 @@ module.exports = {
|
|
|
300
300
|
};
|
|
301
301
|
addCloneMethod(req);
|
|
302
302
|
req.res.__ = req.__;
|
|
303
|
-
const {
|
|
303
|
+
const {
|
|
304
|
+
role: _role, staticBuild: _staticBuild, ...properties
|
|
305
|
+
} = options || {};
|
|
306
|
+
|
|
307
|
+
// When staticBuild is requested, configure the req object
|
|
308
|
+
// with the same properties that Express middlewares
|
|
309
|
+
if (_staticBuild) {
|
|
310
|
+
self.apos.modules['@apostrophecms/express'].applyStaticBuildHeaders(req);
|
|
311
|
+
}
|
|
304
312
|
|
|
305
313
|
Object.assign(req, properties);
|
|
306
314
|
self.apos.i18n.setPrefixUrls(req);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="{% block locale %}{{ data.locale }}{% endblock %}" dir="{% block direction %}{{ data.i18n.direction or 'ltr' }}{% endblock %}" {% block extraHtml %}{% endblock %}>
|
|
3
3
|
<head>
|
|
4
|
+
{% block encoding %}
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
{% endblock %}
|
|
4
7
|
{% block startHead %}
|
|
5
8
|
{% endblock %}
|
|
6
9
|
{% component '@apostrophecms/template:inject' with { where: 'head', end: 'prepend', when: 'hmr' } %}
|
|
@@ -9,6 +9,7 @@ module.exports = {
|
|
|
9
9
|
},
|
|
10
10
|
init(self) {
|
|
11
11
|
self.enableBrowserData();
|
|
12
|
+
self.uiDebug = self.options.debug ?? process.env.NODE_ENV !== 'production';
|
|
12
13
|
},
|
|
13
14
|
methods(self) {
|
|
14
15
|
return {
|
|
@@ -23,6 +24,7 @@ module.exports = {
|
|
|
23
24
|
theme.primary = req.data.user.aposThemePrimary;
|
|
24
25
|
}
|
|
25
26
|
return {
|
|
27
|
+
debug: self.uiDebug,
|
|
26
28
|
theme,
|
|
27
29
|
widgetMargin: self.options.widgetMargin
|
|
28
30
|
};
|
|
@@ -86,6 +86,7 @@
|
|
|
86
86
|
:active-item="activeItem"
|
|
87
87
|
:is-open="isOpen"
|
|
88
88
|
:has-tip="hasTip"
|
|
89
|
+
:ignore-unfocus="ignoreUnfocus"
|
|
89
90
|
@item-clicked="menuItemClicked"
|
|
90
91
|
@set-arrow="setArrow"
|
|
91
92
|
>
|
|
@@ -210,6 +211,10 @@ const props = defineProps({
|
|
|
210
211
|
teleportContent: {
|
|
211
212
|
type: Boolean,
|
|
212
213
|
default: false
|
|
214
|
+
},
|
|
215
|
+
ignoreUnfocus: {
|
|
216
|
+
type: Boolean,
|
|
217
|
+
default: false
|
|
213
218
|
}
|
|
214
219
|
});
|
|
215
220
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
class="apos-primary-scrollbar apos-context-menu__dialog"
|
|
4
4
|
:class="classes"
|
|
5
5
|
role="dialog"
|
|
6
|
+
:data-apos-ignore-unfocus="props.ignoreUnfocus"
|
|
6
7
|
>
|
|
7
8
|
<AposContextMenuTip
|
|
8
9
|
v-if="hasTip"
|
|
@@ -37,6 +38,10 @@
|
|
|
37
38
|
import { computed } from 'vue';
|
|
38
39
|
|
|
39
40
|
const props = defineProps({
|
|
41
|
+
ignoreUnfocus: {
|
|
42
|
+
type: Boolean,
|
|
43
|
+
default: false
|
|
44
|
+
},
|
|
40
45
|
menuPlacement: {
|
|
41
46
|
type: String,
|
|
42
47
|
required: true
|
|
@@ -49,13 +49,18 @@ export const useWidgetStore = defineStore('widget', () => {
|
|
|
49
49
|
|
|
50
50
|
const headerHeight = window.apos.adminBar.height;
|
|
51
51
|
const bufferSpace = 40;
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
const rect = $el.getBoundingClientRect();
|
|
53
|
+
const visibleTop = headerHeight + bufferSpace;
|
|
54
|
+
const visibleBottom = window.innerHeight - bufferSpace;
|
|
55
|
+
const isInView = rect.top >= visibleTop && rect.bottom <= visibleBottom;
|
|
56
|
+
|
|
57
|
+
if (!isInView) {
|
|
58
|
+
const scrollPos = rect.top - headerHeight - bufferSpace;
|
|
59
|
+
window.scrollBy({
|
|
60
|
+
top: scrollPos,
|
|
61
|
+
behavior: 'smooth'
|
|
62
|
+
});
|
|
63
|
+
}
|
|
59
64
|
|
|
60
65
|
$el.focus({
|
|
61
66
|
preventScroll: true
|