@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.
- package/README.md +71 -0
- package/components.json +21 -0
- package/eslint.config.mjs +16 -0
- package/next.config.ts +12 -0
- package/package.json +108 -0
- package/postcss.config.mjs +5 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/images/icons/plus.svg +10 -0
- package/public/next.svg +1 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/src/app/actions.ts +2 -0
- package/src/app/api/ai/route.ts +175 -0
- package/src/app/api/edgestore/[...edgestore]/route.ts +28 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +205 -0
- package/src/app/layout.tsx +38 -0
- package/src/app/page.tsx +12 -0
- package/src/components/editor/Core.tsx +220 -0
- package/src/components/editor/hooks/instructions-messages.ts +300 -0
- package/src/components/editor/hooks/use-mobile.ts +19 -0
- package/src/components/editor/hooks/useReport.ts +67 -0
- package/src/components/editor/hooks/useResizeObservert.ts +22 -0
- package/src/components/editor/index.tsx +39 -0
- package/src/components/editor/lexical-on-change.tsx +28 -0
- package/src/components/editor/nodes/CollapsibleNode/CollapsibleContainerNode.ts +92 -0
- package/src/components/editor/nodes/CollapsibleNode/CollapsibleContentNode.ts +65 -0
- package/src/components/editor/nodes/CollapsibleNode/CollapsibleTitleNode.ts +105 -0
- package/src/components/editor/nodes/EquationNode/EquationComponent.tsx +143 -0
- package/src/components/editor/nodes/EquationNode/EquationNode.tsx +170 -0
- package/src/components/editor/nodes/ExcalidrawNode/ExcalidrawComponent.tsx +228 -0
- package/src/components/editor/nodes/ExcalidrawNode/ExcalidrawImage.tsx +137 -0
- package/src/components/editor/nodes/ExcalidrawNode/ImageResizer.tsx +317 -0
- package/src/components/editor/nodes/ExcalidrawNode/index.tsx +204 -0
- package/src/components/editor/nodes/FigmaNode/FigmaNode.tsx +134 -0
- package/src/components/editor/nodes/Hint/HintComponet.tsx +221 -0
- package/src/components/editor/nodes/Hint/index.tsx +190 -0
- package/src/components/editor/nodes/ImageNode/index.tsx +328 -0
- package/src/components/editor/nodes/InlineImageNode/InlineImageComponent.tsx +383 -0
- package/src/components/editor/nodes/InlineImageNode/InlineImageNode.css +94 -0
- package/src/components/editor/nodes/InlineImageNode/InlineImageNode.tsx +309 -0
- package/src/components/editor/nodes/LayoutNode/LayoutContainerNode.ts +146 -0
- package/src/components/editor/nodes/LayoutNode/LayoutItemNode.ts +79 -0
- package/src/components/editor/nodes/PollNode/index.tsx +204 -0
- package/src/components/editor/nodes/Stepper/index.tsx +260 -0
- package/src/components/editor/nodes/TweetNode/index.tsx +214 -0
- package/src/components/editor/nodes/index.ts +81 -0
- package/src/components/editor/plugins/AutoEmbedPlugin/index.tsx +350 -0
- package/src/components/editor/plugins/AutoLinkPlugin/index.tsx +56 -0
- package/src/components/editor/plugins/CodeActionMenuPlugin/components/CopyButton.tsx +70 -0
- package/src/components/editor/plugins/CodeActionMenuPlugin/components/PrettierButton.tsx +192 -0
- package/src/components/editor/plugins/CodeActionMenuPlugin/index.tsx +217 -0
- package/src/components/editor/plugins/CodeActionMenuPlugin/utils.ts +26 -0
- package/src/components/editor/plugins/CodeHighlightPlugin/index.ts +21 -0
- package/src/components/editor/plugins/CollapsiblePlugin/Collapsible.css +76 -0
- package/src/components/editor/plugins/CollapsiblePlugin/index.ts +228 -0
- package/src/components/editor/plugins/DragDropPastePlugin/index.tsx +44 -0
- package/src/components/editor/plugins/DraggableBlockPlugin/index.tsx +52 -0
- package/src/components/editor/plugins/EquationsPlugin/index.tsx +85 -0
- package/src/components/editor/plugins/ExcalidrawPlugin/index.tsx +98 -0
- package/src/components/editor/plugins/FigmaPlugin/index.tsx +42 -0
- package/src/components/editor/plugins/FloatingLinkEditorPlugin/index.tsx +445 -0
- package/src/components/editor/plugins/FloatingTextFormatToolbarPlugin/index.tsx +275 -0
- package/src/components/editor/plugins/ImagesPlugin/index.tsx +222 -0
- package/src/components/editor/plugins/InlineImagePlugin/index.tsx +351 -0
- package/src/components/editor/plugins/LayoutPlugin/index.tsx +238 -0
- package/src/components/editor/plugins/LinkPlugin/index.tsx +36 -0
- package/src/components/editor/plugins/LinkWithMetaData/index.tsx +271 -0
- package/src/components/editor/plugins/MarkdownShortcutPlugin/index.tsx +11 -0
- package/src/components/editor/plugins/MarkdownTransformers/index.tsx +304 -0
- package/src/components/editor/plugins/PollPlugin/index.tsx +49 -0
- package/src/components/editor/plugins/ShortcutsPlugin/index.tsx +180 -0
- package/src/components/editor/plugins/ShortcutsPlugin/shortcuts.ts +253 -0
- package/src/components/editor/plugins/SlashCommand/index.tsx +621 -0
- package/src/components/editor/plugins/SpeechToTextPlugin/index.ts +127 -0
- package/src/components/editor/plugins/TabFocusPlugin/index.ts +58 -0
- package/src/components/editor/plugins/TableCellActionMenuPlugin/index.tsx +759 -0
- package/src/components/editor/plugins/TableCellResizer/index.tsx +438 -0
- package/src/components/editor/plugins/TableHoverActionsPlugin/index.tsx +314 -0
- package/src/components/editor/plugins/TablePlugin/index.tsx +99 -0
- package/src/components/editor/plugins/ToolbarPlugin/index.tsx +522 -0
- package/src/components/editor/plugins/TwitterPlugin/index.ts +35 -0
- package/src/components/editor/plugins/YouTubeNode/index.tsx +179 -0
- package/src/components/editor/plugins/YouTubePlugin/index.ts +41 -0
- package/src/components/editor/themes/editor-theme.ts +113 -0
- package/src/components/editor/themes/theme.css +377 -0
- package/src/components/editor/utils/ai.ts +291 -0
- package/src/components/editor/utils/canUseDOM.ts +12 -0
- package/src/components/editor/utils/editorFormatting.ts +282 -0
- package/src/components/editor/utils/environment.ts +50 -0
- package/src/components/editor/utils/extract-data.ts +166 -0
- package/src/components/editor/utils/getAllLexicalChildren.ts +13 -0
- package/src/components/editor/utils/getDOMRangeRect.ts +27 -0
- package/src/components/editor/utils/getSelectedNode.ts +27 -0
- package/src/components/editor/utils/gif.ts +29 -0
- package/src/components/editor/utils/invariant.ts +15 -0
- package/src/components/editor/utils/setFloatingElemPosition.ts +51 -0
- package/src/components/editor/utils/setFloatingElemPositionForLinkEditor.ts +40 -0
- package/src/components/editor/utils/setNodePlaceholderFromSelection/getNodePlaceholder.ts +51 -0
- package/src/components/editor/utils/setNodePlaceholderFromSelection/setNodePlaceholderFromSelection.ts +15 -0
- package/src/components/editor/utils/setNodePlaceholderFromSelection/setPlaceholderOnSelection.ts +114 -0
- package/src/components/editor/utils/setNodePlaceholderFromSelection/styles.css +6 -0
- package/src/components/editor/utils/url.ts +109 -0
- package/src/components/editor/utils/useLayoutEffect.ts +13 -0
- package/src/components/providers/QueryProvider.tsx +15 -0
- package/src/components/providers/SharedHistoryContext.tsx +28 -0
- package/src/components/providers/ToolbarContext.tsx +123 -0
- package/src/components/providers/theme-provider.tsx +11 -0
- package/src/components/theme/ModeToggle.tsx +40 -0
- package/src/components/ui/FileInput.tsx +40 -0
- package/src/components/ui/Input.css +32 -0
- package/src/components/ui/Select.css +42 -0
- package/src/components/ui/Select.tsx +36 -0
- package/src/components/ui/TextInput.tsx +48 -0
- package/src/components/ui/ai/ai-button.tsx +574 -0
- package/src/components/ui/ai/border.tsx +99 -0
- package/src/components/ui/ai/placeholder-input-vanish.tsx +282 -0
- package/src/components/ui/button.tsx +89 -0
- package/src/components/ui/card.tsx +76 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/command.tsx +153 -0
- package/src/components/ui/dialog/Dialog.css +25 -0
- package/src/components/ui/dialog/Dialog.tsx +34 -0
- package/src/components/ui/dialog.tsx +122 -0
- package/src/components/ui/drop-downs/background-color.tsx +183 -0
- package/src/components/ui/drop-downs/block-format.tsx +159 -0
- package/src/components/ui/drop-downs/code.tsx +42 -0
- package/src/components/ui/drop-downs/color.tsx +177 -0
- package/src/components/ui/drop-downs/font-size.tsx +138 -0
- package/src/components/ui/drop-downs/font.tsx +155 -0
- package/src/components/ui/drop-downs/index.tsx +122 -0
- package/src/components/ui/drop-downs/insert-node.tsx +213 -0
- package/src/components/ui/drop-downs/text-align.tsx +123 -0
- package/src/components/ui/drop-downs/text-format.tsx +104 -0
- package/src/components/ui/dropdown-menu.tsx +201 -0
- package/src/components/ui/equation/EquationEditor.css +38 -0
- package/src/components/ui/equation/EquationEditor.tsx +56 -0
- package/src/components/ui/equation/KatexEquationAlterer.css +41 -0
- package/src/components/ui/equation/KatexEquationAlterer.tsx +83 -0
- package/src/components/ui/equation/KatexRenderer.tsx +66 -0
- package/src/components/ui/excalidraw/ExcalidrawModal.css +64 -0
- package/src/components/ui/excalidraw/ExcalidrawModal.tsx +234 -0
- package/src/components/ui/excalidraw/Modal.css +62 -0
- package/src/components/ui/excalidraw/Modal.tsx +110 -0
- package/src/components/ui/hover-card.tsx +29 -0
- package/src/components/ui/image/error-image.tsx +17 -0
- package/src/components/ui/image/file-upload.tsx +240 -0
- package/src/components/ui/image/image-resizer.tsx +297 -0
- package/src/components/ui/image/image-toolbar.tsx +264 -0
- package/src/components/ui/image/index.tsx +408 -0
- package/src/components/ui/image/lazy-image.tsx +68 -0
- package/src/components/ui/image/lazy-video.tsx +71 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/models/custom-dialog.tsx +320 -0
- package/src/components/ui/models/insert-gif.tsx +90 -0
- package/src/components/ui/models/insert-image.tsx +52 -0
- package/src/components/ui/models/insert-poll.tsx +29 -0
- package/src/components/ui/models/insert-table.tsx +62 -0
- package/src/components/ui/models/use-model.tsx +91 -0
- package/src/components/ui/poll/poll-component.tsx +304 -0
- package/src/components/ui/popover.tsx +33 -0
- package/src/components/ui/progress.tsx +28 -0
- package/src/components/ui/scroll-area.tsx +48 -0
- package/src/components/ui/separator.tsx +31 -0
- package/src/components/ui/skeleton.tsx +15 -0
- package/src/components/ui/sonner.tsx +31 -0
- package/src/components/ui/stepper/step.tsx +179 -0
- package/src/components/ui/stepper/stepper.tsx +89 -0
- package/src/components/ui/textarea.tsx +22 -0
- package/src/components/ui/toggle.tsx +71 -0
- package/src/components/ui/tooltip.tsx +32 -0
- package/src/components/ui/write/text-format-floting-toolbar.tsx +346 -0
- package/src/lib/edgestore.ts +9 -0
- package/src/lib/pinecone-client.ts +0 -0
- package/src/lib/utils.ts +6 -0
- package/src/utils/docSerialization.ts +77 -0
- package/src/utils/emoji-list.ts +16615 -0
- package/src/utils/getDOMRangeRect.ts +27 -0
- package/src/utils/getSelectedNode.ts +27 -0
- package/src/utils/getThemeSelector.ts +25 -0
- package/src/utils/isMobileWidth.ts +7 -0
- package/src/utils/joinClasses.ts +13 -0
- package/src/utils/setFloatingElemPosition.ts +74 -0
- package/src/utils/setFloatingElemPositionForLinkEditor.ts +46 -0
- package/src/utils/swipe.ts +127 -0
- package/src/utils/url.ts +38 -0
- 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
|
+
};
|
package/src/components/editor/utils/setNodePlaceholderFromSelection/setPlaceholderOnSelection.ts
ADDED
|
@@ -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,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;
|