diff --git a/apps/server/src/core/base/controllers/base.controller.ts b/apps/server/src/core/base/controllers/base.controller.ts index 7822e8c7e..83b79572d 100644 --- a/apps/server/src/core/base/controllers/base.controller.ts +++ b/apps/server/src/core/base/controllers/base.controller.ts @@ -177,10 +177,20 @@ export class BaseController { await this.pageAccessService.validateCanEdit(parent, user); - return this.baseService.create(user.id, parent.workspaceId, { - spaceId: parent.spaceId, - parentPageId: dto.parentPageId, - name: 'Untitled', - }); + // Inline embeds land mid-document and need to be visually meaningful + // on first paint — a single "Title" column with no rows looks broken. + // Seed two extra text columns and one empty row so the freshly- + // created base looks like a typical database (Title + Text 1 + Text 2, + // one ready-to-fill row). + return this.baseService.create( + user.id, + parent.workspaceId, + { + spaceId: parent.spaceId, + parentPageId: dto.parentPageId, + name: 'Untitled', + }, + { extraTextProperties: 2, defaultRows: 1 }, + ); } } diff --git a/apps/server/src/core/base/services/base.service.ts b/apps/server/src/core/base/services/base.service.ts index f5f00e420..8c21554e5 100644 --- a/apps/server/src/core/base/services/base.service.ts +++ b/apps/server/src/core/base/services/base.service.ts @@ -7,6 +7,7 @@ import { KyselyDB } from '@docmost/db/types/kysely.types'; import { executeTx } from '@docmost/db/utils'; import { BaseRepo } from '@docmost/db/repos/base/base.repo'; import { BasePropertyRepo } from '@docmost/db/repos/base/base-property.repo'; +import { BaseRowRepo } from '@docmost/db/repos/base/base-row.repo'; import { BaseViewRepo } from '@docmost/db/repos/base/base-view.repo'; import { PageService } from '../../page/services/page.service'; import { PageRepo } from '@docmost/db/repos/page/page.repo'; @@ -22,12 +23,18 @@ export class BaseService { @InjectKysely() private readonly db: KyselyDB, private readonly baseRepo: BaseRepo, private readonly basePropertyRepo: BasePropertyRepo, + private readonly baseRowRepo: BaseRowRepo, private readonly baseViewRepo: BaseViewRepo, private readonly pageService: PageService, private readonly pageRepo: PageRepo, ) {} - async create(userId: string, workspaceId: string, dto: CreateBaseDto) { + async create( + userId: string, + workspaceId: string, + dto: CreateBaseDto, + defaults: { extraTextProperties?: number; defaultRows?: number } = {}, + ) { return executeTx(this.db, async (trx) => { const page = await this.pageService.create( userId, @@ -56,6 +63,28 @@ export class BaseService { trx, ); + // Extra default text properties (used by the inline-embed flow so + // a freshly-inserted database renders with a few visible columns + // instead of a single "Title" lane). Positions are chained off + // each other so they keep the requested order. + let lastPropertyPosition = firstPosition; + const extraTextProperties = defaults.extraTextProperties ?? 0; + for (let i = 0; i < extraTextProperties; i++) { + const next = generateJitteredKeyBetween(lastPropertyPosition, null); + await this.basePropertyRepo.insertProperty( + { + pageId: page.id, + name: `Text ${i + 1}`, + type: BasePropertyType.TEXT, + position: next, + isPrimary: false, + workspaceId, + }, + trx, + ); + lastPropertyPosition = next; + } + await this.baseViewRepo.insertView( { pageId: page.id, @@ -69,6 +98,26 @@ export class BaseService { { trx }, ); + // Default empty rows. Same flow rationale as extra properties — + // the inline-embed flow asks for one so the freshly-inserted + // database is interactive on first paint. + let lastRowPosition: string | null = null; + const defaultRows = defaults.defaultRows ?? 0; + for (let i = 0; i < defaultRows; i++) { + const next = generateJitteredKeyBetween(lastRowPosition, null); + await this.baseRowRepo.insertRow( + { + pageId: page.id, + workspaceId, + position: next, + cells: {}, + creatorId: userId, + }, + { trx }, + ); + lastRowPosition = next; + } + return this.baseRepo.findById(page.id, { includeProperties: true, includeViews: true,