diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7b6a59683..ea1d3ced2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/lib/commands/core.js b/lib/commands/core.js
index 42ed0455b..a0be7cd67 100644
--- a/lib/commands/core.js
+++ b/lib/commands/core.js
@@ -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);
diff --git a/lib/decrees.js b/lib/decrees.js
index fca8fb331..eed840057 100644
--- a/lib/decrees.js
+++ b/lib/decrees.js
@@ -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);
diff --git a/lib/metadata.js b/lib/metadata.js
index 97f2e484a..d320a5c9b 100644
--- a/lib/metadata.js
+++ b/lib/metadata.js
@@ -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
diff --git a/server.js b/server.js
index 4a107b785..978070b01 100644
--- a/server.js
+++ b/server.js
@@ -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, {
diff --git a/www/checkup/main.js b/www/checkup/main.js
index f02b11c9e..f8b7e5612 100644
--- a/www/checkup/main.js
+++ b/www/checkup/main.js
@@ -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);
diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js
index 5ddc48641..a89092e55 100644
--- a/www/common/common-ui-elements.js
+++ b/www/common/common-ui-elements.js
@@ -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();
diff --git a/www/common/diffMarked.js b/www/common/diffMarked.js
index f2fb97f24..cb95f37aa 100644
--- a/www/common/diffMarked.js
+++ b/www/common/diffMarked.js
@@ -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,
});
diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js
index 775f1f34a..9f7506d61 100644
--- a/www/common/inner/common-mediatag.js
+++ b/www/common/inner/common-mediatag.js
@@ -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('
', '
');
+ 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); }