apostrophe 4.17.1-alpha.3 → 4.18.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 (46) hide show
  1. package/.eslintignore +1 -0
  2. package/.stylelintignore +4 -0
  3. package/CHANGELOG.md +21 -0
  4. package/lib/universal/check-if-conditions.mjs +209 -0
  5. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +5 -2
  6. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBreakpointPreviewMode.vue +77 -30
  7. package/modules/@apostrophecms/area/ui/apos/components/AposWidgetControls.vue +1 -1
  8. package/modules/@apostrophecms/doc-type/ui/apos/logic/AposDocContextMenu.js +1 -1
  9. package/modules/@apostrophecms/express/index.js +6 -0
  10. package/modules/@apostrophecms/i18n/i18n/de.json +9 -1
  11. package/modules/@apostrophecms/i18n/i18n/en.json +8 -0
  12. package/modules/@apostrophecms/i18n/i18n/es.json +8 -0
  13. package/modules/@apostrophecms/i18n/i18n/fr.json +8 -0
  14. package/modules/@apostrophecms/i18n/i18n/it.json +8 -0
  15. package/modules/@apostrophecms/i18n/i18n/pt-BR.json +8 -0
  16. package/modules/@apostrophecms/i18n/i18n/sk.json +8 -0
  17. package/modules/@apostrophecms/image-widget/index.js +130 -4
  18. package/modules/@apostrophecms/image-widget/views/fragment.html +89 -0
  19. package/modules/@apostrophecms/image-widget/views/widget.html +7 -34
  20. package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +14 -4
  21. package/modules/@apostrophecms/module/index.js +5 -0
  22. package/modules/@apostrophecms/rich-text-widget/index.js +80 -7
  23. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposImageControlDialog.vue +164 -12
  24. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +8 -5
  25. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapDivider.vue +3 -1
  26. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapLink.vue +42 -3
  27. package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Image.js +220 -17
  28. package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Link.js +5 -0
  29. package/modules/@apostrophecms/schema/index.js +104 -76
  30. package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +1 -0
  31. package/modules/@apostrophecms/schema/ui/apos/components/AposSearchList.vue +43 -67
  32. package/modules/@apostrophecms/schema/ui/apos/lib/conditionalFields.js +47 -57
  33. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputArray.js +34 -7
  34. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputObject.js +19 -1
  35. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputSlug.js +17 -2
  36. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputString.js +20 -2
  37. package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputConditionalFieldsMixin.js +0 -1
  38. package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputFollowingMixin.js +6 -1
  39. package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +5 -0
  40. package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenu.vue +25 -9
  41. package/modules/@apostrophecms/ui/ui/apos/scss/global/_breakpoint_preview.scss +9 -2
  42. package/modules/@apostrophecms/ui/ui/apos/scss/global/_inputs.scss +75 -3
  43. package/package.json +3 -3
  44. package/test/express.js +136 -1
  45. package/test/schema-conditions.js +1107 -0
  46. package/lib/check-if-conditions.js +0 -62
package/.eslintignore CHANGED
@@ -3,3 +3,4 @@
3
3
  **/node_modules
4
4
  test/public
5
5
  test/apos-build
6
+ coverage
@@ -0,0 +1,4 @@
1
+ **/node_modules
2
+ test/public
3
+ test/apos-build
4
+ coverage
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.18.0 (2025-06-11)
4
+
5
+ ### Adds
6
+
7
+ * Adds MongoDB-style support (comparison operators) for conditional fields and all systems that use conditions. Conditional fields now have access to the `following` values from the parent schema fields.
8
+ * Add `followingIgnore` option to the `string` field schema. A boolean `true` results in all `following` values being ignored (not attempted to be used as a value for the field). When array of strings, the UI will ignore every item that matches a `following` field name.
9
+ * Adds link configuration to the `@apostrophecms/image-widget` UI and a new option `linkWithType` to control what document types can be linked to. Opt-out of the widget inline styles (reset) by setting `inlineStyles: false` in the widget configuration or contextual options (area).
10
+ * Use the link configuration of the Rich Text widget for image links too. It respects the existing `linkWithType` Rich Text option and uses the same schema (`linkFields`) used for text links. The fields from that schema can opt-in for specific tiptap extension now via a field property `extensions` (array) with possible array values `Link` and/or `Image`. You still need to specify the `htmlAttribute` property (the name of the attribute to be added to the link tag) in the schema when adding more fields. If the `extensions` property is not set, the field will be applied for both tiptap extensions.
11
+ * Adds body style support for breakpoint preview mode. Created new `[data-apos-refreshable-body]` div inside the container during breapoint preview.
12
+ Switch body attributes to this new div to keep supporting body styles in breakpoint preview mode.
13
+
14
+ ### Changes
15
+
16
+ * Set the `Cache-Control` header to `no-store` for error pages in order to prevent the risk of serving stale error pages to users.
17
+ * Updates rich-text default configuration.
18
+
19
+ ### Fixes
20
+
21
+ * The Download links in the media library now immediately download the file as expected, rather than navigating to the image in the current tab. `AposButton` now supports the `:download="true"` prop as expected.
22
+ * Using an API key with the editor, contributor or guest role now have a `req` object with the corresponding rights. The old behavior gave non-admin API keys less access than expected.
23
+
3
24
  ## 4.17.1 (2025-05-16)
4
25
 
5
26
  ### Fixes
@@ -0,0 +1,209 @@
1
+
2
+ // NOTE: This is a universal library for evaluating MongoDB-style conditions
3
+ // against a document or sub-document. Do not use any browser or node specific
4
+ // APIs here.
5
+ //
6
+ //
7
+ // Evaluate a field conditions against current schema values (doc or sub-doc).
8
+ // Expects a `conditions` object containing MongoDB-style conditions.
9
+ // Operators `$or` and `$and` are supported as top-level keys to allow
10
+ // for complex logical conditions. All comparison MongoDB operators are supported
11
+ // as object values.
12
+ // The condition object properties are field paths (dot notation is supported
13
+ // for objects), and the values are the conditions to evaluate against the
14
+ // document values.
15
+ // The `voterFn` function can be provided for evaluating
16
+ // conditions - return `false` to stop further evaluation.
17
+ // The function is called with three arguments:
18
+ // `voterFn(propName, condition, docValue)` where `propName` is the condition field path,
19
+ // `condition` is the condition value, and `docValue` is the field value extracted
20
+ // from the document.
21
+ // Example usage:
22
+ // ```js
23
+ // doc = {
24
+ // assignee: 'john',
25
+ // status: 'active',
26
+ // stats: { count: 15 },
27
+ // category: 'news'
28
+ // };
29
+ // const conditions = {
30
+ // assignee: { '$exists': true },
31
+ // status: { '$in': ['active', 'pending'] },
32
+ // 'stats.count': { '$gte': 10 },
33
+ // $or: [
34
+ // { 'category': 'news' },
35
+ // { 'category': 'blog' }
36
+ // ]
37
+ // };
38
+ // function voterFn(propName, condition, docValue) {
39
+ // if (propName === 'status' && docValue === 'inactive') {
40
+ // return false; // Reject the condition for 'status' if it's 'inactive'
41
+ // }
42
+ // // Non-boolean return values are ignored
43
+ // }
44
+ // const result = checkIfConditions(doc, conditions, voterFn);
45
+ // ```
46
+ export default function checkIfConditions(doc, conditions, voterFn = () => null) {
47
+ return Object.entries(conditions).every(([ key, condition ]) => {
48
+ if (key === '$or') {
49
+ return checkOrConditions(doc, condition, voterFn);
50
+ }
51
+
52
+ if (key === '$and') {
53
+ return checkAndConditions(doc, condition, voterFn);
54
+ }
55
+
56
+ const docValue = getNestedPropValue(doc, key);
57
+
58
+ // If the custom voter rejects the condition, we stop evaluating
59
+ // and return false immediately.
60
+ if (voterFn(key, condition, docValue) === false) {
61
+ return false;
62
+ }
63
+
64
+ // External conditions should be handled outside of this function,
65
+ // so we skip them here. Use the `voterFn` to gather external condition
66
+ // entries and evaluate them separately.
67
+ if (isExternalCondition(key)) {
68
+ return true;
69
+ }
70
+
71
+ // Otherwise, we evaluate the condition against the document value.
72
+ return evaluate(docValue, condition);
73
+ });
74
+ }
75
+
76
+ export function isExternalCondition(conditionKey) {
77
+ return conditionKey.endsWith(')');
78
+ }
79
+
80
+ function checkOrConditions(doc, conditions, voterFn) {
81
+ return conditions.some((condition) => {
82
+ return checkIfConditions(doc, condition, voterFn);
83
+ });
84
+ }
85
+
86
+ function checkAndConditions(doc, conditions, voterFn) {
87
+ return conditions.every((condition) => {
88
+ return checkIfConditions(doc, condition, voterFn);
89
+ });
90
+ }
91
+
92
+ function getNestedPropValue(doc, key) {
93
+ if (!key.includes('.')) {
94
+ return doc?.[key];
95
+ }
96
+
97
+ const keys = key.split('.');
98
+ let currentValue = doc;
99
+ while (keys.length > 0) {
100
+ if (
101
+ // The `==` comparison is intentionally used here to match
102
+ // both `null` and `undefined`
103
+ currentValue == null ||
104
+ // Support i.e. `stringField.length`
105
+ // eslint-disable-next-line valid-typeof
106
+ typeof currentValue?.[keys[0]] == null
107
+ ) {
108
+ return undefined;
109
+ }
110
+ if (Array.isArray(currentValue)) {
111
+ // Support i.e. `arrayField.length` or `arrayField.0`
112
+ if (!Object.hasOwn(currentValue, keys[0])) {
113
+ return currentValue.flatMap(item => {
114
+ return getNestedPropValue(item, keys.join('.'));
115
+ });
116
+ }
117
+ }
118
+ const prop = keys.shift();
119
+ currentValue = currentValue[prop];
120
+ }
121
+
122
+ return currentValue;
123
+ }
124
+
125
+ // Comparison operators registry for MongoDB-style conditions.
126
+ // https://www.mongodb.com/docs/manual/reference/operator/query-comparison/
127
+ const opRegistry = {};
128
+ opRegistry.$eq = (docValue, conditionValue) => {
129
+ if (Array.isArray(docValue)) {
130
+ if (Array.isArray(conditionValue)) {
131
+ // Unlike MongoDB, we don't match the index order of the arrays.
132
+ return docValue.length === conditionValue.length &&
133
+ conditionValue.every(value => docValue.includes(value));
134
+ }
135
+ return docValue.includes(conditionValue);
136
+ }
137
+ return docValue === conditionValue;
138
+ };
139
+ opRegistry.$ne = (docValue, conditionValue) => (
140
+ opRegistry.$eq(docValue, conditionValue) === false
141
+ );
142
+ opRegistry.$exists = (docValue, conditionValue) => {
143
+ // Per MongoDB documentation, $exists should treat null and undefined the same.
144
+ // == null and != null are documented to match or reject null, undefined
145
+ // and nothing else.
146
+ return (conditionValue ? docValue != null : docValue == null);
147
+ };
148
+ opRegistry.$in = (docValue, conditionValue) => {
149
+ if (!Array.isArray(conditionValue)) {
150
+ throw new Error('$in and $nin operators require an array as condition value');
151
+ }
152
+ if (Array.isArray(docValue)) {
153
+ return conditionValue.some(value => docValue.includes(value));
154
+ }
155
+ return conditionValue.includes(docValue);
156
+ };
157
+ opRegistry.$nin = (docValue, conditionValue) => (
158
+ opRegistry.$in(docValue, conditionValue) === false
159
+ );
160
+ opRegistry.$gt = (docValue, conditionValue) => (
161
+ docValue > conditionValue
162
+ );
163
+ opRegistry.$lt = (docValue, conditionValue) => (
164
+ docValue < conditionValue
165
+ );
166
+ opRegistry.$gte = (docValue, conditionValue) => (
167
+ docValue >= conditionValue
168
+ );
169
+ opRegistry.$lte = (docValue, conditionValue) => (
170
+ docValue <= conditionValue
171
+ );
172
+
173
+ export function evaluate(docValue, conditionValue) {
174
+ if (
175
+ typeof conditionValue === 'object' &&
176
+ conditionValue !== null
177
+ ) {
178
+ // Empty objects are not valid conditions
179
+ if (Object.keys(conditionValue).length === 0) {
180
+ return false;
181
+ }
182
+ // Evaluate every operator in the conditionValue object.
183
+ // A MongoDB-style condition object is expected.
184
+ // We check for the presence of known comparison operators and
185
+ // evaluate them accordingly.
186
+ return Object.entries(conditionValue).every(([ operator, operand ]) => {
187
+ switch (operator) {
188
+ // BC, support the min and max properties because they were already supported
189
+ // in a different routine.
190
+ case 'min':
191
+ return docValue >= operand;
192
+ case 'max':
193
+ return docValue <= operand;
194
+ default: {
195
+ if (opRegistry[operator]) {
196
+ return opRegistry[operator](docValue, operand);
197
+ }
198
+ throw new Error(`Unsupported operator: ${operator}`);
199
+ }
200
+ }
201
+ });
202
+ }
203
+
204
+ if (Array.isArray(docValue)) {
205
+ return docValue.includes(conditionValue);
206
+ }
207
+
208
+ return conditionValue === docValue;
209
+ }
@@ -551,7 +551,9 @@ export default {
551
551
  }
552
552
  },
553
553
  async refresh(options = {}) {
554
- const refreshable = document.querySelector('[data-apos-refreshable]');
554
+ // In breakpoint preview mode, uses the fake body.
555
+ const refreshable = document.querySelector('[data-apos-refreshable-body]') ||
556
+ document.querySelector('[data-apos-refreshable]');
555
557
  if (options.scrollcheck) {
556
558
  window.apos.adminBar.scrollPosition = {
557
559
  x: window.scrollX,
@@ -613,7 +615,8 @@ export default {
613
615
  // "@ notation" PATCH feature. Sort the areas by DOM depth
614
616
  // to ensure parents patch before children
615
617
  this.original = {};
616
- const els = Array.from(document.querySelectorAll('[data-apos-area-newly-editable]')).filter(el => el.getAttribute('data-doc-id') === this.context._id);
618
+ const els = Array.from(document.querySelectorAll('[data-apos-area-newly-editable]'))
619
+ .filter(el => el.getAttribute('data-doc-id') === this.context._id);
617
620
  els.sort((a, b) => {
618
621
  const da = depth(a);
619
622
  const db = depth(b);
@@ -56,7 +56,6 @@
56
56
  </div>
57
57
  </template>
58
58
  <script>
59
-
60
59
  export default {
61
60
  name: 'TheAposContextBreakpointPreviewMode',
62
61
  props: {
@@ -88,7 +87,10 @@ export default {
88
87
  originalBodyBackground: null,
89
88
  shortcuts: this.getShortcuts(),
90
89
  breakpoints: this.getBreakpointItems(),
91
- showDropdown: false
90
+ showDropdown: false,
91
+ bodyEl: null,
92
+ refreshableBodyEl: null,
93
+ observer: new MutationObserver(this.observerCallback)
92
94
  };
93
95
  },
94
96
  computed: {
@@ -130,6 +132,7 @@ export default {
130
132
  }
131
133
  },
132
134
  mounted() {
135
+ this.bodyEl = document.querySelector('body');
133
136
  this.setShowDropdown();
134
137
  apos.bus.$on(
135
138
  'command-menu-admin-bar-toggle-breakpoint-preview-mode',
@@ -151,18 +154,56 @@ export default {
151
154
  );
152
155
  },
153
156
  methods: {
157
+ observerCallback(mutationList, observer) {
158
+ for (const mutation of mutationList) {
159
+ if (
160
+ mutation.type !== 'attributes' ||
161
+ mutation.attributeName.startsWith('data-apos') ||
162
+ mutation.attributeName === 'data-breakpoint-preview-mode'
163
+ ) {
164
+ continue;
165
+ }
166
+ const bodyAttribute = mutation.target
167
+ .getAttribute(mutation.attributeName);
168
+ this.refreshableBodyEl.setAttribute(mutation.attributeName, bodyAttribute);
169
+ }
170
+ },
171
+
172
+ createFakeBody(refreshableEl) {
173
+ this.refreshableBodyEl = document.createElement('div');
174
+ this.refreshableBodyEl.setAttribute('data-apos-refreshable-body', '');
175
+ Array.from(refreshableEl.childNodes).forEach(child => {
176
+ this.refreshableBodyEl.append(child);
177
+ });
178
+
179
+ Array.from(this.bodyEl.attributes || {})
180
+ .filter(({ name }) => !name.startsWith('data-apos'))
181
+ .forEach(({ name, value }) => {
182
+ this.refreshableBodyEl.setAttribute(name, value);
183
+ });
184
+
185
+ refreshableEl.append(this.refreshableBodyEl);
186
+ },
187
+
154
188
  switchBreakpointPreviewMode({
155
189
  mode,
156
190
  label,
157
191
  width,
158
192
  height
159
193
  }) {
160
- document.querySelector('body').setAttribute('data-breakpoint-preview-mode', mode);
161
- document.querySelector('[data-apos-refreshable]').setAttribute('data-resizable', this.resizable);
162
- document.querySelector('[data-apos-refreshable]').setAttribute('data-label', this.$t(label));
163
- document.querySelector('[data-apos-refreshable]').style.width = width;
164
- document.querySelector('[data-apos-refreshable]').style.height = height;
165
- document.querySelector('[data-apos-refreshable]').style.background = this.originalBodyBackground;
194
+ const refreshableEl = document.querySelector('[data-apos-refreshable]');
195
+
196
+ // Only when switching to mobile preview from the normal state
197
+ if (!this.mode) {
198
+ this.createFakeBody(refreshableEl);
199
+ this.observer.observe(this.bodyEl, { attributes: true });
200
+ }
201
+
202
+ this.bodyEl.setAttribute('data-breakpoint-preview-mode', mode);
203
+ refreshableEl.setAttribute('data-resizable', this.resizable);
204
+ refreshableEl.setAttribute('data-label', this.$t(label));
205
+ refreshableEl.style.width = width;
206
+ refreshableEl.style.height = height;
166
207
 
167
208
  this.mode = mode;
168
209
  this.$emit('switch-breakpoint-preview-mode', {
@@ -178,33 +219,39 @@ export default {
178
219
  height
179
220
  });
180
221
  },
181
- toggleBreakpointPreviewMode({
182
- mode,
183
- label,
184
- width,
185
- height
186
- }) {
187
- if (this.mode === mode || mode === null) {
188
- document.querySelector('body').removeAttribute('data-breakpoint-preview-mode');
189
- document.querySelector('[data-apos-refreshable]').removeAttribute('data-resizable');
190
- document.querySelector('[data-apos-refreshable]').removeAttribute('data-label');
191
- document.querySelector('[data-apos-refreshable]').style.removeProperty('width');
192
- document.querySelector('[data-apos-refreshable]').style.removeProperty('height');
193
- document.querySelector('[data-apos-refreshable]').style.removeProperty('background');
194
-
195
- this.mode = null;
196
- this.$emit('reset-breakpoint-preview-mode');
197
- this.saveState({ mode: this.mode });
222
+ toggleBreakpointPreviewMode(state) {
223
+ if (this.mode === state.mode || state.mode === null) {
224
+ this.resetBreakpointPreview();
225
+ return;
226
+ }
227
+
228
+ this.switchBreakpointPreviewMode(state);
229
+ },
230
+ resetBreakpointPreview() {
231
+ const refreshableEl = document.querySelector('[data-apos-refreshable]');
198
232
 
233
+ this.observer.disconnect();
234
+ if (!this.refreshableBodyEl) {
199
235
  return;
200
236
  }
201
237
 
202
- this.switchBreakpointPreviewMode({
203
- mode,
204
- label,
205
- width,
206
- height
238
+ Array.from(this.refreshableBodyEl.childNodes).forEach(child => {
239
+ if (child.nodeType !== Node.TEXT_NODE || child.nodeValue.trim()) {
240
+ refreshableEl.append(child);
241
+ }
207
242
  });
243
+ this.refreshableBodyEl.remove();
244
+ this.refreshableBodyEl = null;
245
+
246
+ this.bodyEl.removeAttribute('data-breakpoint-preview-mode');
247
+ refreshableEl.removeAttribute('data-resizable');
248
+ refreshableEl.removeAttribute('data-label');
249
+ refreshableEl.style.removeProperty('width');
250
+ refreshableEl.style.removeProperty('height');
251
+
252
+ this.mode = null;
253
+ this.$emit('reset-breakpoint-preview-mode');
254
+ this.saveState({ mode: this.mode });
208
255
  },
209
256
  loadState() {
210
257
  return JSON.parse(sessionStorage.getItem('aposBreakpointPreviewMode') || '{}');
@@ -35,7 +35,7 @@
35
35
 
36
36
  <script>
37
37
 
38
- import checkIfConditions from 'apostrophe/lib/check-if-conditions';
38
+ import checkIfConditions from 'apostrophe/lib/universal/check-if-conditions.mjs';
39
39
 
40
40
  export default {
41
41
  props: {
@@ -2,7 +2,7 @@ import { detectDocChange } from 'Modules/@apostrophecms/schema/lib/detectChange'
2
2
  import AposPublishMixin from 'Modules/@apostrophecms/ui/mixins/AposPublishMixin';
3
3
  import AposArchiveMixin from 'Modules/@apostrophecms/ui/mixins/AposArchiveMixin';
4
4
  import AposModifiedMixin from 'Modules/@apostrophecms/ui/mixins/AposModifiedMixin';
5
- import checkIfConditions from 'apostrophe/lib/check-if-conditions';
5
+ import checkIfConditions from 'apostrophe/lib/universal/check-if-conditions.mjs';
6
6
 
7
7
  export default {
8
8
  name: 'AposDocContextMenu',
@@ -315,6 +315,12 @@ module.exports = {
315
315
  const info = self.options.apiKeys[key];
316
316
  if (info.role === 'admin') {
317
317
  taskReq = self.apos.task.getReq();
318
+ } else if (info.role === 'editor') {
319
+ taskReq = self.apos.task.getEditorReq();
320
+ } else if (info.role === 'contributor') {
321
+ taskReq = self.apos.task.getContributorReq();
322
+ } else if (info.role === 'guest') {
323
+ taskReq = self.apos.task.getGuestReq();
318
324
  } else {
319
325
  taskReq = self.apos.task.getAnonReq();
320
326
  }
@@ -251,7 +251,11 @@
251
251
  "lastUpdatedBy": "Zuletzt gespeichert um {{ updatedAt }} <br /> von {{ updatedBy }}",
252
252
  "leavePageDescription": "Der Inhalt, den du bearbeiten möchtest, gehört zu einem anderen Dokument und muss darin geändert werden.\nÄnderungen an {{ oldTitle }} werden automatisch gespeichert.",
253
253
  "leavePageHeading": "{{ oldTitle }} verlassen um {{ newTitle }} zu bearbeiten?",
254
+ "linkHrefHelp": "Geben Sie die vollständige URL ein, beginnend mit https:// oder http://",
254
255
  "linkTarget": "Link-Ziel",
256
+ "linkTitle": "Linktitel",
257
+ "linkTitleRelHelp": "Wenn leer, wird der Titel des zugehörigen Dokuments verwendet.",
258
+ "linkTitleUrlHelp": "Wenn leer, wird die Bildunterschrift verwendet.",
255
259
  "linkTo": "Verlinken zu...",
256
260
  "listJoiner": ", ",
257
261
  "live": "Live",
@@ -455,11 +459,15 @@
455
459
  "richTextBulletedList": "Liste",
456
460
  "richTextCodeBlock": "Code-Block",
457
461
  "richTextColor": "Farbe",
462
+ "richTextH1": "Überschrift 1",
458
463
  "richTextH2": "Überschrift 2",
459
464
  "richTextH3": "Überschrift 3",
460
465
  "richTextH4": "Überschrift 4",
466
+ "richTextH5": "Überschrift 5",
467
+ "richTextH6": "Überschrift 6",
461
468
  "richTextHighlight": "Hervorheben",
462
469
  "richTextHorizontalRule": "Horizontale Linie",
470
+ "richTextHorizontalRuleDescription": "Fügen Sie einen horizontalen Trenner hinzu",
463
471
  "richTextInsertMenuHeading": "Element einfügen...",
464
472
  "richTextItalic": "Kursiv",
465
473
  "richTextLink": "Link",
@@ -469,7 +477,7 @@
469
477
  "richTextNodeMultipleStyles": "Mehrere blocks...",
470
478
  "richTextNodeStyles": "Block Stile",
471
479
  "richTextOrderedList": "Nummerierte Liste",
472
- "richTextParagraph": "Paragraph",
480
+ "richTextParagraph": "Absatz",
473
481
  "richTextPlaceholder": "Beginne dein Text hier...",
474
482
  "richTextPlaceholderWithInsertMenu": "Beginnen Sie zu tippen oder drücken Sie '/' für Befehle...",
475
483
  "richTextRedo": "Wiederholen",
@@ -251,7 +251,11 @@
251
251
  "lastUpdatedBy": "Last saved on {{ updatedAt }} <br /> by {{ updatedBy }}",
252
252
  "leavePageDescription": "The content you're trying to edit belongs to another document and must be edited there.\nChanges made to {{ oldTitle }} are saved automatically.",
253
253
  "leavePageHeading": "Leave {{ oldTitle }} to edit {{ newTitle }}?",
254
+ "linkHrefHelp": "Write the full URL, beginning with https:// or http://",
254
255
  "linkTarget": "Link Target",
256
+ "linkTitle": "Link Title",
257
+ "linkTitleRelHelp": "If empty, the title of the related document will be used.",
258
+ "linkTitleUrlHelp": "If empty, the caption of the image will be used.",
255
259
  "linkTo": "Link To...",
256
260
  "listJoiner": ", ",
257
261
  "live": "Live",
@@ -455,11 +459,15 @@
455
459
  "richTextBulletedList": "Bulleted List",
456
460
  "richTextCodeBlock": "Code Block",
457
461
  "richTextColor": "Color",
462
+ "richTextH1": "Heading 1 (H1)",
458
463
  "richTextH2": "Heading 2 (H2)",
459
464
  "richTextH3": "Heading 3 (H3)",
460
465
  "richTextH4": "Heading 4 (H4)",
466
+ "richTextH5": "Heading 5 (H5)",
467
+ "richTextH6": "Heading 6 (H6)",
461
468
  "richTextHighlight": "Mark",
462
469
  "richTextHorizontalRule": "Horizontal Rule",
470
+ "richTextHorizontalRuleDescription": "Add a horizontal separator",
463
471
  "richTextInsertMenuHeading": "Insert element...",
464
472
  "richTextItalic": "Italic",
465
473
  "richTextLink": "Link",
@@ -251,7 +251,11 @@
251
251
  "lastUpdatedBy": "Guardado por última vez el {{ updatedAt }} <br /> por {{ updatedBy }}",
252
252
  "leavePageDescription": "El contenido que esta buscando editar pertenece a otro documento y debe ser editado ahí.\nCambios hechos a {{ oldTitle }} son guardados automaticamente.",
253
253
  "leavePageHeading": "¿Salir de {{ oldTitle }} para editar {{ newTitle }}?",
254
+ "linkHrefHelp": "Escribe la URL completa, comenzando con https:// o http://",
254
255
  "linkTarget": "Objetivo del enlace",
256
+ "linkTitle": "Título del enlace",
257
+ "linkTitleRelHelp": "Si está vacío, se utilizará el título del documento relacionado.",
258
+ "linkTitleUrlHelp": "Si está vacío, se utilizará el título de la imagen.",
255
259
  "linkTo": "Enlace a...",
256
260
  "listJoiner": ", ",
257
261
  "live": "En Vivo",
@@ -455,11 +459,15 @@
455
459
  "richTextBulletedList": "Lista con Puntos",
456
460
  "richTextCodeBlock": "Bloque de Código",
457
461
  "richTextColor": "Color",
462
+ "richTextH1": "Título 1 (H1)",
458
463
  "richTextH2": "Título 2 (H2)",
459
464
  "richTextH3": "Título 3 (H3)",
460
465
  "richTextH4": "Título 4 (H4)",
466
+ "richTextH5": "Título 5 (H5)",
467
+ "richTextH6": "Título 6 (H6)",
461
468
  "richTextHighlight": "Marcador",
462
469
  "richTextHorizontalRule": "Regla Horizontal",
470
+ "richTextHorizontalRuleDescription": "Agregar un separador horizontal",
463
471
  "richTextInsertMenuHeading": "Insertar elemento...",
464
472
  "richTextItalic": "Cursiva",
465
473
  "richTextLink": "Liga",
@@ -251,7 +251,11 @@
251
251
  "lastUpdatedBy": "Enregistré dernièrement le {{ updatedAt }} <br /> par {{ updatedBy }}",
252
252
  "leavePageDescription": "Le contenu que vous essayez d'éditer appartient à un autre document et doit être édité là-bas.\nLes modifications effectuées à {{ oldTitle }} sont enregistrées automatiquement.",
253
253
  "leavePageHeading": "Quitter {{ oldTitle }} pour éditer {{ newTitle }} ?",
254
+ "linkHrefHelp": "Écrivez l'URL complète, commençant par https:// ou http://",
254
255
  "linkTarget": "Cible du lien",
256
+ "linkTitle": "Titre du lien",
257
+ "linkTitleRelHelp": "Si vide, le titre du document associé sera utilisé.",
258
+ "linkTitleUrlHelp": "Si vide, la légende de l'image sera utilisée.",
255
259
  "linkTo": "Lien vers...",
256
260
  "listJoiner": ", ",
257
261
  "live": "En Live",
@@ -455,11 +459,15 @@
455
459
  "richTextBulletedList": "Liste à Puces",
456
460
  "richTextCodeBlock": "Bloc de Code",
457
461
  "richTextColor": "Couleur",
462
+ "richTextH1": "Titre de niveau 1 (H1)",
458
463
  "richTextH2": "Titre de niveau 2 (H2)",
459
464
  "richTextH3": "Titre de niveau 3 (H3)",
460
465
  "richTextH4": "Titre de niveau 4 (H4)",
466
+ "richTextH5": "Titre de niveau 5 (H5)",
467
+ "richTextH6": "Titre de niveau 6 (H6)",
461
468
  "richTextHighlight": "Surligner",
462
469
  "richTextHorizontalRule": "Règle Horizontale",
470
+ "richTextHorizontalRuleDescription": "Ajouter un séparateur horizontal",
463
471
  "richTextInsertMenuHeading": "Insérer un élément...",
464
472
  "richTextItalic": "Italique",
465
473
  "richTextLink": "Lien",
@@ -251,7 +251,11 @@
251
251
  "lastUpdatedBy": "Ultimo salvataggio il {{ updatedAt }} <br /> da {{ updatedBy }}",
252
252
  "leavePageDescription": "Il contenuto che stai cercando di modificare appartiene a un altro documento e deve essere modificato lì.\nLe modifiche apportate a {{ oldTitle }} sono salvate automaticamente.",
253
253
  "leavePageHeading": "Lascia {{ oldTitle }} per modificare {{ newTitle }}?",
254
+ "linkHrefHelp": "Scrivi l'URL completo, iniziando con https:// o http://",
254
255
  "linkTarget": "Destinazione del Link",
256
+ "linkTitle": "Titolo del collegamento",
257
+ "linkTitleRelHelp": "Se vuoto, verrà utilizzato il titolo del documento correlato.",
258
+ "linkTitleUrlHelp": "Se vuoto, verrà utilizzata la didascalia dell'immagine.",
255
259
  "linkTo": "Link a...",
256
260
  "listJoiner": ", ",
257
261
  "live": "Live",
@@ -455,11 +459,15 @@
455
459
  "richTextBulletedList": "Lista puntata",
456
460
  "richTextCodeBlock": "Blocco del codice",
457
461
  "richTextColor": "Colore",
462
+ "richTextH1": "Intestazione 1 (H1)",
458
463
  "richTextH2": "Intestazione 2 (H2)",
459
464
  "richTextH3": "Intestazione 3 (H3)",
460
465
  "richTextH4": "Intestazione 4 (H4)",
466
+ "richTextH5": "Intestazione 5 (H5)",
467
+ "richTextH6": "Intestazione 6 (H6)",
461
468
  "richTextHighlight": "Evidenzia",
462
469
  "richTextHorizontalRule": "Regola orizzontale",
470
+ "richTextHorizontalRuleDescription": "Aggiungi un separatore orizzontale",
463
471
  "richTextInsertMenuHeading": "Inserisci elemento...",
464
472
  "richTextItalic": "Corsivo",
465
473
  "richTextLink": "Link",
@@ -251,7 +251,11 @@
251
251
  "lastUpdatedBy": "Salvo por último em {{ updatedAt }} <br /> por {{ updatedBy }}",
252
252
  "leavePageDescription": "O conteúdo que você está tentando editar pertence a outro documento e deve ser editado lá.\nMudanças em {{ oldTitle }} são salvas automaticamente.",
253
253
  "leavePageHeading": "Sair de {{ oldTitle }} para editar {{ newTitle }}?",
254
+ "linkHrefHelp": "Escreva a URL completa, começando com https:// ou http://",
254
255
  "linkTarget": "Alvo do Link",
256
+ "linkTitle": "Título do link",
257
+ "linkTitleRelHelp": "Se estiver vazio, o título do documento relacionado será usado.",
258
+ "linkTitleUrlHelp": "Se estiver vazio, a legenda da imagem será usada.",
255
259
  "linkTo": "Link Para...",
256
260
  "listJoiner": ", ",
257
261
  "live": "Ao vivo",
@@ -455,11 +459,15 @@
455
459
  "richTextBulletedList": "Lista com marcadores",
456
460
  "richTextCodeBlock": "Bloco de código",
457
461
  "richTextColor": "Cor",
462
+ "richTextH1": "Título 1 (H1)",
458
463
  "richTextH2": "Título 2 (H2)",
459
464
  "richTextH3": "Título 3 (H3)",
460
465
  "richTextH4": "Título 4 (H4)",
466
+ "richTextH5": "Título 5 (H5)",
467
+ "richTextH6": "Título 6 (H6)",
461
468
  "richTextHighlight": "Marca",
462
469
  "richTextHorizontalRule": "Regra Horizontal",
470
+ "richTextHorizontalRuleDescription": "Adicionar um separador horizontal",
463
471
  "richTextInsertMenuHeading": "Inserir elemento...",
464
472
  "richTextItalic": "Itálico",
465
473
  "richTextLink": "Link",