Skip to content

feat: add per-user raw Markdown editing (#3570)#8898

Open
soapergem wants to merge 1 commit into
nextcloud:mainfrom
soapergem:main
Open

feat: add per-user raw Markdown editing (#3570)#8898
soapergem wants to merge 1 commit into
nextcloud:mainfrom
soapergem:main

Conversation

@soapergem

@soapergem soapergem commented Jul 22, 2026

Copy link
Copy Markdown

📝 Summary

Adds a per-user "Edit Markdown source" toggle that lets a user switch the current document into a raw Markdown editor on the fly, without disrupting other collaborators.

Why the per-user, detached approach. The rich editor and the plain editor bind to the same shared yjs document but use incompatible ProseMirror schemas (rich node tree vs. a single plain-text document). Two users on different schemas against one shared document would corrupt each other — which is exactly the concern raised in the issue discussion. So raw editing runs on a detached plain-text editor (no Collaboration extension): the collaborative rich editor stays mounted and connected in the background, so the shared document never sees the plain-text schema and other participants are unaffected.

Round-trip and conflicts. On leaving raw mode the edited source is applied back to the live document. If the document drifted in the meantime (concurrent edits from other users), the existing CollisionResolveDialog is reused so the user can keep their raw version or the remote one, instead of silently clobbering either. If the user made no changes, or the document did not drift, the edits are applied directly with no prompt.

Interplay with rich_editing_enabled. The toggle is gated on rich Markdown editing being active, so when an admin has set rich_editing_enabled=0 (the whole instance already edits raw) the toggle simply does not appear — no "raw within raw", no schema mixing. The mode is ephemeral per-user view state and is not persisted, which deliberately sidesteps the open "user vs. file vs. instance setting" question from the issue.

What's included

  • useRawEditing composable — enter/exit state machine with drift detection
  • RawMarkdownEditor — detached plain-text editor seeded with the serialized Markdown
  • RawEditingToggle — overflow-menu entry, gated to rich Markdown editing
  • Autosave suppression while detached; unload warning when raw edits are unsaved
  • A "Discard changes" affordance to leave raw mode without applying edits

🖼️ Screenshots

1. Toggle in the ⋯ overflow menu

Edit Markdown source entry in the editor overflow menu

2. Editing the raw Markdown source

The current user is switched to a detached plain-text editor showing the document's raw Markdown. Rich text returns to the WYSIWYG view.

Detached raw Markdown source editor

3. Conflict resolution when the document changed concurrently

If another collaborator changed the document while it was being edited raw, the existing collision dialog is reused to keep either the local (raw) version or the remote one.

Collision dialog offering to keep local raw edits or the remote version

🚧 TODO

  • Confirm desired wording for the menu entry / buttons with maintainers

🏁 Checklist

  • Code is properly formatted (npm run lint passes; tsc clean under TypeScript 6)
  • Sign-off message is added to all commits
  • Tests (unit, integration and/or end-to-end) passing and the changes are covered with tests
  • Documentation (README or documentation) has been updated or is not required

Testing notes

  • Unit (src/tests/composables/useRawEditing.spec.ts, 7 cases): enter/gating, direct apply when there is no drift, conflict raised on drift, no-op when unchanged, discard, and conflict clearing.
  • End-to-end (cypress/e2e/RawEditing.spec.js, 5 cases): toggling into the source view, applying raw edits back to the rich editor, discarding, and both branches of the conflict dialog when the document changed concurrently. All passing against a local Nextcloud instance.

🤖 AI (if applicable)

  • The content of this PR was partly or fully generated using AI tools
  • The AI-generated content was reviewed, comprehended and tested by a human

Add an "Edit Markdown source" toggle that switches the current user into a
detached plain-text editor showing the document's raw Markdown. Other
collaborators are unaffected: the collaborative rich editor stays mounted
and connected in the background, so the shared document never sees the
plain-text schema.

On return, the edited source is applied to the live document. If the
document drifted in the meantime (concurrent edits from other users), the
existing CollisionResolveDialog is reused so the user can keep their raw
version or the remote one instead of silently clobbering either.

- useRawEditing: enter/exit state machine with drift detection
- RawMarkdownEditor: detached plain editor (no Collaboration extension)
- RawEditingToggle: overflow-menu entry, gated to rich Markdown editing
  (hidden when rich_editing_enabled is off, i.e. the whole instance is raw)
- suppress autosave while detached; warn on unload with unsaved raw edits
- unit tests for the state machine and Cypress e2e covering the round-trip,
  discard, and both conflict-resolution branches

Signed-off-by: Gordon Myers <soapergem@gmail.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@max-nextcloud

Copy link
Copy Markdown
Collaborator

Hi @soapergem

Thanks for your contribution. I like the general approach of a per session single user switch. That seems useful also for inspecting the markdown.

I'm unsure about adding another scenario that runs into a conflict resolution screen. We've observer that it's overwhelming in particular with large files as we currently have no indication of what changed. We want to improve upon that - but we are not there yet.

Technically we could also implement the same approach without a conflict screen and leave the resolution to yjs. We'd need to keep a copy of the ydoc at the start of the editing session. Then compute an yjs update that transforms the ydoc into one that corresponds to the document after the editing session. This update could be applied to the main editing session just like any other yjs update. In cases of small edits that create no actual conflicts this would be ideal. However if people are editing the same section it may lead to counterintuitive results.

I'd be curious what @marcoambrosini thinks from a design point of view.

I also have not looked at the code changes yet at all.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Raw markdown editing

2 participants