@collabchron/notiq 0.2.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 (188) hide show
  1. package/README.md +71 -0
  2. package/components.json +21 -0
  3. package/eslint.config.mjs +16 -0
  4. package/next.config.ts +12 -0
  5. package/package.json +108 -0
  6. package/postcss.config.mjs +5 -0
  7. package/public/file.svg +1 -0
  8. package/public/globe.svg +1 -0
  9. package/public/images/icons/plus.svg +10 -0
  10. package/public/next.svg +1 -0
  11. package/public/vercel.svg +1 -0
  12. package/public/window.svg +1 -0
  13. package/src/app/actions.ts +2 -0
  14. package/src/app/api/ai/route.ts +175 -0
  15. package/src/app/api/edgestore/[...edgestore]/route.ts +28 -0
  16. package/src/app/favicon.ico +0 -0
  17. package/src/app/globals.css +205 -0
  18. package/src/app/layout.tsx +38 -0
  19. package/src/app/page.tsx +12 -0
  20. package/src/components/editor/Core.tsx +220 -0
  21. package/src/components/editor/hooks/instructions-messages.ts +300 -0
  22. package/src/components/editor/hooks/use-mobile.ts +19 -0
  23. package/src/components/editor/hooks/useReport.ts +67 -0
  24. package/src/components/editor/hooks/useResizeObservert.ts +22 -0
  25. package/src/components/editor/index.tsx +39 -0
  26. package/src/components/editor/lexical-on-change.tsx +28 -0
  27. package/src/components/editor/nodes/CollapsibleNode/CollapsibleContainerNode.ts +92 -0
  28. package/src/components/editor/nodes/CollapsibleNode/CollapsibleContentNode.ts +65 -0
  29. package/src/components/editor/nodes/CollapsibleNode/CollapsibleTitleNode.ts +105 -0
  30. package/src/components/editor/nodes/EquationNode/EquationComponent.tsx +143 -0
  31. package/src/components/editor/nodes/EquationNode/EquationNode.tsx +170 -0
  32. package/src/components/editor/nodes/ExcalidrawNode/ExcalidrawComponent.tsx +228 -0
  33. package/src/components/editor/nodes/ExcalidrawNode/ExcalidrawImage.tsx +137 -0
  34. package/src/components/editor/nodes/ExcalidrawNode/ImageResizer.tsx +317 -0
  35. package/src/components/editor/nodes/ExcalidrawNode/index.tsx +204 -0
  36. package/src/components/editor/nodes/FigmaNode/FigmaNode.tsx +134 -0
  37. package/src/components/editor/nodes/Hint/HintComponet.tsx +221 -0
  38. package/src/components/editor/nodes/Hint/index.tsx +190 -0
  39. package/src/components/editor/nodes/ImageNode/index.tsx +328 -0
  40. package/src/components/editor/nodes/InlineImageNode/InlineImageComponent.tsx +383 -0
  41. package/src/components/editor/nodes/InlineImageNode/InlineImageNode.css +94 -0
  42. package/src/components/editor/nodes/InlineImageNode/InlineImageNode.tsx +309 -0
  43. package/src/components/editor/nodes/LayoutNode/LayoutContainerNode.ts +146 -0
  44. package/src/components/editor/nodes/LayoutNode/LayoutItemNode.ts +79 -0
  45. package/src/components/editor/nodes/PollNode/index.tsx +204 -0
  46. package/src/components/editor/nodes/Stepper/index.tsx +260 -0
  47. package/src/components/editor/nodes/TweetNode/index.tsx +214 -0
  48. package/src/components/editor/nodes/index.ts +81 -0
  49. package/src/components/editor/plugins/AutoEmbedPlugin/index.tsx +350 -0
  50. package/src/components/editor/plugins/AutoLinkPlugin/index.tsx +56 -0
  51. package/src/components/editor/plugins/CodeActionMenuPlugin/components/CopyButton.tsx +70 -0
  52. package/src/components/editor/plugins/CodeActionMenuPlugin/components/PrettierButton.tsx +192 -0
  53. package/src/components/editor/plugins/CodeActionMenuPlugin/index.tsx +217 -0
  54. package/src/components/editor/plugins/CodeActionMenuPlugin/utils.ts +26 -0
  55. package/src/components/editor/plugins/CodeHighlightPlugin/index.ts +21 -0
  56. package/src/components/editor/plugins/CollapsiblePlugin/Collapsible.css +76 -0
  57. package/src/components/editor/plugins/CollapsiblePlugin/index.ts +228 -0
  58. package/src/components/editor/plugins/DragDropPastePlugin/index.tsx +44 -0
  59. package/src/components/editor/plugins/DraggableBlockPlugin/index.tsx +52 -0
  60. package/src/components/editor/plugins/EquationsPlugin/index.tsx +85 -0
  61. package/src/components/editor/plugins/ExcalidrawPlugin/index.tsx +98 -0
  62. package/src/components/editor/plugins/FigmaPlugin/index.tsx +42 -0
  63. package/src/components/editor/plugins/FloatingLinkEditorPlugin/index.tsx +445 -0
  64. package/src/components/editor/plugins/FloatingTextFormatToolbarPlugin/index.tsx +275 -0
  65. package/src/components/editor/plugins/ImagesPlugin/index.tsx +222 -0
  66. package/src/components/editor/plugins/InlineImagePlugin/index.tsx +351 -0
  67. package/src/components/editor/plugins/LayoutPlugin/index.tsx +238 -0
  68. package/src/components/editor/plugins/LinkPlugin/index.tsx +36 -0
  69. package/src/components/editor/plugins/LinkWithMetaData/index.tsx +271 -0
  70. package/src/components/editor/plugins/MarkdownShortcutPlugin/index.tsx +11 -0
  71. package/src/components/editor/plugins/MarkdownTransformers/index.tsx +304 -0
  72. package/src/components/editor/plugins/PollPlugin/index.tsx +49 -0
  73. package/src/components/editor/plugins/ShortcutsPlugin/index.tsx +180 -0
  74. package/src/components/editor/plugins/ShortcutsPlugin/shortcuts.ts +253 -0
  75. package/src/components/editor/plugins/SlashCommand/index.tsx +621 -0
  76. package/src/components/editor/plugins/SpeechToTextPlugin/index.ts +127 -0
  77. package/src/components/editor/plugins/TabFocusPlugin/index.ts +58 -0
  78. package/src/components/editor/plugins/TableCellActionMenuPlugin/index.tsx +759 -0
  79. package/src/components/editor/plugins/TableCellResizer/index.tsx +438 -0
  80. package/src/components/editor/plugins/TableHoverActionsPlugin/index.tsx +314 -0
  81. package/src/components/editor/plugins/TablePlugin/index.tsx +99 -0
  82. package/src/components/editor/plugins/ToolbarPlugin/index.tsx +522 -0
  83. package/src/components/editor/plugins/TwitterPlugin/index.ts +35 -0
  84. package/src/components/editor/plugins/YouTubeNode/index.tsx +179 -0
  85. package/src/components/editor/plugins/YouTubePlugin/index.ts +41 -0
  86. package/src/components/editor/themes/editor-theme.ts +113 -0
  87. package/src/components/editor/themes/theme.css +377 -0
  88. package/src/components/editor/utils/ai.ts +291 -0
  89. package/src/components/editor/utils/canUseDOM.ts +12 -0
  90. package/src/components/editor/utils/editorFormatting.ts +282 -0
  91. package/src/components/editor/utils/environment.ts +50 -0
  92. package/src/components/editor/utils/extract-data.ts +166 -0
  93. package/src/components/editor/utils/getAllLexicalChildren.ts +13 -0
  94. package/src/components/editor/utils/getDOMRangeRect.ts +27 -0
  95. package/src/components/editor/utils/getSelectedNode.ts +27 -0
  96. package/src/components/editor/utils/gif.ts +29 -0
  97. package/src/components/editor/utils/invariant.ts +15 -0
  98. package/src/components/editor/utils/setFloatingElemPosition.ts +51 -0
  99. package/src/components/editor/utils/setFloatingElemPositionForLinkEditor.ts +40 -0
  100. package/src/components/editor/utils/setNodePlaceholderFromSelection/getNodePlaceholder.ts +51 -0
  101. package/src/components/editor/utils/setNodePlaceholderFromSelection/setNodePlaceholderFromSelection.ts +15 -0
  102. package/src/components/editor/utils/setNodePlaceholderFromSelection/setPlaceholderOnSelection.ts +114 -0
  103. package/src/components/editor/utils/setNodePlaceholderFromSelection/styles.css +6 -0
  104. package/src/components/editor/utils/url.ts +109 -0
  105. package/src/components/editor/utils/useLayoutEffect.ts +13 -0
  106. package/src/components/providers/QueryProvider.tsx +15 -0
  107. package/src/components/providers/SharedHistoryContext.tsx +28 -0
  108. package/src/components/providers/ToolbarContext.tsx +123 -0
  109. package/src/components/providers/theme-provider.tsx +11 -0
  110. package/src/components/theme/ModeToggle.tsx +40 -0
  111. package/src/components/ui/FileInput.tsx +40 -0
  112. package/src/components/ui/Input.css +32 -0
  113. package/src/components/ui/Select.css +42 -0
  114. package/src/components/ui/Select.tsx +36 -0
  115. package/src/components/ui/TextInput.tsx +48 -0
  116. package/src/components/ui/ai/ai-button.tsx +574 -0
  117. package/src/components/ui/ai/border.tsx +99 -0
  118. package/src/components/ui/ai/placeholder-input-vanish.tsx +282 -0
  119. package/src/components/ui/button.tsx +89 -0
  120. package/src/components/ui/card.tsx +76 -0
  121. package/src/components/ui/checkbox.tsx +30 -0
  122. package/src/components/ui/command.tsx +153 -0
  123. package/src/components/ui/dialog/Dialog.css +25 -0
  124. package/src/components/ui/dialog/Dialog.tsx +34 -0
  125. package/src/components/ui/dialog.tsx +122 -0
  126. package/src/components/ui/drop-downs/background-color.tsx +183 -0
  127. package/src/components/ui/drop-downs/block-format.tsx +159 -0
  128. package/src/components/ui/drop-downs/code.tsx +42 -0
  129. package/src/components/ui/drop-downs/color.tsx +177 -0
  130. package/src/components/ui/drop-downs/font-size.tsx +138 -0
  131. package/src/components/ui/drop-downs/font.tsx +155 -0
  132. package/src/components/ui/drop-downs/index.tsx +122 -0
  133. package/src/components/ui/drop-downs/insert-node.tsx +213 -0
  134. package/src/components/ui/drop-downs/text-align.tsx +123 -0
  135. package/src/components/ui/drop-downs/text-format.tsx +104 -0
  136. package/src/components/ui/dropdown-menu.tsx +201 -0
  137. package/src/components/ui/equation/EquationEditor.css +38 -0
  138. package/src/components/ui/equation/EquationEditor.tsx +56 -0
  139. package/src/components/ui/equation/KatexEquationAlterer.css +41 -0
  140. package/src/components/ui/equation/KatexEquationAlterer.tsx +83 -0
  141. package/src/components/ui/equation/KatexRenderer.tsx +66 -0
  142. package/src/components/ui/excalidraw/ExcalidrawModal.css +64 -0
  143. package/src/components/ui/excalidraw/ExcalidrawModal.tsx +234 -0
  144. package/src/components/ui/excalidraw/Modal.css +62 -0
  145. package/src/components/ui/excalidraw/Modal.tsx +110 -0
  146. package/src/components/ui/hover-card.tsx +29 -0
  147. package/src/components/ui/image/error-image.tsx +17 -0
  148. package/src/components/ui/image/file-upload.tsx +240 -0
  149. package/src/components/ui/image/image-resizer.tsx +297 -0
  150. package/src/components/ui/image/image-toolbar.tsx +264 -0
  151. package/src/components/ui/image/index.tsx +408 -0
  152. package/src/components/ui/image/lazy-image.tsx +68 -0
  153. package/src/components/ui/image/lazy-video.tsx +71 -0
  154. package/src/components/ui/input.tsx +22 -0
  155. package/src/components/ui/models/custom-dialog.tsx +320 -0
  156. package/src/components/ui/models/insert-gif.tsx +90 -0
  157. package/src/components/ui/models/insert-image.tsx +52 -0
  158. package/src/components/ui/models/insert-poll.tsx +29 -0
  159. package/src/components/ui/models/insert-table.tsx +62 -0
  160. package/src/components/ui/models/use-model.tsx +91 -0
  161. package/src/components/ui/poll/poll-component.tsx +304 -0
  162. package/src/components/ui/popover.tsx +33 -0
  163. package/src/components/ui/progress.tsx +28 -0
  164. package/src/components/ui/scroll-area.tsx +48 -0
  165. package/src/components/ui/separator.tsx +31 -0
  166. package/src/components/ui/skeleton.tsx +15 -0
  167. package/src/components/ui/sonner.tsx +31 -0
  168. package/src/components/ui/stepper/step.tsx +179 -0
  169. package/src/components/ui/stepper/stepper.tsx +89 -0
  170. package/src/components/ui/textarea.tsx +22 -0
  171. package/src/components/ui/toggle.tsx +71 -0
  172. package/src/components/ui/tooltip.tsx +32 -0
  173. package/src/components/ui/write/text-format-floting-toolbar.tsx +346 -0
  174. package/src/lib/edgestore.ts +9 -0
  175. package/src/lib/pinecone-client.ts +0 -0
  176. package/src/lib/utils.ts +6 -0
  177. package/src/utils/docSerialization.ts +77 -0
  178. package/src/utils/emoji-list.ts +16615 -0
  179. package/src/utils/getDOMRangeRect.ts +27 -0
  180. package/src/utils/getSelectedNode.ts +27 -0
  181. package/src/utils/getThemeSelector.ts +25 -0
  182. package/src/utils/isMobileWidth.ts +7 -0
  183. package/src/utils/joinClasses.ts +13 -0
  184. package/src/utils/setFloatingElemPosition.ts +74 -0
  185. package/src/utils/setFloatingElemPositionForLinkEditor.ts +46 -0
  186. package/src/utils/swipe.ts +127 -0
  187. package/src/utils/url.ts +38 -0
  188. package/tsconfig.json +27 -0
@@ -0,0 +1,166 @@
1
+ import { $getRoot, ElementNode, LexicalEditor, ParagraphNode, TextNode } from "lexical";
2
+ import {ListItemNode, ListNode} from "@lexical/list"
3
+ import {HeadingNode,QuoteNode} from "@lexical/rich-text"
4
+ import {TableNode} from "@lexical/table"
5
+ import {CodeNode} from "@lexical/code"
6
+ import { CollapsibleContainerNode } from "../nodes/CollapsibleNode/CollapsibleContainerNode";
7
+ import { CollapsibleTitleNode } from "../nodes/CollapsibleNode/CollapsibleTitleNode";
8
+ export type BlockType = "Table"|'heading'|"CollapsibleContent" | 'paragraph' |"Collapsible"| 'text' | 'list' | 'list-item' | 'quote' | 'code' | "Hint"
9
+
10
+ export type ExtractedBlock = {
11
+ blockType: BlockType;
12
+ content: string;
13
+ children?: ExtractedBlock[];
14
+ }
15
+
16
+ function extractChildrenContent(nodes: any[]): string {
17
+ return nodes
18
+ .filter(node => node instanceof TextNode)
19
+ .map(node => node.getTextContent())
20
+ .join(' ');
21
+ }
22
+
23
+ function processListItem(listItem: ListItemNode): ExtractedBlock {
24
+ let textContent = '';
25
+ const children: ExtractedBlock[] = [];
26
+
27
+ listItem.getChildren().forEach(child => {
28
+ if (child instanceof TextNode) {
29
+ textContent += child.getTextContent();
30
+ } else if (child instanceof ElementNode) {
31
+ textContent += extractChildrenContent(child.getChildren());
32
+ }
33
+
34
+ if (child instanceof ListNode) {
35
+ children.push(...processList(child));
36
+ }
37
+ });
38
+
39
+ return {
40
+ blockType: 'list-item',
41
+ content: textContent.trim(),
42
+ children: children.length > 0 ? children : undefined
43
+ };
44
+ }
45
+
46
+ function processList(listNode: ListNode): ExtractedBlock[] {
47
+ const listType = listNode.getListType();
48
+ return listNode.getChildren().map(child => ({
49
+ ...processListItem(child as ListItemNode),
50
+ attributes: { listType }
51
+ }));
52
+ }
53
+
54
+ function processHeading(headingNode: HeadingNode): ExtractedBlock {
55
+ return {
56
+ blockType: 'heading',
57
+ content: headingNode.getTextContent(),
58
+ };
59
+ }
60
+ // should i make better json?
61
+ function processTable(TableNode:TableNode): ExtractedBlock {
62
+ return {
63
+ blockType: 'Table',
64
+ content: TableNode.getTextContent(),
65
+ };
66
+ }
67
+ function processCodeBlock(codeNode: CodeNode): ExtractedBlock {
68
+ return {
69
+ blockType: 'code',
70
+ content: codeNode.getTextContent(),
71
+ };
72
+ }
73
+
74
+ export function ExtractData(editor: LexicalEditor):string {
75
+ const blocks: ExtractedBlock[] = [];
76
+
77
+ editor.update(() => {
78
+ const root = $getRoot();
79
+
80
+ root.getChildren().forEach(node => {
81
+ try {
82
+
83
+ if (node instanceof HeadingNode) {
84
+ blocks.push(processHeading(node));
85
+ }
86
+ else if(node instanceof TableNode){
87
+ blocks.push(processTable(node));
88
+ }
89
+
90
+ else if (node instanceof ListNode) {
91
+ blocks.push({
92
+ blockType: 'list',
93
+ content: '',
94
+ children: processList(node)
95
+ });
96
+ }
97
+ else if (node instanceof CollapsibleContainerNode || node instanceof CollapsibleTitleNode) {
98
+ let title = "";
99
+ let content = "";
100
+
101
+ if (node instanceof CollapsibleTitleNode) {
102
+ title = node.getTextContent();
103
+ }
104
+
105
+ if (node instanceof CollapsibleContainerNode) {
106
+ const children = node.getChildren();
107
+ const titleNode = children.find(child => child instanceof CollapsibleTitleNode);
108
+ if (titleNode) {
109
+ title = titleNode.getTextContent();
110
+ }
111
+ content = children
112
+ .filter(child => !(child instanceof CollapsibleTitleNode))
113
+ .map(child => child.getTextContent())
114
+ .join("\n");
115
+ }
116
+
117
+
118
+ blocks.push({
119
+ blockType: "Collapsible",
120
+ content: title, // The collapsible title.
121
+ children: [
122
+ {
123
+ blockType: "CollapsibleContent",
124
+ content: content, // The collapsible content.
125
+ },
126
+ ],
127
+ });
128
+ }
129
+ else if (node instanceof ParagraphNode) {
130
+ if(node.getTextContent() == "") return;
131
+ blocks.push({
132
+ blockType: 'paragraph',
133
+ content: node.getTextContent()
134
+ });
135
+ }
136
+ else if (node instanceof CodeNode) {
137
+ blocks.push(processCodeBlock(node));
138
+ }
139
+ else if (node instanceof QuoteNode) {
140
+
141
+ blocks.push({
142
+ blockType: 'quote',
143
+ content: node.getTextContent(),
144
+ children: node.getChildren().length>=2 ?node.getChildren().map(child => ({
145
+ blockType: 'text',
146
+ content: child.getTextContent()
147
+ })):undefined
148
+ });
149
+ }
150
+ else if (node instanceof TextNode) {
151
+ blocks.push({
152
+ blockType: 'text',
153
+ content: node.getTextContent()
154
+ });
155
+ }
156
+
157
+ } catch (error) {
158
+ console.warn('Error processing node:', error);
159
+ }
160
+ });
161
+ });
162
+
163
+
164
+
165
+ return JSON.stringify(blocks);
166
+ }
@@ -0,0 +1,13 @@
1
+ import { $getNodeByKey, $getRoot, LexicalEditor } from 'lexical';
2
+
3
+ export const getAllLexicalChildren = (editor: LexicalEditor) => {
4
+ const childrenKeys = editor
5
+ .getEditorState()
6
+ .read(() => $getRoot().getChildrenKeys());
7
+
8
+ return childrenKeys.map((key) => ({
9
+ key: key,
10
+ node: $getNodeByKey(key),
11
+ htmlElement: editor.getElementByKey(key),
12
+ }));
13
+ };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+ export function getDOMRangeRect(
9
+ nativeSelection: Selection,
10
+ rootElement: HTMLElement,
11
+ ): DOMRect {
12
+ const domRange = nativeSelection.getRangeAt(0);
13
+
14
+ let rect;
15
+
16
+ if (nativeSelection.anchorNode === rootElement) {
17
+ let inner = rootElement;
18
+ while (inner.firstElementChild != null) {
19
+ inner = inner.firstElementChild as HTMLElement;
20
+ }
21
+ rect = inner.getBoundingClientRect();
22
+ } else {
23
+ rect = domRange.getBoundingClientRect();
24
+ }
25
+
26
+ return rect;
27
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+ import {$isAtNodeEnd} from '@lexical/selection';
9
+ import {ElementNode, RangeSelection, TextNode} from 'lexical';
10
+
11
+ export function getSelectedNode(
12
+ selection: RangeSelection,
13
+ ): TextNode | ElementNode {
14
+ const anchor = selection.anchor;
15
+ const focus = selection.focus;
16
+ const anchorNode = selection.anchor.getNode();
17
+ const focusNode = selection.focus.getNode();
18
+ if (anchorNode === focusNode) {
19
+ return anchorNode;
20
+ }
21
+ const isBackward = selection.isBackward();
22
+ if (isBackward) {
23
+ return $isAtNodeEnd(focus) ? anchorNode : focusNode;
24
+ } else {
25
+ return $isAtNodeEnd(anchor) ? anchorNode : focusNode;
26
+ }
27
+ }
@@ -0,0 +1,29 @@
1
+
2
+ const API_KEY = process.env.NEXT_PUBLIC_TENOR_API_KEY;
3
+
4
+ const fetchGifs = async ({ q = "trending" }: { q?: string }) => {
5
+ const params = new URLSearchParams({
6
+ key: API_KEY || '',
7
+ q,
8
+ limit: "200",
9
+ media_filter: 'minimal',
10
+ });
11
+
12
+ const response = await fetch(`https://tenor.googleapis.com/v2/search?${params.toString()}`);
13
+
14
+ if (!response.ok) {
15
+ throw new Error(`HTTP error! Status: ${response.status}`);
16
+ }
17
+
18
+ const data = await response.json();
19
+
20
+ const gifs = data.results.map((gif: any) => ({
21
+ id: gif.id,
22
+ url: gif.media_formats.gif.url,
23
+ alt_text: gif.content_description,
24
+ }));
25
+
26
+ return { gifs, next: data.next };
27
+ };
28
+
29
+ export default fetchGifs;
@@ -0,0 +1,15 @@
1
+
2
+ export default function invariant(
3
+ cond?: boolean,
4
+ message?: string,
5
+ ): asserts cond {
6
+ if (cond) {
7
+ return;
8
+ }
9
+
10
+ throw new Error(
11
+ 'Internal Lexical error: invariant() is meant to be replaced at compile ' +
12
+ 'time. There is no runtime version. Error: ' +
13
+ message,
14
+ );
15
+ }
@@ -0,0 +1,51 @@
1
+ const VERTICAL_GAP = 10;
2
+ const HORIZONTAL_OFFSET = 5;
3
+
4
+ export function setFloatingElemPosition(
5
+ targetRect: DOMRect | null,
6
+ floatingElem: HTMLElement,
7
+ anchorElem: HTMLElement,
8
+ verticalGap: number = VERTICAL_GAP,
9
+ horizontalOffset: number = HORIZONTAL_OFFSET,
10
+ ): void {
11
+ const scrollerElem = anchorElem.parentElement;
12
+
13
+ if (targetRect === null || !scrollerElem) {
14
+ floatingElem.style.opacity = '0';
15
+ floatingElem.style.transform = 'translate(-10000px, -10000px)';
16
+ return;
17
+ }
18
+
19
+ const floatingElemRect = floatingElem.getBoundingClientRect();
20
+ const anchorElementRect = anchorElem.getBoundingClientRect();
21
+ const editorScrollerRect = scrollerElem.getBoundingClientRect();
22
+
23
+ // Calculate initial top and left positions
24
+ let top = targetRect.bottom + verticalGap;
25
+ let left = targetRect.left + horizontalOffset;
26
+
27
+ // Ensure the floating element stays within the editor's bounds
28
+ if (top + floatingElemRect.height > editorScrollerRect.bottom) {
29
+ // If it overflows at the bottom, position it above the selection
30
+ top = targetRect.top - floatingElemRect.height - verticalGap;
31
+ }
32
+
33
+ if (left + floatingElemRect.width > editorScrollerRect.right) {
34
+ // If it overflows on the right, align it with the right edge of the editor
35
+ left = editorScrollerRect.right - floatingElemRect.width - horizontalOffset;
36
+ }
37
+
38
+ if (left < editorScrollerRect.left) {
39
+ // If it overflows on the left, align it with the left edge of the editor
40
+ left = editorScrollerRect.left + horizontalOffset;
41
+ }
42
+
43
+ // Adjust for the anchor element's position
44
+ top -= anchorElementRect.top;
45
+ left -= anchorElementRect.left;
46
+
47
+ // Apply smooth transition and positioning
48
+ floatingElem.style.transition = 'transform 0.2s ease-in-out, opacity 0.2s ease-in-out';
49
+ floatingElem.style.opacity = '1';
50
+ floatingElem.style.transform = `translate(${left}px, ${top}px)`;
51
+ }
@@ -0,0 +1,40 @@
1
+
2
+ const VERTICAL_GAP = 10;
3
+ const HORIZONTAL_OFFSET = 5;
4
+
5
+ export function setFloatingElemPositionForLinkEditor(
6
+ targetRect: DOMRect | null,
7
+ floatingElem: HTMLElement,
8
+ anchorElem: HTMLElement,
9
+ verticalGap: number = VERTICAL_GAP,
10
+ horizontalOffset: number = HORIZONTAL_OFFSET,
11
+ ): void {
12
+ const scrollerElem = anchorElem.parentElement;
13
+
14
+ if (targetRect === null || !scrollerElem) {
15
+ floatingElem.style.opacity = '0';
16
+ floatingElem.style.transform = 'translate(-10000px, -10000px)';
17
+ return;
18
+ }
19
+
20
+ const floatingElemRect = floatingElem.getBoundingClientRect();
21
+ const anchorElementRect = anchorElem.getBoundingClientRect();
22
+ const editorScrollerRect = scrollerElem.getBoundingClientRect();
23
+
24
+ let top = targetRect.top - verticalGap;
25
+ let left = targetRect.left - horizontalOffset;
26
+
27
+ if (top < editorScrollerRect.top) {
28
+ top += floatingElemRect.height + targetRect.height + verticalGap * 2;
29
+ }
30
+
31
+ if (left + floatingElemRect.width > editorScrollerRect.right) {
32
+ left = editorScrollerRect.right - floatingElemRect.width - horizontalOffset;
33
+ }
34
+
35
+ top -= anchorElementRect.top;
36
+ left -= anchorElementRect.left;
37
+
38
+ floatingElem.style.opacity = '1';
39
+ floatingElem.style.transform = `translate(${left}px, ${top}px)`;
40
+ }
@@ -0,0 +1,51 @@
1
+ import { $isParagraphNode, LexicalNode } from 'lexical';
2
+ import { $isHeadingNode, $isQuoteNode } from '@lexical/rich-text';
3
+ import {$isListItemNode} from "@lexical/list"
4
+ export const getNodePlaceholder = (lexicalNode: LexicalNode) => {
5
+ let placeholder;
6
+
7
+
8
+ if ($isHeadingNode(lexicalNode)) {
9
+ const tag = lexicalNode.getTag();
10
+ placeholder = 'Heading'
11
+ switch (tag) {
12
+ case 'h1': {
13
+ placeholder += ' 1';
14
+ break;
15
+ }
16
+ case 'h2': {
17
+ placeholder += ' 2';
18
+ break;
19
+ }
20
+ case 'h3': {
21
+ placeholder += ' 3';
22
+ break;
23
+ }
24
+ case 'h4': {
25
+ placeholder += ' 4';
26
+ break;
27
+ }
28
+ case 'h5': {
29
+ placeholder += '5';
30
+ break;
31
+ }
32
+ case 'h6': {
33
+ placeholder += '6';
34
+ break;
35
+ }
36
+ }
37
+ }
38
+ if($isListItemNode(lexicalNode)){
39
+ placeholder = "list"
40
+ }
41
+ if($isQuoteNode(lexicalNode)){
42
+ placeholder = "Quote"
43
+ }
44
+
45
+ if ($isParagraphNode(lexicalNode)) {
46
+
47
+ placeholder = "Press '/' for command";
48
+ }
49
+
50
+ return placeholder;
51
+ };
@@ -0,0 +1,15 @@
1
+
2
+ import { $getSelection, $isRangeSelection, LexicalEditor } from 'lexical';
3
+ import { setPlaceholderOnSelection } from './setPlaceholderOnSelection';
4
+
5
+ export const setNodePlaceholderFromSelection = (
6
+ editor: LexicalEditor,
7
+ ): void => {
8
+ editor.getEditorState().read(() => {
9
+ const selection = $getSelection();
10
+ if (!$isRangeSelection(selection)) {
11
+ return;
12
+ }
13
+ setPlaceholderOnSelection({ selection, editor });
14
+ });
15
+ };
@@ -0,0 +1,114 @@
1
+ import { LexicalEditor, PointType, RangeSelection } from 'lexical';
2
+ import { getAllLexicalChildren } from '../getAllLexicalChildren';
3
+ import { getNodePlaceholder } from './getNodePlaceholder';
4
+
5
+ import './styles.css';
6
+ import { $isCollapsibleContainerNode } from '../../nodes/CollapsibleNode/CollapsibleContainerNode';
7
+ import { $isCollapsibleContentNode } from '../../nodes/CollapsibleNode/CollapsibleContentNode';
8
+
9
+ const PLACEHOLDER_CLASS_NAME = 'node-placeholder';
10
+
11
+ const isHtmlHeadingElement = (el: HTMLElement): el is HTMLHeadingElement => {
12
+ return el instanceof HTMLHeadingElement;
13
+ };
14
+
15
+ export const setPlaceholderOnSelection = ({
16
+ selection,
17
+ editor,
18
+ }: {
19
+ selection: RangeSelection;
20
+ editor: LexicalEditor;
21
+ }): void => {
22
+ /**
23
+ * 1. Get all lexical nodes as HTML elements
24
+ */
25
+ const children = getAllLexicalChildren(editor);
26
+
27
+
28
+ /**
29
+ * 2. Remove "placeholder" class if it was added before
30
+ */
31
+ const removePlaceholderClass = (element: HTMLElement) => {
32
+ if (!element) return;
33
+
34
+ // Remove the placeholder class from the current element
35
+ if (element.classList.contains(PLACEHOLDER_CLASS_NAME)) {
36
+ element.classList.remove(PLACEHOLDER_CLASS_NAME);
37
+ element.removeAttribute('data-placeholder');
38
+ }
39
+
40
+ // Recursively process child elements
41
+ Array.from(element.children).forEach(child => {
42
+ if (child instanceof HTMLElement) {
43
+ removePlaceholderClass(child);
44
+ }
45
+ });
46
+ };
47
+
48
+ children.forEach(({ htmlElement,node }:any) => {
49
+ if (!htmlElement) {
50
+ return;
51
+ }
52
+
53
+ if (isHtmlHeadingElement(htmlElement)) {
54
+ return;
55
+ }
56
+
57
+
58
+
59
+ const classList = htmlElement.classList;
60
+ if (node.__type === 'collapsible-container') {
61
+ removePlaceholderClass(htmlElement);
62
+ return;
63
+ }
64
+ if (node.__type === 'table') {
65
+ removePlaceholderClass(htmlElement);
66
+ return;
67
+ }
68
+ if(node.__type==="layout-container"){
69
+ removePlaceholderClass(htmlElement);
70
+ }
71
+ if (classList.length && classList.contains(PLACEHOLDER_CLASS_NAME)) {
72
+ classList.remove(PLACEHOLDER_CLASS_NAME);
73
+ htmlElement.removeAttribute('data-placeholder');
74
+
75
+ }
76
+ });
77
+
78
+ /**
79
+ * 3. Do nothing if there is only one lexical child,
80
+ * because we already have a placeholder
81
+ * in <RichTextPlugin/> component
82
+ * With on exception: If we converted default node to the "Heading"
83
+ */
84
+ if (
85
+ children.length === 1 &&
86
+ children[0].htmlElement &&
87
+ !isHtmlHeadingElement(children[0].htmlElement)
88
+ ) {
89
+ return;
90
+ }
91
+
92
+ /**
93
+ * 4. Get "PointType" object, that contain Nodes data
94
+ * (that is selected)
95
+ * {
96
+ * key: "5", <- Node's key
97
+ * offset: 7,
98
+ * type: "text"
99
+ * }
100
+ */
101
+ const anchor: PointType = selection.anchor;
102
+
103
+ /**
104
+ * 5. Get placeholder for type ('heading'/'paragraph'/etc...)
105
+ */
106
+ const placeholder = getNodePlaceholder(anchor.getNode());
107
+
108
+ if (placeholder) {
109
+ const selectedHtmlElement = editor.getElementByKey(anchor.key);
110
+
111
+ selectedHtmlElement?.classList.add(PLACEHOLDER_CLASS_NAME);
112
+ selectedHtmlElement?.setAttribute('data-placeholder', placeholder);
113
+ }
114
+ };
@@ -0,0 +1,6 @@
1
+ .node-placeholder:has(br):not(:has(span))::before {
2
+ position: absolute;
3
+ content: attr(data-placeholder);
4
+ color: rgba(136, 136, 136, 0.6);
5
+
6
+ }
@@ -0,0 +1,109 @@
1
+
2
+
3
+ const SUPPORTED_URL_PROTOCOLS = new Set([
4
+ 'http:',
5
+ 'https:',
6
+ 'mailto:',
7
+ 'sms:',
8
+ 'tel:',
9
+ 'ftp:',
10
+ ]);
11
+
12
+ export function sanitizeUrl(url: string): string {
13
+ try {
14
+ const parsedUrl = new URL(url);
15
+ // eslint-disable-next-line no-script-url
16
+ if (!SUPPORTED_URL_PROTOCOLS.has(parsedUrl.protocol)) {
17
+ return 'about:blank';
18
+ }
19
+ } catch {
20
+ return url;
21
+ }
22
+ return url;
23
+ }
24
+
25
+ // Source: https://stackoverflow.com/a/8234912/2013580
26
+ const urlRegExp = new RegExp(
27
+ /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)/,
28
+ );
29
+ export function validateUrl(url: string): boolean {
30
+ // TODO Fix UI for link insertion; it should never default to an invalid URL such as https://.
31
+ // Maybe show a dialog where they user can type the URL before inserting it.
32
+ return url === 'https://' || urlRegExp.test(url);
33
+ }
34
+ interface Metadata{
35
+ title:string,
36
+ description:string,
37
+ image:string | undefined
38
+ website:string,
39
+ logo:string
40
+
41
+ }
42
+
43
+ export async function fetchMetadata(url: string): Promise<Metadata> {
44
+ try {
45
+ const proxyUrl = "https://api.allorigins.win/get?url=";
46
+ const response = await fetch(`${proxyUrl}${encodeURIComponent(url)}`);
47
+ const data = await response.json();
48
+
49
+ // Extract the HTML content from the proxy response
50
+ const html = data.contents;
51
+
52
+ // Parse the HTML using DOMParser
53
+ const parser = new DOMParser();
54
+ const doc = parser.parseFromString(html, "text/html");
55
+
56
+ // Helper function to extract meta tag content
57
+ const getMetaTagContent = (property: string) => {
58
+ const element =
59
+ doc.querySelector(`meta[property="${property}"]`) ||
60
+ doc.querySelector(`meta[name="${property}"]`);
61
+ return element ? element.getAttribute("content") : null;
62
+ };
63
+
64
+ // Helper function to extract favicon or logo
65
+ const getFavicon = () => {
66
+ // Check for common favicon locations
67
+ const favicon =
68
+ doc.querySelector('link[rel="icon"]') ||
69
+ doc.querySelector('link[rel="shortcut icon"]') ||
70
+ doc.querySelector('link[rel="apple-touch-icon"]');
71
+
72
+ if (favicon) {
73
+ const href = favicon.getAttribute("href");
74
+ if (href) {
75
+ // Resolve relative URLs to absolute URLs
76
+ return new URL(href, url).toString();
77
+ }
78
+ }
79
+
80
+ // Fallback to default favicon location
81
+ return new URL("/favicon.ico", url).toString();
82
+ };
83
+
84
+ // Extract metadata
85
+ const metadata = {
86
+ title:
87
+ doc.querySelector("title")?.textContent ||
88
+ getMetaTagContent("og:title") ||
89
+ "No title",
90
+ description:
91
+ getMetaTagContent("og:description") ||
92
+ getMetaTagContent("description") ||
93
+ "No description",
94
+ image: getMetaTagContent("og:image") || undefined, // Use a valid fallback image URL
95
+ website: getMetaTagContent("og:site_name") || new URL(url).hostname, // Use hostname as fallback
96
+ logo: getFavicon(), // Extract favicon or logo
97
+ };
98
+
99
+ return metadata;
100
+ } catch (error) {
101
+ return {
102
+ title: "No title",
103
+ description: "No description",
104
+ image: undefined,
105
+ website: new URL(url).hostname,
106
+ logo: new URL("/favicon.ico", url).toString(), // Fallback to default favicon
107
+ };
108
+ }
109
+ }
@@ -0,0 +1,13 @@
1
+
2
+
3
+ import {useEffect, useLayoutEffect} from 'react';
4
+ import { CAN_USE_DOM } from './canUseDOM';
5
+
6
+ // This workaround is no longer necessary in React 19,
7
+ // but we currently support React >=17.x
8
+ // https://github.com/facebook/react/pull/26395
9
+ const useLayoutEffectImpl: typeof useLayoutEffect = CAN_USE_DOM
10
+ ? useLayoutEffect
11
+ : useEffect;
12
+
13
+ export default useLayoutEffectImpl;