mirror of
https://github.com/outline/outline.git
synced 2026-08-03 13:27:25 +03:00
* fix: Remap internal links when duplicating a document tree Duplicating a document tree now assigns the identifiers of every duplicate before any content is written, so links and mentions between the documents being duplicated point at the copies rather than the originals. * fix: Remap fully qualified links when duplicating a document tree Links written as a full url to this installation are now remapped alongside relative ones, and stay fully qualified. Where a link is displayed as its own url the text is updated to match. Also trims surrounding whitespace in sanitizeUrl, which otherwise failed validation and prepended a second scheme to an already qualified url. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: Remap links across trees when duplicating a collection The collection duplication task duplicated each root document separately, so only links within a single tree were remapped. It now duplicates the whole collection as one unit, with identifiers assigned across every tree before any content is written. * fix: Match document links by identifier rather than host when duplicating An installation can be reached through more than one host, so comparing a link's host against the configured url left fully qualified links to a document in the duplicated set unmapped. The document a link identifies now decides whether it is replaced, and the host it was written with is kept. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
72 lines
2.1 KiB
TypeScript
72 lines
2.1 KiB
TypeScript
import { Op } from "sequelize";
|
|
import { documentsDuplicator } from "@server/commands/documentDuplicator";
|
|
import { createContext } from "@server/context";
|
|
import { Collection, Document, User } from "@server/models";
|
|
import { DocumentHelper } from "@server/models/helpers/DocumentHelper";
|
|
import { sequelize } from "@server/storage/database";
|
|
import { BaseTask } from "./base/BaseTask";
|
|
|
|
type Props = {
|
|
/** The collection to duplicate documents into */
|
|
collectionId: string;
|
|
/** The collection to duplicate documents from */
|
|
originalCollectionId: string;
|
|
/** The user that initiated the duplication */
|
|
actorId: string;
|
|
/** The IP address of the user that initiated the duplication */
|
|
ip: string | null;
|
|
};
|
|
|
|
export default class DuplicateCollectionDocumentsTask extends BaseTask<Props> {
|
|
async perform(props: Props) {
|
|
const [collection, original, actor] = await Promise.all([
|
|
Collection.findByPk(props.collectionId),
|
|
Collection.findByPk(props.originalCollectionId),
|
|
User.findByPk(props.actorId),
|
|
]);
|
|
|
|
if (!actor || !original || !collection?.isActive) {
|
|
return;
|
|
}
|
|
|
|
const rootDocuments = await Document.findAll({
|
|
where: {
|
|
teamId: original.teamId,
|
|
collectionId: original.id,
|
|
parentDocumentId: {
|
|
[Op.is]: null,
|
|
},
|
|
archivedAt: {
|
|
[Op.is]: null,
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!rootDocuments.length) {
|
|
return;
|
|
}
|
|
|
|
const structure = await original.getCachedDocumentStructure();
|
|
const sorted = DocumentHelper.sortDocumentsByStructure(
|
|
rootDocuments,
|
|
structure ?? []
|
|
).reverse(); // we have to reverse since the root documents will be added in reverse order
|
|
|
|
return sequelize.transaction(async (transaction) => {
|
|
const ctx = createContext({
|
|
user: actor,
|
|
ip: props.ip,
|
|
transaction,
|
|
});
|
|
|
|
// The whole collection is duplicated as one unit so that links between
|
|
// documents in different trees are remapped to the copies.
|
|
await documentsDuplicator(ctx, {
|
|
documents: sorted,
|
|
collection,
|
|
recursive: true,
|
|
});
|
|
});
|
|
}
|
|
}
|