From da514ce5fc0475812fdf82eb354ae6b9e0979549 Mon Sep 17 00:00:00 2001 From: Wolfgang Ginolas Date: Tue, 1 Oct 2024 14:58:24 +0200 Subject: [PATCH 01/83] Diagram async importer https://github.com/cryptpad/nextcloud-open-in-cryptpad/issues/35 --- www/diagram/inner.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/www/diagram/inner.js b/www/diagram/inner.js index b6176c1cf..9de5184a8 100644 --- a/www/diagram/inner.js +++ b/www/diagram/inner.js @@ -179,9 +179,10 @@ define([ framework.setFileImporter( {accept: ['.drawio', 'application/x-drawio']}, - (content) => { - return xmlAsJsonContent(content); - } + (content, file, cb) => { + cb(xmlAsJsonContent(content)); + }, + true ); framework.setFileExporter( From ca75aebab1df60bd068c876a6f702a9d0b80628f Mon Sep 17 00:00:00 2001 From: Wolfgang Ginolas Date: Tue, 1 Oct 2024 15:20:03 +0200 Subject: [PATCH 02/83] Add diagram/import.js https://github.com/cryptpad/nextcloud-open-in-cryptpad/issues/35 --- www/diagram/export.js | 14 ++------- www/diagram/import.js | 19 ++++++++++++ www/diagram/inner.js | 63 +++----------------------------------- www/diagram/util.js | 70 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 71 deletions(-) create mode 100644 www/diagram/import.js diff --git a/www/diagram/export.js b/www/diagram/export.js index 09e5da30b..05f508674 100644 --- a/www/diagram/export.js +++ b/www/diagram/export.js @@ -44,7 +44,7 @@ define([ }; const loadCryptPadImages = (doc) => { - return Array.from(doc .querySelectorAll('mxCell')) + return Array.from(doc.querySelectorAll('mxCell')) .map((element) => [element, parseDrawioStyle(element.getAttribute('style'))]) .filter(([, style]) => style && style.image && style.image.startsWith('cryptpad://')) .map(([element, style]) => { @@ -56,16 +56,6 @@ define([ }); }; - const parseXML = (xmlStr) => { - const parser = new DOMParser(); - const doc = parser.parseFromString(xmlStr, "application/xml"); - const errorNode = doc.querySelector("parsererror"); - if (errorNode) { - throw Error("error while parsing " + errorNode); - } - return doc; - }; - return { main: function(userDoc, cb) { delete userDoc.metadata; @@ -74,7 +64,7 @@ define([ let doc; try { - doc = parseXML(xml); + doc = DiagramUtil.parseXML(xml); } catch(e) { console.error(e); return; diff --git a/www/diagram/import.js b/www/diagram/import.js new file mode 100644 index 000000000..52fb5d58b --- /dev/null +++ b/www/diagram/import.js @@ -0,0 +1,19 @@ + +// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +define([ + '/diagram/util.js', +], function ( + DiagramUtil +) { + + const importDiagram = async (content, file) => { + return DiagramUtil.xmlAsJsonContent(content); + }; + + return { + importDiagram + }; +}); diff --git a/www/diagram/inner.js b/www/diagram/inner.js index 9de5184a8..0bf2675a2 100644 --- a/www/diagram/inner.js +++ b/www/diagram/inner.js @@ -7,7 +7,6 @@ define([ 'jquery', '/common/sframe-app-framework.js', '/customize/messages.js', // translation keys - '/components/pako/dist/pako.min.js', '/components/x2js/x2js.js', '/diagram/util.js', '/common/common-ui-elements.js', @@ -18,48 +17,12 @@ define([ $, Framework, Messages, - pako, X2JS, DiagramUtil, UIElements ) { - const Nacl = window.nacl; const APP = window.APP = {}; - // As described here: https://drawio-app.com/extracting-the-xml-from-mxfiles/ - const decompressDrawioXml = function(xmlDocStr) { - var TEXT_NODE = 3; - - var parser = new DOMParser(); - var doc = parser.parseFromString(xmlDocStr, "application/xml"); - - var errorNode = doc.querySelector("parsererror"); - if (errorNode) { - console.error("error while parsing", errorNode); - return xmlDocStr; - } - - doc.firstChild.removeAttribute('modified'); - doc.firstChild.removeAttribute('agent'); - doc.firstChild.removeAttribute('etag'); - - var diagrams = doc.querySelectorAll('diagram'); - - diagrams.forEach(function(diagram) { - if (diagram.childNodes.length === 1 && diagram.firstChild && diagram.firstChild.nodeType === TEXT_NODE) { - const innerText = diagram.firstChild.nodeValue; - const bin = Nacl.util.decodeBase64(innerText); - const xmlUrlStr = pako.inflateRaw(bin, {to: 'string'}); - const xmlStr = decodeURIComponent(xmlUrlStr); - const diagramDoc = parser.parseFromString(xmlStr, "application/xml"); - diagram.replaceChild(diagramDoc.firstChild, diagram.firstChild); - } - }); - - - var result = new XMLSerializer().serializeToString(doc); - return result; - }; const deepEqual = function(o1, o2) { return JSON.stringify(o1) === JSON.stringify(o2); @@ -105,28 +68,8 @@ define([ }); }; - const numbersToNumbers = function(o) { - const type = typeof o; - - if (type === "object") { - for (const key in o) { - o[key] = numbersToNumbers(o[key]); - } - return o; - } else if (type === 'string' && o.match(/^[+-]?(0|(([1-9]\d*)(\.\d+)?))$/)) { - return parseFloat(o, 10); - } else { - return o; - } - }; - - const xmlAsJsonContent = (xml) => { - var decompressedXml = decompressDrawioXml(xml); - return numbersToNumbers(x2js.xml2js(decompressedXml)); - }; - var onDrawioChange = function(newXml) { - var newJson = xmlAsJsonContent(newXml); + var newJson = DiagramUtil.xmlAsJsonContent(newXml); if (!deepEqual(lastContent, newJson)) { lastContent = newJson; framework.localChange(); @@ -180,7 +123,9 @@ define([ framework.setFileImporter( {accept: ['.drawio', 'application/x-drawio']}, (content, file, cb) => { - cb(xmlAsJsonContent(content)); + require(['/diagram/import.js'], (importer) => { + importer.importDiagram(content, file).then(cb); + }); }, true ); diff --git a/www/diagram/util.js b/www/diagram/util.js index 72acda93a..7c10140c9 100644 --- a/www/diagram/util.js +++ b/www/diagram/util.js @@ -7,11 +7,13 @@ define([ '/file/file-crypto.js', '/common/outer/cache-store.js', '/components/x2js/x2js.js', + '/components/pako/dist/pako.min.js', ], function ( Util, FileCrypto, Cache, X2JS, + pako, ) { const Nacl = window.nacl; const x2js = new X2JS(); @@ -49,10 +51,78 @@ define([ return x2js.js2xml(cleaned); }; + const parseXML = (xmlStr) => { + const parser = new DOMParser(); + const doc = parser.parseFromString(xmlStr, "application/xml"); + const errorNode = doc.querySelector("parsererror"); + if (errorNode) { + throw Error("error while parsing " + errorNode); + } + return doc; + }; + + const numbersToNumbers = function(o) { + const type = typeof o; + + if (type === "object") { + for (const key in o) { + o[key] = numbersToNumbers(o[key]); + } + return o; + } else if (type === 'string' && o.match(/^[+-]?(0|(([1-9]\d*)(\.\d+)?))$/)) { + return parseFloat(o, 10); + } else { + return o; + } + }; + + const xmlAsJsonContent = (xml) => { + var decompressedXml = decompressDrawioXml(xml); + return numbersToNumbers(x2js.xml2js(decompressedXml)); + }; + + // As described here: https://drawio-app.com/extracting-the-xml-from-mxfiles/ + const decompressDrawioXml = function(xmlDocStr) { + var TEXT_NODE = 3; + + var parser = new DOMParser(); + var doc = parser.parseFromString(xmlDocStr, "application/xml"); + + var errorNode = doc.querySelector("parsererror"); + if (errorNode) { + console.error("error while parsing", errorNode); + return xmlDocStr; + } + + doc.firstChild.removeAttribute('modified'); + doc.firstChild.removeAttribute('agent'); + doc.firstChild.removeAttribute('etag'); + + var diagrams = doc.querySelectorAll('diagram'); + + diagrams.forEach(function(diagram) { + if (diagram.childNodes.length === 1 && diagram.firstChild && diagram.firstChild.nodeType === TEXT_NODE) { + const innerText = diagram.firstChild.nodeValue; + const bin = Nacl.util.decodeBase64(innerText); + const xmlUrlStr = pako.inflateRaw(bin, {to: 'string'}); + const xmlStr = decodeURIComponent(xmlUrlStr); + const diagramDoc = parser.parseFromString(xmlStr, "application/xml"); + diagram.replaceChild(diagramDoc.firstChild, diagram.firstChild); + } + }); + + + var result = new XMLSerializer().serializeToString(doc); + return result; + }; + return { parseCryptPadUrl, getCryptPadUrl, jsonContentAsXML, + parseXML, + xmlAsJsonContent, + decompressDrawioXml, loadImage: function(href) { return new Promise((resolve, reject) => { From ad99783639d5603e0529af02c9acf1ada756b807 Mon Sep 17 00:00:00 2001 From: Wolfgang Ginolas Date: Tue, 1 Oct 2024 15:42:17 +0200 Subject: [PATCH 03/83] Find data: images in diagram import https://github.com/cryptpad/nextcloud-open-in-cryptpad/issues/35 --- www/diagram/export.js | 18 ++---------------- www/diagram/import.js | 24 +++++++++++++++++++++++- www/diagram/util.js | 15 +++++++++++++++ 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/www/diagram/export.js b/www/diagram/export.js index 05f508674..9a9320670 100644 --- a/www/diagram/export.js +++ b/www/diagram/export.js @@ -7,20 +7,6 @@ define([ ], function ( DiagramUtil ) { - const parseDrawioStyle = (styleAttrValue) => { - if (!styleAttrValue) { - return; - } - - const result = {}; - for (const part of styleAttrValue.split(';')) { - const s = part.split(/=(.*)/); - result[s[0]] = s[1]; - } - - return result; - }; - const stringifyDrawioStyle = (styleAttrValue) => { const parts = []; for (const [key, value] of Object.entries(styleAttrValue)) { @@ -45,8 +31,8 @@ define([ const loadCryptPadImages = (doc) => { return Array.from(doc.querySelectorAll('mxCell')) - .map((element) => [element, parseDrawioStyle(element.getAttribute('style'))]) - .filter(([, style]) => style && style.image && style.image.startsWith('cryptpad://')) + .map((element) => [element, DiagramUtil.parseDrawioStyle(element.getAttribute('style'))]) + .filter(([, style]) => style.image && style.image.startsWith('cryptpad://')) .map(([element, style]) => { return loadImage(style.image) .then((dataUrl) => { diff --git a/www/diagram/import.js b/www/diagram/import.js index 52fb5d58b..e55afa520 100644 --- a/www/diagram/import.js +++ b/www/diagram/import.js @@ -9,8 +9,30 @@ define([ DiagramUtil ) { + const saveImagesToCryptPad = (doc) => { + return Array.from(doc.querySelectorAll('mxCell')) + .map((element) => [element, DiagramUtil.parseDrawioStyle(element.getAttribute('style'))]) + .filter(([, style]) => style.image && style.image.startsWith('data:')) + .map(x => console.log('XXX', x)); + // .map(([element, style]) => { + // return loadImage(style.image) + // .then((dataUrl) => { + // style.image = dataUrl.replace(';base64', ''); // ';' breaks draw.ios style format + // element.setAttribute('style', stringifyDrawioStyle(style)); + // }); + // }); + }; const importDiagram = async (content, file) => { - return DiagramUtil.xmlAsJsonContent(content); + let doc; + try { + doc = DiagramUtil.parseXML(content); + } catch(e) { + console.error(e); + return; + } + + saveImagesToCryptPad(doc); + return DiagramUtil.xmlAsJsonContent(new XMLSerializer().serializeToString(doc)); }; return { diff --git a/www/diagram/util.js b/www/diagram/util.js index 7c10140c9..604bf593f 100644 --- a/www/diagram/util.js +++ b/www/diagram/util.js @@ -116,6 +116,20 @@ define([ return result; }; + const parseDrawioStyle = (styleAttrValue) => { + if (!styleAttrValue) { + return {}; + } + + const result = {}; + for (const part of styleAttrValue.split(';')) { + const s = part.split(/=(.*)/); + result[s[0]] = s[1]; + } + + return result; + }; + return { parseCryptPadUrl, getCryptPadUrl, @@ -123,6 +137,7 @@ define([ parseXML, xmlAsJsonContent, decompressDrawioXml, + parseDrawioStyle, loadImage: function(href) { return new Promise((resolve, reject) => { From eff69d1a2b641c9dab2dee4ea71b864b8ca74a83 Mon Sep 17 00:00:00 2001 From: Wolfgang Ginolas Date: Wed, 2 Oct 2024 09:34:39 +0200 Subject: [PATCH 04/83] Start uploading images on diagram import https://github.com/cryptpad/nextcloud-open-in-cryptpad/issues/35 --- www/diagram/import.js | 46 ++++++++++++++++++++++++++++++------------- www/diagram/inner.js | 2 +- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/www/diagram/import.js b/www/diagram/import.js index e55afa520..b9a2eaa1d 100644 --- a/www/diagram/import.js +++ b/www/diagram/import.js @@ -4,25 +4,31 @@ // SPDX-License-Identifier: AGPL-3.0-or-later define([ + 'jquery', '/diagram/util.js', ], function ( + $, DiagramUtil ) { - const saveImagesToCryptPad = (doc) => { - return Array.from(doc.querySelectorAll('mxCell')) - .map((element) => [element, DiagramUtil.parseDrawioStyle(element.getAttribute('style'))]) - .filter(([, style]) => style.image && style.image.startsWith('data:')) - .map(x => console.log('XXX', x)); - // .map(([element, style]) => { - // return loadImage(style.image) - // .then((dataUrl) => { - // style.image = dataUrl.replace(';base64', ''); // ';' breaks draw.ios style format - // element.setAttribute('style', stringifyDrawioStyle(style)); - // }); - // }); + const saveImagesToCryptPad = async (fileManager, doc) => { + const images = Array.from(doc.querySelectorAll('mxCell')) + .map((element) => ({ + element, + style: DiagramUtil.parseDrawioStyle(element.getAttribute('style')), + })) + .filter(({ style }) => style.image && style.image.startsWith('data:')) + + console.log('XXX', images); + + for(const image of images) { + const blob = await (await fetch(image.style.image)).blob(); + + fileManager.handleFile(blob); + } }; - const importDiagram = async (content, file) => { + + const importDiagram = async (common, content, file) => { let doc; try { doc = DiagramUtil.parseXML(content); @@ -31,7 +37,19 @@ define([ return; } - saveImagesToCryptPad(doc); + var fmConfigImages = { + noHandlers: true, + noStore: true, + body: $('body'), + onUploaded: function (ev, data) { + console.log('XXX onUploaded', { ev, data }); + if (!ev.callback) { return; } + ev.callback(); + } + }; + const fileManager = common.createFileManager(fmConfigImages); + + await saveImagesToCryptPad(fileManager, doc); return DiagramUtil.xmlAsJsonContent(new XMLSerializer().serializeToString(doc)); }; diff --git a/www/diagram/inner.js b/www/diagram/inner.js index 0bf2675a2..ce117af42 100644 --- a/www/diagram/inner.js +++ b/www/diagram/inner.js @@ -124,7 +124,7 @@ define([ {accept: ['.drawio', 'application/x-drawio']}, (content, file, cb) => { require(['/diagram/import.js'], (importer) => { - importer.importDiagram(content, file).then(cb); + importer.importDiagram(framework._.sfCommon, content, file).then(cb); }); }, true From e30aad2fa559dc92fbda26428eb2736383dde317 Mon Sep 17 00:00:00 2001 From: Wolfgang Ginolas Date: Wed, 2 Oct 2024 15:39:03 +0200 Subject: [PATCH 05/83] Upload images in diagram import https://github.com/cryptpad/nextcloud-open-in-cryptpad/issues/35 --- www/diagram/import.js | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/www/diagram/import.js b/www/diagram/import.js index b9a2eaa1d..a8aa99beb 100644 --- a/www/diagram/import.js +++ b/www/diagram/import.js @@ -10,6 +10,35 @@ define([ $, DiagramUtil ) { + const Nacl = window.nacl; + + const splitAt = function(str, char) { + const pos = str.indexOf(char); + if (pos <= 0) { + return [str, '']; + } + return [str.substring(0, pos), str.substring(pos + 1)]; + } + + const parseDataUrl = function (url) { + const [prefix, data] = splitAt(url, ','); + const [, metadata] = splitAt(prefix, ':'); + const [mimeType, ] = splitAt(metadata, ';'); + + const u8 = Nacl.util.decodeBase64(data); + return new Blob([u8], { type: mimeType }); + }; + + const uploadFile = async (fileManager, blob) => { + return new Promise((resolve) => { + fileManager.handleFile(blob, { + callback: (data) => { + console.log('XXX data', data); + resolve(); + } + }); + }); + }; const saveImagesToCryptPad = async (fileManager, doc) => { const images = Array.from(doc.querySelectorAll('mxCell')) @@ -19,12 +48,10 @@ define([ })) .filter(({ style }) => style.image && style.image.startsWith('data:')) - console.log('XXX', images); - for(const image of images) { - const blob = await (await fetch(image.style.image)).blob(); + const blob = parseDataUrl(image.style.image); - fileManager.handleFile(blob); + await uploadFile(fileManager, blob); } }; @@ -44,7 +71,7 @@ define([ onUploaded: function (ev, data) { console.log('XXX onUploaded', { ev, data }); if (!ev.callback) { return; } - ev.callback(); + ev.callback(data); } }; const fileManager = common.createFileManager(fmConfigImages); From d575a5d2d84afd2157075c91f802d178febd5fd4 Mon Sep 17 00:00:00 2001 From: Wolfgang Ginolas Date: Wed, 16 Oct 2024 09:57:36 +0200 Subject: [PATCH 06/83] Replace image URLs with cryptpad:// hrefs https://github.com/cryptpad/nextcloud-open-in-cryptpad/issues/35 --- www/diagram/export.js | 10 +--------- www/diagram/import.js | 26 ++++++++++++++++++++++---- www/diagram/util.js | 9 +++++++++ 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/www/diagram/export.js b/www/diagram/export.js index 9a9320670..b672308d9 100644 --- a/www/diagram/export.js +++ b/www/diagram/export.js @@ -7,14 +7,6 @@ define([ ], function ( DiagramUtil ) { - const stringifyDrawioStyle = (styleAttrValue) => { - const parts = []; - for (const [key, value] of Object.entries(styleAttrValue)) { - parts.push(`${key}=${value}`); - } - return parts.join(';'); - }; - const blobToImage = (blob) => { return new Promise((resolve) => { const reader = new FileReader(); @@ -37,7 +29,7 @@ define([ return loadImage(style.image) .then((dataUrl) => { style.image = dataUrl.replace(';base64', ''); // ';' breaks draw.ios style format - element.setAttribute('style', stringifyDrawioStyle(style)); + element.setAttribute('style', DiagramUtil.stringifyDrawioStyle(style)); }); }); }; diff --git a/www/diagram/import.js b/www/diagram/import.js index a8aa99beb..a103b69f5 100644 --- a/www/diagram/import.js +++ b/www/diagram/import.js @@ -6,9 +6,13 @@ define([ 'jquery', '/diagram/util.js', + '/common/common-hash.js', + '/api/config', ], function ( $, - DiagramUtil + DiagramUtil, + Hash, + ApiConfig, ) { const Nacl = window.nacl; @@ -29,12 +33,24 @@ define([ return new Blob([u8], { type: mimeType }); }; + const getCryptPadUrlForUploadData = (data) => { + const [, urlHash] = splitAt(data.url, '#'); + const secret = Hash.getSecrets('file', urlHash); + + const fileHost = ApiConfig.fileHost || window.location.origin; + const hexFileName = secret.channel; + const src = fileHost + Hash.getBlobPathFromHex(hexFileName); + const key = secret.keys && secret.keys.cryptKey; + const cryptKey = Nacl.util.encodeBase64(key); + return DiagramUtil.getCryptPadUrl(src, cryptKey, data.fileType); + }; + const uploadFile = async (fileManager, blob) => { return new Promise((resolve) => { fileManager.handleFile(blob, { callback: (data) => { - console.log('XXX data', data); - resolve(); + const cryptPadUrl = getCryptPadUrlForUploadData(data); + resolve(cryptPadUrl); } }); }); @@ -51,7 +67,9 @@ define([ for(const image of images) { const blob = parseDataUrl(image.style.image); - await uploadFile(fileManager, blob); + const cryptPadUrl = await uploadFile(fileManager, blob); + image.style.image = cryptPadUrl; + image.element.setAttribute('style', DiagramUtil.stringifyDrawioStyle(image.style)); } }; diff --git a/www/diagram/util.js b/www/diagram/util.js index 604bf593f..1c611f800 100644 --- a/www/diagram/util.js +++ b/www/diagram/util.js @@ -130,6 +130,14 @@ define([ return result; }; + const stringifyDrawioStyle = (styleAttrValue) => { + const parts = []; + for (const [key, value] of Object.entries(styleAttrValue)) { + parts.push(`${key}=${value}`); + } + return parts.join(';'); + }; + return { parseCryptPadUrl, getCryptPadUrl, @@ -138,6 +146,7 @@ define([ xmlAsJsonContent, decompressDrawioXml, parseDrawioStyle, + stringifyDrawioStyle, loadImage: function(href) { return new Promise((resolve, reject) => { From eeb3eddd7149abc2d6a5dcdf118e5ffb156b7f04 Mon Sep 17 00:00:00 2001 From: Wolfgang Ginolas Date: Wed, 16 Oct 2024 11:28:54 +0200 Subject: [PATCH 07/83] WIP upload images as anon user --- www/common/cryptpad-common.js | 1 + www/common/outer/async-store.js | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index e2d6a3746..bcbd7e146 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -853,6 +853,7 @@ define([ // STORAGE - TEMPLATES common.listTemplates = function (type, cb) { + console.trace('XXX listTemplates'); postMessage("GET_TEMPLATES", null, function (obj) { if (obj && obj.error) { return void cb(obj.error); } if (!Array.isArray(obj)) { return void cb ('NOT_AN_ARRAY'); } diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 569db9bff..c486ecb05 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -3165,7 +3165,19 @@ define([ store.messenger = store.modules['messenger']; // And now we're ready - initAnonRpc(null, null, function () { + console.log('XXX onNoDrive'); + nThen(function (waitFor) { + if (!store.rpc) { + let keyPair = nacl.sign.keyPair() + store.proxy = store.proxy || {}; + store.proxy.edPublic = nacl.util.encodeBase64(keyPair.publicKey); + store.proxy.edPrivate = nacl.util.encodeBase64(keyPair.secretKey); + initRpc(null, null, waitFor()); + } + if (!store.anon_rpc) { + initAnonRpc(null, null, waitFor()); + } + }).nThen(function () { cb({}); }); }; From ae6b47b199499c991cbd2ee06e81c7da4e36fd63 Mon Sep 17 00:00:00 2001 From: Wolfgang Ginolas Date: Thu, 17 Oct 2024 09:33:12 +0200 Subject: [PATCH 08/83] Try to fix anon upload --- www/common/outer/async-store.js | 6 ++++++ www/common/sframe-common-outer.js | 9 ++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index c486ecb05..98cae5ae8 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1000,7 +1000,12 @@ define([ Store.getPadAttribute = function (clientId, data, cb) { var res = {}; nThen(function (waitFor) { + console.log('XXX getAllStores', getAllStores()); getAllStores().forEach(function (s) { + if (!s.manager) { + waitFor()(); + return; + } s.manager.getPadAttribute(data, waitFor(function (err, val) { if (err) { return; } if (!val || typeof(val) !== "object") { return void console.error("Not an object!"); } @@ -1011,6 +1016,7 @@ define([ })); }); }).nThen(function () { + console.log('XXX getPadAttribute cb()'); cb(res.value); }); }; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index b4031d155..ba082d172 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -771,10 +771,10 @@ define([ settings = metaObj.priv.settings; })); if (typeof(isTemplate) === "undefined") { - Cryptpad.isTemplate(currentPad.href, waitFor(function (err, t) { - if (err) { console.log(err); } - isTemplate = t; - })); + // Cryptpad.isTemplate(currentPad.href, waitFor(function (err, t) { + // if (err) { console.log(err); } + // isTemplate = t; + // })); } }).nThen(function (/*waitFor*/) { metaObj.doc = { @@ -2445,4 +2445,3 @@ define([ return common; }); - From 353a217cfb5d252b3c3b348b63fbe4229ed18cc2 Mon Sep 17 00:00:00 2001 From: Wolfgang Ginolas Date: Thu, 17 Oct 2024 10:22:48 +0200 Subject: [PATCH 09/83] WIP how to wait for RPC to be ready? --- www/common/common-util.js | 13 +++++++++++-- www/common/outer/async-store.js | 13 ++++++++++--- www/diagram/import.js | 4 ++++ 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/www/common/common-util.js b/www/common/common-util.js index 5faa9171e..0cd280685 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -74,6 +74,12 @@ Util.mkEvent = function (once) { var handlers = []; var fired = false; + let promiseResolve; + + const promise = new Promise(resolve => { + promiseResolve = resolve; + }); + return { reg: function (cb) { if (once && fired) { return void setTimeout(cb); } @@ -87,10 +93,13 @@ }, fire: function () { if (once && fired) { return; } - fired = true; var args = Array.prototype.slice.call(arguments); + if (!fired) { promiseResolve.apply(null, args); } + fired = true; handlers.forEach(function (h) { h.apply(null, args); }); - } + }, + // Since a promise can only resolve once only the 1st call to fire() is reflected here. Even is `once` is `false`. + promise }; }; diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 569db9bff..3878736fd 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -3165,9 +3165,16 @@ define([ store.messenger = store.modules['messenger']; // And now we're ready - initAnonRpc(null, null, function () { - cb({}); - }); + nThen(function (waitFor) { + if (!store.rpc) { + initRpc(null, null, waitFor()); + } + if (!store.anon_rpc) { + initAnonRpc(null, null, waitFor()); + } + }).nThen(function () { + cb({}); + }); }; // We need an anonymous RPC to be able to check if the pad exists and to get diff --git a/www/diagram/import.js b/www/diagram/import.js index a103b69f5..3d8a50c7d 100644 --- a/www/diagram/import.js +++ b/www/diagram/import.js @@ -64,6 +64,9 @@ define([ })) .filter(({ style }) => style.image && style.image.startsWith('data:')) + console.log('XXX saveImagesToCryptPad 1'); + await window.CryptPad_AsyncStore.onRpcReadyEvt.promise; + console.log('XXX saveImagesToCryptPad 2'); for(const image of images) { const blob = parseDataUrl(image.style.image); @@ -74,6 +77,7 @@ define([ }; const importDiagram = async (common, content, file) => { + console.log('XXX importDiagram 1'); let doc; try { doc = DiagramUtil.parseXML(content); From 6538b326c7f3103ef2cc30b46781dd19e3bfd5d7 Mon Sep 17 00:00:00 2001 From: Wolfgang Ginolas Date: Thu, 17 Oct 2024 16:09:26 +0200 Subject: [PATCH 10/83] Handle new Nextcloud images --- www/common/sframe-common-outer.js | 2 +- www/cryptpad-api.js | 5 ++-- www/diagram/import.js | 43 +++---------------------------- www/diagram/inner.js | 5 +++- www/diagram/util.js | 42 ++++++++++++++++++++++++++++++ 5 files changed, 52 insertions(+), 45 deletions(-) diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index b4031d155..1e60325d8 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -2094,6 +2094,7 @@ define([ }); sframeChan.on('Q_INTEGRATION_ON_INSERT_IMAGE', function (data, cb) { if (cfg.integrationUtils && cfg.integrationUtils.onInsertImage) { + console.log('XXX Q_INTEGRATION_ON_INSERT_IMAGE', {cfg, data, cb}); cfg.integrationUtils.onInsertImage(data, cb); } }); @@ -2445,4 +2446,3 @@ define([ return common; }); - diff --git a/www/cryptpad-api.js b/www/cryptpad-api.js index e73ef40bd..6deec1e85 100644 --- a/www/cryptpad-api.js +++ b/www/cryptpad-api.js @@ -214,7 +214,8 @@ cb(); }); chan.on('ON_INSERT_IMAGE', function(data, cb) { - if (config.events.onIntertImage) { + console.log('XXX ON_INSERT_IMAGE', { config, data, cb }); + if (config.events.onInsertImage) { config.events.onInsertImage(data, cb); } else { cb(); } }); @@ -362,5 +363,3 @@ window.CryptPadAPI = factory(); } }()); - - diff --git a/www/diagram/import.js b/www/diagram/import.js index 3d8a50c7d..290e39bc4 100644 --- a/www/diagram/import.js +++ b/www/diagram/import.js @@ -6,13 +6,9 @@ define([ 'jquery', '/diagram/util.js', - '/common/common-hash.js', - '/api/config', ], function ( $, DiagramUtil, - Hash, - ApiConfig, ) { const Nacl = window.nacl; @@ -33,29 +29,6 @@ define([ return new Blob([u8], { type: mimeType }); }; - const getCryptPadUrlForUploadData = (data) => { - const [, urlHash] = splitAt(data.url, '#'); - const secret = Hash.getSecrets('file', urlHash); - - const fileHost = ApiConfig.fileHost || window.location.origin; - const hexFileName = secret.channel; - const src = fileHost + Hash.getBlobPathFromHex(hexFileName); - const key = secret.keys && secret.keys.cryptKey; - const cryptKey = Nacl.util.encodeBase64(key); - return DiagramUtil.getCryptPadUrl(src, cryptKey, data.fileType); - }; - - const uploadFile = async (fileManager, blob) => { - return new Promise((resolve) => { - fileManager.handleFile(blob, { - callback: (data) => { - const cryptPadUrl = getCryptPadUrlForUploadData(data); - resolve(cryptPadUrl); - } - }); - }); - }; - const saveImagesToCryptPad = async (fileManager, doc) => { const images = Array.from(doc.querySelectorAll('mxCell')) .map((element) => ({ @@ -70,13 +43,13 @@ define([ for(const image of images) { const blob = parseDataUrl(image.style.image); - const cryptPadUrl = await uploadFile(fileManager, blob); + const cryptPadUrl = await DiagramUtil.uploadFile(fileManager, blob); image.style.image = cryptPadUrl; image.element.setAttribute('style', DiagramUtil.stringifyDrawioStyle(image.style)); } }; - const importDiagram = async (common, content, file) => { + const importDiagram = async (common, content) => { console.log('XXX importDiagram 1'); let doc; try { @@ -86,17 +59,7 @@ define([ return; } - var fmConfigImages = { - noHandlers: true, - noStore: true, - body: $('body'), - onUploaded: function (ev, data) { - console.log('XXX onUploaded', { ev, data }); - if (!ev.callback) { return; } - ev.callback(data); - } - }; - const fileManager = common.createFileManager(fmConfigImages); + const fileManager = DiagramUtil.createSimpleFileManager(common); await saveImagesToCryptPad(fileManager, doc); return DiagramUtil.xmlAsJsonContent(new XMLSerializer().serializeToString(doc)); diff --git a/www/diagram/inner.js b/www/diagram/inner.js index ce117af42..769f58b0e 100644 --- a/www/diagram/inner.js +++ b/www/diagram/inner.js @@ -93,7 +93,10 @@ define([ return new Promise((resolve) => { framework.insertImage({}, (imageData) => { if (imageData.blob) { - resolve(imageData.blob); + const fileManager = DiagramUtil.createSimpleFileManager(framework._.sfCommon); + DiagramUtil.uploadFile(fileManager, imageData.blob) + .then(url => resolve(url)) + .catch(e => console.error(e)); } else if (imageData.url) { resolve(imageData.url); } else { diff --git a/www/diagram/util.js b/www/diagram/util.js index 1c611f800..9916cb296 100644 --- a/www/diagram/util.js +++ b/www/diagram/util.js @@ -8,12 +8,16 @@ define([ '/common/outer/cache-store.js', '/components/x2js/x2js.js', '/components/pako/dist/pako.min.js', + '/common/common-hash.js', + '/api/config', ], function ( Util, FileCrypto, Cache, X2JS, pako, + Hash, + ApiConfig, ) { const Nacl = window.nacl; const x2js = new X2JS(); @@ -138,6 +142,42 @@ define([ return parts.join(';'); }; + const getCryptPadUrlForUploadData = (data) => { + const [, urlHash] = data.url.split('#')[1]; + const secret = Hash.getSecrets('file', urlHash); + + const fileHost = ApiConfig.fileHost || window.location.origin; + const hexFileName = secret.channel; + const src = fileHost + Hash.getBlobPathFromHex(hexFileName); + const key = secret.keys && secret.keys.cryptKey; + const cryptKey = Nacl.util.encodeBase64(key); + return getCryptPadUrl(src, cryptKey, data.fileType); + }; + + const uploadFile = async (fileManager, blob) => { + return new Promise((resolve) => { + fileManager.handleFile(blob, { + callback: (data) => { + const cryptPadUrl = getCryptPadUrlForUploadData(data); + resolve(cryptPadUrl); + } + }); + }); + }; + + const createSimpleFileManager = (common) => { + const fmConfigImages = { + noHandlers: true, + noStore: true, + body: $('body'), + onUploaded: function (ev, data) { + if (!ev.callback) { return; } + ev.callback(data); + } + }; + return common.createFileManager(fmConfigImages); + }; + return { parseCryptPadUrl, getCryptPadUrl, @@ -147,6 +187,8 @@ define([ decompressDrawioXml, parseDrawioStyle, stringifyDrawioStyle, + uploadFile, + createSimpleFileManager, loadImage: function(href) { return new Promise((resolve, reject) => { From 8500c951d566b6040c167c3009002d7f8149d685 Mon Sep 17 00:00:00 2001 From: Wolfgang Ginolas Date: Fri, 18 Oct 2024 08:18:02 +0200 Subject: [PATCH 11/83] Fix anon image upload on diagram import --- www/common/outer/async-store.js | 19 +++++++++---------- www/common/sframe-common-outer.js | 8 ++++---- www/diagram/import.js | 2 +- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 98cae5ae8..6888fe2f8 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -468,12 +468,15 @@ define([ }; var initRpc = function (clientId, data, cb) { - if (!store.loggedIn) { return cb(); } + if (!store.loggedIn && !(data && data.keys)) { return cb(); } if (store.rpc) { return void cb(account); } - Pinpad.create(store.network, store.proxy, function (e, call) { + Pinpad.create(store.network, data && data.keys || store.proxy, function (e, call) { if (e) { return void cb({error: e}); } store.rpc = call; + + if (data && data.keys) { return void cb(); } + store.onRpcReadyEvt.fire(); Store.getPinLimit(null, null, function (obj) { @@ -1002,10 +1005,6 @@ define([ nThen(function (waitFor) { console.log('XXX getAllStores', getAllStores()); getAllStores().forEach(function (s) { - if (!s.manager) { - waitFor()(); - return; - } s.manager.getPadAttribute(data, waitFor(function (err, val) { if (err) { return; } if (!val || typeof(val) !== "object") { return void console.error("Not an object!"); } @@ -3175,10 +3174,10 @@ define([ nThen(function (waitFor) { if (!store.rpc) { let keyPair = nacl.sign.keyPair() - store.proxy = store.proxy || {}; - store.proxy.edPublic = nacl.util.encodeBase64(keyPair.publicKey); - store.proxy.edPrivate = nacl.util.encodeBase64(keyPair.secretKey); - initRpc(null, null, waitFor()); + const data = { keys: {} }; + data.keys.edPublic = nacl.util.encodeBase64(keyPair.publicKey); + data.keys.edPrivate = nacl.util.encodeBase64(keyPair.secretKey); + initRpc(null, data, waitFor()); } if (!store.anon_rpc) { initAnonRpc(null, null, waitFor()); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index ba082d172..9a576c3a5 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -771,10 +771,10 @@ define([ settings = metaObj.priv.settings; })); if (typeof(isTemplate) === "undefined") { - // Cryptpad.isTemplate(currentPad.href, waitFor(function (err, t) { - // if (err) { console.log(err); } - // isTemplate = t; - // })); + Cryptpad.isTemplate(currentPad.href, waitFor(function (err, t) { + if (err) { console.log(err); } + isTemplate = t; + })); } }).nThen(function (/*waitFor*/) { metaObj.doc = { diff --git a/www/diagram/import.js b/www/diagram/import.js index a103b69f5..505e32eed 100644 --- a/www/diagram/import.js +++ b/www/diagram/import.js @@ -37,7 +37,7 @@ define([ const [, urlHash] = splitAt(data.url, '#'); const secret = Hash.getSecrets('file', urlHash); - const fileHost = ApiConfig.fileHost || window.location.origin; + const fileHost = ApiConfig.fileHost || ApiConfig.httpUnsafeOrigin || window.location.origin; const hexFileName = secret.channel; const src = fileHost + Hash.getBlobPathFromHex(hexFileName); const key = secret.keys && secret.keys.cryptKey; From 5f398b19c7e1ddebfc7628520a0d020cffe5dbd8 Mon Sep 17 00:00:00 2001 From: Wolfgang Ginolas Date: Fri, 18 Oct 2024 15:18:27 +0200 Subject: [PATCH 12/83] Fix diagram image import --- www/common/cryptpad-common.js | 1 - www/diagram/import.js | 1 - www/diagram/util.js | 3 +-- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index bcbd7e146..e2d6a3746 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -853,7 +853,6 @@ define([ // STORAGE - TEMPLATES common.listTemplates = function (type, cb) { - console.trace('XXX listTemplates'); postMessage("GET_TEMPLATES", null, function (obj) { if (obj && obj.error) { return void cb(obj.error); } if (!Array.isArray(obj)) { return void cb ('NOT_AN_ARRAY'); } diff --git a/www/diagram/import.js b/www/diagram/import.js index a38754dac..5daaed1d9 100644 --- a/www/diagram/import.js +++ b/www/diagram/import.js @@ -35,7 +35,6 @@ define([ })) .filter(({ style }) => style.image && style.image.startsWith('data:')); - await window.CryptPad_AsyncStore.onRpcReadyEvt.promise; for(const image of images) { const blob = parseDataUrl(image.style.image); diff --git a/www/diagram/util.js b/www/diagram/util.js index 6087a0adc..b6cdf0067 100644 --- a/www/diagram/util.js +++ b/www/diagram/util.js @@ -145,7 +145,7 @@ define([ }; const getCryptPadUrlForUploadData = (data) => { - const [, urlHash] = data.url.split('#')[1]; + const urlHash = data.url.split('#')[1]; const secret = Hash.getSecrets('file', urlHash); const fileHost = ApiConfig.fileHost || window.location.origin; @@ -171,7 +171,6 @@ define([ const fmConfigImages = { noHandlers: true, noStore: true, - body: $('body'), onUploaded: function (ev, data) { if (!ev.callback) { return; } ev.callback(data); From e1784967d222770db7c967e4590b10382c467346 Mon Sep 17 00:00:00 2001 From: andy Date: Fri, 6 Dec 2024 12:29:23 +0200 Subject: [PATCH 13/83] Fix /checkup/ false positive when OO not installed --- lib/env.js | 11 ++++++--- www/checkup/main.js | 58 ++++++++++++++++++++++++--------------------- 2 files changed, 39 insertions(+), 30 deletions(-) diff --git a/lib/env.js b/lib/env.js index d3748750f..404e20dae 100644 --- a/lib/env.js +++ b/lib/env.js @@ -242,9 +242,14 @@ module.exports.create = function (config) { sso: config.sso, enforceMFA: config.enforceMFA, - onlyOffice: { - availableVersions: getInstalledOOVersions() - }, + ...(getInstalledOOVersions().length > 0 + ? { + onlyOffice: { + availableVersions: getInstalledOOVersions(), + }, + } + : {}), + // initialized as undefined bearerSecret: void 0, diff --git a/www/checkup/main.js b/www/checkup/main.js index 3c42bc016..5f88875b9 100644 --- a/www/checkup/main.js +++ b/www/checkup/main.js @@ -423,34 +423,39 @@ define([ }); }); + const ooEnabled = ApiConfig.onlyOffice && ApiConfig.onlyOffice.availableVersions.includes( + OOCurrentVersion.currentVersion, + ); var sheetURL = `/common/onlyoffice/dist/${OOCurrentVersion.currentVersion}/web-apps/apps/spreadsheeteditor/main/index.html`; - assert(function (cb, msg) { - msg.innerText = "Missing HTTP headers required for .xlsx export from sheets. "; - var expect = { - 'cross-origin-resource-policy': 'cross-origin', - 'cross-origin-embedder-policy': 'require-corp', - }; - - Tools.common_xhr(sheetURL, function (xhr) { - var result = !Object.keys(expect).some(function (k) { - var response = xhr.getResponseHeader(k); - if (response !== expect[k]) { - msg.appendChild(h('span', [ - 'A value of ', - code(expect[k]), - ' was expected for the ', - code(k), - ' HTTP header, but instead a value of "', - code(response), - '" was received.', - ])); - return true; // returning true indicates that a value is incorrect - } + if (ooEnabled) { + assert(function (cb, msg) { + msg.innerText = "Missing HTTP headers required for .xlsx export from sheets. "; + var expect = { + 'cross-origin-resource-policy': 'cross-origin', + 'cross-origin-embedder-policy': 'require-corp', + }; + + Tools.common_xhr(sheetURL, function (xhr) { + var result = !Object.keys(expect).some(function (k) { + var response = xhr.getResponseHeader(k); + if (response !== expect[k]) { + msg.appendChild(h('span', [ + 'A value of ', + code(expect[k]), + ' was expected for the ', + code(k), + ' HTTP header, but instead a value of "', + code(response), + '" was received.', + ])); + return true; // returning true indicates that a value is incorrect + } + }); + cb(result || xhr.getAllResponseHeaders()); }); - cb(result || xhr.getAllResponseHeaders()); }); - }); + } assert(function (cb, msg) { setWarningClass(msg); @@ -720,12 +725,11 @@ define([ }); assert(function (cb, msg) { // FIXME possibly superseded by more advanced CSP tests? - var url = `/common/onlyoffice/dist/${OOCurrentVersion.currentVersion}/web-apps/apps/spreadsheeteditor/main/index.html`; - msg.appendChild(CSP_WARNING(url)); + msg.appendChild(CSP_WARNING(sheetURL)); deferredPostMessage({ command: 'GET_HEADER', content: { - url: url, + url: sheetURL, header: 'content-security-policy', }, }, function (content) { From b5176fafd55e81da73a7510ac0a72f43170a751c Mon Sep 17 00:00:00 2001 From: andy Date: Tue, 10 Dec 2024 10:30:50 +0200 Subject: [PATCH 14/83] Fix code typo --- www/checkup/main.js | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/www/checkup/main.js b/www/checkup/main.js index 5f88875b9..0f2f8cf6c 100644 --- a/www/checkup/main.js +++ b/www/checkup/main.js @@ -724,19 +724,21 @@ define([ }); }); - assert(function (cb, msg) { // FIXME possibly superseded by more advanced CSP tests? - msg.appendChild(CSP_WARNING(sheetURL)); - deferredPostMessage({ - command: 'GET_HEADER', - content: { - url: sheetURL, - header: 'content-security-policy', - }, - }, function (content) { - var CSP_headers = parseCSP(content); - cb(hasOnlyOfficeHeaders(CSP_headers) || CSP_headers); + if (ooEnabled) { + assert(function (cb, msg) { // FIXME possibly superseded by more advanced CSP tests? + msg.appendChild(CSP_WARNING(sheetURL)); + deferredPostMessage({ + command: 'GET_HEADER', + content: { + url: sheetURL, + header: 'content-security-policy', + }, + }, function (content) { + var CSP_headers = parseCSP(content); + cb(hasOnlyOfficeHeaders(CSP_headers) || CSP_headers); + }); }); - }); + } /* assert(function (cb, msg) { From 286d199272e85feaa7410f1fd0c6e004eb80fede Mon Sep 17 00:00:00 2001 From: Jeremy Fleischman Date: Sun, 15 Dec 2024 22:18:42 -0800 Subject: [PATCH 15/83] Add `--check`, `--rdfind`, `--no-rdfind` options to `install-onlyoffice.sh` `--check` is useful for anyone who wants to check if their OnlyOffice installation is up to date without actually installing it. The `--rdfind/--no-rdfind` commands serve a few purposes: 1. It makes it clear that `rdfind` can still run even if you specify `--check`. 2. People can ensure that `rdfind` runs by specifying `--rdfind`, which helps if they accidentally uninstall `rdfind`. 3. People can now skip running `rdfind` even if they have `rdfind` installed. --- install-onlyoffice.sh | 68 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 9 deletions(-) diff --git a/install-onlyoffice.sh b/install-onlyoffice.sh index 6f9981c34..da42811af 100755 --- a/install-onlyoffice.sh +++ b/install-onlyoffice.sh @@ -37,7 +37,17 @@ main() { install_x2t v7.3+1 ab0c05b0e4c81071acea83f0c6a8e75f5870c360ec4abc4af09105dd9b52264af9711ec0b7020e87095193ac9b6e20305e446f2321a541f743626a598e5318c1 rm -rf "$BUILDS_DIR" - if command -v rdfind &>/dev/null; then + + if [ "${RDFIND+x}" != "x" ]; then + if command -v rdfind &>/dev/null; then + RDFIND="1" + else + RDFIND="0" + fi + fi + + if [ "$RDFIND" = "1" ]; then + ensure_command_available rdfind rdfind -makehardlinks true -makeresultsfile false $OO_DIR/v* fi } @@ -73,6 +83,18 @@ parse_arguments() { TRUST_REPOSITORY="1" shift ;; + --check) + CHECK="1" + shift + ;; + --rdfind) + RDFIND="1" + shift + ;; + --no-rdfind) + RDFIND="0" + shift + ;; *) show_help shift @@ -103,9 +125,6 @@ show_help() { cat <x2t.zip.sha512 From c58bee84033422d2bae2a6e0d106a6ff23a22e9a Mon Sep 17 00:00:00 2001 From: Jeremy Fleischman Date: Fri, 13 Dec 2024 23:43:02 -0800 Subject: [PATCH 16/83] Sort files and folders with "natural" sort With this change, we now sort "folder 2" *before* "folder 10". --- www/common/drive-ui.js | 60 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index d7577c78b..33cd0f4dc 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -3453,6 +3453,58 @@ define([ return $fihElement; }; + var lexicographicCompare = function(a, b) { + if (!Array.isArray(a)) { + a = [a]; + } + if (!Array.isArray(b)) { + b = [b]; + } + + if(a.length == 0 && b.length == 0) { + return 0; + } else if (a.length == 0) { + return -1; + } else if (b.length == 0) { + return 1; + } else if(a[0] < b[0]) { + return -1; + } else if(a[0] > b[0]) { + return 1; + } else { + // This means `a[0] == b[0]`. Chop off the first elements and compare the rest. + return lexicographicCompare(a.slice(1), b.slice(1)); + } + }; + + var splitStringToTextAndNumbers = function(s) { + var textOrDigitsRe = /(?\D+)?(?\d+)?/g; + var split = []; + + for (var match of s.matchAll(textOrDigitsRe)) { + if (match.groups.text !== undefined) { + split.push(match.groups.text); + } + if (match.groups.digits !== undefined) { + split.push(parseInt(match.groups.digits)); + } + } + + return split; + }; + + var naturalSort = function(a, b) { + if (typeof(a) == "string") { + a = splitStringToTextAndNumbers(a); + } + if (typeof(b) == "string") { + b = splitStringToTextAndNumbers(b); + } + + var comp = lexicographicCompare(a, b); + return comp; + }; + var sortElements = function (folder, path, oldkeys, prop, asc, useId) { var root = path && manager.find(path); if (path[0] === SHARED_FOLDER) { @@ -3497,9 +3549,8 @@ define([ keys.sort(function(a, b) { var _a = props[(a && a.uid) || a]; var _b = props[(b && b.uid) || b]; - if (_a < _b) { return mult * -1; } - if (_b < _a) { return mult; } - return 0; + + return mult * naturalSort(_a, _b); }); return keys; }; @@ -4513,8 +4564,7 @@ define([ manager.getSharedFolderData(root[a]).title : a; var newB = manager.isSharedFolder(root[b]) ? manager.getSharedFolderData(root[b]).title : b; - return newA < newB ? -1 : - (newA === newB ? 0 : 1); + return naturalSort(newA, newB); }); keys.forEach(function (key) { // Do not display files in the menu From a8c903d53a8f3b5923dce02710e1ea4a808c9661 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 6 Jan 2025 19:11:25 +0530 Subject: [PATCH 17/83] treat links in the sandbox as relative to the outer domain --- www/common/sframe-common.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 8310deb27..915a922fc 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -1057,6 +1057,11 @@ define([ Mailbox.create(funcs); + // automatically configure all relative links in the inner iframe + // to point to the outer domain by adding a 'base' element to iframe's + // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base + document.head.appendChild(h('base', { href: ApiConfig.httpUnsafeOrigin })); + cb(funcs); }); } }; From 6384b530a85ed9b130b064487c82088a419e2792 Mon Sep 17 00:00:00 2001 From: daria Date: Mon, 13 Jan 2025 17:16:37 +0200 Subject: [PATCH 18/83] calendar picker dropdown is accessible via keyboard #1553 --- www/calendar/inner.js | 47 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index 267b69958..04c5627d2 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -2155,6 +2155,7 @@ APP.recurrenceRule = { $el.find('.tui-full-calendar-confirm').addClass('btn btn-primary').prepend(h('i.fa.fa-floppy-o')); $el.find('input').attr('autocomplete', 'off'); $el.find('.tui-full-calendar-dropdown-button').addClass('btn btn-secondary'); + $el.find('.tui-full-calendar-popup-section-item').attr('aria-expanded', 'false'); $el.find('.tui-full-calendar-popup-close').addClass('btn btn-cancel fa fa-times cp-calendar-close').empty(); $el.find('.tui-full-calendar-section-allday').attr('tabindex', 0); $el.find('.cp-calendar-close').attr('tabindex',-1); @@ -2177,6 +2178,7 @@ APP.recurrenceRule = { $el.find('.tui-full-calendar-dropdown-menu li').each(function (i, li) { var $li = $(li); var id = $li.attr('data-calendar-id'); + $li.attr('tabindex', 0); var c = calendars[id]; if (!c || c.readOnly) { return void $li.remove(); @@ -2184,6 +2186,51 @@ APP.recurrenceRule = { // If at least one calendar is editable, show the popup show = true; }); + + $el.find('.tui-full-calendar-dropdown-button').on('keydown', function () { + setTimeout(() => { + $el.find('.tui-full-calendar-dropdown-menu').find('li').first().focus(); + }, 0); + }); + $el.find('.tui-full-calendar-dropdown-menu').on('keydown', function (event) { + var $dropdown = $(this); + var $focused = $dropdown.find('li:focus'); + + if(event.key === 'Enter') { + event.preventDefault(); + $focused.click(); + return; + } + if (event.shiftKey && event.key === 'Tab') { + event.preventDefault(); + $el.find('.tui-full-calendar-popup-section').removeClass('tui-full-calendar-open'); + $el.find('.tui-full-calendar-popup-save').focus(); + return; + } + if (event.key === 'Tab') { + event.preventDefault(); + $el.find('.tui-full-calendar-popup-section').removeClass('tui-full-calendar-open'); + $el.find('#tui-full-calendar-schedule-title').focus(); + return; + } + if (event.key === 'ArrowDown') { + event.preventDefault(); + var $next = $focused.next('li'); + if ($next.length) { + $next.focus(); + } else { + $dropdown.find('li').first().focus(); + } + } else if (event.key === 'ArrowUp') { + event.preventDefault(); + var $prev = $focused.prev('li'); + if ($prev.length) { + $prev.focus(); + } else { + $dropdown.find('li').last().focus(); + } + } + }); if ($el.find('.tui-full-calendar-hide.tui-full-calendar-dropdown').length || !show) { $el.hide(); UI.warn(Messages.calendar_errorNoCalendar); From e099b29ff50e87fe5f98c3b1f01fca09b41e8cda Mon Sep 17 00:00:00 2001 From: daria Date: Wed, 15 Jan 2025 11:49:28 +0200 Subject: [PATCH 19/83] add `aria-expanded` attribute #1553 --- www/calendar/inner.js | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index 04c5627d2..773e5f3ac 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -2186,29 +2186,48 @@ APP.recurrenceRule = { // If at least one calendar is editable, show the popup show = true; }); - - $el.find('.tui-full-calendar-dropdown-button').on('keydown', function () { + $el.find('.tui-full-calendar-dropdown-button').on('click keydown', function (event) { setTimeout(() => { - $el.find('.tui-full-calendar-dropdown-menu').find('li').first().focus(); - }, 0); + if($el.find('.tui-full-calendar-open').length){ + $el.find('.tui-full-calendar-dropdown-button').attr("aria-expanded", "true"); + } + else { + $el.find('.tui-full-calendar-dropdown-button').attr("aria-expanded", "false"); + } + if(event.type === 'keydown'){ + $el.find('.tui-full-calendar-dropdown-menu').find('li').first().focus(); + } + },0); }); + $el.find('.tui-full-calendar-dropdown-menu li').on('click', function () { + $el.find('.tui-full-calendar-dropdown-button').attr("aria-expanded", "false"); + }); + $el.find('.tui-full-calendar-dropdown-menu').on('keydown', function (event) { var $dropdown = $(this); var $focused = $dropdown.find('li:focus'); + if(event.key === 'Escape') { + $el.find('.tui-full-calendar-dropdown-button').attr("aria-expanded", "false"); + return; + } if(event.key === 'Enter') { event.preventDefault(); $focused.click(); + $el.find('.tui-full-calendar-dropdown-button').attr("aria-expanded", "false"); + $el.find('#tui-full-calendar-schedule-title').focus(); return; } if (event.shiftKey && event.key === 'Tab') { event.preventDefault(); + $el.find('.tui-full-calendar-dropdown-button').attr("aria-expanded", "false"); $el.find('.tui-full-calendar-popup-section').removeClass('tui-full-calendar-open'); $el.find('.tui-full-calendar-popup-save').focus(); return; } if (event.key === 'Tab') { event.preventDefault(); + $el.find('.tui-full-calendar-dropdown-button').attr("aria-expanded", "false"); $el.find('.tui-full-calendar-popup-section').removeClass('tui-full-calendar-open'); $el.find('#tui-full-calendar-schedule-title').focus(); return; From 1bde0526700672d08cc24f0358453d08d81848a0 Mon Sep 17 00:00:00 2001 From: daria Date: Wed, 15 Jan 2025 13:09:51 +0200 Subject: [PATCH 20/83] refactor code for calendar dropdown #1553 --- www/calendar/inner.js | 125 +++++++++++++++++++++--------------------- 1 file changed, 63 insertions(+), 62 deletions(-) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index 773e5f3ac..b4d122e67 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -2155,7 +2155,6 @@ APP.recurrenceRule = { $el.find('.tui-full-calendar-confirm').addClass('btn btn-primary').prepend(h('i.fa.fa-floppy-o')); $el.find('input').attr('autocomplete', 'off'); $el.find('.tui-full-calendar-dropdown-button').addClass('btn btn-secondary'); - $el.find('.tui-full-calendar-popup-section-item').attr('aria-expanded', 'false'); $el.find('.tui-full-calendar-popup-close').addClass('btn btn-cancel fa fa-times cp-calendar-close').empty(); $el.find('.tui-full-calendar-section-allday').attr('tabindex', 0); $el.find('.cp-calendar-close').attr('tabindex',-1); @@ -2186,70 +2185,72 @@ APP.recurrenceRule = { // If at least one calendar is editable, show the popup show = true; }); - $el.find('.tui-full-calendar-dropdown-button').on('click keydown', function (event) { - setTimeout(() => { - if($el.find('.tui-full-calendar-open').length){ - $el.find('.tui-full-calendar-dropdown-button').attr("aria-expanded", "true"); - } - else { - $el.find('.tui-full-calendar-dropdown-button').attr("aria-expanded", "false"); - } - if(event.type === 'keydown'){ - $el.find('.tui-full-calendar-dropdown-menu').find('li').first().focus(); - } - },0); - }); - $el.find('.tui-full-calendar-dropdown-menu li').on('click', function () { - $el.find('.tui-full-calendar-dropdown-button').attr("aria-expanded", "false"); - }); + let calendarDropdown = function ($el) { + let $dropdownButton = $el.find('.tui-full-calendar-dropdown-button'); + let $dropdownMenu = $el.find('.tui-full-calendar-dropdown-menu'); - $el.find('.tui-full-calendar-dropdown-menu').on('keydown', function (event) { - var $dropdown = $(this); - var $focused = $dropdown.find('li:focus'); + let toggleAriaExpanded = function (isOpen) { + $dropdownButton.attr('aria-expanded', isOpen ? 'true' : 'false'); + }; - if(event.key === 'Escape') { - $el.find('.tui-full-calendar-dropdown-button').attr("aria-expanded", "false"); - return; - } - if(event.key === 'Enter') { - event.preventDefault(); - $focused.click(); - $el.find('.tui-full-calendar-dropdown-button').attr("aria-expanded", "false"); - $el.find('#tui-full-calendar-schedule-title').focus(); - return; - } - if (event.shiftKey && event.key === 'Tab') { - event.preventDefault(); - $el.find('.tui-full-calendar-dropdown-button').attr("aria-expanded", "false"); - $el.find('.tui-full-calendar-popup-section').removeClass('tui-full-calendar-open'); - $el.find('.tui-full-calendar-popup-save').focus(); - return; - } - if (event.key === 'Tab') { - event.preventDefault(); - $el.find('.tui-full-calendar-dropdown-button').attr("aria-expanded", "false"); - $el.find('.tui-full-calendar-popup-section').removeClass('tui-full-calendar-open'); - $el.find('#tui-full-calendar-schedule-title').focus(); - return; - } - if (event.key === 'ArrowDown') { - event.preventDefault(); - var $next = $focused.next('li'); - if ($next.length) { - $next.focus(); - } else { - $dropdown.find('li').first().focus(); + let calendarDropdownNavigation = function (event) { + let $focusedItem = $dropdownMenu.find('li:focus'); + switch (event.key) { + case 'Enter': + event.preventDefault(); + $focusedItem.click(); + toggleAriaExpanded(false); + $el.find('#tui-full-calendar-schedule-title').focus(); + break; + case 'Tab': + event.preventDefault(); + toggleAriaExpanded(false); + $el.find('.tui-full-calendar-popup-section').removeClass('tui-full-calendar-open'); + if (event.shiftKey) { + $el.find('.tui-full-calendar-popup-save').focus(); + } else { + $el.find('#tui-full-calendar-schedule-title').focus(); + } + break; + case 'ArrowDown': + event.preventDefault(); + var $next = $focusedItem.next('li'); + if ($next.length) { + $next.focus(); + } else { + $dropdownMenu.find('li').first().focus(); + } + break; + case 'ArrowUp': + event.preventDefault(); + var $prev = $focusedItem.prev('li'); + if ($prev.length) { + $prev.focus(); + } else { + $dropdownMenu.find('li').last().focus(); + } + break; } - } else if (event.key === 'ArrowUp') { - event.preventDefault(); - var $prev = $focused.prev('li'); - if ($prev.length) { - $prev.focus(); - } else { - $dropdown.find('li').last().focus(); - } - } - }); + }; + + $dropdownButton.on('click keydown', function(event){ + setTimeout(() => { + let isOpen = $el.find('.tui-full-calendar-open').length > 0; + toggleAriaExpanded(isOpen); + if (event.type === 'keydown') { + $dropdownMenu.find('li').first().focus(); + } + }, 0); + }); + + $dropdownMenu.on('click', function () { + toggleAriaExpanded(false); + }); + + $dropdownMenu.on('keydown', calendarDropdownNavigation); + }; + calendarDropdown($el); + if ($el.find('.tui-full-calendar-hide.tui-full-calendar-dropdown').length || !show) { $el.hide(); UI.warn(Messages.calendar_errorNoCalendar); From 9c154b2b109a423e33fba03eb0b8760a53f40127 Mon Sep 17 00:00:00 2001 From: daria Date: Wed, 15 Jan 2025 13:47:45 +0200 Subject: [PATCH 21/83] fix title overflowing inside calendar dropdown #1741 --- www/calendar/app-calendar.less | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/www/calendar/app-calendar.less b/www/calendar/app-calendar.less index 2b5db6ca5..935458197 100644 --- a/www/calendar/app-calendar.less +++ b/www/calendar/app-calendar.less @@ -176,6 +176,7 @@ } .tui-full-calendar-icon { text-align:center; + flex-shrink: 0; } .tui-full-calendar-popup-detail-item { a { @@ -193,7 +194,7 @@ } li.tui-full-calendar-popup-section-item { padding: 0 6px; - height: 32px; + min-height: 32px; } .tui-full-calendar-popup-section-item { height: auto; @@ -215,6 +216,7 @@ } .tui-full-calendar-content { text-overflow: ellipsis; + overflow: hidden; font: @colortheme_app-font; padding: 0 10px; &:focus{ From 1addf94ed5333a347e9789747b8a2751909cf98c Mon Sep 17 00:00:00 2001 From: daria Date: Mon, 20 Jan 2025 11:38:24 +0200 Subject: [PATCH 22/83] fix focus issues inside modal #1700 --- www/common/drive-ui.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index d7577c78b..1d485f7e6 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -2918,10 +2918,10 @@ define([ h('span', Messages.fm_link_warning) ]); var content = h('p', [ - h('label', {for: 'cp-app-drive-link-name'}, Messages.fm_link_name), - name = h('input#cp-app-drive-link-name', { autocomplete: 'off', placeholder: Messages.fm_link_name_placeholder, tabindex:'1'}), + h('label', {for: 'cp-app-drive-link-name'}, Messages.fm_link_name), + name = h('input#cp-app-drive-link-name', { autocomplete: 'off', placeholder: Messages.fm_link_name_placeholder}), h('label', {for: 'cp-app-drive-link-url'}, Messages.fm_link_url), - url = h('input#cp-app-drive-link-url', { type: 'url', autocomplete: 'off', placeholder: Messages.form_input_ph_url,tabindex:'1'}), + url = h('input#cp-app-drive-link-url', { type: 'url', autocomplete: 'off', placeholder: Messages.form_input_ph_url}), warning, ]); From bc2988380ae0b9e0e02a67db1ddd75466dda2d19 Mon Sep 17 00:00:00 2001 From: daria Date: Mon, 20 Jan 2025 12:12:26 +0200 Subject: [PATCH 23/83] fix URL input validation + change error message #1700 --- www/common/drive-ui.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 1d485f7e6..0ab00843e 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -2936,9 +2936,13 @@ define([ }; var $warning = $(warning).hide(); - var $url = $(url).on('change keypress keyup keydown', function () { + var $url = $(url).on('change keypress keydown', function () { var v = $url.val().trim(); - $url.toggleClass('cp-input-invalid', !Util.isValidURL(v)); + if (Util.isValidURL(v)) { + $url.removeClass('cp-input-invalid'); + } else { + $url.addClass('cp-input-invalid'); + } if (v.length > 200) { $warning.show(); return; @@ -2961,8 +2965,8 @@ define([ var $name = $(name); var n = $name.val().trim() || $name.attr('placeholder'); var u = $url.val().trim(); - if (!n || !u) { return true; } - if (!Util.isValidURL(u)) { + if (!n || !u || !Util.isValidURL(u)) { + Messages.fm_link_invalid = "Please provide a valid URL"; // XXX UI.warn(Messages.fm_link_invalid); return true; } From 7031ba8dc7e7e141d37e4604283139aa6189e40a Mon Sep 17 00:00:00 2001 From: daria Date: Wed, 5 Feb 2025 17:07:04 +0200 Subject: [PATCH 24/83] create skip link #1202 --- customize.dist/src/less2/include/toolbar.less | 16 +++++++++ www/admin/inner.js | 1 + www/calendar/inner.js | 1 + www/common/toolbar.js | 36 ++++++++++++++++++- www/drive/inner.js | 3 +- www/notifications/inner.js | 1 + www/settings/inner.js | 1 + www/teams/inner.js | 3 +- 8 files changed, 59 insertions(+), 3 deletions(-) diff --git a/customize.dist/src/less2/include/toolbar.less b/customize.dist/src/less2/include/toolbar.less index e61d9fbda..9d5ef5625 100644 --- a/customize.dist/src/less2/include/toolbar.less +++ b/customize.dist/src/less2/include/toolbar.less @@ -445,6 +445,22 @@ display: none !important; } + .cp-toolbar-skip-link { + position: absolute; + top: -100px; + left: 0; + background-color: @cryptpad_color_brand; + color: @cryptpad_text_col; + padding: 8px 12px; + text-decoration: none; + border-radius: @variables_radius; + z-index: 1000; + transition: top 0.3s ease; + } + .cp-toolbar-skip-link:focus { + top: 10px; + } + @media screen and (max-width: @browser_media-medium-screen), screen and (max-height: 500px) { flex-wrap: wrap; diff --git a/www/admin/inner.js b/www/admin/inner.js index 9153436e5..f16af284f 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -3971,6 +3971,7 @@ define([ $container: APP.$toolbar, pageTitle: Messages.adminPage || 'Admin', metadataMgr: common.getMetadataMgr(), + skipLink: '#cp-sidebarlayout-container' }; APP.toolbar = Toolbar.create(configTb); APP.toolbar.$rightside.hide(); diff --git a/www/calendar/inner.js b/www/calendar/inner.js index 0994dbb6a..8aff0fe8b 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -2145,6 +2145,7 @@ APP.recurrenceRule = { $container: APP.$toolbar, pageTitle: Messages.calendar, metadataMgr: common.getMetadataMgr(), + skipLink: '#cp-sidebarlayout-leftside' }; APP.toolbar = Toolbar.create(configTb); APP.toolbar.$rightside.hide(); diff --git a/www/common/toolbar.js b/www/common/toolbar.js index c56b2809d..0a6f39758 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -145,7 +145,7 @@ MessengerUI, Messages, Pages, PadTypes) { $('', {'class': USERADMIN_CLS + ' cp-dropdown-container'}).hide().appendTo($userContainer); $toolbar.append($topContainer); - $(h('div.'+BOTTOM_CLS, [ + $(h('div.'+BOTTOM_CLS +"#cp-skip-link", [ h('div.'+BOTTOM_LEFT_CLS), h('div.'+BOTTOM_MID_CLS), h('div.'+BOTTOM_RIGHT_CLS) @@ -863,6 +863,39 @@ MessengerUI, Messages, Pages, PadTypes) { }; }; + var createSkipLink = function (toolbar, config) { + var targetId = config.skipLink; + var $targetElement = $(targetId); + console.log(targetId); + if(targetId === undefined){ + targetId = '#cp-skip-link'; + } + if(!$targetElement.length){ + return; + } + var $skipLink = $('', { + 'class': 'cp-toolbar-skip-link', + 'href': targetId, + 'tabindex': 0, + 'text': 'Skip to Main Content' + }); + + toolbar.$top.append($skipLink); + + $skipLink.on('click', function (event) { + event.preventDefault(); + + var $firstFocusable = $targetElement.find( + 'a, button, input, select, textarea, [tabindex]:not([tabindex="-1"]) ' + ).first(); + + if ($firstFocusable.length) { + $firstFocusable.trigger('focus'); + } + }); + + return $skipLink; + }; var createLinkToMain = function (toolbar, config) { var $linkContainer = $('', { 'class': LINK_CLS @@ -1457,6 +1490,7 @@ MessengerUI, Messages, Pages, PadTypes) { toolbar['linkToMain'] = createLinkToMain(toolbar, config); + toolbar['skipLink'] = createSkipLink(toolbar, config); if (!config.realtime) { toolbar.connected = true; } diff --git a/www/drive/inner.js b/www/drive/inner.js index 8ed880900..151332f9d 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -217,7 +217,8 @@ define([ metadataMgr: metadataMgr, readOnly: privateData.readOnly, sfCommon: common, - $container: APP.$bar + $container: APP.$bar, + skipLink: '#cp-app-drive-tree' }; var toolbar = Toolbar.create(configTb); diff --git a/www/notifications/inner.js b/www/notifications/inner.js index 874a88b15..fdf482eff 100644 --- a/www/notifications/inner.js +++ b/www/notifications/inner.js @@ -238,6 +238,7 @@ define([ $container: APP.$toolbar, pageTitle: Messages.notificationsPage || 'Notifications', metadataMgr: common.getMetadataMgr(), + skipLink: '#cp-sidebarlayout-container', }; APP.toolbar = Toolbar.create(configTb); APP.toolbar.$rightside.hide(); diff --git a/www/settings/inner.js b/www/settings/inner.js index 75cfc84cd..dfe633719 100644 --- a/www/settings/inner.js +++ b/www/settings/inner.js @@ -1978,6 +1978,7 @@ define([ $container: APP.$toolbar, pageTitle: Messages.settings_title, metadataMgr: common.getMetadataMgr(), + skipLink: '#cp-sidebarlayout-leftside' }; APP.toolbar = Toolbar.create(configTb); APP.toolbar.$rightside.hide(); diff --git a/www/teams/inner.js b/www/teams/inner.js index a1c9f2d86..8f5a34d0c 100644 --- a/www/teams/inner.js +++ b/www/teams/inner.js @@ -1564,7 +1564,8 @@ define([ metadataMgr: metadataMgr, readOnly: privateData.readOnly, sfCommon: common, - $container: $bar + $container: $bar, + skipLink: '#cp-sidebarlayout-leftside' }; var toolbar = APP.toolbar = Toolbar.create(configTb); // Update the name in the user menu From 838d40d14cb575b7fe83d847b79ce003f87e9ddd Mon Sep 17 00:00:00 2001 From: daria Date: Wed, 5 Feb 2025 17:08:27 +0200 Subject: [PATCH 25/83] remove spaces --- www/common/toolbar.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/www/common/toolbar.js b/www/common/toolbar.js index 0a6f39758..a083f4fc8 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -885,10 +885,7 @@ MessengerUI, Messages, Pages, PadTypes) { $skipLink.on('click', function (event) { event.preventDefault(); - var $firstFocusable = $targetElement.find( - 'a, button, input, select, textarea, [tabindex]:not([tabindex="-1"]) ' - ).first(); - + var $firstFocusable = $targetElement.find('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])').first(); if ($firstFocusable.length) { $firstFocusable.trigger('focus'); } From 93156ea9200d401acbc1233d70cbee9c58d05087 Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 11 Feb 2025 11:12:57 +0200 Subject: [PATCH 26/83] improve sidebar accessibility for keyboard navigation --- www/notifications/inner.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/www/notifications/inner.js b/www/notifications/inner.js index 874a88b15..bcc8c7004 100644 --- a/www/notifications/inner.js +++ b/www/notifications/inner.js @@ -203,7 +203,7 @@ define([ var active = privateData.category || 'all'; common.setHash(active); Object.keys(categories).forEach(function (key) { - var $category = $('
', {'class': 'cp-sidebarlayout-category'}).appendTo($categories); + var $category = $('
', {'class': 'cp-sidebarlayout-category', 'tabindex': 0}).appendTo($categories); if (key === 'all') { $category.append($('', {'class': 'fa fa-bars'})); } if (key === 'friends') { $category.append($('', {'class': 'fa fa-user'})); } if (key === 'pads') { $category.append($('', {'class': 'cptools cptools-richtext'})); } @@ -213,6 +213,11 @@ define([ $category.addClass('cp-leftside-active'); } + $category.keydown(function (e) { + if (e.keyCode === 13) { + $category.click(); + } + }); $category.click(function () { if (!Array.isArray(categories[key]) && categories[key].onClick) { categories[key].onClick(); From d370e879494d0941840e1dc35beb5a22c7816883 Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 11 Feb 2025 15:05:11 +0200 Subject: [PATCH 27/83] add skip link to calendar --- www/calendar/inner.js | 2 +- www/common/toolbar.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index 8aff0fe8b..cdf3bbeca 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -2145,7 +2145,7 @@ APP.recurrenceRule = { $container: APP.$toolbar, pageTitle: Messages.calendar, metadataMgr: common.getMetadataMgr(), - skipLink: '#cp-sidebarlayout-leftside' + skipLink: '#cp-sidebarlayout-container' }; APP.toolbar = Toolbar.create(configTb); APP.toolbar.$rightside.hide(); diff --git a/www/common/toolbar.js b/www/common/toolbar.js index a083f4fc8..58ffb850f 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -865,6 +865,7 @@ MessengerUI, Messages, Pages, PadTypes) { var createSkipLink = function (toolbar, config) { var targetId = config.skipLink; + console.log("Skip link id: " + targetId); var $targetElement = $(targetId); console.log(targetId); if(targetId === undefined){ From ac129ee838f8e1270d06dd154b10b8dfd228be50 Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 11 Feb 2025 15:40:38 +0200 Subject: [PATCH 28/83] add skip link to profile page --- www/profile/inner.js | 1 + 1 file changed, 1 insertion(+) diff --git a/www/profile/inner.js b/www/profile/inner.js index 63e2c8d2a..f46e09315 100644 --- a/www/profile/inner.js +++ b/www/profile/inner.js @@ -603,6 +603,7 @@ define([ $container: APP.$toolbar, pageTitle: Messages.profileButton, metadataMgr: common.getMetadataMgr(), + skipLink: '#cp-sidebarlayout-container' }; APP.toolbar = Toolbar.create(configTb); APP.toolbar.$rightside.hide(); From b7368c26999ca811247aeb86bebd9e37bc4213f2 Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 11 Feb 2025 17:07:47 +0200 Subject: [PATCH 29/83] add skip link for apps --- www/common/toolbar.js | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/www/common/toolbar.js b/www/common/toolbar.js index 58ffb850f..f0689a7c1 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -864,34 +864,24 @@ MessengerUI, Messages, Pages, PadTypes) { }; var createSkipLink = function (toolbar, config) { - var targetId = config.skipLink; - console.log("Skip link id: " + targetId); - var $targetElement = $(targetId); - console.log(targetId); - if(targetId === undefined){ - targetId = '#cp-skip-link'; - } - if(!$targetElement.length){ - return; - } - var $skipLink = $('', { + const targetId = config.skipLink || '#cp-skip-link'; + const $targetElement = $(targetId); + const $skipLink = $('', { 'class': 'cp-toolbar-skip-link', 'href': targetId, 'tabindex': 0, 'text': 'Skip to Main Content' }); - toolbar.$top.append($skipLink); $skipLink.on('click', function (event) { event.preventDefault(); - var $firstFocusable = $targetElement.find('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])').first(); + const $firstFocusable = $targetElement.find('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])').first(); if ($firstFocusable.length) { $firstFocusable.trigger('focus'); } }); - return $skipLink; }; var createLinkToMain = function (toolbar, config) { From 41731740120bff4bcb62f03b351f8ed71ff114d1 Mon Sep 17 00:00:00 2001 From: Fabrice Mouhartem Date: Thu, 13 Feb 2025 16:32:53 +0100 Subject: [PATCH 30/83] Fix contextual menu download from drive UI - Fix folder detection in CryptPad - Special case for anonymous drive files - Partly fix #1782 - Not working yet: full drive download --- www/common/drive-ui.js | 8 ++++++++ www/common/userObject.js | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index d7577c78b..7daf29b62 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -4961,6 +4961,14 @@ define([ }); } } + // anonymous drive + // `el` already contain file data, and there are no "blobs" + else if (el.channel) { + APP.FM.downloadPad(el, function(err, obj) { + console.log(err, obj); + console.log('DONE'); + }); + } } else if ($this.hasClass('cp-app-drive-context-share')) { if (paths.length !== 1) { return; } diff --git a/www/common/userObject.js b/www/common/userObject.js index aeb446487..7643076c9 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -248,7 +248,7 @@ define([ var isFolder = exp.isFolder = function (element) { if (isFolderData(element)) { return false; } - return typeof(element) === "object" || isSharedFolder(element); + return (typeof(element) === "object" && !element.channel) || isSharedFolder(element); }; exp.isFolderEmpty = function (element) { if (!isFolder(element)) { return false; } From f8442e9b981e2f76ddbb06f493bfdcfe3e262cea Mon Sep 17 00:00:00 2001 From: Fabrice Mouhartem Date: Thu, 13 Feb 2025 16:39:13 +0100 Subject: [PATCH 31/83] Fix anonymous full drive download - Related to #1782 - Anonymous full drive download is now working - One known false positive in the test that should not be too bad (the behaviour stays the same) --- www/common/make-backup.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/www/common/make-backup.js b/www/common/make-backup.js index 75bd19b78..4b4eaf723 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -353,6 +353,21 @@ define([ var filesData = data.sharedFolderId && ctx.sf[data.sharedFolderId] ? ctx.sf[data.sharedFolderId].filesData : ctx.data.filesData; var links = ctx.sf[data.sharedFolderId] && ctx.sf[data.sharedFolderId].static ? ctx.data.static && ctx.sf[data.sharedFolderId].static : ctx.data.static; + if (data.uo.curvePublic == undefined && Object.keys(ctx.data.root).length === 0) { + // Anonymous Drive + // In anonymous drive, there are no root folder in the data + // We are going to emulate it + // One false positive: empty team drive but the result is + // functionally equivalent + console.log("Anonymous drive"); // XXX: remove after testing phase + ctx.data.root = {}; + let index = 0; + Object.keys(ctx.data.filesData).forEach(file => { + ctx.data.root[index] = file; + index += 1; + }); + } + progress('reading', -1); // Msg.settings_export_reading nThen(function (waitFor) { ctx.waitFor = waitFor; From 430fbb50726a7949aa1690d26aaa0f8eef4b82a6 Mon Sep 17 00:00:00 2001 From: Fabrice Mouhartem Date: Thu, 13 Feb 2025 19:13:03 +0100 Subject: [PATCH 32/83] Forgot a strict equality --- www/common/make-backup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/make-backup.js b/www/common/make-backup.js index 4b4eaf723..f22afda91 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -353,7 +353,7 @@ define([ var filesData = data.sharedFolderId && ctx.sf[data.sharedFolderId] ? ctx.sf[data.sharedFolderId].filesData : ctx.data.filesData; var links = ctx.sf[data.sharedFolderId] && ctx.sf[data.sharedFolderId].static ? ctx.data.static && ctx.sf[data.sharedFolderId].static : ctx.data.static; - if (data.uo.curvePublic == undefined && Object.keys(ctx.data.root).length === 0) { + if (data.uo.curvePublic === undefined && Object.keys(ctx.data.root).length === 0) { // Anonymous Drive // In anonymous drive, there are no root folder in the data // We are going to emulate it From 4a6898aac735bbefa7be776a5ed3134697fd6c56 Mon Sep 17 00:00:00 2001 From: Fabrice Mouhartem Date: Fri, 14 Feb 2025 10:07:40 +0100 Subject: [PATCH 33/83] style(comments): uniformize comment style - Uniformize style for added comments in #1784 --- www/common/drive-ui.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 7daf29b62..ea9e9d9dd 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -4961,9 +4961,9 @@ define([ }); } } - // anonymous drive - // `el` already contain file data, and there are no "blobs" else if (el.channel) { + // Anonymous Drive + // `el` already contain file data, and there are no "blobs" APP.FM.downloadPad(el, function(err, obj) { console.log(err, obj); console.log('DONE'); From 30bbf3d4e13674772687e1e6f42e0607ec3b6ad7 Mon Sep 17 00:00:00 2001 From: Fabrice Mouhartem Date: Fri, 14 Feb 2025 17:01:16 +0100 Subject: [PATCH 34/83] Fix drive-ui download - Cleaner approach to #1784 - Should work for blobs --- www/common/drive-ui.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index ea9e9d9dd..504b43d39 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -4923,7 +4923,7 @@ define([ else if ($this.hasClass('cp-app-drive-context-download')) { if (paths.length !== 1) { return; } var path = paths[0]; - el = manager.find(path.path); + el = $(path.element).data('element') || manager.find(path.path); // folder if (manager.isFolder(el)) { // folder @@ -4961,14 +4961,6 @@ define([ }); } } - else if (el.channel) { - // Anonymous Drive - // `el` already contain file data, and there are no "blobs" - APP.FM.downloadPad(el, function(err, obj) { - console.log(err, obj); - console.log('DONE'); - }); - } } else if ($this.hasClass('cp-app-drive-context-share')) { if (paths.length !== 1) { return; } From db620fc81f5873b14b03e067a12af111188ad7e2 Mon Sep 17 00:00:00 2001 From: Fabrice Mouhartem Date: Fri, 14 Feb 2025 17:01:16 +0100 Subject: [PATCH 35/83] Fix unsafe comparison --- www/common/make-backup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/make-backup.js b/www/common/make-backup.js index f22afda91..29b887808 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -353,7 +353,7 @@ define([ var filesData = data.sharedFolderId && ctx.sf[data.sharedFolderId] ? ctx.sf[data.sharedFolderId].filesData : ctx.data.filesData; var links = ctx.sf[data.sharedFolderId] && ctx.sf[data.sharedFolderId].static ? ctx.data.static && ctx.sf[data.sharedFolderId].static : ctx.data.static; - if (data.uo.curvePublic === undefined && Object.keys(ctx.data.root).length === 0) { + if (!data.uo.curvePublic && Object.keys(ctx.data.root).length === 0) { // Anonymous Drive // In anonymous drive, there are no root folder in the data // We are going to emulate it From 42968d2a9ef373bf111f1a902d4de45fb96a6f66 Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 18 Feb 2025 11:01:29 +0200 Subject: [PATCH 36/83] change error message #1762 --- www/settings/inner.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/settings/inner.js b/www/settings/inner.js index 75cfc84cd..a35b3b8d7 100644 --- a/www/settings/inner.js +++ b/www/settings/inner.js @@ -902,7 +902,8 @@ define([ var todo = function () { var val = parseInt($input.val()); - if (typeof(val) !== 'number' || isNaN(val)) { return UI.warn(Messages.error); } + Messages.download_limit_error = "Please enter a valid number"; // XXX + if (typeof(val) !== 'number' || isNaN(val)) { return UI.warn(Messages.download_limit_error); } if (val === oldVal) { return; } spinner.spin(); common.setAttribute(['general', 'mediatag-size'], val, function (err) { From 69b3eae50e3a10897d52c3a308608911791b2ae3 Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 18 Feb 2025 11:14:27 +0200 Subject: [PATCH 37/83] change error message for logo size #1764 --- www/admin/inner.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/www/admin/inner.js b/www/admin/inner.js index 9153436e5..bca683617 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -884,6 +884,12 @@ define([ UI.warn(Messages.error); return; } + if (files[0].size > 200 * 1024) { + Messages.admin_logoSize_error = "The logo size must be smaller than 200KB"; // XXX + UI.warn(Messages.admin_logoSize_error); + $(input).val(''); + return; + } spinner.spin(); $button.attr('disabled', 'disabled'); let reader = new FileReader(); From c7926707998dce8433d623129c8788195c5a704a Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 18 Feb 2025 14:32:37 +0200 Subject: [PATCH 38/83] change error message for form submit type #1763 --- www/form/inner.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/form/inner.js b/www/form/inner.js index 661b0ee98..84576aa4b 100644 --- a/www/form/inner.js +++ b/www/form/inner.js @@ -3574,7 +3574,8 @@ define([ }); var $send = $(send).click(function () { if (!$radio.find('input[type="radio"]:checked').length) { - return UI.warn(Messages.error); + Messages.answerType_error = "Please select how to answer the form"; // XXX + return UI.warn(Messages.answerType_error); } var results = getFormResults(); From c805cb3d2fd3168e778221f31851971ecb1fd584 Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 18 Feb 2025 15:01:59 +0200 Subject: [PATCH 39/83] add error and confirmation messages for Storage limit #1790 + reuse message from #1762 --- www/admin/inner.js | 6 +++++- www/settings/inner.js | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/www/admin/inner.js b/www/admin/inner.js index bca683617..31a2a7c31 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -1640,7 +1640,10 @@ define([ multiple: true, validate: function () { var l = parseInt($(newLimit).val()); - if (isNaN(l)) { return false; } + if (isNaN(l)) { + Messages.limit_error = "Please enter a valid number"; // XXX + return UI.warn(Messages.limit_error); + } return true; } }, function () { @@ -1657,6 +1660,7 @@ define([ } var limit = getPrettySize(l); $(text).text(Messages._getKey('admin_limit', [limit])); + UI.log(Messages.saved); }); }); diff --git a/www/settings/inner.js b/www/settings/inner.js index a35b3b8d7..5a1ba5ef5 100644 --- a/www/settings/inner.js +++ b/www/settings/inner.js @@ -902,8 +902,8 @@ define([ var todo = function () { var val = parseInt($input.val()); - Messages.download_limit_error = "Please enter a valid number"; // XXX - if (typeof(val) !== 'number' || isNaN(val)) { return UI.warn(Messages.download_limit_error); } + Messages.limit_error = "Please enter a valid number"; // XXX + if (typeof(val) !== 'number' || isNaN(val)) { return UI.warn(Messages.limit_error); } if (val === oldVal) { return; } spinner.spin(); common.setAttribute(['general', 'mediatag-size'], val, function (err) { From d60905a81682bec9a1f02830a8ed6e0655641e12 Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 18 Feb 2025 15:29:04 +0200 Subject: [PATCH 40/83] add error message for disk performance #1791 --- www/admin/inner.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/www/admin/inner.js b/www/admin/inner.js index 31a2a7c31..f974267f1 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -3718,7 +3718,10 @@ define([ multiple: true, validate: function () { var l = parseInt($(newDuration).val()); - if (isNaN(l)) { return false; } + if (isNaN(l)) { + Messages.limit_error = "Please enter a valid number"; // XXX + return void UI.warn(Messages.limit_error); + } return true; } }, function () { From 656f99186e8a92d2cdec9326a68d37c1e3a1ce1f Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 18 Feb 2025 15:33:00 +0200 Subject: [PATCH 41/83] add error message for positive input #1791 --- www/admin/inner.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/www/admin/inner.js b/www/admin/inner.js index f974267f1..283f3025d 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -3726,7 +3726,10 @@ define([ } }, function () { var d = parseInt($(newDuration).val()); - if (!isPositiveInteger(d)) { return void UI.warn(Messages.error); } + if (!isPositiveInteger(d)) { + Messages.positiveNumber_error = "Please enter a positive number"; // XXX + return void UI.warn(Messages.positiveNumber_error); + } var data = [d]; sFrameChan.query('Q_ADMIN_RPC', { From a213827d72e7073318330ca4a2c6fe40950c6ee4 Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 18 Feb 2025 15:34:40 +0200 Subject: [PATCH 42/83] add confirmation message for disk performance #1791 --- www/admin/inner.js | 1 + 1 file changed, 1 insertion(+) diff --git a/www/admin/inner.js b/www/admin/inner.js index 283f3025d..30f6df7da 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -3741,6 +3741,7 @@ define([ return void console.error(e, response); } $(form).find('.cp-admin-bytes-written-duration').text(Messages._getKey('admin_bytesWrittenDuration', [d])); + UI.log(Messages.saved); }); }); cb(form); From 9d22059658b659b6c0e90e018cd9d9984d43b614 Mon Sep 17 00:00:00 2001 From: daria Date: Wed, 19 Feb 2025 18:02:09 +0200 Subject: [PATCH 43/83] add skip link for whiteboard app --- www/common/sframe-app-framework.js | 3 ++- www/whiteboard/inner.js | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 3f886725c..170908f62 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -976,7 +976,8 @@ define([ realtime: cpNfInner.chainpad, sfCommon: common, $container: $(toolbarContainer), - $contentContainer: $(contentContainer) + $contentContainer: $(contentContainer), + skipLink: options.skipLink, }; toolbar = Toolbar.create(configTb); title.setToolbar(toolbar); diff --git a/www/whiteboard/inner.js b/www/whiteboard/inner.js index 0cf758008..6118c93ce 100644 --- a/www/whiteboard/inner.js +++ b/www/whiteboard/inner.js @@ -636,6 +636,7 @@ define([ patchTransformer: ChainPad.NaiveJSONTransformer, toolbarContainer: '#cp-toolbar', contentContainer: '#cp-app-whiteboard-canvas-area', + skipLink: '#cp-app-whiteboard-controls' }, waitFor(function (framework) { andThen2(framework); })); From 3e3a950870feecb9a64dd59f22f213e9db5b8812 Mon Sep 17 00:00:00 2001 From: daria Date: Wed, 19 Feb 2025 18:03:58 +0200 Subject: [PATCH 44/83] add skip link for the form app --- www/form/inner.js | 1 + 1 file changed, 1 insertion(+) diff --git a/www/form/inner.js b/www/form/inner.js index 661b0ee98..33ec5f9d6 100644 --- a/www/form/inner.js +++ b/www/form/inner.js @@ -5664,5 +5664,6 @@ define([ Framework.create({ toolbarContainer: '#cp-toolbar', contentContainer: '#cp-app-form-editor', + skipLink: '#cp-app-form-editor' }, andThen); }); From 3df1393083fbe21ab454aaa19b790660a582e773 Mon Sep 17 00:00:00 2001 From: daria Date: Wed, 19 Feb 2025 18:08:59 +0200 Subject: [PATCH 45/83] add skip link for kanban --- www/kanban/inner.js | 1 + 1 file changed, 1 insertion(+) diff --git a/www/kanban/inner.js b/www/kanban/inner.js index 947092c59..c57900701 100644 --- a/www/kanban/inner.js +++ b/www/kanban/inner.js @@ -1391,6 +1391,7 @@ define([ Framework.create({ toolbarContainer: '#cme_toolbox', contentContainer: '#cp-app-kanban-editor', + skipLink: '#cp-app-kanban-content' }, waitFor(function (framework) { andThen2(framework); })); From 699fa618eee91dc8e14b196db94bbb1893dc8546 Mon Sep 17 00:00:00 2001 From: daria Date: Wed, 19 Feb 2025 18:15:21 +0200 Subject: [PATCH 46/83] add skip link for rich text --- www/pad/inner.js | 1 + 1 file changed, 1 insertion(+) diff --git a/www/pad/inner.js b/www/pad/inner.js index ec0c54aea..e4cc93118 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -1335,6 +1335,7 @@ define([ Framework.create({ toolbarContainer: '#cp-app-pad-toolbar', contentContainer: '#cp-app-pad-editor', + skipLink: '#cke_1_contents', patchTransformer: ChainPad.NaiveJSONTransformer, /*thumbnail: { getContainer: function () { return $('iframe').contents().find('html')[0]; }, From 08deef098dbc1e1ce30f7cc69691402e6ae287ba Mon Sep 17 00:00:00 2001 From: Fabrice Mouhartem Date: Mon, 17 Feb 2025 15:11:09 +0100 Subject: [PATCH 47/83] Remove previous hacky way of guessing `isLoggedIn` - Remove reference to `drive.uo.curvePublic` (f8442e9b981e2f76ddbb06f493bfdcfe3e262cea) - Add `common` information to `data` when sent to `make-backup.js` --- www/common/drive-ui.js | 1 + www/common/make-backup.js | 9 +++------ www/settings/inner.js | 1 + 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 504b43d39..a6f8da274 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -4742,6 +4742,7 @@ define([ data.sharedFolderId = sfId; data.name = Util.fixFileName(folderName); data.folderName = Util.fixFileName(folderName) + '.zip'; + data.common = common; var uo = manager.user.userObject; if (sfId && manager.folders[sfId]) { diff --git a/www/common/make-backup.js b/www/common/make-backup.js index 29b887808..cb6b33f97 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -348,17 +348,14 @@ define([ max: 0, done: 0, cache: cache, - sframeChan: sframeChan + sframeChan: sframeChan, + common: data.common, }; var filesData = data.sharedFolderId && ctx.sf[data.sharedFolderId] ? ctx.sf[data.sharedFolderId].filesData : ctx.data.filesData; var links = ctx.sf[data.sharedFolderId] && ctx.sf[data.sharedFolderId].static ? ctx.data.static && ctx.sf[data.sharedFolderId].static : ctx.data.static; - if (!data.uo.curvePublic && Object.keys(ctx.data.root).length === 0) { + if (ctx.common && !ctx.common.isLoggedIn()) { // Anonymous Drive - // In anonymous drive, there are no root folder in the data - // We are going to emulate it - // One false positive: empty team drive but the result is - // functionally equivalent console.log("Anonymous drive"); // XXX: remove after testing phase ctx.data.root = {}; let index = 0; diff --git a/www/settings/inner.js b/www/settings/inner.js index 75cfc84cd..7a3863120 100644 --- a/www/settings/inner.js +++ b/www/settings/inner.js @@ -1196,6 +1196,7 @@ define([ Feedback.send('FULL_DRIVE_EXPORT_START'); var todo = function(data, filename) { var ui = Backup.createExportUI(privateData.origin); + data.common = common; var bu = Backup.create(data, common.getPad, privateData.fileHost, function(blob, errors) { saveAs(blob, filename); From 1533b6914744fc56e5e634c2d860ae9bda60c0c7 Mon Sep 17 00:00:00 2001 From: Fabrice Mouhartem Date: Mon, 24 Feb 2025 10:07:54 +0100 Subject: [PATCH 48/83] Remove an XXX comment - Not useful anymore (the test is more robust) --- www/common/make-backup.js | 1 - 1 file changed, 1 deletion(-) diff --git a/www/common/make-backup.js b/www/common/make-backup.js index cb6b33f97..84a8bd2da 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -356,7 +356,6 @@ define([ if (ctx.common && !ctx.common.isLoggedIn()) { // Anonymous Drive - console.log("Anonymous drive"); // XXX: remove after testing phase ctx.data.root = {}; let index = 0; Object.keys(ctx.data.filesData).forEach(file => { From 24a04476e8b4e0061d670456b7f281e393033433 Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 25 Feb 2025 18:24:14 +0200 Subject: [PATCH 49/83] reformat indentation --- www/common/drive-ui.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 0ab00843e..747bd416c 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -2918,7 +2918,7 @@ define([ h('span', Messages.fm_link_warning) ]); var content = h('p', [ - h('label', {for: 'cp-app-drive-link-name'}, Messages.fm_link_name), + h('label', {for: 'cp-app-drive-link-name'}, Messages.fm_link_name), name = h('input#cp-app-drive-link-name', { autocomplete: 'off', placeholder: Messages.fm_link_name_placeholder}), h('label', {for: 'cp-app-drive-link-url'}, Messages.fm_link_url), url = h('input#cp-app-drive-link-url', { type: 'url', autocomplete: 'off', placeholder: Messages.form_input_ph_url}), From ca88ceb775659c7d4683679fb7c730f3fc0645a4 Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 25 Feb 2025 18:32:21 +0200 Subject: [PATCH 50/83] change translation key location --- customize.dist/messages.js | 1 + www/common/drive-ui.js | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/customize.dist/messages.js b/customize.dist/messages.js index be65e98a9..e88d62d5f 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -118,6 +118,7 @@ define(req, function(AppConfig, Default, Language) { Messages._languages = map; Messages._languageUsed = language; + Messages.fm_link_invalid = "Please provide a valid URL"; // XXX // Get keys with parameters Messages._getKey = function (key, argArray) { diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 747bd416c..54d2e6114 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -2966,7 +2966,6 @@ define([ var n = $name.val().trim() || $name.attr('placeholder'); var u = $url.val().trim(); if (!n || !u || !Util.isValidURL(u)) { - Messages.fm_link_invalid = "Please provide a valid URL"; // XXX UI.warn(Messages.fm_link_invalid); return true; } From 72bde544293b52c755724e25c35ba070d8a085da Mon Sep 17 00:00:00 2001 From: daria Date: Wed, 26 Feb 2025 15:29:14 +0200 Subject: [PATCH 51/83] change translation key location --- customize.dist/messages.js | 4 ++++ www/admin/inner.js | 4 ---- www/form/inner.js | 1 - www/settings/inner.js | 1 - 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/customize.dist/messages.js b/customize.dist/messages.js index be65e98a9..4c530cde7 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -118,6 +118,10 @@ define(req, function(AppConfig, Default, Language) { Messages._languages = map; Messages._languageUsed = language; + Messages.admin_logoSize_error = "The logo size must be smaller than 200KB"; // XXX + Messages.limit_error = "Please enter a valid number"; // XXX + Messages.positiveNumber_error = "Please enter a positive number"; // XXX + Messages.answerType_error = "Please select how to answer the form"; // XXX // Get keys with parameters Messages._getKey = function (key, argArray) { diff --git a/www/admin/inner.js b/www/admin/inner.js index 30f6df7da..15228ba01 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -885,7 +885,6 @@ define([ return; } if (files[0].size > 200 * 1024) { - Messages.admin_logoSize_error = "The logo size must be smaller than 200KB"; // XXX UI.warn(Messages.admin_logoSize_error); $(input).val(''); return; @@ -1641,7 +1640,6 @@ define([ validate: function () { var l = parseInt($(newLimit).val()); if (isNaN(l)) { - Messages.limit_error = "Please enter a valid number"; // XXX return UI.warn(Messages.limit_error); } return true; @@ -3719,7 +3717,6 @@ define([ validate: function () { var l = parseInt($(newDuration).val()); if (isNaN(l)) { - Messages.limit_error = "Please enter a valid number"; // XXX return void UI.warn(Messages.limit_error); } return true; @@ -3727,7 +3724,6 @@ define([ }, function () { var d = parseInt($(newDuration).val()); if (!isPositiveInteger(d)) { - Messages.positiveNumber_error = "Please enter a positive number"; // XXX return void UI.warn(Messages.positiveNumber_error); } diff --git a/www/form/inner.js b/www/form/inner.js index 84576aa4b..972f15daf 100644 --- a/www/form/inner.js +++ b/www/form/inner.js @@ -3574,7 +3574,6 @@ define([ }); var $send = $(send).click(function () { if (!$radio.find('input[type="radio"]:checked').length) { - Messages.answerType_error = "Please select how to answer the form"; // XXX return UI.warn(Messages.answerType_error); } diff --git a/www/settings/inner.js b/www/settings/inner.js index 5a1ba5ef5..0a2a192ab 100644 --- a/www/settings/inner.js +++ b/www/settings/inner.js @@ -902,7 +902,6 @@ define([ var todo = function () { var val = parseInt($input.val()); - Messages.limit_error = "Please enter a valid number"; // XXX if (typeof(val) !== 'number' || isNaN(val)) { return UI.warn(Messages.limit_error); } if (val === oldVal) { return; } spinner.spin(); From 439cef658a2e2f0b20eb770924b20776c0b665b7 Mon Sep 17 00:00:00 2001 From: daria Date: Thu, 27 Feb 2025 14:11:01 +0200 Subject: [PATCH 52/83] update skip link design --- customize.dist/src/less2/include/toolbar.less | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/customize.dist/src/less2/include/toolbar.less b/customize.dist/src/less2/include/toolbar.less index 9d5ef5625..e81645fca 100644 --- a/customize.dist/src/less2/include/toolbar.less +++ b/customize.dist/src/less2/include/toolbar.less @@ -448,10 +448,11 @@ .cp-toolbar-skip-link { position: absolute; top: -100px; - left: 0; + left: 45%; background-color: @cryptpad_color_brand; color: @cryptpad_text_col; - padding: 8px 12px; + padding: 0.3rem 0.5rem; + font-size: 1rem; text-decoration: none; border-radius: @variables_radius; z-index: 1000; From 32e4058179dd697bfabd4eebc0d805a4ea295932 Mon Sep 17 00:00:00 2001 From: daria Date: Thu, 27 Feb 2025 14:44:44 +0200 Subject: [PATCH 53/83] add keydown listener #1793 --- www/kanban/jkanban_cp.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/www/kanban/jkanban_cp.js b/www/kanban/jkanban_cp.js index f6411e36e..f98ec3e65 100644 --- a/www/kanban/jkanban_cp.js +++ b/www/kanban/jkanban_cp.js @@ -160,12 +160,20 @@ define([ } function __onAddItemClickHandler(nodeItem) { - nodeItem.addEventListener('click', function (e) { + function handleAddItem(e, item) { e.preventDefault(); e.stopPropagation(); - self.options.addItemClick(this); - if (typeof (this.clickfn) === 'function') { - this.clickfn(this); + self.options.addItemClick(item); + if (typeof (item.clickfn) === 'function') { + item.clickfn(item); + } + } + nodeItem.addEventListener('click', function (e) { + handleAddItem(e,this); + }); + nodeItem.addEventListener('keydown', function (e) { + if (e.keyCode === 13) { + handleAddItem(e,this); } }); } From 727be79ee6382560362d2d623a6f8bc90bf22aa1 Mon Sep 17 00:00:00 2001 From: daria Date: Thu, 27 Feb 2025 16:15:08 +0200 Subject: [PATCH 54/83] add `Space` event handler --- www/calendar/inner.js | 1 + 1 file changed, 1 insertion(+) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index 6ab3e0a5d..c1c3574a1 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -2247,6 +2247,7 @@ APP.recurrenceRule = { let calendarDropdownNavigation = function (event) { let $focusedItem = $dropdownMenu.find('li:focus'); switch (event.key) { + case ' ': case 'Enter': event.preventDefault(); $focusedItem.click(); From 176a7a2eb869e14530d12fc6f87d5ec744904565 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 27 Feb 2025 15:41:33 +0100 Subject: [PATCH 55/83] Fix link issues with tag in iframe --- www/calendar/inner.js | 8 -------- www/common/common-ui-elements.js | 11 +++++++---- www/common/drive-ui.js | 6 +----- www/common/onlyoffice/inner.js | 5 ++--- www/common/sframe-app-framework.js | 3 --- www/common/sframe-common-codemirror.js | 2 -- www/form/inner.js | 9 --------- 7 files changed, 10 insertions(+), 34 deletions(-) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index 0994dbb6a..bdf42745e 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -670,7 +670,6 @@ define([ tag: 'a', attributes: { 'data-value': '.ics', - 'href': '#' }, content: '.ics' }); @@ -1303,7 +1302,6 @@ ICS ==> create a new event with the same UID and a RECURRENCE-ID field (with a v attributes: { 'class': 'cp-calendar-view', 'data-value': k, - 'href': '#', }, content: Messages['calendar_'+k] // Messages.calendar_day @@ -1564,7 +1562,6 @@ APP.recurrenceRule = { attributes: { 'class': 'cp-calendar-recurrence', 'data-value': '', - 'href': '#', }, content: Messages.calendar_rec_no }]; @@ -1577,7 +1574,6 @@ APP.recurrenceRule = { attributes: { 'class': 'cp-calendar-recurrence', 'data-value': basicStr[rec], - 'href': '#', }, content: Messages._getKey('calendar_rec_' + rec, [ getWeekDays(true)[date.getDay()], @@ -1598,7 +1594,6 @@ APP.recurrenceRule = { attributes: { 'class': 'cp-calendar-recurrence', 'data-value': basicStr.days, - 'href': '#', }, content: Messages['calendar_rec_' + (isWeekend ? 'weekend' : 'weekdays')] }); @@ -1608,7 +1603,6 @@ APP.recurrenceRule = { attributes: { 'class': 'cp-calendar-recurrence', 'data-value': 'custom', - 'href': '#', }, content: Messages.calendar_rec_custom }); @@ -1690,7 +1684,6 @@ APP.recurrenceRule = { attributes: { 'class': 'cp-calendar-recurrence-freq', 'data-value': rec, - 'href': '#', }, content: Messages['calendar_rec_freq_' + rec] }); @@ -2000,7 +1993,6 @@ APP.recurrenceRule = { attributes: { 'class': 'cp-calendar-reminder', 'data-value': k, - 'href': '#', }, content: Messages['calendar_'+k] // Messages.calendar_minutes diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 378ccdb98..db25ae20f 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -1517,6 +1517,9 @@ define([ }); Util.onClickEnter(entry, function(e) { + if ($(e.target).attr('href') === '#') { + e.preventDefault(); + } if (config.isSelect) { return; } e.stopPropagation(); if (typeof(config.action) === "function") { @@ -2406,7 +2409,6 @@ define([ attributes: { 'class': 'cp-language-value', 'data-value': l, - 'href': '#', }, content: [ // supplying content as an array ensures it's a text node, not parsed HTML languages[l] // Pretty name of the language value @@ -3547,14 +3549,15 @@ define([ var text = Messages._getKey('owner_add', [name, title]); + var obj = { pw: msg.content.password || '', f: 1 }; + let newHref = Hash.getNewPadURL(msg.content.href, obj); var link = h('a', { - href: '#' + href: newHref }, Messages.requestEdit_viewPad); $(link).click(function (e) { e.preventDefault(); e.stopPropagation(); - var obj = { pw: msg.content.password || '', f: 1 }; - common.openURL(Hash.getNewPadURL(msg.content.href, obj)); + common.openURL(newHref); }); var div = h('div', [ diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index d7577c78b..2eeb35d7e 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -3137,7 +3137,7 @@ define([ var newObj = { tag: 'a', - attributes: { 'class': obj.class, href: '#' }, + attributes: { 'class': obj.class }, content: [obj.icon, obj.name] }; @@ -3179,7 +3179,6 @@ define([ tag: 'a', attributes: { 'class': 'cp-app-drive-rm-filter', - 'href': '#' }, content: [ h('i.fa.fa-times'), @@ -3192,7 +3191,6 @@ define([ var attributes = { 'class': 'cp-app-drive-filter-doc', 'data-type': type, - 'href': '#' }; var premium = common.checkRestrictedApp(type); @@ -3218,7 +3216,6 @@ define([ attributes: { 'class': 'cp-app-drive-filter-doc', 'data-type': 'link', - 'href': '#' }, content: [ getIcon('link')[0], @@ -3230,7 +3227,6 @@ define([ attributes: { 'class': 'cp-app-drive-filter-doc', 'data-type': 'file', - 'href': '#' }, content: [ getIcon('file')[0], diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 07c2ad1e3..3aeed02b4 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -2211,7 +2211,6 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null tag: 'a', attributes: { 'data-value': val, - href: '#' }, content: val }; @@ -2630,7 +2629,7 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null (content.version <= 3 ? 'v2b/' : CURRENT_VERSION+'/'); var s = h('script', { type:'text/javascript', - src: '/common/onlyoffice/dist/'+version+'web-apps/apps/api/documents/api.js' + src: ApiConfig.httpSafeOrigin + '/common/onlyoffice/dist/'+version+'web-apps/apps/api/documents/api.js' }); $('#cp-app-oo-editor').empty().append(h('div#cp-app-oo-placeholder-a')).append(s); @@ -3072,7 +3071,7 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null var s = h('script', { type:'text/javascript', - src: '/common/onlyoffice/dist/'+version+'web-apps/apps/api/documents/api.js' + src: ApiConfig.httpSafeOrigin + '/common/onlyoffice/dist/'+version+'web-apps/apps/api/documents/api.js' }); $('#cp-app-oo-editor').append(s); diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 3f886725c..1bcb85a39 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -721,7 +721,6 @@ define([ tag: 'a', attributes: { 'data-value': _ext, - 'href': '#' }, content: _ext }); @@ -732,7 +731,6 @@ define([ tag: 'a', attributes: { 'data-value': ext, - 'href': '#' }, content: ext }); @@ -741,7 +739,6 @@ define([ tag: 'a', attributes: { 'data-value': '', - 'href': '#' }, content: ' ', }); diff --git a/www/common/sframe-common-codemirror.js b/www/common/sframe-common-codemirror.js index aa1bde788..e3411dc0e 100644 --- a/www/common/sframe-common-codemirror.js +++ b/www/common/sframe-common-codemirror.js @@ -365,7 +365,6 @@ define([ tag: 'a', attributes: { 'data-value': l.mode, - 'href': '#', }, content: [l.language] // Pretty name of the language value }); @@ -431,7 +430,6 @@ define([ tag: 'a', attributes: { 'data-value': l.name, - 'href': '#', }, content: [l.name] // Pretty name of the language value }); diff --git a/www/form/inner.js b/www/form/inner.js index 661b0ee98..9ec256308 100644 --- a/www/form/inner.js +++ b/www/form/inner.js @@ -159,7 +159,6 @@ define([ attributes: { 'class': 'cp-form-type-value', 'data-value': t, - 'href': '#', }, content: Messages['form_text_'+t] }; @@ -262,7 +261,6 @@ define([ attributes: { 'class': 'cp-form-type-value', 'data-value': t, - 'href': '#', }, content: Messages['form_poll_'+t] }; @@ -1295,7 +1293,6 @@ define([ attributes: { 'class': 'cp-form-condition-question', 'data-value': obj.uid, - 'href': '#', }, content: obj.q }; @@ -1316,14 +1313,12 @@ define([ tag: 'a', attributes: { 'data-value': 1, - 'href': '#', }, content: Messages.form_condition_is }, { tag: 'a', attributes: { 'data-value': 0, - 'href': '#', }, content: Messages.form_condition_isnot }]; @@ -1402,7 +1397,6 @@ define([ attributes: { 'class': 'cp-form-condition-value', 'data-value': str, - 'href': '#', }, content: str }; @@ -1576,7 +1570,6 @@ define([ attributes: { 'class': 'cp-form-condition-question', 'data-value': obj.uid, - 'href': '#', }, content: obj.q }; @@ -1604,7 +1597,6 @@ define([ attributes: { 'class': 'cp-form-condition-value', 'data-value': str, - 'href': '#', }, content: str }; @@ -2988,7 +2980,6 @@ define([ attributes: { 'class': 'cp-form-type-value', 'data-value': t.key, - 'href': '#', }, content: t.str }; From 65d6f4282f7870c27b0bbe4ea1267980eb5aa497 Mon Sep 17 00:00:00 2001 From: daria Date: Fri, 28 Feb 2025 15:15:17 +0200 Subject: [PATCH 56/83] set `aria-expanded` to `false` when clicking outside the dropdown --- www/calendar/inner.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index c1c3574a1..de7184972 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -2298,6 +2298,12 @@ APP.recurrenceRule = { $dropdownMenu.on('click', function () { toggleAriaExpanded(false); }); + // click outside the dropdown button => closes the dropdown => aria-expanded is false + $(document).on('click', function (event) { + if (!$(event.target).closest($dropdownButton).length) { + toggleAriaExpanded(false); + } + }); $dropdownMenu.on('keydown', calendarDropdownNavigation); }; From b5bd1656dadbd402ae2094cd9ba84a00405e0ed7 Mon Sep 17 00:00:00 2001 From: daria Date: Fri, 28 Feb 2025 15:37:20 +0200 Subject: [PATCH 57/83] add `Esc` option for calendar dropdown --- www/calendar/inner.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index de7184972..2b59d88ea 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -2252,6 +2252,7 @@ APP.recurrenceRule = { event.preventDefault(); $focusedItem.click(); toggleAriaExpanded(false); + $dropdownMenu.hide(); $el.find('#tui-full-calendar-schedule-title').focus(); break; case 'Tab': @@ -2282,6 +2283,13 @@ APP.recurrenceRule = { $dropdownMenu.find('li').last().focus(); } break; + case 'Escape': + event.preventDefault(); + event.stopPropagation(); + $dropdownMenu.hide(); + toggleAriaExpanded(false); + $dropdownButton.focus(); + break; } }; @@ -2290,6 +2298,7 @@ APP.recurrenceRule = { let isOpen = $el.find('.tui-full-calendar-open').length > 0; toggleAriaExpanded(isOpen); if (event.type === 'keydown') { + $dropdownMenu.show(); $dropdownMenu.find('li').first().focus(); } }, 0); From 47f77aa29e2961fff4f90b5c15d1b0f7e6327de2 Mon Sep 17 00:00:00 2001 From: daria Date: Fri, 28 Feb 2025 16:31:27 +0200 Subject: [PATCH 58/83] fix opening dropdown bug --- www/calendar/inner.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index 2b59d88ea..988336255 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -2242,6 +2242,12 @@ APP.recurrenceRule = { let toggleAriaExpanded = function (isOpen) { $dropdownButton.attr('aria-expanded', isOpen ? 'true' : 'false'); + if(isOpen){ + $dropdownMenu.show(); + } + else{ + $dropdownMenu.hide(); + } }; let calendarDropdownNavigation = function (event) { @@ -2252,7 +2258,6 @@ APP.recurrenceRule = { event.preventDefault(); $focusedItem.click(); toggleAriaExpanded(false); - $dropdownMenu.hide(); $el.find('#tui-full-calendar-schedule-title').focus(); break; case 'Tab': @@ -2286,9 +2291,7 @@ APP.recurrenceRule = { case 'Escape': event.preventDefault(); event.stopPropagation(); - $dropdownMenu.hide(); toggleAriaExpanded(false); - $dropdownButton.focus(); break; } }; @@ -2298,7 +2301,6 @@ APP.recurrenceRule = { let isOpen = $el.find('.tui-full-calendar-open').length > 0; toggleAriaExpanded(isOpen); if (event.type === 'keydown') { - $dropdownMenu.show(); $dropdownMenu.find('li').first().focus(); } }, 0); From 70ee8d791ef5ea55aefbffc8e244398fa8934608 Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 4 Mar 2025 15:06:56 +0200 Subject: [PATCH 59/83] add focus to the dropdown button when using `Esc` --- www/calendar/inner.js | 1 + 1 file changed, 1 insertion(+) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index 988336255..b71d60a7e 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -2292,6 +2292,7 @@ APP.recurrenceRule = { event.preventDefault(); event.stopPropagation(); toggleAriaExpanded(false); + $dropdownButton.focus(); break; } }; From fcf98869e0e37a697105ec2fdc6819b0756fe2a8 Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 4 Mar 2025 15:35:22 +0200 Subject: [PATCH 60/83] add event handler to dropdown button - open dropdown menu when using certain keys --- www/calendar/inner.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index b71d60a7e..bbb88330c 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -2297,16 +2297,22 @@ APP.recurrenceRule = { } }; - $dropdownButton.on('click keydown', function(event){ - setTimeout(() => { - let isOpen = $el.find('.tui-full-calendar-open').length > 0; - toggleAriaExpanded(isOpen); - if (event.type === 'keydown') { + $dropdownButton.on('click keydown', function (event) { + let isOpen = $el.find('.tui-full-calendar-open').length > 0; + toggleAriaExpanded(!isOpen); + if (event.key === 'ArrowDown' || event.key === 'Enter' || event.key === ' ') { + if (!isOpen) { $dropdownMenu.find('li').first().focus(); } - }, 0); + } + else if(event.key === 'ArrowUp'){ + if (!isOpen) { + $dropdownMenu.find('li').last().focus(); + } + } }); + $dropdownMenu.on('click', function () { toggleAriaExpanded(false); }); From 877d33cb60e2715ce02690bea12e79a0c8fd2ba3 Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 4 Mar 2025 15:41:23 +0200 Subject: [PATCH 61/83] ensure dropdown opens correctly with only the accepted keys --- www/calendar/inner.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index bbb88330c..f460e6be0 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -2298,6 +2298,9 @@ APP.recurrenceRule = { }; $dropdownButton.on('click keydown', function (event) { + if (event.type !== 'click' && event.key !== 'Enter' && event.key !== ' ' && event.key !== 'ArrowDown' && event.key !== 'ArrowUp') { + return; + } let isOpen = $el.find('.tui-full-calendar-open').length > 0; toggleAriaExpanded(!isOpen); if (event.key === 'ArrowDown' || event.key === 'Enter' || event.key === ' ') { From 7f4f69912650c6de13e827bbef8bdfba0f8a5e25 Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 4 Mar 2025 15:47:45 +0200 Subject: [PATCH 62/83] remove redundant code --- www/calendar/inner.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index f460e6be0..b6a5021f1 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -2314,11 +2314,6 @@ APP.recurrenceRule = { } } }); - - - $dropdownMenu.on('click', function () { - toggleAriaExpanded(false); - }); // click outside the dropdown button => closes the dropdown => aria-expanded is false $(document).on('click', function (event) { if (!$(event.target).closest($dropdownButton).length) { From bac9e7a75c704bb17a12cb44a4eb42cb8b468545 Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 4 Mar 2025 16:05:32 +0200 Subject: [PATCH 63/83] simplify code --- www/calendar/inner.js | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index b6a5021f1..e2cb19be4 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -2242,12 +2242,7 @@ APP.recurrenceRule = { let toggleAriaExpanded = function (isOpen) { $dropdownButton.attr('aria-expanded', isOpen ? 'true' : 'false'); - if(isOpen){ - $dropdownMenu.show(); - } - else{ - $dropdownMenu.hide(); - } + isOpen ? $dropdownMenu.show() : $dropdownMenu.hide(); }; let calendarDropdownNavigation = function (event) { @@ -2303,15 +2298,11 @@ APP.recurrenceRule = { } let isOpen = $el.find('.tui-full-calendar-open').length > 0; toggleAriaExpanded(!isOpen); - if (event.key === 'ArrowDown' || event.key === 'Enter' || event.key === ' ') { - if (!isOpen) { - $dropdownMenu.find('li').first().focus(); - } + if (!isOpen && event.key !== 'ArrowUp') { + $dropdownMenu.find('li').first().focus(); } - else if(event.key === 'ArrowUp'){ - if (!isOpen) { - $dropdownMenu.find('li').last().focus(); - } + else if(!isOpen){ + $dropdownMenu.find('li').last().focus(); } }); // click outside the dropdown button => closes the dropdown => aria-expanded is false From 83a4a060788aa957fc9894f459bcf55847ae0485 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 4 Mar 2025 16:46:57 +0100 Subject: [PATCH 64/83] Fix tags category in drive --- www/common/drive-ui.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 2eeb35d7e..88cb5075d 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -4087,7 +4087,8 @@ define([ ]; sortedTags.forEach(function (tag) { var tagLink = h('a', { href: '#' }, '#' + tag); - $(tagLink).click(function () { + $(tagLink).click(function (e) { + e.preventDefault(); if (displayedCategories.indexOf(SEARCH) !== -1) { APP.displayDirectory([SEARCH, '#' + tag]); } From 64cf175623fde407460e820d3d63d00d9378d5f5 Mon Sep 17 00:00:00 2001 From: daria Date: Wed, 5 Mar 2025 16:07:16 +0200 Subject: [PATCH 65/83] change message error --- customize.dist/messages.js | 2 +- www/admin/inner.js | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/customize.dist/messages.js b/customize.dist/messages.js index 4c530cde7..15f602c40 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -118,7 +118,7 @@ define(req, function(AppConfig, Default, Language) { Messages._languages = map; Messages._languageUsed = language; - Messages.admin_logoSize_error = "The logo size must be smaller than 200KB"; // XXX + Messages.admin_logoSize_error = "The logo size is too large"; // XXX Messages.limit_error = "Please enter a valid number"; // XXX Messages.positiveNumber_error = "Please enter a positive number"; // XXX Messages.answerType_error = "Please select how to answer the form"; // XXX diff --git a/www/admin/inner.js b/www/admin/inner.js index 15228ba01..52019fb62 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -884,11 +884,6 @@ define([ UI.warn(Messages.error); return; } - if (files[0].size > 200 * 1024) { - UI.warn(Messages.admin_logoSize_error); - $(input).val(''); - return; - } spinner.spin(); $button.attr('disabled', 'disabled'); let reader = new FileReader(); @@ -897,7 +892,12 @@ define([ sframeCommand('UPLOAD_LOGO', {dataURL}, (err, response) => { $button.removeAttr('disabled'); if (err) { - UI.warn(Messages.error); + if(err === 'E_TOO_LARGE') { + UI.warn(Messages.admin_logoSize_error); + } + else{ + UI.warn(Messages.error); + } $(input).val(''); console.error(err, response); spinner.hide(); From b735d8ffab9becb2797e9c4fc08ad36fb61460db Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 11 Mar 2025 13:46:10 +0200 Subject: [PATCH 66/83] remove id --- www/common/toolbar.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/www/common/toolbar.js b/www/common/toolbar.js index f0689a7c1..c2a66c87a 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -145,7 +145,7 @@ MessengerUI, Messages, Pages, PadTypes) { $('', {'class': USERADMIN_CLS + ' cp-dropdown-container'}).hide().appendTo($userContainer); $toolbar.append($topContainer); - $(h('div.'+BOTTOM_CLS +"#cp-skip-link", [ + $(h('div.'+BOTTOM_CLS, [ h('div.'+BOTTOM_LEFT_CLS), h('div.'+BOTTOM_MID_CLS), h('div.'+BOTTOM_RIGHT_CLS) @@ -863,7 +863,7 @@ MessengerUI, Messages, Pages, PadTypes) { }; }; - var createSkipLink = function (toolbar, config) { + Bar.createSkipLink = function (toolbar, config) { const targetId = config.skipLink || '#cp-skip-link'; const $targetElement = $(targetId); const $skipLink = $('', { @@ -1478,7 +1478,7 @@ MessengerUI, Messages, Pages, PadTypes) { toolbar['linkToMain'] = createLinkToMain(toolbar, config); - toolbar['skipLink'] = createSkipLink(toolbar, config); + toolbar['skipLink'] = Bar.createSkipLink(toolbar, config); if (!config.realtime) { toolbar.connected = true; } From 3494516b6c1bd85d13b8d6da3f36cab7b8b1681f Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 12 Mar 2025 12:40:32 +0100 Subject: [PATCH 67/83] Skip links with iframes --- www/code/inner.js | 1 + www/common/onlyoffice/inner.js | 3 ++- www/common/toolbar.js | 25 +++++++++++++++++++------ www/diagram/inner.js | 1 + www/pad/inner.js | 2 +- 5 files changed, 24 insertions(+), 8 deletions(-) diff --git a/www/code/inner.js b/www/code/inner.js index 4be60351a..fa97426d4 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -610,6 +610,7 @@ define([ Framework.create({ toolbarContainer: '#cme_toolbox', contentContainer: '#cp-app-code-editor', + skipLink: '.CodeMirror', thumbnail: { getContainer: getThumbnailContainer, filter: function (el, before) { diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 07c2ad1e3..b316f69a9 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -2666,7 +2666,8 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null }, sfCommon: common, $container: $bar, - $contentContainer: $('#cp-app-oo-container') + $contentContainer: $('#cp-app-oo-container'), + skipLink: 'iframe[name="frameEditor"]|#editor_sdk' }; toolbar = APP.toolbar = Toolbar.create(configTb); toolbar.showColors(); diff --git a/www/common/toolbar.js b/www/common/toolbar.js index c2a66c87a..f839430c2 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -865,22 +865,35 @@ MessengerUI, Messages, Pages, PadTypes) { Bar.createSkipLink = function (toolbar, config) { const targetId = config.skipLink || '#cp-skip-link'; - const $targetElement = $(targetId); const $skipLink = $('', { 'class': 'cp-toolbar-skip-link', 'href': targetId, 'tabindex': 0, - 'text': 'Skip to Main Content' + 'text': 'Skip to Main Content' // XXX }); toolbar.$top.append($skipLink); $skipLink.on('click', function (event) { event.preventDefault(); - const $firstFocusable = $targetElement.find('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])').first(); - if ($firstFocusable.length) { - $firstFocusable.trigger('focus'); - } + let split = targetId.split('|'); // split for iframes + let $container = $('body'); + split.some(selector => { + let $targetElement = $container.find(selector); + if ($targetElement.is('iframe')) { + $container = $targetElement.contents(); + return; + } + const $firstFocusable = $targetElement.find('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"]), [contenteditable="true"]').first(); + if ($firstFocusable.length) { + $firstFocusable.trigger('focus'); + } + return true; + }); + + + /* + */ }); return $skipLink; }; diff --git a/www/diagram/inner.js b/www/diagram/inner.js index b6176c1cf..606d84d66 100644 --- a/www/diagram/inner.js +++ b/www/diagram/inner.js @@ -250,6 +250,7 @@ define([ Framework.create({ toolbarContainer: '#cme_toolbox', contentContainer: '#cp-app-diagram-editor', + skipLink: '#cp-app-diagram-content|body .geSearchSidebar', // validateContent: validateXml, }, function (framework) { onFrameworkReady(framework); diff --git a/www/pad/inner.js b/www/pad/inner.js index e4cc93118..f912d00f2 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -1335,7 +1335,7 @@ define([ Framework.create({ toolbarContainer: '#cp-app-pad-toolbar', contentContainer: '#cp-app-pad-editor', - skipLink: '#cke_1_contents', + skipLink: '#cke_1_contents .cke_wysiwyg_frame|html', patchTransformer: ChainPad.NaiveJSONTransformer, /*thumbnail: { getContainer: function () { return $('iframe').contents().find('html')[0]; }, From 44c679dfd677e372957246079e22d3206dde9c22 Mon Sep 17 00:00:00 2001 From: daria Date: Wed, 12 Mar 2025 13:58:45 +0200 Subject: [PATCH 68/83] add skip link to slides --- www/slide/inner.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/slide/inner.js b/www/slide/inner.js index c0e0cd97b..98c354f90 100644 --- a/www/slide/inner.js +++ b/www/slide/inner.js @@ -619,7 +619,8 @@ define([ } $(el).css('background-color', ''); } - } + }, + skipLink: '.CodeMirror', }, waitFor(function (fw) { framework = fw; })); nThen(function (waitFor) { From afbe29376d573ff09d4b3340bdd8376ace7ac118 Mon Sep 17 00:00:00 2001 From: daria Date: Wed, 12 Mar 2025 14:14:20 +0200 Subject: [PATCH 69/83] add translation key --- customize.dist/messages.js | 1 + www/common/toolbar.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/customize.dist/messages.js b/customize.dist/messages.js index be65e98a9..0c5009486 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -118,6 +118,7 @@ define(req, function(AppConfig, Default, Language) { Messages._languages = map; Messages._languageUsed = language; + Messages.skipLink = "Skip to main content"; // XXX // Get keys with parameters Messages._getKey = function (key, argArray) { diff --git a/www/common/toolbar.js b/www/common/toolbar.js index f839430c2..d7e3c3d2d 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -869,7 +869,7 @@ MessengerUI, Messages, Pages, PadTypes) { 'class': 'cp-toolbar-skip-link', 'href': targetId, 'tabindex': 0, - 'text': 'Skip to Main Content' // XXX + 'text': Messages.skipLink }); toolbar.$top.append($skipLink); From cac727192723523e824d5dad9e9dde79535be603 Mon Sep 17 00:00:00 2001 From: daria Date: Wed, 12 Mar 2025 14:35:17 +0200 Subject: [PATCH 70/83] remove skip link if it's read-only mode --- www/common/toolbar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/toolbar.js b/www/common/toolbar.js index d7e3c3d2d..82ce0e1a8 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -864,6 +864,7 @@ MessengerUI, Messages, Pages, PadTypes) { }; Bar.createSkipLink = function (toolbar, config) { + if (config.readOnly === 1) {return;} const targetId = config.skipLink || '#cp-skip-link'; const $skipLink = $('', { 'class': 'cp-toolbar-skip-link', @@ -872,7 +873,6 @@ MessengerUI, Messages, Pages, PadTypes) { 'text': Messages.skipLink }); toolbar.$top.append($skipLink); - $skipLink.on('click', function (event) { event.preventDefault(); From 312078109ed183b7d9c160d8aa5cc7a0fc314f05 Mon Sep 17 00:00:00 2001 From: daria Date: Wed, 12 Mar 2025 14:36:17 +0200 Subject: [PATCH 71/83] remove id --- www/common/toolbar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/toolbar.js b/www/common/toolbar.js index 82ce0e1a8..439ee74bc 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -865,7 +865,7 @@ MessengerUI, Messages, Pages, PadTypes) { Bar.createSkipLink = function (toolbar, config) { if (config.readOnly === 1) {return;} - const targetId = config.skipLink || '#cp-skip-link'; + const targetId = config.skipLink; const $skipLink = $('', { 'class': 'cp-toolbar-skip-link', 'href': targetId, From 8b6dcb495b91e576a49d1a39154d5eae9cbe2825 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 12 Mar 2025 15:58:35 +0100 Subject: [PATCH 72/83] Fix diagram iframe source --- www/diagram/inner.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/www/diagram/inner.js b/www/diagram/inner.js index b6176c1cf..c5e7e569b 100644 --- a/www/diagram/inner.js +++ b/www/diagram/inner.js @@ -4,6 +4,7 @@ // This is the initialization loading the CryptPad libraries define([ + '/api/config', 'jquery', '/common/sframe-app-framework.js', '/customize/messages.js', // translation keys @@ -15,6 +16,7 @@ define([ 'less!/diagram/app-diagram.less', 'css!/diagram/drawio.css', ], function ( + ApiConfig, $, Framework, Messages, @@ -207,7 +209,7 @@ define([ // starting the CryptPad framework framework.start(); - drawioFrame.src = '/components/drawio/src/main/webapp/index.html?' + drawioFrame.src = ApiConfig.httpSafeOrigin + '/components/drawio/src/main/webapp/index.html?' + new URLSearchParams({ test: 1, stealth: 1, From cd7dc952ec3ed1b6aff0e709dbdb90b03700e245 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 12 Mar 2025 16:56:06 +0100 Subject: [PATCH 73/83] Hide skip link button when focusable element not found --- www/common/toolbar.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/www/common/toolbar.js b/www/common/toolbar.js index 439ee74bc..3cbe4fd5f 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -887,13 +887,11 @@ MessengerUI, Messages, Pages, PadTypes) { const $firstFocusable = $targetElement.find('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"]), [contenteditable="true"]').first(); if ($firstFocusable.length) { $firstFocusable.trigger('focus'); + } else { + $skipLink.hide(); } return true; }); - - - /* - */ }); return $skipLink; }; From 5e9f6c2edf5ce9ae781c0c808531ac594d99ee40 Mon Sep 17 00:00:00 2001 From: daria Date: Thu, 13 Mar 2025 10:14:28 +0200 Subject: [PATCH 74/83] add aria-label to add buttons --- customize.dist/messages.js | 3 ++- www/kanban/jkanban_cp.js | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/customize.dist/messages.js b/customize.dist/messages.js index be65e98a9..b745088b5 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -118,7 +118,8 @@ define(req, function(AppConfig, Default, Language) { Messages._languages = map; Messages._languageUsed = language; - + Messages.addItemBottom = 'Add item to bottom of the board'; // XXX or 'Add card..' + Messages.addItemTop = 'Add item to top of the board'; // XXX or 'Add card..' // Get keys with parameters Messages._getKey = function (key, argArray) { if (!Messages[key]) { return '?'; } diff --git a/www/kanban/jkanban_cp.js b/www/kanban/jkanban_cp.js index f98ec3e65..1db332c15 100644 --- a/www/kanban/jkanban_cp.js +++ b/www/kanban/jkanban_cp.js @@ -727,6 +727,7 @@ define([ var addTopBoardItem = document.createElement('span'); addTopBoardItem.classList.add('kanban-title-button'); $(addTopBoardItem).attr('tabindex', '0'); + $(addTopBoardItem).attr('aria-label', Messages.addItemTop); addTopBoardItem.setAttribute('data-top', "1"); addTopBoardItem.innerHTML = ''; footerBoard.appendChild(addTopBoardItem); @@ -734,6 +735,7 @@ define([ var addBoardItem = document.createElement('span'); addBoardItem.classList.add('kanban-title-button'); $(addBoardItem).attr('tabindex', '0'); + $(addBoardItem).attr('aria-label', Messages.addItemBottom); addBoardItem.innerHTML = ''; footerBoard.appendChild(addBoardItem); __onAddItemClickHandler(addBoardItem); From d68117500e768c120d8a669a6370eee2acd1fe79 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 13 Mar 2025 14:30:21 +0100 Subject: [PATCH 75/83] Add nowrap to calendar dropdown entries --- www/calendar/app-calendar.less | 1 + 1 file changed, 1 insertion(+) diff --git a/www/calendar/app-calendar.less b/www/calendar/app-calendar.less index ec05ab3f8..245d08d6f 100644 --- a/www/calendar/app-calendar.less +++ b/www/calendar/app-calendar.less @@ -217,6 +217,7 @@ .tui-full-calendar-content { text-overflow: ellipsis; overflow: hidden; + white-space: nowrap; font: @colortheme_app-font; padding: 0 10px; &:focus{ From dddb62d4f1e1ca09bf35b3062a281e3839607fc0 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 13 Mar 2025 14:46:25 +0100 Subject: [PATCH 76/83] lint compliance --- eslint.config.js | 1 + www/common/drive-ui.js | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index eba722d94..535c18440 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -65,6 +65,7 @@ module.exports = [{ "linebreak-style": ["off", "unix"], quotes: ["off", "single"], semi: ["error", "always"], + eqeqeq: ["error", "always"], "no-irregular-whitespace": ["off"], "no-self-assign": ["off"], "no-empty": ["off"], diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 33cd0f4dc..77c2dbc4b 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -3461,11 +3461,11 @@ define([ b = [b]; } - if(a.length == 0 && b.length == 0) { + if(a.length === 0 && b.length === 0) { return 0; - } else if (a.length == 0) { + } else if (a.length === 0) { return -1; - } else if (b.length == 0) { + } else if (b.length === 0) { return 1; } else if(a[0] < b[0]) { return -1; @@ -3494,10 +3494,10 @@ define([ }; var naturalSort = function(a, b) { - if (typeof(a) == "string") { + if (typeof(a) === "string") { a = splitStringToTextAndNumbers(a); } - if (typeof(b) == "string") { + if (typeof(b) === "string") { b = splitStringToTextAndNumbers(b); } From a08088a3596596d576516ccc70bf6fc80f739a5c Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 13 Mar 2025 15:01:12 +0100 Subject: [PATCH 77/83] Move OO doc and presentation out of early access --- www/common/common-constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/common-constants.js b/www/common/common-constants.js index 621214f71..a82de16aa 100644 --- a/www/common/common-constants.js +++ b/www/common/common-constants.js @@ -27,6 +27,6 @@ define(['/customize/application_config.js'], function (AppConfig) { MAX_PREMIUM_TEAMS_OWNED: Math.max(AppConfig.maxTeamsOwned || 0, AppConfig.maxPremiumTeamsOwned || 0) || 5, // Apps criticalApps: ['profile', 'settings', 'debug', 'admin', 'support', 'notifications', 'calendar', 'moderation', 'oldadmin'], // XXX oldadmin - earlyAccessApps: ['doc', 'presentation'] + earlyAccessApps: [] }; }); From 2122417043be48d5cd4c52bf1c2ff70d6263f007 Mon Sep 17 00:00:00 2001 From: daria Date: Thu, 13 Mar 2025 16:25:34 +0200 Subject: [PATCH 78/83] change colors --- customize.dist/src/less2/include/toolbar.less | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/customize.dist/src/less2/include/toolbar.less b/customize.dist/src/less2/include/toolbar.less index e81645fca..8e7879fce 100644 --- a/customize.dist/src/less2/include/toolbar.less +++ b/customize.dist/src/less2/include/toolbar.less @@ -449,8 +449,8 @@ position: absolute; top: -100px; left: 45%; - background-color: @cryptpad_color_brand; - color: @cryptpad_text_col; + background-color: @cp_buttons-primary; + color: @cp_buttons-primary-text; padding: 0.3rem 0.5rem; font-size: 1rem; text-decoration: none; From e3ee2e58072e3977b19f9adfe0882e57b4f0e296 Mon Sep 17 00:00:00 2001 From: daria Date: Mon, 17 Mar 2025 13:13:15 +0200 Subject: [PATCH 79/83] add aria-hidden to icons --- www/common/inner/sidebar-layout.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/inner/sidebar-layout.js b/www/common/inner/sidebar-layout.js index 03ce9c151..a356bffaf 100644 --- a/www/common/inner/sidebar-layout.js +++ b/www/common/inner/sidebar-layout.js @@ -64,7 +64,7 @@ define([ let prefix = icon.slice(0, icon.indexOf('-')); cls = `.${prefix}.${icon}`; } - return h(`i${cls}`); + return h(`i${cls}`, { 'aria-hidden': 'true' }); }; blocks.button = (type, icon, text) => { type = type || 'primary'; From dc5a51b2154da7b57d0ed3aa3e47b317cbdbdee6 Mon Sep 17 00:00:00 2001 From: daria Date: Mon, 17 Mar 2025 14:11:18 +0200 Subject: [PATCH 80/83] add error messages --- customize.dist/messages.js | 2 ++ www/admin/inner.js | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/customize.dist/messages.js b/customize.dist/messages.js index 469279baf..38206fdbf 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -144,6 +144,8 @@ define(req, function(AppConfig, Default, Language) { Messages.admin_addAdminsHint = "Add administrators from their public key or from your contacts list"; Messages.admin_addAdminsAdd = "Promote a contact to admin"; Messages.admin_addKeyLabel = "Add an admin using their public key"; + Messages.admin_errorAddKeyLabel = "Add a valid public key"; + Messages.admin_errorAddAdminsAdd = "Pick a contact to promote to admin"; Messages.admin_listName = "Admin name"; Messages.admin_listKey = "Admin key"; diff --git a/www/admin/inner.js b/www/admin/inner.js index 7c0a157d0..7d53ba7a2 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -346,7 +346,9 @@ define([ Util.onClickEnter($keyBtn, () => { let val = $keyInput.val().trim(); let key = Keys.canonicalize(val); - if (!key) { return; } + if (!key) { + UI.warn(Messages.admin_errorAddKeyLabel); + return; } // We have a valid key let name = Messages.admin_admin; try { @@ -388,6 +390,9 @@ define([ let addBtn = blocks.button('primary', 'fa-plus', Messages.tag_add); Util.onClickEnter($(addBtn), () => { var $sel = $(contactsGrid.div).find('.cp-usergrid-user.cp-selected'); + if (!$sel.length) { + UI.warn(Messages.admin_errorAddAdminsAdd); + return; } nThen((waitFor) => { $sel.each((i, el) => { const $el = $(el); From fb19a8d66793f8785c8d595b4b44168352f02d15 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 17 Mar 2025 14:41:00 +0100 Subject: [PATCH 81/83] Update dependencies --- package-lock.json | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0b7ca9ccc..9a8f9936b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "cryptpad", - "version": "2024.12.0", + "version": "2025.3.0", "license": "AGPL-3.0+", "dependencies": { "@mcrowe/minibloom": "^0.2.0", @@ -45,7 +45,7 @@ "notp": "^2.0.3", "nthen": "0.1.8", "open-sans-fontface": "^1.4.0", - "openid-client": "^5.7.0", + "openid-client": "^5.7.1", "pako": "^2.1.0", "prompt-confirm": "^2.0.4", "pull-stream": "^3.6.1", @@ -5668,9 +5668,10 @@ } }, "node_modules/xml-crypto": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-3.2.0.tgz", - "integrity": "sha512-qVurBUOQrmvlgmZqIVBqmb06TD2a/PpEUfFPgD7BuBfjmoH4zgkqaWSIJrnymlCvM2GGt9x+XtJFA+ttoAufqg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-3.2.1.tgz", + "integrity": "sha512-0GUNbPtQt+PLMsC5HoZRONX+K6NBJEqpXe/lsvrFj0EqfpGPpVfJKGE7a5jCg8s2+Wkrf/2U1G41kIH+zC9eyQ==", + "license": "MIT", "dependencies": { "@xmldom/xmldom": "^0.8.8", "xpath": "0.0.32" From bd418f780738040abf535b45ec5c0e6715ddedd6 Mon Sep 17 00:00:00 2001 From: daria Date: Mon, 17 Mar 2025 16:10:06 +0200 Subject: [PATCH 82/83] fix error messages --- customize.dist/messages.js | 2 +- www/admin/inner.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/customize.dist/messages.js b/customize.dist/messages.js index 38206fdbf..e4befa66d 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -145,7 +145,7 @@ define(req, function(AppConfig, Default, Language) { Messages.admin_addAdminsAdd = "Promote a contact to admin"; Messages.admin_addKeyLabel = "Add an admin using their public key"; Messages.admin_errorAddKeyLabel = "Add a valid public key"; - Messages.admin_errorAddAdminsAdd = "Pick a contact to promote to admin"; + Messages.admin_errorAddAdmins = "Pick a contact to promote to admin"; Messages.admin_listName = "Admin name"; Messages.admin_listKey = "Admin key"; diff --git a/www/admin/inner.js b/www/admin/inner.js index 7d53ba7a2..9c2b531d7 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -347,8 +347,8 @@ define([ let val = $keyInput.val().trim(); let key = Keys.canonicalize(val); if (!key) { - UI.warn(Messages.admin_errorAddKeyLabel); - return; } + return UI.warn(Messages.admin_errorAddKeyLabel); + } // We have a valid key let name = Messages.admin_admin; try { @@ -391,8 +391,8 @@ define([ Util.onClickEnter($(addBtn), () => { var $sel = $(contactsGrid.div).find('.cp-usergrid-user.cp-selected'); if (!$sel.length) { - UI.warn(Messages.admin_errorAddAdminsAdd); - return; } + return UI.warn(Messages.admin_errorAddAdmins); + } nThen((waitFor) => { $sel.each((i, el) => { const $el = $(el); From e9c9fc517af9bd2896386c2c1ad918bc37f0c70e Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 17 Mar 2025 15:22:20 +0100 Subject: [PATCH 83/83] Fix add multiple admins using key --- www/admin/inner.js | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/www/admin/inner.js b/www/admin/inner.js index d24a462fd..e75bd3b4f 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -342,27 +342,6 @@ define([ } } }); - const $keyBtn = $(keyButton); - Util.onClickEnter($keyBtn, () => { - let val = $keyInput.val().trim(); - let key = Keys.canonicalize(val); - if (!key) { return; } - // We have a valid key - let name = Messages.admin_admin; - try { - let parsed = Keys.parseUser(val); - name = parsed.user; - } catch (e) {} - $keyBtn.prop('disabled', 'disabled'); - addAdmin({ ed:key, name }, (err) => { - $keyBtn.prop('disabled', false); - if (!err) { $keyInput.val(''); } - // refresh - APP.updateStatus(function () { - evRefreshAdmins.fire(); - }); - }); - }); const drawContacts = () => { $div.empty(); @@ -402,7 +381,6 @@ define([ }).nThen(() => { APP.updateStatus(function () { evRefreshAdmins.fire(); - drawContacts(); }); }); }); @@ -410,6 +388,28 @@ define([ drawContacts(); }); + const $keyBtn = $(keyButton); + Util.onClickEnter($keyBtn, () => { + let val = $keyInput.val().trim(); + let key = Keys.canonicalize(val); + if (!key) { return; } + // We have a valid key + let name = Messages.admin_admin; + try { + let parsed = Keys.parseUser(val); + name = parsed.user; + } catch (e) {} + $keyBtn.prop('disabled', 'disabled'); + addAdmin({ ed:key, name }, (err) => { + $keyBtn.prop('disabled', false); + if (!err) { $keyInput.val(''); } + // refresh + APP.updateStatus(function () { + evRefreshAdmins.fire(); + }); + }); + }); + const list = blocks.form([ //currentList.div, contactsGrid.div,