feat: excalidraw integration (#214)

* update tiptap version

* excalidraw init

* cleanup

* better file handling and other fixes

* use different modal to fix excalidraw cursor position issue
* see https://github.com/excalidraw/excalidraw/issues/7312
* fix websocket in vite dev mode

* WIP

* add align attribute

* fix table

* menu icons

* Render image in excalidraw html
* add size to custom SVG components

* rewrite undefined font urls
This commit is contained in:
Philip Okugbe
2024-08-31 19:11:07 +01:00
committed by GitHub
parent 77b541ec71
commit 38e9eef2dc
26 changed files with 1440 additions and 799 deletions

View File

@ -28,6 +28,7 @@ import {
TiptapVideo,
TrailingNode,
Attachment,
Excalidraw,
} from '@docmost/editor-ext';
import { generateText, JSONContent } from '@tiptap/core';
import { generateHTML } from '../common/helpers/prosemirror/html';
@ -38,7 +39,7 @@ import { generateJSON } from '@tiptap/html';
export const tiptapExtensions = [
StarterKit.configure({
codeBlock: false
codeBlock: false,
}),
Comment,
TextAlign,
@ -67,7 +68,8 @@ export const tiptapExtensions = [
TiptapVideo,
Callout,
Attachment,
CustomCodeBlock
CustomCodeBlock,
Excalidraw,
] as any;
export function jsonToHtml(tiptapJson: any) {

View File

@ -75,7 +75,7 @@ export class AttachmentController {
let file = null;
try {
file = await req.file({
limits: { fileSize: maxFileSize, fields: 2, files: 1 },
limits: { fileSize: maxFileSize, fields: 3, files: 1 },
});
} catch (err: any) {
this.logger.error(err.message);
@ -112,6 +112,11 @@ export class AttachmentController {
const spaceId = page.spaceId;
const attachmentId = file.fields?.attachmentId?.value;
if (attachmentId && !isValidUUID(attachmentId)) {
throw new BadRequestException('Invalid attachment id');
}
try {
const fileResponse = await this.attachmentService.uploadFile({
filePromise: file,
@ -119,6 +124,7 @@ export class AttachmentController {
spaceId: spaceId,
userId: user.id,
workspaceId: workspace.id,
attachmentId: attachmentId,
});
return res.send(fileResponse);
@ -168,7 +174,7 @@ export class AttachmentController {
try {
const fileStream = await this.storageService.read(attachment.filePath);
res.headers({
'Content-Type': getMimeType(attachment.filePath),
'Content-Type': attachment.mimeType,
'Cache-Control': 'public, max-age=3600',
});
return res.send(fileStream);

View File

@ -1,4 +1,9 @@
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
import {
BadRequestException,
Injectable,
Logger,
NotFoundException,
} from '@nestjs/common';
import { StorageService } from '../../../integrations/storage/storage.service';
import { MultipartFile } from '@fastify/multipart';
import {
@ -36,27 +41,64 @@ export class AttachmentService {
userId: string;
spaceId: string;
workspaceId: string;
attachmentId?: string;
}) {
const { filePromise, pageId, spaceId, userId, workspaceId } = opts;
const preparedFile: PreparedFile = await prepareFile(filePromise);
const attachmentId = uuid7();
let isUpdate = false;
let attachmentId = null;
// passing attachmentId to allow for updating diagrams
// instead of creating new files for each save
if (opts?.attachmentId) {
let existingAttachment = await this.attachmentRepo.findById(
opts.attachmentId,
);
if (!existingAttachment) {
throw new NotFoundException(
'Existing attachment to overwrite not found',
);
}
if (
existingAttachment.pageId !== pageId &&
existingAttachment.fileExt !== preparedFile.fileExtension &&
existingAttachment.workspaceId !== workspaceId
) {
throw new BadRequestException('File attachment does not match');
}
attachmentId = opts.attachmentId;
isUpdate = true;
} else {
attachmentId = uuid7();
}
const filePath = `${getAttachmentFolderPath(AttachmentType.File, workspaceId)}/${attachmentId}/${preparedFile.fileName}`;
await this.uploadToDrive(filePath, preparedFile.buffer);
let attachment: Attachment = null;
try {
attachment = await this.saveAttachment({
attachmentId,
preparedFile,
filePath,
type: AttachmentType.File,
userId,
spaceId,
workspaceId,
pageId,
});
if (isUpdate) {
attachment = await this.attachmentRepo.updateAttachment(
{
updatedAt: new Date(),
},
attachmentId,
);
} else {
attachment = await this.saveAttachment({
attachmentId,
preparedFile,
filePath,
type: AttachmentType.File,
userId,
spaceId,
workspaceId,
pageId,
});
}
} catch (err) {
// delete uploaded file on error
this.logger.error(err);

View File

@ -43,8 +43,8 @@ export class AttachmentRepo {
async updateAttachment(
updatableAttachment: UpdatableAttachment,
attachmentId: string,
): Promise<void> {
await this.db
): Promise<Attachment> {
return await this.db
.updateTable('attachments')
.set(updatableAttachment)
.where('id', '=', attachmentId)