mirror of
https://github.com/cryptpad/cryptpad.git
synced 2026-09-12 19:49:59 +05:00
Merge branch 'staging' into table_pad_md_formatting
This commit is contained in:
commit
ffa122f2a3
@ -1,40 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
node_modules/
|
||||
www/components/
|
||||
www/bower_components/
|
||||
www/common/onlyoffice/dist
|
||||
www/common/onlyoffice/x2t
|
||||
onlyoffice-dist/
|
||||
|
||||
www/scratch
|
||||
www/accounts
|
||||
www/lib
|
||||
www/accounts
|
||||
www/worker
|
||||
www/todo
|
||||
|
||||
#lib/plugins/
|
||||
|
||||
www/common/hyperscript.js
|
||||
|
||||
www/pad/wysiwygarea-plugin.js
|
||||
www/pad/mediatag-plugin.js
|
||||
www/pad/mediatag-plugin-dialog.js
|
||||
www/pad/disable-base64.js
|
||||
www/pad/wordcount/
|
||||
|
||||
www/kanban/jkanban.js
|
||||
www/common/jscolor.js
|
||||
|
||||
www/common/media-tag-nacl.min.js
|
||||
|
||||
customize/
|
||||
|
||||
www/debug/chainpad.dist.js
|
||||
|
||||
www/pad/mathjax/
|
||||
www/code/mermaid*.js
|
||||
www/code/orgmode.js
|
||||
58
.eslintrc.js
58
.eslintrc.js
@ -1,58 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
module.exports = {
|
||||
'env': {
|
||||
'browser': true,
|
||||
'es2021': true,
|
||||
'node': true
|
||||
},
|
||||
'plugins': ['compat'],
|
||||
'extends': ['eslint:recommended', 'plugin:compat/recommended'],
|
||||
"globals": {
|
||||
"define": "readonly",
|
||||
},
|
||||
'overrides': [
|
||||
{
|
||||
'env': {
|
||||
'node': true
|
||||
},
|
||||
'files': [
|
||||
'.eslintrc.{js,cjs}'
|
||||
],
|
||||
'parserOptions': {
|
||||
'sourceType': 'script'
|
||||
}
|
||||
}
|
||||
],
|
||||
'parserOptions': {
|
||||
'ecmaVersion': 'latest'
|
||||
},
|
||||
'rules': {
|
||||
'indent': [
|
||||
'off', // TODO enable this check
|
||||
4
|
||||
],
|
||||
'linebreak-style': [
|
||||
'off', // git handles linebreak conversion for us
|
||||
'unix'
|
||||
],
|
||||
'quotes': [
|
||||
'off', // TODO enable this check
|
||||
'single'
|
||||
],
|
||||
'semi': [
|
||||
'error',
|
||||
'always'
|
||||
],
|
||||
|
||||
// TODO remove these exceptions from the eslint defaults
|
||||
'no-irregular-whitespace': ['off'],
|
||||
'no-self-assign': ['off'],
|
||||
'no-empty': ['off'],
|
||||
'no-useless-escape': ['off'],
|
||||
'no-extra-boolean-cast': ['off'],
|
||||
'no-prototype-builtins': ['off'],
|
||||
}
|
||||
};
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -32,6 +32,8 @@ www/common/onlyoffice/v*
|
||||
/onlyoffice-conf/
|
||||
/onlyoffice-dist/
|
||||
|
||||
_build
|
||||
|
||||
# ---> Node
|
||||
# Logs
|
||||
logs
|
||||
|
||||
@ -167,13 +167,20 @@ define([
|
||||
|
||||
let popup = h('div.cp-extensions-popups');
|
||||
let utils = { h, Util, Hash };
|
||||
Extensions.getExtensions('HOMEPAGE_POPUP').forEach(ext => {
|
||||
if (typeof(ext.check) === "function" && !ext.check()) { return; }
|
||||
ext.getContent(utils, content => {
|
||||
$(popup).append(h('div.cp-extensions-popup', content));
|
||||
|
||||
Extensions.getExtensions('HOMEPAGE_POPUP').forEach(_ext => {
|
||||
_ext.then(ext => {
|
||||
if (ext) {
|
||||
ext.getContent(utils, content => {
|
||||
$(popup).append(h('div.cp-extensions-popup', content));
|
||||
});
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
return [
|
||||
h('div#cp-main', [
|
||||
Pages.infopageTopbar(),
|
||||
|
||||
@ -734,10 +734,6 @@
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
.cp-notifications-empty {
|
||||
color: @cp_dropdown-fg;
|
||||
padding: 5px;
|
||||
}
|
||||
button {
|
||||
position: relative;
|
||||
.cp-dropdown-button-title {
|
||||
@ -765,6 +761,10 @@
|
||||
margin: 0 -5px;
|
||||
padding: 0;
|
||||
}
|
||||
.cp-notifications-empty {
|
||||
color: @cp_dropdown-fg;
|
||||
padding: 0.3em;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-toolbar-link {
|
||||
|
||||
93
eslint.config.js
Normal file
93
eslint.config.js
Normal file
@ -0,0 +1,93 @@
|
||||
// SPDX-FileCopyrightText: 2024 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
const compatPlugin = require("eslint-plugin-compat");
|
||||
const globals = require("globals");
|
||||
const js = require("@eslint/js");
|
||||
const FlatCompat = require("@eslint/eslintrc").FlatCompat;
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
allConfig: js.configs.all
|
||||
});
|
||||
|
||||
module.exports = [{
|
||||
ignores: [
|
||||
"**/node_modules/",
|
||||
"www/components/",
|
||||
"www/bower_components/",
|
||||
"www/common/onlyoffice/dist",
|
||||
"www/common/onlyoffice/x2t",
|
||||
"**/onlyoffice-dist/",
|
||||
"www/scratch",
|
||||
"www/accounts",
|
||||
"www/lib",
|
||||
"www/accounts",
|
||||
"www/worker",
|
||||
"www/todo",
|
||||
"**/*worker.bundle.js",
|
||||
"**/_build",
|
||||
"www/common/hyperscript.js",
|
||||
"www/pad/wysiwygarea-plugin.js",
|
||||
"www/pad/mediatag-plugin.js",
|
||||
"www/pad/mediatag-plugin-dialog.js",
|
||||
"www/pad/disable-base64.js",
|
||||
"www/pad/wordcount/",
|
||||
"www/kanban/jkanban.js",
|
||||
"www/common/jscolor.js",
|
||||
"www/common/media-tag-nacl.min.js",
|
||||
"**/customize/",
|
||||
"www/debug/chainpad.dist.js",
|
||||
"www/pad/mathjax/",
|
||||
"www/code/mermaid*.js",
|
||||
"www/code/orgmode.js",
|
||||
],
|
||||
}, ...compat.extends("eslint:recommended", "plugin:compat/recommended"), {
|
||||
plugins: {
|
||||
compatPlugin,
|
||||
},
|
||||
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node,
|
||||
define: "readonly",
|
||||
},
|
||||
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "commonjs",
|
||||
},
|
||||
|
||||
rules: {
|
||||
indent: ["off", 4],
|
||||
"linebreak-style": ["off", "unix"],
|
||||
quotes: ["off", "single"],
|
||||
semi: ["error", "always"],
|
||||
"no-irregular-whitespace": ["off"],
|
||||
"no-self-assign": ["off"],
|
||||
"no-empty": ["off"],
|
||||
"no-useless-escape": ["off"],
|
||||
"no-extra-boolean-cast": ["off"],
|
||||
"no-prototype-builtins": ["off"],
|
||||
"no-use-before-define": ["error"],
|
||||
"no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
caughtErrors: "none"
|
||||
}
|
||||
]
|
||||
},
|
||||
}, {
|
||||
files: ["**/.eslintrc.{js,cjs}"],
|
||||
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
},
|
||||
|
||||
ecmaVersion: 5,
|
||||
sourceType: "commonjs",
|
||||
},
|
||||
}];
|
||||
@ -108,7 +108,7 @@ nThen(function (w) {
|
||||
};
|
||||
|
||||
// spawn ws server and attach netflux event handlers
|
||||
let Server = NetfluxSrv.create(new WebSocketServer({ server: Env.httpServer}))
|
||||
let Server = Env.Server = NetfluxSrv.create(new WebSocketServer({ server: Env.httpServer}))
|
||||
.on('channelClose', historyKeeper.channelClose)
|
||||
.on('channelMessage', historyKeeper.channelMessage)
|
||||
.on('channelOpen', historyKeeper.channelOpen)
|
||||
|
||||
@ -321,6 +321,7 @@ const storeMessage = function (Env, channel, msg, isCp, optionalMessageHash, tim
|
||||
// Store the message first, and update the index only once it's stored.
|
||||
// store.messageBin can be async so updating the index first may
|
||||
// result in a wrong cpIndex
|
||||
Env.plugins?.MONITORING?.increment(`storeMessage`);
|
||||
nThen((waitFor) => {
|
||||
Env.store.messageBin(id, msgBin, waitFor(function (err) {
|
||||
if (err) {
|
||||
@ -392,6 +393,8 @@ const storeMessage = function (Env, channel, msg, isCp, optionalMessageHash, tim
|
||||
var msgLength = msgBin.length;
|
||||
index.size += msgLength;
|
||||
|
||||
Env.plugins?.MONITORING?.increment('broadcastMessage', (channel.length-1));
|
||||
|
||||
// handle the next element in the queue
|
||||
next();
|
||||
|
||||
@ -522,6 +525,7 @@ const getHistoryAsync = (Env, channelName, lastKnownHash, beforeHash, handler, c
|
||||
const store = Env.store;
|
||||
|
||||
let offset = -1;
|
||||
Env.plugins?.MONITORING?.increment(`getHistoryAsync`);
|
||||
nThen((waitFor) => {
|
||||
getHistoryOffset(Env, channelName, lastKnownHash, waitFor((err, os) => {
|
||||
if (err) {
|
||||
@ -773,6 +777,10 @@ const handleGetHistoryRange = function (Env, Server, seq, userId, parsed) {
|
||||
Env.getOlderHistory(channelName, oldestKnownHash, untilHash, desiredMessages, desiredCheckpoint, function (err, toSend) {
|
||||
if (err && err.code !== 'ENOENT') {
|
||||
Env.Log.error("HK_GET_OLDER_HISTORY", err);
|
||||
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId,
|
||||
JSON.stringify(['HISTORY_RANGE_ERROR', txid, err])
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(toSend)) {
|
||||
|
||||
@ -35,7 +35,7 @@ const guid = () => {
|
||||
return Util.guid(response._pending);
|
||||
};
|
||||
|
||||
const sendMessage = (msg, cb, opt) => {
|
||||
const sendMessage = Env.sendMessage = (msg, cb, opt) => {
|
||||
var txid = guid();
|
||||
var timeout = (opt && opt.timeout) || DEFAULT_QUERY_TIMEOUT;
|
||||
var obj = {
|
||||
@ -71,6 +71,7 @@ EVENTS.ENV_UPDATE = function (data /*, cb */) {
|
||||
Env = JSON.parse(data);
|
||||
Env.Log = Log;
|
||||
Env.plugins = plugins;
|
||||
Env.sendMessage = sendMessage;
|
||||
Env.incrementBytesWritten = function () {};
|
||||
} catch (err) {
|
||||
Log.error('HTTP_WORKER_ENV_UPDATE', Util.serializeError(err));
|
||||
@ -91,6 +92,23 @@ EVENTS.FLUSH_CACHE = function (data) {
|
||||
});
|
||||
};
|
||||
|
||||
Object.keys(plugins || {}).forEach(name => {
|
||||
let plugin = plugins[name];
|
||||
if (!plugin.addHttpEvents) { return; }
|
||||
try {
|
||||
let events = plugin.addHttpEvents(Env);
|
||||
Object.keys(events || {}).forEach(cmd => {
|
||||
// Uppercase event name?
|
||||
if (cmd !== cmd.toUpperCase()) { return; }
|
||||
// Event is a function?
|
||||
if (typeof(events[cmd]) !== "function") { return; }
|
||||
// Event doesn't already exists?
|
||||
if (EVENTS[cmd]) { return; }
|
||||
EVENTS[cmd] = events[cmd];
|
||||
});
|
||||
} catch (e) {}
|
||||
});
|
||||
|
||||
process.on('message', msg => {
|
||||
if (!(msg && msg.txid)) { return; }
|
||||
if (msg.type === 'REPLY') {
|
||||
@ -799,7 +817,12 @@ nThen(function (w) {
|
||||
}));
|
||||
}).nThen(function () {
|
||||
// TODO inform the parent process that this worker is ready
|
||||
|
||||
Object.keys(Env.plugins || {}).forEach(name => {
|
||||
let plugin = plugins[name];
|
||||
if (!plugin.initialize) { return; }
|
||||
try { plugin.initialize(Env, "http-worker"); }
|
||||
catch (e) {}
|
||||
});
|
||||
});
|
||||
|
||||
process.on('uncaughtException', function (err) {
|
||||
|
||||
@ -34,6 +34,8 @@ var isUnauthenticateMessage = function (msg) {
|
||||
var handleUnauthenticatedMessage = function (Env, msg, respond, Server, netfluxId) {
|
||||
Env.Log.silly('LOG_RPC', msg[0]);
|
||||
|
||||
Env.plugins?.MONITORING?.increment(`rpc_${msg[0]}`);
|
||||
|
||||
var method = UNAUTHENTICATED_CALLS[msg[0]];
|
||||
method(Env, msg[1], function (err, value) {
|
||||
if (err) {
|
||||
@ -170,6 +172,8 @@ var rpc = function (Env, Server, userId, data, respond) {
|
||||
|
||||
var command = msg[1];
|
||||
|
||||
Env.plugins?.MONITORING?.increment(`rpc_${command}`);
|
||||
|
||||
if (command === 'UPLOAD') {
|
||||
// UPLOAD is a special case that skips signature validation
|
||||
// intentional fallthrough behaviour
|
||||
|
||||
@ -17,11 +17,18 @@ const Tasks = require("../storage/tasks");
|
||||
const Nacl = require('tweetnacl/nacl-fast');
|
||||
const Eviction = require("../eviction");
|
||||
const CPCrypto = require('../crypto');
|
||||
const plugins = require("../plugin-manager");
|
||||
|
||||
const Env = {
|
||||
Log: {},
|
||||
};
|
||||
|
||||
const Monitoring = plugins && plugins.MONITORING;
|
||||
const monitoringIncrement = key => {
|
||||
if (!Monitoring || !Monitoring.increment) { return; }
|
||||
Monitoring.increment(key);
|
||||
};
|
||||
|
||||
// support the usual log API but pass it to the main process
|
||||
Logger.levels.forEach(function (level) {
|
||||
Env.Log[level] = function (label, info) {
|
||||
@ -33,6 +40,8 @@ Logger.levels.forEach(function (level) {
|
||||
};
|
||||
});
|
||||
|
||||
const HISTORY_SIZE_LIMIT = 1024 * 1024 * 1024; // XXX 1GB
|
||||
|
||||
var DETAIL = 1000;
|
||||
var round = function (n) {
|
||||
return Math.floor(n * DETAIL) / DETAIL;
|
||||
@ -57,6 +66,17 @@ const init = function (config, _cb) {
|
||||
Env.archiveRetentionTime = config.archiveRetentionTime;
|
||||
Env.accountRetentionTime = config.accountRetentionTime;
|
||||
|
||||
Env.sendMessage = data => {
|
||||
process.send(data);
|
||||
};
|
||||
|
||||
Object.keys(plugins || {}).forEach(name => {
|
||||
let plugin = plugins[name];
|
||||
if (!plugin.initialize) { return; }
|
||||
try { plugin.initialize(Env, "db-worker"); }
|
||||
catch (e) {}
|
||||
});
|
||||
|
||||
nThen(function (w) {
|
||||
Store.create(config, w(function (err, _store) {
|
||||
if (err) {
|
||||
@ -277,6 +297,8 @@ const computeIndex = function (data, cb) {
|
||||
const channelName = data.channel;
|
||||
const CB = Util.once(cb);
|
||||
|
||||
monitoringIncrement('computeIndex');
|
||||
|
||||
var start = 0;
|
||||
nThen(function (w) {
|
||||
store.getOffset(channelName, w(function (err, obj) {
|
||||
@ -298,6 +320,7 @@ const computeIndex = function (data, cb) {
|
||||
});
|
||||
}
|
||||
w.abort();
|
||||
monitoringIncrement('computeIndexFromOffset');
|
||||
CB(err, index);
|
||||
}));
|
||||
}).nThen(function (w) {
|
||||
@ -306,6 +329,7 @@ const computeIndex = function (data, cb) {
|
||||
store.clearOffset(channelName, w());
|
||||
}).nThen(function () {
|
||||
// now get the history as though it were the first time
|
||||
monitoringIncrement('computeIndexFromStart');
|
||||
computeIndexFromOffset(channelName, 0, CB);
|
||||
});
|
||||
};
|
||||
@ -313,6 +337,7 @@ const computeIndex = function (data, cb) {
|
||||
const computeMetadata = function (data, cb) {
|
||||
const ref = {};
|
||||
const lineHandler = Meta.createLineHandler(ref, Env.Log.error);
|
||||
monitoringIncrement('computeMetadata');
|
||||
return void store.readChannelMetadata(data.channel, lineHandler, function (err) {
|
||||
if (err) {
|
||||
// stream errors?
|
||||
@ -322,6 +347,31 @@ const computeMetadata = function (data, cb) {
|
||||
});
|
||||
};
|
||||
|
||||
const _getFileSize = function (channel, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
if (!Core.isValidId(channel)) { return void cb('INVALID_CHAN'); }
|
||||
if (channel.length === HK.STANDARD_CHANNEL_LENGTH ||
|
||||
channel.length === HK.ADMIN_CHANNEL_LENGTH) {
|
||||
return void store.getChannelSize(channel, function (e, size) {
|
||||
if (e) {
|
||||
if (e.code === 'ENOENT') { return void cb(void 0, 0); }
|
||||
return void cb(e.code);
|
||||
}
|
||||
cb(void 0, size);
|
||||
});
|
||||
}
|
||||
|
||||
// 'channel' refers to a file, so you need another API
|
||||
blobStore.size(channel, function (e, size) {
|
||||
if (typeof(size) === 'undefined') { return void cb(e); }
|
||||
cb(void 0, size);
|
||||
});
|
||||
};
|
||||
|
||||
const getFileSize = function (data, cb) {
|
||||
_getFileSize(data.channel, cb);
|
||||
};
|
||||
|
||||
/* getOlderHistory
|
||||
* allows clients to query for all messages until a known hash is read
|
||||
* stores all messages in history as they are read
|
||||
@ -339,6 +389,7 @@ const getOlderHistory = function (data, cb) {
|
||||
const desiredMessages = data.desiredMessages;
|
||||
const desiredCheckpoint = data.desiredCheckpoint;
|
||||
|
||||
var next = () => {
|
||||
var messages = [];
|
||||
var found = false;
|
||||
store.getMessages(channelName, function (msgStr) {
|
||||
@ -384,6 +435,15 @@ const getOlderHistory = function (data, cb) {
|
||||
}
|
||||
cb(err, toSend);
|
||||
});
|
||||
};
|
||||
|
||||
_getFileSize(channelName, function (err, size) {
|
||||
if (err) { return void cb(err); }
|
||||
if (size > HISTORY_SIZE_LIMIT) {
|
||||
return void cb('HISTORY_TOO_LARGE');
|
||||
}
|
||||
next();
|
||||
});
|
||||
};
|
||||
|
||||
const getPinState = function (data, cb) {
|
||||
@ -393,6 +453,7 @@ const getPinState = function (data, cb) {
|
||||
var lineHandler = Pins.createLineHandler(ref, Env.Log.error);
|
||||
|
||||
// if channels aren't in memory. load them from disk
|
||||
monitoringIncrement('getPin');
|
||||
pinStore.readMessagesBin(safeKey, 0, (msgObj, readMore) => {
|
||||
lineHandler(msgObj.buff.toString('utf8'));
|
||||
readMore();
|
||||
@ -401,31 +462,6 @@ const getPinState = function (data, cb) {
|
||||
});
|
||||
};
|
||||
|
||||
const _getFileSize = function (channel, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
if (!Core.isValidId(channel)) { return void cb('INVALID_CHAN'); }
|
||||
if (channel.length === HK.STANDARD_CHANNEL_LENGTH ||
|
||||
channel.length === HK.ADMIN_CHANNEL_LENGTH) {
|
||||
return void store.getChannelSize(channel, function (e, size) {
|
||||
if (e) {
|
||||
if (e.code === 'ENOENT') { return void cb(void 0, 0); }
|
||||
return void cb(e.code);
|
||||
}
|
||||
cb(void 0, size);
|
||||
});
|
||||
}
|
||||
|
||||
// 'channel' refers to a file, so you need another API
|
||||
blobStore.size(channel, function (e, size) {
|
||||
if (typeof(size) === 'undefined') { return void cb(e); }
|
||||
cb(void 0, size);
|
||||
});
|
||||
};
|
||||
|
||||
const getFileSize = function (data, cb) {
|
||||
_getFileSize(data.channel, cb);
|
||||
};
|
||||
|
||||
const _iterateFiles = function (channels, handler, cb) {
|
||||
if (!Array.isArray(channels)) { return cb('INVALID_LIST'); }
|
||||
var L = channels.length;
|
||||
@ -449,6 +485,7 @@ const _iterateFiles = function (channels, handler, cb) {
|
||||
|
||||
const getTotalSize = function (data, cb) {
|
||||
var bytes = 0;
|
||||
monitoringIncrement('getTotalSize');
|
||||
_iterateFiles(data.channels, function (channel, next) {
|
||||
_getFileSize(channel, function (err, size) {
|
||||
if (!err) { bytes += size; }
|
||||
@ -494,6 +531,7 @@ const getHashOffset = function (data, cb) {
|
||||
const lastKnownHash = data.hash;
|
||||
if (typeof(lastKnownHash) !== 'string') { return void cb("INVALID_HASH"); }
|
||||
|
||||
monitoringIncrement('getHashOffset');
|
||||
var offset = -1;
|
||||
store.readMessagesBin(channelName, 0, (msgObj, readMore, abort) => {
|
||||
// tryParse return a parsed message or undefined
|
||||
@ -592,6 +630,8 @@ const completeUpload = function (data, cb) {
|
||||
var arg = data.arg;
|
||||
var size = data.size;
|
||||
|
||||
monitoringIncrement('uploadedBlob');
|
||||
|
||||
var method;
|
||||
var label;
|
||||
if (owned) {
|
||||
@ -671,6 +711,7 @@ const COMMANDS = {
|
||||
};
|
||||
|
||||
COMMANDS.INLINE = function (data, cb) {
|
||||
monitoringIncrement('inlineValidation');
|
||||
var signedMsg;
|
||||
try {
|
||||
signedMsg = Nacl.util.decodeBase64(data.msg);
|
||||
@ -695,6 +736,7 @@ COMMANDS.INLINE = function (data, cb) {
|
||||
|
||||
const checkDetachedSignature = function (signedMsg, signature, publicKey) {
|
||||
if (!(signedMsg && publicKey)) { return false; }
|
||||
monitoringIncrement('detachedValidation');
|
||||
|
||||
var signedBuffer;
|
||||
var pubBuffer;
|
||||
@ -758,13 +800,28 @@ COMMANDS.HASH_CHANNEL_LIST = function (data, cb) {
|
||||
};
|
||||
|
||||
COMMANDS.VALIDATE_ANCESTOR_PROOF = function (data, cb) {
|
||||
monitoringIncrement('validateAncestorProof');
|
||||
Block.validateAncestorProof(Env, data && data.proof, cb);
|
||||
};
|
||||
|
||||
COMMANDS.VALIDATE_LOGIN_BLOCK = function (data, cb) {
|
||||
monitoringIncrement('validateLoginBlock');
|
||||
Block.validateLoginBlock(Env, data.publicKey, data.signature, data.block, cb);
|
||||
};
|
||||
|
||||
Object.keys(plugins || {}).forEach(name => {
|
||||
let plugin = plugins[name];
|
||||
if (!plugin.addWorkerCommands) { return; }
|
||||
try {
|
||||
let events = plugin.addWorkerCommands(Env);
|
||||
Object.keys(events || {}).forEach(cmd => {
|
||||
if (typeof(events[cmd]) !== "function") { return; }
|
||||
if (COMMANDS[cmd]) { return; }
|
||||
COMMANDS[cmd] = events[cmd];
|
||||
});
|
||||
} catch (e) {}
|
||||
});
|
||||
|
||||
process.on('message', function (data) {
|
||||
if (!data || !data.txid || !data.pid) {
|
||||
return void process.send({
|
||||
@ -773,6 +830,13 @@ process.on('message', function (data) {
|
||||
});
|
||||
}
|
||||
|
||||
const command = COMMANDS[data.command];
|
||||
|
||||
// Command broadcasted to all workers: no callback expected
|
||||
if (data.type === 'broadcast') {
|
||||
return void command(data);
|
||||
}
|
||||
|
||||
const cb = function (err, value) {
|
||||
process.send({
|
||||
error: Util.serializeError(err),
|
||||
@ -790,7 +854,6 @@ process.on('message', function (data) {
|
||||
});
|
||||
}
|
||||
|
||||
const command = COMMANDS[data.command];
|
||||
if (typeof(command) !== 'function') {
|
||||
return void cb("E_BAD_COMMAND");
|
||||
}
|
||||
|
||||
@ -152,9 +152,34 @@ Workers.initialize = function (Env, config, _cb) {
|
||||
// default to timing out affter 180s if no explicit timeout is passed
|
||||
var timeout = typeof(opt.timeout) !== 'undefined'? opt.timeout: DEFAULT_QUERY_TIMEOUT;
|
||||
response.expect(txid, cb, timeout);
|
||||
|
||||
delete msg._cb;
|
||||
delete msg._opt;
|
||||
state.worker.send(msg);
|
||||
|
||||
// Add original callback to message data in case we need
|
||||
// to resend the command. setTimeout to avoid interfering
|
||||
// with worker.send
|
||||
setTimeout(function () {
|
||||
msg._cb = _cb;
|
||||
msg._opt = opt;
|
||||
});
|
||||
};
|
||||
|
||||
const pluginsResponses = {};
|
||||
Object.keys(Env.plugins || {}).forEach(name => {
|
||||
let plugin = Env.plugins[name];
|
||||
if (!plugin.addWorkerResponses) { return; }
|
||||
try {
|
||||
let res = plugin.addWorkerResponses(Env);
|
||||
Object.keys(res || {}).forEach(key => {
|
||||
if (typeof(res[key]) !== "function") { return; }
|
||||
if (pluginsResponses[key]) { return; }
|
||||
pluginsResponses[key] = res[key];
|
||||
});
|
||||
} catch (e) {}
|
||||
});
|
||||
|
||||
var handleResponse = function (state, res) {
|
||||
if (!res) { return; }
|
||||
// handle log messages before checking if it was addressed to your PID
|
||||
@ -162,6 +187,17 @@ Workers.initialize = function (Env, config, _cb) {
|
||||
if (res.log) {
|
||||
return void handleLog(res.log, res.label, res.info);
|
||||
}
|
||||
|
||||
// handle plugins
|
||||
if (res.plugin) {
|
||||
Object.keys(pluginsResponses).some(key => {
|
||||
if (res.type !== key) { return; }
|
||||
pluginsResponses[key](res.data);
|
||||
return true;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// but don't bother handling things addressed to other processes
|
||||
// since it's basically guaranteed not to work
|
||||
if (res.pid !== PID) {
|
||||
@ -226,7 +262,15 @@ Workers.initialize = function (Env, config, _cb) {
|
||||
handleResponse(state, res);
|
||||
});
|
||||
|
||||
let pid = worker.pid;
|
||||
var substituteWorker = Util.once(function () {
|
||||
Object.keys(Env.plugins || {}).forEach(name => {
|
||||
let plugin = Env.plugins[name];
|
||||
if (!plugin.onWorkerClosed) { return; }
|
||||
try { plugin.onWorkerClosed("db-worker", pid); }
|
||||
catch (e) {}
|
||||
});
|
||||
|
||||
Env.Log.info("SUBSTITUTE_DB_WORKER", '');
|
||||
var idx = workers.indexOf(state);
|
||||
if (idx !== -1) {
|
||||
@ -237,26 +281,36 @@ Workers.initialize = function (Env, config, _cb) {
|
||||
const cb = response.expectation(txid);
|
||||
if (typeof(cb) !== 'function') { return; }
|
||||
const task = state.tasks[txid];
|
||||
if (!(task && task.msg)) { return; }
|
||||
if (!task) { return; }
|
||||
response.clear(txid);
|
||||
Log.info('DB_WORKER_RESEND', task.msg);
|
||||
sendCommand(task.msg, cb);
|
||||
Log.info('DB_WORKER_RESEND', task);
|
||||
sendCommand(task, task._cb || cb, task._opt);
|
||||
});
|
||||
|
||||
var w = fork(DB_PATH);
|
||||
initWorker(w, function (err, state) {
|
||||
initWorker(w, function (err) {
|
||||
if (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
workers.push(state);
|
||||
});
|
||||
});
|
||||
|
||||
worker.on('exit', substituteWorker);
|
||||
worker.on('close', substituteWorker);
|
||||
worker.on('exit', function () {
|
||||
substituteWorker();
|
||||
Env.Log.error("DB_WORKER_EXIT", {
|
||||
pid: state.pid,
|
||||
});
|
||||
});
|
||||
worker.on('close', function () {
|
||||
substituteWorker();
|
||||
Env.Log.error("DB_WORKER_CLOSE", {
|
||||
pid: state.pid,
|
||||
});
|
||||
});
|
||||
worker.on('error', function (err) {
|
||||
substituteWorker();
|
||||
Env.Log.error("DB_WORKER_ERROR", {
|
||||
pid: state.pid,
|
||||
error: err,
|
||||
});
|
||||
});
|
||||
@ -282,6 +336,18 @@ Workers.initialize = function (Env, config, _cb) {
|
||||
}));
|
||||
});
|
||||
}).nThen(function () {
|
||||
Env.broadcastWorkerCommand = (data) => {
|
||||
workers.forEach(state => {
|
||||
state.worker.send({
|
||||
type: 'broadcast',
|
||||
pid: PID,
|
||||
command: data.command,
|
||||
txid: data.txid
|
||||
});
|
||||
});
|
||||
return workers;
|
||||
};
|
||||
|
||||
Env.computeIndex = function (Env, channel, cb) {
|
||||
Env.store.getWeakLock(channel, function (next) {
|
||||
sendCommand({
|
||||
|
||||
828
package-lock.json
generated
828
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "cryptpad",
|
||||
"description": "a collaborative office suite that is end-to-end encrypted and open-source",
|
||||
"version": "2024.9.0",
|
||||
"version": "2024.9.1",
|
||||
"license": "AGPL-3.0+",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -22,23 +22,23 @@
|
||||
"chainpad-crypto": "^0.2.5",
|
||||
"chainpad-listmap": "^1.1.0",
|
||||
"chainpad-netflux": "^1.2.0",
|
||||
"chainpad-server": "^5.2.2",
|
||||
"chainpad-server": "^5.2.4",
|
||||
"ckeditor": "npm:ckeditor4@~4.22.1",
|
||||
"codemirror": "^5.19.0",
|
||||
"components-font-awesome": "^4.6.3",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"croppie": "^2.5.0",
|
||||
"dragula": "3.7.2",
|
||||
"drawio": "github:cryptpad/drawio-npm#npm-21.8.2+5",
|
||||
"express": "~4.21.0",
|
||||
"express": "~4.21.1",
|
||||
"file-saver": "1.3.1",
|
||||
"fs-extra": "^7.0.0",
|
||||
"get-folder-size": "^2.0.1",
|
||||
"html2canvas": "^1.4.0",
|
||||
"http-proxy-middleware": "^3.0.2",
|
||||
"http-proxy-middleware": "^3.0.3",
|
||||
"hyper-json": "~1.4.0",
|
||||
"jquery": "3.6.0",
|
||||
"json.sortify": "~2.1.0",
|
||||
"json.sortify": "github:cryptpad/JSON.sortify",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"jszip": "3.10.1",
|
||||
"localforage": "^1.5.2",
|
||||
@ -67,8 +67,8 @@
|
||||
"x2js": "^3.4.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-compat": "^4.2.0",
|
||||
"eslint": "^9.16.0",
|
||||
"eslint-plugin-compat": "^6.0.1",
|
||||
"stylelint": "^16.9.0",
|
||||
"stylelint-config-standard-less": "^3.0.1"
|
||||
},
|
||||
|
||||
37
server.js
37
server.js
@ -25,6 +25,13 @@ var app = Express();
|
||||
}
|
||||
}());
|
||||
|
||||
Object.keys(Env.plugins || {}).forEach(name => {
|
||||
let plugin = Env.plugins[name];
|
||||
if (!plugin.initialize) { return; }
|
||||
try { plugin.initialize(Env, "main"); }
|
||||
catch (e) {}
|
||||
});
|
||||
|
||||
var COMMANDS = {};
|
||||
|
||||
COMMANDS.LOG = function (msg, cb) {
|
||||
@ -49,6 +56,25 @@ COMMANDS.GET_PROFILING_DATA = function (msg, cb) {
|
||||
cb(void 0, Env.bytesWritten);
|
||||
};
|
||||
|
||||
Object.keys(Env.plugins || {}).forEach(name => {
|
||||
let plugin = Env.plugins[name];
|
||||
if (!plugin.addMainCommands) { return; }
|
||||
try {
|
||||
let commands = plugin.addMainCommands(Env);
|
||||
Object.keys(commands || {}).forEach(cmd => {
|
||||
// Uppercase command name?
|
||||
if (cmd !== cmd.toUpperCase()) { return; }
|
||||
// Command is a function?
|
||||
if (typeof(commands[cmd]) !== "function") { return; }
|
||||
// Command doesn't already exists?
|
||||
if (COMMANDS[cmd]) { return; }
|
||||
|
||||
COMMANDS[cmd] = commands[cmd];
|
||||
});
|
||||
} catch (e) {}
|
||||
});
|
||||
|
||||
|
||||
nThen(function (w) {
|
||||
require("./lib/log").create(config, w(function (_log) {
|
||||
Env.Log = _log;
|
||||
@ -93,6 +119,7 @@ nThen(function (w) {
|
||||
|
||||
var launchWorker = (online) => {
|
||||
var worker = Cluster.fork(workerState);
|
||||
var pid = worker.process.pid;
|
||||
worker.on('online', () => {
|
||||
online();
|
||||
});
|
||||
@ -122,6 +149,13 @@ nThen(function (w) {
|
||||
});
|
||||
|
||||
worker.on('exit', (code, signal) => {
|
||||
Object.keys(Env.plugins || {}).forEach(name => {
|
||||
let plugin = Env.plugins[name];
|
||||
if (!plugin.onWorkerClosed) { return; }
|
||||
try { plugin.onWorkerClosed("http-worker", pid); }
|
||||
catch (e) {}
|
||||
});
|
||||
|
||||
if (!signal && code === 0) { return; }
|
||||
// relaunch http workers if they crash
|
||||
Env.Log.error('HTTP_WORKER_EXIT', {
|
||||
@ -147,10 +181,11 @@ nThen(function (w) {
|
||||
});
|
||||
};
|
||||
|
||||
var broadcast = (command, data/*, cb*/) => {
|
||||
var broadcast = Env.broadcast = (command, data/*, cb*/) => {
|
||||
for (const worker of Object.values(Cluster.workers)) {
|
||||
sendCommand(worker, command, data /*, cb */);
|
||||
}
|
||||
return Object.values(Cluster.workers);
|
||||
};
|
||||
|
||||
var throttledEnvChange = Util.throttle(function () {
|
||||
|
||||
@ -172,7 +172,7 @@ define([
|
||||
const blocks = sidebar.blocks;
|
||||
|
||||
// EXTENSION_POINT:ADMIN_CATEGORY
|
||||
common.getExtensions('ADMIN_CATEGORY').forEach(ext => {
|
||||
common.getExtensionsSync('ADMIN_CATEGORY').forEach(ext => {
|
||||
if (!ext || !ext.id || !ext.name || !ext.content) {
|
||||
return console.error('Invalid extension point', 'ADMIN_CATEGORY', ext);
|
||||
}
|
||||
@ -187,6 +187,7 @@ define([
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
const flushCache = (cb) => {
|
||||
cb = cb || function () {};
|
||||
sFrameChan.query('Q_ADMIN_RPC', {
|
||||
@ -967,12 +968,7 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
let btn = blocks.activeButton('primary', '',
|
||||
Messages.admin_colorChange, (done) => {
|
||||
let color = $input.val();
|
||||
setColor(color, done);
|
||||
});
|
||||
|
||||
let $input = $();
|
||||
let onColorPicked = () => {
|
||||
require(['/lib/less.min.js'], (Less) => {
|
||||
let color = $input.val();
|
||||
@ -994,7 +990,14 @@ define([
|
||||
$preview.find('.cp-admin-color-preview-light a').attr('style', `color: ${color} !important`);
|
||||
});
|
||||
};
|
||||
let $input = $(input).on('change', onColorPicked).addClass('cp-admin-color-picker');
|
||||
$input = $(input).on('change', onColorPicked).addClass('cp-admin-color-picker');
|
||||
|
||||
let btn = blocks.activeButton('primary', '',
|
||||
Messages.admin_colorChange, (done) => {
|
||||
let color = $input.val();
|
||||
setColor(color, done);
|
||||
});
|
||||
|
||||
|
||||
UI.confirmButton($remove, {
|
||||
classes: 'btn-danger',
|
||||
@ -3184,6 +3187,7 @@ define([
|
||||
labelEnd,
|
||||
], blocks.nav([button]));
|
||||
|
||||
let send = function () {};
|
||||
var refresh = getApi(function (Broadcast) {
|
||||
$active.empty();
|
||||
var removeButton = blocks.button('danger', '', Messages.admin_maintenanceCancel);
|
||||
@ -3245,7 +3249,7 @@ define([
|
||||
};
|
||||
};
|
||||
|
||||
var send = function (data) {
|
||||
send = function (data) {
|
||||
disable($button);
|
||||
sFrameChan.query('Q_ADMIN_RPC', {
|
||||
cmd: 'ADMIN_DECREE',
|
||||
@ -3924,16 +3928,15 @@ define([
|
||||
|
||||
// EXTENSION_POINT:ADMIN_ITEM
|
||||
let utils = {
|
||||
h, Util, Hash
|
||||
h, Util, Hash, UIElements
|
||||
};
|
||||
common.getExtensions('ADMIN_ITEM').forEach(ext => {
|
||||
common.getExtensionsSync('ADMIN_ITEM').forEach(ext => {
|
||||
if (!ext || !ext.id || typeof(ext.getContent) !== "function") {
|
||||
return console.error('Invalid extension point', 'ADMIN_CATEGORY', ext);
|
||||
}
|
||||
if (sidebar.hasItem(ext.id)) {
|
||||
return console.error('Extension point ID already used', ext);
|
||||
}
|
||||
|
||||
sidebar.addItem(ext.id, cb => {
|
||||
ext.getContent(common, blocks, utils, content => {
|
||||
cb(content);
|
||||
@ -3946,12 +3949,8 @@ define([
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
sidebar.makeLeftside(categories);
|
||||
};
|
||||
|
||||
|
||||
var updateStatus = APP.updateStatus = function (cb) {
|
||||
sFrameChan.query('Q_ADMIN_RPC', {
|
||||
cmd: 'INSTANCE_STATUS',
|
||||
|
||||
@ -132,7 +132,7 @@ define(function() {
|
||||
// en: "Hello world",
|
||||
// fr: "Bonjour le monde",
|
||||
// de: "Hallo Welt",
|
||||
// "pt-br": "Olá Mundo"<
|
||||
// "pt-br": "Olá Mundo"
|
||||
};
|
||||
|
||||
/* Cryptpad apps use a common API to display notifications to users
|
||||
|
||||
@ -2525,6 +2525,7 @@ define([
|
||||
window.setTimeout(function () {
|
||||
modal.show();
|
||||
$modal.focus();
|
||||
next();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -2713,7 +2713,7 @@ define([
|
||||
window.addEventListener('unload', function () {
|
||||
postMsg('CLOSE');
|
||||
});
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
// eslint-disable-next-line no-constant-condition,no-constant-binary-expression
|
||||
} else if (false && !noWorker && !noSharedWorker && 'serviceWorker' in navigator) {
|
||||
var initializing = true;
|
||||
var stopWaiting = waitFor2(); // Call this function when we're ready
|
||||
|
||||
@ -990,6 +990,9 @@ define([
|
||||
// Arrow keys to modify the selection
|
||||
var onWindowKeydown = function (e) {
|
||||
if (!$content.is(':visible')) { return; }
|
||||
if ($('.cp-modal').is(':visible')) {
|
||||
return;
|
||||
}
|
||||
var $searchBar = $tree.find('#cp-app-drive-tree-search-input');
|
||||
if (document.activeElement && document.activeElement.nodeName === 'INPUT') { return; }
|
||||
if ($searchBar.is(':focus') && $searchBar.val()) { return; }
|
||||
@ -2968,6 +2971,16 @@ define([
|
||||
});
|
||||
UI.openCustomModal(m);
|
||||
};
|
||||
|
||||
var triggerEnter = function (selector, $context) {
|
||||
$context.find(selector).on('keypress', function (event) {
|
||||
if (event.which === 13) { // enter
|
||||
event.preventDefault();
|
||||
$(this).trigger('click'); // the keypress event triggers the click event
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var addNewPadHandlers = function ($block, isInRoot) {
|
||||
// Handlers
|
||||
if (isInRoot) {
|
||||
@ -2983,56 +2996,51 @@ define([
|
||||
refresh();
|
||||
};
|
||||
$block.find('a.cp-app-drive-new-folder, li.cp-app-drive-new-folder')
|
||||
.on('click keypress', function (event) {
|
||||
if (event.type === 'click' || (event.type === 'keypress' && event.which === 13)) {
|
||||
event.preventDefault();
|
||||
manager.addFolder(currentPath, null, onCreated);
|
||||
}
|
||||
.on('click', function (event) {
|
||||
event.preventDefault();
|
||||
manager.addFolder(currentPath, null, onCreated);
|
||||
});
|
||||
triggerEnter('a.cp-app-drive-new-folder, li.cp-app-drive-new-folder', $block);
|
||||
if (!APP.disableSF && !manager.isInSharedFolder(currentPath)) {
|
||||
$block.find('a.cp-app-drive-new-shared-folder, li.cp-app-drive-new-shared-folder')
|
||||
.on('click keypress', function (event) {
|
||||
if (event.type === 'click' || (event.type === 'keypress' && event.which === 13)) {
|
||||
event.preventDefault();
|
||||
addSharedFolderModal(function (obj) {
|
||||
if (!obj) { return; }
|
||||
manager.addSharedFolder(currentPath, obj, refresh);
|
||||
});
|
||||
}
|
||||
.on('click', function (event) {
|
||||
event.preventDefault();
|
||||
addSharedFolderModal(function (obj) {
|
||||
if (!obj) { return; }
|
||||
manager.addSharedFolder(currentPath, obj, refresh);
|
||||
});
|
||||
});
|
||||
triggerEnter('a.cp-app-drive-new-shared-folder, li.cp-app-drive-new-shared-folder', $block);
|
||||
}
|
||||
$block.find('a.cp-app-drive-new-fileupload, li.cp-app-drive-new-fileupload')
|
||||
.on('click keypress', function (event) {
|
||||
if (event.type === 'click' || (event.type === 'keypress' && event.which === 13)) {
|
||||
event.preventDefault();
|
||||
showUploadFilesModal();
|
||||
}
|
||||
.on('click', function (event) {
|
||||
event.preventDefault();
|
||||
showUploadFilesModal();
|
||||
});
|
||||
triggerEnter('a.cp-app-drive-new-fileupload, li.cp-app-drive-new-fileupload', $block);
|
||||
$block.find('a.cp-app-drive-new-folderupload, li.cp-app-drive-new-folderupload')
|
||||
.on('click keypress', function (event) {
|
||||
if (event.type === 'click' || (event.type === 'keypress' && event.which === 13)) {
|
||||
event.preventDefault();
|
||||
showUploadFolderModal();
|
||||
}
|
||||
.on('click', function (event) {
|
||||
event.preventDefault();
|
||||
showUploadFolderModal();
|
||||
});
|
||||
triggerEnter('a.cp-app-drive-new-folderupload, li.cp-app-drive-new-folderupload', $block);
|
||||
$block.find('a.cp-app-drive-new-link, li.cp-app-drive-new-link')
|
||||
.on('click keypress', function (event) {
|
||||
if (event.type === 'click' || (event.type === 'keypress' && event.which === 13)) {
|
||||
event.preventDefault();
|
||||
showLinkModal();
|
||||
}
|
||||
.on('click', function (event) {
|
||||
event.preventDefault();
|
||||
showLinkModal();
|
||||
});
|
||||
triggerEnter('a.cp-app-drive-new-link, li.cp-app-drive-new-link', $block);
|
||||
}
|
||||
$block.find('a.cp-app-drive-new-doc, li.cp-app-drive-new-doc')
|
||||
.on('click auxclick keypress', function (event) {
|
||||
if (event.type === 'click' || event.type === 'auxclick' || (event.type === 'keypress' && event.which === 13))
|
||||
{
|
||||
.on('click auxclick', function (event) {
|
||||
if (event.type === 'click' || event.type === 'auxclick') {
|
||||
event.preventDefault();
|
||||
var type = $(this).attr('data-type') || 'pad';
|
||||
var path = manager.isPathIn(currentPath, [TRASH]) ? '' : currentPath;
|
||||
openIn(type, path, APP.team);
|
||||
}
|
||||
});
|
||||
triggerEnter('a.cp-app-drive-new-doc, li.cp-app-drive-new-doc', $block);
|
||||
};
|
||||
var getNewPadOptions = function (isInRoot) {
|
||||
var options = [];
|
||||
|
||||
@ -10,9 +10,51 @@ define([
|
||||
ext.getExtensions = id => {
|
||||
let e = ext[id];
|
||||
if (!Array.isArray(e)) { e = []; }
|
||||
return e;
|
||||
return e.map(_ext => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// If there's no check function, resolve immediately
|
||||
if (typeof _ext.check !== "function") {
|
||||
return resolve(_ext);
|
||||
}
|
||||
const checkResult = _ext.check();
|
||||
|
||||
checkResult
|
||||
.then((extPassed) => {
|
||||
if (!extPassed) {
|
||||
// Reject if the check didn't pass
|
||||
undefined (resolve());
|
||||
} else {
|
||||
// Extension passed the check
|
||||
resolve(_ext);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
ext.getExtensionsSync = id => {
|
||||
let e = ext[id];
|
||||
if (!Array.isArray(e)) { e = []; }
|
||||
|
||||
return e.map(_ext => {
|
||||
if (typeof _ext.check !== "function") {
|
||||
return _ext;
|
||||
}
|
||||
|
||||
const extPassed = _ext.check();
|
||||
|
||||
if (extPassed) {
|
||||
return _ext;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}).filter(ext => ext !== null);
|
||||
};
|
||||
|
||||
|
||||
if (!Array.isArray(Extensions) || !Extensions.length) { return ext; }
|
||||
|
||||
let all = Extensions.slice();
|
||||
|
||||
@ -2343,6 +2343,12 @@ define([
|
||||
if (completed) { return; }
|
||||
var parsed = parse(msg);
|
||||
if (parsed[1] !== txid) { console.log('bad txid'); return; }
|
||||
if (parsed[0] === 'HISTORY_RANGE_ERROR') {
|
||||
cb({
|
||||
error: parsed[2]
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (parsed[0] === 'HISTORY_RANGE_END') {
|
||||
cb({
|
||||
messages: msgs,
|
||||
|
||||
@ -382,7 +382,8 @@ define([
|
||||
}
|
||||
// History cleared while we were offline
|
||||
// ==> we asked for an invalid last known hash
|
||||
if (parsed.error && parsed.error === "EINVAL") {
|
||||
if (parsed.error && (parsed.error === "EINVAL"
|
||||
|| parsed.error === "EUNKNOWN")) {
|
||||
setChannelHead(ctx, parsed.channel, '', function () {
|
||||
getChannelMessagesSince(ctx, channel, {}, {});
|
||||
});
|
||||
|
||||
@ -531,6 +531,11 @@ define([
|
||||
if (last.sender) {
|
||||
entry.lastAdmin = !last.sender.blockLocation;
|
||||
}
|
||||
if (last.close) {
|
||||
doc.tickets.closed[data.channel] = entry;
|
||||
delete doc.tickets.active[data.channel];
|
||||
notifyClient(ctx, true, 'UPDATE_TICKET', data.channel);
|
||||
}
|
||||
/*
|
||||
let senderKey = last.sender && last.sender.edPublic;
|
||||
if (senderKey) {
|
||||
@ -589,7 +594,9 @@ define([
|
||||
doc.tickets.closed[data.channel] = entry;
|
||||
delete doc.tickets.active[data.channel];
|
||||
delete doc.tickets.pending[data.channel];
|
||||
cb({closed: true});
|
||||
Realtime.whenRealtimeSyncs(ctx.adminDoc.realtime, function () {
|
||||
cb({closed: true});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@ -698,7 +698,7 @@ define([
|
||||
}
|
||||
if (exp.isFolder(obj.element)) { fixRoot(obj.element); }
|
||||
if (typeof obj.element === "number") {
|
||||
var data = files[FILES_DATA][obj.element];
|
||||
var data = files[FILES_DATA][obj.element] || files[STATIC_DATA][obj.element];
|
||||
if (!data) {
|
||||
debug("An element in TRASH doesn't have associated data", obj.element, el);
|
||||
toClean.push(idx);
|
||||
|
||||
@ -212,6 +212,9 @@ define([
|
||||
sharedFolder: config.sharedFolder
|
||||
}, function (err, data) {
|
||||
if (err) { return void console.error(err); }
|
||||
if (data && data.error) {
|
||||
return void cb(data.error);
|
||||
}
|
||||
if (!Array.isArray(data.messages)) { return void console.error('Not an array!'); }
|
||||
lastKnownHash = data.lastKnownHash;
|
||||
isComplete = data.isFull;
|
||||
@ -288,6 +291,14 @@ define([
|
||||
|
||||
UI.spinner($hist).get().show();
|
||||
|
||||
let closeAll = () => {
|
||||
History.state = false;
|
||||
$hist.hide();
|
||||
$bottom.show();
|
||||
$cke.show();
|
||||
$(window).trigger('resize');
|
||||
};
|
||||
|
||||
var update = function (newRt) {
|
||||
realtime = newRt;
|
||||
if (!realtime) { return []; }
|
||||
@ -567,11 +578,7 @@ define([
|
||||
|
||||
var onKeyDown, onKeyUp;
|
||||
var closeUI = function () {
|
||||
History.state = false;
|
||||
$hist.hide();
|
||||
$bottom.show();
|
||||
$cke.show();
|
||||
$(window).trigger('resize');
|
||||
closeAll();
|
||||
$(window).off('keydown', onKeyDown);
|
||||
$(window).off('keyup', onKeyUp);
|
||||
};
|
||||
@ -681,7 +688,12 @@ define([
|
||||
loadMoreHistory(config, common, function (err, newRt, isFull) {
|
||||
History.readOnly = common.getMetadataMgr().getPrivateData().readOnly;
|
||||
History.loading = false;
|
||||
if (err) { throw new Error(err); }
|
||||
if (err) {
|
||||
console.error(err);
|
||||
UI.warn(`${Messages.error}: ${err}`);
|
||||
closeAll();
|
||||
return;
|
||||
}
|
||||
update(newRt);
|
||||
display();
|
||||
if (isFull) {
|
||||
|
||||
@ -78,7 +78,6 @@ define([
|
||||
requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req)));
|
||||
$i.attr('allowfullscreen', 'true');
|
||||
$i.attr('allow', 'clipboard-write');
|
||||
$i.attr('title', 'iframe');
|
||||
$('iframe-placeholder').after($i).remove();
|
||||
|
||||
// This is a cheap trick to avoid loading sframe-channel in parallel with the
|
||||
@ -1322,6 +1321,9 @@ define([
|
||||
toHash: data.toHash,
|
||||
lastKnownHash: data.lastKnownHash
|
||||
}, function (data) {
|
||||
if (data && data.error) {
|
||||
return void cb(data);
|
||||
}
|
||||
cb({
|
||||
isFull: data.isFull,
|
||||
messages: data.messages.map(function (obj) {
|
||||
|
||||
@ -778,6 +778,7 @@ define([
|
||||
};
|
||||
|
||||
funcs.getExtensions = Ext.getExtensions;
|
||||
funcs.getExtensionsSync = Ext.getExtensionsSync;
|
||||
|
||||
funcs.mailbox = {};
|
||||
|
||||
|
||||
@ -1416,7 +1416,7 @@ MessengerUI, Messages, Pages, PadTypes) {
|
||||
|
||||
tb['pad'] = function () {
|
||||
toolbar.$file.show();
|
||||
addElement([
|
||||
toolbar.addElement([
|
||||
'chat',
|
||||
'collapse',
|
||||
'userlist', 'title', 'useradmin', 'spinner',
|
||||
|
||||
@ -980,6 +980,7 @@
|
||||
.cp-radio, .cp-checkmark {
|
||||
display: inline-flex;
|
||||
max-width: 100%;
|
||||
padding: 0.313rem 0;
|
||||
}
|
||||
.cp-checkmark-label {
|
||||
word-break: break-word;
|
||||
@ -1043,7 +1044,7 @@
|
||||
}
|
||||
.cp-form-sort-order {
|
||||
border: 1px solid @cryptpad_text_col;
|
||||
padding: 0 5px;
|
||||
padding: 0 0.313rem;
|
||||
margin-right: 5px;
|
||||
}
|
||||
&:hover {
|
||||
@ -1318,6 +1319,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-radio {
|
||||
margin: 0.2rem 0;
|
||||
}
|
||||
.cp-form-status {
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
.cp-form-mute-radio {
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
.cp-form-privacy-container {
|
||||
margin-top: 0.8rem
|
||||
}
|
||||
.cp-form-results-type {
|
||||
display: inline-block;
|
||||
margin: 0.2rem 0 0.2rem 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
& > .flatpickr-calendar {
|
||||
|
||||
@ -1097,6 +1097,11 @@ define([
|
||||
block = h('div.cp-form-edit-options-block', [t]);
|
||||
cm = SFCodeMirror.create("gfm", CMeditor, t);
|
||||
editor = cm.editor;
|
||||
editor.setOption("extraKeys", {
|
||||
"Esc": function () {
|
||||
editor.display.input.blur();
|
||||
},
|
||||
});
|
||||
editor.setOption('lineNumbers', true);
|
||||
editor.setOption('lineWrapping', true);
|
||||
editor.setOption('styleActiveLine', true);
|
||||
@ -4928,7 +4933,7 @@ define([
|
||||
|
||||
// End date / Closed state
|
||||
var endDateContainer = h('div.cp-form-status-container');
|
||||
var endDateStr = h('div');
|
||||
var endDateStr = h('div.cp-form-status');
|
||||
var $endDate = $(endDateContainer);
|
||||
var $endDateStr = $(endDateStr);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user