Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
df48693
wip: try y-indexeddb
max-nextcloud Jun 13, 2025
5e3d902
chore(split) useIndexedDbProvider from Editor.vue
max-nextcloud Sep 4, 2025
6495fb5
fix(cron): do not reset document
max-nextcloud Sep 4, 2025
3a9cfbe
enh(yjs): store baseVersionEtag alongside doc
max-nextcloud Sep 4, 2025
7931352
fix(offline): persist dirty state in indexed db
max-nextcloud Oct 14, 2025
8922a46
chore(test): explore empty changesets
max-nextcloud Oct 22, 2025
94083f6
chore(rename): use privateMethods for emitError and emitDocumentState…
max-nextcloud Oct 26, 2025
7e62275
chore(cleanup): _getContent alias for serialize
max-nextcloud Oct 26, 2025
07cd6c9
chore(refactor): handle open data in websocket polyfill
max-nextcloud Oct 26, 2025
85a04c3
fix(sync): only accept sync protocol and return sync step 2
max-nextcloud Oct 26, 2025
7d3d21a
enh(sync): recover automatically from outdated / renamed doc
max-nextcloud Oct 27, 2025
c15417f
fix(sync): ensure dirty is updated when saving in onDestroy
max-nextcloud Nov 4, 2025
082d031
chore(logging): some optional debug logging
max-nextcloud Nov 5, 2025
5289898
fix(sync): actually disable browser broadcast
max-nextcloud Nov 5, 2025
9e90693
chore(test): conflict and sync with autoreload
max-nextcloud Nov 5, 2025
612e842
chore(type) a few more files
max-nextcloud Nov 6, 2025
7b04720
chore(test): add initial test for indexed db
max-nextcloud Nov 7, 2025
65aad5e
fix(sync): Cleanup sessions even with unsaved changes
max-nextcloud Nov 7, 2025
e669ddd
chore(type): EditorFactory and setInitialYjsState
max-nextcloud Nov 10, 2025
427cbae
enh(sync): updateFromContent()
max-nextcloud Nov 10, 2025
5d78034
fix(indexedDB): handle conflict with local change
max-nextcloud Jan 7, 2026
00943b5
chore(log): debug log when attempting to open sync service twice
max-nextcloud Jan 7, 2026
16c4087
fix(conflict): handle conflict when opening initial session
max-nextcloud Jan 7, 2026
3ab420c
fix(conflict): label buttons properly for unsaved local changes
max-nextcloud Jan 7, 2026
4253ad0
chore(split): reload handling local change in Editor.js
max-nextcloud Jan 14, 2026
61da331
fix(sync): make bc channel depend on base version etag
max-nextcloud Jan 16, 2026
5f3877d
fix(offline): save dirty file when opening
max-nextcloud Jan 16, 2026
6f70ec8
fix(offline): autosave with disconnects
max-nextcloud Jan 19, 2026
bcb8a4a
chore(spell): fix collision typo
max-nextcloud Mar 25, 2026
7da73c0
chore(names): rename requireReconnect to displayConnectionIssue
max-nextcloud Mar 25, 2026
1f59466
chore(test): save and push offline changes on open
max-nextcloud Mar 26, 2026
47764b7
fix(sync): push steps from offline changes when opening file
max-nextcloud Mar 27, 2026
d6395e3
fix(sync): wait for indexeddb to load before reading dirty flag (ai c…
silverkszlo Apr 2, 2026
a887a33
fix(offline): wait for editor, idb and then save with manualSave=true…
silverkszlo Apr 2, 2026
eeb7cad
fix(test): push first and then get content of file (ai code)
silverkszlo Apr 2, 2026
e5c3e28
fix(types): add missing mentionSearch parameter to createRichEditor
silverkszlo Apr 2, 2026
b607495
chore(cleanup): remove obsolete imports after rebase
silverkszlo Apr 2, 2026
ee3d816
chore(lint): npm run prettier and lint
silverkszlo Apr 2, 2026
58cddcc
fix(test): destroy editor after use
silverkszlo Apr 2, 2026
92b254f
fix(Editor.js): forward slots to inner Editor component (ai code)
silverkszlo Apr 2, 2026
20d0eca
fix(useIndexedDbProvider): don't write on closed connection
mejo- Apr 21, 2026
a1661c5
fix(conflict): persist local state in local storage
mejo- Apr 21, 2026
a40a23b
fix(documentStatus): display document status on conflict
mejo- Apr 21, 2026
9eb8933
test(playwright): migrate conflict tests from Cypress
mejo- Apr 21, 2026
fa44e88
fix: catch IDB error while sync
silverkszlo Apr 2, 2026
839827a
fix(document): don't reset document when uploading same content
mejo- Apr 23, 2026
bb7cd31
refactor(CollisionResolveDialog): rephrase btns in CollisionResolveDi…
silverkszlo Apr 23, 2026
a10e336
chore(test): move `useDelayedFlag` test to `tests` directory
mejo- Apr 23, 2026
9b989d4
feat(offline): customizable delay before read-only, default 5 minutes
mejo- Apr 23, 2026
c98c919
chore(editor): remove unused property from Wrapper component
mejo- Apr 23, 2026
3b28b09
test(playwright): enable sharding to run tests in parallel in CI
mejo- Apr 23, 2026
22a6d56
test(playwright): longer timeout for first conflict UI check
mejo- Apr 23, 2026
21deb81
test(playwright): improve selector for viewer close button
mejo- Apr 23, 2026
930bd9c
fix(WebSocketPolyfill): run onopen callback on initialization
mejo- Apr 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/cypress-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ jobs:
php occ app:list
php occ background:cron
php occ config:system:set session_keepalive --value=false --type=boolean
# Set offline_readonly_delay to 5 seconds to speed up tests that rely on it, like recover tests in sync.spec.js
php occ config:app:set --type integer --value 5 text offline_readonly_delay
curl -v http://localhost:8081/index.php/login
cat data/nextcloud.log

Expand Down
37 changes: 19 additions & 18 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,54 +14,55 @@ jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
strategy:
matrix:
shardIndex: [1, 2, 3]
shardTotal: [3]
steps:
- name: Checkout app
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Check composer.json
id: check_composer
uses: andstor/file-existence-action@076e0072799f4942c8bc574a82233e1e4d13e9d6 # v3.0.0
uses: andstor/file-existence-action@558493d6c74bf472d87c84eab196434afc2fa029 # v3.1.0
with:
files: 'composer.json'

- name: Install composer dependencies
if: steps.check_composer.outputs.files_exists == 'true'
run: composer install --no-dev

- name: Read package.json node and npm engines version
uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3
- name: Read package.json
uses: nextcloud-libraries/parse-package-engines-action@122ae05d4257008180a514e1ddeb0c1b9d094bdd # v0.1.0
id: versions
with:
fallbackNode: '^20'
fallbackNpm: '^10'

- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
- name: Set up node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: ${{ steps.versions.outputs.nodeVersion }}
node-version: ${{ steps.versions.outputs.node-version }}

- name: Set up npm ${{ steps.versions.outputs.npmVersion }}
run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}'
- name: Set up npm
run: npm i -g 'npm@${{ steps.versions.outputs.steps.versions.outputs.package-manager-version }}'

- name: Install node dependencies & build app
env:
CYPRESS_INSTALL_BINARY: 0
run: |
npm ci
TESTING=true npm run build --if-present
npm run build --if-present

- name: Install Playwright Browsers
run: npx playwright install chromium --only-shell

- name: Run Playwright tests
run: npx playwright test
run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}

- name: Upload results
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !cancelled() }}
with:
name: playwright-report
name: playwright-report_shard${{ matrix.shardIndex }}
path: test-results/
retention-days: 30
retention-days: 7
2 changes: 1 addition & 1 deletion composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@
'OCA\\Text\\Listeners\\BeforeAssistantNotificationListener' => $baseDir . '/../lib/Listeners/BeforeAssistantNotificationListener.php',
'OCA\\Text\\Listeners\\BeforeNodeDeletedListener' => $baseDir . '/../lib/Listeners/BeforeNodeDeletedListener.php',
'OCA\\Text\\Listeners\\BeforeNodeRenamedListener' => $baseDir . '/../lib/Listeners/BeforeNodeRenamedListener.php',
'OCA\\Text\\Listeners\\BeforeNodeWrittenListener' => $baseDir . '/../lib/Listeners/BeforeNodeWrittenListener.php',
'OCA\\Text\\Listeners\\FilesLoadAdditionalScriptsListener' => $baseDir . '/../lib/Listeners/FilesLoadAdditionalScriptsListener.php',
'OCA\\Text\\Listeners\\FilesSharingLoadAdditionalScriptsListener' => $baseDir . '/../lib/Listeners/FilesSharingLoadAdditionalScriptsListener.php',
'OCA\\Text\\Listeners\\LoadEditorListener' => $baseDir . '/../lib/Listeners/LoadEditorListener.php',
'OCA\\Text\\Listeners\\LoadViewerListener' => $baseDir . '/../lib/Listeners/LoadViewerListener.php',
'OCA\\Text\\Listeners\\NodeCopiedListener' => $baseDir . '/../lib/Listeners/NodeCopiedListener.php',
'OCA\\Text\\Listeners\\NodeWrittenResetDocumentListener' => $baseDir . '/../lib/Listeners/NodeWrittenResetDocumentListener.php',
'OCA\\Text\\Listeners\\RegisterDirectEditorEventListener' => $baseDir . '/../lib/Listeners/RegisterDirectEditorEventListener.php',
'OCA\\Text\\Listeners\\RegisterTemplateCreatorListener' => $baseDir . '/../lib/Listeners/RegisterTemplateCreatorListener.php',
'OCA\\Text\\Listeners\\VersionRestoredListener' => $baseDir . '/../lib/Listeners/VersionRestoredListener.php',
Expand Down
2 changes: 1 addition & 1 deletion composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,12 @@ class ComposerStaticInitText
'OCA\\Text\\Listeners\\BeforeAssistantNotificationListener' => __DIR__ . '/..' . '/../lib/Listeners/BeforeAssistantNotificationListener.php',
'OCA\\Text\\Listeners\\BeforeNodeDeletedListener' => __DIR__ . '/..' . '/../lib/Listeners/BeforeNodeDeletedListener.php',
'OCA\\Text\\Listeners\\BeforeNodeRenamedListener' => __DIR__ . '/..' . '/../lib/Listeners/BeforeNodeRenamedListener.php',
'OCA\\Text\\Listeners\\BeforeNodeWrittenListener' => __DIR__ . '/..' . '/../lib/Listeners/BeforeNodeWrittenListener.php',
'OCA\\Text\\Listeners\\FilesLoadAdditionalScriptsListener' => __DIR__ . '/..' . '/../lib/Listeners/FilesLoadAdditionalScriptsListener.php',
'OCA\\Text\\Listeners\\FilesSharingLoadAdditionalScriptsListener' => __DIR__ . '/..' . '/../lib/Listeners/FilesSharingLoadAdditionalScriptsListener.php',
'OCA\\Text\\Listeners\\LoadEditorListener' => __DIR__ . '/..' . '/../lib/Listeners/LoadEditorListener.php',
'OCA\\Text\\Listeners\\LoadViewerListener' => __DIR__ . '/..' . '/../lib/Listeners/LoadViewerListener.php',
'OCA\\Text\\Listeners\\NodeCopiedListener' => __DIR__ . '/..' . '/../lib/Listeners/NodeCopiedListener.php',
'OCA\\Text\\Listeners\\NodeWrittenResetDocumentListener' => __DIR__ . '/..' . '/../lib/Listeners/NodeWrittenResetDocumentListener.php',
'OCA\\Text\\Listeners\\RegisterDirectEditorEventListener' => __DIR__ . '/..' . '/../lib/Listeners/RegisterDirectEditorEventListener.php',
'OCA\\Text\\Listeners\\RegisterTemplateCreatorListener' => __DIR__ . '/..' . '/../lib/Listeners/RegisterTemplateCreatorListener.php',
'OCA\\Text\\Listeners\\VersionRestoredListener' => __DIR__ . '/..' . '/../lib/Listeners/VersionRestoredListener.php',
Expand Down
51 changes: 21 additions & 30 deletions cypress/e2e/api/SessionApi.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,34 +73,33 @@ describe('The session Api', function () {
cy.closeConnection(connection)
})

// Echoes all message types but queries
Object.entries(messages)
.filter(([key, _value]) => key !== 'query')
.forEach(([type, sample]) => {
it(`echos ${type} messages`, function () {
const steps = [sample]
const version = 0
cy.pushSteps({ connection, steps, version })
.its('version')
.should('eql', 0)
cy.syncSteps(connection)
.its('steps[0].data')
.should('eql', steps)
})
// Echoes updates and responses
;['update', 'response'].forEach((type) => {
it(`echos ${type} messages`, function () {
const steps = [messages[type]]
const version = 0
cy.pushSteps({ connection, steps, version })
.its('version')
.should('eql', 0)
cy.syncSteps(connection).its('steps[0].data').should('eql', steps)
})
})

it('responds to queries', function () {
it('responds to queries with updates and responses', function () {
const version = 0
Object.entries(messages).forEach(([type, sample]) => {
cy.pushSteps({ connection, steps: [sample], version })
})
cy.pushSteps({ connection, steps: [messages.query], version }).then(
(response) => {
cy.wrap(response).its('version').should('eql', 0)
cy.wrap(response).its('steps.length').should('eql', 1)
cy.wrap(response).its('steps.length').should('eql', 2)
cy.wrap(response)
.its('steps[0].data')
.should('eql', [messages.update])
cy.wrap(response)
.its('steps[1].data')
.should('eql', [messages.response])
},
)
})
Expand All @@ -111,7 +110,6 @@ describe('The session Api', function () {
let connection
let fileId
let filePath
let joining

beforeEach(function () {
cy.testName().then((name) => {
Expand Down Expand Up @@ -156,13 +154,10 @@ describe('The session Api', function () {
manualSave: true,
})
cy.openConnection({ fileId, filePath })
.then(({ connection: con, data }) => {
joining = con
return data
})
.its('documentState')
.as('joining')
.its('data.documentState')
.should('eql', documentState)
cy.closeConnection(joining)
cy.get('@joining').its('connection').then(cy.closeConnection)
})

afterEach(function () {
Expand All @@ -175,7 +170,6 @@ describe('The session Api', function () {
let connection
let filePath
let shareToken
let joining

beforeEach(function () {
cy.testName().then((name) => {
Expand Down Expand Up @@ -232,13 +226,10 @@ describe('The session Api', function () {
manualSave: true,
})
cy.openConnection({ filePath: '', token: shareToken })
.then(({ connection: con, data }) => {
joining = con
return data
})
.its('documentState')
.as('joining')
.its('data.documentState')
.should('eql', documentState)
cy.closeConnection(joining)
cy.get('@joining').its('connection').then(cy.closeConnection)
})
})

Expand Down
21 changes: 13 additions & 8 deletions cypress/e2e/api/SyncServiceProvider.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,20 @@ describe('Sync service provider', function () {
*/
function createProvider(ydoc) {
const relativePath = '.'
const { connection, openConnection, baseVersionEtag } = provideConnection({
fileId,
relativePath,
})
const { syncService } = provideSyncService(
connection,
openConnection,
baseVersionEtag,
let baseVersionEtag
const setBaseVersionEtag = (val) => {
baseVersionEtag = val
}
const getBaseVersionEtag = () => baseVersionEtag
const { connection, openConnection } = provideConnection(
{
fileId,
relativePath,
},
getBaseVersionEtag,
setBaseVersionEtag,
)
const { syncService } = provideSyncService(connection, openConnection)
const queue = []
syncService.bus.on('opened', () => syncService.startSync())
return createSyncServiceProvider({
Expand Down
Loading
Loading