@bendyline/squisq-editor-react 2.13.3 → 2.13.5

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.
@@ -6521,6 +6521,7 @@ import {
6521
6521
  deriveTemplateInputs,
6522
6522
  extractBodyPlainText,
6523
6523
  extractEmbeddedVideos,
6524
+ extractHostedVideos,
6524
6525
  extractBlockquoteText,
6525
6526
  extractImages,
6526
6527
  extractListItems,
@@ -6658,11 +6659,7 @@ function buildTemplatePreviewInputs(templateName, block, headingText2, bodyText,
6658
6659
  }
6659
6660
  case "leftFeature":
6660
6661
  case "rightFeature": {
6661
- const hasFeature = hasText(provided.imageSrc) || extractImages(contents, 1).length > 0 || extractEmbeddedVideos(contents, 1).length > 0 || (contents ?? []).some(
6662
- (node2) => node2.type === "code" && isPictureFence(node2) && !!node2.value.trim()
6663
- ) || ["diagram", "tree", "timeline"].some(
6664
- (template) => !!deriveTemplateInputs(template, headingText2, contents, { placeholders: false })
6665
- );
6662
+ const hasFeature = hasText(provided.imageSrc) || hasVisualFeature(contents, headingText2);
6666
6663
  return {
6667
6664
  inputs: hasFeature ? deriveTemplateInputs(templateName, headingText2, contents, { placeholders: false }) ?? {
6668
6665
  title: headingText2,
@@ -6671,6 +6668,15 @@ function buildTemplatePreviewInputs(templateName, block, headingText2, bodyText,
6671
6668
  ...hasFeature ? {} : { warning: "No visual feature found in this block" }
6672
6669
  };
6673
6670
  }
6671
+ case "topFeature":
6672
+ case "bottomFeature":
6673
+ case "mediaOnly": {
6674
+ const hasFeature = hasVisualFeature(contents, headingText2);
6675
+ return {
6676
+ inputs: hasFeature ? { title: headingText2 } : null,
6677
+ ...hasFeature ? {} : { warning: "No image or video found in this block" }
6678
+ };
6679
+ }
6674
6680
  case "imageWithCaption":
6675
6681
  case "photoGrid": {
6676
6682
  const imageCount = extractImages(contents, templateName === "photoGrid" ? 2 : 1).length;
@@ -6742,6 +6748,13 @@ function buildTemplatePreviewInputs(templateName, block, headingText2, bodyText,
6742
6748
  };
6743
6749
  }
6744
6750
  }
6751
+ function hasVisualFeature(contents, headingText2) {
6752
+ return extractImages(contents, 1).length > 0 || extractEmbeddedVideos(contents, 1).length > 0 || extractHostedVideos(contents, 1).length > 0 || (contents ?? []).some(
6753
+ (node2) => node2.type === "code" && isPictureFence(node2) && !!node2.value.trim()
6754
+ ) || ["diagram", "tree", "timeline"].some(
6755
+ (template) => !!deriveTemplateInputs(template, headingText2, contents, { placeholders: false })
6756
+ );
6757
+ }
6745
6758
  function derivePullQuoteInputs(block, headingText2, bodyText) {
6746
6759
  const image = extractImages(block.contents, 1)[0];
6747
6760
  const text = extractBlockquoteText(block.contents) || bodyText || headingText2;
@@ -7087,6 +7100,39 @@ var TEMPLATE_ENTRIES = [
7087
7100
  /* @__PURE__ */ jsx18("rect", { x: 3, y: 27, width: 16, height: 2.5, rx: 1, fill: F1, opacity: 0.6 })
7088
7101
  ] })
7089
7102
  },
7103
+ {
7104
+ name: "topFeature",
7105
+ label: "Top Feature",
7106
+ description: "Image or video across the full width at the top, title and body text below.",
7107
+ icon: /* @__PURE__ */ jsxs14(TemplateIcon, { children: [
7108
+ /* @__PURE__ */ jsx18("rect", { x: 3, y: 3, width: 50, height: 19, rx: 2, fill: F2, opacity: 0.55 }),
7109
+ /* @__PURE__ */ jsx18("polygon", { points: "25,8.5 25,16.5 32,12.5", fill: "white", opacity: 0.5 }),
7110
+ /* @__PURE__ */ jsx18("rect", { x: 3, y: 25, width: 22, height: 4, rx: 1, fill: FA }),
7111
+ /* @__PURE__ */ jsx18("rect", { x: 3, y: 31, width: 48, height: 2.5, rx: 1, fill: F1 }),
7112
+ /* @__PURE__ */ jsx18("rect", { x: 3, y: 35.5, width: 34, height: 2.5, rx: 1, fill: F1, opacity: 0.75 })
7113
+ ] })
7114
+ },
7115
+ {
7116
+ name: "bottomFeature",
7117
+ label: "Bottom Feature",
7118
+ description: "Title and body text at the top, image or video across the full width below.",
7119
+ icon: /* @__PURE__ */ jsxs14(TemplateIcon, { children: [
7120
+ /* @__PURE__ */ jsx18("rect", { x: 3, y: 3, width: 22, height: 4, rx: 1, fill: FA }),
7121
+ /* @__PURE__ */ jsx18("rect", { x: 3, y: 9, width: 48, height: 2.5, rx: 1, fill: F1 }),
7122
+ /* @__PURE__ */ jsx18("rect", { x: 3, y: 13.5, width: 34, height: 2.5, rx: 1, fill: F1, opacity: 0.75 }),
7123
+ /* @__PURE__ */ jsx18("rect", { x: 3, y: 18, width: 50, height: 19, rx: 2, fill: F2, opacity: 0.55 }),
7124
+ /* @__PURE__ */ jsx18("polygon", { points: "25,23.5 25,31.5 32,27.5", fill: "white", opacity: 0.5 })
7125
+ ] })
7126
+ },
7127
+ {
7128
+ name: "mediaOnly",
7129
+ label: "Media Only",
7130
+ description: "The image or video fills the slide, with no title, text or caption.",
7131
+ icon: /* @__PURE__ */ jsxs14(TemplateIcon, { children: [
7132
+ /* @__PURE__ */ jsx18("rect", { x: 3, y: 3, width: 50, height: 34, rx: 2, fill: F2, opacity: 0.55 }),
7133
+ /* @__PURE__ */ jsx18("polygon", { points: "24,14 24,26 34,20", fill: "white", opacity: 0.5 })
7134
+ ] })
7135
+ },
7090
7136
  {
7091
7137
  name: "map",
7092
7138
  label: "Map",
@@ -8789,7 +8835,6 @@ var BUTTONS = [
8789
8835
  var MEDIA_BUTTONS = BUTTONS.filter((b) => b.group === "media");
8790
8836
  var FILE_STORING_BUTTONS = /* @__PURE__ */ new Set(["image", "file"]);
8791
8837
  var CONVERT_BUTTONS = MEDIA_BUTTONS.filter((b) => b.id === "table" || b.id === "tasklist");
8792
- var INSERT_MENU_WIDTH = 200;
8793
8838
  var CODE_SNIPPET_MENU_WIDTH = 220;
8794
8839
  var MERMAID_TYPE_MENU_WIDTH = 520;
8795
8840
  var CHART_TYPE_MENU_WIDTH = 360;
@@ -20529,7 +20574,7 @@ function Toolbar({
20529
20574
  const codeSnippetMenuRef = useRef36(null);
20530
20575
  const mermaidTypeMenuRef = useRef36(null);
20531
20576
  const chartTypeMenuRef = useRef36(null);
20532
- const insertMenuTriggerRectRef = useRef36(null);
20577
+ const insertMenuTriggerRef = useRef36(null);
20533
20578
  const [insertMenuAnchor, setInsertMenuAnchor] = useState40(null);
20534
20579
  const [codeSnippetMenuAnchor, setCodeSnippetMenuAnchor] = useState40(null);
20535
20580
  const [mermaidTypeMenuAnchor, setMermaidTypeMenuAnchor] = useState40(null);
@@ -20539,15 +20584,8 @@ function Toolbar({
20539
20584
  if (!btn) return;
20540
20585
  insertMenuReturnFocusRef.current = trigger ? overflowTriggerRef.current : insertMenuButtonRef.current;
20541
20586
  const rect = btn.getBoundingClientRect();
20542
- const gap = 4;
20543
- const margin = 8;
20544
- const vw = window.innerWidth;
20545
- let left = rect.left;
20546
- if (left + INSERT_MENU_WIDTH + margin > vw) {
20547
- left = Math.max(margin, rect.right - INSERT_MENU_WIDTH);
20548
- }
20549
- insertMenuTriggerRectRef.current = rect;
20550
- setInsertMenuAnchor({ top: rect.bottom + gap, left, placement: "down" });
20587
+ insertMenuTriggerRef.current = { element: btn, rect };
20588
+ setInsertMenuAnchor({ top: rect.bottom + 4, left: rect.left, placement: "down" });
20551
20589
  setCodeSnippetMenuAnchor(null);
20552
20590
  setMermaidTypeMenuAnchor(null);
20553
20591
  setChartTypeMenuAnchor(null);
@@ -20601,7 +20639,7 @@ function Toolbar({
20601
20639
  setMermaidTypeMenuAnchor(null);
20602
20640
  }, []);
20603
20641
  const closeInsertMenu = useCallback32(() => {
20604
- insertMenuTriggerRectRef.current = null;
20642
+ insertMenuTriggerRef.current = null;
20605
20643
  setInsertMenuAnchor(null);
20606
20644
  setCodeSnippetMenuAnchor(null);
20607
20645
  setMermaidTypeMenuAnchor(null);
@@ -20687,23 +20725,39 @@ function Toolbar({
20687
20725
  useLayoutEffect9(() => {
20688
20726
  if (!insertMenuOpen) return;
20689
20727
  const menu = insertMenuRef.current;
20690
- const triggerRect = insertMenuTriggerRectRef.current;
20691
- if (!menu || !triggerRect) return;
20692
- const gap = 4;
20693
- const margin = 8;
20694
- const spaceBelow = Math.max(0, window.innerHeight - triggerRect.bottom - gap - margin);
20695
- const spaceAbove = Math.max(0, triggerRect.top - gap - margin);
20696
- const naturalHeight = Math.max(menu.scrollHeight, menu.getBoundingClientRect().height);
20697
- const placement = naturalHeight > spaceBelow && spaceAbove > spaceBelow ? "up" : "down";
20698
- const maxHeight = placement === "up" ? spaceAbove : spaceBelow;
20699
- const visibleHeight = Math.min(naturalHeight, maxHeight);
20700
- const top = placement === "up" ? Math.max(margin, triggerRect.top - gap - visibleHeight) : triggerRect.bottom + gap;
20701
- setInsertMenuAnchor((current) => {
20702
- if (!current || current.top === top && current.maxHeight === maxHeight && current.placement === placement) {
20703
- return current;
20704
- }
20705
- return { ...current, top, maxHeight, placement };
20706
- });
20728
+ const trigger = insertMenuTriggerRef.current;
20729
+ if (!menu || !trigger) return;
20730
+ const positionMenu = () => {
20731
+ const liveRect = trigger.element.getBoundingClientRect();
20732
+ const triggerRect = trigger.element.isConnected && liveRect.width > 0 ? liveRect : trigger.rect;
20733
+ const gap = 4;
20734
+ const margin = 8;
20735
+ const menuRect = menu.getBoundingClientRect();
20736
+ const rightEdge = window.innerWidth - margin;
20737
+ const preferredLeft = triggerRect.left + menuRect.width <= rightEdge ? triggerRect.left : triggerRect.right - menuRect.width;
20738
+ const left = Math.max(margin, Math.min(preferredLeft, rightEdge - menuRect.width));
20739
+ const spaceBelow = Math.max(0, window.innerHeight - triggerRect.bottom - gap - margin);
20740
+ const spaceAbove = Math.max(0, triggerRect.top - gap - margin);
20741
+ const naturalHeight = Math.max(menu.scrollHeight, menuRect.height);
20742
+ const placement = naturalHeight > spaceBelow && spaceAbove > spaceBelow ? "up" : "down";
20743
+ const maxHeight = placement === "up" ? spaceAbove : spaceBelow;
20744
+ const visibleHeight = Math.min(naturalHeight, maxHeight);
20745
+ const top = placement === "up" ? Math.max(margin, triggerRect.top - gap - visibleHeight) : triggerRect.bottom + gap;
20746
+ setInsertMenuAnchor((current) => {
20747
+ if (!current || current.left === left && current.top === top && current.maxHeight === maxHeight && current.placement === placement) {
20748
+ return current;
20749
+ }
20750
+ return { ...current, left, top, maxHeight, placement };
20751
+ });
20752
+ };
20753
+ positionMenu();
20754
+ const observer = new ResizeObserver(positionMenu);
20755
+ observer.observe(menu);
20756
+ window.addEventListener("resize", positionMenu);
20757
+ return () => {
20758
+ observer.disconnect();
20759
+ window.removeEventListener("resize", positionMenu);
20760
+ };
20707
20761
  }, [insertMenuOpen]);
20708
20762
  const [, forceUpdate] = useReducer2((c) => c + 1, 0);
20709
20763
  useEffect31(() => {
@@ -35324,6 +35378,35 @@ function looksLikeMarkdown(text) {
35324
35378
  }
35325
35379
 
35326
35380
  // src/wysiwygImageUpload.ts
35381
+ function isPlayableMediaFile(file) {
35382
+ const kind = squisqMediaKind(mediaMimeType(file));
35383
+ return kind === "video" || kind === "audio";
35384
+ }
35385
+ async function uploadAndInsertMediaPlayers(view, files, mediaProvider, onMediaUploaded) {
35386
+ const paths = await processMediaFiles(files, mediaProvider);
35387
+ if (!paths.some(Boolean)) return;
35388
+ onMediaUploaded?.();
35389
+ const { schema } = view.state;
35390
+ const players = [];
35391
+ for (const [index2, path] of paths.entries()) {
35392
+ if (!path) continue;
35393
+ const spec = squisqMediaNode(fileReference(path, files[index2].name, files[index2].type));
35394
+ const nodeType = spec ? schema.nodes[spec.type] : void 0;
35395
+ if (spec && nodeType) players.push(nodeType.create(spec.attrs));
35396
+ }
35397
+ if (players.length > 0) insertTopLevelNodes(view, players);
35398
+ }
35399
+ function insertTopLevelNodes(view, nodes) {
35400
+ const { state } = view;
35401
+ const { $from } = state.selection;
35402
+ if ($from.depth < 1) {
35403
+ view.dispatch(state.tr.insert(state.doc.content.size, nodes).scrollIntoView());
35404
+ return;
35405
+ }
35406
+ const block = $from.node(1);
35407
+ const tr = block.type.name === "paragraph" && block.content.size === 0 ? state.tr.replaceWith($from.before(1), $from.after(1), nodes) : state.tr.insert($from.after(1), nodes);
35408
+ view.dispatch(tr.scrollIntoView());
35409
+ }
35327
35410
  async function uploadAndInsertImages(view, files, mediaProvider, onMediaUploaded) {
35328
35411
  const placeholders = files.map(() => `squisq-upload:${uniquePasteToken()}`);
35329
35412
  const imageType = view.state.schema.nodes.image;
@@ -35900,47 +35983,23 @@ function WysiwygEditor({
35900
35983
  view.pasteHTML(html);
35901
35984
  return true;
35902
35985
  },
35903
- // When image files are dropped onto the editor, upload them via the
35904
- // MediaProvider and insert <img> nodes referencing the relative paths.
35905
- // Drags from the MediaBin can also insert existing image, video, audio,
35906
- // or generic-file references without uploading them again.
35907
- // Falls through to default handling for non-image drops or when no
35908
- // MediaProvider is available.
35986
+ // When image, video or audio files are dropped onto the editor, upload
35987
+ // them via the MediaProvider and insert image nodes / players
35988
+ // referencing the relative paths. Drags from the MediaBin can also
35989
+ // insert existing image, video, audio, or generic-file references
35990
+ // without uploading them again. Falls through to default handling for
35991
+ // other drops or when no MediaProvider is available.
35909
35992
  handleDrop: (view, event, _slice, moved) => {
35910
35993
  const dt = event.dataTransfer;
35911
35994
  if (!dt) return false;
35912
35995
  if (moved) return false;
35913
- const squisqRaw = dt.getData(SQUISQ_MEDIA_MIME);
35914
- if (squisqRaw) {
35915
- const payload = parseSquisqMediaPayload(squisqRaw);
35916
- if (payload) {
35917
- event.preventDefault();
35918
- moveSelectionToDropPoint(view, event);
35919
- return insertExistingMediaReference(view, payload);
35920
- }
35921
- }
35922
- const imageFiles = filesFromDataTransfer(dt);
35923
- if (imageFiles.length === 0) {
35924
- if (dt.files.length > 0 || dt.items.length > 0) {
35925
- console.warn(
35926
- "[squisq-editor] Drop received with no recognizable image File. Types:",
35927
- Array.from(dt.types ?? []),
35928
- "files:",
35929
- Array.from(dt.files).map((f) => `${f.name} (${f.type || "no-type"})`)
35930
- );
35931
- }
35932
- return false;
35933
- }
35934
- if (!mediaProviderRef.current) {
35935
- console.warn(
35936
- "[squisq-editor] Image drop received but no MediaProvider is wired up; cannot persist the image. Pass `mediaProvider` to EditorShell / EditorProvider."
35937
- );
35938
- return false;
35939
- }
35940
- event.preventDefault();
35941
- moveSelectionToDropPoint(view, event);
35942
- uploadAndInsertImages(view, imageFiles, mediaProviderRef.current, bumpMediaRevision);
35943
- return true;
35996
+ return insertDroppedMedia(
35997
+ view,
35998
+ event,
35999
+ { left: event.clientX, top: event.clientY },
36000
+ mediaProviderRef.current,
36001
+ bumpMediaRevision
36002
+ );
35944
36003
  }
35945
36004
  }
35946
36005
  });
@@ -36153,6 +36212,25 @@ function WysiwygEditor({
36153
36212
  if (!editor || editor.isDestroyed) return;
36154
36213
  editor.view.dispatch(editor.state.tr.setMeta(DOCUMENT_TRAY_KEY, layoutMode === "document"));
36155
36214
  }, [editor, layoutMode]);
36215
+ const handleDeskDrop = useCallback43(
36216
+ (event) => {
36217
+ if (event.defaultPrevented || readOnly || !editor || editor.isDestroyed) return;
36218
+ const { view } = editor;
36219
+ if (view.dragging || view.dom.contains(event.target)) return;
36220
+ const page = view.dom.getBoundingClientRect();
36221
+ insertDroppedMedia(
36222
+ view,
36223
+ event.nativeEvent,
36224
+ {
36225
+ left: Math.min(Math.max(event.clientX, page.left + 1), page.right - 1),
36226
+ top: Math.min(Math.max(event.clientY, page.top + 1), page.bottom - 1)
36227
+ },
36228
+ mediaProviderRef.current,
36229
+ bumpMediaRevision
36230
+ );
36231
+ },
36232
+ [bumpMediaRevision, editor, readOnly]
36233
+ );
36156
36234
  return /* @__PURE__ */ jsx72(CustomTemplateProvider, { docTemplates, onDocTemplatesChange, children: /* @__PURE__ */ jsxs58(
36157
36235
  "div",
36158
36236
  {
@@ -36168,6 +36246,7 @@ function WysiwygEditor({
36168
36246
  "data-block-tags": BLOCK_TAG_DATA_VALUES[blockTagVisibility],
36169
36247
  "data-theme-inheritance": themeInheritance,
36170
36248
  ref: containerRef,
36249
+ onDrop: handleDeskDrop,
36171
36250
  children: [
36172
36251
  editor && layoutMode === "document" && /* @__PURE__ */ jsx72(DocumentTray, { editor }),
36173
36252
  /* @__PURE__ */ jsx72(EditorContent2, { editor, style: { height: "100%" } }),
@@ -36255,10 +36334,8 @@ function filesFromDataTransfer(dt) {
36255
36334
  const seen = /* @__PURE__ */ new Set();
36256
36335
  for (let i = 0; i < dt.files.length; i++) {
36257
36336
  const file = dt.files[i];
36258
- if (looksLikeImageFile(file)) {
36259
- files.push(file);
36260
- seen.add(`${file.name}|${file.size}`);
36261
- }
36337
+ files.push(file);
36338
+ seen.add(`${file.name}|${file.size}`);
36262
36339
  }
36263
36340
  if (dt.items) {
36264
36341
  for (let i = 0; i < dt.items.length; i++) {
@@ -36266,7 +36343,6 @@ function filesFromDataTransfer(dt) {
36266
36343
  if (item.kind !== "file") continue;
36267
36344
  const file = item.getAsFile();
36268
36345
  if (!file) continue;
36269
- if (!looksLikeImageFile(file)) continue;
36270
36346
  const key = `${file.name}|${file.size}`;
36271
36347
  if (seen.has(key)) continue;
36272
36348
  files.push(file);
@@ -36306,10 +36382,7 @@ function insertExistingMediaReference(view, payload) {
36306
36382
  if (spec) {
36307
36383
  const nodeType = state.schema.nodes[spec.type];
36308
36384
  if (!nodeType) return false;
36309
- const node3 = nodeType.create(spec.attrs);
36310
- const { $from } = state.selection;
36311
- const pos = $from.depth >= 1 ? $from.after(1) : state.doc.content.size;
36312
- view.dispatch(state.tr.insert(pos, node3).scrollIntoView());
36385
+ insertTopLevelNodes(view, [nodeType.create(spec.attrs)]);
36313
36386
  return true;
36314
36387
  }
36315
36388
  const linkType = state.schema.marks.link;
@@ -36319,12 +36392,54 @@ function insertExistingMediaReference(view, payload) {
36319
36392
  view.dispatch(state.tr.replaceSelectionWith(node2).scrollIntoView());
36320
36393
  return true;
36321
36394
  }
36322
- function moveSelectionToDropPoint(view, event) {
36323
- const coords = view.posAtCoords({ left: event.clientX, top: event.clientY });
36395
+ function moveSelectionToPoint(view, point) {
36396
+ const coords = view.posAtCoords(point);
36324
36397
  if (!coords) return;
36325
36398
  const tr = view.state.tr.setSelection(Selection2.near(view.state.doc.resolve(coords.pos)));
36326
36399
  view.dispatch(tr);
36327
36400
  }
36401
+ function insertDroppedMedia(view, event, point, mediaProvider, onMediaUploaded) {
36402
+ const dt = event.dataTransfer;
36403
+ if (!dt) return false;
36404
+ const squisqRaw = dt.getData(SQUISQ_MEDIA_MIME);
36405
+ if (squisqRaw) {
36406
+ const payload = parseSquisqMediaPayload(squisqRaw);
36407
+ if (payload) {
36408
+ event.preventDefault();
36409
+ moveSelectionToPoint(view, point);
36410
+ return insertExistingMediaReference(view, payload);
36411
+ }
36412
+ }
36413
+ const droppedFiles = filesFromDataTransfer(dt);
36414
+ const imageFiles = droppedFiles.filter(looksLikeImageFile);
36415
+ const playerFiles = droppedFiles.filter(isPlayableMediaFile);
36416
+ if (imageFiles.length === 0 && playerFiles.length === 0) {
36417
+ if (dt.files.length > 0 || dt.items.length > 0) {
36418
+ console.warn(
36419
+ "[squisq-editor] Drop received with no recognizable image, video or audio File. Types:",
36420
+ Array.from(dt.types ?? []),
36421
+ "files:",
36422
+ Array.from(dt.files).map((f) => `${f.name} (${f.type || "no-type"})`)
36423
+ );
36424
+ }
36425
+ return false;
36426
+ }
36427
+ if (!mediaProvider) {
36428
+ console.warn(
36429
+ "[squisq-editor] Media drop received but no MediaProvider is wired up; cannot persist the file. Pass `mediaProvider` to EditorShell / EditorProvider."
36430
+ );
36431
+ return false;
36432
+ }
36433
+ event.preventDefault();
36434
+ moveSelectionToPoint(view, point);
36435
+ if (imageFiles.length > 0) {
36436
+ uploadAndInsertImages(view, imageFiles, mediaProvider, onMediaUploaded);
36437
+ }
36438
+ if (playerFiles.length > 0) {
36439
+ void uploadAndInsertMediaPlayers(view, playerFiles, mediaProvider, onMediaUploaded);
36440
+ }
36441
+ return true;
36442
+ }
36328
36443
 
36329
36444
  // src/InlinePreviewGutter.tsx
36330
36445
  import { useLayoutEffect as useLayoutEffect13, useMemo as useMemo43, useRef as useRef51, useState as useState70 } from "react";
@@ -41894,19 +42009,19 @@ function iconForMime(mimeType) {
41894
42009
  function isImageMime(mimeType) {
41895
42010
  return mimeType.startsWith("image/");
41896
42011
  }
41897
- var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "webp", "svg", "avif", "ico"]);
41898
- function isImageFile(file) {
41899
- if (isImageMime(file.type)) return true;
41900
- const dot = file.name.lastIndexOf(".");
41901
- return dot >= 0 && IMAGE_EXTENSIONS.has(file.name.slice(dot + 1).toLowerCase());
42012
+ function isMediaFile(file) {
42013
+ return squisqMediaKind(mediaMimeType(file)) !== "file";
42014
+ }
42015
+ function isMediaMime(mimeType) {
42016
+ return squisqMediaKind(mimeType) !== "file";
41902
42017
  }
41903
- function dataTransferMayContainImage(dataTransfer) {
42018
+ function dataTransferMayContainMedia(dataTransfer) {
41904
42019
  if (dataTransfer.items.length > 0) {
41905
42020
  return Array.from(dataTransfer.items).some(
41906
- (item) => item.kind === "file" && (item.type === "" || isImageMime(item.type))
42021
+ (item) => item.kind === "file" && (item.type === "" || isMediaMime(item.type))
41907
42022
  );
41908
42023
  }
41909
- return Array.from(dataTransfer.files).some(isImageFile);
42024
+ return Array.from(dataTransfer.files).some(isMediaFile);
41910
42025
  }
41911
42026
  async function resolveThumbnailUrl(provider, name) {
41912
42027
  const url = await provider.resolveUrl(name);
@@ -42047,7 +42162,7 @@ function MediaBin({
42047
42162
  try {
42048
42163
  for (const file of files) {
42049
42164
  const buffer = await file.arrayBuffer();
42050
- const mimeType = file.type || "application/octet-stream";
42165
+ const mimeType = mediaMimeType(file);
42051
42166
  const relativePath = await mediaProvider.addMedia(file.name, buffer, mimeType);
42052
42167
  if (onMediaUploaded) {
42053
42168
  try {
@@ -42075,7 +42190,7 @@ function MediaBin({
42075
42190
  );
42076
42191
  const handleDragEnter = useCallback55(
42077
42192
  (event) => {
42078
- if (!mediaProvider || loading || !dataTransferMayContainImage(event.dataTransfer)) return;
42193
+ if (!mediaProvider || loading || !dataTransferMayContainMedia(event.dataTransfer)) return;
42079
42194
  event.preventDefault();
42080
42195
  event.stopPropagation();
42081
42196
  dropDepthRef.current += 1;
@@ -42085,7 +42200,7 @@ function MediaBin({
42085
42200
  );
42086
42201
  const handleDragOver = useCallback55(
42087
42202
  (event) => {
42088
- if (!mediaProvider || loading || !dataTransferMayContainImage(event.dataTransfer)) return;
42203
+ if (!mediaProvider || loading || !dataTransferMayContainMedia(event.dataTransfer)) return;
42089
42204
  event.preventDefault();
42090
42205
  event.stopPropagation();
42091
42206
  event.dataTransfer.dropEffect = "copy";
@@ -42104,13 +42219,13 @@ function MediaBin({
42104
42219
  );
42105
42220
  const handleDrop = useCallback55(
42106
42221
  (event) => {
42107
- const imageFiles = Array.from(event.dataTransfer.files).filter(isImageFile);
42108
- if (!mediaProvider || !isDropActive && imageFiles.length === 0) return;
42222
+ const mediaFiles = Array.from(event.dataTransfer.files).filter(isMediaFile);
42223
+ if (!mediaProvider || !isDropActive && mediaFiles.length === 0) return;
42109
42224
  event.preventDefault();
42110
42225
  event.stopPropagation();
42111
42226
  dropDepthRef.current = 0;
42112
42227
  setIsDropActive(false);
42113
- if (imageFiles.length > 0) void uploadFiles(imageFiles);
42228
+ if (mediaFiles.length > 0) void uploadFiles(mediaFiles);
42114
42229
  },
42115
42230
  [isDropActive, mediaProvider, uploadFiles]
42116
42231
  );
@@ -42309,7 +42424,7 @@ ${formatSize(entry.size)}`,
42309
42424
  ] }),
42310
42425
  isDropActive && /* @__PURE__ */ jsxs72("div", { className: "squisq-media-bin-drop-target", "aria-hidden": "true", children: [
42311
42426
  /* @__PURE__ */ jsx89("span", { className: "squisq-media-bin-drop-target-icon", children: "+" }),
42312
- /* @__PURE__ */ jsx89("strong", { children: "Drop images here" }),
42427
+ /* @__PURE__ */ jsx89("strong", { children: "Drop media here" }),
42313
42428
  /* @__PURE__ */ jsx89("span", { children: "Attach to this document" })
42314
42429
  ] }),
42315
42430
  contextMenu && /* @__PURE__ */ jsx89(
package/dist/index.d.ts CHANGED
@@ -1335,7 +1335,7 @@ interface MediaBinProps {
1335
1335
  usedMediaPaths?: ReadonlySet<string>;
1336
1336
  /**
1337
1337
  * Fired after a successful upload via the MediaBin's "+ Upload"
1338
- * button or image drop target. `relativePath` is what the provider returned (the same
1338
+ * button or media drop target. `relativePath` is what the provider returned (the same
1339
1339
  * value embedded in markdown refs, e.g. `attachments/xyz.png`);
1340
1340
  * `name` is the uploader-chosen filename before storage renamed
1341
1341
  * it. Consumers typically use this to insert a markdown image ref
package/dist/index.js CHANGED
@@ -243,7 +243,7 @@ import {
243
243
  videoEmbedParagraphJson,
244
244
  viewStateFromBinding,
245
245
  writeHeadingViewState
246
- } from "./chunk-D77UNB6T.js";
246
+ } from "./chunk-WEEHXZUY.js";
247
247
  import {
248
248
  EditorProvider,
249
249
  applySourceEditsToText,
@@ -5,7 +5,7 @@ import {
5
5
  RawEditor,
6
6
  WysiwygEditor,
7
7
  useEditorContextMenuItems
8
- } from "../chunk-D77UNB6T.js";
8
+ } from "../chunk-WEEHXZUY.js";
9
9
  import {
10
10
  EditorProvider,
11
11
  useEditorContext,
@@ -8681,7 +8681,10 @@
8681
8681
  }
8682
8682
  .squisq-insert-menu {
8683
8683
  z-index: 100;
8684
- min-width: 200px;
8684
+ box-sizing: border-box;
8685
+ width: max-content;
8686
+ min-width: min(200px, calc(100vw - 16px));
8687
+ max-width: calc(100vw - 16px);
8685
8688
  padding: 4px 0;
8686
8689
  overflow-y: auto;
8687
8690
  overscroll-behavior: contain;
@@ -8691,6 +8694,17 @@
8691
8694
  border-radius: 6px;
8692
8695
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
8693
8696
  }
8697
+ .squisq-insert-menu .squisq-toolbar-overflow-item {
8698
+ white-space: normal;
8699
+ overflow-wrap: anywhere;
8700
+ }
8701
+ .squisq-insert-menu .squisq-toolbar-overflow-item > span {
8702
+ min-width: 0;
8703
+ }
8704
+ .squisq-insert-menu .squisq-toolbar-overflow-icon,
8705
+ .squisq-insert-menu .squisq-toolbar-overflow-item > svg {
8706
+ flex-shrink: 0;
8707
+ }
8694
8708
  .squisq-insert-menu-header {
8695
8709
  padding: 6px 12px 4px;
8696
8710
  font-size: 11px;
@@ -13599,6 +13613,7 @@ button.squisq-view-menu-radio-row:focus-visible {
13599
13613
  y2 120ms ease-out;
13600
13614
  }
13601
13615
  @container squisq-editor-body (max-width: 720px) {
13616
+ .squisq-block-preview-panel,
13602
13617
  .squisq-inline-preview-gutter,
13603
13618
  .squisq-inline-preview-connectors,
13604
13619
  .squisq-outline {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bendyline/squisq-editor-react",
3
- "version": "2.13.3",
3
+ "version": "2.13.5",
4
4
  "description": "React editor shell with raw/WYSIWYG/preview modes for Squisq documents",
5
5
  "license": "MIT",
6
6
  "author": "Bendyline",
@@ -102,12 +102,12 @@
102
102
  }
103
103
  },
104
104
  "dependencies": {
105
- "@bendyline/squisq": "2.13.1",
105
+ "@bendyline/squisq": "2.13.3",
106
106
  "@bendyline/squisq-calc": "2.11.2",
107
- "@bendyline/squisq-formats": "2.8.2",
108
- "@bendyline/squisq-grid-react": "2.11.30",
109
- "@bendyline/squisq-react": "2.12.10",
110
- "@bendyline/squisq-video-react": "2.5.11",
107
+ "@bendyline/squisq-formats": "2.8.4",
108
+ "@bendyline/squisq-grid-react": "2.11.32",
109
+ "@bendyline/squisq-react": "2.12.12",
110
+ "@bendyline/squisq-video-react": "2.5.13",
111
111
  "@fortawesome/fontawesome-free": "7.3.1",
112
112
  "@tiptap/core": "2.27.3",
113
113
  "@tiptap/extension-heading": "2.27.3",