diff --git a/.dockerignore b/.dockerignore index f09aed879..ba2816f47 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,14 +3,18 @@ # SPDX-License-Identifier: AGPL-3.0-or-later .dockerignore +.editorconfig .git .gitignore .gitmodules .github +.reuse +.stylelintrc.js +.eslint.config.js docker-compose.yml -traefik2.yml Dockerfile* *.png +*.md /onlyoffice-builds.git/ /www/common/onlyoffice/dist/ blob @@ -18,3 +22,4 @@ block customize data datastore +docs diff --git a/CHANGELOG.md b/CHANGELOG.md index 78d1e234d..60f502ae6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,71 @@ SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and cont SPDX-License-Identifier: AGPL-3.0-or-later --> + +# ❄️ Winter release (2024.12.0) + +## Goals + +This version delivers fixes and improvements across CryptPad. We are particularly happy to release a fix of our OnlyOffice integration that could address long-standing issues with documents becoming corrupted. If confirmed at scale, this fix could dramatically improve the use of OnlyOffice apps in CryptPad. + +## Improvements + +- OnlyOffice integration + - Fix bug resulting in corrupted documents [#1736](https://github.com/cryptpad/cryptpad/pull/1736) +- Drive + - Links included in Drive exports [#1695](https://github.com/cryptpad/cryptpad/pull/1695) +- Rich Text + - Formatted tables and strikethrough text in Pad .md exports [#1720](https://github.com/cryptpad/cryptpad/pull/1720) +- Forms + - Form password warning [#1690](https://github.com/cryptpad/cryptpad/pull/1690) +- Performance improvements (1/3) to example-advanced.nginx.conf [#1709](https://github.com/cryptpad/cryptpad/pull/1709) +- Enable toggle in and out of calendars on small screens [#1584](https://github.com/cryptpad/cryptpad/pull/1584) + +## Fixes + +- Accessibility + - Kanban Focus order fix [#1708](https://github.com/cryptpad/cryptpad/pull/1708) + - Change iframe title [#1706](https://github.com/cryptpad/cryptpad/pull/1706) + - Fix keyboard trap inside Form description [#1672](https://github.com/cryptpad/cryptpad/pull/1672) + - Disable arrow key navigation in the drive while modal is active [#1669](https://github.com/cryptpad/cryptpad/pull/1669) + - Simulate click action for keyboard users in Ctrl+E modal [#1726](https://github.com/cryptpad/cryptpad/pull/1726) +- Drive + - Prevent links in trash from disappearing after drive reload [#1697](https://github.com/cryptpad/cryptpad/pull/1697) + - Restore multiple files/directories [#1692](https://github.com/cryptpad/cryptpad/pull/1692) + - Stop selection of all other trashed files when restoring single file [#1681](https://github.com/cryptpad/cryptpad/pull/1681) +- Notifications + - Calendar reminders in notification panel [#1721](https://github.com/cryptpad/cryptpad/pull/1721) + - Add Notifications padding [#1688](https://github.com/cryptpad/cryptpad/pull/1688) + - Notification fixes [#1674](https://github.com/cryptpad/cryptpad/pull/1674) +- Forms + - Fix some form storage-related bugs [#1723](https://github.com/cryptpad/cryptpad/pull/1723) + - Fix spacing issues in Forms [#1682](https://github.com/cryptpad/cryptpad/pull/1682) + - Fix padding in form questions [#1670](https://github.com/cryptpad/cryptpad/pull/1670) +- Helpdesk + - Fix "Closed" support tickets remaining in Inbox [#1719](https://github.com/cryptpad/cryptpad/pull/1719) +- 'Request edit' button [#1680](https://github.com/cryptpad/cryptpad/pull/1680) +- Fix example-code-typo [#1703](https://github.com/cryptpad/cryptpad/pull/1703) + +## Upgrade notes + +If you are upgrading from a version older than `2024.9.1` please read the upgrade notes of all versions between yours and `2024.9.1` to avoid configuration issues. + +To upgrade: + +1. Stop your server +2. Get the latest code with git + +```bash +git fetch origin --tags +git checkout 2024.12.0 +npm ci +npm run install:components +``` + +3. Restart your server +4. Review your instance's checkup page to ensure that you are passing all tests + + # Autumn release (2024.9.0) ## Goals diff --git a/docker-compose.yml b/docker-compose.yml index 49a81a25b..84ffc0e1b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ --- services: cryptpad: - image: "cryptpad/cryptpad:version-2024.12.0" + image: "cryptpad/cryptpad:version-2025.3.0" hostname: cryptpad environment: diff --git a/eslint.config.js b/eslint.config.js index 3b1550849..9b449c36e 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -46,6 +46,8 @@ module.exports = [{ "www/common/worker.bundle.js", "src/tweetnacl", "_build", + "testapi.js", + "rollup.config.mjs", ], }, ...compat.extends("eslint:recommended", "plugin:compat/recommended"), { plugins: { diff --git a/package-lock.json b/package-lock.json index acde575cd..027c522ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cryptpad", - "version": "2024.12.0", + "version": "2025.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cryptpad", - "version": "2024.12.0", + "version": "2025.3.0", "license": "AGPL-3.0+", "dependencies": { "@mcrowe/minibloom": "^0.2.0", diff --git a/package.json b/package.json index 3fede6da3..bd05cab40 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cryptpad", "description": "a collaborative office suite that is end-to-end encrypted and open-source", - "version": "2024.12.0", + "version": "2025.3.0", "license": "AGPL-3.0+", "repository": { "type": "git", diff --git a/src/common/common-util.js b/src/common/common-util.js index 23f4809f0..ac03ff126 100644 --- a/src/common/common-util.js +++ b/src/common/common-util.js @@ -13,6 +13,18 @@ return Array.prototype.slice.call(A, start, end); }; + Util.u8ToBase64 = (u8, cb) => { + const reader = new FileReader(); + reader.onload = () => { + let res = reader.result; + let trim = res.slice(res.indexOf(',') + 1); + cb(trim); + }; + reader.readAsDataURL(new Blob([u8])); + }; + + + Util.shuffleArray = function (a) { for (var i = a.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); @@ -374,7 +386,7 @@ let href = url.href + (ignoreCache ? '?'+(+new Date()) : ''); if (typeof(self) !== "undefined" && self.crypto) { // Browser - fetch(url.href).then(res => { + fetch(href).then(res => { if (!res.ok) { throw new Error(`Fetch error: ${res.status}`); } diff --git a/src/common/user-object-setter.js b/src/common/user-object-setter.js index 720e9ae65..4c778ff66 100644 --- a/src/common/user-object-setter.js +++ b/src/common/user-object-setter.js @@ -3,14 +3,11 @@ // SPDX-License-Identifier: AGPL-3.0-or-later (() => { -const factory = (AppConfig = {}, Util, Hash, - Realtime, Messages = {}) => { +const factory = (Util, Hash, Realtime) => { let window = globalThis; var module = {}; - module.setCustomize = (data) => { - Messages = data.Messages; - AppConfig = data.AppConfig; + module.setCustomize = (/*data*/) => { }; var clone = function (o) { @@ -985,19 +982,15 @@ const factory = (AppConfig = {}, Util, Hash, if (typeof(module) !== 'undefined' && module.exports) { module.exports = factory( - undefined, require('./common-util'), require('./common-hash'), require('./common-realtime'), - undefined ); } else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { define([ - '/customize/application_config.js', '/common/common-util.js', '/common/common-hash.js', '/common/common-realtime.js', - '/customize/messages.js', ], factory); } else { // unsupported initialization diff --git a/src/common/user-object.js b/src/common/user-object.js index 24edac48e..c04987315 100644 --- a/src/common/user-object.js +++ b/src/common/user-object.js @@ -3,14 +3,13 @@ // SPDX-License-Identifier: AGPL-3.0-or-later (() => { -const factory = (AppConfig = {}, Util, Hash, +const factory = (Util, Hash, Constants, UOSetter, Crypto, Messages = {}) => { let window = globalThis; var module = {}; module.setCustomize = (data) => { Messages = data.Messages; - AppConfig = data.AppConfig; UOSetter.setCustomize(data); }; @@ -972,7 +971,6 @@ const factory = (AppConfig = {}, Util, Hash, if (typeof(module) !== 'undefined' && module.exports) { module.exports = factory( - undefined, require('./common-util'), require('./common-hash'), require('./common-constants'), @@ -982,7 +980,6 @@ if (typeof(module) !== 'undefined' && module.exports) { ); } else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { define([ - '/customize/application_config.js', '/common/common-util.js', '/common/common-hash.js', '/common/common-constants.js', diff --git a/src/worker/async-store.js b/src/worker/async-store.js index a0155dbce..0f8f74910 100644 --- a/src/worker/async-store.js +++ b/src/worker/async-store.js @@ -680,7 +680,7 @@ const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager, return metadata; }; - Store.onMaintenanceUpdate = function (uid) { + Store.onMaintenanceUpdate = function (/*uid*/) { // use uid in /api/broadcast so that all connected users will use the same cached // version on the server let origin = ApiConfig.httpUnsafeOrigin; @@ -695,7 +695,7 @@ const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager, }); }); }; - Store.onSurveyUpdate = function (uid) { + Store.onSurveyUpdate = function (/*uid*/) { // use uid in /api/broadcast so that all connected users will use the same cached // version on the server let origin = ApiConfig.httpUnsafeOrigin; diff --git a/src/worker/components/migrate-user-object.js b/src/worker/components/migrate-user-object.js index a38f62e24..638c062a6 100644 --- a/src/worker/components/migrate-user-object.js +++ b/src/worker/components/migrate-user-object.js @@ -3,12 +3,11 @@ // SPDX-License-Identifier: AGPL-3.0-or-later (() => { -const factory = (AppConfig = {}, Feedback, Hash, Util, +const factory = (Feedback, Hash, Util, Messaging, Crypt, Mailbox, Messages = {}, Realtime, nThen, Crypto) => { const setCustomize = data => { - AppConfig = data.AppConfig; Messages = data.Messages; }; @@ -510,7 +509,6 @@ const factory = (AppConfig = {}, Feedback, Hash, Util, if (typeof(module) !== 'undefined' && module.exports) { module.exports = factory( - undefined, require('../../common/common-feedback'), require('../../common/common-hash'), require('../../common/common-util'), @@ -524,7 +522,6 @@ if (typeof(module) !== 'undefined' && module.exports) { ); } else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { define([ - '/customize/application_config.js', '/common/common-feedback.js', '/common/common-hash.js', '/common/common-util.js', diff --git a/src/worker/core/async-connector.js b/src/worker/core/async-connector.js index fd4412257..4cc51136c 100644 --- a/src/worker/core/async-connector.js +++ b/src/worker/core/async-connector.js @@ -2,8 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -/* global importScripts */ - const Interface = require('./interface'); const Util = require('../../common/common-util'); let start = (setConfig) => { diff --git a/src/worker/core/sw-connector.js b/src/worker/core/sw-connector.js index cc2a1a74a..9f8a19af7 100644 --- a/src/worker/core/sw-connector.js +++ b/src/worker/core/sw-connector.js @@ -2,8 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -/* global importScripts */ - const Interface = require('./interface'); let start = (setConfig) => { let ready = false; @@ -50,7 +48,7 @@ let start = (setConfig) => { } }; }); -} +}; module.exports = { start }; diff --git a/src/worker/modules/calendar.js b/src/worker/modules/calendar.js index cab3e20ff..22d1fe5e2 100644 --- a/src/worker/modules/calendar.js +++ b/src/worker/modules/calendar.js @@ -4,11 +4,10 @@ (() => { const factory = (Util, Hash, Constants, Realtime, Cache, Rec, - Messages = {}, nThen, Listmap, FP, Crypto, ChainPad) => { + nThen, Listmap, FP, Crypto, ChainPad) => { var Calendar = {}; - Calendar.setCustomize = data => { - Messages = data.Messages; + Calendar.setCustomize = (/*data*/) => { }; var getStore = function (ctx, id) { @@ -1218,7 +1217,6 @@ if (typeof(module) !== 'undefined' && module.exports) { require('../../common/common-realtime'), require('../../common/cache-store'), require('../../common/recurrence'), - undefined, require('nthen'), require('chainpad-listmap'), require('../../../www/lib/datepicker/flatpickr'), @@ -1233,7 +1231,6 @@ if (typeof(module) !== 'undefined' && module.exports) { '/common/common-realtime.js', '/common/outer/cache-store.js', '/calendar/recurrence.js', - '/customize/messages.js', '/components/nthen/index.js', 'chainpad-listmap', '/lib/datepicker/flatpickr.js', diff --git a/www/common/common-util.js b/www/common/common-util.js index 88bc1a75e..ac03ff126 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -386,7 +386,7 @@ let href = url.href + (ignoreCache ? '?'+(+new Date()) : ''); if (typeof(self) !== "undefined" && self.crypto) { // Browser - fetch(url.href).then(res => { + fetch(href).then(res => { if (!res.ok) { throw new Error(`Fetch error: ${res.status}`); } diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 9da913d45..934c02e43 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -34,8 +34,6 @@ define([ Additionally, there is some basic functionality for import/export. */ - var urlArgs = Util.find(Config, ['requireConf', 'urlArgs']) || ''; - var postMessage = function (/*cmd, data, cb*/) { /*setTimeout(function () { AStore.query(cmd, data, cb); @@ -2701,7 +2699,7 @@ define([ }).then(waitFor(store => { postMsg = store?.postMsg; msgEv = store?.msgEv; - })) + })); }).nThen(function () { Channel.create(msgEv, postMsg, function (chan) { console.log('Outer ready'); diff --git a/www/common/store-interface.js b/www/common/store-interface.js index ce2fae195..d15187926 100644 --- a/www/common/store-interface.js +++ b/www/common/store-interface.js @@ -33,10 +33,11 @@ const factory = function () { let called = false; let msgEv = mkEvent(); - let todo = (resolve, reject) => { + let todo = (resolve/*, reject*/) => { if (called) { return; } called = true; + let worker, postMsg; if (!noWorker && !noSharedWorker && typeof(SharedWorker) !== "undefined") { worker = new SharedWorker('/common/worker.bundle.js?' + urlArgs); worker.onerror = function (e) { @@ -66,7 +67,6 @@ const factory = function () { return void resolve({postMsg, msgEv}); } - // eslint-disable-next-line no-constant-condition if (!noWorker && typeof(Worker) !== "undefined") { worker = new Worker('/common/worker.bundle.js?' + urlArgs); worker.onerror = function (e) { @@ -85,7 +85,7 @@ const factory = function () { // aren't available //if (typeof(require) === "undefined") { return; } require(['/common/worker.bundle.js'], function (Store) { - let store = Store?.store + let store = Store?.store; if (!store) { return void console.error("No store"); } store.onMessage(function (data) { if (data === "STORE_READY") { return; } @@ -107,7 +107,7 @@ const factory = function () { }; let todoNode = (resolve, reject) => { const Store = require('./worker.bundle'); - let store = Store?.store + let store = Store?.store; if (!store) { reject('NOSTORE'); return void console.error("No store"); @@ -116,7 +116,7 @@ const factory = function () { if (data === "STORE_READY") { return; } msgEv.fire({data: data, origin: ''}); }); - postMsg = function (d) { + let postMsg = function (d) { setTimeout(function () { store.query(d); }); @@ -144,7 +144,7 @@ const factory = function () { } if (typeof(Worker) !== "undefined") { try { - worker = new Worker('/common/testworker.js?' + urlArgs); + let worker = new Worker('/common/testworker.js?' + urlArgs); worker.onerror = function (errEv) { errEv.preventDefault(); errEv.stopPropagation(); diff --git a/www/common/translations/messages.pl.json b/www/common/translations/messages.pl.json index 910973218..d978c0dd5 100644 --- a/www/common/translations/messages.pl.json +++ b/www/common/translations/messages.pl.json @@ -444,7 +444,7 @@ "support_formContentError": "Błąd: treść jest pusta", "support_formTitleError": "Błąd: tytuł jest pusty", "support_formButton": "Wyślij", - "support_formHint": "Użyj tego formularza, aby bezpiecznie skontaktować się z administratorami.
Proszę zauważyć, że niektóre kwestie/pytania mogą być już poruszone w Podręczniku Użytkownika CryptPad. Prosimy nie tworzyć nowego zgłoszenia, jeśli masz już otwarte zgłoszenie dotyczące tego samego problemu. Zamiast tego odpowiedz na oryginalną wiadomość, podając dodatkowe informacje.", + "support_formHint": "Użyj tego formularza, aby bezpiecznie skontaktować się z administratorami.
Zwróć uwagę, że niektóre kwestie/pytania mogą być już poruszone w Podręczniku Użytkownika CryptPad. Prosimy nie tworzyć nowego zgłoszenia, jeśli masz już otwarte zgłoszenie dotyczące tego samego problemu. Zamiast tego odpowiedz na oryginalną wiadomość, podając dodatkowe informacje.", "support_formTitle": "Nowe Zgłoszenie", "support_cat_new": "Nowe zgłoszenie", "support_disabledHint": "Ta instancja CryptPad nie jest jeszcze skonfigurowana do korzystania z formularza pomocy technicznej.", @@ -1783,5 +1783,9 @@ "admin_onboardingOptionsHint": "Zaznacz właściwą opcję dla Twojej instancji.
To ustawienie może być później zmienione w panelu administratora.", "team_autoTrim": "Zmniejszanie historii dysku zespołu... Proszę czekać.", "admin_mfa_confirm_enable": "Czy na pewno chcesz włączyć uwierzytelnianie wieloetapowe?", - "admin_mfa_confirm_disable": "Czy na pewno chcesz wyłączyć uwierzytelnianie wieloetapowe?" + "admin_mfa_confirm_disable": "Czy na pewno chcesz wyłączyć uwierzytelnianie wieloetapowe?", + "form_passwordWarning": "Zwróć uwagę, że hasło do formularza można ustawić tylko teraz, podczas tworzenia go, i nie może być ono zmienione później.", + "fm_restoreMultipleDialog": "Czy na pewni chcesz przywrócić {0} plików i/lub folderów do ich poprzedniej lokalizacji?", + "calendar_show": "Pokaż kalendarze", + "calendar_hide": "Ukryj kalendarze" } diff --git a/www/common/translations/messages.zh.json b/www/common/translations/messages.zh.json index c25eb1c22..f611b25c8 100644 --- a/www/common/translations/messages.zh.json +++ b/www/common/translations/messages.zh.json @@ -1783,5 +1783,9 @@ "dph_tmp_pw": "此模板受新密码保护。从您的云盘打开它以输入新密码。", "duplicate": "重复", "admin_logoButton": "上传新的", - "install_notes": "" + "install_notes": "", + "fm_restoreMultipleDialog": "是否确定要将 {0} 个文件和/或文件夹还原到其以前的位置?", + "calendar_hide": "隐藏日历", + "form_passwordWarning": "请注意,表单密码只能在创建时设置,以后不能更改。", + "calendar_show": "显示日历" } diff --git a/www/common/user-object-setter.js b/www/common/user-object-setter.js index 720e9ae65..4c778ff66 100644 --- a/www/common/user-object-setter.js +++ b/www/common/user-object-setter.js @@ -3,14 +3,11 @@ // SPDX-License-Identifier: AGPL-3.0-or-later (() => { -const factory = (AppConfig = {}, Util, Hash, - Realtime, Messages = {}) => { +const factory = (Util, Hash, Realtime) => { let window = globalThis; var module = {}; - module.setCustomize = (data) => { - Messages = data.Messages; - AppConfig = data.AppConfig; + module.setCustomize = (/*data*/) => { }; var clone = function (o) { @@ -985,19 +982,15 @@ const factory = (AppConfig = {}, Util, Hash, if (typeof(module) !== 'undefined' && module.exports) { module.exports = factory( - undefined, require('./common-util'), require('./common-hash'), require('./common-realtime'), - undefined ); } else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { define([ - '/customize/application_config.js', '/common/common-util.js', '/common/common-hash.js', '/common/common-realtime.js', - '/customize/messages.js', ], factory); } else { // unsupported initialization diff --git a/www/common/user-object.js b/www/common/user-object.js index 24edac48e..c04987315 100644 --- a/www/common/user-object.js +++ b/www/common/user-object.js @@ -3,14 +3,13 @@ // SPDX-License-Identifier: AGPL-3.0-or-later (() => { -const factory = (AppConfig = {}, Util, Hash, +const factory = (Util, Hash, Constants, UOSetter, Crypto, Messages = {}) => { let window = globalThis; var module = {}; module.setCustomize = (data) => { Messages = data.Messages; - AppConfig = data.AppConfig; UOSetter.setCustomize(data); }; @@ -972,7 +971,6 @@ const factory = (AppConfig = {}, Util, Hash, if (typeof(module) !== 'undefined' && module.exports) { module.exports = factory( - undefined, require('./common-util'), require('./common-hash'), require('./common-constants'), @@ -982,7 +980,6 @@ if (typeof(module) !== 'undefined' && module.exports) { ); } else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { define([ - '/customize/application_config.js', '/common/common-util.js', '/common/common-hash.js', '/common/common-constants.js', diff --git a/www/notifications/app-notifications.less b/www/notifications/app-notifications.less index 8bf0eccbe..b3dcf439c 100644 --- a/www/notifications/app-notifications.less +++ b/www/notifications/app-notifications.less @@ -78,6 +78,7 @@ justify-content: flex-start; align-items: center; background-color: @cp_notif-bg; + border-top: 1px solid @cp_notif-table-border; &.no-notifications { display: none; padding: 1rem 1rem; @@ -95,12 +96,14 @@ background-color: @cp_notif-hover; } } + .cp-avatar-calendar { + font-size: 45px; + padding: 0 12px; + overflow: hidden; + } &.cp-app-notification-archived { background-color: @cp_notif-bg; } - &:not(:first-child) { - border-top: 1px solid @cp_notif-table-border; - } &.dismissed { display: none; } diff --git a/www/notifications/inner.js b/www/notifications/inner.js index 874a88b15..7abcbac79 100644 --- a/www/notifications/inner.js +++ b/www/notifications/inner.js @@ -87,6 +87,8 @@ define([ // if the type of notification correspond if (filterTypes.indexOf(data.content.msg.type) !== -1) { notifsData.push(data); + var icon = $(el).find(".cp-reminder"); + $(icon).addClass('cp-avatar-calendar'); $(notifsList).prepend(el); } }; @@ -143,7 +145,7 @@ define([ $(loadmore).click(); } - common.mailbox.subscribe(["notifications"], { + common.mailbox.subscribe(["notifications", "reminders"], { onMessage: function (data, el) { addNotification(data, el); },