@tiny-mce-umbraco/backoffice 17.3.4 → 17.6.2

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 (54) hide show
  1. package/dist-cms/README.md +1 -0
  2. package/dist-cms/components/input-tiny-mce/input-tiny-mce.defaults.d.ts.map +1 -1
  3. package/dist-cms/components/input-tiny-mce/input-tiny-mce.defaults.js +175 -6
  4. package/dist-cms/components/input-tiny-mce/input-tiny-mce.element.d.ts +4 -1
  5. package/dist-cms/components/input-tiny-mce/input-tiny-mce.element.d.ts.map +1 -1
  6. package/dist-cms/components/input-tiny-mce/input-tiny-mce.element.js +97 -12
  7. package/dist-cms/components/input-tiny-mce/shadow-dom-selection.d.ts +16 -0
  8. package/dist-cms/components/input-tiny-mce/shadow-dom-selection.d.ts.map +1 -0
  9. package/dist-cms/components/input-tiny-mce/shadow-dom-selection.js +112 -0
  10. package/dist-cms/components/stylesheet-rule-input/stylesheet-rule-input.element.d.ts +1 -1
  11. package/dist-cms/entry-points/blob-download-router-fix.d.ts +56 -0
  12. package/dist-cms/entry-points/blob-download-router-fix.d.ts.map +1 -0
  13. package/dist-cms/entry-points/blob-download-router-fix.js +81 -0
  14. package/dist-cms/entry-points/entry-point.d.ts.map +1 -1
  15. package/dist-cms/entry-points/entry-point.js +7 -1
  16. package/dist-cms/localizations/ar.d.ts.map +1 -1
  17. package/dist-cms/localizations/ar.js +3 -0
  18. package/dist-cms/localizations/bs.d.ts.map +1 -1
  19. package/dist-cms/localizations/bs.js +3 -0
  20. package/dist-cms/localizations/cy.d.ts.map +1 -1
  21. package/dist-cms/localizations/cy.js +3 -0
  22. package/dist-cms/localizations/da.d.ts.map +1 -1
  23. package/dist-cms/localizations/da.js +4 -0
  24. package/dist-cms/localizations/en.d.ts.map +1 -1
  25. package/dist-cms/localizations/en.js +4 -0
  26. package/dist-cms/localizations/hr.d.ts.map +1 -1
  27. package/dist-cms/localizations/hr.js +3 -0
  28. package/dist-cms/localizations/it.d.ts.map +1 -1
  29. package/dist-cms/localizations/it.js +3 -0
  30. package/dist-cms/localizations/manifests.d.ts.map +1 -1
  31. package/dist-cms/localizations/manifests.js +24 -24
  32. package/dist-cms/localizations/nl.d.ts.map +1 -1
  33. package/dist-cms/localizations/nl.js +3 -0
  34. package/dist-cms/localizations/tr.d.ts.map +1 -1
  35. package/dist-cms/localizations/tr.js +3 -0
  36. package/dist-cms/plugins/manifests.d.ts.map +1 -1
  37. package/dist-cms/plugins/manifests.js +17 -0
  38. package/dist-cms/plugins/tiny-mce-block-picker.plugin.d.ts.map +1 -1
  39. package/dist-cms/plugins/tiny-mce-block-picker.plugin.js +20 -2
  40. package/dist-cms/plugins/tiny-mce-embeddedmedia.plugin.d.ts.map +1 -1
  41. package/dist-cms/plugins/tiny-mce-embeddedmedia.plugin.js +1 -0
  42. package/dist-cms/plugins/tiny-mce-mediapicker.plugin.d.ts.map +1 -1
  43. package/dist-cms/plugins/tiny-mce-mediapicker.plugin.js +127 -43
  44. package/dist-cms/property-editors/configEditor/property-editor-ui-tiny-mce-config-editor.element.d.ts.map +1 -1
  45. package/dist-cms/property-editors/configEditor/property-editor-ui-tiny-mce-config-editor.element.js +0 -1
  46. package/dist-cms/property-editors/manifests.js +3 -3
  47. package/dist-cms/property-editors/plugin/property-editor-ui-tiny-mce-plugin-configuration.element.d.ts.map +1 -1
  48. package/dist-cms/property-editors/plugin/property-editor-ui-tiny-mce-plugin-configuration.element.js +15 -5
  49. package/dist-cms/property-editors/tiny-mce/manifests.d.ts.map +1 -1
  50. package/dist-cms/property-editors/tiny-mce/manifests.js +2 -1
  51. package/dist-cms/utils/index.d.ts +15 -5
  52. package/dist-cms/utils/index.d.ts.map +1 -1
  53. package/dist-cms/utils/index.js +27 -5
  54. package/package.json +6 -6
@@ -9,10 +9,10 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- var _UmbTinyMceMediaPickerPlugin_instances, _UmbTinyMceMediaPickerPlugin_modalManager, _UmbTinyMceMediaPickerPlugin_temporaryFileRepository, _UmbTinyMceMediaPickerPlugin_onAction, _UmbTinyMceMediaPickerPlugin_showMediaPicker, _UmbTinyMceMediaPickerPlugin_showMediaCaptionAltText, _UmbTinyMceMediaPickerPlugin_insertInEditor, _UmbTinyMceMediaPickerPlugin_uploadImageHandler;
12
+ var _UmbTinyMceMediaPickerPlugin_instances, _UmbTinyMceMediaPickerPlugin_modalManager, _UmbTinyMceMediaPickerPlugin_temporaryFileRepository, _UmbTinyMceMediaPickerPlugin_allowedMediaTypeIds_get, _UmbTinyMceMediaPickerPlugin_resolveExistingMedia, _UmbTinyMceMediaPickerPlugin_onAction, _UmbTinyMceMediaPickerPlugin_parseDimension, _UmbTinyMceMediaPickerPlugin_showMediaPicker, _UmbTinyMceMediaPickerPlugin_showMediaCaptionAltText, _UmbTinyMceMediaPickerPlugin_insertInEditor, _UmbTinyMceMediaPickerPlugin_uploadImageHandler;
13
13
  import { sizeImageInEditor, uploadBlobImages } from '@tiny-mce-umbraco/backoffice/core';
14
14
  import { UmbTinyMcePluginBase } from '@tiny-mce-umbraco/backoffice/core';
15
- import { getGuidFromUdi } from '@umbraco-cms/backoffice/utils';
15
+ import { getGuidFromUdi, splitStringToArray } from '@umbraco-cms/backoffice/utils';
16
16
  import { UmbId } from '@umbraco-cms/backoffice/id';
17
17
  import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api';
18
18
  import { UmbTemporaryFileRepository } from '@umbraco-cms/backoffice/temporary-file';
@@ -62,8 +62,14 @@ class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase {
62
62
  tooltip: localize.term('general_mediaPicker'),
63
63
  onAction: () => __classPrivateFieldGet(this, _UmbTinyMceMediaPickerPlugin_instances, "m", _UmbTinyMceMediaPickerPlugin_onAction).call(this),
64
64
  onSetup: (api) => {
65
- const changed = this.editor.selection.selectorChangedWithUnbind('img[data-udi]', (state) => api.setActive(state));
66
- return () => changed.unbind();
65
+ // Drive the active state from the SAME resolution the action uses. A selector of
66
+ // `img[data-udi]` only lights up when the image itself is selected, so with the caret in
67
+ // the figcaption the button looked like "nothing selected" while the action would happily
68
+ // have edited that image. Button state and action behaviour must agree.
69
+ const syncActiveState = () => api.setActive(!!__classPrivateFieldGet(this, _UmbTinyMceMediaPickerPlugin_instances, "m", _UmbTinyMceMediaPickerPlugin_resolveExistingMedia).call(this).image);
70
+ this.editor.on('NodeChange', syncActiveState);
71
+ syncActiveState();
72
+ return () => this.editor.off('NodeChange', syncActiveState);
67
73
  },
68
74
  });
69
75
  // Register global options for the editor
@@ -86,37 +92,51 @@ class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase {
86
92
  }
87
93
  }
88
94
  }
89
- _UmbTinyMceMediaPickerPlugin_modalManager = new WeakMap(), _UmbTinyMceMediaPickerPlugin_temporaryFileRepository = new WeakMap(), _UmbTinyMceMediaPickerPlugin_uploadImageHandler = new WeakMap(), _UmbTinyMceMediaPickerPlugin_instances = new WeakSet(), _UmbTinyMceMediaPickerPlugin_onAction =
90
- /*
91
- async #observeCurrentUser() {
92
- if (!this.#currentUserContext) return;
93
-
94
- this.observe(this.#currentUserContext.currentUser, (currentUser) => (this.#currentUser = currentUser));
95
- }
96
- */
97
- async function _UmbTinyMceMediaPickerPlugin_onAction() {
98
- const selectedElm = this.editor.selection.getNode();
95
+ _UmbTinyMceMediaPickerPlugin_modalManager = new WeakMap(), _UmbTinyMceMediaPickerPlugin_temporaryFileRepository = new WeakMap(), _UmbTinyMceMediaPickerPlugin_uploadImageHandler = new WeakMap(), _UmbTinyMceMediaPickerPlugin_instances = new WeakSet(), _UmbTinyMceMediaPickerPlugin_allowedMediaTypeIds_get = function _UmbTinyMceMediaPickerPlugin_allowedMediaTypeIds_get() {
96
+ return splitStringToArray(this.configuration?.getValueByAlias('allowedMediaTypes'));
97
+ }, _UmbTinyMceMediaPickerPlugin_resolveExistingMedia = function _UmbTinyMceMediaPickerPlugin_resolveExistingMedia() {
98
+ const dom = this.editor.dom;
99
+ const selectedNode = this.editor.selection.getNode();
100
+ const figure = dom.getParent(selectedNode, 'figure');
101
+ const image = selectedNode.nodeName === 'IMG'
102
+ ? selectedNode
103
+ : (figure?.querySelector('img') ?? null);
104
+ const anchor = image ? dom.getParent(image, 'a') : null;
105
+ // The `<figcaption>` is what the editor actually sees and edits, so it wins over the
106
+ // `data-caption` attribute, which is only written when the picker last ran and goes stale as
107
+ // soon as someone edits the caption directly in the editor.
108
+ const figcaptionText = figure?.querySelector('figcaption')?.textContent?.trim();
109
+ const caption = figcaptionText || image?.dataset.caption || undefined;
110
+ return { selectedNode, figure, image, anchor, caption };
111
+ }, _UmbTinyMceMediaPickerPlugin_onAction = async function _UmbTinyMceMediaPickerPlugin_onAction() {
112
+ const { image, caption } = __classPrivateFieldGet(this, _UmbTinyMceMediaPickerPlugin_instances, "m", _UmbTinyMceMediaPickerPlugin_resolveExistingMedia).call(this);
99
113
  let currentTarget = {};
100
- if (selectedElm.nodeName === 'IMG') {
101
- const img = selectedElm;
102
- const hasUdi = img.hasAttribute('data-udi');
103
- const hasDataTmpImg = img.hasAttribute('data-tmpimg');
114
+ if (image) {
104
115
  currentTarget = {
105
- altText: img.alt,
106
- url: img.src,
107
- caption: img.dataset.caption,
116
+ altText: image.alt,
117
+ url: image.src,
118
+ caption,
119
+ // Carry the current dimensions in so the dialog opens showing the real size rather
120
+ // than defaulting, and so an unchanged dialog round-trips the size unchanged.
121
+ width: __classPrivateFieldGet(this, _UmbTinyMceMediaPickerPlugin_instances, "m", _UmbTinyMceMediaPickerPlugin_parseDimension).call(this, image.getAttribute('width')),
122
+ height: __classPrivateFieldGet(this, _UmbTinyMceMediaPickerPlugin_instances, "m", _UmbTinyMceMediaPickerPlugin_parseDimension).call(this, image.getAttribute('height')),
108
123
  };
109
- if (hasUdi) {
110
- currentTarget['udi'] = img.dataset.udi;
124
+ if (image.hasAttribute('data-udi')) {
125
+ currentTarget['udi'] = image.dataset.udi;
111
126
  }
112
127
  else {
113
- currentTarget['id'] = img.getAttribute('rel') ?? undefined;
128
+ currentTarget['id'] = image.getAttribute('rel') ?? undefined;
114
129
  }
115
- if (hasDataTmpImg) {
116
- currentTarget['tmpimg'] = img.dataset.tmpimg;
130
+ if (image.hasAttribute('data-tmpimg')) {
131
+ currentTarget['tmpimg'] = image.dataset.tmpimg;
117
132
  }
118
133
  }
119
134
  __classPrivateFieldGet(this, _UmbTinyMceMediaPickerPlugin_instances, "m", _UmbTinyMceMediaPickerPlugin_showMediaPicker).call(this, currentTarget);
135
+ }, _UmbTinyMceMediaPickerPlugin_parseDimension = function _UmbTinyMceMediaPickerPlugin_parseDimension(value) {
136
+ if (!value)
137
+ return undefined;
138
+ const parsed = Number.parseInt(value, 10);
139
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
120
140
  }, _UmbTinyMceMediaPickerPlugin_showMediaPicker = async function _UmbTinyMceMediaPickerPlugin_showMediaPicker(currentTarget) {
121
141
  /*
122
142
  let startNodeId;
@@ -132,10 +152,12 @@ async function _UmbTinyMceMediaPickerPlugin_onAction() {
132
152
  }
133
153
  }
134
154
  */
155
+ const allowedIds = __classPrivateFieldGet(this, _UmbTinyMceMediaPickerPlugin_instances, "a", _UmbTinyMceMediaPickerPlugin_allowedMediaTypeIds_get);
135
156
  const modalHandler = __classPrivateFieldGet(this, _UmbTinyMceMediaPickerPlugin_modalManager, "f")?.open(this, UMB_MEDIA_PICKER_MODAL, {
136
157
  data: {
137
158
  multiple: false,
138
159
  //startNodeIsVirtual,
160
+ pickableFilter: allowedIds.length ? (item) => allowedIds.includes(item.mediaType.unique) : undefined,
139
161
  },
140
162
  value: {
141
163
  selection: currentTarget.udi ? [getGuidFromUdi(currentTarget.udi)] : [],
@@ -152,12 +174,20 @@ async function _UmbTinyMceMediaPickerPlugin_onAction() {
152
174
  if (!mediaUnique)
153
175
  return;
154
176
  const modalHandler = __classPrivateFieldGet(this, _UmbTinyMceMediaPickerPlugin_modalManager, "f")?.open(this, UMB_MEDIA_CAPTION_ALT_TEXT_MODAL, {
155
- data: { mediaUnique },
177
+ // The dialog needs maxImageSize to offer sensible sizes; without it the size it hands back
178
+ // is unconstrained and we have no idea what the editor's limit was meant to be.
179
+ data: { mediaUnique, maxImageSize: this.editor.options.get('maxImageSize') },
156
180
  value: {
157
181
  url: '',
158
182
  altText: currentTarget.altText,
159
183
  caption: currentTarget.caption,
184
+ width: currentTarget.width,
185
+ height: currentTarget.height,
160
186
  },
187
+ // The token defaults this sidebar to `small`, which is too narrow for the width/height row —
188
+ // the unit suffix on the height field gets clipped. Umbraco's own tiptap RTE overrides it the
189
+ // same way for the same reason ("Override default sidebar size for better UX").
190
+ modal: { size: 'medium' },
161
191
  });
162
192
  const mediaData = await modalHandler?.onSubmit().catch(() => null);
163
193
  if (!mediaData)
@@ -167,6 +197,9 @@ async function _UmbTinyMceMediaPickerPlugin_onAction() {
167
197
  caption: mediaData?.caption,
168
198
  url: mediaData?.url,
169
199
  udi: 'umb://media/' + mediaUnique?.replace(/-/g, ''),
200
+ // The dialog is the authority on size once the editor has chosen one.
201
+ width: mediaData?.width,
202
+ height: mediaData?.height,
170
203
  };
171
204
  __classPrivateFieldGet(this, _UmbTinyMceMediaPickerPlugin_instances, "m", _UmbTinyMceMediaPickerPlugin_insertInEditor).call(this, media);
172
205
  }, _UmbTinyMceMediaPickerPlugin_insertInEditor = async function _UmbTinyMceMediaPickerPlugin_insertInEditor(media) {
@@ -174,32 +207,78 @@ async function _UmbTinyMceMediaPickerPlugin_onAction() {
174
207
  return;
175
208
  // We need to create a NEW DOM <img> element to insert
176
209
  // setting an attribute of ID to __mcenew, so we can gather a reference to the node, to be able to update its size accordingly to the size of the image.
210
+ // An explicit size from the dialog is authoritative. Stamping it on the markup up front means
211
+ // the editor never briefly renders at the wrong size, and it tells the post-insert sizing step
212
+ // below to leave the dimensions alone.
213
+ const explicitSize = media.width && media.height ? { width: media.width, height: media.height } : undefined;
177
214
  const img = {
178
215
  alt: media.altText,
179
216
  src: media.url ? media.url : 'nothing.jpg',
180
217
  id: '__mcenew',
181
218
  'data-udi': media.udi,
182
219
  'data-caption': media.caption,
220
+ ...(explicitSize
221
+ ? { width: String(explicitSize.width), height: String(explicitSize.height) }
222
+ : {}),
183
223
  };
184
- const newImage = this.editor.dom.createHTML('img', img);
185
- const parentElement = this.editor.selection.getNode().parentElement;
186
- if (img['data-caption'] && parentElement) {
187
- const figCaption = this.editor.dom.createHTML('figcaption', {}, img['data-caption']);
188
- const combined = newImage + figCaption;
189
- if (parentElement.nodeName !== 'FIGURE') {
190
- const fragment = this.editor.dom.createHTML('figure', {}, combined);
191
- this.editor.selection.setContent(fragment);
192
- }
193
- else {
194
- parentElement.innerHTML = combined;
224
+ const dom = this.editor.dom;
225
+ // Same resolution the edit path used, so both agree on what is being replaced. The caret may
226
+ // be on the image, on an <a> wrapping it, or inside the figcaption.
227
+ //
228
+ // The anchor matters because Umbraco fixed this same class of bug in its own RTE in 17.6
229
+ // (media-picker.tiptap-toolbar.utils.ts); re-picking used to silently drop the <a>.
230
+ const { selectedNode, figure: existingFigure, image: existingImage, anchor: existingAnchor, } = __classPrivateFieldGet(this, _UmbTinyMceMediaPickerPlugin_instances, "m", _UmbTinyMceMediaPickerPlugin_resolveExistingMedia).call(this);
231
+ let markup = dom.createHTML('img', img);
232
+ if (existingAnchor) {
233
+ // Rebuild the wrapping link. `data-mce-*` attributes are TinyMCE's own bookkeeping and are
234
+ // skipped so they get regenerated rather than staling.
235
+ //
236
+ // Because the new markup now CONTAINS the link, whatever we replace below must be the
237
+ // element the old link lived in — never the image inside it. Replacing just the image would
238
+ // nest an <a> inside an <a>; the parser rejects that and splits it, leaving an empty
239
+ // duplicate link behind.
240
+ const anchorAttrs = {};
241
+ for (const attr of Array.from(existingAnchor.attributes)) {
242
+ if (!attr.name.startsWith('data-mce-')) {
243
+ anchorAttrs[attr.name] = attr.value;
244
+ }
195
245
  }
246
+ markup = dom.createHTML('a', anchorAttrs, markup);
196
247
  }
197
- else if (parentElement?.nodeName === 'FIGURE' && parentElement.parentElement) {
198
- //if caption is removed, remove the figure element
199
- parentElement.parentElement.innerHTML = newImage;
248
+ const caption = img['data-caption'];
249
+ if (caption && existingFigure) {
250
+ // Replace only the figure's children, so the figure's own attributes survive along with
251
+ // every sibling elsewhere in the container.
252
+ existingFigure.innerHTML = markup + dom.createHTML('figcaption', {}, caption);
200
253
  }
201
254
  else {
202
- this.editor.selection.setContent(newImage);
255
+ const content = caption ? dom.createHTML('figure', {}, markup + dom.createHTML('figcaption', {}, caption)) : markup;
256
+ // Replace the OUTERMOST element whose markup we have just rebuilt: the figure if there was
257
+ // one, otherwise the link, otherwise the image. Anything narrower would leave a fragment of
258
+ // the old structure behind; anything wider would take out unrelated siblings — the bug in
259
+ // the previous implementation, which assigned to `figure.parentElement.innerHTML`.
260
+ let replaceTarget = existingFigure ?? existingAnchor ?? existingImage;
261
+ if (caption && !existingFigure) {
262
+ // A <figure> is block-level and cannot legally sit inside a <p>. Replacing something
263
+ // *within* the paragraph makes the parser split it, leaving an empty <p> either side of
264
+ // the figure. So when the paragraph holds nothing but this image, promote the target to
265
+ // the paragraph and consume it.
266
+ //
267
+ // Guarded on the paragraph having no text of its own and at most one image, so a figure
268
+ // never swallows a paragraph that has other content in it. `length <= 1` rather than
269
+ // `=== 1` because a brand new insert at a caret has no image in the paragraph yet.
270
+ const paragraph = dom.getParent(replaceTarget ?? selectedNode, 'p');
271
+ if (paragraph && !paragraph.textContent?.trim() && paragraph.querySelectorAll('img').length <= 1) {
272
+ replaceTarget = paragraph;
273
+ }
274
+ }
275
+ if (replaceTarget) {
276
+ dom.setOuterHTML(replaceTarget, content);
277
+ }
278
+ else {
279
+ // Nothing existing to replace: a brand new insert at the caret.
280
+ this.editor.selection.setContent(content);
281
+ }
203
282
  }
204
283
  // Using settimeout to wait for a DoM-render, so we can find the new element by ID.
205
284
  setTimeout(() => {
@@ -208,8 +287,13 @@ async function _UmbTinyMceMediaPickerPlugin_onAction() {
208
287
  return;
209
288
  this.editor.dom.setAttrib(imgElm, 'id', null);
210
289
  // When image is loaded we are ready to call sizeImageInEditor.
290
+ //
291
+ // `sizeImageInEditor` reads the image's natural size and scales it down to maxImageSize,
292
+ // overwriting the width/height attributes. That is right for pasted or dragged images, but
293
+ // it used to silently discard whatever size the editor chose in the dialog. Passing the
294
+ // explicit size keeps it — the sizer then only refreshes the processed image URL.
211
295
  const onImageLoaded = () => {
212
- sizeImageInEditor(this.editor, imgElm, img.src);
296
+ sizeImageInEditor(this.editor, imgElm, img.src, explicitSize);
213
297
  this.editor.dispatch('Change');
214
298
  };
215
299
  // Check if image already is loaded.
@@ -1 +1 @@
1
- {"version":3,"file":"property-editor-ui-tiny-mce-config-editor.element.d.ts","sourceRoot":"","sources":["../../../src/property-editors/configEditor/property-editor-ui-tiny-mce-config-editor.element.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AACpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,OAAO,KAAK,EACX,0BAA0B,EAC1B,iCAAiC,EACjC,MAAM,yCAAyC,CAAC;AAKjD;;GAEG;AACH,qBACa,6CAA8C,SAAQ,aAAc,YAAW,0BAA0B;;IAErH,IACI,KAAK,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI,EAI3B;IACD,IAAI,KAAK,IAAI,IAAI,CAEhB;IAGD,MAAM,CAAC,EAAE,iCAAiC,CAAC;IAG3C,OAAO,CAAC,aAAa,CAA0B;cAEtB,YAAY,CAAC,kBAAkB,EAAE,gBAAgB,CAAC,OAAO,CAAC;IAqC1E,MAAM;IASf,gBAAyB,MAAM,4BAY7B;CACF;AAED,eAAe,6CAA6C,CAAC;AAE7D,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,+CAA+C,EAAE,6CAA6C,CAAC;KAC/F;CACD"}
1
+ {"version":3,"file":"property-editor-ui-tiny-mce-config-editor.element.d.ts","sourceRoot":"","sources":["../../../src/property-editors/configEditor/property-editor-ui-tiny-mce-config-editor.element.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AACpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,OAAO,KAAK,EACX,0BAA0B,EAC1B,iCAAiC,EACjC,MAAM,yCAAyC,CAAC;AAKjD;;GAEG;AACH,qBACa,6CAA8C,SAAQ,aAAc,YAAW,0BAA0B;;IAErH,IACI,KAAK,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI,EAI3B;IACD,IAAI,KAAK,IAAI,IAAI,CAEhB;IAGD,MAAM,CAAC,EAAE,iCAAiC,CAAC;IAG3C,OAAO,CAAC,aAAa,CAA0B;cAEtB,YAAY,CAAC,kBAAkB,EAAE,gBAAgB,CAAC,OAAO,CAAC;IAoC1E,MAAM;IASf,gBAAyB,MAAM,4BAY7B;CACF;AAED,eAAe,6CAA6C,CAAC;AAE7D,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,+CAA+C,EAAE,6CAA6C,CAAC;KAC/F;CACD"}
@@ -64,7 +64,6 @@ _UmbPropertyEditorUITinyMceConfigEditorElement_showCodeEditor = async function _
64
64
  else {
65
65
  try {
66
66
  this._customConfig = JSON.parse(value.content.toString());
67
- console.log('Parsed object:', this._customConfig);
68
67
  // Proceed with using jsonObj
69
68
  }
70
69
  catch (err) {
@@ -9,7 +9,7 @@ export const manifests = [
9
9
  meta: {
10
10
  label: 'TinyMCE Plugin Configuration',
11
11
  icon: 'icon-autofill',
12
- group: 'common',
12
+ group: '#propertyEditorUIGroups_common',
13
13
  },
14
14
  },
15
15
  {
@@ -20,7 +20,7 @@ export const manifests = [
20
20
  meta: {
21
21
  label: 'TinyMCE Toolbar Configuration',
22
22
  icon: 'icon-autofill',
23
- group: 'common',
23
+ group: '#propertyEditorUIGroups_common',
24
24
  },
25
25
  },
26
26
  {
@@ -31,7 +31,7 @@ export const manifests = [
31
31
  meta: {
32
32
  label: 'TinyMCE Custom Configuration',
33
33
  icon: 'icon-autofill',
34
- group: 'common',
34
+ group: '#propertyEditorUIGroups_common',
35
35
  },
36
36
  },
37
37
  ];
@@ -1 +1 @@
1
- {"version":3,"file":"property-editor-ui-tiny-mce-plugin-configuration.element.d.ts","sourceRoot":"","sources":["../../../src/property-editors/plugin/property-editor-ui-tiny-mce-plugin-configuration.element.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AAEpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,OAAO,KAAK,EACX,0BAA0B,EAC1B,iCAAiC,EACjC,MAAM,yCAAyC,CAAC;AAejD;;GAEG;AACH,qBACa,oDACZ,SAAQ,aACR,YAAW,0BAA0B;;IAErC,IACI,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,EAexC;IACD,IAAI,KAAK,IAAI,MAAM,EAAE,CAEpB;IAGD,MAAM,CAAC,EAAE,iCAAiC,CAAC;IAG3C,OAAO,CAAC,aAAa,CAAsB;cAIlB,YAAY,CAAC,kBAAkB,EAAE,gBAAgB,CAAC,OAAO,CAAC;YAuBrE,UAAU;IA4FxB,OAAO,CAAC,QAAQ;IAaP,MAAM;IAoBf,gBAAyB,MAAM,4BAS7B;CACF;AAED,eAAe,oDAAoD,CAAC;AAEpE,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,sDAAsD,EAAE,oDAAoD,CAAC;KAC7G;CACD"}
1
+ {"version":3,"file":"property-editor-ui-tiny-mce-plugin-configuration.element.d.ts","sourceRoot":"","sources":["../../../src/property-editors/plugin/property-editor-ui-tiny-mce-plugin-configuration.element.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AAEpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,OAAO,KAAK,EACX,0BAA0B,EAC1B,iCAAiC,EACjC,MAAM,yCAAyC,CAAC;AAejD;;GAEG;AACH,qBACa,oDACZ,SAAQ,aACR,YAAW,0BAA0B;;IAErC,IACI,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,EAexC;IACD,IAAI,KAAK,IAAI,MAAM,EAAE,CAEpB;IAGD,MAAM,CAAC,EAAE,iCAAiC,CAAC;IAG3C,OAAO,CAAC,aAAa,CAAsB;cAIlB,YAAY,CAAC,kBAAkB,EAAE,gBAAgB,CAAC,OAAO,CAAC;YAuBrE,UAAU;IAqGxB,OAAO,CAAC,QAAQ;IAaP,MAAM;IAoBf,gBAAyB,MAAM,4BAS7B;CACF;AAED,eAAe,oDAAoD,CAAC;AAEpE,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,sDAAsD,EAAE,oDAAoD,CAAC;KAC7G;CACD"}
@@ -114,10 +114,20 @@ let UmbPropertyEditorUITinyMcePluginConfigurationElement = class UmbPropertyEdit
114
114
  });
115
115
  });
116
116
  }
117
- const defaultPluginAliases = this._pluginConfig.map((p) => p.alias);
117
+ // A plugin can be named by more than one source - the fallback defaults, the appsettings
118
+ // `Plugins` list, and a `tinyMcePlugin` manifest. Each source used to push its own row, so a
119
+ // plugin named twice appeared twice in the picker and handed lit's keyed `repeat()` a duplicate
120
+ // key. First source wins, which keeps the defaults' "(default plugin)" label and disabled state.
121
+ const seen = new Set(this._pluginConfig.map((p) => p.alias));
122
+ const addPlugin = (entry) => {
123
+ if (seen.has(entry.alias))
124
+ return;
125
+ seen.add(entry.alias);
126
+ this._pluginConfig.push(entry);
127
+ };
118
128
  configPlugins.forEach((p) => {
119
- if (!excludeList.includes(p) && !defaultPluginAliases.includes(p)) {
120
- this._pluginConfig.push({
129
+ if (!excludeList.includes(p)) {
130
+ addPlugin({
121
131
  alias: p,
122
132
  label: p,
123
133
  icon: undefined,
@@ -129,7 +139,7 @@ let UmbPropertyEditorUITinyMcePluginConfigurationElement = class UmbPropertyEdit
129
139
  plugins.forEach((p) => {
130
140
  if (p.meta?.plugins) {
131
141
  if (typeof p.meta.plugins === 'string' && !excludeList.includes(p.meta.plugins)) {
132
- this._pluginConfig.push({
142
+ addPlugin({
133
143
  alias: p.meta.plugins,
134
144
  label: p.meta.plugins,
135
145
  icon: undefined,
@@ -140,7 +150,7 @@ let UmbPropertyEditorUITinyMcePluginConfigurationElement = class UmbPropertyEdit
140
150
  else if (Array.isArray(p.meta.plugins)) {
141
151
  p.meta.plugins.forEach((pl) => {
142
152
  if (!excludeList.includes(pl)) {
143
- this._pluginConfig.push({
153
+ addPlugin({
144
154
  alias: pl,
145
155
  label: pl,
146
156
  icon: undefined,
@@ -1 +1 @@
1
- {"version":3,"file":"manifests.d.ts","sourceRoot":"","sources":["../../../src/property-editors/tiny-mce/manifests.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,oBAAoB,CAyJjD,CAAC"}
1
+ {"version":3,"file":"manifests.d.ts","sourceRoot":"","sources":["../../../src/property-editors/tiny-mce/manifests.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,oBAAoB,CA0JjD,CAAC"}
@@ -17,8 +17,9 @@ export const manifests = [
17
17
  label: 'Rich Text Editor [TinyMCE]',
18
18
  propertyEditorSchemaAlias: UMB_BLOCK_RTE_PROPERTY_EDITOR_SCHEMA_ALIAS,
19
19
  icon: 'icon-tinymce',
20
- group: 'richContent',
20
+ group: '#propertyEditorUIGroups_richContent',
21
21
  supportsReadOnly: true,
22
+ keywords: ['content', 'article', 'body', 'html', 'wysiwyg', 'richtext', 'rte', 'tinymce', 'editor', 'formatted', 'paragraph'],
22
23
  settings: {
23
24
  properties: [
24
25
  {
@@ -1,11 +1,21 @@
1
1
  import type { Editor } from '@umbraco-cms/backoffice/external/tinymce';
2
2
  /**
3
- * Sizes an image in the editor
4
- * @param editor
5
- * @param imageDomElement
6
- * @param imgUrl
3
+ * Sizes an image in the editor.
4
+ *
5
+ * By default the image is scaled down to the editor's `maxImageSize`, which is what pasted and
6
+ * dragged images need. Pass `explicitSize` when the size has already been chosen deliberately (for
7
+ * example in the media picker's caption/alt-text dialog) — those dimensions are then used as-is and
8
+ * only the processed image URL is refreshed. Without it, a deliberately chosen size is silently
9
+ * overwritten by the max-size scaling.
10
+ * @param editor The TinyMCE editor instance.
11
+ * @param imageDomElement The image element to size.
12
+ * @param imgUrl Source URL, used to generate a resized `data-mce-src`.
13
+ * @param explicitSize Dimensions to apply verbatim instead of scaling to `maxImageSize`.
7
14
  */
8
- export declare function sizeImageInEditor(editor: Editor, imageDomElement: HTMLElement, imgUrl?: string): Promise<void>;
15
+ export declare function sizeImageInEditor(editor: Editor, imageDomElement: HTMLElement, imgUrl?: string, explicitSize?: {
16
+ width: number;
17
+ height: number;
18
+ }): Promise<void>;
9
19
  /**
10
20
  * Scales an image to the max size
11
21
  * @param maxSize
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,0CAA0C,CAAC;AAEvE;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,MAAM,iBAsBpG;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;;;EA4B5E;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,iBAqDzE;AAED,OAAO,EAAE,wBAAwB,EAAE,MAAM,2CAA2C,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,0CAA0C,CAAC;AAEvE;;;;;;;;;;;;GAYG;AACH,wBAAsB,iBAAiB,CACtC,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,WAAW,EAC5B,MAAM,CAAC,EAAE,MAAM,EACf,YAAY,CAAC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,iBA0ChD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;;;EA4B5E;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,iBAqDzE;AAED,OAAO,EAAE,wBAAwB,EAAE,MAAM,2CAA2C,CAAC"}
@@ -3,14 +3,36 @@
3
3
  // TODO: This should not be done in this way, we need to split this into separate defined helper methods. This is also very specific to TinyMCE, so should be named that way. [NL]
4
4
  import { getProcessedImageUrl } from '@umbraco-cms/backoffice/utils';
5
5
  /**
6
- * Sizes an image in the editor
7
- * @param editor
8
- * @param imageDomElement
9
- * @param imgUrl
6
+ * Sizes an image in the editor.
7
+ *
8
+ * By default the image is scaled down to the editor's `maxImageSize`, which is what pasted and
9
+ * dragged images need. Pass `explicitSize` when the size has already been chosen deliberately (for
10
+ * example in the media picker's caption/alt-text dialog) — those dimensions are then used as-is and
11
+ * only the processed image URL is refreshed. Without it, a deliberately chosen size is silently
12
+ * overwritten by the max-size scaling.
13
+ * @param editor The TinyMCE editor instance.
14
+ * @param imageDomElement The image element to size.
15
+ * @param imgUrl Source URL, used to generate a resized `data-mce-src`.
16
+ * @param explicitSize Dimensions to apply verbatim instead of scaling to `maxImageSize`.
10
17
  */
11
- export async function sizeImageInEditor(editor, imageDomElement, imgUrl) {
18
+ export async function sizeImageInEditor(editor, imageDomElement, imgUrl, explicitSize) {
12
19
  const size = editor.dom.getSize(imageDomElement);
13
20
  const maxImageSize = editor.options.get('maxImageSize');
21
+ if (explicitSize) {
22
+ editor.dom.setAttribs(imageDomElement, {
23
+ width: Math.round(explicitSize.width),
24
+ height: Math.round(explicitSize.height),
25
+ });
26
+ if (imgUrl) {
27
+ const resizedImgUrl = await getProcessedImageUrl(imgUrl, {
28
+ width: Math.round(explicitSize.width),
29
+ height: Math.round(explicitSize.height),
30
+ });
31
+ editor.dom.setAttrib(imageDomElement, 'data-mce-src', resizedImgUrl);
32
+ }
33
+ editor.execCommand('mceAutoResize', false);
34
+ return;
35
+ }
14
36
  if (maxImageSize && maxImageSize > 0) {
15
37
  const newSize = scaleToMaxSize(maxImageSize, size.w, size.h);
16
38
  editor.dom.setAttribs(imageDomElement, { width: Math.round(newSize.width), height: Math.round(newSize.height) });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tiny-mce-umbraco/backoffice",
3
- "version": "17.3.4",
4
- "description": "This Umbraco CMS package enables access to the TinyMCE-based Rich Text Editor (RTE) in version 16 and above. It also supports the use of TinyMCE Premium plugins with a valid subscription. Additional features include streamlined configuration for RTE Data Types in Umbraco and enhanced settings that support direct JSON-based configuration via .NET (appsettings.config).",
3
+ "version": "17.6.2",
4
+ "description": "This Umbraco CMS package enables access to the TinyMCE-based Rich Text Editor (RTE). It requires Umbraco 17.6.2 or later - for Umbraco 16 use the 16.x package versions, for Umbraco 18 use 18.x. It also supports the use of TinyMCE Premium plugins with a valid subscription. Additional features include streamlined configuration for RTE Data Types in Umbraco and enhanced settings that support direct JSON-based configuration via .NET (appsettings.config).",
5
5
  "keywords": [
6
6
  "Umbraco",
7
7
  "TinyMCE",
@@ -40,22 +40,22 @@
40
40
  "@rollup/plugin-json": "^6.1.0",
41
41
  "@rollup/plugin-node-resolve": "^16.0.1",
42
42
  "@rollup/plugin-replace": "^6.0.2",
43
- "@umbraco-cms/backoffice": "^17.3.4",
43
+ "@umbraco-cms/backoffice": "^17.6.2",
44
44
  "chalk": "^5.3.0",
45
45
  "cross-env": "^10.1.0",
46
- "eslint": "^9.37.0",
46
+ "eslint": "^10.2.0",
47
47
  "node-fetch": "^3.3.2",
48
48
  "rollup": "^4.40.0",
49
49
  "rollup-plugin-esbuild": "^6.2.1",
50
50
  "rollup-plugin-import-css": "^3.5.8",
51
51
  "tiny-glob": "^0.2.9",
52
52
  "typescript": "^5.9.3",
53
- "vite": "^7.1.11",
53
+ "vite": "^7.3.2",
54
54
  "vite-plugin-static-copy": "^3.1.3",
55
55
  "vite-tsconfig-paths": "^5.1.4"
56
56
  },
57
57
  "peerDependencies": {
58
- "@umbraco-cms/backoffice": "^17.3.4",
58
+ "@umbraco-cms/backoffice": "^17.6.2",
59
59
  "tinymce": "^6.8.5",
60
60
  "tinymce-i18n": "^24.12.30"
61
61
  }