@bendyline/squisq-editor-react 2.11.22 → 2.11.23

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.
@@ -18,7 +18,7 @@ import {
18
18
  useDictationState,
19
19
  useEditorContext,
20
20
  useEditorContextOptional
21
- } from "./chunk-UCYZBMCG.js";
21
+ } from "./chunk-TRBNNRLK.js";
22
22
  import {
23
23
  monacoLanguageRequestKey,
24
24
  monacoLanguagesForDocument,
@@ -17938,14 +17938,27 @@ import { useCallback as useCallback30, useEffect as useEffect27, useId as useId1
17938
17938
 
17939
17939
  // src/find/FindHighlightExtension.ts
17940
17940
  import { Extension as Extension3 } from "@tiptap/core";
17941
- import { Plugin as Plugin3, PluginKey as PluginKey3 } from "@tiptap/pm/state";
17941
+ import { Plugin as Plugin3, PluginKey as PluginKey3, TextSelection } from "@tiptap/pm/state";
17942
17942
  import { Decoration as Decoration3, DecorationSet as DecorationSet3 } from "@tiptap/pm/view";
17943
17943
 
17944
17944
  // src/find/findModel.ts
17945
- function findTextMatches(text, query) {
17946
- const needle = query.trim();
17947
- if (!needle || !text) return [];
17948
- const matcher = new RegExp(escapeRegExp(needle), "giu");
17945
+ var WORD_CHARACTER = /^[\p{L}\p{N}\p{M}_]$/u;
17946
+ var WORD_BOUNDARY_BEFORE = "(?<![\\p{L}\\p{N}\\p{M}_])";
17947
+ var WORD_BOUNDARY_AFTER = "(?![\\p{L}\\p{N}\\p{M}_])";
17948
+ function createFindMatcher(query, options = {}) {
17949
+ const needle = query.trim().normalize("NFC");
17950
+ if (!needle) return null;
17951
+ let source = escapeRegExp(needle);
17952
+ if (options.wholeWord) {
17953
+ const characters = [...needle];
17954
+ if (WORD_CHARACTER.test(characters[0])) source = WORD_BOUNDARY_BEFORE + source;
17955
+ if (WORD_CHARACTER.test(characters[characters.length - 1])) source += WORD_BOUNDARY_AFTER;
17956
+ }
17957
+ return new RegExp(source, options.matchCase ? "gu" : "giu");
17958
+ }
17959
+ function findTextMatches(text, query, options = {}) {
17960
+ const matcher = text ? createFindMatcher(query, options) : null;
17961
+ if (!matcher) return [];
17949
17962
  const matches = [];
17950
17963
  let match;
17951
17964
  while ((match = matcher.exec(text)) !== null) {
@@ -17958,7 +17971,11 @@ function normalizeFindIndex(index2, count) {
17958
17971
  if (count <= 0) return 0;
17959
17972
  return (index2 % count + count) % count;
17960
17973
  }
17961
- function findProseMirrorMatches(doc, query) {
17974
+ function nextFindIndexAfter(matches, offset) {
17975
+ const index2 = matches.findIndex((match) => match.from >= offset);
17976
+ return index2 < 0 ? 0 : index2;
17977
+ }
17978
+ function findProseMirrorMatches(doc, query, options = {}) {
17962
17979
  const matches = [];
17963
17980
  doc.descendants((node2, blockPos) => {
17964
17981
  if (!node2.isTextblock) return true;
@@ -17974,7 +17991,7 @@ function findProseMirrorMatches(doc, query) {
17974
17991
  }
17975
17992
  return true;
17976
17993
  });
17977
- for (const match of findTextMatches(text, query)) {
17994
+ for (const match of findTextMatches(text, query, options)) {
17978
17995
  const startSpan = spans.find((span) => match.from >= span.from && match.from < span.to);
17979
17996
  const endOffset = match.to - 1;
17980
17997
  const endSpan = spans.find((span) => endOffset >= span.from && endOffset < span.to);
@@ -17989,13 +18006,13 @@ function findProseMirrorMatches(doc, query) {
17989
18006
  return matches;
17990
18007
  }
17991
18008
  function escapeRegExp(value) {
17992
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18009
+ return value.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
17993
18010
  }
17994
18011
 
17995
18012
  // src/find/FindHighlightExtension.ts
17996
18013
  var FIND_HIGHLIGHT_KEY = new PluginKey3("squisq-find-highlight");
17997
- function buildState(doc, query, selectedIndex) {
17998
- const matches = findProseMirrorMatches(doc, query);
18014
+ function buildState(doc, query, options, selectedIndex) {
18015
+ const matches = findProseMirrorMatches(doc, query, options);
17999
18016
  const selected = normalizeFindIndex(selectedIndex, matches.length);
18000
18017
  const decorations = matches.map(
18001
18018
  (match, index2) => Decoration3.inline(match.from, match.to, {
@@ -18004,6 +18021,7 @@ function buildState(doc, query, selectedIndex) {
18004
18021
  );
18005
18022
  return {
18006
18023
  query,
18024
+ options,
18007
18025
  selectedIndex: selected,
18008
18026
  decorations: DecorationSet3.create(doc, decorations)
18009
18027
  };
@@ -18015,13 +18033,14 @@ var FindHighlightExtension = Extension3.create({
18015
18033
  new Plugin3({
18016
18034
  key: FIND_HIGHLIGHT_KEY,
18017
18035
  state: {
18018
- init: (_2, state) => buildState(state.doc, "", 0),
18036
+ init: (_2, state) => buildState(state.doc, "", {}, 0),
18019
18037
  apply: (transaction, previous, _oldState, newState) => {
18020
18038
  const meta = transaction.getMeta(FIND_HIGHLIGHT_KEY);
18021
18039
  if (!meta && !transaction.docChanged) return previous;
18022
18040
  return buildState(
18023
18041
  newState.doc,
18024
18042
  meta?.query ?? previous.query,
18043
+ meta?.options ?? previous.options,
18025
18044
  meta?.selectedIndex ?? previous.selectedIndex
18026
18045
  );
18027
18046
  }
@@ -18033,35 +18052,93 @@ var FindHighlightExtension = Extension3.create({
18033
18052
  ];
18034
18053
  }
18035
18054
  });
18036
- function updateTiptapFindHighlights(editor, query, selectedIndex) {
18037
- const matches = findProseMirrorMatches(editor.state.doc, query);
18038
- editor.view.dispatch(
18039
- editor.state.tr.setMeta(FIND_HIGHLIGHT_KEY, {
18040
- query,
18041
- selectedIndex: normalizeFindIndex(selectedIndex, matches.length)
18042
- })
18043
- );
18055
+ function updateTiptapFindHighlights(editor, query, selectedIndex, options = {}, { select = false } = {}) {
18056
+ const matches = findProseMirrorMatches(editor.state.doc, query, options);
18057
+ const selected = normalizeFindIndex(selectedIndex, matches.length);
18058
+ let transaction = editor.state.tr.setMeta(FIND_HIGHLIGHT_KEY, {
18059
+ query,
18060
+ options,
18061
+ selectedIndex: selected
18062
+ });
18063
+ const match = select ? matches[selected] : void 0;
18064
+ if (match) {
18065
+ transaction = transaction.setSelection(
18066
+ TextSelection.create(transaction.doc, match.from, match.to)
18067
+ );
18068
+ }
18069
+ editor.view.dispatch(transaction);
18070
+ return matches.length;
18071
+ }
18072
+ function replaceTiptapFindMatch(editor, query, selectedIndex, replacement, options = {}) {
18073
+ const matches = findProseMirrorMatches(editor.state.doc, query, options);
18074
+ const match = matches[normalizeFindIndex(selectedIndex, matches.length)];
18075
+ if (!match) return null;
18076
+ const transaction = editor.state.tr.insertText(replacement, match.from, match.to);
18077
+ editor.view.dispatch(transaction);
18078
+ const after = match.from + replacement.length;
18079
+ return nextFindIndexAfter(findProseMirrorMatches(editor.state.doc, query, options), after);
18080
+ }
18081
+ function replaceAllTiptapFindMatches(editor, query, replacement, options = {}) {
18082
+ const matches = findProseMirrorMatches(editor.state.doc, query, options);
18083
+ if (matches.length === 0) return 0;
18084
+ const transaction = editor.state.tr;
18085
+ for (let index2 = matches.length - 1; index2 >= 0; index2 -= 1) {
18086
+ transaction.insertText(replacement, matches[index2].from, matches[index2].to);
18087
+ }
18088
+ editor.view.dispatch(transaction);
18044
18089
  return matches.length;
18045
18090
  }
18046
18091
 
18047
18092
  // src/find/FindToolbar.tsx
18048
- import { jsx as jsx45, jsxs as jsxs35 } from "react/jsx-runtime";
18049
- function FindToolbar({ onClose }) {
18050
- const { activeView, markdownSource, tiptapEditor, monacoEditor } = useEditorContext();
18051
- const [query, setQuery] = useState35("");
18052
- const [selectedIndex, setSelectedIndex] = useState35(0);
18093
+ import { Fragment as Fragment14, jsx as jsx45, jsxs as jsxs35 } from "react/jsx-runtime";
18094
+ var MAX_SEEDED_QUERY = 200;
18095
+ function FindToolbar({
18096
+ onClose,
18097
+ allowReplace = false,
18098
+ replaceOpen = false,
18099
+ onReplaceOpenChange,
18100
+ focusRequest = 0
18101
+ }) {
18102
+ const { activeView, markdownSource, tiptapEditor, monacoEditor, findRequest } = useEditorContext();
18103
+ const [initial] = useState35(
18104
+ () => initialFindState(findRequest, activeView, tiptapEditor, monacoEditor)
18105
+ );
18106
+ const [query, setQuery] = useState35(initial.query);
18107
+ const [matchCase, setMatchCase] = useState35(initial.matchCase ?? false);
18108
+ const [wholeWord, setWholeWord] = useState35(initial.wholeWord ?? false);
18109
+ const [selectedIndex, setSelectedIndex] = useState35(initial.occurrence ?? 0);
18053
18110
  const [matchCount, setMatchCount] = useState35(0);
18111
+ const [replacement, setReplacement] = useState35("");
18054
18112
  const inputRef = useRef32(null);
18113
+ const replaceInputRef = useRef32(null);
18055
18114
  const reactId = useId12().replace(/[^a-zA-Z0-9_-]/g, "");
18056
18115
  const previewHighlightName = `squisq-find-${reactId}`;
18057
18116
  const previewSelectedName = `${previewHighlightName}-selected`;
18117
+ const canReplace = allowReplace && activeView !== "preview";
18118
+ const showReplace = canReplace && replaceOpen;
18119
+ const options = { matchCase, wholeWord };
18120
+ const lastNavigationRef = useRef32("");
18121
+ const pendingRevealRef = useRef32(false);
18058
18122
  useEffect27(() => {
18059
18123
  inputRef.current?.focus();
18060
- }, []);
18124
+ inputRef.current?.select();
18125
+ }, [focusRequest]);
18126
+ useEffect27(() => {
18127
+ if (showReplace) replaceInputRef.current?.focus();
18128
+ }, [showReplace, focusRequest]);
18129
+ const appliedRequestRef = useRef32(findRequest);
18130
+ useEffect27(() => {
18131
+ if (!findRequest || findRequest === appliedRequestRef.current) return;
18132
+ appliedRequestRef.current = findRequest;
18133
+ setQuery(findRequest.query);
18134
+ setMatchCase(findRequest.matchCase ?? false);
18135
+ setWholeWord(findRequest.wholeWord ?? false);
18136
+ setSelectedIndex(findRequest.occurrence ?? 0);
18137
+ }, [findRequest]);
18061
18138
  useEffect27(() => {
18062
18139
  const style = document.createElement("style");
18063
18140
  style.dataset.squisqFindHighlight = reactId;
18064
- style.textContent = `::highlight(${previewHighlightName}) { background: #fde68a; color: inherit; } ::highlight(${previewSelectedName}) { background: #f59e0b; color: #111827; }`;
18141
+ style.textContent = `::highlight(${previewHighlightName}) { background: var(--squisq-find-match, #fde68a); color: inherit; } ::highlight(${previewSelectedName}) { background: var(--squisq-find-match-current, #f59e0b); color: var(--squisq-find-match-text, #111827); }`;
18065
18142
  document.head.append(style);
18066
18143
  return () => style.remove();
18067
18144
  }, [previewHighlightName, previewSelectedName, reactId]);
@@ -18069,25 +18146,36 @@ function FindToolbar({ onClose }) {
18069
18146
  let cleanup = () => {
18070
18147
  };
18071
18148
  let count = 0;
18149
+ const navigationKey = [activeView, query, matchCase, wholeWord, selectedIndex].join("\0");
18150
+ if (navigationKey !== lastNavigationRef.current) {
18151
+ lastNavigationRef.current = navigationKey;
18152
+ pendingRevealRef.current = true;
18153
+ }
18154
+ const reveal = pendingRevealRef.current;
18155
+ const findOptions = { matchCase, wholeWord };
18072
18156
  if (activeView === "raw" && monacoEditor) {
18073
18157
  const model2 = monacoEditor.getModel();
18074
- const matches = model2 ? model2.findMatches(query.trim(), false, false, false, null, false) : [];
18075
- count = query.trim() ? matches.length : 0;
18158
+ const ranges = model2 ? findMonacoRanges(model2, query, findOptions) : [];
18159
+ count = ranges.length;
18076
18160
  const selected = normalizeFindIndex(selectedIndex, count);
18077
18161
  const decorations = monacoEditor.createDecorationsCollection(
18078
- matches.slice(0, count).map((match, index2) => ({
18079
- range: match.range,
18162
+ ranges.map((range, index2) => ({
18163
+ range,
18080
18164
  options: monacoDecorationOptions(index2 === selected)
18081
18165
  }))
18082
18166
  );
18083
- if (count > 0) monacoEditor.revealRangeInCenter(matches[selected].range);
18167
+ if (reveal && count > 0) {
18168
+ monacoEditor.setSelection(ranges[selected]);
18169
+ monacoEditor.revealRangeInCenter(ranges[selected]);
18170
+ }
18084
18171
  cleanup = () => decorations.clear();
18085
18172
  } else if (activeView === "wysiwyg" && tiptapEditor && !tiptapEditor.isDestroyed) {
18086
- count = updateTiptapFindHighlights(tiptapEditor, query, selectedIndex);
18087
- const selected = tiptapEditor.view.dom.querySelector(
18088
- ".squisq-find-match--selected"
18089
- );
18090
- selected?.scrollIntoView?.({ block: "center", inline: "nearest" });
18173
+ count = updateTiptapFindHighlights(tiptapEditor, query, selectedIndex, findOptions, {
18174
+ select: reveal
18175
+ });
18176
+ if (reveal) {
18177
+ tiptapEditor.view.dom.querySelector(".squisq-find-match--selected")?.scrollIntoView?.({ block: "center", inline: "nearest" });
18178
+ }
18091
18179
  cleanup = () => {
18092
18180
  if (!tiptapEditor.isDestroyed) updateTiptapFindHighlights(tiptapEditor, "", 0);
18093
18181
  };
@@ -18097,26 +18185,31 @@ function FindToolbar({ onClose }) {
18097
18185
  const result = applyPreviewHighlights(
18098
18186
  root,
18099
18187
  query,
18188
+ findOptions,
18100
18189
  selectedIndex,
18101
18190
  previewHighlightName,
18102
- previewSelectedName
18191
+ previewSelectedName,
18192
+ reveal
18103
18193
  );
18104
18194
  count = result.count;
18105
18195
  cleanup = result.cleanup;
18106
18196
  }
18107
18197
  }
18198
+ if (count > 0) pendingRevealRef.current = false;
18108
18199
  setMatchCount(count);
18109
- setSelectedIndex((current) => normalizeFindIndex(current, count));
18200
+ setSelectedIndex((current) => count === 0 ? current : normalizeFindIndex(current, count));
18110
18201
  return cleanup;
18111
18202
  }, [
18112
18203
  activeView,
18113
18204
  markdownSource,
18205
+ matchCase,
18114
18206
  monacoEditor,
18115
18207
  previewHighlightName,
18116
18208
  previewSelectedName,
18117
18209
  query,
18118
18210
  selectedIndex,
18119
- tiptapEditor
18211
+ tiptapEditor,
18212
+ wholeWord
18120
18213
  ]);
18121
18214
  const moveSelection = useCallback30(
18122
18215
  (delta) => {
@@ -18125,8 +18218,48 @@ function FindToolbar({ onClose }) {
18125
18218
  },
18126
18219
  [matchCount]
18127
18220
  );
18221
+ const replaceCurrent = () => {
18222
+ if (!canReplace || matchCount === 0) return;
18223
+ let next = null;
18224
+ if (activeView === "raw" && monacoEditor) {
18225
+ next = replaceMonacoMatch(monacoEditor, query, selectedIndex, replacement, options);
18226
+ } else if (activeView === "wysiwyg" && tiptapEditor && !tiptapEditor.isDestroyed) {
18227
+ next = replaceTiptapFindMatch(tiptapEditor, query, selectedIndex, replacement, options);
18228
+ }
18229
+ if (next === null) return;
18230
+ pendingRevealRef.current = true;
18231
+ setSelectedIndex(next);
18232
+ };
18233
+ const replaceAll = () => {
18234
+ if (!canReplace || matchCount === 0) return;
18235
+ if (activeView === "raw" && monacoEditor) {
18236
+ replaceAllMonacoMatches(monacoEditor, query, replacement, options);
18237
+ } else if (activeView === "wysiwyg" && tiptapEditor && !tiptapEditor.isDestroyed) {
18238
+ replaceAllTiptapFindMatches(tiptapEditor, query, replacement, options);
18239
+ }
18240
+ setSelectedIndex(0);
18241
+ };
18242
+ const close = () => {
18243
+ if (activeView === "raw") monacoEditor?.focus();
18244
+ else if (activeView === "wysiwyg" && tiptapEditor && !tiptapEditor.isDestroyed) {
18245
+ tiptapEditor.commands.focus();
18246
+ }
18247
+ onClose();
18248
+ };
18128
18249
  const resultLabel = query.trim() === "" ? "0 of 0" : matchCount === 0 ? "No results" : `${normalizeFindIndex(selectedIndex, matchCount) + 1} of ${matchCount}`;
18129
18250
  return /* @__PURE__ */ jsxs35("div", { className: "squisq-find-toolbar", role: "search", "aria-label": "Find in document", children: [
18251
+ canReplace && /* @__PURE__ */ jsx45(
18252
+ "button",
18253
+ {
18254
+ type: "button",
18255
+ className: "squisq-find-button squisq-find-replace-toggle",
18256
+ "aria-label": "Toggle replace",
18257
+ "aria-expanded": showReplace,
18258
+ "data-tooltip": "Toggle replace",
18259
+ onClick: () => onReplaceOpenChange?.(!replaceOpen),
18260
+ children: /* @__PURE__ */ jsx45(Icon, { icon: showReplace ? "fa-solid fa-chevron-down" : "fa-solid fa-chevron-right" })
18261
+ }
18262
+ ),
18130
18263
  /* @__PURE__ */ jsxs35("div", { className: "squisq-find-field", children: [
18131
18264
  /* @__PURE__ */ jsx45(Icon, { icon: "fa-solid fa-magnifying-glass" }),
18132
18265
  /* @__PURE__ */ jsx45(
@@ -18150,13 +18283,43 @@ function FindToolbar({ onClose }) {
18150
18283
  moveSelection(event.shiftKey ? -1 : 1);
18151
18284
  } else if (event.key === "Escape") {
18152
18285
  event.preventDefault();
18153
- onClose();
18286
+ close();
18154
18287
  }
18155
18288
  }
18156
18289
  }
18157
18290
  ),
18158
18291
  /* @__PURE__ */ jsx45("span", { className: "squisq-find-count", "aria-live": "polite", "aria-atomic": "true", children: resultLabel })
18159
18292
  ] }),
18293
+ /* @__PURE__ */ jsx45(
18294
+ "button",
18295
+ {
18296
+ type: "button",
18297
+ className: "squisq-find-button squisq-find-option",
18298
+ "aria-label": "Match case",
18299
+ "aria-pressed": matchCase,
18300
+ "data-tooltip": "Match case",
18301
+ onClick: () => {
18302
+ setMatchCase((value) => !value);
18303
+ setSelectedIndex(0);
18304
+ },
18305
+ children: /* @__PURE__ */ jsx45("span", { "aria-hidden": "true", children: "Aa" })
18306
+ }
18307
+ ),
18308
+ /* @__PURE__ */ jsx45(
18309
+ "button",
18310
+ {
18311
+ type: "button",
18312
+ className: "squisq-find-button squisq-find-option squisq-find-option--word",
18313
+ "aria-label": "Match whole word",
18314
+ "aria-pressed": wholeWord,
18315
+ "data-tooltip": "Match whole word",
18316
+ onClick: () => {
18317
+ setWholeWord((value) => !value);
18318
+ setSelectedIndex(0);
18319
+ },
18320
+ children: /* @__PURE__ */ jsx45("span", { "aria-hidden": "true", children: "ab" })
18321
+ }
18322
+ ),
18160
18323
  /* @__PURE__ */ jsx45(
18161
18324
  "button",
18162
18325
  {
@@ -18181,6 +18344,59 @@ function FindToolbar({ onClose }) {
18181
18344
  children: /* @__PURE__ */ jsx45(Icon, { icon: "fa-solid fa-chevron-down" })
18182
18345
  }
18183
18346
  ),
18347
+ showReplace && /* @__PURE__ */ jsxs35(Fragment14, { children: [
18348
+ /* @__PURE__ */ jsxs35("div", { className: "squisq-find-field squisq-replace-field", children: [
18349
+ /* @__PURE__ */ jsx45(Icon, { icon: "fa-solid fa-right-left" }),
18350
+ /* @__PURE__ */ jsx45(
18351
+ "input",
18352
+ {
18353
+ ref: replaceInputRef,
18354
+ type: "text",
18355
+ value: replacement,
18356
+ className: "squisq-find-input",
18357
+ "aria-label": "Replace with",
18358
+ placeholder: "Replace",
18359
+ autoComplete: "off",
18360
+ spellCheck: false,
18361
+ onChange: (event) => setReplacement(event.target.value),
18362
+ onKeyDown: (event) => {
18363
+ if (event.key === "Enter") {
18364
+ event.preventDefault();
18365
+ if (event.ctrlKey || event.metaKey) replaceAll();
18366
+ else replaceCurrent();
18367
+ } else if (event.key === "Escape") {
18368
+ event.preventDefault();
18369
+ close();
18370
+ }
18371
+ }
18372
+ }
18373
+ )
18374
+ ] }),
18375
+ /* @__PURE__ */ jsx45(
18376
+ "button",
18377
+ {
18378
+ type: "button",
18379
+ className: "squisq-find-button",
18380
+ "aria-label": "Replace",
18381
+ "data-tooltip": "Replace (Enter)",
18382
+ disabled: matchCount === 0,
18383
+ onClick: replaceCurrent,
18384
+ children: /* @__PURE__ */ jsx45(Icon, { icon: "fa-solid fa-arrow-right-long" })
18385
+ }
18386
+ ),
18387
+ /* @__PURE__ */ jsx45(
18388
+ "button",
18389
+ {
18390
+ type: "button",
18391
+ className: "squisq-find-button",
18392
+ "aria-label": "Replace all",
18393
+ "data-tooltip": "Replace all (Ctrl+Enter)",
18394
+ disabled: matchCount === 0,
18395
+ onClick: replaceAll,
18396
+ children: /* @__PURE__ */ jsx45(Icon, { icon: "fa-solid fa-angles-right" })
18397
+ }
18398
+ )
18399
+ ] }),
18184
18400
  /* @__PURE__ */ jsx45(
18185
18401
  "button",
18186
18402
  {
@@ -18188,19 +18404,81 @@ function FindToolbar({ onClose }) {
18188
18404
  className: "squisq-find-button squisq-find-close",
18189
18405
  "aria-label": "Close find",
18190
18406
  "data-tooltip": "Close find (Esc)",
18191
- onClick: onClose,
18407
+ onClick: close,
18192
18408
  children: /* @__PURE__ */ jsx45(Icon, { icon: "fa-solid fa-xmark" })
18193
18409
  }
18194
18410
  )
18195
18411
  ] });
18196
18412
  }
18413
+ function initialFindState(request, activeView, tiptapEditor, monacoEditor) {
18414
+ if (request) return request;
18415
+ let text = "";
18416
+ let index2 = 0;
18417
+ if (activeView === "raw" && monacoEditor) {
18418
+ const selection = monacoEditor.getSelection();
18419
+ const model2 = monacoEditor.getModel();
18420
+ if (selection && model2 && !selection.isEmpty()) {
18421
+ text = model2.getValueInRange(selection);
18422
+ const start = model2.getOffsetAt(selection.getStartPosition());
18423
+ index2 = nextFindIndexAfter(findTextMatches(model2.getValue(), text), start);
18424
+ }
18425
+ } else if (activeView === "wysiwyg" && tiptapEditor && !tiptapEditor.isDestroyed) {
18426
+ const { from, to } = tiptapEditor.state.selection;
18427
+ if (from !== to) {
18428
+ text = tiptapEditor.state.doc.textBetween(from, to, "\n");
18429
+ index2 = nextFindIndexAfter(findProseMirrorMatches(tiptapEditor.state.doc, text), from);
18430
+ }
18431
+ }
18432
+ if (!text.trim() || text.includes("\n") || text.length > MAX_SEEDED_QUERY) return { query: "" };
18433
+ return { query: text, occurrence: index2 };
18434
+ }
18435
+ function findMonacoRanges(model2, query, options) {
18436
+ return findTextMatches(model2.getValue(), query, options).map((match) => {
18437
+ const start = model2.getPositionAt(match.from);
18438
+ const end = model2.getPositionAt(match.to);
18439
+ return {
18440
+ startLineNumber: start.lineNumber,
18441
+ startColumn: start.column,
18442
+ endLineNumber: end.lineNumber,
18443
+ endColumn: end.column
18444
+ };
18445
+ });
18446
+ }
18447
+ function replaceMonacoMatch(editor, query, selectedIndex, replacement, options) {
18448
+ const model2 = editor.getModel();
18449
+ if (!model2) return null;
18450
+ const ranges = findMonacoRanges(model2, query, options);
18451
+ const range = ranges[normalizeFindIndex(selectedIndex, ranges.length)];
18452
+ if (!range) return null;
18453
+ const start = model2.getOffsetAt({ lineNumber: range.startLineNumber, column: range.startColumn });
18454
+ editor.pushUndoStop();
18455
+ editor.executeEdits("squisq-find", [{ range, text: replacement }]);
18456
+ editor.pushUndoStop();
18457
+ return nextFindIndexAfter(
18458
+ findTextMatches(model2.getValue(), query, options),
18459
+ start + replacement.length
18460
+ );
18461
+ }
18462
+ function replaceAllMonacoMatches(editor, query, replacement, options) {
18463
+ const model2 = editor.getModel();
18464
+ if (!model2) return 0;
18465
+ const ranges = findMonacoRanges(model2, query, options);
18466
+ if (ranges.length === 0) return 0;
18467
+ editor.pushUndoStop();
18468
+ editor.executeEdits(
18469
+ "squisq-find",
18470
+ ranges.map((range) => ({ range, text: replacement }))
18471
+ );
18472
+ editor.pushUndoStop();
18473
+ return ranges.length;
18474
+ }
18197
18475
  function monacoDecorationOptions(selected) {
18198
18476
  return {
18199
18477
  inlineClassName: selected ? "squisq-find-match squisq-find-match--selected" : "squisq-find-match"
18200
18478
  };
18201
18479
  }
18202
- function applyPreviewHighlights(root, query, selectedIndex, highlightName, selectedName) {
18203
- const ranges = collectTextRanges(root, query);
18480
+ function applyPreviewHighlights(root, query, options, selectedIndex, highlightName, selectedName, reveal) {
18481
+ const ranges = collectTextRanges(root, query, options);
18204
18482
  const selected = normalizeFindIndex(selectedIndex, ranges.length);
18205
18483
  const css = globalThis.CSS;
18206
18484
  const HighlightConstructor = globalThis.Highlight;
@@ -18208,7 +18486,7 @@ function applyPreviewHighlights(root, query, selectedIndex, highlightName, selec
18208
18486
  css.highlights.set(highlightName, new HighlightConstructor(...ranges));
18209
18487
  if (ranges.length > 0) {
18210
18488
  css.highlights.set(selectedName, new HighlightConstructor(ranges[selected]));
18211
- scrollRangeIntoView(ranges[selected]);
18489
+ if (reveal) scrollRangeIntoView(ranges[selected]);
18212
18490
  } else {
18213
18491
  css.highlights.delete(selectedName);
18214
18492
  }
@@ -18221,10 +18499,12 @@ function applyPreviewHighlights(root, query, selectedIndex, highlightName, selec
18221
18499
  };
18222
18500
  }
18223
18501
  const marks = markPreviewRanges(ranges, selected);
18224
- marks.find((mark) => mark.classList.contains("squisq-find-match--selected"))?.scrollIntoView?.({
18225
- block: "center",
18226
- inline: "nearest"
18227
- });
18502
+ if (reveal) {
18503
+ marks.find((mark) => mark.classList.contains("squisq-find-match--selected"))?.scrollIntoView?.({
18504
+ block: "center",
18505
+ inline: "nearest"
18506
+ });
18507
+ }
18228
18508
  return {
18229
18509
  count: ranges.length,
18230
18510
  cleanup: () => {
@@ -18239,7 +18519,7 @@ function applyPreviewHighlights(root, query, selectedIndex, highlightName, selec
18239
18519
  }
18240
18520
  };
18241
18521
  }
18242
- function collectTextRanges(root, query) {
18522
+ function collectTextRanges(root, query, options) {
18243
18523
  if (!query.trim()) return [];
18244
18524
  const doc = root.ownerDocument;
18245
18525
  const showText = doc.defaultView?.NodeFilter.SHOW_TEXT ?? 4;
@@ -18252,7 +18532,7 @@ function collectTextRanges(root, query) {
18252
18532
  if (parent && !parent.closest(
18253
18533
  'button, input, textarea, select, script, style, [aria-hidden="true"], [data-squisq-find-ignore]'
18254
18534
  )) {
18255
- for (const match of findTextMatches(text.data, query)) {
18535
+ for (const match of findTextMatches(text.data, query, options)) {
18256
18536
  const range = doc.createRange();
18257
18537
  range.setStart(text, match.from);
18258
18538
  range.setEnd(text, match.to);
@@ -18665,8 +18945,8 @@ function isTiptapActive(editor, id) {
18665
18945
  }
18666
18946
 
18667
18947
  // src/toolbar/sceneBlockInserts.ts
18668
- import { Fragment as Fragment14 } from "@tiptap/pm/model";
18669
- import { TextSelection } from "@tiptap/pm/state";
18948
+ import { Fragment as Fragment15 } from "@tiptap/pm/model";
18949
+ import { TextSelection as TextSelection2 } from "@tiptap/pm/state";
18670
18950
 
18671
18951
  // src/codeSnippet/codeSnippetFocus.ts
18672
18952
  var CODE_SNIPPET_FOCUS_INSERTED_META = "squisq-code-snippet-focus-inserted";
@@ -18750,16 +19030,16 @@ function insertChartBlock(editor, opts) {
18750
19030
  const cell = (type, text) => type.create(null, paragraph.create(null, text ? state.schema.text(text) : void 0));
18751
19031
  const headerRow = tableRow.create(
18752
19032
  null,
18753
- Fragment14.fromArray(opts.table.headers.map((h2) => cell(tableHeader, h2)))
19033
+ Fragment15.fromArray(opts.table.headers.map((h2) => cell(tableHeader, h2)))
18754
19034
  );
18755
19035
  const bodyRows = opts.table.rows.map(
18756
- (row) => tableRow.create(null, Fragment14.fromArray(row.map((c) => cell(tableCell, c))))
19036
+ (row) => tableRow.create(null, Fragment15.fromArray(row.map((c) => cell(tableCell, c))))
18757
19037
  );
18758
- nodes.push(table.create(null, Fragment14.fromArray([headerRow, ...bodyRows])));
19038
+ nodes.push(table.create(null, Fragment15.fromArray([headerRow, ...bodyRows])));
18759
19039
  }
18760
19040
  if (dispatch) {
18761
- tr.insert(insertPos, Fragment14.fromArray(nodes));
18762
- tr.setSelection(TextSelection.create(tr.doc, insertPos + 1));
19041
+ tr.insert(insertPos, Fragment15.fromArray(nodes));
19042
+ tr.setSelection(TextSelection2.create(tr.doc, insertPos + 1));
18763
19043
  }
18764
19044
  return true;
18765
19045
  }).run();
@@ -18779,7 +19059,7 @@ function insertTemplateHeading(editor, opts) {
18779
19059
  );
18780
19060
  if (dispatch) {
18781
19061
  tr.insert(insertPos, heading);
18782
- tr.setSelection(TextSelection.create(tr.doc, insertPos + 1));
19062
+ tr.setSelection(TextSelection2.create(tr.doc, insertPos + 1));
18783
19063
  }
18784
19064
  return true;
18785
19065
  }).run();
@@ -18803,8 +19083,8 @@ function insertLayoutBlock(editor) {
18803
19083
  });
18804
19084
  const body = paragraphType.create(null, state.schema.text("Layout"));
18805
19085
  if (dispatch) {
18806
- tr.insert(insertPos, Fragment14.fromArray([parent, child, body]));
18807
- tr.setSelection(TextSelection.create(tr.doc, insertPos + 1));
19086
+ tr.insert(insertPos, Fragment15.fromArray([parent, child, body]));
19087
+ tr.setSelection(TextSelection2.create(tr.doc, insertPos + 1));
18808
19088
  }
18809
19089
  return true;
18810
19090
  }).run();
@@ -18921,13 +19201,13 @@ ${chartTableMarkdown(entry.table)}
18921
19201
  }
18922
19202
 
18923
19203
  // src/chart/ChartTypeThumbnail.tsx
18924
- import { Fragment as Fragment15, jsx as jsx47, jsxs as jsxs36 } from "react/jsx-runtime";
19204
+ import { Fragment as Fragment16, jsx as jsx47, jsxs as jsxs36 } from "react/jsx-runtime";
18925
19205
  function ChartTypeThumbnail({ preview }) {
18926
19206
  const common = { fill: "none", stroke: "currentColor", strokeWidth: 2 };
18927
19207
  let art;
18928
19208
  switch (preview) {
18929
19209
  case "bar":
18930
- art = /* @__PURE__ */ jsxs36(Fragment15, { children: [
19210
+ art = /* @__PURE__ */ jsxs36(Fragment16, { children: [
18931
19211
  /* @__PURE__ */ jsx47("path", { d: "M14 8v48" }),
18932
19212
  /* @__PURE__ */ jsx47("rect", { x: "14", y: "12", width: "80", height: "9", fill: "currentColor", opacity: ".55" }),
18933
19213
  /* @__PURE__ */ jsx47("rect", { x: "14", y: "27", width: "52", height: "9", fill: "currentColor", opacity: ".35" }),
@@ -18935,7 +19215,7 @@ function ChartTypeThumbnail({ preview }) {
18935
19215
  ] });
18936
19216
  break;
18937
19217
  case "column":
18938
- art = /* @__PURE__ */ jsxs36(Fragment15, { children: [
19218
+ art = /* @__PURE__ */ jsxs36(Fragment16, { children: [
18939
19219
  /* @__PURE__ */ jsx47("path", { d: "M10 54h100" }),
18940
19220
  /* @__PURE__ */ jsx47("rect", { x: "22", y: "26", width: "14", height: "28", fill: "currentColor", opacity: ".45" }),
18941
19221
  /* @__PURE__ */ jsx47("rect", { x: "44", y: "14", width: "14", height: "40", fill: "currentColor", opacity: ".55" }),
@@ -18944,14 +19224,14 @@ function ChartTypeThumbnail({ preview }) {
18944
19224
  ] });
18945
19225
  break;
18946
19226
  case "pie":
18947
- art = /* @__PURE__ */ jsxs36(Fragment15, { children: [
19227
+ art = /* @__PURE__ */ jsxs36(Fragment16, { children: [
18948
19228
  /* @__PURE__ */ jsx47("circle", { cx: "60", cy: "32", r: "24" }),
18949
19229
  /* @__PURE__ */ jsx47("path", { d: "M60 32V8a24 24 0 0 1 22.6 31.9Z", fill: "currentColor", opacity: ".45" }),
18950
19230
  /* @__PURE__ */ jsx47("path", { d: "M60 32 82.6 39.9A24 24 0 0 1 46 53.4Z", fill: "currentColor", opacity: ".25" })
18951
19231
  ] });
18952
19232
  break;
18953
19233
  case "donut":
18954
- art = /* @__PURE__ */ jsxs36(Fragment15, { children: [
19234
+ art = /* @__PURE__ */ jsxs36(Fragment16, { children: [
18955
19235
  /* @__PURE__ */ jsx47("circle", { cx: "60", cy: "32", r: "24" }),
18956
19236
  /* @__PURE__ */ jsx47("circle", { cx: "60", cy: "32", r: "11" }),
18957
19237
  /* @__PURE__ */ jsx47(
@@ -18965,7 +19245,7 @@ function ChartTypeThumbnail({ preview }) {
18965
19245
  ] });
18966
19246
  break;
18967
19247
  case "line":
18968
- art = /* @__PURE__ */ jsxs36(Fragment15, { children: [
19248
+ art = /* @__PURE__ */ jsxs36(Fragment16, { children: [
18969
19249
  /* @__PURE__ */ jsx47("path", { d: "M10 54h100M10 8v46", opacity: ".4" }),
18970
19250
  /* @__PURE__ */ jsx47("path", { d: "M16 46l24-16 24 8 24-20 20 6", strokeWidth: "3" }),
18971
19251
  /* @__PURE__ */ jsx47("circle", { cx: "40", cy: "30", r: "3", fill: "currentColor" }),
@@ -18974,7 +19254,7 @@ function ChartTypeThumbnail({ preview }) {
18974
19254
  ] });
18975
19255
  break;
18976
19256
  case "area":
18977
- art = /* @__PURE__ */ jsxs36(Fragment15, { children: [
19257
+ art = /* @__PURE__ */ jsxs36(Fragment16, { children: [
18978
19258
  /* @__PURE__ */ jsx47("path", { d: "M10 54h100", opacity: ".4" }),
18979
19259
  /* @__PURE__ */ jsx47(
18980
19260
  "path",
@@ -18989,7 +19269,7 @@ function ChartTypeThumbnail({ preview }) {
18989
19269
  ] });
18990
19270
  break;
18991
19271
  case "scatter":
18992
- art = /* @__PURE__ */ jsxs36(Fragment15, { children: [
19272
+ art = /* @__PURE__ */ jsxs36(Fragment16, { children: [
18993
19273
  /* @__PURE__ */ jsx47("path", { d: "M14 8v46h92", opacity: ".4" }),
18994
19274
  /* @__PURE__ */ jsx47("circle", { cx: "30", cy: "42", r: "3.5", fill: "currentColor", opacity: ".5" }),
18995
19275
  /* @__PURE__ */ jsx47("circle", { cx: "42", cy: "30", r: "3.5", fill: "currentColor" }),
@@ -19004,7 +19284,7 @@ function ChartTypeThumbnail({ preview }) {
19004
19284
  }
19005
19285
 
19006
19286
  // src/Toolbar.tsx
19007
- import { Fragment as Fragment16, jsx as jsx48, jsxs as jsxs37 } from "react/jsx-runtime";
19287
+ import { Fragment as Fragment17, jsx as jsx48, jsxs as jsxs37 } from "react/jsx-runtime";
19008
19288
  var BLOCK_META_KEYS = new Set(Object.keys(KNOWN_BLOCK_META_KEYS));
19009
19289
  function findBlockBySourceLine(blocks, lineNumber) {
19010
19290
  for (const block of blocks) {
@@ -19039,6 +19319,9 @@ function keepBlockMetaParamString(inner) {
19039
19319
  const params = keepBlockMetaParamTokens(tokenizeAttrTokens2(inner));
19040
19320
  return params.length > 0 ? params.join(" ") : null;
19041
19321
  }
19322
+ function isApplePlatform() {
19323
+ return typeof navigator !== "undefined" && /Mac|iPhone|iPad|iPod/u.test(navigator.platform);
19324
+ }
19042
19325
  function Toolbar({
19043
19326
  className,
19044
19327
  showFiles,
@@ -19051,7 +19334,9 @@ function Toolbar({
19051
19334
  showPlayTab = true,
19052
19335
  showFormattingControls = true,
19053
19336
  showInsertControls = true,
19054
- hostMode = "document"
19337
+ hostMode = "document",
19338
+ readOnly = false,
19339
+ findShortcut = false
19055
19340
  }) {
19056
19341
  const {
19057
19342
  activeView,
@@ -19234,6 +19519,32 @@ function Toolbar({
19234
19519
  closeInsertMenu();
19235
19520
  closeEmojiPicker();
19236
19521
  }, [closeEmojiPicker, closeInsertMenu, findMode]);
19522
+ const toolbarRootRef = useRef33(null);
19523
+ const [replaceOpen, setReplaceOpen] = useState36(false);
19524
+ const [findFocusRequest, setFindFocusRequest] = useState36(0);
19525
+ useEffect28(() => {
19526
+ if (!findMode) setReplaceOpen(false);
19527
+ }, [findMode]);
19528
+ useEffect28(() => {
19529
+ if (!findShortcut || editorMode === "image") return;
19530
+ const apple = isApplePlatform();
19531
+ const onKeyDown = (event) => {
19532
+ if (event.shiftKey) return;
19533
+ const accel = event.ctrlKey || event.metaKey;
19534
+ const find = accel && !event.altKey && event.code === "KeyF";
19535
+ const replace = apple ? event.metaKey && event.altKey && event.code === "KeyF" : event.ctrlKey && !event.altKey && event.code === "KeyH";
19536
+ if (!find && !replace) return;
19537
+ const shell = toolbarRootRef.current?.closest(".squisq-editor-shell");
19538
+ if (!shell || !(event.target instanceof Node) || !shell.contains(event.target)) return;
19539
+ event.preventDefault();
19540
+ event.stopPropagation();
19541
+ if (replace && !readOnly) setReplaceOpen(true);
19542
+ setFindMode(true);
19543
+ setFindFocusRequest((request) => request + 1);
19544
+ };
19545
+ window.addEventListener("keydown", onKeyDown, true);
19546
+ return () => window.removeEventListener("keydown", onKeyDown, true);
19547
+ }, [editorMode, findShortcut, readOnly, setFindMode]);
19237
19548
  const actionsLaneShown = !findMode && activeView !== "preview" && !isCodeMode && (showFormattingControls || showInsertControls);
19238
19549
  const collapsed = new Set(actionsLaneShown && collapsedKey ? collapsedKey.split("|") : []);
19239
19550
  const clippedContextual = new Set(
@@ -20389,6 +20700,7 @@ ${TASK_LIST_MARKDOWN}
20389
20700
  return /* @__PURE__ */ jsxs37(
20390
20701
  "div",
20391
20702
  {
20703
+ ref: toolbarRootRef,
20392
20704
  className: `squisq-toolbar${findMode ? " squisq-toolbar--find" : ""} ${className || ""}`,
20393
20705
  role: "toolbar",
20394
20706
  "aria-label": findMode ? "Find toolbar" : showFormattingControls ? "Formatting toolbar" : "Editor toolbar",
@@ -20422,7 +20734,16 @@ ${TASK_LIST_MARKDOWN}
20422
20734
  ),
20423
20735
  !findMode && slotLeft,
20424
20736
  /* @__PURE__ */ jsx48(EditorViewTabs, { showPlayTab, hostMode }),
20425
- findMode ? /* @__PURE__ */ jsx48(FindToolbar, { onClose: () => setFindMode(false) }) : slotAfterTabs,
20737
+ findMode ? /* @__PURE__ */ jsx48(
20738
+ FindToolbar,
20739
+ {
20740
+ onClose: () => setFindMode(false),
20741
+ allowReplace: !readOnly,
20742
+ replaceOpen: replaceOpen && !readOnly,
20743
+ onReplaceOpenChange: setReplaceOpen,
20744
+ focusRequest: findFocusRequest
20745
+ }
20746
+ ) : slotAfterTabs,
20426
20747
  actionsLaneShown && /* @__PURE__ */ jsxs37("div", { className: "squisq-toolbar-actions", ref: actionsRef, children: [
20427
20748
  showFormattingControls && groups.map((group) => /* @__PURE__ */ jsxs37(
20428
20749
  "div",
@@ -20831,7 +21152,7 @@ ${TASK_LIST_MARKDOWN}
20831
21152
  ]
20832
21153
  }
20833
21154
  ),
20834
- showBlockSectionInOverflow && /* @__PURE__ */ jsxs37(Fragment16, { children: [
21155
+ showBlockSectionInOverflow && /* @__PURE__ */ jsxs37(Fragment17, { children: [
20835
21156
  /* @__PURE__ */ jsx48(
20836
21157
  "div",
20837
21158
  {
@@ -20866,7 +21187,7 @@ ${TASK_LIST_MARKDOWN}
20866
21187
  accentColor: previewSettings?.activeTheme.colors.primary
20867
21188
  }
20868
21189
  ) }),
20869
- showFormattingControls && isInTable && clippedContextual.has("table") && /* @__PURE__ */ jsxs37(Fragment16, { children: [
21190
+ showFormattingControls && isInTable && clippedContextual.has("table") && /* @__PURE__ */ jsxs37(Fragment17, { children: [
20870
21191
  /* @__PURE__ */ jsx48(
20871
21192
  "div",
20872
21193
  {
@@ -20923,7 +21244,7 @@ ${TASK_LIST_MARKDOWN}
20923
21244
  !findMode && slotAfterActions,
20924
21245
  isCodeMode && !findMode && /* @__PURE__ */ jsx48("div", { style: { flex: 1 } }),
20925
21246
  versioning && !isCodeMode && /* @__PURE__ */ jsx48(VersionHistoryPanel, {}),
20926
- allowRecording && !isCodeMode && mediaProvider && /* @__PURE__ */ jsxs37(Fragment16, { children: [
21247
+ allowRecording && !isCodeMode && mediaProvider && /* @__PURE__ */ jsxs37(Fragment17, { children: [
20927
21248
  /* @__PURE__ */ jsx48(RecorderEntry, { open: recorderOpen, onOpenChange: setRecorderOpen, showTrigger: false }),
20928
21249
  /* @__PURE__ */ jsx48(
20929
21250
  RecorderEntry,
@@ -21021,7 +21342,7 @@ ${TASK_LIST_MARKDOWN}
21021
21342
  },
21022
21343
  role: "menu",
21023
21344
  children: [
21024
- showConvertActions && /* @__PURE__ */ jsxs37(Fragment16, { children: [
21345
+ showConvertActions && /* @__PURE__ */ jsxs37(Fragment17, { children: [
21025
21346
  /* @__PURE__ */ jsx48("div", { className: "squisq-insert-menu-header", children: "Convert" }),
21026
21347
  CONVERT_BUTTONS.map((btn) => {
21027
21348
  const label = btn.id === "table" ? "Table" : "Task List";
@@ -26917,7 +27238,7 @@ function setMermaidFlowchartDirection(source, direction) {
26917
27238
 
26918
27239
  // src/mermaid/MermaidDiagramWidget.tsx
26919
27240
  import { DEFAULT_THEME as DEFAULT_THEME3 } from "@bendyline/squisq/doc";
26920
- import { Fragment as Fragment17, jsx as jsx56, jsxs as jsxs44 } from "react/jsx-runtime";
27241
+ import { Fragment as Fragment18, jsx as jsx56, jsxs as jsxs44 } from "react/jsx-runtime";
26921
27242
  var MIN_DIAGRAM_HEIGHT2 = 160;
26922
27243
  var DEFAULT_DIAGRAM_HEIGHT2 = 420;
26923
27244
  var DIRECTION_PICKER_WIDTH = 344;
@@ -27579,7 +27900,7 @@ function MermaidPropertiesPalette({
27579
27900
  }
27580
27901
  ),
27581
27902
  /* @__PURE__ */ jsx56("span", { children: property.label })
27582
- ] }) : /* @__PURE__ */ jsxs44(Fragment17, { children: [
27903
+ ] }) : /* @__PURE__ */ jsxs44(Fragment18, { children: [
27583
27904
  /* @__PURE__ */ jsx56("span", { children: property.label }),
27584
27905
  property.type === "select" ? /* @__PURE__ */ jsx56(
27585
27906
  "select",
@@ -29836,7 +30157,7 @@ var VideoEmbedExtension = Extension11.create({
29836
30157
  });
29837
30158
 
29838
30159
  // src/videoEmbed/videoEmbedPaste.ts
29839
- import { TextSelection as TextSelection2 } from "@tiptap/pm/state";
30160
+ import { TextSelection as TextSelection3 } from "@tiptap/pm/state";
29840
30161
  import { parseVideoEmbedInput as parseVideoEmbedInput3 } from "@bendyline/squisq/markdown";
29841
30162
  function looksLikeEmbedCode(text) {
29842
30163
  return /^<(?!https?:)/i.test(text) && /<iframe\b/i.test(text);
@@ -29858,7 +30179,7 @@ function insertTopLevelBlock(state, node2) {
29858
30179
  tr.insert(start, node2);
29859
30180
  }
29860
30181
  }
29861
- tr.setSelection(TextSelection2.near(tr.doc.resolve(start + node2.nodeSize - 1), -1));
30182
+ tr.setSelection(TextSelection3.near(tr.doc.resolve(start + node2.nodeSize - 1), -1));
29862
30183
  return tr.scrollIntoView();
29863
30184
  }
29864
30185
  function pasteVideoEmbedCode(view, text) {
@@ -31102,7 +31423,7 @@ function applyTimelineCommand(editor, blockId, command) {
31102
31423
  }
31103
31424
 
31104
31425
  // src/timeline/TimelineEditorWidget.tsx
31105
- import { Fragment as Fragment18, jsx as jsx61, jsxs as jsxs48 } from "react/jsx-runtime";
31426
+ import { Fragment as Fragment19, jsx as jsx61, jsxs as jsxs48 } from "react/jsx-runtime";
31106
31427
  var clamp01 = (value) => Math.max(0, Math.min(1, value));
31107
31428
  function TimelineEditorWidget({ editor, blockId }) {
31108
31429
  const view = useTimelineData(editor, blockId);
@@ -31323,7 +31644,7 @@ function TimelineEditorWidget({ editor, blockId }) {
31323
31644
  onFinish: () => setEditingTrackId(null)
31324
31645
  }
31325
31646
  ),
31326
- editable ? /* @__PURE__ */ jsxs48(Fragment18, { children: [
31647
+ editable ? /* @__PURE__ */ jsxs48(Fragment19, { children: [
31327
31648
  /* @__PURE__ */ jsx61(
31328
31649
  "button",
31329
31650
  {
@@ -32289,7 +32610,7 @@ function normalizeMalformedAssetUrl(src) {
32289
32610
  }
32290
32611
 
32291
32612
  // src/ImageNodeView.tsx
32292
- import { Fragment as Fragment19, jsx as jsx63, jsxs as jsxs50 } from "react/jsx-runtime";
32613
+ import { Fragment as Fragment20, jsx as jsx63, jsxs as jsxs50 } from "react/jsx-runtime";
32293
32614
  function ImageComponent({ node: node2, selected, editor, updateAttributes }) {
32294
32615
  const { src, alt, title, width } = node2.attrs;
32295
32616
  const { mediaProvider, imageDisplayMode, openImageEdit, mediaRevision } = useEditorContext();
@@ -32416,11 +32737,11 @@ function ImageComponent({ node: node2, selected, editor, updateAttributes }) {
32416
32737
  strokeLinecap: "round",
32417
32738
  strokeLinejoin: "round",
32418
32739
  "aria-hidden": "true",
32419
- children: failed ? /* @__PURE__ */ jsxs50(Fragment19, { children: [
32740
+ children: failed ? /* @__PURE__ */ jsxs50(Fragment20, { children: [
32420
32741
  /* @__PURE__ */ jsx63("rect", { x: "3", y: "3", width: "18", height: "18", rx: "3" }),
32421
32742
  /* @__PURE__ */ jsx63("circle", { cx: "8", cy: "8", r: "1.5" }),
32422
32743
  /* @__PURE__ */ jsx63("path", { d: "m4 17 5-5 4 4 3-3 4 4M3 3l18 18" })
32423
- ] }) : /* @__PURE__ */ jsxs50(Fragment19, { children: [
32744
+ ] }) : /* @__PURE__ */ jsxs50(Fragment20, { children: [
32424
32745
  /* @__PURE__ */ jsx63("path", { d: "M6 3h12M6 21h12M7 3v4c0 2 3 3 5 5-2 2-5 3-5 5v4M17 3v4c0 2-3 3-5 5 2 2 5 3 5 5v4" }),
32425
32746
  /* @__PURE__ */ jsx63("path", { d: "M9 6h6M9 18h6" })
32426
32747
  ] })
@@ -32471,7 +32792,7 @@ function ImageComponent({ node: node2, selected, editor, updateAttributes }) {
32471
32792
  ]
32472
32793
  }
32473
32794
  ),
32474
- showResize && /* @__PURE__ */ jsxs50(Fragment19, { children: [
32795
+ showResize && /* @__PURE__ */ jsxs50(Fragment20, { children: [
32475
32796
  /* @__PURE__ */ jsx63(
32476
32797
  "span",
32477
32798
  {
@@ -32603,7 +32924,7 @@ function usePlaybackSrc(src, kind, fx) {
32603
32924
  }
32604
32925
 
32605
32926
  // src/mediaEdit/MediaEditButton.tsx
32606
- import { Fragment as Fragment20, jsx as jsx64, jsxs as jsxs51 } from "react/jsx-runtime";
32927
+ import { Fragment as Fragment21, jsx as jsx64, jsxs as jsxs51 } from "react/jsx-runtime";
32607
32928
  function MediaEditButton({ src, kind, fx, className }) {
32608
32929
  const editor = useEditorContextOptional();
32609
32930
  const manager = editor?.mediaEditRenders;
@@ -32624,7 +32945,7 @@ function MediaEditButton({ src, kind, fx, className }) {
32624
32945
  onClick: () => editor.openMediaEdit({ src, kind }),
32625
32946
  children: [
32626
32947
  kind === "video" ? "Edit clip" : "Edit audio",
32627
- progress && /* @__PURE__ */ jsxs51(Fragment20, { children: [
32948
+ progress && /* @__PURE__ */ jsxs51(Fragment21, { children: [
32628
32949
  /* @__PURE__ */ jsx64("span", { className: "squisq-media-edit-button__spinner", "aria-hidden": "true" }),
32629
32950
  /* @__PURE__ */ jsxs51("span", { className: "squisq-media-edit-button__progress", children: [
32630
32951
  /* @__PURE__ */ jsx64("span", { className: "squisq-sr-only", children: ", processing " }),
@@ -34463,7 +34784,7 @@ function usePreviewProjection(doc, transformStyle, workspaceContainer, documentT
34463
34784
  }
34464
34785
 
34465
34786
  // src/InlinePreviewGutter.tsx
34466
- import { Fragment as Fragment21, jsx as jsx68, jsxs as jsxs55 } from "react/jsx-runtime";
34787
+ import { Fragment as Fragment22, jsx as jsx68, jsxs as jsxs55 } from "react/jsx-runtime";
34467
34788
  function isAnnotated(block) {
34468
34789
  const annotation = block.sourceHeading?.templateAnnotation;
34469
34790
  if (!annotation) return false;
@@ -34849,7 +35170,7 @@ function InlinePreviewGutter({
34849
35170
  children: [
34850
35171
  /* @__PURE__ */ jsxs55("div", { className: "squisq-inline-preview-card-label", children: [
34851
35172
  /* @__PURE__ */ jsx68("span", { className: "squisq-inline-preview-card-template", children: templateLabel(item.template) }),
34852
- item.headingText && /* @__PURE__ */ jsxs55(Fragment21, { children: [
35173
+ item.headingText && /* @__PURE__ */ jsxs55(Fragment22, { children: [
34853
35174
  /* @__PURE__ */ jsx68("span", { className: "squisq-inline-preview-card-sep", children: "\u2014" }),
34854
35175
  /* @__PURE__ */ jsx68("span", { className: "squisq-inline-preview-card-title", children: item.headingText })
34855
35176
  ] })
@@ -35671,7 +35992,7 @@ function CodeContextSectionView({
35671
35992
  }
35672
35993
 
35673
35994
  // src/codeContext/CodeContextZones.tsx
35674
- import { Fragment as Fragment22, jsx as jsx71 } from "react/jsx-runtime";
35995
+ import { Fragment as Fragment23, jsx as jsx71 } from "react/jsx-runtime";
35675
35996
  function CodeContextZones({ options }) {
35676
35997
  const { monacoEditor } = useEditorContext();
35677
35998
  const [manager, setManager] = useState65(null);
@@ -35740,7 +36061,7 @@ function CodeContextZones({ options }) {
35740
36061
  [monacoEditor]
35741
36062
  );
35742
36063
  if (!manager) return null;
35743
- return /* @__PURE__ */ jsx71(Fragment22, { children: resolved.map(({ section }) => {
36064
+ return /* @__PURE__ */ jsx71(Fragment23, { children: resolved.map(({ section }) => {
35744
36065
  const domNode = manager.getDomNode(section.id);
35745
36066
  if (!domNode) return null;
35746
36067
  return createPortal14(
@@ -36311,7 +36632,7 @@ function timelineBars(clips) {
36311
36632
  // src/TimelineItemMenu.tsx
36312
36633
  import { useEffect as useEffect56, useLayoutEffect as useLayoutEffect11, useRef as useRef54, useState as useState67 } from "react";
36313
36634
  import { createPortal as createPortal15 } from "react-dom";
36314
- import { Fragment as Fragment23, jsx as jsx73, jsxs as jsxs59 } from "react/jsx-runtime";
36635
+ import { Fragment as Fragment24, jsx as jsx73, jsxs as jsxs59 } from "react/jsx-runtime";
36315
36636
  function TimelineItemMenu({
36316
36637
  anchor,
36317
36638
  target,
@@ -36384,7 +36705,7 @@ function TimelineItemMenu({
36384
36705
  onStartTime: onBlockStartTime,
36385
36706
  onTransition: onBlockTransition
36386
36707
  }
36387
- ) : /* @__PURE__ */ jsxs59(Fragment23, { children: [
36708
+ ) : /* @__PURE__ */ jsxs59(Fragment24, { children: [
36388
36709
  /* @__PURE__ */ jsx73(VideoMenu, { target, onPatch: onVideoPatch }),
36389
36710
  onEditMedia && /* @__PURE__ */ jsx73(
36390
36711
  "button",
@@ -36530,7 +36851,7 @@ function VideoMenu({
36530
36851
  }
36531
36852
  )
36532
36853
  ] }),
36533
- placed && /* @__PURE__ */ jsxs59(Fragment23, { children: [
36854
+ placed && /* @__PURE__ */ jsxs59(Fragment24, { children: [
36534
36855
  /* @__PURE__ */ jsxs59("label", { className: "squisq-timeline-item-menu-check", children: [
36535
36856
  /* @__PURE__ */ jsx73(
36536
36857
  "input",
@@ -36548,7 +36869,7 @@ function VideoMenu({
36548
36869
  ),
36549
36870
  /* @__PURE__ */ jsx73("span", { children: "Lock to block" })
36550
36871
  ] }),
36551
- placement === "picture-in-picture" && /* @__PURE__ */ jsxs59(Fragment23, { children: [
36872
+ placement === "picture-in-picture" && /* @__PURE__ */ jsxs59(Fragment24, { children: [
36552
36873
  /* @__PURE__ */ jsxs59("label", { className: "squisq-timeline-item-menu-field", children: [
36553
36874
  /* @__PURE__ */ jsx73("span", { className: "squisq-timeline-item-menu-label", children: "PIP size" }),
36554
36875
  /* @__PURE__ */ jsxs59(
@@ -39306,7 +39627,7 @@ function collectSlideshowDataSources(doc) {
39306
39627
  }
39307
39628
 
39308
39629
  // src/slideshow/SlideshowDataGrid.tsx
39309
- import { Fragment as Fragment24, jsx as jsx82 } from "react/jsx-runtime";
39630
+ import { Fragment as Fragment25, jsx as jsx82 } from "react/jsx-runtime";
39310
39631
  var EMPTY_CONTEXT = {
39311
39632
  container: null,
39312
39633
  mediaRevision: 0,
@@ -39436,7 +39757,7 @@ function SlideshowDataGridRenderer({
39436
39757
  const initialView = ready?.initialView ?? EMPTY_VIEW;
39437
39758
  const [view, setView] = useState72(initialView);
39438
39759
  useEffect62(() => setView(initialView), [initialView, provider]);
39439
- if (!provider || !ingest || !gridModule) return /* @__PURE__ */ jsx82(Fragment24, { children: fallback });
39760
+ if (!provider || !ingest || !gridModule) return /* @__PURE__ */ jsx82(Fragment25, { children: fallback });
39440
39761
  const { style } = layer.content;
39441
39762
  const rowHeight = Math.max(32, style.fontSize * 2.1);
39442
39763
  const footerHeight = Math.max(28, style.fontSize * 1.5);
@@ -40319,7 +40640,7 @@ ${formatSize(entry.size)}`,
40319
40640
 
40320
40641
  // src/DropZoneOverlay.tsx
40321
40642
  import { useState as useState75 } from "react";
40322
- import { Fragment as Fragment25, jsx as jsx85, jsxs as jsxs69 } from "react/jsx-runtime";
40643
+ import { Fragment as Fragment26, jsx as jsx85, jsxs as jsxs69 } from "react/jsx-runtime";
40323
40644
  function DropZoneOverlay({
40324
40645
  dragContentType,
40325
40646
  zoneProps,
@@ -40340,7 +40661,7 @@ function DropZoneOverlay({
40340
40661
  variant: "media"
40341
40662
  }
40342
40663
  ),
40343
- showText && /* @__PURE__ */ jsxs69(Fragment25, { children: [
40664
+ showText && /* @__PURE__ */ jsxs69(Fragment26, { children: [
40344
40665
  /* @__PURE__ */ jsx85(
40345
40666
  DropZone,
40346
40667
  {
@@ -40835,7 +41156,7 @@ function centeredCrop(ratio, frameWidth, frameHeight) {
40835
41156
  }
40836
41157
 
40837
41158
  // src/mediaEdit/MediaEditAudioSection.tsx
40838
- import { Fragment as Fragment26, jsx as jsx89, jsxs as jsxs71 } from "react/jsx-runtime";
41159
+ import { Fragment as Fragment27, jsx as jsx89, jsxs as jsxs71 } from "react/jsx-runtime";
40839
41160
  var STAGES = [
40840
41161
  {
40841
41162
  id: "highpass",
@@ -41142,7 +41463,7 @@ function MediaEditAudioSection({
41142
41463
  children: previewing ? "Rendering preview\u2026" : preview ? "Update preview" : "Create preview"
41143
41464
  }
41144
41465
  ) }),
41145
- preview != null && player.duration > 0 && /* @__PURE__ */ jsxs71(Fragment26, { children: [
41466
+ preview != null && player.duration > 0 && /* @__PURE__ */ jsxs71(Fragment27, { children: [
41146
41467
  /* @__PURE__ */ jsxs71("div", { className: "squisq-media-edit-playback", "data-playing": player.playing ?? void 0, children: [
41147
41468
  /* @__PURE__ */ jsx89("span", { className: "squisq-media-edit-playback__label", children: player.playing ? `${player.playing === "original" ? "Original" : "Processed"} from ${Math.round(preview.startSec)} s` : "Stopped" }),
41148
41469
  /* @__PURE__ */ jsx89(
@@ -41488,7 +41809,7 @@ function MediaEditFrameSection({ item, draft, onChange }) {
41488
41809
  }
41489
41810
 
41490
41811
  // src/mediaEdit/MediaEditModal.tsx
41491
- import { Fragment as Fragment27, jsx as jsx92, jsxs as jsxs74 } from "react/jsx-runtime";
41812
+ import { Fragment as Fragment28, jsx as jsx92, jsxs as jsxs74 } from "react/jsx-runtime";
41492
41813
  var TABS = [
41493
41814
  { id: "audio", label: "Audio cleanup" },
41494
41815
  { id: "timing", label: "Timing" },
@@ -41523,16 +41844,16 @@ function RenderStatusText({
41523
41844
  }) {
41524
41845
  switch (status.state) {
41525
41846
  case "ready":
41526
- return /* @__PURE__ */ jsx92(Fragment27, { children: "Processed audio is in use." });
41847
+ return /* @__PURE__ */ jsx92(Fragment28, { children: "Processed audio is in use." });
41527
41848
  case "queued":
41528
- return /* @__PURE__ */ jsx92(Fragment27, { children: "Waiting to process\u2026" });
41849
+ return /* @__PURE__ */ jsx92(Fragment28, { children: "Waiting to process\u2026" });
41529
41850
  case "rendering":
41530
- return /* @__PURE__ */ jsxs74(Fragment27, { children: [
41851
+ return /* @__PURE__ */ jsxs74(Fragment28, { children: [
41531
41852
  "Processing\u2026 ",
41532
41853
  percent(status)
41533
41854
  ] });
41534
41855
  case "failed":
41535
- return /* @__PURE__ */ jsxs74(Fragment27, { children: [
41856
+ return /* @__PURE__ */ jsxs74(Fragment28, { children: [
41536
41857
  "Processing failed: ",
41537
41858
  status.error,
41538
41859
  " ",
@@ -41620,7 +41941,7 @@ function MediaEditModal({ target, onClose }) {
41620
41941
  }
41621
41942
  )
41622
41943
  ] }),
41623
- !item ? /* @__PURE__ */ jsx92("p", { className: "squisq-media-edit-modal__empty", children: "This clip is no longer in the document." }) : !mediaEditRenders ? /* @__PURE__ */ jsx92("p", { className: "squisq-media-edit-modal__empty", children: "Media editing needs a media folder for this document." }) : /* @__PURE__ */ jsxs74(Fragment27, { children: [
41944
+ !item ? /* @__PURE__ */ jsx92("p", { className: "squisq-media-edit-modal__empty", children: "This clip is no longer in the document." }) : !mediaEditRenders ? /* @__PURE__ */ jsx92("p", { className: "squisq-media-edit-modal__empty", children: "Media editing needs a media folder for this document." }) : /* @__PURE__ */ jsxs74(Fragment28, { children: [
41624
41945
  /* @__PURE__ */ jsx92("div", { className: "squisq-media-edit-tabs", role: "tablist", "aria-label": "Edit sections", children: tabs.map((tab, index2) => {
41625
41946
  const selected = tab.id === activeTab;
41626
41947
  const edited = tabHasEdits(tab.id, draft);
@@ -41983,7 +42304,7 @@ import {
41983
42304
  createMediaEditRenderManager,
41984
42305
  useProcessedAudio as useProcessedAudio4
41985
42306
  } from "@bendyline/squisq-video-react/media-edit";
41986
- import { Fragment as Fragment28, jsx as jsx93, jsxs as jsxs75 } from "react/jsx-runtime";
42307
+ import { Fragment as Fragment29, jsx as jsx93, jsxs as jsxs75 } from "react/jsx-runtime";
41987
42308
  function EditorShell({
41988
42309
  initialMarkdown = "",
41989
42310
  initialView = "wysiwyg",
@@ -42039,6 +42360,8 @@ function EditorShell({
42039
42360
  language,
42040
42361
  findMode,
42041
42362
  onFindModeChange,
42363
+ findRequest,
42364
+ findShortcut = false,
42042
42365
  mentionProvider,
42043
42366
  proofing = null,
42044
42367
  proofingDefaultEnabled = true,
@@ -42122,6 +42445,7 @@ function EditorShell({
42122
42445
  language,
42123
42446
  findMode,
42124
42447
  onFindModeChange,
42448
+ findRequest,
42125
42449
  inlinePreview,
42126
42450
  showStatusBar,
42127
42451
  outline,
@@ -42170,6 +42494,7 @@ function EditorShell({
42170
42494
  thinMargins,
42171
42495
  writeCanvasSettings,
42172
42496
  readOnly,
42497
+ findShortcut,
42173
42498
  imageSrc,
42174
42499
  imageAlt,
42175
42500
  imageMode,
@@ -42193,7 +42518,7 @@ function UseModeToolbarControls({
42193
42518
  }) {
42194
42519
  const printMode = usePrintMode();
42195
42520
  if (printMode.active) return /* @__PURE__ */ jsx93(PrintPreviewToolbar, {});
42196
- return /* @__PURE__ */ jsxs75(Fragment28, { children: [
42521
+ return /* @__PURE__ */ jsxs75(Fragment29, { children: [
42197
42522
  /* @__PURE__ */ jsx93(PreviewToolbarControls, { summarizationDesigner }),
42198
42523
  /* @__PURE__ */ jsx93(PresentationModeControl, {}),
42199
42524
  allowPrint && /* @__PURE__ */ jsx93(PrintModeControl, {})
@@ -42253,6 +42578,7 @@ function EditorShellInner({
42253
42578
  thinMargins,
42254
42579
  writeCanvasSettings,
42255
42580
  readOnly,
42581
+ findShortcut,
42256
42582
  imageSrc,
42257
42583
  imageAlt,
42258
42584
  imageMode,
@@ -42630,7 +42956,9 @@ function EditorShellInner({
42630
42956
  showPlayTab,
42631
42957
  showFormattingControls,
42632
42958
  showInsertControls,
42633
- hostMode
42959
+ hostMode,
42960
+ readOnly,
42961
+ findShortcut
42634
42962
  }
42635
42963
  ) }),
42636
42964
  /* @__PURE__ */ jsxs75(