diff --git a/apiserver/yarn.lock b/apiserver/yarn.lock deleted file mode 100644 index fb57ccd13a..0000000000 --- a/apiserver/yarn.lock +++ /dev/null @@ -1,4 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - diff --git a/live/src/ce/document-types/project-page/handlers.ts b/live/src/ce/document-types/project-page/handlers.ts index 96eb328965..58fead5a3f 100644 --- a/live/src/ce/document-types/project-page/handlers.ts +++ b/live/src/ce/document-types/project-page/handlers.ts @@ -2,6 +2,7 @@ import { PageService } from "@/core/services/page.service"; import { transformHTMLToBinary } from "./transformers"; import { getAllDocumentFormatsFromBinaryData } from "@/core/helpers/page"; import { logger } from "@plane/logger"; +import { HocusPocusServerContext } from "@/core/types/common"; const pageService = new PageService(); @@ -9,13 +10,15 @@ const pageService = new PageService(); * Fetches the binary description data for a project page * Falls back to HTML transformation if binary is not available */ -export const fetchPageDescriptionBinary = async ( - params: URLSearchParams, - pageId: string, - cookie: string | undefined -) => { - const workspaceSlug = params.get("workspaceSlug")?.toString(); - const projectId = params.get("projectId")?.toString(); +export const fetchPageDescriptionBinary = async ({ + pageId, + context, +}: { + pageId: string; + context: HocusPocusServerContext; +}) => { + const { workspaceSlug, projectId, cookie } = context; + if (!workspaceSlug || !projectId || !cookie) return null; const response = await pageService.fetchDescriptionBinary(workspaceSlug, projectId, pageId, cookie); @@ -35,24 +38,21 @@ export const fetchPageDescriptionBinary = async ( * Updates the description of a project page */ export const updatePageDescription = async ({ - params, + context, pageId, - updatedDescription, - cookie, + state: updatedDescription, title, }: { - params: URLSearchParams | undefined; + context: HocusPocusServerContext; pageId: string; - updatedDescription: Uint8Array; - cookie: string | undefined; + state: Uint8Array; title: string; }) => { if (!(updatedDescription instanceof Uint8Array)) { throw new Error("Invalid updatedDescription: must be an instance of Uint8Array"); } - const workspaceSlug = params?.get("workspaceSlug")?.toString(); - const projectId = params?.get("projectId")?.toString(); + const { workspaceSlug, projectId, cookie } = context; if (!workspaceSlug || !projectId || !cookie) return; const { contentBinaryEncoded, contentHTML, contentJSON } = getAllDocumentFormatsFromBinaryData(updatedDescription); @@ -67,17 +67,14 @@ export const updatePageDescription = async ({ }; export const fetchProjectPageTitle = async ({ - workspaceSlug, - projectId, + context, pageId, - cookie, }: { - workspaceSlug: string; - projectId: string; + context: HocusPocusServerContext; pageId: string; - cookie: string | undefined; }) => { - if (!workspaceSlug || !cookie) return; + const { workspaceSlug, projectId, cookie } = context; + if (!workspaceSlug || !projectId || !cookie) return; try { const pageDetails = await pageService.fetchDetails(workspaceSlug, projectId, pageId, cookie); @@ -89,20 +86,17 @@ export const fetchProjectPageTitle = async ({ }; export const updateProjectPageTitle = async ({ - workspaceSlug, - projectId, + context, pageId, title, - cookie, abortSignal, }: { - workspaceSlug: string; - projectId: string; + context: HocusPocusServerContext; pageId: string; title: string; - cookie: string | undefined; abortSignal?: AbortSignal; }) => { + const { workspaceSlug, projectId, cookie } = context; if (!workspaceSlug || !projectId || !cookie) return; const payload = { diff --git a/live/src/ce/document-types/project-page/project-page-handler.ts b/live/src/ce/document-types/project-page/project-page-handler.ts index eb1b7aaeab..4bd8e6f48c 100644 --- a/live/src/ce/document-types/project-page/project-page-handler.ts +++ b/live/src/ce/document-types/project-page/project-page-handler.ts @@ -19,32 +19,19 @@ export const projectPageHandler: DocumentHandler = { /** * Fetch project page description */ - fetch: async ({ pageId, params, context }: DocumentFetchParams) => { - const { cookie } = context; - return await fetchPageDescriptionBinary(params, pageId, cookie); - }, - + fetch: fetchPageDescriptionBinary, /** * Store project page description */ - store: async ({ pageId, state, params, context, title }: DocumentStoreParams) => { - const { cookie } = context; - await updatePageDescription({ params, pageId, updatedDescription: state, cookie, title }); - }, - + store: updatePageDescription, /** * Fetch project page title */ - fetchTitle: async ({ workspaceSlug, projectId, pageId, cookie }) => { - return await fetchProjectPageTitle({ workspaceSlug, projectId, pageId, cookie }); - }, - + fetchTitle: fetchProjectPageTitle, /** * Store project page title */ - updateTitle: async ({ workspaceSlug, projectId, pageId, title, cookie, abortSignal }) => { - await updateProjectPageTitle({ workspaceSlug, projectId, pageId, title, cookie, abortSignal }); - }, + updateTitle: updateProjectPageTitle, }; // Define the project page handler definition diff --git a/live/src/core/extensions/database.ts b/live/src/core/extensions/database.ts index ce0a219bf1..bed57f32da 100644 --- a/live/src/core/extensions/database.ts +++ b/live/src/core/extensions/database.ts @@ -16,15 +16,11 @@ export const createDatabaseExtension = () => { const handleFetch = async ({ context, documentName: pageId, - requestParameters, }: { context: HocusPocusServerContext; documentName: TDocumentTypes; - requestParameters: URLSearchParams; }) => { const { documentType } = context; - const params = requestParameters; - let fetchedData = null; fetchedData = await catchAsync( async () => { @@ -39,11 +35,10 @@ const handleFetch = async ({ }); } - const documentHandler = getDocumentHandler(documentType); + const documentHandler = getDocumentHandler(context); fetchedData = await documentHandler.fetch({ context: context as HocusPocusServerContext, pageId, - params, }); if (!fetchedData) { @@ -70,7 +65,6 @@ const handleStore = async ({ context, state, documentName: pageId, - requestParameters, document, }: Partial & { context: HocusPocusServerContext; @@ -93,7 +87,6 @@ const handleStore = async ({ title = extractTextFromHTML(document?.getXmlFragment("title")?.toJSON()); } const { documentType } = context as HocusPocusServerContext; - const params = requestParameters; if (!documentType) { handleError(null, { errorType: "bad-request", @@ -105,12 +98,11 @@ const handleStore = async ({ }); } - const documentHandler = getDocumentHandler(documentType); + const documentHandler = getDocumentHandler(context); await documentHandler.store({ context: context as HocusPocusServerContext, pageId, state, - params, title, }); }, diff --git a/live/src/core/extensions/index.ts b/live/src/core/extensions/index.ts index 23b38c4fc6..fc02acfe61 100644 --- a/live/src/core/extensions/index.ts +++ b/live/src/core/extensions/index.ts @@ -4,6 +4,7 @@ import { Logger } from "@hocuspocus/extension-logger"; import { setupRedisExtension } from "@/core/extensions/redis"; import { createDatabaseExtension } from "@/core/extensions/database"; import { logger } from "@plane/logger"; +import { TitleSyncExtension } from "./title-sync"; export const getExtensions = async (): Promise => { const extensions: Extension[] = [ @@ -16,6 +17,9 @@ export const getExtensions = async (): Promise => { createDatabaseExtension(), ]; + const titleSyncExtension = new TitleSyncExtension(); + extensions.push(titleSyncExtension); + // Add Redis extensions if Redis is available const redisExtensions = await setupRedisExtension(); extensions.push(...redisExtensions); diff --git a/live/src/core/extensions/title-sync.ts b/live/src/core/extensions/title-sync.ts index db3683483a..688e56de39 100644 --- a/live/src/core/extensions/title-sync.ts +++ b/live/src/core/extensions/title-sync.ts @@ -1,5 +1,5 @@ // hocuspocus -import { Extension, afterLoadDocumentPayload, Hocuspocus, Document } from "@hocuspocus/server"; +import { Extension, Hocuspocus, Document } from "@hocuspocus/server"; import { TiptapTransformer } from "@hocuspocus/transformer"; import * as Y from "yjs"; // types @@ -18,38 +18,22 @@ import { TitleUpdateManager } from "./title-update/title-update-manager"; */ export class TitleSyncExtension implements Extension { instance!: Hocuspocus; - + // Maps document names to their observers and update managers private titleObservers: Map[]) => void> = new Map(); private titleUpdateManagers: Map = new Map(); - - /** - * Handle document loading - migrate old titles if needed - */ - async onLoadDocument({ - context, - document, - requestParameters, - }: { - context: HocusPocusServerContext; - document: Document; - requestParameters: URLSearchParams; - }) { + + async onLoadDocument({ context, document }: { context: HocusPocusServerContext; document: Document }) { try { // initially for on demand migration of old titles to a new title field // in the yjs binary if (document.isEmpty("title")) { - const typedContext = context as HocusPocusServerContext; - const workspaceSlug = requestParameters.get("workspaceSlug")?.toString(); - const projectId = requestParameters.get("projectId")?.toString(); - - const documentHandler = getDocumentHandler(typedContext.documentType); + const { workspaceSlug, projectId } = context; + const documentHandler = getDocumentHandler(context); if (!workspaceSlug || !projectId) return; const title = await documentHandler.fetchTitle({ - workspaceSlug, - projectId, + context, pageId: document.name, - cookie: typedContext.cookie, }); if (title == null) return; const titleField = TiptapTransformer.toYdoc( @@ -66,46 +50,47 @@ export class TitleSyncExtension implements Extension { /** * Set up title synchronization for a document after it's loaded */ - async afterLoadDocument({ document, documentName, context, requestParameters }: afterLoadDocumentPayload) { - const workspaceSlug = requestParameters.get("workspaceSlug")?.toString(); - const projectId = requestParameters.get("projectId")?.toString(); - + async afterLoadDocument({ + document, + documentName, + context, + }: { + document: Document; + documentName: string; + context: HocusPocusServerContext; + }) { + const { workspaceSlug, projectId } = context; + // Exit if we don't have the required information if (!workspaceSlug || !projectId) return; - - const documentHandler = getDocumentHandler(context.documentType); - + + const documentHandler = getDocumentHandler(context); + // Create a title update manager for this document - const updateManager = new TitleUpdateManager( - documentName, - workspaceSlug, - projectId, - context.cookie, - documentHandler - ); - + const updateManager = new TitleUpdateManager(documentName, documentHandler, context); + // Store the manager this.titleUpdateManagers.set(documentName, updateManager); - + // Set up observer for title field const titleObserver = (events: Y.YEvent[]) => { let title = ""; events.forEach((event) => { title = extractTextFromHTML(event.currentTarget.toJSON()); }); - + // Schedule an update with the manager const manager = this.titleUpdateManagers.get(documentName); if (manager) { manager.scheduleUpdate(title); } }; - + // Observe the title field document.getXmlFragment("title").observeDeep(titleObserver); this.titleObservers.set(documentName, titleObserver); } - + /** * Force save title before unloading the document */ @@ -118,7 +103,7 @@ export class TitleSyncExtension implements Extension { this.titleUpdateManagers.delete(documentName); } } - + /** * Remove observers after document unload */ @@ -126,10 +111,9 @@ export class TitleSyncExtension implements Extension { // Clean up observer when document is unloaded const observer = this.titleObservers.get(documentName); if (observer) { - console.log(`Removing title observer for ${documentName}`); this.titleObservers.delete(documentName); } - + // Ensure manager is cleaned up if beforeUnloadDocument somehow didn't run if (this.titleUpdateManagers.has(documentName)) { const manager = this.titleUpdateManagers.get(documentName)!; diff --git a/live/src/core/extensions/title-update/debounce.ts b/live/src/core/extensions/title-update/debounce.ts index 22f047af65..276beb1a66 100644 --- a/live/src/core/extensions/title-update/debounce.ts +++ b/live/src/core/extensions/title-update/debounce.ts @@ -28,7 +28,7 @@ export const createDebounceState = (): DebounceState => ({ export interface DebounceOptions { /** The wait time in milliseconds */ wait: number; - + /** Optional logging prefix for debug messages */ logPrefix?: string; } @@ -41,7 +41,7 @@ export class DebounceManager { private state: DebounceState; private wait: number; private logPrefix: string; - + /** * Creates a new DebounceManager * @param options Debounce configuration options @@ -49,9 +49,9 @@ export class DebounceManager { constructor(options: DebounceOptions) { this.state = createDebounceState(); this.wait = options.wait; - this.logPrefix = options.logPrefix || ''; + this.logPrefix = options.logPrefix || ""; } - + /** * Schedule a debounced function call * @param func The function to call @@ -60,56 +60,56 @@ export class DebounceManager { schedule(func: (...args: any[]) => Promise, ...args: any[]): void { // Always update the last arguments this.state.lastArgs = args; - + const time = Date.now(); this.state.lastCallTime = time; - + // If an operation is in progress, just store the new args and start the timer if (this.state.inProgress) { if (this.logPrefix) { console.log(`${this.logPrefix}: Operation in progress, storing new args and starting new timer`); } - + // Always restart the timer for the new call, even if an operation is in progress if (this.state.timerId) { clearTimeout(this.state.timerId); } - + this.state.timerId = setTimeout(() => { this.timerExpired(func); }, this.wait); return; } - + // If already scheduled, update the args and restart the timer if (this.state.timerId) { if (this.logPrefix) { console.log(`${this.logPrefix}: Already scheduled, updating args and restarting timer`); } - + clearTimeout(this.state.timerId); this.state.timerId = setTimeout(() => { this.timerExpired(func); }, this.wait); return; } - + // Start the timer for the trailing edge execution this.state.timerId = setTimeout(() => { this.timerExpired(func); }, this.wait); - + if (this.logPrefix) { console.log(`${this.logPrefix}: Scheduled execution with wait time ${this.wait}ms`); } } - + /** * Called when the timer expires */ private timerExpired(func: (...args: any[]) => Promise): void { const time = Date.now(); - + // Check if this timer expiration represents the end of the debounce period if (this.shouldInvoke(time)) { // If an operation is already in progress, abort it if the debounce period has completed @@ -118,104 +118,104 @@ export class DebounceManager { console.log(`${this.logPrefix}: Timer expired while operation in progress - will abort current operation`); } } - + // Execute the function this.executeFunction(func, time); return; } - + // Otherwise restart the timer this.state.timerId = setTimeout(() => { this.timerExpired(func); }, this.remainingWait(time)); } - + /** * Execute the debounced function */ private executeFunction(func: (...args: any[]) => Promise, time: number): void { this.state.timerId = null; this.state.lastExecutionTime = time; - + // Execute the function asynchronously - this.performFunction(func).catch(error => { + this.performFunction(func).catch((error) => { console.error(`${this.logPrefix}: Error in execution:`, error); }); } - + /** * Perform the actual function call, handling any in-progress operations */ private async performFunction(func: (...args: any[]) => Promise): Promise { const args = this.state.lastArgs; if (!args) return; - + // Store the args we're about to use const currentArgs = [...args]; - + // If another operation is in progress, abort it await this.abortOngoingOperation(); - + // Mark that we're starting a new operation this.state.inProgress = true; this.state.abortController = new AbortController(); - + try { if (this.logPrefix) { console.log(`${this.logPrefix}: Starting operation`); } - + // Add the abort signal to the arguments if the function can use it const execArgs = [...currentArgs]; execArgs.push(this.state.abortController.signal); - + await func(...execArgs); - + if (this.logPrefix) { console.log(`${this.logPrefix}: Completed operation`); } - + // Only clear lastArgs if they haven't been changed during this operation if (this.state.lastArgs && this.arraysEqual(this.state.lastArgs, currentArgs)) { this.state.lastArgs = null; - + if (this.logPrefix) { console.log(`${this.logPrefix}: Args have not changed during operation, clearing lastArgs`); } - + // Clear any timer as we've successfully processed the latest args if (this.state.timerId) { clearTimeout(this.state.timerId); this.state.timerId = null; } } else if (this.state.lastArgs) { - // If lastArgs have changed during this operation, the timer should already be running + // If lastArgs have changed during this operation, the timer should already be running // but let's make sure it is if (!this.state.timerId) { if (this.logPrefix) { console.log(`${this.logPrefix}: Args changed during operation, ensuring timer is running`); } - + this.state.timerId = setTimeout(() => { this.timerExpired(func); }, this.wait); } } } catch (error) { - if (error instanceof Error && error.name === 'AbortError') { + if (error instanceof Error && error.name === "AbortError") { if (this.logPrefix) { console.log(`${this.logPrefix}: Operation was aborted, another operation should be starting`); } // Nothing to do here, the new operation will be triggered by the timer expiration } else { console.error(`${this.logPrefix}: Error during operation:`, error); - + // On error (not abort), make sure we have a timer running to retry if (!this.state.timerId && this.state.lastArgs) { if (this.logPrefix) { console.log(`${this.logPrefix}: Rescheduling failed operation`); } - + this.state.timerId = setTimeout(() => { this.timerExpired(func); }, this.wait); @@ -226,7 +226,7 @@ export class DebounceManager { this.state.abortController = null; } } - + /** * Abort any ongoing operation */ @@ -235,35 +235,32 @@ export class DebounceManager { if (this.logPrefix) { console.log(`${this.logPrefix}: Aborting in-progress operation`); } - + this.state.abortController.abort(); - + // Small delay to ensure the abort has had time to propagate - await new Promise(resolve => setTimeout(resolve, 20)); - + await new Promise((resolve) => setTimeout(resolve, 20)); + // Double-check that state has been reset, force it if not if (this.state.inProgress || this.state.abortController) { if (this.logPrefix) { console.log(`${this.logPrefix}: Force resetting in-progress state after abort`); } - + this.state.inProgress = false; this.state.abortController = null; } } } - + /** * Determine if we should invoke the function now */ private shouldInvoke(time: number): boolean { // Either this is the first call, or we've waited long enough since the last call - return ( - this.state.lastCallTime === undefined || - (time - this.state.lastCallTime) >= this.wait - ); + return this.state.lastCallTime === undefined || time - this.state.lastCallTime >= this.wait; } - + /** * Calculate how much longer we should wait */ @@ -271,7 +268,7 @@ export class DebounceManager { const timeSinceLastCall = time - (this.state.lastCallTime || 0); return Math.max(0, this.wait - timeSinceLastCall); } - + /** * Force immediate execution */ @@ -279,16 +276,16 @@ export class DebounceManager { if (this.logPrefix) { console.log(`${this.logPrefix}: Force immediate execution`); } - + // Clear any pending timeout if (this.state.timerId) { clearTimeout(this.state.timerId); this.state.timerId = null; } - + // Reset timing state this.state.lastCallTime = undefined; - + // Perform the function immediately if (this.state.lastArgs) { await this.performFunction(func); @@ -302,7 +299,7 @@ export class DebounceManager { } } } - + /** * Cancel any pending operations without executing */ @@ -310,27 +307,27 @@ export class DebounceManager { if (this.logPrefix) { console.log(`${this.logPrefix}: Cancelling pending operations`); } - + // Clear any pending timeout if (this.state.timerId) { clearTimeout(this.state.timerId); this.state.timerId = null; } - + // Reset timing state this.state.lastCallTime = undefined; - + // Abort any in-progress operation if (this.state.inProgress && this.state.abortController) { this.state.abortController.abort(); this.state.inProgress = false; this.state.abortController = null; } - + // Clear args this.state.lastArgs = null; } - + /** * Compare two arrays for equality */ @@ -341,4 +338,5 @@ export class DebounceManager { } return true; } -} \ No newline at end of file +} + diff --git a/live/src/core/extensions/title-update/title-update-manager.ts b/live/src/core/extensions/title-update/title-update-manager.ts index 513038e7a3..1079d320b6 100644 --- a/live/src/core/extensions/title-update/title-update-manager.ts +++ b/live/src/core/extensions/title-update/title-update-manager.ts @@ -1,5 +1,6 @@ import { DocumentHandler } from "@/core/types/document-handler"; import { DebounceManager } from "./debounce"; +import { HocusPocusServerContext } from "@/core/types/common"; /** * Manages title update operations for a single document @@ -7,28 +8,22 @@ import { DebounceManager } from "./debounce"; */ export class TitleUpdateManager { private documentName: string; - private workspaceSlug: string; - private projectId: string; - private cookie: string; private documentHandler: DocumentHandler; private debounceManager: DebounceManager; private lastTitle: string | null = null; + private context: HocusPocusServerContext; /** * Create a new TitleUpdateManager instance */ constructor( documentName: string, - workspaceSlug: string, - projectId: string, - cookie: string, - documentHandler: any, + documentHandler: DocumentHandler, + context: HocusPocusServerContext, wait: number = 5000 ) { + this.context = context; this.documentName = documentName; - this.workspaceSlug = workspaceSlug; - this.projectId = projectId; - this.cookie = cookie; this.documentHandler = documentHandler; // Set up debounce manager with logging @@ -61,14 +56,12 @@ export class TitleUpdateManager { try { console.log(`Starting title update for ${this.documentName} with: "${title}"`); - await this.documentHandler.updateTitle( - this.workspaceSlug, - this.projectId, - this.documentName, + await this.documentHandler.updateTitle({ + context: this.context, + pageId: this.documentName, title, - this.cookie, - signal - ); + abortSignal: signal, + }); console.log(`Completed title update for ${this.documentName} with: "${title}"`); diff --git a/live/src/core/handlers/document-handlers/index.ts b/live/src/core/handlers/document-handlers/index.ts index 55587ce52e..187a0ffa8c 100644 --- a/live/src/core/handlers/document-handlers/index.ts +++ b/live/src/core/handlers/document-handlers/index.ts @@ -13,17 +13,7 @@ initializeDocumentHandlers(); * @param additionalContext Optional additional context criteria * @returns The appropriate document handler */ -export function getDocumentHandler( - documentType: string, - additionalContext: Omit -): DocumentHandler { - // Create a context object with all criteria - const context: HocusPocusServerContext = { - documentType: documentType as any, - ...additionalContext, - }; - - // Use the factory to get the appropriate handler +export function getDocumentHandler(context: HocusPocusServerContext): DocumentHandler { return handlerFactory.getHandler(context); } diff --git a/live/src/core/hocuspocus-server.ts b/live/src/core/hocuspocus-server.ts index 9d3bdb752b..5af266e274 100644 --- a/live/src/core/hocuspocus-server.ts +++ b/live/src/core/hocuspocus-server.ts @@ -6,7 +6,6 @@ import { handleAuthentication } from "@/core/lib/authentication"; // extensions import { getExtensions } from "@/core/extensions/index"; import { DocumentCollaborativeEvents, TDocumentEventsServer } from "@plane/editor/lib"; -import { TitleSyncExtension } from "./extensions/title-sync"; // editor types import { TUserDetails } from "@plane/editor"; // types @@ -70,6 +69,7 @@ export const getHocusPocusServer = async () => { context.cookie = cookie ?? requestParameters.get("cookie"); context.userId = userId; context.workspaceSlug = requestParameters.get("workspaceSlug")?.toString() as string; + context.projectId = requestParameters.get("projectId")?.toString() as string; return await handleAuthentication({ cookie: context.cookie, @@ -95,7 +95,7 @@ export const getHocusPocusServer = async () => { { extra: { operation: "stateless", payload } } ); }, - extensions: [...extensions, new TitleSyncExtension()], - debounce: 10000, + extensions, + debounce: 1000, }); }; diff --git a/live/src/core/services/page.service.ts b/live/src/core/services/page.service.ts index 00ee1bdc12..eee04b6eae 100644 --- a/live/src/core/services/page.service.ts +++ b/live/src/core/services/page.service.ts @@ -44,11 +44,8 @@ export class PageService extends APIService { cookie: string, abortSignal?: AbortSignal ): Promise { - console.log("aaya update call", data); - // Early abort check if (abortSignal?.aborted) { - console.log(`Title update was already aborted before starting: ${pageId}`); throw new DOMException("Aborted", "AbortError"); } @@ -57,7 +54,6 @@ export class PageService extends APIService { const abortPromise = new Promise((_, reject) => { if (abortSignal) { abortListener = () => { - console.log(`Title update aborted during execution: ${pageId}`); reject(new DOMException("Aborted", "AbortError")); }; abortSignal.addEventListener("abort", abortListener); @@ -65,12 +61,6 @@ export class PageService extends APIService { }); try { - // The simulated delay that can be aborted - // await Promise.race([ - // new Promise((resolve) => setTimeout(resolve, 10000)), - // abortPromise - // ]); - // The actual API call that can be aborted return await Promise.race([ this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/`, data, { @@ -83,7 +73,6 @@ export class PageService extends APIService { .catch((error) => { // Special handling for aborted fetch requests if (error.name === "AbortError") { - console.log(`Fetch request for title update was aborted: ${pageId}`); throw new DOMException("Aborted", "AbortError"); } throw error; diff --git a/live/src/core/types/document-handler.d.ts b/live/src/core/types/document-handler.d.ts index 287f871f0e..db086d5b2d 100644 --- a/live/src/core/types/document-handler.d.ts +++ b/live/src/core/types/document-handler.d.ts @@ -6,7 +6,6 @@ import { HocusPocusServerContext } from "@/core/types/common"; export interface DocumentFetchParams { context: HocusPocusServerContext; pageId: string; - params: URLSearchParams; } /** @@ -16,7 +15,6 @@ export interface DocumentStoreParams { context: HocusPocusServerContext; pageId: string; state: Uint8Array; - params: URLSearchParams | undefined; title: string; } @@ -37,22 +35,15 @@ export interface DocumentHandler { /** * Fetch title */ - fetchTitle: (params: { - workspaceSlug: string; - projectId: string; - pageId: string; - cookie: string; - }) => Promise; + fetchTitle: (params: { pageId: string; context: HocusPocusServerContext }) => Promise; /** * Update title */ updateTitle?: (params: { - workspaceSlug: string; - projectId: string; + context: HocusPocusServerContext; pageId: string; title: string; - cookie: string; abortSignal?: AbortSignal; }) => Promise; } diff --git a/packages/editor/src/core/components/editors/document/page-renderer.tsx b/packages/editor/src/core/components/editors/document/page-renderer.tsx index 63d54b67db..fab96af066 100644 --- a/packages/editor/src/core/components/editors/document/page-renderer.tsx +++ b/packages/editor/src/core/components/editors/document/page-renderer.tsx @@ -21,7 +21,7 @@ export const PageRenderer = (props: IPageRenderer) => { props; return ( -
+
{titleEditor && (
= ({ editor, containe const handleLinkHover = useCallback( (event: MouseEvent) => { - if (!editor || editorState.linkExtensionStorage.isBubbleMenuOpen) return; + if (!editor || editorState?.linkExtensionStorage?.isBubbleMenuOpen) return; // Find the closest anchor tag from the event target const target = (event.target as HTMLElement)?.closest("a"); @@ -109,7 +109,7 @@ export const LinkViewContainer: FC = ({ editor, containe // Close link view when bubble menu opens useEffect(() => { - if (editorState.linkExtensionStorage.isBubbleMenuOpen && isOpen) { + if (editorState?.linkExtensionStorage?.isBubbleMenuOpen && isOpen) { setIsOpen(false); } }, [editorState.linkExtensionStorage, isOpen]);