Mention.hooks.ts 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. import { useCallback, useEffect, useRef, useState } from 'react';
  2. import Delta, { Op } from 'quill-delta';
  3. import { getDeltaText } from '$app/utils/document/delta';
  4. import { useSubscribeNode } from '$app/components/document/_shared/SubscribeNode.hooks';
  5. import { useAppSelector } from '$app/stores/store';
  6. import { Page } from '$app_reducers/pages/slice';
  7. export function useSubscribeMentionSearchText({ blockId, open }: { blockId: string; open: boolean }) {
  8. const [searchText, setSearchText] = useState<string>('');
  9. const beforeOpenDeltaRef = useRef<Op[]>([]);
  10. const { node } = useSubscribeNode(blockId);
  11. const handleSearch = useCallback((newDelta: Delta) => {
  12. const diff = new Delta(beforeOpenDeltaRef.current).diff(newDelta);
  13. const text = getDeltaText(diff);
  14. setSearchText(text);
  15. }, []);
  16. useEffect(() => {
  17. if (!open) return;
  18. handleSearch(new Delta(node?.data?.delta));
  19. }, [handleSearch, node?.data?.delta, open]);
  20. useEffect(() => {
  21. if (!open) return;
  22. beforeOpenDeltaRef.current = node?.data?.delta;
  23. // eslint-disable-next-line react-hooks/exhaustive-deps
  24. }, [open]);
  25. return {
  26. searchText,
  27. };
  28. }
  29. export function useMentionPopoverProps({ open }: { open: boolean }) {
  30. const [anchorPosition, setAnchorPosition] = useState<
  31. | {
  32. top: number;
  33. left: number;
  34. }
  35. | undefined
  36. >(undefined);
  37. const popoverOpen = Boolean(anchorPosition);
  38. const getPosition = useCallback(() => {
  39. const range = document.getSelection()?.getRangeAt(0);
  40. const rangeRect = range?.getBoundingClientRect();
  41. return rangeRect;
  42. }, []);
  43. useEffect(() => {
  44. if (open) {
  45. const position = getPosition();
  46. if (!position) return;
  47. setAnchorPosition({
  48. top: position.top + position.height || 0,
  49. left: position.left + 14 || 0,
  50. });
  51. } else {
  52. setAnchorPosition(undefined);
  53. }
  54. }, [getPosition, open]);
  55. return {
  56. anchorPosition,
  57. popoverOpen,
  58. };
  59. }
  60. export function useLoadRecentPages(searchText: string) {
  61. const [recentPages, setRecentPages] = useState<Page[]>([]);
  62. const pages = useAppSelector((state) => state.pages.pageMap);
  63. useEffect(() => {
  64. const recentPages = Object.values(pages)
  65. .map((page) => {
  66. return page;
  67. })
  68. .filter((page) => {
  69. const text = searchText.slice(1, searchText.length);
  70. if (!text) return true;
  71. return page.name.toLowerCase().includes(text.toLowerCase());
  72. });
  73. setRecentPages(recentPages);
  74. }, [pages, searchText]);
  75. return {
  76. recentPages,
  77. };
  78. }