apostrophe 4.11.2 → 4.12.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.
Files changed (32) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +1 -1
  3. package/modules/@apostrophecms/asset/index.js +7 -1
  4. package/modules/@apostrophecms/asset/lib/globalIcons.js +2 -1
  5. package/modules/@apostrophecms/color-field/index.js +13 -1
  6. package/modules/@apostrophecms/color-field/ui/apos/components/AposColor.vue +66 -21
  7. package/modules/@apostrophecms/color-field/ui/apos/components/AposInputColor.vue +25 -20
  8. package/modules/@apostrophecms/color-field/ui/apos/lib/AposColorHue.vue +1 -1
  9. package/modules/@apostrophecms/color-field/ui/apos/lib/AposColorInfo.vue +62 -0
  10. package/modules/@apostrophecms/color-field/ui/apos/logic/AposInputColor.js +15 -36
  11. package/modules/@apostrophecms/color-field/ui/apos/mixins/AposColorMixin.js +8 -1
  12. package/modules/@apostrophecms/doc-type/ui/apos/logic/AposDocContextMenu.js +8 -3
  13. package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +1 -1
  14. package/modules/@apostrophecms/page/ui/apos/logic/AposPagesManager.js +2 -1
  15. package/modules/@apostrophecms/piece-type/index.js +1 -1
  16. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapColor.vue +11 -52
  17. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapStyles.vue +1 -0
  18. package/modules/@apostrophecms/schema/index.js +209 -79
  19. package/modules/@apostrophecms/schema/lib/addFieldTypes.js +12 -11
  20. package/modules/@apostrophecms/schema/ui/apos/components/AposInputRange.vue +99 -66
  21. package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +3 -1
  22. package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +40 -30
  23. package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +1 -0
  24. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRange.js +27 -16
  25. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputWrapper.js +3 -0
  26. package/modules/@apostrophecms/schema/ui/apos/logic/AposSchema.js +27 -23
  27. package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputChoicesMixin.js +4 -3
  28. package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputMixin.js +10 -4
  29. package/modules/@apostrophecms/ui/ui/apos/components/AposButtonSplit.vue +50 -53
  30. package/modules/@apostrophecms/ui/ui/apos/scss/global/_inputs.scss +3 -2
  31. package/package.json +1 -1
  32. package/test/schemas.js +549 -0
package/CHANGELOG.md CHANGED
@@ -1,10 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.12.0 (2025-01-27)
4
+
5
+ ### Fixes
6
+
7
+ * Fixes ability to change color hue by clicking the color hue bar rather than dragging the indicator.
8
+ * Prevents the rich text control bar from closing while using certain UI within the color picker.
9
+ * Saving a document via the dialog box properly refreshes the main content area when on a "show page" (when the context document is a piece rather than a page)
10
+ * Fixes the `AposButtonSplit` markup to follow the HTML5 specification, optimizes the component performance, visuals and testability.
11
+ * Fixes a case where releationship button overlaps a context menu.
12
+
13
+ ### Adds
14
+
15
+ * Ability to disable the color spectrum UI of a color picker
16
+ * Accessibility improvement for the rich text editor Typography toolbar item.
17
+ * Adds `moduleLabels` prop to `AposDocContextMenu` to pass it to opened modals from custom operations (used by templates to define labels to display on the export modal).
18
+
19
+ ### Changes
20
+
21
+ * Range style updates.
22
+ * The `pickerOptions` sub property of a color field's configuration has been merged with it's parent `options` object.
23
+ * Reworks `inline` and `micro` UI of some fields (color, range, select). Improve global inline style.
24
+ * Makes the range input being a number all the time instead of a string that we convert manually.
25
+ * Command line tasks can run before the first frontend asset build without error messages.
26
+
3
27
  ## 4.11.2 (2024-12-29)
4
28
 
5
29
  ### Fixes
6
30
 
7
31
  * Fixes a bug where images in Media manager are not selectable (click on an image does nothing) in both default and relationship mode.
32
+ * Eliminated superfluous error messages. The convert method now waits for all recursive invocations to complete before attempting to determine if fields are visible.
33
+
34
+ ### Adds
35
+
36
+ * Possibility to set a field not ready when performing async operations, when a field isn't ready, the validation and emit won't occur.
8
37
 
9
38
  ## 4.11.1 (2024-12-18)
10
39
 
@@ -22,7 +51,6 @@
22
51
  * Adds option to disable `tabindex` on `AposToggle` component. A new prop `disableFocus` can be set to `false` to disable the focus on the toggle button. It's enabled by default.
23
52
  * Adds support for event on `addContextOperation`, an option `type` can now be passed and can be `modal` (default) or `event`, in this case it does not try to open a modal but emit a bus event using the action as name.
24
53
 
25
-
26
54
  ### Fixes
27
55
 
28
56
  * Focus properly Widget Editor modals when opened. Keep the previous active focus on the modal when closing the widget editor.
@@ -518,7 +518,7 @@ export default {
518
518
  }
519
519
  }
520
520
 
521
- // Check that refresh hasn't been disbled for this page type
521
+ // Check that refresh hasn't been disabled for this page type
522
522
  const contextOptions = this.context
523
523
  ? apos.modules[this.context.type]
524
524
  : { contentChangedRefresh: true };
@@ -248,6 +248,7 @@ module.exports = {
248
248
  usage: 'Build Apostrophe frontend CSS and JS bundles',
249
249
  afterModuleInit: true,
250
250
  async task(argv = {}) {
251
+ self.inBuildTask = true;
251
252
  if (self.hasBuildModule()) {
252
253
  return self.build(argv);
253
254
  }
@@ -645,9 +646,14 @@ module.exports = {
645
646
  const buildOptions = self.getBuildOptions();
646
647
  const entrypoints = await self.getBuildModule().entrypoints(buildOptions);
647
648
 
649
+ // Command line tasks other than the asset build task do not display a warning
650
+ // if there is no manifest from a previous build attempt as they do not depend
651
+ // on the manifest to succeed
652
+ const silent = self.apos.isTask() && !self.inBuildTask;
648
653
  const {
649
654
  manifest = [], devServerUrl, hmrTypes
650
- } = await self.loadSavedBuildManifest();
655
+ } = await self.loadSavedBuildManifest(silent);
656
+
651
657
  self.currentBuildManifest.devServerUrl = devServerUrl;
652
658
  self.currentBuildManifest.hmrTypes = hmrTypes ?? [];
653
659
 
@@ -61,8 +61,8 @@ module.exports = {
61
61
  'format-list-numbered-icon': 'FormatListNumbered',
62
62
  'format-quote-close-icon': 'FormatQuoteClose',
63
63
  'format-strikethrough-variant-icon': 'FormatStrikethroughVariant',
64
- 'format-superscript-icon': 'FormatSuperscript',
65
64
  'format-subscript-icon': 'FormatSubscript',
65
+ 'format-superscript-icon': 'FormatSuperscript',
66
66
  'format-text-icon': 'FormatText',
67
67
  'format-underline-icon': 'FormatUnderline',
68
68
  'help-circle-icon': 'HelpCircle',
@@ -122,5 +122,6 @@ module.exports = {
122
122
  'unfold-more-horizontal-icon': 'UnfoldMoreHorizontal',
123
123
  'video-icon': 'Video',
124
124
  'view-column-icon': 'ViewColumn',
125
+ 'water-off-icon': 'WaterOff',
125
126
  'web-icon': 'Web'
126
127
  };
@@ -10,6 +10,17 @@ module.exports = {
10
10
  self.name = self.options.name;
11
11
  self.addFieldType();
12
12
  self.enableBrowserData();
13
+ self.defaultOptions = {
14
+ format: 'hex8',
15
+ disableAlpha: false,
16
+ disableFields: false,
17
+ disableSpectrum: false,
18
+ presetColors: [
19
+ '#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321',
20
+ '#417505', '#BD10E0', '#9013FE', '#4A90E2', '#50E3C2',
21
+ '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF'
22
+ ]
23
+ };
13
24
  },
14
25
  methods(self) {
15
26
  return {
@@ -36,7 +47,8 @@ module.exports = {
36
47
  getBrowserData(req) {
37
48
  return {
38
49
  name: self.name,
39
- action: self.action
50
+ action: self.action,
51
+ defaultOptions: self.defaultOptions
40
52
  };
41
53
  }
42
54
  };
@@ -3,14 +3,22 @@
3
3
  role="application"
4
4
  aria-label="Color picker"
5
5
  class="apos-color"
6
- :class="[disableAlpha ? 'apos-color--disable-alpha' : '']"
6
+ :class="[
7
+ disableAlpha ? 'apos-color--disable-alpha' : null,
8
+ disableSpectrum ? 'apos-color--disable-spectrum' : null,
9
+ disableFields ? 'apos-color--disable-fields' : null,
10
+ !presetColors ? 'apos-color--disable-presets' : null
11
+ ]"
7
12
  >
8
- <div class="apos-color__saturation-wrap">
13
+ <div v-if="!disableSpectrum" class="apos-color__saturation-wrap">
9
14
  <Saturation :value="colors" @change="childChange" />
10
15
  </div>
11
- <div class="apos-color__controls">
16
+ <div
17
+ v-if="!(disableSpectrum && disableAlpha)"
18
+ class="apos-color__controls"
19
+ >
12
20
  <div class="apos-color__sliders">
13
- <div class="apos-color__hue-wrap">
21
+ <div v-if="!disableSpectrum" class="apos-color__hue-wrap">
14
22
  <Hue :value="colors" @change="childChange" />
15
23
  </div>
16
24
  <div v-if="!disableAlpha" class="apos-color__alpha-wrap">
@@ -68,6 +76,7 @@
68
76
  </div>
69
77
  </div>
70
78
  <div
79
+ v-if="presetColors"
71
80
  class="apos-color__presets"
72
81
  role="group"
73
82
  aria-label="A color preset, pick one to set as current color"
@@ -103,12 +112,7 @@ import hue from '../lib/AposColorHue.vue';
103
112
  import alpha from '../lib/AposColorAlpha.vue';
104
113
  import checkboard from '../lib/AposColorCheckerboard.vue';
105
114
 
106
- const presetColors = [
107
- '#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321',
108
- '#417505', '#BD10E0', '#9013FE', '#4A90E2', '#50E3C2',
109
- '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF',
110
- 'rgba(0,0,0,0)'
111
- ];
115
+ const defaultOptions = { ...apos.modules['@apostrophecms/color-field'].defaultOptions };
112
116
 
113
117
  export default {
114
118
  name: 'AposColor',
@@ -121,22 +125,57 @@ export default {
121
125
  },
122
126
  mixins: [ colorMixin ],
123
127
  props: {
124
- presetColors: {
125
- type: Array,
128
+ options: {
129
+ type: Object,
126
130
  default() {
127
- return presetColors;
131
+ return defaultOptions;
128
132
  }
129
- },
130
- disableAlpha: {
131
- type: Boolean,
132
- default: false
133
- },
134
- disableFields: {
135
- type: Boolean,
136
- default: false
137
133
  }
138
134
  },
139
135
  computed: {
136
+ defaultOptions() {
137
+ return defaultOptions;
138
+ },
139
+ finalOptions() {
140
+ let final = { ...this.options };
141
+
142
+ // Handle BC `pickerOptions` sub object.
143
+ // Modern API wins out over BC conflicts
144
+ if (final.pickerOptions) {
145
+ final = {
146
+ ...final.pickerOptions,
147
+ ...final
148
+ };
149
+ delete final.pickerOptions;
150
+ }
151
+
152
+ // Normalize disabling presetColors
153
+ if (
154
+ Array.isArray(final.presetColors) &&
155
+ final.presetColors.length === 0
156
+ ) {
157
+ final.presetColors = false;
158
+ }
159
+
160
+ // If `true`, let defaults through
161
+ if (final.presetColors === true) {
162
+ delete final.presetColors;
163
+ }
164
+
165
+ return Object.assign({ ...this.defaultOptions }, final);
166
+ },
167
+ presetColors() {
168
+ return this.finalOptions.presetColors;
169
+ },
170
+ disableAlpha() {
171
+ return this.finalOptions.disableAlpha;
172
+ },
173
+ disableSpectrum() {
174
+ return this.finalOptions.disableSpectrum;
175
+ },
176
+ disableFields() {
177
+ return this.finalOptions.disableFields;
178
+ },
140
179
  hex() {
141
180
  let hex;
142
181
  if (this.colors.a < 1) {
@@ -201,6 +240,12 @@ export default {
201
240
  box-shadow: 0 0 0 1px rgb(0 0 0 / 15%), 0 8px 16px rgb(0 0 0 / 15%);
202
241
  }
203
242
 
243
+ .apos-color--disable-alpha.apos-color--disable-spectrum.apos-color--disable-fields {
244
+ .apos-color__presets {
245
+ border-top: none;
246
+ }
247
+ }
248
+
204
249
  .apos-color__saturation-wrap {
205
250
  position: relative;
206
251
  overflow: hidden;
@@ -8,6 +8,13 @@
8
8
  >
9
9
  <template #body>
10
10
  <div class="apos-input-color">
11
+ <AposColorInfo
12
+ v-if="isInline"
13
+ class="apos-input-color__info apos-input-color__info--inline"
14
+ :value="next"
15
+ :is-micro="isMicro"
16
+ @clear="clear"
17
+ />
11
18
  <div class="apos-input-color__ui">
12
19
  <AposContextMenu
13
20
  :button="buttonOptions"
@@ -18,23 +25,19 @@
18
25
  @close="close"
19
26
  >
20
27
  <AposColor
21
- v-bind="pickerOptions"
28
+ :options="options"
22
29
  :model-value="pickerValue"
23
30
  @update:model-value="update"
24
31
  />
25
32
  </AposContextMenu>
26
33
  </div>
27
- <div class="apos-input-color__info" data-apos-test="colorInfo">
28
- {{ valueLabel }}
29
- <AposButton
30
- v-if="next"
31
- type="quiet"
32
- label="apostrophe:clear"
33
- class="apos-input-color__clear"
34
- :modifiers="['no-motion']"
35
- @click="clear"
36
- />
37
- </div>
34
+ <AposColorInfo
35
+ v-if="!isInline"
36
+ class="apos-input-color__info"
37
+ :value="next"
38
+ :is-micro="isMicro"
39
+ @clear="clear"
40
+ />
38
41
  </div>
39
42
  </template>
40
43
  </AposInputWrapper>
@@ -42,8 +45,13 @@
42
45
 
43
46
  <script>
44
47
  import AposInputColorLogic from '../logic/AposInputColor';
48
+ import AposColorInfo from '../lib/AposColorInfo.vue';
49
+
45
50
  export default {
46
51
  name: 'AposInputColor',
52
+ components: {
53
+ AposColorInfo
54
+ },
47
55
  mixins: [ AposInputColorLogic ]
48
56
  };
49
57
  </script>
@@ -54,16 +62,13 @@ export default {
54
62
  align-items: center;
55
63
  }
56
64
 
57
- .apos-input-color__clear {
58
- margin-left: 10px;
59
- }
60
-
61
65
  .apos-input-color__info {
62
- @include type-base;
66
+ margin-left: 15px;
63
67
 
64
- & {
65
- margin-left: 15px;
66
- color: var(--a-text-primary);
68
+ &--inline {
69
+ margin-right: 5px;
70
+ margin-left: 0;
67
71
  }
68
72
  }
73
+
69
74
  </style>
@@ -2,7 +2,7 @@
2
2
  <div class="apos-color__hue" :class="[directionClass]">
3
3
  <div
4
4
  ref="container"
5
- class="vc-hue-container"
5
+ class="apos-color__hue-container"
6
6
  role="slider"
7
7
  :aria-valuenow="colors.hsl.h"
8
8
  aria-valuemin="0"
@@ -0,0 +1,62 @@
1
+ <template>
2
+ <div class="apos-color-info" data-apos-test="colorInfo">
3
+ <span v-if="!isMicro">
4
+ {{ label }}
5
+ </span>
6
+ <AposButton
7
+ v-if="!isMicro && value"
8
+ type="quiet"
9
+ :label="'apostrophe:clear'"
10
+ class="apos-color-info__clear-btn"
11
+ :modifiers="['no-motion']"
12
+ @click="emit('clear')"
13
+ />
14
+ <AposIndicator
15
+ v-else-if="value"
16
+ class="apos-color-info__clear-icon"
17
+ icon="water-off-icon"
18
+ @click="emit('clear')"
19
+ />
20
+ </div>
21
+ </template>
22
+
23
+ <script setup>
24
+ import { computed } from 'vue';
25
+
26
+ const props = defineProps({
27
+ value: {
28
+ type: String,
29
+ default: null
30
+ },
31
+ isMicro: {
32
+ type: Boolean,
33
+ default: false
34
+ }
35
+ });
36
+
37
+ const label = computed(() => props.value || 'None Selected');
38
+ const emit = defineEmits([ 'clear' ]);
39
+ </script>
40
+
41
+ <style lang="scss">
42
+ .apos-color-info {
43
+ @include type-base;
44
+
45
+ & {
46
+ color: var(--a-text-primary);
47
+ }
48
+ }
49
+
50
+ .apos-color-info__clear-btn {
51
+ margin-left: 10px;
52
+ }
53
+
54
+ .apos-color-info__clear-icon {
55
+ cursor: pointer;
56
+ color: var(--a-base-2);
57
+
58
+ &:hover {
59
+ color: var(--a-primary);
60
+ }
61
+ }
62
+ </style>
@@ -12,24 +12,22 @@ export default {
12
12
  data() {
13
13
  return {
14
14
  active: false,
15
- tinyColorObj: null,
16
- startsNull: false,
17
- defaultFormat: 'hex8',
18
- defaultPickerOptions: {
19
- presetColors: [
20
- '#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321',
21
- '#417505', '#BD10E0', '#9013FE', '#4A90E2', '#50E3C2',
22
- '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF'
23
- ],
24
- disableAlpha: false,
25
- disableFields: false
26
- }
15
+ tinyColorObj: null
27
16
  };
28
17
  },
29
18
  computed: {
30
- // Color picker doesn't allow null or undefined values
19
+ isMicro() {
20
+ return this.modifiers.includes('micro');
21
+ },
22
+ isInline() {
23
+ return this.modifiers.includes('inline');
24
+ },
31
25
  pickerValue() {
32
- return this.next || '';
26
+ return this.next || this.defaultValue;
27
+ },
28
+ // Color picker doesn't allow null or undefined values
29
+ defaultValue() {
30
+ return this.field.def || '';
33
31
  },
34
32
  buttonOptions() {
35
33
  return {
@@ -38,22 +36,8 @@ export default {
38
36
  color: this.modelValue.data || ''
39
37
  };
40
38
  },
41
- format() {
42
- return this.field.options && this.field.options.format
43
- ? this.field.options.format
44
- : this.defaultFormat;
45
- },
46
- pickerOptions() {
47
- const fieldOptions = this.field.options?.pickerOptions || {};
48
- return Object.assign(this.defaultPickerOptions, fieldOptions);
49
- },
50
-
51
- valueLabel() {
52
- if (this.next) {
53
- return this.next;
54
- } else {
55
- return 'None Selected';
56
- }
39
+ options() {
40
+ return this.field?.options || {};
57
41
  },
58
42
  classList() {
59
43
  return [
@@ -75,12 +59,7 @@ export default {
75
59
  this.active = false;
76
60
  },
77
61
  update(value) {
78
- this.tinyColorObj = new TinyColor(value.hsl);
79
- if (value._cssVariable) {
80
- this.next = value._cssVariable;
81
- } else {
82
- this.next = this.tinyColorObj.toString(this.format);
83
- }
62
+ this.next = value;
84
63
  },
85
64
  validate(value) {
86
65
  if (this.field.required) {
@@ -82,8 +82,15 @@ export default {
82
82
  return this.val;
83
83
  },
84
84
  set(newVal) {
85
+ let stringVal;
86
+ if (newVal._cssVariable) {
87
+ stringVal = newVal._cssVariable;
88
+ } else {
89
+ const colorObj = tinycolor(newVal.hsl);
90
+ stringVal = colorObj.toString(this.finalOptions.format);
91
+ }
85
92
  this.val = newVal;
86
- this.$emit('update:modelValue', newVal);
93
+ this.$emit('update:modelValue', stringVal);
87
94
  }
88
95
  }
89
96
  },
@@ -87,6 +87,10 @@ export default {
87
87
  localeSwitched: {
88
88
  type: Boolean,
89
89
  default: false
90
+ },
91
+ moduleLabels: {
92
+ type: Object,
93
+ default: null
90
94
  }
91
95
  },
92
96
  emits: [ 'menu-open', 'menu-close', 'close' ],
@@ -207,7 +211,8 @@ export default {
207
211
 
208
212
  ifProps = ifProps || {};
209
213
  moduleIf = moduleIf || {};
210
- const canSeeOperation = checkIfConditions(this.doc, ifProps) && checkIfConditions(this.moduleOptions, moduleIf);
214
+ const canSeeOperation = checkIfConditions(this.doc, ifProps) &&
215
+ checkIfConditions(this.moduleOptions, moduleIf);
211
216
 
212
217
  if (!canSeeOperation) {
213
218
  return false;
@@ -219,9 +224,8 @@ export default {
219
224
  moduleName() {
220
225
  if (apos.modules[this.context.type].action === apos.modules['@apostrophecms/page'].action) {
221
226
  return '@apostrophecms/page';
222
- } else {
223
- return this.context.type;
224
227
  }
228
+ return this.context.type;
225
229
  },
226
230
  moduleOptions() {
227
231
  return apos.modules[this.moduleName];
@@ -446,6 +450,7 @@ export default {
446
450
  }
447
451
  const props = {
448
452
  moduleName: operation.moduleName || this.moduleName,
453
+ moduleLabels: this.moduleLabels,
449
454
  // For backwards compatibility
450
455
  doc,
451
456
  ...docProps(doc),
@@ -135,7 +135,7 @@ export default {
135
135
  },
136
136
  checkedTypes: {
137
137
  type: Array,
138
- default: () => []
138
+ default: null
139
139
  },
140
140
  checkedCount: {
141
141
  type: Number,
@@ -118,7 +118,8 @@ export default {
118
118
  return this.moduleOptions.canCreate;
119
119
  },
120
120
  checkedTypes() {
121
- return this.checkedDocs.map(doc => doc.type);
121
+ const types = this.checkedDocs.map(doc => doc.type);
122
+ return [ ...new Set(types) ];
122
123
  }
123
124
  },
124
125
  created() {
@@ -1151,7 +1151,7 @@ module.exports = {
1151
1151
  browserOptions.showArchive = self.options.showArchive;
1152
1152
  browserOptions.showDiscardDraft = self.options.showDiscardDraft;
1153
1153
  browserOptions.canDeleteDraft = self.apos.permission.can(req, 'delete', self.name, 'draft');
1154
-
1154
+ browserOptions.contentChangedRefresh = self.options.contentChangedRefresh !== false;
1155
1155
  _.defaults(browserOptions, {
1156
1156
  components: {}
1157
1157
  });