diff --git a/customize.dist/application_config.js b/customize.dist/application_config.js
index 2320bf9af..0cebf4b51 100644
--- a/customize.dist/application_config.js
+++ b/customize.dist/application_config.js
@@ -1,74 +1,10 @@
-define(function() {
- var config = {};
-
- /* Select the buttons displayed on the main page to create new collaborative sessions
- * Existing types : pad, code, poll, slide
- */
- config.availablePadTypes = ['drive', 'pad', 'code', 'slide', 'poll', 'whiteboard', 'file', 'todo', 'contacts'];
- config.registeredOnlyTypes = ['file', 'contacts'];
-
- /* Cryptpad apps use a common API to display notifications to users
- * by default, notifications are hidden after 5 seconds
- * You can change their duration here (measured in milliseconds)
- */
- config.notificationTimeout = 5000;
- config.disableUserlistNotifications = false;
- config.hideLoadingScreenTips = false;
-
- config.enablePinning = true;
-
- config.whiteboardPalette = [
- '#000000', // black
- '#FFFFFF', // white
- '#848484', // grey
- '#8B4513', // saddlebrown
- '#FF0000', // red
- '#FF8080', // peach?
- '#FF8000', // orange
- '#FFFF00', // yellow
- '#80FF80', // light green
- '#00FF00', // green
- '#00FFFF', // cyan
- '#008B8B', // dark cyan
- '#0000FF', // blue
- '#FF00FF', // fuschia
- '#FF00C0', // hot pink
- '#800080', // purple
- ];
-
- config.enableTemplates = true;
-
- config.enableHistory = true;
-
- /* user passwords are hashed with scrypt, and salted with their username.
- this value will be appended to the username, causing the resulting hash
- to differ from other CryptPad instances if customized. This makes it
- such that anyone who wants to bruteforce common credentials must do so
- again on each CryptPad instance that they wish to attack.
-
- WARNING: this should only be set when your CryptPad instance is first
- created. Changing it at a later time will break logins for all existing
- users.
- */
- config.loginSalt = '';
- config.minimumPasswordLength = 8;
-
- config.badStateTimeout = 30000;
-
- config.applicationsIcon = {
- file: 'fa-file-text-o',
- pad: 'fa-file-word-o',
- code: 'fa-file-code-o',
- slide: 'fa-file-powerpoint-o',
- poll: 'fa-calendar',
- whiteboard: 'fa-paint-brush',
- todo: 'fa-tasks',
- contacts: 'fa-users',
- };
-
- config.displayCreationScreen = false;
-
- config.disableAnonymousStore = false;
-
- return config;
+/*
+ * You can override the configurable values from this file.
+ * The recommended method is to make a copy of this file (/customize.dist/application_config.js)
+ in a 'customize' directory (/customize/application_config.js).
+ * If you want to check all the configurable values, you can open the internal configuration file
+ but you should not change it directly (/common/application_config_internal.js)
+*/
+define(['/common/application_config_internal.js'], function (AppConfig) {
+ return AppConfig;
});
diff --git a/customize.dist/index.html b/customize.dist/index.html
index 31d4c99f8..55891ecc4 100644
--- a/customize.dist/index.html
+++ b/customize.dist/index.html
@@ -4,6 +4,7 @@
CryptPad: Zero Knowledge, Collaborative Real Time Editing
+
diff --git a/package.json b/package.json
index e8339d950..dd03426ce 100644
--- a/package.json
+++ b/package.json
@@ -3,10 +3,12 @@
"description": "realtime collaborative visual editor with zero knowlege server",
"version": "1.25.0",
"dependencies": {
- "chainpad-server": "^1.0.1",
+ "chainpad-server": "^2.0.0",
"express": "~4.10.1",
"nthen": "~0.1.0",
+ "pull-stream": "^3.6.1",
"saferphore": "0.0.1",
+ "stream-to-pull-stream": "^1.7.2",
"tweetnacl": "~0.12.2",
"ws": "^1.0.1"
},
diff --git a/storage/file.js b/storage/file.js
index 5d5d8c3db..ce6c3ae13 100644
--- a/storage/file.js
+++ b/storage/file.js
@@ -1,6 +1,11 @@
+/*@flow*/
+/* jshint esversion: 6 */
+/* global Buffer */
var Fs = require("fs");
var Path = require("path");
var nThen = require("nthen");
+const ToPull = require('stream-to-pull-stream');
+const Pull = require('pull-stream');
var mkPath = function (env, channelId) {
return Path.join(env.root, channelId.slice(0, 2), channelId) + '.ndjson';
@@ -8,7 +13,7 @@ var mkPath = function (env, channelId) {
var getMetadataAtPath = function (Env, path, cb) {
var remainder = '';
- var stream = Fs.createReadStream(path, 'utf8');
+ var stream = Fs.createReadStream(path, { encoding: 'utf8' });
var complete = function (err, data) {
var _cb = cb;
cb = undefined;
@@ -25,16 +30,16 @@ var getMetadataAtPath = function (Env, path, cb) {
var parsed = null;
try {
parsed = JSON.parse(metadata);
- complete(void 0, parsed);
+ complete(undefined, parsed);
}
catch (e) {
- console.log();
+ console.log("getMetadataAtPath");
console.error(e);
complete('INVALID_METADATA');
}
});
stream.on('end', function () {
- complete(null);
+ complete();
});
stream.on('error', function (e) { complete(e); });
};
@@ -59,7 +64,7 @@ var closeChannel = function (env, channelName, cb) {
var clearChannel = function (env, channelId, cb) {
var path = mkPath(env, channelId);
getMetadataAtPath(env, path, function (e, metadata) {
- if (e) { return cb(e); }
+ if (e) { return cb(new Error(e)); }
if (!metadata) {
return void Fs.truncate(path, 0, function (err) {
if (err) {
@@ -87,7 +92,7 @@ var clearChannel = function (env, channelId, cb) {
var readMessages = function (path, msgHandler, cb) {
var remainder = '';
- var stream = Fs.createReadStream(path, 'utf8');
+ var stream = Fs.createReadStream(path, { encoding: 'utf8' });
var complete = function (err) {
var _cb = cb;
cb = undefined;
@@ -106,6 +111,60 @@ var readMessages = function (path, msgHandler, cb) {
stream.on('error', function (e) { complete(e); });
};
+const NEWLINE_CHR = ('\n').charCodeAt(0);
+const mkBufferSplit = () => {
+ let remainder = null;
+ return Pull((read) => {
+ return (abort, cb) => {
+ read(abort, function (end, data) {
+ if (end) {
+ cb(end, remainder ? [remainder, data] : [data]);
+ remainder = null;
+ return;
+ }
+ const queue = [];
+ for (;;) {
+ const offset = data.indexOf(NEWLINE_CHR);
+ if (offset < 0) {
+ remainder = remainder ? Buffer.concat([remainder, data]) : data;
+ break;
+ }
+ let subArray = data.slice(0, offset);
+ if (remainder) {
+ subArray = Buffer.concat([remainder, subArray]);
+ remainder = null;
+ }
+ queue.push(subArray);
+ data = data.slice(offset + 1);
+ }
+ cb(end, queue);
+ });
+ };
+ }, Pull.flatten());
+};
+
+const mkOffsetCounter = () => {
+ let offset = 0;
+ return Pull.map((buff) => {
+ const out = { offset: offset, buff: buff };
+ // +1 for the eaten newline
+ offset += buff.length + 1;
+ return out;
+ });
+};
+
+const readMessagesBin = (env, id, start, msgHandler, cb) => {
+ const stream = Fs.createReadStream(mkPath(env, id), { start: start });
+ let keepReading = true;
+ Pull(
+ ToPull.read(stream),
+ mkBufferSplit(),
+ mkOffsetCounter(),
+ Pull.asyncMap((data, moreCb) => { msgHandler(data, moreCb, ()=>{ keepReading = false; moreCb(); }); }),
+ Pull.drain(()=>(keepReading), cb)
+ );
+};
+
var checkPath = function (path, callback) {
// TODO check if we actually need to use stat at all
Fs.stat(path, function (err) {
@@ -117,7 +176,8 @@ var checkPath = function (path, callback) {
callback(err);
return;
}
- Fs.mkdir(Path.dirname(path), function (err) {
+ // 511 -> octal 777
+ Fs.mkdir(Path.dirname(path), 511, function (err) {
if (err && err.code !== 'EEXIST') {
callback(err);
return;
@@ -154,7 +214,28 @@ var flushUnusedChannels = function (env, cb, frame) {
cb();
};
-var getChannel = function (env, id, callback) {
+var channelBytes = function (env, chanName, cb) {
+ var path = mkPath(env, chanName);
+ Fs.stat(path, function (err, stats) {
+ if (err) { return void cb(err); }
+ cb(undefined, stats.size);
+ });
+};
+
+/*::
+export type ChainPadServer_ChannelInternal_t = {
+ atime: number,
+ writeStream: typeof(process.stdout),
+ whenLoaded: ?Array<(err:?Error, chan:?ChainPadServer_ChannelInternal_t)=>void>,
+ onError: Array<(?Error)=>void>,
+ path: string
+};
+*/
+var getChannel = function (
+ env,
+ id,
+ callback /*:(err:?Error, chan:?ChainPadServer_ChannelInternal_t)=>void*/
+) {
if (env.channels[id]) {
var chan = env.channels[id];
chan.atime = +new Date();
@@ -178,9 +259,9 @@ var getChannel = function (env, id, callback) {
});
}
var path = mkPath(env, id);
- var channel = env.channels[id] = {
+ var channel /*:ChainPadServer_ChannelInternal_t*/ = env.channels[id] = {
atime: +new Date(),
- writeStream: undefined,
+ writeStream: (undefined /*:any*/),
whenLoaded: [ callback ],
onError: [ ],
path: path
@@ -193,6 +274,9 @@ var getChannel = function (env, id, callback) {
if (err) {
delete env.channels[id];
}
+ if (!channel.writeStream) {
+ throw new Error("getChannel() complete called without channel writeStream");
+ }
whenLoaded.forEach(function (wl) { wl(err, (err) ? undefined : channel); });
};
var fileExists;
@@ -211,7 +295,7 @@ var getChannel = function (env, id, callback) {
var stream = channel.writeStream = Fs.createWriteStream(path, { flags: 'a' });
env.openFiles++;
stream.on('open', waitFor());
- stream.on('error', function (err) {
+ stream.on('error', function (err /*:?Error*/) {
env.openFiles--;
// this might be called after this nThen block closes.
if (channel.whenLoaded) {
@@ -228,20 +312,22 @@ var getChannel = function (env, id, callback) {
});
};
-var message = function (env, chanName, msg, cb) {
+const messageBin = (env, chanName, msgBin, cb) => {
getChannel(env, chanName, function (err, chan) {
- if (err) {
+ if (!chan) {
cb(err);
return;
}
+ let called = false;
var complete = function (err) {
- var _cb = cb;
- cb = undefined;
- if (_cb) { _cb(err); }
+ if (called) { return; }
+ called = true;
+ cb(err);
};
chan.onError.push(complete);
- chan.writeStream.write(msg + '\n', function () {
- chan.onError.splice(chan.onError.indexOf(complete) - 1, 1);
+ chan.writeStream.write(msgBin, function () {
+ /*::if (!chan) { throw new Error("Flow unreachable"); }*/
+ chan.onError.splice(chan.onError.indexOf(complete), 1);
if (!cb) { return; }
//chan.messages.push(msg);
chan.atime = +new Date();
@@ -250,9 +336,13 @@ var message = function (env, chanName, msg, cb) {
});
};
+var message = function (env, chanName, msg, cb) {
+ messageBin(env, chanName, new Buffer(msg + '\n', 'utf8'), cb);
+};
+
var getMessages = function (env, chanName, handler, cb) {
getChannel(env, chanName, function (err, chan) {
- if (err) {
+ if (!chan) {
cb(err);
return;
}
@@ -271,21 +361,43 @@ var getMessages = function (env, chanName, handler, cb) {
errorState = true;
return void cb(err);
}
+ if (!chan) { throw new Error("impossible, flow checking"); }
chan.atime = +new Date();
cb();
});
});
};
-var channelBytes = function (env, chanName, cb) {
- var path = mkPath(env, chanName);
- Fs.stat(path, function (err, stats) {
- if (err) { return void cb(err); }
- cb(void 0, stats.size);
- });
+/*::
+export type ChainPadServer_MessageObj_t = { buff: Buffer, offset: number };
+export type ChainPadServer_Storage_t = {
+ readMessagesBin: (
+ channelName:string,
+ start:number,
+ asyncMsgHandler:(msg:ChainPadServer_MessageObj_t, moreCb:()=>void, abortCb:()=>void)=>void,
+ cb:(err:?Error)=>void
+ )=>void,
+ message: (channelName:string, content:string, cb:(err:?Error)=>void)=>void,
+ messageBin: (channelName:string, content:Buffer, cb:(err:?Error)=>void)=>void,
+ getMessages: (channelName:string, msgHandler:(msg:string)=>void, cb:(err:?Error)=>void)=>void,
+ removeChannel: (channelName:string, cb:(err:?Error)=>void)=>void,
+ closeChannel: (channelName:string, cb:(err:?Error)=>void)=>void,
+ flushUnusedChannels: (cb:()=>void)=>void,
+ getChannelSize: (channelName:string, cb:(err:?Error, size:?number)=>void)=>void,
+ getChannelMetadata: (channelName:string, cb:(err:?Error|string, data:?any)=>void)=>void,
+ clearChannel: (channelName:string, (err:?Error)=>void)=>void
};
-
-module.exports.create = function (conf, cb) {
+export type ChainPadServer_Config_t = {
+ verbose?: boolean,
+ filePath?: string,
+ channelExpirationMs?: number,
+ openFileLimit?: number
+};
+*/
+module.exports.create = function (
+ conf /*:ChainPadServer_Config_t*/,
+ cb /*:(store:ChainPadServer_Storage_t)=>void*/
+) {
var env = {
root: conf.filePath || './datastore',
channels: { },
@@ -294,15 +406,22 @@ module.exports.create = function (conf, cb) {
openFiles: 0,
openFileLimit: conf.openFileLimit || 2048,
};
- Fs.mkdir(env.root, function (err) {
+ // 0x1ff -> 777
+ Fs.mkdir(env.root, 0x1ff, function (err) {
if (err && err.code !== 'EEXIST') {
// TODO: somehow return a nice error
throw err;
}
cb({
+ readMessagesBin: (channelName, start, asyncMsgHandler, cb) => {
+ readMessagesBin(env, channelName, start, asyncMsgHandler, cb);
+ },
message: function (channelName, content, cb) {
message(env, channelName, content, cb);
},
+ messageBin: (channelName, content, cb) => {
+ messageBin(env, channelName, content, cb);
+ },
getMessages: function (channelName, msgHandler, cb) {
getMessages(env, channelName, msgHandler, cb);
},
@@ -331,4 +450,4 @@ module.exports.create = function (conf, cb) {
setInterval(function () {
flushUnusedChannels(env, function () { });
}, 5000);
-};
+};
\ No newline at end of file
diff --git a/www/code/orgmode.js b/www/code/orgmode.js
index 09e19966b..3377ebb15 100644
--- a/www/code/orgmode.js
+++ b/www/code/orgmode.js
@@ -7,12 +7,12 @@ define([
CodeMirror.defineSimpleMode("orgmode", {
start: [
{regex: /^(^\*{1,6}\s)(TODO|DOING|WAITING|NEXT){0,1}(CANCELLED|CANCEL|DEFERRED|DONE|REJECTED|STOP|STOPPED){0,1}(.*)$/, token: ["header org-level-star", "header org-todo", "header org-done", "header"]},
- {regex: /(^\+[^\/]*\+)/, token: ["strikethrough"]},
- {regex: /(^\*[^\/]*\*)/, token: ["strong"]},
- {regex: /(^\/[^\/]*\/)/, token: ["em"]},
- {regex: /(^\_[^\/]*\_)/, token: ["link"]},
- {regex: /(^\~[^\/]*\~)/, token: ["comment"]},
- {regex: /(^\=[^\/]*\=)/, token: ["comment"]},
+ {regex: /(\+[^\+]+\+)/, token: ["strikethrough"]},
+ {regex: /(\*[^\*]+\*)/, token: ["strong"]},
+ {regex: /(\/[^\/]+\/)/, token: ["em"]},
+ {regex: /(\_[^\_]+\_)/, token: ["link"]},
+ {regex: /(\~[^\~]+\~)/, token: ["comment"]},
+ {regex: /(\=[^\=]+\=)/, token: ["comment"]},
{regex: /\[\[[^\[\]]*\]\[[^\[\]]*\]\]/, token: "url"}, // links
{regex: /\[[xX\s]?\]/, token: 'qualifier'}, // checkbox
{regex: /\#\+BEGIN_[A-Z]*/, token: "comment", next: "env"}, // comments
diff --git a/www/common/application_config_internal.js b/www/common/application_config_internal.js
new file mode 100644
index 000000000..24c46b790
--- /dev/null
+++ b/www/common/application_config_internal.js
@@ -0,0 +1,115 @@
+/*
+ * This is an internal configuration file.
+ * If you want to change some configurable values, use the '/customize/application_config.js'
+ * file (make a copy from /customize.dist/application_config.js)
+ */
+define(function() {
+ var config = {};
+
+ /* Select the buttons displayed on the main page to create new collaborative sessions
+ * Existing types : pad, code, poll, slide
+ */
+ config.availablePadTypes = ['drive', 'pad', 'code', 'slide', 'poll', 'whiteboard', 'file', 'todo', 'contacts'];
+ config.registeredOnlyTypes = ['file', 'contacts'];
+
+ /* Cryptpad apps use a common API to display notifications to users
+ * by default, notifications are hidden after 5 seconds
+ * You can change their duration here (measured in milliseconds)
+ */
+ config.notificationTimeout = 5000;
+ config.disableUserlistNotifications = false;
+ config.hideLoadingScreenTips = false;
+
+ config.enablePinning = true;
+
+ // Update the default colors available in the whiteboard application
+ config.whiteboardPalette = [
+ '#000000', // black
+ '#FFFFFF', // white
+ '#848484', // grey
+ '#8B4513', // saddlebrown
+ '#FF0000', // red
+ '#FF8080', // peach?
+ '#FF8000', // orange
+ '#FFFF00', // yellow
+ '#80FF80', // light green
+ '#00FF00', // green
+ '#00FFFF', // cyan
+ '#008B8B', // dark cyan
+ '#0000FF', // blue
+ '#FF00FF', // fuschia
+ '#FF00C0', // hot pink
+ '#800080', // purple
+ ];
+
+ // Set enableTemplates to false to remove the button allowing users to save a pad as a template
+ // and remove the template category in CryptDrive
+ config.enableTemplates = true;
+
+ // Set enableHistory to false to remove the "History" button in all the apps.
+ config.enableHistory = true;
+
+ /* user passwords are hashed with scrypt, and salted with their username.
+ this value will be appended to the username, causing the resulting hash
+ to differ from other CryptPad instances if customized. This makes it
+ such that anyone who wants to bruteforce common credentials must do so
+ again on each CryptPad instance that they wish to attack.
+
+ WARNING: this should only be set when your CryptPad instance is first
+ created. Changing it at a later time will break logins for all existing
+ users.
+ */
+ config.loginSalt = '';
+ config.minimumPasswordLength = 8;
+
+ // Amount of time (ms) before aborting the session when the algorithm cannot synchronize the pad
+ config.badStateTimeout = 30000;
+
+ // Customize the icon used for each application.
+ // You can update the colors by making a copy of /customize.dist/src/less2/include/colortheme.less
+ config.applicationsIcon = {
+ file: 'fa-file-text-o',
+ pad: 'fa-file-word-o',
+ code: 'fa-file-code-o',
+ slide: 'fa-file-powerpoint-o',
+ poll: 'fa-calendar',
+ whiteboard: 'fa-paint-brush',
+ todo: 'fa-tasks',
+ contacts: 'fa-users',
+ };
+
+ // EXPERIMENTAL: Enabling "displayCreationScreen" may cause UI issues and possible loss of data
+ config.displayCreationScreen = false;
+
+ // Prevent anonymous users from storing pads in their drive
+ config.disableAnonymousStore = false;
+
+ // Hide the usage bar in settings and drive
+ //config.hideUsageBar = true;
+
+ // Disable feedback for all the users and hide the settings part about feedback
+ //config.disableFeedback = true;
+
+ // Add new options in the share modal (extend an existing tab or add a new tab).
+ // More info about how to use it on the wiki:
+ // https://github.com/xwiki-labs/cryptpad/wiki/Application-config#configcustomizeshareoptions
+ //config.customizeShareOptions = function (hashes, tabs, config) {};
+
+ // Add code to be executed on every page before loading the user object. `isLoggedIn` (bool) is
+ // indicating if the user is registered or anonymous. Here you can change the way anonymous users
+ // work in CryptPad, use an external SSO or even force registration
+ // *NOTE*: You have to call the `callback` function to continue the loading process
+ //config.beforeLogin = function(isLoggedIn, callback) {};
+
+ // Add code to be executed on every page after the user object is loaded (also work for
+ // unregistered users). This allows you to interact with your users' drive
+ // *NOTE*: You have to call the `callback` function to continue the loading process
+ //config.afterLogin = function(api, callback) {};
+
+ // Disabling the profile app allows you to import the profile informations (display name, avatar)
+ // from an external source and make sure the users can't change them from CryptPad.
+ // You can use config.afterLogin to import these values in the users' drive.
+ //config.disableProfile = true;
+
+ return config;
+});
diff --git a/www/common/common-feedback.js b/www/common/common-feedback.js
index 728d362cd..6d2c62e30 100644
--- a/www/common/common-feedback.js
+++ b/www/common/common-feedback.js
@@ -1,4 +1,7 @@
-define(['/customize/messages.js'], function (Messages) {
+define([
+ '/customize/messages.js',
+ '/customize/application_config.js'
+], function (Messages, AppConfig) {
var Feedback = {};
Feedback.init = function (state) {
@@ -19,6 +22,7 @@ define(['/customize/messages.js'], function (Messages) {
http.send();
};
Feedback.send = function (action, force) {
+ if (AppConfig.disableFeedback) { return; }
if (!action) { return; }
if (force !== true) {
if (!Feedback.state) { return; }
diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js
index 286fb84d6..0100be3f8 100644
--- a/www/common/common-ui-elements.js
+++ b/www/common/common-ui-elements.js
@@ -953,7 +953,32 @@ define([
};
if (!window.Symbol) { return void displayDefault(); } // IE doesn't have Symbol
if (!href) { return void displayDefault(); }
+
+ var centerImage = function ($img, $image, img) {
+ var w = img.width;
+ var h = img.height;
+ if (w>h) {
+ $image.css('max-height', '100%');
+ $img.css('flex-direction', 'column');
+ if (cb) { cb($img); }
+ return;
+ }
+ $image.css('max-width', '100%');
+ $img.css('flex-direction', 'row');
+ if (cb) { cb($img); }
+ };
+
var parsed = Hash.parsePadUrl(href);
+ if (parsed.type !== "file" || parsed.hashData.type !== "file") {
+ var $img = $('').appendTo($container);
+ var img = new Image();
+ $(img).attr('src', href);
+ img.onload = function () {
+ centerImage($img, $(img), img);
+ $(img).appendTo($img);
+ };
+ return;
+ }
var secret = Hash.getSecrets('file', parsed.hash);
if (secret.keys && secret.channel) {
var cryptKey = secret.keys && secret.keys.fileKeyStr;
@@ -971,17 +996,7 @@ define([
$img.attr('data-crypto-key', 'cryptpad:' + cryptKey);
UIElements.displayMediatagImage(Common, $img, function (err, $image, img) {
if (err) { return void console.error(err); }
- var w = img.width;
- var h = img.height;
- if (w>h) {
- $image.css('max-height', '100%');
- $img.css('flex-direction', 'column');
- if (cb) { cb($img); }
- return;
- }
- $image.css('max-width', '100%');
- $img.css('flex-direction', 'row');
- if (cb) { cb($img); }
+ centerImage($img, $image, img);
});
});
}
@@ -1259,7 +1274,7 @@ define([
$userAdminContent.append($userAccount).append(Util.fixHTML(accountName));
$userAdminContent.append($(' '));
}
- if (config.displayName) {
+ if (config.displayName && !AppConfig.disableProfile) {
// Hide "Display name:" in read only mode
$userName.append(Messages.user_displayName + ': ');
$userName.append($displayedName);
@@ -1282,14 +1297,14 @@ define([
});
}
// Add the change display name button if not in read only mode
- if (config.changeNameButtonCls && config.displayChangeName) {
+ if (config.changeNameButtonCls && config.displayChangeName && !AppConfig.disableProfile) {
options.push({
tag: 'a',
attributes: {'class': config.changeNameButtonCls},
content: Messages.user_rename
});
}
- if (accountName) {
+ if (accountName && !AppConfig.disableProfile) {
options.push({
tag: 'a',
attributes: {'class': 'cp-toolbar-menu-profile'},
diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js
index 775c725ad..5ec198a44 100644
--- a/www/common/cryptpad-common.js
+++ b/www/common/cryptpad-common.js
@@ -727,6 +727,10 @@ define([
};
Nthen(function (waitFor) {
+ if (AppConfig.beforeLogin) {
+ AppConfig.beforeLogin(LocalStore.isLoggedIn(), waitFor());
+ }
+ }).nThen(function (waitFor) {
var cfg = {
query: onMessage, // TODO temporary, will be replaced by a webworker channel
userHash: LocalStore.getUserHash(),
@@ -763,6 +767,7 @@ define([
}
initFeedback(data.feedback);
+ initialized = true;
}));
}).nThen(function (waitFor) {
// Load the new pad when the hash has changed
@@ -829,6 +834,10 @@ define([
delete sessionStorage.migrateAnonDrive;
}));
}
+ }).nThen(function (waitFor) {
+ if (AppConfig.afterLogin) {
+ AppConfig.afterLogin(common, waitFor());
+ }
}).nThen(function () {
updateLocalVersion();
f(void 0, env);
diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js
index 82cbee00d..68c217108 100644
--- a/www/common/outer/async-store.js
+++ b/www/common/outer/async-store.js
@@ -341,7 +341,7 @@ define([
// "priv" is not shared with other users but is needed by the apps
priv: {
edPublic: store.proxy.edPublic,
- friends: store.proxy.friends,
+ friends: store.proxy.friends || {},
settings: store.proxy.settings,
thumbnails: !Util.find(store.proxy, ['settings', 'general', 'disableThumbnails'])
}
diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js
index 03cb9d7c1..c7afbaca8 100644
--- a/www/common/toolbar3.js
+++ b/www/common/toolbar3.js
@@ -238,55 +238,57 @@ define([
var $nameValue = $('', {
'class': 'cp-toolbar-userlist-name-value'
}).text(name).appendTo($nameSpan);
- var $button = $('