Merge branch 'newhash' into newhashNoConflict
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
;(function () { 'use strict';
|
;(function () { 'use strict';
|
||||||
const Crypto = require('crypto');
|
const Crypto = require('crypto');
|
||||||
|
const Nacl = require('tweetnacl');
|
||||||
const LogStore = require('./storage/LogStore');
|
const LogStore = require('./storage/LogStore');
|
||||||
|
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ const USE_FILE_BACKUP_STORAGE = true;
|
|||||||
|
|
||||||
|
|
||||||
let dropUser;
|
let dropUser;
|
||||||
|
let historyKeeperKeys = {};
|
||||||
|
|
||||||
const now = function () { return (new Date()).getTime(); };
|
const now = function () { return (new Date()).getTime(); };
|
||||||
|
|
||||||
@@ -25,6 +27,16 @@ const sendMsg = function (ctx, user, msg) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const storeMessage = function (ctx, channel, msg) {
|
||||||
|
ctx.store.message(channel.id, msg, function (err) {
|
||||||
|
if (err && typeof(err) !== 'function') {
|
||||||
|
// ignore functions because older datastores
|
||||||
|
// might pass waitFors into the callback
|
||||||
|
console.log("Error writing message: " + err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const sendChannelMessage = function (ctx, channel, msgStruct) {
|
const sendChannelMessage = function (ctx, channel, msgStruct) {
|
||||||
msgStruct.unshift(0);
|
msgStruct.unshift(0);
|
||||||
channel.forEach(function (user) {
|
channel.forEach(function (user) {
|
||||||
@@ -33,13 +45,17 @@ const sendChannelMessage = function (ctx, channel, msgStruct) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (USE_HISTORY_KEEPER && msgStruct[2] === 'MSG') {
|
if (USE_HISTORY_KEEPER && msgStruct[2] === 'MSG') {
|
||||||
ctx.store.message(channel.id, JSON.stringify(msgStruct), function (err) {
|
if (historyKeeperKeys[channel.id]) {
|
||||||
if (err && typeof(err) !== 'function') {
|
let signedMsg = msgStruct[4].replace(/^cp\|/, '');
|
||||||
// ignore functions because older datastores
|
signedMsg = Nacl.util.decodeBase64(signedMsg);
|
||||||
// might pass waitFors into the callback
|
let validateKey = Nacl.util.decodeBase64(historyKeeperKeys[channel.id]);
|
||||||
console.log("Error writing message: " + err);
|
let validated = Nacl.sign.open(signedMsg, validateKey);
|
||||||
|
if (!validated) {
|
||||||
|
console.log("Signed message rejected");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
storeMessage(ctx, channel, JSON.stringify(msgStruct));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -68,6 +84,7 @@ dropUser = function (ctx, user) {
|
|||||||
if (chan.length === 0) {
|
if (chan.length === 0) {
|
||||||
console.log("Removing empty channel ["+chanName+"]");
|
console.log("Removing empty channel ["+chanName+"]");
|
||||||
delete ctx.channels[chanName];
|
delete ctx.channels[chanName];
|
||||||
|
delete historyKeeperKeys[chanName];
|
||||||
|
|
||||||
/* Call removeChannel if it is a function and channel removal is
|
/* Call removeChannel if it is a function and channel removal is
|
||||||
set to true in the config file */
|
set to true in the config file */
|
||||||
@@ -94,8 +111,15 @@ dropUser = function (ctx, user) {
|
|||||||
|
|
||||||
const getHistory = function (ctx, channelName, handler, cb) {
|
const getHistory = function (ctx, channelName, handler, cb) {
|
||||||
var messageBuf = [];
|
var messageBuf = [];
|
||||||
|
var messageKey;
|
||||||
ctx.store.getMessages(channelName, function (msgStr) {
|
ctx.store.getMessages(channelName, function (msgStr) {
|
||||||
messageBuf.push(JSON.parse(msgStr));
|
var parsed = JSON.parse(msgStr);
|
||||||
|
if (parsed.validateKey) {
|
||||||
|
historyKeeperKeys[channelName] = parsed.validateKey;
|
||||||
|
handler(parsed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
messageBuf.push(parsed);
|
||||||
}, function (err) {
|
}, function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.log("Error getting messages " + err.stack);
|
console.log("Error getting messages " + err.stack);
|
||||||
@@ -120,7 +144,7 @@ const getHistory = function (ctx, channelName, handler, cb) {
|
|||||||
// no checkpoints.
|
// no checkpoints.
|
||||||
for (var x = msgBuff2.pop(); x; x = msgBuff2.pop()) { handler(x); }
|
for (var x = msgBuff2.pop(); x; x = msgBuff2.pop()) { handler(x); }
|
||||||
}
|
}
|
||||||
cb();
|
cb(messageBuf);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -166,7 +190,13 @@ const handleMessage = function (ctx, user, msg) {
|
|||||||
sendMsg(ctx, user, [seq, 'ACK']);
|
sendMsg(ctx, user, [seq, 'ACK']);
|
||||||
getHistory(ctx, parsed[1], function (msg) {
|
getHistory(ctx, parsed[1], function (msg) {
|
||||||
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(msg)]);
|
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(msg)]);
|
||||||
}, function () {
|
}, function (messages) {
|
||||||
|
// parsed[2] is a validation key if it exists
|
||||||
|
if (messages.length === 0 && parsed[2] && !historyKeeperKeys[parsed[1]]) {
|
||||||
|
var key = {channel: parsed[1], validateKey: parsed[2]};
|
||||||
|
storeMessage(ctx, ctx.channels[parsed[1]], JSON.stringify(key));
|
||||||
|
historyKeeperKeys[parsed[1]] = parsed[2];
|
||||||
|
}
|
||||||
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, 0]);
|
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, 0]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,9 +106,17 @@ define([
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var readOnly = false;
|
||||||
|
if (pad.href.indexOf('#') !== -1) {
|
||||||
|
var modeArray = pad.href.split('#')[1].split('/');
|
||||||
|
if (modeArray.length >= 3 && modeArray[2] === 'view') {
|
||||||
|
readOnly = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var readOnlyText = readOnly ? '(' + Messages.readonly + ') ' : '';
|
||||||
$row
|
$row
|
||||||
.append($('<td>').text(name))
|
.append($('<td>').text(name))
|
||||||
.append($('<td>').append($('<a>', {
|
.append($('<td>').append(readOnlyText).append($('<a>', {
|
||||||
href: pad.href,
|
href: pad.href,
|
||||||
title: pad.title,
|
title: pad.title,
|
||||||
}).text(shortTitle)))
|
}).text(shortTitle)))
|
||||||
@@ -134,6 +142,9 @@ define([
|
|||||||
|
|
||||||
if (hasRecent) {
|
if (hasRecent) {
|
||||||
$('table').attr('style', '');
|
$('table').attr('style', '');
|
||||||
|
// Race condition here, this is triggered before the localization in HTML
|
||||||
|
// so we have to remove the data-localization attr
|
||||||
|
$tryit.removeAttr('data-localization');
|
||||||
$tryit.text(Messages.recentPads);
|
$tryit.text(Messages.recentPads);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -83,3 +83,8 @@
|
|||||||
.cryptpad-spinner {
|
.cryptpad-spinner {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
.cryptpad-readonly {
|
||||||
|
margin-right: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
@@ -3,6 +3,11 @@ define(function () {
|
|||||||
|
|
||||||
out.main_title = "Cryptpad: Editeur collaboratif en temps réel, zero knowledge";
|
out.main_title = "Cryptpad: Editeur collaboratif en temps réel, zero knowledge";
|
||||||
|
|
||||||
|
out.type = {};
|
||||||
|
out.type.pad = 'Pad';
|
||||||
|
out.type.code = 'Code';
|
||||||
|
out.type.poll = 'Sondage';
|
||||||
|
out.type.slide = 'Présentation';
|
||||||
|
|
||||||
out.errorBox_errorType_disconnected = 'Connexion perdue';
|
out.errorBox_errorType_disconnected = 'Connexion perdue';
|
||||||
out.errorBox_errorExplanation_disconnected = [
|
out.errorBox_errorExplanation_disconnected = [
|
||||||
@@ -20,6 +25,12 @@ define(function () {
|
|||||||
out.synchronizing = 'Synchronisation';
|
out.synchronizing = 'Synchronisation';
|
||||||
out.reconnecting = 'Reconnexion...';
|
out.reconnecting = 'Reconnexion...';
|
||||||
out.lag = 'Latence';
|
out.lag = 'Latence';
|
||||||
|
out.readonly = 'Lecture seule';
|
||||||
|
out.nobodyIsEditing = "Personne n'édite le document";
|
||||||
|
out.onePersonIsEditing = 'Une personne édite le document';
|
||||||
|
out.peopleAreEditing = '{0} personnes éditent le document';
|
||||||
|
out.oneViewer = '1 lecteur';
|
||||||
|
out.viewers = '{0} lecteurs';
|
||||||
|
|
||||||
out.importButton = 'IMPORTER';
|
out.importButton = 'IMPORTER';
|
||||||
out.importButtonTitle = 'Importer un document depuis un fichier local';
|
out.importButtonTitle = 'Importer un document depuis un fichier local';
|
||||||
@@ -54,6 +65,10 @@ define(function () {
|
|||||||
|
|
||||||
out.commitButton = 'VALIDER';
|
out.commitButton = 'VALIDER';
|
||||||
|
|
||||||
|
out.getViewButton = 'LECTURE SEULE';
|
||||||
|
out.getViewButtonTitle = "Obtenir l'adresse d'accès à ce document en lecture seule";
|
||||||
|
out.readonlyUrl = 'URL de lecture seule';
|
||||||
|
|
||||||
out.disconnectAlert = 'Perte de la connexion au réseau !';
|
out.disconnectAlert = 'Perte de la connexion au réseau !';
|
||||||
|
|
||||||
out.tryIt = 'Essayez-le !';
|
out.tryIt = 'Essayez-le !';
|
||||||
@@ -65,12 +80,6 @@ define(function () {
|
|||||||
out.loginText = '<p>Votre nom d\'utilisateur et votre mot de passe sont utilisés pour générer une clé unique qui reste inconnue de notre serveur.</p>\n' +
|
out.loginText = '<p>Votre nom d\'utilisateur et votre mot de passe sont utilisés pour générer une clé unique qui reste inconnue de notre serveur.</p>\n' +
|
||||||
'<p>Faites attention de ne pas oublier vos identifiants puisqu\'ils seront impossible à récupérer.</p>';
|
'<p>Faites attention de ne pas oublier vos identifiants puisqu\'ils seront impossible à récupérer.</p>';
|
||||||
|
|
||||||
out.type = {};
|
|
||||||
out.type.pad = 'Pad';
|
|
||||||
out.type.code = 'Code';
|
|
||||||
out.type.poll = 'Sondage';
|
|
||||||
out.type.slide = 'Présentation';
|
|
||||||
|
|
||||||
out.forget = "Oublier";
|
out.forget = "Oublier";
|
||||||
|
|
||||||
// Polls
|
// Polls
|
||||||
|
|||||||
@@ -3,6 +3,11 @@ define(function () {
|
|||||||
|
|
||||||
out.main_title = "Cryptpad: Zero Knowledge, Collaborative Real Time Editing";
|
out.main_title = "Cryptpad: Zero Knowledge, Collaborative Real Time Editing";
|
||||||
|
|
||||||
|
out.type = {};
|
||||||
|
out.type.pad = 'Pad';
|
||||||
|
out.type.code = 'Code';
|
||||||
|
out.type.poll = 'Poll';
|
||||||
|
out.type.slide = 'Presentation';
|
||||||
|
|
||||||
out.errorBox_errorType_disconnected = 'Connection Lost';
|
out.errorBox_errorType_disconnected = 'Connection Lost';
|
||||||
out.errorBox_errorExplanation_disconnected = [
|
out.errorBox_errorExplanation_disconnected = [
|
||||||
@@ -20,6 +25,12 @@ define(function () {
|
|||||||
out.synchronizing = 'Synchronizing';
|
out.synchronizing = 'Synchronizing';
|
||||||
out.reconnecting = 'Reconnecting...';
|
out.reconnecting = 'Reconnecting...';
|
||||||
out.lag = 'Lag';
|
out.lag = 'Lag';
|
||||||
|
out.readonly = 'Read only';
|
||||||
|
out.nobodyIsEditing = 'Nobody is editing';
|
||||||
|
out.onePersonIsEditing = 'One person is editing';
|
||||||
|
out.peopleAreEditing = '{0} people are editing';
|
||||||
|
out.oneViewer = '1 viewer';
|
||||||
|
out.viewers = '{0} viewers';
|
||||||
|
|
||||||
out.importButton = 'IMPORT';
|
out.importButton = 'IMPORT';
|
||||||
out.importButtonTitle = 'Import a document from a local file';
|
out.importButtonTitle = 'Import a document from a local file';
|
||||||
@@ -54,6 +65,10 @@ define(function () {
|
|||||||
|
|
||||||
out.commitButton = 'COMMIT';
|
out.commitButton = 'COMMIT';
|
||||||
|
|
||||||
|
out.getViewButton = 'READ-ONLY URL';
|
||||||
|
out.getViewButtonTitle = 'Get the read-only URL for this document';
|
||||||
|
out.readonlyUrl = 'Read only URL';
|
||||||
|
|
||||||
out.disconnectAlert = 'Network connection lost!';
|
out.disconnectAlert = 'Network connection lost!';
|
||||||
|
|
||||||
out.tryIt = 'Try it out!';
|
out.tryIt = 'Try it out!';
|
||||||
@@ -65,13 +80,6 @@ define(function () {
|
|||||||
out.loginText = '<p>Your username and password are used to generate a unique key which is never known by our server.</p>\n' +
|
out.loginText = '<p>Your username and password are used to generate a unique key which is never known by our server.</p>\n' +
|
||||||
'<p>Be careful not to forget your credentials, as they are impossible to recover</p>';
|
'<p>Be careful not to forget your credentials, as they are impossible to recover</p>';
|
||||||
|
|
||||||
// TODO : move at the beginning
|
|
||||||
out.type = {};
|
|
||||||
out.type.pad = 'Pad';
|
|
||||||
out.type.code = 'Code';
|
|
||||||
out.type.poll = 'Poll';
|
|
||||||
out.type.slide = 'Presentation';
|
|
||||||
|
|
||||||
out.forget = "Forget";
|
out.forget = "Forget";
|
||||||
|
|
||||||
// Polls
|
// Polls
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "~4.10.1",
|
"express": "~4.10.1",
|
||||||
"ws": "^1.0.1",
|
"ws": "^1.0.1",
|
||||||
"nthen": "~0.1.0"
|
"nthen": "~0.1.0",
|
||||||
|
"tweetnacl": "~0.12.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jshint": "~2.9.1",
|
"jshint": "~2.9.1",
|
||||||
|
|||||||
150
www/code/main.js
150
www/code/main.js
@@ -38,6 +38,10 @@ define([
|
|||||||
var toolbar;
|
var toolbar;
|
||||||
|
|
||||||
var secret = Cryptpad.getSecrets();
|
var secret = Cryptpad.getSecrets();
|
||||||
|
var readOnly = secret.keys && !secret.keys.editKeyStr;
|
||||||
|
if (!secret.keys) {
|
||||||
|
secret.keys = secret.key;
|
||||||
|
}
|
||||||
|
|
||||||
var andThen = function (CMeditor) {
|
var andThen = function (CMeditor) {
|
||||||
var CodeMirror = module.CodeMirror = CMeditor;
|
var CodeMirror = module.CodeMirror = CMeditor;
|
||||||
@@ -105,6 +109,7 @@ define([
|
|||||||
}());
|
}());
|
||||||
|
|
||||||
var setEditable = module.setEditable = function (bool) {
|
var setEditable = module.setEditable = function (bool) {
|
||||||
|
if (readOnly && bool) { return; }
|
||||||
editor.setOption('readOnly', !bool);
|
editor.setOption('readOnly', !bool);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -131,7 +136,10 @@ define([
|
|||||||
initialState: '{}',
|
initialState: '{}',
|
||||||
websocketURL: Config.websocketURL,
|
websocketURL: Config.websocketURL,
|
||||||
channel: secret.channel,
|
channel: secret.channel,
|
||||||
crypto: Crypto.createEncryptor(secret.key),
|
// our public key
|
||||||
|
validateKey: secret.keys.validateKey || undefined,
|
||||||
|
readOnly: readOnly,
|
||||||
|
crypto: Crypto.createEncryptor(secret.keys),
|
||||||
setMyID: setMyID,
|
setMyID: setMyID,
|
||||||
transformFunction: JsonOT.validate
|
transformFunction: JsonOT.validate
|
||||||
};
|
};
|
||||||
@@ -142,6 +150,7 @@ define([
|
|||||||
|
|
||||||
var onLocal = config.onLocal = function () {
|
var onLocal = config.onLocal = function () {
|
||||||
if (initializing) { return; }
|
if (initializing) { return; }
|
||||||
|
if (readOnly) { return; }
|
||||||
|
|
||||||
editor.save();
|
editor.save();
|
||||||
var textValue = canonicalize($textarea.val());
|
var textValue = canonicalize($textarea.val());
|
||||||
@@ -275,12 +284,21 @@ define([
|
|||||||
var config = {
|
var config = {
|
||||||
userData: userList,
|
userData: userList,
|
||||||
changeNameID: Toolbar.constants.changeName,
|
changeNameID: Toolbar.constants.changeName,
|
||||||
|
readOnly: readOnly
|
||||||
};
|
};
|
||||||
|
if (readOnly) {delete config.changeNameID; }
|
||||||
toolbar = module.toolbar = Toolbar.create($bar, info.myID, info.realtime, info.getLag, info.userList, config);
|
toolbar = module.toolbar = Toolbar.create($bar, info.myID, info.realtime, info.getLag, info.userList, config);
|
||||||
createChangeName(Toolbar.constants.changeName, $bar);
|
if (!readOnly) { createChangeName(Toolbar.constants.changeName, $bar); }
|
||||||
|
|
||||||
var $rightside = $bar.find('.' + Toolbar.constants.rightside);
|
var $rightside = $bar.find('.' + Toolbar.constants.rightside);
|
||||||
|
|
||||||
|
var editHash;
|
||||||
|
var viewHash = Cryptpad.getViewHashFromKeys(info.channel, secret.keys);
|
||||||
|
|
||||||
|
if (!readOnly) {
|
||||||
|
editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
|
||||||
|
}
|
||||||
|
|
||||||
/* add an export button */
|
/* add an export button */
|
||||||
var $export = $('<button>', {
|
var $export = $('<button>', {
|
||||||
title: Messages.exportButtonTitle,
|
title: Messages.exportButtonTitle,
|
||||||
@@ -290,38 +308,40 @@ define([
|
|||||||
.click(exportText);
|
.click(exportText);
|
||||||
$rightside.append($export);
|
$rightside.append($export);
|
||||||
|
|
||||||
/* add an import button */
|
if (!readOnly) {
|
||||||
var $import = $('<button>',{
|
/* add an import button */
|
||||||
title: Messages.importButtonTitle
|
var $import = $('<button>',{
|
||||||
})
|
title: Messages.importButtonTitle
|
||||||
.text(Messages.importButton)
|
})
|
||||||
.addClass('rightside-button')
|
.text(Messages.importButton)
|
||||||
.click(Cryptpad.importContent('text/plain', function (content, file) {
|
.addClass('rightside-button')
|
||||||
var mode;
|
.click(Cryptpad.importContent('text/plain', function (content, file) {
|
||||||
var mime = CodeMirror.findModeByMIME(file.type);
|
var mode;
|
||||||
|
var mime = CodeMirror.findModeByMIME(file.type);
|
||||||
|
|
||||||
if (!mime) {
|
if (!mime) {
|
||||||
var ext = /.+\.([^.]+)$/.exec(file.name);
|
var ext = /.+\.([^.]+)$/.exec(file.name);
|
||||||
if (ext[1]) {
|
if (ext[1]) {
|
||||||
mode = CodeMirror.findModeByExtension(ext[1]);
|
mode = CodeMirror.findModeByExtension(ext[1]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mode = mime && mime.mode || null;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
mode = mime && mime.mode || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode && Modes.list.some(function (o) { return o.mode === mode; })) {
|
if (mode && Modes.list.some(function (o) { return o.mode === mode; })) {
|
||||||
setMode(mode);
|
setMode(mode);
|
||||||
$bar.find('#language-mode').val(mode);
|
$bar.find('#language-mode').val(mode);
|
||||||
} else {
|
} else {
|
||||||
console.log("Couldn't find a suitable highlighting mode: %s", mode);
|
console.log("Couldn't find a suitable highlighting mode: %s", mode);
|
||||||
setMode('text');
|
setMode('text');
|
||||||
$bar.find('#language-mode').val('text');
|
$bar.find('#language-mode').val('text');
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.setValue(content);
|
editor.setValue(content);
|
||||||
onLocal();
|
onLocal();
|
||||||
}));
|
}));
|
||||||
$rightside.append($import);
|
$rightside.append($import);
|
||||||
|
}
|
||||||
|
|
||||||
/* add a rename button */
|
/* add a rename button */
|
||||||
var $setTitle = $('<button>', {
|
var $setTitle = $('<button>', {
|
||||||
@@ -387,6 +407,21 @@ define([
|
|||||||
});
|
});
|
||||||
$rightside.append($forgetPad);
|
$rightside.append($forgetPad);
|
||||||
|
|
||||||
|
if (!readOnly && viewHash) {
|
||||||
|
/* add a 'links' button */
|
||||||
|
var $links = $('<button>', {
|
||||||
|
title: Messages.getViewButtonTitle
|
||||||
|
})
|
||||||
|
.text(Messages.getViewButton)
|
||||||
|
.addClass('rightside-button')
|
||||||
|
.click(function () {
|
||||||
|
var baseUrl = window.location.origin + window.location.pathname + '#';
|
||||||
|
var content = '<b>' + Messages.readonlyUrl + '</b><br><a>' + baseUrl + viewHash + '</a><br>';
|
||||||
|
Cryptpad.alert(content);
|
||||||
|
});
|
||||||
|
$rightside.append($links);
|
||||||
|
}
|
||||||
|
|
||||||
var configureLanguage = function (cb) {
|
var configureLanguage = function (cb) {
|
||||||
// FIXME this is async so make it happen as early as possible
|
// FIXME this is async so make it happen as early as possible
|
||||||
|
|
||||||
@@ -441,11 +476,20 @@ define([
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
configureLanguage(function () {
|
if (!readOnly) {
|
||||||
|
configureLanguage(function () {
|
||||||
|
configureTheme();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
configureTheme();
|
configureTheme();
|
||||||
});
|
}
|
||||||
|
|
||||||
|
// set the hash
|
||||||
|
if (!readOnly) {
|
||||||
|
window.location.hash = editHash;
|
||||||
|
}
|
||||||
|
|
||||||
window.location.hash = Cryptpad.getHashFromKeys(info.channel, secret.key);
|
|
||||||
Cryptpad.getPadTitle(function (err, title) {
|
Cryptpad.getPadTitle(function (err, title) {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.log("Unable to get pad title");
|
console.log("Unable to get pad title");
|
||||||
@@ -532,7 +576,7 @@ define([
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the user list (metadata) from the hyperjson
|
// Update the user list (metadata) from the hyperjson
|
||||||
//updateUserList(shjson);
|
updateUserList(userDoc);
|
||||||
|
|
||||||
editor.setValue(newDoc || Messages.codeInitialState);
|
editor.setValue(newDoc || Messages.codeInitialState);
|
||||||
|
|
||||||
@@ -554,9 +598,17 @@ define([
|
|||||||
console.error(err);
|
console.error(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Update the toolbar list:
|
||||||
|
// Add the current user in the metadata if he has edit rights
|
||||||
|
if (readOnly) { return; }
|
||||||
|
myData[myID] = {
|
||||||
|
name: ""
|
||||||
|
};
|
||||||
|
addToUserList(myData);
|
||||||
if (typeof(lastName) === 'string' && lastName.length) {
|
if (typeof(lastName) === 'string' && lastName.length) {
|
||||||
setName(lastName);
|
setName(lastName);
|
||||||
}
|
}
|
||||||
|
onLocal();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -627,20 +679,22 @@ define([
|
|||||||
|
|
||||||
editor.scrollTo(scroll.left, scroll.top);
|
editor.scrollTo(scroll.left, scroll.top);
|
||||||
|
|
||||||
var localDoc = canonicalize($textarea.val());
|
if (!readOnly) {
|
||||||
var hjson2 = {
|
var localDoc = canonicalize($textarea.val());
|
||||||
content: localDoc,
|
var hjson2 = {
|
||||||
metadata: {
|
content: localDoc,
|
||||||
users: userList,
|
metadata: {
|
||||||
title: document.title
|
users: userList,
|
||||||
},
|
title: document.title
|
||||||
highlightMode: highlightMode,
|
},
|
||||||
};
|
highlightMode: highlightMode,
|
||||||
var shjson2 = stringify(hjson2);
|
};
|
||||||
if (shjson2 !== shjson) {
|
var shjson2 = stringify(hjson2);
|
||||||
console.error("shjson2 !== shjson");
|
if (shjson2 !== shjson) {
|
||||||
TextPatcher.log(shjson, TextPatcher.diff(shjson, shjson2));
|
console.error("shjson2 !== shjson");
|
||||||
module.patchText(shjson2);
|
TextPatcher.log(shjson, TextPatcher.diff(shjson, shjson2));
|
||||||
|
module.patchText(shjson2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notify();
|
notify();
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ define([
|
|||||||
|
|
||||||
var base64ToHex = common.base64ToHex = function (b64String) {
|
var base64ToHex = common.base64ToHex = function (b64String) {
|
||||||
var hexArray = [];
|
var hexArray = [];
|
||||||
atob(b64String.replace(/-/g, '/') + "==").split("").forEach(function(e){
|
atob(b64String.replace(/-/g, '/')).split("").forEach(function(e){
|
||||||
var h = e.charCodeAt(0).toString(16);
|
var h = e.charCodeAt(0).toString(16);
|
||||||
if (h.length === 1) { h = "0"+h; }
|
if (h.length === 1) { h = "0"+h; }
|
||||||
hexArray.push(h);
|
hexArray.push(h);
|
||||||
@@ -136,18 +136,31 @@ define([
|
|||||||
return hexArray.join("");
|
return hexArray.join("");
|
||||||
};
|
};
|
||||||
|
|
||||||
var getHashFromKeys = common.getHashFromKeys = function (chanKey, cryptKey) {
|
|
||||||
return '/1/' + hexToBase64(chanKey) + '/' + cryptKey.replace(/\//g, '-');
|
var getEditHashFromKeys = common.getEditHashFromKeys = function (chanKey, keys) {
|
||||||
|
if (typeof keys === 'string') {
|
||||||
|
return chanKey + keys;
|
||||||
|
}
|
||||||
|
return '/1/edit/' + hexToBase64(chanKey) + '/' + Crypto.b64RemoveSlashes(keys.editKeyStr);
|
||||||
};
|
};
|
||||||
|
var getViewHashFromKeys = common.getViewHashFromKeys = function (chanKey, keys) {
|
||||||
|
if (typeof keys === 'string') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return '/1/view/' + hexToBase64(chanKey) + '/' + Crypto.b64RemoveSlashes(keys.viewKeyStr);
|
||||||
|
};
|
||||||
|
var getHashFromKeys = common.getHashFromKeys = getEditHashFromKeys;
|
||||||
|
|
||||||
var getSecrets = common.getSecrets = function () {
|
var getSecrets = common.getSecrets = function () {
|
||||||
var secret = {};
|
var secret = {};
|
||||||
if (!/#/.test(window.location.href)) {
|
if (!/#/.test(window.location.href)) {
|
||||||
secret.key = Crypto.genKey();
|
secret.keys = Crypto.createEditCryptor();
|
||||||
|
secret.key = Crypto.createEditCryptor().editKeyStr;
|
||||||
} else {
|
} else {
|
||||||
var hash = window.location.hash.slice(1);
|
var hash = window.location.hash.slice(1);
|
||||||
if (hash.length === 0) {
|
if (hash.length === 0) {
|
||||||
secret.key = Crypto.genKey();
|
secret.keys = Crypto.createEditCryptor();
|
||||||
|
secret.key = Crypto.createEditCryptor().editKeyStr;
|
||||||
return secret;
|
return secret;
|
||||||
}
|
}
|
||||||
common.redirect(hash);
|
common.redirect(hash);
|
||||||
@@ -166,15 +179,35 @@ define([
|
|||||||
throw new Error("Unable to parse the key");
|
throw new Error("Unable to parse the key");
|
||||||
}
|
}
|
||||||
var version = hashArray[1];
|
var version = hashArray[1];
|
||||||
if (version === "1") {
|
/*if (version === "1") {
|
||||||
secret.channel = base64ToHex(hashArray[2]);
|
secret.channel = base64ToHex(hashArray[2]);
|
||||||
secret.key = hashArray[3].replace(/-/g, '/'); //TODO replace / by -
|
secret.key = hashArray[3].replace(/-/g, '/');
|
||||||
if (secret.channel.length !== 32 || secret.key.length !== 24) {
|
if (secret.channel.length !== 32 || secret.key.length !== 24) {
|
||||||
common.alert("The channel key and/or the encryption key is invalid");
|
common.alert("The channel key and/or the encryption key is invalid");
|
||||||
console.log("Channel key length : " + secret.channel.length + " != 32");
|
|
||||||
console.log("Encryption key length : " + secret.key.length + " != 24");
|
|
||||||
throw new Error("The channel key and/or the encryption key is invalid");
|
throw new Error("The channel key and/or the encryption key is invalid");
|
||||||
}
|
}
|
||||||
|
}*/
|
||||||
|
if (version === "1") {
|
||||||
|
var mode = hashArray[2];
|
||||||
|
if (mode === 'edit') {
|
||||||
|
secret.channel = base64ToHex(hashArray[3]);
|
||||||
|
var keys = Crypto.createEditCryptor(hashArray[4].replace(/-/g, '/'));
|
||||||
|
secret.keys = keys;
|
||||||
|
secret.key = keys.editKeyStr;
|
||||||
|
if (secret.channel.length !== 32 || secret.key.length !== 24) {
|
||||||
|
common.alert("The channel key and/or the encryption key is invalid");
|
||||||
|
throw new Error("The channel key and/or the encryption key is invalid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mode === 'view') {
|
||||||
|
secret.channel = base64ToHex(hashArray[3]);
|
||||||
|
var keys = Crypto.createViewCryptor(hashArray[4].replace(/-/g, '/'));
|
||||||
|
secret.keys = keys;
|
||||||
|
if (secret.channel.length !== 32) {
|
||||||
|
common.alert("The channel key is invalid");
|
||||||
|
throw new Error("The channel key is invalid");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,19 +135,50 @@ define([
|
|||||||
return $span[0];
|
return $span[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
var updateUserList = function (myUserName, listElement, userList, userData) {
|
var arrayIntersect = function(a, b) {
|
||||||
|
return $.grep(a, function(i) {
|
||||||
|
return $.inArray(i, b) > -1;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var getViewers = function (n) {
|
||||||
|
if (!n || !parseInt(n) || n === 0) { return ''; }
|
||||||
|
if (n === 1) { return '; + ' + Messages.oneViewer; }
|
||||||
|
return '; + ' + Messages._getKey('viewers', [n]);
|
||||||
|
}
|
||||||
|
var updateUserList = function (myUserName, listElement, userList, userData, readOnly) {
|
||||||
var meIdx = userList.indexOf(myUserName);
|
var meIdx = userList.indexOf(myUserName);
|
||||||
if (meIdx === -1) {
|
if (meIdx === -1) {
|
||||||
listElement.textContent = Messages.synchronizing;
|
listElement.textContent = Messages.synchronizing;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (userList.length === 1) {
|
var numberOfUsers = userList.length;
|
||||||
listElement.innerHTML = Messages.editingAlone;
|
userList = readOnly === -1 ? userList : arrayIntersect(userList, Object.keys(userData));
|
||||||
} else if (userList.length === 2) {
|
var innerHTML;
|
||||||
listElement.innerHTML = Messages.editingWithOneOtherPerson + getOtherUsers(myUserName, userList, userData);
|
var numberOfViewUsers = numberOfUsers - userList.length;
|
||||||
} else {
|
if (readOnly === 1) {
|
||||||
listElement.innerHTML = Messages.editingWith + ' ' + (userList.length - 1) + ' ' + Messages.otherPeople + getOtherUsers(myUserName, userList, userData);
|
innerHTML = '<span class="cryptpad-readonly">' + Messages.readonly + '</span>';
|
||||||
|
if (userList.length === 0) {
|
||||||
|
innerHTML += Messages.nobodyIsEditing;
|
||||||
|
} else if (userList.length === 1) {
|
||||||
|
innerHTML += Messages.onePersonIsEditing + getOtherUsers(myUserName, userList, userData);
|
||||||
|
} else {
|
||||||
|
innerHTML += Messages._getKey('peopleAreEditing', [userList.length]) + getOtherUsers(myUserName, userList, userData);
|
||||||
|
}
|
||||||
|
// Remove the current user
|
||||||
|
numberOfViewUsers--;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
if (userList.length === 1) {
|
||||||
|
innerHTML = Messages.editingAlone;
|
||||||
|
} else if (userList.length === 2) {
|
||||||
|
innerHTML = Messages.editingWithOneOtherPerson + getOtherUsers(myUserName, userList, userData);
|
||||||
|
} else {
|
||||||
|
innerHTML = Messages.editingWith + ' ' + (userList.length - 1) + ' ' + Messages.otherPeople + getOtherUsers(myUserName, userList, userData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
innerHTML += getViewers(numberOfViewUsers);
|
||||||
|
listElement.innerHTML = innerHTML;
|
||||||
};
|
};
|
||||||
|
|
||||||
var createLagElement = function ($container) {
|
var createLagElement = function ($container) {
|
||||||
@@ -187,6 +218,8 @@ define([
|
|||||||
var changeNameID = config.changeNameID;
|
var changeNameID = config.changeNameID;
|
||||||
var saveContentID = config.saveContentID || config.exportContentID;
|
var saveContentID = config.saveContentID || config.exportContentID;
|
||||||
var loadContentID = config.loadContentID || config.importContentID;
|
var loadContentID = config.loadContentID || config.importContentID;
|
||||||
|
// readOnly = 1 (readOnly enabled), 0 (disabled), -1 (old pad without readOnly mode)
|
||||||
|
var readOnly = (typeof config.readOnly !== "undefined") ? (config.readOnly ? 1 : 0) : -1;
|
||||||
var saveElement;
|
var saveElement;
|
||||||
var loadElement;
|
var loadElement;
|
||||||
|
|
||||||
@@ -205,7 +238,7 @@ define([
|
|||||||
if(newUserData) { // Someone has changed his name/color
|
if(newUserData) { // Someone has changed his name/color
|
||||||
userData = newUserData;
|
userData = newUserData;
|
||||||
}
|
}
|
||||||
updateUserList(myUserName, userListElement, users, userData);
|
updateUserList(myUserName, userListElement, users, userData, readOnly);
|
||||||
};
|
};
|
||||||
|
|
||||||
var ks = function () {
|
var ks = function () {
|
||||||
|
|||||||
176
www/pad/main.js
176
www/pad/main.js
@@ -65,6 +65,10 @@ define([
|
|||||||
|
|
||||||
var andThen = function (Ckeditor) {
|
var andThen = function (Ckeditor) {
|
||||||
var secret = Cryptpad.getSecrets();
|
var secret = Cryptpad.getSecrets();
|
||||||
|
var readOnly = secret.keys && !secret.keys.editKeyStr;
|
||||||
|
if (!secret.keys) {
|
||||||
|
secret.keys = secret.key;
|
||||||
|
}
|
||||||
|
|
||||||
var fixThings = false;
|
var fixThings = false;
|
||||||
|
|
||||||
@@ -82,6 +86,11 @@ define([
|
|||||||
|
|
||||||
editor.on('instanceReady', function (Ckeditor) {
|
editor.on('instanceReady', function (Ckeditor) {
|
||||||
|
|
||||||
|
if (readOnly) {
|
||||||
|
$('#pad-iframe')[0].contentWindow.$('#cke_1_toolbox > .cke_toolbar').hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* add a class to the magicline plugin so we can pick it out more easily */
|
/* add a class to the magicline plugin so we can pick it out more easily */
|
||||||
|
|
||||||
var ml = $('iframe')[0].contentWindow.CKEDITOR.instances.editor1.plugins.magicline
|
var ml = $('iframe')[0].contentWindow.CKEDITOR.instances.editor1.plugins.magicline
|
||||||
@@ -115,8 +124,9 @@ define([
|
|||||||
} else {
|
} else {
|
||||||
module.spinner.show();
|
module.spinner.show();
|
||||||
}
|
}
|
||||||
|
if (!readOnly || !bool) {
|
||||||
inner.setAttribute('contenteditable', bool);
|
inner.setAttribute('contenteditable', bool);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// don't let the user edit until the pad is ready
|
// don't let the user edit until the pad is ready
|
||||||
@@ -192,6 +202,12 @@ define([
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do not change the contenteditable value in view mode
|
||||||
|
if (readOnly && info.node && info.node.tagName === 'BODY' &&
|
||||||
|
info.diff.action === 'modifyAttribute' && info.diff.name === 'contenteditable') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// no use trying to recover the cursor if it doesn't exist
|
// no use trying to recover the cursor if it doesn't exist
|
||||||
if (!cursor.exists()) { return; }
|
if (!cursor.exists()) { return; }
|
||||||
|
|
||||||
@@ -296,7 +312,9 @@ define([
|
|||||||
var applyHjson = function (shjson) {
|
var applyHjson = function (shjson) {
|
||||||
var userDocStateDom = hjsonToDom(JSON.parse(shjson));
|
var userDocStateDom = hjsonToDom(JSON.parse(shjson));
|
||||||
|
|
||||||
userDocStateDom.setAttribute("contenteditable", "true"); // lol wtf
|
if (!readOnly) {
|
||||||
|
userDocStateDom.setAttribute("contenteditable", "true"); // lol wtf
|
||||||
|
}
|
||||||
var patch = (DD).diff(inner, userDocStateDom);
|
var patch = (DD).diff(inner, userDocStateDom);
|
||||||
(DD).apply(inner, patch);
|
(DD).apply(inner, patch);
|
||||||
};
|
};
|
||||||
@@ -322,14 +340,15 @@ define([
|
|||||||
// the channel we will communicate over
|
// the channel we will communicate over
|
||||||
channel: secret.channel,
|
channel: secret.channel,
|
||||||
|
|
||||||
// our encryption key
|
// our public key
|
||||||
cryptKey: secret.key,
|
validateKey: secret.keys.validateKey || undefined,
|
||||||
|
readOnly: readOnly,
|
||||||
|
|
||||||
// method which allows us to get the id of the user
|
// method which allows us to get the id of the user
|
||||||
setMyID: setMyID,
|
setMyID: setMyID,
|
||||||
|
|
||||||
// Pass in encrypt and decrypt methods
|
// Pass in encrypt and decrypt methods
|
||||||
crypto: Crypto.createEncryptor(secret.key),
|
crypto: Crypto.createEncryptor(secret.keys),
|
||||||
|
|
||||||
// really basic operational transform
|
// really basic operational transform
|
||||||
transformFunction : JsonOT.validate,
|
transformFunction : JsonOT.validate,
|
||||||
@@ -407,31 +426,33 @@ define([
|
|||||||
// build a dom from HJSON, diff, and patch the editor
|
// build a dom from HJSON, diff, and patch the editor
|
||||||
applyHjson(shjson);
|
applyHjson(shjson);
|
||||||
|
|
||||||
var shjson2 = stringifyDOM(inner);
|
if (!readOnly) {
|
||||||
if (shjson2 !== shjson) {
|
var shjson2 = stringifyDOM(inner);
|
||||||
console.error("shjson2 !== shjson");
|
if (shjson2 !== shjson) {
|
||||||
module.patchText(shjson2);
|
console.error("shjson2 !== shjson");
|
||||||
|
module.patchText(shjson2);
|
||||||
|
|
||||||
/* pushing back over the wire is necessary, but it can
|
/* pushing back over the wire is necessary, but it can
|
||||||
result in a feedback loop, which we call a browser
|
result in a feedback loop, which we call a browser
|
||||||
fight */
|
fight */
|
||||||
if (module.logFights) {
|
if (module.logFights) {
|
||||||
// what changed?
|
// what changed?
|
||||||
var op = TextPatcher.diff(shjson, shjson2);
|
var op = TextPatcher.diff(shjson, shjson2);
|
||||||
// log the changes
|
// log the changes
|
||||||
TextPatcher.log(shjson, op);
|
TextPatcher.log(shjson, op);
|
||||||
var sop = JSON.stringify(TextPatcher.format(shjson, op));
|
var sop = JSON.stringify(TextPatcher.format(shjson, op));
|
||||||
|
|
||||||
var index = module.fights.indexOf(sop);
|
var index = module.fights.indexOf(sop);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
module.fights.push(sop);
|
module.fights.push(sop);
|
||||||
console.log("Found a new type of browser disagreement");
|
console.log("Found a new type of browser disagreement");
|
||||||
console.log("You can inspect the list in your " +
|
console.log("You can inspect the list in your " +
|
||||||
"console at `REALTIME_MODULE.fights`");
|
"console at `REALTIME_MODULE.fights`");
|
||||||
console.log(module.fights);
|
console.log(module.fights);
|
||||||
} else {
|
} else {
|
||||||
console.log("Encountered a known browser disagreement: " +
|
console.log("Encountered a known browser disagreement: " +
|
||||||
"available at `REALTIME_MODULE.fights[%s]`", index);
|
"available at `REALTIME_MODULE.fights[%s]`", index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -491,12 +512,21 @@ define([
|
|||||||
var config = {
|
var config = {
|
||||||
userData: userList,
|
userData: userList,
|
||||||
changeNameID: Toolbar.constants.changeName,
|
changeNameID: Toolbar.constants.changeName,
|
||||||
|
readOnly: readOnly
|
||||||
};
|
};
|
||||||
|
if (readOnly) {delete config.changeNameID; }
|
||||||
toolbar = info.realtime.toolbar = Toolbar.create($bar, info.myID, info.realtime, info.getLag, info.userList, config);
|
toolbar = info.realtime.toolbar = Toolbar.create($bar, info.myID, info.realtime, info.getLag, info.userList, config);
|
||||||
createChangeName(Toolbar.constants.changeName, $bar);
|
if (!readOnly) { createChangeName(Toolbar.constants.changeName, $bar); }
|
||||||
|
|
||||||
var $rightside = $bar.find('.' + Toolbar.constants.rightside);
|
var $rightside = $bar.find('.' + Toolbar.constants.rightside);
|
||||||
|
|
||||||
|
var editHash;
|
||||||
|
var viewHash = Cryptpad.getViewHashFromKeys(info.channel, secret.keys);
|
||||||
|
|
||||||
|
if (!readOnly) {
|
||||||
|
editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
|
||||||
|
}
|
||||||
|
|
||||||
/* add an export button */
|
/* add an export button */
|
||||||
var $export = $('<button>', {
|
var $export = $('<button>', {
|
||||||
title: Messages.exportButtonTitle,
|
title: Messages.exportButtonTitle,
|
||||||
@@ -504,19 +534,22 @@ define([
|
|||||||
.text(Messages.exportButton)
|
.text(Messages.exportButton)
|
||||||
.addClass('rightside-button')
|
.addClass('rightside-button')
|
||||||
.click(exportFile);
|
.click(exportFile);
|
||||||
|
$rightside.append($export);
|
||||||
|
|
||||||
/* add an import button */
|
if (!readOnly) {
|
||||||
var $import = $('<button>', {
|
/* add an import button */
|
||||||
title: Messages.importButtonTitle
|
var $import = $('<button>', {
|
||||||
})
|
title: Messages.importButtonTitle
|
||||||
.text(Messages.importButton)
|
})
|
||||||
.addClass('rightside-button')
|
.text(Messages.importButton)
|
||||||
.click(Cryptpad.importContent('text/plain', function (content) {
|
.addClass('rightside-button')
|
||||||
var shjson = stringify(Hyperjson.fromDOM(domFromHTML(content).body));
|
.click(Cryptpad.importContent('text/plain', function (content) {
|
||||||
applyHjson(shjson);
|
var shjson = stringify(Hyperjson.fromDOM(domFromHTML(content).body));
|
||||||
realtimeOptions.onLocal();
|
applyHjson(shjson);
|
||||||
}));
|
realtimeOptions.onLocal();
|
||||||
$rightside.append($export).append($import);
|
}));
|
||||||
|
$rightside.append($import);
|
||||||
|
}
|
||||||
|
|
||||||
/* add a rename button */
|
/* add a rename button */
|
||||||
var $rename = $('<button>', {
|
var $rename = $('<button>', {
|
||||||
@@ -569,8 +602,25 @@ define([
|
|||||||
});
|
});
|
||||||
$rightside.append($forgetPad);
|
$rightside.append($forgetPad);
|
||||||
|
|
||||||
|
if (!readOnly && viewHash) {
|
||||||
|
/* add a 'links' button */
|
||||||
|
var $links = $('<button>', {
|
||||||
|
title: Messages.getViewButtonTitle
|
||||||
|
})
|
||||||
|
.text(Messages.getViewButton)
|
||||||
|
.addClass('rightside-button')
|
||||||
|
.click(function () {
|
||||||
|
var baseUrl = window.location.origin + window.location.pathname + '#';
|
||||||
|
var content = '<b>' + Messages.readonlyUrl + '</b><br><a>' + baseUrl + viewHash + '</a><br>';
|
||||||
|
Cryptpad.alert(content);
|
||||||
|
});
|
||||||
|
$rightside.append($links);
|
||||||
|
}
|
||||||
|
|
||||||
// set the hash
|
// set the hash
|
||||||
window.location.hash = Cryptpad.getHashFromKeys(info.channel, secret.key);
|
if (!readOnly) {
|
||||||
|
window.location.hash = editHash;
|
||||||
|
}
|
||||||
|
|
||||||
Cryptpad.getPadTitle(function (err, title) {
|
Cryptpad.getPadTitle(function (err, title) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -588,18 +638,6 @@ define([
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var onLocal = realtimeOptions.onLocal = function () {
|
|
||||||
if (initializing) { return; }
|
|
||||||
|
|
||||||
// stringify the json and send it into chainpad
|
|
||||||
var shjson = stringifyDOM(inner);
|
|
||||||
|
|
||||||
module.patchText(shjson);
|
|
||||||
if (module.realtime.getUserDoc() !== shjson) {
|
|
||||||
console.error("realtime.getUserDoc() !== shjson");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// this should only ever get called once, when the chain syncs
|
// this should only ever get called once, when the chain syncs
|
||||||
var onReady = realtimeOptions.onReady = function (info) {
|
var onReady = realtimeOptions.onReady = function (info) {
|
||||||
module.patchText = TextPatcher.create({
|
module.patchText = TextPatcher.create({
|
||||||
@@ -612,6 +650,9 @@ define([
|
|||||||
var shjson = info.realtime.getUserDoc();
|
var shjson = info.realtime.getUserDoc();
|
||||||
applyHjson(shjson);
|
applyHjson(shjson);
|
||||||
|
|
||||||
|
// Update the user list (metadata) from the hyperjson
|
||||||
|
updateUserList(shjson);
|
||||||
|
|
||||||
if (Visible.isSupported()) {
|
if (Visible.isSupported()) {
|
||||||
Visible.onChange(function (yes) {
|
Visible.onChange(function (yes) {
|
||||||
if (yes) { unnotify(); }
|
if (yes) { unnotify(); }
|
||||||
@@ -619,12 +660,19 @@ define([
|
|||||||
}
|
}
|
||||||
|
|
||||||
getLastName(function (err, lastName) {
|
getLastName(function (err, lastName) {
|
||||||
if (typeof(lastName) === 'string' && lastName.length) {
|
|
||||||
setName(lastName);
|
|
||||||
}
|
|
||||||
console.log("Unlocking editor");
|
console.log("Unlocking editor");
|
||||||
setEditable(true);
|
setEditable(true);
|
||||||
initializing = false;
|
initializing = false;
|
||||||
|
// Update the toolbar list:
|
||||||
|
// Add the current user in the metadata if he has edit rights
|
||||||
|
if (readOnly) { return; }
|
||||||
|
myData[myID] = {
|
||||||
|
name: ""
|
||||||
|
};
|
||||||
|
addToUserList(myData);
|
||||||
|
if (typeof(lastName) === 'string' && lastName.length) {
|
||||||
|
setName(lastName);
|
||||||
|
}
|
||||||
onLocal();
|
onLocal();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -650,6 +698,18 @@ define([
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var onLocal = realtimeOptions.onLocal = function () {
|
||||||
|
if (initializing) { return; }
|
||||||
|
if (readOnly) { return; }
|
||||||
|
|
||||||
|
// stringify the json and send it into chainpad
|
||||||
|
var shjson = stringifyDOM(inner);
|
||||||
|
|
||||||
|
module.patchText(shjson);
|
||||||
|
if (module.realtime.getUserDoc() !== shjson) {
|
||||||
|
console.error("realtime.getUserDoc() !== shjson");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
var rti = module.realtimeInput = realtimeInput.start(realtimeOptions);
|
var rti = module.realtimeInput = realtimeInput.start(realtimeOptions);
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
<div id="toolbar" class="buttons">
|
<div id="toolbar" class="buttons">
|
||||||
<sub><a href="/"></a></sub>
|
<sub><a href="/"></a></sub>
|
||||||
</div>
|
</div>
|
||||||
<h1>CryptPoll</h1>
|
<h1 id="mainTitle">CryptPoll</h1>
|
||||||
<h2 data-localization="poll_subtitle"></h2>
|
<h2 data-localization="poll_subtitle"></h2>
|
||||||
|
|
||||||
<p data-localization="poll_p_save"></p>
|
<p data-localization="poll_p_save"></p>
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
<input type="text" id="title" placeholder="title"><br />
|
<input type="text" id="title" placeholder="title"><br />
|
||||||
<textarea id="description" placeholder="description"></textarea>
|
<textarea id="description" placeholder="description"></textarea>
|
||||||
|
|
||||||
<p data-localization="poll_p_howtouse"></p>
|
<p id="howToUse" data-localization="poll_p_howtouse"></p>
|
||||||
|
|
||||||
<!-- Table markup-->
|
<!-- Table markup-->
|
||||||
<table id="table">
|
<table id="table">
|
||||||
|
|||||||
@@ -39,6 +39,14 @@ define([
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
var secret = Cryptpad.getSecrets();
|
var secret = Cryptpad.getSecrets();
|
||||||
|
var readOnly = secret.keys && !secret.keys.editKeyStr;
|
||||||
|
if (!secret.keys) {
|
||||||
|
secret.keys = secret.key;
|
||||||
|
}
|
||||||
|
if (readOnly) {
|
||||||
|
$('#mainTitle').html($('#mainTitle').html() + ' - ' + Messages.readonly);
|
||||||
|
$('#adduser, #addoption, #howToUse').remove();
|
||||||
|
}
|
||||||
|
|
||||||
var module = window.APP = {
|
var module = window.APP = {
|
||||||
Cryptpad: Cryptpad,
|
Cryptpad: Cryptpad,
|
||||||
@@ -139,6 +147,7 @@ define([
|
|||||||
var table = module.table = Table($('#table'), xy);
|
var table = module.table = Table($('#table'), xy);
|
||||||
|
|
||||||
var setEditable = function (bool) {
|
var setEditable = function (bool) {
|
||||||
|
if (readOnly && bool) { return; }
|
||||||
module.isEditable = bool;
|
module.isEditable = bool;
|
||||||
|
|
||||||
items.forEach(function ($item) {
|
items.forEach(function ($item) {
|
||||||
@@ -163,6 +172,7 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
var removeRow = function (proxy, uid) {
|
var removeRow = function (proxy, uid) {
|
||||||
|
if (readOnly) { return; }
|
||||||
// remove proxy.table.rows[uid]
|
// remove proxy.table.rows[uid]
|
||||||
|
|
||||||
proxy.table.rows[uid] = undefined;
|
proxy.table.rows[uid] = undefined;
|
||||||
@@ -186,6 +196,7 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
var removeColumn = function (proxy, uid) {
|
var removeColumn = function (proxy, uid) {
|
||||||
|
if (readOnly) { return; }
|
||||||
// remove proxy.table.cols[uid]
|
// remove proxy.table.cols[uid]
|
||||||
proxy.table.cols[uid] = undefined;
|
proxy.table.cols[uid] = undefined;
|
||||||
delete proxy.table.rows[uid];
|
delete proxy.table.rows[uid];
|
||||||
@@ -212,6 +223,7 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
var makeUserEditable = module.makeUserEditable = function (id, bool) {
|
var makeUserEditable = module.makeUserEditable = function (id, bool) {
|
||||||
|
if (readOnly) { return; }
|
||||||
var $name = $('input[type="text"][id="' + id + '"]').attr('disabled', !bool);
|
var $name = $('input[type="text"][id="' + id + '"]').attr('disabled', !bool);
|
||||||
|
|
||||||
var $edit = $name.parent().find('.edit');
|
var $edit = $name.parent().find('.edit');
|
||||||
@@ -289,6 +301,11 @@ define([
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (readOnly) {
|
||||||
|
$edit = '';
|
||||||
|
$remove = '';
|
||||||
|
}
|
||||||
|
|
||||||
var $wrapper = $('<div>', {
|
var $wrapper = $('<div>', {
|
||||||
'class': 'text-cell',
|
'class': 'text-cell',
|
||||||
})
|
})
|
||||||
@@ -313,6 +330,7 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
var makeOptionEditable = function (id, bool) {
|
var makeOptionEditable = function (id, bool) {
|
||||||
|
if (readOnly) { return; }
|
||||||
if (bool) {
|
if (bool) {
|
||||||
module.rt.proxy.table.rowsOrder.forEach(function (rowuid) {
|
module.rt.proxy.table.rowsOrder.forEach(function (rowuid) {
|
||||||
$('#' + rowuid)
|
$('#' + rowuid)
|
||||||
@@ -363,6 +381,11 @@ define([
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (readOnly) {
|
||||||
|
$edit = '';
|
||||||
|
$remove = '';
|
||||||
|
}
|
||||||
|
|
||||||
var $wrapper = $('<div>', {
|
var $wrapper = $('<div>', {
|
||||||
'class': 'text-cell',
|
'class': 'text-cell',
|
||||||
})
|
})
|
||||||
@@ -738,16 +761,33 @@ define([
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
$toolbar.append(Button({
|
if (!readOnly) {
|
||||||
id: 'wizard',
|
$toolbar.append(Button({
|
||||||
'class': 'wizard button action',
|
id: 'wizard',
|
||||||
title: Messages.wizardTitle,
|
'class': 'wizard button action',
|
||||||
}).text(Messages.wizardButton).click(function () {
|
title: Messages.wizardTitle,
|
||||||
Wizard.show();
|
}).text(Messages.wizardButton).click(function () {
|
||||||
if (Wizard.hasBeenDisplayed) { return; }
|
Wizard.show();
|
||||||
Cryptpad.log(Messages.wizardLog);
|
if (Wizard.hasBeenDisplayed) { return; }
|
||||||
Wizard.hasBeenDisplayed = true;
|
Cryptpad.log(Messages.wizardLog);
|
||||||
}));
|
Wizard.hasBeenDisplayed = true;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!readOnly && module.viewHash) {
|
||||||
|
/* add a 'links' button */
|
||||||
|
var $links = $('<button>', {
|
||||||
|
title: Messages.getViewButtonTitle
|
||||||
|
})
|
||||||
|
.text(Messages.getViewButton)
|
||||||
|
.addClass('button action')
|
||||||
|
.click(function () {
|
||||||
|
var baseUrl = window.location.origin + window.location.pathname + '#';
|
||||||
|
var content = '<b>' + Messages.readonlyUrl + '</b><br><a>' + baseUrl + module.viewHash + '</a><br>';
|
||||||
|
Cryptpad.alert(content);
|
||||||
|
});
|
||||||
|
$toolbar.append($links);
|
||||||
|
}
|
||||||
|
|
||||||
/* Import/Export buttons */
|
/* Import/Export buttons */
|
||||||
/*
|
/*
|
||||||
@@ -807,6 +847,7 @@ define([
|
|||||||
}
|
}
|
||||||
|
|
||||||
Cryptpad.getPadAttribute('column', function (err, column) {
|
Cryptpad.getPadAttribute('column', function (err, column) {
|
||||||
|
if (readOnly) { return; }
|
||||||
if (err) {
|
if (err) {
|
||||||
console.log("unable to retrieve column");
|
console.log("unable to retrieve column");
|
||||||
return;
|
return;
|
||||||
@@ -854,7 +895,10 @@ define([
|
|||||||
websocketURL: Config.websocketURL,
|
websocketURL: Config.websocketURL,
|
||||||
channel: secret.channel,
|
channel: secret.channel,
|
||||||
data: {},
|
data: {},
|
||||||
crypto: Crypto.createEncryptor(secret.key),
|
// our public key
|
||||||
|
validateKey: secret.keys.validateKey || undefined,
|
||||||
|
readOnly: readOnly,
|
||||||
|
crypto: Crypto.createEncryptor(secret.keys),
|
||||||
};
|
};
|
||||||
|
|
||||||
// don't initialize until the store is ready.
|
// don't initialize until the store is ready.
|
||||||
@@ -863,7 +907,17 @@ define([
|
|||||||
var rt = window.rt = module.rt = Listmap.create(config);
|
var rt = window.rt = module.rt = Listmap.create(config);
|
||||||
rt.proxy.on('create', function (info) {
|
rt.proxy.on('create', function (info) {
|
||||||
var realtime = module.realtime = info.realtime;
|
var realtime = module.realtime = info.realtime;
|
||||||
window.location.hash = Cryptpad.getHashFromKeys(info.channel, secret.key);
|
|
||||||
|
var editHash;
|
||||||
|
var viewHash = module.viewHash = Cryptpad.getViewHashFromKeys(info.channel, secret.keys);
|
||||||
|
if (!readOnly) {
|
||||||
|
editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
|
||||||
|
}
|
||||||
|
// set the hash
|
||||||
|
if (!readOnly) {
|
||||||
|
window.location.hash = editHash;
|
||||||
|
}
|
||||||
|
|
||||||
module.patchText = TextPatcher.create({
|
module.patchText = TextPatcher.create({
|
||||||
realtime: realtime,
|
realtime: realtime,
|
||||||
logging: true,
|
logging: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user