Skip to content

Commit ec10edb

Browse files
authored
Cp 48 Add autocomplete and arrow keys support (#14)
* initial setup for color picker * initial tiptap editor setup * floating menu container's layout added * floating menu: heading, list, paragraph features * floating menu: table, list * comment * comment change * comment change * bubble menu container features * editor features: link, callout, embed * added extension starter kit for now * changed cropper aspect ratio to 16/9 * updated styling to match the design * setup of autofield selector in editor after {{ * autofill fields feature integrated * autofill fields feature integrated * fix bug white storing original template * added background color feature * added all icons as svg in src directory * fix minor bug * fix eslint errors * fix import orders * changed state name to generic * removed if editor is defined check * created separate FieldOptions component * added separate component for reusable codes * fix: import order, separated reusable components * fix: import order, separated reusable components * fix merge conflict * fix eslint errors * fix merge conflict * removed top HOME layout * added type / for commands initially * added undo feature * removed autofill fields from bubble menu dropdown * fix bullet list typo * added link on bubble menu * fix autocomplete extra {{ adding issue * added autocomplete on / command * autofill field inserts on cursor position * added callout in formating option * formating option shows the current formating used * fix minor issue * eslint fix * setup for autocomplete * integrated settings api * integrated settings api * changed api base url * changed api base url * changed api base url * removed baseUrl from constants * resolved bugs * added separate component for floating menu btns * added placeholder extension for placeholder * integrated with the copilot apis * smoothen color picker/removed footer after save * aligned colorpicker properly * remove footer after cancel button click * added packages * autofill menu: autocomplete and arrow command * added fixes for autofill * fixed issues * fixed issues * fixes: padding,undefined value,events * resolved PR review comments
1 parent 4494e41 commit ec10edb

29 files changed

+3000
-1337
lines changed

.eslintrc.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"no-unused-vars": "warn",
2828
"@typescript-eslint/no-unused-vars": ["warn"],
2929
"@next/next/no-img-element": "off",
30-
"react-hooks/exhaustive-deps": "off"
30+
"react-hooks/exhaustive-deps": "off",
31+
"@typescript-eslint/no-explicit-any": "warn"
3132
}
3233
}

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@
1919
"@emotion/styled": "^11.11.0",
2020
"@mui/icons-material": "^5.14.18",
2121
"@mui/material": "^5.14.18",
22+
"@prisma/client": "^5.6.0",
2223
"@tiptap/extension-gapcursor": "^2.1.12",
2324
"@tiptap/extension-image": "^2.1.12",
2425
"@tiptap/extension-link": "^2.1.12",
26+
"@tiptap/extension-mention": "^2.1.13",
27+
"@tiptap/extension-placeholder": "^2.1.13",
2528
"@tiptap/extension-table": "^2.1.12",
2629
"@tiptap/extension-table-cell": "^2.1.12",
2730
"@tiptap/extension-table-header": "^2.1.12",
@@ -30,13 +33,15 @@
3033
"@tiptap/pm": "^2.1.12",
3134
"@tiptap/react": "^2.1.12",
3235
"@tiptap/starter-kit": "^2.1.12",
36+
"@tiptap/suggestion": "^2.1.12",
37+
"@types/react-color": "^3.0.10",
3338
"handlebars": "^4.7.8",
34-
"@prisma/client": "^5.6.0",
3539
"@vercel/blob": "^0.15.1",
3640
"@vercel/postgres": "^0.5.1",
3741
"next": "latest",
3842
"prisma": "^5.6.0",
3943
"react": "latest",
44+
"react-color": "^2.19.3",
4045
"react-dom": "latest",
4146
"react-dropzone": "^14.2.3",
4247
"react-easy-crop": "^5.0.2",

src/app/api/autofill/route.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { NextResponse } from 'next/server'
2+
3+
export async function GET() {
4+
const options = {
5+
method: 'GET',
6+
headers: {
7+
accept: 'application/json',
8+
'X-API-KEY': process.env.COPILOT_API_KEY as string,
9+
},
10+
}
11+
12+
const res = await fetch(
13+
'https://api-beta.copilot.com/v1/custom-fields',
14+
options,
15+
)
16+
const { data } = await res.json()
17+
return NextResponse.json({ autofillFields: data })
18+
}

src/app/api/companies/route.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { NextResponse, NextRequest } from 'next/server'
2+
3+
export async function GET(req: NextRequest) {
4+
const companyId = req.nextUrl.searchParams.get('companyId') as string
5+
6+
const options = {
7+
method: 'GET',
8+
headers: {
9+
accept: 'application/json',
10+
'X-API-KEY': process.env.COPILOT_API_KEY as string,
11+
},
12+
}
13+
14+
const res = await fetch(
15+
`https://api-beta.copilot.com/v1/companies/${companyId}`,
16+
options,
17+
)
18+
const company = await res.json()
19+
return NextResponse.json({ data: company })
20+
}

src/app/components/EditorInterface.tsx

Lines changed: 104 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
'use client'
22

3+
import { useEditor, EditorContent } from '@tiptap/react'
4+
import { useEffect, useState } from 'react'
5+
36
import Handlebars from 'handlebars'
47
import CalloutExtension from '@/components/tiptap/callout/CalloutExtension'
58
import Document from '@tiptap/extension-document'
@@ -23,17 +26,20 @@ import Italic from '@tiptap/extension-italic'
2326
import Strike from '@tiptap/extension-strike'
2427
import Gapcursor from '@tiptap/extension-gapcursor'
2528
import History from '@tiptap/extension-history'
29+
import Placeholder from '@tiptap/extension-placeholder'
30+
import Mention from '@tiptap/extension-mention'
31+
import FloatingCommandExtension from '@/components/tiptap/floatingMenu/floatingCommandExtension'
32+
import { floatingMenuSuggestion } from '@/components/tiptap/floatingMenu/floatingMenuSuggestion'
33+
import { autofillMenuSuggestion } from '@/components/tiptap/autofieldSelector/autofillMenuSuggestion'
2634

27-
import AutofieldSelector from '@/components/tiptap/autofieldSelector/AutofieldSelector'
28-
import FloatingMenuContainer from '@/components/tiptap/floatingMenu/FloatingMenu'
2935
import BubbleMenuContainer from '@/components/tiptap/bubbleMenu/BubbleMenu'
3036
import LinkInput from '@/components/tiptap/linkInput/LinkInput'
3137
import NoteDisplay from '@/components/display/NoteDisplay'
3238
import { When } from '@/components/hoc/When'
3339

3440
import { useAppState } from '@/hooks/useAppState'
35-
import { useEditor, EditorContent } from '@tiptap/react'
36-
import { FC, useEffect, useState } from 'react'
41+
import { IClient, ISettings } from '@/types/interfaces'
42+
import LoaderComponent from '@/components/display/Loader'
3743

3844
const EditorInterface = () => {
3945
const appState = useAppState()
@@ -53,6 +59,18 @@ const EditorInterface = () => {
5359
CalloutExtension,
5460
Gapcursor,
5561
History,
62+
FloatingCommandExtension.configure({
63+
suggestion: floatingMenuSuggestion,
64+
}),
65+
Mention.configure({
66+
suggestion: autofillMenuSuggestion,
67+
renderLabel({ node }) {
68+
return `${node.attrs.label ?? node.attrs.id}`
69+
},
70+
}),
71+
Placeholder.configure({
72+
placeholder: initialEditorContent,
73+
}),
5674
Link.extend({
5775
exitable: true,
5876
}),
@@ -86,7 +104,7 @@ const EditorInterface = () => {
86104
CodeBlock,
87105
Code,
88106
],
89-
content: initialEditorContent,
107+
content: '',
90108
})
91109

92110
const [originalTemplate, setOriginalTemplate] = useState<string | undefined>()
@@ -101,46 +119,104 @@ const EditorInterface = () => {
101119
useEffect(() => {
102120
if (appState?.appState.readOnly) {
103121
const template = Handlebars?.compile(originalTemplate || '')
104-
const mockData = appState.appState.mockData.filter(
105-
(el) => el.givenName === appState.appState.selectedClient,
106-
)[0]
107-
const c = template({ client: mockData })
108-
editor?.chain().focus().setContent(c).run()
122+
const _client = appState.appState.clientList.find(
123+
(el) => el.id === (appState.appState.selectedClient as IClient).id,
124+
)
125+
const client = {
126+
..._client,
127+
company: appState?.appState.selectedClientCompanyName,
128+
}
129+
const c = template({ client: client })
130+
setTimeout(() => {
131+
editor?.chain().focus().setContent(c).run()
132+
})
109133
} else {
110-
editor
111-
?.chain()
112-
.focus()
113-
.setContent(originalTemplate as string)
114-
.run()
134+
setTimeout(() => {
135+
editor
136+
?.chain()
137+
.focus()
138+
.setContent(originalTemplate as string)
139+
.run()
140+
})
115141
}
116-
}, [appState?.appState.selectedClient])
142+
}, [
143+
appState?.appState.selectedClient,
144+
appState?.appState.selectedClientCompanyName,
145+
])
117146

118147
useEffect(() => {
119-
if (appState?.appState.readOnly) return
120-
setOriginalTemplate(editor?.getHTML())
148+
if (!appState?.appState.readOnly) {
149+
setOriginalTemplate(editor?.getHTML())
150+
}
121151
}, [editor?.getText(), appState?.appState.readOnly])
122152

123153
useEffect(() => {
124-
if (!editor) return
154+
if (appState?.appState.settings) {
155+
if (
156+
originalTemplate !== appState?.appState.settings.content ||
157+
appState?.appState.settings.backgroundColor !==
158+
appState?.appState.editorColor
159+
) {
160+
appState?.toggleChangesCreated(true)
161+
} else {
162+
appState?.toggleChangesCreated(false)
163+
}
164+
}
165+
}, [
166+
originalTemplate,
167+
appState?.appState.editorColor,
168+
appState?.appState.bannerImg,
169+
appState?.appState.readOnly,
170+
])
125171

126-
appState?.setEditor(editor)
172+
useEffect(() => {
173+
if (editor) {
174+
appState?.setEditor(editor)
127175

128-
const handleKeyDown = (event: KeyboardEvent) => {
129-
if (event.metaKey && event.key === 'z') {
130-
event.preventDefault() // Prevent the default behavior of Cmd+Z (e.g., browser undo)
131-
editor.chain().focus().undo().run() // Perform undo operation
176+
const handleKeyDown = (event: KeyboardEvent) => {
177+
if (event.metaKey && event.key === 'z') {
178+
event.preventDefault() // Prevent the default behavior of Cmd+Z (e.g., browser undo)
179+
editor.chain().focus().undo().run() // Perform undo operation
180+
}
181+
}
182+
document.addEventListener('keydown', handleKeyDown)
183+
return () => {
184+
document.removeEventListener('keydown', handleKeyDown)
132185
}
133-
}
134-
document.addEventListener('keydown', handleKeyDown)
135-
return () => {
136-
document.removeEventListener('keydown', handleKeyDown)
137186
}
138187
}, [editor])
139188

189+
useEffect(() => {
190+
;(async () => {
191+
appState?.setLoading(true)
192+
const res = await fetch(`/api/settings`)
193+
const { data } = await res.json()
194+
setOriginalTemplate(data.content)
195+
appState?.setSettings(data)
196+
appState?.setLoading(false)
197+
})()
198+
}, [])
199+
200+
useEffect(() => {
201+
if (appState?.appState.settings) {
202+
editor
203+
?.chain()
204+
.focus()
205+
.setContent((appState?.appState.settings as ISettings).content)
206+
.run()
207+
appState?.setEditorColor(
208+
(appState?.appState.settings as ISettings).backgroundColor,
209+
)
210+
}
211+
}, [appState?.appState.settings])
212+
140213
if (!editor) return null
141214

142215
return (
143216
<>
217+
<When condition={appState?.appState.loading as boolean}>
218+
<LoaderComponent />
219+
</When>
144220
<div
145221
className={`overflow-y-auto overflow-x-hidden max-h-screen w-full ${
146222
appState?.appState.changesCreated && 'pb-10'
@@ -160,20 +236,13 @@ const EditorInterface = () => {
160236
}}
161237
>
162238
<div>
163-
<FloatingMenuContainer editor={editor} />
164239
<BubbleMenuContainer editor={editor} />
165240
<LinkInput editor={editor} />
166-
<AutofieldSelector editor={editor} />
167241
</div>
168242

169243
<EditorContent
170244
editor={editor}
171245
readOnly={appState?.appState.readOnly}
172-
onClick={() => {
173-
if (editor.getText() === initialEditorContent) {
174-
editor.chain().focus().clearContent().run()
175-
}
176-
}}
177246
/>
178247
</div>
179248
<When condition={!!appState?.appState.readOnly}>

src/app/components/Footer.tsx

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
'use client'
2+
3+
import { When } from '@/components/hoc/When'
4+
import { useAppState } from '@/hooks/useAppState'
5+
6+
export const Footer = () => {
7+
const appState = useAppState()
8+
9+
const handleSave = async () => {
10+
const content = appState?.appState.editor?.getHTML()
11+
try {
12+
appState?.setLoading(true)
13+
await fetch(`/api/settings`, {
14+
method: 'PUT',
15+
body: JSON.stringify({
16+
backgroundColor: appState?.appState.editorColor,
17+
content: content,
18+
}),
19+
})
20+
appState?.setLoading(false)
21+
appState?.toggleChangesCreated(false)
22+
} catch (e) {
23+
console.error(e)
24+
appState?.setLoading(false)
25+
}
26+
}
27+
28+
const handleCancel = async () => {
29+
if (appState?.appState.editor) {
30+
appState?.setEditorColor(
31+
appState.appState.settings?.backgroundColor as string,
32+
)
33+
appState?.appState.editor
34+
.chain()
35+
.focus()
36+
.setContent(appState?.appState.settings?.content as string)
37+
.run()
38+
}
39+
appState?.toggleChangesCreated(false)
40+
}
41+
42+
return (
43+
<When
44+
condition={
45+
(appState?.appState.changesCreated as boolean) &&
46+
!appState?.appState.readOnly
47+
}
48+
>
49+
<div className='w-full flex flex-row justify-end gap-6 py-4 px-6 fixed bottom-0 bg-white'>
50+
<button
51+
className='py-1 px-3 text-new-dark rounded text-[13px] rounded bg-white border border-slate-300'
52+
onClick={handleCancel}
53+
>
54+
Cancel
55+
</button>
56+
<button
57+
className='bg-new-dark py-1 px-3 text-white text-[13px] rounded'
58+
onClick={handleSave}
59+
>
60+
Save changes
61+
</button>
62+
</div>
63+
</When>
64+
)
65+
}

0 commit comments

Comments
 (0)