Skip to content

Commit 2a02a2a

Browse files
Merge pull request #59 from kuluruvineeth/rollback/multi-assistant
Rollback/multi assistant
2 parents 4172ec6 + 2263183 commit 2a02a2a

File tree

22 files changed

+2011
-25
lines changed

22 files changed

+2011
-25
lines changed

apps/api/vercel.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,10 @@
44
"path": "/cron/keep-alive",
55
"schedule": "0 1 * * *"
66
}
7-
]
7+
],
8+
"git": {
9+
"deploymentEnabled": {
10+
"main": true
11+
}
12+
}
813
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
'use client';
2+
3+
import { Separator } from '@repo/design-system/components/ui/separator';
4+
import {
5+
EditorCommand,
6+
EditorCommandEmpty,
7+
EditorCommandItem,
8+
EditorCommandList,
9+
EditorContent,
10+
type EditorInstance,
11+
EditorRoot,
12+
type JSONContent,
13+
} from 'novel';
14+
import { ImageResizer, handleCommandNavigation } from 'novel';
15+
import { useEffect, useState } from 'react';
16+
import { defaultExtensions } from './extensions';
17+
import { ColorSelector } from './selectors/color-selector';
18+
import { LinkSelector } from './selectors/link-selector';
19+
import { MathSelector } from './selectors/math-selector';
20+
import { NodeSelector } from './selectors/node-selector';
21+
22+
import { defaultEditorContent } from '@/lib/content';
23+
import { handleImageDrop, handleImagePaste } from 'novel';
24+
import { useDebouncedCallback } from 'use-debounce';
25+
import GenerativeMenuSwitch from './generative/generative-menu-switch';
26+
import { uploadFn } from './image-upload';
27+
import { TextButtons } from './selectors/text-buttons';
28+
import { slashCommand, suggestionItems } from './slash-command';
29+
30+
const hljs = require('highlight.js');
31+
32+
export const AdvancedEditor = () => {
33+
const [initialContent, setInitialContent] = useState<null | JSONContent>(
34+
null
35+
);
36+
const extensions = [...defaultExtensions, slashCommand];
37+
const [charsCount, setCharsCount] = useState();
38+
const [saveStatus, setSaveStatus] = useState('Saved');
39+
const [openNode, setOpenNode] = useState(false);
40+
const [openColor, setOpenColor] = useState(false);
41+
const [openLink, setOpenLink] = useState(false);
42+
const [openAI, setOpenAI] = useState(false);
43+
44+
//Apply Codeblock Highlighting on the HTML from editor.getHTML()
45+
const highlightCodeblocks = (content: string) => {
46+
const doc = new DOMParser().parseFromString(content, 'text/html');
47+
doc.querySelectorAll('pre code').forEach((el) => {
48+
// @ts-ignore
49+
// https://highlightjs.readthedocs.io/en/latest/api.html?highlight=highlightElement#highlightelement
50+
hljs.highlightElement(el);
51+
});
52+
return new XMLSerializer().serializeToString(doc);
53+
};
54+
55+
const debouncedUpdates = useDebouncedCallback(
56+
async (editor: EditorInstance) => {
57+
const json = editor.getJSON();
58+
console.log('json', json);
59+
setCharsCount(editor.storage.characterCount.words());
60+
window.localStorage.setItem(
61+
'html-content',
62+
highlightCodeblocks(editor.getHTML())
63+
);
64+
window.localStorage.setItem('novel-content', JSON.stringify(json));
65+
window.localStorage.setItem(
66+
'markdown',
67+
editor.storage.markdown.getMarkdown()
68+
);
69+
setSaveStatus('Saved');
70+
},
71+
500
72+
);
73+
74+
useEffect(() => {
75+
const content = window.localStorage.getItem('novel-content');
76+
if (content) setInitialContent(JSON.parse(content));
77+
else setInitialContent(defaultEditorContent);
78+
}, []);
79+
80+
if (!initialContent) return null;
81+
82+
return (
83+
<div className="relative w-full max-w-screen-lg">
84+
<div className="absolute top-5 right-5 z-10 mb-5 flex gap-2">
85+
<div
86+
className={
87+
charsCount
88+
? 'rounded-lg bg-accent px-2 py-1 text-muted-foreground text-sm'
89+
: 'hidden'
90+
}
91+
>
92+
{charsCount} Words
93+
</div>
94+
</div>
95+
<EditorRoot>
96+
<EditorContent
97+
initialContent={initialContent}
98+
extensions={extensions}
99+
className="relative min-h-[500px] w-full p-24"
100+
editorProps={{
101+
handleDOMEvents: {
102+
keydown: (_view, event) => handleCommandNavigation(event),
103+
},
104+
handlePaste: (view, event) =>
105+
handleImagePaste(view, event, uploadFn),
106+
handleDrop: (view, event, _slice, moved) =>
107+
handleImageDrop(view, event, moved, uploadFn),
108+
attributes: {
109+
class:
110+
'prose dark:prose-invert prose-headings:font-title font-default focus:outline-none max-w-full',
111+
},
112+
}}
113+
onUpdate={({ editor }) => {
114+
debouncedUpdates(editor);
115+
setSaveStatus('Unsaved');
116+
}}
117+
slotAfter={<ImageResizer />}
118+
immediatelyRender={false}
119+
>
120+
{/* <div className="absolute left-full ml-4">
121+
<Threads />
122+
</div> */}
123+
<EditorCommand className="z-50 h-auto max-h-[330px] overflow-y-auto rounded-md border border-muted bg-background px-1 py-2 shadow-md transition-all">
124+
<EditorCommandEmpty className="px-2 text-muted-foreground">
125+
No results
126+
</EditorCommandEmpty>
127+
<EditorCommandList>
128+
{suggestionItems.map((item) => (
129+
<EditorCommandItem
130+
value={item.title}
131+
onCommand={(val) => {
132+
if (!item?.command) {
133+
return;
134+
}
135+
136+
item.command(val);
137+
}}
138+
className="flex w-full items-center space-x-2 rounded-md px-2 py-1 text-left text-sm hover:bg-accent aria-selected:bg-accent"
139+
key={item.title}
140+
>
141+
<div className="flex h-10 w-10 items-center justify-center rounded-md border border-muted bg-background">
142+
{item.icon}
143+
</div>
144+
<div>
145+
<p className="font-medium">{item.title}</p>
146+
<p className="text-muted-foreground text-xs">
147+
{item.description}
148+
</p>
149+
</div>
150+
</EditorCommandItem>
151+
))}
152+
</EditorCommandList>
153+
</EditorCommand>
154+
155+
<GenerativeMenuSwitch open={openAI} onOpenChange={setOpenAI}>
156+
<Separator orientation="vertical" />
157+
<NodeSelector open={openNode} onOpenChange={setOpenNode} />
158+
<Separator orientation="vertical" />
159+
<LinkSelector open={openLink} onOpenChange={setOpenLink} />
160+
<Separator orientation="vertical" />
161+
<MathSelector />
162+
<Separator orientation="vertical" />
163+
<TextButtons />
164+
<Separator orientation="vertical" />
165+
<ColorSelector open={openColor} onOpenChange={setOpenColor} />
166+
<Separator orientation="vertical" />
167+
</GenerativeMenuSwitch>
168+
</EditorContent>
169+
</EditorRoot>
170+
</div>
171+
);
172+
};
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import { cn } from '@repo/design-system/lib/utils';
2+
import { common, createLowlight } from 'lowlight';
3+
import {
4+
AIHighlight,
5+
CharacterCount,
6+
CodeBlockLowlight,
7+
Color,
8+
CustomKeymap,
9+
GlobalDragHandle,
10+
HighlightExtension,
11+
HorizontalRule,
12+
Mathematics,
13+
Placeholder,
14+
StarterKit,
15+
TaskItem,
16+
TaskList,
17+
TextStyle,
18+
TiptapImage,
19+
TiptapLink,
20+
TiptapUnderline,
21+
Twitter,
22+
UpdatedImage,
23+
UploadImagesPlugin,
24+
Youtube,
25+
} from 'novel';
26+
import { Markdown } from 'tiptap-markdown';
27+
28+
const aiHighlight = AIHighlight;
29+
//You can overwrite the placeholder with your own configuration
30+
const placeholder = Placeholder;
31+
const tiptapLink = TiptapLink.configure({
32+
HTMLAttributes: {
33+
class: cn(
34+
'cursor-pointer text-muted-foreground underline underline-offset-[3px] transition-colors hover:text-primary'
35+
),
36+
},
37+
});
38+
39+
const MarkdownExtension = Markdown.configure({
40+
html: false,
41+
transformCopiedText: true,
42+
});
43+
44+
const tiptapImage = TiptapImage.extend({
45+
addProseMirrorPlugins() {
46+
return [
47+
UploadImagesPlugin({
48+
imageClass: cn('rounded-lg border border-stone-200 opacity-40'),
49+
}),
50+
];
51+
},
52+
}).configure({
53+
allowBase64: true,
54+
HTMLAttributes: {
55+
class: cn('rounded-lg border border-muted'),
56+
},
57+
});
58+
59+
const updatedImage = UpdatedImage.configure({
60+
HTMLAttributes: {
61+
class: cn('rounded-lg border border-muted'),
62+
},
63+
});
64+
65+
const taskList = TaskList.configure({
66+
HTMLAttributes: {
67+
class: cn('not-prose pl-2 '),
68+
},
69+
});
70+
const taskItem = TaskItem.configure({
71+
HTMLAttributes: {
72+
class: cn('my-4 flex items-start gap-2'),
73+
},
74+
nested: true,
75+
});
76+
77+
const horizontalRule = HorizontalRule.configure({
78+
HTMLAttributes: {
79+
class: cn('mt-4 mb-6 border-muted-foreground border-t'),
80+
},
81+
});
82+
83+
const starterKit = StarterKit.configure({
84+
bulletList: {
85+
HTMLAttributes: {
86+
class: cn('-mt-2 list-outside list-disc leading-3'),
87+
},
88+
},
89+
orderedList: {
90+
HTMLAttributes: {
91+
class: cn('-mt-2 list-outside list-decimal leading-3'),
92+
},
93+
},
94+
listItem: {
95+
HTMLAttributes: {
96+
class: cn('-mb-2 leading-normal'),
97+
},
98+
},
99+
blockquote: {
100+
HTMLAttributes: {
101+
class: cn('border-primary border-l-4'),
102+
},
103+
},
104+
codeBlock: {
105+
HTMLAttributes: {
106+
class: cn(
107+
'rounded-md border bg-muted p-5 font-medium font-mono text-muted-foreground'
108+
),
109+
},
110+
},
111+
code: {
112+
HTMLAttributes: {
113+
class: cn('rounded-md bg-muted px-1.5 py-1 font-medium font-mono'),
114+
spellcheck: 'false',
115+
},
116+
},
117+
horizontalRule: false,
118+
dropcursor: {
119+
color: '#DBEAFE',
120+
width: 4,
121+
},
122+
gapcursor: false,
123+
});
124+
125+
const codeBlockLowlight = CodeBlockLowlight.configure({
126+
// configure lowlight: common / all / use highlightJS in case there is a need to specify certain language grammars only
127+
// common: covers 37 language grammars which should be good enough in most cases
128+
lowlight: createLowlight(common),
129+
});
130+
131+
const youtube = Youtube.configure({
132+
HTMLAttributes: {
133+
class: cn('rounded-lg border border-muted'),
134+
},
135+
inline: false,
136+
});
137+
138+
const twitter = Twitter.configure({
139+
HTMLAttributes: {
140+
class: cn('not-prose'),
141+
},
142+
inline: false,
143+
});
144+
145+
const mathematics = Mathematics.configure({
146+
HTMLAttributes: {
147+
class: cn('cursor-pointer rounded p-1 text-foreground hover:bg-accent'),
148+
},
149+
katexOptions: {
150+
throwOnError: false,
151+
},
152+
});
153+
154+
const characterCount = CharacterCount.configure();
155+
156+
const markdownExtension = MarkdownExtension.configure({
157+
html: true,
158+
tightLists: true,
159+
tightListClass: 'tight',
160+
bulletListMarker: '-',
161+
linkify: false,
162+
breaks: false,
163+
transformPastedText: false,
164+
transformCopiedText: false,
165+
});
166+
167+
export const defaultExtensions: any[] = [
168+
starterKit,
169+
placeholder,
170+
tiptapLink,
171+
tiptapImage,
172+
updatedImage,
173+
taskList,
174+
taskItem,
175+
horizontalRule,
176+
aiHighlight,
177+
codeBlockLowlight,
178+
youtube,
179+
twitter,
180+
mathematics,
181+
characterCount,
182+
TiptapUnderline,
183+
markdownExtension,
184+
HighlightExtension,
185+
TextStyle,
186+
Color,
187+
CustomKeymap,
188+
GlobalDragHandle,
189+
];

0 commit comments

Comments
 (0)