Skip to content

Commit 6e0acaa

Browse files
committed
Merge remote-tracking branch 'upstream/main'
* upstream/main: Release 4.8.0 (apostrophecms#4742) Enable by default, other tweaks (apostrophecms#4741) PRO-6627: Fix array reorder and labels (apostrophecms#4740) Pro 6518 mobile preview (apostrophecms#4720)
2 parents ae65582 + 2c6267b commit 6e0acaa

File tree

18 files changed

+484
-31
lines changed

18 files changed

+484
-31
lines changed

CHANGELOG.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
# Changelog
22

3-
## UNRELEASED
3+
## 4.8.0 (2024-10-03)
44

55
### Adds
66

7+
* Adds a mobile preview feature to the admin UI. The feature can be enabled using the `@apostrophecms/asset` module's new `breakpointPreviewMode` option. Once enabled, the asset build process will duplicate existing media queries as container queries. There are some limitations in the equivalence between media queries and container queries. You can refer to the [CSS @container at-rule](https://developer.mozilla.org/en-US/docs/Web/CSS/@container) documentation for more information. You can also enable `breakpointPreviewMode.debug` to be notified in the console when the build encounters an unsupported media query.
78
* Apostrophe now automatically adds the appropriate default values for new properties in the schema, even for existing documents in the database. This is done automatically during the migration phase of startup.
8-
* Adds focus states for media library's Uploader tile
9-
* Adds focus states file attachment's input UI
9+
* Adds focus states for media library's Uploader tile.
10+
* Adds focus states file attachment's input UI.
1011
* Simplified importing rich text widgets via the REST API. If you you have HTML that contains `img` tags pointing to existing images, you can now import them all quickly. When supplying the rich text widget object, include an `import` property with an `html` subproperty, rather than the usual `content` property. You can optionally provide a `baseUrl` subproperty as well. Any images present in `html` will be imported automatically and the correct `figure` tags will be added to the new rich text widget, along with any other markup acceptable to the widget's configuration.
1112
* Add support for ESM projects.
1213

@@ -18,11 +19,13 @@
1819

1920
### Fixes
2021

21-
* Apostrophe's migration logic is no longer executed twice on every startup and three times in the migration task. It is executed exactly once, always at the same point in the startup process. This bug did not cause significant performance issues because migrations were only executed once, but there is a small performance improvement.
22+
* Apostrophe's migration logic is no longer executed twice on every startup and three times in the migration task. It is executed exactly once, always at the same point in the startup process. This bug did not cause significant performance issues because migrations were always only executed once, but there is a small performance improvement due to not checking for them more than once.
2223
* The `@apostrophecms/page` module APIs no longer allow a page to become a child of itself. Thanks to [Maarten Marx](https://github.com/Pixelguymm) for reporting the issue.
2324
* Uploaded SVGs now permit `<use>` tags granted their `xlink:href` property is a local reference and begins with the `#` character. This improves SVG support while mitgating XSS vulnerabilities.
2425
* Default properties of object fields present in a widget now populate correctly even if never focused in the editor.
2526
* Fixed the "choices" query builder to correctly support dynamic choices, ensuring compatibility with the [`piecesFilters`](https://docs.apostrophecms.org/reference/modules/piece-page-type.html#piecesfilters) feature when using dynamic choices.
27+
* Fix a reordering issue for arrays when dragging and dropping items in the admin UI.
28+
* The inline array item extract the label now using `title` as `titleField` value by default (consistent with the Slat list).
2629

2730
## 4.7.1 (2024-09-20)
2831

modules/@apostrophecms/admin-bar/index.js

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,56 @@ module.exports = {
1414
pageTree: true
1515
},
1616
commands(self) {
17+
const breakpointPreviewModeScreens = (
18+
self.apos.asset.options.breakpointPreviewMode?.enable &&
19+
self.apos.asset.options.breakpointPreviewMode?.screens
20+
) || {};
21+
const breakpointPreviewModeCommands = {
22+
[`${self.__meta.name}:toggle-breakpoint-preview-mode:exit`]: {
23+
type: 'item',
24+
label: {
25+
key: 'apostrophe:commandMenuToggleBreakpointPreviewMode',
26+
breakpoint: '$t(apostrophe:breakpointPreviewExit)'
27+
},
28+
action: {
29+
type: 'command-menu-admin-bar-toggle-breakpoint-preview-mode',
30+
payload: {
31+
mode: null,
32+
width: null,
33+
height: null
34+
}
35+
},
36+
shortcut: 'P,0'
37+
}
38+
};
39+
let index = 1;
40+
for (const [ name, screen ] of Object.entries(breakpointPreviewModeScreens)) {
41+
// Up to 9 shortcuts available
42+
if (index === 9) {
43+
break;
44+
}
45+
46+
breakpointPreviewModeCommands[`${self.__meta.name}:toggle-breakpoint-preview-mode:${name}`] = {
47+
type: 'item',
48+
label: {
49+
key: 'apostrophe:commandMenuToggleBreakpointPreviewMode',
50+
breakpoint: `$t(${screen.label})`
51+
},
52+
action: {
53+
type: 'command-menu-admin-bar-toggle-breakpoint-preview-mode',
54+
payload: {
55+
mode: name,
56+
label: `$t(${screen.label})`,
57+
width: screen.width,
58+
height: screen.height
59+
}
60+
},
61+
shortcut: `P,${index}`
62+
};
63+
64+
index += 1;
65+
};
66+
1767
return {
1868
add: {
1969
[`${self.__meta.name}:undo`]: {
@@ -63,7 +113,8 @@ module.exports = {
63113
type: 'command-menu-admin-bar-toggle-publish-draft'
64114
},
65115
shortcut: 'Ctrl+Shift+D Meta+Shift+D'
66-
}
116+
},
117+
...breakpointPreviewModeCommands
67118
},
68119
modal: {
69120
default: {
@@ -80,7 +131,8 @@ module.exports = {
80131
label: 'apostrophe:commandMenuMode',
81132
commands: [
82133
`${self.__meta.name}:toggle-edit-preview-mode`,
83-
`${self.__meta.name}:toggle-published-draft-document`
134+
`${self.__meta.name}:toggle-published-draft-document`,
135+
...Object.keys(breakpointPreviewModeCommands)
84136
]
85137
}
86138
}
@@ -355,6 +407,13 @@ module.exports = {
355407
aposLocale: context.aposLocale,
356408
aposDocId: context.aposDocId
357409
},
410+
breakpointPreviewMode: self.apos.asset.options.breakpointPreviewMode ||
411+
{
412+
enable: false,
413+
debug: false,
414+
resizable: false,
415+
screens: {}
416+
},
358417
// Base API URL appropriate to the context document
359418
contextBar: context && self.apos.doc.getManager(context.type).options.contextBar,
360419
showAdminBar: self.getShowAdminBar(req),
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
<template>
2+
<div
3+
data-apos-test="breakpointPreviewMode"
4+
class="apos-admin-bar__breakpoint-preview-mode"
5+
>
6+
<component
7+
:is="'AposButton'"
8+
v-for="(screen, name) in screens"
9+
:key="name"
10+
:data-apos-test="`breakpointPreviewMode:${name}`"
11+
:modifiers="['small', 'no-motion']"
12+
:label="screen.label"
13+
:title="$t(screen.label)"
14+
:icon="screen.icon"
15+
:icon-only="true"
16+
type="subtle"
17+
class="apos-admin-bar__breakpoint-preview-mode-button"
18+
:class="{ 'apos-is-active': mode === name }"
19+
@click="toggleBreakpointPreviewMode({ mode: name, label: screen.label, width: screen.width, height: screen.height })"
20+
/>
21+
</div>
22+
</template>
23+
<script>
24+
25+
export default {
26+
name: 'TheAposContextBreakpointPreviewMode',
27+
props: {
28+
// { screenName: { label: string, width: string, height: string, icon: string } }
29+
screens: {
30+
type: Object,
31+
validator(value, props) {
32+
return Object.values(value).every(screen =>
33+
typeof screen.label === 'string' &&
34+
typeof screen.width === 'string' &&
35+
typeof screen.height === 'string' &&
36+
typeof screen.icon === 'string'
37+
);
38+
},
39+
default: () => {
40+
return {};
41+
}
42+
},
43+
resizable: {
44+
type: Boolean,
45+
default: false
46+
}
47+
},
48+
emits: [ 'switch-breakpoint-preview-mode', 'reset-breakpoint-preview-mode' ],
49+
data() {
50+
return {
51+
mode: null,
52+
originalBodyBackground: null
53+
};
54+
},
55+
mounted() {
56+
apos.bus.$on('command-menu-admin-bar-toggle-breakpoint-preview-mode', this.toggleBreakpointPreviewMode);
57+
58+
this.originalBodyBackground = window.getComputedStyle(document.querySelector('body'))?.background ||
59+
'#fff';
60+
61+
const state = this.loadState();
62+
if (state.mode) {
63+
this.toggleBreakpointPreviewMode(state);
64+
}
65+
},
66+
unmounted() {
67+
apos.bus.$off('command-menu-admin-bar-toggle-breakpoint-preview-mode', this.toggleBreakpointPreviewMode);
68+
},
69+
methods: {
70+
switchBreakpointPreviewMode({
71+
mode,
72+
label,
73+
width,
74+
height
75+
}) {
76+
document.querySelector('body').setAttribute('data-breakpoint-preview-mode', mode);
77+
document.querySelector('[data-apos-refreshable]').setAttribute('data-resizable', this.resizable);
78+
document.querySelector('[data-apos-refreshable]').setAttribute('data-label', this.$t(label));
79+
document.querySelector('[data-apos-refreshable]').style.width = width;
80+
document.querySelector('[data-apos-refreshable]').style.height = height;
81+
document.querySelector('[data-apos-refreshable]').style.background = this.originalBodyBackground;
82+
83+
this.mode = mode;
84+
this.$emit('switch-breakpoint-preview-mode', {
85+
mode,
86+
label,
87+
width,
88+
height
89+
});
90+
this.saveState({
91+
mode,
92+
label,
93+
width,
94+
height
95+
});
96+
},
97+
toggleBreakpointPreviewMode({
98+
mode,
99+
label,
100+
width,
101+
height
102+
}) {
103+
if (this.mode === mode || mode === null) {
104+
document.querySelector('body').removeAttribute('data-breakpoint-preview-mode');
105+
document.querySelector('[data-apos-refreshable]').removeAttribute('data-resizable');
106+
document.querySelector('[data-apos-refreshable]').removeAttribute('data-label');
107+
document.querySelector('[data-apos-refreshable]').style.removeProperty('width');
108+
document.querySelector('[data-apos-refreshable]').style.removeProperty('height');
109+
document.querySelector('[data-apos-refreshable]').style.removeProperty('background');
110+
111+
this.mode = null;
112+
this.$emit('reset-breakpoint-preview-mode');
113+
this.saveState({ mode: this.mode });
114+
115+
return;
116+
}
117+
118+
this.switchBreakpointPreviewMode({
119+
mode,
120+
label,
121+
width,
122+
height
123+
});
124+
},
125+
loadState() {
126+
return JSON.parse(sessionStorage.getItem('aposBreakpointPreviewMode') || '{}');
127+
},
128+
saveState({
129+
mode = null,
130+
label = null,
131+
width = null,
132+
height = null
133+
} = {}) {
134+
const state = this.loadState();
135+
if (state.mode !== mode) {
136+
sessionStorage.setItem(
137+
'aposBreakpointPreviewMode',
138+
JSON.stringify({
139+
mode,
140+
label,
141+
width,
142+
height
143+
})
144+
);
145+
}
146+
}
147+
}
148+
};
149+
</script>
150+
<style lang="scss" scoped>
151+
.apos-admin-bar__breakpoint-preview-mode {
152+
display: flex;
153+
gap: $spacing-half;
154+
margin-left: $spacing-double;
155+
}
156+
157+
.apos-admin-bar__breakpoint-preview-mode-button {
158+
&.apos-is-active {
159+
color: var(--a-text-primary);
160+
text-decoration: none;
161+
background-color: var(--a-base-10);
162+
border-radius: var(--a-border-radius);
163+
outline: 1px solid var(--a-base-7);
164+
}
165+
}
166+
</style>

modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextTitle.vue

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@
5050
:tooltip="tooltip"
5151
:modifiers="modifiers"
5252
/>
53+
<TheAposContextBreakpointPreviewMode
54+
v-if="isBreakpointPreviewModeEnabled"
55+
:screens="breakpointPreviewModeScreens"
56+
:resizable="breakpointPreviewModeResizable"
57+
@switch-breakpoint-preview-mode="addContextLabel"
58+
@reset-breakpoint-preview-mode="removeContextLabel"
59+
/>
5360
</span>
5461
</transition-group>
5562
</template>
@@ -94,6 +101,15 @@ export default {
94101
isUnpublished() {
95102
return !this.context.lastPublishedAt;
96103
},
104+
isBreakpointPreviewModeEnabled() {
105+
return this.moduleOptions.breakpointPreviewMode.enable || false;
106+
},
107+
breakpointPreviewModeScreens() {
108+
return this.moduleOptions.breakpointPreviewMode.screens || {};
109+
},
110+
breakpointPreviewModeResizable() {
111+
return this.moduleOptions.breakpointPreviewMode.resizable || false;
112+
},
97113
docTooltip() {
98114
return {
99115
key: 'apostrophe:lastUpdatedBy',
@@ -142,6 +158,15 @@ export default {
142158
},
143159
switchDraftMode(mode) {
144160
this.$emit('switch-draft-mode', mode);
161+
},
162+
addContextLabel({
163+
label
164+
}) {
165+
document.querySelector('[data-apos-context-label]')
166+
?.replaceChildren(document.createTextNode(this.$t(label)));
167+
},
168+
removeContextLabel() {
169+
document.querySelector('[data-apos-context-label]')?.replaceChildren();
145170
}
146171
}
147172
};

modules/@apostrophecms/asset/index.js

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,47 @@ module.exports = {
4444
rebundleModules: undefined,
4545
// In case of external front end like Astro, this option allows to
4646
// disable the build of the public UI assets.
47-
publicBundle: true
47+
publicBundle: true,
48+
// Breakpoint preview in the admin UI.
49+
// NOTE: the whole breakpointPreviewMode option must be carried over
50+
// to the project for overrides to work properly.
51+
// Nested object options are not deep merged in Apostrophe.
52+
breakpointPreviewMode: {
53+
// Enable breakpoint preview mode
54+
enable: true,
55+
// Warn during build about unsupported media queries.
56+
debug: false,
57+
// If we can resize the preview container?
58+
resizable: false,
59+
// Screens with icons
60+
// For adding icons, please refer to the icons documentation
61+
// https://docs.apostrophecms.org/reference/module-api/module-overview.html#icons
62+
screens: {
63+
desktop: {
64+
label: 'apostrophe:breakpointPreviewDesktop',
65+
width: '1440px',
66+
height: '900px',
67+
icon: 'monitor-icon'
68+
},
69+
tablet: {
70+
label: 'apostrophe:breakpointPreviewTablet',
71+
width: '1024px',
72+
height: '768px',
73+
icon: 'tablet-icon'
74+
},
75+
mobile: {
76+
label: 'apostrophe:breakpointPreviewMobile',
77+
width: '414px',
78+
height: '896px',
79+
icon: 'cellphone-icon'
80+
}
81+
},
82+
// Transform method used on media feature
83+
// Can be either:
84+
// - (mediaFeature) => { return mediaFeature.replaceAll('xx', 'yy'); }
85+
// - null
86+
transform: null
87+
}
4888
},
4989

5090
async init(self) {

0 commit comments

Comments
 (0)