From 9be21c21c46bb65bf92c279f33014f1cd7b80afa Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 29 Aug 2023 15:19:34 +0200 Subject: [PATCH 001/218] WIP blob metadata --- lib/commands/metadata.js | 6 ++++- lib/hk-util.js | 2 ++ lib/storage/blob.js | 50 +++++++++++++++++++++++++++++++++++++++- lib/workers/db-worker.js | 1 + 4 files changed, 57 insertions(+), 2 deletions(-) diff --git a/lib/commands/metadata.js b/lib/commands/metadata.js index 9b6a23e02..70a811664 100644 --- a/lib/commands/metadata.js +++ b/lib/commands/metadata.js @@ -10,7 +10,8 @@ Data.getMetadataRaw = function (Env, channel /* channelName */, _cb) { const cb = Util.once(Util.mkAsync(_cb)); if (!Core.isValidId(channel)) { return void cb('INVALID_CHAN'); } if (channel.length !== HK.STANDARD_CHANNEL_LENGTH && - channel.length !== HK.ADMIN_CHANNEL_LENGTH) { return cb("INVALID_CHAN_LENGTH"); } + channel.length !== HK.ADMIN_CHANNEL_LENGTH && + channel.length !== HK.BLOB_ID_LENGTH) { return cb("INVALID_CHAN_LENGTH"); } // return synthetic metadata for admin broadcast channels as a safety net // in case anybody manages to write metadata @@ -76,6 +77,7 @@ Data.setMetadata = function (Env, safeKey, data, cb, Server) { var channel = data.channel; var command = data.command; + // XXX BLOBMD allow blobs if (!channel || !Core.isValidId(channel)) { return void cb ('INVALID_CHAN'); } if (!command || typeof (command) !== 'string') { return void cb('INVALID_COMMAND'); } if (Meta.commands.indexOf(command) === -1) { return void cb('UNSUPPORTED_COMMAND'); } @@ -137,6 +139,7 @@ Data.setMetadata = function (Env, safeKey, data, cb, Server) { cb(void 0, metadata); return void next(); } + // XXX BLOBMD use correct store for blobs Env.msgStore.writeMetadata(channel, JSON.stringify(line), function (e) { if (e) { cb(e); @@ -152,6 +155,7 @@ Data.setMetadata = function (Env, safeKey, data, cb, Server) { // update the cached metadata metadata_cache[channel] = metadata; + Env.checkCache(channel); // XXX ??? // it's easy to check if the channel is restricted const isRestricted = metadata.restricted; diff --git a/lib/hk-util.js b/lib/hk-util.js index 455964ccb..472e9efb1 100644 --- a/lib/hk-util.js +++ b/lib/hk-util.js @@ -40,6 +40,8 @@ const ADMIN_CHANNEL_LENGTH = HK.ADMIN_CHANNEL_LENGTH = 33; // with a 34 character id const EPHEMERAL_CHANNEL_LENGTH = HK.EPHEMERAL_CHANNEL_LENGTH = 34; +const BLOB_ID_LENGTH = HK.BLOB_ID_LENGTH = 48; + // Temporary channels are archived X ms after everyone has left them const TEMPORARY_CHANNEL_LIFETIME = 30 * 1000; diff --git a/lib/storage/blob.js b/lib/storage/blob.js index 7d55676d7..9126c761c 100644 --- a/lib/storage/blob.js +++ b/lib/storage/blob.js @@ -7,6 +7,11 @@ var BlobStore = module.exports; var nThen = require("nthen"); var Semaphore = require("saferphore"); var Util = require("../common-util"); +var Meta = require("../metadata"); + +const BatchRead = require("../batch-read"); +const readFileBin = require("../stream-file").readFileBin; +const Schedule = require("../schedule"); var isValidSafeKey = function (safeKey) { return typeof(safeKey) === 'string' && !/\//.test(safeKey) && safeKey.length === 44; @@ -26,11 +31,16 @@ var prependArchive = function (Env, path) { return Path.join(Env.archivePath, 'blob', relativePathToBlob); }; -// /blob//// +// /blob// var makeBlobPath = function (Env, blobId) { return Path.join(Env.blobPath, blobId.slice(0, 2), blobId); }; +// /blob//.metadata.ndjson +var mkMetadataPath = function (env, channelId) { + return Path.join(env.root, channelId.slice(0, 2), channelId) + '.metadata.ndjson'; +}; + // /blobstate// var makeStagePath = function (Env, safeKey) { return Path.join(Env.blobStagingPath, safeKey.slice(0, 2), safeKey); @@ -355,6 +365,43 @@ var restoreProof = function (Env, safeKey, blobId, cb) { Fse.move(archivePath, proofPath, cb); }; +var getDedicatedMetadata = function (env, blobId, handler, _cb) { + var metadataPath = mkMetadataPath(env, blobId); + var stream = Fs.createReadStream(metadataPath, {start: 0}); + + const collector = createIdleStreamCollector(stream); + var cb = Util.both(_cb, collector); + + readFileBin(stream, function (msgObj, readMore) { + collector.keepAlive(); + var line = msgObj.buff.toString('utf8'); + try { + var parsed = JSON.parse(line); + handler(null, parsed); + } catch (err) { + handler(err, line); + } + readMore(); + }, function (err) { + // ENOENT => there is no metadata log + if (!err || err.code === 'ENOENT') { return void cb(); } + // otherwise stream errors? + cb(err); + }); +}; +/* readMetadata + Load the log of metadata amendments. +*/ +var readMetadata = function (Env, blobId, handler, cb) { + getDedicatedMetadata(env, channelId, handler, function (err) { + if (err) { + // stream errors? + return void cb(err); + } + cb(); + }); +}; + var makeWalker = function (n, handleChild, done) { if (!n || typeof(n) !== 'number' || n < 2) { n = 2; } @@ -486,6 +533,7 @@ BlobStore.create = function (config, _cb) { archivePath: config.archivePath || './data/archive', getSession: config.getSession, }; + var schedule = Env.schedule = Schedule(); nThen(function (w) { var CB = Util.both(w.abort, cb); diff --git a/lib/workers/db-worker.js b/lib/workers/db-worker.js index d0b460b11..adf21456a 100644 --- a/lib/workers/db-worker.js +++ b/lib/workers/db-worker.js @@ -307,6 +307,7 @@ const computeIndex = function (data, cb) { const computeMetadata = function (data, cb) { const ref = {}; const lineHandler = Meta.createLineHandler(ref, Env.Log.error); + // XXX BLOBMD use correct store return void store.readChannelMetadata(data.channel, lineHandler, function (err) { if (err) { // stream errors? From 3940b9c4be40402d50ed7de0fce49a2cea75a2cd Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 15 May 2024 11:17:25 +0200 Subject: [PATCH 002/218] Fix checkpoint issues with integrated OO apps --- www/common/onlyoffice/inner.js | 14 +++++++++----- www/common/outer/async-store.js | 32 ++++++++++++++++++++++++++------ www/cryptpad-api.js | 2 ++ 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 9faa7afc2..5184833c7 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -392,6 +392,10 @@ define([ }; var onUploaded = function (ev, data, err) { + if (!ev && err) { + console.error(err); + return void UI.warn(Messages.error); + } if (ev.newTemplate) { if (err) { console.error(err); @@ -549,7 +553,7 @@ define([ var saveToServer = function (blob, title) { if (APP.cantCheckpoint) { return; } // TOO_LARGE - var text = getContent(); + var text = !blob && getContent(); if (!text && !blob) { setEditable(false, true); sframeChan.query('Q_CLEAR_CACHE_CHANNELS', [ @@ -3154,9 +3158,9 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null return void UI.errorLoadingScreen(Messages.error); } var blob = new Blob([bin], {type: 'text/plain'}); - var file = getFileType(); - resetData(blob, file); - //saveToServer(blob, title); + //var file = getFileType(); + //resetData(blob, file); + saveToServer(blob, title); Title.updateTitle(title); UI.removeLoadingScreen(); }); @@ -3205,7 +3209,7 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null cb(); }); }); - if (privateData.initialState) { + if (privateData.initialState && (!content || !content.hashes)) { var blob = privateData.initialState; let title = `document.${cfg.fileType}`; console.error(blob, title); diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index cd93675fe..9ba3853e0 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -467,6 +467,19 @@ define([ }); }; + var initTempRpc = (clientId, cb) => { + if (store.rpc) { return void cb(store.rpc); } + var kp = Crypto.Nacl.sign.keyPair(); + var keys = { + edPublic: Crypto.Nacl.util.encodeBase64(kp.publicKey), + edPrivate: Crypto.Nacl.util.encodeBase64(kp.secretKey) + }; + Pinpad.create(store.network, keys, function (e, call) { + if (e) { return void cb({error: e}); } + store.rpc = call; + cb(call); + }); + }; var initRpc = function (clientId, data, cb) { if (!store.loggedIn) { return cb(); } if (store.rpc) { return void cb(account); } @@ -3149,7 +3162,7 @@ define([ // If we load CryptPad for the first time from an existing pad, don't create a // drive automatically. - var onNoDrive = function (clientId, cb) { + var onNoDrive = function (clientId, cb, initRpc) { var andThen = function () { // To be able to use all the features inside the pad, we need to // initialize the chat (messenger) and the cursor modules. @@ -3160,9 +3173,16 @@ define([ store.messenger = store.modules['messenger']; // And now we're ready - initAnonRpc(null, null, function () { - cb({}); - }); + let getAnon = () => { + initAnonRpc(null, null, function () { + cb({}); + }); + }; + + if (initRpc) { + return initTempRpc(clientId, getAnon); + } + getAnon(); }; // We need an anonymous RPC to be able to check if the pad exists and to get @@ -3245,7 +3265,7 @@ define([ // First tab, no user hash, no anon hash and this app doesn't need a drive // ==> don't create a drive // Or "neverDrive" (integration into another platform?) - // ==> don't create a drive + // ==> don't create a drive BUT create temp RPC (we may need to upload) if (data.neverDrive || (data.noDrive && !data.userHash && !data.anonHash)) { return void onNoDrive(clientId, function (obj) { if (obj && obj.error) { @@ -3259,7 +3279,7 @@ define([ } Feedback.send("NO_DRIVE", true); callback(obj); - }); + }, !!data.neverDrive); } initialized = true; diff --git a/www/cryptpad-api.js b/www/cryptpad-api.js index e73ef40bd..cdbf7f8dd 100644 --- a/www/cryptpad-api.js +++ b/www/cryptpad-api.js @@ -188,6 +188,7 @@ chan.on('ON_DOWNLOADAS', blob => { let url = URL.createObjectURL(blob); + if (!config.events.onDownloadAs) { return; } config.events.onDownloadAs({ data: { fileType: config.document && config.document.fileType, @@ -198,6 +199,7 @@ chan.on('SAVE', function (data, cb) { blob = data; + if (!config.events.onSave) { return void cb(); } config.events.onSave(data, cb); }); chan.on('RELOAD', function () { From 154b3e66c84d3611e628e3925928282b4d2bd693 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 15 May 2024 11:37:03 +0200 Subject: [PATCH 003/218] Get username and lang from API config --- customize.dist/messages.js | 7 +++---- www/common/onlyoffice/inner.js | 8 ++++++-- www/common/sframe-app-outer.js | 4 +++- www/cryptpad-api.js | 3 ++- www/integration/main.js | 10 ++++++++-- 5 files changed, 22 insertions(+), 10 deletions(-) diff --git a/customize.dist/messages.js b/customize.dist/messages.js index 579dcd31e..0fb495bb2 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -35,11 +35,10 @@ var getStoredLanguage = function () { return localStorage && localStorage.getIte var getBrowserLanguage = function () { return navigator.language || navigator.userLanguage || ''; }; var getLanguage = Messages._getLanguage = function () { if (window.cryptpadLanguage) { return window.cryptpadLanguage; } - try { - if (getStoredLanguage()) { return getStoredLanguage(); } - } catch (e) { console.log(e); } var l = getBrowserLanguage(); - // Edge returns 'fr-FR' --> transform it to 'fr' and check again + try { + l = getStoredLanguage() || getBrowserLanguage(); + } catch (e) { console.log(e); } return map[l] ? l : (map[l.split('-')[0]] ? l.split('-')[0] : (map[l.split('_')[0]] ? l.split('_')[0] : 'en')); diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 5184833c7..dfa949eb6 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -1671,6 +1671,10 @@ define([ var lang = (window.cryptpadLanguage || navigator.language || navigator.userLanguage || '').slice(0,2); + let username = Util.find(privateData, ['integrationConfig', 'user', 'name']) + || metadataMgr.getUserData().name + || Messages.anonymous; + // Config APP.ooconfig = { "document": { @@ -1694,8 +1698,8 @@ define([ }, "user": { "id": String(myOOId), //"c0c3bf82-20d7-4663-bf6d-7fa39c598b1d", - "firstname": metadataMgr.getUserData().name || Messages.anonymous, - "name": metadataMgr.getUserData().name || Messages.anonymous, + "firstname": username, + "name": username }, "mode": "edit", "lang": lang diff --git a/www/common/sframe-app-outer.js b/www/common/sframe-app-outer.js index b1210796f..b634f51b4 100644 --- a/www/common/sframe-app-outer.js +++ b/www/common/sframe-app-outer.js @@ -17,7 +17,9 @@ define([ nThen(function (waitFor) { DomReady.onReady(waitFor()); }).nThen(function (waitFor) { - var obj = SFCommonO.initIframe(waitFor, true, integration.pathname); + let lang = integration && integration.config && integration.config.editorConfig + && integration.config.editorConfig.lang; + var obj = SFCommonO.initIframe(waitFor, true, integration.pathname, lang); href = obj.href; hash = obj.hash; if (isIntegration) { diff --git a/www/cryptpad-api.js b/www/cryptpad-api.js index cdbf7f8dd..d7d2ac7e2 100644 --- a/www/cryptpad-api.js +++ b/www/cryptpad-api.js @@ -116,7 +116,8 @@ application: config.documentType, document: blob, ext: config.document.fileType, - autosave: config.autosave || 10 + autosave: config.autosave || 10, + editorConfig: config.editorConfig || {} }, function (obj) { if (obj && obj.error) { reject(obj.error); return console.error(obj.error); } resolve({}); diff --git a/www/integration/main.js b/www/integration/main.js index fe3ffd010..a09592d3b 100644 --- a/www/integration/main.js +++ b/www/integration/main.js @@ -155,7 +155,12 @@ define([ chan.on('START', function (data) { console.warn('INNER START', data); var href = Hash.hashToHref(data.key, data.application); - console.error(Hash.hrefToHexChannelId(href)); + + if (data.editorConfig.lang) { + var LS_LANG = "CRYPTPAD_LANG"; + localStorage.setItem(LS_LANG, data.editorConfig.lang); + } + window.CP_integration_outer = { pathname: `/${data.application}/`, hash: data.key, @@ -163,7 +168,8 @@ define([ initialState: data.document, config: { fileType: data.ext, - autosave: data.autosave + autosave: data.autosave, + user: data.editorConfig.user }, utils: { onReady: onReady, From 112a222f79f7bf82db96404b1ce73844789494cc Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 21 May 2024 18:05:03 +0200 Subject: [PATCH 004/218] Fix initial OO content issue with API --- www/common/onlyoffice/inner.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index dfa949eb6..9ffbfd47b 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -3213,10 +3213,9 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null cb(); }); }); - if (privateData.initialState && (!content || !content.hashes)) { + if (privateData.initialState && (!content || !content.hashes || !Object.keys(content.hashes).length)) { var blob = privateData.initialState; let title = `document.${cfg.fileType}`; - console.error(blob, title); return convertImportBlob(blob, title); } } From 97a806353e911efb3e1c3bc1ce8dca3c33c5d0aa Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 24 Jun 2024 16:18:27 +0200 Subject: [PATCH 005/218] Fix issue with application names --- www/cryptpad-api.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/www/cryptpad-api.js b/www/cryptpad-api.js index d7d2ac7e2..c72f41f65 100644 --- a/www/cryptpad-api.js +++ b/www/cryptpad-api.js @@ -251,16 +251,22 @@ cryptpadURL = getInstanceURL(); } + config.events = config.events || {}; + // OnlyOffice shim let url = config.document.url; if (/^http:\/\/localhost\/cache\/files\//.test(url)) { url = url.replace(/(http:\/\/localhost\/cache\/files\/)/, getInstanceURL() + 'ooapi/'); } config.document.url = url; - if (config.documentType === "spreadsheet") { + if (config.documentType === "spreadsheet" || config.documentType === "cell") { config.documentType = "sheet"; } - if (config.documentType === "text") { + + if (config.documentType === "slide") { + config.documentType = "presentation"; + } + if (config.documentType === "word" || config.documentType === "text") { config.documentType = "doc"; } @@ -311,8 +317,8 @@ iframe.setAttribute('name', 'frameEditor'); iframe.setAttribute('align', 'top'); iframe.setAttribute("src", url); - iframe.setAttribute("width", config.width); - iframe.setAttribute("height", config.height); + iframe.setAttribute("width", config.width || '100%'); + iframe.setAttribute("height", config.height || '100%'); if (config.editorConfig) { // OnlyOffice container.replaceWith(iframe); container = iframe; From 4ff5f0f94b8e212bff01835f8283e2f434376e6a Mon Sep 17 00:00:00 2001 From: DianaXWiki <139217939+DianaXWiki@users.noreply.github.com> Date: Sun, 4 Aug 2024 00:53:47 +0300 Subject: [PATCH 006/218] Enable toggle in and out of calendars #1371 --- www/calendar/app-calendar.less | 12 +++++++ www/calendar/inner.js | 57 +++++++++++++++++++++++----------- 2 files changed, 51 insertions(+), 18 deletions(-) diff --git a/www/calendar/app-calendar.less b/www/calendar/app-calendar.less index bdf59fb6d..742076764 100644 --- a/www/calendar/app-calendar.less +++ b/www/calendar/app-calendar.less @@ -585,6 +585,18 @@ margin-top: 30px; } } + @media screen and (max-width: @browser_media-medium-screen) { + .cp-calendar-entries { + display: none; + } + + .cp-calendar-entries.visible { + display: block; + } + } + .cp-calendar-entries { + margin-bottom: 10px; + } .cp-calendar-entry { display: flex; align-items: center; diff --git a/www/calendar/inner.js b/www/calendar/inner.js index 9cac57978..1770ce294 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -818,7 +818,17 @@ define([ }); } if (APP.$calendars) { APP.$calendars.append(calendar); } - return calendar; + return $calendar; // return jQuery element + }; + + var appendCalendarEntries = function (teamId, filter) { + var calendars = filter(teamId); + var $entriesContainer = $('
'); + calendars.forEach(function (id) { + var calendarEntry = makeCalendarEntry(id, teamId); + $entriesContainer.append(calendarEntry); + }); + return $entriesContainer; }; var makeLeftside = function (calendar, $container) { // Show calendars @@ -847,10 +857,10 @@ define([ }; var tempCalendars = filter(0); if (tempCalendars.length && tempCalendars[0] === APP.currentCalendar) { - APP.$calendars.append(h('div.cp-calendar-team', [ + var $tempCalendarTeam = $(h('div.cp-calendar-team', [ h('span', Messages.calendar_tempCalendar) - ])); - makeCalendarEntry(tempCalendars[0], 0); + ])).appendTo(APP.$calendars); + var $tempCalendarEntries = appendCalendarEntries(0, filter).appendTo(APP.$calendars); var importTemp = h('button', [ h('i.fa.fa-calendar-plus-o'), h('span', Messages.calendar_import_temp), @@ -868,7 +878,13 @@ define([ }); }); if (APP.loggedIn) { - APP.$calendars.append(h('div.cp-calendar-entry.cp-ghost', importTemp)); + $tempCalendarEntries.append(h('div.cp-calendar-entry.cp-ghost', importTemp)); + } + //on small screens toggle in and out + if (window.innerWidth <= 600) { + $tempCalendarTeam.click(function () { + $tempCalendarEntries.toggleClass('visible'); + }); } return; } @@ -878,16 +894,18 @@ define([ var avatar = h('span.cp-avatar'); var uid = user.uid; var name = user.name || Messages.anonymous; - common.displayAvatar($(avatar), user.avatar, name, function(){}, uid); - APP.$calendars.append(h('div.cp-calendar-team', [ + common.displayAvatar($(avatar), user.avatar, name, function () { }, uid); + var $myCalendarTeam = $(h('div.cp-calendar-team', [ avatar, - h('span.cp-name', {title: name}, name) - ])); + h('span.cp-name', { title: name }, name) + ])).appendTo(APP.$calendars); + var $myCalendarEntries = appendCalendarEntries(1, filter).appendTo(APP.$calendars); + if (window.innerWidth <= 600) { + $myCalendarTeam.click(function () { + $myCalendarEntries.toggleClass('visible'); + }); + } } - myCalendars.forEach(function (id) { - makeCalendarEntry(id, 1); - }); - // Add new button var $newContainer = $(h('div.cp-calendar-entry.cp-ghost')).appendTo($calendars); var newButton = h('button', [ @@ -905,13 +923,16 @@ define([ var team = privateData.teams[teamId]; var avatar = h('span.cp-avatar'); common.displayAvatar($(avatar), team.avatar, team.displayName || team.name); - APP.$calendars.append(h('div.cp-calendar-team', [ + var $teamCalendarTeam = $(h('div.cp-calendar-team', [ avatar, h('span.cp-name', {title: team.name}, team.name) - ])); - calendars.forEach(function (id) { - makeCalendarEntry(id, teamId); - }); + ])).appendTo(APP.$calendars); + var $teamCalendarEntries = appendCalendarEntries(teamId, filter).appendTo(APP.$calendars); + if (window.innerWidth <= 600) { + $teamCalendarTeam.click(function () { + $teamCalendarEntries.toggleClass('visible'); + }); + } }); }); onCalendarsUpdate.fire(); From 0911413604d56f7d9c4a819580bc5055a6e83695 Mon Sep 17 00:00:00 2001 From: DianaXWiki <139217939+DianaXWiki@users.noreply.github.com> Date: Fri, 9 Aug 2024 17:37:33 +0300 Subject: [PATCH 007/218] Add toggle visibility button for calendars on small screens #1371 --- www/calendar/inner.js | 81 +++++++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index 1770ce294..a998d979e 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -880,12 +880,6 @@ define([ if (APP.loggedIn) { $tempCalendarEntries.append(h('div.cp-calendar-entry.cp-ghost', importTemp)); } - //on small screens toggle in and out - if (window.innerWidth <= 600) { - $tempCalendarTeam.click(function () { - $tempCalendarEntries.toggleClass('visible'); - }); - } return; } var myCalendars = filter(1); @@ -895,19 +889,60 @@ define([ var uid = user.uid; var name = user.name || Messages.anonymous; common.displayAvatar($(avatar), user.avatar, name, function () { }, uid); - var $myCalendarTeam = $(h('div.cp-calendar-team', [ + APP.$calendars.append(h('div.cp-calendar-team', [ avatar, - h('span.cp-name', { title: name }, name) - ])).appendTo(APP.$calendars); - var $myCalendarEntries = appendCalendarEntries(1, filter).appendTo(APP.$calendars); + h('span.cp-name', {title: name}, name) + ])); if (window.innerWidth <= 600) { - $myCalendarTeam.click(function () { + var $showCalendarsContainer = $('
').appendTo($calendars); + var showCalendarsBtn = h('button.cp-calendar-showcalendars', [ + h('i.fa.fa-eye'), + h('span', 'Show calendars'), + h('span') + ]); + var $myCalendarEntries = appendCalendarEntries(1, filter).appendTo(APP.$calendars); + $(showCalendarsBtn).click(function (e) { + e.preventDefault(); $myCalendarEntries.toggleClass('visible'); + }).appendTo($showCalendarsContainer); + } else { + myCalendars.forEach(function (id) { + makeCalendarEntry(id, 1); }); } } + Object.keys(privateData.teams).sort().forEach(function (teamId) { + var calendars = filter(teamId); + if (!calendars.length) { return; } + var team = privateData.teams[teamId]; + var avatar = h('span.cp-avatar'); + common.displayAvatar($(avatar), team.avatar, team.displayName || team.name); + APP.$calendars.append(h('div.cp-calendar-team', [ + avatar, + h('span.cp-name', {title: team.name}, team.name) + ])); + if (window.innerWidth <= 600) { + // Add show calendars button for each team + var $showCalendarsContainer = $('
').appendTo($calendars); + var showCalendarsBtn = h('button.cp-calendar-showcalendars', [ + h('i.fa.fa-eye'), + h('span', 'Show calendars'), + h('span') + ]); + var $teamCalendarEntries = appendCalendarEntries(teamId, filter).appendTo(APP.$calendars); + $(showCalendarsBtn).click(function (e) { + e.preventDefault(); + $teamCalendarEntries.toggleClass('visible'); + }).appendTo($showCalendarsContainer); + } else { + calendars.forEach(function (id) { + makeCalendarEntry(id, 1); + }); + } + + }); // Add new button - var $newContainer = $(h('div.cp-calendar-entry.cp-ghost')).appendTo($calendars); + var $newContainer = $('
').appendTo($calendars); var newButton = h('button', [ h('i.fa.fa-calendar-plus-o'), h('span', Messages.calendar_new), @@ -916,27 +951,9 @@ define([ $(newButton).click(function () { editCalendar(); }).appendTo($newContainer); - - Object.keys(privateData.teams).sort().forEach(function (teamId) { - var calendars = filter(teamId); - if (!calendars.length) { return; } - var team = privateData.teams[teamId]; - var avatar = h('span.cp-avatar'); - common.displayAvatar($(avatar), team.avatar, team.displayName || team.name); - var $teamCalendarTeam = $(h('div.cp-calendar-team', [ - avatar, - h('span.cp-name', {title: team.name}, team.name) - ])).appendTo(APP.$calendars); - var $teamCalendarEntries = appendCalendarEntries(teamId, filter).appendTo(APP.$calendars); - if (window.innerWidth <= 600) { - $teamCalendarTeam.click(function () { - $teamCalendarEntries.toggleClass('visible'); - }); - } - }); }); - onCalendarsUpdate.fire(); + onCalendarsUpdate.fire(); }; var _updateRecurring = function () { @@ -1296,7 +1313,6 @@ ICS ==> create a new event with the same UID and a RECURRENCE-ID field (with a v store.put('calendarView', mode, function () {}); }); APP.toolbar.$bottomR.append($block); - // New event button var newEventBtn = h('button.cp-calendar-newevent', [ h('i.fa.fa-plus'), @@ -1306,7 +1322,6 @@ ICS ==> create a new event with the same UID and a RECURRENCE-ID field (with a v e.preventDefault(); cal.openCreationPopup({isAllDay:false}); }).appendTo(APP.toolbar.$bottomL); - // Change page var goLeft = h('button.fa.fa-chevron-left',{'aria-label': Messages.goLeft}); var goRight = h('button.fa.fa-chevron-right', {'aria-label': Messages.goRight}); From 479ff02b5905291c516b0d65281bffceb54400ff Mon Sep 17 00:00:00 2001 From: DianaXWiki <139217939+DianaXWiki@users.noreply.github.com> Date: Fri, 9 Aug 2024 17:41:00 +0300 Subject: [PATCH 008/218] Add translation key #1371 --- customize.dist/messages.js | 2 +- www/calendar/inner.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/customize.dist/messages.js b/customize.dist/messages.js index a5784b495..eaffcadaf 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -135,7 +135,7 @@ define(req, function(AppConfig, Default, Language) { return text; } }; - + Messages.calendar_show = 'Show calendars'; // XXX return Messages; }); diff --git a/www/calendar/inner.js b/www/calendar/inner.js index a998d979e..fad45cbc4 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -897,7 +897,7 @@ define([ var $showCalendarsContainer = $('
').appendTo($calendars); var showCalendarsBtn = h('button.cp-calendar-showcalendars', [ h('i.fa.fa-eye'), - h('span', 'Show calendars'), + h('span', Messages.calendar_show), h('span') ]); var $myCalendarEntries = appendCalendarEntries(1, filter).appendTo(APP.$calendars); @@ -926,7 +926,7 @@ define([ var $showCalendarsContainer = $('
').appendTo($calendars); var showCalendarsBtn = h('button.cp-calendar-showcalendars', [ h('i.fa.fa-eye'), - h('span', 'Show calendars'), + h('span', Messages.calendar_show), h('span') ]); var $teamCalendarEntries = appendCalendarEntries(teamId, filter).appendTo(APP.$calendars); From 82846f185136341cb8f33c86149b57e3c3ccdad3 Mon Sep 17 00:00:00 2001 From: DianaXWiki <139217939+DianaXWiki@users.noreply.github.com> Date: Tue, 27 Aug 2024 15:12:13 +0300 Subject: [PATCH 009/218] Change button to 'Hide calendars' when list is shown #1371 --- customize.dist/messages.js | 1 + www/calendar/inner.js | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/customize.dist/messages.js b/customize.dist/messages.js index eaffcadaf..bb2073466 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -136,6 +136,7 @@ define(req, function(AppConfig, Default, Language) { } }; Messages.calendar_show = 'Show calendars'; // XXX + Messages.calendar_hide = 'Hide calendars'; // XXX return Messages; }); diff --git a/www/calendar/inner.js b/www/calendar/inner.js index fad45cbc4..73c8c6ee0 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -897,13 +897,18 @@ define([ var $showCalendarsContainer = $('
').appendTo($calendars); var showCalendarsBtn = h('button.cp-calendar-showcalendars', [ h('i.fa.fa-eye'), - h('span', Messages.calendar_show), + h('span', Messages.calendar_show), // Initial text: "Show Calendars" h('span') ]); var $myCalendarEntries = appendCalendarEntries(1, filter).appendTo(APP.$calendars); $(showCalendarsBtn).click(function (e) { e.preventDefault(); - $myCalendarEntries.toggleClass('visible'); + $myCalendarEntries.toggle(); + if ($myCalendarEntries.is(':visible')) { + $(this).find('span').first().text(Messages.calendar_hide); + } else { + $(this).find('span').first().text(Messages.calendar_show); + } }).appendTo($showCalendarsContainer); } else { myCalendars.forEach(function (id) { @@ -932,7 +937,12 @@ define([ var $teamCalendarEntries = appendCalendarEntries(teamId, filter).appendTo(APP.$calendars); $(showCalendarsBtn).click(function (e) { e.preventDefault(); - $teamCalendarEntries.toggleClass('visible'); + $teamCalendarEntries.toggle(); + if ($teamCalendarEntries.is(':visible')) { + $(this).find('span').first().text(Messages.calendar_hide); + } else { + $(this).find('span').first().text(Messages.calendar_show); + } }).appendTo($showCalendarsContainer); } else { calendars.forEach(function (id) { From 751d37a097b53a015df5ac3063504a8969d78d0c Mon Sep 17 00:00:00 2001 From: DianaXWiki <139217939+DianaXWiki@users.noreply.github.com> Date: Thu, 29 Aug 2024 16:02:36 +0300 Subject: [PATCH 010/218] Make button appear/disappear automatically when resizing #1371 --- www/calendar/inner.js | 105 +++++++++++++++++++++--------------------- 1 file changed, 52 insertions(+), 53 deletions(-) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index 73c8c6ee0..80ce6895e 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -834,15 +834,17 @@ define([ // Show calendars var calendars = h('div.cp-calendar-list'); var $calendars = APP.$calendars = $(calendars).appendTo($container); - onCalendarsUpdate.reg(function () { + var isMobileView = window.innerWidth <= 600; + + function updateCalendarsView() { $calendars.empty(); var privateData = metadataMgr.getPrivateData(); var filter = function (teamId) { var LOOKUP = {}; return Object.keys(APP.calendars || {}).filter(function (id) { var cal = APP.calendars[id] || {}; - var teams = (cal.teams || []).map(function (tId) { return Number(tId); }); - return teams.indexOf(typeof(teamId) !== "undefined" ? Number(teamId) : 1) !== -1; + var teams = (cal.teams || []).map(function (tId) { return Number(tId); }); + return teams.indexOf(typeof (teamId) !== "undefined" ? Number(teamId) : 1) !== -1; }).map(function (k) { // nearly constant-time pre-sort var cal = APP.calendars[k] || {}; @@ -855,6 +857,7 @@ define([ return t1 > t2 ? 1 : (t1 === t2 ? 0 : -1); }); }; + var tempCalendars = filter(0); if (tempCalendars.length && tempCalendars[0] === APP.currentCalendar) { var $tempCalendarTeam = $(h('div.cp-calendar-team', [ @@ -882,6 +885,7 @@ define([ } return; } + var myCalendars = filter(1); if (myCalendars.length) { var user = metadataMgr.getUserData(); @@ -891,32 +895,29 @@ define([ common.displayAvatar($(avatar), user.avatar, name, function () { }, uid); APP.$calendars.append(h('div.cp-calendar-team', [ avatar, - h('span.cp-name', {title: name}, name) + h('span.cp-name', { title: name }, name) ])); - if (window.innerWidth <= 600) { - var $showCalendarsContainer = $('
').appendTo($calendars); - var showCalendarsBtn = h('button.cp-calendar-showcalendars', [ - h('i.fa.fa-eye'), - h('span', Messages.calendar_show), // Initial text: "Show Calendars" - h('span') - ]); - var $myCalendarEntries = appendCalendarEntries(1, filter).appendTo(APP.$calendars); - $(showCalendarsBtn).click(function (e) { - e.preventDefault(); - $myCalendarEntries.toggle(); - if ($myCalendarEntries.is(':visible')) { - $(this).find('span').first().text(Messages.calendar_hide); - } else { - $(this).find('span').first().text(Messages.calendar_show); - } - }).appendTo($showCalendarsContainer); + if (isMobileView) { + createShowCalendarsButton(1, filter, $calendars); } else { myCalendars.forEach(function (id) { makeCalendarEntry(id, 1); }); } } - Object.keys(privateData.teams).sort().forEach(function (teamId) { + + // Add new button + var $newContainer = $('
').appendTo($calendars); + var newButton = h('button', [ + h('i.fa.fa-calendar-plus-o'), + h('span', Messages.calendar_new), + h('span') + ]); + $(newButton).click(function () { + editCalendar(); + }).appendTo($newContainer); + + Object.keys(privateData.teams).sort().forEach(function (teamId) { var calendars = filter(teamId); if (!calendars.length) { return; } var team = privateData.teams[teamId]; @@ -924,48 +925,46 @@ define([ common.displayAvatar($(avatar), team.avatar, team.displayName || team.name); APP.$calendars.append(h('div.cp-calendar-team', [ avatar, - h('span.cp-name', {title: team.name}, team.name) + h('span.cp-name', { title: team.name }, team.name) ])); - if (window.innerWidth <= 600) { - // Add show calendars button for each team - var $showCalendarsContainer = $('
').appendTo($calendars); - var showCalendarsBtn = h('button.cp-calendar-showcalendars', [ - h('i.fa.fa-eye'), - h('span', Messages.calendar_show), - h('span') - ]); - var $teamCalendarEntries = appendCalendarEntries(teamId, filter).appendTo(APP.$calendars); - $(showCalendarsBtn).click(function (e) { - e.preventDefault(); - $teamCalendarEntries.toggle(); - if ($teamCalendarEntries.is(':visible')) { - $(this).find('span').first().text(Messages.calendar_hide); - } else { - $(this).find('span').first().text(Messages.calendar_show); - } - }).appendTo($showCalendarsContainer); + if (isMobileView) { + createShowCalendarsButton(teamId, filter, $calendars); } else { calendars.forEach(function (id) { makeCalendarEntry(id, 1); }); } - }); - // Add new button - var $newContainer = $('
').appendTo($calendars); - var newButton = h('button', [ - h('i.fa.fa-calendar-plus-o'), - h('span', Messages.calendar_new), + } + function createShowCalendarsButton(teamId, filter, $parentContainer) { + var $showCalendarsContainer = $('
').appendTo($parentContainer); + var showCalendarsBtn = h('button.cp-calendar-showcalendars', [ + h('i.fa.fa-eye'), + h('span', Messages.calendar_show), h('span') ]); - $(newButton).click(function () { - editCalendar(); - }).appendTo($newContainer); - }); - + var $teamCalendarEntries = appendCalendarEntries(teamId, filter).appendTo(APP.$calendars); + $(showCalendarsBtn).click(function (e) { + e.preventDefault(); + $teamCalendarEntries.toggle(); + if ($teamCalendarEntries.is(':visible')) { + $(this).find('span').first().text(Messages.calendar_hide); + } else { + $(this).find('span').first().text(Messages.calendar_show); + } + }).appendTo($showCalendarsContainer); + } + function onResize() { + var newIsMobileView = window.innerWidth <= 600; + if (newIsMobileView !== isMobileView) { + isMobileView = newIsMobileView; + updateCalendarsView(); + } + } + $(window).resize(onResize); + onCalendarsUpdate.reg(updateCalendarsView); onCalendarsUpdate.fire(); }; - var _updateRecurring = function () { var cal = APP.calendar; if (!cal) { return; } From 71ba789f77d5b149ceafb47546f86b034ed73fe5 Mon Sep 17 00:00:00 2001 From: DianaXWiki <139217939+DianaXWiki@users.noreply.github.com> Date: Fri, 30 Aug 2024 14:24:09 +0300 Subject: [PATCH 011/218] Handle saving calendat list state #1371 --- www/calendar/inner.js | 47 +++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index 80ce6895e..2b0674559 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -836,6 +836,10 @@ define([ var $calendars = APP.$calendars = $(calendars).appendTo($container); var isMobileView = window.innerWidth <= 600; + var state = { + teamVisibility: {} + }; + function updateCalendarsView() { $calendars.empty(); var privateData = metadataMgr.getPrivateData(); @@ -885,7 +889,6 @@ define([ } return; } - var myCalendars = filter(1); if (myCalendars.length) { var user = metadataMgr.getUserData(); @@ -905,19 +908,17 @@ define([ }); } } - - // Add new button - var $newContainer = $('
').appendTo($calendars); - var newButton = h('button', [ - h('i.fa.fa-calendar-plus-o'), - h('span', Messages.calendar_new), - h('span') - ]); - $(newButton).click(function () { - editCalendar(); - }).appendTo($newContainer); - - Object.keys(privateData.teams).sort().forEach(function (teamId) { + // Add new button + var $newContainer = $('
').appendTo($calendars); + var newButton = h('button', [ + h('i.fa.fa-calendar-plus-o'), + h('span', Messages.calendar_new), + h('span') + ]); + $(newButton).click(function () { + editCalendar(); + }).appendTo($newContainer); + Object.keys(privateData.teams).sort().forEach(function (teamId) { var calendars = filter(teamId); if (!calendars.length) { return; } var team = privateData.teams[teamId]; @@ -944,27 +945,39 @@ define([ h('span') ]); var $teamCalendarEntries = appendCalendarEntries(teamId, filter).appendTo(APP.$calendars); + if (state.teamVisibility[teamId]) { + $teamCalendarEntries.show(); + $(showCalendarsBtn).find('span').first().text(Messages.calendar_hide); + } else { + $teamCalendarEntries.hide(); + } + $(showCalendarsBtn).click(function (e) { e.preventDefault(); $teamCalendarEntries.toggle(); if ($teamCalendarEntries.is(':visible')) { $(this).find('span').first().text(Messages.calendar_hide); + state.teamVisibility[teamId] = true; // Save visibility state } else { $(this).find('span').first().text(Messages.calendar_show); + state.teamVisibility[teamId] = false; // Save visibility state } }).appendTo($showCalendarsContainer); } - function onResize() { + + $(window).resize(function () { var newIsMobileView = window.innerWidth <= 600; if (newIsMobileView !== isMobileView) { isMobileView = newIsMobileView; updateCalendarsView(); } - } - $(window).resize(onResize); + }); + onCalendarsUpdate.reg(updateCalendarsView); + onCalendarsUpdate.fire(); }; + var _updateRecurring = function () { var cal = APP.calendar; if (!cal) { return; } From b62ff674ef0088a0adae75856e84c13f41af446b Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 6 Sep 2024 11:31:09 +0200 Subject: [PATCH 012/218] API: download document from our server --- www/cryptpad-api.js | 6 +++ www/integration/main.js | 90 +++++++++++++++++++++++++++++------------ 2 files changed, 70 insertions(+), 26 deletions(-) diff --git a/www/cryptpad-api.js b/www/cryptpad-api.js index e73ef40bd..f4a10d147 100644 --- a/www/cryptpad-api.js +++ b/www/cryptpad-api.js @@ -18,7 +18,9 @@ var scripts = document.getElementsByTagName('script'); for (var i = scripts.length - 1; i >= 0; i--) { var match = scripts[i].src.match(/(.*)web-apps\/apps\/api\/documents\/api.js/i); + var match2 = scripts[i].src.match(/(.*)\/cryptpad-api.js/i); if (match) { return match[1]; } + else if (match2) { return match2[1]; } } }; @@ -114,6 +116,7 @@ chan.send('START', { key: key, application: config.documentType, + url: config.document.url, document: blob, ext: config.document.fileType, autosave: config.autosave || 10 @@ -130,6 +133,9 @@ blob = config.document.blob; return start(); } + return start(); + // XXX use server only when not zero knowledge? i.e. no save handler? + // XXX or when error with client? getBlob(function (err, _blob) { if (err) { reject(err); return console.error(err); } _blob.name = `document.${config.document.fileType}`; diff --git a/www/integration/main.js b/www/integration/main.js index fe3ffd010..eb137c039 100644 --- a/www/integration/main.js +++ b/www/integration/main.js @@ -3,9 +3,10 @@ // SPDX-License-Identifier: AGPL-3.0-or-later define([ + '/api/config', '/common/sframe-common-outer.js', '/common/common-hash.js', -], function (SCO, Hash) { +], function (Config, SCO, Hash) { var getTxid = function () { return Math.random().toString(16).replace('0.', ''); @@ -152,36 +153,73 @@ define([ chan.send('ON_DOWNLOADAS', blob); }; - chan.on('START', function (data) { + + let getInstanceURL = function () { + return Config.httpUnsafeOrigin; + }; + let getBlobServer = function (documentURL, cb) { + let xhr = new XMLHttpRequest(); + let data = encodeURIComponent(documentURL); + let url = getInstanceURL() + '/ooapidl?url=' + data; + console.log(url); + xhr.open('GET', url, true); + xhr.responseType = 'blob'; + //xhr.setRequestHeader('Content-Type', 'application/json'); + xhr.onload = function () { + if (this.status === 200) { + var blob = this.response; + // myBlob is now the blob that the object URL pointed to. + cb(null, blob); + } else { + cb(this.status); + } + }; + xhr.onerror = function (e) { + cb(e.message); + }; + xhr.send(); + }; + chan.on('START', function (data, cb) { console.warn('INNER START', data); var href = Hash.hashToHref(data.key, data.application); console.error(Hash.hrefToHexChannelId(href)); - window.CP_integration_outer = { - pathname: `/${data.application}/`, - hash: data.key, - href: href, - initialState: data.document, - config: { - fileType: data.ext, - autosave: data.autosave - }, - utils: { - onReady: onReady, - onDownloadAs, - setDownloadAs, - save: save, - reload: reload, - onHasUnsavedChanges: onHasUnsavedChanges, - onInsertImage: onInsertImage + let startApp = function (blob) { + window.CP_integration_outer = { + pathname: `/${data.application}/`, + hash: data.key, + href: href, + initialState: blob, + config: { + fileType: data.ext, + autosave: data.autosave + }, + utils: { + onReady: onReady, + onDownloadAs, + setDownloadAs, + save: save, + reload: reload, + onHasUnsavedChanges: onHasUnsavedChanges, + onInsertImage: onInsertImage + } + }; + let path = "/common/sframe-app-outer.js"; + if (['sheet', 'doc', 'presentation'].includes(data.application)) { + path = '/common/onlyoffice/main.js'; } + require([path], function () { + console.warn('SAO REQUIRED'); + delete window.CP_integration_outer; + cb(); + }); }; - let path = "/common/sframe-app-outer.js"; - if (['sheet', 'doc', 'presentation'].includes(data.application)) { - path = '/common/onlyoffice/main.js'; - } - require([path], function () { - console.warn('SAO REQUIRED'); - delete window.CP_integration_outer; + + if (data.document) { return void startApp(data.document); } + getBlobServer(data.url, (err, blob) => { + if (err) { + return void cb({error: err}); + } + startApp(blob); }); }); From 8de2bdbda5dd2b9cb374f839c6bfee37acee2789 Mon Sep 17 00:00:00 2001 From: DianaXWiki <139217939+DianaXWiki@users.noreply.github.com> Date: Sun, 22 Sep 2024 13:59:21 +0200 Subject: [PATCH 013/218] Save commit --- www/calendar/inner.js | 143 +++++++++++++----------------------------- 1 file changed, 45 insertions(+), 98 deletions(-) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index b0c0f5296..8064807c7 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -821,148 +821,95 @@ define([ return $calendar; // return jQuery element }; - var appendCalendarEntries = function (teamId, filter) { - var calendars = filter(teamId); - var $entriesContainer = $('
'); - calendars.forEach(function (id) { - var calendarEntry = makeCalendarEntry(id, teamId); - $entriesContainer.append(calendarEntry); - }); - return $entriesContainer; - }; var makeLeftside = function (calendar, $container) { // Show calendars var calendars = h('div.cp-calendar-list'); var $calendars = APP.$calendars = $(calendars).appendTo($container); var isMobileView = window.innerWidth <= 600; - var state = { - teamVisibility: {} - }; - function updateCalendarsView() { $calendars.empty(); var privateData = metadataMgr.getPrivateData(); - var filter = function (teamId) { + var filter = (teamId) => { var LOOKUP = {}; - return Object.keys(APP.calendars || {}).filter(function (id) { + return Object.keys(APP.calendars || {}).filter((id) => { var cal = APP.calendars[id] || {}; - var teams = (cal.teams || []).map(function (tId) { return Number(tId); }); + var teams = (cal.teams || []).map((tId) => Number(tId)); return teams.indexOf(typeof (teamId) !== "undefined" ? Number(teamId) : 1) !== -1; - }).map(function (k) { - // nearly constant-time pre-sort + }).map((k) => { var cal = APP.calendars[k] || {}; var title = Util.find(cal, ['content', 'metadata', 'title']) || ''; LOOKUP[k] = title; return k; - }).sort(function (a, b) { + }).sort((a, b) => { var t1 = LOOKUP[a]; var t2 = LOOKUP[b]; return t1 > t2 ? 1 : (t1 === t2 ? 0 : -1); }); }; - var tempCalendars = filter(0); - if (tempCalendars.length && tempCalendars[0] === APP.currentCalendar) { - var $tempCalendarTeam = $(h('div.cp-calendar-team', [ - h('span', Messages.calendar_tempCalendar) - ])).appendTo(APP.$calendars); - var $tempCalendarEntries = appendCalendarEntries(0, filter).appendTo(APP.$calendars); - var importTemp = h('button', [ - h('i.fa.fa-calendar-plus-o'), - h('span', Messages.calendar_import_temp), - h('span') - ]); - $(importTemp).click(function () { - importCalendar({ - id: tempCalendars[0], - teamId: 0 - }, function (err) { - if (err) { - console.error(err); - return void UI.warn(Messages.error); - } - }); - }); - if (APP.loggedIn) { - $tempCalendarEntries.append(h('div.cp-calendar-entry.cp-ghost', importTemp)); - } - return; - } var myCalendars = filter(1); + var totalCalendars = myCalendars.length + Object.keys(privateData.teams).reduce((sum, teamId) => { + return sum + filter(teamId).length; + }, 0); + var $contentContainer = h('div.cp-calendar-content').appendTo($calendars); if (myCalendars.length) { var user = metadataMgr.getUserData(); var avatar = h('span.cp-avatar'); var uid = user.uid; var name = user.name || Messages.anonymous; - common.displayAvatar($(avatar), user.avatar, name, function () { }, uid); - APP.$calendars.append(h('div.cp-calendar-team', [ + common.displayAvatar($(avatar), user.avatar, name, () => {}, uid); + $contentContainer.append(h('div.cp-calendar-team', [ avatar, h('span.cp-name', { title: name }, name) ])); - if (isMobileView) { - createShowCalendarsButton(1, filter, $calendars); - } else { - myCalendars.forEach(function (id) { - makeCalendarEntry(id, 1); - }); - } + myCalendars.forEach((id) => { + var calendarEntry = makeCalendarEntry(id, 1); + $contentContainer.append(calendarEntry); + }); } - // Add new button - var $newContainer = $('
').appendTo($calendars); + + // Add the new calendar button + var $newContainer = h('div.cp-calendar-entry.cp-ghost').appendTo($contentContainer); var newButton = h('button', [ h('i.fa.fa-calendar-plus-o'), - h('span', Messages.calendar_new), - h('span') + h('span', Messages.calendar_new) ]); - $(newButton).click(function () { + $(newButton).click(() => { editCalendar(); }).appendTo($newContainer); - Object.keys(privateData.teams).sort().forEach(function (teamId) { + + Object.keys(privateData.teams).sort().forEach((teamId) => { var calendars = filter(teamId); - if (!calendars.length) { return; } + if (!calendars.length) return; var team = privateData.teams[teamId]; var avatar = h('span.cp-avatar'); common.displayAvatar($(avatar), team.avatar, team.displayName || team.name); - APP.$calendars.append(h('div.cp-calendar-team', [ + var $teamContainer = h('div.cp-calendar-team', [ avatar, - h('span.cp-name', { title: team.name }, team.name) - ])); - if (isMobileView) { - createShowCalendarsButton(teamId, filter, $calendars); - } else { - calendars.forEach(function (id) { - makeCalendarEntry(id, 1); - }); - } + h('span.cp-name', { title: team.name }, team.name), + h('span') + ]); + $contentContainer.append($teamContainer); + calendars.forEach((id) => { + var calendarEntry = makeCalendarEntry(id, teamId); + $contentContainer.append(calendarEntry); + }); }); - } - function createShowCalendarsButton(teamId, filter, $parentContainer) { - var $showCalendarsContainer = $('
').appendTo($parentContainer); - var showCalendarsBtn = h('button.cp-calendar-showcalendars', [ - h('i.fa.fa-eye'), - h('span', Messages.calendar_show), - h('span') - ]); - var $teamCalendarEntries = appendCalendarEntries(teamId, filter).appendTo(APP.$calendars); - if (state.teamVisibility[teamId]) { - $teamCalendarEntries.show(); - $(showCalendarsBtn).find('span').first().text(Messages.calendar_hide); - } else { - $teamCalendarEntries.hide(); + if (totalCalendars > 2 && isMobileView) { + $contentContainer.hide(); + var $showContainer = $(h('div.cp-calendar-entry.cp-ghost')).appendTo($calendars); + var showCalendarsBtn = h('button', [ + h('i.fa.fa-eye'), + h('span', Messages.calendar_show) + ]); + var visible = false; // Initially hidden + $(showCalendarsBtn).click(() => { + visible = !visible; + $contentContainer.toggle(visible); // Toggle visibility of entire content (personal and team calendars) + $(showCalendarsBtn).find('span').first().text(visible ? Messages.calendar_hide : Messages.calendar_show); + }).appendTo($showContainer); } - - $(showCalendarsBtn).click(function (e) { - e.preventDefault(); - $teamCalendarEntries.toggle(); - if ($teamCalendarEntries.is(':visible')) { - $(this).find('span').first().text(Messages.calendar_hide); - state.teamVisibility[teamId] = true; // Save visibility state - } else { - $(this).find('span').first().text(Messages.calendar_show); - state.teamVisibility[teamId] = false; // Save visibility state - } - }).appendTo($showCalendarsContainer); } $(window).resize(function () { From 4869fafc78ad96df086bc03b3c2802c467f80824 Mon Sep 17 00:00:00 2001 From: DianaXWiki <139217939+DianaXWiki@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:13:14 +0200 Subject: [PATCH 014/218] Toggle calendar visibility on small screens --- www/calendar/app-calendar.less | 1 + www/calendar/inner.js | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/www/calendar/app-calendar.less b/www/calendar/app-calendar.less index f2994eade..bcb240dcc 100644 --- a/www/calendar/app-calendar.less +++ b/www/calendar/app-calendar.less @@ -640,6 +640,7 @@ } &.cp-ghost { padding: 0; + margin-top: 0.5rem; button { .tools_unselectable(); cursor: pointer; diff --git a/www/calendar/inner.js b/www/calendar/inner.js index 8064807c7..9341f6908 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -852,7 +852,7 @@ define([ var totalCalendars = myCalendars.length + Object.keys(privateData.teams).reduce((sum, teamId) => { return sum + filter(teamId).length; }, 0); - var $contentContainer = h('div.cp-calendar-content').appendTo($calendars); + var $contentContainer = $(h('div.cp-calendar-content')).appendTo($calendars); if (myCalendars.length) { var user = metadataMgr.getUserData(); var avatar = h('span.cp-avatar'); @@ -870,10 +870,11 @@ define([ } // Add the new calendar button - var $newContainer = h('div.cp-calendar-entry.cp-ghost').appendTo($contentContainer); + var $newContainer = $(h('div.cp-calendar-entry.cp-ghost')).appendTo($contentContainer); var newButton = h('button', [ h('i.fa.fa-calendar-plus-o'), - h('span', Messages.calendar_new) + h('span', Messages.calendar_new), + h('span') ]); $(newButton).click(() => { editCalendar(); @@ -901,7 +902,8 @@ define([ var $showContainer = $(h('div.cp-calendar-entry.cp-ghost')).appendTo($calendars); var showCalendarsBtn = h('button', [ h('i.fa.fa-eye'), - h('span', Messages.calendar_show) + h('span.cp-calendar-title', Messages.calendar_show), + h('span') ]); var visible = false; // Initially hidden $(showCalendarsBtn).click(() => { From d5cc0d27f22fa8d578fec6edc6987bb674954ede Mon Sep 17 00:00:00 2001 From: Numaan Bashir Mir <71112748+mirnumaan@users.noreply.github.com> Date: Wed, 25 Sep 2024 10:26:29 +1000 Subject: [PATCH 015/218] Update readme.md Such a great project to read about, as it is my area of interest too. I have made a few changes, and I hope you accept them. Looking forward to contributing more to other parts of the repository. Thank you. --- readme.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index d5766eed6..f4c3f9880 100644 --- a/readme.md +++ b/readme.md @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later # CryptPad -CryptPad is a collaboration suite that is end-to-end-encrypted and open-source. It is built to enable collaboration, synchronizing changes to documents in real time. Because all data are encrypted, in the eventuality of a breach, attackers have no way of seeing the stored content. Moreover, if the administrators don’t alter the code, they and the service also cannot infer any piece of information about the users' content. +CryptPad is a collaboration suite that is end-to-end encrypted and open-source. It is designed to facilitate collaboration by synchronizing changes to documents in real time. Since all the user data is encrypted, in the event of a breach, attackers have no way of accessing the stored content. Furthermore, if the administrators do not modify the code, they and the service also cannot access any information about the users' content. ![Drive screenshot](screenshot.png "preview of the CryptDrive") @@ -20,15 +20,16 @@ Our [developer guide](https://docs.cryptpad.org/en/dev_guide/setup.html) provide ## For production -Configuring CryptPad for production requires a little more work, but the process is described in our [admin installation guide](https://docs.cryptpad.org/en/admin_guide/installation.html). From there you can find more information about customization and maintenance. +Configuring CryptPad for production requires additional steps. Refer to our [admin installation guide](https://docs.cryptpad.org/en/admin_guide/installation.html) for production-related instructions, customization, and maintenance details. ## Current version -The most recent version and all past release notes can be found on the [releases page on GitHub](https://github.com/cryptpad/cryptpad/releases/). +The most recent version and all past release notes can be found on [releases page on GitHub](https://github.com/cryptpad/cryptpad/releases/). ## Setup using Docker -You can find `Dockerfile`, `docker-compose.yml` and `docker-entrypoint.sh` files at the root of this repository. We also publish every release on [Docker Hub](https://hub.docker.com/r/cryptpad/cryptpad) as AMD64 & ARM64 official images. +You can find the `Dockerfile`, `docker-compose.yml` and `docker-entrypoint.sh` files at the root of this repository. We also publish every release on [Docker Hub](https://hub.docker.com/r/cryptpad/cryptpad) as AMD64 & ARM64 official images. + Previously, Docker images were community maintained, had their own repository and weren't official supported. We changed that with v5.4.0 during July 2023. Thanks to @promasu for all the work on the community images. From 89c26231c20f315fd7c10e5c762888a1e6d08db8 Mon Sep 17 00:00:00 2001 From: DianaXWiki <139217939+DianaXWiki@users.noreply.github.com> Date: Mon, 30 Sep 2024 15:41:10 +0300 Subject: [PATCH 016/218] Save visibility state #1371 --- www/calendar/inner.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index 9341f6908..f61c345c4 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -826,8 +826,9 @@ define([ var calendars = h('div.cp-calendar-list'); var $calendars = APP.$calendars = $(calendars).appendTo($container); var isMobileView = window.innerWidth <= 600; + var visible = false; // Initialize global 'visible' state for calendars - function updateCalendarsView() { + onCalendarsUpdate.reg(function () { $calendars.empty(); var privateData = metadataMgr.getPrivateData(); var filter = (teamId) => { @@ -868,7 +869,6 @@ define([ $contentContainer.append(calendarEntry); }); } - // Add the new calendar button var $newContainer = $(h('div.cp-calendar-entry.cp-ghost')).appendTo($contentContainer); var newButton = h('button', [ @@ -877,6 +877,7 @@ define([ h('span') ]); $(newButton).click(() => { + visible = $contentContainer.is(':visible'); editCalendar(); }).appendTo($newContainer); @@ -897,6 +898,7 @@ define([ $contentContainer.append(calendarEntry); }); }); + if (totalCalendars > 2 && isMobileView) { $contentContainer.hide(); var $showContainer = $(h('div.cp-calendar-entry.cp-ghost')).appendTo($calendars); @@ -905,25 +907,23 @@ define([ h('span.cp-calendar-title', Messages.calendar_show), h('span') ]); - var visible = false; // Initially hidden + $(showCalendarsBtn).click(() => { visible = !visible; - $contentContainer.toggle(visible); // Toggle visibility of entire content (personal and team calendars) + $contentContainer.toggle(visible); $(showCalendarsBtn).find('span').first().text(visible ? Messages.calendar_hide : Messages.calendar_show); }).appendTo($showContainer); } - } + $contentContainer.toggle(visible); - $(window).resize(function () { - var newIsMobileView = window.innerWidth <= 600; - if (newIsMobileView !== isMobileView) { - isMobileView = newIsMobileView; - updateCalendarsView(); - } + $(window).resize(function () { + var newIsMobileView = window.innerWidth <= 600; + if (newIsMobileView !== isMobileView) { + isMobileView = newIsMobileView; + onCalendarsUpdate.fire(); + } + }); }); - - onCalendarsUpdate.reg(updateCalendarsView); - onCalendarsUpdate.fire(); }; From 062c1550d884079f4ee7e6f2d8719219de4521d3 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 30 Sep 2024 17:27:15 +0200 Subject: [PATCH 017/218] Fix issue with non-base64 keys --- www/integration/main.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/www/integration/main.js b/www/integration/main.js index 4d252f659..1cbf1f241 100644 --- a/www/integration/main.js +++ b/www/integration/main.js @@ -6,8 +6,10 @@ define([ '/api/config', '/common/sframe-common-outer.js', '/common/common-hash.js', + '/components/tweetnacl/nacl-fast.min.js' ], function (Config, SCO, Hash) { + let Nacl = window.nacl; var getTxid = function () { return Math.random().toString(16).replace('0.', ''); }; @@ -96,9 +98,17 @@ define([ }; http.send(); }; + let sanitizeKey = key => { + try { + Nacl.util.decodeBase64(key); + return key; + } catch (e) { + return Nacl.util.encodeBase64(Nacl.util.decodeUTF8(key)); + } + }; chan.on('GET_SESSION', function (data, cb) { - if (data.keepOld) { - var key = data.key + "000000000000000000000000000000000"; + if (data.keepOld) { // they provide their own key, we must turn it into a hash + var key = sanitizeKey(data.key) + "000000000000000000000000000000000"; console.warn('KEY', key); return void cb({ key: `/2/integration/edit/${key.slice(0,24)}/` @@ -181,6 +191,7 @@ define([ }; chan.on('START', function (data, cb) { console.warn('INNER START', data); + // data.key is a hash var href = Hash.hashToHref(data.key, data.application); if (data.editorConfig.lang) { var LS_LANG = "CRYPTPAD_LANG"; From 66047fed03220962f6b98ea34ced9f48a7b4fdd7 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 1 Oct 2024 12:31:23 +0200 Subject: [PATCH 018/218] Fix onlyoffice out of sync with integration API --- www/common/outer/async-store.js | 1 + 1 file changed, 1 insertion(+) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index af20c0722..704038b2c 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -2684,6 +2684,7 @@ define([ }; */ var loadOnlyOffice = function () { + if (store.onlyoffice) { return; } store.onlyoffice = OnlyOffice.init(store, function (ev, data, clients) { clients.forEach(function (cId) { postMessage(cId, 'OO_EVENT', { From da514ce5fc0475812fdf82eb354ae6b9e0979549 Mon Sep 17 00:00:00 2001 From: Wolfgang Ginolas Date: Tue, 1 Oct 2024 14:58:24 +0200 Subject: [PATCH 019/218] 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 020/218] 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 021/218] 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 022/218] 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 d1924d6585f6fae73e1fee56d52f15803f50c9bf Mon Sep 17 00:00:00 2001 From: daria Date: Wed, 2 Oct 2024 14:07:58 +0300 Subject: [PATCH 023/218] open contact page via keyboard navigation on contact request notification fix #1524 --- www/common/toolbar.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/www/common/toolbar.js b/www/common/toolbar.js index 2b0d2f648..b30b954b0 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -1198,7 +1198,13 @@ MessengerUI, Messages, Pages, PadTypes) { $('body').find('.cp-dropdown-content li').first().focus(); return $(el).find('.cp-notification-dismiss').click(); } - $(el).find('.cp-notification-content').click(); + if($(el).find('.cp-avatar')){ + $(el).find('.cp-avatar').click(); + } + else{ + $(el).find('.cp-notification-content').click(); + } + }); refresh(); }, From d47295f597331575a6bdc35ed9f9a2237d9daf23 Mon Sep 17 00:00:00 2001 From: daria Date: Wed, 2 Oct 2024 14:40:19 +0300 Subject: [PATCH 024/218] change hover style on contact request notifications fix #1525 --- customize.dist/src/less2/include/dropdown.less | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/customize.dist/src/less2/include/dropdown.less b/customize.dist/src/less2/include/dropdown.less index 4a5099ee3..003ed86fe 100644 --- a/customize.dist/src/less2/include/dropdown.less +++ b/customize.dist/src/less2/include/dropdown.less @@ -157,16 +157,28 @@ } } } + li[role="menuitem"] { border-radius: @variables_radius; white-space: nowrap; - &:hover { - background-color: @cp_dropdown-bg-hover !important; - } &:focus-visible { outline-color: @cp_dropdown-fg; } + .cp-avatar:hover { + background-color: @cp_dropdown-bg-hover !important; + } + .cp-notification-content:hover { + border-radius: @variables_radius; + background-color: @cp_dropdown-bg-hover !important; + } } + + li[role="menuitem"]:not(:has(.cp-avatar)) { + &:hover { + background-color: @cp_dropdown-bg-hover !important; + } + } + &> span { box-sizing: border-box; height: 26px; From e30aad2fa559dc92fbda26428eb2736383dde317 Mon Sep 17 00:00:00 2001 From: Wolfgang Ginolas Date: Wed, 2 Oct 2024 15:39:03 +0200 Subject: [PATCH 025/218] 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 8edf6001f2dcc2a5b31dca56ed1d83d3757b22e0 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 3 Oct 2024 16:59:55 +0200 Subject: [PATCH 026/218] First version of OO save --- www/common/onlyoffice/inner.js | 12 ++++++ www/common/sframe-common-integration.js | 2 +- www/cryptpad-api.js | 9 ++++- www/integration/main.js | 49 +++++++++++++++++++++---- 4 files changed, 62 insertions(+), 10 deletions(-) diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index a29c10c6b..4e878005b 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -3230,11 +3230,23 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null }); } integrationChannel.on('Q_INTEGRATION_NEEDSAVE', function (data, cb) { + if (!cfg.autosave) { return; } integrationSave(function (obj) { if (obj && obj.error) { console.error(obj.error); } cb(); }); }); + + if (!cfg.autosave) { + let $save = common.createButton('save', true, {}, function () { + $save.attr('disabled', 'disabled'); + integrationSave(err => { + $save.removeAttr('disabled'); + }); + }); + $('body').prepend($save); + } + if (privateData.initialState && (!content || !content.hashes || !Object.keys(content.hashes).length)) { var blob = privateData.initialState; let title = `document.${cfg.fileType}`; diff --git a/www/common/sframe-common-integration.js b/www/common/sframe-common-integration.js index 8b1ab5374..41d81a7b9 100644 --- a/www/common/sframe-common-integration.js +++ b/www/common/sframe-common-integration.js @@ -17,7 +17,7 @@ define([ var privateData = metadataMgr.getPrivateData(); var config = privateData.integrationConfig; - if (!config.autosave) { return; } + if (!config.autosave) { return void exp; } if (typeof(saveHandler) !== "function") { throw new Error("Incorrect save handler"); } diff --git a/www/cryptpad-api.js b/www/cryptpad-api.js index 9e873cba3..efe065618 100644 --- a/www/cryptpad-api.js +++ b/www/cryptpad-api.js @@ -88,6 +88,7 @@ var start = function (config, chan) { return new Promise(function (resolve, reject) { setTimeout(function () { + var docID = config.document.key; var key = config.document.key; var blob; @@ -116,10 +117,12 @@ chan.send('START', { key: key, application: config.documentType, + name: config.document.title, url: config.document.url, + documentKey: docID, document: blob, ext: config.document.fileType, - autosave: config.autosave || 10, + autosave: config.events.onSave && (config.autosave || 10), editorConfig: config.editorConfig || {} }, function (obj) { if (obj && obj.error) { reject(obj.error); return console.error(obj.error); } @@ -134,9 +137,11 @@ blob = config.document.blob; return start(); } - return start(); // XXX use server only when not zero knowledge? i.e. no save handler? // XXX or when error with client? + if (!config.events.onSave) { + return start(); + } getBlob(function (err, _blob) { if (err) { reject(err); return console.error(err); } _blob.name = `document.${config.document.fileType}`; diff --git a/www/integration/main.js b/www/integration/main.js index 1cbf1f241..cffd22a33 100644 --- a/www/integration/main.js +++ b/www/integration/main.js @@ -129,12 +129,6 @@ define([ }); }); - var save = function (obj, cb) { - chan.send('SAVE', obj.blob, function (err) { - if (err) { return cb({error: err}); } - cb(); - }); - }; var reload = function (data) { chan.send('RELOAD', data); }; @@ -171,7 +165,6 @@ define([ let xhr = new XMLHttpRequest(); let data = encodeURIComponent(documentURL); let url = getInstanceURL() + '/ooapidl?url=' + data; - console.log(url); xhr.open('GET', url, true); xhr.responseType = 'blob'; //xhr.setRequestHeader('Content-Type', 'application/json'); @@ -189,6 +182,30 @@ define([ }; xhr.send(); }; + let saveBlobServer = function (cfg, blob, cb) { + let {callbackUrl, name, key} = cfg; + let xhr = new XMLHttpRequest(); + name = encodeURIComponent(name); + callbackUrl = encodeURIComponent(callbackUrl); + key = encodeURIComponent(key); + let query = `name=${name}&cb=${callbackUrl}&key=${key}` + let url = getInstanceURL() + `/oosave?${query}`; + xhr.open('POST', url, true); + xhr.responseType = 'blob'; + //xhr.setRequestHeader('Content-Type', 'application/json'); + xhr.onload = function () { + console.error(this.status); + if (this.status === 200) { + cb(); + } else { + cb(this.status); + } + }; + xhr.onerror = function (e) { + cb(e.message); + }; + xhr.send(blob); + }; chan.on('START', function (data, cb) { console.warn('INNER START', data); // data.key is a hash @@ -198,6 +215,23 @@ define([ localStorage.setItem(LS_LANG, data.editorConfig.lang); } + let fileName = data.name || `document.${data.ext}`; + var save = function (obj, cb) { + let cbUrl = data.editorConfig.callbackUrl; + if (!data.autosave && cbUrl) { + saveBlobServer({ + callbackUrl: cbUrl, + name: fileName, + key: data.documentKey + }, obj.blob, cb); + return; + } + chan.send('SAVE', obj.blob, function (err) { + if (err) { return cb({error: err}); } + cb(); + }); + }; + console.error(Hash.hrefToHexChannelId(href)); let startApp = function (blob) { window.CP_integration_outer = { @@ -206,6 +240,7 @@ define([ href: href, initialState: blob, config: { + fileName: data.name, fileType: data.ext, autosave: data.autosave, user: data.editorConfig.user From b45026c45031dc974cacd46f2fcc430123ae71d3 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Wed, 9 Oct 2024 11:44:14 +0100 Subject: [PATCH 027/218] Stops selection of all other trashed files when restoring single file #1617 --- www/common/drive-ui.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 135b0eae0..d8293a3d4 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -2420,9 +2420,7 @@ define([ draggable: true })); $element.data('path', newPath); - if (isElementSelected($element)) { - selectElement($element); - } + $element.prepend($icon).dblclick(function () { if (restricted) { UI.warn(Messages.fm_restricted); From 8c11128b193e0e7b8c6f880c8cb0dcc1e38bef57 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Wed, 9 Oct 2024 17:04:47 +0100 Subject: [PATCH 028/218] Stops 'Request edit access' button from being displayed if edit access already granted #1027 --- www/common/inner/access.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/inner/access.js b/www/common/inner/access.js index 9159aac93..389311642 100644 --- a/www/common/inner/access.js +++ b/www/common/inner/access.js @@ -1108,7 +1108,7 @@ define([ var owned = Modal.isOwned(Env, data); // Request edit access - if (common.isLoggedIn() && data.roHref && !owned && !opts.calendar && priv.app !== 'form') { + if (common.isLoggedIn() && data.roHref && !owned && !opts.calendar && priv.app !== 'form' && !data.href) { var requestButton = h('button.btn.btn-secondary.no-margin.cp-access-margin-right', Messages.requestEdit_button); var requestBlock = h('p', requestButton); From 8465fc0e443e7d8e2b48bb113644cebdfa5f8231 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Wed, 9 Oct 2024 20:08:03 +0100 Subject: [PATCH 029/218] Added password warning to Form creation modal #1455 --- customize.dist/src/less2/include/creation.less | 5 +++++ www/common/common-ui-elements.js | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/customize.dist/src/less2/include/creation.less b/customize.dist/src/less2/include/creation.less index cd04cf858..5ff730a64 100644 --- a/customize.dist/src/less2/include/creation.less +++ b/customize.dist/src/less2/include/creation.less @@ -165,6 +165,7 @@ //margin: 10px 0; min-height: 28px; line-height: 28px; + flex-flow: column; label { flex: 1; // Force wrap when the other element in the line is 100% (IE bug): @@ -178,6 +179,10 @@ } } } + + .cp-creation-password-warning { + background-color:@creation-bg-color-light; + } .cp-creation-help, .cp-creation-warning { font-size: 16px; color: @cp_creation-fg; diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index ae0dea302..b54cc0f14 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -2770,7 +2770,8 @@ define([ ]); // Password - var password = h('div.cp-creation-password', [ + var text = h('div.cp-creation-password-warning', 'Note that for Forms, you can only set the password during creation. This password cannot be changed later.'); + var password = h('div.cp-creation-password', [text, UI.createCheckbox('cp-creation-password', Messages.properties_addPassword, false), h('span.cp-creation-password-picker.cp-creation-slider', [ UI.passwordInput({id: 'cp-creation-password-val'}) From 11fd6990a43fc4cca0dda8fd965216274c1126c3 Mon Sep 17 00:00:00 2001 From: DianaXWiki <139217939+DianaXWiki@users.noreply.github.com> Date: Mon, 14 Oct 2024 15:45:46 +0300 Subject: [PATCH 030/218] Handle visibility toggling between resizing events --- www/calendar/inner.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index f61c345c4..67e4a87e1 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -826,11 +826,12 @@ define([ var calendars = h('div.cp-calendar-list'); var $calendars = APP.$calendars = $(calendars).appendTo($container); var isMobileView = window.innerWidth <= 600; - var visible = false; // Initialize global 'visible' state for calendars + var visible = !isMobileView; // Initialize 'visible' state: true for large screens, false for mobile onCalendarsUpdate.reg(function () { $calendars.empty(); var privateData = metadataMgr.getPrivateData(); + var filter = (teamId) => { var LOOKUP = {}; return Object.keys(APP.calendars || {}).filter((id) => { @@ -853,7 +854,9 @@ define([ var totalCalendars = myCalendars.length + Object.keys(privateData.teams).reduce((sum, teamId) => { return sum + filter(teamId).length; }, 0); + var $contentContainer = $(h('div.cp-calendar-content')).appendTo($calendars); + if (myCalendars.length) { var user = metadataMgr.getUserData(); var avatar = h('span.cp-avatar'); @@ -877,7 +880,6 @@ define([ h('span') ]); $(newButton).click(() => { - visible = $contentContainer.is(':visible'); editCalendar(); }).appendTo($newContainer); @@ -898,9 +900,7 @@ define([ $contentContainer.append(calendarEntry); }); }); - if (totalCalendars > 2 && isMobileView) { - $contentContainer.hide(); var $showContainer = $(h('div.cp-calendar-entry.cp-ghost')).appendTo($calendars); var showCalendarsBtn = h('button', [ h('i.fa.fa-eye'), @@ -914,19 +914,26 @@ define([ $(showCalendarsBtn).find('span').first().text(visible ? Messages.calendar_hide : Messages.calendar_show); }).appendTo($showContainer); } - $contentContainer.toggle(visible); + $contentContainer.toggle(visible); $(window).resize(function () { var newIsMobileView = window.innerWidth <= 600; + if (!newIsMobileView) { + visible = true; + $contentContainer.show(); + } if (newIsMobileView !== isMobileView) { isMobileView = newIsMobileView; + if (isMobileView) { + visible = false; + $contentContainer.hide(); + } onCalendarsUpdate.fire(); } }); }); onCalendarsUpdate.fire(); }; - var _updateRecurring = function () { var cal = APP.calendar; if (!cal) { return; } From d575a5d2d84afd2157075c91f802d178febd5fd4 Mon Sep 17 00:00:00 2001 From: Wolfgang Ginolas Date: Wed, 16 Oct 2024 09:57:36 +0200 Subject: [PATCH 031/218] 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 a38964998ad37e065720585487593603696db26f Mon Sep 17 00:00:00 2001 From: daria Date: Wed, 16 Oct 2024 12:21:04 +0300 Subject: [PATCH 032/218] remove ``!important` from dropdown elements styling --- customize.dist/src/less2/include/dropdown.less | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/customize.dist/src/less2/include/dropdown.less b/customize.dist/src/less2/include/dropdown.less index 003ed86fe..baaed29a4 100644 --- a/customize.dist/src/less2/include/dropdown.less +++ b/customize.dist/src/less2/include/dropdown.less @@ -165,17 +165,17 @@ outline-color: @cp_dropdown-fg; } .cp-avatar:hover { - background-color: @cp_dropdown-bg-hover !important; + background-color: @cp_dropdown-bg-hover; } .cp-notification-content:hover { border-radius: @variables_radius; - background-color: @cp_dropdown-bg-hover !important; + background-color: @cp_dropdown-bg-hover; } } li[role="menuitem"]:not(:has(.cp-avatar)) { &:hover { - background-color: @cp_dropdown-bg-hover !important; + background-color: @cp_dropdown-bg-hover; } } From eeb3eddd7149abc2d6a5dcdf118e5ffb156b7f04 Mon Sep 17 00:00:00 2001 From: Wolfgang Ginolas Date: Wed, 16 Oct 2024 11:28:54 +0200 Subject: [PATCH 033/218] 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 8872927e210308e670ada369efe610102ecc6cad Mon Sep 17 00:00:00 2001 From: daria Date: Wed, 16 Oct 2024 13:29:34 +0300 Subject: [PATCH 034/218] notifications can be accessed via the keyboard #1524 --- www/common/toolbar.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/www/common/toolbar.js b/www/common/toolbar.js index b30b954b0..c01fa9f45 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -1198,12 +1198,9 @@ MessengerUI, Messages, Pages, PadTypes) { $('body').find('.cp-dropdown-content li').first().focus(); return $(el).find('.cp-notification-dismiss').click(); } - if($(el).find('.cp-avatar')){ - $(el).find('.cp-avatar').click(); - } - else{ + setTimeout(function () { $(el).find('.cp-notification-content').click(); - } + }, 0); }); refresh(); From ae6b47b199499c991cbd2ee06e81c7da4e36fd63 Mon Sep 17 00:00:00 2001 From: Wolfgang Ginolas Date: Thu, 17 Oct 2024 09:33:12 +0200 Subject: [PATCH 035/218] 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 036/218] 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 9a648b01f3360aebc59f127e38e96791a90049bd Mon Sep 17 00:00:00 2001 From: DianaXWiki <139217939+DianaXWiki@users.noreply.github.com> Date: Thu, 17 Oct 2024 17:04:25 +0300 Subject: [PATCH 037/218] Make 2 or less calendars visible on small screens --- www/calendar/inner.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index 67e4a87e1..f291e9a0b 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -900,7 +900,8 @@ define([ $contentContainer.append(calendarEntry); }); }); - if (totalCalendars > 2 && isMobileView) { + if(isMobileView) { + if (totalCalendars > 2) { var $showContainer = $(h('div.cp-calendar-entry.cp-ghost')).appendTo($calendars); var showCalendarsBtn = h('button', [ h('i.fa.fa-eye'), @@ -914,6 +915,8 @@ define([ $(showCalendarsBtn).find('span').first().text(visible ? Messages.calendar_hide : Messages.calendar_show); }).appendTo($showContainer); } + else {visible = true;} + } $contentContainer.toggle(visible); $(window).resize(function () { From dd30a9edf93f376494ee251d4ce0430258f8421e Mon Sep 17 00:00:00 2001 From: DianaXWiki <139217939+DianaXWiki@users.noreply.github.com> Date: Thu, 17 Oct 2024 17:07:29 +0300 Subject: [PATCH 038/218] Display correct name of show/hide calendars --- www/calendar/inner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index f291e9a0b..187d51c6a 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -905,7 +905,7 @@ define([ var $showContainer = $(h('div.cp-calendar-entry.cp-ghost')).appendTo($calendars); var showCalendarsBtn = h('button', [ h('i.fa.fa-eye'), - h('span.cp-calendar-title', Messages.calendar_show), + h('span.cp-calendar-title', visible ? Messages.calendar_hide : Messages.calendar_show), h('span') ]); From 6538b326c7f3103ef2cc30b46781dd19e3bfd5d7 Mon Sep 17 00:00:00 2001 From: Wolfgang Ginolas Date: Thu, 17 Oct 2024 16:09:26 +0200 Subject: [PATCH 039/218] 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 040/218] 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 041/218] 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 c7be93af3d3acd625e201e0cc1835cbc08f3931a Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Sat, 19 Oct 2024 11:16:53 +0200 Subject: [PATCH 042/218] Changed styling of warning --- customize.dist/src/less2/include/creation.less | 5 ++++- www/common/common-ui-elements.js | 11 +++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/customize.dist/src/less2/include/creation.less b/customize.dist/src/less2/include/creation.less index 5ff730a64..65b7b6e62 100644 --- a/customize.dist/src/less2/include/creation.less +++ b/customize.dist/src/less2/include/creation.less @@ -181,8 +181,11 @@ } .cp-creation-password-warning { - background-color:@creation-bg-color-light; + margin-top: 0.4rem; + font-size: 12px !important; + line-height: normal !important } + .cp-creation-help, .cp-creation-warning { font-size: 16px; color: @cp_creation-fg; diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index b54cc0f14..4882efbe1 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -25,6 +25,9 @@ define([ 'css!/customize/fonts/cptools/style.css', ], function ($, Config, Broadcast, Util, Hash, Language, UI, Constants, Feedback, h, Clipboard, Messages, AppConfig, Pages, NThen, InviteInner, Visible, PadTypes) { + + Messages.form_passwordWarning = 'For Forms, you can only set the password during creation. It cannot be changed later.' //XX + var UIElements = {}; var urlArgs = Config.requireConf.urlArgs; @@ -2770,8 +2773,11 @@ define([ ]); // Password - var text = h('div.cp-creation-password-warning', 'Note that for Forms, you can only set the password during creation. This password cannot be changed later.'); - var password = h('div.cp-creation-password', [text, + let text; + if (type === 'form') { + text = h('div.cp-creation-password-warning.alert.alert-warning.dismissable', h('span.cp-inline-alert-text', Messages.form_passwordWarning)); + } + var password = h('div.cp-creation-password', [ UI.createCheckbox('cp-creation-password', Messages.properties_addPassword, false), h('span.cp-creation-password-picker.cp-creation-slider', [ UI.passwordInput({id: 'cp-creation-password-val'}) @@ -2779,6 +2785,7 @@ define([ type: "text" // TODO type password with click to show }),*/ ]), + text, //createHelper('#', "TODO: password protection adds another layer of security ........") // TODO ]); From 5186dbef8d6cb8552b1cf7e198416a13418a3a06 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Sun, 20 Oct 2024 22:20:35 +0200 Subject: [PATCH 043/218] Fixes element selection --- www/common/drive-ui.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index d8293a3d4..a8bb7ed99 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -316,7 +316,14 @@ define([ APP.selectedFiles = []; var isElementSelected = function ($element) { - var elementId = $element.data("path").slice(-1)[0]; + var isTrashed = $element.data("path")[0] === 'trash' + let elementId; + if (isTrashed) { + elementId = $element.data("path")[1]; + } else { + elementId = $element.data("path").slice(-1); + } + console.log('ELEMENT', elementId) return APP.selectedFiles.indexOf(elementId) !== -1; }; var selectElement = function ($element) { @@ -2420,6 +2427,11 @@ define([ draggable: true })); $element.data('path', newPath); + if (newPath[0] !== 'trash') { + if (isElementSelected($element)) { + selectElement($element); + } + } $element.prepend($icon).dblclick(function () { if (restricted) { From 8587965d684e831e8999df7aea9f9c32bd765c1f Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Sun, 20 Oct 2024 22:30:20 +0200 Subject: [PATCH 044/218] Element selection --- 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 a8bb7ed99..0d4e35a4d 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -321,7 +321,7 @@ define([ if (isTrashed) { elementId = $element.data("path")[1]; } else { - elementId = $element.data("path").slice(-1); + elementId = $element.data("path").slice(-1)[0]; } console.log('ELEMENT', elementId) return APP.selectedFiles.indexOf(elementId) !== -1; From 54efbe1f82fc175bd4d8812516da0311fd75e5f9 Mon Sep 17 00:00:00 2001 From: David Benque Date: Mon, 21 Oct 2024 11:59:59 +0100 Subject: [PATCH 045/218] Fix LESS nesting --- customize.dist/src/less2/include/creation.less | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/customize.dist/src/less2/include/creation.less b/customize.dist/src/less2/include/creation.less index 65b7b6e62..5955b6725 100644 --- a/customize.dist/src/less2/include/creation.less +++ b/customize.dist/src/less2/include/creation.less @@ -180,12 +180,6 @@ } } - .cp-creation-password-warning { - margin-top: 0.4rem; - font-size: 12px !important; - line-height: normal !important - } - .cp-creation-help, .cp-creation-warning { font-size: 16px; color: @cp_creation-fg; @@ -300,6 +294,11 @@ } } } + .cp-creation-password-warning { + margin-top: 0.4rem; + font-size: 0.75em; + line-height: 100%; + } } .cp-creation-settings { button { From 3f2d8566e16bfcb827c8b6ed7b017139e520087c Mon Sep 17 00:00:00 2001 From: David Benque Date: Mon, 21 Oct 2024 15:07:43 +0100 Subject: [PATCH 046/218] Add temporary translation key --- customize.dist/messages.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/customize.dist/messages.js b/customize.dist/messages.js index a5784b495..4c640c17d 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -136,6 +136,8 @@ define(req, function(AppConfig, Default, Language) { } }; + Messages.form_passwordWarning = 'Please note that a Form password can only be set now at creation time and cannot be changed later.' // XXX + return Messages; }); From 36be1e7fc914be4b57eea19ba5bfdbdf27f254ed Mon Sep 17 00:00:00 2001 From: David Benque Date: Mon, 21 Oct 2024 15:08:33 +0100 Subject: [PATCH 047/218] Adjust alert - changed type from warning to info - increased line-height to 120% --- customize.dist/src/less2/include/creation.less | 2 +- www/common/common-ui-elements.js | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/customize.dist/src/less2/include/creation.less b/customize.dist/src/less2/include/creation.less index 5955b6725..c9bb17a7c 100644 --- a/customize.dist/src/less2/include/creation.less +++ b/customize.dist/src/less2/include/creation.less @@ -297,7 +297,7 @@ .cp-creation-password-warning { margin-top: 0.4rem; font-size: 0.75em; - line-height: 100%; + line-height: 120%; } } .cp-creation-settings { diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 4882efbe1..3433f297d 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -26,8 +26,6 @@ define([ ], function ($, Config, Broadcast, Util, Hash, Language, UI, Constants, Feedback, h, Clipboard, Messages, AppConfig, Pages, NThen, InviteInner, Visible, PadTypes) { - Messages.form_passwordWarning = 'For Forms, you can only set the password during creation. It cannot be changed later.' //XX - var UIElements = {}; var urlArgs = Config.requireConf.urlArgs; @@ -2775,7 +2773,7 @@ define([ // Password let text; if (type === 'form') { - text = h('div.cp-creation-password-warning.alert.alert-warning.dismissable', h('span.cp-inline-alert-text', Messages.form_passwordWarning)); + text = h('div.cp-creation-password-warning.alert.alert-info.dismissable', h('span.cp-inline-alert-text', Messages.form_passwordWarning)); } var password = h('div.cp-creation-password', [ UI.createCheckbox('cp-creation-password', Messages.properties_addPassword, false), From a6dfc51e0de693ea7d071942e2a00ba785f1d550 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Mon, 21 Oct 2024 16:33:33 +0200 Subject: [PATCH 048/218] Removed redundant check --- www/common/drive-ui.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 0d4e35a4d..b4fe033db 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -316,14 +316,13 @@ define([ APP.selectedFiles = []; var isElementSelected = function ($element) { - var isTrashed = $element.data("path")[0] === 'trash' + var isTrashed = $element.data("path")[0] === TRASH let elementId; if (isTrashed) { elementId = $element.data("path")[1]; } else { elementId = $element.data("path").slice(-1)[0]; } - console.log('ELEMENT', elementId) return APP.selectedFiles.indexOf(elementId) !== -1; }; var selectElement = function ($element) { @@ -2427,10 +2426,8 @@ define([ draggable: true })); $element.data('path', newPath); - if (newPath[0] !== 'trash') { - if (isElementSelected($element)) { - selectElement($element); - } + if (isElementSelected($element)) { + selectElement($element); } $element.prepend($icon).dblclick(function () { From 6e3a756d511d3091f1d2756d533b9cfedc3b3c7f Mon Sep 17 00:00:00 2001 From: DianaXWiki <139217939+DianaXWiki@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:51:59 +0300 Subject: [PATCH 049/218] Implement UI review fixes --- www/calendar/app-calendar.less | 2 +- www/calendar/inner.js | 25 +++++++++++++++---------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/www/calendar/app-calendar.less b/www/calendar/app-calendar.less index bcb240dcc..65424d200 100644 --- a/www/calendar/app-calendar.less +++ b/www/calendar/app-calendar.less @@ -640,7 +640,7 @@ } &.cp-ghost { padding: 0; - margin-top: 0.5rem; + margin-top: 1rem; button { .tools_unselectable(); cursor: pointer; diff --git a/www/calendar/inner.js b/www/calendar/inner.js index 187d51c6a..40ffc1e47 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -775,7 +775,7 @@ define([ var data = APP.calendars[id]; var edit; if (data.loading) { - edit = h('i.fa.fa-spinner.fa-spin'); + edit = h('i.fa.fa-spinner.fa-spin', {'aria-hidden': 'true'}); } else { edit = makeEditDropdown(id, teamId); } @@ -796,8 +796,8 @@ define([ h('i.cp-calendar-inactive.fa.fa-calendar-o') ]), h('span.cp-calendar-title', md.title), - data.restricted ? h('i.fa.fa-ban', {title: Messages.fm_restricted}) : - (isReadOnly(id, teamId) ? h('i.fa.fa-eye', {title: Messages.readonly}) : undefined), + data.restricted ? h('i.fa.fa-ban', {title: Messages.fm_restricted}, {'aria-hidden': 'true'}) : + (isReadOnly(id, teamId) ? h('i.fa.fa-eye', {title: Messages.readonly}) : undefined, {'aria-hidden': 'true'}), edit ]); var $calendar = $(calendar).click(function () { @@ -875,7 +875,7 @@ define([ // Add the new calendar button var $newContainer = $(h('div.cp-calendar-entry.cp-ghost')).appendTo($contentContainer); var newButton = h('button', [ - h('i.fa.fa-calendar-plus-o'), + h('i.fa.fa-calendar-plus-o', {'aria-hidden': 'true'}), h('span', Messages.calendar_new), h('span') ]); @@ -903,15 +903,20 @@ define([ if(isMobileView) { if (totalCalendars > 2) { var $showContainer = $(h('div.cp-calendar-entry.cp-ghost')).appendTo($calendars); + var iconClass = visible ? 'fa-eye-slash' : 'fa-eye'; + var buttonText = visible ? Messages.calendar_hide : Messages.calendar_show; var showCalendarsBtn = h('button', [ - h('i.fa.fa-eye'), - h('span.cp-calendar-title', visible ? Messages.calendar_hide : Messages.calendar_show), + h('i.fa.' + iconClass, {'aria-hidden': 'true'}), + h('span.cp-calendar-title', buttonText), h('span') ]); $(showCalendarsBtn).click(() => { visible = !visible; $contentContainer.toggle(visible); + iconClass = visible ? 'fa-eye-slash' : 'fa-eye'; + buttonText = visible ? Messages.calendar_hide : Messages.calendar_show; + $(showCalendarsBtn).find('i').attr('class', 'fa ' + iconClass); $(showCalendarsBtn).find('span').first().text(visible ? Messages.calendar_hide : Messages.calendar_show); }).appendTo($showContainer); } @@ -1296,7 +1301,7 @@ ICS ==> create a new event with the same UID and a RECURRENCE-ID field (with a v APP.toolbar.$bottomR.append($block); // New event button var newEventBtn = h('button.cp-calendar-newevent', [ - h('i.fa.fa-plus'), + h('i.fa.fa-plus', {'aria-hidden': 'true'}), h('span', Messages.calendar_newEvent) ]); $(newEventBtn).click(function (e) { @@ -2169,7 +2174,7 @@ APP.recurrenceRule = { // Customize creation/update popup var onCalendarPopup = function (el) { var $el = $(el); - $el.find('.tui-full-calendar-confirm').addClass('btn btn-primary').prepend(h('i.fa.fa-floppy-o')); + $el.find('.tui-full-calendar-confirm').addClass('btn btn-primary').prepend(h('i.fa.fa-floppy-o', {'aria-hidden': 'true'})); $el.find('input').attr('autocomplete', 'off'); $el.find('.tui-full-calendar-dropdown-button').addClass('btn btn-secondary'); $el.find('.tui-full-calendar-popup-close').addClass('btn btn-cancel fa fa-times cp-calendar-close').empty(); @@ -2251,7 +2256,7 @@ APP.recurrenceRule = { $el.find('.tui-full-calendar-content').removeClass('tui-full-calendar-content'); var delButton = h('button.btn.btn-danger', [ - h('i.fa.fa-trash'), + h('i.fa.fa-trash', {'aria-hidden': 'true'}), h('span', Messages.kanban_delete) ]); var $del = $el.find('.tui-full-calendar-popup-delete').hide(); @@ -2284,7 +2289,7 @@ APP.recurrenceRule = { // This is a recurring event, add button to stop recurrence now var $b = $(h('button.btn.btn-default', [ - h('i.fa.fa-times'), + h('i.fa.fa-times', {'aria-hidden': 'true'}), h('span', Messages.calendar_rec_stop) ])).insertBefore($section); UI.confirmButton($b[0], { classes: 'btn-default' }, function () { From a14d1b0dbf390f18320f8c15305c5022933a6f7e Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Tue, 22 Oct 2024 11:18:10 +0200 Subject: [PATCH 050/218] Changed way elementIds are assigned --- www/common/drive-ui.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index b4fe033db..397c14769 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -314,26 +314,29 @@ define([ }; APP.selectedFiles = []; - - var isElementSelected = function ($element) { + var findElementId = function ($element) { var isTrashed = $element.data("path")[0] === TRASH let elementId; if (isTrashed) { - elementId = $element.data("path")[1]; + elementId = $element.data("path").join(',') } else { elementId = $element.data("path").slice(-1)[0]; } + return elementId + } + var isElementSelected = function ($element) { + var elementId = findElementId($element) return APP.selectedFiles.indexOf(elementId) !== -1; }; var selectElement = function ($element) { - var elementId = $element.data("path").slice(-1)[0]; + var elementId = findElementId($element) if (APP.selectedFiles.indexOf(elementId) === -1) { APP.selectedFiles.push(elementId); } $element.addClass("cp-app-drive-element-selected"); }; var unselectElement = function ($element) { - var elementId = $element.data("path").slice(-1)[0]; + var elementId = findElementId($element) var index = APP.selectedFiles.indexOf(elementId); if (index !== -1) { APP.selectedFiles.splice(index, 1); From 4a09f8637ea9d8d94208bb383cedb054234e0bdd Mon Sep 17 00:00:00 2001 From: Weblate Date: Tue, 22 Oct 2024 12:12:33 +0200 Subject: [PATCH 051/218] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1783 of 1783 strings) Co-authored-by: Subtext6676 Co-authored-by: Weblate Translate-URL: https://weblate.cryptpad.org/projects/cryptpad/app/zh_Hans/ Translation: CryptPad/App --- www/common/translations/messages.zh.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/translations/messages.zh.json b/www/common/translations/messages.zh.json index d3c10c419..c25eb1c22 100644 --- a/www/common/translations/messages.zh.json +++ b/www/common/translations/messages.zh.json @@ -20,7 +20,7 @@ "diagram": "图表" }, "common_connectionLost": "伺服器連線中斷
現在是唯讀狀態,直到連線恢復正常。", - "typeError": "此文档与所选应用进程不兼容", + "typeError": "此文档与所选应用不兼容", "onLogout": "您已退出登录,{0}点击此处{1}登录
或按 Esc 以只读模式访问您的文档。", "loading": "載入中...", "error": "錯誤", From a481c3c802b9c04b33b09fb68e411e6443fd6855 Mon Sep 17 00:00:00 2001 From: Weblate Date: Tue, 22 Oct 2024 12:12:33 +0200 Subject: [PATCH 052/218] Translated using Weblate (Polish) Currently translated at 99.9% (1782 of 1783 strings) Translated using Weblate (Polish) Currently translated at 100.0% (1783 of 1783 strings) Translated using Weblate (Polish) Currently translated at 100.0% (1783 of 1783 strings) Translated using Weblate (Polish) Currently translated at 100.0% (1783 of 1783 strings) Translated using Weblate (Polish) Currently translated at 99.7% (1779 of 1783 strings) Translated using Weblate (Polish) Currently translated at 99.8% (1781 of 1783 strings) Translated using Weblate (Polish) Currently translated at 99.9% (1782 of 1783 strings) Translated using Weblate (Polish) Currently translated at 99.8% (1780 of 1783 strings) Translated using Weblate (Polish) Currently translated at 99.8% (1780 of 1783 strings) Translated using Weblate (Polish) Currently translated at 99.6% (1777 of 1783 strings) Translated using Weblate (Polish) Currently translated at 99.6% (1777 of 1783 strings) Translated using Weblate (Polish) Currently translated at 99.9% (1782 of 1783 strings) Translated using Weblate (Polish) Currently translated at 99.9% (1782 of 1783 strings) Translated using Weblate (Polish) Currently translated at 99.9% (1782 of 1783 strings) Translated using Weblate (Polish) Currently translated at 100.0% (1783 of 1783 strings) Translated using Weblate (Polish) Currently translated at 100.0% (1783 of 1783 strings) Translated using Weblate (Polish) Currently translated at 100.0% (1783 of 1783 strings) Translated using Weblate (Polish) Currently translated at 100.0% (1783 of 1783 strings) Translated using Weblate (Polish) Currently translated at 100.0% (1783 of 1783 strings) Translated using Weblate (Polish) Currently translated at 100.0% (1783 of 1783 strings) Co-authored-by: Magpie Co-authored-by: Weblate Co-authored-by: Zuzanna Maria Co-authored-by: dlaska Translate-URL: https://weblate.cryptpad.org/projects/cryptpad/app/pl/ Translation: CryptPad/App --- www/common/translations/messages.pl.json | 220 +++++++++++------------ 1 file changed, 110 insertions(+), 110 deletions(-) diff --git a/www/common/translations/messages.pl.json b/www/common/translations/messages.pl.json index 705f6a363..8b9efeab6 100644 --- a/www/common/translations/messages.pl.json +++ b/www/common/translations/messages.pl.json @@ -1,16 +1,16 @@ { - "main_title": "CryptPad: Wspólne edytowanie w czasie rzeczywistym, bez wiedzy specjalistycznej", + "main_title": "CryptPad: Współpraca w czasie rzeczywistym, szyfrowana z wiedzą zerową", "type": { - "pad": "Pad", + "pad": "Tekst sformatowany", "code": "Kod", - "poll": "Balot", + "poll": "Ankieta", "slide": "Slajdy Markdown", "contacts": "Kontakty", "file": "Plik", "kanban": "Kanban", "presentation": "Prezentacja", "doc": "Dokument", - "form": "Formularz", + "form": "Ankieta", "teams": "Zespoły", "sheet": "Arkusz", "todo": "Do zrobienia", @@ -49,7 +49,7 @@ "poll_userPlaceholder": "Twoje imię", "poll_removeOption": "Jesteś pewien, że chcesz usunąć tę opcję?", "poll_removeUser": "Jesteś pewien, że chcesz usunąć tego użytkownika?", - "poll_descriptionHint": "Opisz swoją ankietę i użyj przycisku ✓ (opublikuj), gdy skończysz.\nW opisie możesz użyć składni markdown oraz osadzać elementy multimedialne z dysku CryptDrive.\nKażda osoba posiadająca link może zmienić opis, co jednak nie jest zalecane.", + "poll_descriptionHint": "Opisz swoją ankietę i użyj przycisku ✓ (opublikuj), gdy skończysz.\nW opisie możesz użyć składni Markdown oraz osadzać elementy multimedialne z dysku CryptDrive.\nKażda osoba posiadająca link może zmienić opis, co jednak nie jest zalecane.", "header_logoTitle": "Przejdź na stronę główną", "storageStatus": "Pamięć:
Wykorzystałeś {0} z {1}", "upgradeAccount": "Ulepsz swoje konto", @@ -60,25 +60,25 @@ "forgotten": "Przeniesiono do kosza", "initializing": "Inicjalizacja...", "typing": "Edytowanie", - "realtime_unrecoverableError": "Wystąpił nieodwracalny błąd. Wciśnij OK, aby odświeżyć stronę.", - "disabledApp": "Ta aplikacja nie jest dostępna. Skontaktuj się z administratorem, aby uzyskać więcej informacji.", - "mustLogin": "Musisz być zalogowany/a aby otrzymać dostęp do tej strony", + "realtime_unrecoverableError": "Wystąpił nieodwracalny błąd. Kliknij OK, aby załadować ponownie.", + "disabledApp": "Ta aplikacja nie jest dostępna. Skontaktuj się z administratorem, aby uzyskać więcej informacji.", + "mustLogin": "Musisz być zalogowany/a, aby otrzymać dostęp do tej strony", "deletedFromServer": "Dokument został zniszczony", "deleted": "Usunięto", "saved": "Zapisano", "error": "Błąd", "loading": "Ładowanie...", - "newVersionError": "Nowa wersja CryptPad jest dostępna.
Odśwież aby korzystać z nowej wersji lub wciśnij klawisz Esc aby pracować w trybie offline.", - "errorRedirectToHome": "Wciśnij Esc, by zostać przekierowanym do własnego dysku CryptDrive.", - "errorCopy": " Możesz korzystać z aktualnej wersji w trybie tylko do odczytu, klikając Esc.", - "invalidHashError": "Dokument, który chcesz zobaczyć ma błędny adres URL.", - "chainpadError": "Podczas aktualizacji zawartości wystąpił krytyczny błąd. Dokument wyświetlany jest w trybie tylko do odczytu, aby umożliwić zachowanie wyników pracy.
Wciśnij klawisz Esc aby wyświetlić dokument lub odśwież stronę, by spróbować wrócić do trybu edycji.", - "inactiveError": "Ten dokument został usunięty z powodu braku aktywności. Wciśnij klawisz Esc, aby stworzyć nowy dokument.", + "newVersionError": "Nowa wersja CryptPad jest dostępna.
Odśwież aby korzystać z nowej wersji, lub wciśnij klawisz Esc, aby pracować w trybie offline.", + "errorRedirectToHome": "Wciśnij Esc, by zostać przekierowanym do Twojego dysku CryptDrive.", + "errorCopy": " Możesz korzystać z aktualnej wersji w trybie tylko do odczytu wciskając Esc.", + "invalidHashError": "Dokument, który próbujesz wyświetlić, ma nieprawidłowy adres URL.", + "chainpadError": "Podczas aktualizacji zawartości wystąpił krytyczny błąd. Dokument wyświetlany jest w trybie tylko do odczytu, aby umożliwić zachowanie wyników pracy.
Wciśnij klawisz ESC aby wyświetlić dokument lub załaduj ponownie, by spróbować dalszej edycji.", + "inactiveError": "Ten dokument został usunięty z powodu nieaktywności. Kliknij klawisz ESC, aby stworzyć nowy dokument.", "deletedError": "Dokument został usunięty i nie jest już dostępny.", "expiredError": "Dokument wygasł i nie jest już dostępny.", "anonymousStoreDisabled": "Administrator tej instancji CryptPad wyłączył zapisywanie danych dla niezalogowanych użytkowników. Zaloguj się, aby korzystać z własnego dysku CryptDrive.", - "padNotPinnedVariable": "Dokument zostanie usunięty po {4} dniach braku aktywności, {0}zaloguj się{1} lub {2}zarejestruj{3} aby go zachować.", - "padNotPinned": "Ten dokument zostanie usunięty po 3 miesiącach braku aktywności, {0}zaloguj się{1} lub {2}zarejestruj{3} aby go zachować.", + "padNotPinnedVariable": "Dokument zostanie usunięty po {4} dniach braku aktywności, {0}zaloguj się{1} lub {2}zarejestruj{3}, aby go zachować.", + "padNotPinned": "Ten dokument zostanie usunięty po 3 miesiącach braku aktywności, {0}zaloguj się{1} lub {2}zarejestruj{3}, aby go zachować.", "onLogout": "Jesteś wylogowany, {0}kliknij tutaj{1} aby się zalogować
lub użyj klawisza Esc aby wyświetlić swój dokument (tryb tylko do odczytu).", "typeError": "Dokument nie jest kompatybilny z wybraną aplikacją", "template_empty": "Brak szablonów", @@ -90,9 +90,9 @@ "templateSaved": "Szablon zapisany!", "saveTemplatePrompt": "Wybierz nazwę dla szablonu", "saveTemplateButton": "Zapisz jako szablon", - "uploadButtonTitle": "Prześlij nowy plik na swój dysk CryptDrive", - "uploadFolderButton": "Wgraj folder", - "uploadButton": "Prześlij pliki", + "uploadButtonTitle": "Wrzuć nowy plik na swój dysk CryptDrive", + "uploadFolderButton": "Wrzuć folder", + "uploadButton": "Wrzuć pliki", "newButtonTitle": "Utwórz nowy dokument", "newButton": "Nowy", "userAccountButton": "Menu użytkownika", @@ -109,7 +109,7 @@ "pinLimitDrive": "Osiągnięto limit przestrzeni dyskowej.
Nie możesz tworzyć nowych dokumentów.", "pinLimitNotPinned": "Osiągnięto limit przestrzeni dyskowej.
Dokument nie będzie zapisany na Twoim CryptDrive.", "pinLimitReachedAlertNoAccounts": "Osiągnięto limit przestrzeni dyskowej", - "pinLimitReachedAlert": "Osiągnięto limit przestrzeni dyskowej. Nowe dokumenty nie będą przechowywane na Twoim dysku CryptDrive.
Możesz usunąć dokumenty ze swojego CryptDrive lub skorzystać z oferty premium aby zwiększyć jego pojemność.", + "pinLimitReachedAlert": "Osiągnięto limit przestrzeni dyskowej. Nowe dokumenty nie będą przechowywane na Twoim dysku CryptDrive.
Możesz usunąć dokumenty ze swojego CryptDrive lub skorzystać z oferty premium, aby zwiększyć jego pojemność.", "pinLimitReached": "Osiągnięto limit przestrzeni dyskowej", "formattedKB": "{0} kB", "formattedGB": "{0} GB", @@ -118,9 +118,9 @@ "GB": "GB", "MB": "MB", "fm_viewListButton": "Widok listy", - "fm_error_cantPin": "Wewnętrzny błąd serwera. Proszę przeładować stronę i spróbować ponownie.", + "fm_error_cantPin": "Wewnętrzny błąd serwera. Proszę załadować stronę ponownie i spróbować jeszcze raz.", "fm_info_owned": "Jesteś właścicielem wyświetlanych tu dokumentów. Oznacza to, że możesz je trwale usunąć z serwera, kiedy tylko zechcesz. Jeśli to zrobisz, inni użytkownicy nie będą mieli już do nich dostępu.", - "fm_info_sharedFolder": "To jest folder współdzielony. Nie jesteś zalogowany, więc masz do niego dostęp tylko w trybie do odczytu.
Zarejestruj się lub zaloguj aby móc zaimportować go na swój dysk CryptDrive i modyfikować.", + "fm_info_sharedFolder": "To jest folder współdzielony. Nie jesteś zalogowany, więc masz do niego dostęp wyłącznie w trybie tylko do odczytu.
Zarejestruj się lub zaloguj aby móc zaimportować go na swój dysk CryptDrive i modyfikować.", "fm_info_anonymous": "Nie jesteś zalogowany, więc Twoje dokumenty wygasną po {0} dniach. Wyczyszczenie historii przeglądarki może spowodować ich zniknięcie.
Zarejestruj się (dane osobowe nie są wymagane) lubZaloguj się, aby przechowywać je na dysku przez czas nieokreślony.Dowiedz się więcej o zarejestrowanych kontach.", "fm_info_trash": "Opróżnij swój kosz, aby zwolnić miejsce na swoim dysku CryptDrive.", "fm_info_recent": "Dokumenty te były ostatnio otwierane lub modyfikowane przez Ciebie lub osoby, z którymi współpracujesz.", @@ -128,7 +128,7 @@ "fm_info_root": "Utwórz tutaj tyle zagnieżdżonych folderów, ile chcesz, aby uporządkować swoje pliki.", "fm_categoryError": "Nie można otworzyć wybranej kategorii, wyświetlany jest folder główny.", "fm_selectError": "Nie można wybrać elementu docelowego. Jeśli problem nadal występuje, spróbuj ponownie załadować stronę.", - "fm_contextMenuError": "Nie można otworzyć menu kontekstowego dla tego elementu. Jeśli problem nadal występuje, spróbuj przeładować stronę.", + "fm_contextMenuError": "Nie można otworzyć menu kontekstowego dla tego elementu. Jeśli problem nadal występuje, spróbuj ponownie załadować stronę.", "fm_unknownFolderError": "Wybrany lub ostatnio odwiedzany folder już nie istnieje. Otwieranie folderu nadrzędnego...", "fm_restoreDialog": "Czy na pewno chcesz przywrócić {0} do poprzedniej lokalizacji?", "fm_deleteOwnedPads": "Czy jesteś pewien, że chcesz trwale zniszczyć te dokumenty?", @@ -189,9 +189,9 @@ "profile_viewMyProfile": "Wyświetl mój profil", "profile_register": "Musisz się zarejestrować, aby stworzyć profil!", "profile_error": "Błąd podczas tworzenia profilu: {0}", - "profile_uploadTypeError": "Błąd: typ Twojego avatara jest niedozwolony. Dozwolone typy to: {0}", + "profile_uploadTypeError": "Błąd: typ Twojego awatara jest niedozwolony. Dozwolone typy to: {0}", "profile_uploadSizeError": "Błąd: Twój awatar musi być mniejszy niż {0}", - "profile_upload": " Prześlij nowy awatar", + "profile_upload": " Wrzuć nowy awatar", "profileButton": "Profil", "canvas_imageEmbed": "Dołącz obraz z komputera", "canvas_currentBrush": "Aktualny pędzel", @@ -202,9 +202,9 @@ "canvas_width": "Szerokość", "canvas_delete": "Usuń zaznaczenie", "canvas_clear": "Wyczyść", - "oo_cantUpload": "Przesyłanie danych nie jest dozwolone, gdy inni użytkownicy są aktywni.", - "oo_uploaded": "Przesyłanie danych zostało zakończone. Kliknij OK, aby ponownie załadować stronę lub Anuluj, aby kontynuować w trybie tylko do odczytu.", - "oo_reconnect": "Połączenie z serwerem zostało przywrócone. Kliknij OK, aby przeładować i kontynuować edycję.", + "oo_cantUpload": "Wrzucanie plików nie jest dozwolone, gdy inni użytkownicy są aktywni.", + "oo_uploaded": "Wrzucanie plików zostało zakończone. Kliknij OK, aby ponownie załadować stronę lub anuluj, aby kontynuować w trybie tylko do odczytu.", + "oo_reconnect": "Połączenie z serwerem zostało przywrócone. Kliknij OK, aby załadować ponownie i kontynuować edycję.", "poll_comment_disabled": "Opublikuj tę ankietę używając przycisku ✓, aby włączyć komentarze.", "poll_comment_placeholder": "Twój komentarz", "poll_comment_remove": "Usuń ten komentarz", @@ -218,7 +218,7 @@ "poll_locked": "Zablokowane", "poll_edit": "Edytuj", "poll_remove": "Usuń", - "poll_commit": "Wyślij", + "poll_commit": "Prześlij", "poll_create_option": "Dodaj nową opcję", "poll_create_user": "Dodaj nowego użytkownika", "poll_publish_button": "Opublikuj", @@ -232,10 +232,10 @@ "pad_mediatagImport": "Zapisz na swoim CryptDrive", "pad_mediatagPreview": "Podgląd", "pad_mediatagBorder": "Szerokość ramki (w pikselach)", - "pad_mediatagRatio": "Zachowuj proporcje", + "pad_mediatagRatio": "Zachowaj proporcje", "pad_mediatagHeight": "Wysokość (w pikselach)", "pad_mediatagWidth": "Szerokość (w pikselach)", - "pad_mediatagTitle": "Ustawienia Tagów Medialnych", + "pad_mediatagTitle": "Ustawienia Tagów Mediów", "openLinkInNewTab": "Otwórz link w nowej karcie", "history_restoreDone": "Dokument przywrócony", "history_restorePrompt": "Czy na pewno chcesz zastąpić aktualną wersję dokumentu wyświetlaną wersją?", @@ -258,7 +258,7 @@ "languageButtonTitle": "Wybierz język, który ma być używany do kolorowania składni", "languageButton": "Język", "slide_invalidLess": "Nieprawidłowy styl własny", - "slideOptionsTitle": "Dostosuj swoje slajdy", + "slideOptionsTitle": "Personalizuj swoje slajdy", "slideOptionsText": "Opcje", "tags_noentry": "Nie możesz oznaczyć usuniętego dokumentu", "tags_duplicate": "Duplikat tagu: {0}", @@ -266,12 +266,12 @@ "tags_add": "Aktualizuj tagi dla wybranych dokumentów", "tags_title": "Tagi (tylko dla Ciebie)", "filePicker_filter": "Filtruj pliki według nazwy", - "filePicker_description": "Wybierz plik ze swojego CryptDrive który chcesz osadzić, lub załaduj nowy", + "filePicker_description": "Wybierz plik który chcesz osadzić ze swojego CryptDrive, lub załaduj nowy", "filePicker_close": "Zamknij", "filePickerButton": "Osadź plik przechowywany na dysku CryptDrive", "printBackgroundRemove": "Usuń ten obraz tła", "printBackgroundNoValue": "Nie wyświetlono żadnego obrazu tła", - "printBackgroundValue": "Aktualny obraz tła: {0}", + "printBackgroundValue": "Obecne tło: {0}", "printBackgroundButton": "Wybierz obraz", "printBackground": "Użyj obrazu tła", "printTransition": "Włącz animacje przejść", @@ -283,7 +283,7 @@ "printButtonTitle2": "Drukuj dokument lub zapisz go jako plik PDF", "printButton": "Drukuj (Enter)", "printText": "Drukuj", - "propertiesButtonTitle": "Pobierz właściwości dokumentu", + "propertiesButtonTitle": "Wyświetl właściwości dokumentu", "propertiesButton": "Właściwości", "previewButtonTitle": "Wyświetl lub ukryj tryb podglądu Markdown", "settings_codeIndentation": "Wcięcie w edytorze kodu (spacje)", @@ -340,7 +340,7 @@ "settings_export_compressing": "Kompresja danych...", "settings_export_download": "Pobieranie i odszyfrowywanie dokumentów...", "settings_exportCancel": "Czy na pewno chcesz anulować eksport? Następnym razem będziesz musiał zacząć od początku.", - "settings_exportWarning": "Uwaga: to narzędzie jest wciąż w wersji beta i może mieć problemy ze skalowalnością. Aby uzyskać lepszą wydajność, zaleca się pozostawienie tej zakładki aktywnej.", + "settings_exportWarning": "Uwaga: narzędzie jest wciąż w wersji beta i może mieć problemy ze skalowalnością. W celu uzyskania lepszej wydajności, zaleca się pozostawienie tej karty aktywnej.", "settings_exportFailed": "Jeśli pobranie dokumentu wymaga więcej niż 1 minutę, nie zostanie on wyeksportowany. Zostanie wyświetlony link do każdego dokumentu, który nie został wyeksportowany.", "settings_exportDescription": "Zaczekaj, aż pobierzemy i odszyfrujemy Twoje dokumenty. Może to potrwać kilka minut. Zamknięcie karty spowoduje przerwanie tego procesu.", "settings_exportTitle": "Eksportuj swój CryptDrive", @@ -359,10 +359,10 @@ "settings_cat_cursor": "Kursor", "settings_cat_drive": "Dysk CryptDrive", "settings_cat_account": "Konto", - "register_emailWarning3": "Jeśli to rozumiesz i chcesz używać swojego adresu e-mail jako nazwy użytkownika, kliknij OK.", - "register_emailWarning2": "Nie będziesz mógł zresetować swojego hasła za pomocą swojego e-maila, tak jak można to zrobić w przypadku innych usług.", - "register_emailWarning1": "Możesz to zrobić, jeśli chcesz, ale nie zostanie to wysłane na nasz serwer.", - "register_emailWarning0": "Wygląda na to, że podałeś swój e-mail jako nazwę użytkownika.", + "register_emailWarning3": "Jeśli rozumiesz i nadal chcesz użyć swojego adresu email jako nazwy użytkownika, kliknij OK.", + "register_emailWarning2": "Nie będziesz mógł zresetować swojego hasła drogą mailową, tak jak można to zrobić w przypadku wielu innych usług.", + "register_emailWarning1": "Możesz tak zrobić, jeśli chcesz, ale nie zostanie on przesłany na nasz serwer.", + "register_emailWarning0": "Wygląda na to, że próbujesz użyć swojego maila jako nazwy użytkownika.", "register_alreadyRegistered": "Ten użytkownik już istnieje, czy chcesz się zalogować?", "register_warning": "Ostrzeżenie", "register_cancel": "Anuluj", @@ -386,7 +386,7 @@ "logoutButton": "Wyloguj się", "login_register": "Zarejestruj się", "login_login": "Zaloguj się", - "fo_unavailableName": "W nowej lokalizacji istnieje już plik lub folder o tej samej nazwie. Zmień nazwę elementu i spróbuj ponownie.", + "fo_unavailableName": "W nowej lokalizacji istnieje już plik lub folder o tej samej nazwie. Zmień nazwę elementu i spróbuj jeszcze raz.", "fo_moveFolderToChildError": "Nie można przenieść folderu do folderu podrzędnego", "fo_existingNameError": "Nazwa jest już używana w tym folderze. Proszę wybrać inną.", "fo_moveUnsortedError": "Nie można przenieść folderu do listy szablonów", @@ -403,7 +403,7 @@ "fc_delete": "Przenieś do kosza", "fc_expandAll": "Rozwiń Wszystkie", "fc_collapseAll": "Zwiń Wszystkie", - "fc_open_ro": "Otwó©z (tylko do odczytu)", + "fc_open_ro": "Otwórz (tylko do odczytu)", "fc_open": "Otwórz", "fc_color": "Zmień kolor", "fc_rename": "Zmień nazwę", @@ -421,11 +421,11 @@ "fm_renamedPad": "Nadano nazwę własną dla tego dokumentu. Jego udostępniony tytuł to:
{0}", "fm_viewGridButton": "Widok siatki", "requestEdit_confirm": "{1} poprosił o możliwość edycji dokumentu {0}. Czy chcesz przyznać mu dostęp?", - "requestEdit_button": "Poproś o prawa do edycji", + "requestEdit_button": "Poproś o zezwolenie edycji", "support_notification": "Administrator odpowiedział na Twoje zgłoszenie", "notifications_dismissAll": "Odrzuć wszystkie", "notifications_cat_archived": "Historia", - "notifications_cat_pads": "Udostępnione Tobie", + "notifications_cat_pads": "Udostępnione dla Ciebie", "notifications_cat_friends": "Prośby o kontakt", "notifications_cat_all": "Wszystkie", "openNotificationsApp": "Otwórz panel powiadomień", @@ -438,8 +438,8 @@ "support_close": "Zamknij zgłoszenie", "support_answer": "Odpowiedz", "support_listHint": "Oto lista zgłoszeń wysłanych do administratorów i ich odpowiedzi. Zamknięte zgłoszenie nie może być ponownie otwarte, ale możesz utworzyć nowe. Możesz ukryć zgłoszenia, które zostały zamknięte.", - "support_listTitle": "Zgłoszenia pomocy technicznej", - "support_cat_tickets": "Aktualne zgłoszenia", + "support_listTitle": "Zgłoszenia wsparcia technicznego", + "support_cat_tickets": "Istniejące zgłoszenia", "support_formMessage": "Wpisz swoją wiadomość…", "support_formContentError": "Błąd: treść jest pusta", "support_formTitleError": "Błąd: tytuł jest pusty", @@ -453,7 +453,7 @@ "admin_supportListTitle": "Skrzynka pocztowa wsparcia technicznego", "admin_supportInitHint": "Możesz skonfigurować skrzynkę mailową która posłuży jako adres wsparcia technicznego, w celu zapewnienia użytkownikom Twojej instancji CryptPad sposobu na bezpieczny kontakt w razie problemów z kontem.", "admin_supportInitTitle": "Wsparcie inicjalizacji skrzynki pocztowej", - "admin_supportAddError": "Niepoprawny klucz prywatny", + "admin_supportAddError": "Nieprawidłowy klucz prywatny", "admin_supportAddKey": "Dodaj klucz prywatny", "admin_supportInitPrivate": "Twoja instancja CryptPad ma skonfigurowany adres wsparcia technicznego, ale Twoje konto nie ma poprawnego klucza prywatnego, aby uzyskać do niej dostęp. Użyj poniższego formularza, aby dodać lub zaktualizować klucz prywatny do swojego konta.", "admin_supportInitHelp": "Twój serwer nie jest jeszcze skonfigurowany do używania skrzynki pocztowej służącej do udzielania pomocy technicznej. Jeśli chcesz, aby skrzynka pocztowa służąca do udzielania pomocy technicznej otrzymywała wiadomości od Twoich użytkowników, powinieneś poprosić administratora serwera o uruchomienie skryptu znajdującego się w \"./scripts/generate-admin-keys.js\", a następnie zapisanie klucza publicznego w pliku \"config.js\" i przesłanie Ci klucza prywatnego.", @@ -467,7 +467,7 @@ "notification_folderShared": "{0} udostępnił Ci folder: {1}", "notification_fileShared": "{0} udostępnił Ci plik: {1}", "notification_padShared": "{0} udostępnił Ci dokument: {1}", - "isNotContact": "{0} jest nie jednym z Twoich kontaktów", + "isNotContact": "{0} nie jest jednym z Twoich kontaktów", "isContact": "{0} jest jednym z Twoich kontaktów", "profile_friendRequestSent": "Prośba o kontakt w toku...", "profile_info": "Inni użytkownicy mogą znaleźć Twój profil za pomocą Twojego awataru na listach użytkowników dokumentów.", @@ -491,8 +491,8 @@ "admin_diskUsageButton": "Wygeneruj raport", "admin_diskUsageHint": "Ilość przestrzeni dyskowej zużywanej przez różne zasoby CryptPada", "admin_diskUsageTitle": "Wykorzystanie dysku", - "timeoutError": "Wystąpił błąd, który przerwał połączenie z serwerem.
Naciśnij Esc, aby przeładować stronę.", - "contact_email": "E-mail", + "timeoutError": "Wystąpił błąd, który przerwał połączenie z serwerem.
Naciśnij Esc, aby ponownie załadować stronę.", + "contact_email": "Email", "team_cat_chat": "Czat", "contact_chat": "Czat", "contact_bug": "Zgłoszenie błędu", @@ -518,25 +518,25 @@ "adminPage": "Administracja", "admin_cat_stats": "Statystyki", "admin_cat_general": "Ogólne", - "admin_authError": "Tylko administratorzy mogą mieć dostęp do tej strony", + "admin_authError": "Tylko administratorzy mają dostęp do tej strony", "fm_expirablePad": "Traci ważność: {0}", "markdown_toc": "Zawartość", - "survey": "Ankieta CryptPada", + "survey": "Ankieta CryptPad", "crowdfunding_popup_no": "Nie teraz", - "crowdfunding_popup_text": "

Potrzebujemy Twojej pomocy!

Aby zapewnić, że CryptPad jest aktywnie rozwijany, rozważ wsparcie projektu poprzez stronę OpenCollective, gdzie możesz zobaczyć nasze nasz Plan działania i Cele Finansowania.", - "crowdfunding_button2": "Wspomóż", + "crowdfunding_popup_text": "

Potrzebujemy Twojej pomocy!

Aby zapewnić aktywny rozwój pakietu CryptPad, rozważ wsparcie projektu poprzez stronę OpenCollective, gdzie możesz zobaczyć nasz Plan działania i Cele Finansowania.", + "crowdfunding_button2": "Przekaż darowiznę", "crowdfunding_button": "Wspieraj CryptPad", "autostore_notAvailable": "Aby móc korzystać z tej funkcji, dokument musi znajdować się na Twoim CryptDrive.", "autostore_hide": "Nie zapisuj", "autostore_store": "Zapisz", "autostore_forceSave": "Przechowaj plik na swoim CryptDrive", "autostore_saved": "Dokument został pomyślnie zapisany na Twoim CryptDrive!", - "autostore_error": "Nieoczekiwany błąd: nie udało się zapisać tego dokumentu, proszę spróbować ponownie.", + "autostore_error": "Nieoczekiwany błąd: nie udało się zapisać tego dokumentu, spróbuj jeszcze raz.", "autostore_settings": "Możesz włączyć automatyczne przechowywanie dokumentów na stronie Ustawienia.", "autostore_notstored": "Tego {0} nie ma na Twoim CryptDrive. Czy chcesz go teraz zapisać?", - "autostore_pad": "pad", - "autostore_sf": "folder", - "autostore_file": "plik", + "autostore_pad": "dokumentu", + "autostore_sf": "folderu", + "autostore_file": "pliku", "chrome68": "Wygląda na to, że używasz przeglądarki Chrome lub Chromium w wersji 68. Zawiera ona błąd powodujący, że po kilku sekundach strona staje się całkowicie biała lub nie reaguje na kliknięcia. Aby naprawić ten problem, możesz przełączyć się na inną kartę i wrócić do niej lub spróbować przewinąć stronę. Ten błąd powinien zostać naprawiony w następnej wersji przeglądarki.", "convertFolderToSF_confirm": "Ten folder musi zostać przekształcony w folder współdzielony, aby inni mogli go przeglądać. Kontynuować?", "convertFolderToSF_SFChildren": "Ten folder nie może zostać przekształcony na folder współdzielony, ponieważ zawiera już foldery współdzielone. Aby kontynuować, przenieś te foldery współdzielone w inne miejsce.", @@ -560,12 +560,12 @@ "share_linkPresent": "Obecny", "share_linkView": "Podgląd", "share_linkEdit": "Edytuj", - "share_linkAccess": "Prawa dostępu do danych", + "share_linkAccess": "Zezwolenia dostępu", "share_linkCategory": "Link", "properties_changePasswordButton": "Prześlij", - "properties_passwordSuccess": "Hasło zostało pomyślnie zmienione.
Naciśnij OK, aby przeładować i zaktualizować swoje prawa dostępu.", - "properties_passwordWarning": "Hasło zostało pomyślnie zmienione, ale nie udało nam się zaktualizować danych na Twoim dysku CryptDrive. Konieczne może być manualne usunięcie starej wersji dokumentu.
Naciśnij OK, aby ponownie załadować stronę i zaktualizować dane dostępowe.", - "properties_passwordError": "Wystąpił błąd podczas próby zmiany hasła. Proszę spróbować ponownie.", + "properties_passwordSuccess": "Hasło zostało pomyślnie zmienione.
Naciśnij OK, aby załadować ponownie i zaktualizować swoje zezwolenia dostępu.", + "properties_passwordWarning": "Hasło zostało pomyślnie zmienione, ale nie udało nam się zaktualizować danych na Twoim dysku CryptDrive. Konieczne może być manualne usunięcie starej wersji dokumentu.
Naciśnij OK, aby ponownie załadować stronę i zaktualizować zezwolenia dostępu.", + "properties_passwordError": "Wystąpił błąd podczas próby zmiany hasła. Spróbuj jeszcze raz.", "properties_passwordSame": "Nowe hasła muszą się różnić od dotychczasowych.", "properties_confirmChange": "Czy jesteś pewien? Zmiana hasła spowoduje usunięcie jego historii. Użytkownicy bez nowego hasła stracą dostęp do tego dokumentu", "properties_confirmNew": "Czy jesteś pewien? Dodanie hasła spowoduje zmianę adresu URL tego dokumentu i usunięcie jego historii. Użytkownicy bez hasła stracą dostęp do tego dokumentu", @@ -592,7 +592,7 @@ "creation_owned1": "Dokument własny może zostać zniszczony, kiedy tylko właściciel tego chce. Zniszczenie dokumentu sprawia, że jest on niedostępny w CryptDrivach innych użytkowników.", "creation_owned": "Dokument własny", "creation_404": "Ten dokument już nie istnieje. Użyj poniższego formularza, aby utworzyć nowy dokument.", - "feedback_optout": "Jeśli chcesz zrezygnować, odwiedź swoją stronę ustawień użytkownika, gdzie znajdziesz pole wyboru pozwalające włączyć lub wyłączyć opinie użytkowników.", + "feedback_optout": "Jeśli chcesz z tego zrezygnować, odwiedź swoją stronę ustawień użytkownika, gdzie znajdziesz pole wyboru pozwalające włączyć lub wyłączyć opinie użytkowników.", "feedback_privacy": "Dbamy o Twoją prywatność, a jednocześnie chcemy, aby CryptPad był bardzo łatwy w użyciu. Używamy tego pliku, aby dowiedzieć się, które cechy interfejsu użytkownika mają znaczenie dla naszych użytkowników, pytając ich o to oraz rejestrując parametr określający, jaka akcja została podjęta.", "feedback_about": "Jeśli to czytasz, prawdopodobnie byłeś ciekaw, dlaczego CryptPad żąda stron internetowych, gdy wykonujesz pewne czynności.", "view": "pokaż", @@ -603,8 +603,8 @@ "features_f_subscribe_note": "Zarejestrowane konto jest wymagane do subskrypcji", "features_f_subscribe": "Subskrybuj", "features_f_supporter_note": "Pomóż CryptPad osiągnąć stabilność finansową i udowodnić, że oprogramowanie zwiększające prywatność, dobrowolnie finansowane przez użytkowników, powinno być normą", - "features_f_supporter": "Wsparcie prywatności", - "features_f_support_note": "Priorytetowa odpowiedź od zespołu administracyjnego poprzez e-mail i wbudowany system zgłoszeń", + "features_f_supporter": "Wspieraj prywatność", + "features_f_support_note": "Priorytetowa odpowiedź od zespołu administracyjnego drogą mailową i przez wbudowany system zgłoszeń", "features_f_support": "Szybsze wsparcie", "features_f_storage2_note": "Zwiększony limit {0}MB na wysyłanie plików, od 5GB do 50GB, w zależności od planu", "features_f_storage2": "Dodatkowe miejsce do przechowywania danych", @@ -640,7 +640,7 @@ "contact": "Kontakt", "privacy": "Polityka prywatności", "about": "O stronie", - "main_catch_phrase": "Pakiet do współpracy
end-to-end szyfrowany i oparty na otwartym kodzie źródłowym", + "main_catch_phrase": "Pakiet do współpracy
kompleksowo szyfrowany i oparty na otwartym kodzie źródłowym", "home_host": "Niezależna społecznościowa instancja CryptPada.", "mdToolbar_toc": "Spis Treści", "mdToolbar_code": "Kod", @@ -678,22 +678,22 @@ "upload_notEnoughSpace": "Nie ma wystarczającej ilości miejsca na ten plik w Twoim CryptDrive.", "upload_success": "Twój plik ({0}) został pomyślnie przesłany i dodany do Twojego dysku.", "upload_uploadPending": "Przesyłanie pliku jest już w toku. Anulować je i przesłać nowy plik?", - "upload_serverError": "Błąd serwera: nie można w tej chwili przesłać pliku.", + "upload_serverError": "Błąd serwera: nie można w tej chwili wrzucić pliku.", "uploadFolder_modal_forceSave": "Przechowuj pliki w swoim CryptDrive", "uploadFolder_modal_owner": "Pliki własne", "uploadFolder_modal_filesPassword": "Hasło do plików", - "uploadFolder_modal_title": "Opcje przesyłania folderów", + "uploadFolder_modal_title": "Opcje wrzucania folderów", "upload_modal_owner": "Plik własny", "upload_modal_filename": "Nazwa pliku (rozszerzenie {0} dodane automatycznie)", - "upload_modal_title": "Opcje wysyłania plików", - "upload_title": "Prześlij plik", + "upload_modal_title": "Opcje wrzucania plików", + "upload_title": "Wrzucanie plików", "settings_cursorShowLabel": "Pokaż kursory", "settings_cursorShowTitle": "Wyświetlanie pozycji kursora innych użytkowników", "settings_cursorShareLabel": "Udostępnij swoją pozycję", "settings_cursorShareTitle": "Udostępnij moją pozycję kursora", "settings_cursorColorTitle": "Kolor kursora", "settings_changePasswordNewPasswordSameAsOld": "Twoje nowe hasło musi być inne niż obecne hasło.", - "settings_changePasswordPending": "Twoje hasło jest w trakcie aktualizacji. Prosimy nie zamykać ani nie przeładowywać tej strony do czasu zakończenia procesu.", + "settings_changePasswordPending": "Twoje hasło jest w trakcie aktualizacji. Prosimy nie zamykać ani nie ładować tej strony ponownie do czasu zakończenia procesu.", "settings_changePasswordError": "Wystąpił nieoczekiwany błąd. Jeśli nie możesz się zalogować lub zmienić swojego hasła, skontaktuj się z administratorami CryptPad.", "settings_changePasswordConfirm": "Czy na pewno chcesz zmienić swoje hasło? Będziesz musiał ponownie zalogować się na wszystkich swoich urządzeniach.", "settings_changePasswordNewConfirm": "Potwierdź nowe hasło", @@ -702,7 +702,7 @@ "settings_changePasswordButton": "Zmień hasło", "settings_changePasswordHint": "Zmień hasło do swojego konta. Wpisz swoje aktualne hasło i potwierdź nowe hasło wpisując je dwukrotnie.
Nie możemy zresetować Twojego hasła, jeśli je zapomnisz, więc bądź bardzo ostrożny!", "settings_changePasswordTitle": "Zmień swoje hasło", - "settings_ownDrivePending": "Twoje konto jest w trakcie aktualizacji. Prosimy nie zamykać ani nie przeładowywać tej strony do czasu zakończenia procesu.", + "settings_ownDrivePending": "Twoje konto jest w trakcie aktualizacji. Prosimy nie zamykać ani nie ładować tej strony ponownie do czasu zakończenia procesu.", "settings_ownDriveConfirm": "Aktualizacja konta może zająć trochę czasu. Będziesz musiał ponownie zalogować się na wszystkich swoich urządzeniach. Czy jesteś pewien?", "settings_ownDriveButton": "Aktualizuj swoje konto", "settings_ownDriveHint": "Starsze konta nie mają dostępu do najnowszych funkcji z powodów technicznych. Darmowa aktualizacja umożliwi korzystanie z aktualnych funkcji i przygotuje Twój CryptDrive na przyszłe aktualizacje.", @@ -711,7 +711,7 @@ "settings_padOpenLinkHint": "Dzięki tej opcji możesz otwierać osadzone linki po kliknięciu bez otwierania okienka podglądu", "settings_padOpenLinkTitle": "Otwieranie linków przy pierwszym kliknięciu", "settings_padSpellcheckLabel": "Włączanie sprawdzania pisowni w dokumentach tekstowych", - "settings_padSpellcheckHint": "Ta opcja pozwala na włączenie sprawdzania pisowni w dokumentach tekstowych. Błędy ortograficzne będą podkreślone na czerwono i będziesz musiał przytrzymać klawisz Ctrl lub Meta podczas klikania prawym przyciskiem myszy, aby zobaczyć odpowiednie opcje.", + "settings_padSpellcheckHint": "Ta opcja pozwala na włączenie sprawdzania pisowni w dokumentach sformatowanych. Błędy ortograficzne będą podkreślone na czerwono i będą wymagały przytrzymania klawisza Ctrl lub Meta podczas klikania prawym przyciskiem myszy, aby zobaczyć poprawne opcje.", "settings_padSpellcheckTitle": "Sprawdzanie pisowni", "settings_padWidthLabel": "Zmniejsz szerokość edytora", "settings_padWidthHint": "Przełączanie pomiędzy trybem strony (domyślnym), który ogranicza szerokość edytora tekstu, a wykorzystaniem pełnej szerokości ekranu.", @@ -742,20 +742,20 @@ "form_condition_v": "Wybierz odpowiednią wartość", "form_condition_q": "Wybierz pytanie", "form_type_section": "Sekcja warunkowa", - "form_editable": "Edycja po przesłaniu", - "form_makeAnon": "Zanonimizuj odpowiedzi", - "form_responseMsg": "Ta wiadomość zostanie wyświetlona po wysłaniu formularza przez uczestników.", - "form_addMsg": "Dodaj wiadomość o wysłaniu", + "form_editable": "Późniejsza edycja odpowiedzi", + "form_makeAnon": "Anonimowe odpowiedzi", + "form_responseMsg": "Komunikat, który zostanie wyświetlony po wypełnianiu formularza przez uczestnika.", + "form_addMsg": "Dodaj komunikat końcowy", "toolbar_preview": "Podgląd", "form_geturl": "Skopiuj publiczny link", "form_changeTypeConfirm": "Wybierz rodzaj nowego pytania.", - "form_corruptAnswers": "Ten formularz zawiera już odpowiedzi. Zmiana tego rodzaju pytania może unieważnić poprzednie dane odpowiedzi.", + "form_corruptAnswers": "Ten formularz zawiera już odpowiedzi. Zmiana typu dla tego pytania może spowodować, że dane dla przesłanych już odpowiedzi będą nieprawidłowe.", "form_preview": "Zobacz ten formularz", "form_required_off": "Opcjonalne", "form_required_on": "Wymagane", "form_required_answer": "Odpowiedź: ", "form_requiredWarning": "Na poniższe pytania należy udzielić odpowiedzi:", - "form_authAnswer": "Ten formularz nie może być przesłany anonimowo", + "form_authAnswer": "Ten formularz nie może być wypełniony anonimowo", "form_anonAnswer": "Odpowiedzi na ten formularz są anonimowe", "form_viewAllAnswers": "Wyświetl wszystkie odpowiedzi ({0})", "form_viewAnswer": "Wyświetl moje odpowiedzi", @@ -813,7 +813,7 @@ "admin_listMyInstanceHint": "Jeśli Twoja instancja nadaje się do użytku publicznego, możesz wyrazić zgodę na umieszczenie jej w katalogach internetowych. Telemetria serwera musi być włączona, aby miało to jakikolwiek efekt.", "admin_listMyInstanceTitle": "Umieść tę instancję w katalogach publicznych", "admin_consentToContactLabel": "Zgadzam się", - "admin_consentToContactHint": "Telemetria serwera zawiera e-mail kontaktowy administratora, aby programiści mogli powiadomić Cię o poważnych problemach z oprogramowaniem lub Twoją konfiguracją. Nigdy nie będą one udostępniane, sprzedawane lub wykorzystywane w celach marketingowych. Wyraź zgodę na kontakt, jeśli chcesz być informowany o krytycznych problemach z Twoim serwerem.", + "admin_consentToContactHint": "Telemetria serwera zawiera email kontaktowy administratora, aby programiści mogli powiadomić Cię o poważnych problemach z oprogramowaniem lub Twoją konfiguracją. Nigdy nie będą one udostępniane, sprzedawane lub wykorzystywane w celach marketingowych. Wyraź zgodę na kontakt, jeśli chcesz być informowany o krytycznych problemach z Twoim serwerem.", "admin_consentToContactTitle": "Zgoda na kontakt", "admin_checkupButton": "Uruchom diagnostykę", "admin_checkupHint": "CryptPad zawiera stronę, która automatycznie diagnozuje typowe problemy z konfiguracją i sugeruje, jak je poprawić, jeśli to konieczne.", @@ -831,7 +831,7 @@ "form_anonymousBox": "Odpowiedz anonimowo", "form_page": "Strona {0}/{1}", "form_clear": "Wyczyść", - "form_submitWarning": "Przekaż mimo wszystko", + "form_submitWarning": "Prześlij mimo to", "form_delete": "Usuń", "form_reset": "Zresetuj", "form_update": "Zaktualizuj", @@ -852,7 +852,7 @@ "form_type_input": "Tekst", "form_default": "Twoje pytanie?", "form_text_number": "Liczba", - "form_text_email": "E-mail", + "form_text_email": "Email", "form_text_url": "Link", "form_text_text": "Tekst", "form_textType": "Typ tekstu", @@ -874,8 +874,8 @@ "admin_supportPrivHint": "Wyświetl klucz prywatny, który będzie potrzebny innym administratorom do przeglądania zgłoszeń. Formularz do wprowadzenia tego klucza będzie wyświetlany w ich panelu administracyjnym.", "admin_supportInitGenerate": "Generowanie kluczy wsparcia", "admin_supportPrivTitle": "Obsługa klucza prywatnego skrzynki pocztowej", - "admin_emailHint": "Wprowadź e-mail kontaktowy dla Twojej instancji", - "admin_emailTitle": "E-mail kontaktowy administratora", + "admin_emailHint": "Wprowadź email kontaktowy dla Twojej instancji", + "admin_emailTitle": "Email kontaktowy administratora", "oo_importBin": "Kliknij OK, aby zaimportować wewnętrzny format .bin programu CryptPad.", "oo_conversionSupport": "Twoja przeglądarka nie radzi sobie z konwersją do i z formatów biurowych. Zalecamy korzystanie z najnowszej wersji Firefoxa lub Chrome.", "register_registrationIsClosed": "Rejestracja została zamknięta.", @@ -921,7 +921,7 @@ "calendar_before": "przed", "calendar_weekNumber": "Tydzień {0}", "calendar_import_temp": "Importuj ten kalendarz", - "oo_cantMigrate": "Ten arkusz przekracza maksymalny rozmiar wysyłania i jest zbyt duży, aby mógł zostać przeniesiony.", + "oo_cantMigrate": "Arkusz przekracza maksymalny rozmiar wrzutu i jest zbyt duży, aby mógł zostać przemigrowany.", "footer_roadmap": "Plan działania", "settings_deleteSubscription": "Zarządzaj moją subskrypcją", "settings_deleteContinue": "Usuń moje konto", @@ -1165,12 +1165,12 @@ "teams": "Zespoły", "allow_text": "Użycie listy dostępu oznacza, że tylko wybrani użytkownicy i właściciele będą mogli uzyskać dostęp do tego dokumentu.", "logoutEverywhere": "Wyloguj się wszędzie", - "owner_text": "Właściciel(e) dokumentu są jedynymi użytkownikami uprawnionymi do: dodawania/usuwania właścicieli, ograniczania dostępu do dokumentu za pomocą listy dostępu lub usuwania dokumentu.", - "access_muteRequests": "Ignorowanie próśb o dostęp do tego dokumentu", + "owner_text": "Właściciel(e) dokumentu są jedynymi użytkownikami uprawnionymi do: dodawania/usuwania właścicieli, ograniczania dostępu do dokumentu za pomocą listy dostępu, lub usuwania dokumentu.", + "access_muteRequests": "Ignoruj prośby o dostęp do tego dokumentu", "allow_label": "Lista dostępu: {0}", "form_addMultipleHint": "Dodaj wiele dat i godzin", "form_addMultiple": "Dodaj wszystkie", - "form_anonymous_blocked": "Odpowiedzi gości są zablokowane dla tego formularza. Musisz zalogować się lub zarejestrować się, aby przesyłać odpowiedzi.", + "form_anonymous_blocked": "Odpowiedzi gości są zablokowane dla tego formularza. Musisz zalogować się lub zarejestrować się, aby przesłać odpowiedzi.", "form_add_item": "Dodaj element", "form_add_option": "Dodaj opcję", "form_newItem": "Nowy element", @@ -1247,12 +1247,12 @@ "oo_invalidFormat": "Ten plik nie może być zaimportowany", "burnAfterReading_warningDeleted": "Ten dokument został trwale usunięty, po zamknięciu tego okna nie będzie można się do niego ponownie dostać.", "burnAfterReading_proceed": "wyświetl i usuń", - "burnAfterReading_warningAccess": "Ten dokument ulegnie samozniszczeniu. Po kliknięciu przycisku poniżej zobaczysz zawartość tylko raz, zanim zostanie trwale usunięta. Po zamknięciu tego okna nie będzie można ponownie uzyskać do niego dostępu. Jeśli nie jesteś gotowy, aby kontynuować, możesz zamknąć to okno i wrócić do niego później.", + "burnAfterReading_warningAccess": "Ten dokument ulegnie samozniszczeniu. Po kliknięciu przycisku poniżej zobaczysz jego zawartość tylko raz, zanim zostanie trwale usunięta. Po zamknięciu tego okna nie będzie można ponownie uzyskać do niego dostępu. Jeśli nie jesteś gotowy, aby kontynuować, możesz zamknąć to okno i wrócić do niego później.", "burnAfterReading_generateLink": "Kliknij na poniższy przycisk, aby wygenerować link.", "burnAfterReading_warningLink": "Ustawiłeś ten dokument na samozniszczenie. Gdy odbiorca odwiedzi ten link, będzie mógł zobaczyć dokument tylko raz, zanim zostanie on trwale usunięty.", "burnAfterReading_linkBurnAfterReading": "Wyświetl raz i dokonaj samozniszczenia", "team_inviteLinkError": "Wystąpił błąd podczas tworzenia linku.", - "team_inviteInvalidLinkError": "Ten link do zaproszenia jest nieważny.", + "team_inviteInvalidLinkError": "To zaproszenie nie jest prawidłowe.", "team_cat_link": "Link do zaproszenia", "team_links": "Linki do zaproszeń", "team_inviteGetData": "Uzyskiwanie danych zespołu", @@ -1292,13 +1292,13 @@ "teams_table_admins": "Zarządzaj członkami", "teams_table_specificHint": "Są to stare foldery współdzielone, w których przeglądający nadal mają uprawnienia do edycji istniejących dokumentów. Dokumenty utworzone lub skopiowane do tych folderów będą miały standardowe uprawnienia.", "teams_table_specific": "Wyjątki", - "teams_table_generic_own": "Zarządzaj zespołem: zmień nazwę zespołu i awatar, dodaj lub usuń właścicieli, zmień subskrypcję zespołu, usuń zespół.", - "teams_table_generic_admin": "Zarządzaj członkami: zapraszaj i odwołuj członków, zmieniaj role członków aż do Administratora.", - "teams_table_generic_edit": "Edytuj: tworzenie, modyfikowanie i usuwanie folderów i dokumentów.", - "teams_table_generic_view": "Przeglądaj: dostęp do folderów i dokumentów (tylko do odczytu).", + "teams_table_generic_own": "Zarządzanie zespołem: zmień nazwę zespołu i awatar, dodaj lub usuń właścicieli, zmień subskrypcję zespołu, usuń zespół.", + "teams_table_generic_admin": "Zarządzanie członkami: zapraszaj i odwołuj członków, zmieniaj role członków aż do Administratora.", + "teams_table_generic_edit": "Edycja: tworzenie, modyfikowanie i usuwanie folderów i dokumentów.", + "teams_table_generic_view": "Podgląd: dostęp do folderów i dokumentów (tylko do odczytu).", "teams_table_generic": "Role i uprawnienia", "teams_table": "Role", - "driveOfflineError": "Twoje połączenie z CryptPad zostało utracone. Zmiany w tym dokumencie nie zostaną zapisane w Twoim CryptDrive. Zamknij wszystkie zakładki CryptPad i spróbuj ponownie w nowym oknie. ", + "driveOfflineError": "Twoje połączenie z CryptPad zostało utracone. Zmiany w tym dokumencie nie zostaną zapisane w Twoim CryptDrive. Zamknij wszystkie zakładki CryptPad i spróbuj jeszcze raz, w nowym oknie. ", "properties_passwordSuccessFile": "Hasło zostało pomyślnie zmienione.", "properties_passwordWarningFile": "Hasło zostało pomyślnie zmienione, ale nie udało nam się zaktualizować Twojego CryptDrive nowymi danymi. Może być konieczne ręczne usunięcie starej wersji pliku.", "properties_confirmNewFile": "Czy jesteś pewien? Dodanie hasła spowoduje zmianę adresu URL tego pliku. Użytkownicy bez hasła stracą dostęp do tego pliku.", @@ -1310,7 +1310,7 @@ "settings_codeBrackets": "Automatyczne zamykanie nawiasów", "team_quota": "Limit miejsca Twojego zespołu", "team_title": "Zespół: {0}", - "team_demoteMeConfirm": "Właśnie rezygnujesz ze swoich praw. Nie będziesz w stanie cofnąć tego działania. Czy jest pan pewien?", + "team_demoteMeConfirm": "Właśnie rezygnujesz ze swoich praw. Nie będziesz w stanie cofnąć tego działania. Czy jesteś pewien?", "team_pendingOwnerTitle": "Administrator ten nie przyjął jeszcze oferty przyznania własności.", "team_pendingOwner": "(w toku)", "team_deleteConfirm": "Zamierzasz usunąć wszystkie dane całego zespołu. Może to wpłynąć na dostęp innych członków zespołu do ich danych. Nie można tego cofnąć. Czy jesteś pewien, że chcesz kontynuować?", @@ -1365,20 +1365,20 @@ "owner_request_accepted": "{0} zaakceptował twoją ofertę zostania właścicielem {1}", "owner_request": "{0} chce, abyś był właścicielem {1}", "owner_add": "{0} chce, abyś był właścicielem dokumentu {1}. Czy zgadzasz się na to?", - "owner_addConfirm": "Współwłaściciele będą mogli zmienić zawartość i usunąć Cię jako właściciela. Czy jest Pan/Pani pewien/a?", - "owner_removeMeConfirm": "Właśnie rezygnujesz z prawa własności. Nie będziesz w stanie cofnąć tego działania. Czy jest Pan/Pani pewien/a?", + "owner_addConfirm": "Współwłaściciele będą mogli zmienić zawartość i usunąć Cię jako właściciela. Czy jesteś pewny/pewna?", + "owner_removeMeConfirm": "Właśnie rezygnujesz z prawa własności. Nie będziesz w stanie cofnąć tego działania. Czy jesteś pewien/pewna?", "owner_removeConfirm": "Czy na pewno chcesz usunąć prawa własności dla wybranych użytkowników? Zostaną oni powiadomieni o tym działaniu.", "owner_unknownUser": "nieznany", "owner_removePendingText": "W trakcie realizacji", "owner_removeText": "Właściciele", - "features_emailRequired": "Wymagany adres e-mail", + "features_emailRequired": "Wymagany adres email", "features_pricing": "Od {0} do {2} € miesięcznie", "features_noData": "Nie są wymagane dane osobowe", "homePage": "Strona główna", "pricing": "Cennik", "properties_unknownUser": "{0} nieznanych użytkowników", - "requestEdit_sent": "Wniosek wysłano", - "requestEdit_accepted": "{1} przyznał Ci prawa do edycji dokumentu {0}", + "requestEdit_sent": "Prośbę wysłano", + "requestEdit_accepted": "{1} przyznał Ci zezwolenie edycji dokumentu {0}", "requestEdit_request": "{1} chce edytować dokument {0}", "later": "Zdecyduj później", "requestEdit_viewPad": "Otwórz dokument w nowej karcie", @@ -1414,7 +1414,7 @@ "ui_openDirectly": "Ta funkcjonalność nie jest dostępna, gdy CryptPad jest osadzony w innej witrynie. Otworzyć ten dokument w nowej karcie?", "support_cat_debugging": "Debuguj dane", "support_debuggingDataTitle": "Informacje dotyczące debugowania konta", - "support_debuggingDataHint": "Poniższe informacje są zawarte w przesyłanych zgłoszeniach do pomocy technicznej. Żadna z nich nie umożliwia administratorom dostępu do dokumentów użytkownika ani ich odszyfrowania. Informacje te są zaszyfrowane w taki sposób, że tylko administratorzy mogą je odczytać.", + "support_debuggingDataHint": "Poniższe informacje są zawarte w zgłoszeniach przesłanych do pomocy technicznej. Nie umożliwiają administratorom dostępu do dokumentów użytkownika ani ich odszyfrowania. Informacje te są zaszyfrowane w taki sposób, że tylko administratorzy mogą je odczytać.", "fivehundred_internalServerError": "Wewnętrzny błąd serwera", "admin_cacheEvictionRequired": "Serwer został zaktualizowany o nowe ustawienia. Użyj przycisku Wyczyść pamięć podręczną, aby upewnić się, że ta zmiana będzie widoczna dla wszystkich użytkowników.", "support_warning_document": "Określ typ dokumentu, który powoduje problem i podaj jego identyfikator lub link", @@ -1477,7 +1477,7 @@ "admin_channelAvailable": "Dostępne", "admin_uptimeTitle": "Data uruchomienia", "admin_blockMetadataTitle": "Informacje segmentu logowania", - "admin_blockMetadataHint": "Segment logowania pozwala użytkownikowi na logowanie się do CryptPad'a za pomocą loginu _ hasła", + "admin_blockMetadataHint": "Segment logowania pozwala użytkownikowi na logowanie się do CryptPad'a za pomocą nazwy użytkownika + hasła", "admin_blockMetadataPlaceholder": "Względny lub bezwzględny adres URL", "admin_planName": "Nazwa planu", "admin_note": "Adnotacja planu", @@ -1560,7 +1560,7 @@ "form_exportJSON": "Wyeskportuj do JSON", "form_alreadyAnsweredMult": "Udzieliłeś/łaś odpowiedzi na ten formularz w dniu:", "form_responseNotification": "Nowe odpowiedzi w formularzu: {0}", - "form_answer_new": "Wyślij ponownie", + "form_answer_new": "Prześlij ponownie", "form_editable_off": "Jednorazowy", "form_editable_on_del": "Jednorazowy i edytuj/usuń", "form_multiple": "Wielokrotnie", @@ -1585,7 +1585,7 @@ "done": "Zakończone", "continue": "Kontynuuj", "settings_otp_invalid": "Niewłaściwy kod weryfikacyjny", - "duplicate": "Duplikat", + "duplicate": "Duplikuj", "mfa_revoke_label": "Aby wyłączyć 2FA, na początku wprowadź swoje hasło", "mfa_revoke_button": "Potwierdź wyłączenie 2FA", "team_nameAlreadySet": "{0} jest już nazwą zespołu", @@ -1621,7 +1621,7 @@ "mfa_revoke_code": "Wprowadź swój kod uwierzytelniający", "dph_pad_pw": "Ten dokument jest chroniony nowym hasłem", "support_insertRecorded": "Wstaw fragment", - "support_team": "Zespół Pomocy Technicznej", + "support_team": "Zespół Wsparcia Technicznego", "support_answerAs": "Odpowiadasz jako {0}", "support_movePending": "Przenieś do archiwum", "support_moveActive": "Przenieś do aktywnych", @@ -1639,7 +1639,7 @@ "mfa_enable": "Włącz 2FA", "settings_removeOwnedButton": "Zniszcz swoje dokumenty", "admin_totpEnabled": "2FA jest włączone", - "recovery_mfa_error": "Nieznany błąd. Odśwież i spróbuj ponownie.", + "recovery_mfa_error": "Nieznany błąd. Załaduj ponownie i spróbuj jeszcze raz.", "recovery_mfa_disabled": "Uwierzytelnianie wieloetapowe jest już wyłączone dla tego konta.", "recovery_mfa_secret_ph": "Kod odzyskiwania", "admin_invitationTitle": "Zaproszenia", @@ -1666,7 +1666,7 @@ "register_nameTooLong": "Nazwa użytkownika musi zawierać poniżej {0} znaków", "loading_enter_otp": "To konto jest chronione za pomocą uwierzytelniania dwuskładnikowego. Wprowadź swój kod weryfikacyjny", "loading_recover": "Nie możesz zdobyć kodu? Przywróć swoje konto", - "goLeft": "Lewo", + "goLeft": "W lewo", "ssoauth_header": "Hasło CryptPad", "ssoauth_form_hint_login": "Wprowadź swoje hasło CryptPad", "kanban_showTags": "Wszystkie tagi", @@ -1688,7 +1688,7 @@ "recovery_header": "Przywracanie 2FA", "recovery_forgot": "Zapomniany kod odzyskiwania", "recovery_forgot_text": "Skopiuj następujące informacje i prześlij je administratorom swojej instancji", - "goRight": "Prawo", + "goRight": "W prawo", "loading_mfa_required": "Uwierzytelnianie dwuskłądnikowe jest wymagane na tej instancji. Uaktualnij swoje konto z użyciem aplikacji uwierzytelniającej i poniższego formularza.", "admin_invitationLink": "Zaproszenie", "admin_registrationSsoTitle": "Zamknij rejestrację SSO", @@ -1736,7 +1736,7 @@ "support_active_tag": "Skrzynka odbiorcza", "support_closed_tag": "Zamknięte", "support_privacyTitle": "Odpowiedz anonimowo", - "support_privacyHint": "Zaznacz tę opcję żeby odpowiedzieć jako 'Zespół Pomocy Technicznej' zamiast własną nazwą użytkownika", + "support_privacyHint": "Zaznacz tę opcję żeby odpowiedzieć jako 'Zespół Pomocy Technicznej' zamiast pod własną nazwą użytkownika", "support_notificationsTitle": "Wyłącz powiadomienia", "support_userChannel": "ID kanału powiadomień użytkownika", "support_openTicketHint": "Skopiuj dane użytkownika odbiorcy z ich profilu lub z istniejącego zgłoszenia. Otrzymają powiadomienie o wiadomości.", From 84556511b2d645f2ee3257c9bdfb9c1997b79238 Mon Sep 17 00:00:00 2001 From: Weblate Date: Tue, 22 Oct 2024 12:12:33 +0200 Subject: [PATCH 053/218] Translated using Weblate (Hungarian) Currently translated at 25.1% (449 of 1783 strings) Co-authored-by: Balazs SZALAI Co-authored-by: Weblate Translate-URL: https://weblate.cryptpad.org/projects/cryptpad/app/hu/ Translation: CryptPad/App --- www/common/translations/messages.hu.json | 32 +++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/www/common/translations/messages.hu.json b/www/common/translations/messages.hu.json index a6957d7e4..9056314dc 100644 --- a/www/common/translations/messages.hu.json +++ b/www/common/translations/messages.hu.json @@ -421,5 +421,35 @@ "settings_import": "Importálás", "upload_pending": "Függöben lévő", "upload_up": "Feltöltés", - "features_f_subscribe": "Feliratkozás" + "features_f_subscribe": "Feliratkozás", + "toolbar_savetodrive": "Mentés képként", + "comments_comment": "Hozzászólás", + "drive_treeButton": "Fájlok", + "fm_noResult": "Keresés eredménytelen", + "settings_cat_style": "Megjelenés", + "logoutButton": "Kijelentkezés", + "login_noSuchUser": "Hibás felhasználónév vagy jelszó", + "login_invalUser": "Felhasználónév szükséges", + "login_unhandledError": "Váratlan hiba történt :(", + "register_passwordsDontMatch": "A jelszavak nem egyeznek!", + "register_alreadyRegistered": "Ez a felhasználónév már létezik! Szeretnél bejelentkezni?", + "settings_changePasswordNew": "Új jelszó", + "settings_changePasswordCurrent": "Aktuális jelszó", + "settings_codeSpellcheckTitle": "Helyesírás-ellenőrzés", + "comments_submit": "Beküldés", + "toolbar_insert": "Beszúrás", + "toolbar_tools": "Eszközök", + "slide_backCol": "Háttérszín", + "slide_textCol": "Szövegszín", + "toolbar_file": "Fájl", + "support_cat_bug": "Hibajelentés", + "support_attachments": "Csatolmányok", + "settings_kanbanTagsOr": "VAGY", + "history_close": "Bezár", + "history_restore": "Visszaállít", + "fm_restricted": "Nincs hozzáférése", + "support_cat_all": "Összes", + "support_addAttachment": "Csatolmány hozzáadása", + "oo_refresh": "Frissítés", + "support_formCategoryError": "Hiba: üres kategória" } From 217c1de00adc476d3c312aeba95ed2f59df1c4b5 Mon Sep 17 00:00:00 2001 From: Weblate Date: Tue, 22 Oct 2024 12:12:33 +0200 Subject: [PATCH 054/218] Translated using Weblate (Dutch) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 35.8% (640 of 1783 strings) Co-authored-by: Daniël Co-authored-by: Weblate Translate-URL: https://weblate.cryptpad.org/projects/cryptpad/app/nl/ Translation: CryptPad/App --- www/common/translations/messages.nl.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/www/common/translations/messages.nl.json b/www/common/translations/messages.nl.json index 022193059..400941007 100644 --- a/www/common/translations/messages.nl.json +++ b/www/common/translations/messages.nl.json @@ -663,5 +663,9 @@ "admin_broadcastTitle": "Broadcast bericht", "pad_settings_hide": "Verbergen", "importError": "Importeren mislukt (verkeerd formaat)", - "terms": "Servicevoorwaarden" + "terms": "Servicevoorwaarden", + "creation_404": "Dit document bestaat niet meer. Gebruik het volgende formulier om een nieuw document te maken.", + "feedback_about": "Als je dit leest was je waarschijnlijk nieuwsgierig waarom CryptPad om webpagina's vraagt wanneer je bepaalde acties uitvoert.", + "features_f_social_note": "Voeg contacten toe voor veilige samenwerking, creëer een profiel, fijnmazige toegangscontroles", + "feedback_optout": "Als u zich wilt afmelden, ga dan naar uw gebruikersinstellingenpagina, waar u een selectievakje vindt om gebruikersfeedback in of uit te schakelen." } From a534b38671afb232193536a143f4a6962a4c30f0 Mon Sep 17 00:00:00 2001 From: Weblate Date: Tue, 22 Oct 2024 12:12:33 +0200 Subject: [PATCH 055/218] Translated using Weblate (Bulgarian) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 28.7% (513 of 1783 strings) Translated using Weblate (Bulgarian) Currently translated at 27.2% (485 of 1783 strings) Co-authored-by: Weblate Co-authored-by: Мария Рангелова Translate-URL: https://weblate.cryptpad.org/projects/cryptpad/app/bg/ Translation: CryptPad/App --- www/common/translations/messages.bg.json | 48 +++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/www/common/translations/messages.bg.json b/www/common/translations/messages.bg.json index 43804428d..cfea66f8e 100644 --- a/www/common/translations/messages.bg.json +++ b/www/common/translations/messages.bg.json @@ -467,5 +467,51 @@ "upload_modal_title": "Опции за качване на файлове", "upload_tooLarge": "Този файл надвишава максималния разрешен размер на качване за вашия акаунт.", "upload_serverError": "Грешка в сървъра: файлът ви не може да бъде качен в момента.", - "upload_success": "Вашият файл ({0}) бе успешно качен и е добавен към устройството ви." + "upload_success": "Вашият файл ({0}) бе успешно качен и е добавен към устройството ви.", + "todo_markAsCompleteTitle": "Маркирайте тази задача като завършена", + "todo_markAsIncompleteTitle": "Маркирайте тази задача като незавършена", + "upload_tooLargeBrief": "Файлът надвишава ограничението от {0}MB за това устройство", + "upload_choose": "Избиране на файл", + "upload_pending": "Изчакване", + "upload_cancelled": "Отменено", + "upload_size": "Размер", + "upload_mustLogin": "Трябва да сте влезли, за да качвате файлове", + "upload_up": "Качване", + "download_mt_button": "Изтегляне", + "download_dl": "Изтегляне", + "download_step1": "Изтегля се", + "download_step2": "Декриптиране", + "todo_title": "CryptTodo", + "todo_removeTaskTitle": "Премахнете тази задача от вашия списък със задачи", + "pad_base64": "Този документ съдържа изображения, съхранени по неефективен начин. Тези изображения значително ще увеличат размера на документа във вашия CryptDrive и ще направят зареждането му по-бавно. Можете да промените тези файлове в нов формат, който ще се съхранява отделно във вашия CryptDrive. Искате ли да промените тези изображения сега?", + "mdToolbar_button": "Показване или скриване на лентата с инструменти Markdown", + "mdToolbar_defaultText": "Вашият текст тук", + "mdToolbar_help": "Помощ", + "mdToolbar_tutorial": "https://www.markdowntutorial.com/", + "mdToolbar_bold": "Удебелен", + "mdToolbar_italic": "Курсив", + "mdToolbar_strikethrough": "Зачертано", + "mdToolbar_heading": "Заглавие", + "mdToolbar_nlist": "Подреден списък", + "mdToolbar_list": "Неподреден списък", + "mdToolbar_check": "Списък със задачи", + "mdToolbar_code": "Код", + "home_host": "Това е отделен екземпляр от общността на CryptPad.", + "about": "Относно", + "privacy": "Политика за поверителност", + "contact": "Контакт", + "terms": "Условия за ползване", + "features": "Характеристики", + "features_title": "Характеристики", + "features_anon": "Гост", + "features_registered": "Регистриран", + "features_premium": "Премиум", + "features_f_core": "Общи характеристики", + "features_f_file0": "Отваряне на документи", + "mdToolbar_quote": "Цитат", + "features_f_apps": "Достъп до всички приложения", + "features_f_core_note": "Редактиране, импортиране и експортиране, история, потребителски списък, чат", + "mdToolbar_toc": "Съдържание", + "mdToolbar_link": "Връзка", + "main_catch_phrase": "Пакет за сътрудничество
криптиран от край до край и с отворен код" } From 2e73c453f60b48b1f06d94f12d552d8829ffc659 Mon Sep 17 00:00:00 2001 From: Fabrice Mouhartem Date: Tue, 22 Oct 2024 13:28:08 +0200 Subject: [PATCH 056/218] style(linter): missing semicolons - also remove some trailing spaces --- www/common/drive-ui.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 397c14769..ac78d201d 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -315,28 +315,28 @@ define([ APP.selectedFiles = []; var findElementId = function ($element) { - var isTrashed = $element.data("path")[0] === TRASH + var isTrashed = $element.data("path")[0] === TRASH; let elementId; if (isTrashed) { - elementId = $element.data("path").join(',') + elementId = $element.data("path").join(','); } else { elementId = $element.data("path").slice(-1)[0]; } - return elementId - } + return elementId; + }; var isElementSelected = function ($element) { - var elementId = findElementId($element) + var elementId = findElementId($element); return APP.selectedFiles.indexOf(elementId) !== -1; }; var selectElement = function ($element) { - var elementId = findElementId($element) + var elementId = findElementId($element); if (APP.selectedFiles.indexOf(elementId) === -1) { APP.selectedFiles.push(elementId); } $element.addClass("cp-app-drive-element-selected"); }; var unselectElement = function ($element) { - var elementId = findElementId($element) + var elementId = findElementId($element); var index = APP.selectedFiles.indexOf(elementId); if (index !== -1) { APP.selectedFiles.splice(index, 1); @@ -2430,7 +2430,7 @@ define([ })); $element.data('path', newPath); if (isElementSelected($element)) { - selectElement($element); + selectElement($element); } $element.prepend($icon).dblclick(function () { From f255683caffc2bb08b7038bfcb86bb267f5476d4 Mon Sep 17 00:00:00 2001 From: Fabrice Mouhartem Date: Tue, 22 Oct 2024 15:21:05 +0200 Subject: [PATCH 057/218] Restoring multiple files from trash - Resolve #1651 --- www/common/drive-ui.js | 52 ++++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 135b0eae0..b7edb5fac 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -1459,7 +1459,6 @@ define([ } }); if (paths.length > 1) { - hide.push('restore'); hide.push('properties', 'access'); hide.push('rename'); hide.push('openparent'); @@ -5181,24 +5180,43 @@ define([ return void deletePaths(paths); } else if ($this.hasClass("cp-app-drive-context-restore")) { - if (paths.length !== 1) { return; } - var restorePath = paths[0].path; - var restoreName = paths[0].path[paths[0].path.length - 1]; - if (restorePath.length === 4) { - var rEl = manager.find(restorePath); - if (manager.isFile(rEl)) { - restoreName = manager.getTitle(rEl); - } else if (manager.isSharedFolder(rEl)) { - var sfData = manager.getSharedFolderData(rEl); - restoreName = sfData.title || sfData.lastTitle || Messages.fm_deletedFolder; - } else { - restoreName = restorePath[1]; + let getRestoreProperties = (path) => { + let restorePath = path; + let restoreName = path.at(-1); + if (restorePath.length === 4) { + let rEl = manager.find(restorePath); + if (manager.isFile(rEl)) { + restoreName = manager.getTitle(rEl); + } else if (manager.isSharedFolder(rEl)) { + let sfData = manager.getSharedFolderData(rEl); + restoreName = sfData.title || sfData.lastTitle || Messages.fm_deletedFolder; + } else { + restoreName = restorePath[1]; + } } + return [restorePath, restoreName]; + }; + let restoreNumber = paths.length; + if (restoreNumber === 0) { return; } + if (restoreNumber === 1) { // single file restoration + let [restorePath, restoreName] = getRestoreProperties(paths[0].path); + UI.confirm(Messages._getKey("fm_restoreDialog", [restoreName]), function(res) { + if (!res) { return; } + manager.restore(restorePath, refresh); + }); + } else { // multiple files restoration + UI.confirm(Messages._getKey("fm_restoreMultipleDialog", [restoreNumber]), function(res) { + if (!res) { return; } + paths.forEach(path => { + if (!path) { // We met an error + console.error("Error while restoring files: no path"); + return; + } + let restorePath = getRestoreProperties(path.path)[0]; + manager.restore(restorePath, refresh); + }); + }); } - UI.confirm(Messages._getKey("fm_restoreDialog", [restoreName]), function(res) { - if (!res) { return; } - manager.restore(restorePath, refresh); - }); } else if ($this.hasClass("cp-app-drive-context-openparent")) { if (paths.length !== 1) { return; } From d709c6c2d0efd75c2d82da4583c63a79a591f89e Mon Sep 17 00:00:00 2001 From: Fabrice Mouhartem Date: Tue, 22 Oct 2024 15:27:45 +0200 Subject: [PATCH 058/218] Add translation key for restoring multiple files - Related to #1651 --- customize.dist/messages.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/customize.dist/messages.js b/customize.dist/messages.js index a5784b495..2ee88fc81 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -136,6 +136,8 @@ define(req, function(AppConfig, Default, Language) { } }; + Messages.fm_restoreMultipleDialog = "Are you sure you want to restore {0} files and/or folders to their previous locations?"; // XXX: new translation key + return Messages; }); From b7ffb5dc6f4af8b331f5a380d9623cb18df672c9 Mon Sep 17 00:00:00 2001 From: Fabrice Mouhartem Date: Tue, 22 Oct 2024 16:11:49 +0200 Subject: [PATCH 059/218] Minor: consistent callback calls in #1692 --- 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 b7edb5fac..e656ebb38 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -5200,12 +5200,12 @@ define([ if (restoreNumber === 0) { return; } if (restoreNumber === 1) { // single file restoration let [restorePath, restoreName] = getRestoreProperties(paths[0].path); - UI.confirm(Messages._getKey("fm_restoreDialog", [restoreName]), function(res) { + UI.confirm(Messages._getKey("fm_restoreDialog", [restoreName]), res => { if (!res) { return; } manager.restore(restorePath, refresh); }); } else { // multiple files restoration - UI.confirm(Messages._getKey("fm_restoreMultipleDialog", [restoreNumber]), function(res) { + UI.confirm(Messages._getKey("fm_restoreMultipleDialog", [restoreNumber]), res => { if (!res) { return; } paths.forEach(path => { if (!path) { // We met an error From 6fa7ea583f507ea71be3d7afd5c30eb34c3bd390 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Mon, 28 Oct 2024 12:48:16 +0100 Subject: [PATCH 060/218] Links are now part of Drive exports #942 --- www/common/make-backup.js | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/www/common/make-backup.js b/www/common/make-backup.js index 4e1d2b205..0b5e7d578 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -170,9 +170,17 @@ define([ }); } - var href = (fData.href && fData.href.indexOf('#') !== -1) ? fData.href : fData.roHref; - var parsed = Hash.parsePadUrl(href); - if (['pad', 'file'].indexOf(parsed.hashData.type) === -1) { return; } + var href; + var parsed; + if (fData.href.indexOf('https') !== -1 && fData.href.indexOf('http') !== -1) { + href = fData.href; + parsed = {}; + parsed['hashData'] = {type: 'link'}; + } else { + href = (fData.href && fData.href.indexOf('#') !== -1) ? fData.href : fData.roHref; + parsed = Hash.parsePadUrl(href); + } + if (['pad', 'file', 'link'].indexOf(parsed.hashData.type) === -1) { return; } // waitFor is used to make sure all the pads and files are process before downloading the zip. var w = ctx.waitFor(); @@ -220,7 +228,7 @@ define([ var opts = { password: fData.password }; - var rawName = fData.filename || fData.title || 'File'; + var rawName = fData.filename || fData.title || fData.name || 'File'; console.log(rawName); // Pads (pad,code,slide,kanban,poll,...) @@ -276,8 +284,21 @@ define([ } }, 50); }; + var todoLink = function () { + var opts = { + binary: true, + }; + var fileName = getUnique(sanitize(rawName), '.txt', existingNames); + existingNames.push(fileName.toLowerCase()); + var content = new Blob([fData.href], { type : "text/html;charset=utf-8" }); + zip.file(fileName, content, opts); + console.log('DONE ---- ' + fileName); + setTimeout(done, 1000); + }; if (parsed.hashData.type === 'file') { return void todoFile(); + } else if (parsed.hashData.type === 'link') { + return void todoLink(); } todoPad(); }); @@ -330,6 +351,10 @@ define([ sframeChan: sframeChan }; var filesData = data.sharedFolderId && ctx.sf[data.sharedFolderId] ? ctx.sf[data.sharedFolderId].filesData : ctx.data.filesData; + var links = ctx.data.static; + Object.keys(links).forEach(function(key) { + filesData[key] = links[key]; + }); progress('reading', -1); // Msg.settings_export_reading nThen(function (waitFor) { ctx.waitFor = waitFor; From 235c04f2ad9148d226d270b8753ddcaebc0533e4 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 29 Oct 2024 11:20:12 +0100 Subject: [PATCH 061/218] Fix placeholder issues --- lib/hk-util.js | 3 ++- www/cryptpad-api.js | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/hk-util.js b/lib/hk-util.js index 93c3f9c0e..069be04a3 100644 --- a/lib/hk-util.js +++ b/lib/hk-util.js @@ -143,7 +143,8 @@ const dropChannel = HK.dropChannel = function (Env, chanName) { delete Env.channel_cache[chanName]; if (meta && meta.selfdestruct && Env.selfDestructTo) { Env.selfDestructTo[chanName] = setTimeout(function () { - expireChannel(Env, chanName); + if (!Env.store) { return; } + Env.store.archiveChannel(chanName, false, () => {}); }, TEMPORARY_CHANNEL_LIFETIME); } if (Env.store) { Env.store.closeChannel(chanName, function () {}); } diff --git a/www/cryptpad-api.js b/www/cryptpad-api.js index e73ef40bd..9dfcc55b9 100644 --- a/www/cryptpad-api.js +++ b/www/cryptpad-api.js @@ -131,7 +131,10 @@ return start(); } getBlob(function (err, _blob) { - if (err) { reject(err); return console.error(err); } + if (err) { // Can't get blob from client, try from server + console.warn(err); + return void start(); + } _blob.name = `document.${config.document.fileType}`; blob = _blob; start(); From 1fb3dbc2ceb421beddc9d7f4addfde518a945e36 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 29 Oct 2024 11:23:17 +0100 Subject: [PATCH 062/218] Fix merge error --- www/cryptpad-api.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/www/cryptpad-api.js b/www/cryptpad-api.js index f41aad8ec..6fd6d3be4 100644 --- a/www/cryptpad-api.js +++ b/www/cryptpad-api.js @@ -137,11 +137,6 @@ blob = config.document.blob; return start(); } - // XXX use server only when not zero knowledge? i.e. no save handler? - // XXX or when error with client? - if (!config.events.onSave) { - return start(); - } getBlob(function (err, _blob) { if (err) { // Can't get blob from client, try from server console.warn(err); From 4a881555817333798133a18073f49fc3de0a92a0 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 29 Oct 2024 11:33:45 +0100 Subject: [PATCH 063/218] Fix expire selfdestruct channel --- lib/historyKeeper.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/historyKeeper.js b/lib/historyKeeper.js index 7178b5f76..b8ac2ca58 100644 --- a/lib/historyKeeper.js +++ b/lib/historyKeeper.js @@ -67,7 +67,10 @@ module.exports.create = function (Env, cb) { } if (metadata && metadata.selfdestruct && metadata.selfdestruct !== Env.id) { - HK.expireChannel(Env, channelName); + if (Env.store) { + Env.store.archiveChannel(channelName, false, + () => {}); + } return void cb('ESELFDESTRUCT'); } From b4dc9715d6021d38c6b860751e0376235c3c5a1e Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Tue, 29 Oct 2024 13:20:02 +0100 Subject: [PATCH 064/218] Added regex & fixed issue with read-only pads --- www/common/make-backup.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/common/make-backup.js b/www/common/make-backup.js index 0b5e7d578..e83e4a781 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -169,10 +169,10 @@ define([ data: fData }); } - var href; var parsed; - if (fData.href.indexOf('https') !== -1 && fData.href.indexOf('http') !== -1) { + var linkRegex = new RegExp("^(http|https)://"); + if (fData.href && linkRegex.test(fData.href)) { href = fData.href; parsed = {}; parsed['hashData'] = {type: 'link'}; From c90541d35e9c68b34d14e8506c6e9a26fb128720 Mon Sep 17 00:00:00 2001 From: Fabrice Mouhartem Date: Tue, 29 Oct 2024 16:43:37 +0100 Subject: [PATCH 065/218] Fix too much calls to refresh with nThen - Add a non-zero timeout to avoid flooding the browser - Fix an issue in #1692 --- www/common/drive-ui.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index e656ebb38..acf1b2f7e 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -5207,14 +5207,16 @@ define([ } else { // multiple files restoration UI.confirm(Messages._getKey("fm_restoreMultipleDialog", [restoreNumber]), res => { if (!res) { return; } - paths.forEach(path => { - if (!path) { // We met an error - console.error("Error while restoring files: no path"); - return; - } - let restorePath = getRestoreProperties(path.path)[0]; - manager.restore(restorePath, refresh); - }); + nThen(waitFor => { + paths.forEach(path => { + if (!path) { // We met an error + console.error("Error while restoring files: no path"); + return; + } + let restorePath = getRestoreProperties(path.path)[0]; + setTimeout(manager.restore(restorePath, waitFor()), 10); + }); + }).nThen(refresh); }); } } From 38d97b647f3dc1b00190ac53aca5a64b890810c7 Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 5 Nov 2024 15:14:24 +0200 Subject: [PATCH 066/218] fix focus order on kanban boards #1640 --- www/kanban/app-kanban.less | 3 +++ www/kanban/jkanban_cp.js | 11 +++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/www/kanban/app-kanban.less b/www/kanban/app-kanban.less index 86e1052b5..86e0f00c8 100644 --- a/www/kanban/app-kanban.less +++ b/www/kanban/app-kanban.less @@ -576,6 +576,9 @@ display: flex; max-height: 100%; } + .kanban-boards-container{ + display: flex; + } } #kanban-trash { height: 1px; diff --git a/www/kanban/jkanban_cp.js b/www/kanban/jkanban_cp.js index 1b3a9967b..ee7d33ea0 100644 --- a/www/kanban/jkanban_cp.js +++ b/www/kanban/jkanban_cp.js @@ -109,16 +109,19 @@ define([ //create container var boardContainerOuter = document.createElement('div'); boardContainerOuter.classList.add('kanban-container-outer'); + var kanbanContainer = document.createElement('div'); + kanbanContainer.classList.add('kanban-container'); + boardContainerOuter.appendChild(kanbanContainer); var boardContainer = document.createElement('div'); - boardContainer.classList.add('kanban-container'); - boardContainerOuter.appendChild(boardContainer); + boardContainer.classList.add('kanban-boards-container'); + kanbanContainer.appendChild(boardContainer); self.container = boardContainer; //add boards self.addBoards(); var addBoard = document.createElement('div'); addBoard.id = 'kanban-addboard'; addBoard.innerHTML = ''; - boardContainer.appendChild(addBoard); + kanbanContainer.appendChild(addBoard); var trash = self.trashContainer = document.createElement('div'); trash.setAttribute('id', 'kanban-trash'); trash.setAttribute('class', 'kanban-trash'); @@ -131,7 +134,7 @@ define([ //appends to container self.element.appendChild(boardContainerOuter); - self.element.appendChild(trash); + boardContainerOuter.appendChild(trash); // send event that board has changed self.onChange(); From d6db1948e82bfc61930b8cb0a6735f539dd55b61 Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 5 Nov 2024 15:27:56 +0200 Subject: [PATCH 067/218] fix trash position --- www/kanban/jkanban_cp.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/kanban/jkanban_cp.js b/www/kanban/jkanban_cp.js index ee7d33ea0..95e92325a 100644 --- a/www/kanban/jkanban_cp.js +++ b/www/kanban/jkanban_cp.js @@ -134,7 +134,7 @@ define([ //appends to container self.element.appendChild(boardContainerOuter); - boardContainerOuter.appendChild(trash); + self.element.appendChild(trash); // send event that board has changed self.onChange(); From cff1544a907cd2cd001b1c8ce1f9d6631e43ab1a Mon Sep 17 00:00:00 2001 From: Weblate Date: Thu, 7 Nov 2024 10:15:19 +0100 Subject: [PATCH 068/218] Translated using Weblate (Turkish) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 12.2% (219 of 1783 strings) Translated using Weblate (Turkish) Currently translated at 5.7% (102 of 1783 strings) Co-authored-by: Aliberk Sandıkçı Co-authored-by: Weblate Translate-URL: https://weblate.cryptpad.org/projects/cryptpad/app/tr/ Translation: CryptPad/App --- www/common/translations/messages.tr.json | 165 ++++++++++++++++++++++- 1 file changed, 163 insertions(+), 2 deletions(-) diff --git a/www/common/translations/messages.tr.json b/www/common/translations/messages.tr.json index 62b85d2e5..16efae457 100644 --- a/www/common/translations/messages.tr.json +++ b/www/common/translations/messages.tr.json @@ -66,7 +66,7 @@ "mustLogin": "Bu sayfaya erişmek için giriş yapmalısınız", "forgotten": "Çöp kutusuna taşındı", "errorState": "Kritik hata: {0}", - "KB": "KB", + "KB": "kB", "formattedMB": "{0} MB", "typeError": "Bu doküman seçilmiş uygulama ile uyumlu değil", "disconnected": "Bağlantı kesildi", @@ -84,5 +84,166 @@ "pinLimitReachedAlert": "Depolama sınırınıza ulaştınız. Yeni belgeler CryptDrive'ınızda saklanmaz.
Sınırınızı artırmak için belgeleri CryptDrive'ınızdan kaldırabilir veya premium bir teklife abone olabilirsiniz.", "pinLimitNotPinned": "Depolama sınırınıza ulaştınız.
Bu belge CryptDrive'ınızda saklanmıyor.", "movedToTrash": "Bu doküman çöp kutusuna taşındı.
Drive'ıma erişin", - "saveTemplateButton": "Şablon olarak kaydet" + "saveTemplateButton": "Şablon olarak kaydet", + "propertiesButton": "Özellikler", + "filePicker_close": "Kapat", + "poll_unlocked": "Kilidi açık", + "pad_mediatagPreview": "Ön izleme", + "printText": "Yazdır", + "slideOptionsText": "Seçenekler", + "languageButton": "Dil", + "themeButton": "Tema", + "ok": "Tamam", + "cancel": "İptal", + "help_button": "Yardım", + "historyText": "Geçmiş", + "kanban_done": "Tamamlandı", + "poll_publish_button": "Yayınla", + "poll_commit": "Gönder", + "poll_optionPlaceholder": "Seçenek", + "poll_remove": "Kaldır", + "poll_edit": "Düzenle", + "poll_locked": "Kilitli", + "exportButton": "Dışarı Aktar", + "canvas_clear": "Temizle", + "settings_import": "İçeri Aktar", + "upload_pending": "Bekleniyor", + "fm_templateName": "Şablonlar", + "settingsButton": "Ayarlar", + "fm_searchPlaceholder": "Ara...", + "fm_prop_tagsList": "Etiketler", + "fc_rename": "Yeniden Adlandır", + "settings_resetButton": "Kaldır", + "settings_cat_account": "Hesap", + "upload_size": "Size", + "notificationsPage": "Bildirimler", + "settings_thumbnails": "Küçük Resimler", + "settings_autostoreYes": "Otomatik", + "upload_cancelled": "İptal Edildi", + "upload_up": "Karşıya Yükle", + "properties_changePasswordButton": "Gönder", + "share_withFriends": "Paylaş", + "mdToolbar_tutorial": "https://www.markdowntutorial.com/", + "edit": "düzenle", + "features": "Özellikler", + "share_linkEdit": "Düzenle", + "share_linkPresent": "Present", + "contact_chat": "Sohbet", + "settings_codeSpellcheckTitle": "Yazım denetimi", + "support_formButton": "Gönder", + "team_cat_list": "Takımlar", + "team_cat_chat": "Sohbet", + "poll_total": "TOPLAM", + "poll_comment_list": "Yorumlar", + "poll_comment_submit": "Gönder", + "canvas_width": "Genişlik", + "canvas_opacity": "Opaklık", + "profileButton": "Profil", + "contacts_title": "Kişiler", + "contacts_send": "Gönder", + "contacts_padTitle": "Sohbet", + "contacts_rooms": "Odalar", + "fm_rootName": "Drive", + "fm_trashName": "Çöp", + "fm_searchName": "Ara", + "fm_recentPadsName": "Son", + "fm_ownedPadsName": "Sahiplenmiş", + "fm_tagsName": "Etiketler", + "fm_newButton": "Yeni", + "fm_folder": "Klasör", + "fm_type": "Tür", + "fm_creation": "Oluşturma", + "fc_open": "Aç", + "fc_delete_owned": "Yok Et", + "fc_restore": "Geri Getir", + "fc_remove": "Kaldır", + "fc_remove_sharedfolder": "Kaldır", + "fc_prop": "Özellikler", + "fc_hashtag": "Etiketler", + "login_username": "Kullanıcı Adı", + "login_password": "Parola", + "register_header": "Kayıt Ol", + "register_cancel": "İptal", + "register_warning": "Uyarı", + "settings_cat_drive": "CryptDrive", + "settings_cat_cursor": "İmleç", + "settings_cat_code": "Kod", + "settings_cat_subscription": "Abonelik", + "settings_title": "Ayarlar", + "settings_save": "Kaydet", + "settings_backupCategory": "Yedekle", + "settings_backup": "Yedekle", + "settings_restore": "Geri Getir", + "settings_resetTipsAction": "Sıfırla", + "settings_resetTips": "İpuçları", + "settings_resetThumbnailsAction": "Temizle", + "settings_userFeedbackTitle": "Geri Bildirim", + "settings_padSpellcheckTitle": "Yazım Denetimi", + "download_mt_button": "İndir", + "download_step1": "İndiriliyor", + "download_dl": "İndir", + "download_step2": "Şifre çözülüyor", + "todo_title": "CryptTodo", + "mdToolbar_help": "Yardım", + "mdToolbar_bold": "Kalın", + "mdToolbar_italic": "İtalik", + "mdToolbar_strikethrough": "Üstü çizili", + "mdToolbar_heading": "Başlık", + "mdToolbar_link": "Link", + "mdToolbar_quote": "Quote", + "mdToolbar_code": "Kod", + "about": "Hakkında", + "contact": "İletişim", + "features_title": "Özellikler", + "features_anon": "Misafir", + "features_registered": "Kayıt olundu", + "features_premium": "Premium", + "features_f_subscribe": "Abone ol", + "view": "görüntüle", + "creation_expireFalse": "Sınırsız", + "creation_expireHours": "Saat", + "creation_expireDays": "Gün", + "creation_expireMonths": "Ay", + "creation_password": "Parola\n", + "creation_create": "Oluştur", + "creation_owners": "Sahipler", + "creation_passwordValue": "Parola", + "password_submit": "Gönder", + "share_linkCategory": "Link", + "share_linkView": "Görüntüle", + "share_contactCategory": "Contacts", + "share_embedCategory": "Gömülü", + "autostore_file": "dosya", + "autostore_sf": "klasör", + "autostore_pad": "pad", + "autostore_store": "Depola", + "crowdfunding_button2": "Bağış Yap", + "markdown_toc": "İçerikler", + "admin_cat_general": "Genel", + "admin_cat_stats": "İstatistikler", + "adminPage": "Yönetim", + "footer_donate": "Bağış", + "contact_email": "E-posta", + "friendRequest_decline": "Reddet", + "notifications_dismiss": "Anımsatma", + "supportPage": "Destek", + "admin_cat_support": "Destek", + "support_answer": "Yanıtla", + "notifications_cat_all": "Tüm", + "notifications_cat_archived": "Geçmiş", + "pricing": "Fiyatlandırma", + "owner_removeText": "Sahipler", + "owner_removePendingText": "Bekleniyor", + "owner_unknownUser": "bilinmiyor", + "team_inviteModalButton": "Davet et", + "team_cat_general": "Hakkında", + "team_cat_create": "Yeni", + "team_cat_members": "Üyeler", + "team_cat_drive": "Drive", + "team_cat_admin": "Yönetim", + "team_rosterPromote": "Promote", + "team_rosterDemote": "Demote", + "team_owner": "Sahipler", + "team_admins": "Yöneticiler", + "team_members": "Üyeler" } From 4033076e46a3b4be5d6cd2f5a6283e82f3a1aecb Mon Sep 17 00:00:00 2001 From: Weblate Date: Thu, 7 Nov 2024 10:15:19 +0100 Subject: [PATCH 069/218] Translated using Weblate (Polish) Currently translated at 100.0% (1783 of 1783 strings) Translated using Weblate (Polish) Currently translated at 100.0% (1783 of 1783 strings) Translated using Weblate (Polish) Currently translated at 99.7% (1778 of 1783 strings) Translated using Weblate (Polish) Currently translated at 99.7% (1779 of 1783 strings) Translated using Weblate (Polish) Currently translated at 99.3% (1772 of 1783 strings) Translated using Weblate (Polish) Currently translated at 99.5% (1775 of 1783 strings) Translated using Weblate (Polish) Currently translated at 100.0% (1783 of 1783 strings) Translated using Weblate (Polish) Currently translated at 99.9% (1782 of 1783 strings) Translated using Weblate (Polish) Currently translated at 99.8% (1781 of 1783 strings) Translated using Weblate (Polish) Currently translated at 99.8% (1781 of 1783 strings) Co-authored-by: Magpie Co-authored-by: Weblate Co-authored-by: Zuzanna Maria Translate-URL: https://weblate.cryptpad.org/projects/cryptpad/app/pl/ Translation: CryptPad/App --- www/common/translations/messages.pl.json | 242 +++++++++++------------ 1 file changed, 121 insertions(+), 121 deletions(-) diff --git a/www/common/translations/messages.pl.json b/www/common/translations/messages.pl.json index 8b9efeab6..910973218 100644 --- a/www/common/translations/messages.pl.json +++ b/www/common/translations/messages.pl.json @@ -32,7 +32,7 @@ "exportButtonTitle": "Eksportuj ten dokument do lokalnego pliku", "exportPrompt": "Jak chciałbyś nazwać swój plik?", "clickToEdit": "Naciśnij by edytować", - "forgetPrompt": "Kliknięcie OK przeniesie ten dokument do kosza. Jesteś pewien/pewna?", + "forgetPrompt": "Kliknięcie OK przeniesie ten dokument do kosza. Jesteś pewien?", "shareButton": "Udostępnij", "shareSuccess": "Pomyślnie skopiowano URL", "presentButtonTitle": "Otwórz tryb prezentacji", @@ -255,7 +255,7 @@ "viewEmbedTag": "Aby osadzić ten dokument, umieść tę ramkę na swojej stronie, gdziekolwiek chcesz. Możesz ją stylizować za pomocą CSS lub atrybutów HTML.", "themeButtonTitle": "Wybierz motyw kolorystyczny, który ma być używany dla edytorów kodu i slajdów", "themeButton": "Motyw", - "languageButtonTitle": "Wybierz język, który ma być używany do kolorowania składni", + "languageButtonTitle": "Wybierz język, który ma być używany do podświetlenia składni", "languageButton": "Język", "slide_invalidLess": "Nieprawidłowy styl własny", "slideOptionsTitle": "Personalizuj swoje slajdy", @@ -308,7 +308,7 @@ "settings_autostoreMaybe": "Ręcznie (zawsze pytaj)", "settings_autostoreNo": "Ręcznie (nigdy nie pytaj)", "settings_autostoreYes": "Automatycznie", - "settings_autostoreHint": "Automatycznie Wszystkie odwiedzane przez Ciebie dokumenty są przechowywane w Twoim CryptDrive.
Manual (zawsze pytaj) Jeśli nie zapisałeś jeszcze żadnego dokumentu, zostaniesz zapytany czy chcesz go zapisać w swoim CryptDrive.
Ręcznie (nigdy nie pytaj) Dokumenty nie są przechowywane automatycznie w Twoim CryptDrive. Opcja przechowywania ich będzie ukryta.", + "settings_autostoreHint": "Automatycznie Wszystkie odwiedzane przez Ciebie dokumenty są przechowywane w Twoim CryptDrive.
Ręcznie (zawsze pytaj) Jeśli nie zapisałeś jeszcze żadnego dokumentu, zostaniesz zapytany czy chcesz go zapisać w swoim CryptDrive.
Ręcznie (nigdy nie pytaj) Dokumenty nie są przechowywane automatycznie w Twoim CryptDrive. Opcja przechowywania ich będzie ukryta.", "settings_autostoreTitle": "Przechowywanie dokumentów w CryptDrive", "settings_importDone": "Import zakończony", "settings_importConfirm": "Czy na pewno chcesz zaimportować ostatnie dokumenty z tej przeglądarki do CryptDrive Twojego konta użytkownika?", @@ -361,7 +361,7 @@ "settings_cat_account": "Konto", "register_emailWarning3": "Jeśli rozumiesz i nadal chcesz użyć swojego adresu email jako nazwy użytkownika, kliknij OK.", "register_emailWarning2": "Nie będziesz mógł zresetować swojego hasła drogą mailową, tak jak można to zrobić w przypadku wielu innych usług.", - "register_emailWarning1": "Możesz tak zrobić, jeśli chcesz, ale nie zostanie on przesłany na nasz serwer.", + "register_emailWarning1": "Możesz tak zrobić, jeśli chcesz, ale nie zostanie to przesłane na nasz serwer.", "register_emailWarning0": "Wygląda na to, że próbujesz użyć swojego maila jako nazwy użytkownika.", "register_alreadyRegistered": "Ten użytkownik już istnieje, czy chcesz się zalogować?", "register_warning": "Ostrzeżenie", @@ -449,14 +449,14 @@ "support_cat_new": "Nowe zgłoszenie", "support_disabledHint": "Ta instancja CryptPad nie jest jeszcze skonfigurowana do korzystania z formularza pomocy technicznej.", "support_disabledTitle": "Wsparcie nie jest włączone", - "admin_supportListHint": "Tutaj znajduje się lista zgłoszeń wysłanych przez użytkowników na skrzynkę wsparcia. Wszyscy administratorzy mogą zobaczyć te wiadomości i odpowiedzi na nie. Zamkniętego zgłoszenia nie można ponownie otworzyć. Możesz jedynie usunąć (ukryć) zamknięte zgłoszenia, a usunięte zgłoszenia są nadal widoczne dla innych administratorów.", + "admin_supportListHint": "Tutaj znajduje się lista zgłoszeń wysłanych przez użytkowników na skrzynkę pomocy technicznej. Wszyscy administratorzy mogą zobaczyć te wiadomości i odpowiedzi na nie. Zamkniętego zgłoszenia nie można ponownie otworzyć. Możesz jedynie usunąć (ukryć) zamknięte zgłoszenia, a usunięte zgłoszenia są nadal widoczne dla innych administratorów.", "admin_supportListTitle": "Skrzynka pocztowa wsparcia technicznego", "admin_supportInitHint": "Możesz skonfigurować skrzynkę mailową która posłuży jako adres wsparcia technicznego, w celu zapewnienia użytkownikom Twojej instancji CryptPad sposobu na bezpieczny kontakt w razie problemów z kontem.", - "admin_supportInitTitle": "Wsparcie inicjalizacji skrzynki pocztowej", + "admin_supportInitTitle": "Inicjalizacja skrzynki pocztowej wsparcia technicznego", "admin_supportAddError": "Nieprawidłowy klucz prywatny", "admin_supportAddKey": "Dodaj klucz prywatny", - "admin_supportInitPrivate": "Twoja instancja CryptPad ma skonfigurowany adres wsparcia technicznego, ale Twoje konto nie ma poprawnego klucza prywatnego, aby uzyskać do niej dostęp. Użyj poniższego formularza, aby dodać lub zaktualizować klucz prywatny do swojego konta.", - "admin_supportInitHelp": "Twój serwer nie jest jeszcze skonfigurowany do używania skrzynki pocztowej służącej do udzielania pomocy technicznej. Jeśli chcesz, aby skrzynka pocztowa służąca do udzielania pomocy technicznej otrzymywała wiadomości od Twoich użytkowników, powinieneś poprosić administratora serwera o uruchomienie skryptu znajdującego się w \"./scripts/generate-admin-keys.js\", a następnie zapisanie klucza publicznego w pliku \"config.js\" i przesłanie Ci klucza prywatnego.", + "admin_supportInitPrivate": "Twoja instancja CryptPad ma skonfigurowaną skrzynkę mailową do wsparcia technicznego, ale Twoje konto nie ma poprawnego klucza prywatnego, aby uzyskać do niej dostęp. Użyj poniższego formularza, aby dodać lub zaktualizować klucz prywatny do swojego konta.", + "admin_supportInitHelp": "Twój serwer nie jest jeszcze skonfigurowany do używania skrzynki mailowej służącej do udzielania pomocy technicznej. Jeśli chcesz, aby skrzynka mailowa służąca do udzielania pomocy technicznej otrzymywała wiadomości od Twoich użytkowników, powinieneś poprosić administratora serwera o uruchomienie skryptu znajdującego się w \"./scripts/generate-admin-keys.js\", a następnie zapisanie klucza publicznego w pliku \"config.js\" i przesłanie Ci klucza prywatnego.", "admin_cat_support": "Wsparcie", "supportPage": "Wsparcie", "fm_info_sharedFolderHistory": "Historia należy tylko do Twojego folderu współdzielonego: {0}
Twój CryptDrive pozostanie w trybie tylko do odczytu podczas nawigacji.", @@ -557,10 +557,10 @@ "share_contactCategory": "Kontakty", "share_linkCopy": "Kopiuj link", "share_linkOpen": "Otwórz link", - "share_linkPresent": "Obecny", + "share_linkPresent": "Prezentuj", "share_linkView": "Podgląd", "share_linkEdit": "Edytuj", - "share_linkAccess": "Zezwolenia dostępu", + "share_linkAccess": "Prawa dostępu", "share_linkCategory": "Link", "properties_changePasswordButton": "Prześlij", "properties_passwordSuccess": "Hasło zostało pomyślnie zmienione.
Naciśnij OK, aby załadować ponownie i zaktualizować swoje zezwolenia dostępu.", @@ -593,7 +593,7 @@ "creation_owned": "Dokument własny", "creation_404": "Ten dokument już nie istnieje. Użyj poniższego formularza, aby utworzyć nowy dokument.", "feedback_optout": "Jeśli chcesz z tego zrezygnować, odwiedź swoją stronę ustawień użytkownika, gdzie znajdziesz pole wyboru pozwalające włączyć lub wyłączyć opinie użytkowników.", - "feedback_privacy": "Dbamy o Twoją prywatność, a jednocześnie chcemy, aby CryptPad był bardzo łatwy w użyciu. Używamy tego pliku, aby dowiedzieć się, które cechy interfejsu użytkownika mają znaczenie dla naszych użytkowników, pytając ich o to oraz rejestrując parametr określający, jaka akcja została podjęta.", + "feedback_privacy": "Dbamy o Twoją prywatność, a jednocześnie chcemy, aby CryptPad był bardzo łatwy w użyciu. Używamy tego pliku, aby dowiedzieć się, które cechy interfejsu mają znaczenie dla naszych użytkowników, dlatego pobieramy go wraz z parametrem określającym, jaka czynność została wykonana.", "feedback_about": "Jeśli to czytasz, prawdopodobnie byłeś ciekaw, dlaczego CryptPad żąda stron internetowych, gdy wykonujesz pewne czynności.", "view": "pokaż", "edit": "edytuj", @@ -666,7 +666,7 @@ "download_step1": "Pobieranie", "download_dl": "Pobierz", "download_mt_button": "Pobierz", - "upload_up": "Prześlij", + "upload_up": "Wrzuć", "upload_mustLogin": "Musisz być zalogowany, aby przesyłać pliki", "upload_size": "Rozmiar", "upload_cancelled": "Anulowane", @@ -704,7 +704,7 @@ "settings_changePasswordTitle": "Zmień swoje hasło", "settings_ownDrivePending": "Twoje konto jest w trakcie aktualizacji. Prosimy nie zamykać ani nie ładować tej strony ponownie do czasu zakończenia procesu.", "settings_ownDriveConfirm": "Aktualizacja konta może zająć trochę czasu. Będziesz musiał ponownie zalogować się na wszystkich swoich urządzeniach. Czy jesteś pewien?", - "settings_ownDriveButton": "Aktualizuj swoje konto", + "settings_ownDriveButton": "Ulepsz swoje konto", "settings_ownDriveHint": "Starsze konta nie mają dostępu do najnowszych funkcji z powodów technicznych. Darmowa aktualizacja umożliwi korzystanie z aktualnych funkcji i przygotuje Twój CryptDrive na przyszłe aktualizacje.", "settings_ownDriveTitle": "Aktualizuj konto", "settings_padOpenLinkLabel": "Włącz bezpośrednie otwieranie linków", @@ -721,7 +721,7 @@ "pad_goToAnchor": "Przejdź do kotwicy", "bounce_danger": "Link, który kliknąłeś nie prowadzi do strony internetowej, ale do jakiegoś kodu lub danych, które mogą być złośliwe.\n\n(\"{0}\")\n\nCryptPad blokuje je ze względów bezpieczeństwa. Kliknięcie OK spowoduje zamknięcie tej zakładki.", "bounce_confirm": "Właśnie wychodzisz z: {0}\n\nCzy na pewno chcesz odwiedzić \"{1}\"?", - "form_answerChoice": "Proszę wybrać, w jaki sposób chcesz odpowiedzieć na ten formularz:", + "form_answerChoice": "Proszę wybrać, w jaki sposób chcesz odpowiedzieć na tą ankietę:", "form_exportSheet": "Eksportuj do Arkusza", "premiumAccess": "Jako subskrybent {0}, możesz tworzyć nowe dokumenty w tej aplikacji wczesnego dostępu. Należy pamiętać, że jest ona eksperymentalna i nie należy jej jeszcze powierzać ważnych danych.", "earlyAccessBlocked": "Ta aplikacja nie jest jeszcze dostępna w tej instancji", @@ -744,23 +744,23 @@ "form_type_section": "Sekcja warunkowa", "form_editable": "Późniejsza edycja odpowiedzi", "form_makeAnon": "Anonimowe odpowiedzi", - "form_responseMsg": "Komunikat, który zostanie wyświetlony po wypełnianiu formularza przez uczestnika.", + "form_responseMsg": "Komunikat, który zostanie wyświetlony po wypełnianiu ankiety przez uczestnika.", "form_addMsg": "Dodaj komunikat końcowy", "toolbar_preview": "Podgląd", "form_geturl": "Skopiuj publiczny link", "form_changeTypeConfirm": "Wybierz rodzaj nowego pytania.", - "form_corruptAnswers": "Ten formularz zawiera już odpowiedzi. Zmiana typu dla tego pytania może spowodować, że dane dla przesłanych już odpowiedzi będą nieprawidłowe.", - "form_preview": "Zobacz ten formularz", + "form_corruptAnswers": "Ta ankieta zawiera już odpowiedzi. Zmiana typu dla tego pytania może spowodować, że dane dla przesłanych już odpowiedzi będą nieprawidłowe.", + "form_preview": "Zobacz tą ankietę", "form_required_off": "Opcjonalne", "form_required_on": "Wymagane", "form_required_answer": "Odpowiedź: ", "form_requiredWarning": "Na poniższe pytania należy udzielić odpowiedzi:", - "form_authAnswer": "Ten formularz nie może być wypełniony anonimowo", - "form_anonAnswer": "Odpowiedzi na ten formularz są anonimowe", + "form_authAnswer": "Ta ankieta nie może być wypełniona anonimowo", + "form_anonAnswer": "Odpowiedzi na tą ankietę są anonimowe", "form_viewAllAnswers": "Wyświetl wszystkie odpowiedzi ({0})", "form_viewAnswer": "Wyświetl moje odpowiedzi", "form_editAnswer": "Edytuj moje odpowiedzi", - "form_alreadyAnswered": "Odpowiedziałeś na ten formularz w dniu {0}", + "form_alreadyAnswered": "Odpowiedziałeś ną ankietę w dniu {0}", "form_preview_button": "Podgląd", "form_template_poll": "Szybka ankieta dotycząca harmonogramu", "upload_addOptionalAlt": "Dodaj tekst opisowy (opcjonalnie)", @@ -789,8 +789,8 @@ "admin_instancePurposeHint": "Do czego wykorzystujesz tę instancję? Twoja odpowiedź zostanie użyta do opracowania planu rozwoju, jeśli telemetria jest włączona.", "admin_purpose_business": "Dla firmy lub organizacji komercyjnej", "admin_purpose_public": "W celu świadczenia bezpłatnych usług na rzecz społeczeństwa", - "admin_purpose_education": "Dla szkoły, kolegium lub uniwersytetu", - "admin_purpose_org": "Dla organizacji non-profit lub grupy wspierającej", + "admin_purpose_education": "Dla szkoły, uczelni, lub uniwersytetu", + "admin_purpose_org": "Dla organizacji non-profit lub grupy rzeczniczej", "admin_purpose_personal": "Dla siebie, rodziny lub przyjaciół", "admin_purpose_experiment": "Aby przetestować platformę lub opracować nowe funkcje", "admin_purpose_noanswer": "Wolę nie mówić", @@ -798,9 +798,9 @@ "resources_learnWhy": "Dowiedz się, dlaczego został zablokowany", "resources_openInNewTab": "Otwórz w nowej karcie", "resources_imageBlocked": "CryptPad zablokował zdalny obraz", - "fc_open_formro": "Otwarte (jako uczestnik)", + "fc_open_formro": "Otwórz (jako uczestnik)", "form_poll_hint": ": Tak, : Nie, : Akceptowalne", - "admin_provideAggregateStatisticsLabel": "Przedstawiaj łączne dane statystyczne", + "admin_provideAggregateStatisticsLabel": "Przedstawiaj zagregowane dane statystyczne", "admin_provideAggregateStatisticsHint": "Możesz zdecydować się na udostępnienie deweloperom dodatkowych danych dotyczących użytkowania, takich jak przybliżona liczba zarejestrowanych i aktywnych codziennie użytkowników Twojej instancji.", "admin_provideAggregateStatisticsTitle": "Agregacja statystyczna", "admin_blockDailyCheckLabel": "Wyłączenie telemetrii serwera", @@ -818,12 +818,12 @@ "admin_checkupButton": "Uruchom diagnostykę", "admin_checkupHint": "CryptPad zawiera stronę, która automatycznie diagnozuje typowe problemy z konfiguracją i sugeruje, jak je poprawić, jeśli to konieczne.", "admin_checkupTitle": "Zweryfikuj konfigurację tej instancji", - "admin_updateAvailableButton": "Zobacz informacje o wydaniu", - "admin_updateAvailableHint": "Nowa wersja CryptPada jest dostępna", + "admin_updateAvailableButton": "Zobacz informacje o wersji", + "admin_updateAvailableHint": "Nowa wersja CryptPad jest dostępna", "admin_updateAvailableTitle": "Nowe wersje", "admin_cat_network": "Sieć", "mdToolbar_embed": "Osadź plik", - "restrictedLoginPrompt": "Nie jesteś upoważniony do dostępu do tego dokumentu.Zaloguj się, jeśli uważasz, że Twoje konto powinno być w stanie uzyskać dostęp do niego.", + "restrictedLoginPrompt": "Nie jesteś upoważniony do dostępu do tego dokumentu.Zaloguj się, jeśli uważasz, że Twoje konto powinno być w stanie uzyskać do niego dostęp.", "copyToClipboard": "Skopiuj do schowka", "settings_driveRedirect": "Przekieruj mnie automatycznie", "settings_driveRedirectHint": "Automatyczne przekierowanie ze strony głównej na dysk po zalogowaniu nie jest już domyślnie włączone. Starsze ustawienie może być włączone poniżej.", @@ -859,21 +859,21 @@ "form_pollYourAnswers": "Twoje odpowiedzi", "form_pollTotal": "Razem", "form_poll_switch": "Zamień osie", - "form_poll_time": "Czas", + "form_poll_time": "Godzina", "form_poll_day": "Dzień", "form_poll_text": "Tekst", "form_editType": "Rodzaj opcji", "form_editMaxLength": "Maksymalna liczba znaków", "form_editMax": "Maksymalna liczba możliwych do wyboru opcji", "form_editBlock": "Edytuj", - "form_invalid": "Nieprawidłowy formularz", + "form_invalid": "Nieprawidłowa ankieta", "share_formView": "Uczestnik", "share_formAuditor": "Audytor", "share_formEdit": "Autor", "admin_supportPrivButton": "Pokaż klucz", "admin_supportPrivHint": "Wyświetl klucz prywatny, który będzie potrzebny innym administratorom do przeglądania zgłoszeń. Formularz do wprowadzenia tego klucza będzie wyświetlany w ich panelu administracyjnym.", - "admin_supportInitGenerate": "Generowanie kluczy wsparcia", - "admin_supportPrivTitle": "Obsługa klucza prywatnego skrzynki pocztowej", + "admin_supportInitGenerate": "Generowanie kluczy pomocy technicznej", + "admin_supportPrivTitle": "Klucz prywatny skrzynki pocztowej pomocy technicznej", "admin_emailHint": "Wprowadź email kontaktowy dla Twojej instancji", "admin_emailTitle": "Email kontaktowy administratora", "oo_importBin": "Kliknij OK, aby zaimportować wewnętrzny format .bin programu CryptPad.", @@ -893,7 +893,7 @@ "reminder_minutes": "{0} rozpocznie się za {1} minut(y)", "reminder_inProgressAllDay": "Dzisiaj: {0}", "reminder_inProgress": "{0} rozpoczął się w {1}", - "reminder_now": "{0} miało miejsce w dniu", + "reminder_now": "{0} rozpoczął się", "reminder_missed": "{0} miało miejsce w dniu {1}", "calendar_more": "{0} więcej", "calendar_days": "Dni", @@ -927,13 +927,13 @@ "settings_deleteContinue": "Usuń moje konto", "settings_deleteWarning": "Ostrzeżenie: jesteś obecnie zapisany na plan premium (płatny lub przekazany przez innego użytkownika). Proszę anulować swój plan przed usunięciem konta, ponieważ nie będzie to możliwe bez kontaktu z obsługą po usunięciu konta.", "broadcast_newCustom": "Wiadomość od administratorów", - "broadcast_preview": "Przeglądaj powiadomienia", + "broadcast_preview": "Podgląd powiadomienia", "broadcast_defaultLanguage": "Powrót do tego języka", "broadcast_translations": "Tłumaczenia", "admin_broadcastCancel": "Usuń wiadomość", "admin_broadcastActive": "Aktywna wiadomość", "admin_broadcastButton": "Wyślij", - "admin_broadcastHint": "Wyślij wiadomość do wszystkich użytkowników tej instancji. Wszyscy istniejący i nowi użytkownicy otrzymają ją jako powiadomienie. Podgląd wiadomości przed wysłaniem jest możliwy dzięki opcji \"Podgląd powiadomienia\". Powiadomienia z podglądem mają czerwoną ikonę i są widoczne tylko dla Ciebie.", + "admin_broadcastHint": "Wyślij wiadomość do wszystkich użytkowników tej instancji. Wszyscy istniejący i nowi użytkownicy otrzymają ją jako powiadomienie. Podgląd wiadomości przed jej wysłaniem jest możliwy dzięki opcji \"Podgląd powiadomienia\". Podgląd ma czerwoną ikonę i jest widoczny tylko dla Ciebie.", "admin_cat_broadcast": "Rozsyłanie", "admin_broadcastTitle": "Roześlij wiadomość", "broadcast_surveyURL": "Link do ankiety", @@ -951,14 +951,14 @@ "broadcast_end": "Koniec", "broadcast_start": "Początek", "toolbar_degraded": "Ponad {0} redaktorów jest obecnie obecnych w tym dokumencie. Lista użytkowników i czat są wyłączone, aby poprawić wydajność.", - "oo_lostEdits": "Niestety po zsynchronizowaniu nowej zawartości nie można odzyskać ostatnio niezapisanych edycji.", + "oo_lostEdits": "Niestety po zsynchronizowaniu nowej zawartości nie można odzyskać Twoich ostatnich niezapisanych zmian.", "fm_cantUploadHere": "Nie można tutaj przesłać pliku", "importError": "Nie udało się zaimportować (niewłaściwy format)", "addOptionalPassword": "Dodaj hasło (opcjonalnie)", - "settings_colortheme_custom": "Własny", + "settings_colortheme_custom": "Niestandardowy", "pad_settings_show": "Pokaż", "pad_settings_hide": "Ukryj", - "pad_settings_comments": "Określ, czy Komentarz ma być domyślnie widoczny czy ukryty.", + "pad_settings_comments": "Określ, czy Komentarze mają być domyślnie widoczne czy ukryte.", "pad_settings_outline": "Określ, czy spis treści ma być domyślnie widoczny czy ukryty.", "pad_settings_width_large": "Pełna szerokość", "pad_settings_width_small": "Tryb strony", @@ -984,11 +984,11 @@ "settings_cacheTitle": "Pamięć podręczna", "docs_link": "Dokumentacja", "creation_helperText": "Otwórz w dokumentacji", - "creation_expiresIn": "Zniszcz w", + "creation_expiresIn": "Zniszcz za", "register_warning_note": "Ze względu na szyfrowany charakter CryptPada, administratorzy serwisu nie będą w stanie odzyskać danych w przypadku, gdy zapomnisz swoją nazwę użytkownika i/lub hasło. Prosimy o zapisanie ich w bezpiecznym miejscu.", "register_notes": "
  • Twoje hasło jest prywatnym kluczem, który szyfruje wszystkie Twoje dokumenty. W przypadku jego utraty nie ma możliwości odzyskania danych.
  • Jeśli korzystasz z udostępnionego komputera, pamiętaj o wylogowaniu po zakończeniu pracy. Tylko zamknięcie okna przeglądarki powoduje, że Twoje konto nadal pozostaje narażone.
  • Aby zachować dokumenty, które utworzyłeś i/lub zapisałeś bez logowania, zaznacz \"Importuj dokumenty z sesji gościa\".
", "register_notes_title": "Ważne uwagi", - "admin_getlimitsHint": "Lista wszystkich własnych limitów przestrzeni zastosowanych w Twojej instancji.", + "admin_getlimitsHint": "Lista wszystkich niestandardowych limitów przestrzeni zastosowanych w Twojej instancji.", "info_imprintFlavour": "Informacje prawne o administratorach tej instancji", "offlineError": "Nie można zsynchronizować najnowszych danych, ta strona nie może zostać wyświetlona w tej chwili. Ładowanie będzie kontynuowane po przywróceniu połączenia z serwisem.", "share_noContactsOffline": "Jesteś obecnie w trybie offline. Kontakty nie są dostępne.", @@ -1002,7 +1002,7 @@ "admin_support_normal": "Zgłoszenia bez odpowiedzi:", "admin_support_premium": "Zgłoszenia premium:", "contacts_confirmCancel": "Czy na pewno chcesz anulować prośbę o kontakt z {0}?", - "history_trimPrompt": "Ten dokument ma zapisaną {0} historię, która może spowolnić czas ładowania. Rozważ usunięcie historii, jeśli nie jest ona potrzebna.", + "history_trimPrompt": "Ten dokument zgromadził {0} historii, która może spowolnić czas ładowania. Rozważ usunięcie historii, jeśli nie jest ona potrzebna.", "mediatag_loadButton": "Wczytaj załącznik", "settings_mediatagSizeHint": "Maksymalny rozmiar w megabajtach (MB) dla automatycznie ładowanych elementów multimedialnych (obrazy, wideo, pdf) osadzonych w dokumentach. Elementy większe niż podany rozmiar mogą zostać załadowane ręcznie. Użyj \"-1\", aby zawsze ładować elementy multimedialne automatycznie.", "settings_mediatagSizeTitle": "Automatyczny limit pobrań", @@ -1014,12 +1014,12 @@ "download_zip_file": "Plik {0}/{1}", "download_zip": "Tworzenie pliku ZIP…", "fileTableHeader": "Pobrane i przesłane dane", - "allowNotifications": "Wszystkie powiadomienia", + "allowNotifications": "Zezwól na powiadomienia", "archivedFromServer": "Dokument zarchiwizowany", "restoredFromServer": "Przywrócony dokument", "admin_archiveInput2": "Hasło dokumentu", "admin_archiveInput": "URL dokumentu", - "admin_unarchiveButton": "Przywróc", + "admin_unarchiveButton": "Przywróć", "admin_unarchiveHint": "Przywróć dokument, który został wcześniej zarchiwizowany", "admin_archiveButton": "Archiwizuj", "admin_archiveHint": "Oznacz dokument jako niedostępny bez usuwania go na stałe. Zostanie on umieszczony w katalogu 'archiwum' i usunięty po kilku dniach (można tę opcję zmienić w pliku konfiguracyjnym serwera).", @@ -1029,14 +1029,14 @@ "error_unhelpfulScriptError": "Błąd skryptu: Sprawdź konsolę przeglądarki, aby uzyskać szczegółowe informacje", "tag_edit": "Edytuj", "tag_add": "Dodaj", - "loading_state_5": "Przekształć dokument", + "loading_state_5": "Zrekonstruuj dokument", "loading_state_4": "Wczytaj zespoły", "loading_state_1": "Wczytaj dysk", "loading_state_3": "Wczytaj foldery współdzielone", "loading_state_2": "Aktualizuj zawartość", "loading_state_0": "Zbuduj interfejs", "fm_shareFolderPassword": "Chroń ten folder hasłem (opcjonalnie)", - "access_destroyPad": "Zniszcz ten dokument lub folder na trwałe", + "access_destroyPad": "Trwale zniszcz ten dokument lub folder", "fm_deletedFolder": "Usunięty folder", "admin_limitUser": "Klucz publiczny użytkownika", "team_exportButton": "Pobierz", @@ -1048,13 +1048,13 @@ "admin_limitNote": "Notatka: {0}", "admin_limitSetNote": "Notatka", "admin_limitMB": "Limit (w MB)", - "admin_setlimitTitle": "Zastosuj limit własny", - "admin_setlimitHint": "Ustaw własne limity dla użytkowników za pomocą ich klucza publicznego. Możesz zaktualizować lub usunąć istniejący limit.", + "admin_setlimitTitle": "Zastosuj limit niestandardowy", + "admin_setlimitHint": "Ustaw niestandardowe limity dla użytkowników za pomocą ich klucza publicznego. Możesz zaktualizować lub usunąć istniejący limit.", "admin_limitPlan": "Plan: {0}", - "admin_getlimitsTitle": "Własne limity", + "admin_getlimitsTitle": "Niestandardowe limity", "admin_limit": "Obecny limit: {0}", "admin_setlimitButton": "Ustaw limit", - "admin_defaultlimitHint": "Maksymalny limit przestrzeni na dyskach CryptDrive (użytkownicy i zespoły), gdy nie nadano żadnych reguły niestandardowych", + "admin_defaultlimitHint": "Maksymalny limit przestrzeni na dyskach CryptDrive (użytkownicy i zespoły), gdy nie nadano żadnych reguł niestandardowych", "admin_defaultlimitTitle": "Maksymalny limit przechowywania danych (MB)", "admin_registrationTitle": "Zamknij rejestrację", "admin_registrationHint": "Goście odwiedzający instancję nie mają możliwości stworzenia konta. Zaproszenia mogą być tworzone przez administratorów.", @@ -1092,7 +1092,7 @@ "settings_kanbanTagsAnd": "ORAZ", "settings_kanbanTagsHint": "Wybierz, w jaki sposób filtr tagów ma działać przy wyborze wielu tagów: pokazywać tylko karty zawierające wszystkie wybrane tagi (ORAZ) lub pokazywać karty zawierające dowolny z wybranych tagów (LUB)", "settings_kanbanTagsTitle": "Filtr tagów", - "pad_tocHide": "Konspekt", + "pad_tocHide": "Zarys", "fm_noResult": "Nie znaleziono żadnych wyników", "fm_restricted": "Nie masz dostępu", "fm_emptyTrashOwned": "Twój kosz zawiera dokumenty, których jesteś właścicielem. Możesz je usunąć tylko z Twojego dysku lub zniszczyć dla wszystkich użytkowników.", @@ -1109,9 +1109,9 @@ "support_cat_bug": "Zgłoszenie błędu", "support_cat_data": "Utrata zawartości", "support_cat_account": "Konto użytkownika", - "info_privacyFlavour": "polityka prywatności w tym przypadku", + "info_privacyFlavour": "Polityka prywatności tej instancji", "user_about": "O CryptPad", - "settings_safeLinkDefault": "Bezpieczne łącza są teraz domyślnie włączone. Do kopiowania linków proszę używać menu Udostępnij , a nie paska adresu przeglądarki.", + "settings_safeLinkDefault": "Bezpieczne linki są teraz domyślnie włączone. Do kopiowania linków proszę używać menu Udostępnij , a nie paska adresu przeglądarki.", "support_languagesPreamble": "Zespół wsparcia technicznego posługuje się następującymi językami:", "slide_textCol": "Kolor tekstu", "slide_backCol": "Kolor tła", @@ -1180,21 +1180,21 @@ "form_anonymous_off": "Zablokowane", "form_anonymous_on": "Dozwolone", "form_anonymous": "Dostęp dla gości (niezalogowanych)", - "form_willClose": "Ten formularz zostanie zamknięty w dniu {0}", - "form_isClosed": "Ten formularz został zamknięty w dniu {0}", - "form_isOpen": "Ten formularz jest aktywny", + "form_willClose": "Ta ankieta zostanie zamknięta w dniu {0}", + "form_isClosed": "Ta ankieta została zamknięta w dniu {0}", + "form_isOpen": "Ta ankieta jest aktywna", "form_setEnd": "Ustal datę zamknięcia", "form_removeEnd": "Usuń datę zamknięcia", "form_open": "Otwórz", "form_isPrivate": "Odpowiedzi są poufne", "form_isPublic": "Odpowiedzi są publiczne", - "form_makePublicWarning": "Czy na pewno chcesz upublicznić odpowiedzi na ten formularz? Poprzednie i przyszłe odpowiedzi będą widoczne dla uczestników. Nie można tego cofnąć.", + "form_makePublicWarning": "Czy na pewno chcesz upublicznić odpowiedzi na tą ankietę? Poprzednie i przyszłe odpowiedzi będą widoczne dla uczestników. Nie można tego cofnąć.", "form_makePublic": "Opublikuj odpowiedzi", "form_invalidQuestion": "Pytanie {0}", "form_invalidWarning": "W niektórych odpowiedziach są błędy:", - "form_input_ph_url": "https://example.com", - "form_input_ph_email": "email@example.com", - "form_notAnswered": "{0} odpowiedzi puste", + "form_input_ph_url": "https://przyklad.pl", + "form_input_ph_email": "email@przyklad.pl", + "form_notAnswered": "{0} pustych odpowiedzi", "form_answerWarning": "Niepotwierdzona tożsamość", "form_answerName": "Odpowiedź od {0} na {1}", "form_backButton": "Wstecz", @@ -1205,8 +1205,8 @@ "form_editor": "Edytor", "form_results_empty": "Nie ma żadnych odpowiedzi", "form_results": "Odpowiedzi ({0})", - "form_answered": "Odpowiedziałeś już na ten formularz", - "form_cantFindAnswers": "Nie można pobrać istniejących odpowiedzi dla tego formularza.", + "form_answered": "Odpowiedziałeś już na tą ankietę", + "form_cantFindAnswers": "Nie można pobrać istniejących odpowiedzi dla tej ankiety.", "form_updateWarning": "Zaktualizuj mimo wszystko", "allow_disabled": "wyłączony", "allow_enabled": "włączony", @@ -1234,7 +1234,7 @@ "settings_safeLinksHint": "CryptPad zawiera klucze do odszyfrowania Twoich dokumentów w swoich linkach. Każdy, kto ma dostęp do historii przeglądania, może potencjalnie odczytać Twoje dane. Obejmuje to natrętne rozszerzenia przeglądarki oraz przeglądarki, które synchronizują historię na różnych urządzeniach. Włączenie funkcji \"bezpieczne łącza\" zapobiega wprowadzaniu kluczy do historii przeglądania lub wyświetlaniu ich w pasku adresu, jeśli jest to możliwe. Zdecydowanie zalecamy włączenie tej funkcji i korzystanie z menu {0} Udostępnij, aby generować linki, które można udostępniać.", "profile_login": "Musisz się zalogować, aby dodać tego użytkownika do swoich kontaktów", "dontShowAgain": "Nie pokazuj więcej", - "safeLinks_error": "Ten link został skopiowany z paska adresu przeglądarki i nie zapewnia dostępu do dokumentu. Proszę skorzystać z menu Udostępnij , aby udostępniać bezpośrednio kontaktom lub skopiować link. Przeczytaj więcej o funkcji bezpiecznych linków.", + "safeLinks_error": "Ten link został skopiowany z paska adresu przeglądarki i nie zapewnia dostępu do dokumentu. Proszę skorzystać z menu Udostępnij , aby udostępniać kontaktom bezpośrednio lub skopiować link. Przeczytaj więcej o funkcji bezpiecznych linków.", "settings_safeLinksCheckbox": "Włącz bezpieczne linki", "settings_safeLinksTitle": "Bezpieczne linki", "settings_cat_security": "Bezpieczeństwo & Ochrona prywatności", @@ -1256,17 +1256,17 @@ "team_cat_link": "Link do zaproszenia", "team_links": "Linki do zaproszeń", "team_inviteGetData": "Uzyskiwanie danych zespołu", - "team_inviteTitle": "Zaproszenie zespołu", + "team_inviteTitle": "Zaproszenie do zespołu", "team_inviteJoin": "Dołącz do zespołu", "team_invitePasswordLoading": "Odszyfrowywanie zaproszenia", "team_inviteEnterPassword": "Aby kontynuować, wprowadź hasło do zaproszenia.", "team_invitePleaseLogin": "Proszę się zalogować lub zarejestrować, aby zaakceptować to zaproszenie.", - "team_inviteFromMsg": "{0} zaprosił cię do dołączenia do zespołu {1}", + "team_inviteFromMsg": "{0} zaprosił Cię do dołączenia do zespołu {1}", "team_inviteFrom": "Od:", "team_inviteLinkCopy": "Skopiuj link", "team_inviteLinkCreate": "Utwórz link", "team_inviteLinkErrorName": "Proszę dodać nazwę dla osoby, którą zapraszasz. Osoba ta może je później zmienić. ", - "team_inviteLinkWarning": "Osoby, które uzyskają dostęp do tego linku, będą mogły dołączyć do tego zespołu i przeglądać jego zawartość. Udostępniaj go ostrożnie.", + "team_inviteLinkWarning": "Osoby, które uzyskają dostęp do tego linku, będą mogły dołączyć do tego zespołu i przeglądać jego zawartość. Uważaj, gdzie go udostępniasz.", "team_inviteLinkLoading": "Generowanie linku", "team_inviteLinkNoteMsg": "Ta wiadomość zostanie wyświetlona zanim odbiorca zdecyduje, czy chce dołączyć do zespołu.", "team_inviteLinkNote": "Dodaj prywatną wiadomość", @@ -1322,7 +1322,7 @@ "team_kickConfirm": "{0} będzie wiedział, że usunąłeś ich z zespołu. Czy jesteś pewien?", "team_ownerConfirm": "Współwłaściciele mogą modyfikować lub usunąć zespół oraz usunąć Ciebie jako właściciela. Czy jesteś pewien?", "team_rosterPromoteOwner": "Zaoferuj prawo własności", - "owner_team_add": "{0} chce, abyś został właścicielem drużyny {1}. Czy zgadzasz się na to?", + "owner_team_add": "{0} chce, abyś został właścicielem zespołu {1}. Czy zgadzasz się na to?", "team_listSlot": "Dostępne miejsce dla zespołu", "team_listTitle": "Twoje zespoły", "team_maxTeams": "Każde konto użytkownika może być członkiem tylko {0} zespołów.", @@ -1338,8 +1338,8 @@ "team_leaveButton": "Opuść ten zespół", "team_inviteButton": "Zaproś nowych członków", "team_rosterKick": "Wyrzuć z zespołu", - "team_rosterDemote": "Zdegraduj", - "team_rosterPromote": "Awansuj", + "team_rosterDemote": "Zmniejsz kompetencje", + "team_rosterPromote": "Zwiększ kompetencje", "team_createName": "Nazwa zespołu", "team_createLabel": "Utwórz nowy zespół", "team_infoLabel": "O zespołach", @@ -1350,12 +1350,12 @@ "team_cat_create": "Nowy", "team_cat_list": "Zespoły", "team_cat_general": "Informacje ogólne", - "team_declineInvitation": "{0} odrzucił twoją ofertę dołączenia do zespołu: {1}", - "team_acceptInvitation": "{0} zaakceptował twoją ofertę dołączenia do zespołu: {1}", + "team_declineInvitation": "{0} odrzucił Twoją ofertę dołączenia do zespołu: {1}", + "team_acceptInvitation": "{0} zaakceptował Twoją ofertę dołączenia do zespołu: {1}", "team_kickedFromTeam": "{0} wyrzucił Cię z zespołu: {1}", "team_invitedToTeam": "{0} zaprasza Cię do swojego zespołu: {1}", "team_pcsSelectHelp": "Utworzenie dokumentu na dysku zespołu przekazuje zespołowi prawo własności.", - "team_pcsSelectLabel": "Przechowuj", + "team_pcsSelectLabel": "Przechowuj w", "team_inviteModalButton": "Zaproś", "team_pickFriends": "Wybierz kontakty, które chcesz zaprosić do tego zespołu", "share_linkTeam": "Dodaj do dysku zespołu", @@ -1365,8 +1365,8 @@ "owner_request_accepted": "{0} zaakceptował twoją ofertę zostania właścicielem {1}", "owner_request": "{0} chce, abyś był właścicielem {1}", "owner_add": "{0} chce, abyś był właścicielem dokumentu {1}. Czy zgadzasz się na to?", - "owner_addConfirm": "Współwłaściciele będą mogli zmienić zawartość i usunąć Cię jako właściciela. Czy jesteś pewny/pewna?", - "owner_removeMeConfirm": "Właśnie rezygnujesz z prawa własności. Nie będziesz w stanie cofnąć tego działania. Czy jesteś pewien/pewna?", + "owner_addConfirm": "Współwłaściciele będą mogli zmienić zawartość i usunąć Cię jako właściciela. Czy jesteś pewny?", + "owner_removeMeConfirm": "Właśnie rezygnujesz z prawa własności. Nie będziesz w stanie cofnąć tego działania. Czy jesteś pewien?", "owner_removeConfirm": "Czy na pewno chcesz usunąć prawa własności dla wybranych użytkowników? Zostaną oni powiadomieni o tym działaniu.", "owner_unknownUser": "nieznany", "owner_removePendingText": "W trakcie realizacji", @@ -1382,20 +1382,20 @@ "requestEdit_request": "{1} chce edytować dokument {0}", "later": "Zdecyduj później", "requestEdit_viewPad": "Otwórz dokument w nowej karcie", - "ui_restore": "Przywróc", + "ui_restore": "Przywróć", "ui_archive": "Archiwizuj", "ui_undefined": "nieznany", - "admin_documentType": "Typ", + "admin_documentType": "Typ dokumentu", "support_warning_prompt": "Wybierz najbardziej odpowiednią kategorię dla swojego zgłoszenia. Pomaga to administratorom w selekcji zgłoszeń i zapewnia dalsze sugestie dotyczące informacji, które należy podać", "info_sourceFlavour": "Kod źródłowy CryptPad", - "info_termsFlavour": "Warunki korzystania z usługidla tego urządzenia", + "info_termsFlavour": "Warunki korzystania z usługidla tej instancji", "footer_source": "Kod źródłowy", - "admin_jurisdictionHint": "Kraj, w którym przechowywane są zaszyfrowane dane tego urządzenia", + "admin_jurisdictionHint": "Kraj, w którym przechowywane są zaszyfrowane dane tej instancji", "admin_jurisdictionTitle": "Lokalizacja hostingu", "admin_descriptionHint": "Opis, który wyświetli się dla tej instancji na liście publicznych instancji na stronie cryptpad.org", "admin_descriptionTitle": "Opis instancji", "ui_saved": "{0} zapisano", - "admin_nameHint": "Wyświetlana nazwa dla tego urządzenia z listy publicznych instacji na stronie cryptpad.org", + "admin_nameHint": "Wyświetlana nazwa dla tej instancji na liście publicznych instacji na stronie cryptpad.org", "admin_archiveNote": "Notatka", "common_connectionLost": "Utracono Połączenie z Serwerem
Dopóki połączenie nie wróci, włączony będzie tryb tylko do odczytu.", "admin_infoNotice2": "Więcej informacji można znaleźć w zakładce \"Sieć\".", @@ -1419,15 +1419,15 @@ "admin_cacheEvictionRequired": "Serwer został zaktualizowany o nowe ustawienia. Użyj przycisku Wyczyść pamięć podręczną, aby upewnić się, że ta zmiana będzie widoczna dla wszystkich użytkowników.", "support_warning_document": "Określ typ dokumentu, który powoduje problem i podaj jego identyfikator lub link", "admin_enableDiskMeasurementsTitle": "Pomiar wydajności dysku", - "admin_bytesWrittenTitle": "Okno pomiaru wydajności dysku", + "admin_bytesWrittenTitle": "Okno czasowe pomiaru wydajności dysku", "error_evalPermitted": "Przerwanie, ponieważ eval nie powinien być dozwolony.\n\nTen błąd jest związany z nagłówkami Content-Security-Policy, może być spowodowany: przestarzałą przeglądarką, która ich nie obsługuje, rozszerzeniami przeglądarki, które zakłócają ich prawidłowe działanie lub nieprawidłową konfiguracją tej instancji CryptPad.", "admin_enableembedsHint": "Zezwól na osadzanie dokumentów i multimediów z tej instancji na innych stronach internetowych. Spowoduje to dodanie opcji \"Osadź\" do menu Udostępnij. Ze względów bezpieczeństwa aplikacje korzystające z OnlyOffice (Arkusze, Dokument, Prezentacja) nie mogą być osadzane, nawet jeśli to ustawienie jest aktywne.", - "admin_bytesWrittenHint": "Jeśli włączono pomiary wydajności dysku, czas trwania okna można skonfigurować poniżej.", + "admin_bytesWrittenHint": "Jeśli włączono pomiary wydajności dysku, czas trwania można skonfigurować poniżej.", "admin_setDuration": "Ustaw czas trwania", "ui_ms": "milisekundy", "admin_enableembedsTitle": "Włącz osadzanie zdalne", "error_embeddingDisabled": "Osadzanie jest wyłączone dla tej instancji CryptPad", - "error_embeddingDisabledSpecific": "Osadzanie jest wyłączone dla tej instancji CryptPad.", + "error_embeddingDisabledSpecific": "Osadzanie jest wyłączone dla tej aplikacji CryptPad.", "error_incorrectAccess": "Dostęp do tej strony można uzyskać tylko przez {0}.", "ui_experimental": "Ta funkcja jest uznawana za eksperymentalną.", "admin_noticeTitle": "Ogłoszenie na stronie głównej", @@ -1437,7 +1437,7 @@ "home_location": "Zaszyfrowane dane znajdują się w {0}", "home_morestorage": "By uzyskać więcej miejsca:", "register_instance": "Tworzenie nowego konta na {0}", - "admin_uptimeHint": "Data i godzina założenia serwera", + "admin_uptimeHint": "Data i godzina uruchomienia serwera", "admin_cat_database": "Baza danych", "ui_false": "fałsz", "ui_none": "brak", @@ -1463,7 +1463,7 @@ "admin_restoreDocument": "Przywróć dokument", "admin_planlimit": "Limit pamięci", "admin_getRawMetadata": "Historia metadanych", - "og_teamDrive": "Dysk grupowy", + "og_teamDrive": "Dysk zespołowy", "admin_cat_users": "Katalog użytkowników", "admin_archiveReason": "Podaj powód archiwizacji i potwierdź, że chcesz kontynuować", "admin_accountMetadataPlaceholder": "ID Użytkownika (klucz publiczny)", @@ -1477,7 +1477,7 @@ "admin_channelAvailable": "Dostępne", "admin_uptimeTitle": "Data uruchomienia", "admin_blockMetadataTitle": "Informacje segmentu logowania", - "admin_blockMetadataHint": "Segment logowania pozwala użytkownikowi na logowanie się do CryptPad'a za pomocą nazwy użytkownika + hasła", + "admin_blockMetadataHint": "Segment logowania pozwala użytkownikowi na logowanie się do CryptPad za pomocą nazwy użytkownika + hasła", "admin_blockMetadataPlaceholder": "Względny lub bezwzględny adres URL", "admin_planName": "Nazwa planu", "admin_note": "Adnotacja planu", @@ -1513,12 +1513,12 @@ "admin_pinLogAvailable": "Dostępny log pinezek", "admin_pinLogArchived": "Log pinezek został zarchiwizowany", "admin_getPinList": "Lista obecnych pinezek", - "og_default": "CryptPad: szyfrowany end-to-end pakiet narzędzi do wspólnej pracy", + "og_default": "CryptPad: kompleksowo szyfrowany pakiet narzędzi do wspólnej pracy", "calendar_rec_edit": "Powtarzające się wydarzenie", "calendar_str_filter_day": "Dni: {0}", - "calendar_rec_weekdays": "Codziennie w weekendy", + "calendar_rec_weekdays": "Codziennie w dni powszednie", "calendar_str_filter_monthday": "Dni miesiąca: {0}", - "calendar_rec_custom": "Własne", + "calendar_rec_custom": "Niestandardowe", "calendar_str_filter_yearday": "Dni roku: {0}", "calendar_str_filter_weekno": "Tygodnie: {0}", "calendar_str_filter_month": "Miesiące: {0}", @@ -1549,7 +1549,7 @@ "calendar_nth_4": "czwarty", "calendar_rec_monthly_pick": "W dni", "calendar_nth_5": "piąty", - "calendar_str_monthly": "{0} miesiąc/y", + "calendar_str_monthly": "{0} miesięcy", "calendar_str_weekly": "{0} tygodni", "calendar_str_yearly": "{0} lat", "calendar_str_nthdayofmonth": "w {0} {1}", @@ -1558,23 +1558,23 @@ "calendar_str_daily": "{0} dni", "team_inviteRole": "Domyślna rola", "form_exportJSON": "Wyeskportuj do JSON", - "form_alreadyAnsweredMult": "Udzieliłeś/łaś odpowiedzi na ten formularz w dniu:", - "form_responseNotification": "Nowe odpowiedzi w formularzu: {0}", + "form_alreadyAnsweredMult": "Udzieliłeś odpowiedzi na tą ankietę w dniu:", + "form_responseNotification": "Nowe odpowiedzi w ankiecie: {0}", "form_answer_new": "Prześlij ponownie", - "form_editable_off": "Jednorazowy", - "form_editable_on_del": "Jednorazowy i edytuj/usuń", - "form_multiple": "Wielokrotnie", - "form_multiple_edit": "Wielokrotnie i edytuj/usuń", - "form_editable_on": "Jednorazowy i edytuj", - "form_editable_str": "Zgłoszenie", + "form_editable_off": "Jednorazowe", + "form_editable_on_del": "Jednorazowe i edytuj/usuń", + "form_multiple": "Wielokrotne", + "form_multiple_edit": "Wielokrotne i edytuj/usuń", + "form_editable_on": "Jednorazowe i edytuj", + "form_editable_str": "Przesłanie odpowiedzi", "team_linkUses": "(pozostało {0}/{1})", "form_anonymized": "Odpowiedzi są anonimowe", - "form_settingsButton": "Ustawienia formularza", + "form_settingsButton": "Ustawienia ankiety", "calendar_desc": "Opis", "calendar_description": "Opis:{0}{1}", "sso_login_description": "Zaloguj się z", "sso_register_description": "Zarejestruj się z", - "recovery_mfa_description": "Jeśli straciłeś dostęp do swojej metody 2FA możesz wyłączyć je dla swojego konta używając kodu odzyskiwania. Zacznij od wprowadzenia swojej nazwy użytkownika i hasła:", + "recovery_mfa_description": "Jeśli straciłeś dostęp do swojej metody 2FA, możesz wyłączyć ją dla swojego konta używając kodu odzyskiwania. Zacznij od wprowadzenia swojej nazwy użytkownika i hasła:", "calendar_removeNotification": "Usuń przypomnienie", "team_inviteUses": "Dozwolona ilość użycia linku (0 = nielimitowany)", "form_deleteAll": "Usuń wszystko", @@ -1596,7 +1596,7 @@ "admin_archiveAccountInfo": "Łącznie z dokumentami, które są jego własnością", "admin_restoreAccount": "Przywróć to konto", "admin_accountSuspended": "Konto zarchiwizowane przez administratora", - "dph_sf_pw": "Folder współdzielony {0} jest chroniony nowym hasłem. Wprowadź nowe hasło aby uzyskać dostęp, lub usuń folder ze swojego dysku.", + "dph_sf_pw": "Folder współdzielony {0} jest chroniony nowym hasłem. Wprowadź nowe hasło, aby uzyskać dostęp, lub usuń folder ze swojego dysku.", "settings_mfaTitle": "Uwierzytelnianie Dwuetapowe (2FA)", "dph_account_inactive": "To konto zostało usunięte z powodu braku aktywności", "dph_account_moderated": "To konto zostało zawieszone przez moderatora", @@ -1614,7 +1614,7 @@ "form_condorcetRanked": "Metoda Tidemana (Ranked Pairs)", "form_showCondorcetWinner": "zwycięzca: ", "form_showDetails": "Szczegóły", - "form_condorcetExtendedDisplay": "Ilość cząstkowych wygranych dla każdego z kandydatów: ", + "form_condorcetExtendedDisplay": "Ilość rund wygranych przez każdego z kandydatów: ", "form_noCondorcetWinner": "Brak zwycięzcy", "form_type_date": "Data", "mfa_setup_label": "Aby włączyć 2FA, na początku wprowadź swoje hasło", @@ -1648,18 +1648,18 @@ "admin_usersRemove": "Usuń", "register_invalidToken": "Zaproszenie jest nieprawidłowe", "label_logo": "Logo CryptPad", - "recovery_mfa_secret": "Wprowadź swój kod odzyskiwania aby wyłączyć 2FA dla swojego konta:", + "recovery_mfa_secret": "Wprowadź swój kod odzyskiwania, aby wyłączyć 2FA dla swojego konta:", "login_notFilledUser": "Wprowadź nazwę użytkownika", "login_notFilledPass": "Wprowadź hasło", "date": "Data", - "settings_removeOwnedHint": "Wszystkie dokumenty których jesteś jedynym właścicielem zostaną bezpowrotnie zniszczone", + "settings_removeOwnedHint": "Wszystkie dokumenty, których jesteś jedynym właścicielem, zostaną bezpowrotnie zniszczone", "mfa_status_off": "2FA nieaktywne na tym koncie", - "mfa_recovery_hint": "Jeśli stracisz dostęp do swojej aplikacji uwierzytelniającej możesz pozbawić się możliwości dostępu do konta CryptPad. Kod odzyskiwania może zostać użyty aby wyłączyć 2FA i przywrócić dostęp.", - "admin_totpRecoveryHint": "Użytkownik może skopiować dane ze strony /recovery/ przywracania 2FA i wysłać je mailem do administratora instancji. Wklej dane przywracania poniżej aby wyłączyć 2FA dla konta", + "mfa_recovery_hint": "Jeśli stracisz dostęp do swojej aplikacji uwierzytelniającej, możesz pozbawić się możliwości dostępu do konta CryptPad. Kod odzyskiwania może zostać użyty, aby wyłączyć 2FA i przywrócić dostęp.", + "admin_totpRecoveryHint": "Użytkownik może skopiować dane ze strony /recovery/ przywracania 2FA i wysłać je mailem do administratora instancji. Wklej dane przywracania poniżej, aby wyłączyć 2FA dla konta", "admin_totpRecoveryMethod": "Sposób przywrócenia 2FA", "admin_totpDisable": "Wyłącz 2FA dla tego konta", "mfa_recovery_warning": "Ten kod nie zostanie wyświetlony ponownie, zapisz go w bezpiecznym miejscu i nie udostępniaj nikomu.", - "settings_otp_tuto": "Zeskanuj kod QR swoją aplikacją uwierzytelniającą i wpisz kod weryfikacyjny aby potwierdzić.", + "settings_otp_tuto": "Zeskanuj kod QR swoją aplikacją uwierzytelniającą i wpisz kod weryfikacyjny, aby potwierdzić.", "admin_totpFailed": "Weryfikacja podpisu nie udała się", "admin_totpCheck": "Weryfikacja podpisu powiodła się", "admin_totpDisableButton": "Wyłącz", @@ -1671,11 +1671,11 @@ "ssoauth_form_hint_login": "Wprowadź swoje hasło CryptPad", "kanban_showTags": "Wszystkie tagi", "kanban_hideTags": "Mniej tagów", - "admin_forcemfaTitle": "Wymagaj Uwierzytelniania Dwuskładnikowego", - "admin_forcemfaHint": "Wszyscy użytkownicy tej instancji zostaną poproszeni o skonfigurowanie uwierzytelniania dwuskładnikowego, aby zalogować się do swojego konta. Użytkownicy już istniejący również nie będą mogli korzystać ze swojego konta bez konfiguracji aplikacji TOTP.", + "admin_forcemfaTitle": "Wymagaj Uwierzytelniania Dwuetapowego", + "admin_forcemfaHint": "Wszyscy użytkownicy tej instancji zostaną poproszeni o skonfigurowanie uwierzytelniania dwuetapowego, aby zalogować się do swojego konta. Użytkownicy już istniejący również nie będą mogli korzystać ze swojego konta bez konfiguracji aplikacji TOTP.", "support_recordedContent": "Treść", "support_legacyDump": "Wyeksportuj wszystko", - "support_legacyClear": "Usuń to konto", + "support_legacyClear": "Usuń dla tego konta", "mfa_disable": "Wyłącz 2FA", "admin_totpRecoveryTitle": "Przywrócenie 2FA", "mfa_recovery_title": "Zapisz kod odzyskania teraz", @@ -1689,22 +1689,22 @@ "recovery_forgot": "Zapomniany kod odzyskiwania", "recovery_forgot_text": "Skopiuj następujące informacje i prześlij je administratorom swojej instancji", "goRight": "W prawo", - "loading_mfa_required": "Uwierzytelnianie dwuskłądnikowe jest wymagane na tej instancji. Uaktualnij swoje konto z użyciem aplikacji uwierzytelniającej i poniższego formularza.", + "loading_mfa_required": "Uwierzytelnianie dwuetapowe jest wymagane na tej instancji. Uaktualnij swoje konto z użyciem aplikacji uwierzytelniającej i poniższego formularza.", "admin_invitationLink": "Zaproszenie", "admin_registrationSsoTitle": "Zamknij rejestrację SSO", "admin_usersAdd": "Dodaj istniejącego użytkownika", "admin_storeInvitedLabel": "Automatycznie dodaj zaproszonych użytkowników", "admin_usersRemoveConfirm": "Czy na pewno chcesz usunąć użytkownika z katalogu? Nadal będą mieć dostęp do i będą w stanie używać swojego konta.", - "ssoauth_form_hint_register": "Dodaj hasło CryptPad aby zwiększyć poziom zabezpieczenia, lub pozostaw puste i kontynuuj. Jeśli nie dodasz hasła, klucze chroniące Twoje dane będą dostępne dla administratorów instancji.", + "ssoauth_form_hint_register": "Dodaj hasło CryptPad, aby zwiększyć poziom zabezpieczenia, lub pozostaw puste i kontynuuj. Jeśli nie dodasz hasła, klucze chroniące Twoje dane będą dostępne dla administratorów instancji.", "label_viewMode": "Włącz tryb podglądu", "team_nameTooLong": "Zbyt długa nazwa zespołu (maks. 50 znaków)", "context_menu": "Akcje folderów", "admin_accountReport": "Raport archiwizacji konta", "admin_accountReportFull": "Pobierz raport szczegółowy", "admin_channelPlaceholder": "Zastępstwo zniszczonego dokumentu", - "status": "Status strony", + "status": "Strona statusu", "admin_diskUsageWarning": "Uwaga! W zależności od ilości danych przechowywanych w tej instancji, wygenerowanie raportu może pochłonąć całą pamięć dostępną na serwerze i spowodować awarię.", - "calendar_rec_change": "Przenoszenie powtarzającego się wydarzenia do innego kalendarza. Zmiana może zostać zrobiona tylko dla tego wydarzenia, lub dla wszystkich jego powtórzeń.", + "calendar_rec_change": "Przenoszenie powtarzającego się wydarzenia do innego kalendarza. Zmiana może zostać zastosowana tylko dla tego wydarzenia, lub dla wszystkich jego powtórzeń.", "calendar_rec_change_first": "Przenoszenie pierwszego powtarzającego się wydarzenia do innego kalendarza. Powtórzenia również zostaną przeniesione.", "admin_cat_security": "Bezpieczeństwo", "admin_cat_customize": "Dostosuj", @@ -1712,9 +1712,9 @@ "admin_logoTitle": "Własne logo", "admin_supportAdd": "Dodaj kontakt do zespołu pomocy technicznej", "admin_logoHint": "SVG, PNG lub JPG, maksymalny rozmiar 200KB", - "admin_logoButton": "Prześlij nowe", + "admin_logoButton": "Wrzuć nowe", "admin_logoRemoveButton": "Przywróć domyślne", - "admin_colorHint": "Zmień kolor akcentu Twojej instancji CryptPad. Dopilnuj, żeby tekst i przyciski były czytelne i posiadały wystarczający kontrast zarówno dla jasnego jak i ciemnego motywu.", + "admin_colorHint": "Zmień kolor akcentu Twojej instancji CryptPad. Dopilnuj, żeby tekst i przyciski były czytelne i posiadały wystarczający kontrast zarówno dla jasnego, jak i ciemnego motywu.", "admin_colorCurrent": "Obecny kolor akcentu", "admin_colorChange": "Zmień kolor", "admin_colorPick": "Wybierz kolor", @@ -1736,20 +1736,20 @@ "support_active_tag": "Skrzynka odbiorcza", "support_closed_tag": "Zamknięte", "support_privacyTitle": "Odpowiedz anonimowo", - "support_privacyHint": "Zaznacz tę opcję żeby odpowiedzieć jako 'Zespół Pomocy Technicznej' zamiast pod własną nazwą użytkownika", + "support_privacyHint": "Zaznacz tę opcję, żeby odpowiedzieć jako 'Zespół Pomocy Technicznej' zamiast pod własną nazwą użytkownika", "support_notificationsTitle": "Wyłącz powiadomienia", "support_userChannel": "ID kanału powiadomień użytkownika", "support_openTicketHint": "Skopiuj dane użytkownika odbiorcy z ich profilu lub z istniejącego zgłoszenia. Otrzymają powiadomienie o wiadomości.", - "support_recordedId": "ID formatki (unikalny)", + "support_recordedId": "ID fragmentu (unikalny)", "support_userKey": "Klucz publiczny użytkownika", "support_invalChan": "Nieprawidłowy kanał powiadomień", - "support_recordedTitle": "Formatki", + "support_recordedTitle": "Fragmenty", "admin_supportTeamTitle": "Zarządzaj zespołem pomocy technicznej", "admin_supportTeamHint": "Dodaj lub usuń osoby z zespołu pomocy technicznej tej instancji", "support_pasteUserData": "Wklej tu dane użytkownika", "support_legacyButton": "Pokaż aktywne zgłoszenia", "support_recordedHint": "Zdefiniuj często używane fragmenty tekstu, które będzie można potem wstawić do wiadomości jednym kliknięciem.", - "support_recordedEmpty": "Brak formatek", + "support_recordedEmpty": "Brak fragmentów", "support_legacyTitle": "Zobacz stare dane pomocy", "support_searchLabel": "Znajdź (tytuł lub ID zgłoszenia)", "support_legacyHint": "Zobacz zgłoszenia z poprzedniego systemu pomocy technicznej i odtwórz je w nowym.", @@ -1757,10 +1757,10 @@ "admin_supportInit": "Inicjalizuj helpdesk na tej instancji", "moderationPage": "Helpdesk", "support_userNotification": "Nowe zgłoszenie lub odpowiedź: {0}", - "admin_invitationHint": "Każde zaproszenie stworzy jedno konto, nawet jeśli rejestracja jest zamknięta. Nazwa użytkownika i email są wyłącznie poglądowe. CryptPad nie wyśle zaproszenia (czy czegokolwiek innego) mailem, skopiuj link i prześlij go za pomocą wybranego bezpiecznego kanału.", + "admin_invitationHint": "Każde zaproszenie stworzy jedno konto, nawet jeśli rejestracja jest zamknięta. Nazwa użytkownika i email służą wyłącznie do identyfikacji. CryptPad nie wyśle zaproszenia (czy czegokolwiek innego) mailem, skopiuj link i prześlij go za pomocą wybranego bezpiecznego kanału.", "admin_storeSsoLabel": "Automatycznie dodaj użytkowników SSO", "admin_usersBlock": "URL bloku logowania użytkownika (opcjonalne)", - "admin_usersHint": "Lista znanych Ci kont na tej instancji. Zaznacz poniżej aby dodać konta automatycznie, lub wprowadź informacje ręcznie za pomocą formularza.", + "admin_usersHint": "Lista znanych Ci kont na tej instancji. Zaznacz poniżej, aby dodać konta automatycznie, lub wprowadź informacje ręcznie za pomocą formularza.", "admin_supportSetupHint": "Stwórz lub aktualizuj klucze pomocy.", "admin_supportRotateNotify": "Uwaga: nowe klucze zostały wygenerowane, ale nieoczekiwany błąd nie pozwolił systemowi wysłać ich moderatorom. Usuń i dodaj ponownie członków zespołu", "support_notificationsHint": "Zaznacz tę opcję, żeby wyłączyć powiadomienia o nowych zgłoszeniach i odpowiedziach", @@ -1772,7 +1772,7 @@ "install_header": "Instalacja", "install_instance": "Stwórz pierwsze konto administratora, a następnie dostosuj instancję", "install_launch": "Konfiguracja instancji", - "install_notes": "
  • Na tej stronie stworzysz swoje pierwsze konto administratora. Administratorzy zarządzają ustawieniami instancji, w tym przydziałem przestrzeni dyskowej i mają dostęp do narzędzi moderacji.
  • Twoje hasło to tajny klucz, szyfrujący wszystkie Twoje dokumenty i przywileje administratora na tej instancji. Jeśli je zgubisz, nie mamy możliwości odzyskania Twoich danych.
  • Jeśli dzielisz komputer z innymi osobami, pamiętaj, aby się wylogować po zakończeniu pracy. Zamknięcie jedynie okna przeglądarki naraża bezpieczeństwo Twojego konta.
", + "install_notes": "
  • Na tej stronie stworzysz swoje pierwsze konto administratora. Administratorzy zarządzają ustawieniami instancji, w tym przydziałem przestrzeni dyskowej, i mają dostęp do narzędzi moderacji.
  • Twoje hasło to tajny klucz, szyfrujący wszystkie Twoje dokumenty i przywileje administratora na tej instancji. Jeśli je zgubisz, nie mamy możliwości odzyskania Twoich danych.
  • Jeśli dzielisz komputer z innymi osobami, pamiętaj, aby się wylogować po zakończeniu pracy. Zamknięcie jedynie okna przeglądarki naraża bezpieczeństwo Twojego konta.
", "onboarding_upload": "Wybierz logo", "onboarding_save_error": "Niektóre opcje nie zostały prawidłowo zapisane. Odwiedź panel administratora, aby sprawdzić ich wartości.", "admin_onboardingNameHint": "Wybierz tytuł, opis, kolor wiodący i logo (opcjonalnie)", @@ -1781,7 +1781,7 @@ "admin_onboardingNamePlaceholder": "Tytuł instancji", "admin_onboardingDescPlaceholder": "Treść opisu instancji", "admin_onboardingOptionsHint": "Zaznacz właściwą opcję dla Twojej instancji.
To ustawienie może być później zmienione w panelu administratora.", - "team_autoTrim": "Zmniejszanie historii dysku... Proszę czekać.", + "team_autoTrim": "Zmniejszanie historii dysku zespołu... Proszę czekać.", "admin_mfa_confirm_enable": "Czy na pewno chcesz włączyć uwierzytelnianie wieloetapowe?", "admin_mfa_confirm_disable": "Czy na pewno chcesz wyłączyć uwierzytelnianie wieloetapowe?" } From 6fb36bd0769d93a12fdae52b020b1394080409a5 Mon Sep 17 00:00:00 2001 From: Weblate Date: Thu, 7 Nov 2024 10:15:19 +0100 Subject: [PATCH 070/218] Translated using Weblate (French) Currently translated at 100.0% (1783 of 1783 strings) Co-authored-by: Mathilde Translate-URL: https://weblate.cryptpad.org/projects/cryptpad/app/fr/ Translation: CryptPad/App --- www/common/translations/messages.fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/common/translations/messages.fr.json b/www/common/translations/messages.fr.json index 2bcf34bf7..ad219842a 100644 --- a/www/common/translations/messages.fr.json +++ b/www/common/translations/messages.fr.json @@ -1442,8 +1442,8 @@ "admin_uptimeHint": "Date et heure auxquelles le serveur a été démarré", "admin_cat_database": "Base de données", "admin_generatedAt": "Horodatage du rapport", - "ui_true": "activé", - "ui_false": "désactivé", + "ui_true": "vrai", + "ui_false": "faux", "ui_none": "aucun", "ui_generateReport": "Générer un rapport", "ui_success": "Succès", From a27b008f9e8f2880df19222c8c0b22c62304e7c4 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Thu, 7 Nov 2024 13:57:27 +0100 Subject: [PATCH 071/218] Fixed incorrect data.href assignment --- www/common/inner/common-modal.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/www/common/inner/common-modal.js b/www/common/inner/common-modal.js index a7a3de0c0..4b00c0c02 100644 --- a/www/common/inner/common-modal.js +++ b/www/common/inner/common-modal.js @@ -49,7 +49,13 @@ define([ // Access modal and the pad is not stored: get the hashes from outer var hashes = priv.hashes || {}; // For calendars, individual href is passed via opts - data.href = ((priv.app === 'calendar') && opts.href) || Hash.hashToHref(hashes.editHash || hashes.fileHash, priv.app); + if (priv.app === 'calendar') { + data.href = (priv.app === 'calendar') && opts.href; + } else if (hashes.editHash || hashes.fileHash) { + data.href = Hash.hashToHref(hashes.editHash || hashes.fileHash); + } else { + data.href = undefined; + } if (hashes.viewHash) { data.roHref = Hash.hashToHref(hashes.viewHash, priv.app); } From 3de5784be76daba5a9d0135ddf11d816e94fe279 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 7 Nov 2024 16:23:07 +0100 Subject: [PATCH 072/218] Fix issues with temporary documents --- lib/historyKeeper.js | 5 +---- lib/hk-util.js | 13 ++++++++++--- www/common/outer/async-store.js | 3 ++- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/historyKeeper.js b/lib/historyKeeper.js index b8ac2ca58..e0a9fea2c 100644 --- a/lib/historyKeeper.js +++ b/lib/historyKeeper.js @@ -67,10 +67,7 @@ module.exports.create = function (Env, cb) { } if (metadata && metadata.selfdestruct && metadata.selfdestruct !== Env.id) { - if (Env.store) { - Env.store.archiveChannel(channelName, false, - () => {}); - } + HK.removeChannel(Env, channelName); return void cb('ESELFDESTRUCT'); } diff --git a/lib/hk-util.js b/lib/hk-util.js index 069be04a3..2ba656376 100644 --- a/lib/hk-util.js +++ b/lib/hk-util.js @@ -134,6 +134,13 @@ const expireChannel = HK.expireChannel = function (Env, channel) { }); }; +const removeChannel = HK.removeChannel = function (Env, channel) { + if (!Env.store) { return; } + Env.store.archiveChannel(channel, void 0, () => {}); + delete Env.metadata_cache[channel]; + delete Env.channel_cache[channel]; +}; + /* dropChannel * cleans up memory structures which are managed entirely by the historyKeeper */ @@ -143,8 +150,7 @@ const dropChannel = HK.dropChannel = function (Env, chanName) { delete Env.channel_cache[chanName]; if (meta && meta.selfdestruct && Env.selfDestructTo) { Env.selfDestructTo[chanName] = setTimeout(function () { - if (!Env.store) { return; } - Env.store.archiveChannel(chanName, false, () => {}); + removeChannel(Env, chanName); }, TEMPORARY_CHANNEL_LIFETIME); } if (Env.store) { Env.store.closeChannel(chanName, function () {}); } @@ -693,6 +699,7 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) { }, (err, reason) => { // Any error but ENOENT: abort // ENOENT is allowed in case we want to create a new pad + if (err && err.error) { err = err.error; } if (err && err.code !== 'ENOENT') { if (err.message === "EUNKNOWN") { Log.error("HK_GET_HISTORY", { @@ -708,7 +715,7 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) { stack: err && err.stack, }); } // FIXME err.message isn't useful for users - const parsedMsg = {error:err.message, channel: channelName, txid: txid}; + const parsedMsg = {error:err.message || 'ERROR', channel: channelName, txid: txid}; Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(parsedMsg)]); return; } diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 704038b2c..f4568dbc9 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1843,7 +1843,7 @@ define([ Store.leavePad(null, data, function () {}); }; var conf = { - Cache: Cache, // ICE pad cache + Cache: store.neverCache ? undefined : Cache, // ICE pad cache onCacheStart: function () { postMessage(clientId, "PAD_CACHE"); }, @@ -3271,6 +3271,7 @@ define([ // ==> don't create a drive // Or "neverDrive" (integration into another platform?) // ==> don't create a drive BUT create temp RPC (we may need to upload) + if (data.neverDrive) { store.neverCache = true; } if (data.neverDrive || (data.noDrive && !data.userHash && !data.anonHash)) { return void onNoDrive(clientId, function (obj) { if (obj && obj.error) { From f38ae7905b22fcb5b34fb22f552bb448729f760b Mon Sep 17 00:00:00 2001 From: Weblate Date: Sat, 9 Nov 2024 13:40:30 +0100 Subject: [PATCH 073/218] Translated using Weblate (Turkish) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 26.2% (468 of 1783 strings) Translated using Weblate (Turkish) Currently translated at 24.6% (439 of 1783 strings) Co-authored-by: Aliberk Sandıkçı Co-authored-by: Weblate Translate-URL: https://weblate.cryptpad.org/projects/cryptpad/app/tr/ Translation: CryptPad/App --- www/common/translations/messages.tr.json | 309 ++++++++++++++++++++++- 1 file changed, 298 insertions(+), 11 deletions(-) diff --git a/www/common/translations/messages.tr.json b/www/common/translations/messages.tr.json index 16efae457..40c8eccb4 100644 --- a/www/common/translations/messages.tr.json +++ b/www/common/translations/messages.tr.json @@ -11,8 +11,8 @@ "slide": "Markdown Sunumları", "poll": "Anket", "code": "Kod", - "pad": "Zengin metin biçimi", - "doc": "Doküman", + "pad": "Zengin Metin", + "doc": "Belge", "presentation": "Sunum", "diagram": "Diyagram", "sheet": "Sheet", @@ -45,12 +45,12 @@ "MB": "MB", "GB": "GB", "formattedGB": "{0} GB", - "formattedKB": "{0} KB", + "formattedKB": "{0} kB", "importButton": "İçe aktar", "clickToEdit": "Düzenlemek için tıkla", "forgetButton": "Sil", "shareButton": "Paylaş", - "uploadFolderButton": "Klasör yükle", + "uploadFolderButton": "Dizini karşıya yükle", "saveTemplatePrompt": "Şablon için bir başlık seçin", "templateSaved": "Şablon kaydedildi!", "selectTemplate": "Bir şablon seçin veya escape tuşuna basın", @@ -68,13 +68,13 @@ "errorState": "Kritik hata: {0}", "KB": "kB", "formattedMB": "{0} MB", - "typeError": "Bu doküman seçilmiş uygulama ile uyumlu değil", + "typeError": "Bu belge seçilen uygulama ile uyumlu değil", "disconnected": "Bağlantı kesildi", "forgetPrompt": "Tamam'ı tıkladığınızda bu belge çöp kutunuza taşınacaktır. Emin misin?", "uploadButtonTitle": "CryptDrive'ınıza yeni bir dosya yükleyin", "userAccountButton": "Kullanıcı menüsü", "deletedError": "Bu belge silindi ve artık mevcut değil.", - "inactiveError": "Bu belge, işlem yapılmaması nedeniyle silinmiştir. Yeni bir belge oluşturmak için Esc tuşuna basın.", + "inactiveError": "Bu belge, işlem yapılmaması nedeniyle silindi. Yeni bir belge oluşturmak için Esc tuşuna basın.", "chainpadError": "İçeriğiniz güncellenirken kritik bir hata oluştu. Çalışmanızı kaybetmemeniz için bu sayfa salt okunur modundadır.
Bu belgeyi görüntülemeye devam etmek için Esc tuşuna basın veya yeniden düzenlemeyi denemek için yeniden yükleyin.", "errorRedirectToHome": "CryptDrive'ınıza yönlendirilmek için Esc tuşuna basın.", "newVersionError": "CryptPad'in yeni bir sürümü mevcut. Yeni sürümü kullanmak için
yeniden yükleyin veya çevrimdışı modda içeriğinize erişmek için escape tuşuna basın.", @@ -150,7 +150,7 @@ "fm_ownedPadsName": "Sahiplenmiş", "fm_tagsName": "Etiketler", "fm_newButton": "Yeni", - "fm_folder": "Klasör", + "fm_folder": "Dizin", "fm_type": "Tür", "fm_creation": "Oluşturma", "fc_open": "Aç", @@ -189,7 +189,7 @@ "mdToolbar_italic": "İtalik", "mdToolbar_strikethrough": "Üstü çizili", "mdToolbar_heading": "Başlık", - "mdToolbar_link": "Link", + "mdToolbar_link": "Bağlantı", "mdToolbar_quote": "Quote", "mdToolbar_code": "Kod", "about": "Hakkında", @@ -209,12 +209,12 @@ "creation_owners": "Sahipler", "creation_passwordValue": "Parola", "password_submit": "Gönder", - "share_linkCategory": "Link", + "share_linkCategory": "Bağlantı", "share_linkView": "Görüntüle", "share_contactCategory": "Contacts", "share_embedCategory": "Gömülü", "autostore_file": "dosya", - "autostore_sf": "klasör", + "autostore_sf": "dizin", "autostore_pad": "pad", "autostore_store": "Depola", "crowdfunding_button2": "Bağış Yap", @@ -245,5 +245,292 @@ "team_rosterDemote": "Demote", "team_owner": "Sahipler", "team_admins": "Yöneticiler", - "team_members": "Üyeler" + "team_members": "Üyeler", + "broadcast_start": "Başlat", + "broadcast_end": "Bitir", + "kanban_delete": "Sil", + "toolbar_tools": "Araçlar", + "drive_treeButton": "Dosyalar", + "support_cat_other": "Diğer", + "admin_cat_performance": "Performans", + "settings_colortheme_dark": "Koyu", + "ui_ms": "milisaniye", + "form_editor": "Düzenleyici", + "team_inviteFrom": "From:", + "snapshots_delete": "Sil", + "Offline": "Çevrim dışı", + "admin_performanceKeyHeading": "Komut", + "expiredError": "Bu belgenin geçerlilik süresi doldu ve artık mevcut değil.", + "team_viewers": "İzleyiciler", + "oo_refresh": "Yenile", + "access_main": "Erişim", + "calendar_notifications": "Hatırlatıcılar", + "form_poll_day": "Gün", + "form_pollTotal": "Toplam", + "form_text_number": "Sayı", + "form_input_ph_email": "eposta@ornek.com", + "calendar_rec_freq_daily": "gün", + "admin_limitSetNote": "Not", + "tag_edit": "Düzenle", + "form_preview_button": "Ön izleme", + "download_step3": "Dönüştürülüyor...", + "initializing": "Initializing...", + "admin_documentConflict": "Arşiv/geri getir", + "upgradeAccount": "Hesabı yükselt", + "error_incorrectAccess": "Bu sayfaya yalnızca {0} üzerinden erişilebilir.", + "toolbar_preview": "Ön izleme", + "calendar_rec_daily": "Günlük", + "calendar_str_filter": "Filtreler:", + "form_editable_str": "Başvuru", + "admin_cat_network": "Ağ bağlantısı", + "fm_link_type": "Bağlantı", + "ui_archive": "Arşivle", + "admin_totpDisableButton": "Devre dışı bırak", + "printButton": "Yazdır (enter)", + "invalidHashError": "İstediğiniz belgenin bağlantısı geçersiz.", + "deletedFromServer": "Belge yok edildi", + "user_displayName": "Görünen ad", + "printOptions": "Tasarım seçenekleri", + "editShare": "Düzenleme bağlantısı", + "viewShare": "salt-okunur bağlantı", + "okButton": "Tamam (enter)", + "cancelButton": "İptal (esc)", + "show_help_button": "Yardımı göster", + "history_next": "Sonraki sürüm", + "history_prev": "Önceki sürüm", + "history_restoreDone": "Belge geri getirildi", + "pad_mediatagTitle": "Medya-Etiket ayarları", + "pad_mediatagWidth": "Genişlik (px)", + "pad_mediatagHeight": "Yükseklik (px)", + "pad_mediatagRatio": "Oranı koru", + "pad_mediatagOptions": "Resim özellikleri", + "kanban_newBoard": "Yeni Tahta", + "kanban_item": "Öge {0}", + "kanban_todo": "Yapılacaklar", + "kanban_working": "Üzerinden çalışılıyor", + "poll_userPlaceholder": "İsminiz", + "poll_comment_placeholder": "Yorumunuz", + "canvas_delete": "Seçili kısmı sil", + "canvas_opacityLabel": "Opaklık: {0}", + "canvas_widthLabel": "Genişlik: {0}", + "canvas_currentBrush": "Geçerli fırça", + "fm_filesDataName": "Tüm dosyalar", + "ui_more": "Daha fazla", + "fm_newFile": "Yeni belge", + "fm_sharedFolder": "Paylaşılmış dizinler", + "fm_folderName": "Dizin ismi", + "fm_fileName": "Dosya ismi", + "fm_lastAccess": "Son erişim", + "fm_forbidden": "Yasaklı eylem", + "fm_newFolder": "Yeni dizin", + "fm_sharedFolderName": "Paylaşılmış dizinle", + "team_pending": "Davet Edildi", + "team_deleteButton": "Sil", + "team_pendingOwner": "(bekleniyor)", + "teams_table": "Roller", + "teams_table_specific": "İstisnalar", + "teams_table_role": "Rol", + "contacts_mute": "Sessize Al", + "contacts_unmute": "Sesi Aç", + "allow_disabled": "devre dışı", + "access_allow": "Liste", + "accessButton": "Erişim", + "contacts": "Contacts", + "allow_enabled": "etkinleştirildi", + "teams": "Takımlar", + "kanban_title": "Başlık", + "kanban_body": "İçerik", + "kanban_color": "Renk", + "canvas_brush": "Fırça", + "canvas_select": "Seç", + "cba_enable": "Etkinleştir", + "comments_edited": "Düzenlendi", + "comments_submit": "Gönder", + "comments_reply": "Yanıtla", + "comments_resolve": "Çözümle", + "comments_comment": "Yorum", + "fm_sort": "Sırala", + "toolbar_theme": "Tema", + "toolbar_insert": "Insert", + "toolbar_file": "Dosya", + "support_cat_all": "Tüm", + "support_attachments": "Ekler", + "pad_tocHide": "Taslak", + "settings_kanbanTagsAnd": "VE", + "settings_kanbanTagsOr": "VEYA", + "settings_cat_kanban": "Kanban", + "history_restore": "Geri Getir", + "history_close": "Kapat", + "snaphot_title": "Anlık Görüntü Al", + "snapshots_button": "Anlık Görüntüler", + "snapshots_open": "Aç", + "snapshots_restore": "Geri Getir", + "snapshots_close": "Kapat", + "oo_version_latest": "En son", + "oo_version": "Versiyon: ", + "team_exportButton": "İndir", + "tag_add": "Ekle", + "admin_archiveButton": "Arşivle", + "admin_unarchiveButton": "Geri Getir", + "mediatag_saveButton": "Kaydet", + "admin_support_open": "Göster", + "admin_support_collapse": "Daralt", + "docs_link": "Belgeleme (Dokümantasyon)", + "settings_cacheTitle": "Önbellek", + "undo": "Geri Al", + "redo": "Yeniden Yap", + "admin_performanceProfilingTitle": "Performans", + "admin_performancePercentHeading": "Yüzde", + "settings_cat_style": "Görünüm", + "settings_colortheme_light": "Açık", + "pad_settings_hide": "Gizle", + "pad_settings_show": "Göster", + "settings_colortheme_custom": "Özel", + "admin_cat_broadcast": "Broadcast", + "admin_maintenanceTitle": "Bakım", + "admin_surveyTitle": "Anket", + "admin_surveyCancel": "Kaldır", + "admin_broadcastButton": "Gönder", + "broadcast_translations": "Çeviriler", + "footer_roadmap": "Roadmap", + "calendar_before": "önceki", + "calendar": "Takvim", + "calendar_day": "Gün", + "calendar_week": "Hafta", + "calendar_month": "Ay", + "calendar_today": "Bugün", + "calendar_update": "Güncelle", + "calendar_title": "Başlık", + "calendar_loc": "Konum", + "form_required_answer": "Cevap: ", + "form_required_on": "Gerekli", + "form_required_off": "Opsiyonel", + "admin_usersRemove": "Kaldır", + "calendar_minutes": "Dakika", + "calendar_hours": "Saat", + "calendar_days": "Gün", + "calendar_noNotification": "None", + "mediatag_defaultImageName": "resim", + "share_formEdit": "Yazar", + "share_formAuditor": "Denetçi", + "share_formView": "Katılımcı", + "form_editBlock": "Düzenle", + "form_poll_time": "Zaman", + "form_text_url": "Bağlantı", + "form_text_text": "Metin", + "form_poll_text": "Metin", + "form_text_email": "E-posta", + "form_type_input": "Metin", + "form_type_textarea": "Paragraf", + "form_type_radio": "Seçim", + "form_type_checkbox": "Checkbox", + "form_type_poll": "Anket", + "form_type_md": "Açıklama", + "form_submit": "Gönder", + "form_update": "Güncelle", + "form_reset": "Sıfırla", + "form_delete": "Sil", + "form_viewButton": "Görüntüle", + "form_backButton": "Geri", + "form_input_ph_url": "https://ornek.com", + "form_open": "Aç", + "form_anonymous_on": "İzin verildi", + "form_anonymous_off": "Blocked", + "form_clear": "Temizle", + "fm_link_url": "Bağlantı", + "ui_collapse": "Daralt", + "ui_expand": "Genişlet", + "form_condition_is": "dır", + "form_condition_has": "sahiptir", + "admin_archiveNote": "Not", + "support_cat_document": "Belge", + "admin_noticeTitle": "Ana sayfa bildirimi", + "home_morestorage": "Daha fazla depolama alanı için:", + "admin_cat_database": "Veri tabanı", + "ui_true": "doğru", + "ui_false": "yanlış", + "ui_undefined": "bilinmiyor", + "ui_none": "none", + "ui_success": "Başarılı", + "ui_restore": "Geri Getir", + "ui_fetch": "Fetch", + "ui_confirm": "Onayla", + "admin_documentCreationTime": "Oluşturuldu", + "admin_channelAvailable": "Uygun", + "admin_channelArchived": "Arşivlendi", + "fm_filterBy": "Filtre", + "calendar_rec": "Tekrarla", + "calendar_rec_custom": "Özel", + "calendar_rec_freq_weekly": "hafta", + "calendar_rec_freq_monthly": "ay", + "calendar_rec_freq_yearly": "yıl", + "calendar_rec_until_no": "Asla", + "calendar_rec_until_count": "Sonra", + "calendar_rec_until_count2": "kere", + "calendar_nth_1": "ilk", + "calendar_nth_2": "ikinci", + "calendar_nth_3": "üçüncü", + "calendar_nth_4": "dördüncü", + "calendar_nth_last": "en son", + "calendar_nth_5": "beşinci", + "form_condorcetSchulze": "Schulze", + "form_showCondorcetWinner": "kazanan: ", + "form_showDetails": "Detaylar", + "form_type_date": "Tarih", + "done": "Tamamlandı", + "continue": "Devam et", + "goLeft": "Sol", + "goRight": "Sağ", + "date": "Tarih", + "calendar_desc": "Açıklama", + "calendar_description": "Açıklama:{0}{1}", + "duplicate": "Kopya", + "admin_cat_security": "Güvenlik", + "admin_cat_customize": "Özelleştir", + "support_cat_open": "Gelen kutusu", + "support_cat_search": "Ara", + "support_cat_closed": "Kapatıldı", + "support_cat_settings": "Ayarlar", + "support_cat_legacy": "Eskiden kalma", + "support_pending_tag": "Arşivlendi", + "support_active_tag": "Gelen Kutusu", + "support_closed_tag": "Kapatıldı", + "support_recordedTitle": "Kod parçacıkları", + "support_recordedContent": "İçerik", + "moderationPage": "Yardım-masası", + "install_header": "Yükleme", + "admin_cat_apps": "Uygulamalar", + "fm_noname": "Başlıksız Belge", + "fm_originalPath": "Orijinal yol", + "fm_viewListButton": "Liste görünümü", + "fm_viewGridButton": "Tablo görünümü", + "fm_tags_name": "Etiket ismi", + "fm_passwordProtected": "Parola korundu", + "fc_newfolder": "Yeni dizin", + "fc_color": "Rengi değiştir", + "fc_open_ro": "Aç (salt-okunur)", + "fc_expandAll": "Hepsini genişlet", + "fc_collapseAll": "Hepsini daralt", + "login_login": "Giriş", + "login_register": "Kayıt ol", + "logoutButton": "Çıkış", + "login_invalUser": "Kullanıcı adı gerekli", + "login_invalPass": "Parola gerekli", + "settings_cat_pad": "Zengin metin", + "settings_export_compressing": "Veri sıkıştırılıyor...", + "settings_exportError": "Hataları görüntüle", + "settings_resetNewTitle": "CryptDrive'ı Temizle", + "settings_importDone": "İçeri aktarma tamamlandı", + "settings_deleteTitle": "Hesap silme", + "settings_logoutEverywhereButton": "Çıkış", + "settings_driveDuplicateLabel": "Kopyaları gizle", + "settings_ownDriveTitle": "Hesabı Güncelle", + "settings_changePasswordButton": "Parola değiştir", + "settings_changePasswordCurrent": "Şuanki parola", + "settings_changePasswordNew": "Yeni parola", + "settings_cursorColorTitle": "İmleç rengi", + "settings_cursorShowLabel": "İmleçleri göster", + "upload_title": "Dosya yükleme", + "upload_modal_owner": "Sahip olunan dosya", + "uploadFolder_modal_filesPassword": "Dosya parolası" } From 9e5fa44d2a853f2d35af04d18714d5d1cc30eb04 Mon Sep 17 00:00:00 2001 From: Weblate Date: Sat, 9 Nov 2024 13:40:30 +0100 Subject: [PATCH 074/218] Translated using Weblate (Italian) Currently translated at 99.3% (1771 of 1783 strings) Co-authored-by: Viki Halwick Translate-URL: https://weblate.cryptpad.org/projects/cryptpad/app/it/ Translation: CryptPad/App --- www/common/translations/messages.it.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/www/common/translations/messages.it.json b/www/common/translations/messages.it.json index a50327151..9aab3c535 100644 --- a/www/common/translations/messages.it.json +++ b/www/common/translations/messages.it.json @@ -648,7 +648,7 @@ "contact_dev": "Contatta la quadra di sviluppo", "contact_admin": "Contatta gli amministratori e le amministratrici per: {0}", "footer_donate": "Dona", - "admin_registeredTitle": "Utenti registrati", + "admin_registeredTitle": "Drive di utenti e gruppi", "admin_activePadsTitle": "Documenti attivi", "admin_activeSessionsTitle": "Connessioni attive", "adminPage": "Amministrazione", @@ -843,7 +843,7 @@ "support_disabledHint": "Questa istanza di CryptPad non è ancora configurata per utilizzare un modulo di assistenza.", "sharedFolders_share": "Condividi questo link con altri utenti registrati o altre utenti registrate per dare loro accesso alla cartella condivisa. Una volta che aprono questo link, la cartella condivisa sarà aggiunta al loro CryptDrive.", "autostore_notAvailable": "Devi salvare questo documento nel tuo CryptDrive prima di poter utilizzare questa funzionalità.", - "admin_registeredHint": "Numero di utenti registrati/e nella tua istanza", + "admin_registeredHint": "Numero di drive attivi sulla tua istanza", "admin_updateLimitDone": "Aggiornamento completato con successo", "requestEdit_button": "Richiedi i diritti di modifica", "requestEdit_accepted": "{1} ti ha permesso di modificare il documento {0}", @@ -1768,5 +1768,8 @@ "admin_appsHint": "Scegli le app da abilitare su questa istanza.", "admin_cat_apps": "ApplicazIoni", "admin_onboardingOptionsTitle": "Opzioni dell’istanza", - "admin_onboardingOptionsHint": "Scegli l’opzione appropriata per la tua istanza.
Queste configurazioni possono essere cambiate successivamente nel pannello di amministrazione." + "admin_onboardingOptionsHint": "Scegli l’opzione appropriata per la tua istanza.
Queste configurazioni possono essere cambiate successivamente nel pannello di amministrazione.", + "team_autoTrim": "Eliminazione della cronologia del drive del gruppo... Si prega di attendere.", + "admin_mfa_confirm_enable": "Sei sicuro/a di voler attivare l'autenticazione a più fattori?", + "admin_mfa_confirm_disable": "Sei sicuro/a di voler disattivare l'autenticazione a più fattori?" } From b4af2bba382478e137275dff1500f2d137fb949f Mon Sep 17 00:00:00 2001 From: Weblate Date: Sat, 9 Nov 2024 13:40:30 +0100 Subject: [PATCH 075/218] Translated using Weblate (Bulgarian) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 30.1% (538 of 1783 strings) Co-authored-by: Мария Рангелова Translate-URL: https://weblate.cryptpad.org/projects/cryptpad/app/bg/ Translation: CryptPad/App --- www/common/translations/messages.bg.json | 27 +++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/www/common/translations/messages.bg.json b/www/common/translations/messages.bg.json index cfea66f8e..28bc9f1b1 100644 --- a/www/common/translations/messages.bg.json +++ b/www/common/translations/messages.bg.json @@ -513,5 +513,30 @@ "features_f_core_note": "Редактиране, импортиране и експортиране, история, потребителски списък, чат", "mdToolbar_toc": "Съдържание", "mdToolbar_link": "Връзка", - "main_catch_phrase": "Пакет за сътрудничество
криптиран от край до край и с отворен код" + "main_catch_phrase": "Пакет за сътрудничество
криптиран от край до край и с отворен код", + "features_f_file0_note": "Преглеждане и изтегляне на документи, споделени от другите потребители", + "features_f_cryptdrive0": "Ограничен достъп до CryptDrive", + "features_f_cryptdrive0_note": "Възможност за съхраняване на последно използваните документи във вашия браузър, за да можете да ги отворите по-късно", + "features_f_storage0": "Ограничено време за съхранение", + "features_f_storage0_note": "Документите се изтриват след {0} дни неактивност", + "features_f_anon": "Всички потребителски функции за гости", + "features_f_anon_note": "С допълнителна функционалност", + "features_f_cryptdrive1": "Пълна функционалност на CryptDrive", + "features_f_cryptdrive1_note": "Папки, споделени папки, шаблони, тагове", + "features_f_devices": "Вашите документи във всичките ви устройства", + "features_f_devices_note": "Достъп до вашия CryptDrive отвсякъде чрез акаунта ви", + "features_f_social": "Социални характеристики", + "features_f_social_note": "Добавете контакти за сигурно сътрудничество, създайте профил, прецизни контроли за достъп", + "features_f_file1": "Качване и споделяне на файлове", + "features_f_file1_note": "Съхранявайте файлове във вашия CryptDrive: изображения, PDF файлове, видеоклипове и др. Споделете ги с вашите контакти или ги вградете във вашите документи. (до {0}MB)", + "features_f_storage1": "Лично хранилище ({0})", + "features_f_storage1_note": "Документите, съхранявани във вашия CryptDrive, не се изтриват при неактивност", + "features_f_register": "Безплатно регистриране", + "features_f_reg": "Всички регистрирани потребителски функции", + "features_f_reg_note": "С допълнителни предимства", + "features_f_storage2": "Допълнително място за съхранение", + "features_f_storage2_note": "От 5 GB на 50 GB в зависимост от плана, увеличен лимит от {0} MB за качване на файлове", + "features_f_support": "По-бърза поддръжка", + "features_f_support_note": "Приоритетен отговор от административния екип чрез имейл и вградена билетна система", + "features_f_supporter": "Поверителност при поддръжка" } From 0072cb9324fd5bbd4bfaac48b549abe5b0d04bef Mon Sep 17 00:00:00 2001 From: daria Date: Wed, 13 Nov 2024 14:30:50 +0200 Subject: [PATCH 076/218] fix hover style for notifications #1525 remove `:has` selector --- customize.dist/src/less2/include/dropdown.less | 14 ++++++++------ www/common/sframe-common-mailbox.js | 4 +++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/customize.dist/src/less2/include/dropdown.less b/customize.dist/src/less2/include/dropdown.less index baaed29a4..ef9cd374c 100644 --- a/customize.dist/src/less2/include/dropdown.less +++ b/customize.dist/src/less2/include/dropdown.less @@ -164,6 +164,14 @@ &:focus-visible { outline-color: @cp_dropdown-fg; } + &:hover { + background-color: @cp_dropdown-bg-hover; + } + } + li[role="menuitem"].cp-notification-avatar { + &:hover { + background-color: transparent; + } .cp-avatar:hover { background-color: @cp_dropdown-bg-hover; } @@ -173,12 +181,6 @@ } } - li[role="menuitem"]:not(:has(.cp-avatar)) { - &:hover { - background-color: @cp_dropdown-bg-hover; - } - } - &> span { box-sizing: border-box; height: 26px; diff --git a/www/common/sframe-common-mailbox.js b/www/common/sframe-common-mailbox.js index 857b46bbe..bf05bdd18 100644 --- a/www/common/sframe-common-mailbox.js +++ b/www/common/sframe-common-mailbox.js @@ -105,7 +105,9 @@ define([ h('p', data.content.msg.type + ' - ' +formatData(data)) ]) ]); - + if ($(notif).find('.cp-avatar').length) { + $(notif).addClass('cp-notification-avatar'); + } if (typeof(data.content.getFormatText) === "function") { $(notif).find('.cp-notification-content p').html(data.content.getFormatText()); if (data.content.autorefresh) { From d4771050a45d59bf4bf504dc602969388eca658f Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Thu, 21 Nov 2024 10:29:15 +0100 Subject: [PATCH 077/218] Added table formatting for Pad docs exported as .md --- www/pad/export.js | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/www/pad/export.js b/www/pad/export.js index 6e9de71c1..0370e1b71 100644 --- a/www/pad/export.js +++ b/www/pad/export.js @@ -96,7 +96,29 @@ define([ if (ext === ".md") { var md = Turndown({ headingStyle: 'atx' - }).turndown(toExport); + }).addRule('table', { + filter: ['tr'], + replacement: function (content, node) { + var indexOf = Array.prototype.indexOf; + var index = indexOf.call(node.parentNode.childNodes, node); + var rowContent = node.innerHTML.replace(/|<\/td>/g, '').split('
') + rowContent[0] = `|${rowContent[0]}` + var row = '' + var rowLength = rowContent.filter(Boolean).length + for (var i =0; i < rowLength; i++) { + var cell = rowContent[i] + cell += ' |' + row += cell + } + var newRow = row.concat('\n') + if (index === 0) { + var separator = '|-' + newRow += `${separator.repeat(rowLength)}\n` + } + return newRow + }}) + .turndown(toExport); + console.log(md) var mdBlob = new Blob([md], { type: 'text/markdown;charset=utf-8' }); From fac385c7c259584cdfaa972bdf4edf0234cfde9c Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Fri, 22 Nov 2024 11:50:42 +0100 Subject: [PATCH 078/218] Add calendar reminders to notification panel --- www/notifications/inner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/notifications/inner.js b/www/notifications/inner.js index 874a88b15..0e956c182 100644 --- a/www/notifications/inner.js +++ b/www/notifications/inner.js @@ -143,7 +143,7 @@ define([ $(loadmore).click(); } - common.mailbox.subscribe(["notifications"], { + common.mailbox.subscribe(["notifications", "reminders"], { onMessage: function (data, el) { addNotification(data, el); }, From 9cc2569078a21ba2b5bb85638515b6d1bd405ebe Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Fri, 22 Nov 2024 13:14:26 +0100 Subject: [PATCH 079/218] Added strikethrough formatting support and linting --- www/pad/export.js | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/www/pad/export.js b/www/pad/export.js index 0370e1b71..1225bc918 100644 --- a/www/pad/export.js +++ b/www/pad/export.js @@ -101,24 +101,27 @@ define([ replacement: function (content, node) { var indexOf = Array.prototype.indexOf; var index = indexOf.call(node.parentNode.childNodes, node); - var rowContent = node.innerHTML.replace(/|<\/td>/g, '').split('
') - rowContent[0] = `|${rowContent[0]}` - var row = '' - var rowLength = rowContent.filter(Boolean).length + var rowContent = node.innerHTML.replace(/|<\/td>/g, '').split('
'); + rowContent[0] = `|${rowContent[0]}`; + var row = ''; + var rowLength = rowContent.filter(Boolean).length; for (var i =0; i < rowLength; i++) { - var cell = rowContent[i] - cell += ' |' - row += cell + var cell = rowContent[i] + ' |'; + row += cell; } - var newRow = row.concat('\n') + var newRow = row.concat('\n'); if (index === 0) { - var separator = '|-' - newRow += `${separator.repeat(rowLength)}\n` + var separator = '|-'; + newRow += `${separator.repeat(rowLength)}\n`; } - return newRow - }}) + return newRow; + }}).addRule('strikethrough', { + filter: ['s', 'del', 'strike'], + replacement: function (content) { + return '~' + content + '~'; + } + }) .turndown(toExport); - console.log(md) var mdBlob = new Blob([md], { type: 'text/markdown;charset=utf-8' }); From 406f5d6e22046ed610fdac829ee15220ff7de866 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Fri, 22 Nov 2024 15:54:36 +0100 Subject: [PATCH 080/218] Fixed layout of reminder icon --- www/notifications/inner.js | 1 + 1 file changed, 1 insertion(+) diff --git a/www/notifications/inner.js b/www/notifications/inner.js index 0e956c182..fc3206ee6 100644 --- a/www/notifications/inner.js +++ b/www/notifications/inner.js @@ -145,6 +145,7 @@ define([ common.mailbox.subscribe(["notifications", "reminders"], { onMessage: function (data, el) { + el.innerHTML = el.innerHTML.replace('') addNotification(data, el); }, onViewed: function (data) { From 63e3fbd7d45c4644527e194ae396d447d6a02648 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Fri, 22 Nov 2024 15:57:39 +0100 Subject: [PATCH 081/218] Linting --- www/notifications/inner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/notifications/inner.js b/www/notifications/inner.js index fc3206ee6..d927f7de9 100644 --- a/www/notifications/inner.js +++ b/www/notifications/inner.js @@ -145,7 +145,7 @@ define([ common.mailbox.subscribe(["notifications", "reminders"], { onMessage: function (data, el) { - el.innerHTML = el.innerHTML.replace('') + el.innerHTML = el.innerHTML.replace(''); addNotification(data, el); }, onViewed: function (data) { From 6533d7f0daa08da70344fdf8ae136ef3e71b3446 Mon Sep 17 00:00:00 2001 From: Fabrice Mouhartem Date: Tue, 26 Nov 2024 17:47:54 +0100 Subject: [PATCH 082/218] =?UTF-8?q?Add=20title=20for=20=E2=80=9Cquick=20sc?= =?UTF-8?q?heduling=20poll=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix #1718 --- www/form/templates.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/www/form/templates.js b/www/form/templates.js index 0bc17d352..631246983 100644 --- a/www/form/templates.js +++ b/www/form/templates.js @@ -44,7 +44,10 @@ define([ } } }, - order: ["1", "2"] + order: ["1", "2"], + metadata: { + title: Messages.form_template_poll + } } }]; }); From a854b622ed201ac887978fc7036846978fa0ba2a Mon Sep 17 00:00:00 2001 From: Fabrice Mouhartem Date: Thu, 28 Nov 2024 10:50:38 +0100 Subject: [PATCH 083/218] Fix missing submit message when cloning forms (#1716) --- www/common/cryptpad-common.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index e2d6a3746..b365693c1 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -928,13 +928,15 @@ define([ delete meta.cursor; if (meta.type === "form") { - // Keep anonymous and makeAnonymous values from templates + // Keep anonymous, makeAnonymous and submit message values from templates var anonymous = parsed.answers.anonymous || false; var makeAnonymous = parsed.answers.makeAnonymous || false; + var msg = parsed.answers.msg || undefined; delete parsed.answers; parsed.answers = { anonymous: anonymous, - makeAnonymous: makeAnonymous + makeAnonymous: makeAnonymous, + msg: msg }; } } From 2c1554cf49ad788cca778bb89ff2f5b19449316a Mon Sep 17 00:00:00 2001 From: DianaXWiki <139217939+DianaXWiki@users.noreply.github.com> Date: Sun, 1 Dec 2024 15:46:26 +0200 Subject: [PATCH 084/218] Fix accessibility focus issue --- www/calendar/inner.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index 40ffc1e47..4daf2f30f 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -796,8 +796,8 @@ define([ h('i.cp-calendar-inactive.fa.fa-calendar-o') ]), h('span.cp-calendar-title', md.title), - data.restricted ? h('i.fa.fa-ban', {title: Messages.fm_restricted}, {'aria-hidden': 'true'}) : - (isReadOnly(id, teamId) ? h('i.fa.fa-eye', {title: Messages.readonly}) : undefined, {'aria-hidden': 'true'}), + data.restricted ? h('i.fa.fa-ban', {title: Messages.fm_restricted, 'aria-hidden': 'true'}) : + (isReadOnly(id, teamId) ? h('i.fa.fa-eye', {title: Messages.readonly, 'aria-hidden': 'true'}) : undefined), edit ]); var $calendar = $(calendar).click(function () { @@ -906,7 +906,7 @@ define([ var iconClass = visible ? 'fa-eye-slash' : 'fa-eye'; var buttonText = visible ? Messages.calendar_hide : Messages.calendar_show; var showCalendarsBtn = h('button', [ - h('i.fa.' + iconClass, {'aria-hidden': 'true'}), + h('i.fa.' + iconClass, {'aria-hidden': "true"}), h('span.cp-calendar-title', buttonText), h('span') ]); @@ -916,7 +916,7 @@ define([ $contentContainer.toggle(visible); iconClass = visible ? 'fa-eye-slash' : 'fa-eye'; buttonText = visible ? Messages.calendar_hide : Messages.calendar_show; - $(showCalendarsBtn).find('i').attr('class', 'fa ' + iconClass); + $(showCalendarsBtn).find('i').attr('class', 'fa ' + iconClass).attr('aria-hidden', "true"); $(showCalendarsBtn).find('span').first().text(visible ? Messages.calendar_hide : Messages.calendar_show); }).appendTo($showContainer); } From ced24e655beb511fae94d8047fa3559cd202dc9a Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 3 Dec 2024 16:34:10 +0100 Subject: [PATCH 085/218] Fix API issues --- lib/hk-util.js | 2 +- www/common/outer/async-store.js | 2 +- www/cryptpad-api.js | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/hk-util.js b/lib/hk-util.js index 2ba656376..61374d7a4 100644 --- a/lib/hk-util.js +++ b/lib/hk-util.js @@ -135,7 +135,7 @@ const expireChannel = HK.expireChannel = function (Env, channel) { }; const removeChannel = HK.removeChannel = function (Env, channel) { - if (!Env.store) { return; } + if (!Env.store) { return; } Env.store.archiveChannel(channel, void 0, () => {}); delete Env.metadata_cache[channel]; delete Env.channel_cache[channel]; diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index f4568dbc9..6e37a97c2 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1843,7 +1843,7 @@ define([ Store.leavePad(null, data, function () {}); }; var conf = { - Cache: store.neverCache ? undefined : Cache, // ICE pad cache + Cache: store.neverCache ? undefined : Cache, onCacheStart: function () { postMessage(clientId, "PAD_CACHE"); }, diff --git a/www/cryptpad-api.js b/www/cryptpad-api.js index 6fd6d3be4..c398cdb54 100644 --- a/www/cryptpad-api.js +++ b/www/cryptpad-api.js @@ -113,7 +113,7 @@ }; var start = function () { - config.document.key = key; + //config.document.key = key; chan.send('START', { key: key, application: config.documentType, @@ -137,6 +137,7 @@ blob = config.document.blob; return start(); } + // XXX Nextcloud will log us out if we try from the client getBlob(function (err, _blob) { if (err) { // Can't get blob from client, try from server console.warn(err); From 59ce5936330c2168adbab6e871142a9e92e71683 Mon Sep 17 00:00:00 2001 From: daria Date: Wed, 4 Dec 2024 12:47:40 +0200 Subject: [PATCH 086/218] clean unnecessary changes --- customize.dist/src/less2/include/dropdown.less | 2 -- www/common/toolbar.js | 1 - 2 files changed, 3 deletions(-) diff --git a/customize.dist/src/less2/include/dropdown.less b/customize.dist/src/less2/include/dropdown.less index ef9cd374c..00d1d35ed 100644 --- a/customize.dist/src/less2/include/dropdown.less +++ b/customize.dist/src/less2/include/dropdown.less @@ -157,7 +157,6 @@ } } } - li[role="menuitem"] { border-radius: @variables_radius; white-space: nowrap; @@ -180,7 +179,6 @@ background-color: @cp_dropdown-bg-hover; } } - &> span { box-sizing: border-box; height: 26px; diff --git a/www/common/toolbar.js b/www/common/toolbar.js index c01fa9f45..d86b2abb7 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -1201,7 +1201,6 @@ MessengerUI, Messages, Pages, PadTypes) { setTimeout(function () { $(el).find('.cp-notification-content').click(); }, 0); - }); refresh(); }, From 5ee473ee3aeab62618dfd19e056a63ff2d7f4b8c Mon Sep 17 00:00:00 2001 From: DianaXWiki <139217939+DianaXWiki@users.noreply.github.com> Date: Wed, 4 Dec 2024 17:08:48 +0200 Subject: [PATCH 087/218] Delete unnecessary refactoring --- www/calendar/inner.js | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index 4daf2f30f..21d662656 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -818,7 +818,7 @@ define([ }); } if (APP.$calendars) { APP.$calendars.append(calendar); } - return $calendar; // return jQuery element + return calendar; // return jQuery element }; var makeLeftside = function (calendar, $container) { @@ -834,16 +834,17 @@ define([ var filter = (teamId) => { var LOOKUP = {}; - return Object.keys(APP.calendars || {}).filter((id) => { + return Object.keys(APP.calendars || {}).filter(function(id) { var cal = APP.calendars[id] || {}; - var teams = (cal.teams || []).map((tId) => Number(tId)); - return teams.indexOf(typeof (teamId) !== "undefined" ? Number(teamId) : 1) !== -1; - }).map((k) => { + var teams = (cal.teams || []).map(function (tId) { return Number(tId); }); + return teams.indexOf(typeof(teamId) !== "undefined" ? Number(teamId) : 1) !== -1; + }).map(function (k) { + // nearly constant-time pre-sort var cal = APP.calendars[k] || {}; var title = Util.find(cal, ['content', 'metadata', 'title']) || ''; LOOKUP[k] = title; return k; - }).sort((a, b) => { + }).sort(function(a, b) { var t1 = LOOKUP[a]; var t2 = LOOKUP[b]; return t1 > t2 ? 1 : (t1 === t2 ? 0 : -1); @@ -862,10 +863,10 @@ define([ var avatar = h('span.cp-avatar'); var uid = user.uid; var name = user.name || Messages.anonymous; - common.displayAvatar($(avatar), user.avatar, name, () => {}, uid); + common.displayAvatar($(avatar), user.avatar, name, function(){}, uid); $contentContainer.append(h('div.cp-calendar-team', [ avatar, - h('span.cp-name', { title: name }, name) + h('span.cp-name', {title: name}, name) ])); myCalendars.forEach((id) => { var calendarEntry = makeCalendarEntry(id, 1); @@ -891,7 +892,7 @@ define([ common.displayAvatar($(avatar), team.avatar, team.displayName || team.name); var $teamContainer = h('div.cp-calendar-team', [ avatar, - h('span.cp-name', { title: team.name }, team.name), + h('span.cp-name', {title: team.name}, team.name), h('span') ]); $contentContainer.append($teamContainer); @@ -901,15 +902,15 @@ define([ }); }); if(isMobileView) { - if (totalCalendars > 2) { - var $showContainer = $(h('div.cp-calendar-entry.cp-ghost')).appendTo($calendars); - var iconClass = visible ? 'fa-eye-slash' : 'fa-eye'; - var buttonText = visible ? Messages.calendar_hide : Messages.calendar_show; - var showCalendarsBtn = h('button', [ - h('i.fa.' + iconClass, {'aria-hidden': "true"}), - h('span.cp-calendar-title', buttonText), - h('span') - ]); + if (totalCalendars > 2) { + var $showContainer = $(h('div.cp-calendar-entry.cp-ghost')).appendTo($calendars); + var iconClass = visible ? 'fa-eye-slash' : 'fa-eye'; + var buttonText = visible ? Messages.calendar_hide : Messages.calendar_show; + var showCalendarsBtn = h('button', [ + h('i.fa.' + iconClass, {'aria-hidden': "true"}), + h('span.cp-calendar-title', buttonText), + h('span') + ]); $(showCalendarsBtn).click(() => { visible = !visible; From 80c97b09e591ec341a46aa34ff24fa99acd1b4f6 Mon Sep 17 00:00:00 2001 From: DianaXWiki <139217939+DianaXWiki@users.noreply.github.com> Date: Wed, 4 Dec 2024 17:14:21 +0200 Subject: [PATCH 088/218] Add the temp calendars logic --- www/calendar/inner.js | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index 21d662656..c2928d8a4 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -818,7 +818,7 @@ define([ }); } if (APP.$calendars) { APP.$calendars.append(calendar); } - return calendar; // return jQuery element + return calendar; }; var makeLeftside = function (calendar, $container) { @@ -832,9 +832,9 @@ define([ $calendars.empty(); var privateData = metadataMgr.getPrivateData(); - var filter = (teamId) => { + var filter = function (teamId) { var LOOKUP = {}; - return Object.keys(APP.calendars || {}).filter(function(id) { + return Object.keys(APP.calendars || {}).filter(function (id) { var cal = APP.calendars[id] || {}; var teams = (cal.teams || []).map(function (tId) { return Number(tId); }); return teams.indexOf(typeof(teamId) !== "undefined" ? Number(teamId) : 1) !== -1; @@ -844,12 +844,39 @@ define([ var title = Util.find(cal, ['content', 'metadata', 'title']) || ''; LOOKUP[k] = title; return k; - }).sort(function(a, b) { + }).sort(function (a, b) { var t1 = LOOKUP[a]; var t2 = LOOKUP[b]; return t1 > t2 ? 1 : (t1 === t2 ? 0 : -1); }); }; + var tempCalendars = filter(0); + if (tempCalendars.length && tempCalendars[0] === APP.currentCalendar) { + APP.$calendars.append(h('div.cp-calendar-team', [ + h('span', Messages.calendar_tempCalendar) + ])); + makeCalendarEntry(tempCalendars[0], 0); + var importTemp = h('button', [ + h('i.fa.fa-calendar-plus-o'), + h('span', Messages.calendar_import_temp), + h('span') + ]); + $(importTemp).click(function () { + importCalendar({ + id: tempCalendars[0], + teamId: 0 + }, function (err) { + if (err) { + console.error(err); + return void UI.warn(Messages.error); + } + }); + }); + if (APP.loggedIn) { + APP.$calendars.append(h('div.cp-calendar-entry.cp-ghost', importTemp)); + } + return; + } var myCalendars = filter(1); var totalCalendars = myCalendars.length + Object.keys(privateData.teams).reduce((sum, teamId) => { From c0f9c54d8caa87610231ffe8fafe8eb60923dc57 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Wed, 4 Dec 2024 21:31:54 +0100 Subject: [PATCH 089/218] DOM manipulation --- www/notifications/inner.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/notifications/inner.js b/www/notifications/inner.js index d927f7de9..ca93f5d17 100644 --- a/www/notifications/inner.js +++ b/www/notifications/inner.js @@ -86,6 +86,8 @@ define([ var addNotification = function (data, el) { // if the type of notification correspond if (filterTypes.indexOf(data.content.msg.type) !== -1) { + var icon = $(".cp-reminder"); + $(icon).wrap('').wrap('') notifsData.push(data); $(notifsList).prepend(el); } @@ -145,7 +147,6 @@ define([ common.mailbox.subscribe(["notifications", "reminders"], { onMessage: function (data, el) { - el.innerHTML = el.innerHTML.replace(''); addNotification(data, el); }, onViewed: function (data) { From 23c82d4e4fae679b20e42b13ece30daeaaedb432 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Thu, 5 Dec 2024 08:51:45 +0100 Subject: [PATCH 090/218] Added reminder icon .less style --- www/notifications/app-notifications.less | 9 ++++++--- www/notifications/inner.js | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/www/notifications/app-notifications.less b/www/notifications/app-notifications.less index 8bf0eccbe..b3dcf439c 100644 --- a/www/notifications/app-notifications.less +++ b/www/notifications/app-notifications.less @@ -78,6 +78,7 @@ justify-content: flex-start; align-items: center; background-color: @cp_notif-bg; + border-top: 1px solid @cp_notif-table-border; &.no-notifications { display: none; padding: 1rem 1rem; @@ -95,12 +96,14 @@ background-color: @cp_notif-hover; } } + .cp-avatar-calendar { + font-size: 45px; + padding: 0 12px; + overflow: hidden; + } &.cp-app-notification-archived { background-color: @cp_notif-bg; } - &:not(:first-child) { - border-top: 1px solid @cp_notif-table-border; - } &.dismissed { display: none; } diff --git a/www/notifications/inner.js b/www/notifications/inner.js index ca93f5d17..8489e3809 100644 --- a/www/notifications/inner.js +++ b/www/notifications/inner.js @@ -86,9 +86,9 @@ define([ var addNotification = function (data, el) { // if the type of notification correspond if (filterTypes.indexOf(data.content.msg.type) !== -1) { - var icon = $(".cp-reminder"); - $(icon).wrap('').wrap('') notifsData.push(data); + var icon = $(el).find(".cp-reminder"); + $(icon).addClass('cp-avatar-calendar') $(notifsList).prepend(el); } }; From 9ed99afc85189525691718f228295300fe9805ea Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Thu, 5 Dec 2024 09:51:16 +0100 Subject: [PATCH 091/218] Parsing out HTML tags on Firefox --- www/pad/export.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/www/pad/export.js b/www/pad/export.js index 1225bc918..9fd2bcaaa 100644 --- a/www/pad/export.js +++ b/www/pad/export.js @@ -101,7 +101,7 @@ define([ replacement: function (content, node) { var indexOf = Array.prototype.indexOf; var index = indexOf.call(node.parentNode.childNodes, node); - var rowContent = node.innerHTML.replace(/|<\/td>/g, '').split('
'); + var rowContent = node.innerHTML.replace(//g, '').replace(/
/g, ' ').split(''); rowContent[0] = `|${rowContent[0]}`; var row = ''; var rowLength = rowContent.filter(Boolean).length; @@ -114,6 +114,8 @@ define([ var separator = '|-'; newRow += `${separator.repeat(rowLength)}\n`; } + var parser = new DOMParser() + newRow = parser.parseFromString(newRow, 'text/html').children[0].innerText return newRow; }}).addRule('strikethrough', { filter: ['s', 'del', 'strike'], From 6f72a14cfc2aec676c57e069f77d615055334063 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Thu, 5 Dec 2024 09:57:42 +0100 Subject: [PATCH 092/218] Linting --- www/notifications/inner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/notifications/inner.js b/www/notifications/inner.js index 8489e3809..7abcbac79 100644 --- a/www/notifications/inner.js +++ b/www/notifications/inner.js @@ -88,7 +88,7 @@ define([ if (filterTypes.indexOf(data.content.msg.type) !== -1) { notifsData.push(data); var icon = $(el).find(".cp-reminder"); - $(icon).addClass('cp-avatar-calendar') + $(icon).addClass('cp-avatar-calendar'); $(notifsList).prepend(el); } }; From fcbf122f3c6b0e8d4e59a30fa1abf278803e06a1 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Thu, 5 Dec 2024 09:59:24 +0100 Subject: [PATCH 093/218] Linting --- www/pad/export.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/pad/export.js b/www/pad/export.js index 9fd2bcaaa..d6650c817 100644 --- a/www/pad/export.js +++ b/www/pad/export.js @@ -114,8 +114,8 @@ define([ var separator = '|-'; newRow += `${separator.repeat(rowLength)}\n`; } - var parser = new DOMParser() - newRow = parser.parseFromString(newRow, 'text/html').children[0].innerText + var parser = new DOMParser(); + newRow = parser.parseFromString(newRow, 'text/html').children[0].innerText; return newRow; }}).addRule('strikethrough', { filter: ['s', 'del', 'strike'], From 0b677cf01ec863bd957c6403d310783e8ca9bc15 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Mon, 21 Oct 2024 16:01:22 +0200 Subject: [PATCH 094/218] Moved .form_passwordWarning key to customize.dist/messages.js --- customize.dist/messages.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/customize.dist/messages.js b/customize.dist/messages.js index 4c640c17d..e3f0c30de 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -135,6 +135,8 @@ define(req, function(AppConfig, Default, Language) { return text; } }; + + Messages.form_passwordWarning = 'For Forms, you can only set the password during creation. It cannot be changed later.' // XXX Messages.form_passwordWarning = 'Please note that a Form password can only be set now at creation time and cannot be changed later.' // XXX From 8daae414533e1e3c25b533a0d918466a8f0cfdf2 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Thu, 5 Dec 2024 14:04:47 +0100 Subject: [PATCH 095/218] Removed redundant newlines --- customize.dist/src/less2/include/creation.less | 1 - www/common/common-ui-elements.js | 1 - 2 files changed, 2 deletions(-) diff --git a/customize.dist/src/less2/include/creation.less b/customize.dist/src/less2/include/creation.less index c9bb17a7c..a25640398 100644 --- a/customize.dist/src/less2/include/creation.less +++ b/customize.dist/src/less2/include/creation.less @@ -179,7 +179,6 @@ } } } - .cp-creation-help, .cp-creation-warning { font-size: 16px; color: @cp_creation-fg; diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 3433f297d..c2dc088fc 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -25,7 +25,6 @@ define([ 'css!/customize/fonts/cptools/style.css', ], function ($, Config, Broadcast, Util, Hash, Language, UI, Constants, Feedback, h, Clipboard, Messages, AppConfig, Pages, NThen, InviteInner, Visible, PadTypes) { - var UIElements = {}; var urlArgs = Config.requireConf.urlArgs; From a1a8260fae64f2555f07ab6f15ef6c457ab6e663 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Thu, 5 Dec 2024 14:08:03 +0100 Subject: [PATCH 096/218] Removed redundant newlines --- www/common/drive-ui.js | 1 - 1 file changed, 1 deletion(-) diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index ac78d201d..cfed9aa2d 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -2432,7 +2432,6 @@ define([ if (isElementSelected($element)) { selectElement($element); } - $element.prepend($icon).dblclick(function () { if (restricted) { UI.warn(Messages.fm_restricted); From 2796999be82b508b142bd4626f481e54135759aa Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Thu, 5 Dec 2024 14:33:32 +0100 Subject: [PATCH 097/218] Changed export file type & formatting --- 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 e83e4a781..e90c93921 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -290,7 +290,7 @@ define([ }; var fileName = getUnique(sanitize(rawName), '.txt', existingNames); existingNames.push(fileName.toLowerCase()); - var content = new Blob([fData.href], { type : "text/html;charset=utf-8" }); + var content = new Blob([fData.href, '\n'], { type: "text/plain;charset=utf-8" }); zip.file(fileName, content, opts); console.log('DONE ---- ' + fileName); setTimeout(done, 1000); From 994bd61c9380d9ce8784500cbe7c17e083729747 Mon Sep 17 00:00:00 2001 From: DianaXWiki <139217939+DianaXWiki@users.noreply.github.com> Date: Thu, 5 Dec 2024 18:03:16 +0200 Subject: [PATCH 098/218] Remove some more refactoring --- www/calendar/inner.js | 1 - 1 file changed, 1 deletion(-) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index c2928d8a4..40381dc87 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -831,7 +831,6 @@ define([ onCalendarsUpdate.reg(function () { $calendars.empty(); var privateData = metadataMgr.getPrivateData(); - var filter = function (teamId) { var LOOKUP = {}; return Object.keys(APP.calendars || {}).filter(function (id) { From ddf4e56304b967825c490622fb0b0ece142f71ec Mon Sep 17 00:00:00 2001 From: DianaXWiki <139217939+DianaXWiki@users.noreply.github.com> Date: Thu, 5 Dec 2024 18:06:03 +0200 Subject: [PATCH 099/218] Add function explicitly --- www/calendar/inner.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index 40381dc87..d02383370 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -906,13 +906,13 @@ define([ h('span', Messages.calendar_new), h('span') ]); - $(newButton).click(() => { + $(newButton).click(function () { editCalendar(); }).appendTo($newContainer); - Object.keys(privateData.teams).sort().forEach((teamId) => { + Object.keys(privateData.teams).sort().forEach(function (teamId) { var calendars = filter(teamId); - if (!calendars.length) return; + if (!calendars.length) { return; } var team = privateData.teams[teamId]; var avatar = h('span.cp-avatar'); common.displayAvatar($(avatar), team.avatar, team.displayName || team.name); From 0b998a890dd4e5c86829fad2d97e0b2d70a60e93 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Fri, 6 Dec 2024 13:33:01 +0100 Subject: [PATCH 100/218] Links in shared folders included in .zip --- www/common/make-backup.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/www/common/make-backup.js b/www/common/make-backup.js index e90c93921..baf3161e8 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -307,26 +307,32 @@ define([ }; // Add folders and their content recursively in the zip - var makeFolder = function (ctx, root, zip, fd) { + var makeFolder = function (ctx, root, zip, fd, sd) { if (typeof (root) !== "object") { return; } var existingNames = []; Object.keys(root).forEach(function (k) { var el = root[k]; + let staticData; if (typeof el === "object" && el.metadata !== true) { // if folder var fName = getUnique(sanitize(k), '', existingNames); existingNames.push(fName.toLowerCase()); return void makeFolder(ctx, el, zip.folder(fName), fd); } if (ctx.data.sharedFolders[el]) { // if shared folder + staticData = ctx.sf[el].static; var sfData = ctx.sf[el].metadata; var sfName = getUnique(sanitize((sfData && sfData.title) || 'Folder'), '', existingNames); existingNames.push(sfName.toLowerCase()); - return void makeFolder(ctx, ctx.sf[el].root, zip.folder(sfName), ctx.sf[el].filesData); + return void makeFolder(ctx, ctx.sf[el].root, zip.folder(sfName), ctx.sf[el].filesData, staticData); } var fData = fd[el]; + var sData = sd[el]; if (fData) { addFile(ctx, zip, fData, existingNames); return; + } else if (sData) { + addFile(ctx, zip, sData, existingNames); + return; } }); }; From 2fb05156f0924d5ec7d3a9633cac61b0814bdd4a Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Fri, 6 Dec 2024 14:26:55 +0100 Subject: [PATCH 101/218] Shared folder fixes --- www/common/make-backup.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/www/common/make-backup.js b/www/common/make-backup.js index baf3161e8..93e4e2850 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -316,7 +316,7 @@ define([ if (typeof el === "object" && el.metadata !== true) { // if folder var fName = getUnique(sanitize(k), '', existingNames); existingNames.push(fName.toLowerCase()); - return void makeFolder(ctx, el, zip.folder(fName), fd); + return void makeFolder(ctx, el, zip.folder(fName), fd, sd); } if (ctx.data.sharedFolders[el]) { // if shared folder staticData = ctx.sf[el].static; @@ -325,8 +325,9 @@ define([ existingNames.push(sfName.toLowerCase()); return void makeFolder(ctx, ctx.sf[el].root, zip.folder(sfName), ctx.sf[el].filesData, staticData); } + var sData; var fData = fd[el]; - var sData = sd[el]; + sd ? sData = sd[el] : sData = undefined if (fData) { addFile(ctx, zip, fData, existingNames); return; From 470a5711cf6732749dbf8033df66200173614c46 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Fri, 6 Dec 2024 14:29:24 +0100 Subject: [PATCH 102/218] Linting --- 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 93e4e2850..17a5be462 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -327,7 +327,7 @@ define([ } var sData; var fData = fd[el]; - sd ? sData = sd[el] : sData = undefined + sd ? sData = sd[el] : sData = undefined; if (fData) { addFile(ctx, zip, fData, existingNames); return; From dc7d94ea770da9a6f346738fc4f4f1aff703b38f Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Fri, 6 Dec 2024 18:47:56 +0100 Subject: [PATCH 103/218] Links included in standalone folder downloads --- www/common/make-backup.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/common/make-backup.js b/www/common/make-backup.js index 17a5be462..78270831a 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -358,7 +358,8 @@ define([ sframeChan: sframeChan }; var filesData = data.sharedFolderId && ctx.sf[data.sharedFolderId] ? ctx.sf[data.sharedFolderId].filesData : ctx.data.filesData; - var links = ctx.data.static; + var links = ctx.sf[data.sharedFolderId] && ctx.sf[data.sharedFolderId].static ? ctx.data.static && ctx.sf[data.sharedFolderId].static : ctx.data.static + Object.keys(links).forEach(function(key) { filesData[key] = links[key]; }); From 3385ac8843bd3c94f56dc1d068ea2ce558512977 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Fri, 6 Dec 2024 18:49:18 +0100 Subject: [PATCH 104/218] Syntax correction --- www/common/make-backup.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/www/common/make-backup.js b/www/common/make-backup.js index 78270831a..98a4f5357 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -325,9 +325,8 @@ define([ existingNames.push(sfName.toLowerCase()); return void makeFolder(ctx, ctx.sf[el].root, zip.folder(sfName), ctx.sf[el].filesData, staticData); } - var sData; var fData = fd[el]; - sd ? sData = sd[el] : sData = undefined; + var sData = sd ? sd[el] : undefined; if (fData) { addFile(ctx, zip, fData, existingNames); return; From 04dff2738139291c922945774a78ce2e6a414bb1 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Fri, 6 Dec 2024 18:52:05 +0100 Subject: [PATCH 105/218] Linting --- 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 98a4f5357..fd2145ee2 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -357,7 +357,7 @@ define([ sframeChan: sframeChan }; 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 + var links = ctx.sf[data.sharedFolderId] && ctx.sf[data.sharedFolderId].static ? ctx.data.static && ctx.sf[data.sharedFolderId].static : ctx.data.static; Object.keys(links).forEach(function(key) { filesData[key] = links[key]; From a86fe963933d1dd3bc145e04b03a15958b6d7fd0 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Fri, 6 Dec 2024 19:17:25 +0100 Subject: [PATCH 106/218] Removed flex-flow style --- customize.dist/src/less2/include/creation.less | 1 - 1 file changed, 1 deletion(-) diff --git a/customize.dist/src/less2/include/creation.less b/customize.dist/src/less2/include/creation.less index a25640398..d200558af 100644 --- a/customize.dist/src/less2/include/creation.less +++ b/customize.dist/src/less2/include/creation.less @@ -165,7 +165,6 @@ //margin: 10px 0; min-height: 28px; line-height: 28px; - flex-flow: column; label { flex: 1; // Force wrap when the other element in the line is 100% (IE bug): From 650bb2360463f9e2e3c1dd252c384e354e4369db Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Fri, 6 Dec 2024 20:06:51 +0100 Subject: [PATCH 107/218] Fixes --- www/common/inner/common-modal.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/common/inner/common-modal.js b/www/common/inner/common-modal.js index 4b00c0c02..4fe7d6bef 100644 --- a/www/common/inner/common-modal.js +++ b/www/common/inner/common-modal.js @@ -50,9 +50,9 @@ define([ var hashes = priv.hashes || {}; // For calendars, individual href is passed via opts if (priv.app === 'calendar') { - data.href = (priv.app === 'calendar') && opts.href; + data.href = opts.href; } else if (hashes.editHash || hashes.fileHash) { - data.href = Hash.hashToHref(hashes.editHash || hashes.fileHash); + data.href = Hash.hashToHref(hashes.editHash || hashes.fileHash, priv.app); } else { data.href = undefined; } From 7f6005bb584ce346592b622ea09bfed9bc9a2c2d Mon Sep 17 00:00:00 2001 From: Weblate Date: Sun, 8 Dec 2024 03:25:05 +0100 Subject: [PATCH 108/218] Translated using Weblate (Bulgarian) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 33.5% (599 of 1783 strings) Translated using Weblate (Bulgarian) Currently translated at 31.8% (568 of 1783 strings) Co-authored-by: Weblate Co-authored-by: Мария Рангелова Translate-URL: https://weblate.cryptpad.org/projects/cryptpad/app/bg/ Translation: CryptPad/App --- www/common/translations/messages.bg.json | 65 +++++++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/www/common/translations/messages.bg.json b/www/common/translations/messages.bg.json index 28bc9f1b1..56ac012e0 100644 --- a/www/common/translations/messages.bg.json +++ b/www/common/translations/messages.bg.json @@ -481,7 +481,7 @@ "download_dl": "Изтегляне", "download_step1": "Изтегля се", "download_step2": "Декриптиране", - "todo_title": "CryptTodo", + "todo_title": "Crypt Todo", "todo_removeTaskTitle": "Премахнете тази задача от вашия списък със задачи", "pad_base64": "Този документ съдържа изображения, съхранени по неефективен начин. Тези изображения значително ще увеличат размера на документа във вашия CryptDrive и ще направят зареждането му по-бавно. Можете да промените тези файлове в нов формат, който ще се съхранява отделно във вашия CryptDrive. Искате ли да промените тези изображения сега?", "mdToolbar_button": "Показване или скриване на лентата с инструменти Markdown", @@ -538,5 +538,66 @@ "features_f_storage2_note": "От 5 GB на 50 GB в зависимост от плана, увеличен лимит от {0} MB за качване на файлове", "features_f_support": "По-бърза поддръжка", "features_f_support_note": "Приоритетен отговор от административния екип чрез имейл и вградена билетна система", - "features_f_supporter": "Поверителност при поддръжка" + "features_f_supporter": "Поверителност при поддръжка", + "features_f_supporter_note": "Помогнете на CryptPad да стане финансово устойчив и покажете, че софтуерът за подобряване на поверителността, доброволно финансиран от потребителите, трябва да бъде норма", + "four04_pageNotFound": "Не успяхме да намерим страницата, която търсите.", + "help_genericMore": "Научете повече за това как CryptPad може да работи за вас, като прочетете нашата Документация", + "feedback_about": "Ако четете това, вероятно сте били любопитни защо CryptPad търси уеб страници, когато извършвате определени действия.", + "creation_owned1": "Притежаван документ може да бъде унищожен, когато собственикът поиска. Унищожаването на притежаван документ го прави недостъпен чрез CryptDrives за другите потребители.", + "feedback_privacy": "Грижим се за вашата поверителност и в същото време искаме CryptPad да бъде много лесен за използване. Използваме този файл, за да разберем кои функции на потребителския интерфейс имат значение за нашите потребители, като го изискваме заедно с параметър, указващ кое действие е предприето.", + "creation_newPadModalDescription": "Кликнете върху приложението, за да създадете нов документ. Можете също да натиснете Tab, за да изберете приложението, и да натиснете Enter, за да потвърдите.", + "features_f_subscribe": "Абониране", + "features_f_subscribe_note": "За абониране е необходим акаунт", + "header_logoTitle": "Към вашия CryptDrive", + "header_homeTitle": "Към началната страница на CryptPad", + "edit": "редактиране", + "view": "преглед", + "feedback_optout": "Ако искате да се откажете, посетете страницата си с потребителски настройки, където ще намерите отметка, за да активирате или деактивирате обратната връзка с потребителя.", + "creation_404": "Този документ вече не съществува. Използвайте следната форма, за да създадете нов документ.", + "creation_owned": "Собственост на документа", + "creation_expire": "Изтичащ документ", + "creation_expireFalse": "Неограничен", + "creation_expireHours": "Час(ове)", + "creation_expireDays": "Ден(дни)", + "creation_expireMonths": "Месец(и)", + "creation_password": "Парола\n", + "creation_noTemplate": "Празен документ", + "creation_newTemplate": "Нов шаблон", + "creation_create": "Създаване", + "creation_owners": "Собственици", + "creation_noOwner": "Без собственик", + "creation_expiration": "Дата на унищожаване", + "creation_passwordValue": "Парола", + "password_info": "Документът, който се опитвате да отворите, вече не съществува или е защитен с нова парола. Въведете правилната парола за достъп до съдържанието.", + "properties_changePasswordButton": "Изпращане", + "sharedFolders_forget": "Този документ се съхранява само в споделена папка, не можете да го преместите в кошчето. Можете да използвате своя CryptDrive, ако искате да го изтриете.", + "share_linkEmbed": "Режим на вграждане (скриване на лентата с инструментите и потребителския списък)", + "share_mediatagCopy": "Копиране на медийния маркер в клипборда", + "sharedFolders_share": "Споделете тази връзка с други регистрирани потребители, за да им дадете достъп до споделената папка. След като отворят тази връзка, споделената папка ще бъде добавена към техния CryptDrive.", + "convertFolderToSF_SFChildren": "Тази папка не може да бъде преобразувана в споделена папка, защото вече съдържа споделени папки. Преместете тези споделени папки другаде, за да продължите.", + "password_error": "Документът не е намерен
Тази грешка може да бъде причинена от две причини: или паролата е невалидна, или документът е унищожен.", + "password_placeholder": "Въведете паролата тук...", + "password_submit": "Изпращане", + "properties_addPassword": "Добавяне на парола", + "properties_changePassword": "Промяна на паролата", + "properties_confirmNew": "Сигурен ли си? Добавянето на парола ще промени адреса на този документ и ще премахне неговата история. Потребителите без паролата ще загубят достъпа до този документ", + "properties_confirmChange": "Сигурен ли си? Промяната на паролата ще премахне нейната история. Потребителите без новата парола ще загубят достъпа до този документ", + "properties_passwordSame": "Новите пароли трябва да се различават от текущата.", + "properties_passwordError": "Възникна грешка при опит за промяна на паролата. Моля, опитайте отново.", + "properties_passwordWarning": "Паролата беше променена успешно, но не успяхме да актуализираме вашия CryptDrive с новите данни. Може да се наложи да премахнете старата версия на документа ръчно.
Натиснете OK, за да презаредите и актуализирате правата си за достъп.", + "properties_passwordSuccess": "Паролата бе променена успешно.
Натиснете OK, за да презаредите и актуализирате правата си за достъп.", + "share_linkCategory": "Връзка", + "share_linkAccess": "Права за достъп", + "share_linkEdit": "Редактиране", + "share_linkView": "Преглед", + "share_linkPresent": "Текущ", + "share_linkOpen": "Отваряне на връзка", + "share_linkCopy": "Копиране на връзка", + "share_contactCategory": "Контакти", + "share_embedCategory": "Вграждане", + "sharedFolders_duplicate": "Някои от документите, които се опитвахте да преместите, вече бяха споделени в целевата папка.", + "sharedFolders_create": "Създаване на споделена папка", + "sharedFolders_create_name": "Име на папка", + "sharedFolders_create_owned": "Собствена папка", + "convertFolderToSF_SFParent": "Тази папка не може да бъде преобразувана в споделена папка в текущото си местоположение. Преместете го извън споделената папка, за да продължите." } From 7a00b6b4a20c06e7e14666374b83efc7d52c3594 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Sun, 8 Dec 2024 22:05:04 +0100 Subject: [PATCH 109/218] Formatting accounts for headers --- www/pad/export.js | 52 ++++++++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/www/pad/export.js b/www/pad/export.js index d6650c817..897bd778a 100644 --- a/www/pad/export.js +++ b/www/pad/export.js @@ -97,26 +97,40 @@ define([ var md = Turndown({ headingStyle: 'atx' }).addRule('table', { - filter: ['tr'], + filter: ['table'], replacement: function (content, node) { - var indexOf = Array.prototype.indexOf; - var index = indexOf.call(node.parentNode.childNodes, node); - var rowContent = node.innerHTML.replace(//g, '').replace(/
/g, ' ').split(''); - rowContent[0] = `|${rowContent[0]}`; - var row = ''; - var rowLength = rowContent.filter(Boolean).length; - for (var i =0; i < rowLength; i++) { - var cell = rowContent[i] + ' |'; - row += cell; - } - var newRow = row.concat('\n'); - if (index === 0) { - var separator = '|-'; - newRow += `${separator.repeat(rowLength)}\n`; - } - var parser = new DOMParser(); - newRow = parser.parseFromString(newRow, 'text/html').children[0].innerText; - return newRow; + var childNodeArr = Array.from(node.childNodes) + var table = '' + childNodeArr.forEach(function(cN) { + cN.childNodes.forEach(function(childNode) { + var childNodes = Array.from(childNode.childNodes) + var rowContent = childNodes + var indexOf = Array.prototype.indexOf; + var index; + if (childNodeArr.length > 1) { + index = indexOf.call(node.childNodes, cN); + } else { + index = indexOf.call(cN.childNodes, childNode); + } + rowContent[0].textContent = `|${rowContent[0].textContent}`; + var row = ''; + var rowLength = rowContent.filter(Boolean).length; + for (var i =0; i < rowLength; i++) { + var cell = rowContent[i].textContent + ' |'; + row += cell; + } + var newRow = row.concat('\n'); + if (index === 0) { + var separator = '|-'; + newRow += `${separator.repeat(rowLength)}\n`; + } + var parser = new DOMParser(); + newRow = parser.parseFromString(newRow, 'text/html').children[0].innerText; + table += newRow + return newRow; + }) + }) + return table }}).addRule('strikethrough', { filter: ['s', 'del', 'strike'], replacement: function (content) { From e8136b32da1f616e7eb1c6a4bc19160d85d9f5cc Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Sun, 8 Dec 2024 23:10:51 +0100 Subject: [PATCH 110/218] Formatting accounts for newlines inside cells --- www/pad/export.js | 73 +++++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/www/pad/export.js b/www/pad/export.js index 897bd778a..1999a5164 100644 --- a/www/pad/export.js +++ b/www/pad/export.js @@ -97,40 +97,45 @@ define([ var md = Turndown({ headingStyle: 'atx' }).addRule('table', { - filter: ['table'], - replacement: function (content, node) { - var childNodeArr = Array.from(node.childNodes) - var table = '' - childNodeArr.forEach(function(cN) { - cN.childNodes.forEach(function(childNode) { - var childNodes = Array.from(childNode.childNodes) - var rowContent = childNodes - var indexOf = Array.prototype.indexOf; - var index; - if (childNodeArr.length > 1) { - index = indexOf.call(node.childNodes, cN); - } else { - index = indexOf.call(cN.childNodes, childNode); - } - rowContent[0].textContent = `|${rowContent[0].textContent}`; - var row = ''; - var rowLength = rowContent.filter(Boolean).length; - for (var i =0; i < rowLength; i++) { - var cell = rowContent[i].textContent + ' |'; - row += cell; - } - var newRow = row.concat('\n'); - if (index === 0) { - var separator = '|-'; - newRow += `${separator.repeat(rowLength)}\n`; - } - var parser = new DOMParser(); - newRow = parser.parseFromString(newRow, 'text/html').children[0].innerText; - table += newRow - return newRow; - }) - }) - return table + filter: ['table'], + replacement: function (content, node) { + var childNodeArr = Array.from(node.childNodes); + var table = ''; + childNodeArr.forEach(function(rowNode) { + rowNode.childNodes.forEach(function(childNode) { + var rowContent = Array.from(childNode.childNodes); + var indexOf = Array.prototype.indexOf; + var index = childNodeArr.length > 1 ? indexOf.call(node.childNodes, rowNode) : indexOf.call(rowNode.childNodes, childNode); + var row = ''; + var rowLength = rowContent.filter(Boolean).length; + for (var i =0; i < rowLength; i++) { + var cell = rowContent[i]; + var cellContent = Array.from(cell.childNodes); + cellContent.pop(); + if (cellContent.length > 1) { + var cellString = ''; + cellContent.forEach(function(string) { + if (string.nodeType === 3) { + cellString += string.textContent; + } else if (string.nodeName === "BR") { + cellString += '
'; + } + }); + row += cellString + ' |'; + } else { + row += cellContent[0].textContent + ' |'; + } + } + var newRow = row.concat('\n'); + if (index === 0) { + var separator = '|-'; + newRow += `${separator.repeat(rowLength)}\n`; + } + table += newRow; + return newRow; + }); + }); + return table; }}).addRule('strikethrough', { filter: ['s', 'del', 'strike'], replacement: function (content) { From 1354b0a238bf0a866d3c0823c55951a93f233d5e Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Mon, 9 Dec 2024 13:08:36 +0100 Subject: [PATCH 111/218] Formatting accounts for bold, italic, underlined text and blank cells --- www/pad/export.js | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/www/pad/export.js b/www/pad/export.js index 1999a5164..0bd62d9b0 100644 --- a/www/pad/export.js +++ b/www/pad/export.js @@ -111,19 +111,29 @@ define([ for (var i =0; i < rowLength; i++) { var cell = rowContent[i]; var cellContent = Array.from(cell.childNodes); - cellContent.pop(); if (cellContent.length > 1) { var cellString = ''; - cellContent.forEach(function(string) { + cellContent.forEach(function(string) { + var stringContent = string.childNodes.length ? string.innerHTML : string.textContent if (string.nodeType === 3) { - cellString += string.textContent; + cellString += stringContent; } else if (string.nodeName === "BR") { cellString += '
'; + } else if (string.nodeName === "EM") { + cellString += '' + stringContent + ''; + } else if (string.nodeName === "STRONG") { + cellString += '' + stringContent + ''; + } else if (string.nodeName === "U") { + cellString += '' + stringContent + ''; + } else if (string.nodeName === "S") { + cellString += '~' + stringContent + '~'; } }); row += cellString + ' |'; - } else { - row += cellContent[0].textContent + ' |'; + } else if (cellContent[0].nodeName === "BR") { + row += '| |'; + } else { + row += cellContent[0].innerHTML + ' |'; } } var newRow = row.concat('\n'); @@ -137,10 +147,15 @@ define([ }); return table; }}).addRule('strikethrough', { - filter: ['s', 'del', 'strike'], - replacement: function (content) { - return '~' + content + '~'; - } + filter: ['s', 'del', 'strike'], + replacement: function (content) { + return '~' + content + '~'; + } + }).addRule('strikethrough', { + filter: ['u'], + replacement: function (content) { + return '' + content + ''; + } }) .turndown(toExport); var mdBlob = new Blob([md], { From 8d5ac837a1db48a2e7112dbb701e949865734573 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Mon, 9 Dec 2024 14:44:59 +0100 Subject: [PATCH 112/218] Linting --- www/pad/export.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/pad/export.js b/www/pad/export.js index 0bd62d9b0..abd45ce20 100644 --- a/www/pad/export.js +++ b/www/pad/export.js @@ -114,7 +114,7 @@ define([ if (cellContent.length > 1) { var cellString = ''; cellContent.forEach(function(string) { - var stringContent = string.childNodes.length ? string.innerHTML : string.textContent + var stringContent = string.childNodes.length ? string.innerHTML : string.textContent; if (string.nodeType === 3) { cellString += stringContent; } else if (string.nodeName === "BR") { From 14e56aef791050bc7ddfdf239803241f76c0d931 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Mon, 9 Dec 2024 14:45:40 +0100 Subject: [PATCH 113/218] Indentation --- www/pad/export.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/www/pad/export.js b/www/pad/export.js index abd45ce20..aac4ae4aa 100644 --- a/www/pad/export.js +++ b/www/pad/export.js @@ -147,15 +147,15 @@ define([ }); return table; }}).addRule('strikethrough', { - filter: ['s', 'del', 'strike'], - replacement: function (content) { - return '~' + content + '~'; - } + filter: ['s', 'del', 'strike'], + replacement: function (content) { + return '~' + content + '~'; + } }).addRule('strikethrough', { - filter: ['u'], - replacement: function (content) { - return '' + content + ''; - } + filter: ['u'], + replacement: function (content) { + return '' + content + ''; + } }) .turndown(toExport); var mdBlob = new Blob([md], { From 495435ec07d91f20fc3d8c46dd7673d9a6b5d258 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 9 Dec 2024 15:07:56 +0100 Subject: [PATCH 114/218] Fix indentation --- www/calendar/inner.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index d02383370..8c6bf7317 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -938,17 +938,17 @@ define([ h('span') ]); - $(showCalendarsBtn).click(() => { - visible = !visible; - $contentContainer.toggle(visible); - iconClass = visible ? 'fa-eye-slash' : 'fa-eye'; - buttonText = visible ? Messages.calendar_hide : Messages.calendar_show; - $(showCalendarsBtn).find('i').attr('class', 'fa ' + iconClass).attr('aria-hidden', "true"); - $(showCalendarsBtn).find('span').first().text(visible ? Messages.calendar_hide : Messages.calendar_show); - }).appendTo($showContainer); + $(showCalendarsBtn).click(() => { + visible = !visible; + $contentContainer.toggle(visible); + iconClass = visible ? 'fa-eye-slash' : 'fa-eye'; + buttonText = visible ? Messages.calendar_hide : Messages.calendar_show; + $(showCalendarsBtn).find('i').attr('class', 'fa ' + iconClass).attr('aria-hidden', "true"); + $(showCalendarsBtn).find('span').first().text(visible ? Messages.calendar_hide : Messages.calendar_show); + }).appendTo($showContainer); + } + else {visible = true;} } - else {visible = true;} - } $contentContainer.toggle(visible); $(window).resize(function () { From e1c5285f608edb3ef1c312480ad0c3673e31f25b Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 9 Dec 2024 15:19:41 +0100 Subject: [PATCH 115/218] Fix initial calendar visibility on small screens --- www/calendar/inner.js | 8 ++++++-- www/common/outer/calendar.js | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index 8c6bf7317..0994dbb6a 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -928,7 +928,9 @@ define([ }); }); if(isMobileView) { - if (totalCalendars > 2) { + // If initial number of calendars or current number > 2, + // hide the calendars list and display a "show" button + if (APP.numberCalendars > 2 || totalCalendars > 2) { var $showContainer = $(h('div.cp-calendar-entry.cp-ghost')).appendTo($calendars); var iconClass = visible ? 'fa-eye-slash' : 'fa-eye'; var buttonText = visible ? Messages.calendar_hide : Messages.calendar_show; @@ -2389,7 +2391,9 @@ APP.recurrenceRule = { }); var store = window.cryptpadStore; APP.module.execCommand('SUBSCRIBE', null, function (obj) { - if (obj.empty && !privateData.calendarHash) { + let empty = !obj.length; + APP.numberCalendars = obj.length; + if (empty && !privateData.calendarHash) { if (!privateData.loggedIn) { return void UI.errorLoadingScreen(Messages.mustLogin, false, function () { common.setLoginRedirect('login'); diff --git a/www/common/outer/calendar.js b/www/common/outer/calendar.js index 21bff21ff..4d9d88aea 100644 --- a/www/common/outer/calendar.js +++ b/www/common/outer/calendar.js @@ -599,7 +599,7 @@ define([ ctx.clients.push(cId); } cb({ - empty: !Object.keys(ctx.calendars).length + length: Object.keys(ctx.calendars).length }); Object.keys(ctx.calendars).forEach(function (channel) { var c = ctx.calendars[channel] || {}; From 0b960439caafdeeadc6f46def8dfc8da23ae703c Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 9 Dec 2024 15:58:46 +0100 Subject: [PATCH 116/218] Fix multi-level nodes for pad table .md export --- www/pad/export.js | 57 +++++++++++++++++++---------------------------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/www/pad/export.js b/www/pad/export.js index aac4ae4aa..a4bda9599 100644 --- a/www/pad/export.js +++ b/www/pad/export.js @@ -94,6 +94,18 @@ define([ return void cb(blob); } if (ext === ".md") { + let strikethrough = { + filter: ['s', 'del', 'strike'], + replacement: function (content) { + return '~' + content + '~'; + } + }; + let underline = { + filter: ['u'], + replacement: function (content) { + return '' + content + ''; + } + }; var md = Turndown({ headingStyle: 'atx' }).addRule('table', { @@ -111,29 +123,15 @@ define([ for (var i =0; i < rowLength; i++) { var cell = rowContent[i]; var cellContent = Array.from(cell.childNodes); - if (cellContent.length > 1) { - var cellString = ''; - cellContent.forEach(function(string) { - var stringContent = string.childNodes.length ? string.innerHTML : string.textContent; - if (string.nodeType === 3) { - cellString += stringContent; - } else if (string.nodeName === "BR") { - cellString += '
'; - } else if (string.nodeName === "EM") { - cellString += '' + stringContent + ''; - } else if (string.nodeName === "STRONG") { - cellString += '' + stringContent + ''; - } else if (string.nodeName === "U") { - cellString += '' + stringContent + ''; - } else if (string.nodeName === "S") { - cellString += '~' + stringContent + '~'; - } - }); - row += cellString + ' |'; - } else if (cellContent[0].nodeName === "BR") { - row += '| |'; - } else { - row += cellContent[0].innerHTML + ' |'; + if ((cellContent.length === 1 && cellContent[0].nodeName === "BR") || !cellContent.length) { + row += '|'; + } else if (cellContent.length >= 1) { + row += Turndown({ + headingStyle: 'atx' + }).addRule('strikethrough', strikethrough) + .addRule('underline', underline) + .turndown(cell.innerHTML).replaceAll('\n', '
'); + row += '|'; } } var newRow = row.concat('\n'); @@ -146,17 +144,8 @@ define([ }); }); return table; - }}).addRule('strikethrough', { - filter: ['s', 'del', 'strike'], - replacement: function (content) { - return '~' + content + '~'; - } - }).addRule('strikethrough', { - filter: ['u'], - replacement: function (content) { - return '' + content + ''; - } - }) + }}).addRule('strikethrough', strikethrough) + .addRule('underline', underline) .turndown(toExport); var mdBlob = new Blob([md], { type: 'text/markdown;charset=utf-8' From c10bf97e756a608962b38a7d62372f8c06d88543 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Mon, 9 Dec 2024 18:17:55 +0100 Subject: [PATCH 117/218] .cp-creation-form>div styling --- customize.dist/src/less2/include/creation.less | 1 - 1 file changed, 1 deletion(-) diff --git a/customize.dist/src/less2/include/creation.less b/customize.dist/src/less2/include/creation.less index d200558af..5dbce1046 100644 --- a/customize.dist/src/less2/include/creation.less +++ b/customize.dist/src/less2/include/creation.less @@ -160,7 +160,6 @@ max-width: 100%; display: flex; align-items: center; - flex-wrap: wrap; font-size: 16px; //margin: 10px 0; min-height: 28px; From 2a693e3a9086692f0e87a0ff036f99ff3cccbb93 Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 10 Dec 2024 14:24:50 +0200 Subject: [PATCH 118/218] move notifications styles to the correct file(`notifications.less`) --- .../src/less2/include/dropdown.less | 12 ----------- .../src/less2/include/notifications.less | 20 +++++++++++++++++++ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/customize.dist/src/less2/include/dropdown.less b/customize.dist/src/less2/include/dropdown.less index 00d1d35ed..ee63ab5bc 100644 --- a/customize.dist/src/less2/include/dropdown.less +++ b/customize.dist/src/less2/include/dropdown.less @@ -167,18 +167,6 @@ background-color: @cp_dropdown-bg-hover; } } - li[role="menuitem"].cp-notification-avatar { - &:hover { - background-color: transparent; - } - .cp-avatar:hover { - background-color: @cp_dropdown-bg-hover; - } - .cp-notification-content:hover { - border-radius: @variables_radius; - background-color: @cp_dropdown-bg-hover; - } - } &> span { box-sizing: border-box; height: 26px; diff --git a/customize.dist/src/less2/include/notifications.less b/customize.dist/src/less2/include/notifications.less index 6a69b8beb..81e5fb5f8 100644 --- a/customize.dist/src/less2/include/notifications.less +++ b/customize.dist/src/less2/include/notifications.less @@ -97,6 +97,26 @@ margin: 0; } } + .cp-dropdown-container .cp-dropdown-content { + li[role="menuitem"]{ + .cp-notification-dismiss:hover { + border-radius: @variables_radius; + background-color: @cp_dropdown-bg-hover; + } + } + li[role="menuitem"].cp-notification-avatar { + &:hover { + background-color: transparent; + } + .cp-avatar:hover { + background-color: @cp_dropdown-bg-hover; + } + .cp-notification-content:hover { + border-radius: @variables_radius; + background-color: @cp_dropdown-bg-hover; + } + } + } } From 8b5df20220bea3f5d6313c7e0697d51f6659d701 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Tue, 10 Dec 2024 13:44:24 +0100 Subject: [PATCH 119/218] Fixes --- www/common/make-backup.js | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/www/common/make-backup.js b/www/common/make-backup.js index fd2145ee2..75bd19b78 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -171,8 +171,7 @@ define([ } var href; var parsed; - var linkRegex = new RegExp("^(http|https)://"); - if (fData.href && linkRegex.test(fData.href)) { + if (!fData.channel) { href = fData.href; parsed = {}; parsed['hashData'] = {type: 'link'}; @@ -312,28 +311,23 @@ define([ var existingNames = []; Object.keys(root).forEach(function (k) { var el = root[k]; - let staticData; if (typeof el === "object" && el.metadata !== true) { // if folder var fName = getUnique(sanitize(k), '', existingNames); existingNames.push(fName.toLowerCase()); return void makeFolder(ctx, el, zip.folder(fName), fd, sd); } if (ctx.data.sharedFolders[el]) { // if shared folder - staticData = ctx.sf[el].static; + let staticData = ctx.sf[el].static; var sfData = ctx.sf[el].metadata; var sfName = getUnique(sanitize((sfData && sfData.title) || 'Folder'), '', existingNames); existingNames.push(sfName.toLowerCase()); return void makeFolder(ctx, ctx.sf[el].root, zip.folder(sfName), ctx.sf[el].filesData, staticData); } - var fData = fd[el]; - var sData = sd ? sd[el] : undefined; + var fData = fd[el] || (sd && sd[el]); if (fData) { addFile(ctx, zip, fData, existingNames); return; - } else if (sData) { - addFile(ctx, zip, sData, existingNames); - return; - } + } }); }; @@ -359,14 +353,11 @@ 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; - Object.keys(links).forEach(function(key) { - filesData[key] = links[key]; - }); progress('reading', -1); // Msg.settings_export_reading nThen(function (waitFor) { ctx.waitFor = waitFor; var zipRoot = ctx.zip.folder(data.name || Messages.fm_rootName); - makeFolder(ctx, ctx.folder || ctx.data.root, zipRoot, filesData); + makeFolder(ctx, ctx.folder || ctx.data.root, zipRoot, filesData, links); progress('download', {}); // Msg.settings_export_download }).nThen(function () { console.log(ctx.zip); From 6c4fa5fbefa35d778743c58db1ae136723a450b8 Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 10 Dec 2024 14:45:34 +0200 Subject: [PATCH 120/218] fix bug preventing kanban board movement --- www/kanban/jkanban_cp.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/kanban/jkanban_cp.js b/www/kanban/jkanban_cp.js index 95e92325a..47ea2a2ba 100644 --- a/www/kanban/jkanban_cp.js +++ b/www/kanban/jkanban_cp.js @@ -254,7 +254,7 @@ define([ accepts: function (el, target, source, sibling) { if (self.options.readOnly) { return false; } if (sibling && sibling.getAttribute('id') === "kanban-addboard") { return false; } - return target.classList.contains('kanban-container') || + return target.classList.contains('kanban-boards-container') || target.classList.contains('kanban-trash'); }, revertOnSpill: true, From ef3ff95bab820f37b62b0c4cf2af71b789b8429a Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 10 Dec 2024 17:21:13 +0100 Subject: [PATCH 121/218] Fix margin issues in form creation screen --- .../src/less2/include/creation.less | 19 +++++++++---------- www/common/common-ui-elements.js | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/customize.dist/src/less2/include/creation.less b/customize.dist/src/less2/include/creation.less index 5dbce1046..f61f20d90 100644 --- a/customize.dist/src/less2/include/creation.less +++ b/customize.dist/src/less2/include/creation.less @@ -69,7 +69,7 @@ outline: none; width: 700px; max-width: 90vw; - height: 500px; + //height: 500px; border-radius: @variables_radius_L; max-height: ~"calc(100vh - 20px)"; margin: 0px; @@ -85,6 +85,7 @@ } .cp-creation-checkboxes { + min-width: 300px; flex-flow: column; align-items: baseline !important; max-height: 150px; @@ -156,7 +157,7 @@ flex: 1 0 auto; justify-content: space-around; & > div { - width: 300px; + //width: 300px; max-width: 100%; display: flex; align-items: center; @@ -291,11 +292,11 @@ } } } - .cp-creation-password-warning { - margin-top: 0.4rem; - font-size: 0.75em; - line-height: 120%; - } + } + .cp-creation-password-warning { + font-size: 0.75em; + line-height: 120%; + margin: 0.4rem 1rem calc(0.4rem + 6px); } .cp-creation-settings { button { @@ -425,7 +426,6 @@ #cp-creation-form { & > div { width: 95%; - margin: 0 auto; } .cp-creation-expire { &.active { @@ -435,7 +435,6 @@ .cp-creation-slider { flex: none; order: 10; - width: 100%; } } } @@ -444,7 +443,7 @@ } @media screen and (max-width: 800px) { #cp-creation { - height: 550px; + //height: 550px; #cp-creation-form { div.cp-creation-template { flex-flow: column; diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index c2dc088fc..da840dfc4 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -2782,7 +2782,6 @@ define([ type: "text" // TODO type password with click to show }),*/ ]), - text, //createHelper('#', "TODO: password protection adds another layer of security ........") // TODO ]); @@ -2813,6 +2812,7 @@ define([ expire, password, ]), + text, templates, createDiv ])).appendTo($creation); From a5b2fe7a68d14b901576b57c952772aa90b219ab Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 10 Dec 2024 17:43:55 +0100 Subject: [PATCH 122/218] Reorder DOM when updating kanban boards --- www/kanban/app-kanban.less | 3 --- www/kanban/jkanban_cp.js | 23 ++++++++++++++++------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/www/kanban/app-kanban.less b/www/kanban/app-kanban.less index 86e0f00c8..86e1052b5 100644 --- a/www/kanban/app-kanban.less +++ b/www/kanban/app-kanban.less @@ -576,9 +576,6 @@ display: flex; max-height: 100%; } - .kanban-boards-container{ - display: flex; - } } #kanban-trash { height: 1px; diff --git a/www/kanban/jkanban_cp.js b/www/kanban/jkanban_cp.js index 47ea2a2ba..f6411e36e 100644 --- a/www/kanban/jkanban_cp.js +++ b/www/kanban/jkanban_cp.js @@ -109,19 +109,17 @@ define([ //create container var boardContainerOuter = document.createElement('div'); boardContainerOuter.classList.add('kanban-container-outer'); - var kanbanContainer = document.createElement('div'); - kanbanContainer.classList.add('kanban-container'); - boardContainerOuter.appendChild(kanbanContainer); var boardContainer = document.createElement('div'); - boardContainer.classList.add('kanban-boards-container'); - kanbanContainer.appendChild(boardContainer); + boardContainer.setAttribute('id', 'kanban-container'); + boardContainer.classList.add('kanban-container'); + boardContainerOuter.appendChild(boardContainer); self.container = boardContainer; //add boards self.addBoards(); var addBoard = document.createElement('div'); addBoard.id = 'kanban-addboard'; addBoard.innerHTML = ''; - kanbanContainer.appendChild(addBoard); + boardContainer.appendChild(addBoard); var trash = self.trashContainer = document.createElement('div'); trash.setAttribute('id', 'kanban-trash'); trash.setAttribute('class', 'kanban-trash'); @@ -254,7 +252,7 @@ define([ accepts: function (el, target, source, sibling) { if (self.options.readOnly) { return false; } if (sibling && sibling.getAttribute('id') === "kanban-addboard") { return false; } - return target.classList.contains('kanban-boards-container') || + return target.classList.contains('kanban-container') || target.classList.contains('kanban-trash'); }, revertOnSpill: true, @@ -740,6 +738,15 @@ define([ return boardNode; }; + + let reorder = () => { + // Push "add" button to the end of the list + let add = document.getElementById('kanban-addboard'); + let list = document.getElementById('kanban-container'); + if (!add || !list) { return; } + list.appendChild(add); + }; + this.addBoard = function (board) { if (!board || !board.id) { return; } // We need to store all the columns in _boards too because it's used to @@ -760,6 +767,7 @@ define([ _boards.list.push(board.id); var boardNode = getBoardNode(board); self.container.appendChild(boardNode); + reorder(); }; this.addBoards = function() { @@ -827,6 +835,7 @@ define([ $('.kanban-board[data-id="'+id+'"] .kanban-drag').scrollTop(scroll[id]); }); $el.scrollLeft(scrollLeft); + reorder(); }; // If the tab is not focused, redraw on focus From 556135bddacba98a4dcb15cdcecf156d5856cde6 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 10 Dec 2024 17:48:49 +0100 Subject: [PATCH 123/218] Add missing pipes in pad tables markdown export --- www/pad/export.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/pad/export.js b/www/pad/export.js index a4bda9599..5e5a23689 100644 --- a/www/pad/export.js +++ b/www/pad/export.js @@ -118,7 +118,7 @@ define([ var rowContent = Array.from(childNode.childNodes); var indexOf = Array.prototype.indexOf; var index = childNodeArr.length > 1 ? indexOf.call(node.childNodes, rowNode) : indexOf.call(rowNode.childNodes, childNode); - var row = ''; + var row = '|'; var rowLength = rowContent.filter(Boolean).length; for (var i =0; i < rowLength; i++) { var cell = rowContent[i]; @@ -137,7 +137,7 @@ define([ var newRow = row.concat('\n'); if (index === 0) { var separator = '|-'; - newRow += `${separator.repeat(rowLength)}\n`; + newRow += `${separator.repeat(rowLength)}|\n`; } table += newRow; return newRow; From 89c53dc4ab3c89c5adc7ecbe93fee703faf1065c Mon Sep 17 00:00:00 2001 From: Wolfgang Ginolas Date: Wed, 11 Dec 2024 11:47:28 +0100 Subject: [PATCH 124/218] WIP --- www/common/onlyoffice/inner.js | 72 +++++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 6 deletions(-) diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 12d95112a..90431fc4a 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -68,6 +68,7 @@ define([ var NEW_VERSION = 7; // version of the .bin, patches and ChainPad formats var PENDING_TIMEOUT = 30000; var CURRENT_VERSION = X2T.CURRENT_VERSION; + const HISTORY_KEEPER_INDEX_USER = 1; //var READONLY_REFRESH_TO = 15000; @@ -181,11 +182,25 @@ define([ }); }; - const getNewUserIndex = function () { + /** + * This function retrieves the highest `indexUser`/`index` value, when `latestIndexUser` is not set. + * This is case for older OO documents. + * + * @returns the higest `indexUser` value or `-Infinity` + */ + const getHighestLegacyIndexUser = function() { const ids = content.ids || {}; - const indexes = Object.values(ids).map((user) => user.index); - const maxIndex = Math.max(...indexes); - return maxIndex === -Infinity ? 1 : maxIndex+1; + const indexes = Object.values(ids) + .map((user) => user.index) + .filter(Boolean); + return Math.max(...indexes); + } + + const getNextUserIndex = function () { + const latestIndexUser = content.latestIndexUser + ? content.latestIndexUser + : getHighestLegacyIndexUser(); + return Math.max(latestIndexUser, HISTORY_KEEPER_INDEX_USER) + 1; }; var setMyId = function () { @@ -205,7 +220,6 @@ define([ var myId = getId(); ids[myId] = { ooid: myOOId, - index: getNewUserIndex(), netflux: metadataMgr.getNetfluxId() }; oldIds = JSON.parse(JSON.stringify(ids)); @@ -925,7 +939,53 @@ define([ : content.ids.length; // Assign an unused id to read-only users }; - var getParticipants = function () { + const getParticipants = function () { + // Add an history keeper user to show that we're never alone + var hkId = Util.createRandomInteger(); + const historyKeeper = [{ + id: hkId, + idOriginal: String(hkId), + username: "History", + indexUser: HISTORY_KEEPER_INDEX_USER, + connectionId: Hash.createChannelId(), + isCloseCoAuthoring:false, + view: false + }]; + + const other = content.ids.map(user => ({ + id: String(user.ooId), + idOriginal: String(user.ooId), + username: TODO user.name || Messages.anonymous, + indexUser: user.index, + connectionId: user.netflux || Hash.createChannelId(), + isCloseCoAuthoring: false, + view: false + })); + + const myOOIndex = getNextUserIndex(); + + const me = [{ + id: String(myOOId), + idOriginal: String(myOOId), + username: metadataMgr.getUserData().name || Messages.anonymous, + indexUser: myOOIndex, + connectionId: metadataMgr.getNetfluxId() || Hash.createChannelId(), + isCloseCoAuthoring:false, + view: false + }]; + + + + + + return { + index: myOOIndex, + list: p.filter(Boolean) + }; + }; + + // TODO remove me + var getParticipantsOld = function () { var users = metadataMgr.getMetadata().users; var i = 1; var p = Object.keys(content.ids || {}).map(function (id) { From 946cbe8a834af8deece507d1be390388c04336ae Mon Sep 17 00:00:00 2001 From: Wolfgang Ginolas Date: Wed, 11 Dec 2024 14:39:28 +0100 Subject: [PATCH 125/218] WIP getPaticipants finished, but editing is broken --- www/common/onlyoffice/inner.js | 65 ++++++++++++++++------------------ 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 90431fc4a..3321d7edc 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -73,6 +73,7 @@ define([ //var READONLY_REFRESH_TO = 15000; var debug = function (x, type) { + console.log('XXX debug', type, x); if (!window.CP_DEV_MODE) { return; } console.debug(x, type); }; @@ -160,6 +161,7 @@ define([ Object.keys(ids).forEach(function (id) { var nId = id.slice(0,32); if (users.indexOf(nId) === -1) { + console.log('XXX deleteOffline', { nId, id }); delete ids[id]; } }); @@ -188,13 +190,13 @@ define([ * * @returns the higest `indexUser` value or `-Infinity` */ - const getHighestLegacyIndexUser = function() { - const ids = content.ids || {}; + const getHighestLegacyIndexUser = function () { + const ids = content.ids || {}; const indexes = Object.values(ids) .map((user) => user.index) .filter(Boolean); return Math.max(...indexes); - } + }; const getNextUserIndex = function () { const latestIndexUser = content.latestIndexUser @@ -204,25 +206,33 @@ define([ }; var setMyId = function () { - // Remove ids for users that have left the channel - deleteOffline(); - var ids = content.ids; + deleteOffline(); // Remove ids for users that have left the channel + console.log('XXX setMyId start', { content: structuredClone(content) }); + const ids = content.ids; if (!myOOId) { myOOId = Util.createRandomInteger(); // f: function used in .some(f) but defined outside of the while var f = function (id) { return ids[id].ooid === myOOId; }; + // TODO Object.keys(ids) is incorrect here while (Object.keys(ids).some(f)) { myOOId = Util.createRandomInteger(); } } - var myId = getId(); + + const myId = getId(); + const myIndex = getNextUserIndex(); + ids[myId] = { ooid: myOOId, + index: myIndex, netflux: metadataMgr.getNetfluxId() }; - oldIds = JSON.parse(JSON.stringify(ids)); + + content.latestIndexUser = myIndex; + oldIds = structuredClone(ids); + console.log('XXX setMyId end', { myOOId, myIndex, content: structuredClone(content) }); APP.onLocal(); }; @@ -934,16 +944,16 @@ define([ const getMyOOIndex = function() { const user = findUserByOOId(myOOId); - return user - ? user.index - : content.ids.length; // Assign an unused id to read-only users + return user.index; }; const getParticipants = function () { + const users = metadataMgr.getMetadata().users; + console.log('XXX getParticipants', users); // Add an history keeper user to show that we're never alone var hkId = Util.createRandomInteger(); const historyKeeper = [{ - id: hkId, + id: String(hkId), idOriginal: String(hkId), username: "History", indexUser: HISTORY_KEEPER_INDEX_USER, @@ -952,35 +962,22 @@ define([ view: false }]; - const other = content.ids.map(user => ({ - id: String(user.ooId), - idOriginal: String(user.ooId), - username: TODO user.name || Messages.anonymous, + const realParticipants = Object.entries(content.ids).map(([id, user]) => ({ + id: String(user.ooid), + idOriginal: String(user.ooid), + username: (users[id.slice(0, 32)] || {}).name || Messages.anonymous, indexUser: user.index, connectionId: user.netflux || Hash.createChannelId(), isCloseCoAuthoring: false, view: false })); - const myOOIndex = getNextUserIndex(); - - const me = [{ - id: String(myOOId), - idOriginal: String(myOOId), - username: metadataMgr.getUserData().name || Messages.anonymous, - indexUser: myOOIndex, - connectionId: metadataMgr.getNetfluxId() || Hash.createChannelId(), - isCloseCoAuthoring:false, - view: false - }]; - - - - + const participants = historyKeeper.concat(realParticipants); + console.log('XXX getParticipants end', participants); return { - index: myOOIndex, - list: p.filter(Boolean) + index: getMyOOIndex(), + list: participants, }; }; @@ -3469,7 +3466,7 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null if (content.ids) { handleNewIds(oldIds, content.ids); - oldIds = JSON.parse(JSON.stringify(content.ids)); + oldIds = structuredClone(content.ids); } if (content.locks) { handleNewLocks(oldLocks, content.locks); From 18b878bd9fc070cb99822cfcb77ae07819b044ce Mon Sep 17 00:00:00 2001 From: Wolfgang Ginolas Date: Wed, 11 Dec 2024 15:12:07 +0100 Subject: [PATCH 126/218] editing works --- www/common/onlyoffice/inner.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 3321d7edc..71c61bec2 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -230,6 +230,10 @@ define([ netflux: metadataMgr.getNetfluxId() }; + if (!myUniqueOOId) { + myUniqueOOId = String(myOOId) + myIndex; + } + content.latestIndexUser = myIndex; oldIds = structuredClone(ids); console.log('XXX setMyId end', { myOOId, myIndex, content: structuredClone(content) }); @@ -1035,13 +1039,17 @@ define([ var type = common.getMetadataMgr().getPrivateData().ooType; content.locks = content.locks || {}; var l = content.locks[id] || {}; + console.log('XXX getUserLock l', structuredClone(l)); if (type === "sheet" || forceArray) { - return Object.keys(l).map(function (uid) { return l[uid]; }); + const res = Object.keys(l).map(function (uid) { return l[uid]; }); + console.log('XXX getUserLock result', structuredClone(res)); + return res; } var res = {}; Object.keys(l).forEach(function (uid) { res[uid] = l[uid]; }); + console.log('XXX getUserLock result', structuredClone(res)); return res; }; var getLock = function () { @@ -1051,12 +1059,14 @@ define([ Object.keys(content.locks || {}).forEach(function (id) { Array.prototype.push.apply(locks, getUserLock(id)); }); + console.log('XXX getLock result', structuredClone(locks)); return locks; } locks = {}; Object.keys(content.locks || {}).forEach(function (id) { Util.extend(locks, getUserLock(id)); }); + console.log('XXX getLock result', structuredClone(locks)); return locks; }; From f8c57ff3e7ba7980f45f3f068155e49faf279809 Mon Sep 17 00:00:00 2001 From: Wolfgang Ginolas Date: Wed, 11 Dec 2024 16:11:37 +0100 Subject: [PATCH 127/218] Use random value for indexUser --- www/common/onlyoffice/inner.js | 98 +++++++--------------------------- 1 file changed, 20 insertions(+), 78 deletions(-) diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 71c61bec2..422f55427 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -69,6 +69,7 @@ define([ var PENDING_TIMEOUT = 30000; var CURRENT_VERSION = X2T.CURRENT_VERSION; const HISTORY_KEEPER_INDEX_USER = 1; + const READ_ONLY_INDEX_USER = 2; //var READONLY_REFRESH_TO = 15000; @@ -184,25 +185,12 @@ define([ }); }; - /** - * This function retrieves the highest `indexUser`/`index` value, when `latestIndexUser` is not set. - * This is case for older OO documents. - * - * @returns the higest `indexUser` value or `-Infinity` - */ - const getHighestLegacyIndexUser = function () { - const ids = content.ids || {}; - const indexes = Object.values(ids) - .map((user) => user.index) - .filter(Boolean); - return Math.max(...indexes); - }; - const getNextUserIndex = function () { - const latestIndexUser = content.latestIndexUser - ? content.latestIndexUser - : getHighestLegacyIndexUser(); - return Math.max(latestIndexUser, HISTORY_KEEPER_INDEX_USER) + 1; + let nextUserIndex; + do { + nextUserIndex = Util.createRandomInteger(); + } while (nextUserIndex === HISTORY_KEEPER_INDEX_USER || nextUserIndex === READ_ONLY_INDEX_USER); + return nextUserIndex; }; var setMyId = function () { @@ -234,7 +222,6 @@ define([ myUniqueOOId = String(myOOId) + myIndex; } - content.latestIndexUser = myIndex; oldIds = structuredClone(ids); console.log('XXX setMyId end', { myOOId, myIndex, content: structuredClone(content) }); APP.onLocal(); @@ -948,7 +935,7 @@ define([ const getMyOOIndex = function() { const user = findUserByOOId(myOOId); - return user.index; + return user ? user.index : READ_ONLY_INDEX_USER; }; const getParticipants = function () { @@ -966,15 +953,19 @@ define([ view: false }]; - const realParticipants = Object.entries(content.ids).map(([id, user]) => ({ - id: String(user.ooid), - idOriginal: String(user.ooid), - username: (users[id.slice(0, 32)] || {}).name || Messages.anonymous, - indexUser: user.index, - connectionId: user.netflux || Hash.createChannelId(), - isCloseCoAuthoring: false, - view: false - })); + const realParticipants = Object.entries(content.ids).map(([id, user]) => { + const nId = id.slice(0,32); + const username = (users[nId] || {}).name || Messages.anonymous; + return { + id: String(user.ooid), + idOriginal: String(user.ooid), + username, + indexUser: user.index, + connectionId: user.netflux || Hash.createChannelId(), + isCloseCoAuthoring: false, + view: false + }; + }); const participants = historyKeeper.concat(realParticipants); console.log('XXX getParticipants end', participants); @@ -985,55 +976,6 @@ define([ }; }; - // TODO remove me - var getParticipantsOld = function () { - var users = metadataMgr.getMetadata().users; - var i = 1; - var p = Object.keys(content.ids || {}).map(function (id) { - var nId = id.slice(0,32); - if (!users[nId]) { return; } - var ooId = content.ids[id].ooid; - var idx = content.ids[id].index; - if (!ooId || ooId === myOOId) { return; } - if (idx >= i) { i = idx + 1; } - return { - id: String(ooId) + idx, - idOriginal: String(ooId), - username: (users[nId] || {}).name || Messages.anonymous, - indexUser: idx, - connectionId: content.ids[id].netflux || Hash.createChannelId(), - isCloseCoAuthoring:false, - view: false - }; - }); - // Add an history keeper user to show that we're never alone - var hkId = Util.createRandomInteger(); - p.push({ - id: hkId, - idOriginal: String(hkId), - username: "History", - indexUser: i, - connectionId: Hash.createChannelId(), - isCloseCoAuthoring:false, - view: false - }); - const myOOIndex = getMyOOIndex(); - if (!myUniqueOOId) { myUniqueOOId = String(myOOId) + myOOIndex; } - p.push({ - id: String(myOOId), - idOriginal: String(myOOId), - username: metadataMgr.getUserData().name || Messages.anonymous, - indexUser: myOOIndex, - connectionId: metadataMgr.getNetfluxId() || Hash.createChannelId(), - isCloseCoAuthoring:false, - view: false - }); - return { - index: myOOIndex, - list: p.filter(Boolean) - }; - }; - // Get all existing locks var getUserLock = function (id, forceArray) { var type = common.getMetadataMgr().getPrivateData().ooType; From fe1c029188986492faabe2ccb87b834cf3bb38bf Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 11 Dec 2024 16:44:33 +0100 Subject: [PATCH 128/218] Fix 'Guest' users in OO --- www/common/onlyoffice/inner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 422f55427..8e67d9b44 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -957,7 +957,7 @@ define([ const nId = id.slice(0,32); const username = (users[nId] || {}).name || Messages.anonymous; return { - id: String(user.ooid), + id: String(user.ooid) + user.index, idOriginal: String(user.ooid), username, indexUser: user.index, From cd124a659e359c94c439096418cd531d3bd7c926 Mon Sep 17 00:00:00 2001 From: Wolfgang Ginolas Date: Wed, 11 Dec 2024 16:50:58 +0100 Subject: [PATCH 129/218] Remove debug messages --- www/common/onlyoffice/inner.js | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 8e67d9b44..07c2ad1e3 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -74,7 +74,6 @@ define([ //var READONLY_REFRESH_TO = 15000; var debug = function (x, type) { - console.log('XXX debug', type, x); if (!window.CP_DEV_MODE) { return; } console.debug(x, type); }; @@ -162,7 +161,6 @@ define([ Object.keys(ids).forEach(function (id) { var nId = id.slice(0,32); if (users.indexOf(nId) === -1) { - console.log('XXX deleteOffline', { nId, id }); delete ids[id]; } }); @@ -195,7 +193,6 @@ define([ var setMyId = function () { deleteOffline(); // Remove ids for users that have left the channel - console.log('XXX setMyId start', { content: structuredClone(content) }); const ids = content.ids; if (!myOOId) { myOOId = Util.createRandomInteger(); @@ -203,7 +200,6 @@ define([ var f = function (id) { return ids[id].ooid === myOOId; }; - // TODO Object.keys(ids) is incorrect here while (Object.keys(ids).some(f)) { myOOId = Util.createRandomInteger(); } @@ -223,7 +219,6 @@ define([ } oldIds = structuredClone(ids); - console.log('XXX setMyId end', { myOOId, myIndex, content: structuredClone(content) }); APP.onLocal(); }; @@ -940,7 +935,6 @@ define([ const getParticipants = function () { const users = metadataMgr.getMetadata().users; - console.log('XXX getParticipants', users); // Add an history keeper user to show that we're never alone var hkId = Util.createRandomInteger(); const historyKeeper = [{ @@ -968,7 +962,6 @@ define([ }); const participants = historyKeeper.concat(realParticipants); - console.log('XXX getParticipants end', participants); return { index: getMyOOIndex(), @@ -981,17 +974,13 @@ define([ var type = common.getMetadataMgr().getPrivateData().ooType; content.locks = content.locks || {}; var l = content.locks[id] || {}; - console.log('XXX getUserLock l', structuredClone(l)); if (type === "sheet" || forceArray) { - const res = Object.keys(l).map(function (uid) { return l[uid]; }); - console.log('XXX getUserLock result', structuredClone(res)); - return res; + return Object.keys(l).map(function (uid) { return l[uid]; }); } var res = {}; Object.keys(l).forEach(function (uid) { res[uid] = l[uid]; }); - console.log('XXX getUserLock result', structuredClone(res)); return res; }; var getLock = function () { @@ -1001,14 +990,12 @@ define([ Object.keys(content.locks || {}).forEach(function (id) { Array.prototype.push.apply(locks, getUserLock(id)); }); - console.log('XXX getLock result', structuredClone(locks)); return locks; } locks = {}; Object.keys(content.locks || {}).forEach(function (id) { Util.extend(locks, getUserLock(id)); }); - console.log('XXX getLock result', structuredClone(locks)); return locks; }; From 811ed1079210df24d5093b478be487e3fb45b56b Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 12 Dec 2024 14:04:40 +0100 Subject: [PATCH 130/218] Fix merge issue with translation keys --- customize.dist/messages.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/customize.dist/messages.js b/customize.dist/messages.js index dac93c936..bb2073466 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -137,8 +137,6 @@ define(req, function(AppConfig, Default, Language) { }; Messages.calendar_show = 'Show calendars'; // XXX Messages.calendar_hide = 'Hide calendars'; // XXX - Messages.admin_mfa_confirm_enable = "Are you sure you want to enable Multi-Factor Authentication?"; // XXX - Messages.admin_mfa_confirm_disable = "Are you sure you want to disable Multi-Factor Authentication?"; // XXX return Messages; }); From a1d6218d1f4c75af0658b5f0b3ba636da90a35e0 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 12 Dec 2024 14:09:44 +0100 Subject: [PATCH 131/218] Update expressjs --- package-lock.json | 20 ++++++++++++-------- package.json | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index f2c348c8a..7a13d510a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "croppie": "^2.5.0", "dragula": "3.7.2", "drawio": "github:cryptpad/drawio-npm#npm-21.8.2+5", - "express": "~4.21.1", + "express": "~4.21.2", "file-saver": "1.3.1", "fs-extra": "^7.0.0", "get-folder-size": "^2.0.1", @@ -2199,9 +2199,9 @@ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -2222,7 +2222,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -2237,6 +2237,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/encodeurl": { @@ -3845,9 +3849,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, "node_modules/picocolors": { "version": "1.1.0", diff --git a/package.json b/package.json index bc66cddf7..9ddd23123 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "croppie": "^2.5.0", "dragula": "3.7.2", "drawio": "github:cryptpad/drawio-npm#npm-21.8.2+5", - "express": "~4.21.1", + "express": "~4.21.2", "file-saver": "1.3.1", "fs-extra": "^7.0.0", "get-folder-size": "^2.0.1", From 0afcc2120889213d94c0f4ea11895e5c0d4d920c Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 12 Dec 2024 14:10:15 +0100 Subject: [PATCH 132/218] Update dependency --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7a13d510a..4c6655198 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3564,9 +3564,9 @@ "integrity": "sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==" }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true, "funding": [ { From a8c1d1e1b613cf91544db806c9fe9b30fff353db Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 12 Dec 2024 14:30:39 +0100 Subject: [PATCH 133/218] Remove hardcoded translations --- customize.dist/messages.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/customize.dist/messages.js b/customize.dist/messages.js index 9e218dc11..be65e98a9 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -135,13 +135,6 @@ define(req, function(AppConfig, Default, Language) { return text; } }; - Messages.form_passwordWarning = 'For Forms, you can only set the password during creation. It cannot be changed later.' // XXX - - Messages.form_passwordWarning = 'Please note that a Form password can only be set now at creation time and cannot be changed later.' // XXX - - Messages.fm_restoreMultipleDialog = "Are you sure you want to restore {0} files and/or folders to their previous locations?"; // XXX: new translation key - Messages.calendar_show = 'Show calendars'; // XXX - Messages.calendar_hide = 'Hide calendars'; // XXX return Messages; }); From 1d1c18c20ff2af7c4a92d1373c5333918e91b736 Mon Sep 17 00:00:00 2001 From: Weblate Date: Thu, 12 Dec 2024 14:41:58 +0100 Subject: [PATCH 134/218] Translated using Weblate (English) Currently translated at 100.0% (1787 of 1787 strings) Co-authored-by: Yann Flory Translate-URL: https://weblate.cryptpad.org/projects/cryptpad/app/en/ Translation: CryptPad/App --- www/common/translations/messages.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/www/common/translations/messages.json b/www/common/translations/messages.json index 548ce9200..fa940fb1f 100644 --- a/www/common/translations/messages.json +++ b/www/common/translations/messages.json @@ -1783,5 +1783,9 @@ "admin_onboardingDescPlaceholder": "Instance description text", "team_autoTrim": "Trimming team drive history... Please wait.", "admin_mfa_confirm_enable": "Are you sure you want to enable Multi-Factor Authentication?", - "admin_mfa_confirm_disable": "Are you sure you want to disable Multi-Factor Authentication?" + "admin_mfa_confirm_disable": "Are you sure you want to disable Multi-Factor Authentication?", + "form_passwordWarning": "Please note that a Form password can only be set now at creation time and cannot be changed later.", + "fm_restoreMultipleDialog": "Are you sure you want to restore {0} files and/or folders to their previous locations?", + "calendar_show": "Show calendars", + "calendar_hide": "Hide calendars" } From f71f8a573dd1b47de1a9c2ddbdf26e4b96b28436 Mon Sep 17 00:00:00 2001 From: Weblate Date: Thu, 12 Dec 2024 14:41:58 +0100 Subject: [PATCH 135/218] Translated using Weblate (German) Currently translated at 100.0% (1787 of 1787 strings) Co-authored-by: Fabrice Mouhartem Translate-URL: https://weblate.cryptpad.org/projects/cryptpad/app/de/ Translation: CryptPad/App --- www/common/translations/messages.de.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/www/common/translations/messages.de.json b/www/common/translations/messages.de.json index 81b14ccc1..d5efe86a3 100644 --- a/www/common/translations/messages.de.json +++ b/www/common/translations/messages.de.json @@ -1783,5 +1783,9 @@ "admin_onboardingNameHint": "Bitte wähle einen Namen, eine Beschreibung, eine Akzentfarbe und ein Logo (alle Angaben sind optional)", "team_autoTrim": "Verlauf des Team-Drives wird gelöscht... Bitte warten.", "admin_mfa_confirm_enable": "Bist du sicher, dass du die Multi-Faktor-Authentifizierung aktivieren möchtest?", - "admin_mfa_confirm_disable": "Bist du sicher, dass du die Multi-Faktor-Authentifizierung deaktivieren möchtest?" + "admin_mfa_confirm_disable": "Bist du sicher, dass du die Multi-Faktor-Authentifizierung deaktivieren möchtest?", + "fm_restoreMultipleDialog": "Bist du sicher, dass du {0} Dateien/Ordner zurück in den ursprünglichen Ordner verschieben möchtest?", + "calendar_show": "Kalender anzeigen", + "form_passwordWarning": "Bitte beachte, dass ein Formularpasswort nur zum Zeitpunkt der Erstellung festgelegt und später nicht mehr geändert werden kann.", + "calendar_hide": "Kalender verbergen" } From 6566ed2994be62c71c67284fde6ddfb87a7838ee Mon Sep 17 00:00:00 2001 From: Weblate Date: Thu, 12 Dec 2024 14:41:58 +0100 Subject: [PATCH 136/218] Translated using Weblate (French) Currently translated at 100.0% (1787 of 1787 strings) Co-authored-by: Fabrice Mouhartem Translate-URL: https://weblate.cryptpad.org/projects/cryptpad/app/fr/ Translation: CryptPad/App --- www/common/translations/messages.fr.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/www/common/translations/messages.fr.json b/www/common/translations/messages.fr.json index ad219842a..7b809d426 100644 --- a/www/common/translations/messages.fr.json +++ b/www/common/translations/messages.fr.json @@ -1783,5 +1783,9 @@ "install_notes": "
  • Créez votre premier compte administrateur·ice sur cette page. Les administrateur·ices peuvent paramétrer l'instance, ceci incluant les quotas de stockage, et ont accès aux outils de modération.
  • Votre mot de passe est la clé secrète qui chiffre l'ensemble de vos documents et vos privilèges d'administration sur l'instance. Si vous le perdez il n'est pas possible de récupérer vos données.
  • Si vous utilisez un ordinateur partagé, n'oubliez pas de vous déconnecter quand vous aurez terminé. Simplement fermer la fenêtre du navigateur web laisse votre compte exposé à des risques de sécurité.
", "team_autoTrim": "Suppression de l'historique du drive d'équipe... Veuillez patienter.", "admin_mfa_confirm_disable": "Êtes-vous sûr de vouloir désactiver l'authentification multi-facteur ?", - "admin_mfa_confirm_enable": "Êtes-vous sûr de vouloir activer l'authentification multi-facteur ?" + "admin_mfa_confirm_enable": "Êtes-vous sûr de vouloir activer l'authentification multi-facteur ?", + "fm_restoreMultipleDialog": "Êtes-vous sûr·e de vouloir restaurer {0} fichiers et/ou dossiers à leurs emplacements précédents ?", + "calendar_hide": "Cacher les calendriers", + "calendar_show": "Afficher les calendriers", + "form_passwordWarning": "Veuillez noter qu'un mot de passe pour Formulaire peut uniquement être spécifié lors de la création du document et ne peut pas être changé plus tard." } From 7277117c43174b2164bb0bd96d8b8006178b6f52 Mon Sep 17 00:00:00 2001 From: David Benque Date: Fri, 13 Dec 2024 13:27:34 +0000 Subject: [PATCH 137/218] Fix typo from #1662 --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 2b3b92cea..a37e767ab 100644 --- a/readme.md +++ b/readme.md @@ -24,7 +24,7 @@ Configuring CryptPad for production requires additional steps. Refer to our [adm ## Current version -The most recent version and all past release notes can be found on [releases page on GitHub](https://github.com/cryptpad/cryptpad/releases/). +The most recent version and all past release notes can be found on the [releases page on GitHub](https://github.com/cryptpad/cryptpad/releases/). ## Setup using Docker From bedab10578614b1fab6ac9a8dc1631cbc0677163 Mon Sep 17 00:00:00 2001 From: Weblate Date: Sun, 15 Dec 2024 17:59:29 +0100 Subject: [PATCH 138/218] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (1787 of 1787 strings) Co-authored-by: Subtext6676 Co-authored-by: Weblate Translate-URL: https://weblate.cryptpad.org/projects/cryptpad/app/zh_Hans/ Translation: CryptPad/App --- www/common/translations/messages.zh.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/www/common/translations/messages.zh.json b/www/common/translations/messages.zh.json index c25eb1c22..f611b25c8 100644 --- a/www/common/translations/messages.zh.json +++ b/www/common/translations/messages.zh.json @@ -1783,5 +1783,9 @@ "dph_tmp_pw": "此模板受新密码保护。从您的云盘打开它以输入新密码。", "duplicate": "重复", "admin_logoButton": "上传新的", - "install_notes": "
  • 在此页面上创建您的第一个管理员账号。管理员管理实例设置,包括存储配额,并有权访问审核工具。
  • 您的密码是加密此实例上所有文档和管理员权限的密钥。如果您丢失了密码,我们将无法恢复您的数据。
  • 如果您使用的是共享计算机,请记住在使用完后登出。只关闭浏览器窗口会暴露您的账号。
" + "install_notes": "
  • 在此页面上创建您的第一个管理员账号。管理员管理实例设置,包括存储配额,并有权访问审核工具。
  • 您的密码是加密此实例上所有文档和管理员权限的密钥。如果您丢失了密码,我们将无法恢复您的数据。
  • 如果您使用的是共享计算机,请记住在使用完后登出。只关闭浏览器窗口会暴露您的账号。
", + "fm_restoreMultipleDialog": "是否确定要将 {0} 个文件和/或文件夹还原到其以前的位置?", + "calendar_hide": "隐藏日历", + "form_passwordWarning": "请注意,表单密码只能在创建时设置,以后不能更改。", + "calendar_show": "显示日历" } From dfe4c59ac438e49c2c912d0044eb01acfe54688b Mon Sep 17 00:00:00 2001 From: Weblate Date: Sun, 15 Dec 2024 17:59:29 +0100 Subject: [PATCH 139/218] Translated using Weblate (Polish) Currently translated at 100.0% (1787 of 1787 strings) Co-authored-by: Magpie Translate-URL: https://weblate.cryptpad.org/projects/cryptpad/app/pl/ Translation: CryptPad/App --- www/common/translations/messages.pl.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/www/common/translations/messages.pl.json b/www/common/translations/messages.pl.json index 910973218..d978c0dd5 100644 --- a/www/common/translations/messages.pl.json +++ b/www/common/translations/messages.pl.json @@ -444,7 +444,7 @@ "support_formContentError": "Błąd: treść jest pusta", "support_formTitleError": "Błąd: tytuł jest pusty", "support_formButton": "Wyślij", - "support_formHint": "Użyj tego formularza, aby bezpiecznie skontaktować się z administratorami.
Proszę zauważyć, że niektóre kwestie/pytania mogą być już poruszone w Podręczniku Użytkownika CryptPad. Prosimy nie tworzyć nowego zgłoszenia, jeśli masz już otwarte zgłoszenie dotyczące tego samego problemu. Zamiast tego odpowiedz na oryginalną wiadomość, podając dodatkowe informacje.", + "support_formHint": "Użyj tego formularza, aby bezpiecznie skontaktować się z administratorami.
Zwróć uwagę, że niektóre kwestie/pytania mogą być już poruszone w Podręczniku Użytkownika CryptPad. Prosimy nie tworzyć nowego zgłoszenia, jeśli masz już otwarte zgłoszenie dotyczące tego samego problemu. Zamiast tego odpowiedz na oryginalną wiadomość, podając dodatkowe informacje.", "support_formTitle": "Nowe Zgłoszenie", "support_cat_new": "Nowe zgłoszenie", "support_disabledHint": "Ta instancja CryptPad nie jest jeszcze skonfigurowana do korzystania z formularza pomocy technicznej.", @@ -1783,5 +1783,9 @@ "admin_onboardingOptionsHint": "Zaznacz właściwą opcję dla Twojej instancji.
To ustawienie może być później zmienione w panelu administratora.", "team_autoTrim": "Zmniejszanie historii dysku zespołu... Proszę czekać.", "admin_mfa_confirm_enable": "Czy na pewno chcesz włączyć uwierzytelnianie wieloetapowe?", - "admin_mfa_confirm_disable": "Czy na pewno chcesz wyłączyć uwierzytelnianie wieloetapowe?" + "admin_mfa_confirm_disable": "Czy na pewno chcesz wyłączyć uwierzytelnianie wieloetapowe?", + "form_passwordWarning": "Zwróć uwagę, że hasło do formularza można ustawić tylko teraz, podczas tworzenia go, i nie może być ono zmienione później.", + "fm_restoreMultipleDialog": "Czy na pewni chcesz przywrócić {0} plików i/lub folderów do ich poprzedniej lokalizacji?", + "calendar_show": "Pokaż kalendarze", + "calendar_hide": "Ukryj kalendarze" } From 08dfdbda3c0a77530369027f9cb8397c55b3cf28 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 17 Dec 2024 12:09:05 +0100 Subject: [PATCH 140/218] Delete blob proof when associated blob doesn't exist anymore --- lib/eviction.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/eviction.js b/lib/eviction.js index b747ba99f..95f430cdf 100644 --- a/lib/eviction.js +++ b/lib/eviction.js @@ -669,6 +669,7 @@ module.exports = function (Env, cb) { if (item.mtime > inactiveTime) { return void next(); } nThen(function (w) { blobs.size(item.blobId, w(function (err, size) { + if (err && err === 'ENOENT') { return; } // XXX delete the proof if (err) { w.abort(); return void Log.error("EVICT_BLOB_LIST_PROOFS_ERROR", err, next); From 90c95beefee96de3ea61e112dfd5833e1ecc7c2b Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 17 Dec 2024 16:05:28 +0100 Subject: [PATCH 141/218] Fix issues --- lib/archive-account.js | 18 ++- lib/eviction.js | 59 ---------- lib/storage/blob.js | 245 +++++++++++++++------------------------ lib/workers/db-worker.js | 6 - 4 files changed, 107 insertions(+), 221 deletions(-) diff --git a/lib/archive-account.js b/lib/archive-account.js index 43ff1357e..44928f6be 100644 --- a/lib/archive-account.js +++ b/lib/archive-account.js @@ -13,6 +13,7 @@ const Metadata = require("./commands/metadata"); const Meta = require("./metadata"); const Logger = require("./log"); const plugins = require("./plugin-manager"); +const HK = require('./hk-util'); let SSOUtils = plugins.SSO && plugins.SSO.utils; @@ -53,7 +54,13 @@ const init = (cb) => { Env.computeMetadata = function (channel, cb) { const ref = {}; const lineHandler = Meta.createLineHandler(ref, (err) => { console.log(err); }); - return void Env.store.readChannelMetadata(channel, lineHandler, function (err) { + + let f = Env.store.readChannelMetadata; + if (channel.length === HK.BLOB_ID_LENGTH) { + f = Env.blobStore.readMetadata; + } + + return void f(channel, lineHandler, function (err) { if (err) { // stream errors? return void cb(err); @@ -132,9 +139,12 @@ COMMANDS.start = (edPublic, blockId, reason) => { n = n((w) => { // Blobs if (Env.blobStore.isFileId(chanId)) { - return void Env.blobStore.isOwnedBy(safeKey, chanId, w((err, owned) => { - if (err || !owned) { return; } - blobsToArchive.push(chanId); + return Env.computeMetadata(chanId, w((e, md) => { + if (e || !md) { return; } + if (md && md.owners + && md.owners.includes(edPublic)) { + blobsToArchive.push(chanId); + } })); } // Pads diff --git a/lib/eviction.js b/lib/eviction.js index 95f430cdf..8975e3fdd 100644 --- a/lib/eviction.js +++ b/lib/eviction.js @@ -642,64 +642,6 @@ module.exports = function (Env, cb) { })); }; - var archiveInactiveBlobProofs = function (w) { - // iterate over blob proofs and remove them - // if they don't correspond to a pinned or active file - var removed = 0; - var total = 0; - - Log.info("EVICT_ARCHIVE_INACTIVE_BLOB_PROOFS_START", {}); - blobs.list.proofs(function (err, item, next) { - next = Util.mkAsync(next, THROTTLE_FACTOR); - if (err) { - return void Log.error("EVICT_BLOB_LIST_PROOFS_ERROR", err, next); - } - if (!item) { - return void Log.error('EVICT_BLOB_LIST_PROOFS_NO_ITEM', item, next); - } - total++; - - if (total % PROGRESS_FACTOR === 0) { - Log.info('EVICT_BLOB_PROOF_PROGRESS', { - proofs: total, - }); - } - - if (pinnedDocs.test(item.blobId)) { return void next(); } - if (item.mtime > inactiveTime) { return void next(); } - nThen(function (w) { - blobs.size(item.blobId, w(function (err, size) { - if (err && err === 'ENOENT') { return; } // XXX delete the proof - if (err) { - w.abort(); - return void Log.error("EVICT_BLOB_LIST_PROOFS_ERROR", err, next); - } - if (size !== 0) { - w.abort(); - next(); - } - })); - }).nThen(function () { - if (Env.DRY_RUN) { - removed++; - return void Log.info("EVICT_BLOB_PROOF_LONELY_DRY_RUN", item, next); - } - blobs.remove.proof(item.safeKey, item.blobId, function (err) { - if (err) { - return Log.error("EVICT_BLOB_PROOF_LONELY_ERROR", item, next); - } - removed++; - return Log.info("EVICT_BLOB_PROOF_LONELY", item, next); - }); - }); - }, w(function () { - Log.info("EVICT_BLOB_PROOFS_REMOVED", { - removed, - total, - }, w()); - })); - }; - var archiveInactiveChannels = function (w) { var channels = 0; var archived = 0; @@ -802,7 +744,6 @@ module.exports = function (Env, cb) { // (documents which are not in either bloom filter) .nThen(archiveInactiveBlobs) - .nThen(archiveInactiveBlobProofs) .nThen(archiveInactiveChannels) .nThen(function () { var runningTime = report.runningTime = msSinceStart(); diff --git a/lib/storage/blob.js b/lib/storage/blob.js index 8c6b9b2be..629dfd9d3 100644 --- a/lib/storage/blob.js +++ b/lib/storage/blob.js @@ -54,23 +54,10 @@ var makeStagePath = function (Env, safeKey) { return Path.join(Env.blobStagingPath, safeKey.slice(0, 2), safeKey); }; -// /blob//// -var makeProofPath = function (Env, safeKey, blobId) { - return Path.join(Env.blobPath, safeKey.slice(0, 3), safeKey, blobId.slice(0, 2), blobId); -}; - var mkPlaceholderPath = function (Env, blobId) { return makeBlobPath(Env, blobId) + '.placeholder'; }; -var parseProofPath = function (path) { - var parts = path.split('/'); - return { - blobId: parts[parts.length -1], - safeKey: parts[parts.length - 3], - }; -}; - // Placeholder for deleted files var addPlaceholder = function (Env, blobId, reason, cb) { if (!reason) { return cb(); } @@ -239,6 +226,11 @@ var archiveMetadata = (Env, blobId, cb) => { // if we fail to delete the metadata file, it can still be removed later by the eviction script Fse.move(path, archivePath, { overwrite: true }, cb); }; +var restoreActivity = function (Env, blobId, cb) { + var path = mkMetadataPath(Env, blobId); + var archivePath = prependArchive(Env, path); + Fse.move(archivePath, path, cb); +}; var readBlobMetadata = function (env, blobId, handler, _cb) { var metadataPath = mkMetadataPath(env, blobId); var stream = Fs.createReadStream(metadataPath, {start: 0}); @@ -403,7 +395,6 @@ var owned_upload_complete = function (Env, safeKey, id, cb) { var finalPath = makeBlobPath(Env, id); let unsafeKey = unescapeKeyCharacters(safeKey); - //var finalOwnPath = makeProofPath(Env, safeKey, id); // the user wants to move it into blob and create a metadata log with an owner @@ -465,19 +456,6 @@ var remove = function (Env, blobId, cb) { clearActivity(Env, blobId, () => {}); }; -// removeProof -var removeProof = function (Env, safeKey, blobId, cb) { - var proofPath = makeProofPath(Env, safeKey, blobId); - Fs.unlink(proofPath, cb); -}; - -// isOwnedBy(id, safeKey) -var isOwnedBy = function (Env, safeKey, blobId, cb) { - var proofPath = makeProofPath(Env, safeKey, blobId); - isFile(proofPath, cb); -}; - - // archiveBlob var archiveBlob = function (Env, blobId, reason, cb) { var blobPath = makeBlobPath(Env, blobId); @@ -499,29 +477,11 @@ var restoreBlob = function (Env, blobId, cb) { var blobPath = makeBlobPath(Env, blobId); var archivePath = prependArchive(Env, blobPath); Fse.move(archivePath, blobPath, cb); + restoreMetadata(Env, blobId, () => {}); restoreActivity(Env, blobId, () => {}); clearPlaceholder(Env, blobId, () => {}); }; -// archiveProof -var archiveProof = function (Env, safeKey, blobId, cb) { - var proofPath = makeProofPath(Env, safeKey, blobId); - var archivePath = prependArchive(Env, proofPath); - Fse.move(proofPath, archivePath, { overwrite: true }, cb); -}; - -var removeArchivedProof = function (Env, safeKey, blobId, cb) { - var archivedPath = prependArchive(Env, makeProofPath(Env, safeKey, blobId)); - Fs.unlink(archivedPath, cb); -}; - -// restoreProof -var restoreProof = function (Env, safeKey, blobId, cb) { - var proofPath = makeProofPath(Env, safeKey, blobId); - var archivePath = prependArchive(Env, proofPath); - Fse.move(archivePath, proofPath, cb); -}; - var makeWalker = function (n, handleChild, done) { if (!n || typeof(n) !== 'number' || n < 2) { n = 2; } @@ -553,7 +513,7 @@ var makeWalker = function (n, handleChild, done) { } if (!stats.isDirectory()) { w.abort(); - if (/\.activity$/.test(path)) { + if (/\.activity$/.test(path)) { // NOTE: some activity files were created for deleted blobs due to // a bug. We're going to detect them here in order to be able to clean // them. @@ -586,46 +546,6 @@ var makeWalker = function (n, handleChild, done) { return recurse; }; -var listProofs = function (root, handler, cb) { - Fs.readdir(root, function (err, dir) { - if (err) { return void cb(err); } - - var walk = makeWalker(20, function (err, path, next, loneActivity) { - if (loneActivity) { return void next(); } - // path is the path to a child node on the filesystem - - // next handles the next job in a queue - - // iterate over proofs - // check for presence of corresponding files - Fs.stat(path, function (err, stats) { - if (err) { - return void handler(err, void 0, next); - } - - var parsed = parseProofPath(path); - handler(void 0, { - path: path, - blobId: parsed.blobId, - safeKey: parsed.safeKey, - atime: stats.atime, - ctime: stats.ctime, - mtime: stats.mtime, - }, next); - }); - }, function () { - // called when there are no more directories or children to process - cb(); - }); - - dir.forEach(function (d) { - // ignore directories that aren't 3 characters long... - if (d.length !== 3) { return; } - walk(Path.join(root, d)); - }); - }); -}; - var getActivityStat = function (path, base, cb) { var suffix = base ? '' : '.activity'; Fs.stat(path+suffix, function (err, stats) { @@ -633,32 +553,91 @@ var getActivityStat = function (path, base, cb) { cb(err, stats); }); }; -var listBlobs = function (root, handler, cb) { - // iterate over files - Fs.readdir(root, function (err, dir) { - if (err) { return void cb(err); } - var walk = makeWalker(20, function (err, path, next, loneActivity) { - if (loneActivity) { return void next(); } - getActivityStat(path, false, function (err, stats) { - if (err) { - return void handler(err, void 0, next); - } - handler(void 0, { - blobId: Path.basename(path), - atime: stats.atime, - ctime: stats.ctime, - mtime: stats.mtime, - }, next); - }); - }, function () { - cb(); - }); +let blobRegex = /^[0-9a-fA-F]{48}(\.metadata)*(\.ndjson)*$/; +var listBlobs = function (root, handler, fast, cb) { + var dirList = []; - dir.forEach(function (d) { - if (d.length !== 2) { return; } - walk(Path.join(root, d)); + nThen(function (w) { + // the root of your datastore contains nested directories... + Fs.readdir(root, w(function (err, list) { + if (err) { + w.abort(); + // TODO check if we normally return strings or errors + return void cb(err); + } + dirList = list; + })); + }).nThen(function (waitFor) { + // search inside the nested directories + // stream it so you don't put unnecessary data in memory + var n = nThen; + dirList.forEach(function (dir) { + if (dir.length !== 2) { return; } + // Handle one directory at a time to save some memory + n = n(function (w) { + // do twenty things at a time + var sema = Semaphore.create(20); + var nestedDirPath = Path.join(root, dir); + Fs.readdir(nestedDirPath, w(function (err, list) { + if (err) { return void handler(err); } // Is this correct? + list.forEach(function (item) { + // ignore hidden files + if (/^\./.test(item)) { return; } + // ignore anything that isn't channel or metadata + if (!blobRegex.test(item)) { return; } + + var isLonelyMetadata = false; + var blobName; + var metadataName; + + // if the current file is not the channel data, then it must be metadata + if (!/^[0-9a-fA-F]{48}$/.test(item)) { + metadataName = item; + blobName = item.replace(/\.metadata\.ndjson/, ''); + // check if blob already exists + if (list.indexOf(blobName) !== -1) { return; } + // otherwise set a flag indicating that we should + // handle the metadata on its own + isLonelyMetadata = true; + } else { + blobName = item; + metadataName = blobName + '.metadata.ndjson'; + } + if (blobName.length !== 48) { return; } + + sema.take(function (give) { + var next = w(give()); + + if (fast) { + return void handler(void 0, { + blobId: blobName + }, next); + } + + var filePath = Path.join(nestedDirPath, blobName); + if (isLonelyMetadata) { + // Set time to 0 to delete this + // lonely metadata file + return void handler(void 0, { + blobId: blobName, + mtime: 0, + atime: 0, + ctime: 0 + }, next); + } + return void getActivityStat(filePath, false, (err, data) => { + data.blobId = blobName; + handler(err, data, next); + }); + }); + }); + })); + }).nThen; }); + n(waitFor()); + }).nThen(function () { + cb(); }); }; @@ -741,12 +720,6 @@ BlobStore.create = function (config, _cb) { upload_cancel(Env, safeKey, fileSize, cb); }, - isOwnedBy: function (safeKey, blobId, _cb) { - var cb = Util.once(Util.mkAsync(_cb)); - if (!isValidSafeKey(safeKey)) { return void cb('INVALID_SAFEKEY'); } - isOwnedBy(Env, safeKey, blobId, cb); - }, - readMetadata: (blobId, handler, cb) => { if (!isValidId(blobId)) { return void cb("INVALID_ID"); } readBlobMetadata(Env, blobId, handler, cb); @@ -762,24 +735,12 @@ BlobStore.create = function (config, _cb) { if (!isValidId(blobId)) { return void cb("INVALID_ID"); } remove(Env, blobId, cb); }, - proof: function (safeKey, blobId, _cb) { - var cb = Util.once(Util.mkAsync(_cb)); - if (!isValidSafeKey(safeKey)) { return void cb('INVALID_SAFEKEY'); } - if (!isValidId(blobId)) { return void cb("INVALID_ID"); } - removeProof(Env, safeKey, blobId, cb); - }, archived: { blob: function (blobId, _cb) { var cb = Util.once(Util.mkAsync(_cb)); if (!isValidId(blobId)) { return void cb("INVALID_ID"); } removeArchivedBlob(Env, blobId, cb); }, - proof: function (safeKey, blobId, _cb) { - var cb = Util.once(Util.mkAsync(_cb)); - if (!isValidSafeKey(safeKey)) { return void cb('INVALID_SAFEKEY'); } - if (!isValidId(blobId)) { return void cb("INVALID_ID"); } - removeArchivedProof(Env, safeKey, blobId, cb); - }, }, loneActivity: function (_cb) { var cb = Util.once(Util.mkAsync(_cb)); @@ -793,12 +754,6 @@ BlobStore.create = function (config, _cb) { if (!isValidId(blobId)) { return void cb("INVALID_ID"); } archiveBlob(Env, blobId, reason, cb); }, - proof: function (safeKey, blobId, _cb) { - var cb = Util.once(Util.mkAsync(_cb)); - if (!isValidSafeKey(safeKey)) { return void cb('INVALID_SAFEKEY'); } - if (!isValidId(blobId)) { return void cb("INVALID_ID"); } - archiveProof(Env, safeKey, blobId, cb); - }, }, restore: { @@ -807,12 +762,6 @@ BlobStore.create = function (config, _cb) { if (!isValidId(blobId)) { return void cb("INVALID_ID"); } restoreBlob(Env, blobId, cb); }, - proof: function (safeKey, blobId, _cb) { - var cb = Util.once(Util.mkAsync(_cb)); - if (!isValidSafeKey(safeKey)) { return void cb('INVALID_SAFEKEY'); } - if (!isValidId(blobId)) { return void cb("INVALID_ID"); } - restoreProof(Env, safeKey, blobId, cb); - }, }, isBlobAvailable: function (blobId, _cb) { @@ -865,22 +814,14 @@ BlobStore.create = function (config, _cb) { }, list: { - blobs: function (handler, _cb) { + blobs: function (handler, _cb, fast) { var cb = Util.once(Util.mkAsync(_cb)); - listBlobs(Env.blobPath, handler, cb); - }, - proofs: function (handler, _cb) { - var cb = Util.once(Util.mkAsync(_cb)); - listProofs(Env.blobPath, handler, cb); + listBlobs(Env.blobPath, handler, fast, cb); }, archived: { - proofs: function (handler, _cb) { + blobs: function (handler, _cb, fast) { var cb = Util.once(Util.mkAsync(_cb)); - listProofs(prependArchive(Env, Env.blobPath), handler, cb); - }, - blobs: function (handler, _cb) { - var cb = Util.once(Util.mkAsync(_cb)); - listBlobs(prependArchive(Env, Env.blobPath), handler, cb); + listBlobs(prependArchive(Env, Env.blobPath), handler, fast, cb); }, } }, diff --git a/lib/workers/db-worker.js b/lib/workers/db-worker.js index ddd49ccc0..d1a0fe962 100644 --- a/lib/workers/db-worker.js +++ b/lib/workers/db-worker.js @@ -570,12 +570,6 @@ const removeOwnedBlob = function (data, cb) { nThen(function (w) { // check if you have permissions - blobStore.isOwnedBy(safeKey, blobId, w(function (err, owned) { - if (err || !owned) { - w.abort(); - return void cb("INSUFFICIENT_PERMISSIONS"); - } - })); computeMetadata({channel: blobId}, w((err, meta) => { if (err || !meta) { w.abort(); From 1e2f57a28f30dfc1280cc443b355534bf1391ddf Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 17 Dec 2024 17:03:27 +0100 Subject: [PATCH 142/218] Blob metadata --- lib/eviction.js | 85 +++++++++++++++++---------------------------- lib/storage/blob.js | 9 +++++ 2 files changed, 40 insertions(+), 54 deletions(-) diff --git a/lib/eviction.js b/lib/eviction.js index 8975e3fdd..b75fc78a6 100644 --- a/lib/eviction.js +++ b/lib/eviction.js @@ -51,7 +51,6 @@ var evictArchived = function (Env, cb) { var report = { // archivedChannelsRemoved, // archivedAccountsRemoved, - // archivedBlobProofsRemoved, // archivedBlobsRemoved, // totalChannels, @@ -237,37 +236,6 @@ var evictArchived = function (Env, cb) { store.listArchivedChannels(handler, w(done)); }; - var removeArchivedBlobProofs = function (w) { - if (typeof(Env.archiveRetentionTime) !== "number") { return; } - // Iterate over archive blob ownership proofs and remove them - // if they are older than the specified retention time - var removed = 0; - blobs.list.archived.proofs(function (err, item, next) { - next = Util.mkAsync(next, THROTTLE_FACTOR); - if (err) { - Log.error("EVICT_BLOB_LIST_ARCHIVED_PROOF_ERROR", err); - return void next(); - } - if (item && item.ctime > retentionTime) { return void next(); } - if (Env.DRY_RUN) { - removed++; - return void Log.info("EVICT_ARCHIVED_BLOB_PROOF_DRY_RUN", item, next); - } - blobs.remove.archived.proof(item.safeKey, item.blobId, (function (err) { - if (err) { - Log.error("EVICT_ARCHIVED_BLOB_PROOF_ERROR", item); - return void next(); - } - Log.info("EVICT_ARCHIVED_BLOB_PROOF", item); - removed++; - next(); - })); - }, w(function () { - report.archivedBlobProofsRemoved = removed; - Log.info('EVICT_ARCHIVED_BLOB_PROOFS_REMOVED', removed); - })); - }; - var removeArchivedBlobs = function (w) { if (typeof(Env.archiveRetentionTime) !== "number") { return; } // Iterate over archived blobs and remove them @@ -303,7 +271,6 @@ var evictArchived = function (Env, cb) { nThen(loadStorage) .nThen(migrateIncorrectBlobs) .nThen(removeArchivedChannels) - .nThen(removeArchivedBlobProofs) .nThen(removeArchivedBlobs) .nThen(function () { cb(void 0, report); @@ -315,7 +282,6 @@ module.exports = function (Env, cb) { var report = { // archivedChannelsRemoved, // archivedAccountsRemoved, - // archivedBlobProofsRemoved, // archivedBlobsRemoved, // totalChannels, @@ -612,34 +578,45 @@ module.exports = function (Env, cb) { if (pinnedDocs.test(item.blobId)) { return void next(); } if (activeDocs.test(item.blobId)) { return void next(); } - // This seems redundant because we're already checking the bloom filter - // but we can't implement a 'fast mode' for the iterator - // unless we address this race condition with this last-minute double-check - if (item.mtime > inactiveTime) { return void next(); } - - if (Env.DRY_RUN) { - removed++; - return void Log.info("EVICT_ARCHIVE_BLOB_DRY_RUN", { - item: item, - }, next); - } - blobs.archive.blob(item.blobId, 'INACTIVE', function (err) { - if (err) { - return Log.error("EVICT_ARCHIVE_BLOB_ERROR", { - error: err, + // NOTE: fast mode allows us to skip getStats for + // the pinned and active channels + nThen(function (w) { + // double check that the channel really is inactive before archiving it + // because it might have been created after the initial activity scan + blobs.getStats(item.blobId, w(function (err, newerItem) { + if (err) { return; } + if (newerItem && getNewestTime(newerItem) > retentionTime) { + // it's actually active, so don't archive it. + w.abort(); + cb(); + } + // else fall through to the archival + })); + }).nThen(function () { + if (Env.DRY_RUN) { + removed++; + return void Log.info("EVICT_ARCHIVE_BLOB_DRY_RUN", { item: item, }, next); } - removed++; - Log.info("EVICT_ARCHIVE_BLOB", { - item: item, - }, next); + blobs.archive.blob(item.blobId, 'INACTIVE', function (err) { + if (err) { + return Log.error("EVICT_ARCHIVE_BLOB_ERROR", { + error: err, + item: item, + }, next); + } + removed++; + Log.info("EVICT_ARCHIVE_BLOB", { + item: item, + }, next); + }); }); }, w(function () { report.totalBlobs = total; report.activeBlobs = total - removed; Log.info('EVICT_BLOBS_REMOVED', removed, w()); - })); + }), true); }; var archiveInactiveChannels = function (w) { diff --git a/lib/storage/blob.js b/lib/storage/blob.js index 629dfd9d3..86b3fbeeb 100644 --- a/lib/storage/blob.js +++ b/lib/storage/blob.js @@ -553,6 +553,10 @@ var getActivityStat = function (path, base, cb) { cb(err, stats); }); }; +var getStats = function (Env, blobId, cb) { + var path = makeBlobPath(Env, blobId); + getActivityStat(path, false, cb); +}; let blobRegex = /^[0-9a-fA-F]{48}(\.metadata)*(\.ndjson)*$/; var listBlobs = function (root, handler, fast, cb) { @@ -812,6 +816,11 @@ BlobStore.create = function (config, _cb) { if (!isValidId(id)) { return void cb("INVALID_ID"); } getActivity(Env, id, cb); }, + getStats: function (id, _cb) { + var cb = Util.once(Util.mkAsync(_cb)); + if (!isValidId(id)) { return void cb("INVALID_ID"); } + getStats(Env, id, cb); + }, list: { blobs: function (handler, _cb, fast) { From 57552d0104dcc0f2123c4dccbe78560d9722cb6d Mon Sep 17 00:00:00 2001 From: David Benque Date: Tue, 17 Dec 2024 16:29:17 +0000 Subject: [PATCH 143/218] Update changelog --- CHANGELOG.md | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78d1e234d..60f502ae6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,71 @@ SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and cont SPDX-License-Identifier: AGPL-3.0-or-later --> + +# ❄️ Winter release (2024.12.0) + +## Goals + +This version delivers fixes and improvements across CryptPad. We are particularly happy to release a fix of our OnlyOffice integration that could address long-standing issues with documents becoming corrupted. If confirmed at scale, this fix could dramatically improve the use of OnlyOffice apps in CryptPad. + +## Improvements + +- OnlyOffice integration + - Fix bug resulting in corrupted documents [#1736](https://github.com/cryptpad/cryptpad/pull/1736) +- Drive + - Links included in Drive exports [#1695](https://github.com/cryptpad/cryptpad/pull/1695) +- Rich Text + - Formatted tables and strikethrough text in Pad .md exports [#1720](https://github.com/cryptpad/cryptpad/pull/1720) +- Forms + - Form password warning [#1690](https://github.com/cryptpad/cryptpad/pull/1690) +- Performance improvements (1/3) to example-advanced.nginx.conf [#1709](https://github.com/cryptpad/cryptpad/pull/1709) +- Enable toggle in and out of calendars on small screens [#1584](https://github.com/cryptpad/cryptpad/pull/1584) + +## Fixes + +- Accessibility + - Kanban Focus order fix [#1708](https://github.com/cryptpad/cryptpad/pull/1708) + - Change iframe title [#1706](https://github.com/cryptpad/cryptpad/pull/1706) + - Fix keyboard trap inside Form description [#1672](https://github.com/cryptpad/cryptpad/pull/1672) + - Disable arrow key navigation in the drive while modal is active [#1669](https://github.com/cryptpad/cryptpad/pull/1669) + - Simulate click action for keyboard users in Ctrl+E modal [#1726](https://github.com/cryptpad/cryptpad/pull/1726) +- Drive + - Prevent links in trash from disappearing after drive reload [#1697](https://github.com/cryptpad/cryptpad/pull/1697) + - Restore multiple files/directories [#1692](https://github.com/cryptpad/cryptpad/pull/1692) + - Stop selection of all other trashed files when restoring single file [#1681](https://github.com/cryptpad/cryptpad/pull/1681) +- Notifications + - Calendar reminders in notification panel [#1721](https://github.com/cryptpad/cryptpad/pull/1721) + - Add Notifications padding [#1688](https://github.com/cryptpad/cryptpad/pull/1688) + - Notification fixes [#1674](https://github.com/cryptpad/cryptpad/pull/1674) +- Forms + - Fix some form storage-related bugs [#1723](https://github.com/cryptpad/cryptpad/pull/1723) + - Fix spacing issues in Forms [#1682](https://github.com/cryptpad/cryptpad/pull/1682) + - Fix padding in form questions [#1670](https://github.com/cryptpad/cryptpad/pull/1670) +- Helpdesk + - Fix "Closed" support tickets remaining in Inbox [#1719](https://github.com/cryptpad/cryptpad/pull/1719) +- 'Request edit' button [#1680](https://github.com/cryptpad/cryptpad/pull/1680) +- Fix example-code-typo [#1703](https://github.com/cryptpad/cryptpad/pull/1703) + +## Upgrade notes + +If you are upgrading from a version older than `2024.9.1` please read the upgrade notes of all versions between yours and `2024.9.1` to avoid configuration issues. + +To upgrade: + +1. Stop your server +2. Get the latest code with git + +```bash +git fetch origin --tags +git checkout 2024.12.0 +npm ci +npm run install:components +``` + +3. Restart your server +4. Review your instance's checkup page to ensure that you are passing all tests + + # Autumn release (2024.9.0) ## Goals From c58bee84033422d2bae2a6e0d106a6ff23a22e9a Mon Sep 17 00:00:00 2001 From: Jeremy Fleischman Date: Fri, 13 Dec 2024 23:43:02 -0800 Subject: [PATCH 144/218] 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 337e2ba589fb79908e3c612469cc397e76f15c7f Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 2 Jan 2025 10:59:37 +0100 Subject: [PATCH 145/218] Fix support issue --- www/common/outer/support.js | 2 ++ www/support/ui.js | 1 + 2 files changed, 3 insertions(+) diff --git a/www/common/outer/support.js b/www/common/outer/support.js index 912fa5d23..20ee7200d 100644 --- a/www/common/outer/support.js +++ b/www/common/outer/support.js @@ -104,6 +104,7 @@ define([ msg = JSON.parse(msg); } catch (e) { console.error(e); + return; // Don't show "undefined" messages } msg.time = time; if (author) { msg.author = author; } @@ -232,6 +233,7 @@ define([ var anonRpc = Util.find(ctx, [ 'store', 'anon_rpc' ]); if (!mailbox) { return void cb('E_NOT_READY'); } if (!anonRpc) { return void cb("anonymous rpc session not ready"); } + if (!data?.ticket) { return void cb('E_NO_DATA'); } var theirPublic, myCurve, notifKey; var time; nThen((waitFor) => { diff --git a/www/support/ui.js b/www/support/ui.js index 45dc74a44..b9e377c26 100644 --- a/www/support/ui.js +++ b/www/support/ui.js @@ -162,6 +162,7 @@ define([ var makeForm = function (ctx, opts, cb) { let { oldData, recorded, title, hideNotice } = opts || {}; var button; + cb = Util.once(cb); if (typeof(cb) === "function") { button = h('button.btn.btn-primary.cp-support-list-send', Messages.contacts_send); From ededf142d93b4b959b527e2c11a4c8848b7201d4 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 6 Jan 2025 16:52:59 +0100 Subject: [PATCH 146/218] Blob metadata and migration --- lib/storage/blob.js | 8 +- scripts/migrations/migrate-blob-proofs.js | 164 ++++++++++++++++++++++ 2 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 scripts/migrations/migrate-blob-proofs.js diff --git a/lib/storage/blob.js b/lib/storage/blob.js index 86b3fbeeb..63932a2ce 100644 --- a/lib/storage/blob.js +++ b/lib/storage/blob.js @@ -226,7 +226,7 @@ var archiveMetadata = (Env, blobId, cb) => { // if we fail to delete the metadata file, it can still be removed later by the eviction script Fse.move(path, archivePath, { overwrite: true }, cb); }; -var restoreActivity = function (Env, blobId, cb) { +var restoreMetadata = function (Env, blobId, cb) { var path = mkMetadataPath(Env, blobId); var archivePath = prependArchive(Env, path); Fse.move(archivePath, path, cb); @@ -732,6 +732,12 @@ BlobStore.create = function (config, _cb) { if (!isValidId(blobId)) { return void cb("INVALID_ID"); } writeMetadata(Env, blobId, data, cb); }, + hasMetadata: (blobId, _cb) => { + var cb = Util.once(Util.mkAsync(_cb)); + if (!isValidId(blobId)) { return void cb("INVALID_ID"); } + var path = mkMetadataPath(Env, blobId); + isFile(path, cb); + }, remove: { blob: function (blobId, _cb) { diff --git a/scripts/migrations/migrate-blob-proofs.js b/scripts/migrations/migrate-blob-proofs.js new file mode 100644 index 000000000..8c977693c --- /dev/null +++ b/scripts/migrations/migrate-blob-proofs.js @@ -0,0 +1,164 @@ +// SPDX-FileCopyrightText: 2025 XWiki CryptPad Team and contributors +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +const Path = require('node:path'); +const nThen = require("nthen"); +const Semaphore = require("saferphore"); +const Logger = require("../../lib/log"); +const config = require("../../lib/load-config"); +const BlobStorage = require("../../lib/storage/blob"); +const Fs = require('node:fs'); + + +const blobPath = config.blobPath || './blob'; +let Log = {}; + +// XXX NOTE: in cleaning mode, we DON'T migrate +// (we suppose data has already been migrated) +const DRY_RUN = true; +const CLEAN_OLD = false; + +const start = (clean) => { + let dirList = []; + let blobStore; + nThen(w => { + Logger.create(config, w(function (_log) { + Log = _log; + })); + }).nThen(w => { + config.getSession = function () {}; + BlobStorage.create(config, w(function (err, _store) { + if (err) { + w.abort(); + return void Log.error("ERR_BLOB_STORE", err); + } + blobStore = _store; + })); + }).nThen(w => { + Fs.readdir(blobPath, w((err, list) => { + if (err) { + w.abort(); + return void Log.error("ERR_READING_ROOT", err); + } + dirList = list; + })); + }).nThen(() => { + let n = nThen; + dirList.forEach(dir => { + if (dir.length !== 3) { return; } + // ./blob/abc + const nestedDirPath = Path.join(blobPath, dir); + + if (clean) { + n = n(ww => { + Log.info("REMOVING_DIR", nestedDirPath); + if (DRY_RUN) { return; } + Fs.rm(nestedDirPath, { + recursive: true, force: true + }, ww(err => { + if (err) { + Log.error("ERR_REMOVE_DIR", { + path: nestedDirPath, + err + }); + } + })); + }).nThen; + return; + } + + n = n(w => { + // One user at a time + const sema = Semaphore.create(1); + let nestedDirList = []; + nThen(ww => { + Fs.readdir(nestedDirPath, ww((err, list) => { + if (err) { + w.abort(); + ww.abort(); + return Log.error("ERR_READING_DIR", { + path: nestedDirPath, + err + }); + } + nestedDirList = list; + })); + }).nThen(ww => { + nestedDirList.forEach(key => { + // ./blob/abc/abcdefg... + const keyPath = Path.join(nestedDirPath, key); + sema.take(give => { + let edPublic = key.replace(/\-/g, '/'); + let md = JSON.stringify({ owners: [edPublic] }); + Log.info("START_USER", edPublic); + Fs.readdir(keyPath, ww((err, list) => { + if (err) { + w.abort(); + ww.abort(); + return Log.error("ERR_READING_DIR", { + path: keyPath, + err + }); + } + let blobs = [] + nThen(www => { + list.forEach(dir => { + // ./blob/abc/abcdefg.../01 + const path = Path.join(keyPath, dir); + Fs.readdir(path, www((err, blobsList) => { + if (err) { + w.abort(); + ww.abort(); + www.abort(); + return Log.error("ERR_READING_DIR", { + path, err + }); + } + Array.prototype.push.apply(blobs, blobsList); + })); + }); + }).nThen(www => { + // migrate 20 blobs at a time for a given user + const sema = Semaphore.create(20); + blobs.forEach(blobId => { + sema.take(ggive => { + blobStore.isBlobAvailable(blobId, www((err, blobExists) => { + blobStore.hasMetadata(blobId, www((err, exists) => { + // If blob is not available or metadata already + // exists, don't write md file + if (!blobExists || exists) { return void ggive(); } + Log.info('WRITE_METADATA', blobId); + if (DRY_RUN) { return void ggive(); } + blobStore.writeMetadata(blobId, md, www(e => { + if (e) { + w.abort(); + ww.abort(); + www.abort(); + return Log.error("ERR_WRITING_MD", { blobId }); + } + ggive(); + })); + + })); + })); + }) + }); + }).nThen(ww(give(() => { + Log.info("END_USER", edPublic); + }))); + })); + }); + + }); + }).nThen(w()); + }).nThen; + }); + n(() => { + Log.info("DONE"); + process.exit(0); + }); + }); +}; + +start(CLEAN_OLD); From 267f6c56d48b583aa4b0a73b77a8f8012e0c9f25 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 10 Jan 2025 15:49:31 +0100 Subject: [PATCH 147/218] User stats script --- lib/pins.js | 9 ++- scripts/user-statistics.js | 133 +++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 scripts/user-statistics.js diff --git a/lib/pins.js b/lib/pins.js index 2201e561c..d2504077f 100644 --- a/lib/pins.js +++ b/lib/pins.js @@ -33,6 +33,7 @@ var createLineHandler = Pins.createLineHandler = function (ref, errorHandler) { // it's a weird API but it's faster than unpinning manually var pins = ref.pins = {}; ref.index = 0; + ref.first = 0; ref.latest = 0; // the latest message (timestamp in ms) ref.surplus = 0; // how many lines exist behind a reset @@ -58,7 +59,7 @@ var createLineHandler = Pins.createLineHandler = function (ref, errorHandler) { return sanitized; }; - return function (line) { + return function (line, i) { ref.index++; if (!Boolean(line)) { return; } @@ -74,6 +75,7 @@ var createLineHandler = Pins.createLineHandler = function (ref, errorHandler) { } if (typeof(l[2]) === 'number') { + if (!ref.first) { ref.first = l[2]; } ref.latest = l[2]; // date } @@ -109,6 +111,11 @@ var createLineHandler = Pins.createLineHandler = function (ref, errorHandler) { default: errorHandler("PIN_LINE_UNSUPPORTED_COMMAND", l); } + + if (i === 0) { // First line when using Pins.load + if (l[0] === 'PIN' || ref.block) { ref.user = true; } // teams always start with RESET + } + }; }; diff --git a/scripts/user-statistics.js b/scripts/user-statistics.js new file mode 100644 index 000000000..bc6b16227 --- /dev/null +++ b/scripts/user-statistics.js @@ -0,0 +1,133 @@ +// SPDX-FileCopyrightText: 2025 XWiki CryptPad Team and contributors +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +const Path = require('node:path'); +const nThen = require("nthen"); +const Semaphore = require("saferphore"); +const Logger = require("../lib/log"); +const Pins = require("../lib/pins"); +const config = require("../lib/load-config"); +const BlobStorage = require("../lib/storage/blob"); +const Store = require("../lib/storage/file"); +const Fs = require('node:fs'); +const Quota = require("../lib/commands/quota"); +const Environment = require('../lib/env'); +const Env = Environment.create(config); + +const CSV = true; + +config.logPath = false; +config.logToStdout = true; + +const start = () => { + let time = +new Date(); + let Log = {}; + let all = {}; + let blobStore, pinStore, store; + nThen(w => { + Logger.create(config, w(function (_log) { + Env.Log = Log = _log; + })); + }).nThen(w => { + config.getSession = function () {}; + Store.create(config, w(function (err, _store) { + if (err) { + w.abort(); + return void Log.error("ERR_PAD_STORE", err); + } + store = _store; + })); + BlobStorage.create(config, w(function (err, _store) { + if (err) { + w.abort(); + return void Log.error("ERR_BLOB_STORE", err); + } + blobStore = _store; + })); + }).nThen(w => { + Quota.updateCachedLimits(Env, w((err) => { + if (err) { + return Env.Log.warn('UPDATE_QUOTA_ERR', err); + } + Env.Log.info('QUOTA_UPDATED', {}); + })); + }).nThen(w => { + Env.Log.info('START_LOADING_PINS'); + const handlePinLog = (content, id, next) => { + const sema = Semaphore.create(20); + const data = all[id] = { + size: 0, + n_pads: 0, + n_blobs: 0, + n_total: 0, + first: content.first, + last: content.latest + }; + if (!content.user) { + data.maybeTeam = true; + } + nThen(ww => { + Object.keys(content.pins).forEach(id => { + sema.take(give => { + let addSize = ww(give((e, s) => { + if (typeof(s) !== "number") { + return; // XXX + } + data.size += s; + data.n_total++; + if (id.length === 32) { + data.n_pads++; + } else { + data.n_blobs++; + } + })); + if (id.length === 32) { // PAD + return store.getChannelSize(id, addSize); + } + blobStore.size(id, addSize); + }); + }); + }).nThen(() => { + let key = id.replace(/-/g, '/'); + if (Env.limits[key]) { + let sub = Env.limits[key]; + data.premium = sub?.plan; + } + Env.Log.info('PIN_LOG_HANDLED', key); + next(); + }); + }; + + Pins.load(w(() => { + let duration = +new Date() - time; + Env.Log.info('ALL_PINS_LOADED', duration); + }), { + pinPath: config.pinPath, + handler: handlePinLog, + }); + }).nThen(() => { + if (!CSV) { return console.log(all); } + let csv = `"User key","Premium plan","Bytes","Number pads","Number blobs","First activity","Last activity","May be a team"\n`; + Object.keys(all).sort((a,b) => { + return all[b].size - all[a].size; + }).forEach(k => { + const data = all[k]; + k = k.replace(/-/g, '/'); + let first = new Date(data.first).toISOString().slice(0,10); + let last = new Date(data.last).toISOString().slice(0,10); + let plan = data.premium || ''; + let t = String(!!data.maybeTeam); + csv += `"${k}","${plan}","${data.size}","${data.n_pads}","${data.n_blobs}","${first}","${last}","${t}"\n`; + }); + let filename = `../${new Date().toISOString().slice(0,10)}-stats.csv`; + Fs.writeFile(filename, csv, err => { + if (err) { + console.error(err); + } else { + console.log('CSV available at', filename); + } + }); + }); +}; +start(); From 18e8f057cb2661389bca517ac307af437e777c9c Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 10 Jan 2025 16:54:37 +0100 Subject: [PATCH 148/218] Start blob proofs migration automatically --- lib/api.js | 25 ++++++++++++ lib/commands/admin-rpc.js | 2 +- lib/decrees.js | 8 ++++ lib/storage/blob.js | 13 ++++++ scripts/migrations/migrate-blob-proofs.js | 50 +++++++++++++++++++---- 5 files changed, 89 insertions(+), 9 deletions(-) diff --git a/lib/api.js b/lib/api.js index df3206b1e..d8313521c 100644 --- a/lib/api.js +++ b/lib/api.js @@ -27,6 +27,31 @@ nThen(function (w) { console.error(err); } })); +}).nThen(function (w) { + if (Env.proofsMigrated) { return; } + const { Worker } = require('node:worker_threads'); + const Admin = require("./commands/admin-rpc"); + + const worker = new Worker('./scripts/migrations/migrate-blob-proofs.js'); + + worker.on('message', message => { + if (message === 'READY') { + log.info('BLOB_PROOFS_MIGRATION'); + return void worker.postMessage({ + start: 1, + }); + } + if (message === 'MIGRATED') { + return void log.info('BLOB_PROOFS_DELETION'); + } + if (message === 'CLEANED') { + log.info('BLOB_PROOFS_MIGRATED'); + Admin.sendDecree(Env, null, function (err) { + if (err) { return void log.error('BLOB_PROOF', err); } + Env.flushCache(); + }, ['PROOFS_MIGRATED', ['PROOFS_MIGRATED', 1]], 'server'); + } + }); }).nThen(function (w) { let admins = Env.admins || []; diff --git a/lib/commands/admin-rpc.js b/lib/commands/admin-rpc.js index 9030159f8..d029fc079 100644 --- a/lib/commands/admin-rpc.js +++ b/lib/commands/admin-rpc.js @@ -409,7 +409,7 @@ var getChannelMetadata = function (Env, Server, cb, data) { }; // CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['RESTRICT_REGISTRATION', [true]]], console.log) -var adminDecree = function (Env, Server, cb, data, unsafeKey) { +var adminDecree = Admin.sendDecree = function (Env, Server, cb, data, unsafeKey) { var value = data[1]; if (!Array.isArray(value)) { return void cb('INVALID_DECREE'); } diff --git a/lib/decrees.js b/lib/decrees.js index 0124d9d90..c52476865 100644 --- a/lib/decrees.js +++ b/lib/decrees.js @@ -395,6 +395,14 @@ commands.ADD_ADMIN_KEY = function (Env, args) { return true; }; +commands.PROOFS_MIGRATED = function (Env, args) { + if (args !== 1) { + throw new Error("INVALID_ARGS"); + } + Env.proofsMigrated = true; + return true; +}; + commands.SET_BEARER_SECRET = function (Env, args) { if (!args_isString(args) || args.length !== 1 || !args[0]) { throw new Error("INVALID_ARGS"); diff --git a/lib/storage/blob.js b/lib/storage/blob.js index 63932a2ce..f3d44c5d7 100644 --- a/lib/storage/blob.js +++ b/lib/storage/blob.js @@ -40,6 +40,7 @@ var makeBlobPath = function (Env, blobId) { return Path.join(Env.blobPath, blobId.slice(0, 2), blobId); }; + var makeActivityPath = function (Env, blobId) { return makeBlobPath(Env, blobId) + '.activity'; }; @@ -116,6 +117,18 @@ var isFile = function (filePath, cb) { }); }; +// PROOFS +// DEPRECATED, keep for compatibility +// /blob//// +var makeProofPath = function (Env, safeKey, blobId) { + return Path.join(Env.blobPath, safeKey.slice(0, 3), safeKey, blobId.slice(0, 2), blobId); +}; +// isOwnedBy(id, safeKey) +var isOwnedBy = function (Env, safeKey, blobId, cb) { + var proofPath = makeProofPath(Env, safeKey, blobId); + isFile(proofPath, cb); +}; + var makeFileStream = function (full, _cb) { var cb = Util.once(Util.mkAsync(_cb)); Fse.mkdirp(Path.dirname(full), function (e) { diff --git a/scripts/migrations/migrate-blob-proofs.js b/scripts/migrations/migrate-blob-proofs.js index 8c977693c..c8f87bb3a 100644 --- a/scripts/migrations/migrate-blob-proofs.js +++ b/scripts/migrations/migrate-blob-proofs.js @@ -2,24 +2,24 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later +const { parentPort } = require('node:worker_threads'); const Path = require('node:path'); +const Fs = require('node:fs'); const nThen = require("nthen"); const Semaphore = require("saferphore"); const Logger = require("../../lib/log"); -const config = require("../../lib/load-config"); const BlobStorage = require("../../lib/storage/blob"); -const Fs = require('node:fs'); +let config = require("../../lib/load-config"); const blobPath = config.blobPath || './blob'; let Log = {}; -// XXX NOTE: in cleaning mode, we DON'T migrate +// NOTE: in cleaning mode, we DON'T migrate // (we suppose data has already been migrated) -const DRY_RUN = true; -const CLEAN_OLD = false; +const start = (clean, dry, cb) => { + const DRY_RUN = dry; -const start = (clean) => { let dirList = []; let blobStore; nThen(w => { @@ -156,9 +156,43 @@ const start = (clean) => { }); n(() => { Log.info("DONE"); - process.exit(0); + cb(); }); }); }; -start(CLEAN_OLD); +if (parentPort) { + // Loaded as worker script + config = JSON.parse(JSON.stringify(config)); + config.logToStdout = false; + parentPort.on('message', (message) => { + let parsed = message; //JSON.parse(message); + if (!parsed?.start) { return; } + // Migrate + start(false, false, () => { + parentPort.postMessage('MIGRATED'); + // If success, clean + start(true, false, () => { + parentPort.postMessage('CLEANED'); + }); + }); + }); + parentPort.postMessage('READY'); +} else if (require.main === module) { + // Loaded from command-line + let dry = false; + let clean = false; + process.argv.forEach(key => { + if (key === '--dry') { + dry = true; + return; + } + if (key === '--clean') { + clean = true; + return; + } + }); + start(clean, dry, () => { + process.exit(0); + }); +} From 5fbcd78ab3b069dac57ab2eeb294c06b92218bb2 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 10 Jan 2025 17:23:06 +0100 Subject: [PATCH 149/218] Fallback to owners proofs during migration --- lib/env.js | 1 + lib/storage/blob.js | 5 +++++ lib/workers/db-worker.js | 20 ++++++++++++++++++++ lib/workers/index.js | 5 ++++- server.js | 10 +++++++++- 5 files changed, 39 insertions(+), 2 deletions(-) diff --git a/lib/env.js b/lib/env.js index d3748750f..a3593c7a1 100644 --- a/lib/env.js +++ b/lib/env.js @@ -415,6 +415,7 @@ const BAD = [ 'limits', 'customLimits', 'scheduleDecree', + 'plugins', 'httpServer', diff --git a/lib/storage/blob.js b/lib/storage/blob.js index f3d44c5d7..62719cefe 100644 --- a/lib/storage/blob.js +++ b/lib/storage/blob.js @@ -737,6 +737,11 @@ BlobStore.create = function (config, _cb) { upload_cancel(Env, safeKey, fileSize, cb); }, + isOwnedBy: function (safeKey, blobId, _cb) { + var cb = Util.once(Util.mkAsync(_cb)); + if (!isValidSafeKey(safeKey)) { return void cb('INVALID_SAFEKEY'); } + isOwnedBy(Env, safeKey, blobId, cb); + }, readMetadata: (blobId, handler, cb) => { if (!isValidId(blobId)) { return void cb("INVALID_ID"); } readBlobMetadata(Env, blobId, handler, cb); diff --git a/lib/workers/db-worker.js b/lib/workers/db-worker.js index d1a0fe962..9a4a39903 100644 --- a/lib/workers/db-worker.js +++ b/lib/workers/db-worker.js @@ -158,6 +158,12 @@ const isValidOffsetNumber = function (n) { return typeof(n) === 'number' && n >= 0; }; +const updateEnv = data => { + const {value} = data; + let env = Util.tryParse(value) || {}; + Env.proofsMigrated = env?.proofsMigrated; +}; + const computeIndexFromOffset = function (channelName, offset, cb) { let cpIndex = []; let messageBuf = []; @@ -576,6 +582,16 @@ const removeOwnedBlob = function (data, cb) { return void cb("INSUFFICIENT_PERMISSIONS"); } let owners = meta.owners; + if (!owners && !Env.proofsMigrated) { + // Check old proofs during migration + blobStore.isOwnedBy(safeKey, blobId, w((e, owned) => { + if (e || !owned) { + w.abort(); + return void cb("INSUFFICIENT_PERMISSIONS"); + } + })) + return; + } if (!owners || !owners.includes(unsafeKey)) { w.abort(); return void cb("INSUFFICIENT_PERMISSIONS"); @@ -693,6 +709,7 @@ const getLastChannelTime = function (data, cb) { }; const COMMANDS = { + ENV_UPDATE: updateEnv, COMPUTE_INDEX: computeIndex, COMPUTE_METADATA: computeMetadata, GET_OLDER_HISTORY: getOlderHistory, @@ -848,6 +865,9 @@ process.on('message', function (data) { }; if (!ready) { + if (data.env) { + updateEnv({value:data.env}); + } return void init(data.config, function (err) { if (err) { return void cb(Util.serializeError(err)); } ready = true; diff --git a/lib/workers/index.js b/lib/workers/index.js index 5bdbd5d7a..bc1780cc5 100644 --- a/lib/workers/index.js +++ b/lib/workers/index.js @@ -9,6 +9,7 @@ const { fork } = require('child_process'); const Workers = module.exports; const PID = process.pid; const Block = require("../storage/block"); +const Environment = require('../env'); const DB_PATH = 'lib/workers/db-worker'; const MAX_JOBS = 16; @@ -256,6 +257,7 @@ Workers.initialize = function (Env, config, _cb) { pid: PID, txid: txid, config: config, + env: Environment.serialize(Env) }); worker.on('message', function (res) { @@ -342,7 +344,8 @@ Workers.initialize = function (Env, config, _cb) { type: 'broadcast', pid: PID, command: data.command, - txid: data.txid + txid: data.txid, + value: data.value }); }); return workers; diff --git a/server.js b/server.js index 407f10f43..ca2b2ae77 100644 --- a/server.js +++ b/server.js @@ -190,7 +190,15 @@ nThen(function (w) { var throttledEnvChange = Util.throttle(function () { Env.Log.info('WORKER_ENV_UPDATE', 'Updating HTTP workers with latest state'); - broadcast('ENV_UPDATE', Environment.serialize(Env)); + let serialized = Environment.serialize(Env); + broadcast('ENV_UPDATE', serialized); + if (Env.broadcastWorkerCommand) { + Env.broadcastWorkerCommand({ + command: 'ENV_UPDATE', + value: serialized, + txid: Util.uid() + }); + } }, 250); // NOTE: changing this value will impact lib/commands/admin-rpc.js#adminDecree callback var throttledCacheFlush = Util.throttle(function () { From 7186f3e2ef2aa222fae06474f83960dd70693693 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 10 Jan 2025 17:28:05 +0100 Subject: [PATCH 150/218] lint compliance --- lib/api.js | 2 +- lib/hk-util.js | 2 +- lib/storage/blob.js | 10 ---------- lib/workers/db-worker.js | 2 +- scripts/migrations/migrate-blob-proofs.js | 4 ++-- scripts/user-statistics.js | 3 +-- 6 files changed, 6 insertions(+), 17 deletions(-) diff --git a/lib/api.js b/lib/api.js index d8313521c..d613df78e 100644 --- a/lib/api.js +++ b/lib/api.js @@ -27,7 +27,7 @@ nThen(function (w) { console.error(err); } })); -}).nThen(function (w) { +}).nThen(function () { if (Env.proofsMigrated) { return; } const { Worker } = require('node:worker_threads'); const Admin = require("./commands/admin-rpc"); diff --git a/lib/hk-util.js b/lib/hk-util.js index 3355f1c09..0ee685bba 100644 --- a/lib/hk-util.js +++ b/lib/hk-util.js @@ -42,7 +42,7 @@ const ADMIN_CHANNEL_LENGTH = HK.ADMIN_CHANNEL_LENGTH = 33; // with a 34 character id const EPHEMERAL_CHANNEL_LENGTH = HK.EPHEMERAL_CHANNEL_LENGTH = 34; -const BLOB_ID_LENGTH = HK.BLOB_ID_LENGTH = 48; +HK.BLOB_ID_LENGTH = 48; // Temporary channels are archived X ms after everyone has left them const TEMPORARY_CHANNEL_LIFETIME = 30 * 1000; diff --git a/lib/storage/blob.js b/lib/storage/blob.js index 62719cefe..a7a053038 100644 --- a/lib/storage/blob.js +++ b/lib/storage/blob.js @@ -10,7 +10,6 @@ var BlobStore = module.exports; var nThen = require("nthen"); var Semaphore = require("saferphore"); var Util = require("../common-util"); -var Meta = require("../metadata"); const PERMISSIVE = 511; const readFileBin = require("../stream-file").readFileBin; @@ -443,12 +442,6 @@ var owned_upload_complete = function (Env, safeKey, id, cb) { // move the existing file to its new path Fse.move(oldPath, finalPath, w(function (e) { if (e) { - // if there's an error putting the file into its final location... - // ... you should remove the ownership file - Fs.unlink(finalOwnPath, function () { - // but if you can't, it's not catestrophic - // we can clean it up later - }); w.abort(); return void cb(e.code); } @@ -606,11 +599,9 @@ var listBlobs = function (root, handler, fast, cb) { var isLonelyMetadata = false; var blobName; - var metadataName; // if the current file is not the channel data, then it must be metadata if (!/^[0-9a-fA-F]{48}$/.test(item)) { - metadataName = item; blobName = item.replace(/\.metadata\.ndjson/, ''); // check if blob already exists if (list.indexOf(blobName) !== -1) { return; } @@ -619,7 +610,6 @@ var listBlobs = function (root, handler, fast, cb) { isLonelyMetadata = true; } else { blobName = item; - metadataName = blobName + '.metadata.ndjson'; } if (blobName.length !== 48) { return; } diff --git a/lib/workers/db-worker.js b/lib/workers/db-worker.js index 9a4a39903..88fcd5989 100644 --- a/lib/workers/db-worker.js +++ b/lib/workers/db-worker.js @@ -589,7 +589,7 @@ const removeOwnedBlob = function (data, cb) { w.abort(); return void cb("INSUFFICIENT_PERMISSIONS"); } - })) + })); return; } if (!owners || !owners.includes(unsafeKey)) { diff --git a/scripts/migrations/migrate-blob-proofs.js b/scripts/migrations/migrate-blob-proofs.js index c8f87bb3a..d38c94bb0 100644 --- a/scripts/migrations/migrate-blob-proofs.js +++ b/scripts/migrations/migrate-blob-proofs.js @@ -101,7 +101,7 @@ const start = (clean, dry, cb) => { err }); } - let blobs = [] + let blobs = []; nThen(www => { list.forEach(dir => { // ./blob/abc/abcdefg.../01 @@ -142,7 +142,7 @@ const start = (clean, dry, cb) => { })); })); - }) + }); }); }).nThen(ww(give(() => { Log.info("END_USER", edPublic); diff --git a/scripts/user-statistics.js b/scripts/user-statistics.js index bc6b16227..02a74ef89 100644 --- a/scripts/user-statistics.js +++ b/scripts/user-statistics.js @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -const Path = require('node:path'); const nThen = require("nthen"); const Semaphore = require("saferphore"); const Logger = require("../lib/log"); @@ -24,7 +23,7 @@ const start = () => { let time = +new Date(); let Log = {}; let all = {}; - let blobStore, pinStore, store; + let blobStore, store; nThen(w => { Logger.create(config, w(function (_log) { Env.Log = Log = _log; From b135e815e7bd353ac7240ecffbdfb904c5cf7711 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 14 Jan 2025 18:13:45 +0100 Subject: [PATCH 151/218] Allow new types of decrees in different files --- lib/commands/admin-rpc.js | 13 ++ lib/decrees-core.js | 141 +++++++++++++++++ lib/decrees.js | 324 ++++++++++++-------------------------- lib/env.js | 2 +- lib/http-worker.js | 15 +- lib/load-config.js | 7 - package-lock.json | 16 +- package.json | 2 +- www/admin/inner.js | 22 ++- 9 files changed, 287 insertions(+), 255 deletions(-) create mode 100644 lib/decrees-core.js diff --git a/lib/commands/admin-rpc.js b/lib/commands/admin-rpc.js index 9030159f8..b682bbf62 100644 --- a/lib/commands/admin-rpc.js +++ b/lib/commands/admin-rpc.js @@ -1138,6 +1138,19 @@ Admin.command = function (Env, safeKey, data, _cb, Server) { var command = commands[data[0]]; + Object.keys(Env.plugins || {}).forEach(name => { + let plugin = Env.plugins[name]; + if (!plugin.addAdminCommands) { return; } + try { + let c = plugin.addAdminCommands(Env); + Object.keys(c || {}).forEach(cmd => { + if (typeof(c[cmd]) !== "function") { return; } + if (commands[cmd]) { return; } + commands[cmd] = c[cmd]; + }); + } catch (e) {} + }); + if (typeof(command) === 'function') { return void command(Env, Server, cb, data, unsafeKey); } diff --git a/lib/decrees-core.js b/lib/decrees-core.js new file mode 100644 index 000000000..a8320a922 --- /dev/null +++ b/lib/decrees-core.js @@ -0,0 +1,141 @@ +// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +var Decrees = module.exports; +var Util = require("./common-util"); +var Fs = require("fs"); +var Path = require("path"); +var readFileBin = require("./stream-file").readFileBin; +var Schedule = require("./schedule"); +var Fse = require("fs-extra"); +var nThen = require("nthen"); + + +const Utils = Decrees.Utils = {}; +var isString = (str) => { + return typeof(str) === "string"; +}; +var isInteger = function (n) { + return !(typeof(n) !== 'number' || isNaN(n) || (n % 1) !== 0); +}; +Utils.args_isBoolean = function (args) { + return !(!Array.isArray(args) || typeof(args[0]) !== 'boolean'); +}; +Utils.args_isString = function (args) { + return !(!Array.isArray(args) || !isString(args[0])); +}; +Utils.args_isInteger = function (args) { + return !(!Array.isArray(args) || !isInteger(args[0])); +}; +Utils.args_isPositiveInteger = function (args) { + return Array.isArray(args) && isInteger(args[0]) && args[0] > 0; +}; + + +Decrees.create = (name, commands) => { + // [, , ,