Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Améliorer la saisie avec un éditeur WYSIWYG #796

Open
wants to merge 55 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
0da9c0e
Add Tiptap and a first integration in NotesModal
yaaax Aug 8, 2024
af23880
Fix markdown content recovery
yaaax Aug 9, 2024
ffb6046
Set default code block language to html
yaaax Aug 16, 2024
4becae3
Fix eslint imports
yaaax Aug 16, 2024
8bfbe35
Fix eslint finally
yaaax Aug 16, 2024
ba218d0
Fix tiptap code blocks style
yaaax Aug 17, 2024
df48b96
Fix eslint warning (type)
yaaax Aug 26, 2024
c8d1262
Factorize code in AuditService
yaaax Sep 8, 2024
beeef1b
Add display field for uploads
yaaax Sep 9, 2024
7a165f8
Tiptap: handle images drag and drop
yaaax Sep 13, 2024
bf48bc2
refactor(tiptap): simplify code
yaaax Oct 9, 2024
0b86107
Titap: prevent layout shift on image load
yaaax Oct 14, 2024
10d792d
Titap: fix and improve image style
yaaax Oct 14, 2024
4e653bb
Ajout de la config de debug globale
yaaax Oct 21, 2024
1091882
Titap: fix drop cursor and image position when dropped
yaaax Oct 21, 2024
0506218
Titap: handle multiple image drop
yaaax Oct 21, 2024
963e492
Attachment: fix error 500
yaaax Oct 23, 2024
0866f14
Tiptap: improve accessibility
yaaax Oct 23, 2024
8a946b1
Image upload: add error message
yaaax Oct 23, 2024
0e6190c
Minor typo: Tiptap instead of TipTap
yaaax Oct 24, 2024
b571d24
Tiptap: improve drag and drop + upload
yaaax Oct 24, 2024
d98b78e
Add upload/delete timeout error messages
yaaax Oct 28, 2024
c92eddf
Improve error messages (avoid displaying null)
yaaax Oct 28, 2024
5197c70
Tiptap: improve drop cursor style
yaaax Oct 28, 2024
43588d4
Tiptap: handle and fix drop + paste
yaaax Oct 28, 2024
331b86a
Tiptap style: improve focus outline
yaaax Nov 15, 2024
cd147ed
Tiptap style: do not fix height
yaaax Nov 15, 2024
627f805
Tiptap: refactor to allow image upload from button
yaaax Nov 15, 2024
a28acde
Fix CSS errors
yaaax Nov 15, 2024
46f21eb
Fix type issues (error messages can be strings)
yaaax Nov 15, 2024
b3b41d2
Tiptap: add buttons
yaaax Dec 4, 2024
d689d71
Fix duplicate image when dropping an image from an external website
yaaax Jan 15, 2025
6229c2a
Audit store: fix deleteAuditFile
yaaax Jan 21, 2025
a303230
feat(Audit service) Add deleteAuditFiles
yaaax Jan 21, 2025
59ea358
chore(titap): Add comments for future use
yaaax Jan 21, 2025
0315f0c
feat(system): add pruneUploads function (wip)
yaaax Jan 21, 2025
e196744
Sidebar: fix width on desktop
yaaax Jan 24, 2025
51247c8
Prisma (AuditFile): add creationDate
yaaax Jan 24, 2025
f576a70
Prisma (StoredFile): add creationDate
yaaax Jan 24, 2025
52d0e07
Prisma(chore): reorder fields in StoredFile model
yaaax Jan 24, 2025
58a5cf3
StoredFile(db): thumbnailKey becomes optionnal
yaaax Jan 27, 2025
0c967c8
Fix DTO files (sync between StoredFile and AuditFile)
yaaax Jan 27, 2025
4acd69b
feat(FileStorageService): Add getAllFileKeys
yaaax Jan 27, 2025
7dab077
Prisma: better query logs (DEBUG="prisma:query")
yaaax Jan 27, 2025
7d6a344
tiptap(feat): fix multiple image selection style
yaaax Jan 28, 2025
a9abf6d
Tiptap: handle upload files from criteria
yaaax Jan 29, 2025
26ffd7d
feat(system): fix pruneUploads to handle StoredFiles
yaaax Jan 29, 2025
d44a6ae
results store: improve uploadExampleImage (no more timeout)
yaaax Jan 29, 2025
e3fa5f5
tiptap: remove image properly when an error occured
yaaax Jan 29, 2025
5ccf1a3
tiptap: smaller buttons
yaaax Jan 29, 2025
29056bb
tiptap: focus editor after image drop (or paste)
yaaax Jan 29, 2025
45bf037
tiptap(chore): remove useless code
yaaax Jan 29, 2025
a0f94ae
System store: remove pruneUploads from web app
yaaax Jan 29, 2025
fb6476b
tiptap: fix report with non editable editors
yaaax Jan 29, 2025
236d908
fix(tiptap): display selection on leaf nodes only
yaaax Jan 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"version": "0.2.0",
"configurations": [
// Frontend (Chrome)
{
"type": "chrome",
"request": "launch",
"name": "FRONT (Vue.js) – Chrome",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}/confiture-web-app/src",
"sourceMapPathOverrides": {
"webpack:///src/*": "${webRoot}/*"
}
},
// Frontend (Chrome)
{
"type": "firefox",
"request": "launch",
"name": "FRONT (Vue.js) – Firefox",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}/confiture-web-app/src",
"pathMappings": [
{ "url": "webpack:///confiture-web-app/src/", "path": "${webRoot}/" }
]
},
// Backend (REST API)
{
"type": "node",
"request": "launch",
"name": "BACKEND (nest)",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "start:debug", "--", "--inspect-brk"],
"autoAttachChildProcesses": true,
"restart": true,
"sourceMaps": true,
"stopOnEntry": false,
"console": "integratedTerminal"
}
]
}
6 changes: 5 additions & 1 deletion confiture-rest-api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,8 @@ S3_VIRTUAL_HOST="xxx"
AWS_ACCESS_KEY_ID="xxx"
AWS_SECRET_ACCESS_KEY="xxx"

JWT_SECRET="xxx"
JWT_SECRET="xxx"

# Debug Prisma queries
# More info: [Logging | Prisma Documentation](https://www.prisma.io/docs/orm/prisma-client/observability-and-logging/logging)
DEBUG="prisma:query"
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- CreateEnum
CREATE TYPE "FileDisplay" AS ENUM ('EDITOR', 'ATTACHMENT');

-- AlterTable
ALTER TABLE "AuditFile" ADD COLUMN "display" "FileDisplay" NOT NULL DEFAULT 'ATTACHMENT';

-- AlterTable
ALTER TABLE "StoredFile" ADD COLUMN "display" "FileDisplay" NOT NULL DEFAULT 'ATTACHMENT';
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "AuditFile" ADD COLUMN "creationDate" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "StoredFile" ADD COLUMN "creationDate" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "StoredFile" ALTER COLUMN "thumbnailKey" DROP NOT NULL;
17 changes: 16 additions & 1 deletion confiture-rest-api/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,11 @@ model AuditTrace {
Audit Audit?
}

enum FileDisplay {
EDITOR
ATTACHMENT
}

model StoredFile {
id Int @id @default(autoincrement())
originalFilename String
Expand All @@ -182,7 +187,12 @@ model StoredFile {

// S3 storage keys
key String
thumbnailKey String
thumbnailKey String?

// Inside TipTap editor (EDITOR) or added as an attachment (ATTACHMENT)?
display FileDisplay @default(ATTACHMENT)

creationDate DateTime @default(now())

criterionResult CriterionResult? @relation(fields: [criterionResultId], references: [id], onDelete: Cascade, onUpdate: Cascade)
criterionResultId Int?
Expand All @@ -203,6 +213,11 @@ model AuditFile {
key String
thumbnailKey String?

creationDate DateTime @default(now())

// Inside TipTap editor (EDITOR) or added as an attachment (ATTACHMENT)?
display FileDisplay @default(ATTACHMENT)

audit Audit? @relation(fields: [auditUniqueId], references: [editUniqueId], onDelete: Cascade)
auditUniqueId String?
}
Expand Down
4 changes: 3 additions & 1 deletion confiture-rest-api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { configValidationSchema } from "./config-validation-schema";
import { MailModule } from "./mail/mail.module";
import { AuthModule } from "./auth/auth.module";
import { ProfileModule } from "./profile/profile.module";
import { SystemModule } from "./system/system.module";
import { UserMiddleware } from "./auth/user.middleware";

@Module({
Expand All @@ -19,7 +20,8 @@ import { UserMiddleware } from "./auth/user.middleware";
AuditsModule,
MailModule,
AuthModule,
ProfileModule
ProfileModule,
SystemModule
],
controllers: [HealthCheckController]
})
Expand Down
147 changes: 95 additions & 52 deletions confiture-rest-api/src/audits/audit.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
CriterionResult,
CriterionResultStatus,
CriterionResultUserImpact,
FileDisplay,
Prisma,
StoredFile
} from "@prisma/client";
Expand Down Expand Up @@ -418,30 +419,14 @@ export class AuditService {
pageId: number,
topic: number,
criterium: number,
file: Express.Multer.File
file: Express.Multer.File,
display: FileDisplay = FileDisplay.ATTACHMENT
) {
const randomPrefix = nanoid();

const key = `audits/${editUniqueId}/${randomPrefix}/${file.originalname}`;

const thumbnailKey = `audits/${editUniqueId}/${randomPrefix}/thumbnail_${file.originalname}`;

const thumbnailBuffer = await sharp(file.buffer)
.jpeg({
mozjpeg: true
})
.flatten({ background: { r: 255, g: 255, b: 255, alpha: 0 } })
.resize(200, 200, { fit: "inside" })
.toBuffer();

await Promise.all([
this.fileStorageService.uploadFile(file.buffer, file.mimetype, key),
this.fileStorageService.uploadFile(
thumbnailBuffer,
"image/jpeg",
thumbnailKey
)
]);
const { key, thumbnailKey } = await this.uploadFileToStorage(
editUniqueId,
file,
{ createThumbnail: display === FileDisplay.ATTACHMENT }
);

const storedFile = await this.prisma.storedFile.create({
data: {
Expand All @@ -459,7 +444,8 @@ export class AuditService {
originalFilename: file.originalname,
mimetype: file.mimetype,
size: file.size,
thumbnailKey
thumbnailKey,
display
}
});

Expand Down Expand Up @@ -500,22 +486,59 @@ export class AuditService {
return true;
}

async saveNotesFile(editUniqueId: string, file: Express.Multer.File) {
async saveNotesFile(
editUniqueId: string,
file: Express.Multer.File,
display: FileDisplay = FileDisplay.ATTACHMENT
) {
const { key, thumbnailKey } = await this.uploadFileToStorage(
editUniqueId,
file,
{ createThumbnail: display === FileDisplay.ATTACHMENT }
);

const storedFile = await this.prisma.auditFile.create({
data: {
audit: {
connect: {
editUniqueId
}
},

key,
originalFilename: file.originalname,
mimetype: file.mimetype,
size: file.size,

thumbnailKey,
display
}
});

return storedFile;
}

async uploadFileToStorage(
uniqueId: string,
file: Express.Multer.File,
options?: { createThumbnail: boolean }
): Promise<{ key: string; thumbnailKey?: string }> {
const randomPrefix = nanoid();

const key = `audits/${editUniqueId}/${randomPrefix}/${file.originalname}`;
const key: string = `audits/${uniqueId}/${randomPrefix}/${file.originalname}`;

let thumbnailKey;
let thumbnailKey: string;

if (file.mimetype.startsWith("image")) {
if (file.mimetype.startsWith("image") && options.createThumbnail) {
// If it's an image, create a thumbnail and upload it
thumbnailKey = `audits/${editUniqueId}/${randomPrefix}/thumbnail_${file.originalname}`;
thumbnailKey = `audits/${uniqueId}/${randomPrefix}/thumbnail_${file.originalname}`;

const thumbnailBuffer = await sharp(file.buffer)
.resize(200, 200, { fit: "inside" })
.jpeg({
mozjpeg: true
})
.flatten({ background: { r: 255, g: 255, b: 255, alpha: 0 } })
.resize(200, 200, { fit: "inside" })
.toBuffer();

await Promise.all([
Expand All @@ -529,29 +552,11 @@ export class AuditService {
} else {
await this.fileStorageService.uploadFile(file.buffer, file.mimetype, key);
}

const storedFile = await this.prisma.auditFile.create({
data: {
audit: {
connect: {
editUniqueId
}
},

key,
originalFilename: file.originalname,
mimetype: file.mimetype,
size: file.size,

thumbnailKey
}
});

return storedFile;
return { key, thumbnailKey };
}

/**
* Returns true if stored filed was found and deleted. False if not found.
* Returns true if stored file has been found and deleted. False if not found.
*/
async deleteAuditFile(
editUniqueId: string,
Expand Down Expand Up @@ -585,6 +590,42 @@ export class AuditService {
return true;
}

/**
* Returns true if all stored file have been found and deleted. False otherwise.
*/
async deleteAuditFiles(fileIds: number[]): Promise<boolean> {
const storedFiles = await this.prisma.auditFile.findMany({
select: {
id: true,
key: true,
thumbnailKey: true
},
where: {
id: {
in: fileIds
}
}
});

const filesToDelete = storedFiles.map((e) => e.key);
const thumbnailsToDelete = storedFiles
.map((e) => e.thumbnailKey)
.filter((e) => e != null);
await this.fileStorageService.deleteMultipleFiles(
...filesToDelete.concat(thumbnailsToDelete)
);

await this.prisma.auditFile.deleteMany({
where: {
id: {
in: fileIds
}
}
});

return true;
}

/**
* Completely delete an audit and all the data associated with it.
* @returns True if an audit was deleted, false otherwise.
Expand Down Expand Up @@ -832,7 +873,8 @@ export class AuditService {
key: file.key,
thumbnailKey: file.thumbnailKey,
size: file.size,
mimetype: file.mimetype
mimetype: file.mimetype,
display: file.display
})),

criteriaCount: {
Expand Down Expand Up @@ -999,7 +1041,8 @@ export class AuditService {
exampleImages: r.exampleImages.map((img) => ({
filename: img.originalFilename,
key: img.key,
thumbnailKey: img.thumbnailKey
thumbnailKey: img.thumbnailKey,
display: img.display
}))
}))
};
Expand Down
9 changes: 6 additions & 3 deletions confiture-rest-api/src/audits/audits.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
import { Audit } from "src/generated/nestjs-dto/audit.entity";
import { CriterionResult } from "src/generated/nestjs-dto/criterionResult.entity";
import { MailService } from "../mail/mail.service";
import { NotesFileDto } from "./dto/notes-file.dto";
import { AuditExportService } from "./audit-export.service";
import { AuditService } from "./audit.service";
import { CreateAuditDto } from "./dto/create-audit.dto";
Expand Down Expand Up @@ -174,7 +175,8 @@ export class AuditsController {
body.pageId,
body.topic,
body.criterium,
file
file,
body.display
);
}

Expand All @@ -191,15 +193,16 @@ export class AuditsController {
errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY
})
)
file: Express.Multer.File
file: Express.Multer.File,
@Body() body: NotesFileDto
) {
const audit = await this.auditService.getAuditWithEditUniqueId(uniqueId);

if (!audit) {
return this.sendAuditNotFoundStatus(uniqueId);
}

return await this.auditService.saveNotesFile(uniqueId, file);
return await this.auditService.saveNotesFile(uniqueId, file, body.display);
}

@Delete("/:uniqueId/results/examples/:exampleId")
Expand Down
Loading