mirror of
https://github.com/docmost/docmost.git
synced 2025-11-12 16:02:35 +10:00
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:
@ -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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
|
||||
Reference in New Issue
Block a user