Collaborative editing for other apps (deck) - #9062
max-nextcloud wants to merge 104 commits into
Conversation
3e17210 to
a2260eb
Compare
ae7bbf6 to
92e1890
Compare
c9d35f7 to
cd2d5b9
Compare
b0a7b15 to
7cedf66
Compare
cd2d5b9 to
c4cca9f
Compare
7cedf66 to
1bb5586
Compare
7232808 to
5907ff4
Compare
1bc577e to
58fe27f
Compare
|
So far I read up to commit What I stumble over is that the IContext file contains the additional classes Maybe this is also OK, just to double check: a call to No biggie: new classes miss the Can consider to mark the new event unstable or experimental, though I am not aware of a phpdoc or internal established annotation. |
|
On my dev instance I get an error during Migration: {
"reqId": "miQrQfvExjkhgL8Z3V6T",
"level": 3,
"time": "2026-08-19T14:14:45+00:00",
"remoteAddr": "",
"user": "--",
"app": "updater",
"method": "",
"url": "--",
"scriptName": "occ",
"message": "\\OC\\Updater::failure: Exception: Database error when running migration 090000Date20260817110024 for app text\nAn exception occurred while executing a query: SQLSTATE[23502]: Not null violation: 7 ERROR: column \"context_type\" of relation \"oc_text_documents\" contains null values",
"userAgent": "--",
"version": "35.0.0.3",
"occ_command": [
"occ",
"upgrade"
],
"data": {
"app": "updater"
}
}DB is postgres. Probably happens because the column is added as UPDATE: same with context_id. Default to 0? |
Signed-off-by: Max <max@nextcloud.com>
In some scenarios (MarkdownContentEditor with fileId) there is no session. Thus we need the file id of the markdown file to access the attachments as there may not even be a document record in the database. Get the context inside the AttachmentController using either the session or the fileId and then hand it on to the AttachmentService. When getting the attachment list also adjust the urls to include the fileId when needed. Do not return the documentId in some responses. The caller provided it in the request already. Signed-off-by: Max <max@nextcloud.com>
Signed-off-by: Max <max@nextcloud.com>
The dirname is available both in the `checkAttachment` function as well as when checking the files later on. Signed-off-by: Max <max@nextcloud.com>
The loop used $fileId instead of $id, so --all was broken. Signed-off-by: Max <max@nextcloud.com>
* Cleanup session for files without a mount (e.g. deleted). * Without --force, only release the lock if the document has been reset. * Catch NotPermittedException from getFileById(). Signed-off-by: Max <max@nextcloud.com>
This is the fallback for when there is no session. So use the fileId instead. Signed-off-by: Max <max@nextcloud.com>
Signed-off-by: Max <max@nextcloud.com>
Signed-off-by: Jonas <jonas@freesources.org> Assisted-by: ClaudeCode:claude-opus-5.5
AttachmentService had a private copy of FileService::isDownloadDisabled, and FileService::checkFileAccessFromShare repeated the share attribute check inline. Move the attribute check into FileService::isShareDownloadDisabled(IShare). isDownloadDisabled(File) now uses it for internal shares, and checkFileAccessFromShare uses it for the link share from the token. AttachmentService now uses FileService instead of its own copy. Signed-off-by: Jonas <jonas@freesources.org> Assisted-by: ClaudeCode:claude-opus-5.5
Signed-off-by: Max <max@nextcloud.com>
Rename to match the effect / meaning. This was named according to an implementation detail: Federated shares do not have an owner on the instance they were shared to which prevents attaching files. Signed-off-by: Max <max@nextcloud.com>
Signed-off-by: Max <max@nextcloud.com>
Signed-off-by: Max <max@nextcloud.com>
Signed-off-by: Jonas <jonas@freesources.org> Assisted-by: ClaudeCode:claude-opus-5.5
Signed-off-by: Jonas <jonas@freesources.org> Assisted-by: ClaudeCode:claude-opus-5.5
Doctrine renders the alias as `UPDATE oc_text_documents d SET ...`, which SQLite rejects, so upgrading a SQLite instance aborted. Signed-off-by: Jonas <jonas@freesources.org> Assisted-by: ClaudeCode:claude-opus-5.5
resetDocument() is called with force=true, so it never throws DocumentHasUnsavedChangesException, and it handles NotFoundException itself. The DB and NotPermitted exceptions it can throw escaped the event handler and turned the triggering file write into an error. Also check isSaveFromText() before loading the document, which skips a query on every autosave, and drop a redundant IContext check. Signed-off-by: Jonas <jonas@freesources.org> Assisted-by: ClaudeCode:claude-opus-5.5
NodeWrittenResetDocumentListener only handles NodeWrittenEvent now. Signed-off-by: Jonas <jonas@freesources.org> Assisted-by: ClaudeCode:claude-opus-5.5
The migration creates a unique index, so the AddIndex attribute should say so. Signed-off-by: Jonas <jonas@freesources.org> Assisted-by: ClaudeCode:claude-opus-5.5
resetDocument() refuses to reset a document with unsaved steps. Unlocking before it left the file unlocked while those sessions kept editing. Only unlock once the reset went through. Signed-off-by: Jonas <jonas@freesources.org> Assisted-by: ClaudeCode:claude-opus-5.5
cleanupAttachments() resolves the context id as a file id. For other context types, such as deck cards, this could hit an unrelated markdown file and delete its unreferenced attachments. Signed-off-by: Jonas <jonas@freesources.org> Assisted-by: ClaudeCode:claude-opus-5.5
Public endpoints build the context from the document's type and pass the share as auth. Only the file context ties a share to its document, so a valid share token could reach documents of other types. Reject shares for non-file contexts in ContextManager, which every public path uses. Signed-off-by: Jonas <jonas@freesources.org> Assisted-by: ClaudeCode:claude-opus-5.5
CollaborativeEditor no longer has a fileId prop, so the download callback never found the editor to save and unsaved changes were missing from the download. Match on the file context instead. Signed-off-by: Jonas <jonas@freesources.org> Assisted-by: ClaudeCode:claude-opus-5.5
The props spread from CollaborativeEditor.props is loosely typed, so vue-tsc did not see the now required context prop being passed. Signed-off-by: Jonas <jonas@freesources.org> Assisted-by: ClaudeCode:claude-opus-5.5
Makes sure that vue files get type checked as well. Signed-off-by: Jonas <jonas@freesources.org>
NodeWrittenResetDocumentListener unlocked the file on every write, including Text's autosave, so the file stayed unlocked for the rest of the editing session. Release the lock through IContext::cleanup() once the document was actually reset, as the reset ends all editing sessions. Signed-off-by: Jonas <jonas@freesources.org> Assisted-by: ClaudeCode:claude-opus-5.5
Push and the attachment endpoints let NotFoundException and NotPermittedException escape when the context became inaccessible. Map them in SessionMiddleware for all session aware controllers. Closing the last session no longer fails when the context cleanup does, as the session is already closed at that point. Signed-off-by: Jonas <jonas@freesources.org> Assisted-by: ClaudeCode:claude-opus-5.5
If the outside content could not be loaded for the conflict response, autosave stored the new checksum and overwrote the outside change. Skip the save instead and keep the old checksum, so the next save raises the conflict again. Also skip the save when the file is locked while autosave reads it, instead of failing with a 500. Signed-off-by: Jonas <jonas@freesources.org> Assisted-by: ClaudeCode:claude-opus-5.5
Typed defineProps made both booleans required, so Viewer logged missing prop warnings for files it renders without them, like the preloaded previous and next file. They defaulted to false before. Signed-off-by: Jonas <jonas@freesources.org> Assisted-by: ClaudeCode:claude-opus-5.5
Todo
Prerequisits
Backend
syncrequests with IContext.AttachmentServicebased on context.ApiService.pushrequests.Frontend
Provide these props when saving.We still use thedocumentIdto save and resolve the context on the backend.Testing
Feedback
removeInactiveSessionsWithoutSteps()within the create flow was removed, and not introduced back elsewhere.consider to mark the new event unstable or experimental (not aware of a phpdoc or internal established annotation. )Documentation