apostrophe 4.7.2 → 4.8.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +32 -2
  2. package/index.js +1 -1
  3. package/lib/moog.js +1 -1
  4. package/modules/@apostrophecms/admin-bar/index.js +61 -2
  5. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBreakpointPreviewMode.vue +166 -0
  6. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextTitle.vue +25 -0
  7. package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +3 -12
  8. package/modules/@apostrophecms/asset/index.js +41 -1
  9. package/modules/@apostrophecms/asset/lib/globalIcons.js +6 -0
  10. package/modules/@apostrophecms/asset/lib/webpack/apos/webpack.scss.js +16 -16
  11. package/modules/@apostrophecms/asset/lib/webpack/media-to-container-queries-loader.js +94 -0
  12. package/modules/@apostrophecms/asset/lib/webpack/src/webpack.scss.js +12 -0
  13. package/modules/@apostrophecms/attachment/index.js +8 -1
  14. package/modules/@apostrophecms/command-menu/ui/apos/components/AposCommandMenuKey.vue +1 -1
  15. package/modules/@apostrophecms/command-menu/ui/apos/components/AposCommandMenuShortcut.vue +5 -2
  16. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +1 -19
  17. package/modules/@apostrophecms/i18n/i18n/en.json +12 -1
  18. package/modules/@apostrophecms/image/ui/apos/components/AposMediaUploader.vue +9 -18
  19. package/modules/@apostrophecms/migration/index.js +20 -13
  20. package/modules/@apostrophecms/migration/lib/addMissingSchemaFields.js +182 -0
  21. package/modules/@apostrophecms/page/index.js +4 -0
  22. package/modules/@apostrophecms/piece-type/ui/apos/components/AposRelationshipEditor.vue +2 -7
  23. package/modules/@apostrophecms/rich-text-widget/index.js +66 -0
  24. package/modules/@apostrophecms/schema/index.js +20 -29
  25. package/modules/@apostrophecms/schema/lib/addFieldTypes.js +2 -27
  26. package/modules/@apostrophecms/schema/lib/newInstance.js +36 -0
  27. package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +238 -80
  28. package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +34 -24
  29. package/modules/@apostrophecms/schema/ui/apos/logic/AposArrayEditor.js +2 -7
  30. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputArray.js +207 -44
  31. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRelationship.js +3 -14
  32. package/modules/@apostrophecms/schema/ui/apos/scss/AposInputArray.scss +288 -105
  33. package/modules/@apostrophecms/template/views/outerLayoutBase.html +1 -0
  34. package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +2 -1
  35. package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenu.vue +9 -6
  36. package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenuDialog.vue +8 -6
  37. package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenuItem.vue +52 -51
  38. package/modules/@apostrophecms/ui/ui/apos/components/AposFile.vue +16 -6
  39. package/modules/@apostrophecms/ui/ui/apos/components/AposIndicator.vue +1 -0
  40. package/modules/@apostrophecms/ui/ui/apos/components/AposLocalePicker.vue +6 -4
  41. package/modules/@apostrophecms/ui/ui/apos/components/AposSlatList.vue +6 -2
  42. package/modules/@apostrophecms/ui/ui/apos/components/AposTagApply.vue +8 -6
  43. package/modules/@apostrophecms/ui/ui/apos/scss/global/_breakpoint_preview.scss +38 -0
  44. package/modules/@apostrophecms/ui/ui/apos/scss/global/_tables.scss +3 -1
  45. package/modules/@apostrophecms/ui/ui/apos/scss/global/import-all.scss +1 -0
  46. package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +2 -13
  47. package/package.json +3 -2
  48. package/test/add-missing-schema-fields.js +323 -0
  49. package/test/pages.js +10 -0
package/CHANGELOG.md CHANGED
@@ -1,10 +1,32 @@
1
1
  # Changelog
2
2
 
3
- ## 4.7.2 (2024-10-09)
3
+ ## 4.8.1 (2024-10-09)
4
+
5
+ * Correct a race condition that can cause a crash at startup when custom `uploadfs` options are present in some environments.
6
+
7
+ ## 4.8.0 (2024-10-03)
8
+
9
+ ### Adds
10
+
11
+ * 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.
12
+ * 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.
13
+ * Adds focus states for media library's Uploader tile.
14
+ * Adds focus states file attachment's input UI.
15
+ * 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.
16
+
17
+ ### Changes
18
+
19
+ * The various implementations of `newInstance` found in Apostrophe, e.g. for widgets, array items, relationship fields and documents themselves, have been consolidated in one implementation. The same code is now reused both on the front and the back end, ensuring the same result without the need to introduce additional back end API calls.
4
20
 
5
21
  ### Fixes
6
22
 
7
- * Correct a race condition that can cause a crash at startup when custom `uploadfs` options are present in some specific cloud environments e.g. when using Azure Blob Storage.
23
+ * 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.
24
+ * 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.
25
+ * 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.
26
+ * Default properties of object fields present in a widget now populate correctly even if never focused in the editor.
27
+ * 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.
28
+ * Fix a reordering issue for arrays when dragging and dropping items in the admin UI.
29
+ * The inline array item extract the label now using `title` as `titleField` value by default (consistent with the Slat list).
8
30
 
9
31
  ## 4.7.1 (2024-09-20)
10
32
 
@@ -14,6 +36,10 @@
14
36
 
15
37
  ## 4.7.0 (2024-09-05)
16
38
 
39
+ ### Changes
40
+
41
+ * UI and UX of inline arrays and their table styles
42
+
17
43
  ### Adds
18
44
 
19
45
  * To aid debugging, when a file extension is unacceptable as an Apostrophe attachment the rejected extension is now printed as part of the error message.
@@ -51,6 +77,9 @@ This resolves the issue for new uploads.
51
77
  * Fix widget focus state so that the in-context Add Content menu stays visible during animation.
52
78
  * Fix UI of areas in schemas so that their context menus are layered overtop sibling schema fields UI.
53
79
 
80
+ ### Removes
81
+ * Inline array option for `alwaysOpen` replaced with UI toggles
82
+
54
83
  ## 4.6.0 (2024-08-08)
55
84
 
56
85
  ### Adds
@@ -78,6 +107,7 @@ The shape of the relationship field is still validated.
78
107
 
79
108
  ### Fixes
80
109
 
110
+ * Fixes the rendering of conditional fields in arrays where the `inline: true` option is used.
81
111
  * Fixes the rich text link tool's detection and display of the Remove Link button for removing existing links
82
112
  * Fixes the rich text link tool's detection and display of Apostrophe Page relationship field.
83
113
  * Overriding standard Vue.js components with `editorModal` and `managerModal` are now applied all the time.
package/index.js CHANGED
@@ -300,7 +300,7 @@ async function apostrophe(options, telemetry, rootSpan) {
300
300
  self.apos.schema.validateAllSchemas();
301
301
  self.apos.schema.registerAllSchemas();
302
302
  await self.apos.lock.withLock('@apostrophecms/migration:migrate', async () => {
303
- await self.apos.migration.migrate(); // emits before and after events, inside the lock
303
+ await self.apos.migration.migrate(self.argv);
304
304
  // Inserts the global doc in the default locale if it does not exist; same for other
305
305
  // singleton piece types registered by other modules
306
306
  for (const module of Object.values(self.modules)) {
package/lib/moog.js CHANGED
@@ -170,7 +170,7 @@ module.exports = function(options) {
170
170
  }
171
171
  for (const key of Object.keys(step)) {
172
172
  if (!(validKeys.includes(key) || cascades.includes(key))) {
173
- const message = upgradeHints[key] || `${key} is not a valid top level property for an Apostrophe 3.x module. Make sure you nest regular module options in the new "options" property.`;
173
+ const message = upgradeHints[key] || `${key} is not a valid top level property for an Apostrophe module. Make sure you nest regular module options in the "options" property.`;
174
174
  throw `${clarifyModuleName(step.__meta.name)}: ${message}`;
175
175
  }
176
176
  }
@@ -14,6 +14,56 @@ module.exports = {
14
14
  pageTree: true
15
15
  },
16
16
  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
+
17
67
  return {
18
68
  add: {
19
69
  [`${self.__meta.name}:undo`]: {
@@ -63,7 +113,8 @@ module.exports = {
63
113
  type: 'command-menu-admin-bar-toggle-publish-draft'
64
114
  },
65
115
  shortcut: 'Ctrl+Shift+D Meta+Shift+D'
66
- }
116
+ },
117
+ ...breakpointPreviewModeCommands
67
118
  },
68
119
  modal: {
69
120
  default: {
@@ -80,7 +131,8 @@ module.exports = {
80
131
  label: 'apostrophe:commandMenuMode',
81
132
  commands: [
82
133
  `${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)
84
136
  ]
85
137
  }
86
138
  }
@@ -355,6 +407,13 @@ module.exports = {
355
407
  aposLocale: context.aposLocale,
356
408
  aposDocId: context.aposDocId
357
409
  },
410
+ breakpointPreviewMode: self.apos.asset.options.breakpointPreviewMode ||
411
+ {
412
+ enable: false,
413
+ debug: false,
414
+ resizable: false,
415
+ screens: {}
416
+ },
358
417
  // Base API URL appropriate to the context document
359
418
  contextBar: context && self.apos.doc.getManager(context.type).options.contextBar,
360
419
  showAdminBar: self.getShowAdminBar(req),
@@ -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>
@@ -50,6 +50,13 @@
50
50
  :tooltip="tooltip"
51
51
  :modifiers="modifiers"
52
52
  />
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
+ />
53
60
  </span>
54
61
  </transition-group>
55
62
  </template>
@@ -94,6 +101,15 @@ export default {
94
101
  isUnpublished() {
95
102
  return !this.context.lastPublishedAt;
96
103
  },
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
+ },
97
113
  docTooltip() {
98
114
  return {
99
115
  key: 'apostrophe:lastUpdatedBy',
@@ -142,6 +158,15 @@ export default {
142
158
  },
143
159
  switchDraftMode(mode) {
144
160
  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();
145
170
  }
146
171
  }
147
172
  };
@@ -75,6 +75,7 @@
75
75
  import { createId } from '@paralleldrive/cuid2';
76
76
  import { klona } from 'klona';
77
77
  import AposThemeMixin from 'Modules/@apostrophecms/ui/mixins/AposThemeMixin';
78
+ import newInstance from 'apostrophe/modules/@apostrophecms/schema/lib/newInstance.js';
78
79
 
79
80
  export default {
80
81
  name: 'AposAreaEditor',
@@ -595,21 +596,11 @@ export default {
595
596
  // Return a new widget object in which defaults are fully populated,
596
597
  // especially valid sub-area objects, so that nested edits work on the page
597
598
  newWidget(type) {
599
+ const schema = apos.modules[apos.area.widgetManagers[type]].schema;
598
600
  const widget = {
601
+ ...newInstance(schema),
599
602
  type
600
603
  };
601
- const schema = apos.modules[apos.area.widgetManagers[type]].schema;
602
- schema.forEach(field => {
603
- if (field.type === 'area') {
604
- widget[field.name] = {
605
- _id: createId(),
606
- metaType: 'area',
607
- items: []
608
- };
609
- } else {
610
- widget[field.name] = field.def ? klona(field.def) : field.def;
611
- }
612
- });
613
604
  return widget;
614
605
  }
615
606
  }
@@ -44,7 +44,47 @@ module.exports = {
44
44
  rebundleModules: undefined,
45
45
  // In case of external front end like Astro, this option allows to
46
46
  // 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
+ }
48
88
  },
49
89
 
50
90
  async init(self) {
@@ -11,12 +11,15 @@ module.exports = {
11
11
  'apple-keyboard-shift': 'AppleKeyboardShift',
12
12
  'archive-arrow-down-icon': 'ArchiveArrowDown',
13
13
  'archive-arrow-up-icon': 'ArchiveArrowUp',
14
+ 'arrow-collapse-vertical-icon': 'ArrowCollapseVertical',
14
15
  'arrow-down-icon': 'ArrowDown',
16
+ 'arrow-expand-vertical-icon': 'ArrowExpandVertical',
15
17
  'arrow-left-icon': 'ArrowLeft',
16
18
  'arrow-right-icon': 'ArrowRight',
17
19
  'arrow-up-icon': 'ArrowUp',
18
20
  'binoculars-icon': 'Binoculars',
19
21
  'calendar-icon': 'Calendar',
22
+ 'cellphone-icon': 'Cellphone',
20
23
  'check-all-icon': 'CheckAll',
21
24
  'check-bold-icon': 'CheckBold',
22
25
  'check-circle-icon': 'CheckCircle',
@@ -96,6 +99,7 @@ module.exports = {
96
99
  'menu-down-icon': 'MenuDown',
97
100
  'minus-box-icon': 'MinusBox',
98
101
  'minus-icon': 'Minus',
102
+ 'monitor-icon': 'Monitor',
99
103
  'paperclip-icon': 'Paperclip',
100
104
  'pencil-icon': 'Pencil',
101
105
  'phone-icon': 'Phone',
@@ -105,8 +109,10 @@ module.exports = {
105
109
  'refresh-icon': 'Refresh',
106
110
  'shape-icon': 'Shape',
107
111
  'sign-text-icon': 'SignText',
112
+ 'tablet-icon': 'Tablet',
108
113
  'tag-icon': 'Tag',
109
114
  'text-box-icon': 'TextBox',
115
+ 'text-box-multiple-icon': 'TextBoxMultiple',
110
116
  'text-box-remove-icon': 'TextBoxRemove',
111
117
  'trash-can-icon': 'TrashCan',
112
118
  'trash-can-outline-icon': 'TrashCanOutline',
@@ -1,4 +1,16 @@
1
+ const path = require('path');
2
+
1
3
  module.exports = (options, apos) => {
4
+ const mediaToContainerQueriesLoader = apos.asset.options.breakpointPreviewMode?.enable === true
5
+ ? {
6
+ loader: path.resolve(__dirname, '../media-to-container-queries-loader.js'),
7
+ options: {
8
+ debug: apos.asset.options.breakpointPreviewMode?.debug === true,
9
+ transform: apos.asset.options.breakpointPreviewMode?.transform || null
10
+ }
11
+ }
12
+ : '';
13
+
2
14
  return {
3
15
  module: {
4
16
  rules: [
@@ -6,28 +18,16 @@ module.exports = (options, apos) => {
6
18
  test: /\.css$/,
7
19
  use: [
8
20
  'vue-style-loader',
9
- // https://github.com/vuejs/vue-style-loader/issues/46#issuecomment-670624576
10
- {
11
- loader: 'css-loader',
12
- options: {
13
- esModule: false,
14
- sourceMap: true
15
- }
16
- }
21
+ mediaToContainerQueriesLoader,
22
+ 'css-loader'
17
23
  ]
18
24
  },
19
- // https://github.com/vuejs/vue-style-loader/issues/46#issuecomment-670624576
20
25
  {
21
26
  test: /\.s[ac]ss$/,
22
27
  use: [
23
28
  'vue-style-loader',
24
- {
25
- loader: 'css-loader',
26
- options: {
27
- esModule: false,
28
- sourceMap: true
29
- }
30
- },
29
+ mediaToContainerQueriesLoader,
30
+ 'css-loader',
31
31
  {
32
32
  loader: 'postcss-loader',
33
33
  options: {
@@ -0,0 +1,94 @@
1
+ const postcss = require('postcss');
2
+
3
+ module.exports = function (source) {
4
+ const schema = {
5
+ title: 'Media to Container Queries Loader options',
6
+ type: 'object',
7
+ properties: {
8
+ debug: {
9
+ type: 'boolean'
10
+ },
11
+ transform: {
12
+ anyOf: [
13
+ { type: 'null' },
14
+ { instanceof: 'Function' }
15
+ ]
16
+ }
17
+ }
18
+ };
19
+ const options = this.getOptions(schema);
20
+
21
+ const mediaQueryRegex = /@media[^{]*{([\s\S]*?})\s*(\\n)*}/g;
22
+
23
+ const convertToContainerQuery = (mediaFeature) => {
24
+ // NOTE: media queries does not work with the combo
25
+ // - min-width, max-width, min-height, max-height
26
+ // - lower than equal, greater than equal
27
+ const DESCRIPTORS = [
28
+ 'min-width',
29
+ 'max-width',
30
+ 'min-height',
31
+ 'max-height'
32
+ ];
33
+ const OPERATORS = [
34
+ '>=',
35
+ '<='
36
+ ];
37
+
38
+ const containerFeature = typeof options.transform === 'function'
39
+ ? options.transform(mediaFeature)
40
+ : mediaFeature;
41
+
42
+ if (
43
+ options.debug &&
44
+ DESCRIPTORS.some(descriptor => containerFeature.includes(descriptor)) &&
45
+ OPERATORS.some(operator => containerFeature.includes(operator))
46
+ ) {
47
+ console.warn('[mediaToContainerQueryLoader] Unsupported media query', containerFeature);
48
+ }
49
+
50
+ return containerFeature;
51
+ };
52
+
53
+ // Prepend container query to media queries
54
+ const modifiedSource = source.replace(mediaQueryRegex, (match) => {
55
+ const root = postcss.parse(match.replaceAll(/(?<!\\)\\[frntv]/g, ''));
56
+ root.walkAtRules('media', atRule => {
57
+ if (
58
+ atRule.params.includes('print') &&
59
+ (!atRule.params.includes('all') || !atRule.params.includes('screen'))
60
+ ) {
61
+ return;
62
+ }
63
+
64
+ // Container query
65
+ const containerAtRule = atRule.clone({
66
+ name: 'container',
67
+ params: convertToContainerQuery(atRule.params)
68
+ .replaceAll(/(only\s*)?(all|screen|print)(,)?(\s)*(and\s*)?/g, '')
69
+ });
70
+
71
+ // Media query
72
+ // Only apply when data-breakpoint-preview-mode is not set
73
+ atRule.walkRules(rule => {
74
+ const newRule = rule.clone({
75
+ selectors: rule.selectors.map(selector => {
76
+ if (selector.startsWith('body')) {
77
+ return selector.replace('body', ':where(body:not([data-breakpoint-preview-mode]))');
78
+ }
79
+
80
+ return `:where(body:not([data-breakpoint-preview-mode])) ${selector}`;
81
+ })
82
+ });
83
+
84
+ rule.replaceWith(newRule);
85
+ });
86
+
87
+ root.append(containerAtRule);
88
+ });
89
+
90
+ return root.toString();
91
+ });
92
+
93
+ return modifiedSource;
94
+ };
@@ -1,6 +1,17 @@
1
+ const path = require('path');
1
2
  const MiniCssExtractPlugin = require('mini-css-extract-plugin');
2
3
 
3
4
  module.exports = (options, apos, srcBuildNames) => {
5
+ const mediaToContainerQueriesLoader = apos.asset.options.breakpointPreviewMode?.enable === true
6
+ ? {
7
+ loader: path.resolve(__dirname, '../media-to-container-queries-loader.js'),
8
+ options: {
9
+ debug: apos.asset.options.breakpointPreviewMode?.debug === true,
10
+ transform: apos.asset.options.breakpointPreviewMode?.transform || null
11
+ }
12
+ }
13
+ : '';
14
+
4
15
  return {
5
16
  module: {
6
17
  rules: [
@@ -9,6 +20,7 @@ module.exports = (options, apos, srcBuildNames) => {
9
20
  use: [
10
21
  // Instead of style-loader, to avoid FOUC
11
22
  MiniCssExtractPlugin.loader,
23
+ mediaToContainerQueriesLoader,
12
24
  // Parses CSS imports and make css-loader ignore urls. Urls will still be handled by webpack
13
25
  {
14
26
  loader: 'css-loader',