From da514ce5fc0475812fdf82eb354ae6b9e0979549 Mon Sep 17 00:00:00 2001 From: Wolfgang Ginolas Date: Tue, 1 Oct 2024 14:58:24 +0200 Subject: [PATCH 001/281] 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 002/281] 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 003/281] 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 004/281] 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 005/281] 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 006/281] 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 007/281] 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 008/281] 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 009/281] 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 010/281] 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 011/281] 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 012/281] 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 f458fc105f981486b42fb119fb978391663e56c1 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Tue, 26 Nov 2024 14:11:56 +0100 Subject: [PATCH 013/281] Added functionality to move Kanban boards with arrow buttons --- www/kanban/inner.js | 65 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/www/kanban/inner.js b/www/kanban/inner.js index 947092c59..737e19798 100644 --- a/www/kanban/inner.js +++ b/www/kanban/inner.js @@ -559,6 +559,71 @@ define([ e.stopPropagation(); }).appendTo($(el).find('.kanban-board-header')); }); + $container.find('.kanban-board').each(function (i, el) { + $('