|
1 | 1 | // eslint-disable-next-line import/order |
2 | 2 | import './services/sentry'; |
| 3 | +import { ServerBlockNoteEditor } from '@blocknote/server-util'; |
3 | 4 | import { Server } from '@hocuspocus/server'; |
4 | 5 | import * as Sentry from '@sentry/node'; |
5 | 6 | import express, { Request, Response } from 'express'; |
6 | 7 | import expressWebsockets from 'express-ws'; |
| 8 | +import * as Y from 'yjs'; |
7 | 9 |
|
8 | 10 | import { PORT } from './env'; |
9 | 11 | import { httpSecurity, wsSecurity } from './middlewares'; |
10 | 12 | import { routes } from './routes'; |
11 | | -import { logger } from './utils'; |
| 13 | +import { logger, toBase64 } from './utils'; |
12 | 14 |
|
13 | 15 | export const hocuspocusServer = Server.configure({ |
14 | 16 | name: 'docs-y-server', |
@@ -133,6 +135,63 @@ export const initServer = () => { |
133 | 135 | }, |
134 | 136 | ); |
135 | 137 |
|
| 138 | + interface ConversionRequest { |
| 139 | + content: string; |
| 140 | + } |
| 141 | + |
| 142 | + interface ConversionResponse { |
| 143 | + content: string; |
| 144 | + } |
| 145 | + |
| 146 | + interface ErrorResponse { |
| 147 | + error: string; |
| 148 | + } |
| 149 | + |
| 150 | + /** |
| 151 | + * Route to convert markdown |
| 152 | + */ |
| 153 | + app.post( |
| 154 | + routes.CONVERT_MARKDOWN, |
| 155 | + httpSecurity, |
| 156 | + async ( |
| 157 | + req: Request< |
| 158 | + object, |
| 159 | + ConversionResponse | ErrorResponse, |
| 160 | + ConversionRequest, |
| 161 | + object |
| 162 | + >, |
| 163 | + res: Response<ConversionResponse | ErrorResponse>, |
| 164 | + ) => { |
| 165 | + const content = req.body?.content; |
| 166 | + |
| 167 | + if (!content) { |
| 168 | + res.status(400).json({ error: 'Invalid request: missing content' }); |
| 169 | + return; |
| 170 | + } |
| 171 | + |
| 172 | + try { |
| 173 | + const editor = ServerBlockNoteEditor.create(); |
| 174 | + |
| 175 | + // Perform the conversion from markdown to Blocknote.js blocks |
| 176 | + const blocks = await editor.tryParseMarkdownToBlocks(content); |
| 177 | + |
| 178 | + if (!blocks || blocks.length === 0) { |
| 179 | + res.status(500).json({ error: 'No valid blocks were generated' }); |
| 180 | + return; |
| 181 | + } |
| 182 | + |
| 183 | + // Create a Yjs Document from blocks, and encode it as a base64 string |
| 184 | + const yDocument = editor.blocksToYDoc(blocks, 'document-store'); |
| 185 | + const documentContent = toBase64(Y.encodeStateAsUpdate(yDocument)); |
| 186 | + |
| 187 | + res.status(200).json({ content: documentContent }); |
| 188 | + } catch (e) { |
| 189 | + logger('conversion failed:', e); |
| 190 | + res.status(500).json({ error: 'An error occurred' }); |
| 191 | + } |
| 192 | + }, |
| 193 | + ); |
| 194 | + |
136 | 195 | Sentry.setupExpressErrorHandler(app); |
137 | 196 |
|
138 | 197 | app.get('/ping', (req, res) => { |
|
0 commit comments