diff --git a/app/components/Sidebar/components/SidebarLink.tsx b/app/components/Sidebar/components/SidebarLink.tsx index 5137c86b6..44e20293b 100644 --- a/app/components/Sidebar/components/SidebarLink.tsx +++ b/app/components/Sidebar/components/SidebarLink.tsx @@ -194,8 +194,17 @@ const Link = styled(NavLink)<{ ${(props) => props.$isDraft && css` - padding: 4px 14px; - border: 1px dashed ${props.theme.sidebarDraftBorder}; + &:after { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + pointer-events: none; + border-radius: 4px; + border: 1.5px dashed ${props.theme.sidebarDraftBorder}; + } `} svg { diff --git a/app/stores/DocumentsStore.ts b/app/stores/DocumentsStore.ts index bba003750..ffee4c7ec 100644 --- a/app/stores/DocumentsStore.ts +++ b/app/stores/DocumentsStore.ts @@ -686,15 +686,24 @@ export default class DocumentsStore extends BaseStore { lastRevision: number; } ) { - const document = await super.update(params, options); + this.isSaving = true; - // Because the collection object contains the url and title - // we need to ensure they are updated there as well. - const collection = this.getCollectionForDocument(document); - if (collection) { - collection.updateDocument(document); + try { + const res = await client.post(`/${this.apiEndpoint}.update`, { + ...params, + ...options, + apiVersion: 2, + }); + + invariant(res?.data, "Data should be available"); + this.addPolicies(res.policies); + const document = this.add(res.data.document); + const collection = this.getCollectionForDocument(document); + collection?.updateFromJson(res.data.collection); + return document; + } finally { + this.isSaving = false; } - return document; } @action @@ -763,16 +772,16 @@ export default class DocumentsStore extends BaseStore { unpublish = async (document: Document) => { const res = await client.post("/documents.unpublish", { id: document.id, + apiVersion: 2, }); + runInAction("Document#unpublish", () => { invariant(res?.data, "Data should be available"); - document.updateFromJson(res.data); + document.updateFromJson(res.data.document); + const collection = this.getCollectionForDocument(document); + collection?.updateFromJson(res.data.collection); this.addPolicies(res.policies); }); - const collection = this.getCollectionForDocument(document); - if (collection) { - collection.refresh(); - } }; star = (document: Document) => { diff --git a/server/routes/api/documents/documents.ts b/server/routes/api/documents/documents.ts index b5a0f3acd..82d5d8cd3 100644 --- a/server/routes/api/documents/documents.ts +++ b/server/routes/api/documents/documents.ts @@ -826,6 +826,7 @@ router.post( templateId, collectionId, append, + apiVersion, } = ctx.input.body; const editorVersion = ctx.headers["x-editor-version"] as string | undefined; const { user } = ctx.state.auth; @@ -845,8 +846,6 @@ router.post( "collectionId is required to publish a draft without collection" ); collection = await Collection.findByPk(collectionId as string); - } else { - collection = document.collection; } authorize(user, "publish", collection); } @@ -855,7 +854,7 @@ router.post( throw InvalidRequestError("Document has changed since last revision"); } - const updatedDocument = await sequelize.transaction(async (transaction) => { + await sequelize.transaction((transaction) => { return documentUpdater({ document, user, @@ -872,12 +871,27 @@ router.post( }); }); - updatedDocument.updatedBy = user; - updatedDocument.collection = collection; + document.updatedBy = user; + + // Original collection has membership data for calculating policies so we + // use that collection and just update the documentStructure for performance + if (collection) { + const { documentStructure } = collection; + document.collection = collection; + document.collection.documentStructure = documentStructure; + } ctx.body = { - data: await presentDocument(updatedDocument), - policies: presentPolicies(user, [updatedDocument]), + data: + apiVersion === 2 + ? { + document: await presentDocument(document), + collection: document.collection + ? presentCollection(document.collection) + : undefined, + } + : await presentDocument(document), + policies: presentPolicies(user, [document]), }; } ); @@ -1043,7 +1057,7 @@ router.post( auth(), validate(T.DocumentsUnpublishSchema), async (ctx: APIContext) => { - const { id } = ctx.input.body; + const { id, apiVersion } = ctx.input.body; const { user } = ctx.state.auth; const document = await Document.findByPk(id, { @@ -1072,7 +1086,15 @@ router.post( }); ctx.body = { - data: await presentDocument(document), + data: + apiVersion === 2 + ? { + document: await presentDocument(document), + collection: document.collection + ? presentCollection(document.collection) + : undefined, + } + : await presentDocument(document), policies: presentPolicies(user, [document]), }; } diff --git a/server/routes/api/documents/schema.ts b/server/routes/api/documents/schema.ts index 286ed977c..db84bd48d 100644 --- a/server/routes/api/documents/schema.ts +++ b/server/routes/api/documents/schema.ts @@ -201,6 +201,9 @@ export const DocumentsUpdateSchema = BaseSchema.extend({ /** Boolean to denote if text should be appended */ append: z.boolean().optional(), + + /** Version of the API to be used */ + apiVersion: z.number().optional(), }), }).refine((req) => !(req.body.append && !req.body.text), { message: "text is required while appending", @@ -241,7 +244,10 @@ export const DocumentsDeleteSchema = BaseSchema.extend({ export type DocumentsDeleteReq = z.infer; export const DocumentsUnpublishSchema = BaseSchema.extend({ - body: BaseIdSchema, + body: BaseIdSchema.extend({ + /** Version of the API to be used */ + apiVersion: z.number().optional(), + }), }); export type DocumentsUnpublishReq = z.infer;