@kopexa/tiptap 17.11.4 → 17.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +1944 -1921
  2. package/dist/index.mjs +1992 -1969
  3. package/package.json +25 -25
package/dist/index.js CHANGED
@@ -4441,481 +4441,22 @@ function useUiEditorState(editor) {
4441
4441
  }
4442
4442
 
4443
4443
  // src/ui/bubble-menu/index.tsx
4444
- var import_toolbar3 = require("@kopexa/toolbar");
4444
+ var import_toolbar5 = require("@kopexa/toolbar");
4445
4445
  var import_menus = require("@tiptap/react/menus");
4446
4446
 
4447
- // src/ui/link-popover/link-popover.tsx
4448
- var import_button8 = require("@kopexa/button");
4447
+ // src/ui/color-highlight-button/color-highlight-button.tsx
4449
4448
  var import_editor_utils6 = require("@kopexa/editor-utils");
4450
- var import_icons9 = require("@kopexa/icons");
4451
- var import_input3 = require("@kopexa/input");
4452
- var import_popover = require("@kopexa/popover");
4449
+ var import_theme7 = require("@kopexa/theme");
4453
4450
  var import_toolbar = require("@kopexa/toolbar");
4454
4451
  var import_react37 = require("react");
4455
4452
 
4456
- // src/ui/link-popover/use-link-popover.ts
4453
+ // src/ui/color-highlight-button/use-color-highlight.ts
4457
4454
  var import_editor_utils5 = require("@kopexa/editor-utils");
4458
4455
  var import_icons8 = require("@kopexa/icons");
4456
+ var import_use_is_mobile = require("@kopexa/use-is-mobile");
4459
4457
  var React7 = __toESM(require("react"));
4460
-
4461
- // src/utils/index.ts
4462
- var MAX_FILE_SIZE = 5 * 1024 * 1024;
4463
- var handleImageUpload = async (file, onProgress, abortSignal) => {
4464
- if (!file) {
4465
- throw new Error("No file provided");
4466
- }
4467
- if (file.size > MAX_FILE_SIZE) {
4468
- throw new Error(
4469
- `File size exceeds maximum allowed (${MAX_FILE_SIZE / (1024 * 1024)}MB)`
4470
- );
4471
- }
4472
- for (let progress = 0; progress <= 100; progress += 10) {
4473
- if (abortSignal == null ? void 0 : abortSignal.aborted) {
4474
- throw new Error("Upload cancelled");
4475
- }
4476
- await new Promise((resolve) => setTimeout(resolve, 500));
4477
- onProgress == null ? void 0 : onProgress({ progress });
4478
- }
4479
- return "/images/placeholder-image.png";
4480
- };
4481
- var convertFileToBase64 = (file, abortSignal) => {
4482
- if (!file) {
4483
- return Promise.reject(new Error("No file provided"));
4484
- }
4485
- return new Promise((resolve, reject) => {
4486
- const reader = new FileReader();
4487
- const abortHandler = () => {
4488
- reader.abort();
4489
- reject(new Error("Upload cancelled"));
4490
- };
4491
- if (abortSignal) {
4492
- abortSignal.addEventListener("abort", abortHandler);
4493
- }
4494
- reader.onloadend = () => {
4495
- if (abortSignal) {
4496
- abortSignal.removeEventListener("abort", abortHandler);
4497
- }
4498
- if (typeof reader.result === "string") {
4499
- resolve(reader.result);
4500
- } else {
4501
- reject(new Error("Failed to convert File to base64"));
4502
- }
4503
- };
4504
- reader.onerror = (error) => reject(new Error(`File reading error: ${error}`));
4505
- reader.readAsDataURL(file);
4506
- });
4507
- };
4508
- var ATTR_WHITESPACE = (
4509
- // eslint-disable-next-line no-control-regex
4510
- // biome-ignore lint/suspicious/noControlCharactersInRegex: we can do this yay
4511
- /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g
4512
- );
4513
- function isAllowedUri(uri, protocols) {
4514
- const allowedProtocols = [
4515
- "http",
4516
- "https",
4517
- "ftp",
4518
- "ftps",
4519
- "mailto",
4520
- "tel",
4521
- "callto",
4522
- "sms",
4523
- "cid",
4524
- "xmpp"
4525
- ];
4526
- if (protocols) {
4527
- for (const protocol of protocols) {
4528
- const nextProtocol = typeof protocol === "string" ? protocol : protocol.scheme;
4529
- if (nextProtocol) {
4530
- allowedProtocols.push(nextProtocol);
4531
- }
4532
- }
4533
- }
4534
- return !uri || uri.replace(ATTR_WHITESPACE, "").match(
4535
- new RegExp(
4536
- // eslint-disable-next-line no-useless-escape
4537
- `^(?:(?:${allowedProtocols.join("|")}):|[^a-z]|[a-z0-9+.-]+(?:[^a-z+.-:]|$))`,
4538
- "i"
4539
- )
4540
- );
4541
- }
4542
- function sanitizeUrl(inputUrl, baseUrl, protocols) {
4543
- try {
4544
- const url = new URL(inputUrl, baseUrl);
4545
- if (isAllowedUri(url.href, protocols)) {
4546
- return url.href;
4547
- }
4548
- } catch {
4549
- }
4550
- return "#";
4551
- }
4552
-
4553
- // src/ui/link-popover/use-link-popover.ts
4554
- function canSetLink(editor) {
4555
- if (!editor || !editor.isEditable) return false;
4556
- return editor.can().setMark("link");
4557
- }
4558
- function isLinkActive(editor) {
4559
- if (!editor || !editor.isEditable) return false;
4560
- return editor.isActive("link");
4561
- }
4562
- function shouldShowLinkButton(props) {
4563
- const { editor, hideWhenUnavailable } = props;
4564
- const linkInSchema = (0, import_editor_utils5.isMarkInSchema)("link", editor);
4565
- if (!linkInSchema || !editor) {
4566
- return false;
4567
- }
4568
- if (hideWhenUnavailable && !editor.isActive("code")) {
4569
- return canSetLink(editor);
4570
- }
4571
- return true;
4572
- }
4573
- function useLinkHandler(props) {
4574
- const { editor, onSetLink } = props;
4575
- const [url, setUrl] = React7.useState(null);
4576
- React7.useEffect(() => {
4577
- if (!editor) return;
4578
- const { href } = editor.getAttributes("link");
4579
- if (isLinkActive(editor) && url === null) {
4580
- setUrl(href || "");
4581
- }
4582
- }, [editor, url]);
4583
- React7.useEffect(() => {
4584
- if (!editor) return;
4585
- const updateLinkState = () => {
4586
- const { href } = editor.getAttributes("link");
4587
- setUrl(href || "");
4588
- };
4589
- editor.on("selectionUpdate", updateLinkState);
4590
- return () => {
4591
- editor.off("selectionUpdate", updateLinkState);
4592
- };
4593
- }, [editor]);
4594
- const setLink = React7.useCallback(() => {
4595
- if (!url || !editor) return;
4596
- const { selection } = editor.state;
4597
- const isEmpty = selection.empty;
4598
- let chain = editor.chain().focus();
4599
- chain = chain.extendMarkRange("link").setLink({ href: url });
4600
- if (isEmpty) {
4601
- chain = chain.insertContent({ type: "text", text: url });
4602
- }
4603
- chain.run();
4604
- setUrl(null);
4605
- onSetLink == null ? void 0 : onSetLink();
4606
- }, [editor, onSetLink, url]);
4607
- const removeLink = React7.useCallback(() => {
4608
- if (!editor) return;
4609
- editor.chain().focus().extendMarkRange("link").unsetLink().setMeta("preventAutolink", true).run();
4610
- setUrl("");
4611
- }, [editor]);
4612
- const openLink = React7.useCallback(
4613
- (target = "_blank", features = "noopener,noreferrer") => {
4614
- if (!url) return;
4615
- const safeUrl = sanitizeUrl(url, window.location.href);
4616
- if (safeUrl !== "#") {
4617
- window.open(safeUrl, target, features);
4618
- }
4619
- },
4620
- [url]
4621
- );
4622
- return {
4623
- url: url || "",
4624
- setUrl,
4625
- setLink,
4626
- removeLink,
4627
- openLink
4628
- };
4629
- }
4630
- function useLinkState(props) {
4631
- const { editor, hideWhenUnavailable = false } = props;
4632
- const canSet = canSetLink(editor);
4633
- const isActive = isLinkActive(editor);
4634
- const [isVisible, setIsVisible] = React7.useState(false);
4635
- React7.useEffect(() => {
4636
- if (!editor) return;
4637
- const handleSelectionUpdate = () => {
4638
- setIsVisible(
4639
- shouldShowLinkButton({
4640
- editor,
4641
- hideWhenUnavailable
4642
- })
4643
- );
4644
- };
4645
- handleSelectionUpdate();
4646
- editor.on("selectionUpdate", handleSelectionUpdate);
4647
- return () => {
4648
- editor.off("selectionUpdate", handleSelectionUpdate);
4649
- };
4650
- }, [editor, hideWhenUnavailable]);
4651
- return {
4652
- isVisible,
4653
- canSet,
4654
- isActive
4655
- };
4656
- }
4657
- function useLinkPopover(config) {
4658
- const {
4659
- editor: providedEditor,
4660
- hideWhenUnavailable = false,
4661
- onSetLink
4662
- } = config || {};
4663
- const { editor } = (0, import_editor_utils5.useTiptapEditor)(providedEditor);
4664
- const { isVisible, canSet, isActive } = useLinkState({
4665
- editor,
4666
- hideWhenUnavailable
4667
- });
4668
- const linkHandler = useLinkHandler({
4669
- editor,
4670
- onSetLink
4671
- });
4672
- return {
4673
- isVisible,
4674
- canSet,
4675
- isActive,
4676
- label: "Link",
4677
- Icon: import_icons8.LinkIcon,
4678
- ...linkHandler
4679
- };
4680
- }
4681
-
4682
- // src/ui/link-popover/link-popover.tsx
4683
- var import_jsx_runtime16 = require("react/jsx-runtime");
4684
- var LinkButton = ({ className, children, ...props }) => {
4685
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4686
- import_toolbar.ToolbarButton,
4687
- {
4688
- type: "button",
4689
- className,
4690
- variant: "ghost",
4691
- color: "default",
4692
- tabIndex: -1,
4693
- "aria-label": "Link",
4694
- title: "Link",
4695
- isIconOnly: !children,
4696
- ...props,
4697
- children: children || /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_icons9.LinkIcon, {})
4698
- }
4699
- );
4700
- };
4701
- var LinkMain = ({
4702
- url,
4703
- setUrl,
4704
- setLink,
4705
- removeLink,
4706
- openLink,
4707
- isActive,
4708
- onSave
4709
- }) => {
4710
- const [isEditing, setIsEditing] = (0, import_react37.useState)(!isActive || !url);
4711
- (0, import_react37.useEffect)(() => {
4712
- setIsEditing(!isActive || !url);
4713
- }, [isActive, url]);
4714
- const handleKeyDown = (event) => {
4715
- if (event.key === "Enter") {
4716
- event.preventDefault();
4717
- setLink();
4718
- setIsEditing(false);
4719
- onSave == null ? void 0 : onSave();
4720
- } else if (event.key === "Escape") {
4721
- event.preventDefault();
4722
- setIsEditing(false);
4723
- }
4724
- };
4725
- const handleSave = () => {
4726
- setLink();
4727
- setIsEditing(false);
4728
- onSave == null ? void 0 : onSave();
4729
- };
4730
- const handleEdit = () => {
4731
- setIsEditing(true);
4732
- };
4733
- if (isEditing) {
4734
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-1 min-w-[280px]", children: [
4735
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4736
- import_input3.Input,
4737
- {
4738
- type: "url",
4739
- placeholder: "Enter URL...",
4740
- value: url,
4741
- onChange: (e) => setUrl(e.target.value),
4742
- onKeyDown: handleKeyDown,
4743
- autoComplete: "off",
4744
- autoCorrect: "off",
4745
- autoCapitalize: "off",
4746
- className: "flex-1 h-8 text-sm",
4747
- autoFocus: true
4748
- }
4749
- ),
4750
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4751
- import_button8.IconButton,
4752
- {
4753
- type: "button",
4754
- size: "sm",
4755
- variant: "ghost",
4756
- onClick: handleSave,
4757
- "aria-label": "Save link",
4758
- disabled: !url,
4759
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_icons9.CheckIcon, { className: "size-4" })
4760
- }
4761
- )
4762
- ] });
4763
- }
4764
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-1 min-w-[280px]", children: [
4765
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4766
- "a",
4767
- {
4768
- href: url,
4769
- target: "_blank",
4770
- rel: "noopener noreferrer",
4771
- className: "flex-1 text-sm text-primary truncate max-w-[200px] hover:underline px-2",
4772
- onClick: (e) => {
4773
- e.preventDefault();
4774
- openLink();
4775
- },
4776
- children: url
4777
- }
4778
- ),
4779
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-0.5 border-l pl-1 ml-1", children: [
4780
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4781
- import_button8.IconButton,
4782
- {
4783
- type: "button",
4784
- size: "sm",
4785
- variant: "ghost",
4786
- onClick: openLink,
4787
- "aria-label": "Open link in new tab",
4788
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_icons9.ExternalLinkIcon, { className: "size-4" })
4789
- }
4790
- ),
4791
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4792
- import_button8.IconButton,
4793
- {
4794
- type: "button",
4795
- size: "sm",
4796
- variant: "ghost",
4797
- onClick: handleEdit,
4798
- "aria-label": "Edit link",
4799
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_icons9.EditIcon, { className: "size-4" })
4800
- }
4801
- ),
4802
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4803
- import_button8.IconButton,
4804
- {
4805
- type: "button",
4806
- size: "sm",
4807
- variant: "ghost",
4808
- onClick: removeLink,
4809
- "aria-label": "Remove link",
4810
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_icons9.TrashIcon, { className: "size-4" })
4811
- }
4812
- )
4813
- ] })
4814
- ] });
4815
- };
4816
- function LinkPopover({
4817
- editor: providedEditor,
4818
- hideWhenUnavailable = false,
4819
- onSetLink,
4820
- onOpenChange,
4821
- autoOpenOnLinkActive = true,
4822
- onClick,
4823
- children,
4824
- ...buttonProps
4825
- }) {
4826
- const { editor } = (0, import_editor_utils6.useTiptapEditor)(providedEditor);
4827
- const [isOpen, setIsOpen] = (0, import_react37.useState)(false);
4828
- const {
4829
- isVisible,
4830
- canSet,
4831
- isActive,
4832
- url,
4833
- setUrl,
4834
- setLink,
4835
- removeLink,
4836
- openLink,
4837
- label
4838
- } = useLinkPopover({
4839
- editor,
4840
- hideWhenUnavailable,
4841
- onSetLink
4842
- });
4843
- const handleOnOpenChange = (0, import_react37.useCallback)(
4844
- (nextIsOpen) => {
4845
- setIsOpen(nextIsOpen);
4846
- onOpenChange == null ? void 0 : onOpenChange(nextIsOpen);
4847
- },
4848
- [onOpenChange]
4849
- );
4850
- const handleSetLink = (0, import_react37.useCallback)(() => {
4851
- setLink();
4852
- setIsOpen(false);
4853
- }, [setLink]);
4854
- const handleClick = (0, import_react37.useCallback)(
4855
- (event) => {
4856
- onClick == null ? void 0 : onClick(event);
4857
- if (event.defaultPrevented) return;
4858
- setIsOpen(!isOpen);
4859
- },
4860
- [onClick, isOpen]
4861
- );
4862
- (0, import_react37.useEffect)(() => {
4863
- if (autoOpenOnLinkActive && isActive) {
4864
- setIsOpen(true);
4865
- }
4866
- }, [autoOpenOnLinkActive, isActive]);
4867
- if (!isVisible) {
4868
- return null;
4869
- }
4870
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
4871
- import_popover.Popover.Root,
4872
- {
4873
- open: isOpen,
4874
- onOpenChange: handleOnOpenChange,
4875
- spacing: "dense",
4876
- children: [
4877
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4878
- import_popover.Popover.Trigger,
4879
- {
4880
- render: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4881
- LinkButton,
4882
- {
4883
- "data-disabled": !canSet,
4884
- disabled: !canSet,
4885
- "data-active-state": isActive ? "on" : "off",
4886
- "aria-label": label,
4887
- "aria-pressed": isActive,
4888
- onClick: handleClick,
4889
- ...buttonProps
4890
- }
4891
- )
4892
- }
4893
- ),
4894
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_popover.Popover.Content, { children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4895
- LinkMain,
4896
- {
4897
- url,
4898
- setUrl,
4899
- setLink: handleSetLink,
4900
- removeLink,
4901
- openLink,
4902
- isActive,
4903
- onSave: () => setIsOpen(false)
4904
- }
4905
- ) })
4906
- ]
4907
- }
4908
- );
4909
- }
4910
- LinkButton.displayName = "LinkButton";
4911
-
4912
- // src/ui/mark-button/index.tsx
4913
- var import_editor_utils7 = require("@kopexa/editor-utils");
4914
- var import_icons10 = require("@kopexa/icons");
4915
- var import_toolbar2 = require("@kopexa/toolbar");
4916
- var import_react38 = require("@tiptap/react");
4917
- var import_react39 = require("react");
4918
- var import_react_intl16 = require("react-intl");
4458
+ var import_react_hotkeys_hook = require("react-hotkeys-hook");
4459
+ var import_react_intl16 = require("react-intl");
4919
4460
 
4920
4461
  // src/ui/messages.ts
4921
4462
  var import_react_intl15 = require("react-intl");
@@ -5333,1588 +4874,2070 @@ var messages7 = (0, import_react_intl15.defineMessages)({
5333
4874
  }
5334
4875
  });
5335
4876
 
5336
- // src/ui/mark-button/index.tsx
5337
- var import_jsx_runtime17 = require("react/jsx-runtime");
5338
- var markIcons = {
5339
- bold: import_icons10.BoldIcon,
5340
- italic: import_icons10.ItalicIcon,
5341
- underline: import_icons10.UnderlineIcon,
5342
- strike: import_icons10.StrikeIcon,
5343
- code: import_icons10.CodeIcon,
5344
- superscript: import_icons10.SuperscriptIcon,
5345
- subscript: import_icons10.SubscriptIcon
5346
- };
5347
- var markShortcutKeys = {
5348
- bold: "mod+b",
5349
- italic: "mod+i",
5350
- underline: "mod+u",
5351
- strike: "mod+shift+s",
5352
- code: "mod+e",
5353
- superscript: "mod+.",
5354
- subscript: "mod+,"
5355
- };
5356
- function canToggleMark(editor, type) {
4877
+ // src/ui/color-highlight-button/use-color-highlight.ts
4878
+ var COLOR_HIGHLIGHT_SHORTCUT_KEY = "mod+shift+h";
4879
+ var HIGHLIGHT_COLORS = [
4880
+ {
4881
+ label: "Default background",
4882
+ value: "var(--tt-bg-color)",
4883
+ border: "var(--tt-bg-color-contrast)"
4884
+ },
4885
+ {
4886
+ label: "Gray background",
4887
+ value: "var(--tt-color-highlight-gray)",
4888
+ border: "var(--tt-color-highlight-gray-contrast)"
4889
+ },
4890
+ {
4891
+ label: "Brown background",
4892
+ value: "var(--tt-color-highlight-brown)",
4893
+ border: "var(--tt-color-highlight-brown-contrast)"
4894
+ },
4895
+ {
4896
+ label: "Orange background",
4897
+ value: "var(--tt-color-highlight-orange)",
4898
+ border: "var(--tt-color-highlight-orange-contrast)"
4899
+ },
4900
+ {
4901
+ label: "Yellow background",
4902
+ value: "var(--tt-color-highlight-yellow)",
4903
+ border: "var(--tt-color-highlight-yellow-contrast)"
4904
+ },
4905
+ {
4906
+ label: "Green background",
4907
+ value: "var(--tt-color-highlight-green)",
4908
+ border: "var(--tt-color-highlight-green-contrast)"
4909
+ },
4910
+ {
4911
+ label: "Blue background",
4912
+ value: "var(--tt-color-highlight-blue)",
4913
+ border: "var(--tt-color-highlight-blue-contrast)"
4914
+ },
4915
+ {
4916
+ label: "Purple background",
4917
+ value: "var(--tt-color-highlight-purple)",
4918
+ border: "var(--tt-color-highlight-purple-contrast)"
4919
+ },
4920
+ {
4921
+ label: "Pink background",
4922
+ value: "var(--tt-color-highlight-pink)",
4923
+ border: "var(--tt-color-highlight-pink-contrast)"
4924
+ },
4925
+ {
4926
+ label: "Red background",
4927
+ value: "var(--tt-color-highlight-red)",
4928
+ border: "var(--tt-color-highlight-red-contrast)"
4929
+ }
4930
+ ];
4931
+ function pickHighlightColorsByValue(values) {
4932
+ const colorMap = new Map(
4933
+ HIGHLIGHT_COLORS.map((color) => [color.value, color])
4934
+ );
4935
+ return values.map((value) => colorMap.get(value)).filter((color) => !!color);
4936
+ }
4937
+ function canColorHighlight(editor) {
5357
4938
  if (!editor || !editor.isEditable) return false;
5358
- if (!(0, import_editor_utils7.isMarkInSchema)(type, editor) || (0, import_editor_utils7.isNodeTypeSelected)(editor, ["image"]))
4939
+ if (!(0, import_editor_utils5.isMarkInSchema)("highlight", editor) || (0, import_editor_utils5.isNodeTypeSelected)(editor, ["image"]))
5359
4940
  return false;
5360
- return editor.can().toggleMark(type);
5361
- }
5362
- function isMarkActive(editor, type) {
5363
- if (!editor) return false;
5364
- return editor.isActive(type);
4941
+ return editor.can().setMark("highlight");
5365
4942
  }
5366
- function toggleMark(editor, type) {
5367
- if (!editor) return;
5368
- editor.chain().focus().toggleMark(type).run();
4943
+ function isColorHighlightActive(editor, highlightColor) {
4944
+ if (!editor || !editor.isEditable) return false;
4945
+ return highlightColor ? editor.isActive("highlight", { color: highlightColor }) : editor.isActive("highlight");
5369
4946
  }
5370
- function isMarkButtonDisabled(editor, type, userDisabled = false) {
5371
- if (!editor) return true;
5372
- if (userDisabled) return true;
5373
- if (editor.isActive("codeBlock")) return true;
5374
- if (!canToggleMark(editor, type)) return true;
5375
- return false;
4947
+ function removeHighlight(editor) {
4948
+ if (!editor || !editor.isEditable) return false;
4949
+ if (!canColorHighlight(editor)) return false;
4950
+ return editor.chain().focus().unsetMark("highlight").run();
5376
4951
  }
5377
- function shouldShowMarkButton(params) {
5378
- const { editor, type, hideWhenUnavailable, markInSchema } = params;
5379
- if (!markInSchema || !editor) {
5380
- return false;
5381
- }
5382
- if (hideWhenUnavailable) {
5383
- if ((0, import_react38.isNodeSelection)(editor.state.selection) || !canToggleMark(editor, type)) {
5384
- return false;
5385
- }
4952
+ function shouldShowButton(props) {
4953
+ const { editor, hideWhenUnavailable } = props;
4954
+ if (!editor || !editor.isEditable) return false;
4955
+ if (!(0, import_editor_utils5.isMarkInSchema)("highlight", editor)) return false;
4956
+ if (hideWhenUnavailable && !editor.isActive("code")) {
4957
+ return canColorHighlight(editor);
5386
4958
  }
5387
4959
  return true;
5388
4960
  }
5389
- var markMessages = {
5390
- bold: messages7.bold,
5391
- italic: messages7.italic,
5392
- underline: messages7.underline,
5393
- strike: messages7.strikethrough,
5394
- code: messages7.code,
5395
- superscript: messages7.superscript,
5396
- subscript: messages7.subscript
5397
- };
5398
- function getTranslatedMarkName(type, intl) {
5399
- return intl.formatMessage(markMessages[type]);
5400
- }
5401
- function useMarkState(editor, type, disabled = false) {
4961
+ function useColorHighlight(config) {
4962
+ const {
4963
+ editor: providedEditor,
4964
+ label,
4965
+ highlightColor,
4966
+ hideWhenUnavailable = false,
4967
+ onApplied
4968
+ } = config;
5402
4969
  const intl = (0, import_react_intl16.useIntl)();
5403
- const markInSchema = (0, import_editor_utils7.isMarkInSchema)(type, editor);
5404
- const isDisabled = isMarkButtonDisabled(editor, type, disabled);
5405
- const isActive = isMarkActive(editor, type);
5406
- const Icon = markIcons[type];
5407
- const shortcutKey = markShortcutKeys[type];
5408
- const formattedName = getTranslatedMarkName(type, intl);
4970
+ const { editor } = (0, import_editor_utils5.useTiptapEditor)(providedEditor);
4971
+ const isMobile = (0, import_use_is_mobile.useIsMobile)();
4972
+ const [isVisible, setIsVisible] = React7.useState(true);
4973
+ const canColorHighlightState = canColorHighlight(editor);
4974
+ const isActive = isColorHighlightActive(editor, highlightColor);
4975
+ React7.useEffect(() => {
4976
+ if (!editor) return;
4977
+ const handleSelectionUpdate = () => {
4978
+ setIsVisible(shouldShowButton({ editor, hideWhenUnavailable }));
4979
+ };
4980
+ handleSelectionUpdate();
4981
+ editor.on("selectionUpdate", handleSelectionUpdate);
4982
+ return () => {
4983
+ editor.off("selectionUpdate", handleSelectionUpdate);
4984
+ };
4985
+ }, [editor, hideWhenUnavailable]);
4986
+ const handleColorHighlight = React7.useCallback(() => {
4987
+ if (!editor || !canColorHighlightState || !highlightColor || !label)
4988
+ return false;
4989
+ if (editor.state.storedMarks) {
4990
+ const highlightMarkType = editor.schema.marks.highlight;
4991
+ if (highlightMarkType) {
4992
+ editor.view.dispatch(
4993
+ editor.state.tr.removeStoredMark(highlightMarkType)
4994
+ );
4995
+ }
4996
+ }
4997
+ setTimeout(() => {
4998
+ const success = editor.chain().focus().toggleMark("highlight", { color: highlightColor }).run();
4999
+ if (success) {
5000
+ onApplied == null ? void 0 : onApplied({ color: highlightColor, label });
5001
+ }
5002
+ return success;
5003
+ }, 0);
5004
+ }, [canColorHighlightState, highlightColor, editor, label, onApplied]);
5005
+ const handleRemoveHighlight = React7.useCallback(() => {
5006
+ const success = removeHighlight(editor);
5007
+ if (success) {
5008
+ onApplied == null ? void 0 : onApplied({ color: "", label: "Remove highlight" });
5009
+ }
5010
+ return success;
5011
+ }, [editor, onApplied]);
5012
+ (0, import_react_hotkeys_hook.useHotkeys)(
5013
+ COLOR_HIGHLIGHT_SHORTCUT_KEY,
5014
+ (event) => {
5015
+ event.preventDefault();
5016
+ handleColorHighlight();
5017
+ },
5018
+ {
5019
+ enabled: isVisible && canColorHighlightState,
5020
+ enableOnContentEditable: !isMobile,
5021
+ enableOnFormTags: true
5022
+ }
5023
+ );
5409
5024
  return {
5410
- markInSchema,
5411
- isDisabled,
5025
+ isVisible,
5412
5026
  isActive,
5413
- Icon,
5414
- shortcutKey,
5415
- formattedName
5027
+ handleColorHighlight,
5028
+ handleRemoveHighlight,
5029
+ canColorHighlight: canColorHighlightState,
5030
+ label: label || intl.formatMessage(messages7.highlight_color),
5031
+ shortcutKeys: COLOR_HIGHLIGHT_SHORTCUT_KEY,
5032
+ Icon: import_icons8.HighlighterIcon
5416
5033
  };
5417
5034
  }
5418
- var MarkButton = ({
5035
+
5036
+ // src/ui/color-highlight-button/color-highlight-button.tsx
5037
+ var import_jsx_runtime16 = require("react/jsx-runtime");
5038
+ var ColorHighlightButton = ({
5419
5039
  editor: providedEditor,
5420
- type,
5040
+ highlightColor,
5421
5041
  text,
5422
5042
  hideWhenUnavailable = false,
5423
- className = "",
5424
- disabled,
5043
+ onApplied,
5044
+ showShortcut = false,
5425
5045
  onClick,
5426
5046
  children,
5047
+ style,
5048
+ className,
5427
5049
  ...buttonProps
5428
5050
  }) => {
5429
- const { editor } = (0, import_editor_utils7.useTiptapEditor)(providedEditor);
5051
+ const { editor } = (0, import_editor_utils6.useTiptapEditor)(providedEditor);
5430
5052
  const {
5431
- markInSchema,
5432
- isDisabled,
5053
+ isVisible,
5054
+ canColorHighlight: canColorHighlight2,
5433
5055
  isActive,
5434
- Icon,
5435
- shortcutKey,
5436
- formattedName
5437
- } = useMarkState(editor, type, disabled);
5438
- const handleClick = (0, import_react39.useCallback)(
5439
- (e) => {
5440
- onClick == null ? void 0 : onClick(e);
5441
- if (!e.defaultPrevented && !isDisabled && editor) {
5442
- toggleMark(editor, type);
5443
- }
5056
+ handleColorHighlight,
5057
+ label,
5058
+ shortcutKeys
5059
+ } = useColorHighlight({
5060
+ editor,
5061
+ highlightColor,
5062
+ label: text || `Toggle highlight (${highlightColor})`,
5063
+ hideWhenUnavailable,
5064
+ onApplied
5065
+ });
5066
+ const handleClick = (0, import_react37.useCallback)(
5067
+ (event) => {
5068
+ onClick == null ? void 0 : onClick(event);
5069
+ if (event.defaultPrevented) return;
5070
+ handleColorHighlight();
5444
5071
  },
5445
- [onClick, isDisabled, editor, type]
5072
+ [handleColorHighlight, onClick]
5446
5073
  );
5447
- const show = (0, import_react39.useMemo)(() => {
5448
- return shouldShowMarkButton({
5449
- editor,
5450
- type,
5451
- hideWhenUnavailable,
5452
- markInSchema
5453
- });
5454
- }, [editor, type, hideWhenUnavailable, markInSchema]);
5455
- if (!show || !editor || !editor.isEditable) {
5074
+ const buttonStyle = (0, import_react37.useMemo)(
5075
+ () => ({
5076
+ ...style,
5077
+ "--highlight-color": highlightColor
5078
+ }),
5079
+ [highlightColor, style]
5080
+ );
5081
+ if (!isVisible) {
5456
5082
  return null;
5457
5083
  }
5458
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5459
- import_toolbar2.ToolbarButton,
5084
+ const styles = (0, import_theme7.colorHighlightButton)();
5085
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
5086
+ import_toolbar.ToolbarButton,
5460
5087
  {
5461
5088
  type: "button",
5462
- className: className.trim(),
5463
- disabled: isDisabled,
5089
+ disabled: !canColorHighlight2,
5090
+ "data-disabled": !canColorHighlight2,
5464
5091
  variant: "ghost",
5465
5092
  color: "default",
5466
5093
  "data-active-state": isActive ? "on" : "off",
5467
- "data-disabled": isDisabled,
5468
5094
  tabIndex: -1,
5469
- "aria-label": formattedName,
5095
+ "aria-label": label,
5096
+ shortcutKeys,
5470
5097
  "aria-pressed": isActive,
5471
- title: formattedName,
5472
- shortcutKeys: shortcutKey,
5473
5098
  onClick: handleClick,
5099
+ style: buttonStyle,
5100
+ className: styles.button({ className }),
5474
5101
  isIconOnly: true,
5475
5102
  ...buttonProps,
5476
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Icon, {})
5103
+ children: [
5104
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5105
+ "span",
5106
+ {
5107
+ "data-active-state": isActive ? "on" : "off",
5108
+ className: styles.mark()
5109
+ }
5110
+ ),
5111
+ children || /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
5112
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5113
+ "span",
5114
+ {
5115
+ style: { "--highlight-color": highlightColor }
5116
+ }
5117
+ ),
5118
+ text
5119
+ ] })
5120
+ ]
5477
5121
  }
5478
5122
  );
5479
5123
  };
5480
5124
 
5481
- // src/ui/bubble-menu/index.tsx
5482
- var import_jsx_runtime18 = require("react/jsx-runtime");
5483
- function BubbleMenu({ editor }) {
5484
- if (!editor) {
5485
- return null;
5125
+ // src/ui/color-highlight-popover/color-highlight-popover.tsx
5126
+ var import_button8 = require("@kopexa/button");
5127
+ var import_editor_utils7 = require("@kopexa/editor-utils");
5128
+ var import_icons9 = require("@kopexa/icons");
5129
+ var import_popover = require("@kopexa/popover");
5130
+ var import_toolbar2 = require("@kopexa/toolbar");
5131
+ var import_react38 = require("react");
5132
+ var import_jsx_runtime17 = require("react/jsx-runtime");
5133
+ var ColorHighlightPopoverButton = ({
5134
+ className,
5135
+ children,
5136
+ ...props
5137
+ }) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5138
+ import_button8.IconButton,
5139
+ {
5140
+ type: "button",
5141
+ className,
5142
+ variant: "ghost",
5143
+ color: "default",
5144
+ tabIndex: -1,
5145
+ "aria-label": "Highlight text",
5146
+ tooltip: "Highlight",
5147
+ isIconOnly: !children,
5148
+ ...props,
5149
+ children: children != null ? children : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_icons9.HighlighterIcon, {})
5486
5150
  }
5487
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5488
- import_menus.BubbleMenu,
5489
- {
5490
- editor,
5491
- shouldShow: ({ editor: e, state, view }) => {
5492
- const { selection } = state;
5493
- const { empty } = selection;
5494
- if (!view.hasFocus()) return false;
5495
- if (empty) return false;
5496
- if (e.isActive("codeBlock")) return false;
5497
- if (e.isActive("link")) return false;
5498
- if (e.isActive("variable")) return false;
5499
- if (!e.isEditable) return false;
5500
- return true;
5501
- },
5502
- options: {
5503
- placement: "top",
5504
- offset: 8
5505
- },
5506
- className: "rounded-lg border bg-background shadow-md",
5507
- children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_toolbar3.Toolbar, { radius: "md", border: "none", className: "p-1", children: [
5508
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_toolbar3.ToolbarGroup, { children: [
5509
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MarkButton, { type: "bold" }),
5510
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MarkButton, { type: "italic" }),
5511
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MarkButton, { type: "strike" }),
5512
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MarkButton, { type: "code" })
5513
- ] }),
5514
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_toolbar3.ToolbarSeparator, {}),
5515
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_toolbar3.ToolbarGroup, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(LinkPopover, { autoOpenOnLinkActive: false }) })
5516
- ] })
5517
- }
5151
+ );
5152
+ function ColorHighlightPopoverContent({
5153
+ editor,
5154
+ colors = pickHighlightColorsByValue([
5155
+ "var(--tt-color-highlight-green)",
5156
+ "var(--tt-color-highlight-blue)",
5157
+ "var(--tt-color-highlight-red)",
5158
+ "var(--tt-color-highlight-purple)",
5159
+ "var(--tt-color-highlight-yellow)"
5160
+ ])
5161
+ }) {
5162
+ const { handleRemoveHighlight } = useColorHighlight({ editor });
5163
+ const containerRef = (0, import_react38.useRef)(null);
5164
+ const menuItems = (0, import_react38.useMemo)(
5165
+ () => [...colors, { label: "Remove highlight", value: "none" }],
5166
+ [colors]
5518
5167
  );
5168
+ const { selectedIndex } = useMenuNavigation({
5169
+ containerRef,
5170
+ items: menuItems,
5171
+ orientation: "both",
5172
+ onSelect: (item) => {
5173
+ if (!containerRef.current) return false;
5174
+ const highlightedElement = containerRef.current.querySelector(
5175
+ '[data-highlighted="true"]'
5176
+ );
5177
+ if (highlightedElement) highlightedElement.click();
5178
+ if (item.value === "none") handleRemoveHighlight();
5179
+ },
5180
+ autoSelectFirstItem: false
5181
+ });
5182
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { ref: containerRef, className: "flex gap-1 items-center", children: [
5183
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5184
+ "div",
5185
+ {
5186
+ className: "flex items-center gap-1 outline-none",
5187
+ "data-orientation": "horizontal",
5188
+ children: colors.map((color, index) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5189
+ ColorHighlightButton,
5190
+ {
5191
+ editor,
5192
+ highlightColor: color.value,
5193
+ "aria-label": `${color.label} highlight color`,
5194
+ tabIndex: index === selectedIndex ? 0 : -1,
5195
+ "data-highlighted": selectedIndex === index
5196
+ },
5197
+ color.value
5198
+ ))
5199
+ }
5200
+ ),
5201
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_toolbar2.ToolbarSeparator, { orientation: "vertical" }),
5202
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "tiptap-button-group", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5203
+ import_button8.IconButton,
5204
+ {
5205
+ onClick: handleRemoveHighlight,
5206
+ "aria-label": "Remove highlight",
5207
+ tabIndex: selectedIndex === colors.length ? 0 : -1,
5208
+ type: "button",
5209
+ role: "menuitem",
5210
+ variant: "ghost",
5211
+ color: "default",
5212
+ "data-highlighted": selectedIndex === colors.length,
5213
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_icons9.BanIcon, {})
5214
+ }
5215
+ ) })
5216
+ ] });
5217
+ }
5218
+ function ColorHighlightPopover({
5219
+ editor: providedEditor,
5220
+ colors = pickHighlightColorsByValue([
5221
+ "var(--tt-color-highlight-green)",
5222
+ "var(--tt-color-highlight-blue)",
5223
+ "var(--tt-color-highlight-red)",
5224
+ "var(--tt-color-highlight-purple)",
5225
+ "var(--tt-color-highlight-yellow)"
5226
+ ]),
5227
+ hideWhenUnavailable = false,
5228
+ onApplied,
5229
+ ...props
5230
+ }) {
5231
+ const { editor } = (0, import_editor_utils7.useTiptapEditor)(providedEditor);
5232
+ const [isOpen, setIsOpen] = (0, import_react38.useState)(false);
5233
+ const { isVisible, canColorHighlight: canColorHighlight2, isActive, label } = useColorHighlight({
5234
+ editor,
5235
+ hideWhenUnavailable,
5236
+ onApplied
5237
+ });
5238
+ if (!isVisible) return null;
5239
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_popover.Popover.Root, { open: isOpen, onOpenChange: setIsOpen, spacing: "dense", children: [
5240
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5241
+ import_popover.Popover.Trigger,
5242
+ {
5243
+ render: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5244
+ ColorHighlightPopoverButton,
5245
+ {
5246
+ disabled: !canColorHighlight2,
5247
+ "data-disabled": !canColorHighlight2,
5248
+ "data-active-state": isActive ? "on" : "off",
5249
+ "aria-pressed": isActive,
5250
+ "aria-label": label,
5251
+ title: label,
5252
+ ...props
5253
+ }
5254
+ )
5255
+ }
5256
+ ),
5257
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_popover.Popover.Content, { "aria-label": "Highlight colors", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ColorHighlightPopoverContent, { editor, colors }) })
5258
+ ] });
5519
5259
  }
5520
5260
 
5521
- // src/ui/copy-anchor-link-button/use-scroll-to-hash.ts
5522
- var import_editor_utils8 = require("@kopexa/editor-utils");
5523
- var React9 = __toESM(require("react"));
5261
+ // src/ui/link-popover/link-popover.tsx
5262
+ var import_button9 = require("@kopexa/button");
5263
+ var import_editor_utils9 = require("@kopexa/editor-utils");
5264
+ var import_icons11 = require("@kopexa/icons");
5265
+ var import_input3 = require("@kopexa/input");
5266
+ var import_popover2 = require("@kopexa/popover");
5267
+ var import_toolbar3 = require("@kopexa/toolbar");
5268
+ var import_react39 = require("react");
5524
5269
 
5525
- // src/hooks/use-floating-toolbar-visibility.ts
5526
- var import_state6 = require("@tiptap/pm/state");
5527
- var import_react40 = require("@tiptap/react");
5270
+ // src/ui/link-popover/use-link-popover.ts
5271
+ var import_editor_utils8 = require("@kopexa/editor-utils");
5272
+ var import_icons10 = require("@kopexa/icons");
5528
5273
  var React8 = __toESM(require("react"));
5529
- var HIDE_FLOATING_META = "hideFloatingToolbar";
5530
- var selectNodeAndHideFloating = (editor, pos) => {
5531
- if (!editor) return;
5532
- const { state, view } = editor;
5533
- view.dispatch(
5534
- state.tr.setSelection(import_state6.NodeSelection.create(state.doc, pos)).setMeta(HIDE_FLOATING_META, true)
5535
- );
5536
- };
5537
5274
 
5538
- // src/ui/copy-anchor-link-button/use-scroll-to-hash.ts
5539
- function useScrollToHash(config = {}) {
5540
- const {
5541
- editor: providedEditor,
5542
- onTargetFound = () => {
5543
- },
5544
- onTargetNotFound = () => {
5545
- }
5546
- } = config;
5547
- const { editor } = (0, import_editor_utils8.useTiptapEditor)(providedEditor);
5548
- const scrollToNode = React9.useCallback(
5549
- (id) => {
5550
- var _a, _b, _c;
5551
- if (!editor) return false;
5552
- const attributeName = (_c = (_b = (_a = (0, import_editor_utils8.getEditorExtension)(editor, "uniqueID")) == null ? void 0 : _a.options) == null ? void 0 : _b.attributeName) != null ? _c : "data-id";
5553
- let position = null;
5554
- editor.state.doc.descendants((node, pos) => {
5555
- var _a2;
5556
- if (((_a2 = node.attrs) == null ? void 0 : _a2[attributeName]) === id) {
5557
- position = pos;
5558
- return false;
5559
- }
5560
- return true;
5561
- });
5562
- if (position === null) return false;
5563
- selectNodeAndHideFloating(editor, position);
5564
- setTimeout(() => {
5565
- let dom = null;
5566
- if (typeof position === "number") {
5567
- dom = editor.view.nodeDOM(position);
5568
- }
5569
- if (dom) {
5570
- dom.scrollIntoView({ behavior: "smooth", block: "center" });
5571
- }
5572
- }, 0);
5573
- return true;
5574
- },
5575
- [editor]
5576
- );
5577
- const handleScroll = React9.useCallback(
5578
- (delay = 0) => {
5579
- var _a;
5580
- const hash = (_a = window.location.hash) == null ? void 0 : _a.substring(1);
5581
- if (!hash) return;
5582
- setTimeout(() => {
5583
- if (scrollToNode(hash)) {
5584
- onTargetFound(hash);
5585
- } else {
5586
- onTargetNotFound(hash);
5587
- }
5588
- }, delay);
5589
- },
5590
- [scrollToNode, onTargetFound, onTargetNotFound]
5591
- );
5592
- React9.useEffect(() => {
5593
- var _a, _b;
5594
- if (!editor) return;
5595
- const provider = (_b = (_a = editor.extensionManager.extensions.find(
5596
- (ext) => ext.name === "collaborationCaret"
5597
- )) == null ? void 0 : _a.options) == null ? void 0 : _b.provider;
5598
- if (provider == null ? void 0 : provider.on) {
5599
- const syncHandler = () => handleScroll(500);
5600
- provider.on("synced", syncHandler);
5601
- return () => {
5602
- var _a2;
5603
- (_a2 = provider.off) == null ? void 0 : _a2.call(provider, "synced", syncHandler);
5604
- };
5605
- } else {
5606
- handleScroll(500);
5275
+ // src/utils/index.ts
5276
+ var MAX_FILE_SIZE = 5 * 1024 * 1024;
5277
+ var handleImageUpload = async (file, onProgress, abortSignal) => {
5278
+ if (!file) {
5279
+ throw new Error("No file provided");
5280
+ }
5281
+ if (file.size > MAX_FILE_SIZE) {
5282
+ throw new Error(
5283
+ `File size exceeds maximum allowed (${MAX_FILE_SIZE / (1024 * 1024)}MB)`
5284
+ );
5285
+ }
5286
+ for (let progress = 0; progress <= 100; progress += 10) {
5287
+ if (abortSignal == null ? void 0 : abortSignal.aborted) {
5288
+ throw new Error("Upload cancelled");
5607
5289
  }
5608
- }, [editor, handleScroll]);
5609
- React9.useEffect(() => {
5610
- const immediateScroll = () => handleScroll();
5611
- const delayedScroll = () => handleScroll(500);
5612
- window.addEventListener("hashchange", immediateScroll);
5613
- window.addEventListener("pageshow", delayedScroll);
5614
- window.addEventListener("popstate", immediateScroll);
5615
- return () => {
5616
- window.removeEventListener("hashchange", immediateScroll);
5617
- window.removeEventListener("pageshow", delayedScroll);
5618
- window.removeEventListener("popstate", immediateScroll);
5290
+ await new Promise((resolve) => setTimeout(resolve, 500));
5291
+ onProgress == null ? void 0 : onProgress({ progress });
5292
+ }
5293
+ return "/images/placeholder-image.png";
5294
+ };
5295
+ var convertFileToBase64 = (file, abortSignal) => {
5296
+ if (!file) {
5297
+ return Promise.reject(new Error("No file provided"));
5298
+ }
5299
+ return new Promise((resolve, reject) => {
5300
+ const reader = new FileReader();
5301
+ const abortHandler = () => {
5302
+ reader.abort();
5303
+ reject(new Error("Upload cancelled"));
5619
5304
  };
5620
- }, [handleScroll]);
5621
- return { scrollToHash: scrollToNode };
5622
- }
5623
-
5624
- // src/ui/link-bubble/index.tsx
5625
- var import_button9 = require("@kopexa/button");
5626
- var import_icons11 = require("@kopexa/icons");
5627
- var import_input4 = require("@kopexa/input");
5628
- var import_menus2 = require("@tiptap/react/menus");
5629
- var import_react41 = require("react");
5630
- var import_react_intl17 = require("react-intl");
5631
- var import_jsx_runtime19 = require("react/jsx-runtime");
5632
- function LinkBubble({ editor }) {
5633
- const intl = (0, import_react_intl17.useIntl)();
5634
- const [isEditing, setIsEditing] = (0, import_react41.useState)(false);
5635
- const [url, setUrl] = (0, import_react41.useState)("");
5636
- const getCurrentUrl = (0, import_react41.useCallback)(() => {
5637
- if (!editor) return "";
5638
- const attrs = editor.getAttributes("link");
5639
- return attrs.href || "";
5640
- }, [editor]);
5641
- (0, import_react41.useEffect)(() => {
5642
- const isLinkActive2 = editor == null ? void 0 : editor.isActive("link");
5643
- if (isLinkActive2) {
5644
- setUrl(getCurrentUrl());
5645
- setIsEditing(false);
5646
- }
5647
- }, [editor, getCurrentUrl]);
5648
- const handleOpenLink = (0, import_react41.useCallback)(() => {
5649
- const href = getCurrentUrl();
5650
- if (href) {
5651
- window.open(href, "_blank", "noopener,noreferrer");
5305
+ if (abortSignal) {
5306
+ abortSignal.addEventListener("abort", abortHandler);
5652
5307
  }
5653
- }, [getCurrentUrl]);
5654
- const handleRemoveLink = (0, import_react41.useCallback)(() => {
5655
- editor == null ? void 0 : editor.chain().focus().unsetLink().run();
5656
- }, [editor]);
5657
- const handleEdit = (0, import_react41.useCallback)(() => {
5658
- setUrl(getCurrentUrl());
5659
- setIsEditing(true);
5660
- }, [getCurrentUrl]);
5661
- const handleSave = (0, import_react41.useCallback)(() => {
5662
- if (url) {
5663
- editor == null ? void 0 : editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
5664
- } else {
5665
- editor == null ? void 0 : editor.chain().focus().unsetLink().run();
5666
- }
5667
- setIsEditing(false);
5668
- }, [editor, url]);
5669
- const handleKeyDown = (0, import_react41.useCallback)(
5670
- (e) => {
5671
- if (e.key === "Enter") {
5672
- e.preventDefault();
5673
- handleSave();
5674
- } else if (e.key === "Escape") {
5675
- e.preventDefault();
5676
- setIsEditing(false);
5677
- setUrl(getCurrentUrl());
5308
+ reader.onloadend = () => {
5309
+ if (abortSignal) {
5310
+ abortSignal.removeEventListener("abort", abortHandler);
5311
+ }
5312
+ if (typeof reader.result === "string") {
5313
+ resolve(reader.result);
5314
+ } else {
5315
+ reject(new Error("Failed to convert File to base64"));
5316
+ }
5317
+ };
5318
+ reader.onerror = (error) => reject(new Error(`File reading error: ${error}`));
5319
+ reader.readAsDataURL(file);
5320
+ });
5321
+ };
5322
+ var ATTR_WHITESPACE = (
5323
+ // eslint-disable-next-line no-control-regex
5324
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: we can do this yay
5325
+ /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g
5326
+ );
5327
+ function isAllowedUri(uri, protocols) {
5328
+ const allowedProtocols = [
5329
+ "http",
5330
+ "https",
5331
+ "ftp",
5332
+ "ftps",
5333
+ "mailto",
5334
+ "tel",
5335
+ "callto",
5336
+ "sms",
5337
+ "cid",
5338
+ "xmpp"
5339
+ ];
5340
+ if (protocols) {
5341
+ for (const protocol of protocols) {
5342
+ const nextProtocol = typeof protocol === "string" ? protocol : protocol.scheme;
5343
+ if (nextProtocol) {
5344
+ allowedProtocols.push(nextProtocol);
5678
5345
  }
5679
- },
5680
- [handleSave, getCurrentUrl]
5681
- );
5682
- if (!editor) {
5683
- return null;
5684
- }
5685
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5686
- import_menus2.BubbleMenu,
5687
- {
5688
- editor,
5689
- pluginKey: "linkBubbleMenu",
5690
- shouldShow: ({ editor: e, view }) => {
5691
- if (!view.hasFocus()) return false;
5692
- return e.isActive("link") && e.isEditable;
5693
- },
5694
- options: {
5695
- placement: "bottom-start",
5696
- offset: 8
5697
- },
5698
- className: "rounded-lg border bg-background shadow-md",
5699
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "flex items-center gap-1 p-1.5 min-w-[280px]", children: isEditing ? /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
5700
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5701
- import_input4.Input,
5702
- {
5703
- type: "url",
5704
- value: url,
5705
- onChange: (e) => setUrl(e.target.value),
5706
- onKeyDown: handleKeyDown,
5707
- placeholder: intl.formatMessage(messages7.link_placeholder),
5708
- className: "flex-1 h-8 text-sm",
5709
- autoFocus: true
5710
- }
5711
- ),
5712
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5713
- import_button9.IconButton,
5714
- {
5715
- type: "button",
5716
- size: "sm",
5717
- variant: "ghost",
5718
- onClick: handleSave,
5719
- "aria-label": intl.formatMessage(messages7.link_save),
5720
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_icons11.EditIcon, { className: "size-4" })
5721
- }
5722
- )
5723
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
5724
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5725
- "a",
5726
- {
5727
- href: getCurrentUrl(),
5728
- target: "_blank",
5729
- rel: "noopener noreferrer",
5730
- className: "flex-1 text-sm text-primary truncate max-w-[200px] hover:underline px-2",
5731
- onClick: (e) => {
5732
- e.preventDefault();
5733
- handleOpenLink();
5734
- },
5735
- children: getCurrentUrl()
5736
- }
5737
- ),
5738
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-center gap-0.5 border-l pl-1 ml-1", children: [
5739
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5740
- import_button9.IconButton,
5741
- {
5742
- type: "button",
5743
- size: "sm",
5744
- variant: "ghost",
5745
- onClick: handleOpenLink,
5746
- "aria-label": intl.formatMessage(messages7.link_open),
5747
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_icons11.ExternalLinkIcon, { className: "size-4" })
5748
- }
5749
- ),
5750
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5751
- import_button9.IconButton,
5752
- {
5753
- type: "button",
5754
- size: "sm",
5755
- variant: "ghost",
5756
- onClick: handleEdit,
5757
- "aria-label": intl.formatMessage(messages7.link_edit),
5758
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_icons11.EditIcon, { className: "size-4" })
5759
- }
5760
- ),
5761
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5762
- import_button9.IconButton,
5763
- {
5764
- type: "button",
5765
- size: "sm",
5766
- variant: "ghost",
5767
- onClick: handleRemoveLink,
5768
- "aria-label": intl.formatMessage(messages7.link_remove),
5769
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_icons11.TrashIcon, { className: "size-4" })
5770
- }
5771
- )
5772
- ] })
5773
- ] }) })
5774
5346
  }
5347
+ }
5348
+ return !uri || uri.replace(ATTR_WHITESPACE, "").match(
5349
+ new RegExp(
5350
+ // eslint-disable-next-line no-useless-escape
5351
+ `^(?:(?:${allowedProtocols.join("|")}):|[^a-z]|[a-z0-9+.-]+(?:[^a-z+.-:]|$))`,
5352
+ "i"
5353
+ )
5775
5354
  );
5776
5355
  }
5777
-
5778
- // src/ui/slash-dropdown-menu/slash-dropdown-menu.tsx
5779
- var import_button10 = require("@kopexa/button");
5780
- var import_editor_utils11 = require("@kopexa/editor-utils");
5781
- var import_separator = require("@kopexa/separator");
5782
- var import_theme7 = require("@kopexa/theme");
5783
- var React11 = __toESM(require("react"));
5784
-
5785
- // src/ui/slash-dropdown-menu/use-slash-dropdown-menu.ts
5786
- var import_editor_utils10 = require("@kopexa/editor-utils");
5787
- var import_icons13 = require("@kopexa/icons");
5788
- var React10 = __toESM(require("react"));
5789
- var import_react_intl19 = require("react-intl");
5790
-
5791
- // src/ui/table-button/use-table.ts
5792
- var import_editor_utils9 = require("@kopexa/editor-utils");
5793
- var import_icons12 = require("@kopexa/icons");
5794
- var import_react42 = require("@tiptap/react");
5795
- var import_react43 = require("react");
5796
- var import_react_intl18 = require("react-intl");
5797
- function canToggle(editor) {
5798
- if (!editor || !editor.isEditable) return false;
5799
- if (!(0, import_editor_utils9.isNodeInSchema)("table", editor) || (0, import_editor_utils9.isNodeTypeSelected)(editor, ["image"])) {
5800
- return false;
5801
- }
5356
+ function sanitizeUrl(inputUrl, baseUrl, protocols) {
5802
5357
  try {
5803
- return editor.can().insertTable({ rows: 3, cols: 3, withHeaderRow: true });
5358
+ const url = new URL(inputUrl, baseUrl);
5359
+ if (isAllowedUri(url.href, protocols)) {
5360
+ return url.href;
5361
+ }
5804
5362
  } catch {
5805
- return false;
5806
5363
  }
5364
+ return "#";
5807
5365
  }
5808
- function toggleTable(editor, config) {
5809
- var _a;
5366
+
5367
+ // src/ui/link-popover/use-link-popover.ts
5368
+ function canSetLink(editor) {
5810
5369
  if (!editor || !editor.isEditable) return false;
5811
- if (!canToggle(editor)) return false;
5812
- try {
5813
- return editor.chain().focus().insertTable({
5814
- rows: (config == null ? void 0 : config.rows) || 3,
5815
- cols: (config == null ? void 0 : config.cols) || 3,
5816
- withHeaderRow: (_a = config == null ? void 0 : config.withHeaderRow) != null ? _a : true
5817
- }).run();
5818
- } catch {
5819
- return false;
5820
- }
5370
+ return editor.can().setMark("link");
5821
5371
  }
5822
- function shouldShowButton(props) {
5823
- const { editor, hideWhenUnavailable } = props;
5372
+ function isLinkActive(editor) {
5824
5373
  if (!editor || !editor.isEditable) return false;
5825
- if (!(0, import_editor_utils9.isNodeInSchema)("table", editor)) return false;
5826
- if (hideWhenUnavailable) {
5827
- if ((0, import_react42.isNodeSelection)(editor.state.selection) || !canToggle) {
5828
- return false;
5829
- }
5374
+ return editor.isActive("link");
5375
+ }
5376
+ function shouldShowLinkButton(props) {
5377
+ const { editor, hideWhenUnavailable } = props;
5378
+ const linkInSchema = (0, import_editor_utils8.isMarkInSchema)("link", editor);
5379
+ if (!linkInSchema || !editor) {
5380
+ return false;
5381
+ }
5382
+ if (hideWhenUnavailable && !editor.isActive("code")) {
5383
+ return canSetLink(editor);
5830
5384
  }
5831
5385
  return true;
5832
5386
  }
5833
- function useTableBlock(config) {
5834
- const {
5835
- editor: providedEditor,
5836
- hideWhenUnavailable = false,
5837
- onToggled
5838
- } = config || {};
5839
- const intl = (0, import_react_intl18.useIntl)();
5840
- const { editor } = (0, import_editor_utils9.useTiptapEditor)(providedEditor);
5841
- const [isVisible, setIsVisible] = (0, import_react43.useState)(true);
5842
- const canToggleState = canToggle(editor);
5843
- const isActive = (editor == null ? void 0 : editor.isActive("table")) || false;
5844
- (0, import_react43.useEffect)(() => {
5387
+ function useLinkHandler(props) {
5388
+ const { editor, onSetLink } = props;
5389
+ const [url, setUrl] = React8.useState(null);
5390
+ React8.useEffect(() => {
5845
5391
  if (!editor) return;
5846
- const handleSelectionUpdate = () => {
5847
- setIsVisible(shouldShowButton({ editor, hideWhenUnavailable }));
5392
+ const { href } = editor.getAttributes("link");
5393
+ if (isLinkActive(editor) && url === null) {
5394
+ setUrl(href || "");
5395
+ }
5396
+ }, [editor, url]);
5397
+ React8.useEffect(() => {
5398
+ if (!editor) return;
5399
+ const updateLinkState = () => {
5400
+ const { href } = editor.getAttributes("link");
5401
+ setUrl(href || "");
5848
5402
  };
5849
- handleSelectionUpdate();
5850
- editor.on("selectionUpdate", handleSelectionUpdate);
5403
+ editor.on("selectionUpdate", updateLinkState);
5851
5404
  return () => {
5852
- editor.off("selectionUpdate", handleSelectionUpdate);
5405
+ editor.off("selectionUpdate", updateLinkState);
5853
5406
  };
5854
- }, [editor, hideWhenUnavailable]);
5855
- const handleToggle = (0, import_react43.useCallback)(() => {
5856
- if (!editor) return false;
5857
- const success = toggleTable(editor);
5858
- if (success) {
5859
- onToggled == null ? void 0 : onToggled();
5407
+ }, [editor]);
5408
+ const setLink = React8.useCallback(() => {
5409
+ if (!url || !editor) return;
5410
+ const { selection } = editor.state;
5411
+ const isEmpty = selection.empty;
5412
+ let chain = editor.chain().focus();
5413
+ chain = chain.extendMarkRange("link").setLink({ href: url });
5414
+ if (isEmpty) {
5415
+ chain = chain.insertContent({ type: "text", text: url });
5860
5416
  }
5861
- return success;
5862
- }, [editor, onToggled]);
5863
- return {
5864
- isVisible,
5865
- isActive,
5866
- handleToggle,
5867
- canToggle: canToggleState,
5868
- label: intl.formatMessage(messages7.table_insert),
5869
- // shortcutKeys: CODE_BLOCK_SHORTCUT_KEY,
5870
- Icon: import_icons12.TableIcon
5417
+ chain.run();
5418
+ setUrl(null);
5419
+ onSetLink == null ? void 0 : onSetLink();
5420
+ }, [editor, onSetLink, url]);
5421
+ const removeLink = React8.useCallback(() => {
5422
+ if (!editor) return;
5423
+ editor.chain().focus().extendMarkRange("link").unsetLink().setMeta("preventAutolink", true).run();
5424
+ setUrl("");
5425
+ }, [editor]);
5426
+ const openLink = React8.useCallback(
5427
+ (target = "_blank", features = "noopener,noreferrer") => {
5428
+ if (!url) return;
5429
+ const safeUrl = sanitizeUrl(url, window.location.href);
5430
+ if (safeUrl !== "#") {
5431
+ window.open(safeUrl, target, features);
5432
+ }
5433
+ },
5434
+ [url]
5435
+ );
5436
+ return {
5437
+ url: url || "",
5438
+ setUrl,
5439
+ setLink,
5440
+ removeLink,
5441
+ openLink
5871
5442
  };
5872
5443
  }
5873
-
5874
- // src/ui/slash-dropdown-menu/use-slash-dropdown-menu.ts
5875
- function createSlashMenuTexts(formatMessage) {
5444
+ function useLinkState(props) {
5445
+ const { editor, hideWhenUnavailable = false } = props;
5446
+ const canSet = canSetLink(editor);
5447
+ const isActive = isLinkActive(editor);
5448
+ const [isVisible, setIsVisible] = React8.useState(false);
5449
+ React8.useEffect(() => {
5450
+ if (!editor) return;
5451
+ const handleSelectionUpdate = () => {
5452
+ setIsVisible(
5453
+ shouldShowLinkButton({
5454
+ editor,
5455
+ hideWhenUnavailable
5456
+ })
5457
+ );
5458
+ };
5459
+ handleSelectionUpdate();
5460
+ editor.on("selectionUpdate", handleSelectionUpdate);
5461
+ return () => {
5462
+ editor.off("selectionUpdate", handleSelectionUpdate);
5463
+ };
5464
+ }, [editor, hideWhenUnavailable]);
5876
5465
  return {
5877
- // AI
5878
- continue_writing: {
5879
- title: formatMessage(messages7.slash_continue_writing),
5880
- subtext: formatMessage(messages7.slash_continue_writing_subtext),
5881
- keywords: ["continue", "write", "continue writing", "ai"],
5882
- badge: import_icons13.AiSparklesIcon,
5883
- group: formatMessage(messages7.group_ai)
5884
- },
5885
- ai_ask_button: {
5886
- title: formatMessage(messages7.slash_ask_ai),
5887
- subtext: formatMessage(messages7.slash_ask_ai_subtext),
5888
- keywords: ["ai", "ask", "generate"],
5889
- badge: import_icons13.AiSparklesIcon,
5890
- group: formatMessage(messages7.group_ai)
5891
- },
5892
- // Style
5893
- text: {
5894
- title: formatMessage(messages7.slash_text),
5895
- subtext: formatMessage(messages7.slash_text_subtext),
5896
- keywords: ["p", "paragraph", "text"],
5897
- badge: import_icons13.TypeIcon,
5898
- group: formatMessage(messages7.group_style)
5899
- },
5900
- heading_1: {
5901
- title: formatMessage(messages7.slash_heading_1),
5902
- subtext: formatMessage(messages7.slash_heading_1_subtext),
5903
- keywords: ["h", "heading1", "h1"],
5904
- badge: import_icons13.HeadingOneIcon,
5905
- group: formatMessage(messages7.group_style)
5906
- },
5907
- heading_2: {
5908
- title: formatMessage(messages7.slash_heading_2),
5909
- subtext: formatMessage(messages7.slash_heading_2_subtext),
5910
- keywords: ["h2", "heading2", "subheading"],
5911
- badge: import_icons13.HeadingTwoIcon,
5912
- group: formatMessage(messages7.group_style)
5913
- },
5914
- heading_3: {
5915
- title: formatMessage(messages7.slash_heading_3),
5916
- subtext: formatMessage(messages7.slash_heading_3_subtext),
5917
- keywords: ["h3", "heading3", "subheading"],
5918
- badge: import_icons13.HeadingThreeIcon,
5919
- group: formatMessage(messages7.group_style)
5920
- },
5921
- bullet_list: {
5922
- title: formatMessage(messages7.slash_bullet_list),
5923
- subtext: formatMessage(messages7.slash_bullet_list_subtext),
5924
- keywords: ["ul", "li", "list", "bulletlist", "bullet list"],
5925
- badge: import_icons13.ListIcon,
5926
- group: formatMessage(messages7.group_style)
5927
- },
5928
- ordered_list: {
5929
- title: formatMessage(messages7.slash_ordered_list),
5930
- subtext: formatMessage(messages7.slash_ordered_list_subtext),
5931
- keywords: ["ol", "li", "list", "numberedlist", "numbered list"],
5932
- badge: import_icons13.ListOrderedIcon,
5933
- group: formatMessage(messages7.group_style)
5934
- },
5935
- task_list: {
5936
- title: formatMessage(messages7.slash_task_list),
5937
- subtext: formatMessage(messages7.slash_task_list_subtext),
5938
- keywords: ["tasklist", "task list", "todo", "checklist"],
5939
- badge: import_icons13.ListTodoIcon,
5940
- group: formatMessage(messages7.group_style)
5941
- },
5942
- quote: {
5943
- title: formatMessage(messages7.slash_blockquote),
5944
- subtext: formatMessage(messages7.slash_blockquote_subtext),
5945
- keywords: ["quote", "blockquote"],
5946
- badge: import_icons13.BlockquoteIcon,
5947
- group: formatMessage(messages7.group_style)
5948
- },
5949
- code_block: {
5950
- title: formatMessage(messages7.slash_code_block),
5951
- subtext: formatMessage(messages7.slash_code_block_subtext),
5952
- keywords: ["code", "pre"],
5953
- badge: import_icons13.CodeBlockIcon,
5954
- group: formatMessage(messages7.group_style)
5955
- },
5956
- // Insert
5957
- control: {
5958
- title: formatMessage(messages7.slash_control),
5959
- subtext: formatMessage(messages7.slash_control_subtext),
5960
- keywords: ["control"],
5961
- badge: import_icons13.ControlsIcon,
5962
- group: formatMessage(messages7.group_insert)
5963
- },
5964
- divider: {
5965
- title: formatMessage(messages7.slash_separator),
5966
- subtext: formatMessage(messages7.slash_separator_subtext),
5967
- keywords: ["hr", "horizontalRule", "line", "separator"],
5968
- badge: import_icons13.MinusIcon,
5969
- group: formatMessage(messages7.group_insert)
5970
- },
5971
- table: {
5972
- title: formatMessage(messages7.slash_table),
5973
- subtext: formatMessage(messages7.slash_table_subtext),
5974
- keywords: ["table", "grid", "spreadsheet"],
5975
- badge: import_icons13.TableIcon,
5976
- group: formatMessage(messages7.group_insert)
5977
- },
5978
- table_of_contents: {
5979
- title: formatMessage(messages7.slash_toc),
5980
- subtext: formatMessage(messages7.slash_toc_subtext),
5981
- keywords: ["toc", "table of contents", "index", "navigation", "headings"],
5982
- badge: import_icons13.TableOfContentsIcon,
5983
- group: formatMessage(messages7.group_insert)
5984
- },
5985
- callout: {
5986
- title: formatMessage(messages7.slash_callout),
5987
- subtext: formatMessage(messages7.slash_callout_subtext),
5988
- keywords: ["callout", "info", "warning", "alert", "note", "tip"],
5989
- badge: import_icons13.InfoIcon,
5990
- group: formatMessage(messages7.group_insert)
5991
- },
5992
- callout_warning: {
5993
- title: formatMessage(messages7.slash_warning),
5994
- subtext: formatMessage(messages7.slash_warning_subtext),
5995
- keywords: ["warning", "caution", "attention"],
5996
- badge: import_icons13.AlertIcon,
5997
- group: formatMessage(messages7.group_insert)
5998
- },
5999
- math: {
6000
- title: formatMessage(messages7.slash_formula),
6001
- subtext: formatMessage(messages7.slash_formula_subtext),
6002
- keywords: ["math", "latex", "formula", "equation", "katex"],
6003
- badge: import_icons13.TypeIcon,
6004
- group: formatMessage(messages7.group_insert)
6005
- },
6006
- // Upload
6007
- image: {
6008
- title: formatMessage(messages7.slash_image),
6009
- subtext: formatMessage(messages7.slash_image_subtext),
6010
- keywords: [
6011
- "image",
6012
- "imageUpload",
6013
- "upload",
6014
- "img",
6015
- "picture",
6016
- "media",
6017
- "url"
6018
- ],
6019
- badge: import_icons13.ImageIcon,
6020
- group: formatMessage(messages7.group_upload)
6021
- }
5466
+ isVisible,
5467
+ canSet,
5468
+ isActive
6022
5469
  };
6023
5470
  }
6024
- var getItemImplementations = () => {
5471
+ function useLinkPopover(config) {
5472
+ const {
5473
+ editor: providedEditor,
5474
+ hideWhenUnavailable = false,
5475
+ onSetLink
5476
+ } = config || {};
5477
+ const { editor } = (0, import_editor_utils8.useTiptapEditor)(providedEditor);
5478
+ const { isVisible, canSet, isActive } = useLinkState({
5479
+ editor,
5480
+ hideWhenUnavailable
5481
+ });
5482
+ const linkHandler = useLinkHandler({
5483
+ editor,
5484
+ onSetLink
5485
+ });
6025
5486
  return {
6026
- // AI
6027
- continue_writing: {
6028
- check: (editor) => {
6029
- const { hasContent } = (0, import_editor_utils10.hasContentAbove)(editor);
6030
- const extensionsReady = (0, import_editor_utils10.isExtensionAvailable)(editor, [
6031
- "ai",
6032
- "aiAdvanced"
6033
- ]);
6034
- return extensionsReady && hasContent;
6035
- },
6036
- action: ({ editor }) => {
6037
- const editorChain = editor.chain().focus();
6038
- const nodeSelectionPosition = (0, import_editor_utils10.findSelectionPosition)({ editor });
6039
- if (nodeSelectionPosition !== null) {
6040
- editorChain.setNodeSelection(nodeSelectionPosition);
6041
- }
6042
- editorChain.run();
6043
- editor.chain().focus().aiGenerationShow().run();
6044
- requestAnimationFrame(() => {
6045
- const { hasContent, content } = (0, import_editor_utils10.hasContentAbove)(editor);
6046
- const snippet = content.length > 500 ? `...${content.slice(-500)}` : content;
6047
- const prompt = hasContent ? `Context: ${snippet}
5487
+ isVisible,
5488
+ canSet,
5489
+ isActive,
5490
+ label: "Link",
5491
+ Icon: import_icons10.LinkIcon,
5492
+ ...linkHandler
5493
+ };
5494
+ }
6048
5495
 
6049
- Continue writing from where the text above ends. Write ONLY ONE SENTENCE. DONT REPEAT THE TEXT.` : "Start writing a new paragraph. Write ONLY ONE SENTENCE.";
6050
- editor.chain().focus().aiTextPrompt({
6051
- stream: true,
6052
- format: "rich-text",
6053
- text: prompt
6054
- }).run();
6055
- });
6056
- }
6057
- },
6058
- ai_ask_button: {
6059
- check: (editor) => (0, import_editor_utils10.isExtensionAvailable)(editor, ["ai", "aiAdvanced"]),
6060
- action: ({ editor }) => {
6061
- const editorChain = editor.chain().focus();
6062
- const nodeSelectionPosition = (0, import_editor_utils10.findSelectionPosition)({ editor });
6063
- if (nodeSelectionPosition !== null) {
6064
- editorChain.setNodeSelection(nodeSelectionPosition);
6065
- }
6066
- editorChain.run();
6067
- editor.chain().focus().aiGenerationShow().run();
6068
- }
6069
- },
6070
- // Style
6071
- text: {
6072
- check: (editor) => (0, import_editor_utils10.isNodeInSchema)("paragraph", editor),
6073
- action: ({ editor }) => {
6074
- editor.chain().focus().setParagraph().run();
6075
- }
6076
- },
6077
- heading_1: {
6078
- check: (editor) => (0, import_editor_utils10.isNodeInSchema)("heading", editor),
6079
- action: ({ editor }) => {
6080
- editor.chain().focus().toggleHeading({ level: 1 }).run();
6081
- }
6082
- },
6083
- heading_2: {
6084
- check: (editor) => (0, import_editor_utils10.isNodeInSchema)("heading", editor),
6085
- action: ({ editor }) => {
6086
- editor.chain().focus().toggleHeading({ level: 2 }).run();
6087
- }
6088
- },
6089
- heading_3: {
6090
- check: (editor) => (0, import_editor_utils10.isNodeInSchema)("heading", editor),
6091
- action: ({ editor }) => {
6092
- editor.chain().focus().toggleHeading({ level: 3 }).run();
6093
- }
6094
- },
6095
- bullet_list: {
6096
- check: (editor) => (0, import_editor_utils10.isNodeInSchema)("bulletList", editor),
6097
- action: ({ editor }) => {
6098
- editor.chain().focus().toggleBulletList().run();
6099
- }
6100
- },
6101
- ordered_list: {
6102
- check: (editor) => (0, import_editor_utils10.isNodeInSchema)("orderedList", editor),
6103
- action: ({ editor }) => {
6104
- editor.chain().focus().toggleOrderedList().run();
6105
- }
6106
- },
6107
- task_list: {
6108
- check: (editor) => (0, import_editor_utils10.isNodeInSchema)("taskList", editor),
6109
- action: ({ editor }) => {
6110
- editor.chain().focus().toggleTaskList().run();
6111
- }
6112
- },
6113
- quote: {
6114
- check: (editor) => (0, import_editor_utils10.isNodeInSchema)("blockquote", editor),
6115
- action: ({ editor }) => {
6116
- editor.chain().focus().toggleBlockquote().run();
6117
- }
6118
- },
6119
- code_block: {
6120
- check: (editor) => (0, import_editor_utils10.isNodeInSchema)("codeBlock", editor),
6121
- action: ({ editor }) => {
6122
- editor.chain().focus().toggleNode("codeBlock", "paragraph").run();
6123
- }
6124
- },
6125
- // Insert
6126
- // mention: {
6127
- // check: (editor: Editor) =>
6128
- // isExtensionAvailable(editor, ["mention", "mentionAdvanced"]),
6129
- // action: ({ editor }: { editor: Editor }) => addMentionTrigger(editor),
6130
- // },
6131
- // emoji: {
6132
- // check: (editor: Editor) =>
6133
- // isExtensionAvailable(editor, ["emoji", "emojiPicker"]),
6134
- // action: ({ editor }: { editor: Editor }) => addEmojiTrigger(editor),
6135
- // },
6136
- divider: {
6137
- check: (editor) => (0, import_editor_utils10.isNodeInSchema)("horizontalRule", editor),
6138
- action: ({ editor }) => {
6139
- editor.chain().focus().setHorizontalRule().run();
6140
- }
6141
- },
6142
- table: {
6143
- check: (editor) => (0, import_editor_utils10.isNodeInSchema)("table", editor),
6144
- action: ({ editor }) => toggleTable(editor, { rows: 3, cols: 3, withHeaderRow: true })
6145
- },
6146
- control: {
6147
- check: (editor) => (0, import_editor_utils10.isNodeInSchema)("controlBlock", editor),
6148
- action: ({ editor }) => {
6149
- try {
6150
- return editor.chain().focus().insertControlBlock().run();
6151
- } catch (e) {
6152
- console.error(e);
6153
- }
6154
- }
6155
- },
6156
- table_of_contents: {
6157
- check: (editor) => (0, import_editor_utils10.isNodeInSchema)("tableOfContentsNode", editor),
6158
- action: ({ editor }) => {
6159
- editor.chain().focus().insertTableOfContents().run();
6160
- }
6161
- },
6162
- callout: {
6163
- check: (editor) => (0, import_editor_utils10.isNodeInSchema)("calloutNode", editor),
6164
- action: ({ editor }) => {
6165
- editor.chain().focus().insertCallout("info").run();
6166
- }
6167
- },
6168
- callout_warning: {
6169
- check: (editor) => (0, import_editor_utils10.isNodeInSchema)("calloutNode", editor),
6170
- action: ({ editor }) => {
6171
- editor.chain().focus().insertCallout("warning").run();
6172
- }
6173
- },
6174
- math: {
6175
- check: (editor) => (0, import_editor_utils10.isNodeInSchema)("mathBlock", editor),
6176
- action: ({ editor }) => {
6177
- editor.chain().focus().insertMathBlock().run();
6178
- }
6179
- },
6180
- // Upload
6181
- image: {
6182
- check: (editor) => (0, import_editor_utils10.isNodeInSchema)("imageUpload", editor),
6183
- action: ({ editor }) => {
6184
- editor.chain().focus().setImageUpload().run();
6185
- }
6186
- }
6187
- };
6188
- };
6189
- function organizeItemsByGroups(items, showGroups) {
6190
- if (!showGroups) {
6191
- return items.map((item) => ({ ...item, group: "" }));
6192
- }
6193
- const groups = {};
6194
- items.forEach((item) => {
6195
- const groupLabel = item.group || "";
6196
- if (!groups[groupLabel]) {
6197
- groups[groupLabel] = [];
6198
- }
6199
- groups[groupLabel].push(item);
6200
- });
6201
- const organizedItems = [];
6202
- Object.entries(groups).forEach(([, groupItems]) => {
6203
- organizedItems.push(...groupItems);
6204
- });
6205
- return organizedItems;
6206
- }
6207
- var ALL_SLASH_MENU_ITEMS = [
6208
- "continue_writing",
6209
- "ai_ask_button",
6210
- "text",
6211
- "heading_1",
6212
- "heading_2",
6213
- "heading_3",
6214
- "bullet_list",
6215
- "ordered_list",
6216
- "task_list",
6217
- "quote",
6218
- "code_block",
6219
- "control",
6220
- "divider",
6221
- "table",
6222
- "table_of_contents",
6223
- "callout",
6224
- "callout_warning",
6225
- "math",
6226
- "image"
6227
- ];
6228
- function useSlashDropdownMenu(config) {
6229
- const intl = (0, import_react_intl19.useIntl)();
6230
- const getSlashMenuItems = React10.useCallback(
6231
- (editor) => {
6232
- const items = [];
6233
- const texts = createSlashMenuTexts(intl.formatMessage);
6234
- const enabledItems = (config == null ? void 0 : config.enabledItems) || ALL_SLASH_MENU_ITEMS;
6235
- const showGroups = (config == null ? void 0 : config.showGroups) !== false;
6236
- const itemImplementations = getItemImplementations();
6237
- enabledItems.forEach((itemType) => {
6238
- var _a;
6239
- const itemImpl = itemImplementations[itemType];
6240
- const itemText = texts[itemType];
6241
- if (itemImpl && itemText && itemImpl.check(editor)) {
6242
- const item = {
6243
- onSelect: ({ editor: editor2 }) => itemImpl.action({ editor: editor2 }),
6244
- ...itemText
6245
- };
6246
- if ((_a = config == null ? void 0 : config.itemGroups) == null ? void 0 : _a[itemType]) {
6247
- item.group = config.itemGroups[itemType];
6248
- } else if (!showGroups) {
6249
- item.group = "";
6250
- }
6251
- items.push(item);
6252
- }
6253
- });
6254
- if (config == null ? void 0 : config.customItems) {
6255
- items.push(...config.customItems);
6256
- }
6257
- return organizeItemsByGroups(items, showGroups);
6258
- },
6259
- [config, intl]
6260
- );
6261
- return {
6262
- getSlashMenuItems,
6263
- config
6264
- };
6265
- }
6266
-
6267
- // src/ui/slash-dropdown-menu/slash-dropdown-menu.tsx
6268
- var import_jsx_runtime20 = require("react/jsx-runtime");
6269
- var SlashDropdownMenu = (props) => {
6270
- const { config, ...restProps } = props;
6271
- const { getSlashMenuItems } = useSlashDropdownMenu(config);
6272
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6273
- SuggestionMenu,
6274
- {
6275
- char: "/",
6276
- pluginKey: "slashDropdownMenu",
6277
- decorationClass: "tiptap-slash-decoration",
6278
- decorationContent: "Filter...",
6279
- selector: "tiptap-slash-dropdown-menu",
6280
- items: ({ query, editor }) => filterSuggestionItems(getSlashMenuItems(editor), query),
6281
- ...restProps,
6282
- children: (props2) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(List, { ...props2, config })
6283
- }
6284
- );
6285
- };
6286
- var Item = (props) => {
6287
- const { item, isSelected, onSelect } = props;
6288
- const itemRef = React11.useRef(null);
6289
- React11.useEffect(() => {
6290
- const selector = document.querySelector(
6291
- '[data-selector="tiptap-slash-dropdown-menu"]'
6292
- );
6293
- if (!itemRef.current || !isSelected || !selector) return;
6294
- const overflow = (0, import_editor_utils11.getElementOverflowPosition)(itemRef.current, selector);
6295
- if (overflow === "top") {
6296
- itemRef.current.scrollIntoView(true);
6297
- } else if (overflow === "bottom") {
6298
- itemRef.current.scrollIntoView(false);
6299
- }
6300
- }, [isSelected]);
6301
- const BadgeIcon = item.badge;
6302
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6303
- import_button10.Button,
5496
+ // src/ui/link-popover/link-popover.tsx
5497
+ var import_jsx_runtime18 = require("react/jsx-runtime");
5498
+ var LinkButton = ({ className, children, ...props }) => {
5499
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5500
+ import_toolbar3.ToolbarButton,
6304
5501
  {
6305
- ref: itemRef,
5502
+ type: "button",
5503
+ className,
6306
5504
  variant: "ghost",
6307
5505
  color: "default",
6308
- startContent: BadgeIcon && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(BadgeIcon, {}),
6309
- "data-active-state": isSelected ? "on" : "off",
6310
- onClick: onSelect,
6311
- fullWidth: true,
6312
- spacing: "start",
6313
- children: item.title
5506
+ tabIndex: -1,
5507
+ "aria-label": "Link",
5508
+ title: "Link",
5509
+ isIconOnly: !children,
5510
+ ...props,
5511
+ children: children || /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_icons11.LinkIcon, {})
6314
5512
  }
6315
5513
  );
6316
5514
  };
6317
- var List = ({
6318
- items,
6319
- selectedIndex,
6320
- onSelect,
6321
- config
5515
+ var LinkMain = ({
5516
+ url,
5517
+ setUrl,
5518
+ setLink,
5519
+ removeLink,
5520
+ openLink,
5521
+ isActive,
5522
+ onSave
6322
5523
  }) => {
6323
- const styles = (0, import_theme7.slashDropdownMenu)();
6324
- const renderedItems = React11.useMemo(() => {
6325
- const rendered = [];
6326
- const showGroups = (config == null ? void 0 : config.showGroups) !== false;
6327
- if (!showGroups) {
6328
- items.forEach((item, index) => {
6329
- rendered.push(
6330
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6331
- Item,
6332
- {
6333
- item,
6334
- isSelected: index === selectedIndex,
6335
- onSelect: () => onSelect(item)
6336
- },
6337
- `item-${index}-${item.title}`
6338
- )
6339
- );
6340
- });
6341
- return rendered;
5524
+ const [isEditing, setIsEditing] = (0, import_react39.useState)(!isActive || !url);
5525
+ (0, import_react39.useEffect)(() => {
5526
+ setIsEditing(!isActive || !url);
5527
+ }, [isActive, url]);
5528
+ const handleKeyDown = (event) => {
5529
+ if (event.key === "Enter") {
5530
+ event.preventDefault();
5531
+ setLink();
5532
+ setIsEditing(false);
5533
+ onSave == null ? void 0 : onSave();
5534
+ } else if (event.key === "Escape") {
5535
+ event.preventDefault();
5536
+ setIsEditing(false);
6342
5537
  }
6343
- const groups = {};
6344
- items.forEach((item, index) => {
6345
- const groupLabel = item.group || "";
6346
- if (!groups[groupLabel]) {
6347
- groups[groupLabel] = { items: [], indices: [] };
6348
- }
6349
- groups[groupLabel].items.push(item);
6350
- groups[groupLabel].indices.push(index);
6351
- });
6352
- Object.entries(groups).forEach(([groupLabel, groupData], groupIndex) => {
6353
- if (groupIndex > 0) {
6354
- rendered.push(
6355
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6356
- import_separator.Separator,
6357
- {
6358
- orientation: "horizontal"
6359
- },
6360
- `separator-${groupIndex.toString()}`
6361
- )
6362
- );
6363
- }
6364
- const groupItems = groupData.items.map((item, itemIndex) => {
6365
- const originalIndex = groupData.indices[itemIndex];
6366
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6367
- Item,
6368
- {
6369
- item,
6370
- isSelected: originalIndex === selectedIndex,
6371
- onSelect: () => onSelect(item)
6372
- },
6373
- `item-${originalIndex}-${item.title}`
6374
- );
6375
- });
6376
- if (groupLabel) {
6377
- rendered.push(
6378
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
6379
- "div",
6380
- {
6381
- className: styles.cardItemGroup(),
6382
- children: [
6383
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: styles.cardGroupLabel(), children: groupLabel }),
6384
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: styles.cardGroup(), children: groupItems })
6385
- ]
6386
- },
6387
- `group-${groupIndex}-${groupLabel}`
6388
- )
6389
- );
6390
- } else {
6391
- rendered.push(...groupItems);
5538
+ };
5539
+ const handleSave = () => {
5540
+ setLink();
5541
+ setIsEditing(false);
5542
+ onSave == null ? void 0 : onSave();
5543
+ };
5544
+ const handleEdit = () => {
5545
+ setIsEditing(true);
5546
+ };
5547
+ if (isEditing) {
5548
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-1 min-w-[280px]", children: [
5549
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5550
+ import_input3.Input,
5551
+ {
5552
+ type: "url",
5553
+ placeholder: "Enter URL...",
5554
+ value: url,
5555
+ onChange: (e) => setUrl(e.target.value),
5556
+ onKeyDown: handleKeyDown,
5557
+ autoComplete: "off",
5558
+ autoCorrect: "off",
5559
+ autoCapitalize: "off",
5560
+ className: "flex-1 h-8 text-sm",
5561
+ autoFocus: true
5562
+ }
5563
+ ),
5564
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5565
+ import_button9.IconButton,
5566
+ {
5567
+ type: "button",
5568
+ size: "sm",
5569
+ variant: "ghost",
5570
+ onClick: handleSave,
5571
+ "aria-label": "Save link",
5572
+ disabled: !url,
5573
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_icons11.CheckIcon, { className: "size-4" })
5574
+ }
5575
+ )
5576
+ ] });
5577
+ }
5578
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-1 min-w-[280px]", children: [
5579
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5580
+ "a",
5581
+ {
5582
+ href: url,
5583
+ target: "_blank",
5584
+ rel: "noopener noreferrer",
5585
+ className: "flex-1 text-sm text-primary truncate max-w-[200px] hover:underline px-2",
5586
+ onClick: (e) => {
5587
+ e.preventDefault();
5588
+ openLink();
5589
+ },
5590
+ children: url
6392
5591
  }
6393
- });
6394
- return rendered;
6395
- }, [items, selectedIndex, onSelect, config == null ? void 0 : config.showGroups, styles]);
6396
- if (!renderedItems.length) {
5592
+ ),
5593
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-0.5 border-l pl-1 ml-1", children: [
5594
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5595
+ import_button9.IconButton,
5596
+ {
5597
+ type: "button",
5598
+ size: "sm",
5599
+ variant: "ghost",
5600
+ onClick: openLink,
5601
+ "aria-label": "Open link in new tab",
5602
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_icons11.ExternalLinkIcon, { className: "size-4" })
5603
+ }
5604
+ ),
5605
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5606
+ import_button9.IconButton,
5607
+ {
5608
+ type: "button",
5609
+ size: "sm",
5610
+ variant: "ghost",
5611
+ onClick: handleEdit,
5612
+ "aria-label": "Edit link",
5613
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_icons11.EditIcon, { className: "size-4" })
5614
+ }
5615
+ ),
5616
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5617
+ import_button9.IconButton,
5618
+ {
5619
+ type: "button",
5620
+ size: "sm",
5621
+ variant: "ghost",
5622
+ onClick: removeLink,
5623
+ "aria-label": "Remove link",
5624
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_icons11.TrashIcon, { className: "size-4" })
5625
+ }
5626
+ )
5627
+ ] })
5628
+ ] });
5629
+ };
5630
+ function LinkPopover({
5631
+ editor: providedEditor,
5632
+ hideWhenUnavailable = false,
5633
+ onSetLink,
5634
+ onOpenChange,
5635
+ autoOpenOnLinkActive = true,
5636
+ onClick,
5637
+ children,
5638
+ ...buttonProps
5639
+ }) {
5640
+ const { editor } = (0, import_editor_utils9.useTiptapEditor)(providedEditor);
5641
+ const [isOpen, setIsOpen] = (0, import_react39.useState)(false);
5642
+ const {
5643
+ isVisible,
5644
+ canSet,
5645
+ isActive,
5646
+ url,
5647
+ setUrl,
5648
+ setLink,
5649
+ removeLink,
5650
+ openLink,
5651
+ label
5652
+ } = useLinkPopover({
5653
+ editor,
5654
+ hideWhenUnavailable,
5655
+ onSetLink
5656
+ });
5657
+ const handleOnOpenChange = (0, import_react39.useCallback)(
5658
+ (nextIsOpen) => {
5659
+ setIsOpen(nextIsOpen);
5660
+ onOpenChange == null ? void 0 : onOpenChange(nextIsOpen);
5661
+ },
5662
+ [onOpenChange]
5663
+ );
5664
+ const handleSetLink = (0, import_react39.useCallback)(() => {
5665
+ setLink();
5666
+ setIsOpen(false);
5667
+ }, [setLink]);
5668
+ const handleClick = (0, import_react39.useCallback)(
5669
+ (event) => {
5670
+ onClick == null ? void 0 : onClick(event);
5671
+ if (event.defaultPrevented) return;
5672
+ setIsOpen(!isOpen);
5673
+ },
5674
+ [onClick, isOpen]
5675
+ );
5676
+ (0, import_react39.useEffect)(() => {
5677
+ if (autoOpenOnLinkActive && isActive) {
5678
+ setIsOpen(true);
5679
+ }
5680
+ }, [autoOpenOnLinkActive, isActive]);
5681
+ if (!isVisible) {
6397
5682
  return null;
6398
5683
  }
6399
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6400
- "div",
5684
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
5685
+ import_popover2.Popover.Root,
6401
5686
  {
6402
- className: styles.card(),
6403
- style: {
6404
- maxHeight: "var(--suggestion-menu-max-height)"
6405
- },
6406
- children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: styles.body(), children: renderedItems })
5687
+ open: isOpen,
5688
+ onOpenChange: handleOnOpenChange,
5689
+ spacing: "dense",
5690
+ children: [
5691
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5692
+ import_popover2.Popover.Trigger,
5693
+ {
5694
+ render: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5695
+ LinkButton,
5696
+ {
5697
+ "data-disabled": !canSet,
5698
+ disabled: !canSet,
5699
+ "data-active-state": isActive ? "on" : "off",
5700
+ "aria-label": label,
5701
+ "aria-pressed": isActive,
5702
+ onClick: handleClick,
5703
+ ...buttonProps
5704
+ }
5705
+ )
5706
+ }
5707
+ ),
5708
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_popover2.Popover.Content, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5709
+ LinkMain,
5710
+ {
5711
+ url,
5712
+ setUrl,
5713
+ setLink: handleSetLink,
5714
+ removeLink,
5715
+ openLink,
5716
+ isActive,
5717
+ onSave: () => setIsOpen(false)
5718
+ }
5719
+ ) })
5720
+ ]
6407
5721
  }
6408
5722
  );
6409
- };
6410
-
6411
- // src/presets/basic/editor-header.tsx
6412
- var import_editor_utils31 = require("@kopexa/editor-utils");
6413
- var import_icons26 = require("@kopexa/icons");
6414
- var import_popover3 = require("@kopexa/popover");
6415
- var import_toolbar10 = require("@kopexa/toolbar");
6416
- var import_use_is_mobile3 = require("@kopexa/use-is-mobile");
6417
- var import_react61 = require("react");
6418
- var import_react_intl25 = require("react-intl");
6419
-
6420
- // src/hooks/use-cursor-visibility.ts
6421
- var React13 = __toESM(require("react"));
6422
-
6423
- // src/hooks/use-window-size.ts
6424
- var React12 = __toESM(require("react"));
6425
- function useWindowSize() {
6426
- const [windowSize, setWindowSize] = React12.useState({
6427
- width: 0,
6428
- height: 0,
6429
- offsetTop: 0
6430
- });
6431
- React12.useEffect(() => {
6432
- handleResize();
6433
- function handleResize() {
6434
- if (typeof window === "undefined") return;
6435
- const vp = window.visualViewport;
6436
- if (!vp) return;
6437
- const { width = 0, height = 0, offsetTop = 0 } = vp;
6438
- setWindowSize((state) => {
6439
- if (width === state.width && height === state.height && offsetTop === state.offsetTop) {
6440
- return state;
6441
- }
6442
- return { width, height, offsetTop };
6443
- });
6444
- }
6445
- const visualViewport = window.visualViewport;
6446
- if (visualViewport) {
6447
- visualViewport.addEventListener("resize", handleResize);
6448
- visualViewport.addEventListener("scroll", handleResize);
6449
- }
6450
- return () => {
6451
- if (visualViewport) {
6452
- visualViewport.removeEventListener("resize", handleResize);
6453
- visualViewport.removeEventListener("scroll", handleResize);
6454
- }
6455
- };
6456
- }, []);
6457
- return windowSize;
6458
5723
  }
5724
+ LinkButton.displayName = "LinkButton";
6459
5725
 
6460
- // src/hooks/use-cursor-visibility.ts
6461
- function useCursorVisibility({
6462
- editor,
6463
- overlayHeight = 0,
6464
- elementRef = null
6465
- }) {
6466
- const { height: windowHeight } = useWindowSize();
6467
- const [rect, setRect] = React13.useState({
6468
- x: 0,
6469
- y: 0,
6470
- width: 0,
6471
- height: 0
6472
- });
6473
- const updateRect = React13.useCallback(() => {
6474
- var _a;
6475
- const element = (_a = elementRef == null ? void 0 : elementRef.current) != null ? _a : document.body;
6476
- const { x, y, width, height } = element.getBoundingClientRect();
6477
- setRect({ x, y, width, height });
6478
- }, [elementRef]);
6479
- React13.useEffect(() => {
6480
- var _a;
6481
- const element = (_a = elementRef == null ? void 0 : elementRef.current) != null ? _a : document.body;
6482
- updateRect();
6483
- const resizeObserver = new ResizeObserver(() => {
6484
- window.requestAnimationFrame(updateRect);
6485
- });
6486
- resizeObserver.observe(element);
6487
- window.addEventListener("scroll", updateRect, { passive: true });
6488
- return () => {
6489
- resizeObserver.disconnect();
6490
- window.removeEventListener("scroll", updateRect);
6491
- };
6492
- }, [elementRef, updateRect]);
6493
- React13.useEffect(() => {
6494
- const ensureCursorVisibility = () => {
6495
- if (!editor) return;
6496
- const { state, view } = editor;
6497
- if (!view.hasFocus()) return;
6498
- const { from } = state.selection;
6499
- const cursorCoords = view.coordsAtPos(from);
6500
- if (windowHeight < rect.height) {
6501
- if (cursorCoords) {
6502
- const availableSpace = windowHeight - cursorCoords.top - overlayHeight > 0;
6503
- if (!availableSpace) {
6504
- const targetScrollY = (
6505
- // TODO: Needed?
6506
- // window.scrollY + (cursorCoords.top - windowHeight / 2)
6507
- cursorCoords.top - windowHeight / 2
6508
- );
6509
- window.scrollTo({
6510
- top: targetScrollY,
6511
- behavior: "smooth"
6512
- });
6513
- }
6514
- }
6515
- }
6516
- };
6517
- ensureCursorVisibility();
6518
- }, [editor, overlayHeight, windowHeight, rect.height]);
6519
- return rect;
6520
- }
6521
-
6522
- // src/ui/color-highlight-popover/color-highlight-popover.tsx
6523
- var import_button11 = require("@kopexa/button");
6524
- var import_editor_utils14 = require("@kopexa/editor-utils");
6525
- var import_icons15 = require("@kopexa/icons");
6526
- var import_popover2 = require("@kopexa/popover");
6527
- var import_toolbar5 = require("@kopexa/toolbar");
6528
- var import_react45 = require("react");
6529
-
6530
- // src/ui/color-highlight-button/color-highlight-button.tsx
6531
- var import_editor_utils13 = require("@kopexa/editor-utils");
6532
- var import_theme8 = require("@kopexa/theme");
5726
+ // src/ui/mark-button/index.tsx
5727
+ var import_editor_utils10 = require("@kopexa/editor-utils");
5728
+ var import_icons12 = require("@kopexa/icons");
6533
5729
  var import_toolbar4 = require("@kopexa/toolbar");
6534
- var import_react44 = require("react");
6535
-
6536
- // src/ui/color-highlight-button/use-color-highlight.ts
6537
- var import_editor_utils12 = require("@kopexa/editor-utils");
6538
- var import_icons14 = require("@kopexa/icons");
6539
- var import_use_is_mobile = require("@kopexa/use-is-mobile");
6540
- var React14 = __toESM(require("react"));
6541
- var import_react_hotkeys_hook = require("react-hotkeys-hook");
6542
- var import_react_intl20 = require("react-intl");
6543
- var COLOR_HIGHLIGHT_SHORTCUT_KEY = "mod+shift+h";
6544
- var HIGHLIGHT_COLORS = [
6545
- {
6546
- label: "Default background",
6547
- value: "var(--tt-bg-color)",
6548
- border: "var(--tt-bg-color-contrast)"
6549
- },
6550
- {
6551
- label: "Gray background",
6552
- value: "var(--tt-color-highlight-gray)",
6553
- border: "var(--tt-color-highlight-gray-contrast)"
6554
- },
6555
- {
6556
- label: "Brown background",
6557
- value: "var(--tt-color-highlight-brown)",
6558
- border: "var(--tt-color-highlight-brown-contrast)"
6559
- },
6560
- {
6561
- label: "Orange background",
6562
- value: "var(--tt-color-highlight-orange)",
6563
- border: "var(--tt-color-highlight-orange-contrast)"
6564
- },
6565
- {
6566
- label: "Yellow background",
6567
- value: "var(--tt-color-highlight-yellow)",
6568
- border: "var(--tt-color-highlight-yellow-contrast)"
6569
- },
6570
- {
6571
- label: "Green background",
6572
- value: "var(--tt-color-highlight-green)",
6573
- border: "var(--tt-color-highlight-green-contrast)"
6574
- },
6575
- {
6576
- label: "Blue background",
6577
- value: "var(--tt-color-highlight-blue)",
6578
- border: "var(--tt-color-highlight-blue-contrast)"
6579
- },
6580
- {
6581
- label: "Purple background",
6582
- value: "var(--tt-color-highlight-purple)",
6583
- border: "var(--tt-color-highlight-purple-contrast)"
6584
- },
6585
- {
6586
- label: "Pink background",
6587
- value: "var(--tt-color-highlight-pink)",
6588
- border: "var(--tt-color-highlight-pink-contrast)"
6589
- },
6590
- {
6591
- label: "Red background",
6592
- value: "var(--tt-color-highlight-red)",
6593
- border: "var(--tt-color-highlight-red-contrast)"
6594
- }
6595
- ];
6596
- function pickHighlightColorsByValue(values) {
6597
- const colorMap = new Map(
6598
- HIGHLIGHT_COLORS.map((color) => [color.value, color])
6599
- );
6600
- return values.map((value) => colorMap.get(value)).filter((color) => !!color);
6601
- }
6602
- function canColorHighlight(editor) {
5730
+ var import_react40 = require("@tiptap/react");
5731
+ var import_react41 = require("react");
5732
+ var import_react_intl17 = require("react-intl");
5733
+ var import_jsx_runtime19 = require("react/jsx-runtime");
5734
+ var markIcons = {
5735
+ bold: import_icons12.BoldIcon,
5736
+ italic: import_icons12.ItalicIcon,
5737
+ underline: import_icons12.UnderlineIcon,
5738
+ strike: import_icons12.StrikeIcon,
5739
+ code: import_icons12.CodeIcon,
5740
+ superscript: import_icons12.SuperscriptIcon,
5741
+ subscript: import_icons12.SubscriptIcon
5742
+ };
5743
+ var markShortcutKeys = {
5744
+ bold: "mod+b",
5745
+ italic: "mod+i",
5746
+ underline: "mod+u",
5747
+ strike: "mod+shift+s",
5748
+ code: "mod+e",
5749
+ superscript: "mod+.",
5750
+ subscript: "mod+,"
5751
+ };
5752
+ function canToggleMark(editor, type) {
6603
5753
  if (!editor || !editor.isEditable) return false;
6604
- if (!(0, import_editor_utils12.isMarkInSchema)("highlight", editor) || (0, import_editor_utils12.isNodeTypeSelected)(editor, ["image"]))
5754
+ if (!(0, import_editor_utils10.isMarkInSchema)(type, editor) || (0, import_editor_utils10.isNodeTypeSelected)(editor, ["image"]))
6605
5755
  return false;
6606
- return editor.can().setMark("highlight");
5756
+ return editor.can().toggleMark(type);
6607
5757
  }
6608
- function isColorHighlightActive(editor, highlightColor) {
6609
- if (!editor || !editor.isEditable) return false;
6610
- return highlightColor ? editor.isActive("highlight", { color: highlightColor }) : editor.isActive("highlight");
5758
+ function isMarkActive(editor, type) {
5759
+ if (!editor) return false;
5760
+ return editor.isActive(type);
6611
5761
  }
6612
- function removeHighlight(editor) {
6613
- if (!editor || !editor.isEditable) return false;
6614
- if (!canColorHighlight(editor)) return false;
6615
- return editor.chain().focus().unsetMark("highlight").run();
5762
+ function toggleMark(editor, type) {
5763
+ if (!editor) return;
5764
+ editor.chain().focus().toggleMark(type).run();
6616
5765
  }
6617
- function shouldShowButton2(props) {
6618
- const { editor, hideWhenUnavailable } = props;
6619
- if (!editor || !editor.isEditable) return false;
6620
- if (!(0, import_editor_utils12.isMarkInSchema)("highlight", editor)) return false;
6621
- if (hideWhenUnavailable && !editor.isActive("code")) {
6622
- return canColorHighlight(editor);
5766
+ function isMarkButtonDisabled(editor, type, userDisabled = false) {
5767
+ if (!editor) return true;
5768
+ if (userDisabled) return true;
5769
+ if (editor.isActive("codeBlock")) return true;
5770
+ if (!canToggleMark(editor, type)) return true;
5771
+ return false;
5772
+ }
5773
+ function shouldShowMarkButton(params) {
5774
+ const { editor, type, hideWhenUnavailable, markInSchema } = params;
5775
+ if (!markInSchema || !editor) {
5776
+ return false;
5777
+ }
5778
+ if (hideWhenUnavailable) {
5779
+ if ((0, import_react40.isNodeSelection)(editor.state.selection) || !canToggleMark(editor, type)) {
5780
+ return false;
5781
+ }
6623
5782
  }
6624
5783
  return true;
6625
5784
  }
6626
- function useColorHighlight(config) {
5785
+ var markMessages = {
5786
+ bold: messages7.bold,
5787
+ italic: messages7.italic,
5788
+ underline: messages7.underline,
5789
+ strike: messages7.strikethrough,
5790
+ code: messages7.code,
5791
+ superscript: messages7.superscript,
5792
+ subscript: messages7.subscript
5793
+ };
5794
+ function getTranslatedMarkName(type, intl) {
5795
+ return intl.formatMessage(markMessages[type]);
5796
+ }
5797
+ function useMarkState(editor, type, disabled = false) {
5798
+ const intl = (0, import_react_intl17.useIntl)();
5799
+ const markInSchema = (0, import_editor_utils10.isMarkInSchema)(type, editor);
5800
+ const isDisabled = isMarkButtonDisabled(editor, type, disabled);
5801
+ const isActive = isMarkActive(editor, type);
5802
+ const Icon = markIcons[type];
5803
+ const shortcutKey = markShortcutKeys[type];
5804
+ const formattedName = getTranslatedMarkName(type, intl);
5805
+ return {
5806
+ markInSchema,
5807
+ isDisabled,
5808
+ isActive,
5809
+ Icon,
5810
+ shortcutKey,
5811
+ formattedName
5812
+ };
5813
+ }
5814
+ var MarkButton = ({
5815
+ editor: providedEditor,
5816
+ type,
5817
+ text,
5818
+ hideWhenUnavailable = false,
5819
+ className = "",
5820
+ disabled,
5821
+ onClick,
5822
+ children,
5823
+ ...buttonProps
5824
+ }) => {
5825
+ const { editor } = (0, import_editor_utils10.useTiptapEditor)(providedEditor);
6627
5826
  const {
6628
- editor: providedEditor,
6629
- label,
6630
- highlightColor,
6631
- hideWhenUnavailable = false,
6632
- onApplied
6633
- } = config;
6634
- const intl = (0, import_react_intl20.useIntl)();
6635
- const { editor } = (0, import_editor_utils12.useTiptapEditor)(providedEditor);
6636
- const isMobile = (0, import_use_is_mobile.useIsMobile)();
6637
- const [isVisible, setIsVisible] = React14.useState(true);
6638
- const canColorHighlightState = canColorHighlight(editor);
6639
- const isActive = isColorHighlightActive(editor, highlightColor);
6640
- React14.useEffect(() => {
6641
- if (!editor) return;
6642
- const handleSelectionUpdate = () => {
6643
- setIsVisible(shouldShowButton2({ editor, hideWhenUnavailable }));
6644
- };
6645
- handleSelectionUpdate();
6646
- editor.on("selectionUpdate", handleSelectionUpdate);
6647
- return () => {
6648
- editor.off("selectionUpdate", handleSelectionUpdate);
6649
- };
6650
- }, [editor, hideWhenUnavailable]);
6651
- const handleColorHighlight = React14.useCallback(() => {
6652
- if (!editor || !canColorHighlightState || !highlightColor || !label)
6653
- return false;
6654
- if (editor.state.storedMarks) {
6655
- const highlightMarkType = editor.schema.marks.highlight;
6656
- if (highlightMarkType) {
6657
- editor.view.dispatch(
6658
- editor.state.tr.removeStoredMark(highlightMarkType)
6659
- );
5827
+ markInSchema,
5828
+ isDisabled,
5829
+ isActive,
5830
+ Icon,
5831
+ shortcutKey,
5832
+ formattedName
5833
+ } = useMarkState(editor, type, disabled);
5834
+ const handleClick = (0, import_react41.useCallback)(
5835
+ (e) => {
5836
+ onClick == null ? void 0 : onClick(e);
5837
+ if (!e.defaultPrevented && !isDisabled && editor) {
5838
+ toggleMark(editor, type);
5839
+ }
5840
+ },
5841
+ [onClick, isDisabled, editor, type]
5842
+ );
5843
+ const show = (0, import_react41.useMemo)(() => {
5844
+ return shouldShowMarkButton({
5845
+ editor,
5846
+ type,
5847
+ hideWhenUnavailable,
5848
+ markInSchema
5849
+ });
5850
+ }, [editor, type, hideWhenUnavailable, markInSchema]);
5851
+ if (!show || !editor || !editor.isEditable) {
5852
+ return null;
5853
+ }
5854
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5855
+ import_toolbar4.ToolbarButton,
5856
+ {
5857
+ type: "button",
5858
+ className: className.trim(),
5859
+ disabled: isDisabled,
5860
+ variant: "ghost",
5861
+ color: "default",
5862
+ "data-active-state": isActive ? "on" : "off",
5863
+ "data-disabled": isDisabled,
5864
+ tabIndex: -1,
5865
+ "aria-label": formattedName,
5866
+ "aria-pressed": isActive,
5867
+ title: formattedName,
5868
+ shortcutKeys: shortcutKey,
5869
+ onClick: handleClick,
5870
+ isIconOnly: true,
5871
+ ...buttonProps,
5872
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Icon, {})
5873
+ }
5874
+ );
5875
+ };
5876
+
5877
+ // src/ui/bubble-menu/index.tsx
5878
+ var import_jsx_runtime20 = require("react/jsx-runtime");
5879
+ function useResolvedHighlightColors(editor) {
5880
+ var _a;
5881
+ if (!((_a = editor == null ? void 0 : editor.view) == null ? void 0 : _a.dom)) return void 0;
5882
+ const editorEl = editor.view.dom;
5883
+ const computed = getComputedStyle(editorEl);
5884
+ return HIGHLIGHT_COLORS.filter((c) => c.value !== "var(--tt-bg-color)").map(
5885
+ (color) => {
5886
+ const resolveVar = (v) => {
5887
+ const match = v.match(/^var\((.+)\)$/);
5888
+ if (!match) return v;
5889
+ return computed.getPropertyValue(match[1]).trim() || v;
5890
+ };
5891
+ return {
5892
+ ...color,
5893
+ value: resolveVar(color.value),
5894
+ border: resolveVar(color.border)
5895
+ };
5896
+ }
5897
+ );
5898
+ }
5899
+ function BubbleMenu({ editor }) {
5900
+ const resolvedColors = useResolvedHighlightColors(editor);
5901
+ if (!editor) {
5902
+ return null;
5903
+ }
5904
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
5905
+ import_menus.BubbleMenu,
5906
+ {
5907
+ editor,
5908
+ shouldShow: ({ editor: e, state, view }) => {
5909
+ const { selection } = state;
5910
+ const { empty } = selection;
5911
+ if (!view.hasFocus()) return false;
5912
+ if (empty) return false;
5913
+ if (e.isActive("codeBlock")) return false;
5914
+ if (e.isActive("link")) return false;
5915
+ if (e.isActive("variable")) return false;
5916
+ if (!e.isEditable) return false;
5917
+ return true;
5918
+ },
5919
+ options: {
5920
+ placement: "top",
5921
+ offset: 8
5922
+ },
5923
+ className: "rounded-lg border bg-background shadow-md",
5924
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_toolbar5.Toolbar, { radius: "md", border: "none", className: "p-1", children: [
5925
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_toolbar5.ToolbarGroup, { children: [
5926
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(MarkButton, { type: "bold" }),
5927
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(MarkButton, { type: "italic" }),
5928
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(MarkButton, { type: "underline" }),
5929
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(MarkButton, { type: "strike" }),
5930
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(MarkButton, { type: "code" })
5931
+ ] }),
5932
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_toolbar5.ToolbarSeparator, {}),
5933
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_toolbar5.ToolbarGroup, { children: [
5934
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ColorHighlightPopover, { colors: resolvedColors }),
5935
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(LinkPopover, { autoOpenOnLinkActive: false })
5936
+ ] })
5937
+ ] })
5938
+ }
5939
+ );
5940
+ }
5941
+
5942
+ // src/ui/copy-anchor-link-button/use-scroll-to-hash.ts
5943
+ var import_editor_utils11 = require("@kopexa/editor-utils");
5944
+ var React10 = __toESM(require("react"));
5945
+
5946
+ // src/hooks/use-floating-toolbar-visibility.ts
5947
+ var import_state6 = require("@tiptap/pm/state");
5948
+ var import_react42 = require("@tiptap/react");
5949
+ var React9 = __toESM(require("react"));
5950
+ var HIDE_FLOATING_META = "hideFloatingToolbar";
5951
+ var selectNodeAndHideFloating = (editor, pos) => {
5952
+ if (!editor) return;
5953
+ const { state, view } = editor;
5954
+ view.dispatch(
5955
+ state.tr.setSelection(import_state6.NodeSelection.create(state.doc, pos)).setMeta(HIDE_FLOATING_META, true)
5956
+ );
5957
+ };
5958
+
5959
+ // src/ui/copy-anchor-link-button/use-scroll-to-hash.ts
5960
+ function useScrollToHash(config = {}) {
5961
+ const {
5962
+ editor: providedEditor,
5963
+ onTargetFound = () => {
5964
+ },
5965
+ onTargetNotFound = () => {
5966
+ }
5967
+ } = config;
5968
+ const { editor } = (0, import_editor_utils11.useTiptapEditor)(providedEditor);
5969
+ const scrollToNode = React10.useCallback(
5970
+ (id) => {
5971
+ var _a, _b, _c;
5972
+ if (!editor) return false;
5973
+ const attributeName = (_c = (_b = (_a = (0, import_editor_utils11.getEditorExtension)(editor, "uniqueID")) == null ? void 0 : _a.options) == null ? void 0 : _b.attributeName) != null ? _c : "data-id";
5974
+ let position = null;
5975
+ editor.state.doc.descendants((node, pos) => {
5976
+ var _a2;
5977
+ if (((_a2 = node.attrs) == null ? void 0 : _a2[attributeName]) === id) {
5978
+ position = pos;
5979
+ return false;
5980
+ }
5981
+ return true;
5982
+ });
5983
+ if (position === null) return false;
5984
+ selectNodeAndHideFloating(editor, position);
5985
+ setTimeout(() => {
5986
+ let dom = null;
5987
+ if (typeof position === "number") {
5988
+ dom = editor.view.nodeDOM(position);
5989
+ }
5990
+ if (dom) {
5991
+ dom.scrollIntoView({ behavior: "smooth", block: "center" });
5992
+ }
5993
+ }, 0);
5994
+ return true;
5995
+ },
5996
+ [editor]
5997
+ );
5998
+ const handleScroll = React10.useCallback(
5999
+ (delay = 0) => {
6000
+ var _a;
6001
+ const hash = (_a = window.location.hash) == null ? void 0 : _a.substring(1);
6002
+ if (!hash) return;
6003
+ setTimeout(() => {
6004
+ if (scrollToNode(hash)) {
6005
+ onTargetFound(hash);
6006
+ } else {
6007
+ onTargetNotFound(hash);
6008
+ }
6009
+ }, delay);
6010
+ },
6011
+ [scrollToNode, onTargetFound, onTargetNotFound]
6012
+ );
6013
+ React10.useEffect(() => {
6014
+ var _a, _b;
6015
+ if (!editor) return;
6016
+ const provider = (_b = (_a = editor.extensionManager.extensions.find(
6017
+ (ext) => ext.name === "collaborationCaret"
6018
+ )) == null ? void 0 : _a.options) == null ? void 0 : _b.provider;
6019
+ if (provider == null ? void 0 : provider.on) {
6020
+ const syncHandler = () => handleScroll(500);
6021
+ provider.on("synced", syncHandler);
6022
+ return () => {
6023
+ var _a2;
6024
+ (_a2 = provider.off) == null ? void 0 : _a2.call(provider, "synced", syncHandler);
6025
+ };
6026
+ } else {
6027
+ handleScroll(500);
6028
+ }
6029
+ }, [editor, handleScroll]);
6030
+ React10.useEffect(() => {
6031
+ const immediateScroll = () => handleScroll();
6032
+ const delayedScroll = () => handleScroll(500);
6033
+ window.addEventListener("hashchange", immediateScroll);
6034
+ window.addEventListener("pageshow", delayedScroll);
6035
+ window.addEventListener("popstate", immediateScroll);
6036
+ return () => {
6037
+ window.removeEventListener("hashchange", immediateScroll);
6038
+ window.removeEventListener("pageshow", delayedScroll);
6039
+ window.removeEventListener("popstate", immediateScroll);
6040
+ };
6041
+ }, [handleScroll]);
6042
+ return { scrollToHash: scrollToNode };
6043
+ }
6044
+
6045
+ // src/ui/link-bubble/index.tsx
6046
+ var import_button10 = require("@kopexa/button");
6047
+ var import_icons13 = require("@kopexa/icons");
6048
+ var import_input4 = require("@kopexa/input");
6049
+ var import_menus2 = require("@tiptap/react/menus");
6050
+ var import_react43 = require("react");
6051
+ var import_react_intl18 = require("react-intl");
6052
+ var import_jsx_runtime21 = require("react/jsx-runtime");
6053
+ function LinkBubble({ editor }) {
6054
+ const intl = (0, import_react_intl18.useIntl)();
6055
+ const [isEditing, setIsEditing] = (0, import_react43.useState)(false);
6056
+ const [url, setUrl] = (0, import_react43.useState)("");
6057
+ const getCurrentUrl = (0, import_react43.useCallback)(() => {
6058
+ if (!editor) return "";
6059
+ const attrs = editor.getAttributes("link");
6060
+ return attrs.href || "";
6061
+ }, [editor]);
6062
+ (0, import_react43.useEffect)(() => {
6063
+ const isLinkActive2 = editor == null ? void 0 : editor.isActive("link");
6064
+ if (isLinkActive2) {
6065
+ setUrl(getCurrentUrl());
6066
+ setIsEditing(false);
6067
+ }
6068
+ }, [editor, getCurrentUrl]);
6069
+ const handleOpenLink = (0, import_react43.useCallback)(() => {
6070
+ const href = getCurrentUrl();
6071
+ if (href) {
6072
+ window.open(href, "_blank", "noopener,noreferrer");
6073
+ }
6074
+ }, [getCurrentUrl]);
6075
+ const handleRemoveLink = (0, import_react43.useCallback)(() => {
6076
+ editor == null ? void 0 : editor.chain().focus().unsetLink().run();
6077
+ }, [editor]);
6078
+ const handleEdit = (0, import_react43.useCallback)(() => {
6079
+ setUrl(getCurrentUrl());
6080
+ setIsEditing(true);
6081
+ }, [getCurrentUrl]);
6082
+ const handleSave = (0, import_react43.useCallback)(() => {
6083
+ if (url) {
6084
+ editor == null ? void 0 : editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
6085
+ } else {
6086
+ editor == null ? void 0 : editor.chain().focus().unsetLink().run();
6087
+ }
6088
+ setIsEditing(false);
6089
+ }, [editor, url]);
6090
+ const handleKeyDown = (0, import_react43.useCallback)(
6091
+ (e) => {
6092
+ if (e.key === "Enter") {
6093
+ e.preventDefault();
6094
+ handleSave();
6095
+ } else if (e.key === "Escape") {
6096
+ e.preventDefault();
6097
+ setIsEditing(false);
6098
+ setUrl(getCurrentUrl());
6099
+ }
6100
+ },
6101
+ [handleSave, getCurrentUrl]
6102
+ );
6103
+ if (!editor) {
6104
+ return null;
6105
+ }
6106
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6107
+ import_menus2.BubbleMenu,
6108
+ {
6109
+ editor,
6110
+ pluginKey: "linkBubbleMenu",
6111
+ shouldShow: ({ editor: e, view }) => {
6112
+ if (!view.hasFocus()) return false;
6113
+ return e.isActive("link") && e.isEditable;
6114
+ },
6115
+ options: {
6116
+ placement: "bottom-start",
6117
+ offset: 8
6118
+ },
6119
+ className: "rounded-lg border bg-background shadow-md",
6120
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "flex items-center gap-1 p-1.5 min-w-[280px]", children: isEditing ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
6121
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6122
+ import_input4.Input,
6123
+ {
6124
+ type: "url",
6125
+ value: url,
6126
+ onChange: (e) => setUrl(e.target.value),
6127
+ onKeyDown: handleKeyDown,
6128
+ placeholder: intl.formatMessage(messages7.link_placeholder),
6129
+ className: "flex-1 h-8 text-sm",
6130
+ autoFocus: true
6131
+ }
6132
+ ),
6133
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6134
+ import_button10.IconButton,
6135
+ {
6136
+ type: "button",
6137
+ size: "sm",
6138
+ variant: "ghost",
6139
+ onClick: handleSave,
6140
+ "aria-label": intl.formatMessage(messages7.link_save),
6141
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_icons13.EditIcon, { className: "size-4" })
6142
+ }
6143
+ )
6144
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
6145
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6146
+ "a",
6147
+ {
6148
+ href: getCurrentUrl(),
6149
+ target: "_blank",
6150
+ rel: "noopener noreferrer",
6151
+ className: "flex-1 text-sm text-primary truncate max-w-[200px] hover:underline px-2",
6152
+ onClick: (e) => {
6153
+ e.preventDefault();
6154
+ handleOpenLink();
6155
+ },
6156
+ children: getCurrentUrl()
6157
+ }
6158
+ ),
6159
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-0.5 border-l pl-1 ml-1", children: [
6160
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6161
+ import_button10.IconButton,
6162
+ {
6163
+ type: "button",
6164
+ size: "sm",
6165
+ variant: "ghost",
6166
+ onClick: handleOpenLink,
6167
+ "aria-label": intl.formatMessage(messages7.link_open),
6168
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_icons13.ExternalLinkIcon, { className: "size-4" })
6169
+ }
6170
+ ),
6171
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6172
+ import_button10.IconButton,
6173
+ {
6174
+ type: "button",
6175
+ size: "sm",
6176
+ variant: "ghost",
6177
+ onClick: handleEdit,
6178
+ "aria-label": intl.formatMessage(messages7.link_edit),
6179
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_icons13.EditIcon, { className: "size-4" })
6180
+ }
6181
+ ),
6182
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6183
+ import_button10.IconButton,
6184
+ {
6185
+ type: "button",
6186
+ size: "sm",
6187
+ variant: "ghost",
6188
+ onClick: handleRemoveLink,
6189
+ "aria-label": intl.formatMessage(messages7.link_remove),
6190
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_icons13.TrashIcon, { className: "size-4" })
6191
+ }
6192
+ )
6193
+ ] })
6194
+ ] }) })
6195
+ }
6196
+ );
6197
+ }
6198
+
6199
+ // src/ui/slash-dropdown-menu/slash-dropdown-menu.tsx
6200
+ var import_button11 = require("@kopexa/button");
6201
+ var import_editor_utils14 = require("@kopexa/editor-utils");
6202
+ var import_separator = require("@kopexa/separator");
6203
+ var import_theme8 = require("@kopexa/theme");
6204
+ var React12 = __toESM(require("react"));
6205
+
6206
+ // src/ui/slash-dropdown-menu/use-slash-dropdown-menu.ts
6207
+ var import_editor_utils13 = require("@kopexa/editor-utils");
6208
+ var import_icons15 = require("@kopexa/icons");
6209
+ var React11 = __toESM(require("react"));
6210
+ var import_react_intl20 = require("react-intl");
6211
+
6212
+ // src/ui/table-button/use-table.ts
6213
+ var import_editor_utils12 = require("@kopexa/editor-utils");
6214
+ var import_icons14 = require("@kopexa/icons");
6215
+ var import_react44 = require("@tiptap/react");
6216
+ var import_react45 = require("react");
6217
+ var import_react_intl19 = require("react-intl");
6218
+ function canToggle(editor) {
6219
+ if (!editor || !editor.isEditable) return false;
6220
+ if (!(0, import_editor_utils12.isNodeInSchema)("table", editor) || (0, import_editor_utils12.isNodeTypeSelected)(editor, ["image"])) {
6221
+ return false;
6222
+ }
6223
+ try {
6224
+ return editor.can().insertTable({ rows: 3, cols: 3, withHeaderRow: true });
6225
+ } catch {
6226
+ return false;
6227
+ }
6228
+ }
6229
+ function toggleTable(editor, config) {
6230
+ var _a;
6231
+ if (!editor || !editor.isEditable) return false;
6232
+ if (!canToggle(editor)) return false;
6233
+ try {
6234
+ return editor.chain().focus().insertTable({
6235
+ rows: (config == null ? void 0 : config.rows) || 3,
6236
+ cols: (config == null ? void 0 : config.cols) || 3,
6237
+ withHeaderRow: (_a = config == null ? void 0 : config.withHeaderRow) != null ? _a : true
6238
+ }).run();
6239
+ } catch {
6240
+ return false;
6241
+ }
6242
+ }
6243
+ function shouldShowButton2(props) {
6244
+ const { editor, hideWhenUnavailable } = props;
6245
+ if (!editor || !editor.isEditable) return false;
6246
+ if (!(0, import_editor_utils12.isNodeInSchema)("table", editor)) return false;
6247
+ if (hideWhenUnavailable) {
6248
+ if ((0, import_react44.isNodeSelection)(editor.state.selection) || !canToggle) {
6249
+ return false;
6250
+ }
6251
+ }
6252
+ return true;
6253
+ }
6254
+ function useTableBlock(config) {
6255
+ const {
6256
+ editor: providedEditor,
6257
+ hideWhenUnavailable = false,
6258
+ onToggled
6259
+ } = config || {};
6260
+ const intl = (0, import_react_intl19.useIntl)();
6261
+ const { editor } = (0, import_editor_utils12.useTiptapEditor)(providedEditor);
6262
+ const [isVisible, setIsVisible] = (0, import_react45.useState)(true);
6263
+ const canToggleState = canToggle(editor);
6264
+ const isActive = (editor == null ? void 0 : editor.isActive("table")) || false;
6265
+ (0, import_react45.useEffect)(() => {
6266
+ if (!editor) return;
6267
+ const handleSelectionUpdate = () => {
6268
+ setIsVisible(shouldShowButton2({ editor, hideWhenUnavailable }));
6269
+ };
6270
+ handleSelectionUpdate();
6271
+ editor.on("selectionUpdate", handleSelectionUpdate);
6272
+ return () => {
6273
+ editor.off("selectionUpdate", handleSelectionUpdate);
6274
+ };
6275
+ }, [editor, hideWhenUnavailable]);
6276
+ const handleToggle = (0, import_react45.useCallback)(() => {
6277
+ if (!editor) return false;
6278
+ const success = toggleTable(editor);
6279
+ if (success) {
6280
+ onToggled == null ? void 0 : onToggled();
6281
+ }
6282
+ return success;
6283
+ }, [editor, onToggled]);
6284
+ return {
6285
+ isVisible,
6286
+ isActive,
6287
+ handleToggle,
6288
+ canToggle: canToggleState,
6289
+ label: intl.formatMessage(messages7.table_insert),
6290
+ // shortcutKeys: CODE_BLOCK_SHORTCUT_KEY,
6291
+ Icon: import_icons14.TableIcon
6292
+ };
6293
+ }
6294
+
6295
+ // src/ui/slash-dropdown-menu/use-slash-dropdown-menu.ts
6296
+ function createSlashMenuTexts(formatMessage) {
6297
+ return {
6298
+ // AI
6299
+ continue_writing: {
6300
+ title: formatMessage(messages7.slash_continue_writing),
6301
+ subtext: formatMessage(messages7.slash_continue_writing_subtext),
6302
+ keywords: ["continue", "write", "continue writing", "ai"],
6303
+ badge: import_icons15.AiSparklesIcon,
6304
+ group: formatMessage(messages7.group_ai)
6305
+ },
6306
+ ai_ask_button: {
6307
+ title: formatMessage(messages7.slash_ask_ai),
6308
+ subtext: formatMessage(messages7.slash_ask_ai_subtext),
6309
+ keywords: ["ai", "ask", "generate"],
6310
+ badge: import_icons15.AiSparklesIcon,
6311
+ group: formatMessage(messages7.group_ai)
6312
+ },
6313
+ // Style
6314
+ text: {
6315
+ title: formatMessage(messages7.slash_text),
6316
+ subtext: formatMessage(messages7.slash_text_subtext),
6317
+ keywords: ["p", "paragraph", "text"],
6318
+ badge: import_icons15.TypeIcon,
6319
+ group: formatMessage(messages7.group_style)
6320
+ },
6321
+ heading_1: {
6322
+ title: formatMessage(messages7.slash_heading_1),
6323
+ subtext: formatMessage(messages7.slash_heading_1_subtext),
6324
+ keywords: ["h", "heading1", "h1"],
6325
+ badge: import_icons15.HeadingOneIcon,
6326
+ group: formatMessage(messages7.group_style)
6327
+ },
6328
+ heading_2: {
6329
+ title: formatMessage(messages7.slash_heading_2),
6330
+ subtext: formatMessage(messages7.slash_heading_2_subtext),
6331
+ keywords: ["h2", "heading2", "subheading"],
6332
+ badge: import_icons15.HeadingTwoIcon,
6333
+ group: formatMessage(messages7.group_style)
6334
+ },
6335
+ heading_3: {
6336
+ title: formatMessage(messages7.slash_heading_3),
6337
+ subtext: formatMessage(messages7.slash_heading_3_subtext),
6338
+ keywords: ["h3", "heading3", "subheading"],
6339
+ badge: import_icons15.HeadingThreeIcon,
6340
+ group: formatMessage(messages7.group_style)
6341
+ },
6342
+ bullet_list: {
6343
+ title: formatMessage(messages7.slash_bullet_list),
6344
+ subtext: formatMessage(messages7.slash_bullet_list_subtext),
6345
+ keywords: ["ul", "li", "list", "bulletlist", "bullet list"],
6346
+ badge: import_icons15.ListIcon,
6347
+ group: formatMessage(messages7.group_style)
6348
+ },
6349
+ ordered_list: {
6350
+ title: formatMessage(messages7.slash_ordered_list),
6351
+ subtext: formatMessage(messages7.slash_ordered_list_subtext),
6352
+ keywords: ["ol", "li", "list", "numberedlist", "numbered list"],
6353
+ badge: import_icons15.ListOrderedIcon,
6354
+ group: formatMessage(messages7.group_style)
6355
+ },
6356
+ task_list: {
6357
+ title: formatMessage(messages7.slash_task_list),
6358
+ subtext: formatMessage(messages7.slash_task_list_subtext),
6359
+ keywords: ["tasklist", "task list", "todo", "checklist"],
6360
+ badge: import_icons15.ListTodoIcon,
6361
+ group: formatMessage(messages7.group_style)
6362
+ },
6363
+ quote: {
6364
+ title: formatMessage(messages7.slash_blockquote),
6365
+ subtext: formatMessage(messages7.slash_blockquote_subtext),
6366
+ keywords: ["quote", "blockquote"],
6367
+ badge: import_icons15.BlockquoteIcon,
6368
+ group: formatMessage(messages7.group_style)
6369
+ },
6370
+ code_block: {
6371
+ title: formatMessage(messages7.slash_code_block),
6372
+ subtext: formatMessage(messages7.slash_code_block_subtext),
6373
+ keywords: ["code", "pre"],
6374
+ badge: import_icons15.CodeBlockIcon,
6375
+ group: formatMessage(messages7.group_style)
6376
+ },
6377
+ // Insert
6378
+ control: {
6379
+ title: formatMessage(messages7.slash_control),
6380
+ subtext: formatMessage(messages7.slash_control_subtext),
6381
+ keywords: ["control"],
6382
+ badge: import_icons15.ControlsIcon,
6383
+ group: formatMessage(messages7.group_insert)
6384
+ },
6385
+ divider: {
6386
+ title: formatMessage(messages7.slash_separator),
6387
+ subtext: formatMessage(messages7.slash_separator_subtext),
6388
+ keywords: ["hr", "horizontalRule", "line", "separator"],
6389
+ badge: import_icons15.MinusIcon,
6390
+ group: formatMessage(messages7.group_insert)
6391
+ },
6392
+ table: {
6393
+ title: formatMessage(messages7.slash_table),
6394
+ subtext: formatMessage(messages7.slash_table_subtext),
6395
+ keywords: ["table", "grid", "spreadsheet"],
6396
+ badge: import_icons15.TableIcon,
6397
+ group: formatMessage(messages7.group_insert)
6398
+ },
6399
+ table_of_contents: {
6400
+ title: formatMessage(messages7.slash_toc),
6401
+ subtext: formatMessage(messages7.slash_toc_subtext),
6402
+ keywords: ["toc", "table of contents", "index", "navigation", "headings"],
6403
+ badge: import_icons15.TableOfContentsIcon,
6404
+ group: formatMessage(messages7.group_insert)
6405
+ },
6406
+ callout: {
6407
+ title: formatMessage(messages7.slash_callout),
6408
+ subtext: formatMessage(messages7.slash_callout_subtext),
6409
+ keywords: ["callout", "info", "warning", "alert", "note", "tip"],
6410
+ badge: import_icons15.InfoIcon,
6411
+ group: formatMessage(messages7.group_insert)
6412
+ },
6413
+ callout_warning: {
6414
+ title: formatMessage(messages7.slash_warning),
6415
+ subtext: formatMessage(messages7.slash_warning_subtext),
6416
+ keywords: ["warning", "caution", "attention"],
6417
+ badge: import_icons15.AlertIcon,
6418
+ group: formatMessage(messages7.group_insert)
6419
+ },
6420
+ math: {
6421
+ title: formatMessage(messages7.slash_formula),
6422
+ subtext: formatMessage(messages7.slash_formula_subtext),
6423
+ keywords: ["math", "latex", "formula", "equation", "katex"],
6424
+ badge: import_icons15.TypeIcon,
6425
+ group: formatMessage(messages7.group_insert)
6426
+ },
6427
+ // Upload
6428
+ image: {
6429
+ title: formatMessage(messages7.slash_image),
6430
+ subtext: formatMessage(messages7.slash_image_subtext),
6431
+ keywords: [
6432
+ "image",
6433
+ "imageUpload",
6434
+ "upload",
6435
+ "img",
6436
+ "picture",
6437
+ "media",
6438
+ "url"
6439
+ ],
6440
+ badge: import_icons15.ImageIcon,
6441
+ group: formatMessage(messages7.group_upload)
6442
+ }
6443
+ };
6444
+ }
6445
+ var getItemImplementations = () => {
6446
+ return {
6447
+ // AI
6448
+ continue_writing: {
6449
+ check: (editor) => {
6450
+ const { hasContent } = (0, import_editor_utils13.hasContentAbove)(editor);
6451
+ const extensionsReady = (0, import_editor_utils13.isExtensionAvailable)(editor, [
6452
+ "ai",
6453
+ "aiAdvanced"
6454
+ ]);
6455
+ return extensionsReady && hasContent;
6456
+ },
6457
+ action: ({ editor }) => {
6458
+ const editorChain = editor.chain().focus();
6459
+ const nodeSelectionPosition = (0, import_editor_utils13.findSelectionPosition)({ editor });
6460
+ if (nodeSelectionPosition !== null) {
6461
+ editorChain.setNodeSelection(nodeSelectionPosition);
6462
+ }
6463
+ editorChain.run();
6464
+ editor.chain().focus().aiGenerationShow().run();
6465
+ requestAnimationFrame(() => {
6466
+ const { hasContent, content } = (0, import_editor_utils13.hasContentAbove)(editor);
6467
+ const snippet = content.length > 500 ? `...${content.slice(-500)}` : content;
6468
+ const prompt = hasContent ? `Context: ${snippet}
6469
+
6470
+ Continue writing from where the text above ends. Write ONLY ONE SENTENCE. DONT REPEAT THE TEXT.` : "Start writing a new paragraph. Write ONLY ONE SENTENCE.";
6471
+ editor.chain().focus().aiTextPrompt({
6472
+ stream: true,
6473
+ format: "rich-text",
6474
+ text: prompt
6475
+ }).run();
6476
+ });
6477
+ }
6478
+ },
6479
+ ai_ask_button: {
6480
+ check: (editor) => (0, import_editor_utils13.isExtensionAvailable)(editor, ["ai", "aiAdvanced"]),
6481
+ action: ({ editor }) => {
6482
+ const editorChain = editor.chain().focus();
6483
+ const nodeSelectionPosition = (0, import_editor_utils13.findSelectionPosition)({ editor });
6484
+ if (nodeSelectionPosition !== null) {
6485
+ editorChain.setNodeSelection(nodeSelectionPosition);
6486
+ }
6487
+ editorChain.run();
6488
+ editor.chain().focus().aiGenerationShow().run();
6489
+ }
6490
+ },
6491
+ // Style
6492
+ text: {
6493
+ check: (editor) => (0, import_editor_utils13.isNodeInSchema)("paragraph", editor),
6494
+ action: ({ editor }) => {
6495
+ editor.chain().focus().setParagraph().run();
6496
+ }
6497
+ },
6498
+ heading_1: {
6499
+ check: (editor) => (0, import_editor_utils13.isNodeInSchema)("heading", editor),
6500
+ action: ({ editor }) => {
6501
+ editor.chain().focus().toggleHeading({ level: 1 }).run();
6502
+ }
6503
+ },
6504
+ heading_2: {
6505
+ check: (editor) => (0, import_editor_utils13.isNodeInSchema)("heading", editor),
6506
+ action: ({ editor }) => {
6507
+ editor.chain().focus().toggleHeading({ level: 2 }).run();
6508
+ }
6509
+ },
6510
+ heading_3: {
6511
+ check: (editor) => (0, import_editor_utils13.isNodeInSchema)("heading", editor),
6512
+ action: ({ editor }) => {
6513
+ editor.chain().focus().toggleHeading({ level: 3 }).run();
6514
+ }
6515
+ },
6516
+ bullet_list: {
6517
+ check: (editor) => (0, import_editor_utils13.isNodeInSchema)("bulletList", editor),
6518
+ action: ({ editor }) => {
6519
+ editor.chain().focus().toggleBulletList().run();
6520
+ }
6521
+ },
6522
+ ordered_list: {
6523
+ check: (editor) => (0, import_editor_utils13.isNodeInSchema)("orderedList", editor),
6524
+ action: ({ editor }) => {
6525
+ editor.chain().focus().toggleOrderedList().run();
6526
+ }
6527
+ },
6528
+ task_list: {
6529
+ check: (editor) => (0, import_editor_utils13.isNodeInSchema)("taskList", editor),
6530
+ action: ({ editor }) => {
6531
+ editor.chain().focus().toggleTaskList().run();
6532
+ }
6533
+ },
6534
+ quote: {
6535
+ check: (editor) => (0, import_editor_utils13.isNodeInSchema)("blockquote", editor),
6536
+ action: ({ editor }) => {
6537
+ editor.chain().focus().toggleBlockquote().run();
6538
+ }
6539
+ },
6540
+ code_block: {
6541
+ check: (editor) => (0, import_editor_utils13.isNodeInSchema)("codeBlock", editor),
6542
+ action: ({ editor }) => {
6543
+ editor.chain().focus().toggleNode("codeBlock", "paragraph").run();
6544
+ }
6545
+ },
6546
+ // Insert
6547
+ // mention: {
6548
+ // check: (editor: Editor) =>
6549
+ // isExtensionAvailable(editor, ["mention", "mentionAdvanced"]),
6550
+ // action: ({ editor }: { editor: Editor }) => addMentionTrigger(editor),
6551
+ // },
6552
+ // emoji: {
6553
+ // check: (editor: Editor) =>
6554
+ // isExtensionAvailable(editor, ["emoji", "emojiPicker"]),
6555
+ // action: ({ editor }: { editor: Editor }) => addEmojiTrigger(editor),
6556
+ // },
6557
+ divider: {
6558
+ check: (editor) => (0, import_editor_utils13.isNodeInSchema)("horizontalRule", editor),
6559
+ action: ({ editor }) => {
6560
+ editor.chain().focus().setHorizontalRule().run();
6561
+ }
6562
+ },
6563
+ table: {
6564
+ check: (editor) => (0, import_editor_utils13.isNodeInSchema)("table", editor),
6565
+ action: ({ editor }) => toggleTable(editor, { rows: 3, cols: 3, withHeaderRow: true })
6566
+ },
6567
+ control: {
6568
+ check: (editor) => (0, import_editor_utils13.isNodeInSchema)("controlBlock", editor),
6569
+ action: ({ editor }) => {
6570
+ try {
6571
+ return editor.chain().focus().insertControlBlock().run();
6572
+ } catch (e) {
6573
+ console.error(e);
6574
+ }
6575
+ }
6576
+ },
6577
+ table_of_contents: {
6578
+ check: (editor) => (0, import_editor_utils13.isNodeInSchema)("tableOfContentsNode", editor),
6579
+ action: ({ editor }) => {
6580
+ editor.chain().focus().insertTableOfContents().run();
6581
+ }
6582
+ },
6583
+ callout: {
6584
+ check: (editor) => (0, import_editor_utils13.isNodeInSchema)("calloutNode", editor),
6585
+ action: ({ editor }) => {
6586
+ editor.chain().focus().insertCallout("info").run();
6660
6587
  }
6661
- }
6662
- setTimeout(() => {
6663
- const success = editor.chain().focus().toggleMark("highlight", { color: highlightColor }).run();
6664
- if (success) {
6665
- onApplied == null ? void 0 : onApplied({ color: highlightColor, label });
6588
+ },
6589
+ callout_warning: {
6590
+ check: (editor) => (0, import_editor_utils13.isNodeInSchema)("calloutNode", editor),
6591
+ action: ({ editor }) => {
6592
+ editor.chain().focus().insertCallout("warning").run();
6666
6593
  }
6667
- return success;
6668
- }, 0);
6669
- }, [canColorHighlightState, highlightColor, editor, label, onApplied]);
6670
- const handleRemoveHighlight = React14.useCallback(() => {
6671
- const success = removeHighlight(editor);
6672
- if (success) {
6673
- onApplied == null ? void 0 : onApplied({ color: "", label: "Remove highlight" });
6674
- }
6675
- return success;
6676
- }, [editor, onApplied]);
6677
- (0, import_react_hotkeys_hook.useHotkeys)(
6678
- COLOR_HIGHLIGHT_SHORTCUT_KEY,
6679
- (event) => {
6680
- event.preventDefault();
6681
- handleColorHighlight();
6682
6594
  },
6683
- {
6684
- enabled: isVisible && canColorHighlightState,
6685
- enableOnContentEditable: !isMobile,
6686
- enableOnFormTags: true
6595
+ math: {
6596
+ check: (editor) => (0, import_editor_utils13.isNodeInSchema)("mathBlock", editor),
6597
+ action: ({ editor }) => {
6598
+ editor.chain().focus().insertMathBlock().run();
6599
+ }
6600
+ },
6601
+ // Upload
6602
+ image: {
6603
+ check: (editor) => (0, import_editor_utils13.isNodeInSchema)("imageUpload", editor),
6604
+ action: ({ editor }) => {
6605
+ editor.chain().focus().setImageUpload().run();
6606
+ }
6607
+ }
6608
+ };
6609
+ };
6610
+ function organizeItemsByGroups(items, showGroups) {
6611
+ if (!showGroups) {
6612
+ return items.map((item) => ({ ...item, group: "" }));
6613
+ }
6614
+ const groups = {};
6615
+ items.forEach((item) => {
6616
+ const groupLabel = item.group || "";
6617
+ if (!groups[groupLabel]) {
6618
+ groups[groupLabel] = [];
6687
6619
  }
6620
+ groups[groupLabel].push(item);
6621
+ });
6622
+ const organizedItems = [];
6623
+ Object.entries(groups).forEach(([, groupItems]) => {
6624
+ organizedItems.push(...groupItems);
6625
+ });
6626
+ return organizedItems;
6627
+ }
6628
+ var ALL_SLASH_MENU_ITEMS = [
6629
+ "continue_writing",
6630
+ "ai_ask_button",
6631
+ "text",
6632
+ "heading_1",
6633
+ "heading_2",
6634
+ "heading_3",
6635
+ "bullet_list",
6636
+ "ordered_list",
6637
+ "task_list",
6638
+ "quote",
6639
+ "code_block",
6640
+ "control",
6641
+ "divider",
6642
+ "table",
6643
+ "table_of_contents",
6644
+ "callout",
6645
+ "callout_warning",
6646
+ "math",
6647
+ "image"
6648
+ ];
6649
+ function useSlashDropdownMenu(config) {
6650
+ const intl = (0, import_react_intl20.useIntl)();
6651
+ const getSlashMenuItems = React11.useCallback(
6652
+ (editor) => {
6653
+ const items = [];
6654
+ const texts = createSlashMenuTexts(intl.formatMessage);
6655
+ const enabledItems = (config == null ? void 0 : config.enabledItems) || ALL_SLASH_MENU_ITEMS;
6656
+ const showGroups = (config == null ? void 0 : config.showGroups) !== false;
6657
+ const itemImplementations = getItemImplementations();
6658
+ enabledItems.forEach((itemType) => {
6659
+ var _a;
6660
+ const itemImpl = itemImplementations[itemType];
6661
+ const itemText = texts[itemType];
6662
+ if (itemImpl && itemText && itemImpl.check(editor)) {
6663
+ const item = {
6664
+ onSelect: ({ editor: editor2 }) => itemImpl.action({ editor: editor2 }),
6665
+ ...itemText
6666
+ };
6667
+ if ((_a = config == null ? void 0 : config.itemGroups) == null ? void 0 : _a[itemType]) {
6668
+ item.group = config.itemGroups[itemType];
6669
+ } else if (!showGroups) {
6670
+ item.group = "";
6671
+ }
6672
+ items.push(item);
6673
+ }
6674
+ });
6675
+ if (config == null ? void 0 : config.customItems) {
6676
+ items.push(...config.customItems);
6677
+ }
6678
+ return organizeItemsByGroups(items, showGroups);
6679
+ },
6680
+ [config, intl]
6688
6681
  );
6689
6682
  return {
6690
- isVisible,
6691
- isActive,
6692
- handleColorHighlight,
6693
- handleRemoveHighlight,
6694
- canColorHighlight: canColorHighlightState,
6695
- label: label || intl.formatMessage(messages7.highlight_color),
6696
- shortcutKeys: COLOR_HIGHLIGHT_SHORTCUT_KEY,
6697
- Icon: import_icons14.HighlighterIcon
6683
+ getSlashMenuItems,
6684
+ config
6698
6685
  };
6699
6686
  }
6700
6687
 
6701
- // src/ui/color-highlight-button/color-highlight-button.tsx
6702
- var import_jsx_runtime21 = require("react/jsx-runtime");
6703
- var ColorHighlightButton = ({
6704
- editor: providedEditor,
6705
- highlightColor,
6706
- text,
6707
- hideWhenUnavailable = false,
6708
- onApplied,
6709
- showShortcut = false,
6710
- onClick,
6711
- children,
6712
- style,
6713
- className,
6714
- ...buttonProps
6715
- }) => {
6716
- const { editor } = (0, import_editor_utils13.useTiptapEditor)(providedEditor);
6717
- const {
6718
- isVisible,
6719
- canColorHighlight: canColorHighlight2,
6720
- isActive,
6721
- handleColorHighlight,
6722
- label,
6723
- shortcutKeys
6724
- } = useColorHighlight({
6725
- editor,
6726
- highlightColor,
6727
- label: text || `Toggle highlight (${highlightColor})`,
6728
- hideWhenUnavailable,
6729
- onApplied
6730
- });
6731
- const handleClick = (0, import_react44.useCallback)(
6732
- (event) => {
6733
- onClick == null ? void 0 : onClick(event);
6734
- if (event.defaultPrevented) return;
6735
- handleColorHighlight();
6736
- },
6737
- [handleColorHighlight, onClick]
6688
+ // src/ui/slash-dropdown-menu/slash-dropdown-menu.tsx
6689
+ var import_jsx_runtime22 = require("react/jsx-runtime");
6690
+ var SlashDropdownMenu = (props) => {
6691
+ const { config, ...restProps } = props;
6692
+ const { getSlashMenuItems } = useSlashDropdownMenu(config);
6693
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
6694
+ SuggestionMenu,
6695
+ {
6696
+ char: "/",
6697
+ pluginKey: "slashDropdownMenu",
6698
+ decorationClass: "tiptap-slash-decoration",
6699
+ decorationContent: "Filter...",
6700
+ selector: "tiptap-slash-dropdown-menu",
6701
+ items: ({ query, editor }) => filterSuggestionItems(getSlashMenuItems(editor), query),
6702
+ ...restProps,
6703
+ children: (props2) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(List, { ...props2, config })
6704
+ }
6738
6705
  );
6739
- const buttonStyle = (0, import_react44.useMemo)(
6740
- () => ({
6741
- ...style,
6742
- "--highlight-color": highlightColor
6743
- }),
6744
- [highlightColor, style]
6706
+ };
6707
+ var Item = (props) => {
6708
+ const { item, isSelected, onSelect } = props;
6709
+ const itemRef = React12.useRef(null);
6710
+ React12.useEffect(() => {
6711
+ const selector = document.querySelector(
6712
+ '[data-selector="tiptap-slash-dropdown-menu"]'
6713
+ );
6714
+ if (!itemRef.current || !isSelected || !selector) return;
6715
+ const overflow = (0, import_editor_utils14.getElementOverflowPosition)(itemRef.current, selector);
6716
+ if (overflow === "top") {
6717
+ itemRef.current.scrollIntoView(true);
6718
+ } else if (overflow === "bottom") {
6719
+ itemRef.current.scrollIntoView(false);
6720
+ }
6721
+ }, [isSelected]);
6722
+ const BadgeIcon = item.badge;
6723
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
6724
+ import_button11.Button,
6725
+ {
6726
+ ref: itemRef,
6727
+ variant: "ghost",
6728
+ color: "default",
6729
+ startContent: BadgeIcon && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(BadgeIcon, {}),
6730
+ "data-active-state": isSelected ? "on" : "off",
6731
+ onClick: onSelect,
6732
+ fullWidth: true,
6733
+ spacing: "start",
6734
+ children: item.title
6735
+ }
6745
6736
  );
6746
- if (!isVisible) {
6737
+ };
6738
+ var List = ({
6739
+ items,
6740
+ selectedIndex,
6741
+ onSelect,
6742
+ config
6743
+ }) => {
6744
+ const styles = (0, import_theme8.slashDropdownMenu)();
6745
+ const renderedItems = React12.useMemo(() => {
6746
+ const rendered = [];
6747
+ const showGroups = (config == null ? void 0 : config.showGroups) !== false;
6748
+ if (!showGroups) {
6749
+ items.forEach((item, index) => {
6750
+ rendered.push(
6751
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
6752
+ Item,
6753
+ {
6754
+ item,
6755
+ isSelected: index === selectedIndex,
6756
+ onSelect: () => onSelect(item)
6757
+ },
6758
+ `item-${index}-${item.title}`
6759
+ )
6760
+ );
6761
+ });
6762
+ return rendered;
6763
+ }
6764
+ const groups = {};
6765
+ items.forEach((item, index) => {
6766
+ const groupLabel = item.group || "";
6767
+ if (!groups[groupLabel]) {
6768
+ groups[groupLabel] = { items: [], indices: [] };
6769
+ }
6770
+ groups[groupLabel].items.push(item);
6771
+ groups[groupLabel].indices.push(index);
6772
+ });
6773
+ Object.entries(groups).forEach(([groupLabel, groupData], groupIndex) => {
6774
+ if (groupIndex > 0) {
6775
+ rendered.push(
6776
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
6777
+ import_separator.Separator,
6778
+ {
6779
+ orientation: "horizontal"
6780
+ },
6781
+ `separator-${groupIndex.toString()}`
6782
+ )
6783
+ );
6784
+ }
6785
+ const groupItems = groupData.items.map((item, itemIndex) => {
6786
+ const originalIndex = groupData.indices[itemIndex];
6787
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
6788
+ Item,
6789
+ {
6790
+ item,
6791
+ isSelected: originalIndex === selectedIndex,
6792
+ onSelect: () => onSelect(item)
6793
+ },
6794
+ `item-${originalIndex}-${item.title}`
6795
+ );
6796
+ });
6797
+ if (groupLabel) {
6798
+ rendered.push(
6799
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
6800
+ "div",
6801
+ {
6802
+ className: styles.cardItemGroup(),
6803
+ children: [
6804
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: styles.cardGroupLabel(), children: groupLabel }),
6805
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: styles.cardGroup(), children: groupItems })
6806
+ ]
6807
+ },
6808
+ `group-${groupIndex}-${groupLabel}`
6809
+ )
6810
+ );
6811
+ } else {
6812
+ rendered.push(...groupItems);
6813
+ }
6814
+ });
6815
+ return rendered;
6816
+ }, [items, selectedIndex, onSelect, config == null ? void 0 : config.showGroups, styles]);
6817
+ if (!renderedItems.length) {
6747
6818
  return null;
6748
6819
  }
6749
- const styles = (0, import_theme8.colorHighlightButton)();
6750
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
6751
- import_toolbar4.ToolbarButton,
6820
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
6821
+ "div",
6752
6822
  {
6753
- type: "button",
6754
- disabled: !canColorHighlight2,
6755
- "data-disabled": !canColorHighlight2,
6756
- variant: "ghost",
6757
- color: "default",
6758
- "data-active-state": isActive ? "on" : "off",
6759
- tabIndex: -1,
6760
- "aria-label": label,
6761
- shortcutKeys,
6762
- "aria-pressed": isActive,
6763
- onClick: handleClick,
6764
- style: buttonStyle,
6765
- className: styles.button({ className }),
6766
- isIconOnly: true,
6767
- ...buttonProps,
6768
- children: [
6769
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6770
- "span",
6771
- {
6772
- "data-active-state": isActive ? "on" : "off",
6773
- className: styles.mark()
6774
- }
6775
- ),
6776
- children || /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
6777
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6778
- "span",
6779
- {
6780
- style: { "--highlight-color": highlightColor }
6781
- }
6782
- ),
6783
- text
6784
- ] })
6785
- ]
6823
+ className: styles.card(),
6824
+ style: {
6825
+ maxHeight: "var(--suggestion-menu-max-height)"
6826
+ },
6827
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: styles.body(), children: renderedItems })
6786
6828
  }
6787
6829
  );
6788
6830
  };
6789
6831
 
6790
- // src/ui/color-highlight-popover/color-highlight-popover.tsx
6791
- var import_jsx_runtime22 = require("react/jsx-runtime");
6792
- var ColorHighlightPopoverButton = ({
6793
- className,
6794
- children,
6795
- ...props
6796
- }) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
6797
- import_button11.IconButton,
6798
- {
6799
- type: "button",
6800
- className,
6801
- variant: "ghost",
6802
- color: "default",
6803
- tabIndex: -1,
6804
- "aria-label": "Highlight text",
6805
- tooltip: "Highlight",
6806
- isIconOnly: !children,
6807
- ...props,
6808
- children: children != null ? children : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_icons15.HighlighterIcon, {})
6809
- }
6810
- );
6811
- function ColorHighlightPopoverContent({
6812
- editor,
6813
- colors = pickHighlightColorsByValue([
6814
- "var(--tt-color-highlight-green)",
6815
- "var(--tt-color-highlight-blue)",
6816
- "var(--tt-color-highlight-red)",
6817
- "var(--tt-color-highlight-purple)",
6818
- "var(--tt-color-highlight-yellow)"
6819
- ])
6820
- }) {
6821
- const { handleRemoveHighlight } = useColorHighlight({ editor });
6822
- const containerRef = (0, import_react45.useRef)(null);
6823
- const menuItems = (0, import_react45.useMemo)(
6824
- () => [...colors, { label: "Remove highlight", value: "none" }],
6825
- [colors]
6826
- );
6827
- const { selectedIndex } = useMenuNavigation({
6828
- containerRef,
6829
- items: menuItems,
6830
- orientation: "both",
6831
- onSelect: (item) => {
6832
- if (!containerRef.current) return false;
6833
- const highlightedElement = containerRef.current.querySelector(
6834
- '[data-highlighted="true"]'
6835
- );
6836
- if (highlightedElement) highlightedElement.click();
6837
- if (item.value === "none") handleRemoveHighlight();
6838
- },
6839
- autoSelectFirstItem: false
6832
+ // src/presets/basic/editor-header.tsx
6833
+ var import_editor_utils31 = require("@kopexa/editor-utils");
6834
+ var import_icons26 = require("@kopexa/icons");
6835
+ var import_popover3 = require("@kopexa/popover");
6836
+ var import_toolbar10 = require("@kopexa/toolbar");
6837
+ var import_use_is_mobile3 = require("@kopexa/use-is-mobile");
6838
+ var import_react61 = require("react");
6839
+ var import_react_intl25 = require("react-intl");
6840
+
6841
+ // src/hooks/use-cursor-visibility.ts
6842
+ var React14 = __toESM(require("react"));
6843
+
6844
+ // src/hooks/use-window-size.ts
6845
+ var React13 = __toESM(require("react"));
6846
+ function useWindowSize() {
6847
+ const [windowSize, setWindowSize] = React13.useState({
6848
+ width: 0,
6849
+ height: 0,
6850
+ offsetTop: 0
6840
6851
  });
6841
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { ref: containerRef, className: "flex gap-1 items-center", children: [
6842
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
6843
- "div",
6844
- {
6845
- className: "flex items-center gap-1 outline-none",
6846
- "data-orientation": "horizontal",
6847
- children: colors.map((color, index) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
6848
- ColorHighlightButton,
6849
- {
6850
- editor,
6851
- highlightColor: color.value,
6852
- "aria-label": `${color.label} highlight color`,
6853
- tabIndex: index === selectedIndex ? 0 : -1,
6854
- "data-highlighted": selectedIndex === index
6855
- },
6856
- color.value
6857
- ))
6858
- }
6859
- ),
6860
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_toolbar5.ToolbarSeparator, { orientation: "vertical" }),
6861
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "tiptap-button-group", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
6862
- import_button11.IconButton,
6863
- {
6864
- onClick: handleRemoveHighlight,
6865
- "aria-label": "Remove highlight",
6866
- tabIndex: selectedIndex === colors.length ? 0 : -1,
6867
- type: "button",
6868
- role: "menuitem",
6869
- variant: "ghost",
6870
- color: "default",
6871
- "data-highlighted": selectedIndex === colors.length,
6872
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_icons15.BanIcon, {})
6852
+ React13.useEffect(() => {
6853
+ handleResize();
6854
+ function handleResize() {
6855
+ if (typeof window === "undefined") return;
6856
+ const vp = window.visualViewport;
6857
+ if (!vp) return;
6858
+ const { width = 0, height = 0, offsetTop = 0 } = vp;
6859
+ setWindowSize((state) => {
6860
+ if (width === state.width && height === state.height && offsetTop === state.offsetTop) {
6861
+ return state;
6862
+ }
6863
+ return { width, height, offsetTop };
6864
+ });
6865
+ }
6866
+ const visualViewport = window.visualViewport;
6867
+ if (visualViewport) {
6868
+ visualViewport.addEventListener("resize", handleResize);
6869
+ visualViewport.addEventListener("scroll", handleResize);
6870
+ }
6871
+ return () => {
6872
+ if (visualViewport) {
6873
+ visualViewport.removeEventListener("resize", handleResize);
6874
+ visualViewport.removeEventListener("scroll", handleResize);
6873
6875
  }
6874
- ) })
6875
- ] });
6876
+ };
6877
+ }, []);
6878
+ return windowSize;
6876
6879
  }
6877
- function ColorHighlightPopover({
6878
- editor: providedEditor,
6879
- colors = pickHighlightColorsByValue([
6880
- "var(--tt-color-highlight-green)",
6881
- "var(--tt-color-highlight-blue)",
6882
- "var(--tt-color-highlight-red)",
6883
- "var(--tt-color-highlight-purple)",
6884
- "var(--tt-color-highlight-yellow)"
6885
- ]),
6886
- hideWhenUnavailable = false,
6887
- onApplied,
6888
- ...props
6880
+
6881
+ // src/hooks/use-cursor-visibility.ts
6882
+ function useCursorVisibility({
6883
+ editor,
6884
+ overlayHeight = 0,
6885
+ elementRef = null
6889
6886
  }) {
6890
- const { editor } = (0, import_editor_utils14.useTiptapEditor)(providedEditor);
6891
- const [isOpen, setIsOpen] = (0, import_react45.useState)(false);
6892
- const { isVisible, canColorHighlight: canColorHighlight2, isActive, label } = useColorHighlight({
6893
- editor,
6894
- hideWhenUnavailable,
6895
- onApplied
6887
+ const { height: windowHeight } = useWindowSize();
6888
+ const [rect, setRect] = React14.useState({
6889
+ x: 0,
6890
+ y: 0,
6891
+ width: 0,
6892
+ height: 0
6896
6893
  });
6897
- if (!isVisible) return null;
6898
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_popover2.Popover.Root, { open: isOpen, onOpenChange: setIsOpen, spacing: "dense", children: [
6899
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
6900
- import_popover2.Popover.Trigger,
6901
- {
6902
- render: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
6903
- ColorHighlightPopoverButton,
6904
- {
6905
- disabled: !canColorHighlight2,
6906
- "data-disabled": !canColorHighlight2,
6907
- "data-active-state": isActive ? "on" : "off",
6908
- "aria-pressed": isActive,
6909
- "aria-label": label,
6910
- title: label,
6911
- ...props
6894
+ const updateRect = React14.useCallback(() => {
6895
+ var _a;
6896
+ const element = (_a = elementRef == null ? void 0 : elementRef.current) != null ? _a : document.body;
6897
+ const { x, y, width, height } = element.getBoundingClientRect();
6898
+ setRect({ x, y, width, height });
6899
+ }, [elementRef]);
6900
+ React14.useEffect(() => {
6901
+ var _a;
6902
+ const element = (_a = elementRef == null ? void 0 : elementRef.current) != null ? _a : document.body;
6903
+ updateRect();
6904
+ const resizeObserver = new ResizeObserver(() => {
6905
+ window.requestAnimationFrame(updateRect);
6906
+ });
6907
+ resizeObserver.observe(element);
6908
+ window.addEventListener("scroll", updateRect, { passive: true });
6909
+ return () => {
6910
+ resizeObserver.disconnect();
6911
+ window.removeEventListener("scroll", updateRect);
6912
+ };
6913
+ }, [elementRef, updateRect]);
6914
+ React14.useEffect(() => {
6915
+ const ensureCursorVisibility = () => {
6916
+ if (!editor) return;
6917
+ const { state, view } = editor;
6918
+ if (!view.hasFocus()) return;
6919
+ const { from } = state.selection;
6920
+ const cursorCoords = view.coordsAtPos(from);
6921
+ if (windowHeight < rect.height) {
6922
+ if (cursorCoords) {
6923
+ const availableSpace = windowHeight - cursorCoords.top - overlayHeight > 0;
6924
+ if (!availableSpace) {
6925
+ const targetScrollY = (
6926
+ // TODO: Needed?
6927
+ // window.scrollY + (cursorCoords.top - windowHeight / 2)
6928
+ cursorCoords.top - windowHeight / 2
6929
+ );
6930
+ window.scrollTo({
6931
+ top: targetScrollY,
6932
+ behavior: "smooth"
6933
+ });
6912
6934
  }
6913
- )
6935
+ }
6914
6936
  }
6915
- ),
6916
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_popover2.Popover.Content, { "aria-label": "Highlight colors", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ColorHighlightPopoverContent, { editor, colors }) })
6917
- ] });
6937
+ };
6938
+ ensureCursorVisibility();
6939
+ }, [editor, overlayHeight, windowHeight, rect.height]);
6940
+ return rect;
6918
6941
  }
6919
6942
 
6920
6943
  // src/ui/list-dropdown-menu/index.tsx