and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
-(() => {
const factory = (SRpc, Channel, Util) => {
const Interface = {};
let store;
@@ -92,6 +91,7 @@ const factory = (SRpc, Channel, Util) => {
cb(data);
});
});
+ // XXX allow multiple pads in same tab
chan.on('JOIN_PAD', function (data, cb) {
client.channelId = data.channel;
try {
@@ -122,19 +122,8 @@ const factory = (SRpc, Channel, Util) => {
return Interface;
};
-if (typeof(module) !== 'undefined' && module.exports) {
- module.exports = factory(
- require('./store-rpc'),
- require('../../common/worker-channel'),
- require('../../common/common-util')
- );
-} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
- define([
- '/common/outer/store-rpc.js',
- '/common/outer/worker-channel.js',
- '/common/common-util.js'
- ], factory);
-} else {
- // unsupported initialization
-}
-})();
+module.exports = factory(
+ require('./store-rpc'),
+ require('../../common/events-channel'),
+ require('../../common/common-util')
+);
diff --git a/src/worker/core/store-rpc.js b/src/worker/core/store-rpc.js
index 329a9a75c..b3c04a89f 100644
--- a/src/worker/core/store-rpc.js
+++ b/src/worker/core/store-rpc.js
@@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
-(() => {
const factory = AStore => {
var create = function (config) {
var Store = AStore.create(config);
@@ -13,23 +12,18 @@ const factory = AStore => {
// Ready
CONNECT: Store.init,
DISCONNECT: Store.disconnect,
- MIGRATE_ANON_DRIVE: Store.migrateAnonDrive,
PING: function (cId, data, cb) { cb(); },
CACHE_DISABLE: Store.disableCache,
- HAS_DRIVE: Store.hasDrive,
// RPC
- UPDATE_PIN_LIMIT: Store.updatePinLimit,
GET_PIN_LIMIT: Store.getPinLimit,
- CLEAR_OWNED_CHANNEL: Store.clearOwnedChannel,
- REMOVE_OWNED_CHANNEL: Store.removeOwnedChannel,
+ PIN_PADS: Store.pinPads,
+ UNPIN_PADS: Store.unpinPads,
+ GET_PINNED_USAGE: Store.getPinnedUsage,
+ GET_DELETED_PADS: Store.getDeletedPads,
UPLOAD_CHUNK: Store.uploadChunk,
UPLOAD_COMPLETE: Store.uploadComplete,
UPLOAD_STATUS: Store.uploadStatus,
UPLOAD_CANCEL: Store.uploadCancel,
- PIN_PADS: Store.pinPads,
- UNPIN_PADS: Store.unpinPads,
- GET_DELETED_PADS: Store.getDeletedPads,
- GET_PINNED_USAGE: Store.getPinnedUsage,
// ANON RPC
ANON_RPC_MESSAGE: Store.anonRpcMsg,
GET_FILE_SIZE: Store.getFileSize,
@@ -37,8 +31,6 @@ const factory = AStore => {
// Store
GET: Store.get,
SET: Store.set,
- GET_DRIVE: Store.drive.get,
- SET_DRIVE: Store.drive.set,
ADD_PAD: Store.addPad,
SET_PAD_TITLE: Store.setPadTitle,
MOVE_TO_TRASH: Store.moveToTrash,
@@ -74,9 +66,13 @@ const factory = AStore => {
// Universal
UNIVERSAL_COMMAND: Store.universal.execCommand,
// Pad
- SEND_PAD_MSG: Store.sendPadMsg,
- JOIN_PAD: Store.joinPad,
- LEAVE_PAD: Store.leavePad,
+ SEND_PAD_MSG: Store.pad.sendMessage,
+ JOIN_PAD: Store.pad.join,
+ LEAVE_PAD: Store.pad.leave,
+ REMOVE_OWNED_CHANNEL: Store.pad.destroy,
+ CLEAR_OWNED_CHANNEL: Store.pad.clear,
+ CORRUPTED_CACHE: Store.pad.onCorruptedCache,
+ GET_LAST_HASH: Store.pad.getLastHash,
GET_FULL_HISTORY: Store.getFullHistory,
GET_HISTORY: Store.getHistory,
GET_HISTORY_RANGE: Store.getHistoryRange,
@@ -84,15 +80,17 @@ const factory = AStore => {
CONTACT_PAD_OWNER: Store.contactPadOwner,
GIVE_PAD_ACCESS: Store.givePadAccess,
BURN_PAD: Store.burnPad,
- GET_PAD_METADATA: Store.getPadMetadata,
- SET_PAD_METADATA: Store.setPadMetadata,
+ GET_PAD_METADATA: Store.pad?.getMetadata,
+ SET_PAD_METADATA: Store.pad?.setMetadata,
CHANGE_PAD_PASSWORD_PIN: Store.changePadPasswordPin,
- GET_LAST_HASH: Store.getLastHash,
GET_SNAPSHOT: Store.getSnapshot,
- CORRUPTED_CACHE: Store.corruptedCache,
DELETE_MAILBOX_MESSAGE: Store.deleteMailboxMessage,
// Drive
DRIVE_USEROBJECT: Store.userObjectCommand,
+ GET_DRIVE: Store.drive.get,
+ SET_DRIVE: Store.drive.set,
+ MIGRATE_ANON_DRIVE: Store.drive.migrateAnon,
+ HAS_DRIVE: Store.drive.exists,
// Settings,
DELETE_ACCOUNT: Store.deleteAccount,
REMOVE_OWNED_PADS: Store.removeOwnedPads,
@@ -118,16 +116,6 @@ const factory = AStore => {
return { create };
};
-if (typeof(module) !== 'undefined' && module.exports) {
- // Code from customize can't be laoded directly in the build
- module.exports = factory(
- require('../async-store')
- );
-} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
- define([
- '/common/outer/async-store.js'
- ], factory);
-} else {
- // unsupported initialization
-}
-})();
+module.exports = factory(
+ require('../async-store')
+);
diff --git a/src/worker/modules/badge.ts b/src/worker/modules/badge.ts
index 0b3c844ab..893d39bc3 100644
--- a/src/worker/modules/badge.ts
+++ b/src/worker/modules/badge.ts
@@ -93,10 +93,34 @@ const Badge: BadgeModule = {
updateMetadata: config.updateMetadata
};
+ const _listBadges = (data, cb) => {
+ listBadges(ctx, data, undefined, cb);
+ };
+
+ // On ready, check if our badge is still valid
+ const Store = ctx.Store;
+ Store.onReadyEvt.reg(() => {
+ const md = Store.getMetadata(void 0, 'drive', () => {});
+ const myBadge:string = md?.user?.badge || "";
+ if (!myBadge) { return; }
+ listBadges(ctx, {}, undefined, all => {
+ if (!all.includes(myBadge)) {
+ const profile = ctx?.store?.modules?.profile;
+ profile?.execCommand(void 0, {
+ cmd: 'SET',
+ data: {
+ key: 'badge',
+ value: ''
+ }
+ }, () => {});
+ }
+ });
+ });
+
return {
+ listBadges: _listBadges,
removeClient: () => {},
execCommand: (clientId, obj, cb) => {
- console.log('Exex command', clientId, obj);
const cmd = obj.cmd;
const data = obj.data;
if (cmd === 'LIST_BADGES') {
diff --git a/src/worker/modules/calendar.js b/src/worker/modules/calendar.js
index 22d1fe5e2..a280709d8 100644
--- a/src/worker/modules/calendar.js
+++ b/src/worker/modules/calendar.js
@@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
-(() => {
const factory = (Util, Hash, Constants, Realtime, Cache, Rec,
nThen, Listmap, FP, Crypto, ChainPad) => {
var Calendar = {};
@@ -100,7 +99,7 @@ const factory = (Util, Hash, Constants, Realtime, Cache, Rec,
var all = [ev];
Array.prototype.push.apply(all, toAdd);
- return Rec.applyUpdates(all);
+ return all;
};
var clearDismissed = function (ctx, uid) {
var h = Util.find(ctx, ['store', 'proxy', 'hideReminders']) || {};
@@ -1208,38 +1207,16 @@ const factory = (Util, Hash, Constants, Realtime, Cache, Rec,
return Calendar;
};
-if (typeof(module) !== 'undefined' && module.exports) {
- // Code from customize can't be laoded directly in the build
- module.exports = factory(
- require('../../common/common-util'),
- require('../../common/common-hash'),
- require('../../common/common-constants'),
- require('../../common/common-realtime'),
- require('../../common/cache-store'),
- require('../../common/recurrence'),
- require('nthen'),
- require('chainpad-listmap'),
- require('../../../www/lib/datepicker/flatpickr'),
- require('chainpad-crypto'),
- require('chainpad'),
- );
-} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
- define([
- '/common/common-util.js',
- '/common/common-hash.js',
- '/common/common-constants.js',
- '/common/common-realtime.js',
- '/common/outer/cache-store.js',
- '/calendar/recurrence.js',
- '/components/nthen/index.js',
- 'chainpad-listmap',
- '/lib/datepicker/flatpickr.js',
- '/components/chainpad-crypto/crypto.js',
- '/components/chainpad/chainpad.dist.js',
- ], factory);
-} else {
- // unsupported initialization
-}
-
-})();
-
+module.exports = factory(
+ require('../../common/common-util'),
+ require('../../common/common-hash'),
+ require('../../common/common-constants'),
+ require('../../common/common-realtime'),
+ require('../../common/cache-store'),
+ require('../../common/recurrence'),
+ require('nthen'),
+ require('chainpad-listmap'),
+ require('../../../www/lib/datepicker/flatpickr'),
+ require('chainpad-crypto'),
+ require('chainpad'),
+);
diff --git a/src/worker/modules/cursor.js b/src/worker/modules/cursor.js
index e7aa5840b..9a27f4dd9 100644
--- a/src/worker/modules/cursor.js
+++ b/src/worker/modules/cursor.js
@@ -2,10 +2,10 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
-(() => {
-const factory = (Util, Constants, Messages = {},
- AppConfig = {}, Crypto) => {
- var Cursor = {};
+const factory = (Util, Constants, Crypto) => {
+ const Cursor = {};
+ let Messages = {};
+ let AppConfig = {};
Cursor.setCustomize = data => {
Messages = data.Messages;
@@ -286,25 +286,8 @@ const factory = (Util, Constants, Messages = {},
return Cursor;
};
-if (typeof(module) !== 'undefined' && module.exports) {
- // Code from customize can't be laoded directly in the build
- module.exports = factory(
- require('../../common/common-util'),
- require('../../common/common-constants'),
- undefined,
- undefined,
- require('chainpad-crypto')
- );
-} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
- define([
- '/common/common-util.js',
- '/common/common-constants.js',
- '/customize/messages.js',
- '/customize/application_config.js',
- '/components/chainpad-crypto/crypto.js',
- ], factory);
-} else {
- // unsupported initialization
-}
-
-})();
+module.exports = factory(
+ require('../../common/common-util'),
+ require('../../common/common-constants'),
+ require('chainpad-crypto')
+);
diff --git a/src/worker/modules/history.js b/src/worker/modules/history.js
index f27b57b36..4e3ea258e 100644
--- a/src/worker/modules/history.js
+++ b/src/worker/modules/history.js
@@ -2,10 +2,9 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
-(() => {
const factory = (Util, Hash, UserObject, nThen) => {
- var History = {};
- var commands = {};
+ const History = {};
+ const commands = {};
var getAccountChannels = function (ctx) {
var channels = [];
@@ -133,7 +132,7 @@ const factory = (Util, Hash, UserObject, nThen) => {
history = messages.join('\n').length;
}), true);
// Metadata
- Store.getPadMetadata(null, {
+ Store.pad.getMetadata(null, {
channel: channel
}, waitFor(function (obj) {
if (obj && obj.error) { return; }
@@ -257,22 +256,9 @@ const factory = (Util, Hash, UserObject, nThen) => {
return History;
};
-if (typeof(module) !== 'undefined' && module.exports) {
- // Code from customize can't be laoded directly in the build
- module.exports = factory(
- require('../../common/common-util'),
- require('../../common/common-hash'),
- require('../../common/user-object'),
- require('nthen')
- );
-} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
- define([
- '/common/common-util.js',
- '/common/common-hash.js',
- '/common/user-object.js',
- '/components/nthen/index.js',
- ], factory);
-} else {
- // unsupported initialization
-}
-})();
+module.exports = factory(
+ require('../../common/common-util'),
+ require('../../common/common-hash'),
+ require('../../common/user-object'),
+ require('nthen')
+);
diff --git a/src/worker/modules/integration.js b/src/worker/modules/integration.js
index a3ee75c72..ac733cfcd 100644
--- a/src/worker/modules/integration.js
+++ b/src/worker/modules/integration.js
@@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
-(() => {
const factory = (Crypto) => {
var Integration = {};
@@ -203,17 +202,6 @@ const factory = (Crypto) => {
return Integration;
};
-if (typeof(module) !== 'undefined' && module.exports) {
- // Code from customize can't be laoded directly in the build
- module.exports = factory(
- require('chainpad-crypto')
- );
-} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
- define([
- '/components/chainpad-crypto/crypto.js',
- ], factory);
-} else {
- // unsupported initialization
-}
-
-})();
+module.exports = factory(
+ require('chainpad-crypto')
+);
diff --git a/src/worker/modules/mailbox.js b/src/worker/modules/mailbox.js
index 48359a73e..1c0a5e10b 100644
--- a/src/worker/modules/mailbox.js
+++ b/src/worker/modules/mailbox.js
@@ -2,10 +2,10 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
-(() => {
-const factory = (BCast = {}, Util, Hash,
+const factory = (Util, Hash,
Realtime, Messaging, Notify, Handlers, CpNetflux, Crypto) => {
- var Mailbox = {};
+ const Mailbox = {};
+ let BCast = {};
Mailbox.setCustomize = data => {
BCast = data.Broadcast;
@@ -673,32 +673,13 @@ proxy.mailboxes = {
return Mailbox;
};
-if (typeof(module) !== 'undefined' && module.exports) {
- module.exports = factory(
- undefined,
- require('../../common/common-util'),
- require('../../common/common-hash'),
- require('../../common/common-realtime'),
- require('../components/messaging'),
- require('../../common/notify'),
- require('../components/mailbox-handlers'),
- require('chainpad-netflux'),
- require('chainpad-crypto')
- );
-} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
- define([
- '/api/broadcast',
- '/common/common-util.js',
- '/common/common-hash.js',
- '/common/common-realtime.js',
- '/common/outer/messaging.js',
- '/common/notify.js',
- '/common/outer/mailbox-handlers.js',
- 'chainpad-netflux',
- '/components/chainpad-crypto/crypto.js',
- ], factory);
-} else {
- // unsupported initialization
-}
-
-})();
+module.exports = factory(
+ require('../../common/common-util'),
+ require('../../common/common-hash'),
+ require('../../common/common-realtime'),
+ require('../components/messaging'),
+ require('../../common/notify'),
+ require('../components/mailbox-handlers'),
+ require('chainpad-netflux'),
+ require('chainpad-crypto')
+);
diff --git a/src/worker/modules/messenger.js b/src/worker/modules/messenger.js
index 281fc13d5..ad55143a7 100644
--- a/src/worker/modules/messenger.js
+++ b/src/worker/modules/messenger.js
@@ -2,12 +2,12 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
-(() => {
const factory = (Crypto, Hash, Util, Realtime, Messaging,
- Constants, Messages = {}, PadTypes, nThen) => {
+ Constants, PadTypes, nThen) => {
var Curve = Crypto.Curve;
- var Msg = {};
+ const Msg = {};
+ let Messages = {};
Msg.setCustomize = data => {
Messages = data.Messages;
@@ -1119,34 +1119,13 @@ const factory = (Crypto, Hash, Util, Realtime, Messaging,
return Msg;
};
-if (typeof(module) !== 'undefined' && module.exports) {
- // Code from customize can't be laoded directly in the build
- module.exports = factory(
- require('chainpad-crypto'),
- require('../../common/common-hash'),
- require('../../common/common-util'),
- require('../../common/common-realtime'),
- require('../components/messaging'),
- require('../../common/common-constants'),
- undefined,
- require('../../common/pad-types'),
- require('nthen')
- );
-} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
- define([
- '/components/chainpad-crypto/crypto.js',
- '/common/common-hash.js',
- '/common/common-util.js',
- '/common/common-realtime.js',
- '/common/outer/messaging.js',
- '/common/common-constants.js',
- '/customize/messages.js',
- '/common/pad-types.js',
-
- '/components/nthen/index.js',
- ], factory);
-} else {
- // unsupported initialization
-}
-
-})();
+module.exports = factory(
+ require('chainpad-crypto'),
+ require('../../common/common-hash'),
+ require('../../common/common-util'),
+ require('../../common/common-realtime'),
+ require('../components/messaging'),
+ require('../../common/common-constants'),
+ require('../../common/pad-types'),
+ require('nthen')
+);
diff --git a/src/worker/modules/onlyoffice.js b/src/worker/modules/onlyoffice.js
index 720fefd44..ea89f2416 100644
--- a/src/worker/modules/onlyoffice.js
+++ b/src/worker/modules/onlyoffice.js
@@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
-(() => {
const factory = () => {
var OO = {};
@@ -354,12 +353,4 @@ const factory = () => {
return OO;
};
-if (typeof(module) !== 'undefined' && module.exports) {
- // Code from customize can't be laoded directly in the build
- module.exports = factory();
-} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
- define([], factory);
-} else {
- // unsupported initialization
-}
-})();
+module.exports = factory();
diff --git a/src/worker/modules/profile.js b/src/worker/modules/profile.js
index abd9a4816..41d509449 100644
--- a/src/worker/modules/profile.js
+++ b/src/worker/modules/profile.js
@@ -2,11 +2,12 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
-(() => {
const factory = (Util, Hash, Constants, Realtime,
- Listmap, Crypto, ChainPad) => {
+ Messaging, Listmap, Crypto, ChainPad) => {
var Profile = {};
+ const onReady = Util.mkEvent(true);
+
var initializeProfile = function (ctx, cb) {
var profile = ctx.profile;
if (!profile.edit || !profile.view) {
@@ -55,6 +56,15 @@ const factory = (Util, Hash, Constants, Realtime,
if (!lm.proxy.edPublic) {
lm.proxy.edPublic = ctx.store.proxy.edPublic;
}
+ if (!lm.proxy.proof) {
+ let str = secret.channel;
+ let myIDu8 = Util.decodeUTF8(str);
+ let k = Util.decodeBase64(ctx.store.proxy.edPrivate);
+ let nacl = Crypto.Nacl;
+ let s = nacl.sign(myIDu8, k);
+ let signature = Util.encodeBase64(s);
+ lm.proxy.proof = signature;
+ }
if (ctx.onReadyHandlers.length) {
ctx.onReadyHandlers.forEach(function (f) {
try {
@@ -63,6 +73,7 @@ const factory = (Util, Hash, Constants, Realtime,
});
ctx.onReadyHandlers = [];
}
+ onReady.fire();
}).on('change', [], function () {
ctx.emit('UPDATE', lm.proxy, ctx.clients);
});
@@ -91,21 +102,26 @@ const factory = (Util, Hash, Constants, Realtime,
};
var setValue = function (ctx, data, cId, cb) {
- var key = data.key;
- var value = data.value;
- if (!key) { return; }
- ctx.listmap.proxy[key] = value;
- Realtime.whenRealtimeSyncs(ctx.listmap.realtime, function () {
- ctx.emit('UPDATE', ctx.listmap.proxy, ctx.clients.filter(function (clientId) {
- return clientId !== cId;
- }));
- if (key === 'badge') {
- ctx.Store.set(null, {
- key: ['profile', 'badge'],
- value: value || undefined
- }, () => {});
- }
- cb(ctx.listmap.proxy);
+ onReady.reg(() => {
+ var key = data.key;
+ var value = data.value;
+ if (!key) { return; }
+ ctx.listmap.proxy[key] = value;
+ Realtime.whenRealtimeSyncs(ctx.listmap.realtime, function () {
+ ctx.emit('UPDATE', ctx.listmap.proxy, ctx.clients.filter(function (clientId) {
+ return clientId !== cId;
+ }));
+ if (key === 'badge') {
+ ctx.Store.set(null, {
+ key: ['profile', 'badge'],
+ value: value || undefined
+ }, () => {
+ Messaging.updateMyData(ctx.store);
+ ctx.updateMetadata();
+ });
+ }
+ cb(ctx.listmap.proxy);
+ });
});
};
@@ -163,29 +179,13 @@ const factory = (Util, Hash, Constants, Realtime,
return Profile;
};
-if (typeof(module) !== 'undefined' && module.exports) {
- // Code from customize can't be laoded directly in the build
- module.exports = factory(
- require('../../common/common-util'),
- require('../../common/common-hash'),
- require('../../common/common-constants'),
- require('../../common/common-realtime'),
- require('chainpad-listmap'),
- require('chainpad-crypto'),
- require('chainpad')
- );
-} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
- define([
- '/common/common-util.js',
- '/common/common-hash.js',
- '/common/common-constants.js',
- '/common/common-realtime.js',
- 'chainpad-listmap',
- '/components/chainpad-crypto/crypto.js',
- '/components/chainpad/chainpad.dist.js',
- ], factory);
-} else {
- // unsupported initialization
-}
-
-})();
+module.exports = factory(
+ require('../../common/common-util'),
+ require('../../common/common-hash'),
+ require('../../common/common-constants'),
+ require('../../common/common-realtime'),
+ require('../components/messaging'),
+ require('chainpad-listmap'),
+ require('chainpad-crypto'),
+ require('chainpad')
+);
diff --git a/src/worker/modules/support.js b/src/worker/modules/support.js
index 3250e218c..262cb37f7 100644
--- a/src/worker/modules/support.js
+++ b/src/worker/modules/support.js
@@ -2,10 +2,10 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
-(() => {
-const factory = (ApiConfig = {}, Util, Hash, Realtime, Pinpad, Crypt,
+const factory = (Util, Hash, Realtime, Pinpad, Crypt,
nThen, Crypto, Listmap, ChainPad, CpNetflux) => {
- var Support = {};
+ const Support = {};
+ let ApiConfig = {};
Support.setCustomize = data => {
ApiConfig = data.ApiConfig;
@@ -1420,37 +1420,15 @@ const factory = (ApiConfig = {}, Util, Hash, Realtime, Pinpad, Crypt,
return Support;
};
-if (typeof(module) !== 'undefined' && module.exports) {
- // Code from customize can't be laoded directly in the build
- module.exports = factory(
- undefined,
- require('../../common/common-util'),
- require('../../common/common-hash'),
- require('../../common/common-realtime'),
- require('../../common/pinpad'),
- require('../../common/cryptget'),
- require('nthen'),
- require('chainpad-crypto'),
- require('chainpad-listmap'),
- require('chainpad'),
- require('chainpad-netflux')
- );
-} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
- define([
- '/api/config',
- '/common/common-util.js',
- '/common/common-hash.js',
- '/common/common-realtime.js',
- '/common/pinpad.js',
- '/common/cryptget.js',
- '/components/nthen/index.js',
- '/components/chainpad-crypto/crypto.js',
- 'chainpad-listmap',
- '/components/chainpad/chainpad.dist.js',
- 'chainpad-netflux'
- ], factory);
-} else {
- // unsupported initialization
-}
-
-})();
+module.exports = factory(
+ require('../../common/common-util'),
+ require('../../common/common-hash'),
+ require('../../common/common-realtime'),
+ require('../../common/pinpad'),
+ require('../../common/cryptget'),
+ require('nthen'),
+ require('chainpad-crypto'),
+ require('chainpad-listmap'),
+ require('chainpad'),
+ require('chainpad-netflux')
+);
diff --git a/src/worker/modules/team.js b/src/worker/modules/team.js
index cd67627b8..9e7ba7a77 100644
--- a/src/worker/modules/team.js
+++ b/src/worker/modules/team.js
@@ -2,12 +2,11 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
-(() => {
const factory = (Util, Hash, Constants, Realtime, ProxyManager,
UserObject, SF, Roster, Messaging, Feedback,
Invite, Crypt, Cache, Pinpad, Listmap, Crypto,
CpNetflux, ChainPad, nThen, Nacl) => {
- var Team = {};
+ const Team = {};
Nacl = Nacl || (typeof(window) !== "undefined" && window.nacl);
var onStoreReady = Util.mkEvent(true);
@@ -298,7 +297,7 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
teamId: id
};
}
- ctx.Store.removeOwnedChannel('', data, cb);
+ ctx.Store.pad.destroy('', data, cb);
},
Store: ctx.Store,
store: ctx.store
@@ -880,7 +879,7 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
}));
}).nThen(function (_w) {
if (otherOwners) {
- ctx.Store.setPadMetadata(null, {
+ ctx.Store.pad.setMetadata(null, {
channel: c,
command: 'RM_OWNERS',
value: [teamEdPublic],
@@ -984,7 +983,7 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
var md;
nThen(function (waitFor) {
// Get pending owners
- ctx.Store.getPadMetadata(null, {
+ ctx.Store.pad.getMetadata(null, {
channel: teamData.channel
}, waitFor(function (obj) {
if (obj && obj.error) {
@@ -1007,7 +1006,7 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
});
if (!member && teamData.owner) {
var removeOwnership = function (chan) {
- ctx.Store.setPadMetadata(null, {
+ ctx.Store.pad.setMetadata(null, {
channel: chan,
command: 'RM_PENDING_OWNERS',
value: [ed],
@@ -1120,7 +1119,7 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
}
};
var addPendingOwner = function (chan) {
- ctx.Store.setPadMetadata(null, {
+ ctx.Store.pad.setMetadata(null, {
channel: chan,
command: 'ADD_PENDING_OWNERS',
value: [user.edPublic],
@@ -1176,7 +1175,7 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
}
};
var removeOwnership = function (chan) {
- ctx.Store.setPadMetadata(null, {
+ ctx.Store.pad.setMetadata(null, {
channel: chan,
command: cmd,
value: [user.edPublic],
@@ -1393,7 +1392,7 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
var md;
nThen(function (waitFor) {
// Get pending owners
- ctx.Store.getPadMetadata(null, {
+ ctx.Store.pad.getMetadata(null, {
channel: teamData.channel
}, waitFor(function (obj) {
if (obj && obj.error) {
@@ -2241,59 +2240,27 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
return Team;
};
-if (typeof(module) !== 'undefined' && module.exports) {
- // Code from customize can't be laoded directly in the build
- module.exports = factory(
- require('../../common/common-util'),
- require('../../common/common-hash'),
- require('../../common/common-constants'),
- require('../../common/common-realtime'),
+module.exports = factory(
+ require('../../common/common-util'),
+ require('../../common/common-hash'),
+ require('../../common/common-constants'),
+ require('../../common/common-realtime'),
- require('../../common/proxy-manager'),
- require('../../common/user-object'),
- require('../components/sharedfolder'),
- require('../components/roster'),
- require('../components/messaging'),
- require('../../common/common-feedback'),
- require('../components/invitation'),
- require('../../common/cryptget'),
- require('../../common/cache-store'),
- require('../../common/pinpad'),
+ require('../../common/proxy-manager'),
+ require('../../common/user-object'),
+ require('../components/sharedfolder'),
+ require('../components/roster'),
+ require('../components/messaging'),
+ require('../../common/common-feedback'),
+ require('../components/invitation'),
+ require('../../common/cryptget'),
+ require('../../common/cache-store'),
+ require('../../common/pinpad'),
- require('chainpad-listmap'),
- require('chainpad-crypto'),
- require('chainpad-netflux'),
- require('chainpad'),
- require('nthen'),
- require('tweetnacl/nacl-fast'),
- );
-} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
- define([
- '/common/common-util.js',
- '/common/common-hash.js',
- '/common/common-constants.js',
- '/common/common-realtime.js',
-
- '/common/proxy-manager.js',
- '/common/user-object.js',
- '/common/outer/sharedfolder.js',
- '/common/outer/roster.js',
- '/common/outer/messaging.js',
- '/common/common-feedback.js',
- '/common/outer/invitation.js',
- '/common/cryptget.js',
- '/common/outer/cache-store.js',
- '/common/pinpad.js',
-
- 'chainpad-listmap',
- '/components/chainpad-crypto/crypto.js',
- 'chainpad-netflux',
- '/components/chainpad/chainpad.dist.js',
- '/components/nthen/index.js',
- '/components/tweetnacl/nacl-fast.min.js',
- ], factory);
-} else {
- // unsupported initialization
-}
-
-})();
+ require('chainpad-listmap'),
+ require('chainpad-crypto'),
+ require('chainpad-netflux'),
+ require('chainpad'),
+ require('nthen'),
+ require('tweetnacl/nacl-fast'),
+);
diff --git a/src/worker/store.ts b/src/worker/store.ts
index 7929454ac..9fda2de74 100644
--- a/src/worker/store.ts
+++ b/src/worker/store.ts
@@ -23,7 +23,7 @@ import * as UOSetter from '../common/user-object-setter.js';
import * as Pinpad from '../common/pinpad.js';
import * as PadTypes from '../common/pad-types.js';
import * as NetworkConfig from '../common/network-config.js';
-import * as LoginBlock from '../common/login-block.js';
+import * as LoginBlock from '../common/outer/login-block.js';
// Core
import * as Store from './async-store.js';
@@ -78,6 +78,7 @@ let start = (cfg: StoreConfig):void => {
Store,
Account,
Mailbox,
+ Messenger,
Badge
].forEach(dep => {
if (typeof(dep.setCustomize) === "function") {
diff --git a/src/worker/types.ts b/src/worker/types.ts
index 263575c45..07d0a8a6b 100644
--- a/src/worker/types.ts
+++ b/src/worker/types.ts
@@ -5,6 +5,7 @@
// Default CryptPad worker module, extended with
// specific methods for each module
export type Callback = (...args: any[]) => void
+export type RpcCall = (clientId: string, data: any, cb: Callback) => void;
export type ModuleConfig = {
store: any,
@@ -53,6 +54,7 @@ export interface Account {
export type DriveConfig = {
store: any,
+ Store: any,
broadcast: (exclude: object, cmd: string, data?: any, cb?: any) => void,
postMessage: (clientId: string, cmd: string, data?: any, cb?: any) => void
}
@@ -64,5 +66,31 @@ export interface DriveObject {
onReconnect: any
}
export interface Drive {
+ initAPI: (config: DriveConfig) => any
init: (config: DriveConfig) => DriveObject
}
+
+export type PadConfig = {
+ Store: any,
+ store: any,
+ broadcast: (exclude: object, cmd: string, data?: any, cb?: any) => void,
+ postMessage: (clientId: string, cmd: string, data?: any, cb?: any) => void
+}
+export interface PadObject {
+ join: RpcCall,
+ destroy: RpcCall,
+ clear: RpcCall,
+ setMetadata: RpcCall,
+ getMetadata: RpcCall,
+ leave: RpcCall,
+ removeClient: (clientId: string) => void,
+ sendMessage: RpcCall,
+ getLastHash: RpcCall,
+ onCorruptedCache: RpcCall,
+ getChannels: () => string[],
+ onJoined: any,
+ onCacheReady: any,
+}
+export interface Pad {
+ init: (config: PadConfig) => PadObject
+}
diff --git a/www/admin/inner.js b/www/admin/inner.js
index d0fefaaa5..4978e4218 100644
--- a/www/admin/inner.js
+++ b/www/admin/inner.js
@@ -127,9 +127,9 @@ define([
content : [
'account-metadata',
'document-metadata',
+ 'documents-deletion',
'block-metadata',
'totp-recovery',
-
]
},
'support' : { // Msg.admin_cat_support
@@ -734,20 +734,6 @@ define([
return tableObj.table;
};
- // Msg.admin_updateLimitHint, .admin_updateLimitTitle, .admin_updateLimitButton
- sidebar.addItem('update-limit', function (cb) {
- var button = blocks.activeButton('primary', '',
- Messages.admin_updateLimitButton, done => {
- sFrameChan.query('Q_ADMIN_RPC', {
- cmd: 'Q_UPDATE_LIMIT',
- }, function (e, data) {
- done(!!data);
- UI.alert(data ? Messages.admin_updateLimitDone || 'done' : 'error' + e);
- });
- });
- cb(button);
- });
-
// Msg.admin_enableembedsHint, .admin_enableembedsTitle
sidebar.addCheckboxItem({
key: 'enableembeds',
@@ -2404,6 +2390,48 @@ define([
return tableObj.table;
};
+ // Msg.admin_documentsDeletionHint.admin_documentsDeletionTitle
+ sidebar.addItem('documents-deletion', cb => {
+ const textarea = blocks.textarea({
+ 'aria-labelledby': 'cp-admin-documents-deletion'
+ });
+ const $textarea = $(textarea);
+ const archiveButton = blocks.activeButton('danger', '',
+ Messages.admin_archiveButton, () => {
+ const $btn = $(archiveButton);
+ justifyArchivalDialog('', result => {
+ const val = $textarea.val().trim();
+ const all = val.split('\n').filter(str => {
+ let type = DOCUMENT_TYPES[str.length];
+ return ['channel', 'file'].includes(type);
+ });
+ console.error(val);
+ console.error(result);
+ disable($btn);
+ sframeCommand('ARCHIVE_DOCUMENTS', {
+ list: all,
+ reason: result,
+ }, (err, arr) => {
+ const res = Array.isArray(arr) && arr[0];
+ enable($btn);
+ if (err) {
+ console.error(err);
+ return void UI.warn(Messages.error);
+ }
+ if (Array.isArray(res?.failed)
+ && res?.failed.length) {
+ console.error("Failed deletion:");
+ console.error(res?.failed);
+ }
+ $textarea.val('');
+ UI.log(Messages.archivedFromServer);
+ });
+ });
+ }, true);
+ const nav = blocks.nav([archiveButton]);
+ const div = blocks.form([textarea], nav);
+ cb(div);
+ });
// Msg.admin_documentMetadataHint.admin_documentMetadataTitle
sidebar.addItem('document-metadata', function(cb){
diff --git a/www/admin/main.js b/www/admin/main.js
index 849a3feaf..654bf9c05 100644
--- a/www/admin/main.js
+++ b/www/admin/main.js
@@ -24,11 +24,6 @@ define([
sframeChan.on('Q_ADMIN_RPC', function (data, cb) {
Cryptpad.adminRpc(data, cb);
});
- sframeChan.on('Q_UPDATE_LIMIT', function (data, cb) {
- Cryptpad.updatePinLimit(function (e) {
- cb({error: e});
- });
- });
};
var category;
if (window.location.hash) {
diff --git a/www/calendar/app-calendar.less b/www/calendar/app-calendar.less
index 245d08d6f..50c129531 100644
--- a/www/calendar/app-calendar.less
+++ b/www/calendar/app-calendar.less
@@ -171,17 +171,57 @@
color: @cryptpad_text_col;
border-radius: @variables_radius_L;
font-weight: normal;
- .tui-full-calendar-icon:not(.tui-full-calendar-calendar-dot):not(.tui-full-calendar-dropdown-arrow):not(.tui-full-calendar-ic-checkbox):not(.fa-map-marker) {
+ .tui-full-calendar-icon:not(.tui-full-calendar-calendar-dot):not(.tui-full-calendar-dropdown-arrow):not(.tui-full-calendar-ic-checkbox):not(.fa-map-marker):not(.fa-align-left) {
display: none;
}
.tui-full-calendar-icon {
text-align:center;
flex-shrink: 0;
}
+ .tui-full-calendar-calendar-dot {
+ margin-right: 10px;
+ top: 0;
+ }
.tui-full-calendar-popup-detail-item {
+ display: flex;
+ margin-bottom: 0.3rem;
+ align-items: center;
+ .event-location {
+ display: flex;
+ align-items: center;
+ }
+ .fa-align-left {
+ margin-top: 4px;
+ }
+ &.tui-full-calendar-popup-detail-item-separate {
+ &>span {
+ display: flex;
+ width: 100%;
+ }
+ .event-description {
+ display: block;
+ line-height: 1.1em;
+ p, ul {
+ margin:0.1rem;
+ }
+ }
+ }
a {
+ border-radius: @variables_radius;
color: @cryptpad_color_link;
text-decoration: underline;
+ &:focus-visible{
+ outline: @variables_focus_style;
+ }
+ }
+ span {
+ overflow: hidden;
+ word-break: break-word;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ .event-description{
+ white-space: normal;
+ }
}
}
.tui-full-calendar-section-button-save {
@@ -284,6 +324,9 @@
font-size: inherit;
line-height: inherit;
}
+ * {
+ box-sizing: content-box;
+ }
}
.CodeMirror-placeholder {
color: @cp_forms-placeholder;
diff --git a/www/calendar/export.js b/www/calendar/export.js
index 92ab261f4..e6662eee0 100644
--- a/www/calendar/export.js
+++ b/www/calendar/export.js
@@ -7,7 +7,7 @@
define([
'/customize/pages.js',
'/common/common-util.js',
- '/calendar/recurrence.js',
+ '/common/recurrence.js',
'/lib/ical.min.js'
], function (Pages, Util, Rec) {
@@ -97,6 +97,10 @@ define([
});
return;
}
+ if (k === "until") {
+ rrule += ";"+k.toUpperCase()+"="+getICSDate(r[k]);
+ return;
+ }
rrule += ";"+k.toUpperCase()+"="+r[k];
});
return rrule;
diff --git a/www/calendar/inner.js b/www/calendar/inner.js
index 82482d4ba..32ab46360 100644
--- a/www/calendar/inner.js
+++ b/www/calendar/inner.js
@@ -21,7 +21,7 @@ define([
'/customize/application_config.js',
'/lib/calendar/tui-calendar.min.js',
'/calendar/export.js',
- '/calendar/recurrence.js',
+ '/common/recurrence.js',
'/lib/datepicker/flatpickr.js',
'tui-date-picker',
@@ -408,13 +408,16 @@ define([
str = `${str} `;
APP.nextLocationUid = uid;
}
- let location_icon = h('i.fa.fa-map-marker.tui-full-calendar-icon', { 'aria-label': Messages.calendar_loc }, []);
- return location_icon.outerHTML + str;
+ let location_icon = h('i.fa.fa-map-marker.tui-full-calendar-icon', { 'aria-hidden': true }, []);
+ return ` ${location_icon.outerHTML} ${str}
`;
},
popupDetailBody: function(schedule) {
var str = schedule.body;
delete APP.eventBody;
- return diffMk.render(str, true);
+
+ let description_icon = h('i.fa.fa-align-left.tui-full-calendar-icon', { 'aria-hidden': true }, []);
+ let description = diffMk.render(str, true);
+ return `${description_icon.outerHTML}${description}
`;
},
popupIsAllDay: function() { return Messages.calendar_allDay; },
titlePlaceholder: function() { return Messages.calendar_title; },
@@ -997,7 +1000,8 @@ define([
// Mark selected months as done
todo.forEach(function (monthId) { APP.recurringDone.push(monthId); });
- cal.createSchedules(applyUpdates(toAdd));
+ //cal.createSchedules(applyUpdates(toAdd));
+ cal.createSchedules(toAdd);
};
updateRecurring = function () {
try {
@@ -2354,6 +2358,14 @@ APP.recurrenceRule = {
};
var onCalendarEditPopup = function (el) {
var $el = $(el);
+
+ const $header = $el.find('.tui-full-calendar-section-header');
+ $header.attr('id', 'tui-full-calendar-section-header');
+ $el.attr('aria-labelledby', 'tui-full-calendar-section-header');
+ const $desc = $el.find('.tui-full-calendar-section-detail');
+ $desc.attr('id', 'tui-full-calendar-section-detail');
+ $el.attr('aria-describedby', 'tui-full-calendar-section-detail');
+
$el.find('.tui-full-calendar-popup-edit').addClass('btn btn-primary');
$el.find('.tui-full-calendar-popup-edit .tui-full-calendar-icon').addClass('fa fa-pencil').removeClass('tui-full-calendar-icon');
$el.find('.tui-full-calendar-content').removeClass('tui-full-calendar-content');
@@ -2381,6 +2393,23 @@ APP.recurrenceRule = {
});
}
+ var privateData = metadataMgr.getPrivateData();
+ $el.find('.event-description').click(e => {
+ if (!e.target) { return; }
+ var $t = $(e.target);
+ if (!$t.is('a') && !$t.parents('a').length) { return; }
+ e.preventDefault();
+ var $a = $t.is('a') ? $t : $t.parents('a').first();
+ var href = $a.attr('href');
+ if (/^#/.test(href)) { return; }
+ if (/^\/[^\/]/.test(href)) {
+ href = privateData.origin + href;
+ return void common.openURL(href);
+ }
+ common.openUnsafeURL(href);
+ });
+ console.error($el.find('.event-description'), $el);
+
var $section = $el.find('.tui-full-calendar-section-button');
var ev = APP.editModalData;
var data = ev.schedule || {};
diff --git a/www/calendar/recurrence.js b/www/calendar/recurrence.js
deleted file mode 100644
index 9fbb786ba..000000000
--- a/www/calendar/recurrence.js
+++ /dev/null
@@ -1,908 +0,0 @@
-// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors
-//
-// SPDX-License-Identifier: AGPL-3.0-or-later
-
-(() => {
-const factory = (Util) => {
- var Rec = {};
-
- const window = globalThis;
- var debug = function () {};
-
- // Get week number with any "WKST" (firts day of the week)
- // Week 1 is the first week of the year containing at least 4 days in this year
- // It depends on which day is considered the first day of the week (default Monday)
- // In our case, wkst is a number matching the JS rule: 0 == Sunday
- var getWeekNo = Rec.getWeekNo = function (date, wkst) {
- if (typeof(wkst) !== "number") { wkst = 1; } // Default monday
-
- var newYear = new Date(date.getFullYear(),0,1);
- var day = newYear.getDay() - wkst; //the day of week the year begins on
- day = (day >= 0 ? day : day + 7);
- var daynum = Math.floor((date.getTime() - newYear.getTime())/86400000) + 1;
- var weeknum;
- // Week 1 / week 53
- if (day < 4) {
- weeknum = Math.floor((daynum+day-1)/7) + 1;
- if (weeknum > 52) {
- var nYear = new Date(date.getFullYear() + 1,0,1);
- var nday = nYear.getDay() - wkst;
- nday = nday >= 0 ? nday : nday + 7;
- weeknum = nday < 4 ? 1 : 53;
- }
- }
- else {
- weeknum = Math.floor((daynum+day-1)/7);
- }
- return weeknum;
- };
-
- var getYearDay = function (date) {
- var start = new Date(date.getFullYear(), 0, 0);
- var diff = (date - start) +
- ((start.getTimezoneOffset() - date.getTimezoneOffset()) * 60 * 1000);
- var oneDay = 1000 * 60 * 60 * 24;
- return Math.floor(diff / oneDay);
- };
- var setYearDay = function (date, day) {
- if (typeof(day) !== "number" || Math.abs(day) < 1 || Math.abs(day) > 366) { return; }
- if (day < 0) {
- var max = getYearDay(new Date(date.getFullYear(), 11, 31));
- day = max + day + 1;
- }
- date.setMonth(0);
- date.setDate(day);
- return true;
- };
-
- var getEndData = function (s, e) {
- if (s > e) { return void console.error("Wrong data"); }
- var days;
- if (e.getFullYear() === s.getFullYear()) {
- days = getYearDay(e) - getYearDay(s);
- } else { // eYear < sYear
- var tmp = new Date(s.getFullYear(), 11, 31);
- var d1 = getYearDay(tmp) - getYearDay(s); // Number of days before December 31st
- var de = getYearDay(e);
- days = d1 + de;
- while ((tmp.getFullYear()+1) < e.getFullYear()) {
- tmp.setFullYear(tmp.getFullYear()+1);
- days += getYearDay(tmp);
- }
- }
- return {
- h: e.getHours(),
- m: e.getMinutes(),
- days: days
- };
- };
- var setEndData = function (s, e, data) {
- e.setTime(+s);
- if (!data) { return; }
- e.setHours(data.h);
- e.setMinutes(data.m);
- e.setSeconds(0);
- e.setDate(s.getDate() + data.days);
- };
-
- var DAYORDER = Rec.DAYORDER = ["SU", "MO", "TU", "WE", "TH", "FR", "SA"];
- var getDayData = function (str) {
- var pos = Number(str.slice(0,-2));
- var day = DAYORDER.indexOf(str.slice(-2));
- return pos ? [pos, day] : day;
- };
-
- var goToFirstWeekDay = function (date, wkst) {
- var d = date.getDay();
- wkst = typeof(wkst) === "number" ? wkst : 1;
- if (d >= wkst) {
- date.setDate(date.getDate() - (d-wkst));
- } else {
- date.setDate(date.getDate() - (7+d-wkst));
- }
- };
-
- var getDateStr = function (date) {
- return date.getFullYear() + '-' + (date.getMonth()+1) + '-' + date.getDate();
- };
- var FREQ = {};
- FREQ['daily'] = function (s, i) {
- s.setDate(s.getDate()+i);
- };
- FREQ['weekly'] = function (s,i) {
- s.setDate(s.getDate()+(i*7));
- };
- FREQ['monthly'] = function (s,i) {
- s.setMonth(s.getMonth()+i);
- };
- FREQ['yearly'] = function (s,i) {
- s.setFullYear(s.getFullYear()+i);
- };
-
- // EXPAND is used to create iterations added from a BYxxx rule
- // dateA is the start date and b is the number or id of the BYxxx rule item
- var EXPAND = {};
- EXPAND['month'] = function (dateS, origin, b) {
- var oS = new Date(origin.start);
- var a = dateS.getMonth() + 1;
- var toAdd = (b-a+12)%12;
- var m = dateS.getMonth() + toAdd;
- dateS.setMonth(m);
- dateS.setDate(oS.getDate());
- if (dateS.getMonth() !== m) { return; } // Day 31 may move us to the next month
- return true;
- };
-
-
- EXPAND['weekno'] = function (dateS, origin, week, rule) {
- var wkst = rule && rule.wkst;
- if (typeof(wkst) !== "number") { wkst = 1; } // Default monday
- var oS = new Date(origin.start);
-
- var lastD = new Date(dateS.getFullYear(), 11, 31); // December 31st
- var lastW = getWeekNo(lastD, wkst); // Last week of the year is either 52 or 53
-
- var doubleOne = lastW === 1;
- if (lastW === 1) { lastW = 52; }
-
- var a = getWeekNo(dateS, wkst);
- if (!week || week > lastW) { return false; } // Week 53 may not exist this year
-
- if (week < 0) { week = lastW + week + 1; } // Turn negative week number into positive
-
- var toAdd = week - a;
- var weekS = new Date(+dateS);
- // Go to the selected week
- weekS.setDate(weekS.getDate() + (toAdd * 7));
- goToFirstWeekDay(weekS, wkst);
-
- // Then make sure we are in the correct start day
- var all = 'aaaaaaa'.split('').map(function (o, i) {
- var date = new Date(+weekS);
- date.setDate(date.getDate() + i);
- if (date.getFullYear() !== dateS.getFullYear()) { return; }
- return date.toLocaleDateString() !== oS.toLocaleDateString() && date;
- }).filter(Boolean);
-
- // If we're looking for week 1 and the last week is a week 1, add the days
- if (week === 1 && doubleOne) {
- goToFirstWeekDay(lastD, wkst);
- 'aaaaaaa'.split('').some(function (o, i) {
- var date = new Date(+lastD);
- date.setDate(date.getDate() + i);
- if (date.toLocaleDateString() === oS.toLocaleDateString()) { return; }
- if (date.getFullYear() > dateS.getFullYear()) { return true; }
- all.push(date);
- });
- }
-
- return all.length ? all : undefined;
- };
- EXPAND['yearday'] = function (dateS, origin, b) {
- var y = dateS.getFullYear();
- var state = setYearDay(dateS, b);
- if (!state) { return; } // Invalid day "b"
- if (dateS.getFullYear() !== y) { return; } // Day 366 make move us to the next year
- return true;
- };
- EXPAND['monthday'] = function (dateS, origin, b, rule) {
- if (typeof(b) !== "number" || Math.abs(b) < 1 || Math.abs(b) > 31) { return false; }
-
- var setMonthDay = function (date, day) {
- var m = date.getMonth();
- if (day < 0) {
- var tmp = new Date(date.getFullYear(), date.getMonth()+1, 0); // Last day
- day = tmp.getDate() + day + 1;
- }
- date.setDate(day);
- return date.getMonth() === m; // Don't push if day 31 moved us to the next month
-
- };
-
- // Monthly events
- if (rule.freq === 'monthly') {
- return setMonthDay(dateS, b);
- }
-
- var all = 'aaaaaaaaaaaa'.split('').map(function (o, i) {
- var date = new Date(dateS.getFullYear(), i, 1);
- var ok = setMonthDay(date, b);
- return ok ? date : undefined;
- }).filter(Boolean);
- return all.length ? all : undefined;
- };
- EXPAND['day'] = function (dateS, origin, b, rule) {
- // Here "b" can be a single day ("TU") or a position and a day ("1MO")
- var day = getDayData(b);
- var pos;
- if (Array.isArray(day)) {
- pos = day[0];
- day = day[1];
- }
-
- var all = [];
- if (![0,1,2,3,4,5,6].includes(day)) { return false; }
-
- var filterPos = function (m) {
- if (!pos) { return; }
-
- var _all = [];
- 'aaaaaaaaaaaa'.split('').some(function (a, i) {
- if (typeof(m) !== "undefined" && i !== m) { return; }
-
- var _pos;
- var tmp = all.filter(function (d) {
- return d.getMonth() === i;
- });
- if (pos < 0) {
- _pos = tmp.length + pos;
- } else {
- _pos = pos - 1; // An array starts at 0 but the recurrence rule starts at 1
- }
- _all.push(tmp[_pos]);
-
- return typeof(m) !== "undefined" && i === m;
- });
- all = _all.filter(Boolean); // The "5th" {day} won't always exist
- };
-
- var tmp;
- if (rule.freq === 'yearly') {
- tmp = new Date(+dateS);
- var y = dateS.getFullYear();
- while (tmp.getDay() !== day) { tmp.setDate(tmp.getDate()+1); }
- while (tmp.getFullYear() === y) {
- all.push(new Date(+tmp));
- tmp.setDate(tmp.getDate()+7);
- }
- filterPos();
- return all;
- }
-
- if (rule.freq === 'monthly') {
- tmp = new Date(+dateS);
- var m = dateS.getMonth();
- while (tmp.getDay() !== day) { tmp.setDate(tmp.getDate()+1); }
- while (tmp.getMonth() === m) {
- all.push(new Date(+tmp));
- tmp.setDate(tmp.getDate()+7);
- }
- filterPos(m);
- return all;
- }
-
- if (rule.freq === 'weekly') {
- while (dateS.getDay() !== day) { dateS.setDate(dateS.getDate()+1); }
- }
- return true;
- };
-
- var LIMIT = {};
- LIMIT['month'] = function (events, rule) {
- return events.filter(function (s) {
- return rule.includes(s.getMonth()+1);
- });
- };
- LIMIT['weekno'] = function (events, weeks, rules) {
- return events.filter(function (s) {
- var wkst = rules && rules.wkst;
- if (typeof(wkst) !== "number") { wkst = 1; } // Default monday
-
- var lastD = new Date(s.getFullYear(), 11, 31); // December 31st
- var lastW = getWeekNo(lastD, wkst); // Last week of the year is either 52 or 53
- if (lastW === 1) { lastW = 52; }
-
- var w = getWeekNo(s, wkst);
-
- return weeks.some(function (week) {
- if (week > 0) { return week === w; }
- return w === (lastW + week + 1);
- });
- });
- };
- LIMIT['yearday'] = function (events, days) {
- return events.filter(function (s) {
- var d = getYearDay(s);
- var max = getYearDay(new Date(s.getFullYear(), 11, 31));
-
- return days.some(function (day) {
- if (day > 0) { return day === d; }
- return d === (max + day + 1);
- });
- });
- };
- LIMIT['monthday'] = function (events, rule) {
- return events.filter(function (s) {
- var r = Util.clone(rule);
- // Transform the negative monthdays into positive for this specific month
- r = r.map(function (b) {
- if (b < 0) {
- var tmp = new Date(s.getFullYear(), s.getMonth()+1, 0); // Last day
- b = tmp.getDate() + b + 1;
- }
- return b;
- });
- return r.includes(s.getDate());
- });
- };
- LIMIT['day'] = function (events, days, rules) {
- return events.filter(function (s) {
- var dayStr = s.toLocaleDateString();
-
- // Check how to handle position in BYDAY rules (last day of the month or the year?)
- var type = 'yearly';
- if (rules.freq === 'monthly' ||
- (rules.freq === 'yearly' && rules.by && rules.by.month)) {
- type = 'monthly';
- }
-
- // Check if this event matches one of the allowed days
- return days.some(function (r) {
- // rule elements are strings with pos and day
- var day = getDayData(r);
- var pos;
- if (Array.isArray(day)) {
- pos = day[0];
- day = day[1];
- }
- if (!pos) {
- return s.getDay() === day;
- }
-
- // If we have a position, we can use EXPAND.day to get the nth {day} of the
- // year/month and compare if it matches with
- var d = new Date(s.getFullYear(), s.getMonth(), 1);
- if (type === 'yearly') { d.setMonth(0); }
- var res = EXPAND["day"](d, {}, r, {freq: type});
- return res.some(function (date) {
- return date.toLocaleDateString() === dayStr;
- });
- });
- });
- };
- LIMIT['setpos'] = function (events, rule) {
- var init = events.slice();
- var rules = Util.deduplicateString(rule.slice().map(function (n) {
- if (n > 0) { return (n-1); }
- if (n === 0) { return; }
- return init.length + n;
- }));
- return events.filter(function (ev) {
- var idx = init.indexOf(ev);
- return rules.includes(idx);
- });
- };
-
- var BYORDER = ['month','weekno','yearday','monthday','day'];
- var BYDAYORDER = ['month','monthday','day'];
-
- Rec.getMonthId = function (d) {
- return d.getFullYear() + '-' + d.getMonth();
- };
- var cache = window.CP_calendar_cache = {};
- var recurringAcross = {};
- Rec.resetCache = function () {
- cache = window.CP_calendar_cache = {};
- recurringAcross = {};
- };
-
- var iterate = function (rule, _origin, s) {
- // "origin" is the original event to detect the start of BYxxx
- var origin = Util.clone(_origin);
- var oS = new Date(origin.start);
-
- var id = origin.id.split('|')[0]; // Use same cache when updating recurrence rule
-
- // "uid" is used for the cache
- var uid = s.toLocaleDateString();
- cache[id] = cache[id] || {};
-
- var inter = rule.interval || 1;
- var freq = rule.freq;
-
- var all = [];
- var limit = function (byrule, n) {
- all = LIMIT[byrule](all, n, rule);
- };
- var expand = function (byrule) {
- return function (n) {
- // Set the start date at the beginning of the current FREQ
- var _s = new Date(+s);
- if (rule.freq === 'yearly') {
- // January 1st
- _s.setMonth(0);
- _s.setDate(1);
- } else if (rule.freq === 'monthly') {
- _s.setDate(1);
- } else if (rule.freq === 'weekly') {
- goToFirstWeekDay(_s, rule.wkst);
- } else if (rule.freq === 'daily') {
- // We don't have < byday rules so we can't expand daily rules
- }
-
- var add = EXPAND[byrule](_s, origin, n, rule);
-
- if (!add) { return; }
-
- if (Array.isArray(add)) {
- add = add.filter(function (dateS) {
- return dateS.toLocaleDateString() !== oS.toLocaleDateString();
- });
- Array.prototype.push.apply(all, add);
- } else {
- if (_s.toLocaleDateString() === oS.toLocaleDateString()) { return; }
- all.push(_s);
- }
- };
- };
-
- // Manage interval for the next iteration
- var it = Util.once(function () {
- FREQ[freq](s, inter);
- });
- var addDefault = function () {
- if (freq === "monthly") {
- s.setDate(15);
- } else if (freq === "yearly" && oS.getMonth() === 1 && oS.getDate() === 29) {
- s.setDate(28);
- }
-
- it();
-
- var _s = new Date(+s);
- if (freq === "monthly" || freq === "yearly") {
- _s.setDate(oS.getDate());
- if (_s.getDate() !== oS.getDate()) { return; } // If 31st or Feb 29th doesn't exist
- if (freq === "yearly" && _s.getMonth() !== oS.getMonth()) { return; }
-
- // FIXME if there is a recUpdate that moves the 31st to the 30th, the event
- // will still only be displayed on months with 31 days
- }
- all.push(_s);
- };
-
- if (Array.isArray(cache[id][uid])) {
- debug('Get cache', id, uid);
- if (freq === "monthly") {
- s.setDate(15);
- } else if (freq === "yearly" && oS.getMonth() === 1 && oS.getDate() === 29) {
- s.setDate(28);
- }
- it();
- return cache[id][uid];
- }
-
- if (rule.by && freq === 'yearly') {
- var order = BYORDER.slice();
- var monthLimit = false;
- if (rule.by.weekno || rule.by.yearday || rule.by.monthday || rule.by.day) {
- order.shift();
- monthLimit = true;
- }
- var first = true;
- order.forEach(function (_order) {
- var r = rule.by[_order];
- if (!r) { return; }
- if (first) {
- r.forEach(expand(_order));
- first = false;
- } else if (_order === "day") {
- if (rule.by.yearday || rule.by.monthday || rule.by.weekno) {
- limit('day', rule.by.day);
- } else {
- rule.by.day.forEach(expand('day'));
- }
- } else {
- limit(_order, r);
- }
- });
- if (rule.by.month && monthLimit) {
- limit('month', rule.by.month);
- }
- }
- if (rule.by && freq === 'monthly') {
- // We're going to compute all the entries for the coming month
- if (!rule.by.monthday && !rule.by.day) {
- addDefault();
- } else if (rule.by.monthday) {
- rule.by.monthday.forEach(expand('monthday'));
- } else if (rule.by.day) {
- rule.by.day.forEach(expand('day'));
- }
- if (rule.by.month) {
- limit('month', rule.by.month);
- }
- if (rule.by.day && rule.by.monthday) {
- limit('day', rule.by.day);
- }
- }
- if (rule.by && freq === 'weekly') {
- // We're going to compute all the entries for the coming week
- if (!rule.by.day) {
- addDefault();
- } else {
- rule.by.day.forEach(expand('day'));
- }
- if (rule.by.month) {
- limit('month', rule.by.month);
- }
- }
- if (rule.by && freq === 'daily') {
- addDefault();
- BYDAYORDER.forEach(function (_order) {
- var r = rule.by[_order];
- if (!r) { return; }
- limit(_order, r);
- });
- }
-
- all.sort(function (a, b) {
- return a-b;
- });
-
- if (rule.by && rule.by.setpos) {
- limit('setpos', rule.by.setpos);
- }
-
- if (!rule.by || !Object.keys(rule.by).length) {
- addDefault();
- } else {
- it();
- }
-
-
- var done = [];
- all = all.filter(function (newS) {
- var start = new Date(+newS).toLocaleDateString();
- if (done.includes(start)) { return false; }
- done.push(start);
- return true;
- });
-
- debug('Set cache', id, uid);
- cache[id][uid] = all;
-
- return all;
- };
-
- var getNextRules = function (obj) {
- if (!obj.recUpdate) { return []; }
- var _allRules = {};
- var _obj = obj.recUpdate.from;
- Object.keys(_obj || {}).forEach(function (d) {
- var u = _obj[d];
- if (u.recurrenceRule) { _allRules[d] = u.recurrenceRule; }
- });
- return Object.keys(_allRules).sort(function (a, b) { return Number(a)-Number(b); })
- .map(function (k) {
- var r = Util.clone(_allRules[k]);
- if (!FREQ[r.freq]) { return; }
- if (r.interval && r.interval < 1) { return; }
- r._start = Number(k);
- return r;
- }).filter(Boolean);
- };
-
- var fixTimeZone = function (evTimeZone, origin, target) {
- var getOffset = function (date, tz) {
- // Get an ISO string using Canadian local format
- let iso = date.toLocaleString('en-CA', { timeZone:tz, hour12: false }).replace(', ', 'T');
- iso += '.' + date.getMilliseconds().toString().padStart(3, '0');
-
- // Get a UTC version of this time
- let utcDate = new Date(iso + 'Z');
-
- // Return the difference in timestamps, as minutes (60*1000)
- return -(utcDate - date);
- };
-
- var myTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
- var offset = getOffset(origin, evTimeZone) - getOffset(target, evTimeZone);
- var myOffset = getOffset(origin, myTimeZone) - getOffset(target, myTimeZone);
-
- return myOffset - offset;
- };
-
- Rec.getRecurring = function (months, events) {
- if (window.CP_DEV_MODE) { debug = console.warn; }
-
- var toAdd = [];
- months.forEach(function (monthId) {
- // from 1st day of the month at 00:00 to last day at 23:59:59:999
- var ms = monthId.split('-');
- var _startMonth = new Date(ms[0], ms[1]);
- var _endMonth = new Date(+_startMonth);
- _endMonth.setMonth(_endMonth.getMonth() + 1);
- _endMonth.setMilliseconds(-1);
-
- debug('Compute month', _startMonth.toLocaleDateString());
-
- var rec = events || [];
- rec.forEach(function (obj) {
- var _start = new Date(obj.start);
- var _end = new Date(obj.end);
- var _origin = obj;
- var rule = obj.recurrenceRule;
- if (!rule) { return; }
-
- var nextRules = getNextRules(obj);
- var nextRule = nextRules.shift();
-
- if (_start >= _endMonth) { return; }
-
- // Check the "until" date of the latest rule we can use and stop now
- // if the recurrence ends before the current month
- var until = rule.until;
- var _nextRules = nextRules.slice();
- var _nextRule = nextRule;
- while (_nextRule && _nextRule._start && _nextRule._start < _startMonth) {
- until = nextRule.until;
- _nextRule = _nextRules.shift();
- }
- if (until < _startMonth) { return; }
-
- var endData = getEndData(_start, _end);
-
- if (rule.interval && rule.interval < 1) { return; }
- if (!FREQ[rule.freq]) { return; }
-
- /*
- // Rule examples
- rule.by = {
- //month: [1, 4, 5, 8, 12],
- //weekno: [1, 2, 4, 5, 32, 34, 35, 50],
- //yearday: [1, 2, 29, 30, -2, -1, 250],
- //monthday: [1, 2, 3, -3, -2, -1],
- //day: ["MO", "WE", "FR"],
- //setpos: [1, 2, -1, -2]
- };
- rule.wkst = 0;
- rule.interval = 2;
- rule.freq = 'yearly';
- rule.count = 10;
- */
- debug('Iterate over', obj.title, obj);
- debug('Use rule', rule);
-
- var count = rule.count;
- var c = 1;
-
- var next = function (start) {
- var evS = new Date(+start);
-
- if (count && c >= count) { return; }
-
- debug('Start iteration', evS.toLocaleDateString());
-
- var _toAdd = iterate(rule, obj, evS);
-
- debug('Iteration results', JSON.stringify(_toAdd.map(function (o) { return new Date(o).toLocaleDateString();})));
-
- // Make sure to continue if the current year doesn't provide any result
- if (!_toAdd.length) {
- if (evS.getFullYear() < _startMonth.getFullYear() ||
- evS < _endMonth) {
- return void next(evS);
- }
- return;
- }
-
-
- var stop = false;
- var newrule = false;
- _toAdd.some(function (_newS) {
- // Make event with correct start and end time
- var _ev = Util.clone(obj);
- _ev.id = _origin.id + '|' + (+_newS);
- var _evS = new Date(+_newS);
- var _evE = new Date(+_newS);
- setEndData(_evS, _evE, endData);
- _ev.start = +_evS;
- _ev.end = +_evE;
- _ev._count = c;
- if (_ev.isAllDay && _ev.startDay) { _ev.startDay = getDateStr(_evS); }
- if (_ev.isAllDay && _ev.endDay) { _ev.endDay = getDateStr(_evE); }
-
- if (nextRule && _ev.start === nextRule._start) {
- newrule = true;
- }
-
- var useNewRule = function () {
- if (!newrule) { return; }
- debug('Use new rule', nextRule);
- _ev._count = c;
- count = nextRule.count;
- c = 1;
- evS = +_evS;
- obj = _ev;
- rule = nextRule;
- nextRule = nextRules.shift();
- };
-
-
- if (c >= count) { // Limit reached
- debug(_evS.toLocaleDateString(), 'count');
- stop = true;
- return true;
- }
- if (_evS >= _endMonth) { // Won't affect us anymore
- debug(_evS.toLocaleDateString(), 'endMonth');
- stop = true;
- return true;
- }
- if (rule.until && _evS > rule.until) {
- debug(_evS.toLocaleDateString(), 'until');
- stop = true;
- return true;
- }
- if (_evS < _start) { // "Expand" rules may create events before the _start
- debug(_evS.toLocaleDateString(), 'start');
- return;
- }
- c++;
- if (_evE < _startMonth) { // Ended before the current month
- // Nothing to display but continue the recurrence
- debug(_evS.toLocaleDateString(), 'startMonth');
- if (newrule) { useNewRule(); }
- return;
- }
- // If a recurring event start and end in different months, make sure
- // it is only added once
- if ((_evS < _endMonth && _evE >= _endMonth) ||
- (_evS < _startMonth && _evE >= _startMonth)) {
- if (recurringAcross[_ev.id] && recurringAcross[_ev.id].includes(_ev.start)) {
- return;
- } else {
- recurringAcross[_ev.id] = recurringAcross[_ev.id] || [];
- recurringAcross[_ev.id].push(_ev.start);
- }
-
- }
-
- // Add this event
- if (_origin.timeZone && !_ev.isAllDay) {
- var offset = fixTimeZone(_origin.timeZone, _start, _evS);
- _ev.start += offset;
- _ev.end += offset;
- }
- toAdd.push(_ev);
- if (newrule) {
- useNewRule();
- return true;
- }
- });
- if (!stop) { next(evS); }
- };
- next(_start);
- debug('Added this month (all events)', toAdd.map(function (ev) {
- return new Date(ev.start).toLocaleDateString();
- }));
- });
- });
- return toAdd;
- };
- Rec.getAllOccurrences = function (ev) {
- if (!ev.recurrenceRule) { return [ev.start]; }
- var r = ev.recurrenceRule;
- // In case of infinite recursion, we can't get all
- if (!r.until && !r.count) { return false; }
- var all = [ev.start];
- var d = new Date(ev.start);
- d.setDate(15); // Make sure we won't skip a month if the event starts on day > 28
- var toAdd = [];
-
- var i = 0;
- var check = function () {
- return r.count ? (all.length < r.count) : (+d <= r.until);
- };
- while ((toAdd = Rec.getRecurring([Rec.getMonthId(d)], [ev])) && check() && i < (r.count*12)) {
- Array.prototype.push.apply(all, toAdd.map(function (_ev) { return _ev.start; }));
- d.setMonth(d.getMonth() + 1);
- i++;
- }
-
- return all;
- };
-
- Rec.diffDate = function (oldTime, newTime) {
- var n = new Date(newTime);
- var o = new Date(oldTime);
-
- // Diff Days
- var d = 0;
- var mult = n < o ? -1 : 1;
- while (n.toLocaleDateString() !== o.toLocaleDateString() || mult >= 10000) {
- n.setDate(n.getDate() - mult);
- d++;
- }
- d = mult * d;
-
- // Diff hours
- n = new Date(newTime);
- var h = n.getHours() - o.getHours();
-
- // Diff minutes
- var m = n.getMinutes() - o.getMinutes();
-
- return {
- d: d,
- h: h,
- m: m
- };
- };
-
- var sortUpdate = function (obj) {
- return Object.keys(obj).sort(function (d1, d2) {
- return Number(d1) - Number(d2);
- });
- };
- Rec.applyUpdates = function (events) {
- events.forEach(function (ev) {
- ev.raw = {
- start: ev.start,
- end: ev.end,
- };
-
- if (!ev.recUpdate) { return; }
-
- var from = ev.recUpdate.from || {};
- var one = ev.recUpdate.one || {};
- var s = ev.start;
-
- // Add "until" date to our recurrenceRule if it has been modified in future occurences
- var nextRules = getNextRules(ev).filter(function (r) {
- return r._start > s;
- });
- var nextRule = nextRules.shift();
-
- var applyDiff = function (obj, k) {
- var diff = obj[k]; // Diff is always compared to origin start/end
- var d = new Date(ev.raw[k]);
- d.setDate(d.getDate() + diff.d);
- d.setHours(d.getHours() + diff.h);
- d.setMinutes(d.getMinutes() + diff.m);
- ev[k] = +d;
- };
-
- sortUpdate(from).forEach(function (d) {
- if (s < Number(d)) { return; }
- Object.keys(from[d]).forEach(function (k) {
- if (k === 'start' || k === 'end') { return void applyDiff(from[d], k); }
- if (k === "recurrenceRule" && !from[d][k]) { return; }
- ev[k] = from[d][k];
- });
- });
- Object.keys(one[s] || {}).forEach(function (k) {
- if (k === 'start' || k === 'end') { return void applyDiff(one[s], k); }
- if (k === "recurrenceRule" && !one[s][k]) { return; }
- ev[k] = one[s][k];
- });
- if (ev.deleted) {
- Object.keys(ev).forEach(function (k) {
- delete ev[k];
- });
- }
-
- if (nextRule && ev.recurrenceRule) {
- ev.recurrenceRule._next = nextRule._start - 1;
- }
-
- if (ev.reminders) {
- ev.raw.reminders = ev.reminders;
- }
- });
- return events;
- };
-
-
- return Rec;
-};
-
-if (typeof(module) !== 'undefined' && module.exports) {
- module.exports = factory(require('./common-util'));
-} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
- define(['/common/common-util.js'], factory);
-} else {
- // unsupported initialization
-}
-})();
diff --git a/www/checkup/main.js b/www/checkup/main.js
index 0f2f8cf6c..ff6fe231e 100644
--- a/www/checkup/main.js
+++ b/www/checkup/main.js
@@ -15,7 +15,7 @@ define([
'/common/common-hash.js',
'/common/common-util.js',
'/common/pinpad.js',
- '/common/outer/network-config.js',
+ '/common/network-config.js',
'/common/outer/login-block.js',
'/customize/pages.js',
'/checkup/checkup-tools.js',
@@ -82,7 +82,7 @@ define([
var trimmedSafe = trimSlashes(ApiConfig.httpSafeOrigin);
var trimmedUnsafe = trimSlashes(ApiConfig.httpUnsafeOrigin);
var fileHost = ApiConfig.fileHost;
- var accounts_api = ApiConfig.accounts_api || AppConfig.accounts_api || undefined;
+ var accounts_api = ApiConfig.accounts_api || undefined;
var getAPIPlaceholderPath = function (relative) {
var absolute;
@@ -113,15 +113,6 @@ define([
} catch (e) {}
}
- var ACCOUNTS_URL;
- try {
- if (typeof(AppConfig.upgradeURL) === 'string') {
- ACCOUNTS_URL = new URL(AppConfig.upgradeURL, trimmedUnsafe).origin;
- }
- } catch (err) {
- console.error(err);
- }
-
var debugOrigins = {
httpUnsafeOrigin: trimmedUnsafe,
httpSafeOrigin: trimmedSafe,
@@ -1026,8 +1017,7 @@ define([
(HTTP_API_URL && HTTP_API_URL !== $outer) ? HTTP_API_URL : undefined,
isHTTPS(fileHost)? fileHost: undefined,
// support for cryptpad.fr configuration
- accounts_api,
- ![trimmedUnsafe, trimmedSafe].includes(ACCOUNTS_URL)? ACCOUNTS_URL: undefined,
+ accounts_api
],
'img-src': ["'self'", 'data:', 'blob:', $outer],
@@ -1066,8 +1056,7 @@ define([
API_URL.origin,
(HTTP_API_URL && HTTP_API_URL !== $outer) ? HTTP_API_URL : undefined,
isHTTPS(fileHost)? fileHost: undefined,
- accounts_api,
- ![trimmedUnsafe, trimmedSafe].includes(ACCOUNTS_URL)? ACCOUNTS_URL: undefined,
+ accounts_api
],
'img-src': ["'self'", 'data:', 'blob:', $outer],
'media-src': ['blob:'],
diff --git a/www/code/inner.js b/www/code/inner.js
index b445aaa20..2248fef1f 100644
--- a/www/code/inner.js
+++ b/www/code/inner.js
@@ -179,6 +179,9 @@ define([
var isSmallScreen = () => window.innerWidth <= 600;
+ const showEditor = Util.once(() => {
+ $('#cp-app-code-editor').css('display', '');
+});
var mkPreviewPane = function (editor, CodeMirror, framework, isPresentMode) {
var $previewContainer = $('#cp-app-code-preview');
var $preview = $('#cp-app-code-preview-content');
@@ -345,8 +348,8 @@ define([
$codeMirrorContainer.addClass('cp-app-code-fullpage');
};
-
framework.onReady(function () {
+ showEditor();
handleResize();
window.addEventListener('resize', handleResize);
@@ -536,6 +539,7 @@ define([
}
framework.onContentUpdate(function (newContent) {
+ showEditor();
var highlightMode = newContent.highlightMode;
if (highlightMode && highlightMode !== CodeMirror.highlightMode) {
CodeMirror.setMode(highlightMode, evModeChange.fire);
@@ -578,6 +582,13 @@ define([
framework.setCursorGetter(CodeMirror.getCursor);
editor.on('cursorActivity', updateCursor);
+ editor.setOption("extraKeys", {
+ "Esc": function(cm, event) {
+ event.preventDefault();
+ document.body.focus();
+ }
+ });
+
framework.onEditableChange(function () {
editor.setOption('readOnly', framework.isLocked() || framework.isReadOnly());
});
@@ -696,7 +707,7 @@ define([
h('div#cp-app-code-preview-content'),
h('div#cp-app-code-print')
])
- ]);
+ ]).hide();
nThen(function (waitFor) {
$(waitFor());
diff --git a/www/common/common-constants.js b/www/common/common-constants.js
deleted file mode 100644
index 1764ffbd1..000000000
--- a/www/common/common-constants.js
+++ /dev/null
@@ -1,47 +0,0 @@
-// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors
-//
-// SPDX-License-Identifier: AGPL-3.0-or-later
-
-(() => {
-const factory = function (AppConfig = {}) {
- return {
- setCustomize: data => {
- AppConfig = data.AppConfig;
- },
-
- // localStorage
- userHashKey: 'User_hash',
- userNameKey: 'User_name',
- blockHashKey: 'Block_hash',
- fileHashKey: 'FS_hash',
- sessionJWT: 'Session_JWT',
- ssoSeed: 'SSO_seed',
-
- // Store
- displayNameKey: 'cryptpad.username',
- oldStorageKey: 'CryptPad_RECENTPADS',
- storageKey: 'filesData',
- tokenKey: 'loginToken',
- prefersDriveRedirectKey: 'prefersDriveRedirect',
- isPremiumKey: 'isPremiumUser',
- displayPadCreationScreen: 'displayPadCreationScreen',
- deprecatedKey: 'deprecated',
- MAX_TEAMS_SLOTS: AppConfig.maxTeamsSlots || 5,
- MAX_TEAMS_OWNED: AppConfig.maxOwnedTeams || 5,
- MAX_PREMIUM_TEAMS_SLOTS: Math.max(AppConfig.maxTeamsSlots || 0, AppConfig.maxPremiumTeamsSlots || 0) || 5,
- MAX_PREMIUM_TEAMS_OWNED: Math.max(AppConfig.maxOwnedTeams || 0, AppConfig.maxPremiumTeamsOwned || 0) || 5,
- // Apps
- criticalApps: ['profile', 'settings', 'debug', 'admin', 'support', 'notifications', 'calendar', 'moderation', 'oldadmin'], // XXX oldadmin
- earlyAccessApps: []
- };
-};
-
-if (typeof(module) !== 'undefined' && module.exports) {
- module.exports = factory(undefined);
-} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
- define(['/customize/application_config.js'], factory);
-} else {
- // unsupported initialization
-}
-})();
-
diff --git a/www/common/common-credential.js b/www/common/common-credential.js
deleted file mode 100644
index 151a1ced4..000000000
--- a/www/common/common-credential.js
+++ /dev/null
@@ -1,116 +0,0 @@
-// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors
-//
-// SPDX-License-Identifier: AGPL-3.0-or-later
-
-(function () {
-var factory = function (AppConfig = {}, Scrypt) {
- var Cred = {};
-
- Cred.setCustomize = data => {
- AppConfig = data.AppConfig;
- };
-
- Cred.MINIMUM_PASSWORD_LENGTH = typeof(AppConfig.minimumPasswordLength) === 'number'?
- AppConfig.minimumPasswordLength: 8; // TODO 14 or higher is a decent default for 2023
-
- Cred.MINIMUM_NAME_LENGTH = 1;
- Cred.MAXIMUM_NAME_LENGTH = 64;
-
- // https://stackoverflow.com/questions/46155/how-to-validate-an-email-address-in-javascript
- Cred.isEmail = function (email) {
- var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
- return re.test(String(email).toLowerCase());
- };
-
- Cred.isLongEnoughPassword = function (passwd) {
- return passwd.length >= Cred.MINIMUM_PASSWORD_LENGTH;
- };
-
- var isString = Cred.isString = function (x) {
- return typeof(x) === 'string';
- };
-
- // Maximum username length is enforced at registration time
- // rather than in this function
- // in order to maintain backwards compatibility with accounts
- // that might have already registered with a longer name.
- Cred.isValidUsername = function (name) {
- return !!(isString(name) && name.length >= Cred.MINIMUM_NAME_LENGTH);
- };
-
- Cred.isValidPassword = function (passwd) {
- return !!(passwd && isString(passwd));
- };
-
- Cred.passwordsMatch = function (a, b) {
- return isString(a) && isString(b) && a === b;
- };
-
- Cred.customSalt = function () {
- return typeof(AppConfig.loginSalt) === 'string'?
- AppConfig.loginSalt: '';
- };
-
- Cred.deriveFromPassphrase = function (username, password, len, cb) {
- Scrypt(password,
- username + Cred.customSalt(), // salt
- 8, // memoryCost (n)
- 1024, // block size parameter (r)
- len || 128, // dkLen
- 200, // interruptStep
- cb,
- undefined); // format, could be 'base64'
- };
-
- Cred.dispenser = function (bytes) {
- var entropy = {
- used: 0,
- };
-
- // crypto hygeine
- var consume = function (n) {
- // explode if you run out of bytes
- if (entropy.used + n > bytes.length) {
- throw new Error('exceeded available entropy');
- }
- if (typeof(n) !== 'number') { throw new Error('expected a number'); }
- if (n <= 0) {
- throw new Error('expected to consume a positive number of bytes');
- }
-
- // grab an unused slice of the entropy
- // Note: Internet Explorer doesn't support .slice on Uint8Array
- var A;
- if (bytes.slice) {
- A = bytes.slice(entropy.used, entropy.used + n);
- } else {
- A = bytes.subarray(entropy.used, entropy.used + n);
- }
-
- // account for the bytes you used so you don't reuse bytes
- entropy.used += n;
-
- //console.info("%s bytes of entropy remaining", bytes.length - entropy.used);
- return A;
- };
-
- return consume;
- };
-
- return Cred;
-};
-
- if (typeof(module) !== 'undefined' && module.exports) {
- module.exports = factory(
- undefined, //require("../../customize.dist/application_config.js"),
- require("scrypt-async")
- );
- } else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
- define([
- '/customize/application_config.js',
- '/components/scrypt-async/scrypt-async.min.js',
- ], function (AppConfig) {
- return factory(AppConfig, window.scrypt);
- });
- }
-}());
diff --git a/www/common/common-feedback.js b/www/common/common-feedback.js
deleted file mode 100644
index e4ab25a14..000000000
--- a/www/common/common-feedback.js
+++ /dev/null
@@ -1,79 +0,0 @@
-// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors
-//
-// SPDX-License-Identifier: AGPL-3.0-or-later
-
-(() => {
-const factory = (AppConfig = {}, Messages= {}) => {
- var Feedback = {};
-
- Feedback.setCustomize = data => {
- Messages = data.Messages;
- AppConfig = data.AppConfig;
- };
-
- Feedback.init = function (state) {
- Feedback.state = state;
- };
-
- var randomToken = function () {
- return Math.random().toString(16).replace(/0./, '');
- };
- var ajax = function (url, cb) {
- var http = new XMLHttpRequest();
- http.open('HEAD', url);
- http.onreadystatechange = function() {
- if (this.readyState === this.DONE) {
- if (cb) { cb(); }
- }
- };
- http.send();
- };
- Feedback.send = function (action, force, cb) {
- if (typeof(cb) !== 'function') { cb = function () {}; }
- if (AppConfig.disableFeedback) { return void cb(); }
- if (!action) { return void cb(); }
- if (force !== true) {
- if (!Feedback.state) { return void cb(); }
- }
-
- var href = '/common/feedback.html?' + action + '=' + randomToken();
- ajax(href, cb);
- };
-
- Feedback.reportAppUsage = function () {
- var pattern = window.location.pathname.split('/')
- .filter(function (x) { return x; }).join('.');
- if (/^#\/1\/view\//.test(window.location.hash)) {
- Feedback.send(pattern + '_VIEW');
- } else {
- Feedback.send(pattern);
- }
- };
-
- Feedback.reportScreenDimensions = function () {
- var h = window.innerHeight;
- var w = window.innerWidth;
- Feedback.send('DIMENSIONS:' + h + 'x' + w);
- };
- Feedback.reportLanguage = function () {
- if (!Messages) { return; }
- Feedback.send('LANG_' + Messages._languageUsed);
- };
-
-
- return Feedback;
-};
-
-if (typeof(module) !== 'undefined' && module.exports) {
- // Code from customize can't be laoded directly in the build
- module.exports = factory(undefined, undefined);
-} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
- define([
- '/customize/application_config.js',
- '/customize/messages.js'
- ], factory);
-} else {
- // unsupported initialization
-}
-
-})();
diff --git a/www/common/common-hash.js b/www/common/common-hash.js
deleted file mode 100644
index 0eb4faa37..000000000
--- a/www/common/common-hash.js
+++ /dev/null
@@ -1,766 +0,0 @@
-// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors
-//
-// SPDX-License-Identifier: AGPL-3.0-or-later
-
-(function (window) {
-var factory = function (Util, Crypto, Keys, Nacl) {
- var Hash = window.CryptPad_Hash = {};
-
- var uint8ArrayToHex = Util.uint8ArrayToHex;
- var hexToBase64 = Util.hexToBase64;
- var base64ToHex = Util.base64ToHex;
- Hash.encodeBase64 = Util.encodeBase64;
- Hash.decodeBase64 = Util.decodeBase64;
-
- // This implementation must match that on the server
- // it's used for a checksum
- Hash.hashChannelList = function (list) {
- return Util.encodeBase64(Nacl.hash(Util
- .decodeUTF8(JSON.stringify(list))));
- };
-
- Hash.generateSignPair = function () {
- var ed = Nacl.sign.keyPair();
- var makeSafe = function (key) {
- return Crypto.b64RemoveSlashes(key).replace(/=+$/g, '');
- };
- return {
- validateKey: Hash.encodeBase64(ed.publicKey),
- signKey: Hash.encodeBase64(ed.secretKey),
- safeValidateKey: makeSafe(Hash.encodeBase64(ed.publicKey)),
- safeSignKey: makeSafe(Hash.encodeBase64(ed.secretKey)),
- };
- };
-
- Hash.getSignPublicFromPrivate = function (edPrivateSafeStr) {
- var edPrivateStr = Crypto.b64AddSlashes(edPrivateSafeStr);
- var privateKey = Util.decodeBase64(edPrivateStr);
- var keyPair = Nacl.sign.keyPair.fromSecretKey(privateKey);
- return Util.encodeBase64(keyPair.publicKey);
- };
- Hash.getCurvePublicFromPrivate = function (curvePrivateSafeStr) {
- var curvePrivateStr = Crypto.b64AddSlashes(curvePrivateSafeStr);
- var privateKey = Util.decodeBase64(curvePrivateStr);
- var keyPair = Nacl.box.keyPair.fromSecretKey(privateKey);
- return Util.encodeBase64(keyPair.publicKey);
- };
-
- var getEditHashFromKeys = Hash.getEditHashFromKeys = function (secret) {
- var version = secret.version;
- var data = secret.keys;
- if (version === 0) {
- return secret.channel + secret.key;
- }
- if (version === 1) {
- if (!data.editKeyStr) { return; }
- return '/1/edit/' + hexToBase64(secret.channel) +
- '/' + Crypto.b64RemoveSlashes(data.editKeyStr) + '/';
- }
- if (version === 2) {
- if (!data.editKeyStr) { return; }
- var pass = secret.password ? 'p/' : '';
- return '/2/' + secret.type + '/edit/' + Crypto.b64RemoveSlashes(data.editKeyStr) + '/' + pass;
- }
- };
- var getViewHashFromKeys = Hash.getViewHashFromKeys = function (secret) {
- var version = secret.version;
- var data = secret.keys;
- if (version === 0) { return; }
- if (version === 1) {
- if (!data.viewKeyStr) { return; }
- return '/1/view/' + hexToBase64(secret.channel) +
- '/'+Crypto.b64RemoveSlashes(data.viewKeyStr)+'/';
- }
- if (version === 2) {
- if (!data.viewKeyStr) { return; }
- var pass = secret.password ? 'p/' : '';
- return '/2/' + secret.type + '/view/' + Crypto.b64RemoveSlashes(data.viewKeyStr) + '/' + pass;
- }
- };
-
- Hash.getHiddenHashFromKeys = function (type, secret, opts) {
- opts = opts || {};
- var canEdit = (secret.keys && secret.keys.editKeyStr) || secret.key;
- var mode = (!opts.view && canEdit) ? 'edit/' : 'view/';
- var pass = secret.password ? 'p/' : '';
-
- if (secret.keys && secret.keys.fileKeyStr) { mode = ''; }
-
- var hash = '/3/' + type + '/' + mode + secret.channel + '/' + pass;
- var hashData = Hash.parseTypeHash(type, hash);
- if (hashData && hashData.getHash) {
- return hashData.getHash(opts || {});
- }
- return hash;
- };
-
- var getFileHashFromKeys = Hash.getFileHashFromKeys = function (secret) {
- var version = secret.version;
- var data = secret.keys;
- if (version === 0) { return; }
- if (version === 1) {
- return '/1/' + hexToBase64(secret.channel) + '/' +
- Crypto.b64RemoveSlashes(data.fileKeyStr) + '/';
- }
- if (version === 2) {
- if (!data.fileKeyStr) { return; }
- var pass = secret.password ? 'p/' : '';
- return '/2/' + secret.type + '/' + Crypto.b64RemoveSlashes(data.fileKeyStr) + '/' + pass;
- }
- };
-
- Hash.getPublicSigningKeyString = Keys.serialize;
-
- var fixDuplicateSlashes = function (s) {
- return s.replace(/\/+/g, '/');
- };
-
- Hash.ephemeralChannelLength = 34;
- Hash.createChannelId = function (ephemeral) {
- var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(ephemeral? 17: 16));
- if ([32, 34].indexOf(id.length) === -1 || /[^a-f0-9]/.test(id)) {
- throw new Error('channel ids must consist of 32 hex characters');
- }
- return id;
- };
-
- /* Given a base64-encoded public key, deterministically derive a channel id
- Used for support mailboxes
- */
- Hash.getChannelIdFromKey = function (publicKey) {
- if (!publicKey) { return; }
- return uint8ArrayToHex(Hash.decodeBase64(publicKey).subarray(0,16));
- };
-
- /* Given a base64-encoded asymmetric private key
- derive the corresponding public key
- */
- Hash.getBoxPublicFromSecret = function (priv) {
- if (!priv) { return; }
- var u8_priv = Hash.decodeBase64(priv);
- var pair = Nacl.box.keyPair.fromSecretKey(u8_priv);
- return Hash.encodeBase64(pair.publicKey);
- };
-
- /* Given a base64-encoded private key and public key
- check that the keys are part of a valid keypair
- */
- Hash.checkBoxKeyPair = function (priv, pub) {
- if (!pub || !priv) { return false; }
- var u8_priv = Hash.decodeBase64(priv);
- var pair = Nacl.box.keyPair.fromSecretKey(u8_priv);
- return pub === Hash.encodeBase64(pair.publicKey);
- };
-
- Hash.createRandomHash = function (type, password) {
- var cryptor;
- if (type === 'file') {
- cryptor = Crypto.createFileCryptor2(void 0, password);
- return getFileHashFromKeys({
- password: Boolean(password),
- version: 2,
- type: type,
- keys: cryptor
- });
- }
- cryptor = Crypto.createEditCryptor2(void 0, void 0, password);
- return getEditHashFromKeys({
- password: Boolean(password),
- version: 2,
- type: type,
- keys: cryptor
- });
- };
-
-/*
-Version 0
- /pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy
-Version 1: Add support for read-only access
- /code/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI
-Version 2: Add support for password-protection
- /code/#/2/code/edit/u5ACvxAYmhvG0FtrNn9FJQcf/p/
-Version 3: Safe links
- /code/#/3/code/edit/f0d8055aa640a97e7fd25020ca4e93b3/
-Version 4: Data URL when not a realtime link yet (new pad or "static" app)
- /login/#/4/login/newpad=eyJocmVmIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwL2NvZGUvIy8yL2NvZGUvZWRpdC91NUFDdnhBWW1odkcwRnRyTm45RklRY2YvIn0%3D/
- /drive/#/4/drive/login=e30%3D/
-*/
-
- var getLoginOpts = function (hashArr) {
- var k;
- // Check if we have a ownerKey for this pad
- hashArr.some(function (data) {
- if (/^login=/.test(data)) {
- k = data.slice(6);
- return true;
- }
- });
- return k || '';
- };
- var getNewPadOpts = function (hashArr) {
- var k;
- // Check if we have a ownerKey for this pad
- hashArr.some(function (data) {
- if (/^newpad=/.test(data)) {
- k = data.slice(7);
- return true;
- }
- });
- return k || '';
- };
- var getVersionHash = function (hashArr) {
- var k;
- // Check if we have a ownerKey for this pad
- hashArr.some(function (data) {
- if (/^hash=/.test(data)) {
- k = data.slice(5);
- return true;
- }
- });
- return k ? Crypto.b64AddSlashes(k) : '';
- };
- var getAuditorKey = function (hashArr) {
- var k;
- // Check if we have a ownerKey for this pad
- hashArr.some(function (data) {
- if (/^auditor=/.test(data)) {
- k = data.slice(8);
- return true;
- }
- });
- return k ? Crypto.b64AddSlashes(k) : '';
- };
- var getOwnerKey = function (hashArr) {
- var k;
- // Check if we have a ownerKey for this pad
- hashArr.some(function (data) {
- if (data.length === 86) {
- k = data;
- return true;
- }
- });
- return k;
- };
- var parseTypeHash = Hash.parseTypeHash = function (type, hash) {
- if (!hash) { return; }
- var options = [];
- var parsed = {};
- var hashArr = fixDuplicateSlashes(hash).split('/');
-
- var addOptions = function () {
- parsed.password = options.indexOf('p') !== -1;
- parsed.present = options.indexOf('present') !== -1;
- parsed.embed = options.indexOf('embed') !== -1;
- parsed.versionHash = getVersionHash(options);
- parsed.auditorKey = getAuditorKey(options);
- parsed.newPadOpts = getNewPadOpts(options);
- parsed.loginOpts = getLoginOpts(options);
- parsed.ownerKey = getOwnerKey(options);
- };
-
- // Version 4: only login or newpad options, same for all the apps
- if (hashArr[1] && hashArr[1] === '4') {
- parsed.getHash = function (opts) {
- if (!opts || !Object.keys(opts).length) { return ''; }
- var hash = '/4/' + type + '/';
- if (opts.newPadOpts) { hash += 'newpad=' + opts.newPadOpts + '/'; }
- if (opts.loginOpts) { hash += 'login=' + opts.loginOpts + '/'; }
- return hash;
- };
- parsed.getOptions = function () {
- var options = {};
- if (parsed.newPadOpts) { options.newPadOpts = parsed.newPadOpts; }
- if (parsed.loginOpts) { options.loginOpts = parsed.loginOpts; }
- return options;
- };
-
- parsed.version = 4;
- parsed.app = hashArr[2];
- options = hashArr.slice(3);
- addOptions();
-
- return parsed;
- }
-
- // The other versions depends on the type
- if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) {
- parsed.type = 'pad';
- parsed.getHash = function () {
- return hash;
- };
- parsed.getOptions = function () {
- return {
- embed: parsed.embed,
- present: parsed.present,
- ownerKey: parsed.ownerKey,
- versionHash: parsed.versionHash,
- auditorKey: parsed.auditorKey,
- newPadOpts: parsed.newPadOpts,
- loginOpts: parsed.loginOpts,
- password: parsed.password
- };
- };
-
- if (hash.slice(0,1) !== '/' && hash.length >= 56) { // Version 0
- // Old hash
- parsed.channel = hash.slice(0, 32);
- parsed.key = hash.slice(32, 56);
- parsed.version = 0;
- return parsed;
- }
-
- // Version >= 1: more hash options
- parsed.getHash = function (opts) {
- var hash = hashArr.slice(0, 5).join('/') + '/';
- var owner = typeof(opts.ownerKey) !== "undefined" ? opts.ownerKey : parsed.ownerKey;
- if (owner) { hash += owner + '/'; }
- if (parsed.password || opts.password) { hash += 'p/'; }
- if (opts.embed) { hash += 'embed/'; }
- if (opts.present) { hash += 'present/'; }
- var versionHash = typeof(opts.versionHash) !== "undefined" ? opts.versionHash : parsed.versionHash;
- if (versionHash) {
- hash += 'hash=' + Crypto.b64RemoveSlashes(versionHash) + '/';
- }
- var auditorKey = typeof(opts.auditorKey) !== "undefined" ? opts.auditorKey : parsed.auditorKey;
- if (auditorKey) {
- hash += 'auditor=' + Crypto.b64RemoveSlashes(auditorKey) + '/';
- }
- if (opts.newPadOpts) { hash += 'newpad=' + opts.newPadOpts + '/'; }
- if (opts.loginOpts) { hash += 'login=' + opts.loginOpts + '/'; }
- return hash;
- };
-
- if (hashArr[1] && hashArr[1] === '1') { // Version 1
- parsed.version = 1;
- parsed.mode = hashArr[2];
- parsed.channel = hashArr[3];
- parsed.key = Crypto.b64AddSlashes(hashArr[4]);
-
- options = hashArr.slice(5);
- addOptions();
-
- return parsed;
- }
- if (hashArr[1] && hashArr[1] === '2') { // Version 2
- parsed.version = 2;
- parsed.app = hashArr[2];
- parsed.mode = hashArr[3];
- parsed.key = hashArr[4];
-
- options = hashArr.slice(5);
- addOptions();
-
- return parsed;
- }
- if (hashArr[1] && hashArr[1] === '3') { // Version 3: hidden hash
- parsed.version = 3;
- parsed.app = hashArr[2];
- parsed.mode = hashArr[3];
- parsed.channel = hashArr[4];
-
- options = hashArr.slice(5);
- addOptions();
-
- return parsed;
- }
- return parsed;
- }
- parsed.getHash = function () { return hashArr.join('/'); };
- if (['media', 'file'].indexOf(type) !== -1) {
- parsed.type = 'file';
-
- parsed.getOptions = function () {
- return {
- embed: parsed.embed,
- present: parsed.present,
- ownerKey: parsed.ownerKey,
- newPadOpts: parsed.newPadOpts,
- loginOpts: parsed.loginOpts,
- password: parsed.password
- };
- };
-
- parsed.getHash = function (opts) {
- var hash = hashArr.slice(0, 4).join('/') + '/';
- var owner = typeof(opts.ownerKey) !== "undefined" ? opts.ownerKey : parsed.ownerKey;
- if (owner) { hash += owner + '/'; }
- if (parsed.password || opts.password) { hash += 'p/'; }
- if (opts.embed) { hash += 'embed/'; }
- if (opts.present) { hash += 'present/'; }
- if (opts.newPadOpts) { hash += 'newpad=' + opts.newPadOpts + '/'; }
- if (opts.loginOpts) { hash += 'login=' + opts.loginOpts + '/'; }
- return hash;
- };
-
- if (hashArr[1] && hashArr[1] === '1') {
- parsed.version = 1;
- parsed.channel = hashArr[2].replace(/-/g, '/');
- parsed.key = hashArr[3].replace(/-/g, '/');
- options = hashArr.slice(4);
- addOptions();
- return parsed;
- }
-
- if (hashArr[1] && hashArr[1] === '2') { // Version 2
- parsed.version = 2;
- parsed.app = hashArr[2];
- parsed.key = hashArr[3];
-
- options = hashArr.slice(4);
- addOptions();
-
- return parsed;
- }
-
- if (hashArr[1] && hashArr[1] === '3') { // Version 3: hidden hash
- parsed.version = 3;
- parsed.app = hashArr[2];
- parsed.channel = hashArr[3];
-
- options = hashArr.slice(4);
- addOptions();
-
- return parsed;
- }
- return parsed;
- }
- if (['user'].indexOf(type) !== -1) {
- parsed.type = 'user';
- if (hashArr[1] && hashArr[1] === '1') {
- parsed.version = 1;
- parsed.user = hashArr[2];
- parsed.pubkey = hashArr[3].replace(/-/g, '/');
- return parsed;
- }
- return parsed;
- }
- if (['invite'].indexOf(type) !== -1) {
- parsed.type = 'invite';
- if (hashArr[1] && hashArr[1] === '2') {
- parsed.version = 2;
- parsed.app = hashArr[2];
- parsed.mode = hashArr[3];
- parsed.key = hashArr[4];
-
- options = hashArr.slice(5);
- parsed.password = options.indexOf('p') !== -1;
- return parsed;
- }
- return parsed;
- }
- return;
- };
- var parsePadUrl = Hash.parsePadUrl = function (href) {
- var patt = /^https*:\/\/([^\/]*)\/(.*?)\//i;
-
- var ret = {};
-
- if (!href) { return ret; }
- if (href.slice(-1) !== '/' && href.slice(-1) !== '#') { href += '/'; }
- href = href.replace(/\/\?[^#]+#/, '/#');
-
- var idx;
-
- // When we start without a hash, use version 4 links to add login or newpad options
- var getHash = function (opts) {
- if (!opts || !Object.keys(opts).length) { return ''; }
- var hash = '/4/' + ret.type + '/';
- if (opts.newPadOpts) { hash += 'newpad=' + opts.newPadOpts + '/'; }
- if (opts.loginOpts) { hash += 'login=' + opts.loginOpts + '/'; }
- return hash;
- };
- ret.getUrl = function (options) {
- options = options || {};
- var url = '/';
- if (!ret.type) { return url; }
- url += ret.type + '/';
- // New pad with options: append the options to the hash
- if (!ret.hashData && options && Object.keys(options).length) {
- return url + '#' + getHash(options);
- }
- if (!ret.hashData) { return url; }
- //if (ret.hashData.version === 0) { return url + '#' + ret.hash; }
- //if (ret.hashData.type !== 'pad') { return url + '#' + ret.hash; }
- var hash = ret.hashData.getHash(options);
- url += '#' + hash;
- return url;
- };
- ret.getOptions = function () {
- if (!ret.hashData || !ret.hashData.getOptions) { return {}; }
- return ret.hashData.getOptions();
- };
-
- if (!/^https*:\/\//.test(href)) {
- // If it doesn't start with http(s), it should be a relative href
- if (!/^\/($|[^\/])/.test(href)) { return ret; }
- idx = href.indexOf('/#');
- ret.type = href.slice(1, idx);
- if (idx === -1) { return ret; }
- ret.hash = href.slice(idx + 2);
- ret.hashData = parseTypeHash(ret.type, ret.hash);
- return ret;
- }
-
- href.replace(patt, function (a, domain, type) {
- ret.domain = domain;
- ret.type = type;
- return '';
- });
- idx = href.indexOf('/#');
- if (idx === -1) { return ret; }
- ret.hash = href.slice(idx + 2);
- ret.hashData = parseTypeHash(ret.type, ret.hash);
- return ret;
- };
-
- Hash.hashToHref = function (hash, type) {
- return '/' + type + '/#' + hash;
- };
- Hash.hrefToHash = function (href) {
- var parsed = Hash.parsePadUrl(href);
- return parsed.hash;
- };
-
- Hash.getRelativeHref = function (href) {
- if (!href) { return; }
- if (href.indexOf('#') === -1) { return; }
- var parsed = parsePadUrl(href);
- return '/' + parsed.type + '/#' + parsed.hash;
- };
-
- /*
- * Returns all needed keys for a realtime channel
- * - no argument: use the URL hash or create one if it doesn't exist
- * - secretHash provided: use secretHash to find the keys
- */
- Hash.getSecrets = function (type, secretHash, password) {
- var secret = {};
- var generate = function () {
- secret.keys = Crypto.createEditCryptor2(void 0, void 0, password);
- secret.channel = base64ToHex(secret.keys.chanId);
- secret.version = 2;
- secret.type = type;
- };
- if (!secretHash) {
- generate();
- return secret;
- } else {
- var parsed;
- var hash;
- if (secretHash) {
- if (!type) { throw new Error("getSecrets with a hash requires a type parameter"); }
- parsed = parseTypeHash(type, secretHash);
- hash = secretHash;
- }
- if (hash.length === 0) {
- generate();
- return secret;
- }
- // old hash system : #{hexChanKey}{cryptKey}
- // new hash system : #/{hashVersion}/{b64ChanKey}/{cryptKey}
- if (parsed.version === 0) {
- // Old hash
- secret.channel = parsed.channel;
- secret.key = parsed.key;
- secret.version = 0;
- } else if (parsed.version === 1) {
- // New hash
- secret.version = 1;
- if (parsed.type === "pad") {
- secret.channel = base64ToHex(parsed.channel);
- if (parsed.mode === 'edit') {
- secret.keys = Crypto.createEditCryptor(parsed.key);
- secret.key = secret.keys.editKeyStr;
- if (secret.channel.length !== 32 || secret.key.length !== 24) {
- throw new Error("The channel key and/or the encryption key is invalid");
- }
- }
- else if (parsed.mode === 'view') {
- secret.keys = Crypto.createViewCryptor(parsed.key);
- if (secret.channel.length !== 32) {
- throw new Error("The channel key is invalid");
- }
- }
- } else if (parsed.type === "file") {
- secret.channel = base64ToHex(parsed.channel);
- secret.keys = {
- fileKeyStr: parsed.key,
- cryptKey: Util.decodeBase64(parsed.key)
- };
- } else if (parsed.type === "user") {
- throw new Error("User hashes can't be opened (yet)");
- }
- } else if (parsed.version === 2) {
- // New hash
- secret.version = 2;
- secret.type = type;
- secret.password = password;
- if (parsed.type === "pad") {
- if (parsed.mode === 'edit') {
- secret.keys = Crypto.createEditCryptor2(parsed.key, void 0, password);
- secret.channel = base64ToHex(secret.keys.chanId);
- secret.key = secret.keys.editKeyStr;
- if (secret.channel.length !== 32 || secret.key.length !== 24) {
- throw new Error("The channel key and/or the encryption key is invalid");
- }
- }
- else if (parsed.mode === 'view') {
- secret.keys = Crypto.createViewCryptor2(parsed.key, password);
- secret.channel = base64ToHex(secret.keys.chanId);
- if (secret.channel.length !== 32) {
- throw new Error("The channel key is invalid");
- }
- }
- } else if (parsed.type === "file") {
- secret.keys = Crypto.createFileCryptor2(parsed.key, password);
- secret.channel = base64ToHex(secret.keys.chanId);
- secret.key = secret.keys.fileKeyStr;
- if (secret.channel.length !== 48 || secret.key.length !== 24) {
- throw new Error("The channel key and/or the encryption key is invalid");
- }
- } else if (parsed.type === "user") {
- throw new Error("User hashes can't be opened (yet)");
- }
- }
- }
- return secret;
- };
-
- Hash.getHashes = function (secret) {
- var hashes = {};
- secret = JSON.parse(JSON.stringify(secret));
-
- if (!secret.keys && !secret.key) {
- return hashes;
- } else if (!secret.keys) {
- secret.keys = {};
- }
-
- if (secret.keys.editKeyStr || (secret.version === 0 && secret.key)) {
- hashes.editHash = getEditHashFromKeys(secret);
- }
- if (secret.keys.viewKeyStr) {
- hashes.viewHash = getViewHashFromKeys(secret);
- }
- if (secret.keys.fileKeyStr) {
- hashes.fileHash = getFileHashFromKeys(secret);
- }
- return hashes;
- };
-
- Hash.getFormData = function (secret, hash, password) {
- secret = secret || Hash.getSecrets('form', hash, password);
- var keys = secret && secret.keys;
- var secondary = keys && keys.secondaryKey;
- if (!secondary) { return; }
- var curvePair = Nacl.box.keyPair.fromSecretKey(Util.decodeUTF8(secondary).slice(0,32));
- var ret = {};
- ret.form_public = Util.encodeBase64(curvePair.publicKey);
- var privateKey = ret.form_private = Util.encodeBase64(curvePair.secretKey);
-
- var auditorHash = Hash.getViewHashFromKeys({
- version: 1,
- channel: secret.channel,
- keys: { viewKeyStr: Util.encodeBase64(keys.cryptKey) }
- });
- var _parsed = Hash.parseTypeHash('pad', auditorHash);
- ret.form_auditorHash = _parsed.getHash({auditorKey: privateKey});
-
- return ret;
- };
-
- // STORAGE
- Hash.hrefToHexChannelId = function (href, password) {
- var parsed = Hash.parsePadUrl(href);
- if (!parsed || !parsed.hash) { return; }
- var secret = Hash.getSecrets(parsed.type, parsed.hash, password);
- return secret.channel;
- };
-
- Hash.getBlobPathFromHex = function (id) {
- return '/blob/' + id.slice(0,2) + '/' + id;
- };
-
- Hash.serializeHash = function (hash) {
- if (hash && hash.slice(-1) !== "/") { hash += "/"; }
- return hash;
- };
-
- Hash.createInviteUrl = function (curvePublic, channel) {
- channel = channel || Hash.createChannelId();
- return window.location.origin + '/invite/#/1/' + channel +
- '/' + curvePublic.replace(/\//g, '-') + '/';
- };
-
- Hash.isValidChannel = function (channelId) {
- return /^[a-zA-Z0-9]{32,48}$/.test(channelId);
- };
-
- Hash.isValidHref = function (href) {
- // Non-empty href?
- if (!href) { return; }
- var parsed = Hash.parsePadUrl(href);
- // Can be parsed?
- if (!parsed) { return; }
- // Link to a CryptPad app?
- if (!parsed.type) { return; }
- // Valid hash?
- if (parsed.hash) {
- if (!parsed.hashData) { return; }
- // Version should be a number
- if (typeof(parsed.hashData.version) === "undefined") { return; }
- // pads and files should have a base64 (or hex) key
- if (parsed.hashData.type === 'pad' || parsed.hashData.type === 'file') {
- if (!parsed.hashData.key && !parsed.hashData.channel) { return; }
- if (parsed.hashData.key && !/^[a-zA-Z0-9+-/=]+$/.test(parsed.hashData.key)) { return; }
- }
- }
- return parsed;
- };
-
- Hash.decodeDataOptions = function (opts) {
- var b64 = decodeURIComponent(opts);
- var str = Util.encodeUTF8(Util.decodeBase64(b64));
- return Util.tryParse(str) || {};
- };
- Hash.encodeDataOptions = function (opts) {
- var str = JSON.stringify(opts);
- var b64 = Util.encodeBase64(Util.decodeUTF8(str));
- return encodeURIComponent(b64);
- };
- Hash.getNewPadURL = function (href, opts) {
- var parsed = Hash.parsePadUrl(href);
- var options = parsed.getOptions();
- options.newPadOpts = Hash.encodeDataOptions(opts);
- return parsed.getUrl(options);
- };
- Hash.getLoginURL = function (href, opts) {
- var parsed = Hash.parsePadUrl(href);
- var options = parsed.getOptions();
- options.loginOpts = Hash.encodeDataOptions(opts);
- return parsed.getUrl(options);
- };
-
- return Hash;
-};
-
- if (typeof(module) !== 'undefined' && module.exports) {
- module.exports = factory(
- require("./common-util"),
- require("chainpad-crypto"),
- require("./common-signing-keys"),
- require("tweetnacl/nacl-fast")
- );
- } else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
- define([
- '/common/common-util.js',
- '/components/chainpad-crypto/crypto.js',
- '/common/common-signing-keys.js',
- '/components/tweetnacl/nacl-fast.min.js'
- ], function (Util, Crypto, Keys) {
- return factory(Util, Crypto, Keys, window.nacl);
- });
- } else {
- // unsupported initialization
- }
-}(typeof(window) !== 'undefined'? window : {}));
diff --git a/www/common/common-interface.js b/www/common/common-interface.js
index e95e5dbf9..653c659f2 100644
--- a/www/common/common-interface.js
+++ b/www/common/common-interface.js
@@ -212,6 +212,12 @@ define([
h('div'+cls, content),
])
]);
+
+ var dialogContent = frame.querySelector('div' + cls);
+ dialogContent.setAttribute('aria-live', 'assertive');
+ dialogContent.setAttribute('role', 'alertdialog');
+ dialogContent.setAttribute('aria-modal', 'true');
+
var $frame = $(frame);
frame.closeModal = function (cb) {
frame.closeModal = function () {}; // Prevent further calls
@@ -578,6 +584,7 @@ define([
};
let addTabListener = UI.addTabListener = frame => {
+ $(frame).attr('role', 'dialog').attr('aria-modal', 'true');
// find focusable elements
let modalElements = $(frame).find('a, button, input, [tabindex]:not([tabindex="-1"]), textarea').filter(':visible').filter(':not(:disabled)');
@@ -990,7 +997,10 @@ define([
var input = h('input.cp-password-input', attributes);
var eye = h('span.fa.fa-eye.cp-password-reveal', {
- tabindex: 0
+ tabindex: 0,
+ role: 'button',
+ 'aria-label': Messages.show_password,
+ 'aria-pressed': 'false'
});
var $eye = $(eye);
@@ -1013,12 +1023,12 @@ define([
if ($eye.hasClass('fa-eye')) {
$input.prop('type', 'text');
$input.focus();
- $eye.removeClass('fa-eye').addClass('fa-eye-slash');
+ $eye.removeClass('fa-eye').addClass('fa-eye-slash').attr('aria-label', Messages.hide_password).attr('aria-pressed', 'true');
return;
}
$input.prop('type', 'password');
$input.focus();
- $eye.removeClass('fa-eye-slash').addClass('fa-eye');
+ $eye.removeClass('fa-eye-slash').addClass('fa-eye').attr('aria-label', Messages.show_password).attr('aria-pressed', 'false');
});
}
@@ -1036,7 +1046,7 @@ define([
href: href,
target: "_blank",
'data-tippy-placement': "right",
- 'aria-label': Messages.help_genericMore //TBC XXX
+ 'aria-label': text
});
return q;
};
@@ -1077,6 +1087,7 @@ define([
$loading.css('display', '');
$loading.removeClass('cp-loading-hidden');
$loading.removeClass('cp-loading-transparent');
+ $loading.attr('aria-live','polite');
if (config.newProgress) {
var progress = h('div.cp-loading-progress', [
h('p.cp-loading-progress-list'),
@@ -1124,6 +1135,13 @@ define([
$('head > link[href^="/customize/src/pre-loading.css"]').remove();
$('html').toggleClass('cp-loading-noscroll', false);
};
+ UI.emptyLoadingScreen = function (content) {
+ UI.addLoadingScreen();
+ var $loading = $('#' + LOADING);
+ $loading.find('.cp-loading-container').hide();
+ $loading.find('.cp-loading-logo').hide();
+ $loading.append(content);
+ };
UI.errorLoadingScreen = function (error, transparent, exitable) {
if (error === 'Error: XDR encoding failure') {
console.warn(error);
@@ -1253,6 +1271,10 @@ define([
arrow: true,
maxWidth: '200px',
flip: true,
+ onShow: () => {
+ // Hide other tooltips
+ $('body').find('.tippy-popper').hide();
+ },
popperOptions: {
modifiers: {
preventOverflow: { boundariesElement: 'window' }
@@ -1353,13 +1375,12 @@ define([
});
$input.change(function () {
+ $mark.attr('aria-checked', $input.is(':checked'));
if (!opts.labelAlt) { return; }
if ($input.is(':checked') !== checked) {
$(label).text(opts.labelAlt);
- $mark.attr('aria-checked', 'true');
} else {
$(label).text(labelTxt);
- $mark.attr('aria-checked', 'false');
}
});
diff --git a/www/common/common-login.js b/www/common/common-login.js
index f2c2a4a90..37cd08aa0 100644
--- a/www/common/common-login.js
+++ b/www/common/common-login.js
@@ -6,7 +6,7 @@ define([
'chainpad-listmap',
'/components/chainpad-crypto/crypto.js',
'/common/common-util.js',
- '/common/outer/network-config.js',
+ '/common/network-config.js',
'/common/common-credential.js',
'/components/chainpad/chainpad.dist.js',
'/common/common-realtime.js',
diff --git a/www/common/common-realtime.js b/www/common/common-realtime.js
deleted file mode 100644
index cdf942609..000000000
--- a/www/common/common-realtime.js
+++ /dev/null
@@ -1,36 +0,0 @@
-// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors
-//
-// SPDX-License-Identifier: AGPL-3.0-or-later
-
-(() => {
-const factory = () => {
- var common = {};
-
- /*
- TODO make this not blow up when disconnected or lagging...
- */
- common.whenRealtimeSyncs = function (realtime, cb) {
- if (typeof(realtime.getAuthDoc) !== 'function') {
- return void console.error('improper use of this function');
- }
- setTimeout(function () {
- if (realtime.getAuthDoc() === realtime.getUserDoc()) {
- return void cb();
- } else {
- realtime.onSettle(cb);
- }
- }, 0);
- };
-
- return common;
-};
-
-if (typeof(module) !== 'undefined' && module.exports) {
- module.exports = factory();
-} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
- define([], factory);
-} else {
- // unsupported initialization
-}
-
-})();
diff --git a/www/common/common-signing-keys.js b/www/common/common-signing-keys.js
deleted file mode 100644
index 1d1132c34..000000000
--- a/www/common/common-signing-keys.js
+++ /dev/null
@@ -1,110 +0,0 @@
-// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors
-//
-// SPDX-License-Identifier: AGPL-3.0-or-later
-
-(function () {
-var factory = function () {
- var Keys = {};
-
- var unescape = function (s) {
- return s.replace(/-/g, '/');
- };
-
-/* Parse the new format of "Signing Public Keys".
- If anything about the input is found to be invalid, return;
- this will fall back to the old parsing method
-
-
-*/
- var parseNewUser = function (userString) {
- if (!/^\[.*?@.*\]$/.test(userString)) { return; }
- var temp = userString.slice(1, -1);
- var domain, username, pubkey;
-
- temp = temp
- .replace(/\/([a-zA-Z0-9+-]{43}=)$/, function (all, k) {
- pubkey = unescape(k);
- return '';
- });
- if (!pubkey) { return; }
-
- var index = temp.lastIndexOf('@');
- if (index < 1) { return; }
-
- domain = temp.slice(index + 1);
- username = temp.slice(0, index);
-
- return {
- domain: domain,
- user: username,
- pubkey: pubkey
- };
- };
-
- var isValidUser = function (parsed) {
- if (!parsed) { return; }
- if (!(parsed.domain && parsed.user && parsed.pubkey)) { return; }
- return true;
- };
-
- Keys.parseUser = function (user) {
- var parsed = parseNewUser(user);
- if (isValidUser(parsed)) { return parsed; }
-
- var domain, username, pubkey;
- user.replace(/^https*:\/\/([^\/]+)\/user\/#\/1\/([^\/]+)\/([a-zA-Z0-9+-]{43}=)$/,
- function (a, d, u, k) {
- domain = d;
- username = u;
- pubkey = unescape(k);
- return '';
- });
- if (!domain) { throw new Error("Could not parse user id [" + user + "]"); }
- return {
- domain: domain,
- user: username,
- pubkey: pubkey
- };
- };
-
-/*
-
-0. usernames may contain spaces or many other wacky characters, so enclose the whole thing in square braces so we know its boundaries. If the formatted string does not include these we know it is either a _v1 public key string_ or _an incomplete string_. Start parsing by removing them.
-1. public keys should have a fixed length, so slice them off of the end of the string.
-2. domains cannot include `@`, so find the last occurence of it in the signing key and slice everything thereafter.
-3. the username is everything before the `@`.
-
-*/
- Keys.serialize = function (origin, username, pubkey) {
- return '[' +
- username +
- '@' +
- origin.replace(/https*:\/\//, '') +
- '/' +
- pubkey.replace(/\//g, '-') +
- ']';
- // return origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-');
- };
-
- Keys.canonicalize = function (input) {
- if (typeof(input) !== 'string') { return; }
- // key is already in simple form. ensure that it is an 'unsafeKey'
- if (input.length === 44) {
- return unescape(input);
- }
- try {
- return Keys.parseUser(input).pubkey;
- } catch (err) {
- return;
- }
- };
-
- return Keys;
-};
-
- if (typeof(module) !== 'undefined' && module.exports) {
- module.exports = factory();
- } else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
- define([], factory);
- }
-}());
diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js
index 62db751d6..d47c44b56 100644
--- a/www/common/common-ui-elements.js
+++ b/www/common/common-ui-elements.js
@@ -199,7 +199,9 @@ define([
}).filter(function (x) { return x; });
var noOthers = icons.length === 0 ? '.cp-usergrid-empty' : '';
- var classes = noOthers + (config.large?'.large':'') + (config.list?'.list':'');
+ var classes = noOthers + (config.large?'.large':'') +
+ (config.list?'.list':'') +
+ (config.radio?'.radio':'');
var inputFilter = h('input', {
placeholder: Messages.share_filterFriend
@@ -221,20 +223,24 @@ define([
$div.find('.cp-usergrid-user:not(.cp-selected):not([data-name*="'+name+'"])').hide();
}
};
- $(inputFilter).on('keydown keyup change', redraw);
+ $(inputFilter).on('input keydown keyup change', redraw);
+ if (config.evOnFilter) {
+ config.evOnFilter.reg(redraw);
+ }
$(div).append(h('div.cp-usergrid-grid', icons));
if (!config.noSelect) {
$div.on('click', '.cp-usergrid-user', function () {
var sel = $(this).hasClass('cp-selected');
+ if (config.radio) {
+ $div.find('.cp-usergrid-user.cp-selected').removeClass('cp-selected');
+ }
if (!sel) {
$(this).addClass('cp-selected');
- } else {
- var order = $(this).attr('data-order');
- order = order ? 'order:'+order : '';
- $(this).removeClass('cp-selected').attr('style', order);
+ } else if (!config.radio) {
+ $(this).removeClass('cp-selected');
}
- onSelect();
+ onSelect(!sel ? this : undefined);
});
$div.on('keydown', '.cp-usergrid-user', function (e) {
if (e.which === 13) {
@@ -251,6 +257,55 @@ define([
};
};
+ UIElements.getUserTeamPicker = (common, config, onSelected) => {
+ const { msg, friendsData, teamsData } = config;
+ let teams;
+ const filter = h('input', {
+ placeholder: Messages.share_filterFriend
+ });
+ const evOnFilter = Util.mkEvent();
+ const contacts = UIElements.getUserGrid(Messages.contacts, {
+ common: common,
+ data: friendsData,
+ noFilter: false,
+ radio: true,
+ large: true,
+ evOnFilter
+ }, el => {
+ onSelected(el);
+ $(teams.div).find('.cp-selected').removeClass('cp-selected');
+ });
+ teams = UIElements.getUserGrid(Messages.teams, {
+ common: common,
+ data: teamsData,
+ noFilter: false,
+ radio: true,
+ large: true,
+ evOnFilter
+ }, el => {
+ onSelected(el);
+ $(contacts.div).find('.cp-selected').removeClass('cp-selected');
+ });
+
+ const $contactFilter = $(contacts.div).find('.cp-usergrid-filter input').hide();
+ const $teamFilter = $(teams.div).find('.cp-usergrid-filter input').hide();
+
+ $(filter).on('input', () => {
+ const val = filter.value;
+ $teamFilter.val(val);
+ $contactFilter.val(val);
+ evOnFilter.fire();
+ });
+
+ const content = h('div.cp-userteam-picker', [
+ h('div', msg),
+ h('div.cp-userteam-filter', filter),
+ contacts.div,
+ teams.div
+ ]);
+
+ return content;
+ };
UIElements.noContactsMessage = function (common) {
var metadataMgr = common.getMetadataMgr();
@@ -905,7 +960,8 @@ define([
case 'toggle':
button = $(h('button.cp-toolbar-tools', {
//title: data.title || '', // TODO display if the label text is collapsed
- 'aria-label': data.text || Messages.toolbar_tools // Fallback
+ 'aria-label': data.text || Messages.toolbar_tools, // Fallback
+ 'aria-pressed': false
}, [
h('i.fa.' + (data.icon || 'fa-wrench')),
h('span.cp-toolbar-name', data.text || Messages.toolbar_tools)
@@ -923,6 +979,7 @@ define([
button.click(function (e) {
data.element.toggle();
var isVisible = data.element.is(':visible');
+ button.attr('aria-pressed', isVisible ? 'true' : 'false');
if (callback) { callback(isVisible); }
if (isVisible) {
button.addClass('cp-toolbar-button-active');
@@ -1157,20 +1214,40 @@ define([
};
for (var k in actions) {
let $b = $('', {
+ 'data-notippy':1,
'data-type': k,
- 'class': 'pure-button fa ' + actions[k].icon,
- title: Messages['mdToolbar_' + k] || k
- }).click(onClick);
+ 'class': 'pure-button cp-markdown-' + k,
+ 'title': Messages['mdToolbar_' + k] || k,
+ 'aria-label': Messages['mdToolbar_' + k] || k
+ }).append(
+ $('', {
+ 'class': 'fa ' + actions[k].icon,
+ 'aria-hidden': 'true'
+ })).click(onClick);
if (k === "embed") { $toolbar.prepend($b); }
else { $toolbar.append($b); }
}
$('', {
- 'class': 'pure-button fa fa-question cp-markdown-help',
- title: Messages.mdToolbar_help
- }).click(function () {
+ 'data-notippy':1,
+ 'class': 'pure-button cp-markdown-help',
+ 'title': Messages.mdToolbar_help,
+ 'aria-label': Messages.mdToolbar_help
+ }).append(
+ $('', {
+ 'class': 'fa fa-question',
+ 'aria-hidden': 'true'
+ })).click(function () {
var href = Messages.mdToolbar_tutorial;
common.openUnsafeURL(href);
}).appendTo($toolbar);
+
+ $toolbar.on('keydown', function (e) {
+ if (e.key === 'Escape' || e.keyCode === 27) {
+ editor.focus();
+ e.preventDefault();
+ }
+ });
+
return $toolbar;
};
UIElements.createMarkdownToolbar = function (common, editor, opts) {
@@ -1229,9 +1306,65 @@ define([
$toolbarButton.show();
};
+ function isSmallScreen() {
+ return window.innerHeight < 530 || window.innerWidth < 530;
+ }
+
+ var toolbarVisibleOnSmallScreen = false;
+
+ const $toolbarToggleButton = $(h('button.cp-markdown-toggle-button', {
+ 'aria-label': Messages.toolbar_show_text_tools,
+ 'aria-pressed': 'false',
+ 'data-notippy': 1,
+ 'type': 'button',
+ 'title': Messages.toolbar_show_text_tools
+ })).append([
+ h('i.fa.fa-pencil', { 'aria-hidden': 'true' }),
+ h('span.cp-toolbar-label', {}, Messages.toolbar_text_tools)
+ ]).click(function () {
+ var isExpanded = $toolbar.is(':visible');
+ $toolbar.toggle();
+ $(this).toggleClass('cp-toolbar-button-active', !isExpanded)
+ .attr('aria-pressed', String(!isExpanded))
+ .attr('title', !isExpanded ? Messages.toolbar_hide_text_tools : Messages.toolbar_show_text_tools)
+ .attr('aria-label', !isExpanded ? Messages.toolbar_hide_text_tools : Messages.toolbar_show_text_tools);
+ toolbarVisibleOnSmallScreen = !isExpanded;
+ }).on('keydown keyup', e => {
+ // don't close modals when pressing Enter
+ // on the button
+ e.stopPropagation();
+ }).hide();
+
+ const updateToolbarVisibility = () => {
+ if (isSmallScreen()) {
+ $toolbarToggleButton.show();
+ if (toolbarVisibleOnSmallScreen) {
+ $toolbar.show();
+ $toolbarToggleButton.addClass('cp-toolbar-button-active')
+ .attr('aria-pressed', 'true');
+ } else {
+ $toolbar.hide();
+ $toolbarToggleButton.removeClass('cp-toolbar-button-active')
+ .attr('aria-pressed', 'false');
+ }
+ return;
+ }
+
+ $toolbarToggleButton.hide();
+ $toolbar.show();
+ };
+
+ if (opts?.toggleBar) {
+ $(window).on('resize', updateToolbarVisibility);
+ // Small delay to ensure the toolbar layout has rendered
+ // before checking for wrapping
+ setTimeout(updateToolbarVisibility);
+ }
+
return {
toolbar: $toolbar,
button: $toolbarButton,
+ toggleButton: $toolbarToggleButton[0],
setState: setState
};
};
@@ -1279,14 +1412,16 @@ define([
common.fixLinks(text);
- var closeButton = h('span.cp-help-close.fa.fa-times');
+ var closeButton = h('button.cp-help-close.fa.fa-times', {
+ title: Messages.help_close_button
+ });
var $toolbarButton = common.createButton('', true, {
text: Messages.help_button,
name: 'help'
}).addClass('cp-toolbar-button-active');
var help = h('div.cp-help-container', [
- closeButton,
- text
+ text,
+ closeButton
]);
$toolbarButton.attr('title', Messages.show_help_button);
@@ -1318,7 +1453,6 @@ define([
text: text
};
};
-
/* Create a usage bar which keeps track of how much storage space is used
by your CryptDrive. The getPinnedUsage RPC is one of the heavier calls,
so we throttle its usage. Clients will not update more than once per
@@ -1366,6 +1500,7 @@ define([
var urls = common.getMetadataMgr().getPrivateData().accounts;
var makeDonateButton = function () {
+ if (plan) { return; }
var $a = $('', {
'class': 'cp-limit-upgrade btn btn-primary',
href: urls.donateURL,
@@ -1377,30 +1512,16 @@ define([
});
};
- var makeUpgradeButton = function () {
- var $a = $(' ', {
- 'class': 'cp-limit-upgrade btn btn-success',
- href: urls.upgradeURL,
- rel: "noreferrer noopener",
- target: "_blank",
- }).text(Messages.upgradeAccount).appendTo($buttons);
- $a.click(function () {
- Feedback.send('UPGRADE_ACCOUNT');
- });
- };
-
if (!Config.removeDonateButton) {
- if (!common.isLoggedIn() || !Config.allowSubscriptions) {
- // user is not logged in, or subscriptions are disallowed
- makeDonateButton();
- } else if (!plan) {
- // user is logged in and subscriptions are allowed
- // and they don't have one. show upgrades
- makeUpgradeButton();
- makeDonateButton();
- } else {
- // they have a plan. show nothing
- }
+ // Messages.upgradeAccount
+ common.getExtensionsSync('USAGE_BUTTON').some(ext => {
+ if (!ext.getButton) { return; }
+ let $b = ext.getButton(common, plan);
+ if (!$b) { return; }
+ $buttons.append($b);
+ });
+ // Add donate button
+ makeDonateButton();
}
var prettyUsage;
@@ -2198,19 +2319,16 @@ define([
// section to determine if we have to manually hide a separator.
var surveyAlone = true;
- if (Config.allowSubscriptions) {
+ Common.getExtensionsSync('USERMENU_ITEM').forEach(ext => {
+ if (!ext.getItem) {
+ return void console.error("Missing attribute for extension point", "USERMENU_ITEM", ext);
+ }
+ let item = ext.getItem(Common);
+ if (!item) { return; }
surveyAlone = false;
- options.push({
- tag: 'a',
- attributes: {
- 'class': 'fa fa-star-o'
- },
- content: h('span', priv.plan ? Messages.settings_cat_subscription : Messages.pricing),
- action: function () {
- Common.openURL(priv.plan ? priv.accounts.upgradeURL :'/features.html');
- },
- });
- }
+ options.push(item);
+ });
+
if (!priv.plan && !Config.removeDonateButton) {
surveyAlone = false;
options.push({
@@ -2649,10 +2767,10 @@ define([
var $creationContainer = $('