@kubik-cms/wysiwyg 0.1.26 → 0.1.28

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.
@@ -2406,7 +2406,7 @@ var Document$4 = function(_super) {
2406
2406
  return Document2;
2407
2407
  }(NodeWithChildren);
2408
2408
  node.Document = Document$4;
2409
- var Element = function(_super) {
2409
+ var Element$1 = function(_super) {
2410
2410
  __extends(Element2, _super);
2411
2411
  function Element2(name, attribs, children, type) {
2412
2412
  if (children === void 0) {
@@ -2456,7 +2456,7 @@ var Element = function(_super) {
2456
2456
  });
2457
2457
  return Element2;
2458
2458
  }(NodeWithChildren);
2459
- node.Element = Element;
2459
+ node.Element = Element$1;
2460
2460
  function isTag(node2) {
2461
2461
  return (0, domelementtype_1$1.isTag)(node2);
2462
2462
  }
@@ -2496,7 +2496,7 @@ function cloneNode$1(node2, recursive) {
2496
2496
  result2 = new Comment$5(node2.data);
2497
2497
  } else if (isTag(node2)) {
2498
2498
  var children = recursive ? cloneChildren(node2.children) : [];
2499
- var clone_1 = new Element(node2.name, __assign$1({}, node2.attribs), children);
2499
+ var clone_1 = new Element$1(node2.name, __assign$1({}, node2.attribs), children);
2500
2500
  children.forEach(function(child) {
2501
2501
  return child.parent = clone_1;
2502
2502
  });
@@ -9618,7 +9618,7 @@ and ensure you are accounting for this risk.
9618
9618
  if (skip) {
9619
9619
  if (options.disallowedTagsMode === "discard" || options.disallowedTagsMode === "completelyDiscard") {
9620
9620
  if (frame.innerText && !hasText) {
9621
- const escaped = escapeHtml(frame.innerText);
9621
+ const escaped = escapeHtml2(frame.innerText);
9622
9622
  if (options.textFilter) {
9623
9623
  result2 += options.textFilter(escaped, name);
9624
9624
  } else {
@@ -9641,7 +9641,7 @@ and ensure you are accounting for this risk.
9641
9641
  const shouldPreserveEscapedAttributes = isBeingEscaped && options.preserveEscapedAttributes;
9642
9642
  if (shouldPreserveEscapedAttributes) {
9643
9643
  each(attribs, function(value, a) {
9644
- result2 += " " + a + '="' + escapeHtml(value || "", true) + '"';
9644
+ result2 += " " + a + '="' + escapeHtml2(value || "", true) + '"';
9645
9645
  });
9646
9646
  } else if (!allowedAttributesMap || has(allowedAttributesMap, name) || allowedAttributesMap["*"]) {
9647
9647
  each(attribs, function(value, a) {
@@ -9812,7 +9812,7 @@ and ensure you are accounting for this risk.
9812
9812
  }
9813
9813
  result2 += " " + a;
9814
9814
  if (value && value.length) {
9815
- result2 += '="' + escapeHtml(value, true) + '"';
9815
+ result2 += '="' + escapeHtml2(value, true) + '"';
9816
9816
  } else if (options.allowedEmptyAttributes.includes(a)) {
9817
9817
  result2 += '=""';
9818
9818
  }
@@ -9826,12 +9826,12 @@ and ensure you are accounting for this risk.
9826
9826
  } else {
9827
9827
  result2 += ">";
9828
9828
  if (frame.innerText && !hasText && !options.textFilter) {
9829
- result2 += escapeHtml(frame.innerText);
9829
+ result2 += escapeHtml2(frame.innerText);
9830
9830
  addedText = true;
9831
9831
  }
9832
9832
  }
9833
9833
  if (skip) {
9834
- result2 = tempResult + escapeHtml(result2);
9834
+ result2 = tempResult + escapeHtml2(result2);
9835
9835
  tempResult = "";
9836
9836
  }
9837
9837
  frame.openingTagLength = result2.length - frame.tagPosition;
@@ -9853,7 +9853,7 @@ and ensure you are accounting for this risk.
9853
9853
  } else if (tag && tagAllowed(tag) && (options.disallowedTagsMode === "discard" || options.disallowedTagsMode === "completelyDiscard") && (tag === "textarea" || tag === "xmp")) {
9854
9854
  result2 += text;
9855
9855
  } else if (!addedText) {
9856
- const escaped = escapeHtml(text, false);
9856
+ const escaped = escapeHtml2(text, false);
9857
9857
  if (options.textFilter) {
9858
9858
  result2 += options.textFilter(escaped, tag);
9859
9859
  } else {
@@ -9926,7 +9926,7 @@ and ensure you are accounting for this risk.
9926
9926
  }
9927
9927
  result2 += "</" + name + ">";
9928
9928
  if (skip) {
9929
- result2 = tempResult + escapeHtml(result2);
9929
+ result2 = tempResult + escapeHtml2(result2);
9930
9930
  tempResult = "";
9931
9931
  }
9932
9932
  addedText = false;
@@ -9938,9 +9938,9 @@ and ensure you are accounting for this risk.
9938
9938
  const lastParsedIndex = parser2.endIndex;
9939
9939
  if (lastParsedIndex != null && lastParsedIndex >= 0 && lastParsedIndex < html.length) {
9940
9940
  const unparsed = html.substring(lastParsedIndex);
9941
- result2 += escapeHtml(unparsed);
9941
+ result2 += escapeHtml2(unparsed);
9942
9942
  } else if ((lastParsedIndex == null || lastParsedIndex < 0) && html.length > 0 && result2 === "") {
9943
- result2 = escapeHtml(html);
9943
+ result2 = escapeHtml2(html);
9944
9944
  }
9945
9945
  }
9946
9946
  return result2;
@@ -9953,7 +9953,7 @@ and ensure you are accounting for this risk.
9953
9953
  skipText = false;
9954
9954
  skipTextDepth = 0;
9955
9955
  }
9956
- function escapeHtml(s, quote) {
9956
+ function escapeHtml2(s, quote) {
9957
9957
  if (typeof s !== "string") {
9958
9958
  s = s + "";
9959
9959
  }
@@ -10635,7 +10635,8 @@ class KubikWidgetController extends Controller {
10635
10635
  }
10636
10636
  const addedItem = diff["items"] && diff["items"]["repeated_items"] && JSON.stringify(Object.values(diff["items"]["repeated_items"])[0]) === JSON.stringify({});
10637
10637
  const resourceIdChange = changedKeys.some((key) => key.match(/(^|\.)id$/));
10638
- if (addedItem || resourceIdChange) {
10638
+ const sourceChange = changedKeys.some((key) => key === "settings.source" || key.startsWith("settings.source."));
10639
+ if (addedItem || resourceIdChange || sourceChange) {
10639
10640
  this.getNewWidget();
10640
10641
  }
10641
10642
  }
@@ -10776,6 +10777,9 @@ __publicField(KubikRepeaterController, "values", {
10776
10777
  index: Number,
10777
10778
  expanded: { type: Boolean, default: false }
10778
10779
  });
10780
+ function escapeHtml(value) {
10781
+ return String(value != null ? value : "").replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
10782
+ }
10779
10783
  class KubikKeyValueRepeaterController extends Controller {
10780
10784
  connect() {
10781
10785
  this.dataValue = this.compactRows(this.dataValue);
@@ -10796,30 +10800,49 @@ class KubikKeyValueRepeaterController extends Controller {
10796
10800
  rowIsEmpty(row) {
10797
10801
  return this.fieldsValue.every((field) => (row[field] || "").trim() === "");
10798
10802
  }
10799
- renderFields() {
10800
- this.valuesContainerTarget.innerHTML = "";
10801
- this.dataValue.forEach((item, index2) => {
10802
- const prefix = this.prefixValue != "" ? `<span class='kubik--key_value_repeater--prefix para--small'>${this.prefixValue}</span>` : "";
10803
- const formFields = this.fieldsValue.map((field) => {
10804
- return `<input
10805
- id='${this.fieldNameValue}-${this.widgetIdValue}'
10806
- name='${field}'
10807
- tabindex='1'
10808
- type='text'
10809
- value='${item[field]}'
10810
- class='input'
10811
- placeholder='${field}'
10812
- data-index='${index2}'
10803
+ fieldMarkup(field, item, index2) {
10804
+ const config = (this.fieldConfigValue || {})[field] || {};
10805
+ const value = escapeHtml(item[field] || "");
10806
+ if (config.type === "select") {
10807
+ const options = (config.options || []).map(
10808
+ (option) => `<option value='${escapeHtml(option.value)}' ${option.value === (item[field] || "") ? "selected" : ""}>${escapeHtml(option.label)}</option>`
10809
+ ).join("");
10810
+ return `<div class="input select kubik--key_value_repeater--field-select">
10811
+ <select
10812
+ id='${this.fieldNameValue}-${this.widgetIdValue}-${field}-${index2}'
10813
+ name='${field}'
10814
+ tabindex='1'
10815
+ class='input'
10816
+ data-index='${index2}'
10817
+ data-repeated-key-value='true'
10818
+ data-action='change->kubik-key-value-repeater#updateField'
10819
+ data-item-index='${index2}'
10820
+ >${options}</select>
10821
+ </div>`;
10822
+ }
10823
+ return `<input
10824
+ id='${this.fieldNameValue}-${this.widgetIdValue}-${field}-${index2}'
10825
+ name='${field}'
10826
+ tabindex='1'
10827
+ type='text'
10828
+ value='${value}'
10829
+ class='input'
10830
+ placeholder='${field}'
10831
+ data-index='${index2}'
10813
10832
  data-repeated-key-value='true'
10814
10833
  data-action='input->kubik-key-value-repeater#updateField'
10815
10834
  data-item-index='${index2}'
10816
10835
  >`;
10817
- }).join("");
10818
- const deleteButton = `<button
10819
- class='kubik--key_value_repeater--delete_button material-symbols-outlined'
10836
+ }
10837
+ renderFields() {
10838
+ this.valuesContainerTarget.innerHTML = "";
10839
+ this.dataValue.forEach((item, index2) => {
10840
+ const prefix = this.prefixValue !== "" ? `<span class='kubik--key_value_repeater--prefix para--small'>${this.prefixValue}</span>` : "";
10841
+ const formFields = this.fieldsValue.map((field) => this.fieldMarkup(field, item, index2)).join("");
10842
+ const deleteButton = `<button
10843
+ class='kubik--key_value_repeater--delete_button material-symbols-outlined'
10820
10844
  data-action='click->kubik-key-value-repeater#deleteField'
10821
- data-index='${index2}'>close</button>
10822
- `;
10845
+ data-index='${index2}'>close</button>`;
10823
10846
  this.valuesContainerTarget.insertAdjacentHTML(
10824
10847
  "beforeend",
10825
10848
  `
@@ -10853,11 +10876,7 @@ class KubikKeyValueRepeaterController extends Controller {
10853
10876
  ...this.dataValue[index2],
10854
10877
  [name]: value
10855
10878
  };
10856
- this.dataValue = [
10857
- ...this.dataValue.slice(0, index2),
10858
- updatedField,
10859
- ...this.dataValue.slice(index2 + 1)
10860
- ];
10879
+ this.dataValue = [...this.dataValue.slice(0, index2), updatedField, ...this.dataValue.slice(index2 + 1)];
10861
10880
  }
10862
10881
  addField() {
10863
10882
  const newField = this.fieldsValue.reduce((acc, field) => {
@@ -10875,15 +10894,16 @@ __publicField(KubikKeyValueRepeaterController, "values", {
10875
10894
  widgetId: String,
10876
10895
  fields: Array,
10877
10896
  fieldName: String,
10878
- prefix: String
10897
+ prefix: String,
10898
+ fieldConfig: Object
10879
10899
  });
10880
- function activeAdminPath() {
10900
+ function activeAdminPath$1() {
10881
10901
  var _a;
10882
10902
  return ((_a = document.querySelector("meta[name='active-admin-path']")) == null ? void 0 : _a.content) || "/admin";
10883
10903
  }
10884
10904
  function kubikWysiwygWidgetSrc() {
10885
10905
  var _a;
10886
- return ((_a = document.querySelector("meta[name='kubik-wysiwyg-widget-src']")) == null ? void 0 : _a.content) || `${activeAdminPath()}/kubik_wysiwyg_widget/show`;
10906
+ return ((_a = document.querySelector("meta[name='kubik-wysiwyg-widget-src']")) == null ? void 0 : _a.content) || `${activeAdminPath$1()}/kubik_wysiwyg_widget/show`;
10887
10907
  }
10888
10908
  function makeElement(tagName, classNames = [], attributes = {}, textContent2 = "") {
10889
10909
  const el = document.createElement(tagName);
@@ -11092,12 +11112,13 @@ const widgetWrapper = function widgetWrapper2(details = {}, data) {
11092
11112
  if (details.items_limit) {
11093
11113
  wrapperAttributes["data-kubik-widget-items-max-items-value"] = details.items_limit;
11094
11114
  }
11115
+ const extraClasses = details.setup.widget_classes || (details.setup.widget_class ? details.setup.widget_class.split(/\s+/).filter(Boolean) : []);
11095
11116
  const wrapper = makeElement(
11096
11117
  "turbo-frame",
11097
11118
  [
11098
11119
  "kubik_media_wrapper",
11099
11120
  "kubik-wysiwyg-component",
11100
- details.setup.widget_class
11121
+ ...extraClasses
11101
11122
  ],
11102
11123
  wrapperAttributes
11103
11124
  );
@@ -11133,8 +11154,14 @@ class PluginFactory {
11133
11154
  }
11134
11155
  }
11135
11156
  render() {
11157
+ var _a;
11136
11158
  const widgetId = [this.config.widget_name, this.randomString].join("-");
11137
- const widgetClass = `widget_${this.api.ui.nodes.wrapper.parentElement.dataset.editorId}`;
11159
+ const editorRoot = this.api.ui.nodes.wrapper.parentElement;
11160
+ const editorId = (_a = editorRoot == null ? void 0 : editorRoot.dataset) == null ? void 0 : _a.editorId;
11161
+ const widgetClasses = [
11162
+ editorId ? `widget_${editorId}` : null,
11163
+ this.config.widget_model ? "kubik-wysiwyg-record-list" : null
11164
+ ].filter(Boolean);
11138
11165
  const wrapper = widgetWrapper({
11139
11166
  setup: {
11140
11167
  label: this.label,
@@ -11142,7 +11169,7 @@ class PluginFactory {
11142
11169
  src: this.config.data_src,
11143
11170
  widget_id: widgetId,
11144
11171
  widget_type: this.config.widget_name,
11145
- widget_class: widgetClass,
11172
+ widget_classes: widgetClasses,
11146
11173
  config: this.config
11147
11174
  }
11148
11175
  }, this.data);
@@ -11181,16 +11208,327 @@ function defineBlock({ toolbox, sanitize = {}, widgetConfig }) {
11181
11208
  }
11182
11209
  };
11183
11210
  }
11184
- function contentTab(fields = []) {
11211
+ const DEFAULT_HEADING_LEVEL_OPTIONS = [
11212
+ { value: "h2", label: "H2" },
11213
+ { value: "h3", label: "H3" },
11214
+ { value: "h4", label: "H4" },
11215
+ { value: "h5", label: "H5" },
11216
+ { value: "h6", label: "H6" }
11217
+ ];
11218
+ const DEFAULT_IMAGE_HINT = "Should be at least 1200px wide and not bigger than 4MB";
11219
+ const DEFAULT_DOCUMENT_HINT = "PDF or other downloadable file from the media library";
11220
+ function kubikNavigableWysiwygEnabled() {
11221
+ const meta = document.querySelector("meta[name='kubik-navigable-wysiwyg']");
11222
+ return (meta == null ? void 0 : meta.content) === "true";
11223
+ }
11224
+ function headingLevelField({
11225
+ name = "header_level",
11226
+ label = "Heading level",
11227
+ options = DEFAULT_HEADING_LEVEL_OPTIONS,
11228
+ section
11229
+ } = {}) {
11230
+ const field = {
11231
+ name,
11232
+ label,
11233
+ type: "select",
11234
+ options
11235
+ };
11236
+ if (section)
11237
+ field.section = section;
11238
+ return field;
11239
+ }
11240
+ function titleWithHeadingLevel({
11241
+ titleName = "title",
11242
+ titleLabel = "Title",
11243
+ levelName = "title_header_level",
11244
+ levelLabel = "Title heading level",
11245
+ levels = DEFAULT_HEADING_LEVEL_OPTIONS,
11246
+ section
11247
+ } = {}) {
11248
+ const title = { name: titleName, label: titleLabel, type: "text" };
11249
+ const level = headingLevelField({ name: levelName, label: levelLabel, options: levels, section });
11250
+ if (section)
11251
+ title.section = section;
11252
+ return [title, level];
11253
+ }
11254
+ function imageResourceField({
11255
+ name = "image",
11256
+ label = "Image",
11257
+ model = "Kubik::MediaUpload",
11258
+ section = "thumb",
11259
+ hint = DEFAULT_IMAGE_HINT,
11260
+ variant = "media"
11261
+ } = {}) {
11262
+ return {
11263
+ name,
11264
+ label,
11265
+ model,
11266
+ type: "resource",
11267
+ section,
11268
+ hint,
11269
+ variant
11270
+ };
11271
+ }
11272
+ function documentResourceField({
11273
+ name = "document",
11274
+ label = "Document",
11275
+ model = "Kubik::MediaUpload",
11276
+ section = "thumb",
11277
+ hint = DEFAULT_DOCUMENT_HINT,
11278
+ variant = "document"
11279
+ } = {}) {
11280
+ return {
11281
+ name,
11282
+ label,
11283
+ model,
11284
+ type: "resource",
11285
+ section,
11286
+ hint,
11287
+ variant
11288
+ };
11289
+ }
11290
+ function urlLinkFields({
11291
+ linkName = "link",
11292
+ linkLabel = "Link URL",
11293
+ linkTextName = "link_text",
11294
+ linkTextLabel = "Link text",
11295
+ section
11296
+ } = {}) {
11297
+ const link = { name: linkName, label: linkLabel, type: "text" };
11298
+ const linkText = { name: linkTextName, label: linkTextLabel, type: "text" };
11299
+ if (section) {
11300
+ link.section = section;
11301
+ linkText.section = section;
11302
+ }
11303
+ return [link, linkText];
11304
+ }
11305
+ function navigableLinkField({
11306
+ name = "nav_link",
11307
+ label = "Link",
11308
+ section,
11309
+ fallbackToUrl = true
11310
+ } = {}) {
11311
+ if (!kubikNavigableWysiwygEnabled()) {
11312
+ if (!fallbackToUrl)
11313
+ return [];
11314
+ return urlLinkFields({ linkName: "link", linkTextName: "link_text", section });
11315
+ }
11316
+ const field = {
11317
+ name,
11318
+ label,
11319
+ type: "navigable_link",
11320
+ hint: "Pick a navigation item or published resource. Stored as structured link data."
11321
+ };
11322
+ if (section)
11323
+ field.section = section;
11324
+ return [field];
11325
+ }
11326
+ function linkFieldsForHost({ section, preferNavigable = "auto" } = {}) {
11327
+ const useNavigable = preferNavigable === true || preferNavigable === "auto" && kubikNavigableWysiwygEnabled();
11328
+ if (useNavigable) {
11329
+ return navigableLinkField({ section, fallbackToUrl: true });
11330
+ }
11331
+ return urlLinkFields({ section });
11332
+ }
11333
+ function dataAttributesRepeater({ section } = {}) {
11334
+ const field = {
11335
+ name: "data_attributes",
11336
+ label: "Data attributes",
11337
+ type: "key_value_repeater",
11338
+ prefix: "data",
11339
+ fields: ["key", "value"]
11340
+ };
11341
+ if (section)
11342
+ field.section = section;
11343
+ return field;
11344
+ }
11345
+ function linksKeyValueRepeater({
11346
+ name = "links",
11347
+ label = "Links",
11348
+ prefix = "link",
11349
+ fields = ["text", "url", "target", "style"],
11350
+ styleOptions = [],
11351
+ hint
11352
+ } = {}) {
11353
+ const repeater = {
11354
+ name,
11355
+ label,
11356
+ type: "key_value_repeater",
11357
+ prefix,
11358
+ fields
11359
+ };
11360
+ if (hint)
11361
+ repeater.hint = hint;
11362
+ if (styleOptions.length > 0) {
11363
+ repeater.field_config = {
11364
+ style: {
11365
+ type: "select",
11366
+ options: styleOptions
11367
+ }
11368
+ };
11369
+ }
11370
+ return repeater;
11371
+ }
11372
+ function standardListingItemFields({
11373
+ includeDataAttributes = true,
11374
+ includeHeadingLevel = false,
11375
+ headingLevelOptions = DEFAULT_HEADING_LEVEL_OPTIONS,
11376
+ sections
11377
+ } = {}) {
11378
+ const sectionThumb = sections ? "thumb" : void 0;
11379
+ const sectionFields = sections ? "fields" : void 0;
11380
+ const titleBlock = includeHeadingLevel ? titleWithHeadingLevel({ section: sectionFields, levels: headingLevelOptions }) : [{ name: "title", label: "Title", type: "text", ...sectionFields ? { section: sectionFields } : {} }];
11381
+ const fields = [
11382
+ imageResourceField({ section: sectionThumb }),
11383
+ ...titleBlock,
11384
+ { name: "subtitle", label: "Subtitle", type: "text", ...sectionFields ? { section: sectionFields } : {} },
11385
+ { name: "alt_text", label: "Alt text", type: "text", ...sectionFields ? { section: sectionFields } : {} },
11386
+ { name: "caption", label: "Caption", type: "textarea", ...sectionFields ? { section: sectionFields } : {} },
11387
+ { name: "credit", label: "Credit", type: "text", ...sectionFields ? { section: sectionFields } : {} },
11388
+ { name: "description", label: "Description", type: "textarea", ...sectionFields ? { section: sectionFields } : {} },
11389
+ ...urlLinkFields({ section: sectionFields })
11390
+ ];
11391
+ if (includeDataAttributes) {
11392
+ fields.push(dataAttributesRepeater({ section: sectionFields }));
11393
+ }
11394
+ return fields;
11395
+ }
11396
+ function standardListingItemTabConfig(options = {}) {
11397
+ const sections = options.sections !== false;
11398
+ const sectionDefs = sections ? [
11399
+ { name: "thumb", classes: "thumb" },
11400
+ { name: "fields", classes: "fields" }
11401
+ ] : void 0;
11402
+ return {
11403
+ sections: sectionDefs,
11404
+ fields: standardListingItemFields({ ...options, sections })
11405
+ };
11406
+ }
11407
+ const mediaTypeField = {
11408
+ name: "media_type",
11409
+ label: "Media type",
11410
+ type: "select",
11411
+ section: "fields",
11412
+ options: [
11413
+ { label: "Image", value: "image" },
11414
+ { label: "Video", value: "video" }
11415
+ ]
11416
+ };
11417
+ function mediaTypeSelectField({ types = ["image", "video"], section = "fields" } = {}) {
11418
+ const labelMap = { image: "Image", video: "Video", document: "Document" };
11419
+ return {
11420
+ name: "media_type",
11421
+ label: "Media type",
11422
+ type: "select",
11423
+ section,
11424
+ options: types.map((value) => ({ label: labelMap[value] || value, value }))
11425
+ };
11426
+ }
11427
+ const imageMediaField = {
11428
+ name: "media",
11429
+ label: "Image",
11430
+ model: "Kubik::MediaUpload",
11431
+ type: "resource",
11432
+ section: "thumb",
11433
+ hint: "For image media, or poster frame when using video. At least 1200px wide, max 4MB.",
11434
+ variant: "media"
11435
+ };
11436
+ const videoProviderField = {
11437
+ name: "video_provider",
11438
+ label: "Video source",
11439
+ type: "select",
11440
+ section: "fields",
11441
+ hint: "YouTube and Vimeo accept watch or embed URLs. Stream is for HLS (.m3u8) with Plyr.",
11442
+ options: [
11443
+ { label: "YouTube", value: "youtube" },
11444
+ { label: "Vimeo", value: "vimeo" },
11445
+ { label: "Stream (HLS)", value: "stream" },
11446
+ { label: "Other URL", value: "url" }
11447
+ ]
11448
+ };
11449
+ const videoUrlField = {
11450
+ name: "video_url",
11451
+ label: "Video URL",
11452
+ type: "text",
11453
+ section: "fields",
11454
+ hint: "Embed, watch, direct MP4/WebM, or HLS playlist URL."
11455
+ };
11456
+ const videoPosterField = {
11457
+ name: "video_poster_url",
11458
+ label: "Poster image URL",
11459
+ type: "text",
11460
+ section: "fields",
11461
+ hint: "Optional poster frame for stream or self-hosted video."
11462
+ };
11463
+ const videoCaptionsField = {
11464
+ name: "video_captions_url",
11465
+ label: "Captions URL (English)",
11466
+ type: "text",
11467
+ section: "fields"
11468
+ };
11469
+ const videoCaptionsScotsField = {
11470
+ name: "video_captions_scots_url",
11471
+ label: "Captions URL (Scots)",
11472
+ type: "text",
11473
+ section: "fields"
11474
+ };
11475
+ const videoFields = [
11476
+ videoProviderField,
11477
+ videoUrlField,
11478
+ videoPosterField,
11479
+ videoCaptionsField,
11480
+ videoCaptionsScotsField
11481
+ ];
11482
+ const videoVisibleWhen = { visible_when: { field: "media_type", eq: "video" } };
11483
+ const contentWithMediaVideoFields = [
11484
+ { ...videoProviderField, ...videoVisibleWhen },
11485
+ { ...videoUrlField, ...videoVisibleWhen },
11486
+ { ...videoCaptionsField, ...videoVisibleWhen },
11487
+ { ...videoCaptionsScotsField, ...videoVisibleWhen }
11488
+ ];
11489
+ function mediaTabFields({ mode = "full" } = {}) {
11490
+ if (mode === "content_with_media") {
11491
+ return [mediaTypeField, imageMediaField, ...contentWithMediaVideoFields];
11492
+ }
11493
+ return [mediaTypeField, imageMediaField, ...videoFields];
11494
+ }
11495
+ var fieldPresets = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
11496
+ __proto__: null,
11497
+ DEFAULT_HEADING_LEVEL_OPTIONS,
11498
+ kubikNavigableWysiwygEnabled,
11499
+ headingLevelField,
11500
+ titleWithHeadingLevel,
11501
+ imageResourceField,
11502
+ documentResourceField,
11503
+ urlLinkFields,
11504
+ navigableLinkField,
11505
+ linkFieldsForHost,
11506
+ dataAttributesRepeater,
11507
+ linksKeyValueRepeater,
11508
+ standardListingItemFields,
11509
+ standardListingItemTabConfig,
11510
+ mediaTypeField,
11511
+ mediaTypeSelectField,
11512
+ imageMediaField,
11513
+ videoProviderField,
11514
+ videoUrlField,
11515
+ videoPosterField,
11516
+ videoCaptionsField,
11517
+ videoCaptionsScotsField,
11518
+ videoFields,
11519
+ contentWithMediaVideoFields,
11520
+ mediaTabFields
11521
+ }, Symbol.toStringTag, { value: "Module" }));
11522
+ function contentTab(extraFields = [], { links = "default" } = {}) {
11523
+ const linkFields = links === "none" ? [] : links === "navigable_auto" ? linkFieldsForHost() : urlLinkFields();
11185
11524
  return {
11186
11525
  name: "content",
11187
11526
  label: "Content",
11188
11527
  fields: [
11189
11528
  { name: "title", label: "Title", type: "text" },
11190
11529
  { name: "intro", label: "Intro", type: "textarea" },
11191
- { name: "link", label: "Link", type: "text" },
11192
- { name: "link_text", label: "Link text", type: "text" },
11193
- ...fields
11530
+ ...linkFields,
11531
+ ...extraFields
11194
11532
  ]
11195
11533
  };
11196
11534
  }
@@ -11257,6 +11595,125 @@ function manualItemsTab({
11257
11595
  }
11258
11596
  };
11259
11597
  }
11598
+ function activeAdminPath() {
11599
+ var _a;
11600
+ return ((_a = document.querySelector("meta[name='active-admin-path']")) == null ? void 0 : _a.content) || "/admin";
11601
+ }
11602
+ function recordListSourceField({ allowLiveQuery = true, allowSnapshot = false }) {
11603
+ return {
11604
+ name: "source",
11605
+ label: "Item source",
11606
+ type: "select",
11607
+ store_path: "settings.source",
11608
+ options: [
11609
+ { value: "manual", label: "Hand-picked items (default)" },
11610
+ ...allowLiveQuery ? [{ value: "live_query", label: "Live query (updates on each page view)" }] : [],
11611
+ ...allowSnapshot ? [{ value: "snapshot", label: "Snapshot (fixed list of IDs)" }] : []
11612
+ ]
11613
+ };
11614
+ }
11615
+ function recordListQueryFields(queries = []) {
11616
+ if (queries.length === 0)
11617
+ return [];
11618
+ const liveQueryVisible = { tab: "settings", field: "source", eq: "live_query" };
11619
+ return [
11620
+ {
11621
+ name: "query_key",
11622
+ label: "Query preset",
11623
+ type: "select",
11624
+ store_path: "settings.query_key",
11625
+ visible_when: liveQueryVisible,
11626
+ options: queries.map((q) => ({
11627
+ value: q.value,
11628
+ label: q.label
11629
+ }))
11630
+ },
11631
+ {
11632
+ name: "query_limit",
11633
+ label: "Limit",
11634
+ type: "text",
11635
+ store_path: "settings.query_limit",
11636
+ visible_when: liveQueryVisible,
11637
+ hint: "Number of records (e.g. 5)"
11638
+ },
11639
+ {
11640
+ name: "query_params",
11641
+ label: "Query parameters (JSON)",
11642
+ type: "textarea",
11643
+ store_path: "settings.query_params",
11644
+ visible_when: liveQueryVisible,
11645
+ hint: 'e.g. {"article_type":"news"}'
11646
+ }
11647
+ ];
11648
+ }
11649
+ function recordListItemsTab({
11650
+ name = "items",
11651
+ label = "Items",
11652
+ model,
11653
+ adminSearchPath,
11654
+ itemFields,
11655
+ addLabel = "Add item",
11656
+ summary = "record",
11657
+ sections = thumbAndFieldsSections(),
11658
+ queries = [],
11659
+ allowLiveQuery = true,
11660
+ allowSnapshot = false
11661
+ }) {
11662
+ const base = activeAdminPath().replace(/\/$/, "");
11663
+ let path = adminSearchPath.startsWith("/") ? adminSearchPath : `/${adminSearchPath}`;
11664
+ if (!path.startsWith(base)) {
11665
+ path = `${base}${path}`;
11666
+ }
11667
+ if (!path.endsWith("/wysiwyg_search")) {
11668
+ path = `${path.replace(/\/$/, "")}/wysiwyg_search`;
11669
+ }
11670
+ const fields = itemFields || [
11671
+ {
11672
+ name: "record",
11673
+ label: "Record",
11674
+ model,
11675
+ type: "resource",
11676
+ src: path,
11677
+ section: "thumb"
11678
+ }
11679
+ ];
11680
+ const manualTab = manualItemsTab({
11681
+ name,
11682
+ label,
11683
+ itemFields: fields,
11684
+ addLabel,
11685
+ summary,
11686
+ sections
11687
+ });
11688
+ return {
11689
+ ...manualTab,
11690
+ header_fields: [
11691
+ recordListSourceField({ allowLiveQuery, allowSnapshot }),
11692
+ ...recordListQueryFields(queries)
11693
+ ],
11694
+ repeater_visible_when: { tab: "settings", field: "source", not_eq: "live_query" }
11695
+ };
11696
+ }
11697
+ function recordListSettingsTab({
11698
+ layouts = [],
11699
+ extras = []
11700
+ }) {
11701
+ const fields = [];
11702
+ if (layouts.length > 0) {
11703
+ fields.push({
11704
+ name: "layout",
11705
+ label: "Layout",
11706
+ type: "select",
11707
+ options: layouts
11708
+ });
11709
+ }
11710
+ fields.push(
11711
+ { name: "additional_classes", type: "text", label: "Additional classes" },
11712
+ { name: "element_id", type: "text", label: "Element ID" },
11713
+ ...extras
11714
+ );
11715
+ return { name: "settings", label: "Settings", fields };
11716
+ }
11260
11717
  function resourceListSettingsTab({
11261
11718
  filterField = "filter",
11262
11719
  filterLabel = "Filter",
@@ -11294,10 +11751,136 @@ const presets = {
11294
11751
  standardSettingsExtras,
11295
11752
  thumbAndFieldsSections,
11296
11753
  manualItemsTab,
11297
- resourceListSettingsTab
11754
+ recordListItemsTab,
11755
+ recordListSettingsTab,
11756
+ resourceListSettingsTab,
11757
+ ...fieldPresets
11298
11758
  };
11299
- const DEFAULT_INLINE_TOOLBAR = ["bold", "italic", "underline", "hyperlink"];
11300
- const WIDGET_INLINE_TOOLBAR = ["underline", "hyperlink"];
11759
+ function normalizeSearchPath$1(adminSearchPath) {
11760
+ const base = activeAdminPath$1().replace(/\/$/, "");
11761
+ let path = adminSearchPath.startsWith("/") ? adminSearchPath : `/${adminSearchPath}`;
11762
+ if (!path.startsWith(base)) {
11763
+ path = `${base}${path}`;
11764
+ }
11765
+ if (!path.endsWith("/wysiwyg_search")) {
11766
+ path = `${path.replace(/\/$/, "")}/wysiwyg_search`;
11767
+ }
11768
+ return path;
11769
+ }
11770
+ function defaultRecordItemFields(model, adminSearchPath) {
11771
+ return [
11772
+ {
11773
+ name: "record",
11774
+ label: "Record",
11775
+ model,
11776
+ type: "resource",
11777
+ src: normalizeSearchPath$1(adminSearchPath),
11778
+ section: "thumb"
11779
+ }
11780
+ ];
11781
+ }
11782
+ function defineRecordListBlock({
11783
+ toolbox,
11784
+ sanitize = { intro: { br: true } },
11785
+ widget_name,
11786
+ model,
11787
+ adminSearchPath,
11788
+ itemFields,
11789
+ queries = [],
11790
+ layouts = [],
11791
+ settingsExtras = [],
11792
+ addLabel = "Add item",
11793
+ allowLiveQuery = true,
11794
+ allowSnapshot = false
11795
+ }) {
11796
+ if (!toolbox || !widget_name || !model || !adminSearchPath) {
11797
+ throw new Error("defineRecordListBlock requires toolbox, widget_name, model, and adminSearchPath");
11798
+ }
11799
+ const fields = itemFields || defaultRecordItemFields(model, adminSearchPath);
11800
+ return defineBlock({
11801
+ toolbox,
11802
+ sanitize,
11803
+ widgetConfig: {
11804
+ widget_name,
11805
+ widget_model: model,
11806
+ icon: toolbox.icon,
11807
+ tabs: [
11808
+ presets.recordListItemsTab({
11809
+ model,
11810
+ adminSearchPath,
11811
+ itemFields: fields,
11812
+ addLabel,
11813
+ queries,
11814
+ allowLiveQuery,
11815
+ allowSnapshot
11816
+ }),
11817
+ presets.contentTab(),
11818
+ presets.recordListSettingsTab({
11819
+ layouts,
11820
+ extras: settingsExtras
11821
+ })
11822
+ ]
11823
+ }
11824
+ });
11825
+ }
11826
+ function normalizeSearchPath(adminSearchPath) {
11827
+ const base = activeAdminPath$1().replace(/\/$/, "");
11828
+ let path = adminSearchPath.startsWith("/") ? adminSearchPath : `/${adminSearchPath}`;
11829
+ if (!path.startsWith(base)) {
11830
+ path = `${base}${path}`;
11831
+ }
11832
+ if (!path.endsWith("/wysiwyg_search")) {
11833
+ path = `${path.replace(/\/$/, "")}/wysiwyg_search`;
11834
+ }
11835
+ return path;
11836
+ }
11837
+ function defineResourceReferenceBlock({
11838
+ toolbox,
11839
+ sanitize = {},
11840
+ widget_name,
11841
+ model,
11842
+ adminSearchPath,
11843
+ referenceTabName = "reference",
11844
+ referenceFieldName,
11845
+ referenceLabel,
11846
+ contentFields = [],
11847
+ settings = {}
11848
+ }) {
11849
+ var _a, _b;
11850
+ const fieldName = referenceFieldName || referenceTabName;
11851
+ const searchPath = normalizeSearchPath(adminSearchPath);
11852
+ const referenceTab = {
11853
+ name: referenceTabName,
11854
+ label: referenceLabel || model.split("::").pop(),
11855
+ fields: [
11856
+ {
11857
+ name: fieldName,
11858
+ label: referenceLabel || model.split("::").pop(),
11859
+ model,
11860
+ type: "resource",
11861
+ src: searchPath
11862
+ }
11863
+ ]
11864
+ };
11865
+ const tabs = [
11866
+ referenceTab,
11867
+ presets.contentTab(contentFields)
11868
+ ];
11869
+ if (((_a = settings.layouts) == null ? void 0 : _a.length) || ((_b = settings.extras) == null ? void 0 : _b.length)) {
11870
+ tabs.push(presets.settingsTab(settings));
11871
+ }
11872
+ return defineBlock({
11873
+ toolbox,
11874
+ sanitize,
11875
+ widgetConfig: {
11876
+ widget_name,
11877
+ widget_model: model,
11878
+ tabs
11879
+ }
11880
+ });
11881
+ }
11882
+ const DEFAULT_INLINE_TOOLBAR = ["bold", "italic", "underline", "hyperlink", "kubikNavigableLink"];
11883
+ const WIDGET_INLINE_TOOLBAR = ["underline", "hyperlink", "kubikNavigableLink"];
11301
11884
  const EDITORJS_INLINE_ICON_SPRITE_ID = "kubik-editorjs-inline-icons";
11302
11885
  const EDITORJS_LINK_ICON_SPRITE = `
11303
11886
  <symbol id="link" viewBox="0 0 14 10">
@@ -11320,6 +11903,63 @@ function ensureEditorJsInlineIcons() {
11320
11903
  svg.innerHTML = EDITORJS_LINK_ICON_SPRITE;
11321
11904
  document.body.appendChild(svg);
11322
11905
  }
11906
+ class KubikNavigableLinkTool {
11907
+ static get isInline() {
11908
+ return true;
11909
+ }
11910
+ static get title() {
11911
+ return "Kubik link";
11912
+ }
11913
+ constructor({ api }) {
11914
+ this.api = api;
11915
+ this.button = null;
11916
+ this.state = false;
11917
+ }
11918
+ render() {
11919
+ this.button = document.createElement("button");
11920
+ this.button.type = "button";
11921
+ this.button.textContent = "Nav";
11922
+ this.button.classList.add(this.api.styles.inlineToolButton);
11923
+ return this.button;
11924
+ }
11925
+ surround(range) {
11926
+ const selected = range.toString();
11927
+ const kind = window.prompt("Link kind: item, resource, or url", "url");
11928
+ if (!kind)
11929
+ return;
11930
+ const anchor = document.createElement("a");
11931
+ if (kind === "url") {
11932
+ const href = window.prompt("URL", "https://");
11933
+ if (!href)
11934
+ return;
11935
+ anchor.setAttribute("href", href);
11936
+ anchor.setAttribute("data-kubik-nav", JSON.stringify({ kind: "url", href }));
11937
+ } else if (kind === "item") {
11938
+ const id = window.prompt("Navigation item id");
11939
+ if (!id)
11940
+ return;
11941
+ anchor.setAttribute("href", "#");
11942
+ anchor.setAttribute("data-kubik-nav", JSON.stringify({ kind: "item", navigation_item_id: id }));
11943
+ } else {
11944
+ const resourceType = window.prompt("Resource type (e.g. Page)");
11945
+ const resourceId = window.prompt("Resource id");
11946
+ const locationId = window.prompt("Navigation item id (blank = canonical)", "");
11947
+ if (!resourceType || !resourceId)
11948
+ return;
11949
+ const payload = { kind: "resource", resource_type: resourceType, resource_id: resourceId };
11950
+ if (locationId)
11951
+ payload.navigation_item_id = locationId;
11952
+ anchor.setAttribute("href", "#");
11953
+ anchor.setAttribute("data-kubik-nav", JSON.stringify(payload));
11954
+ }
11955
+ anchor.textContent = selected;
11956
+ range.deleteContents();
11957
+ range.insertNode(anchor);
11958
+ }
11959
+ checkState() {
11960
+ return false;
11961
+ }
11962
+ }
11323
11963
  function buildBaseEditorTools({
11324
11964
  widgetInlineToolbar = WIDGET_INLINE_TOOLBAR,
11325
11965
  richInlineToolbar = DEFAULT_INLINE_TOOLBAR
@@ -11333,6 +11973,9 @@ function buildBaseEditorTools({
11333
11973
  class: Hyperlink,
11334
11974
  config: {}
11335
11975
  },
11976
+ kubikNavigableLink: {
11977
+ class: KubikNavigableLinkTool
11978
+ },
11336
11979
  embed: {
11337
11980
  class: Embed,
11338
11981
  config: {
@@ -11369,17 +12012,476 @@ function buildBaseEditorTools({
11369
12012
  underline: Underline
11370
12013
  };
11371
12014
  }
12015
+ const RECORD_LIST_FRAME_SELECTOR = "turbo-frame.kubik-wysiwyg-component.kubik-wysiwyg-record-list";
12016
+ const ITEMS_TAB = "items";
12017
+ function stimulusApplication() {
12018
+ return window.KubikInterfaceStimulus;
12019
+ }
12020
+ function recordListFrames(scope = document) {
12021
+ return scope.querySelectorAll(RECORD_LIST_FRAME_SELECTOR);
12022
+ }
12023
+ function widgetController(frame) {
12024
+ var _a;
12025
+ const app = stimulusApplication();
12026
+ if (!app || !((_a = frame == null ? void 0 : frame.hasAttribute) == null ? void 0 : _a.call(frame, "data-controller")))
12027
+ return null;
12028
+ return app.getControllerForElementAndIdentifier(frame, "kubik-widget");
12029
+ }
12030
+ function itemsRepeaterContainer(frame) {
12031
+ const panel = frame.querySelector(`[class*="-tabs-${ITEMS_TAB}"]`);
12032
+ if (!panel)
12033
+ return null;
12034
+ return panel.querySelector(".kubik-wysiwyg-tabs-container:has(> .kubik-wysiwyg-repeated-item)");
12035
+ }
12036
+ const DRAG_HANDLE_SELECTOR = ".kubik-wysiwyg-repeated-item-header .kubik-wysiwyg-component--items-action--more";
12037
+ function enhanceDragHandles(frame) {
12038
+ frame.querySelectorAll(DRAG_HANDLE_SELECTOR).forEach((more) => {
12039
+ var _a;
12040
+ more.classList.add("kubik-wysiwyg-repeater-drag-handle");
12041
+ more.setAttribute("title", "Drag to reorder");
12042
+ more.removeAttribute("tabindex");
12043
+ const icon = more.querySelector(".material-symbols-outlined");
12044
+ if (icon) {
12045
+ icon.textContent = "drag_indicator";
12046
+ }
12047
+ (_a = more.querySelector(".kubik-wysiwyg-component--items-more-actions")) == null ? void 0 : _a.remove();
12048
+ });
12049
+ }
12050
+ function watchRecordListFrame(frame) {
12051
+ if (frame._recordListMutationObserver)
12052
+ return;
12053
+ let scheduled = false;
12054
+ const scheduleEnhance = () => {
12055
+ if (scheduled)
12056
+ return;
12057
+ scheduled = true;
12058
+ requestAnimationFrame(() => {
12059
+ scheduled = false;
12060
+ enhanceDragHandles(frame);
12061
+ });
12062
+ };
12063
+ frame._recordListMutationObserver = new MutationObserver(scheduleEnhance);
12064
+ frame._recordListMutationObserver.observe(frame, { childList: true, subtree: true });
12065
+ }
12066
+ const DROP_PLACEHOLDER_CLASS = "kubik-wysiwyg-repeated-item-drop-placeholder";
12067
+ function createDropPlaceholder(row) {
12068
+ const placeholder = document.createElement("div");
12069
+ placeholder.className = DROP_PLACEHOLDER_CLASS;
12070
+ placeholder.setAttribute("aria-hidden", "true");
12071
+ const rowHeight = row.offsetHeight;
12072
+ const header = row.querySelector(".kubik-wysiwyg-repeated-item-header");
12073
+ const slotHeight = header ? header.offsetHeight : rowHeight;
12074
+ placeholder.style.minHeight = `${Math.max(slotHeight, 40)}px`;
12075
+ return placeholder;
12076
+ }
12077
+ function dropInsertReference(container2, over, placeholder, insertBefore) {
12078
+ let ref = insertBefore ? over : over.nextSibling;
12079
+ while (ref && ref === placeholder) {
12080
+ ref = ref.nextSibling;
12081
+ }
12082
+ return ref;
12083
+ }
12084
+ function movePlaceholder(dragging, over, insertBefore) {
12085
+ const { container: container2, placeholder } = dragging;
12086
+ const ref = dropInsertReference(container2, over, placeholder, insertBefore);
12087
+ if (!ref) {
12088
+ container2.appendChild(placeholder);
12089
+ return;
12090
+ }
12091
+ container2.insertBefore(placeholder, ref);
12092
+ }
12093
+ function sanitizeDragGhost(ghost) {
12094
+ ghost.querySelectorAll("[id]").forEach((element) => element.removeAttribute("id"));
12095
+ ghost.querySelectorAll("[data-controller]").forEach((element) => element.removeAttribute("data-controller"));
12096
+ ghost.querySelectorAll("[data-action]").forEach((element) => element.removeAttribute("data-action"));
12097
+ ghost.querySelectorAll(".kubik-repeater-expand-action").forEach((icon) => {
12098
+ var _a;
12099
+ (_a = icon.closest(".kubik-wysiwyg-component--items-action")) == null ? void 0 : _a.remove();
12100
+ });
12101
+ }
12102
+ function createDragGhost(row) {
12103
+ const ghost = row.cloneNode(true);
12104
+ ghost.classList.add("kubik-wysiwyg-repeated-item-drag-ghost");
12105
+ ghost.classList.remove("kubik-widget-expanded");
12106
+ ghost.setAttribute("aria-hidden", "true");
12107
+ sanitizeDragGhost(ghost);
12108
+ return ghost;
12109
+ }
12110
+ function mountDragGhost(ghost, row, frame, event, rowRect) {
12111
+ ghost.style.width = `${rowRect.width}px`;
12112
+ ghost.style.maxWidth = `${rowRect.width}px`;
12113
+ ghost.style.boxSizing = "border-box";
12114
+ ghost.style.position = "fixed";
12115
+ ghost.style.margin = "0";
12116
+ ghost.style.zIndex = "100000";
12117
+ ghost.style.pointerEvents = "none";
12118
+ frame.classList.add("kubik-wysiwyg-component--drag-active");
12119
+ document.body.appendChild(ghost);
12120
+ const offsets = {
12121
+ offsetX: event.clientX - rowRect.left,
12122
+ offsetY: event.clientY - rowRect.top
12123
+ };
12124
+ moveDragGhost(ghost, event, offsets);
12125
+ return offsets;
12126
+ }
12127
+ function moveDragGhost(ghost, event, offsets) {
12128
+ ghost.style.left = `${event.clientX - offsets.offsetX}px`;
12129
+ ghost.style.top = `${event.clientY - offsets.offsetY}px`;
12130
+ }
12131
+ function removeDragGhost(ghost, frame) {
12132
+ frame == null ? void 0 : frame.classList.remove("kubik-wysiwyg-component--drag-active");
12133
+ ghost == null ? void 0 : ghost.remove();
12134
+ }
12135
+ function dropTargetRow(container2, event) {
12136
+ const target = document.elementFromPoint(event.clientX, event.clientY);
12137
+ let over = target == null ? void 0 : target.closest(".kubik-wysiwyg-repeated-item");
12138
+ if (!over && (target == null ? void 0 : target.closest(`.${DROP_PLACEHOLDER_CLASS}`))) {
12139
+ const placeholder = target.closest(`.${DROP_PLACEHOLDER_CLASS}`);
12140
+ const prev = placeholder.previousElementSibling;
12141
+ const next = placeholder.nextElementSibling;
12142
+ if (prev == null ? void 0 : prev.classList.contains("kubik-wysiwyg-repeated-item")) {
12143
+ over = prev;
12144
+ } else if (next == null ? void 0 : next.classList.contains("kubik-wysiwyg-repeated-item")) {
12145
+ over = next;
12146
+ }
12147
+ }
12148
+ if (!over || !container2.contains(over)) {
12149
+ return null;
12150
+ }
12151
+ return over;
12152
+ }
12153
+ function finishDragPlacement(row, container2, placeholder) {
12154
+ container2.insertBefore(row, placeholder);
12155
+ placeholder.remove();
12156
+ }
12157
+ function moveRepeaterItem(controller, tab, fromIndex, toIndex) {
12158
+ if (fromIndex === toIndex || fromIndex < 0 || toIndex < 0)
12159
+ return;
12160
+ const tabData = controller.dataValue[tab] || {};
12161
+ const items = [...tabData.repeated_items || []];
12162
+ if (fromIndex >= items.length || toIndex >= items.length)
12163
+ return;
12164
+ const [moved] = items.splice(fromIndex, 1);
12165
+ items.splice(toIndex, 0, moved);
12166
+ controller.dataValue = {
12167
+ ...controller.dataValue,
12168
+ [tab]: {
12169
+ ...tabData,
12170
+ repeated_items: items
12171
+ }
12172
+ };
12173
+ controller.getNewWidget();
12174
+ }
12175
+ function installManualDragReorder(frame) {
12176
+ if (frame.dataset.recordListReorderBound === "true")
12177
+ return;
12178
+ frame.dataset.recordListReorderBound = "true";
12179
+ let dragging = null;
12180
+ const endDrag = (event) => {
12181
+ if (!dragging)
12182
+ return;
12183
+ if (event.type === "pointerup" && event.pointerId !== dragging.pointerId)
12184
+ return;
12185
+ document.removeEventListener("pointermove", onPointerMove, true);
12186
+ document.removeEventListener("pointerup", endDrag, true);
12187
+ document.removeEventListener("pointercancel", endDrag, true);
12188
+ const { row, ghost, container: container2, fromIndex, placeholder } = dragging;
12189
+ removeDragGhost(ghost, frame);
12190
+ finishDragPlacement(row, container2, placeholder);
12191
+ dragging = null;
12192
+ const controller = widgetController(frame);
12193
+ if (!controller)
12194
+ return;
12195
+ const rows = [...container2.querySelectorAll(":scope > .kubik-wysiwyg-repeated-item")];
12196
+ const toIndex = rows.indexOf(row);
12197
+ moveRepeaterItem(controller, ITEMS_TAB, fromIndex, toIndex);
12198
+ };
12199
+ const onPointerMove = (event) => {
12200
+ if (!dragging || event.pointerId !== dragging.pointerId)
12201
+ return;
12202
+ moveDragGhost(dragging.ghost, event, dragging);
12203
+ const over = dropTargetRow(dragging.container, event);
12204
+ if (!over)
12205
+ return;
12206
+ const rect = over.getBoundingClientRect();
12207
+ const insertBefore = event.clientY < rect.top + rect.height / 2;
12208
+ movePlaceholder(dragging, over, insertBefore);
12209
+ };
12210
+ frame.addEventListener(
12211
+ "pointerdown",
12212
+ (event) => {
12213
+ const handle = event.target.closest(DRAG_HANDLE_SELECTOR);
12214
+ if (!handle || !frame.contains(handle))
12215
+ return;
12216
+ const container2 = itemsRepeaterContainer(frame);
12217
+ const row = handle.closest(".kubik-wysiwyg-repeated-item");
12218
+ if (!container2 || !row || !container2.contains(row))
12219
+ return;
12220
+ const fromIndex = Number(row.dataset.kubikRepeaterIndexValue);
12221
+ if (Number.isNaN(fromIndex))
12222
+ return;
12223
+ const rowRect = row.getBoundingClientRect();
12224
+ event.preventDefault();
12225
+ event.stopImmediatePropagation();
12226
+ const placeholder = createDropPlaceholder(row);
12227
+ container2.insertBefore(placeholder, row);
12228
+ container2.removeChild(row);
12229
+ const ghost = createDragGhost(row);
12230
+ const offsets = mountDragGhost(ghost, row, frame, event, rowRect);
12231
+ dragging = {
12232
+ row,
12233
+ ghost,
12234
+ container: container2,
12235
+ fromIndex,
12236
+ placeholder,
12237
+ pointerId: event.pointerId,
12238
+ ...offsets
12239
+ };
12240
+ document.addEventListener("pointermove", onPointerMove, true);
12241
+ document.addEventListener("pointerup", endDrag, true);
12242
+ document.addEventListener("pointercancel", endDrag, true);
12243
+ },
12244
+ true
12245
+ );
12246
+ }
12247
+ function enhanceRecordListFrame(frame) {
12248
+ enhanceDragHandles(frame);
12249
+ installManualDragReorder(frame);
12250
+ watchRecordListFrame(frame);
12251
+ }
12252
+ function scanRecordListWidgets(scope = document) {
12253
+ recordListFrames(scope).forEach(enhanceRecordListFrame);
12254
+ }
12255
+ function patchGetNewWidgetRebind() {
12256
+ var _a, _b;
12257
+ if ((_a = window.Turbo) == null ? void 0 : _a.__recordListRenderPatched)
12258
+ return;
12259
+ if (!((_b = window.Turbo) == null ? void 0 : _b.renderStreamMessage))
12260
+ return;
12261
+ const originalRender = window.Turbo.renderStreamMessage.bind(window.Turbo);
12262
+ window.Turbo.renderStreamMessage = (html) => {
12263
+ const result2 = originalRender(html);
12264
+ requestAnimationFrame(() => scanRecordListWidgets());
12265
+ return result2;
12266
+ };
12267
+ window.Turbo.__recordListRenderPatched = true;
12268
+ }
12269
+ function patchRecordListWidgetConnect() {
12270
+ if (KubikWidgetController.__recordListConnectPatched)
12271
+ return;
12272
+ const previousConnect = KubikWidgetController.prototype.connect;
12273
+ KubikWidgetController.prototype.connect = function connectRecordListEnhancements() {
12274
+ previousConnect.call(this);
12275
+ if (this.element.classList.contains("kubik-wysiwyg-record-list")) {
12276
+ requestAnimationFrame(() => enhanceRecordListFrame(this.element));
12277
+ }
12278
+ };
12279
+ KubikWidgetController.__recordListConnectPatched = true;
12280
+ }
12281
+ function patchAutocompleteUnpublishedResults() {
12282
+ if (KubikAutocompleteController.__recordListUnpublishedClassPatched)
12283
+ return;
12284
+ const previousResultsChanged = KubikAutocompleteController.prototype.resultsValueChanged;
12285
+ KubikAutocompleteController.prototype.resultsValueChanged = function resultsValueChangedWithUnpublishedClass() {
12286
+ previousResultsChanged.call(this);
12287
+ this.resultTargets.forEach((result2) => {
12288
+ var _a;
12289
+ try {
12290
+ const payload = JSON.parse(result2.dataset.returnObject || "{}");
12291
+ const statusInfo = ((_a = payload.return_object) == null ? void 0 : _a.status_info) || {};
12292
+ if (statusInfo.unpublished) {
12293
+ result2.classList.add("kubik-wysiwyg-autocomplete-result--unpublished");
12294
+ }
12295
+ } catch {
12296
+ }
12297
+ });
12298
+ };
12299
+ KubikAutocompleteController.__recordListUnpublishedClassPatched = true;
12300
+ }
12301
+ function applyRecordListWidgetAdminEnhancements() {
12302
+ patchRecordListWidgetConnect();
12303
+ patchGetNewWidgetRebind();
12304
+ patchAutocompleteUnpublishedResults();
12305
+ scanRecordListWidgets();
12306
+ document.addEventListener("turbo:frame-load", (event) => {
12307
+ const target = event.target;
12308
+ if (!(target instanceof Element))
12309
+ return;
12310
+ scanRecordListWidgets(target);
12311
+ });
12312
+ document.addEventListener("turbo:load", () => scanRecordListWidgets());
12313
+ }
12314
+ const LOADING_CLASS = "kubik-wysiwyg-component--loading";
12315
+ const LOADING_SELECTOR = ".kubik-wysiwyg-component__loading";
12316
+ const SETTLE_MS = 120;
12317
+ const WIDGET_FRAME_SELECTOR = "turbo-frame.kubik-wysiwyg-component";
12318
+ function widgetContentLoaded(frame) {
12319
+ return Boolean(
12320
+ frame.querySelector(
12321
+ ".kubik-wysiwyg-tabs-container, .kubik-wysiwyg-widget-header, .kubik-wysiwyg--standard-fields"
12322
+ )
12323
+ );
12324
+ }
12325
+ function ensureLoadingSpinner(frame) {
12326
+ if (!(frame instanceof Element))
12327
+ return;
12328
+ if (frame.querySelector(LOADING_SELECTOR))
12329
+ return;
12330
+ const overlay = document.createElement("div");
12331
+ overlay.className = "kubik-wysiwyg-component__loading";
12332
+ overlay.setAttribute("aria-busy", "true");
12333
+ overlay.setAttribute("aria-label", "Loading widget");
12334
+ overlay.innerHTML = '<span class="kubik-wysiwyg-component__loading-spinner"></span>';
12335
+ frame.appendChild(overlay);
12336
+ }
12337
+ function showWidgetLoading(frame) {
12338
+ if (!(frame instanceof Element))
12339
+ return;
12340
+ ensureLoadingSpinner(frame);
12341
+ frame.classList.add(LOADING_CLASS);
12342
+ frame.setAttribute("aria-busy", "true");
12343
+ }
12344
+ function hideWidgetLoading(frame) {
12345
+ if (!(frame instanceof Element))
12346
+ return;
12347
+ if (!widgetContentLoaded(frame))
12348
+ return;
12349
+ frame.classList.remove(LOADING_CLASS);
12350
+ frame.removeAttribute("aria-busy");
12351
+ }
12352
+ function waitForWidgetContent(frame) {
12353
+ return new Promise((resolve2) => {
12354
+ if (widgetContentLoaded(frame)) {
12355
+ window.setTimeout(resolve2, SETTLE_MS);
12356
+ return;
12357
+ }
12358
+ let settleTimer;
12359
+ const finish = () => {
12360
+ if (!widgetContentLoaded(frame))
12361
+ return;
12362
+ window.clearTimeout(settleTimer);
12363
+ observer.disconnect();
12364
+ window.setTimeout(resolve2, SETTLE_MS);
12365
+ };
12366
+ const observer = new MutationObserver(() => {
12367
+ window.clearTimeout(settleTimer);
12368
+ settleTimer = window.setTimeout(finish, SETTLE_MS);
12369
+ });
12370
+ observer.observe(frame, { childList: true, subtree: true, attributes: true });
12371
+ settleTimer = window.setTimeout(() => {
12372
+ observer.disconnect();
12373
+ resolve2();
12374
+ }, 15e3);
12375
+ });
12376
+ }
12377
+ function scanWidgetFrames(root2 = document) {
12378
+ root2.querySelectorAll(WIDGET_FRAME_SELECTOR).forEach((frame) => {
12379
+ if (widgetContentLoaded(frame)) {
12380
+ hideWidgetLoading(frame);
12381
+ } else {
12382
+ showWidgetLoading(frame);
12383
+ }
12384
+ });
12385
+ }
12386
+ function patchPluginFactoryInitialLoading() {
12387
+ if (!PluginFactory || PluginFactory.__initialWidgetLoadingPatched)
12388
+ return;
12389
+ const originalRender = PluginFactory.prototype.render;
12390
+ PluginFactory.prototype.render = function renderWithInitialLoading() {
12391
+ const wrapper = originalRender.call(this);
12392
+ showWidgetLoading(wrapper);
12393
+ return wrapper;
12394
+ };
12395
+ PluginFactory.__initialWidgetLoadingPatched = true;
12396
+ }
12397
+ function patchKubikWidgetLoading() {
12398
+ if (!KubikWidgetController || KubikWidgetController.__widgetLoadingUiPatched)
12399
+ return;
12400
+ const previousConnect = KubikWidgetController.prototype.connect;
12401
+ KubikWidgetController.prototype.connect = function connectWithLoadingUi() {
12402
+ showWidgetLoading(this.element);
12403
+ return previousConnect.call(this);
12404
+ };
12405
+ const previousGetNewWidget = KubikWidgetController.prototype.getNewWidget;
12406
+ KubikWidgetController.prototype.getNewWidget = function getNewWidgetWithLoadingUi() {
12407
+ const frame = this.element;
12408
+ showWidgetLoading(frame);
12409
+ previousGetNewWidget.call(this);
12410
+ waitForWidgetContent(frame).then(() => {
12411
+ hideWidgetLoading(frame);
12412
+ });
12413
+ };
12414
+ KubikWidgetController.__widgetLoadingUiPatched = true;
12415
+ }
12416
+ function observeEditorWidgets() {
12417
+ const observer = new MutationObserver((mutations) => {
12418
+ mutations.forEach((mutation) => {
12419
+ mutation.addedNodes.forEach((node2) => {
12420
+ if (!(node2 instanceof Element))
12421
+ return;
12422
+ if (node2.matches(WIDGET_FRAME_SELECTOR)) {
12423
+ scanWidgetFrames(node2.parentElement || document);
12424
+ return;
12425
+ }
12426
+ scanWidgetFrames(node2);
12427
+ });
12428
+ });
12429
+ });
12430
+ observer.observe(document.body, { childList: true, subtree: true });
12431
+ }
12432
+ function applyKubikWysiwygWidgetLoading() {
12433
+ patchPluginFactoryInitialLoading();
12434
+ patchKubikWidgetLoading();
12435
+ scanWidgetFrames();
12436
+ observeEditorWidgets();
12437
+ document.addEventListener("turbo:load", () => scanWidgetFrames());
12438
+ document.addEventListener("turbo:frame-load", (event) => {
12439
+ if (event.target instanceof Element) {
12440
+ scanWidgetFrames(event.target);
12441
+ }
12442
+ });
12443
+ }
12444
+ const DEFAULT_MEDIA_TYPE_FIELD = "media.media_type";
12445
+ function refreshWidgetAfterMediaTypeChange(select) {
12446
+ var _a;
12447
+ const widgetEl = select.closest('[data-controller~="kubik-widget"]');
12448
+ if (!widgetEl)
12449
+ return;
12450
+ const app = window.KubikInterfaceStimulus;
12451
+ const controller = app == null ? void 0 : app.getControllerForElementAndIdentifier(widgetEl, "kubik-widget");
12452
+ (_a = controller == null ? void 0 : controller.getNewWidget) == null ? void 0 : _a.call(controller);
12453
+ }
12454
+ function onMediaTypeChange(event, fieldName) {
12455
+ const select = event.target;
12456
+ if (!select.matches(`select[name="${fieldName}"]`))
12457
+ return;
12458
+ window.requestAnimationFrame(() => refreshWidgetAfterMediaTypeChange(select));
12459
+ }
12460
+ function installMediaTypeChangeRefresh({ fieldName = DEFAULT_MEDIA_TYPE_FIELD } = {}) {
12461
+ const key = `__kubikMediaTypeRefresh_${fieldName}`;
12462
+ if (window[key])
12463
+ return;
12464
+ window[key] = true;
12465
+ document.addEventListener("change", (event) => onMediaTypeChange(event, fieldName));
12466
+ }
11372
12467
  var index = {
12468
+ KubikNavigableLinkTool,
11373
12469
  KubikWidgetController,
11374
12470
  KubikRepeaterController,
11375
12471
  KubikKeyValueRepeaterController,
11376
12472
  PluginFactory,
11377
12473
  KubikAutocompleteController,
11378
12474
  defineBlock,
12475
+ defineRecordListBlock,
12476
+ defineResourceReferenceBlock,
11379
12477
  presets,
11380
12478
  DEFAULT_INLINE_TOOLBAR,
11381
12479
  WIDGET_INLINE_TOOLBAR,
11382
12480
  ensureEditorJsInlineIcons,
11383
- buildBaseEditorTools
12481
+ buildBaseEditorTools,
12482
+ applyRecordListWidgetAdminEnhancements,
12483
+ applyKubikWysiwygWidgetLoading,
12484
+ installMediaTypeChangeRefresh,
12485
+ fieldPresets
11384
12486
  };
11385
- export { DEFAULT_INLINE_TOOLBAR, KubikAutocompleteController, KubikKeyValueRepeaterController, KubikRepeaterController, KubikWidgetController, PluginFactory, WIDGET_INLINE_TOOLBAR, buildBaseEditorTools, index as default, defineBlock, ensureEditorJsInlineIcons, presets };
12487
+ export { DEFAULT_INLINE_TOOLBAR, KubikAutocompleteController, KubikKeyValueRepeaterController, KubikNavigableLinkTool, KubikRepeaterController, KubikWidgetController, PluginFactory, WIDGET_INLINE_TOOLBAR, applyKubikWysiwygWidgetLoading, applyRecordListWidgetAdminEnhancements, buildBaseEditorTools, index as default, defineBlock, defineRecordListBlock, defineResourceReferenceBlock, ensureEditorJsInlineIcons, fieldPresets, installMediaTypeChangeRefresh, presets };