@innovastudio/contentbox 1.6.195 → 1.6.197

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@innovastudio/contentbox",
4
- "version": "1.6.195",
4
+ "version": "1.6.197",
5
5
  "description": "",
6
6
  "main": "public/contentbox/contentbox.esm.js",
7
7
  "types": "index.d.ts",
@@ -67,7 +67,7 @@
67
67
  "ws": "^8.13.0"
68
68
  },
69
69
  "dependencies": {
70
- "@innovastudio/contentbuilder": "^1.5.213",
70
+ "@innovastudio/contentbuilder": "^1.5.215",
71
71
  "js-beautify": "^1.14.0",
72
72
  "marked": "^17.0.0",
73
73
  "sortablejs": "^1.15.2"
@@ -46429,11 +46429,13 @@ class Dom {
46429
46429
  // source: https://stackoverflow.com/questions/2871081/jquery-setting-cursor-position-in-contenteditable-div
46430
46430
  moveCursorToElement(element) {
46431
46431
  var sel, range;
46432
- if (window.getSelection && this.builder.doc.createRange) {
46432
+ if (this.builder.win.getSelection && this.builder.doc.createRange) {
46433
46433
  range = this.builder.doc.createRange();
46434
46434
  range.selectNodeContents(element);
46435
46435
  range.collapse(false);
46436
- sel = window.getSelection();
46436
+ // The CONTENT window's selection — with the editor in an iframe the
46437
+ // global one belongs to the host page, so the caret never moved.
46438
+ sel = this.builder.win.getSelection();
46437
46439
  sel.removeAllRanges();
46438
46440
  sel.addRange(range);
46439
46441
  } else if (this.builder.doc.body.createTextRange) {
@@ -69761,11 +69763,23 @@ class Plugin {
69761
69763
  }
69762
69764
  };
69763
69765
  if (!this.builder.handlePluginClick_) {
69764
- document.addEventListener('click', handlePluginClick);
69765
- if (this.builder.iframeDocument) {
69766
- this.builder.doc.addEventListener('click', handlePluginClick);
69767
- }
69768
69766
  this.builder.handlePluginClick_ = true;
69767
+ // Register on the NEXT task, not now. showPluginEditor() can be
69768
+ // reached from a microtask inside the very click that opens the
69769
+ // panel: the RTE button runs convertSelectionToPlugin(), whose
69770
+ // runtime.reinitialize().then() resolves while that click is still
69771
+ // propagating towards document. A listener added mid-propagation
69772
+ // still receives that same click, so the panel was closed the
69773
+ // instant it opened — and only on a first open, because afterwards
69774
+ // the previous listener had already fired and removed itself.
69775
+ // Deferring means this listener can never see the click that
69776
+ // created it, whichever toolbar or panel opened the panel.
69777
+ setTimeout(() => {
69778
+ document.addEventListener('click', handlePluginClick);
69779
+ if (this.builder.iframeDocument) {
69780
+ this.builder.doc.addEventListener('click', handlePluginClick);
69781
+ }
69782
+ }, 0);
69769
69783
  }
69770
69784
  }
69771
69785
  hidePluginEditor() {
@@ -107081,16 +107095,42 @@ class Resize {
107081
107095
  enable() {
107082
107096
  const col = this.element;
107083
107097
  const row = col.parentNode;
107098
+
107099
+ // The builder injects its own elements into a row — tools, the add-row
107100
+ // bar, and the row overlay that appears once the row is selected in the
107101
+ // side panel. They are not columns, and measuring them as columns makes
107102
+ // the totals below nonsense. Filter them out in ONE place so a new
107103
+ // injected element can never silently break the maths again.
107104
+ const isBuilderUI = item => item.nodeType !== 1 || item.classList.contains('is-tool') || item.classList.contains('is-row-tool') || item.classList.contains('is-col-tool') || item.classList.contains('is-rowadd-tool') || item.classList.contains('is-row-overlay');
107105
+ const columnsOf = parent => Array.from(parent.children).filter(item => !isBuilderUI(item));
107106
+
107107
+ // A column width is only ever stored as a percentage; pixels are used
107108
+ // purely while a drag is in progress. resizeEnd normally converts them
107109
+ // back, but it does not run if the drag is interrupted — and pixel
107110
+ // widths saved into the page push content off screen on smaller
107111
+ // devices. This is the backstop for that: whatever happens to the drag,
107112
+ // a leftover pixel width is converted on the next tick. Capture phase,
107113
+ // so it still runs if something stops the event bubbling.
107114
+ const normalizeToPercent = () => {
107115
+ setTimeout(() => {
107116
+ if (!col.style.width || col.style.width.indexOf('px') === -1) return;
107117
+ const parent = col.parentNode;
107118
+ if (!parent || !parent.offsetWidth) return;
107119
+ const pct = col.offsetWidth / parent.offsetWidth * 100;
107120
+ if (isFinite(pct) && pct > 0) col.style.width = pct + '%';
107121
+ }, 0);
107122
+ };
107123
+ this.normalizeToPercent = normalizeToPercent;
107124
+ this.eventTarget = col.ownerDocument;
107125
+ this.eventTarget.addEventListener('mouseup', normalizeToPercent, true);
107126
+ this.eventTarget.addEventListener('touchend', normalizeToPercent, true);
107084
107127
  this.resize = new Resizeable({
107085
107128
  pane: col,
107086
107129
  resizeHeight: this.builder.resizeHeight,
107087
107130
  onResize: (width, height) => {
107088
107131
  this.builder.util.hideControls();
107089
107132
  let numOfCols = 1;
107090
- Array.from(col.parentNode.children).map(item => {
107091
- if (item.classList.contains('is-row-tool')) return;
107092
- if (item.classList.contains('is-rowadd-tool')) return;
107093
- if (item.classList.contains('is-col-tool')) return;
107133
+ columnsOf(col.parentNode).map(item => {
107094
107134
  if (item === col) return;
107095
107135
  numOfCols++;
107096
107136
  });
@@ -107101,10 +107141,7 @@ class Resize {
107101
107141
  // adjust others
107102
107142
  const maxWidth = () => {
107103
107143
  let otherWidth = 0;
107104
- Array.from(col.parentNode.children).map(item => {
107105
- if (item.classList.contains('is-row-tool')) return;
107106
- if (item.classList.contains('is-rowadd-tool')) return;
107107
- if (item.classList.contains('is-col-tool')) return;
107144
+ columnsOf(col.parentNode).map(item => {
107108
107145
  // if(this.opts.filterSibling) if(item.classList.contains(this.opts.filterSibling)) return;
107109
107146
 
107110
107147
  if (item === col) return;
@@ -107118,10 +107155,7 @@ class Resize {
107118
107155
  // percentage = (max / row.offsetWidth) * 100;
107119
107156
  // Or make others auto
107120
107157
 
107121
- Array.from(col.parentNode.children).map(item => {
107122
- if (item.classList.contains('is-row-tool')) return;
107123
- if (item.classList.contains('is-rowadd-tool')) return;
107124
- if (item.classList.contains('is-col-tool')) return;
107158
+ columnsOf(col.parentNode).map(item => {
107125
107159
  // if(this.opts.filterSibling) if(item.classList.contains(this.opts.filterSibling)) return;
107126
107160
 
107127
107161
  if (item === col) return;
@@ -107159,10 +107193,7 @@ class Resize {
107159
107193
  }
107160
107194
  } else {
107161
107195
  // New: if other cols has no width, set to 100%
107162
- Array.from(col.parentNode.children).map(item => {
107163
- if (item.classList.contains('is-row-tool')) return;
107164
- if (item.classList.contains('is-rowadd-tool')) return;
107165
- if (item.classList.contains('is-col-tool')) return;
107196
+ columnsOf(col.parentNode).map(item => {
107166
107197
  // if(this.opts.filterSibling) if(item.classList.contains(this.opts.filterSibling)) return;
107167
107198
 
107168
107199
  if (item === col) return;
@@ -107192,10 +107223,7 @@ class Resize {
107192
107223
  },
107193
107224
  resizeEnd: () => {
107194
107225
  let numOfCols = 1;
107195
- Array.from(col.parentNode.children).map(item => {
107196
- if (item.classList.contains('is-row-tool')) return;
107197
- if (item.classList.contains('is-rowadd-tool')) return;
107198
- if (item.classList.contains('is-col-tool')) return;
107226
+ columnsOf(col.parentNode).map(item => {
107199
107227
  if (item === col) return;
107200
107228
  numOfCols++;
107201
107229
  });
@@ -107207,13 +107235,9 @@ class Resize {
107207
107235
  // adjust others
107208
107236
  const maxWidth = () => {
107209
107237
  let otherWidth = 0;
107210
- Array.from(col.parentNode.children).map(item => {
107211
- if (item.classList.contains('is-row-tool')) return;
107212
- if (item.classList.contains('is-rowadd-tool')) return;
107213
- if (item.classList.contains('is-col-tool')) return;
107238
+ columnsOf(col.parentNode).map(item => {
107214
107239
  // if(this.opts.filterSibling) if(item.classList.contains(this.opts.filterSibling)) return;
107215
107240
 
107216
- if (item.classList.contains('row-tool')) return;
107217
107241
  if (item === col) return;
107218
107242
  otherWidth += item.offsetWidth;
107219
107243
  });
@@ -107225,10 +107249,7 @@ class Resize {
107225
107249
  // percentage = (max / row.offsetWidth) * 100;
107226
107250
  // Or make others auto
107227
107251
 
107228
- Array.from(col.parentNode.children).map(item => {
107229
- if (item.classList.contains('is-row-tool')) return;
107230
- if (item.classList.contains('is-rowadd-tool')) return;
107231
- if (item.classList.contains('is-col-tool')) return;
107252
+ columnsOf(col.parentNode).map(item => {
107232
107253
  // if(this.opts.filterSibling) if(item.classList.contains(this.opts.filterSibling)) return;
107233
107254
 
107234
107255
  if (item === col) return;
@@ -107258,10 +107279,7 @@ class Resize {
107258
107279
  // New: Final fix (if column resized exceeds its max)
107259
107280
  let othersWidth = 0;
107260
107281
  let activeColumnWidth = 0;
107261
- Array.from(col.parentNode.children).map(item => {
107262
- if (item.classList.contains('is-row-tool')) return;
107263
- if (item.classList.contains('is-rowadd-tool')) return;
107264
- if (item.classList.contains('is-col-tool')) return;
107282
+ columnsOf(col.parentNode).map(item => {
107265
107283
  // if(this.opts.filterSibling) if(item.classList.contains(this.opts.filterSibling)) return;
107266
107284
 
107267
107285
  // // Refresh Module
@@ -107279,13 +107297,23 @@ class Resize {
107279
107297
  if (activeColumnWidth > maxResult) {
107280
107298
  percentage = maxResult / row.offsetWidth * 100;
107281
107299
  }
107282
- col.style.width = percentage + '%';
107300
+
107301
+ // The column is carrying a pixel width at this point (set
107302
+ // just above, to measure it). A width is only ever stored as
107303
+ // a percentage, so if the maths produced something CSS will
107304
+ // reject — negative, zero, NaN, Infinity — the assignment
107305
+ // would silently fail and leave those pixels behind, which
107306
+ // then breaks the layout on smaller screens. Fall back to
107307
+ // the column's real share of the row instead.
107308
+ if (!isFinite(percentage) || percentage <= 0) {
107309
+ percentage = col.offsetWidth / row.offsetWidth * 100;
107310
+ }
107311
+ if (isFinite(percentage) && percentage > 0) {
107312
+ col.style.width = percentage + '%';
107313
+ }
107283
107314
 
107284
107315
  // Refresh Module
107285
- Array.from(col.parentNode.children).map(item => {
107286
- if (item.classList.contains('is-row-tool')) return;
107287
- if (item.classList.contains('is-rowadd-tool')) return;
107288
- if (item.classList.contains('is-col-tool')) return;
107316
+ columnsOf(col.parentNode).map(item => {
107289
107317
  if (item.getAttribute('data-html')) {
107290
107318
  let moduleWidth = item.offsetWidth / row.offsetWidth * 100;
107291
107319
  item.style.width = moduleWidth + '%';
@@ -107297,10 +107325,7 @@ class Resize {
107297
107325
  if (col.style.height) {
107298
107326
  col.style.minHeight = col.style.height;
107299
107327
  col.style.height = '';
107300
- Array.from(col.parentNode.children).map(item => {
107301
- if (item.classList.contains('is-row-tool')) return;
107302
- if (item.classList.contains('is-rowadd-tool')) return;
107303
- if (item.classList.contains('is-col-tool')) return;
107328
+ columnsOf(col.parentNode).map(item => {
107304
107329
  // if(this.opts.filterSibling) if(item.classList.contains(this.opts.filterSibling)) return;
107305
107330
 
107306
107331
  if (item === col) return;
@@ -107320,6 +107345,10 @@ class Resize {
107320
107345
  });
107321
107346
  }
107322
107347
  destroy() {
107348
+ if (this.eventTarget && this.normalizeToPercent) {
107349
+ this.eventTarget.removeEventListener('mouseup', this.normalizeToPercent, true);
107350
+ this.eventTarget.removeEventListener('touchend', this.normalizeToPercent, true);
107351
+ }
107323
107352
  this.resize.destroy();
107324
107353
  }
107325
107354
  }
@@ -122499,20 +122528,6 @@ class ContentBuilder {
122499
122528
  // force (and the option itself) disappears at the final step.
122500
122529
  this.opts.engine = 'inscribe';
122501
122530
  this.engine = 'inscribe';
122502
- if (!this.opts.uploadFile && !this.opts.upload) {
122503
- this.opts.uploadFile = async e => {
122504
- const selectedFile = e.target.files[0];
122505
- const reader = new FileReader();
122506
- const result = await new Promise((resolve, reject) => {
122507
- reader.onload = () => resolve(reader.result);
122508
- reader.onerror = reject;
122509
- reader.readAsDataURL(selectedFile);
122510
- });
122511
- return {
122512
- url: result
122513
- };
122514
- };
122515
- }
122516
122531
  if (this.opts.uploadFile) {
122517
122532
  const uploadHandler = async e => {
122518
122533
  const data = await this.opts.uploadFile(e);
@@ -122713,6 +122728,26 @@ class ContentBuilder {
122713
122728
  this.opts.onLargerImageUpload = this.opts.onImageUpload;
122714
122729
  }
122715
122730
 
122731
+ // Last-resort uploader for a builder configured without one (the demo
122732
+ // and no-backend setups): embed the picked file as a data URL. Filled
122733
+ // in per upload type, and only where nothing else provided a handler,
122734
+ // so it can never overwrite `upload`, `uploadFile`, or a legacy
122735
+ // per-type callback — overwriting those turned every upload into a
122736
+ // silent base64 embed instead of calling the host app's uploader.
122737
+ const embedAsDataUrl = async e => {
122738
+ const selectedFile = e.target.files[0];
122739
+ const reader = new FileReader();
122740
+ const result = await new Promise((resolve, reject) => {
122741
+ reader.onload = () => resolve(reader.result);
122742
+ reader.onerror = reject;
122743
+ reader.readAsDataURL(selectedFile);
122744
+ });
122745
+ this.returnUrl(result);
122746
+ };
122747
+ ['onImageUpload', 'onLargerImageUpload', 'onVideoUpload', 'onAudioUpload', 'onMediaUpload', 'onFileUpload'].forEach(name => {
122748
+ if (!this.opts[name]) this.opts[name] = embedAsDataUrl;
122749
+ });
122750
+
122716
122751
  // useButtonPlugin
122717
122752
  if (this.opts.emailMode) {
122718
122753
  this.useButtonPlugin = true;
@@ -128215,7 +128250,11 @@ Please obtain a license at: https://innovastudio.com/contentbox`);
128215
128250
  handleCellKeydown(col, e) {
128216
128251
  if (e.which === 46 || e.which === 8) {
128217
128252
  //delete or backspace
128218
- if (this.activeIcon) {
128253
+ // Only when the selected icon is still live AND belongs to THIS
128254
+ // column: a stale activeIcon (already deleted, or left over from
128255
+ // another column) would otherwise swallow the key — cancelling the
128256
+ // delete without removing anything, or throwing on a detached node.
128257
+ if (this.activeIcon && this.activeIcon.isConnected && col.contains(this.activeIcon)) {
128219
128258
  // Delete icon
128220
128259
  if (this.activeIcon.parentNode.tagName.toLowerCase() === 'a' && this.activeIcon.parentNode.innerText.trim() === '') {
128221
128260
  let link = this.activeIcon.parentNode;
@@ -128303,46 +128342,56 @@ Please obtain a license at: https://innovastudio.com/contentbox`);
128303
128342
  }
128304
128343
  }
128305
128344
  }
128306
- if (e.keyCode === 46) {
128307
- // console.log("delete");
128308
- let curr;
128345
+
128346
+ // LEGACY empty-block guards for Delete/Backspace (module/custom-code
128347
+ // columns only). Inscribe owns block joining in its regions
128348
+ // (_joinBack/_joinFwd + ensureBlocks), so running these there is both
128349
+ // unnecessary and harmful: the Delete branch mutates the DOM behind
128350
+ // Inscribe's back (bypassing its history) and the Backspace branch
128351
+ // cancels the key outright.
128352
+ //
128353
+ // Both read the caret from `this.win` — the CONTENT window — not the
128354
+ // global `window`, and only act when the caret really is inside this
128355
+ // column. Reading the global selection was the cause of "Backspace
128356
+ // stopped working": with the editor in an iframe (or after a click
128357
+ // landed a caret in the editor UI), the global selection points at
128358
+ // some unrelated node whose textContent is '' and which has no
128359
+ // previousElementSibling, so EVERY Backspace was preventDefault()ed —
128360
+ // typing kept working, and only a page reload cleared it.
128361
+ const legacyDeleteGuard = !this.inscribeRegionAt(col, e.target);
128362
+ if (legacyDeleteGuard && (e.keyCode === 46 || e.keyCode === 8)) {
128363
+ let curr = null;
128309
128364
  try {
128310
- if (window.getSelection) {
128311
- curr = window.getSelection().getRangeAt(0).commonAncestorContainer;
128312
- } else if (document.selection) {
128313
- curr = document.selection.createRange();
128314
- }
128315
- if (curr.innerHTML === '<br>') {
128316
- let next = curr.nextElementSibling;
128317
- if (next) {
128318
- curr.parentNode.removeChild(curr); //without this, empty P, H1, H2 (contains only <br />) cannot be deleted (when there is another P below)
128319
- // Of course, we can backspace from the P below, but the formatting will change.
128320
- e.preventDefault();
128365
+ const sel = this.win.getSelection ? this.win.getSelection() : null;
128366
+ if (sel && sel.rangeCount) curr = sel.getRangeAt(0).commonAncestorContainer;
128367
+ } catch (err) {
128368
+ curr = null;
128369
+ }
128370
+ if (curr && col.contains(curr)) {
128371
+ if (e.keyCode === 46) {
128372
+ if (curr.innerHTML === '<br>') {
128373
+ let next = curr.nextElementSibling;
128374
+ if (next) {
128375
+ curr.parentNode.removeChild(curr); //without this, empty P, H1, H2 (contains only <br />) cannot be deleted (when there is another P below)
128376
+ // Of course, we can backspace from the P below, but the formatting will change.
128377
+ e.preventDefault();
128378
+ }
128321
128379
  }
128322
128380
  }
128323
- } catch (e) {
128324
- // Do Nothing
128325
- }
128326
- }
128327
- if (e.keyCode === 8) {
128328
- // delete on Mac
128329
- let curr;
128330
- try {
128331
- if (window.getSelection) {
128332
- curr = window.getSelection().getRangeAt(0).commonAncestorContainer;
128333
- } else if (document.selection) {
128334
- curr = document.selection.createRange();
128335
- }
128336
- if (curr.textContent === '') {
128337
- let prev = curr.previousElementSibling;
128338
- if (!prev) {
128339
- e.preventDefault(); //Without this, empty P, H1, H2 (that doesn't have prev element) will be lost => can make empty column.
128381
+ if (e.keyCode === 8) {
128382
+ // delete on Mac
128383
+ // Element nodes only: a text node has no previousElementSibling,
128384
+ // so an empty text node under the caret would always cancel the key.
128385
+ if (curr.nodeType === 1 && curr.textContent === '') {
128386
+ let prev = curr.previousElementSibling;
128387
+ if (!prev) {
128388
+ e.preventDefault(); //Without this, empty P, H1, H2 (that doesn't have prev element) will be lost => can make empty column.
128389
+ }
128340
128390
  }
128341
128391
  }
128342
- } catch (e) {
128343
- // Do Nothing
128344
128392
  }
128345
128393
  }
128394
+
128346
128395
  if (e.which === 9 && !e.shiftKey) {
128347
128396
  // tab key pressed
128348
128397
  const activeCol = this.activeCol;
@@ -128353,7 +128402,7 @@ Please obtain a license at: https://innovastudio.com/contentbox`);
128353
128402
  if (target) target.click();
128354
128403
  // if(target.firstElementChild) target.firstElementChild.click();
128355
128404
 
128356
- let selection = window.getSelection();
128405
+ let selection = this.win.getSelection();
128357
128406
  let range = this.createRange(target, {
128358
128407
  count: 0
128359
128408
  });
@@ -128377,7 +128426,7 @@ Please obtain a license at: https://innovastudio.com/contentbox`);
128377
128426
  if (firstCol) firstCol.click();
128378
128427
  // if(firstCol.firstElementChild) firstCol.firstElementChild.click();
128379
128428
 
128380
- let selection = window.getSelection();
128429
+ let selection = this.win.getSelection();
128381
128430
  let range = this.createRange(firstCol, {
128382
128431
  count: 0
128383
128432
  });
@@ -128406,7 +128455,7 @@ Please obtain a license at: https://innovastudio.com/contentbox`);
128406
128455
  const target = activeCol.previousElementSibling;
128407
128456
  if (target) target.click();
128408
128457
  if (target.firstElementChild) target.firstElementChild.click();
128409
- let selection = window.getSelection();
128458
+ let selection = this.win.getSelection();
128410
128459
  let range = this.createRange(target, {
128411
128460
  count: 0
128412
128461
  });
@@ -128429,7 +128478,7 @@ Please obtain a license at: https://innovastudio.com/contentbox`);
128429
128478
  const target = divs[divs.length - 1];
128430
128479
  if (target) target.click();
128431
128480
  if (target.firstElementChild) target.firstElementChild.click();
128432
- let selection = window.getSelection();
128481
+ let selection = this.win.getSelection();
128433
128482
  let range = this.createRange(target, {
128434
128483
  count: 0
128435
128484
  });
@@ -128498,7 +128547,7 @@ Please obtain a license at: https://innovastudio.com/contentbox`);
128498
128547
  }
128499
128548
  createRange(node, chars, range) {
128500
128549
  if (!range) {
128501
- range = document.createRange();
128550
+ range = this.doc.createRange();
128502
128551
  range.selectNode(node);
128503
128552
  range.setStart(node, 0);
128504
128553
  }
@@ -161222,6 +161271,7 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
161222
161271
  videoHandler: this.settings.videoHandler,
161223
161272
  audioHandler: this.settings.audioHandler,
161224
161273
  fileHandler: this.settings.fileHandler,
161274
+ upload: this.settings.upload,
161225
161275
  uploadFile: this.settings.uploadFile,
161226
161276
  onVideoUpload: this.settings.onVideoUpload,
161227
161277
  onAudioUpload: this.settings.onAudioUpload,