diff --git a/src/adapter/next/cms.tsx b/src/adapter/next/cms.tsx index 6d03a8e90..74c88bd2c 100644 --- a/src/adapter/next/cms.tsx +++ b/src/adapter/next/cms.tsx @@ -5,19 +5,79 @@ import {generatedStore} from 'alinea/backend/store/GeneratedStore' import {AuthenticateRequest, Client, ClientOptions} from 'alinea/core/Client' import {CMS} from 'alinea/core/CMS' import {Config} from 'alinea/core/Config' +import {EntryRow} from 'alinea/core/EntryRow' import {outcome} from 'alinea/core/Outcome' -import {ResolveDefaults, ResolveParams, Resolver} from 'alinea/core/Resolver' +import { + PreviewPayload, + ResolveDefaults, + ResolveParams +} from 'alinea/core/Resolver' import {User} from 'alinea/core/User' import {getPreviewPayloadFromCookies} from 'alinea/preview/PreviewCookies' +import {decodePreviewPayload} from 'alinea/preview/PreviewPayload' +import * as Y from 'yjs' import {devUrl, requestContext} from './context.js' class NextClient extends Client { - constructor(private resolver: Resolver, options: ClientOptions) { + resolver: EntryResolver + + constructor(private db: Database, options: ClientOptions) { super(options) + this.resolver = new EntryResolver(db, db.config.schema) + } + + async resolve(params: ResolveParams): Promise { + const {PHASE_PRODUCTION_BUILD} = await import('next/constants.js') + const isBuild = process.env.NEXT_PHASE === PHASE_PRODUCTION_BUILD + if (!params.preview && !isBuild) { + const syncInterval = params.syncInterval ?? 60 + const now = Date.now() + if (now - lastSync >= syncInterval * 1000) { + lastSync = now + const db = await database + await db.syncWith(client).catch(() => {}) + } + } + return this.resolver.resolve({...resolveDefaults, ...params}) } - resolve(params: ResolveParams): Promise { - return this.resolver.resolve(params) + async parsePreview(preview: PreviewPayload): Promise { + const update = await decodePreviewPayload(preview.payload) + let meta = await db.meta() + if (update.contentHash !== meta.contentHash) { + await syncPending(ctx) + meta = await db.meta() + } + const entry = await resolver.resolve({ + selection: createSelection(Entry({entryId: update.entryId}).maybeFirst()), + realm: Realm.PreferDraft + }) + if (!entry) return + const cachedDraft = await drafts.get(update.entryId) + let currentDraft: Draft | undefined + if (cachedDraft?.contentHash === meta.contentHash) { + currentDraft = cachedDraft.draft + } else { + try { + const pending = backend.drafts.get(ctx, update.entryId) + drafts.set( + update.entryId, + pending.then(draft => ({contentHash: meta.contentHash, draft})) + ) + currentDraft = await pending + } catch (error) { + console.warn('> could not fetch draft', error) + } + } + const apply = currentDraft + ? mergeUpdatesV2([currentDraft.draft, update.update]) + : update.update + const type = cms.config.schema[entry.type] + if (!type) return + const doc = new Y.Doc() + Y.applyUpdateV2(doc, apply) + const entryData = parseYDoc(type, doc) + return {...entry, ...entryData, path: entry.path} } } @@ -54,6 +114,7 @@ export class NextCMS< if (isDraft) { const cookie = cookies() const payload = getPreviewPayloadFromCookies(cookie.getAll()) + const update = payload && (await decodePreviewPayload(payload)) if (payload) resolveDefaults.preview = {payload} } if (devUrl()) return new Client({url, applyAuth, resolveDefaults}) @@ -61,7 +122,7 @@ export class NextCMS< { async resolve(params) { const resolver = await dbResolver - if (!params.preview && !isBuild) { + if (!resolveDefaults.preview && !isBuild) { const syncInterval = params.syncInterval ?? 60 const now = Date.now() if (now - lastSync >= syncInterval * 1000) { diff --git a/src/backend/resolver/EntryResolver.ts b/src/backend/resolver/EntryResolver.ts index a9cb34f28..24da7c06e 100644 --- a/src/backend/resolver/EntryResolver.ts +++ b/src/backend/resolver/EntryResolver.ts @@ -78,11 +78,7 @@ export interface PostContext { export class EntryResolver { targets: Schema.Targets - constructor( - public db: Database, - public schema: Schema, - public defaults?: Partial - ) { + constructor(public db: Database, public schema: Schema) { this.targets = Schema.targets(schema) } @@ -668,8 +664,8 @@ export class EntryResolver { selection, location, locale, - realm = this.defaults?.realm ?? Realm.Published, - preview = this.defaults?.preview + realm = Realm.Published, + preview }: ResolveRequest): Promise => { const ctx = new ResolveContext({realm, location, locale}) const query = this.query(ctx, selection)