mirror of
https://github.com/docmost/docmost.git
synced 2025-11-15 17:21:49 +10:00
feat: move page between spaces (#988)
* feat: Move the page to another space - The ability to move a page to another space has been added * feat: Move the page to another space * feat: Move the page to another space - Correction of the visibility attribute of elements that extend beyond the boundaries of the space selection modal window * feat: Move the page to another space - Added removal of query keys when moving pages * feat: Move the page to another space - Fix locales * feat: Move the page to another space * feat: Move the page to another space - Fix docker compose * feat: Move the page to another space * feat: Move the page to another space - Some refactor * feat: Move the page to another space - Attachments update * feat: Move the page to another space - The function of searching for attachments by page ID and updating attachments has been combined * feat: Move the page to another space - Fix variable name * feat: Move the page to another space - Move current space to parameter of component SpaceSelectionModal * refactor ui --------- Co-authored-by: plekhanov <astecom@mail.ru>
This commit is contained in:
@ -13,3 +13,11 @@ export class MovePageDto {
|
||||
@IsString()
|
||||
parentPageId?: string | null;
|
||||
}
|
||||
|
||||
export class MovePageToSpaceDto {
|
||||
@IsString()
|
||||
pageId: string;
|
||||
|
||||
@IsString()
|
||||
spaceId: string;
|
||||
}
|
||||
|
||||
@ -7,11 +7,12 @@ import {
|
||||
UseGuards,
|
||||
ForbiddenException,
|
||||
NotFoundException,
|
||||
BadRequestException,
|
||||
} from '@nestjs/common';
|
||||
import { PageService } from './services/page.service';
|
||||
import { CreatePageDto } from './dto/create-page.dto';
|
||||
import { UpdatePageDto } from './dto/update-page.dto';
|
||||
import { MovePageDto } from './dto/move-page.dto';
|
||||
import { MovePageDto, MovePageToSpaceDto } from './dto/move-page.dto';
|
||||
import { PageHistoryIdDto, PageIdDto, PageInfoDto } from './dto/page.dto';
|
||||
import { PageHistoryService } from './services/page-history.service';
|
||||
import { AuthUser } from '../../common/decorators/auth-user.decorator';
|
||||
@ -93,11 +94,7 @@ export class PageController {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
return this.pageService.update(
|
||||
page,
|
||||
updatePageDto,
|
||||
user.id,
|
||||
);
|
||||
return this.pageService.update(page, updatePageDto, user.id);
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ -210,6 +207,36 @@ export class PageController {
|
||||
return this.pageService.getSidebarPages(dto.spaceId, pagination, pageId);
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('move-to-space')
|
||||
async movePageToSpace(
|
||||
@Body() dto: MovePageToSpaceDto,
|
||||
@AuthUser() user: User,
|
||||
) {
|
||||
const movedPage = await this.pageRepo.findById(dto.pageId);
|
||||
if (!movedPage) {
|
||||
throw new NotFoundException('Page to move not found');
|
||||
}
|
||||
if (movedPage.spaceId === dto.spaceId) {
|
||||
throw new BadRequestException('Page is already in this space');
|
||||
}
|
||||
|
||||
const abilities = await Promise.all([
|
||||
this.spaceAbility.createForUser(user, movedPage.spaceId),
|
||||
this.spaceAbility.createForUser(user, dto.spaceId),
|
||||
]);
|
||||
|
||||
if (
|
||||
abilities.some((ability) =>
|
||||
ability.cannot(SpaceCaslAction.Edit, SpaceCaslSubject.Page),
|
||||
)
|
||||
) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
return this.pageService.movePageToSpace(movedPage, dto.spaceId);
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('move')
|
||||
async movePage(@Body() dto: MovePageDto, @AuthUser() user: User) {
|
||||
|
||||
@ -19,11 +19,14 @@ import { MovePageDto } from '../dto/move-page.dto';
|
||||
import { ExpressionBuilder } from 'kysely';
|
||||
import { DB } from '@docmost/db/types/db';
|
||||
import { generateSlugId } from '../../../common/helpers';
|
||||
import { executeTx } from '@docmost/db/utils';
|
||||
import { AttachmentRepo } from '@docmost/db/repos/attachment/attachment.repo';
|
||||
|
||||
@Injectable()
|
||||
export class PageService {
|
||||
constructor(
|
||||
private pageRepo: PageRepo,
|
||||
private attachmentRepo: AttachmentRepo,
|
||||
@InjectKysely() private readonly db: KyselyDB,
|
||||
) {}
|
||||
|
||||
@ -60,12 +63,31 @@ export class PageService {
|
||||
parentPageId = parentPage.id;
|
||||
}
|
||||
|
||||
const createdPage = await this.pageRepo.insertPage({
|
||||
slugId: generateSlugId(),
|
||||
title: createPageDto.title,
|
||||
position: await this.nextPagePosition(
|
||||
createPageDto.spaceId,
|
||||
parentPageId,
|
||||
),
|
||||
icon: createPageDto.icon,
|
||||
parentPageId: parentPageId,
|
||||
spaceId: createPageDto.spaceId,
|
||||
creatorId: userId,
|
||||
workspaceId: workspaceId,
|
||||
lastUpdatedById: userId,
|
||||
});
|
||||
|
||||
return createdPage;
|
||||
}
|
||||
|
||||
async nextPagePosition(spaceId: string, parentPageId?: string) {
|
||||
let pagePosition: string;
|
||||
|
||||
const lastPageQuery = this.db
|
||||
.selectFrom('pages')
|
||||
.select(['id', 'position'])
|
||||
.where('spaceId', '=', createPageDto.spaceId)
|
||||
.select(['position'])
|
||||
.where('spaceId', '=', spaceId)
|
||||
.orderBy('position', 'desc')
|
||||
.limit(1);
|
||||
|
||||
@ -96,19 +118,7 @@ export class PageService {
|
||||
}
|
||||
}
|
||||
|
||||
const createdPage = await this.pageRepo.insertPage({
|
||||
slugId: generateSlugId(),
|
||||
title: createPageDto.title,
|
||||
position: pagePosition,
|
||||
icon: createPageDto.icon,
|
||||
parentPageId: parentPageId,
|
||||
spaceId: createPageDto.spaceId,
|
||||
creatorId: userId,
|
||||
workspaceId: workspaceId,
|
||||
lastUpdatedById: userId,
|
||||
});
|
||||
|
||||
return createdPage;
|
||||
return pagePosition;
|
||||
}
|
||||
|
||||
async update(
|
||||
@ -192,6 +202,36 @@ export class PageService {
|
||||
return result;
|
||||
}
|
||||
|
||||
async movePageToSpace(rootPage: Page, spaceId: string) {
|
||||
await executeTx(this.db, async (trx) => {
|
||||
// Update root page
|
||||
const nextPosition = await this.nextPagePosition(spaceId);
|
||||
await this.pageRepo.updatePage(
|
||||
{ spaceId, parentPageId: null, position: nextPosition },
|
||||
rootPage.id,
|
||||
trx,
|
||||
);
|
||||
const pageIds = await this.pageRepo
|
||||
.getPageAndDescendants(rootPage.id)
|
||||
.then((pages) => pages.map((page) => page.id));
|
||||
// The first id is the root page id
|
||||
if (pageIds.length > 1) {
|
||||
// Update sub pages
|
||||
await this.pageRepo.updatePages(
|
||||
{ spaceId },
|
||||
pageIds.filter((id) => id !== rootPage.id),
|
||||
trx,
|
||||
);
|
||||
}
|
||||
// Update attachments
|
||||
await this.attachmentRepo.updateAttachmentsByPageId(
|
||||
{ spaceId },
|
||||
pageIds,
|
||||
trx,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async movePage(dto: MovePageDto, movedPage: Page) {
|
||||
// validate position value by attempting to generate a key
|
||||
try {
|
||||
|
||||
@ -55,6 +55,18 @@ export class AttachmentRepo {
|
||||
.execute();
|
||||
}
|
||||
|
||||
updateAttachmentsByPageId(
|
||||
updatableAttachment: UpdatableAttachment,
|
||||
pageIds: string[],
|
||||
trx?: KyselyTransaction,
|
||||
) {
|
||||
return dbOrTx(this.db, trx)
|
||||
.updateTable('attachments')
|
||||
.set(updatableAttachment)
|
||||
.where('pageId', 'in', pageIds)
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
async updateAttachment(
|
||||
updatableAttachment: UpdatableAttachment,
|
||||
attachmentId: string,
|
||||
|
||||
@ -96,18 +96,19 @@ export class PageRepo {
|
||||
pageId: string,
|
||||
trx?: KyselyTransaction,
|
||||
) {
|
||||
const db = dbOrTx(this.db, trx);
|
||||
let query = db
|
||||
return this.updatePages(updatablePage, [pageId], trx);
|
||||
}
|
||||
|
||||
async updatePages(
|
||||
updatePageData: UpdatablePage,
|
||||
pageIds: string[],
|
||||
trx?: KyselyTransaction,
|
||||
) {
|
||||
return dbOrTx(this.db, trx)
|
||||
.updateTable('pages')
|
||||
.set({ ...updatablePage, updatedAt: new Date() });
|
||||
|
||||
if (isValidUUID(pageId)) {
|
||||
query = query.where('id', '=', pageId);
|
||||
} else {
|
||||
query = query.where('slugId', '=', pageId);
|
||||
}
|
||||
|
||||
return query.executeTakeFirst();
|
||||
.set({ ...updatePageData, updatedAt: new Date() })
|
||||
.where(pageIds.some(pageId => !isValidUUID(pageId)) ? "slugId" : "id", "in", pageIds)
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
async insertPage(
|
||||
|
||||
Reference in New Issue
Block a user