Compare commits

...

5 Commits

Author SHA1 Message Date
Apoorv Mishra 4034f5f0e8 fix: review 2024-06-30 11:02:00 +05:30
Apoorv Mishra c4255b6cf2 fix:review 2024-06-29 15:28:19 +05:30
Apoorv Mishra 78fd524bed fix: text 2024-06-27 01:07:11 +05:30
Apoorv Mishra a98a2fb44a fix: avoid full traversal to validate comment 2024-06-27 01:05:28 +05:30
Apoorv Mishra b10e82313e fix: disallow empty comments 2024-06-26 17:52:34 +05:30
4 changed files with 142 additions and 3 deletions
+109
View File
@@ -176,7 +176,116 @@ describe("#comments.create", () => {
},
});
const anotherRes = await server.post("/api/comments.create", {
body: {
token: user.getJwtToken(),
documentId: document.id,
data: {
type: "doc",
content: [{ type: "paragraph" }],
},
},
});
expect(res.status).toEqual(400);
expect(anotherRes.status).toEqual(400);
});
it("should not allow comments containing only whitespaces", async () => {
const team = await buildTeam();
const user = await buildUser({ teamId: team.id });
const document = await buildDocument({
userId: user.id,
teamId: user.teamId,
});
const res = await server.post("/api/comments.create", {
body: {
token: user.getJwtToken(),
documentId: document.id,
data: {
type: "doc",
content: [
{
type: "paragraph",
content: [{ type: "text", text: " \n\r\n" }],
},
],
},
},
});
expect(res.status).toEqual(400);
});
it("should allow adding images to comments", async () => {
const team = await buildTeam();
const user = await buildUser({ teamId: team.id });
const document = await buildDocument({
userId: user.id,
teamId: user.teamId,
});
const res = await server.post("/api/comments.create", {
body: {
token: user.getJwtToken(),
documentId: document.id,
data: {
type: "doc",
content: [
{
type: "paragraph",
content: [
{
type: "image",
attrs: {
src: "https://example.com/image.png",
alt: "Example image",
},
},
],
},
],
},
},
});
expect(res.status).toEqual(200);
});
it("should allow adding images from internal sources", async () => {
const team = await buildTeam();
const user = await buildUser({ teamId: team.id });
const document = await buildDocument({
userId: user.id,
teamId: user.teamId,
});
const res = await server.post("/api/comments.create", {
body: {
token: user.getJwtToken(),
documentId: document.id,
data: {
type: "doc",
content: [
{
type: "paragraph",
content: [
{
type: "image",
attrs: {
src: "/api/attachments.redirect?id=1401323b-c4e2-40de-b172-e1668ec89111",
alt: null,
},
},
],
},
],
},
},
});
expect(res.status).toEqual(200);
});
it("should not allow invalid comment data", async () => {
+2 -1
View File
@@ -2,6 +2,7 @@ import formidable from "formidable";
import { Node } from "prosemirror-model";
import { z } from "zod";
import { ProsemirrorData as TProsemirrorData } from "@shared/types";
import { ProsemirrorHelper } from "@shared/utils/ProsemirrorHelper";
import { schema } from "@server/editor";
export const BaseSchema = z.object({
@@ -14,7 +15,7 @@ export const ProsemirrorSchema = z.custom<TProsemirrorData>((val) => {
try {
const node = Node.fromJSON(schema, val);
node.check();
return true;
return !ProsemirrorHelper.isEmpty(node, schema);
} catch (_e) {
return false;
}
+2
View File
@@ -166,6 +166,8 @@ export default class Image extends SimpleImage {
["p", { class: "caption" }, 0],
];
},
toPlainText: (node) =>
node.attrs.alt ? `(image: ${node.attrs.alt})` : "(image)",
};
}
+29 -2
View File
@@ -142,8 +142,35 @@ export class ProsemirrorHelper {
*
* @returns True if the editor is empty
*/
static isEmpty(doc: Node) {
return !doc || doc.textContent.trim() === "";
static isEmpty(doc: Node, schema?: Schema) {
if (!schema) {
return !doc || doc.textContent.trim() === "";
}
const textSerializers = Object.fromEntries(
Object.entries(schema.nodes)
.filter(([, node]) => node.spec.toPlainText)
.map(([name, node]) => [name, node.spec.toPlainText])
);
let empty = true;
doc.descendants((child: Node) => {
// If we've already found non-empty data, we can stop descending further
if (!empty) {
return false;
}
const toPlainText = textSerializers[child.type.name];
if (toPlainText) {
empty = !toPlainText(child).trim();
} else if (child.isText) {
empty = !child.text?.trim();
}
return empty;
});
return empty;
}
/**