mirror of https://github.com/xwiki-labs/cryptpad
Merge branch 'staging' into restricted-registration
This commit is contained in:
commit
30fc2a5edf
|
@ -5,6 +5,9 @@
|
|||
* reminders in calendars
|
||||
* import/export
|
||||
* include LICENSE for ical.js
|
||||
* translations
|
||||
* out of BETA
|
||||
* available from user admin menu
|
||||
* use a specific version of bootstrap-tokenfield in bower.json
|
||||
* don't create readmes
|
||||
* support displaying a roadmap in static pages' footer
|
||||
|
@ -18,6 +21,12 @@
|
|||
* lock sheets faster when applying checkpoints
|
||||
* guard against undefined checkpoints
|
||||
* don't spam users with prompts to checkpoints when they can't
|
||||
* decrees
|
||||
* SET_ADMIN_EMAIL
|
||||
* SET_SUPPORT_MAILBOX
|
||||
* Add DAPSI to our sponsor list
|
||||
* checkup
|
||||
* check for duplicate or incorrect headers
|
||||
|
||||
# 4.4.0
|
||||
|
||||
|
|
|
@ -13,6 +13,10 @@ Core.isValidId = function (chan) {
|
|||
[32, 33, 48].indexOf(chan.length) > -1;
|
||||
};
|
||||
|
||||
Core.isValidPublicKey = function (owner) {
|
||||
return typeof(owner) === 'string' && owner.length === 44;
|
||||
};
|
||||
|
||||
var makeToken = Core.makeToken = function () {
|
||||
return Number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER))
|
||||
.toString(16);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
var Decrees = module.exports;
|
||||
var Core = require("./commands/core");
|
||||
|
||||
/* Admin decrees which modify global server state
|
||||
|
||||
|
@ -29,6 +30,10 @@ SET_LAST_BROADCAST_HASH
|
|||
SET_SURVEY_URL
|
||||
SET_MAINTENANCE
|
||||
|
||||
// EASIER CONFIG
|
||||
SET_ADMIN_EMAIL
|
||||
SET_SUPPORT_MAILBOX
|
||||
|
||||
NOT IMPLEMENTED:
|
||||
|
||||
// RESTRICTED REGISTRATION
|
||||
|
@ -37,9 +42,11 @@ REVOKE_INVITE
|
|||
REDEEM_INVITE
|
||||
|
||||
// 2.0
|
||||
Env.adminEmail
|
||||
Env.supportMailbox
|
||||
Env.DEV_MODE || Env.FRESH_MODE,
|
||||
|
||||
ADD_ADMIN_KEY
|
||||
RM_ADMIN_KEY
|
||||
|
||||
*/
|
||||
|
||||
var commands = {};
|
||||
|
@ -88,6 +95,20 @@ var isNonNegativeNumber = function (n) {
|
|||
};
|
||||
*/
|
||||
|
||||
var default_validator = function () { return true; };
|
||||
var makeGenericSetter = function (attr, validator) {
|
||||
validator = validator || default_validator;
|
||||
return function (Env, args) {
|
||||
if (!validator(args)) {
|
||||
throw new Error("INVALID_ARGS");
|
||||
}
|
||||
var value = args[0];
|
||||
if (value === Env[attr]) { return false; }
|
||||
Env[attr] = value;
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
||||
var isInteger = function (n) {
|
||||
return !(typeof(n) !== 'number' || isNaN(n) || (n % 1) !== 0);
|
||||
};
|
||||
|
@ -97,15 +118,7 @@ var args_isInteger = function (args) {
|
|||
};
|
||||
|
||||
var makeIntegerSetter = function (attr) {
|
||||
return function (Env, args) {
|
||||
if (!args_isInteger(args)) {
|
||||
throw new Error('INVALID_ARGS');
|
||||
}
|
||||
var integer = args[0];
|
||||
if (integer === Env[attr]) { return false; }
|
||||
Env[attr] = integer;
|
||||
return true;
|
||||
};
|
||||
return makeGenericSetter(attr, args_isInteger);
|
||||
};
|
||||
|
||||
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_MAX_UPLOAD_SIZE', [50 * 1024 * 1024]]], console.log)
|
||||
|
@ -130,6 +143,14 @@ var args_isString = function (args) {
|
|||
return Array.isArray(args) && typeof(args[0]) === "string";
|
||||
};
|
||||
|
||||
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_ADMIN_EMAIL', ['admin@website.tld']]], console.log)
|
||||
commands.SET_ADMIN_EMAIL = makeGenericSetter('adminEmail', args_isString);
|
||||
|
||||
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_SUPPORT_MAILBOX', ["Tdz6+fE9N9XXBY93rW5qeNa/k27yd40c0vq7EJyt7jA="]]], console.log)
|
||||
commands.SET_SUPPORT_MAILBOX = makeGenericSetter('supportMailbox', function (args) {
|
||||
return args_isString(args) && Core.isValidPublicKey(args[0]);
|
||||
});
|
||||
|
||||
// Maintenance: Empty string or an object with a start and end time
|
||||
var isNumber = function (value) {
|
||||
return typeof(value) === "number" && !isNaN(value);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
var Meta = module.exports;
|
||||
var Core = require("./commands/core");
|
||||
|
||||
var deduplicate = require("./common-util").deduplicateString;
|
||||
|
||||
|
@ -35,9 +36,7 @@ the owners field is guaranteed to exist.
|
|||
|
||||
var commands = {};
|
||||
|
||||
var isValidPublicKey = function (owner) {
|
||||
return typeof(owner) === 'string' && owner.length === 44;
|
||||
};
|
||||
var isValidPublicKey = Core.isValidPublicKey;
|
||||
|
||||
// isValidPublicKey is a better indication of what the above function does
|
||||
// I'm preserving this function name in case we ever want to expand its
|
||||
|
|
|
@ -105,13 +105,15 @@ var setHeaders = (function () {
|
|||
}
|
||||
if (Object.keys(headers).length) {
|
||||
return function (req, res) {
|
||||
|
||||
// apply a bunch of cross-origin headers for XLSX export in FF and printing elsewhere
|
||||
applyHeaderMap(res, {
|
||||
"Cross-Origin-Opener-Policy": /^\/sheet\//.test(req.url)? 'same-origin': '',
|
||||
"Cross-Origin-Embedder-Policy": 'require-corp',
|
||||
});
|
||||
|
||||
if (Env.NO_SANDBOX) {
|
||||
if (Env.NO_SANDBOX) { // handles correct configuration for local development
|
||||
// https://stackoverflow.com/questions/11531121/add-duplicate-http-response-headers-in-nodejs
|
||||
applyHeaderMap(res, {
|
||||
"Cross-Origin-Resource-Policy": 'cross-origin',
|
||||
});
|
||||
|
@ -120,11 +122,13 @@ var setHeaders = (function () {
|
|||
// Don't set CSP headers on /api/config because they aren't necessary and they cause problems
|
||||
// when duplicated by NGINX in production environments
|
||||
if (/^\/api\/(broadcast|config)/.test(req.url)) {
|
||||
if (!Env.NO_SANDBOX) {
|
||||
/*
|
||||
if (Env.NO_SANDBOX) {
|
||||
applyHeaderMap(res, {
|
||||
"Cross-Origin-Resource-Policy": 'cross-origin',
|
||||
});
|
||||
}
|
||||
*/
|
||||
return;
|
||||
}
|
||||
applyHeaderMap(res, {
|
||||
|
|
|
@ -365,7 +365,7 @@ define([
|
|||
});
|
||||
|
||||
assert(function (cb, msg) {
|
||||
msg = msg; // XXX
|
||||
msg = msg;
|
||||
return void cb(true);
|
||||
/*
|
||||
msg.appendChild(h('span', [
|
||||
|
@ -415,6 +415,58 @@ define([
|
|||
});
|
||||
});
|
||||
|
||||
var checkAPIHeaders = function (url, cb) {
|
||||
$.ajax(url, {
|
||||
dataType: 'text',
|
||||
complete: function (xhr) {
|
||||
var allHeaders = xhr.getAllResponseHeaders();
|
||||
console.error(allHeaders);
|
||||
|
||||
var headers = {};
|
||||
|
||||
var duplicated = allHeaders.split('\n').some(function (header) {
|
||||
var duplicate;
|
||||
header.replace(/([^:]+):(.*)/, function (all, type, value) {
|
||||
type = type.trim();
|
||||
if (typeof(headers[type]) !== 'undefined') {
|
||||
duplicate = true;
|
||||
}
|
||||
headers[type] = value.trim();
|
||||
});
|
||||
return duplicate;
|
||||
});
|
||||
|
||||
if (duplicated) { return void cb(false); }
|
||||
|
||||
var expect = {
|
||||
'cross-origin-resource-policy': 'cross-origin',
|
||||
};
|
||||
var incorrect = Object.keys(expect).some(function (k) {
|
||||
var response = xhr.getResponseHeader(k);
|
||||
if (response !== expect[k]) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
cb(!incorrect);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
var INCORRECT_HEADER_TEXT = ' was served with duplicated or incorrect headers. Compare your reverse-proxy configuration against the provided example.';
|
||||
|
||||
assert(function (cb, msg) {
|
||||
var url = '/api/config';
|
||||
msg.innerText = url + INCORRECT_HEADER_TEXT;
|
||||
checkAPIHeaders(url, cb);
|
||||
});
|
||||
|
||||
assert(function (cb, msg) {
|
||||
var url = '/api/broadcast';
|
||||
msg.innerText = url + INCORRECT_HEADER_TEXT;
|
||||
checkAPIHeaders(url, cb);
|
||||
});
|
||||
|
||||
var row = function (cells) {
|
||||
return h('tr', cells.map(function (cell) {
|
||||
return h('td', cell);
|
||||
|
|
|
@ -798,7 +798,6 @@ define([
|
|||
])).click(common.prepareFeedback(type)).click(function () {
|
||||
$(button).hide();
|
||||
common.getSframeChannel().query("Q_AUTOSTORE_STORE", null, function (err, obj) {
|
||||
waitingForStoringCb = false;
|
||||
var error = err || (obj && obj.error);
|
||||
if (error) {
|
||||
$(button).show();
|
||||
|
|
|
@ -40,6 +40,7 @@ define([
|
|||
Mermaid = _Mermaid;
|
||||
Mermaid.initialize({
|
||||
gantt: { axisFormat: '%m-%d', },
|
||||
flowchart: { htmlLabels: false, },
|
||||
theme: (window.CryptPad_theme === 'dark') ? 'dark' : 'default',
|
||||
"themeCSS": mermaidThemeCSS,
|
||||
});
|
||||
|
|
|
@ -386,11 +386,11 @@ define([
|
|||
'tabindex': '-1',
|
||||
'data-icon': "fa-eye",
|
||||
}, Messages.pad_mediatagPreview)),
|
||||
h('li.cp-svg', h('a.cp-app-code-context-openin.dropdown-item', {
|
||||
h('li', h('a.cp-app-code-context-openin.dropdown-item', {
|
||||
'tabindex': '-1',
|
||||
'data-icon': "fa-external-link",
|
||||
}, Messages.pad_mediatagOpen)),
|
||||
h('li.cp-svg', h('a.cp-app-code-context-share.dropdown-item', {
|
||||
h('li', h('a.cp-app-code-context-share.dropdown-item', {
|
||||
'tabindex': '-1',
|
||||
'data-icon': "fa-shhare-alt",
|
||||
}, Messages.pad_mediatagShare)),
|
||||
|
@ -398,7 +398,7 @@ define([
|
|||
'tabindex': '-1',
|
||||
'data-icon': "fa-cloud-upload",
|
||||
}, Messages.pad_mediatagImport)),
|
||||
h('li', h('a.cp-app-code-context-download.dropdown-item', {
|
||||
h('li.cp-svg', h('a.cp-app-code-context-download.dropdown-item', {
|
||||
'tabindex': '-1',
|
||||
'data-icon': "fa-download",
|
||||
}, Messages.download_mt_button)),
|
||||
|
@ -429,6 +429,52 @@ define([
|
|||
common.importMediaTag($mt);
|
||||
}
|
||||
else if ($this.hasClass("cp-app-code-context-download")) {
|
||||
if ($mt.is('pre.mermaid') || $mt.is('pre.markmap')) {
|
||||
(function () {
|
||||
var name = 'image.svg'; // XXX
|
||||
var svg = $mt.find('svg')[0].cloneNode(true);
|
||||
$(svg).attr('xmlns', 'http://www.w3.org/2000/svg').attr('width', $mt.width()).attr('height', $mt.height());
|
||||
$(svg).find('foreignObject').each(function (i, el) {
|
||||
var $el = $(el);
|
||||
$el.find('br').after('\n');
|
||||
$el.find('br').remove();
|
||||
var t = $el[0].innerText || $el[0].textContent;
|
||||
t.split('\n').forEach(function (text, i) {
|
||||
var dy = (i+1)+'em';
|
||||
$el.after(h('text', {y:0, dy:dy, style: ''}, text));
|
||||
});
|
||||
$el.remove();
|
||||
});
|
||||
var html = svg.outerHTML;
|
||||
html = html.replace('<br>', '<br/>');
|
||||
var b = new Blob([html], { type: 'image/svg+xml' });
|
||||
window.saveAs(b, name);
|
||||
})();
|
||||
return;
|
||||
}
|
||||
if ($mt.is('pre.mathjax')) {
|
||||
(function () {
|
||||
var name = 'image.png'; // XXX
|
||||
var svg = $mt.find('> span > svg')[0];
|
||||
var clone = svg.cloneNode(true);
|
||||
var html = clone.outerHTML;
|
||||
var b = new Blob([html], { type: 'image/svg+xml' });
|
||||
var blobURL = URL.createObjectURL(b);
|
||||
var i = new Image();
|
||||
i.onload = function () {
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = i.width;
|
||||
canvas.height = i.height;
|
||||
var context = canvas.getContext('2d');
|
||||
context.drawImage(i, 0, 0, i.width, i.height);
|
||||
canvas.toBlob(function (blob) {
|
||||
window.saveAs(blob, name);
|
||||
});
|
||||
};
|
||||
i.src = blobURL;
|
||||
})();
|
||||
return;
|
||||
}
|
||||
var media = Util.find($mt, [0, '_mediaObject']);
|
||||
if (!media) { return void console.error('no media'); }
|
||||
if (!media.complete) { return void UI.warn(Messages.mediatag_notReady); }
|
||||
|
|
Loading…
Reference in New Issue