diff --git a/cypress/support/fill.ts b/cypress/support/fill.ts
index 1f6909da8..486df37de 100644
--- a/cypress/support/fill.ts
+++ b/cypress/support/fill.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
+ * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
  *
  * SPDX-License-Identifier: AGPL-3.0-only
  */
@@ -13,8 +13,17 @@ declare namespace Cypress {
 
 Cypress.Commands.add('setCodemirrorContent', (content: string) => {
   const line = content.split('\n').find((value) => value !== '')
-  cy.get('.cm-editor').click().get('.cm-content').fill(content)
+  cy.getByCypressId('editor-pane')
+    .should('have.attr', 'data-cypress-editor-ready', 'true')
+    .get('.cm-editor')
+    .click()
+    .get('.cm-content')
+    .fill(content)
   if (line) {
-    cy.get('.cm-editor').find('.cm-line').should('contain.text', line)
+    cy.getByCypressId('editor-pane')
+      .should('have.attr', 'data-cypress-editor-ready', 'true')
+      .get('.cm-editor')
+      .find('.cm-line')
+      .should('contain.text', line)
   }
 })
diff --git a/src/components/editor-page/editor-pane/editor-pane.tsx b/src/components/editor-page/editor-pane/editor-pane.tsx
index c7362fa5a..7cfb201d6 100644
--- a/src/components/editor-page/editor-pane/editor-pane.tsx
+++ b/src/components/editor-page/editor-pane/editor-pane.tsx
@@ -32,7 +32,10 @@ import { useYDoc } from './hooks/yjs/use-y-doc'
 import { useAwareness } from './hooks/yjs/use-awareness'
 import { useWebsocketConnection } from './hooks/yjs/use-websocket-connection'
 import { useBindYTextToRedux } from './hooks/yjs/use-bind-y-text-to-redux'
-import { useInsertInitialNoteContentIntoEditorInMockMode } from './hooks/yjs/use-insert-initial-note-content-into-editor-in-mock-mode'
+import { useInsertNoteContentIntoYTextInMockModeEffect } from './hooks/yjs/use-insert-note-content-into-y-text-in-mock-mode-effect'
+import { useOnFirstEditorUpdateExtension } from './hooks/yjs/use-on-first-editor-update-extension'
+import { useIsConnectionSynced } from './hooks/yjs/use-is-connection-synced'
+import { useMarkdownContentYText } from './hooks/yjs/use-markdown-content-y-text'
 
 export const EditorPane: React.FC<ScrollProps> = ({ scrollState, onScroll, onMakeScrollSource }) => {
   const ligaturesEnabled = useApplicationState((state) => state.editorConfig.ligatures)
@@ -57,13 +60,14 @@ export const EditorPane: React.FC<ScrollProps> = ({ scrollState, onScroll, onMak
 
   const yDoc = useYDoc()
   const awareness = useAwareness(yDoc)
-  const yText = useMemo(() => yDoc.getText('markdownContent'), [yDoc])
-
-  useWebsocketConnection(yDoc, awareness)
+  const yText = useMarkdownContentYText(yDoc)
+  const websocketConnection = useWebsocketConnection(yDoc, awareness)
+  const connectionSynced = useIsConnectionSynced(websocketConnection)
   useBindYTextToRedux(yText)
 
   const yjsExtension = useCodeMirrorYjsExtension(yText, awareness)
-  const mockContentExtension = useInsertInitialNoteContentIntoEditorInMockMode(yText)
+  const [firstEditorUpdateExtension, firstUpdateHappened] = useOnFirstEditorUpdateExtension()
+  useInsertNoteContentIntoYTextInMockModeEffect(firstUpdateHappened, websocketConnection)
 
   const extensions = useMemo(
     () => [
@@ -79,7 +83,7 @@ export const EditorPane: React.FC<ScrollProps> = ({ scrollState, onScroll, onMak
       cursorActivityExtension,
       updateViewContext,
       yjsExtension,
-      ...(mockContentExtension ? [mockContentExtension] : [])
+      firstEditorUpdateExtension
     ],
     [
       editorScrollExtension,
@@ -88,7 +92,7 @@ export const EditorPane: React.FC<ScrollProps> = ({ scrollState, onScroll, onMak
       cursorActivityExtension,
       updateViewContext,
       yjsExtension,
-      mockContentExtension
+      firstEditorUpdateExtension
     ]
   )
 
@@ -107,10 +111,11 @@ export const EditorPane: React.FC<ScrollProps> = ({ scrollState, onScroll, onMak
       onTouchStart={onMakeScrollSource}
       onMouseEnter={onMakeScrollSource}
       {...cypressId('editor-pane')}
-      {...cypressAttribute('editor-ready', String(codeMirrorRef !== undefined))}>
+      {...cypressAttribute('editor-ready', String(firstUpdateHappened && connectionSynced))}>
       <MaxLengthWarning />
       <ToolBar />
       <ReactCodeMirror
+        editable={firstUpdateHappened && connectionSynced}
         placeholder={t('editor.placeholder')}
         extensions={extensions}
         width={'100%'}
diff --git a/src/components/editor-page/editor-pane/hooks/yjs/mock-connection.ts b/src/components/editor-page/editor-pane/hooks/yjs/mock-connection.ts
index 55e8e4408..79f6b3bdb 100644
--- a/src/components/editor-page/editor-pane/hooks/yjs/mock-connection.ts
+++ b/src/components/editor-page/editor-pane/hooks/yjs/mock-connection.ts
@@ -7,6 +7,7 @@
 import { YDocMessageTransporter } from '@hedgedoc/realtime'
 import type { Doc } from 'yjs'
 import type { Awareness } from 'y-protocols/awareness'
+import { MARKDOWN_CONTENT_CHANNEL_NAME } from './use-markdown-content-y-text'
 
 /**
  * A mocked connection that doesn't send or receive any data and is instantly ready.
@@ -16,7 +17,17 @@ export class MockConnection extends YDocMessageTransporter {
     super(doc, awareness)
     this.onOpen()
     this.emit('ready')
-    this.markAsSynced()
+  }
+
+  /**
+   * Simulates a complete sync from the server by inserting the given content at position 0 of the editor yText channel.
+   *
+   * @param content The content to insert
+   */
+  public simulateFirstSync(content: string): void {
+    const yText = this.doc.getText(MARKDOWN_CONTENT_CHANNEL_NAME)
+    yText.insert(0, content)
+    super.markAsSynced()
   }
 
   disconnect(): void {
diff --git a/src/components/editor-page/editor-pane/hooks/yjs/use-insert-initial-note-content-into-editor-in-mock-mode.ts b/src/components/editor-page/editor-pane/hooks/yjs/use-insert-initial-note-content-into-editor-in-mock-mode.ts
deleted file mode 100644
index 3eec0bef6..000000000
--- a/src/components/editor-page/editor-pane/hooks/yjs/use-insert-initial-note-content-into-editor-in-mock-mode.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
- *
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import { useEffect, useMemo, useState } from 'react'
-import { isMockMode } from '../../../../../utils/test-modes'
-import { getGlobalState } from '../../../../../redux'
-import type { YText } from 'yjs/dist/src/types/YText'
-import type { Extension } from '@codemirror/state'
-import { EditorView } from '@codemirror/view'
-
-/**
- * When in mock mode this hook inserts the current markdown content into the given yText to write it into the editor.
- * This happens only one time because after that the editor writes it changes into the yText which writes it into the redux.
- *
- * Usually the CodeMirror gets its content from yjs sync via websocket. But in mock mode this connection isn't available.
- * That's why this hook inserts the current markdown content, that is currently saved in the global application state
- * and was saved there by the {@link NoteLoadingBoundary note loading boundary}, into the y-text to write it into the codemirror.
- * This has to be done AFTER the CodeMirror sync extension (yCollab) has been loaded because the extension reacts only to updates of the yText
- * and doesn't write the existing content into the editor when being loaded.
- *
- * @param yText The yText in which the content should be inserted
- */
-export const useInsertInitialNoteContentIntoEditorInMockMode = (yText: YText): Extension | undefined => {
-  const [firstUpdateHappened, setFirstUpdateHappened] = useState<boolean>(false)
-
-  useEffect(() => {
-    if (firstUpdateHappened) {
-      yText.insert(0, getGlobalState().noteDetails.markdownContent.plain)
-    }
-  }, [firstUpdateHappened, yText])
-
-  return useMemo(() => {
-    return isMockMode && !firstUpdateHappened
-      ? EditorView.updateListener.of(() => setFirstUpdateHappened(true))
-      : undefined
-  }, [firstUpdateHappened])
-}
diff --git a/src/components/editor-page/editor-pane/hooks/yjs/use-insert-note-content-into-y-text-in-mock-mode-effect.ts b/src/components/editor-page/editor-pane/hooks/yjs/use-insert-note-content-into-y-text-in-mock-mode-effect.ts
new file mode 100644
index 000000000..e956779e9
--- /dev/null
+++ b/src/components/editor-page/editor-pane/hooks/yjs/use-insert-note-content-into-y-text-in-mock-mode-effect.ts
@@ -0,0 +1,35 @@
+/*
+ * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { useEffect } from 'react'
+import { isMockMode } from '../../../../../utils/test-modes'
+import { getGlobalState } from '../../../../../redux'
+import type { YDocMessageTransporter } from '@hedgedoc/realtime'
+import { MockConnection } from './mock-connection'
+
+/**
+ * When in mock mode this effect inserts the current markdown content into the yDoc of the given connection to simulate a sync from the server.
+ * This should happen only one time because after that the editor writes its changes into the yText which writes it into the redux.
+ *
+ * Usually the CodeMirror gets its content from yjs sync via websocket. But in mock mode this connection isn't available.
+ * That's why this hook inserts the current markdown content, that is currently saved in the global application state
+ * and was saved there by the {@link NoteLoadingBoundary note loading boundary}, into the y-text to write it into the codemirror.
+ * This has to be done AFTER the CodeMirror sync extension (yCollab) has been loaded because the extension reacts only to updates of the yText
+ * and doesn't write the existing content into the editor when being loaded.
+ *
+ * @param connection The connection into whose yDoc the content should be written
+ * @param firstUpdateHappened Defines if the first update already happened
+ */
+export const useInsertNoteContentIntoYTextInMockModeEffect = (
+  firstUpdateHappened: boolean,
+  connection: YDocMessageTransporter
+): void => {
+  useEffect(() => {
+    if (firstUpdateHappened && isMockMode && connection instanceof MockConnection) {
+      connection.simulateFirstSync(getGlobalState().noteDetails.markdownContent.plain)
+    }
+  }, [firstUpdateHappened, connection])
+}
diff --git a/src/components/editor-page/editor-pane/hooks/yjs/use-is-connection-synced.ts b/src/components/editor-page/editor-pane/hooks/yjs/use-is-connection-synced.ts
new file mode 100644
index 000000000..b43c08d85
--- /dev/null
+++ b/src/components/editor-page/editor-pane/hooks/yjs/use-is-connection-synced.ts
@@ -0,0 +1,28 @@
+/*
+ * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { useEffect, useState } from 'react'
+import type { YDocMessageTransporter } from '@hedgedoc/realtime'
+
+/**
+ * Checks if the given message transporter has received at least one full synchronisation.
+ *
+ * @param connection The connection whose sync status should be checked
+ */
+export const useIsConnectionSynced = (connection: YDocMessageTransporter): boolean => {
+  const [editorEnabled, setEditorEnabled] = useState<boolean>(false)
+
+  useEffect(() => {
+    const enableEditor = () => setEditorEnabled(true)
+    const disableEditor = () => setEditorEnabled(false)
+    connection.on('synced', enableEditor).on('disconnected', disableEditor)
+    return () => {
+      connection.off('synced', enableEditor).off('disconnected', disableEditor)
+    }
+  }, [connection])
+
+  return editorEnabled
+}
diff --git a/src/components/editor-page/editor-pane/hooks/yjs/use-markdown-content-y-text.ts b/src/components/editor-page/editor-pane/hooks/yjs/use-markdown-content-y-text.ts
new file mode 100644
index 000000000..2a1f67303
--- /dev/null
+++ b/src/components/editor-page/editor-pane/hooks/yjs/use-markdown-content-y-text.ts
@@ -0,0 +1,21 @@
+/*
+ * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import type { Doc } from 'yjs'
+import { useMemo } from 'react'
+import type { YText } from 'yjs/dist/src/types/YText'
+
+export const MARKDOWN_CONTENT_CHANNEL_NAME = 'markdownContent'
+
+/**
+ * Extracts the y-text channel that saves the markdown content from the given yDoc.
+ *
+ * @param yDoc The yjs document from which the yText should be extracted
+ * @return the extracted yText channel
+ */
+export const useMarkdownContentYText = (yDoc: Doc): YText => {
+  return useMemo(() => yDoc.getText(MARKDOWN_CONTENT_CHANNEL_NAME), [yDoc])
+}
diff --git a/src/components/editor-page/editor-pane/hooks/yjs/use-on-first-editor-update-extension.ts b/src/components/editor-page/editor-pane/hooks/yjs/use-on-first-editor-update-extension.ts
new file mode 100644
index 000000000..c80b63fcf
--- /dev/null
+++ b/src/components/editor-page/editor-pane/hooks/yjs/use-on-first-editor-update-extension.ts
@@ -0,0 +1,20 @@
+/*
+ * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { useMemo, useState } from 'react'
+import { EditorView } from '@codemirror/view'
+import type { Extension } from '@codemirror/state'
+
+/**
+ * Provides an extension that checks when the code mirror, that loads the extension, has its first update.
+ *
+ * @return [Extension, boolean] The extension that listens for editor updates and a boolean that defines if the first update already happened
+ */
+export const useOnFirstEditorUpdateExtension = (): [Extension, boolean] => {
+  const [firstUpdateHappened, setFirstUpdateHappened] = useState<boolean>(false)
+  const extension = useMemo(() => EditorView.updateListener.of(() => setFirstUpdateHappened(true)), [])
+  return [extension, firstUpdateHappened]
+}