Merge branch 'soon' of github.com:xwiki-labs/cryptpad into soon

This commit is contained in:
yflory
2017-06-09 14:36:38 +02:00
109 changed files with 5489 additions and 2436 deletions

View File

@@ -7,8 +7,18 @@ define([], function () {
// jquery declares itself as literally "jquery" so it cannot be pulled by path :(
"jquery": "/bower_components/jquery/dist/jquery.min",
// json.sortify same
"json.sortify": "/bower_components/json.sortify/dist/JSON.sortify"
"json.sortify": "/bower_components/json.sortify/dist/JSON.sortify",
"pdfjs-dist/build/pdf": "/bower_components/pdfjs-dist/build/pdf",
"pdfjs-dist/build/pdf.worker": "/bower_components/pdfjs-dist/build/pdf.worker"
}
});
// most of CryptPad breaks if you don't support isArray
if (!Array.isArray) {
Array.isArray = function(arg) { // CRYPTPAD_SHIM
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
require([document.querySelector('script[data-bootload]').getAttribute('data-bootload')]);
});

View File

@@ -0,0 +1,300 @@
define([
'jquery',
'/common/modes.js',
'/common/themes.js',
'/bower_components/file-saver/FileSaver.min.js'
], function ($, Modes, Themes) {
var saveAs = window.saveAs;
var module = {};
module.create = function (CMeditor, ifrw, Cryptpad) {
var exp = {};
var Messages = Cryptpad.Messages;
var CodeMirror = exp.CodeMirror = CMeditor;
CodeMirror.modeURL = "/bower_components/codemirror/mode/%N/%N.js";
var $pad = $('#pad-iframe');
var $textarea = exp.$textarea = $pad.contents().find('#editor1');
var Title;
var onLocal = function () {};
var $rightside;
exp.init = function (local, title, toolbar) {
if (typeof local === "function") {
onLocal = local;
}
Title = title;
$rightside = toolbar.$rightside;
};
var editor = exp.editor = CMeditor.fromTextArea($textarea[0], {
lineNumbers: true,
lineWrapping: true,
autoCloseBrackets: true,
matchBrackets : true,
showTrailingSpace : true,
styleActiveLine : true,
search: true,
highlightSelectionMatches: {showToken: /\w+/},
extraKeys: {"Shift-Ctrl-R": undefined},
foldGutter: true,
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
mode: "javascript",
readOnly: true
});
editor.setValue(Messages.codeInitialState);
var setMode = exp.setMode = function (mode, cb) {
exp.highlightMode = mode;
if (mode === 'text') {
editor.setOption('mode', 'text');
if (cb) { cb('text'); }
return;
}
CMeditor.autoLoadMode(editor, mode);
editor.setOption('mode', mode);
if (exp.$language) {
var name = exp.$language.find('a[data-value="' + mode + '"]').text() || 'Mode';
exp.$language.setValue(name);
}
if(cb) { cb(mode); }
};
var setTheme = exp.setTheme = (function () {
var path = '/common/theme/';
var $head = $(ifrw.document.head);
var themeLoaded = exp.themeLoaded = function (theme) {
return $head.find('link[href*="'+theme+'"]').length;
};
var loadTheme = exp.loadTheme = function (theme) {
$head.append($('<link />', {
rel: 'stylesheet',
href: path + theme + '.css',
}));
};
return function (theme, $select) {
if (!theme) {
editor.setOption('theme', 'default');
} else {
if (!themeLoaded(theme)) {
loadTheme(theme);
}
editor.setOption('theme', theme);
}
if ($select) {
$select.setValue(theme || 'Theme');
}
};
}());
exp.getHeadingText = function () {
var lines = editor.getValue().split(/\n/);
var text = '';
lines.some(function (line) {
// lisps?
var lispy = /^\s*(;|#\|)(.*?)$/;
if (lispy.test(line)) {
line.replace(lispy, function (a, one, two) {
text = two;
});
return true;
}
// lines beginning with a hash are potentially valuable
// works for markdown, python, bash, etc.
var hash = /^#(.*?)$/;
if (hash.test(line)) {
line.replace(hash, function (a, one) {
text = one;
});
return true;
}
// lines including a c-style comment are also valuable
var clike = /^\s*(\/\*|\/\/)(.*)?(\*\/)*$/;
if (clike.test(line)) {
line.replace(clike, function (a, one, two) {
if (!(two && two.replace)) { return; }
text = two.replace(/\*\/\s*$/, '').trim();
});
return true;
}
// TODO make one more pass for multiline comments
});
return text.trim();
};
exp.configureLanguage = function (cb, onModeChanged) {
var options = [];
Modes.list.forEach(function (l) {
options.push({
tag: 'a',
attributes: {
'data-value': l.mode,
'href': '#',
},
content: l.language // Pretty name of the language value
});
});
var dropdownConfig = {
text: 'Mode', // Button initial text
options: options, // Entries displayed in the menu
left: true, // Open to the left of the button
isSelect: true,
};
var $block = exp.$language = Cryptpad.createDropdown(dropdownConfig);
$block.find('a').click(function () {
setMode($(this).attr('data-value'), onModeChanged);
onLocal();
});
if ($rightside) { $rightside.append($block); }
if (cb) { cb(); }
};
exp.configureTheme = function (cb) {
/* Remember the user's last choice of theme using localStorage */
var themeKey = 'CRYPTPAD_CODE_THEME';
var lastTheme = localStorage.getItem(themeKey) || 'default';
var options = [];
Themes.forEach(function (l) {
options.push({
tag: 'a',
attributes: {
'data-value': l.name,
'href': '#',
},
content: l.name // Pretty name of the language value
});
});
var dropdownConfig = {
text: 'Theme', // Button initial text
options: options, // Entries displayed in the menu
left: true, // Open to the left of the button
isSelect: true,
initialValue: lastTheme
};
var $block = exp.$theme = Cryptpad.createDropdown(dropdownConfig);
setTheme(lastTheme, $block);
$block.find('a').click(function () {
var theme = $(this).attr('data-value');
setTheme(theme, $block);
localStorage.setItem(themeKey, theme);
});
if ($rightside) { $rightside.append($block); }
if (cb) { cb(); }
};
exp.exportText = function () {
var text = editor.getValue();
var ext = Modes.extensionOf(exp.highlightMode);
var title = Cryptpad.fixFileName(Title ? Title.suggestTitle('cryptpad') : "?") + (ext || '.txt');
Cryptpad.prompt(Messages.exportPrompt, title, function (filename) {
if (filename === null) { return; }
var blob = new Blob([text], {
type: 'text/plain;charset=utf-8'
});
saveAs(blob, filename);
});
};
exp.importText = function (content, file) {
var $bar = ifrw.$('#cme_toolbox');
var mode;
var mime = CodeMirror.findModeByMIME(file.type);
if (!mime) {
var ext = /.+\.([^.]+)$/.exec(file.name);
if (ext[1]) {
mode = CMeditor.findModeByExtension(ext[1]);
}
} else {
mode = mime && mime.mode || null;
}
if (mode && Modes.list.some(function (o) { return o.mode === mode; })) {
setMode(mode);
$bar.find('#language-mode').val(mode);
} else {
console.log("Couldn't find a suitable highlighting mode: %s", mode);
setMode('text');
$bar.find('#language-mode').val('text');
}
editor.setValue(content);
onLocal();
};
var cursorToPos = function(cursor, oldText) {
var cLine = cursor.line;
var cCh = cursor.ch;
var pos = 0;
var textLines = oldText.split("\n");
for (var line = 0; line <= cLine; line++) {
if(line < cLine) {
pos += textLines[line].length+1;
}
else if(line === cLine) {
pos += cCh;
}
}
return pos;
};
var posToCursor = function(position, newText) {
var cursor = {
line: 0,
ch: 0
};
var textLines = newText.substr(0, position).split("\n");
cursor.line = textLines.length - 1;
cursor.ch = textLines[cursor.line].length;
return cursor;
};
exp.setValueAndCursor = function (oldDoc, remoteDoc, TextPatcher) {
var scroll = editor.getScrollInfo();
//get old cursor here
var oldCursor = {};
oldCursor.selectionStart = cursorToPos(editor.getCursor('from'), oldDoc);
oldCursor.selectionEnd = cursorToPos(editor.getCursor('to'), oldDoc);
editor.setValue(remoteDoc);
editor.save();
var op = TextPatcher.diff(oldDoc, remoteDoc);
var selects = ['selectionStart', 'selectionEnd'].map(function (attr) {
return TextPatcher.transformCursor(oldCursor[attr], op);
});
if(selects[0] === selects[1]) {
editor.setCursor(posToCursor(selects[0], remoteDoc));
}
else {
editor.setSelection(posToCursor(selects[0], remoteDoc), posToCursor(selects[1], remoteDoc));
}
editor.scrollTo(scroll.left, scroll.top);
};
return exp;
};
return module;
});

View File

@@ -1,8 +1,9 @@
define([
'/common/common-util.js',
'/common/common-interface.js',
'/bower_components/chainpad-crypto/crypto.js',
'/bower_components/tweetnacl/nacl-fast.min.js'
], function (Util, Crypto) {
], function (Util, UI, Crypto) {
var Nacl = window.nacl;
var Hash = {};
@@ -32,9 +33,68 @@ define([
return '/1/view/' + hexToBase64(chanKey) + '/'+Crypto.b64RemoveSlashes(keys.viewKeyStr)+'/';
};
var getFileHashFromKeys = Hash.getFileHashFromKeys = function (fileKey, cryptKey) {
return '/2/' + hexToBase64(fileKey) + '/' + Crypto.b64RemoveSlashes(cryptKey) + '/';
return '/1/' + hexToBase64(fileKey) + '/' + Crypto.b64RemoveSlashes(cryptKey) + '/';
};
Hash.getUserHrefFromKeys = function (username, pubkey) {
return window.location.origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-');
};
var fixDuplicateSlashes = function (s) {
return s.replace(/\/+/g, '/');
};
/*
Version 0
/pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy
Version 1
/code/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI
*/
var parseTypeHash = Hash.parseTypeHash = function (type, hash) {
if (!hash) { return; }
var parsed = {};
var hashArr = fixDuplicateSlashes(hash).split('/');
if (['media', 'file', 'user'].indexOf(type) === -1) {
parsed.type = 'pad';
if (hash.slice(0,1) !== '/' && hash.length >= 56) {
// Old hash
parsed.channel = hash.slice(0, 32);
parsed.key = hash.slice(32, 56);
parsed.version = 0;
return parsed;
}
if (hashArr[1] && hashArr[1] === '1') {
parsed.version = 1;
parsed.mode = hashArr[2];
parsed.channel = hashArr[3];
parsed.key = hashArr[4].replace(/-/g, '/');
parsed.present = typeof(hashArr[5]) === "string" && hashArr[5] === 'present';
return parsed;
}
return parsed;
}
if (['media', 'file'].indexOf(type) !== -1) {
parsed.type = 'file';
if (hashArr[1] && hashArr[1] === '1') {
parsed.version = 1;
parsed.channel = hashArr[2].replace(/-/g, '/');
parsed.key = hashArr[3].replace(/-/g, '/');
return parsed;
}
return parsed;
}
if (['user'].indexOf(type) !== -1) {
parsed.type = 'user';
if (hashArr[1] && hashArr[1] === '1') {
parsed.version = 1;
parsed.user = hashArr[2];
parsed.pubkey = hashArr[3].replace(/-/g, '/');
return parsed;
}
return parsed;
}
return;
};
var parsePadUrl = Hash.parsePadUrl = function (href) {
var patt = /^https*:\/\/([^\/]*)\/(.*?)\//i;
@@ -43,19 +103,24 @@ define([
if (!href) { return ret; }
if (href.slice(-1) !== '/') { href += '/'; }
var idx;
if (!/^https*:\/\//.test(href)) {
var idx = href.indexOf('/#');
idx = href.indexOf('/#');
ret.type = href.slice(1, idx);
ret.hash = href.slice(idx + 2);
ret.hashData = parseTypeHash(ret.type, ret.hash);
return ret;
}
var hash = href.replace(patt, function (a, domain, type) {
href.replace(patt, function (a, domain, type) {
ret.domain = domain;
ret.type = type;
return '';
});
ret.hash = hash.replace(/#/g, '');
idx = href.indexOf('/#');
ret.hash = href.slice(idx + 2);
ret.hashData = parseTypeHash(ret.type, ret.hash);
return ret;
};
@@ -71,7 +136,7 @@ define([
* - no argument: use the URL hash or create one if it doesn't exist
* - secretHash provided: use secretHash to find the keys
*/
Hash.getSecrets = function (secretHash) {
Hash.getSecrets = function (type, secretHash) {
var secret = {};
var generate = function () {
secret.keys = Crypto.createEditCryptor();
@@ -81,50 +146,56 @@ define([
generate();
return secret;
} else {
var hash = secretHash || window.location.hash.slice(1);
var parsed;
var hash;
if (secretHash) {
if (!type) { throw new Error("getSecrets with a hash requires a type parameter"); }
parsed = parseTypeHash(type, secretHash);
hash = secretHash;
} else {
var pHref = parsePadUrl(window.location.href);
parsed = pHref.hashData;
hash = pHref.hash;
}
//var parsed = parsePadUrl(window.location.href);
//var hash = secretHash || window.location.hash.slice(1);
if (hash.length === 0) {
generate();
return secret;
}
// old hash system : #{hexChanKey}{cryptKey}
// new hash system : #/{hashVersion}/{b64ChanKey}/{cryptKey}
if (hash.slice(0,1) !== '/' && hash.length >= 56) {
if (parsed.version === 0) {
// Old hash
secret.channel = hash.slice(0, 32);
secret.key = hash.slice(32);
secret.channel = parsed.channel;
secret.key = parsed.key;
}
else {
else if (parsed.version === 1) {
// New hash
var hashArray = hash.split('/');
if (hashArray.length < 4) {
Hash.alert("Unable to parse the key");
throw new Error("Unable to parse the key");
}
var version = hashArray[1];
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 (parsed.type === "pad") {
secret.channel = base64ToHex(parsed.channel);
if (parsed.mode === 'edit') {
secret.keys = Crypto.createEditCryptor(parsed.key);
secret.key = secret.keys.editKeyStr;
if (secret.channel.length !== 32 || secret.key.length !== 24) {
Hash.alert("The channel key and/or the encryption key is invalid");
UI.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]);
secret.keys = Crypto.createViewCryptor(hashArray[4].replace(/-/g, '/'));
else if (parsed.mode === 'view') {
secret.keys = Crypto.createViewCryptor(parsed.key);
if (secret.channel.length !== 32) {
Hash.alert("The channel key is invalid");
UI.alert("The channel key is invalid");
throw new Error("The channel key is invalid");
}
}
} else if (version === "2") {
} else if (parsed.type === "file") {
// version 2 hashes are to be used for encrypted blobs
secret.channel = hashArray[2].replace(/-/g, '/');
secret.keys = { fileKeyStr: hashArray[3].replace(/-/g, '/') };
secret.channel = parsed.channel;
secret.keys = { fileKeyStr: parsed.key };
} else if (parsed.type === "user") {
// version 2 hashes are to be used for encrypted blobs
throw new Error("User hashes can't be opened (yet)");
}
}
}
@@ -161,42 +232,6 @@ define([
return '/1/edit/' + [channelId, key].join('/') + '/';
};
/*
Version 0
/pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy
Version 1
/code/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI
Version 2
/file/#/2/<fileId>/<cryptKey>/<contentType>
/file/#/2/K6xWU-LT9BJHCQcDCT-DcQ/ajExFODrFH4lVBwxxsrOKw/image-png
*/
var parseHash = Hash.parseHash = function (hash) {
var parsed = {};
if (hash.slice(0,1) !== '/' && hash.length >= 56) {
// Old hash
parsed.channel = hash.slice(0, 32);
parsed.key = hash.slice(32);
parsed.version = 0;
return parsed;
}
var hashArr = hash.split('/');
if (hashArr[1] && hashArr[1] === '1') {
parsed.version = 1;
parsed.mode = hashArr[2];
parsed.channel = hashArr[3];
parsed.key = hashArr[4];
parsed.present = typeof(hashArr[5]) === "string" && hashArr[5] === 'present';
return parsed;
}
if (hashArr[1] && hashArr[1] === '2') {
parsed.version = 2;
parsed.channel = hashArr[2].replace(/-/g, '/');
parsed.key = hashArr[3].replace(/-/g, '/');
return parsed;
}
return;
};
// STORAGE
Hash.findWeaker = function (href, recents) {
var rHref = href || getRelativeHref(window.location.href);
@@ -207,9 +242,13 @@ Version 2
var p = parsePadUrl(pad.href);
if (p.type !== parsed.type) { return; } // Not the same type
if (p.hash === parsed.hash) { return; } // Same hash, not stronger
var pHash = parseHash(p.hash);
var parsedHash = parseHash(parsed.hash);
var pHash = p.hashData;
var parsedHash = parsed.hashData;
if (!parsedHash || !pHash) { return; }
// We don't have stronger/weaker versions of files or users
if (pHash.type !== 'pad' && parsedHash.type !== 'pad') { return; }
if (pHash.version !== parsedHash.version) { return; }
if (pHash.channel !== parsedHash.channel) { return; }
if (pHash.mode === 'view' && parsedHash.mode === 'edit') {
@@ -229,9 +268,13 @@ Version 2
var p = parsePadUrl(pad.href);
if (p.type !== parsed.type) { return; } // Not the same type
if (p.hash === parsed.hash) { return; } // Same hash, not stronger
var pHash = parseHash(p.hash);
var parsedHash = parseHash(parsed.hash);
var pHash = p.hashData;
var parsedHash = parsed.hashData;
if (!parsedHash || !pHash) { return; }
// We don't have stronger/weaker versions of files or users
if (pHash.type !== 'pad' && parsedHash.type !== 'pad') { return; }
if (pHash.version !== parsedHash.version) { return; }
if (pHash.channel !== parsedHash.channel) { return; }
if (pHash.mode === 'edit' && parsedHash.mode === 'view') {
@@ -250,8 +293,7 @@ Version 2
var parsed = Hash.parsePadUrl(href);
if (!parsed || !parsed.hash) { return; }
parsed = Hash.parseHash(parsed.hash);
parsed = parsed.hashData;
if (parsed.version === 0) {
return parsed.channel;
} else if (parsed.version !== 1 && parsed.version !== 2) {

View File

@@ -24,7 +24,6 @@ define([
var wcId = common.hrefToHexChannelId(config.href || window.location.href);
console.log(wcId);
var createRealtime = function () {
return ChainPad.create({
userName: 'history',
@@ -36,8 +35,8 @@ define([
};
var realtime = createRealtime();
var hash = config.href ? common.parsePadUrl(config.href).hash : undefined;
var secret = common.getSecrets(hash);
var parsed = config.href ? common.parsePadUrl(config.href) : {};
var secret = common.getSecrets(parsed.type, parsed.hash);
var crypto = Crypto.createEncryptor(secret.keys);
var to = window.setTimeout(function () {
@@ -80,11 +79,32 @@ define([
if (History.loading) { return void console.error("History is already being loaded..."); }
History.loading = true;
var $toolbar = config.$toolbar;
var noFunc = function () {};
var render = config.onRender || noFunc;
var onClose = config.onClose || noFunc;
var onRevert = config.onRevert || noFunc;
var onReady = config.onReady || noFunc;
if (!config.applyVal || !config.setHistory || !config.onLocal || !config.onRemote) {
throw new Error("Missing config element: applyVal, onLocal, onRemote, setHistory");
}
// config.setHistory(bool, bool)
// - bool1: history value
// - bool2: reset old content?
var render = function (val) {
if (typeof val === "undefined") { return; }
try {
config.applyVal(val);
} catch (e) {
// Probably a parse error
console.error(e);
}
};
var onClose = function () { config.setHistory(false, true); };
var onRevert = function () {
config.setHistory(false, false);
config.onLocal();
config.onRemote();
};
var onReady = function () {
config.setHistory(true);
};
var Messages = common.Messages;

View File

@@ -3,8 +3,10 @@ define([
'/customize/messages.js',
'/common/common-util.js',
'/customize/application_config.js',
'/bower_components/alertifyjs/dist/js/alertify.js'
], function ($, Messages, Util, AppConfig, Alertify) {
'/bower_components/alertifyjs/dist/js/alertify.js',
'/common/notify.js',
'/common/visible.js'
], function ($, Messages, Util, AppConfig, Alertify, Notify, Visible) {
var UI = {};
@@ -141,7 +143,7 @@ define([
return {
show: function () {
$target.show();
$target.css('display', 'inline');
return this;
},
hide: function () {
@@ -192,10 +194,17 @@ define([
};
UI.removeLoadingScreen = function (cb) {
$('#' + LOADING).fadeOut(750, cb);
$('#loadingTip').css('top', '');
window.setTimeout(function () {
$('#loadingTip').fadeOut(750);
}, 3000);
var $tip = $('#loadingTip').css('top', '')
// loading.less sets transition-delay: $wait-time
// and transition: opacity $fadeout-time
.css({
'opacity': 0,
'pointer-events': 'none',
});
setTimeout(function () {
$tip.remove();
}, 3750);
// jquery.fadeout can get stuck
};
UI.errorLoadingScreen = function (error, transparent) {
if (!$('#' + LOADING).is(':visible')) { UI.addLoadingScreen(undefined, true); }
@@ -204,6 +213,28 @@ define([
$('#' + LOADING).find('p').html(error || Messages.error);
};
// Notify
var notify = {};
UI.unnotify = function () {
if (notify.tabNotification &&
typeof(notify.tabNotification.cancel) === 'function') {
notify.tabNotification.cancel();
}
};
UI.notify = function () {
if (Visible.isSupported() && !Visible.currently()) {
UI.unnotify();
notify.tabNotification = Notify.tab(1000, 10);
}
};
if (Visible.isSupported()) {
Visible.onChange(function (yes) {
if (yes) { UI.unnotify(); }
});
}
UI.importContent = function (type, f) {
return function () {
var $files = $('<input type="file">').click();

View File

@@ -0,0 +1,51 @@
define(function () {
var module = {};
module.create = function (UserList, Title, cfg) {
var exp = {};
exp.update = function (shjson) {
// Extract the user list (metadata) from the hyperjson
var json = (!shjson || typeof shjson !== "string") ? "" : JSON.parse(shjson);
var titleUpdated = false;
var metadata;
if (Array.isArray(json)) {
metadata = json[3] && json[3].metadata;
} else {
metadata = json.metadata;
}
if (typeof metadata === "object") {
if (metadata.users) {
var userData = metadata.users;
// Update the local user data
UserList.addToUserData(userData);
}
if (metadata.defaultTitle) {
Title.updateDefaultTitle(metadata.defaultTitle);
}
if (typeof metadata.title !== "undefined") {
Title.updateTitle(metadata.title || Title.defaultTitle);
titleUpdated = true;
}
if (metadata.slideOptions && cfg.slideOptions) {
cfg.slideOptions(metadata.slideOptions);
}
if (metadata.color && cfg.slideColors) {
cfg.slideColors(metadata.color, metadata.backColor);
}
if (typeof(metadata.palette) !== 'undefined' && cfg.updatePalette) {
cfg.updatePalette(metadata.palette);
}
}
if (!titleUpdated) {
Title.updateTitle(Title.defaultTitle);
}
};
return exp;
};
return module;
});

View File

@@ -0,0 +1,84 @@
define(function () {
var module = {};
module.create = function (cfg, onLocal, Cryptpad) {
var exp = {};
var parsed = exp.parsedHref = Cryptpad.parsePadUrl(window.location.href);
exp.defaultTitle = Cryptpad.getDefaultName(parsed);
exp.title = document.title; // TOOD slides
cfg = cfg || {};
var getHeadingText = cfg.getHeadingText || function () { return; };
var updateLocalTitle = function (newTitle) {
exp.title = newTitle;
if (typeof cfg.updateLocalTitle === "function") {
cfg.updateLocalTitle(newTitle);
} else {
document.title = newTitle;
}
};
var $title;
exp.setToolbar = function (toolbar) {
$title = toolbar && toolbar.title;
};
exp.getTitle = function () { return exp.title; };
var isDefaultTitle = exp.isDefaultTitle = function (){return exp.title === exp.defaultTitle;};
var suggestTitle = exp.suggestTitle = function (fallback) {
if (isDefaultTitle()) {
return getHeadingText() || fallback || "";
} else {
return exp.title || getHeadingText() || exp.defaultTitle;
}
};
var renameCb = function (err, newTitle) {
if (err) { return; }
updateLocalTitle(newTitle);
console.log('here');
onLocal();
};
exp.updateTitle = function (newTitle) {
if (newTitle === exp.title) { return; }
// Change the title now, and set it back to the old value if there is an error
var oldTitle = exp.title;
Cryptpad.renamePad(newTitle, function (err, data) {
if (err) {
console.log("Couldn't set pad title");
console.error(err);
updateLocalTitle(oldTitle);
return;
}
updateLocalTitle(data);
if (!$title) { return; }
$title.find('span.title').text(data);
$title.find('input').val(data);
});
};
exp.updateDefaultTitle = function (newDefaultTitle) {
exp.defaultTitle = newDefaultTitle;
if (!$title) { return; }
$title.find('input').attr("placeholder", exp.defaultTitle);
};
exp.getTitleConfig = function () {
return {
onRename: renameCb,
suggestName: suggestTitle,
defaultName: exp.defaultTitle
};
};
return exp;
};
return module;
});

View File

@@ -21,7 +21,7 @@ define([], function () {
.replace(/ +$/, "")
.split(" ");
var byteString = String.fromCharCode.apply(null, hexArray);
return window.btoa(byteString).replace(/\//g, '-').slice(0,-2);
return window.btoa(byteString).replace(/\//g, '-').replace(/=+$/, '');
};
Util.base64ToHex = function (b64String) {
@@ -81,12 +81,58 @@ define([], function () {
.replace(/_+/g, '_');
};
var oneKilobyte = 1024;
var oneMegabyte = 1024 * oneKilobyte;
var oneGigabyte = 1024 * oneMegabyte;
Util.bytesToGigabytes = function (bytes) {
return Math.ceil(bytes / oneGigabyte * 100) / 100;
};
Util.bytesToMegabytes = function (bytes) {
return Math.floor((bytes / (1024 * 1024) * 100)) / 100;
return Math.ceil(bytes / oneMegabyte * 100) / 100;
};
Util.bytesToKilobytes = function (bytes) {
return Math.floor(bytes / 1024 * 100) / 100;
return Math.ceil(bytes / oneKilobyte * 100) / 100;
};
Util.magnitudeOfBytes = function (bytes) {
if (bytes >= oneGigabyte) { return 'GB'; }
else if (bytes >= oneMegabyte) { return 'MB'; }
};
Util.fetch = function (src, cb) {
var done = false;
var CB = function (err, res) {
if (done) { return; }
done = true;
cb(err, res);
};
var xhr = new XMLHttpRequest();
xhr.open("GET", src, true);
xhr.responseType = "arraybuffer";
xhr.onload = function () {
if (/^4/.test(''+this.status)) {
return CB('XHR_ERROR');
}
return void CB(void 0, new Uint8Array(xhr.response));
};
xhr.send(null);
};
Util.throttle = function (f, ms) {
var to;
var g = function () {
window.clearTimeout(to);
to = window.setTimeout(f, ms);
};
return g;
};
Util.createRandomInteger = function () {
return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
};
return Util;

View File

@@ -22,7 +22,8 @@ define([
};
var makeConfig = function (hash) {
var secret = Cryptpad.getSecrets(hash);
// We can't use cryptget with a file or a user so we can use 'pad' as hash type
var secret = Cryptpad.getSecrets('pad', hash);
if (!secret.keys) { secret.keys = secret.key; } // support old hashses
var config = {
websocketURL: Cryptpad.getWebsocketURL(),

View File

@@ -8,11 +8,14 @@ define([
'/common/common-interface.js',
'/common/common-history.js',
'/common/common-userlist.js',
'/common/common-title.js',
'/common/common-metadata.js',
'/common/common-codemirror.js',
'/common/clipboard.js',
'/common/pinpad.js',
'/customize/application_config.js'
], function ($, Config, Messages, Store, Util, Hash, UI, History, UserList, Clipboard, Pinpad, AppConfig) {
], function ($, Config, Messages, Store, Util, Hash, UI, History, UserList, Title, Metadata, CodeMirror, Clipboard, Pinpad, AppConfig) {
/* This file exposes functionality which is specific to Cryptpad, but not to
any particular pad type. This includes functions for committing metadata
@@ -20,10 +23,12 @@ define([
Additionally, there is some basic functionality for import/export.
*/
var common = window.Cryptpad = {
Messages: Messages,
Clipboard: Clipboard
Clipboard: Clipboard,
donateURL: 'https://accounts.cryptpad.fr/#/donate?on=' + window.location.hostname,
upgradeURL: 'https://accounts.cryptpad.fr/#/?on=' + window.location.hostname,
account: {},
};
// constants
@@ -53,6 +58,8 @@ define([
common.addLoadingScreen = UI.addLoadingScreen;
common.removeLoadingScreen = UI.removeLoadingScreen;
common.errorLoadingScreen = UI.errorLoadingScreen;
common.notify = UI.notify;
common.unnotify = UI.unnotify;
// import common utilities for export
common.find = Util.find;
@@ -66,18 +73,23 @@ define([
common.fixFileName = Util.fixFileName;
common.bytesToMegabytes = Util.bytesToMegabytes;
common.bytesToKilobytes = Util.bytesToKilobytes;
common.fetch = Util.fetch;
common.throttle = Util.throttle;
common.createRandomInteger = Util.createRandomInteger;
// import hash utilities for export
var createRandomHash = common.createRandomHash = Hash.createRandomHash;
common.parseTypeHash = Hash.parseTypeHash;
var parsePadUrl = common.parsePadUrl = Hash.parsePadUrl;
var isNotStrongestStored = common.isNotStrongestStored = Hash.isNotStrongestStored;
var hrefToHexChannelId = common.hrefToHexChannelId = Hash.hrefToHexChannelId;
var parseHash = common.parseHash = Hash.parseHash;
var getRelativeHref = common.getRelativeHref = Hash.getRelativeHref;
common.getBlobPathFromHex = Hash.getBlobPathFromHex;
common.getEditHashFromKeys = Hash.getEditHashFromKeys;
common.getViewHashFromKeys = Hash.getViewHashFromKeys;
common.getFileHashFromKeys = Hash.getFileHashFromKeys;
common.getUserHrefFromKeys = Hash.getUserHrefFromKeys;
common.getSecrets = Hash.getSecrets;
common.getHashes = Hash.getHashes;
common.createChannelId = Hash.createChannelId;
@@ -88,6 +100,15 @@ define([
// Userlist
common.createUserList = UserList.create;
// Title
common.createTitle = Title.create;
// Metadata
common.createMetadata = Metadata.create;
// CodeMirror
common.createCodemirror = CodeMirror.create;
// History
common.getHistory = function (config) { return History.create(common, config); };
@@ -197,6 +218,7 @@ define([
userNameKey,
userHashKey,
'loginToken',
'plan',
].forEach(function (k) {
sessionStorage.removeItem(k);
localStorage.removeItem(k);
@@ -225,6 +247,11 @@ define([
var getUserHash = common.getUserHash = function () {
var hash = localStorage[userHashKey];
if (['undefined', 'undefined/'].indexOf(hash) !== -1) {
localStorage.removeItem(userHashKey);
return;
}
if (hash) {
var sHash = common.serializeHash(hash);
if (sHash !== hash) { localStorage[userHashKey] = sHash; }
@@ -270,27 +297,22 @@ define([
if (!pad.title) {
pad.title = common.getDefaultname(parsed);
}
return parsed.hash;
return parsed.hashData;
};
// Migrate from legacy store (localStorage)
var migrateRecentPads = common.migrateRecentPads = function (pads) {
return pads.map(function (pad) {
var hash;
var parsedHash;
if (Array.isArray(pad)) { // TODO DEPRECATE_F
var href = pad[0];
href.replace(/\#(.*)$/, function (a, h) {
hash = h;
});
return {
href: pad[0],
atime: pad[1],
title: pad[2] || hash && hash.slice(0,8),
title: pad[2] || '',
ctime: pad[1],
};
} else if (pad && typeof(pad) === 'object') {
hash = checkObjectData(pad);
if (!hash || !common.parseHash(hash)) { return; }
parsedHash = checkObjectData(pad);
if (!parsedHash || !parsedHash.type) { return; }
return pad;
} else {
console.error("[Cryptpad.migrateRecentPads] pad had unexpected value");
@@ -303,8 +325,8 @@ define([
var checkRecentPads = common.checkRecentPads = function (pads) {
pads.forEach(function (pad, i) {
if (pad && typeof(pad) === 'object') {
var hash = checkObjectData(pad);
if (!hash || !common.parseHash(hash)) {
var parsedHash = checkObjectData(pad);
if (!parsedHash || !parsedHash.type) {
console.error("[Cryptpad.checkRecentPads] pad had unexpected value", pad);
getStore().removeData(i);
return;
@@ -434,6 +456,7 @@ define([
Crypt.put(p.hash, val, function () {
common.findOKButton().click();
common.removeLoadingScreen();
common.feedback('TEMPLATE_USED');
});
});
}).appendTo($p);
@@ -522,6 +545,7 @@ define([
common.setPadTitle = function (name, cb) {
var href = window.location.href;
var parsed = parsePadUrl(href);
if (!parsed.hash) { return; }
href = getRelativeHref(href);
// getRecentPads return the array from the drive, not a copy
// We don't have to call "set..." at the end, everything is stored with listmap
@@ -542,8 +566,8 @@ define([
// Version 1 : we have up to 4 differents hash for 1 pad, keep the strongest :
// Edit > Edit (present) > View > View (present)
var pHash = parseHash(p.hash);
var parsedHash = parseHash(parsed.hash);
var pHash = p.hashData;
var parsedHash = parsed.hashData;
if (!pHash) { return; } // We may have a corrupted pad in our storage, abort here in that case
@@ -584,7 +608,7 @@ define([
var data = makePad(href, name);
getStore().pushData(data, function (e) {
if (e) {
if (e === 'E_OVER_LIMIT' && AppConfig.enablePinLimit) {
if (e === 'E_OVER_LIMIT') {
common.alert(Messages.pinLimitNotPinned, null, true);
return;
}
@@ -645,7 +669,8 @@ define([
var userHash = localStorage && localStorage.User_hash;
if (!userHash) { return null; }
var userChannel = common.parseHash(userHash).channel;
var userParsedHash = common.parseTypeHash('drive', userHash);
var userChannel = userParsedHash && userParsedHash.channel;
if (!userChannel) { return null; }
var list = fo.getFiles([fo.FILES_DATA]).map(hrefToHexChannelId)
@@ -728,29 +753,119 @@ define([
});
};
common.updatePinLimit = function (cb) {
if (!pinsReady()) { return void cb('[RPC_NOT_READY]'); }
rpc.updatePinLimits(function (e, limit, plan, note) {
if (e) { return cb(e); }
cb(e, limit, plan, note);
});
};
common.getPinLimit = function (cb) {
cb(void 0, typeof(AppConfig.pinLimit) === 'number'? AppConfig.pinLimit: 1000);
if (!pinsReady()) { return void cb('[RPC_NOT_READY]'); }
rpc.getLimit(function (e, limit, plan, note) {
if (e) { return cb(e); }
cb(void 0, limit, plan, note);
});
};
common.isOverPinLimit = function (cb) {
if (!common.isLoggedIn() || !AppConfig.enablePinLimit) { return void cb(null, false); }
if (!common.isLoggedIn()) { return void cb(null, false); }
var usage;
var andThen = function (e, limit) {
var andThen = function (e, limit, plan) {
if (e) { return void cb(e); }
var data = {usage: usage, limit: limit};
var data = {usage: usage, limit: limit, plan: plan};
if (usage > limit) {
return void cb (null, true, data);
}
return void cb (null, false, data);
};
var todo = function (e, used) {
usage = common.bytesToMegabytes(used);
usage = used; //common.bytesToMegabytes(used);
if (e) { return void cb(e); }
common.getPinLimit(andThen);
};
common.getPinnedUsage(todo);
};
common.uploadComplete = function (cb) {
if (!pinsReady()) { return void cb('[RPC_NOT_READY]'); }
rpc.uploadComplete(cb);
};
common.uploadStatus = function (size, cb) {
if (!pinsReady()) { return void cb('[RPC_NOT_READY]'); }
rpc.uploadStatus(size, cb);
};
common.uploadCancel = function (cb) {
if (!pinsReady()) { return void cb('[RPC_NOT_READY]'); }
rpc.uploadCancel(cb);
};
var LIMIT_REFRESH_RATE = 30000; // milliseconds
common.createUsageBar = function (cb, alwaysDisplayUpgrade) {
var todo = function (err, state, data) {
var $container = $('<span>', {'class':'limit-container'});
if (!data) {
return void window.setTimeout(function () {
common.isOverPinLimit(todo);
}, LIMIT_REFRESH_RATE);
}
var unit = Util.magnitudeOfBytes(data.limit);
var usage = unit === 'GB'? Util.bytesToGigabytes(data.usage):
Util.bytesToMegabytes(data.usage);
var limit = unit === 'GB'? Util.bytesToGigabytes(data.limit):
Util.bytesToMegabytes(data.limit);
var $limit = $('<span>', {'class': 'cryptpad-limit-bar'}).appendTo($container);
var quota = usage/limit;
var width = Math.floor(Math.min(quota, 1)*200); // the bar is 200px width
var $usage = $('<span>', {'class': 'usage'}).css('width', width+'px');
if (Config.noSubscriptionButton !== true &&
(quota >= 0.8 || alwaysDisplayUpgrade) &&
data.plan !== "power")
{
var origin = encodeURIComponent(window.location.hostname);
var $upgradeLink = $('<a>', {
href: "https://accounts.cryptpad.fr/#!on=" + origin,
rel: "noreferrer noopener",
target: "_blank",
}).appendTo($container);
$('<button>', {
'class': 'upgrade buttonSuccess',
title: Messages.upgradeTitle
}).text(Messages.upgrade).appendTo($upgradeLink);
}
var prettyUsage;
var prettyLimit;
if (unit === 'GB') {
prettyUsage = Messages._getKey('formattedGB', [usage]);
prettyLimit = Messages._getKey('formattedGB', [limit]);
} else {
prettyUsage = Messages._getKey('formattedMB', [usage]);
prettyLimit = Messages._getKey('formattedMB', [limit]);
}
if (quota < 0.8) { $usage.addClass('normal'); }
else if (quota < 1) { $usage.addClass('warning'); }
else { $usage.addClass('above'); }
var $text = $('<span>', {'class': 'usageText'});
$text.text(usage + ' / ' + prettyLimit);
$limit.append($usage).append($text);
window.setTimeout(function () {
common.isOverPinLimit(todo);
}, LIMIT_REFRESH_RATE);
cb(err, $container);
};
common.isOverPinLimit(todo);
};
common.createButton = function (type, rightside, data, callback) {
var button;
var size = "17px";
@@ -816,6 +931,7 @@ define([
common.addTemplate(makePad(href, title));
whenRealtimeSyncs(getStore().getProxy().info.realtime, function () {
common.alert(Messages.templateSaved);
common.feedback('TEMPLATE_CREATED');
});
});
};
@@ -838,7 +954,8 @@ define([
if (callback) {
button.click(function() {
var href = window.location.href;
common.confirm(Messages.forgetPrompt, function (yes) {
var msg = isLoggedIn() ? Messages.forgetPrompt : Messages.fm_removePermanentlyDialog;
common.confirm(msg, function (yes) {
if (!yes) { return; }
common.forgetPad(href, function (err) {
if (err) {
@@ -857,7 +974,8 @@ define([
} else {
callback();
}
common.alert(Messages.movedToTrash, undefined, true);
var cMsg = isLoggedIn() ? Messages.movedToTrash : Messages.deleted;
common.alert(cMsg, undefined, true);
return;
});
});
@@ -1207,6 +1325,27 @@ define([
return $userAdmin;
};
var CRYPTPAD_VERSION = 'cryptpad-version';
var updateLocalVersion = function () {
// Check for CryptPad updates
var urlArgs = Config.requireConf ? Config.requireConf.urlArgs : null;
if (!urlArgs) { return; }
var arr = /ver=([0-9.]+)(-[0-9]*)?/.exec(urlArgs);
var ver = arr[1];
if (!ver) { return; }
var verArr = ver.split('.');
verArr[2] = 0;
if (verArr.length !== 3) { return; }
var stored = localStorage[CRYPTPAD_VERSION] || '0.0.0';
var storedArr = stored.split('.');
storedArr[2] = 0;
var shouldUpdate = parseInt(verArr[0]) > parseInt(storedArr[0]) ||
(parseInt(verArr[0]) === parseInt(storedArr[0]) &&
parseInt(verArr[1]) > parseInt(storedArr[1]));
if (!shouldUpdate) { return; }
common.alert(Messages._getKey('newVersion', [verArr.join('.')]), null, true);
localStorage[CRYPTPAD_VERSION] = ver;
};
common.ready = (function () {
var env = {};
@@ -1224,6 +1363,9 @@ define([
block--;
if (!block) {
initialized = true;
updateLocalVersion();
f(void 0, env);
}
};
@@ -1247,7 +1389,7 @@ define([
feedback("NO_PROXIES");
}
if (typeof(Array.isArray) !== 'function') {
if (/CRYPTPAD_SHIM/.test(Array.isArray.toString())) {
feedback("NO_ISARRAY");
}
@@ -1257,20 +1399,21 @@ define([
UI.Alertify.reset();
// Load the new pad when the hash has changed
var oldHash = document.location.hash.slice(1);
var oldHref = document.location.href;
window.onhashchange = function () {
var newHash = document.location.hash.slice(1);
var parsedOld = parseHash(oldHash);
var parsedNew = parseHash(newHash);
var newHref = document.location.href;
var parsedOld = parsePadUrl(oldHref).hashData;
var parsedNew = parsePadUrl(newHref).hashData;
if (parsedOld && parsedNew && (
parsedOld.channel !== parsedNew.channel
parsedOld.type !== parsedNew.type
|| parsedOld.channel !== parsedNew.channel
|| parsedOld.mode !== parsedNew.mode
|| parsedOld.key !== parsedNew.key)) {
document.location.reload();
return;
}
if (parsedNew) {
oldHash = newHash;
oldHref = newHref;
}
};
@@ -1287,6 +1430,14 @@ define([
console.log('RPC handshake complete');
rpc = common.rpc = env.rpc = call;
common.getPinLimit(function (e, limit, plan, note) {
if (e) { return void console.error(e); }
common.account.limit = limit;
localStorage.plan = common.account.plan = plan;
common.account.note = note;
cb();
});
common.arePinsSynced(function (err, yes) {
if (!yes) {
common.resetPins(function (err) {
@@ -1295,7 +1446,6 @@ define([
});
}
});
cb();
});
} else if (PINNING_ENABLED) {
console.log('not logged in. pads will not be pinned');

127
www/common/diffMarked.js Normal file
View File

@@ -0,0 +1,127 @@
define([
'jquery',
'/bower_components/marked/marked.min.js',
'/bower_components/diff-dom/diffDOM.js'
],function ($, Marked) {
var DiffMd = {};
var DiffDOM = window.diffDOM;
var renderer = new Marked.Renderer();
Marked.setOptions({
renderer: renderer
});
DiffMd.render = function (md) {
return Marked(md);
};
// Tasks list
var checkedTaskItemPtn = /^\s*\[x\]\s*/;
var uncheckedTaskItemPtn = /^\s*\[ \]\s*/;
renderer.listitem = function (text) {
var isCheckedTaskItem = checkedTaskItemPtn.test(text);
var isUncheckedTaskItem = uncheckedTaskItemPtn.test(text);
if (isCheckedTaskItem) {
text = text.replace(checkedTaskItemPtn,
'<i class="fa fa-check-square" aria-hidden="true"></i>&nbsp;') + '\n';
}
if (isUncheckedTaskItem) {
text = text.replace(uncheckedTaskItemPtn,
'<i class="fa fa-square-o" aria-hidden="true"></i>&nbsp;') + '\n';
}
var cls = (isCheckedTaskItem || isUncheckedTaskItem) ? ' class="todo-list-item"' : '';
return '<li'+ cls + '>' + text + '</li>\n';
};
var forbiddenTags = [
'SCRIPT',
'IFRAME',
'OBJECT',
'APPLET',
'VIDEO',
'AUDIO',
];
var unsafeTag = function (info) {
if (['addAttribute', 'modifyAttribute'].indexOf(info.diff.action) !== -1) {
if (/^on/.test(info.diff.name)) {
console.log("Rejecting forbidden element attribute with name", info.diff.name);
return true;
}
}
if (['addElement', 'replaceElement'].indexOf(info.diff.action) !== -1) {
var msg = "Rejecting forbidden tag of type (%s)";
if (info.diff.element && forbiddenTags.indexOf(info.diff.element.nodeName) !== -1) {
console.log(msg, info.diff.element.nodeName);
return true;
} else if (info.diff.newValue && forbiddenTags.indexOf(info.diff.newValue.nodeName) !== -1) {
console.log("Replacing restricted element type (%s) with PRE", info.diff.newValue.nodeName);
info.diff.newValue.nodeName = 'PRE';
}
}
};
var slice = function (coll) {
return Array.prototype.slice.call(coll);
};
/* remove listeners from the DOM */
var removeListeners = function (root) {
slice(root.attributes).map(function (attr) {
if (/^on/.test(attr.name)) {
root.attributes.removeNamedItem(attr.name);
}
});
// all the way down
slice(root.children).forEach(removeListeners);
};
var domFromHTML = function (html) {
var Dom = new DOMParser().parseFromString(html, "text/html");
removeListeners(Dom.body);
return Dom;
};
var DD = new DiffDOM({
preDiffApply: function (info) {
if (unsafeTag(info)) { return true; }
}
});
var makeDiff = function (A, B, id) {
var Err;
var Els = [A, B].map(function (frag) {
if (typeof(frag) === 'object') {
if (!frag || (frag && !frag.body)) {
Err = "No body";
return;
}
var els = frag.body.querySelectorAll('#'+id);
if (els.length) {
return els[0];
}
}
Err = 'No candidate found';
});
if (Err) { return Err; }
var patch = DD.diff(Els[0], Els[1]);
return patch;
};
DiffMd.apply = function (newHtml, $content) {
var id = $content.attr('id');
if (!id) { throw new Error("The element must have a valid id"); }
var $div = $('<div>', {id: id}).append(newHtml);
var Dom = domFromHTML($('<div>').append($div).html());
var oldDom = domFromHTML($content[0].outerHTML);
var patch = makeDiff(oldDom, Dom, id);
if (typeof(patch) === 'string') {
throw new Error(patch);
} else {
DD.apply($content[0], patch);
}
};
return DiffMd;
});

View File

@@ -173,6 +173,10 @@ define([
proxy[tokenKey] = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER);
}
// copy User_hash into sessionStorage because cross-domain iframes
// on safari replaces localStorage with sessionStorage or something
if (sessionStorage) { sessionStorage.setItem('User_hash', localStorage.getItem('User_hash')); }
var localToken = tryParsing(localStorage.getItem(tokenKey));
if (localToken === null) {
// if that number hasn't been set to localStorage, do so.
@@ -222,7 +226,7 @@ define([
if (!hash) {
throw new Error('[Store.init] Unable to find or create a drive hash. Aborting...');
}
var secret = Cryptpad.getSecrets(hash);
var secret = Cryptpad.getSecrets('drive', hash);
var listmapConfig = {
data: {},
websocketURL: Cryptpad.getWebsocketURL(),

File diff suppressed because one or more lines are too long

View File

@@ -188,6 +188,10 @@ define([
newRecentPads.push(data);
}
});
if (!proxy.FS_hashes || !Array.isArray(proxy.FS_hashes)) {
proxy.FS_hashes = [];
}
proxy.FS_hashes.push(localStorage.FS_hash);
}
if (typeof(cb) === "function") { cb(); }
};

View File

@@ -77,6 +77,13 @@ define([
return;
}
rpc.send('RESET', channels, function (e, response) {
if (e) {
return void cb(e);
}
if (!response.length) {
console.log(response);
return void cb('INVALID_RESPONSE');
}
cb(e, response[0]);
});
};
@@ -121,6 +128,63 @@ define([
});
};
// Update the limit value for all the users and return the limit for your publicKey
exp.updatePinLimits = function (cb) {
rpc.send('UPDATE_LIMITS', undefined, function (e, response) {
if (e) { return void cb(e); }
if (response && response.length && typeof(response[0]) === "number") {
cb (void 0, response[0], response[1], response[2]);
} else {
cb('INVALID_RESPONSE');
}
});
};
// Get the storage limit associated with your publicKey
exp.getLimit = function (cb) {
rpc.send('GET_LIMIT', undefined, function (e, response) {
if (e) { return void cb(e); }
if (response && response.length && typeof(response[0]) === "number") {
cb (void 0, response[0], response[1], response[2]);
} else {
cb('INVALID_RESPONSE');
}
});
};
exp.uploadComplete = function (cb) {
rpc.send('UPLOAD_COMPLETE', null, function (e, res) {
if (e) { return void cb(e); }
var id = res[0];
if (typeof(id) !== 'string') {
return void cb('INVALID_ID');
}
cb(void 0, id);
});
};
exp.uploadStatus = function (size, cb) {
if (typeof(size) !== 'number') {
return void window.setTimeout(function () {
cb('INVALID_SIZE');
});
}
rpc.send('UPLOAD_STATUS', size, function (e, res) {
if (e) { return void cb(e); }
var pending = res[0];
if (typeof(pending) !== 'boolean') {
return void cb('INVALID_RESPONSE');
}
cb(void 0, pending);
});
};
exp.uploadCancel = function (cb) {
rpc.send('UPLOAD_CANCEL', void 0, function (e) {
if (e) { return void cb(e); }
cb();
});
};
cb(e, exp);
});
};

View File

@@ -129,6 +129,24 @@ types of messages:
return sendMsg(ctx, data, cb);
};
send.unauthenticated = function (type, msg, cb) {
if (!ctx.connected) {
return void window.setTimeout(function () {
cb('DISCONNECTED');
});
}
// construct an unsigned message
var data = [null, edPublicKey, null, type, msg];
if (ctx.cookie && ctx.cookie.join) {
data[2] = ctx.cookie.join('|');
} else {
data[2] = ctx.cookie;
}
return sendMsg(ctx, data, cb);
};
network.on('message', function (msg) {
onMsg(ctx, msg);
});

75
www/common/test.js Normal file
View File

@@ -0,0 +1,75 @@
define([], function () {
var out = function () { };
out.passed = out.failed = out;
if (window.location.hash.indexOf("?test=test") > -1) {
var cpt = window.__CRYPTPAD_TEST__ = {
data: [],
getData: function () {
var data = JSON.stringify(cpt.data);
cpt.data = [];
return data;
}
};
// jshint -W103
var errProto = (new Error()).__proto__;
var doLog = function (o) {
var s;
if (typeof(o) === 'object' && o.__proto__ === errProto) {
s = JSON.stringify([ o.message, o.stack ]);
} else if (typeof(s) !== 'string') {
try {
s = JSON.stringify(o);
} catch (e) {
s = String(o);
}
}
var out = [s];
try { throw new Error(); } catch (e) { out.push(e.stack.split('\n')[3]); }
cpt.data.push({ type: 'log', val: out.join('') });
};
window.console._error = window.console.error;
window.console._log = window.console.log;
window.console.error = function (e) { window.console._error(e); doLog(e); };
window.console.log = function (l) { window.console._log(l); doLog(l); };
window.onerror = function (msg, url, lineNo, columnNo, e) {
cpt.data.push({
type: 'report',
val: 'failed',
error: {
message: e ? e.message : msg,
stack: e ? e.stack : (url + ":" + lineNo)
}
});
};
require.onError = function (e) {
cpt.data.push({
type: 'report',
val: 'failed',
error: { message: e.message, stack: e.stack }
});
};
out = function (f) { f(); };
out.testing = true;
out.passed = function () {
cpt.data.push({
type: 'report',
val: 'passed'
});
};
out.failed = function (reason) {
var e;
try { throw new Error(reason); } catch (err) { e = err; }
cpt.data.push({
type: 'report',
val: 'failed',
error: { message: e.message, stack: e.stack }
});
};
} else {
out.testing = false;
}
return out;
});

View File

@@ -500,8 +500,12 @@ define([
var todo = function (e, overLimit) {
if (e) { return void console.error("Unable to get the pinned usage"); }
if (overLimit) {
var message = Messages.pinLimitReachedAlert;
if (ApiConfig.noSubscriptionButton === true) {
message = Messages.pinLimitReachedAlertNoAccounts;
}
$limit.show().click(function () {
Cryptpad.alert(Messages.pinLimitReachedAlert, null, true);
Cryptpad.alert(message, null, true);
});
}
};

View File

@@ -10,7 +10,7 @@ define([
constants: {},
};
var SPINNER_DISAPPEAR_TIME = 3000;
var SPINNER_DISAPPEAR_TIME = 1000;
// Toolbar parts
var TOOLBAR_CLS = Bar.constants.toolbar = 'cryptpad-toolbar';
@@ -33,6 +33,7 @@ define([
var LIMIT_CLS = Bar.constants.lag = 'cryptpad-limit';
var TITLE_CLS = Bar.constants.title = "cryptpad-title";
var NEWPAD_CLS = Bar.constants.newpad = "cryptpad-newpad";
var UPGRADE_CLS = Bar.constants.upgrade = "cryptpad-upgrade";
// User admin menu
var USERADMIN_CLS = Bar.constants.user = 'cryptpad-user-dropdown';
@@ -70,6 +71,7 @@ define([
var $userContainer = $('<span>', {
'class': USER_CLS
}).appendTo($topContainer);
$('<button>', {'class': UPGRADE_CLS + ' buttonSuccess'}).hide().appendTo($userContainer);
$('<span>', {'class': SPINNER_CLS}).hide().appendTo($userContainer);
$('<span>', {'class': STATE_CLS}).hide().appendTo($userContainer);
$('<span>', {'class': LAG_CLS}).hide().appendTo($userContainer);
@@ -367,7 +369,7 @@ define([
return "Loading share button";
};
var createFileShare = function () {
var createFileShare = function (toolbar) {
if (!window.location.hash) {
throw new Error("Unable to display the share button: hash required in the URL");
}
@@ -380,6 +382,7 @@ define([
if (success) { Cryptpad.log(Messages.shareSuccess); }
});
toolbar.$leftside.append($button);
return $button;
};
@@ -594,7 +597,6 @@ define([
'class': 'synced fa fa-check',
title: Messages.synced
}).appendTo($spin);
toolbar.$userAdmin.prepend($spin);
if (config.realtime) {
config.realtime.onPatch(ks(toolbar, config));
config.realtime.onMessage(ks(toolbar, config, true));
@@ -615,8 +617,12 @@ define([
var todo = function (e, overLimit) {
if (e) { return void console.error("Unable to get the pinned usage"); }
if (overLimit) {
var key = 'pinLimitReachedAlert';
if (ApiConfig.noSubscriptionButton === true) {
key = 'pinLimitReachedAlertNoAccounts';
}
$limit.show().click(function () {
Cryptpad.alert(Messages.pinLimitReachedAlert, null, true);
Cryptpad.alert(Messages._getKey(key, [encodeURIComponent(window.location.hostname)]), null, true);
});
}
};
@@ -630,6 +636,8 @@ define([
var pads_options = [];
Config.availablePadTypes.forEach(function (p) {
if (p === 'drive') { return; }
if (!Cryptpad.isLoggedIn() && Config.registeredOnlyTypes &&
Config.registeredOnlyTypes.indexOf(p) !== -1) { return; }
pads_options.push({
tag: 'a',
attributes: {
@@ -691,6 +699,33 @@ define([
return $userAdmin;
};
var createUpgrade = function (toolbar) {
if (ApiConfig.removeDonateButton) { return; }
if (Cryptpad.account.plan) { return; }
var text;
var feedback;
var url;
if (ApiConfig.allowSubscriptions && Cryptpad.isLoggedIn()) {
text = Messages.upgradeAccount;
feedback = "UPGRADE_ACCOUNT";
url = Cryptpad.upgradeURL;
} else {
text = Messages.supportCryptpad;
feedback = "SUPPORT_CRYPTPAD";
url = Cryptpad.donateURL;
}
var $upgrade = toolbar.$top.find('.' + UPGRADE_CLS).attr({
'title': Messages.supportCryptpad
}).text(text).show()
.click(function () {
Cryptpad.feedback(feedback);
window.open(url,'_blank');
});
return $upgrade;
};
// Events
var initClickEvents = function (toolbar, config) {
var removeDropdowns = function () {
@@ -848,11 +883,11 @@ define([
tb['spinner'] = createSpinner;
tb['state'] = createState;
tb['limit'] = createLimit;
tb['upgrade'] = createUpgrade;
tb['newpad'] = createNewPad;
tb['useradmin'] = createUserAdmin;
var addElement = function (arr, additionnalCfg, init) {
var addElement = toolbar.addElement = function (arr, additionnalCfg, init) {
if (typeof additionnalCfg === "object") { $.extend(true, config, additionnalCfg); }
arr.forEach(function (el) {
if (typeof el !== "string" || !el.trim()) { return; }

View File

@@ -61,9 +61,7 @@ define([
if (!isFile(element)) { return false; }
var parsed = Cryptpad.parsePadUrl(element);
if (!parsed) { return false; }
var hash = parsed.hash;
var pHash = Cryptpad.parseHash(hash);
if (pHash && !pHash.mode) { return; }
var pHash = parsed.hashData;
return pHash && pHash.mode === 'view';
};
@@ -541,6 +539,7 @@ define([
// ADD
var add = exp.add = function (data, path) {
if (!Cryptpad.isLoggedIn()) { return; }
if (!data || typeof(data) !== "object") { return; }
var href = data.href;
var name = data.title;
@@ -576,7 +575,10 @@ define([
atime: +new Date(),
ctime: +new Date()
}, function (err) {
if (err) { return void cb(err); }
if (err) {
logError(err);
return void cb(err);
}
parentEl[fileName] = href;
var newPath = filePath.slice();
newPath.push(fileName);
@@ -598,6 +600,18 @@ define([
// FORGET (move with href not path)
exp.forget = function (href) {
if (!Cryptpad.isLoggedIn()) {
// delete permanently
var data = getFileData(href);
if (data) {
var i = find([FILES_DATA]).indexOf(data);
if (i !== -1) {
exp.removePadAttribute(href);
spliceFileData(i);
}
}
return;
}
var paths = findFile(href);
move(paths, [TRASH]);
};
@@ -605,7 +619,7 @@ define([
// DELETE
// Permanently delete multiple files at once using a list of paths
// NOTE: We have to be careful when removing elements from arrays (trash root, unsorted or template)
var removePadAttribute = function (f) {
var removePadAttribute = exp.removePadAttribute = function (f) {
if (typeof(f) !== 'string') {
console.error("Can't find pad attribute for an undefined pad");
return;
@@ -621,7 +635,7 @@ define([
};
var checkDeletedFiles = function () {
// Nothing in FILES_DATA for workgroups
if (workgroup) { return; }
if (workgroup || !Cryptpad.isLoggedIn()) { return; }
var filesList = getFiles([ROOT, 'hrefArray', TRASH]);
var toRemove = [];
@@ -656,6 +670,23 @@ define([
var hrefPaths = paths.filter(function(x) { return isPathIn(x, ['hrefArray']); });
var rootPaths = paths.filter(function(x) { return isPathIn(x, [ROOT]); });
var trashPaths = paths.filter(function(x) { return isPathIn(x, [TRASH]); });
var allFilesPaths = paths.filter(function(x) { return isPathIn(x, [FILES_DATA]); });
if (!Cryptpad.isLoggedIn()) {
var toSplice = [];
allFilesPaths.forEach(function (path) {
var el = find(path);
toSplice.push(el);
});
toSplice.forEach(function (el) {
var i = find([FILES_DATA]).indexOf(el);
if (i === -1) { return; }
removePadAttribute(el.href);
console.log(el.href);
spliceFileData(i);
});
return;
}
var hrefs = [];
hrefPaths.forEach(function (path) {
@@ -884,7 +915,7 @@ define([
toClean.push(el);
return;
}
if (rootFiles.indexOf(el.href) === -1) {
if (Cryptpad.isLoggedIn() && rootFiles.indexOf(el.href) === -1) {
debug("An element in filesData was not in ROOT, TEMPLATE or TRASH.", el);
var name = el.title || NEW_FILE_NAME;
var newName = getAvailableName(root, name);