feat: add "Copy document" dialog (#6009)
This commit is contained in:
@@ -6,7 +6,7 @@ type Props = {
|
||||
id?: string;
|
||||
urlId?: string;
|
||||
title: string;
|
||||
emoji?: string;
|
||||
emoji?: string | null;
|
||||
text?: string;
|
||||
state?: Buffer;
|
||||
publish?: boolean;
|
||||
|
||||
84
server/commands/documentDuplicator.test.ts
Normal file
84
server/commands/documentDuplicator.test.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { sequelize } from "@server/storage/database";
|
||||
import { buildDocument, buildUser } from "@server/test/factories";
|
||||
import documentDuplicator from "./documentDuplicator";
|
||||
|
||||
describe("documentDuplicator", () => {
|
||||
const ip = "127.0.0.1";
|
||||
|
||||
it("should duplicate existing document", async () => {
|
||||
const user = await buildUser();
|
||||
const original = await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
});
|
||||
|
||||
const response = await sequelize.transaction((transaction) =>
|
||||
documentDuplicator({
|
||||
document: original,
|
||||
collection: original.collection,
|
||||
transaction,
|
||||
user,
|
||||
ip,
|
||||
})
|
||||
);
|
||||
|
||||
expect(response).toHaveLength(1);
|
||||
expect(response[0].title).toEqual(original.title);
|
||||
expect(response[0].text).toEqual(original.text);
|
||||
expect(response[0].emoji).toEqual(original.emoji);
|
||||
});
|
||||
|
||||
it("should duplicate document with title override", async () => {
|
||||
const user = await buildUser();
|
||||
const original = await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
emoji: "👋",
|
||||
});
|
||||
|
||||
const response = await sequelize.transaction((transaction) =>
|
||||
documentDuplicator({
|
||||
document: original,
|
||||
collection: original.collection,
|
||||
title: "New title",
|
||||
transaction,
|
||||
user,
|
||||
ip,
|
||||
})
|
||||
);
|
||||
|
||||
expect(response).toHaveLength(1);
|
||||
expect(response[0].title).toEqual("New title");
|
||||
expect(response[0].text).toEqual(original.text);
|
||||
expect(response[0].emoji).toEqual(original.emoji);
|
||||
});
|
||||
|
||||
it("should duplicate child documents with recursive=true", async () => {
|
||||
const user = await buildUser();
|
||||
const original = await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
emoji: "👋",
|
||||
});
|
||||
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
parentDocumentId: original.id,
|
||||
collection: original.collection,
|
||||
});
|
||||
|
||||
const response = await sequelize.transaction((transaction) =>
|
||||
documentDuplicator({
|
||||
document: original,
|
||||
collection: original.collection,
|
||||
user,
|
||||
transaction,
|
||||
recursive: true,
|
||||
ip,
|
||||
})
|
||||
);
|
||||
|
||||
expect(response).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
97
server/commands/documentDuplicator.ts
Normal file
97
server/commands/documentDuplicator.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { Transaction, Op } from "sequelize";
|
||||
import { User, Collection, Document } from "@server/models";
|
||||
import documentCreator from "./documentCreator";
|
||||
|
||||
type Props = {
|
||||
/** The user who is creating the document */
|
||||
user: User;
|
||||
/** The document to duplicate */
|
||||
document: Document;
|
||||
/** The collection to add the duplicated document to */
|
||||
collection?: Collection | null;
|
||||
/** Override of the parent document to add the duplicate to */
|
||||
parentDocumentId?: string;
|
||||
/** Override of the duplicated document title */
|
||||
title?: string;
|
||||
/** Override of the duplicated document publish state */
|
||||
publish?: boolean;
|
||||
/** Whether to duplicate child documents */
|
||||
recursive?: boolean;
|
||||
/** The database transaction to use for the creation */
|
||||
transaction?: Transaction;
|
||||
/** The IP address of the request */
|
||||
ip: string;
|
||||
};
|
||||
|
||||
export default async function documentDuplicator({
|
||||
user,
|
||||
document,
|
||||
collection,
|
||||
parentDocumentId,
|
||||
title,
|
||||
publish,
|
||||
recursive,
|
||||
transaction,
|
||||
ip,
|
||||
}: Props): Promise<Document[]> {
|
||||
const newDocuments: Document[] = [];
|
||||
const sharedProperties = {
|
||||
user,
|
||||
collectionId: collection?.id,
|
||||
publish: publish ?? !!document.publishedAt,
|
||||
ip,
|
||||
transaction,
|
||||
};
|
||||
|
||||
const duplicated = await documentCreator({
|
||||
parentDocumentId: parentDocumentId ?? document.parentDocumentId,
|
||||
emoji: document.emoji,
|
||||
template: document.template,
|
||||
title: title ?? document.title,
|
||||
text: document.text,
|
||||
...sharedProperties,
|
||||
});
|
||||
|
||||
duplicated.collection = collection;
|
||||
newDocuments.push(duplicated);
|
||||
|
||||
async function duplicateChildDocuments(
|
||||
original: Document,
|
||||
duplicated: Document
|
||||
) {
|
||||
const childDocuments = await original.findChildDocuments(
|
||||
{
|
||||
archivedAt: original.archivedAt
|
||||
? {
|
||||
[Op.ne]: null,
|
||||
}
|
||||
: {
|
||||
[Op.eq]: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
transaction,
|
||||
}
|
||||
);
|
||||
|
||||
for (const childDocument of childDocuments) {
|
||||
const duplicatedChildDocument = await documentCreator({
|
||||
parentDocumentId: duplicated.id,
|
||||
emoji: childDocument.emoji,
|
||||
title: childDocument.title,
|
||||
text: childDocument.text,
|
||||
...sharedProperties,
|
||||
});
|
||||
|
||||
duplicatedChildDocument.collection = collection;
|
||||
newDocuments.push(duplicatedChildDocument);
|
||||
await duplicateChildDocuments(childDocument, duplicatedChildDocument);
|
||||
}
|
||||
}
|
||||
|
||||
if (recursive && !document.template) {
|
||||
await duplicateChildDocuments(document, duplicated);
|
||||
}
|
||||
|
||||
return newDocuments;
|
||||
}
|
||||
@@ -167,7 +167,7 @@ export default async function loadDocument({
|
||||
}
|
||||
|
||||
const childDocumentIds =
|
||||
(await share.document?.getChildDocumentIds({
|
||||
(await share.document?.findAllChildDocumentIds({
|
||||
archivedAt: {
|
||||
[Op.is]: null,
|
||||
},
|
||||
|
||||
@@ -137,7 +137,7 @@ async function documentMover({
|
||||
if (collectionChanged) {
|
||||
// Efficiently find the ID's of all the documents that are children of
|
||||
// the moved document and update in one query
|
||||
const childDocumentIds = await document.getChildDocumentIds();
|
||||
const childDocumentIds = await document.findAllChildDocumentIds();
|
||||
|
||||
if (collectionId) {
|
||||
// Reload the collection to get relationship data
|
||||
|
||||
Reference in New Issue
Block a user