Merge branch 'soon' of github.com:xwiki-labs/cryptpad into soon
This commit is contained in:
@@ -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')]);
|
||||
});
|
||||
|
||||
300
www/common/common-codemirror.js
Normal file
300
www/common/common-codemirror.js
Normal 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;
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
51
www/common/common-metadata.js
Normal file
51
www/common/common-metadata.js
Normal 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;
|
||||
});
|
||||
|
||||
|
||||
84
www/common/common-title.js
Normal file
84
www/common/common-title.js
Normal 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;
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
127
www/common/diffMarked.js
Normal 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> ') + '\n';
|
||||
}
|
||||
if (isUncheckedTaskItem) {
|
||||
text = text.replace(uncheckedTaskItemPtn,
|
||||
'<i class="fa fa-square-o" aria-hidden="true"></i> ') + '\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;
|
||||
});
|
||||
|
||||
@@ -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
@@ -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(); }
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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
75
www/common/test.js
Normal 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;
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user