Merge branch 'staging' into soon
commit
60b3728164
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -15,6 +15,8 @@
|
|||
* websockets
|
||||
* sandbox CSP
|
||||
* login block
|
||||
* recommend against trailing slashes for configured domains
|
||||
* remove slashes in server.js anyway
|
||||
* admin page
|
||||
* support responses to closed tickets
|
||||
* collapse very long messages
|
||||
|
@ -24,7 +26,9 @@
|
|||
* display survey URL
|
||||
* support 'KB' in Util.magnitudeOfBytes
|
||||
* degraded mode
|
||||
* decide on a number
|
||||
* decide on a number: 8
|
||||
* provide an easy way to change it (application_config.js)
|
||||
* inform users what the limit is (when degraded mode "kicks in")
|
||||
* sheets
|
||||
* fix naming collisions between images in spreadsheets
|
||||
* degraded mode not supported
|
||||
|
@ -32,14 +36,29 @@
|
|||
* pinning?
|
||||
* oo rebuild
|
||||
* OnlyOffice v6.2
|
||||
* some buttons that we were hiding have new ids and needed to be hidden again
|
||||
* translations
|
||||
* updated catch-phrase (Collaboration suite\nend-to-end-encrypted and open-source
|
||||
* CKEditor
|
||||
* cursor jump when clicking on a comment bubble
|
||||
* keybindings for common styles
|
||||
* test if this affects scroll position (it shouldn't)
|
||||
* check that CTRL-space doesn't mess with anything and that it is what Google uses
|
||||
* test on Mac
|
||||
* nodrive
|
||||
* load anonymous accounts without creating a drive
|
||||
* faster load time, less junk on the server
|
||||
* `AppConfig.allowDrivelessMode`
|
||||
* cursor color is randomly generated each time and doesn't persist after creating a drive
|
||||
* only affects framework apps for now
|
||||
* secure iframe now always knows the channel of the related document
|
||||
* more consistent API with other APPs
|
||||
* debug app doesn't create a drive
|
||||
* implement/fix ability to destroy pads whether they exist in your drive or not
|
||||
|
||||
|
||||
* Known issues
|
||||
* change password for documents in your drive when you don't have the most recent password (multi-owner pads)
|
||||
|
||||
|
||||
# 4.2.1
|
||||
|
|
|
@ -54,7 +54,7 @@ module.exports = {
|
|||
* and it may have unintended consequences in practice.
|
||||
*
|
||||
*/
|
||||
httpUnsafeOrigin: 'http://localhost:3000/',
|
||||
httpUnsafeOrigin: 'http://localhost:3000',
|
||||
|
||||
/* httpSafeOrigin is the URL that is used for the 'sandbox' described above.
|
||||
* If you're testing or developing with CryptPad on your local machine then
|
||||
|
|
|
@ -184,6 +184,7 @@
|
|||
}
|
||||
&.btn-register {
|
||||
margin-top: 10px !important;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -661,6 +661,8 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) {
|
|||
if (txid) { msg[0] = txid; }
|
||||
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(msg)], readMore);
|
||||
}, (err) => {
|
||||
// Any error but ENOENT: abort
|
||||
// ENOENT is allowed in case we want to create a new pad
|
||||
if (err && err.code !== 'ENOENT') {
|
||||
if (err.message === "EUNKNOWN") {
|
||||
Log.error("HK_GET_HISTORY", {
|
||||
|
@ -675,11 +677,28 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) {
|
|||
err: err && err.message || err,
|
||||
stack: err && err.stack,
|
||||
}); }
|
||||
// FIXME err.message isn't useful for users
|
||||
const parsedMsg = {error:err.message, channel: channelName, txid: txid};
|
||||
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(parsedMsg)]);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're asking for a specific version (lastKnownHash) but we receive an
|
||||
// ENOENT, this is not a pad creation so we need to abort.
|
||||
if (err && err.code === 'ENOENT' && lastKnownHash) {
|
||||
/*
|
||||
This informs clients that the pad they're trying to load was deleted by its owner.
|
||||
The user in question might be reconnecting or might have loaded the document from their cache.
|
||||
The owner that deleted it could be another user or the same user from a different device.
|
||||
Either way, the respectful thing to do is display an error screen informing them that the content
|
||||
is no longer on the server so they don't abuse the data and so that they don't unintentionally continue
|
||||
to edit it in a broken state.
|
||||
*/
|
||||
const parsedMsg2 = {error:'EDELETED', channel: channelName, txid: txid};
|
||||
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(parsedMsg2)]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (msgCount === 0 && !metadata_cache[channelName] && Server.channelContainsUser(channelName, userId)) {
|
||||
handleFirstMessage(Env, channelName, metadata);
|
||||
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(metadata)]);
|
||||
|
|
|
@ -16,15 +16,19 @@ var Env = require("./lib/env").create(config);
|
|||
|
||||
var app = Express();
|
||||
|
||||
var canonicalizeOrigin = function (s) {
|
||||
return (s || '').trim().replace(/\/+$/, '');
|
||||
};
|
||||
|
||||
(function () {
|
||||
// you absolutely must provide an 'httpUnsafeOrigin'
|
||||
if (typeof(config.httpUnsafeOrigin) !== 'string') {
|
||||
throw new Error("No 'httpUnsafeOrigin' provided");
|
||||
}
|
||||
|
||||
config.httpUnsafeOrigin = config.httpUnsafeOrigin.trim();
|
||||
config.httpUnsafeOrigin = canonicalizeOrigin(config.httpUnsafeOrigin);
|
||||
if (typeof(config.httpSafeOrigin) === 'string') {
|
||||
config.httpSafeOrigin = config.httpSafeOrigin.trim().replace(/\/$/, '');
|
||||
config.httpSafeOrigin = canonicalizeOrigin(config.httpSafeOrigin);
|
||||
}
|
||||
|
||||
// fall back to listening on a local address
|
||||
|
|
|
@ -42,12 +42,13 @@ define([
|
|||
return void cb(trimmedSafe !== trimmedUnsafe);
|
||||
}, _alert('Sandbox configuration: httpUnsafeOrigin !== httpSafeOrigin'));
|
||||
|
||||
assert(function (cb) {
|
||||
cb(trimmedSafe === ApiConfig.httpSafeOrigin);
|
||||
}, "httpSafeOrigin must not have a trailing slash");
|
||||
|
||||
assert(function (cb) {
|
||||
var origin = window.location.origin;
|
||||
return void cb([
|
||||
origin,
|
||||
origin + '/'
|
||||
].indexOf(ApiConfig.httpUnsafeOrigin) !== -1);
|
||||
return void cb(ApiConfig.httpUnsafeOrigin === origin);
|
||||
}, _alert('Sandbox configuration: loading via httpUnsafeOrigin'));
|
||||
|
||||
|
||||
|
|
|
@ -162,7 +162,7 @@ define(function() {
|
|||
// making it much faster to open new tabs.
|
||||
config.disableWorkers = false;
|
||||
|
||||
config.surveyURL = "https://survey.cryptpad.fr/index.php/672782";
|
||||
//config.surveyURL = "";
|
||||
|
||||
// Teams are always loaded during the initial loading screen (for the first tab only if
|
||||
// SharedWorkers are available). Allowing users to be members of multiple teams can
|
||||
|
@ -179,5 +179,23 @@ define(function() {
|
|||
// You can change the value here.
|
||||
// config.maxOwnedTeams = 5;
|
||||
|
||||
// The userlist displayed in collaborative documents is stored alongside the document data.
|
||||
// Everytime someone with edit rights joins a document or modify their user data (display
|
||||
// name, avatar, color, etc.), they update the "userlist" part of the document. When too many
|
||||
// editors are in the same document, all these changes increase the risks of conflicts which
|
||||
// require CPU time to solve. A "degraded" mode can now be set when a certain number of editors
|
||||
// are in a document at the same time. This mode disables the userlist, the chat and the
|
||||
// position of other users' cursor. You can configure the number of user from which the session
|
||||
// will enter into degraded mode. A big number may result in collaborative edition being broken,
|
||||
// but this number depends on the network and CPU performances of each user's device.
|
||||
config.degradedLimit = 8;
|
||||
|
||||
// In "legacy" mode, one-time users were always creating an "anonymous" drive when visiting CryptPad
|
||||
// in which they could store their pads. The new "driveless" mode allow users to open an existing
|
||||
// pad without creating a drive in the background. The drive will only be created if they visit
|
||||
// a different page (Drive, Settings, etc.) or try to create a new pad themselves. You can disable
|
||||
// the driveless mode by changing the following value to "false"
|
||||
config.allowDrivelessMode = true;
|
||||
|
||||
return config;
|
||||
});
|
||||
|
|
|
@ -1552,6 +1552,8 @@ define([
|
|||
faqLine,
|
||||
]);
|
||||
|
||||
$(content).find('a').attr('target', '_blank');
|
||||
|
||||
var buttons = [
|
||||
{
|
||||
className: 'primary',
|
||||
|
@ -2538,6 +2540,7 @@ define([
|
|||
UIElements.onServerError = function (common, err, toolbar, cb) {
|
||||
//if (["EDELETED", "EEXPIRED", "ERESTRICTED"].indexOf(err.type) === -1) { return; }
|
||||
var priv = common.getMetadataMgr().getPrivateData();
|
||||
var sframeChan = common.getSframeChannel();
|
||||
var msg = err.type;
|
||||
if (err.type === 'EEXPIRED') {
|
||||
msg = Messages.expiredError;
|
||||
|
@ -2547,10 +2550,29 @@ define([
|
|||
if (toolbar && typeof toolbar.deleted === "function") { toolbar.deleted(); }
|
||||
} else if (err.type === 'EDELETED') {
|
||||
if (priv.burnAfterReading) { return void cb(); }
|
||||
|
||||
if (autoStoreModal[priv.channel]) {
|
||||
autoStoreModal[priv.channel].delete();
|
||||
delete autoStoreModal[priv.channel];
|
||||
}
|
||||
// View users have the wrong seed, thay can't retireve access directly
|
||||
// Version 1 hashes don't support passwords
|
||||
if (!priv.readOnly && !priv.oldVersionHash) {
|
||||
sframeChan.event('EV_SHARE_OPEN', {hidden: true}); // Close share modal
|
||||
UIElements.displayPasswordPrompt(common, {
|
||||
fromServerError: true,
|
||||
loaded: err.loaded,
|
||||
});
|
||||
if (toolbar && typeof toolbar.deleted === "function") { toolbar.deleted(); }
|
||||
(cb || function () {})();
|
||||
return;
|
||||
}
|
||||
|
||||
msg = Messages.deletedError;
|
||||
if (err.loaded) {
|
||||
msg += Messages.errorCopy;
|
||||
}
|
||||
|
||||
if (toolbar && typeof toolbar.deleted === "function") { toolbar.deleted(); }
|
||||
} else if (err.type === 'ERESTRICTED') {
|
||||
msg = Messages.restrictedError;
|
||||
|
@ -2559,7 +2581,6 @@ define([
|
|||
msg = Messages.oo_deletedVersion;
|
||||
if (toolbar && typeof toolbar.failed === "function") { toolbar.failed(true); }
|
||||
}
|
||||
var sframeChan = common.getSframeChannel();
|
||||
sframeChan.event('EV_SHARE_OPEN', {hidden: true});
|
||||
UI.errorLoadingScreen(msg, Boolean(err.loaded), Boolean(err.loaded));
|
||||
(cb || function () {})();
|
||||
|
@ -2568,7 +2589,10 @@ define([
|
|||
UIElements.displayPasswordPrompt = function (common, cfg, isError) {
|
||||
var error;
|
||||
if (isError) { error = setHTML(h('p.cp-password-error'), Messages.password_error); }
|
||||
|
||||
var info = h('p.cp-password-info', Messages.password_info);
|
||||
var info_loaded = setHTML(h('p.cp-password-info'), Messages.errorCopy);
|
||||
|
||||
var password = UI.passwordInput({placeholder: Messages.password_placeholder});
|
||||
var $password = $(password);
|
||||
var button = h('button.btn.btn-primary', Messages.password_submit);
|
||||
|
@ -2580,6 +2604,21 @@ define([
|
|||
|
||||
var submit = function () {
|
||||
var value = $password.find('.cp-password-input').val();
|
||||
|
||||
// Password-prompt called from UIElements.onServerError
|
||||
if (cfg.fromServerError) {
|
||||
common.getSframeChannel().query('Q_PASSWORD_CHECK', value, function (err, obj) {
|
||||
if (obj && obj.error) {
|
||||
console.error(obj.error);
|
||||
return void UI.warn(Messages.error);
|
||||
}
|
||||
// On success, outer will reload the page: this is a wrong password
|
||||
UIElements.displayPasswordPrompt(common, cfg, true);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Initial load
|
||||
UI.addLoadingScreen({newProgress: true});
|
||||
if (window.CryptPad_updateLoadingProgress) {
|
||||
window.CryptPad_updateLoadingProgress({
|
||||
|
@ -2593,6 +2632,8 @@ define([
|
|||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
$password.find('.cp-password-input').on('keydown', function (e) { if (e.which === 13) { submit(); } });
|
||||
$(button).on('click', function () { submit(); });
|
||||
|
||||
|
@ -2600,12 +2641,13 @@ define([
|
|||
var block = h('div#cp-loading-password-prompt', [
|
||||
error,
|
||||
info,
|
||||
cfg.loaded ? info_loaded : undefined,
|
||||
h('p.cp-password-form', [
|
||||
password,
|
||||
button
|
||||
])
|
||||
]),
|
||||
]);
|
||||
UI.errorLoadingScreen(block);
|
||||
UI.errorLoadingScreen(block, Boolean(cfg.loaded), Boolean(cfg.loaded));
|
||||
|
||||
$password.find('.cp-password-input').focus();
|
||||
};
|
||||
|
|
|
@ -481,10 +481,20 @@ define([
|
|||
});
|
||||
};
|
||||
|
||||
common.isNewChannel = function (href, password, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
var channel = Hash.hrefToHexChannelId(href, password);
|
||||
postMessage('IS_NEW_CHANNEL', {channel: channel}, function (obj) {
|
||||
var error = obj && obj.error;
|
||||
if (error) { return void cb(error); }
|
||||
if (!obj) { return void cb('ERROR'); }
|
||||
cb (null, obj.isNew);
|
||||
}, {timeout: -1});
|
||||
};
|
||||
// This function is used when we want to open a pad. We first need
|
||||
// to check if it exists. With the cached drive, we need to wait for
|
||||
// the network to be available before we can continue.
|
||||
common.isNewChannel = function (href, password, _cb) {
|
||||
common.hasChannelHistory = function (href, password, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
var channel = Hash.hrefToHexChannelId(href, password);
|
||||
var error;
|
||||
|
@ -1095,6 +1105,7 @@ define([
|
|||
|
||||
common.changePadPassword = function (Crypt, Crypto, data, cb) {
|
||||
var href = data.href;
|
||||
var oldPassword = data.oldPassword;
|
||||
var newPassword = data.password;
|
||||
var teamId = data.teamId;
|
||||
if (!href) { return void cb({ error: 'EINVAL_HREF' }); }
|
||||
|
@ -1123,7 +1134,9 @@ define([
|
|||
|
||||
var isSharedFolder = parsed.type === 'drive';
|
||||
|
||||
var optsGet = {};
|
||||
var optsGet = {
|
||||
password: oldPassword
|
||||
};
|
||||
var optsPut = {
|
||||
password: newPassword,
|
||||
metadata: {},
|
||||
|
@ -1133,7 +1146,7 @@ define([
|
|||
var cryptgetVal;
|
||||
|
||||
Nthen(function (waitFor) {
|
||||
if (parsed.hashData && parsed.hashData.password) {
|
||||
if (parsed.hashData && parsed.hashData.password && !oldPassword) {
|
||||
common.getPadAttribute('password', waitFor(function (err, password) {
|
||||
optsGet.password = password;
|
||||
}), href);
|
||||
|
@ -1179,7 +1192,6 @@ define([
|
|||
} else if (mailbox && typeof(mailbox) === "object") {
|
||||
m = {};
|
||||
Object.keys(mailbox).forEach(function (ed) {
|
||||
console.log(mailbox[ed]);
|
||||
try {
|
||||
m[ed] = newCrypto.encrypt(oldCrypto.decrypt(mailbox[ed], true, true));
|
||||
} catch (e) {
|
||||
|
@ -1214,6 +1226,7 @@ define([
|
|||
cryptgetVal = JSON.stringify(parsed);
|
||||
}
|
||||
}), optsGet);
|
||||
Cache.clearChannel(newSecret.channel, waitFor());
|
||||
}).nThen(function (waitFor) {
|
||||
optsPut.metadata.restricted = oldMetadata.restricted;
|
||||
optsPut.metadata.allowed = oldMetadata.allowed;
|
||||
|
@ -1418,6 +1431,7 @@ define([
|
|||
common.changeOOPassword = function (data, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
var href = data.href;
|
||||
var oldPassword = data.oldPassword;
|
||||
var newPassword = data.password;
|
||||
var teamId = data.teamId;
|
||||
if (!href) { return void cb({ error: 'EINVAL_HREF' }); }
|
||||
|
@ -1431,7 +1445,6 @@ define([
|
|||
var oldMetadata;
|
||||
var oldRtChannel;
|
||||
var privateData;
|
||||
var padData;
|
||||
|
||||
var newSecret;
|
||||
if (parsed.hashData.version >= 2) {
|
||||
|
@ -1452,19 +1465,22 @@ define([
|
|||
validateKey: newSecret.keys.validateKey
|
||||
},
|
||||
};
|
||||
var optsGet = {};
|
||||
var optsGet = {
|
||||
password: oldPassword
|
||||
};
|
||||
|
||||
Nthen(function (waitFor) {
|
||||
common.getPadAttribute('', waitFor(function (err, _data) {
|
||||
padData = _data;
|
||||
optsGet.password = padData.password;
|
||||
if (!oldPassword && _data) {
|
||||
optsGet.password = _data.password;
|
||||
}
|
||||
}), href);
|
||||
common.getAccessKeys(waitFor(function (keys) {
|
||||
optsGet.accessKeys = keys;
|
||||
optsPut.accessKeys = keys;
|
||||
}));
|
||||
}).nThen(function (waitFor) {
|
||||
oldSecret = Hash.getSecrets(parsed.type, parsed.hash, padData.password);
|
||||
oldSecret = Hash.getSecrets(parsed.type, parsed.hash, optsGet.password);
|
||||
|
||||
require([
|
||||
'/common/cryptget.js',
|
||||
|
@ -1592,6 +1608,7 @@ define([
|
|||
}
|
||||
}));
|
||||
}));
|
||||
Cache.clearChannel(newSecret.channel, waitFor());
|
||||
}).nThen(function (waitFor) {
|
||||
// The new rt channel is ready
|
||||
// The blob uses its own encryption and doesn't need to be reencrypted
|
||||
|
@ -2500,7 +2517,25 @@ define([
|
|||
}
|
||||
if (parsedNew.hashData) { oldHref = newHref; }
|
||||
};
|
||||
// XXX if you're in noDrive mode, check if an FS_hash is added and reload if that's the case
|
||||
// Listen for login/logout in other tabs
|
||||
if (rdyCfg.noDrive && !localStorage[Constants.fileHashKey]) {
|
||||
window.addEventListener('storage', function (e) {
|
||||
if (e.key !== Constants.fileHashKey) { return; }
|
||||
// New entry added to FS_hash: drive created in another tab, reload
|
||||
var o = e.oldValue;
|
||||
var n = e.newValue;
|
||||
if (!o && n) {
|
||||
postMessage('HAS_DRIVE', null, function(obj) {
|
||||
// If we're still in noDrive mode, reload
|
||||
if (!obj.state) {
|
||||
LocalStore.loginReload();
|
||||
}
|
||||
// Otherwise this worker is connected, nothing to do
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
window.addEventListener('storage', function (e) {
|
||||
if (e.key !== Constants.userHashKey) { return; }
|
||||
var o = e.oldValue;
|
||||
|
|
|
@ -25,10 +25,12 @@ define([
|
|||
var sframeChan = common.getSframeChannel();
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
|
||||
var channel = data.channel;
|
||||
var priv = metadataMgr.getPrivateData();
|
||||
var channel = data.channel || priv.channel;
|
||||
var owners = data.owners || [];
|
||||
var pending_owners = data.pending_owners || [];
|
||||
var teamOwner = data.teamId;
|
||||
var title = opts.title;
|
||||
|
||||
opts = opts || {};
|
||||
var redrawAll = function () {};
|
||||
|
@ -115,7 +117,7 @@ define([
|
|||
if (!friend) { return; }
|
||||
common.mailbox.sendTo("RM_OWNER", {
|
||||
channel: channel,
|
||||
title: data.title,
|
||||
title: data.title || title,
|
||||
pending: pending
|
||||
}, {
|
||||
channel: friend.notifications,
|
||||
|
@ -271,7 +273,7 @@ define([
|
|||
href: data.href || data.rohref,
|
||||
password: data.password,
|
||||
path: isTemplate ? ['template'] : undefined,
|
||||
title: data.title || '',
|
||||
title: data.title || title || "",
|
||||
teamId: obj.id
|
||||
}, waitFor(function (err) {
|
||||
if (err) { return void console.error(err); }
|
||||
|
@ -320,6 +322,12 @@ define([
|
|||
}));
|
||||
}
|
||||
}).nThen(function (waitFor) {
|
||||
var href = data.href;
|
||||
var hashes = priv.hashes || {};
|
||||
var bestHash = hashes.editHash || hashes.viewHash || hashes.fileHash;
|
||||
if (data.fakeHref) {
|
||||
href = Hash.hashToHref(bestHash, priv.app);
|
||||
}
|
||||
sel.forEach(function (el) {
|
||||
var curve = $(el).attr('data-curve');
|
||||
if (curve === user.curvePublic) { return; }
|
||||
|
@ -327,9 +335,9 @@ define([
|
|||
if (!friend) { return; }
|
||||
common.mailbox.sendTo("ADD_OWNER", {
|
||||
channel: channel,
|
||||
href: data.href,
|
||||
password: data.password,
|
||||
title: data.title
|
||||
href: href,
|
||||
password: data.password || priv.password,
|
||||
title: data.title || title
|
||||
}, {
|
||||
channel: friend.notifications,
|
||||
curvePublic: friend.curvePublic
|
||||
|
@ -398,7 +406,8 @@ define([
|
|||
var sframeChan = common.getSframeChannel();
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
|
||||
var channel = data.channel;
|
||||
var priv = metadataMgr.getPrivateData();
|
||||
var channel = data.channel || priv.channel;
|
||||
var owners = data.owners || [];
|
||||
var restricted = data.restricted || false;
|
||||
var allowed = data.allowed || [];
|
||||
|
@ -888,9 +897,17 @@ define([
|
|||
});
|
||||
}
|
||||
|
||||
var href = data.href;
|
||||
var hashes = priv.hashes || {};
|
||||
var bestHash = hashes.editHash || hashes.viewHash || hashes.fileHash;
|
||||
if (data.fakeHref) {
|
||||
href = Hash.hashToHref(bestHash, priv.app);
|
||||
}
|
||||
var isNotStored = Boolean(data.fakeHref);
|
||||
sframeChan.query(q, {
|
||||
teamId: typeof(owned) !== "boolean" ? owned : undefined,
|
||||
href: data.href,
|
||||
href: href,
|
||||
oldPassword: priv.password,
|
||||
password: newPass
|
||||
}, function (err, data) {
|
||||
$(passwordOk).text(Messages.properties_changePasswordButton);
|
||||
|
@ -924,22 +941,26 @@ define([
|
|||
// Pad password changed: update the href
|
||||
// Use hidden hash if needed (we're an owner of this pad so we know it is stored)
|
||||
var useUnsafe = Util.find(priv, ['settings', 'security', 'unsafeLinks']);
|
||||
var href = (priv.readOnly && data.roHref) ? data.roHref : data.href;
|
||||
if (isNotStored) { useUnsafe = true; }
|
||||
var _href = (priv.readOnly && data.roHref) ? data.roHref : data.href;
|
||||
if (useUnsafe !== true) {
|
||||
var newParsed = Hash.parsePadUrl(href);
|
||||
var newParsed = Hash.parsePadUrl(_href);
|
||||
var newSecret = Hash.getSecrets(newParsed.type, newParsed.hash, newPass);
|
||||
var newHash = Hash.getHiddenHashFromKeys(parsed.type, newSecret, {});
|
||||
href = Hash.hashToHref(newHash, parsed.type);
|
||||
_href = Hash.hashToHref(newHash, parsed.type);
|
||||
}
|
||||
|
||||
// Trigger a page reload if the href didn't change
|
||||
if (_href === href) { _href = undefined; }
|
||||
|
||||
if (data.warning) {
|
||||
return void UI.alert(Messages.properties_passwordWarning, function () {
|
||||
common.gotoURL(href);
|
||||
common.gotoURL(_href);
|
||||
}, {force: true});
|
||||
}
|
||||
return void UI.alert(Messages.properties_passwordSuccess, function () {
|
||||
if (!isSharedFolder) {
|
||||
common.gotoURL(href);
|
||||
common.gotoURL(_href);
|
||||
}
|
||||
}, {force: true});
|
||||
});
|
||||
|
@ -956,7 +977,7 @@ define([
|
|||
spinner.spin();
|
||||
sframeChan.query('Q_DELETE_OWNED', {
|
||||
teamId: typeof(owned) !== "boolean" ? owned : undefined,
|
||||
channel: data.channel
|
||||
channel: data.channel || priv.channel
|
||||
}, function (err, obj) {
|
||||
spinner.done();
|
||||
UI.findCancelButton().click();
|
||||
|
|
|
@ -1281,13 +1281,15 @@ define([
|
|||
'#title-user-name { display: none !important; }' +
|
||||
(supportsXLSX() ? '' : '#slot-btn-dt-print { display: none !important; }') +
|
||||
// New OO:
|
||||
'#asc-gen566 { display: none !important; }' + // Insert image from url
|
||||
'#asc-gen257 { display: none !important; }' + // Insert image from url
|
||||
'section[data-tab="ins"] .separator:nth-last-child(2) { display: none !important; }' + // separator
|
||||
'#slot-btn-insequation { display: none !important; }' + // Insert equation
|
||||
//'.toolbar .tabs .ribtab:not(.canedit) { display: none !important; }' + // Switch collaborative mode
|
||||
'#fm-btn-info { display: none !important; }' + // Author name, doc title, etc. in "File" (menu entry)
|
||||
'#panel-info { display: none !important; }' + // Same but content
|
||||
'#image-button-from-url { display: none !important; }' + // Inline image settings: replace with url
|
||||
'#asc-gen1839 { display: none !important; }' + // Image context menu: replace with url
|
||||
'#asc-gen5883 { display: none !important; }' + // Rightside image menu: replace with url
|
||||
'#file-menu-panel .devider { display: none !important; }' + // separator in the "File" menu
|
||||
'#left-btn-spellcheck, #left-btn-about { display: none !important; }'+
|
||||
'div.btn-users.dropdown-toggle { display: none; !important }';
|
||||
|
|
|
@ -331,6 +331,8 @@ define([
|
|||
teamId = data.teamId;
|
||||
}
|
||||
|
||||
// XXX CLEAR CACHE
|
||||
|
||||
if (channel === store.driveChannel && !force) {
|
||||
return void cb({error: 'User drive removal blocked!'});
|
||||
}
|
||||
|
@ -450,7 +452,7 @@ define([
|
|||
account.note = obj.note;
|
||||
cb(obj);
|
||||
});
|
||||
});
|
||||
}, Cache);
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
@ -586,11 +588,14 @@ define([
|
|||
var proxy = store.proxy || {};
|
||||
var disableThumbnails = Util.find(proxy, ['settings', 'general', 'disableThumbnails']);
|
||||
var teams = (store.modules['team'] && store.modules['team'].getTeamsData(app)) || {};
|
||||
if (!proxy.uid) {
|
||||
store.noDriveUid = store.noDriveUid || Hash.createChannelId();
|
||||
}
|
||||
var metadata = {
|
||||
// "user" is shared with everybody via the userlist
|
||||
user: {
|
||||
name: proxy[Constants.displayNameKey] || store.noDriveName || "",
|
||||
uid: proxy.uid || Hash.createChannelId(), // Random uid in nodrive mode
|
||||
uid: proxy.uid || store.noDriveUid, // Random uid in nodrive mode
|
||||
avatar: Util.find(proxy, ['profile', 'avatar']),
|
||||
profile: Util.find(proxy, ['profile', 'view']),
|
||||
color: getUserColor(),
|
||||
|
@ -858,6 +863,7 @@ define([
|
|||
Store.setDisplayName = function (clientId, value, cb) {
|
||||
if (!store.proxy) {
|
||||
store.noDriveName = value;
|
||||
broadcast([clientId], "UPDATE_METADATA");
|
||||
return void cb();
|
||||
}
|
||||
if (store.modules['profile']) {
|
||||
|
@ -1704,6 +1710,10 @@ define([
|
|||
var onError = function (err) {
|
||||
channel.bcast("PAD_ERROR", err);
|
||||
|
||||
if (err && err.type === "EDELETED" && Cache && Cache.clearChannel) {
|
||||
Cache.clearChannel(data.channel);
|
||||
}
|
||||
|
||||
// If this is a DELETED, EXPIRED or RESTRICTED pad, leave the channel
|
||||
if (["EDELETED", "EEXPIRED", "ERESTRICTED"].indexOf(err.type) === -1) { return; }
|
||||
Store.leavePad(null, data, function () {});
|
||||
|
@ -1714,11 +1724,13 @@ define([
|
|||
postMessage(clientId, "PAD_CACHE");
|
||||
},
|
||||
onCacheReady: function () {
|
||||
channel.hasCache = true;
|
||||
postMessage(clientId, "PAD_CACHE_READY");
|
||||
},
|
||||
onReady: function (pad) {
|
||||
var padData = pad.metadata || {};
|
||||
channel.data = padData;
|
||||
channel.ready = true;
|
||||
if (padData && padData.validateKey && store.messenger) {
|
||||
store.messenger.storeValidateKey(data.channel, padData.validateKey);
|
||||
}
|
||||
|
@ -2835,6 +2847,12 @@ define([
|
|||
if (store.ready) { return; } // the store is already ready, it is a reconnection
|
||||
store.driveMetadata = info.metadata;
|
||||
if (!rt.proxy.drive || typeof(rt.proxy.drive) !== 'object') { rt.proxy.drive = {}; }
|
||||
if (!rt.proxy[Constants.displayNameKey] && store.noDriveName) {
|
||||
rt.proxy[Constants.displayNameKey] = store.noDriveName;
|
||||
}
|
||||
if (!rt.proxy.uid && store.noDriveUid) {
|
||||
rt.proxy.uid = store.noDriveUid;
|
||||
}
|
||||
/*
|
||||
// deprecating localStorage migration as of 4.2.0
|
||||
var drive = rt.proxy.drive;
|
||||
|
@ -2929,6 +2947,13 @@ define([
|
|||
*/
|
||||
var initialized = false;
|
||||
|
||||
// Are we still in noDrive mode?
|
||||
Store.hasDrive = function (clientId, data, cb) {
|
||||
cb({
|
||||
state: Boolean(store.proxy)
|
||||
});
|
||||
};
|
||||
|
||||
// If we load CryptPad for the first time from an existing pad, don't create a
|
||||
// drive automatically.
|
||||
var onNoDrive = function (clientId, cb) {
|
||||
|
@ -2950,7 +2975,13 @@ define([
|
|||
if (!store.network) {
|
||||
var wsUrl = NetConfig.getWebsocketURL();
|
||||
return void Netflux.connect(wsUrl).then(function (network) {
|
||||
store.network = network;
|
||||
// If we already haave a network (race condition), use the
|
||||
// existing one and forget this one
|
||||
if (!store.network) { store.network = network; }
|
||||
else {
|
||||
network.disconnect();
|
||||
network = store.network;
|
||||
}
|
||||
// We need to know the HistoryKeeper ID to initialize the anon RPC
|
||||
// Join a basic ephemeral channel, get the ID and leave it instantly
|
||||
network.join('0000000000000000000000000000000000').then(function (wc) {
|
||||
|
|
|
@ -2,11 +2,12 @@ define([
|
|||
'/common/common-util.js',
|
||||
'/common/common-constants.js',
|
||||
'/customize/messages.js',
|
||||
'/customize/application_config.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
], function (Util, Constants, Messages, Crypto) {
|
||||
], function (Util, Constants, Messages, AppConfig, Crypto) {
|
||||
var Cursor = {};
|
||||
|
||||
var DEGRADED = 3; // XXX Number of users before switching to degraded mode
|
||||
var DEGRADED = AppConfig.degradedLimit || 8;
|
||||
|
||||
var convertToUint8 = function (obj) {
|
||||
var l = Object.keys(obj).length;
|
||||
|
@ -50,6 +51,12 @@ define([
|
|||
});
|
||||
};
|
||||
|
||||
var updateDegraded = function (ctx, wc, chan) {
|
||||
var m = wc.members;
|
||||
chan.degraded = (m.length-1) >= DEGRADED;
|
||||
ctx.emit('DEGRADED', { degraded: chan.degraded }, chan.clients);
|
||||
};
|
||||
|
||||
var initCursor = function (ctx, obj, client, cb) {
|
||||
var channel = obj.channel;
|
||||
var secret = obj.secret;
|
||||
|
@ -92,14 +99,10 @@ define([
|
|||
|
||||
// ==> And push the new tab to the list
|
||||
chan.clients.push(client);
|
||||
updateDegraded(ctx, chan.wc, chan);
|
||||
return void cb();
|
||||
}
|
||||
|
||||
var updateDegraded = function (ctx, wc, chan) {
|
||||
var m = wc.members;
|
||||
chan.degraded = (m.length-1) >= DEGRADED;
|
||||
ctx.emit('DEGRADED', { degraded: chan.degraded }, chan.clients);
|
||||
};
|
||||
var onOpen = function (wc) {
|
||||
|
||||
ctx.channels[channel] = ctx.channels[channel] || {};
|
||||
|
|
|
@ -537,10 +537,9 @@ define([
|
|||
if (peer === hk) { return; }
|
||||
if (channel.readOnly) { return; }
|
||||
|
||||
// XXX review
|
||||
// Sending myData is used to build a "mapId" object which links
|
||||
// netflux IDs to a curvePublic/uid. We use this map in friend chat
|
||||
// to detect is the other user is online and we also use it in team chat
|
||||
// to detect if the other user is online and we also use it in team chat
|
||||
// to show if other team members are online (in the roster section).
|
||||
// It is not needed in the pad chat for now and only causes useless
|
||||
// network usage.
|
||||
|
|
|
@ -15,6 +15,7 @@ define([
|
|||
MIGRATE_ANON_DRIVE: Store.migrateAnonDrive,
|
||||
PING: function (cId, data, cb) { cb(); },
|
||||
CACHE_DISABLE: Store.disableCache,
|
||||
HAS_DRIVE: Store.hasDrive,
|
||||
// RPC
|
||||
UPDATE_PIN_LIMIT: Store.updatePinLimit,
|
||||
GET_PIN_LIMIT: Store.getPinLimit,
|
||||
|
|
|
@ -868,7 +868,6 @@ define([
|
|||
if (fId && Env.folders[fId] && Env.folders[fId].deleting) {
|
||||
delete Env.folders[fId].deleting;
|
||||
}
|
||||
console.error(obj.error, chan);
|
||||
Feedback.send('ERROR_DELETING_OWNED_PAD=' + chan + '|' + obj.error, true);
|
||||
return void cb();
|
||||
}
|
||||
|
@ -881,6 +880,11 @@ define([
|
|||
ids.push(fId);
|
||||
}
|
||||
|
||||
if (!ids.length) {
|
||||
toDelete = undefined;
|
||||
return void cb();
|
||||
}
|
||||
|
||||
ids.forEach(function (id) {
|
||||
var paths = findFile(Env, id);
|
||||
var _resolved = _resolvePaths(Env, paths);
|
||||
|
@ -912,8 +916,13 @@ define([
|
|||
});
|
||||
});
|
||||
}).nThen(function () {
|
||||
// Remove deleted pads from the drive
|
||||
_delete(Env, { resolved: toDelete }, cb);
|
||||
if (!toDelete) {
|
||||
// Nothing to delete
|
||||
cb();
|
||||
} else {
|
||||
// Remove deleted pads from the drive
|
||||
_delete(Env, { resolved: toDelete }, cb);
|
||||
}
|
||||
// If we were using the access modal, send a refresh command
|
||||
if (data.channel) {
|
||||
Env.Store.refreshDriveUI();
|
||||
|
|
|
@ -78,7 +78,7 @@ define([
|
|||
};
|
||||
var AppConfig;
|
||||
var Test;
|
||||
var password, newPadPassword;
|
||||
var password, newPadPassword, newPadPasswordForce;
|
||||
var initialPathInDrive;
|
||||
var burnAfterReading;
|
||||
|
||||
|
@ -221,8 +221,11 @@ define([
|
|||
}
|
||||
} catch (e) { console.error(e); }
|
||||
|
||||
// NOTE: Driveless mode should only work for existing pads, but we can't check that
|
||||
// before creating the worker because we need the anon RPC to do so.
|
||||
// We're only going to check if a hash exists in the URL or not.
|
||||
Cryptpad.ready(waitFor(), {
|
||||
noDrive: cfg.noDrive,
|
||||
noDrive: cfg.noDrive && AppConfig.allowDrivelessMode && currentPad.hash,
|
||||
driveEvents: cfg.driveEvents,
|
||||
cache: Boolean(cfg.cache),
|
||||
currentPad: currentPad
|
||||
|
@ -309,6 +312,7 @@ define([
|
|||
newPadPassword = Crypto.decrypt(newPad.pw, uKey);
|
||||
} catch (e) { console.error(e); }
|
||||
}
|
||||
if (newPad.f) { newPadPasswordForce = 1; }
|
||||
if (newPad.d) {
|
||||
Cryptpad.fromFileData = newPad.d;
|
||||
var _parsed1 = Utils.Hash.parsePadUrl(Cryptpad.fromFileData.href);
|
||||
|
@ -316,6 +320,7 @@ define([
|
|||
delete Cryptpad.fromFileData;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
console.error(e, parsed.hashData.newPadOpts);
|
||||
}
|
||||
|
@ -346,7 +351,7 @@ define([
|
|||
}
|
||||
|
||||
// We now need to check if there is a password and if we know the correct password.
|
||||
// We'll use getFileSize and isNewChannel to detect incorrect passwords.
|
||||
// We'll use getFileSize and hasChannelHistory to detect incorrect passwords.
|
||||
|
||||
// First we'll get the password value from our drive (getPadAttribute), and we'll check
|
||||
// if the channel is valid. If the pad is not stored in our drive, we'll test with an
|
||||
|
@ -394,15 +399,15 @@ define([
|
|||
}
|
||||
};
|
||||
if (parsed.type === "file") {
|
||||
// `isNewChannel` doesn't work for files (not a channel)
|
||||
// `hasChannelHistory` doesn't work for files (not a channel)
|
||||
// `getFileSize` is not adapted to channels because of metadata
|
||||
Cryptpad.getFileSize(currentPad.href, password, function (e, size) {
|
||||
next(e, size === 0);
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Not a file, so we can use `isNewChannel`
|
||||
Cryptpad.isNewChannel(currentPad.href, password, next);
|
||||
// Not a file, so we can use `hasChannelHistory`
|
||||
Cryptpad.hasChannelHistory(currentPad.href, password, next);
|
||||
});
|
||||
sframeChan.event("EV_PAD_PASSWORD", cfg);
|
||||
};
|
||||
|
@ -464,19 +469,32 @@ define([
|
|||
currentPad.href = parsed.getUrl(opts);
|
||||
currentPad.hash = parsed.hashData && parsed.hashData.getHash(opts);
|
||||
}
|
||||
Cryptpad.getPadAttribute('title', w(function (err, data) {
|
||||
Cryptpad.getPadAttribute('channel', w(function (err, data) {
|
||||
stored = (!err && typeof (data) === "string");
|
||||
}));
|
||||
Cryptpad.getPadAttribute('password', w(function (err, val) {
|
||||
password = val;
|
||||
}), parsed.getUrl());
|
||||
}).nThen(function (w) {
|
||||
// If we've already tested this password and this is a redirect, force
|
||||
if (typeof(newPadPassword) !== "undefined" && newPadPasswordForce) {
|
||||
password = newPadPassword;
|
||||
return void todo();
|
||||
}
|
||||
|
||||
// If the pad is not stored and we have a newPadPassword, it probably
|
||||
// comes from a notification: password prompt pre-filled
|
||||
if (!password && !stored && newPadPassword) {
|
||||
passwordCfg.value = newPadPassword;
|
||||
}
|
||||
|
||||
// Pad not stored && password required: always ask for the password
|
||||
if (!stored && parsed.hashData.password && !newPadPasswordForce) {
|
||||
return void askPassword(true, passwordCfg);
|
||||
}
|
||||
|
||||
if (parsed.type === "file") {
|
||||
// `isNewChannel` doesn't work for files (not a channel)
|
||||
// `hasChannelHistory` doesn't work for files (not a channel)
|
||||
// `getFileSize` is not adapted to channels because of metadata
|
||||
Cryptpad.getFileSize(currentPad.href, password, w(function (e, size) {
|
||||
if (size !== 0) { return void todo(); }
|
||||
|
@ -485,8 +503,8 @@ define([
|
|||
}));
|
||||
return;
|
||||
}
|
||||
// Not a file, so we can use `isNewChannel`
|
||||
Cryptpad.isNewChannel(currentPad.href, password, w(function(e, isNew) {
|
||||
// Not a file, so we can use `hasChannelHistory`
|
||||
Cryptpad.hasChannelHistory(currentPad.href, password, w(function(e, isNew) {
|
||||
if (isNew && expire && expire < (+new Date())) {
|
||||
sframeChan.event("EV_EXPIRED_ERROR");
|
||||
waitFor.abort();
|
||||
|
@ -500,10 +518,6 @@ define([
|
|||
waitFor.abort();
|
||||
return;
|
||||
}
|
||||
if (!stored && !parsed.hashData.password) {
|
||||
// We've received a link without /p/ and it doesn't work without a password: abort
|
||||
return void todo();
|
||||
}
|
||||
// Wrong password or deleted file?
|
||||
askPassword(true, passwordCfg);
|
||||
}));
|
||||
|
@ -537,8 +551,7 @@ define([
|
|||
|
||||
if (realtime) {
|
||||
// TODO we probably don't need to check again for password-protected pads
|
||||
// (we use isNewChannel to test the password...)
|
||||
Cryptpad.isNewChannel(currentPad.href, password, waitFor(function (e, isNew) {
|
||||
Cryptpad.hasChannelHistory(currentPad.href, password, waitFor(function (e, isNew) {
|
||||
if (e) { return console.error(e); }
|
||||
isNewFile = Boolean(isNew);
|
||||
}));
|
||||
|
@ -604,6 +617,7 @@ define([
|
|||
feedbackAllowed: Utils.Feedback.state,
|
||||
isPresent: parsed.hashData && parsed.hashData.present,
|
||||
isEmbed: parsed.hashData && parsed.hashData.embed,
|
||||
oldVersionHash: parsed.hashData && parsed.hashData.version < 2, // password
|
||||
isHistoryVersion: parsed.hashData && parsed.hashData.versionHash,
|
||||
notifications: notifs,
|
||||
accounts: {
|
||||
|
@ -1404,6 +1418,7 @@ define([
|
|||
};
|
||||
config.data = {
|
||||
app: parsed.type,
|
||||
channel: secret.channel,
|
||||
hashes: hashes,
|
||||
password: password,
|
||||
isTemplate: isTemplate,
|
||||
|
@ -1669,6 +1684,45 @@ define([
|
|||
});
|
||||
});
|
||||
|
||||
sframeChan.on('Q_PASSWORD_CHECK', function (pw, cb) {
|
||||
Cryptpad.isNewChannel(currentPad.href, pw, function (e, isNew) {
|
||||
if (isNew === false) {
|
||||
var channel = Utils.Hash.hrefToHexChannelId(currentPad.href, pw);
|
||||
|
||||
nThen(function (w) {
|
||||
// If the pad is stored, update its data
|
||||
var _secret = Utils.Hash.getSecrets(parsed.type, parsed.hash, pw);
|
||||
var chan = _secret.channel;
|
||||
var editH = Utils.Hash.getEditHashFromKeys(_secret);
|
||||
var viewH = Utils.Hash.getViewHashFromKeys(_secret);
|
||||
var href = Utils.Hash.hashToHref(editH, parsed.type);
|
||||
var roHref = Utils.Hash.hashToHref(viewH, parsed.type);
|
||||
Cryptpad.setPadAttribute('password', password, w(), parsed.getUrl());
|
||||
Cryptpad.setPadAttribute('channel', chan, w(), parsed.getUrl());
|
||||
Cryptpad.setPadAttribute('href', href, w(), parsed.getUrl());
|
||||
Cryptpad.setPadAttribute('roHref', roHref, w(), parsed.getUrl());
|
||||
}).nThen(function () {
|
||||
// Get redirect URL
|
||||
var uHash = Utils.LocalStore.getUserHash();
|
||||
var uSecret = Utils.Hash.getSecrets('drive', uHash);
|
||||
var uKey = uSecret.keys.cryptKey;
|
||||
var url = Utils.Hash.getNewPadURL(currentPad.href, {
|
||||
pw: Crypto.encrypt(pw, uKey),
|
||||
f: 1
|
||||
});
|
||||
// redirect
|
||||
window.location.href = url;
|
||||
document.location.reload();
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
cb({
|
||||
error: e
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (cfg.messaging) {
|
||||
sframeChan.on('Q_CHAT_OPENPADCHAT', function (data, cb) {
|
||||
Cryptpad.universal.execCommand({
|
||||
|
|
|
@ -212,6 +212,7 @@ MessengerUI, Messages) {
|
|||
var $editUsersList = $('<div>', {'class': 'cp-toolbar-userlist-others'})
|
||||
.appendTo($editUsers);
|
||||
|
||||
var degradedLimit = Config.degradedLimit || 8;
|
||||
if (!online) {
|
||||
$('<em>').text(Messages.userlist_offline).appendTo($editUsersList);
|
||||
numberOfEditUsers = '?';
|
||||
|
@ -219,8 +220,7 @@ MessengerUI, Messages) {
|
|||
} else if (metadataMgr.isDegraded() === true) {
|
||||
numberOfEditUsers = Math.max(metadataMgr.getChannelMembers().length - 1, 0);
|
||||
numberOfViewUsers = '';
|
||||
Messages.toolbar_degraded = "Too many editors are present in the pad. The userlist has been disabled to improve performances"; // XXX
|
||||
$('<em>').text(Messages.toolbar_degraded).appendTo($editUsersList);
|
||||
$('<em>').text(Messages._getKey('toolbar_degraded', [degradedLimit])).appendTo($editUsersList);
|
||||
}
|
||||
|
||||
// Update the buttons
|
||||
|
@ -565,7 +565,12 @@ MessengerUI, Messages) {
|
|||
h('span.cp-button-name', Messages.accessButton)
|
||||
]));
|
||||
$accessBlock.click(function () {
|
||||
Common.getSframeChannel().event('EV_ACCESS_OPEN');
|
||||
var title = (config.title && config.title.getTitle && config.title.getTitle())
|
||||
|| (config.title && config.title.defaultName)
|
||||
|| "";
|
||||
Common.getSframeChannel().event('EV_ACCESS_OPEN', {
|
||||
title: title
|
||||
});
|
||||
});
|
||||
|
||||
toolbar.$bottomM.append($accessBlock);
|
||||
|
|
|
@ -44,6 +44,7 @@ define([
|
|||
meta.debugDrive = drive;
|
||||
};
|
||||
SFCommonO.start({
|
||||
noDrive: true,
|
||||
addData:addData
|
||||
});
|
||||
});
|
||||
|
|
|
@ -70,7 +70,8 @@ define([
|
|||
if ($uname.val()) {
|
||||
localStorage.login_user = $uname.val();
|
||||
}
|
||||
window.location.href = '/register/';
|
||||
var hash = (window.location.hash || '').replace(/\/login\//, '/register/');
|
||||
window.location.href = '/register/' + hash;
|
||||
});
|
||||
|
||||
Test(function (t) {
|
||||
|
|
|
@ -89,9 +89,10 @@ define([
|
|||
};
|
||||
|
||||
// Access modal
|
||||
create['access'] = function () {
|
||||
create['access'] = function (data) {
|
||||
require(['/common/inner/access.js'], function (Access) {
|
||||
Access.getAccessModal(common, {
|
||||
title: data.title,
|
||||
onClose: function () {
|
||||
hideIframe();
|
||||
}
|
||||
|
|
|
@ -101,6 +101,7 @@ define([
|
|||
origin: window.location.origin,
|
||||
pathname: window.location.pathname,
|
||||
feedbackAllowed: Utils.Feedback.state,
|
||||
channel: config.data.channel,
|
||||
hashes: config.data.hashes,
|
||||
password: config.data.password,
|
||||
propChannels: config.data.getPropChannels(),
|
||||
|
|
Loading…
Reference in New Issue