Skip to content

Commit 4eb0a0b

Browse files
lot of stuff is working
1 parent 0556caa commit 4eb0a0b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+2157
-589
lines changed

.eslintrc.cjs

+9-5
Original file line numberDiff line numberDiff line change
@@ -11,33 +11,37 @@ module.exports = {
1111
'plugin:react-hooks/recommended',
1212
'plugin:import/recommended',
1313
'plugin:import/typescript',
14+
'plugin:@tanstack/eslint-plugin-query/recommended',
15+
'plugin:valtio/recommended',
1416
],
15-
overrides: [],
1617
parser: '@typescript-eslint/parser',
1718
parserOptions: {
1819
project: './tsconfig.json',
1920
ecmaVersion: 'latest',
2021
sourceType: 'module',
2122
},
2223
settings: {
23-
"import/parsers": {
24-
"@typescript-eslint/parser": [".ts", ".tsx"]
24+
'import/parsers': {
25+
'@typescript-eslint/parser': ['.ts', '.tsx'],
2526
},
2627
'import/resolver': {
2728
node: {
2829
extensions: ['.js', '.jsx'],
2930
},
3031
typescript: {
31-
"project": "tsconfig.json"
32+
project: 'tsconfig.json',
3233
},
3334
},
3435
react: {
3536
version: '18',
3637
},
3738
},
38-
plugins: ['react', '@typescript-eslint', 'import'],
39+
plugins: ['react', '@typescript-eslint', 'import', '@tanstack/query'],
40+
ignorePatterns: ['.eslintrc.cjs'],
3941
rules: {
42+
'react/prop-types': 0,
4043
'react/react-in-jsx-scope': 0,
4144
'@typescript-eslint/no-explicit-any': 0,
45+
'import/no-unresolved': ['error', { ignore: ['^virtual:'] }],
4246
},
4347
}

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,4 @@ dist
106106
dev.db
107107
.DS_Store
108108
dev-dist/
109+
secret-key

.node-version

-1
This file was deleted.

.prettierrc

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"importOrderSortSpecifiers": true,
3+
"semi": false,
4+
"singleQuote": true,
5+
"jsxSingleQuote": true,
6+
"trailingComma": "all",
7+
"plugins": ["prettier-plugin-packagejson"]
8+
}

.vscode/settings.json

-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
"editor.defaultFormatter": "esbenp.prettier-vscode"
99
},
1010
"prettier.enable": true,
11-
"prettier.configPath": "prettier.config.cjs",
1211
"npm.packageManager": "pnpm",
1312
"editor.defaultFormatter": "esbenp.prettier-vscode",
1413
"editor.formatOnSave": true,

README.md

+13-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@ docker run -d -p 3000:3000 --env-file ./.env --name collab-editor -d collab-edit
99

1010
## Instructions
1111

12-
* rename `.env` to `.env.example`
13-
* install dependencies `pnpm i`
14-
* run in dev mode `pnpm dev`
12+
- rename `.env` to `.env.example`
13+
- install dependencies `pnpm i`
14+
- run in dev mode `pnpm dev`
15+
16+
17+
## File structure
18+
19+
* client
20+
* server
21+
* features
22+
* feature-api
23+
* feature-route
24+
* feature-query

client/features/editor/audio-item.tsx

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { createRef, useRef } from 'react'
2+
import { AudioRecorder } from 'react-audio-voice-recorder'
3+
import WaveSurfer from 'wavesurfer.js'
4+
5+
export const AudioItem = () => {
6+
const divRef = createRef<HTMLDivElement>()
7+
const wavesurferRef = useRef<WaveSurfer>()
8+
9+
return (
10+
<div className='flex flex-row'>
11+
<AudioRecorder
12+
onRecordingComplete={(blob) => {
13+
const url = URL.createObjectURL(blob)
14+
15+
if (wavesurferRef.current) {
16+
wavesurferRef.current.destroy()
17+
}
18+
19+
const wavesurfer = WaveSurfer.create({
20+
container: divRef.current!,
21+
waveColor: '#4F4A85',
22+
progressColor: '#383351',
23+
url,
24+
cursorWidth: 4,
25+
height: 40,
26+
})
27+
28+
wavesurferRef.current = wavesurfer
29+
30+
wavesurfer.once('interaction', () => {
31+
if (wavesurfer.isPlaying()) {
32+
wavesurfer.pause()
33+
} else {
34+
wavesurfer.play()
35+
}
36+
})
37+
}}
38+
audioTrackConstraints={{
39+
noiseSuppression: true,
40+
echoCancellation: true,
41+
}}
42+
downloadOnSavePress={false}
43+
downloadFileExtension='webm'
44+
/>
45+
<div ref={divRef} className='flex-1 pl-2' />
46+
</div>
47+
)
48+
}

client/features/my-block-note-editor.tsx client/features/editor/my-block-note-editor.tsx

+23-13
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,40 @@ import { BlockNoteEditor } from '@blocknote/core'
22
import '@blocknote/core/style.css'
33
import { BlockNoteView, useBlockNote } from '@blocknote/react'
44
import { HocuspocusProvider } from '@hocuspocus/provider'
5+
import { useEffect } from 'react'
6+
import useContant from 'use-constant'
57
import { IndexeddbPersistence } from 'y-indexeddb'
68
import * as Y from 'yjs'
79

810
const getRandomColor = () => Math.floor(Math.random() * 16777215).toString(16)
9-
const ydoc = new Y.Doc()
11+
1012
const DOC_NAME = 'block-note-editor'
1113
const url =
1214
window.location.hostname === 'localhost'
1315
? `ws://localhost:3000/api/editor/${DOC_NAME}`
1416
: `wss://collab-editor.fly.dev/api/editor/${DOC_NAME}`
1517

16-
new IndexeddbPersistence(DOC_NAME, ydoc)
17-
const hocuspocus = new HocuspocusProvider({
18-
document: ydoc,
19-
url,
20-
name: DOC_NAME,
21-
onConnect() {
22-
console.log(`${DOC_NAME} connected`)
23-
},
24-
})
25-
2618
export function MyBlockNoteEditor() {
27-
// Creates a new editor instance.
19+
const ydoc = useContant(() => new Y.Doc())
20+
const hocuspocus = useContant(
21+
() =>
22+
new HocuspocusProvider({
23+
document: ydoc,
24+
url,
25+
name: DOC_NAME,
26+
onConnect() {
27+
console.log(`${DOC_NAME} connected`)
28+
},
29+
}),
30+
)
31+
const persistence = useContant(() => new IndexeddbPersistence(DOC_NAME, ydoc))
32+
33+
useEffect(() => {
34+
persistence.on('sync', () => {
35+
console.log('synced')
36+
})
37+
}, [persistence])
38+
2839
const editor: BlockNoteEditor | null = useBlockNote({
2940
collaboration: {
3041
provider: hocuspocus,
@@ -36,6 +47,5 @@ export function MyBlockNoteEditor() {
3647
},
3748
})
3849

39-
// Renders the editor instance using a React component.
4050
return <BlockNoteView editor={editor} />
4151
}

client/features/page-animation.tsx

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { motion } from 'framer-motion'
2+
import { ReactNode } from 'react'
3+
4+
export const PageAnimation: React.FC<{ children: ReactNode }> = ({
5+
children,
6+
}) => {
7+
return (
8+
<motion.article
9+
role='main'
10+
className='prose'
11+
initial={{ opacity: 0 }}
12+
animate={{ opacity: 1 }}
13+
exit={{ opacity: 0 }}
14+
transition={{
15+
type: 'spring',
16+
stiffness: 260,
17+
damping: 20,
18+
}}
19+
>
20+
{children}
21+
</motion.article>
22+
)
23+
}

client/main.tsx

+54-38
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,63 @@
11
import React from 'react'
22
import ReactDOM from 'react-dom/client'
3+
import { createBrowserRouter, RouterProvider } from 'react-router-dom'
4+
import 'react-toastify/dist/ReactToastify.css'
35

4-
import { CollabEditor } from './features/collab-editor.js'
56
import './features/monaco-worker.js'
67

78
import './main.css'
89
// eslint-disable-next-line import/no-unresolved
10+
import { TrpcClient } from '@client/trpc-client.js'
11+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
12+
import { httpBatchLink } from '@trpc/client'
13+
import { toast, ToastContainer } from 'react-toastify'
914
import { registerSW } from 'virtual:pwa-register'
10-
import { MyBlockNoteEditor } from './features/my-block-note-editor.js'
11-
12-
const updateSW = registerSW({
13-
immediate: true,
14-
onOfflineReady() {
15-
alert('Offline ready')
16-
},
17-
onNeedRefresh() {
18-
if (confirm('New content available. Reload?')) {
19-
updateSW(true)
15+
import { routes } from './pages/__routes.js'
16+
17+
if (import.meta.env.PROD) {
18+
const updateSW = registerSW({
19+
immediate: true,
20+
onOfflineReady() {
21+
toast('Offline ready')
22+
},
23+
onNeedRefresh() {
24+
if (confirm('New content available. Reload?')) {
25+
updateSW(true)
26+
}
27+
},
28+
})
29+
}
30+
31+
if (!import.meta.env.SSR) {
32+
let name: string | null | undefined
33+
34+
do {
35+
name = localStorage.getItem('name') ?? window.prompt('Write your name')
36+
37+
if (name) {
38+
localStorage.setItem('name', name)
2039
}
21-
},
22-
})
23-
24-
let name: string | null | undefined
25-
26-
do {
27-
name = localStorage.getItem('name') ?? window.prompt('Write your name')
28-
29-
if (name) {
30-
localStorage.setItem('name', name)
31-
}
32-
} while (!name)
33-
34-
window.name = name
35-
36-
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
37-
<React.StrictMode>
38-
<div className='grid grid-cols-2'>
39-
<div>
40-
<CollabEditor />
41-
</div>
42-
<div className='bg-white'>
43-
<MyBlockNoteEditor />
44-
</div>
45-
</div>
46-
</React.StrictMode>,
47-
)
40+
} while (!name)
41+
42+
const router = createBrowserRouter(routes)
43+
44+
const queryClient = new QueryClient()
45+
const trpcClient = TrpcClient.createClient({
46+
links: [
47+
httpBatchLink({
48+
url: '/trpc',
49+
}),
50+
],
51+
})
52+
53+
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
54+
<React.StrictMode>
55+
<TrpcClient.Provider client={trpcClient} queryClient={queryClient}>
56+
<QueryClientProvider client={queryClient}>
57+
<RouterProvider router={router} />
58+
<ToastContainer />
59+
</QueryClientProvider>
60+
</TrpcClient.Provider>
61+
</React.StrictMode>,
62+
)
63+
}

0 commit comments

Comments
 (0)