Merge branch 'staging' of github.com:xwiki-labs/cryptpad into staging
commit
d1beb6493b
@ -1,54 +0,0 @@
|
|||||||
define([
|
|
||||||
'jquery',
|
|
||||||
'/customize/application_config.js',
|
|
||||||
'/common/cryptpad-common.js',
|
|
||||||
'/api/config',
|
|
||||||
], function ($, Config, Cryptpad, ApiConfig) {
|
|
||||||
|
|
||||||
window.APP = {
|
|
||||||
Cryptpad: Cryptpad,
|
|
||||||
};
|
|
||||||
|
|
||||||
var Messages = Cryptpad.Messages;
|
|
||||||
|
|
||||||
$(function () {
|
|
||||||
// Language selector
|
|
||||||
var $sel = $('#language-selector');
|
|
||||||
Cryptpad.createLanguageSelector(undefined, $sel);
|
|
||||||
$sel.find('button').addClass('btn').addClass('btn-secondary');
|
|
||||||
$sel.show();
|
|
||||||
|
|
||||||
var $upgrade = $('#upgrade');
|
|
||||||
|
|
||||||
var showUpgrade = function (text, feedback, url) {
|
|
||||||
if (ApiConfig.removeDonateButton) { return; }
|
|
||||||
if (localStorage.plan) { return; }
|
|
||||||
if (!text) { return; }
|
|
||||||
$upgrade.text(text).show();
|
|
||||||
$upgrade.click(function () {
|
|
||||||
Cryptpad.feedback(feedback);
|
|
||||||
window.open(url,'_blank');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// User admin menu
|
|
||||||
var $userMenu = $('#user-menu');
|
|
||||||
var userMenuCfg = {
|
|
||||||
$initBlock: $userMenu,
|
|
||||||
'static': true
|
|
||||||
};
|
|
||||||
var $userAdmin = Cryptpad.createUserAdminMenu(userMenuCfg);
|
|
||||||
$userAdmin.find('button').addClass('btn').addClass('btn-secondary');
|
|
||||||
|
|
||||||
$(window).click(function () {
|
|
||||||
$('.cp-dropdown-content').hide();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Cryptpad.isLoggedIn() && ApiConfig.allowSubscriptions) {
|
|
||||||
showUpgrade(Messages.upgradeAccount, "HOME_UPGRADE_ACCOUNT", Cryptpad.upgradeURL);
|
|
||||||
} else {
|
|
||||||
showUpgrade(Messages.supportCryptpad, "HOME_SUPPORT_CRYPTPAD", Cryptpad.donateURL);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,308 +0,0 @@
|
|||||||
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 (ifrw, Cryptpad, defaultMode, CMeditor) {
|
|
||||||
var exp = {};
|
|
||||||
var Messages = Cryptpad.Messages;
|
|
||||||
|
|
||||||
var CodeMirror = exp.CodeMirror = CMeditor;
|
|
||||||
CodeMirror.modeURL = "cm/mode/%N/%N";
|
|
||||||
|
|
||||||
var $pad = $('#pad-iframe');
|
|
||||||
var $textarea = exp.$textarea = $('#editor1');
|
|
||||||
if (!$textarea.length) { $textarea = exp.$textarea = $pad.contents().find('#editor1'); }
|
|
||||||
|
|
||||||
var Title;
|
|
||||||
var onLocal = function () {};
|
|
||||||
var $rightside;
|
|
||||||
var $drawer;
|
|
||||||
exp.init = function (local, title, toolbar) {
|
|
||||||
if (typeof local === "function") {
|
|
||||||
onLocal = local;
|
|
||||||
}
|
|
||||||
Title = title;
|
|
||||||
$rightside = toolbar.$rightside;
|
|
||||||
$drawer = toolbar.$drawer;
|
|
||||||
};
|
|
||||||
|
|
||||||
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: defaultMode || "javascript",
|
|
||||||
readOnly: true
|
|
||||||
});
|
|
||||||
editor.setValue(Messages.codeInitialState);
|
|
||||||
|
|
||||||
var setMode = exp.setMode = function (mode, cb) {
|
|
||||||
exp.highlightMode = mode;
|
|
||||||
if (mode !== "text") {
|
|
||||||
CMeditor.autoLoadMode(editor, mode);
|
|
||||||
}
|
|
||||||
editor.setOption('mode', mode);
|
|
||||||
if (exp.$language) {
|
|
||||||
var name = exp.$language.find('a[data-value="' + mode + '"]').text() || undefined;
|
|
||||||
name = name ? Messages.languageButton + ' ('+name+')' : Messages.languageButton;
|
|
||||||
exp.$language.setValue(mode, 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) {
|
|
||||||
var name = theme || undefined;
|
|
||||||
name = name ? Messages.themeButton + ' ('+theme+')' : Messages.themeButton;
|
|
||||||
$select.setValue(theme, name);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}());
|
|
||||||
|
|
||||||
exp.getHeadingText = function () {
|
|
||||||
var lines = editor.getValue().split(/\n/);
|
|
||||||
|
|
||||||
var text = '';
|
|
||||||
lines.some(function (line) {
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
feedback: 'CODE_LANGUAGE',
|
|
||||||
};
|
|
||||||
var $block = exp.$language = Cryptpad.createDropdown(dropdownConfig);
|
|
||||||
$block.find('button').attr('title', Messages.languageButtonTitle);
|
|
||||||
$block.find('a').click(function () {
|
|
||||||
setMode($(this).attr('data-value'), onModeChanged);
|
|
||||||
onLocal();
|
|
||||||
});
|
|
||||||
|
|
||||||
if ($drawer) { $drawer.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,
|
|
||||||
feedback: 'CODE_THEME',
|
|
||||||
};
|
|
||||||
var $block = exp.$theme = Cryptpad.createDropdown(dropdownConfig);
|
|
||||||
$block.find('button').attr('title', Messages.themeButtonTitle);
|
|
||||||
|
|
||||||
setTheme(lastTheme, $block);
|
|
||||||
|
|
||||||
$block.find('a').click(function () {
|
|
||||||
var theme = $(this).attr('data-value');
|
|
||||||
setTheme(theme, $block);
|
|
||||||
localStorage.setItem(themeKey, theme);
|
|
||||||
});
|
|
||||||
|
|
||||||
if ($drawer) { $drawer.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]);
|
|
||||||
mode = mode && mode.mode || null;
|
|
||||||
}
|
|
||||||
} 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,366 +0,0 @@
|
|||||||
define([
|
|
||||||
'jquery',
|
|
||||||
'/file/file-crypto.js',
|
|
||||||
'/common/common-thumbnail.js',
|
|
||||||
'/bower_components/tweetnacl/nacl-fast.min.js',
|
|
||||||
], function ($, FileCrypto, Thumb) {
|
|
||||||
var Nacl = window.nacl;
|
|
||||||
var module = {};
|
|
||||||
|
|
||||||
var blobToArrayBuffer = module.blobToArrayBuffer = function (blob, cb) {
|
|
||||||
var reader = new FileReader();
|
|
||||||
reader.onloadend = function () {
|
|
||||||
cb(void 0, this.result);
|
|
||||||
};
|
|
||||||
reader.readAsArrayBuffer(blob);
|
|
||||||
};
|
|
||||||
|
|
||||||
var arrayBufferToString = function (AB) {
|
|
||||||
try {
|
|
||||||
return Nacl.util.encodeBase64(new Uint8Array(AB));
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.upload = function (file, noStore, common, updateProgress, onComplete, onError, onPending) {
|
|
||||||
var u8 = file.blob; // This is not a blob but a uint8array
|
|
||||||
var metadata = file.metadata;
|
|
||||||
|
|
||||||
// if it exists, path contains the new pad location in the drive
|
|
||||||
var path = file.path;
|
|
||||||
|
|
||||||
var key = Nacl.randomBytes(32);
|
|
||||||
var next = FileCrypto.encrypt(u8, metadata, key);
|
|
||||||
|
|
||||||
var estimate = FileCrypto.computeEncryptedSize(u8.length, metadata);
|
|
||||||
|
|
||||||
var sendChunk = function (box, cb) {
|
|
||||||
var enc = Nacl.util.encodeBase64(box);
|
|
||||||
common.rpc.send.unauthenticated('UPLOAD', enc, function (e, msg) {
|
|
||||||
cb(e, msg);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var actual = 0;
|
|
||||||
var again = function (err, box) {
|
|
||||||
if (err) { throw new Error(err); }
|
|
||||||
if (box) {
|
|
||||||
actual += box.length;
|
|
||||||
var progressValue = (actual / estimate * 100);
|
|
||||||
updateProgress(progressValue);
|
|
||||||
|
|
||||||
return void sendChunk(box, function (e) {
|
|
||||||
if (e) { return console.error(e); }
|
|
||||||
next(again);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actual !== estimate) {
|
|
||||||
console.error('Estimated size does not match actual size');
|
|
||||||
}
|
|
||||||
|
|
||||||
// if not box then done
|
|
||||||
common.uploadComplete(function (e, id) {
|
|
||||||
if (e) { return void console.error(e); }
|
|
||||||
var uri = ['', 'blob', id.slice(0,2), id].join('/');
|
|
||||||
console.log("encrypted blob is now available as %s", uri);
|
|
||||||
|
|
||||||
var b64Key = Nacl.util.encodeBase64(key);
|
|
||||||
|
|
||||||
var hash = common.getFileHashFromKeys(id, b64Key);
|
|
||||||
var href = '/file/#' + hash;
|
|
||||||
|
|
||||||
var title = metadata.name;
|
|
||||||
|
|
||||||
if (noStore) { return void onComplete(href); }
|
|
||||||
|
|
||||||
common.initialPath = path;
|
|
||||||
common.renamePad(title || "", href, function (err) {
|
|
||||||
if (err) { return void console.error(err); }
|
|
||||||
onComplete(href);
|
|
||||||
common.setPadAttribute('fileType', metadata.type, null, href);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
common.uploadStatus(estimate, function (e, pending) {
|
|
||||||
if (e) {
|
|
||||||
console.error(e);
|
|
||||||
onError(e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pending) {
|
|
||||||
return void onPending(function () {
|
|
||||||
// if the user wants to cancel the pending upload to execute that one
|
|
||||||
common.uploadCancel(function (e, res) {
|
|
||||||
if (e) {
|
|
||||||
return void console.error(e);
|
|
||||||
}
|
|
||||||
console.log(res);
|
|
||||||
next(again);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
next(again);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.create = function (common, config) {
|
|
||||||
var File = {};
|
|
||||||
|
|
||||||
var Messages = common.Messages;
|
|
||||||
|
|
||||||
var queue = File.queue = {
|
|
||||||
queue: [],
|
|
||||||
inProgress: false
|
|
||||||
};
|
|
||||||
|
|
||||||
var uid = function () {
|
|
||||||
return 'file-' + String(Math.random()).substring(2);
|
|
||||||
};
|
|
||||||
|
|
||||||
var $table = File.$table = $('<table>', { id: 'uploadStatus' });
|
|
||||||
var $thead = $('<tr>').appendTo($table);
|
|
||||||
$('<td>').text(Messages.upload_name).appendTo($thead);
|
|
||||||
$('<td>').text(Messages.upload_size).appendTo($thead);
|
|
||||||
$('<td>').text(Messages.upload_progress).appendTo($thead);
|
|
||||||
$('<td>').text(Messages.cancel).appendTo($thead);
|
|
||||||
|
|
||||||
var createTableContainer = function ($body) {
|
|
||||||
File.$container = $('<div>', { id: 'uploadStatusContainer' }).append($table).appendTo($body);
|
|
||||||
return File.$container;
|
|
||||||
};
|
|
||||||
|
|
||||||
var getData = function (file, href) {
|
|
||||||
var data = {};
|
|
||||||
|
|
||||||
data.name = file.metadata.name;
|
|
||||||
data.url = href;
|
|
||||||
if (file.metadata.type.slice(0,6) === 'image/') {
|
|
||||||
data.mediatag = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
var upload = function (file) {
|
|
||||||
var blob = file.blob; // This is not a blob but an array buffer
|
|
||||||
var u8 = new Uint8Array(blob);
|
|
||||||
var metadata = file.metadata;
|
|
||||||
var id = file.id;
|
|
||||||
if (queue.inProgress) { return; }
|
|
||||||
queue.inProgress = true;
|
|
||||||
|
|
||||||
var $row = $table.find('tr[id="'+id+'"]');
|
|
||||||
|
|
||||||
$row.find('.upCancel').html('-');
|
|
||||||
var $pv = $row.find('.progressValue');
|
|
||||||
var $pb = $row.find('.progressContainer');
|
|
||||||
var $pc = $row.find('.upProgress');
|
|
||||||
var $link = $row.find('.upLink');
|
|
||||||
|
|
||||||
var updateProgress = function (progressValue) {
|
|
||||||
$pv.text(Math.round(progressValue*100)/100 + '%');
|
|
||||||
$pb.css({
|
|
||||||
width: (progressValue/100)*$pc.width()+'px'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var onComplete = function (href) {
|
|
||||||
$link.attr('href', href)
|
|
||||||
.click(function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
window.open($link.attr('href'), '_blank');
|
|
||||||
});
|
|
||||||
var title = metadata.name;
|
|
||||||
common.log(Messages._getKey('upload_success', [title]));
|
|
||||||
common.prepareFeedback('upload')();
|
|
||||||
|
|
||||||
if (config.onUploaded) {
|
|
||||||
var data = getData(file, href);
|
|
||||||
config.onUploaded(file.dropEvent, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
queue.inProgress = false;
|
|
||||||
queue.next();
|
|
||||||
};
|
|
||||||
|
|
||||||
var onError = function (e) {
|
|
||||||
queue.inProgress = false;
|
|
||||||
queue.next();
|
|
||||||
if (e === 'TOO_LARGE') {
|
|
||||||
// TODO update table to say too big?
|
|
||||||
return void common.alert(Messages.upload_tooLarge);
|
|
||||||
}
|
|
||||||
if (e === 'NOT_ENOUGH_SPACE') {
|
|
||||||
// TODO update table to say not enough space?
|
|
||||||
return void common.alert(Messages.upload_notEnoughSpace);
|
|
||||||
}
|
|
||||||
console.error(e);
|
|
||||||
return void common.alert(Messages.upload_serverError);
|
|
||||||
};
|
|
||||||
|
|
||||||
var onPending = function (cb) {
|
|
||||||
common.confirm(Messages.upload_uploadPending, function (yes) {
|
|
||||||
if (!yes) { return; }
|
|
||||||
cb();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
file.blob = u8;
|
|
||||||
module.upload(file, config.noStore, common, updateProgress, onComplete, onError, onPending);
|
|
||||||
};
|
|
||||||
|
|
||||||
var prettySize = function (bytes) {
|
|
||||||
var kB = common.bytesToKilobytes(bytes);
|
|
||||||
if (kB < 1024) { return kB + Messages.KB; }
|
|
||||||
var mB = common.bytesToMegabytes(bytes);
|
|
||||||
return mB + Messages.MB;
|
|
||||||
};
|
|
||||||
|
|
||||||
queue.next = function () {
|
|
||||||
if (queue.queue.length === 0) {
|
|
||||||
queue.to = window.setTimeout(function () {
|
|
||||||
if (config.keepTable) { return; }
|
|
||||||
File.$container.fadeOut();
|
|
||||||
}, 3000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (queue.inProgress) { return; }
|
|
||||||
File.$container.show();
|
|
||||||
var file = queue.queue.shift();
|
|
||||||
upload(file);
|
|
||||||
};
|
|
||||||
queue.push = function (obj) {
|
|
||||||
var id = uid();
|
|
||||||
obj.id = id;
|
|
||||||
queue.queue.push(obj);
|
|
||||||
|
|
||||||
$table.show();
|
|
||||||
var estimate = FileCrypto.computeEncryptedSize(obj.blob.byteLength, obj.metadata);
|
|
||||||
|
|
||||||
var $progressBar = $('<div>', {'class':'progressContainer'});
|
|
||||||
var $progressValue = $('<span>', {'class':'progressValue'}).text(Messages.upload_pending);
|
|
||||||
|
|
||||||
var $tr = $('<tr>', {id: id}).appendTo($table);
|
|
||||||
|
|
||||||
var $cancel = $('<span>', {'class': 'cancel fa fa-times'}).click(function () {
|
|
||||||
queue.queue = queue.queue.filter(function (el) { return el.id !== id; });
|
|
||||||
$cancel.remove();
|
|
||||||
$tr.find('.upCancel').text('-');
|
|
||||||
$tr.find('.progressValue').text(Messages.upload_cancelled);
|
|
||||||
});
|
|
||||||
|
|
||||||
var $link = $('<a>', {
|
|
||||||
'class': 'upLink',
|
|
||||||
'rel': 'noopener noreferrer'
|
|
||||||
}).text(obj.metadata.name);
|
|
||||||
|
|
||||||
$('<td>').append($link).appendTo($tr);
|
|
||||||
$('<td>').text(prettySize(estimate)).appendTo($tr);
|
|
||||||
$('<td>', {'class': 'upProgress'}).append($progressBar).append($progressValue).appendTo($tr);
|
|
||||||
$('<td>', {'class': 'upCancel'}).append($cancel).appendTo($tr);
|
|
||||||
|
|
||||||
queue.next();
|
|
||||||
};
|
|
||||||
|
|
||||||
var handleFile = File.handleFile = function (file, e, thumbnail) {
|
|
||||||
var thumb;
|
|
||||||
var file_arraybuffer;
|
|
||||||
var finish = function () {
|
|
||||||
var metadata = {
|
|
||||||
name: file.name,
|
|
||||||
type: file.type,
|
|
||||||
};
|
|
||||||
if (thumb) { metadata.thumbnail = thumb; }
|
|
||||||
queue.push({
|
|
||||||
blob: file_arraybuffer,
|
|
||||||
metadata: metadata,
|
|
||||||
dropEvent: e
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
blobToArrayBuffer(file, function (e, buffer) {
|
|
||||||
if (e) { console.error(e); }
|
|
||||||
file_arraybuffer = buffer;
|
|
||||||
if (thumbnail) { // there is already a thumbnail
|
|
||||||
return blobToArrayBuffer(thumbnail, function (e, buffer) {
|
|
||||||
if (e) { console.error(e); }
|
|
||||||
thumb = arrayBufferToString(buffer);
|
|
||||||
finish();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Thumb.isSupportedType(file.type)) { return finish(); }
|
|
||||||
// make a resized thumbnail from the image..
|
|
||||||
Thumb.fromBlob(file, function (e, thumb64) {
|
|
||||||
if (e) { console.error(e); }
|
|
||||||
if (!thumb64) { return finish(); }
|
|
||||||
thumb = thumb64;
|
|
||||||
finish();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var onFileDrop = File.onFileDrop = function (file, e) {
|
|
||||||
if (!common.isLoggedIn()) {
|
|
||||||
return common.alert(common.Messages.upload_mustLogin);
|
|
||||||
}
|
|
||||||
|
|
||||||
Array.prototype.slice.call(file).forEach(function (d) {
|
|
||||||
handleFile(d, e);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var createAreaHandlers = File.createDropArea = function ($area, $hoverArea) {
|
|
||||||
var counter = 0;
|
|
||||||
if (!$hoverArea) { $hoverArea = $area; }
|
|
||||||
if (!$area) { return; }
|
|
||||||
$hoverArea
|
|
||||||
.on('dragenter', function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
counter++;
|
|
||||||
$hoverArea.addClass('hovering');
|
|
||||||
})
|
|
||||||
.on('dragleave', function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
counter--;
|
|
||||||
if (counter <= 0) {
|
|
||||||
$hoverArea.removeClass('hovering');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$area
|
|
||||||
.on('drag dragstart dragend dragover drop dragenter dragleave', function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
})
|
|
||||||
.on('drop', function (e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
var dropped = e.originalEvent.dataTransfer.files;
|
|
||||||
counter = 0;
|
|
||||||
$hoverArea.removeClass('hovering');
|
|
||||||
onFileDrop(dropped, e);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var createUploader = function ($area, $hover, $body) {
|
|
||||||
if (!config.noHandlers) {
|
|
||||||
createAreaHandlers($area, null);
|
|
||||||
}
|
|
||||||
createTableContainer($body);
|
|
||||||
};
|
|
||||||
|
|
||||||
createUploader(config.dropArea, config.hoverArea, config.body);
|
|
||||||
|
|
||||||
return File;
|
|
||||||
};
|
|
||||||
|
|
||||||
return module;
|
|
||||||
});
|
|
@ -1,268 +0,0 @@
|
|||||||
define([
|
|
||||||
'jquery',
|
|
||||||
'/bower_components/chainpad-json-validator/json-ot.js',
|
|
||||||
'/bower_components/chainpad-crypto/crypto.js',
|
|
||||||
'/bower_components/chainpad/chainpad.dist.js',
|
|
||||||
], function ($, JsonOT, Crypto) {
|
|
||||||
var ChainPad = window.ChainPad;
|
|
||||||
var History = {};
|
|
||||||
|
|
||||||
var getStates = function (rt) {
|
|
||||||
var states = [];
|
|
||||||
var b = rt.getAuthBlock();
|
|
||||||
if (b) { states.unshift(b); }
|
|
||||||
while (b.getParent()) {
|
|
||||||
b = b.getParent();
|
|
||||||
states.unshift(b);
|
|
||||||
}
|
|
||||||
return states;
|
|
||||||
};
|
|
||||||
|
|
||||||
var loadHistory = function (config, common, cb) {
|
|
||||||
var network = common.getNetwork();
|
|
||||||
var hkn = network.historyKeeper;
|
|
||||||
|
|
||||||
var wcId = common.hrefToHexChannelId(config.href || window.location.href);
|
|
||||||
|
|
||||||
var createRealtime = function () {
|
|
||||||
return ChainPad.create({
|
|
||||||
userName: 'history',
|
|
||||||
initialState: '',
|
|
||||||
transformFunction: JsonOT.validate,
|
|
||||||
logLevel: 0,
|
|
||||||
noPrune: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
var realtime = createRealtime();
|
|
||||||
|
|
||||||
var parsed = config.href ? common.parsePadUrl(config.href) : {};
|
|
||||||
var secret = common.getSecrets(parsed.type, parsed.hash);
|
|
||||||
|
|
||||||
History.readOnly = 0;
|
|
||||||
if (!secret.keys) {
|
|
||||||
secret.keys = secret.key;
|
|
||||||
History.readOnly = 0;
|
|
||||||
}
|
|
||||||
else if (!secret.keys.validateKey) {
|
|
||||||
History.readOnly = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var crypto = Crypto.createEncryptor(secret.keys);
|
|
||||||
|
|
||||||
var to = window.setTimeout(function () {
|
|
||||||
cb('[GET_FULL_HISTORY_TIMEOUT]');
|
|
||||||
}, 30000);
|
|
||||||
|
|
||||||
var parse = function (msg) {
|
|
||||||
try {
|
|
||||||
return JSON.parse(msg);
|
|
||||||
} catch (e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var onMsg = function (msg) {
|
|
||||||
var parsed = parse(msg);
|
|
||||||
if (parsed[0] === 'FULL_HISTORY_END') {
|
|
||||||
console.log('END');
|
|
||||||
window.clearTimeout(to);
|
|
||||||
cb(null, realtime);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (parsed[0] !== 'FULL_HISTORY') { return; }
|
|
||||||
if (parsed[1] && parsed[1].validateKey) { // First message
|
|
||||||
secret.keys.validateKey = parsed[1].validateKey;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
msg = parsed[1][4];
|
|
||||||
if (msg) {
|
|
||||||
msg = msg.replace(/^cp\|/, '');
|
|
||||||
var decryptedMsg = crypto.decrypt(msg, secret.keys.validateKey);
|
|
||||||
realtime.message(decryptedMsg);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
network.on('message', function (msg) {
|
|
||||||
onMsg(msg);
|
|
||||||
});
|
|
||||||
|
|
||||||
network.sendto(hkn, JSON.stringify(['GET_FULL_HISTORY', wcId, secret.keys.validateKey]));
|
|
||||||
};
|
|
||||||
|
|
||||||
History.create = function (common, config) {
|
|
||||||
if (!config.$toolbar) { return void console.error("config.$toolbar is undefined");}
|
|
||||||
if (History.loading) { return void console.error("History is already being loaded..."); }
|
|
||||||
History.loading = true;
|
|
||||||
var $toolbar = config.$toolbar;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
var realtime;
|
|
||||||
|
|
||||||
var states = [];
|
|
||||||
var c = states.length - 1;
|
|
||||||
|
|
||||||
var $hist = $toolbar.find('.cryptpad-toolbar-history');
|
|
||||||
var $left = $toolbar.find('.cryptpad-toolbar-leftside');
|
|
||||||
var $right = $toolbar.find('.cryptpad-toolbar-rightside');
|
|
||||||
var $cke = $toolbar.find('.cke_toolbox_main');
|
|
||||||
|
|
||||||
$hist.html('').show();
|
|
||||||
$left.hide();
|
|
||||||
$right.hide();
|
|
||||||
$cke.hide();
|
|
||||||
|
|
||||||
common.spinner($hist).get().show();
|
|
||||||
|
|
||||||
var onUpdate;
|
|
||||||
|
|
||||||
var update = function () {
|
|
||||||
if (!realtime) { return []; }
|
|
||||||
states = getStates(realtime);
|
|
||||||
if (typeof onUpdate === "function") { onUpdate(); }
|
|
||||||
return states;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get the content of the selected version, and change the version number
|
|
||||||
var get = function (i) {
|
|
||||||
i = parseInt(i);
|
|
||||||
if (isNaN(i)) { return; }
|
|
||||||
if (i < 0) { i = 0; }
|
|
||||||
if (i > states.length - 1) { i = states.length - 1; }
|
|
||||||
var val = states[i].getContent().doc;
|
|
||||||
c = i;
|
|
||||||
if (typeof onUpdate === "function") { onUpdate(); }
|
|
||||||
$hist.find('.next, .previous').css('visibility', '');
|
|
||||||
if (c === states.length - 1) { $hist.find('.next').css('visibility', 'hidden'); }
|
|
||||||
if (c === 0) { $hist.find('.previous').css('visibility', 'hidden'); }
|
|
||||||
return val || '';
|
|
||||||
};
|
|
||||||
|
|
||||||
var getNext = function (step) {
|
|
||||||
return typeof step === "number" ? get(c + step) : get(c + 1);
|
|
||||||
};
|
|
||||||
var getPrevious = function (step) {
|
|
||||||
return typeof step === "number" ? get(c - step) : get(c - 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create the history toolbar
|
|
||||||
var display = function () {
|
|
||||||
$hist.html('');
|
|
||||||
var $prev =$('<button>', {
|
|
||||||
'class': 'previous fa fa-step-backward buttonPrimary',
|
|
||||||
title: Messages.history_prev
|
|
||||||
}).appendTo($hist);
|
|
||||||
var $nav = $('<div>', {'class': 'goto'}).appendTo($hist);
|
|
||||||
var $next = $('<button>', {
|
|
||||||
'class': 'next fa fa-step-forward buttonPrimary',
|
|
||||||
title: Messages.history_next
|
|
||||||
}).appendTo($hist);
|
|
||||||
|
|
||||||
$('<label>').text(Messages.history_version).appendTo($nav);
|
|
||||||
var $cur = $('<input>', {
|
|
||||||
'class' : 'gotoInput',
|
|
||||||
'type' : 'number',
|
|
||||||
'min' : '1',
|
|
||||||
'max' : states.length
|
|
||||||
}).val(c + 1).appendTo($nav).mousedown(function (e) {
|
|
||||||
// stopPropagation because the event would be cancelled by the dropdown menus
|
|
||||||
e.stopPropagation();
|
|
||||||
});
|
|
||||||
var $label2 = $('<label>').text(' / '+ states.length).appendTo($nav);
|
|
||||||
$('<br>').appendTo($nav);
|
|
||||||
var $close = $('<button>', {
|
|
||||||
'class':'closeHistory',
|
|
||||||
title: Messages.history_closeTitle
|
|
||||||
}).text(Messages.history_closeTitle).appendTo($nav);
|
|
||||||
var $rev = $('<button>', {
|
|
||||||
'class':'revertHistory buttonSuccess',
|
|
||||||
title: Messages.history_restoreTitle
|
|
||||||
}).text(Messages.history_restore).appendTo($nav);
|
|
||||||
if (History.readOnly) { $rev.hide(); }
|
|
||||||
|
|
||||||
onUpdate = function () {
|
|
||||||
$cur.attr('max', states.length);
|
|
||||||
$cur.val(c+1);
|
|
||||||
$label2.text(' / ' + states.length);
|
|
||||||
};
|
|
||||||
|
|
||||||
var close = function () {
|
|
||||||
$hist.hide();
|
|
||||||
$left.show();
|
|
||||||
$right.show();
|
|
||||||
$cke.show();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Buttons actions
|
|
||||||
$prev.click(function () { render(getPrevious()); });
|
|
||||||
$next.click(function () { render(getNext()); });
|
|
||||||
$cur.keydown(function (e) {
|
|
||||||
var p = function () { e.preventDefault(); };
|
|
||||||
if (e.which === 13) { p(); return render( get($cur.val() - 1) ); } // Enter
|
|
||||||
if ([37, 40].indexOf(e.which) >= 0) { p(); return render(getPrevious()); } // Left
|
|
||||||
if ([38, 39].indexOf(e.which) >= 0) { p(); return render(getNext()); } // Right
|
|
||||||
if (e.which === 33) { p(); return render(getNext(10)); } // PageUp
|
|
||||||
if (e.which === 34) { p(); return render(getPrevious(10)); } // PageUp
|
|
||||||
if (e.which === 27) { p(); $close.click(); }
|
|
||||||
}).keyup(function (e) { e.stopPropagation(); }).focus();
|
|
||||||
$cur.on('change', function () {
|
|
||||||
render( get($cur.val() - 1) );
|
|
||||||
});
|
|
||||||
$close.click(function () {
|
|
||||||
states = [];
|
|
||||||
close();
|
|
||||||
onClose();
|
|
||||||
});
|
|
||||||
$rev.click(function () {
|
|
||||||
common.confirm(Messages.history_restorePrompt, function (yes) {
|
|
||||||
if (!yes) { return; }
|
|
||||||
close();
|
|
||||||
onRevert();
|
|
||||||
common.log(Messages.history_restoreDone);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Display the latest content
|
|
||||||
render(get(c));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load all the history messages into a new chainpad object
|
|
||||||
loadHistory(config, common, function (err, newRt) {
|
|
||||||
History.loading = false;
|
|
||||||
if (err) { throw new Error(err); }
|
|
||||||
realtime = newRt;
|
|
||||||
update();
|
|
||||||
c = states.length - 1;
|
|
||||||
display();
|
|
||||||
onReady();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return History;
|
|
||||||
});
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
|||||||
define(function () {
|
|
||||||
var module = {};
|
|
||||||
|
|
||||||
module.create = function (UserList, Title, cfg, Cryptpad) {
|
|
||||||
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 (Cryptpad) {
|
|
||||||
if (typeof(metadata.type) === 'undefined') {
|
|
||||||
// initialize pad type by location.pathname
|
|
||||||
metadata.type = Cryptpad.getAppType();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log("Cryptpad should exist but it does not");
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
|||||||
|
define([
|
||||||
|
'/common/visible.js',
|
||||||
|
'/common/notify.js'
|
||||||
|
], function (Visible, Notify) {
|
||||||
|
var Notifier = {};
|
||||||
|
|
||||||
|
var notify = {};
|
||||||
|
Notifier.unnotify = function () {
|
||||||
|
if (notify.tabNotification &&
|
||||||
|
typeof(notify.tabNotification.cancel) === 'function') {
|
||||||
|
notify.tabNotification.cancel();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Notifier.notify = function () {
|
||||||
|
if (Visible.isSupported() && !Visible.currently()) {
|
||||||
|
Notifier.unnotify();
|
||||||
|
notify.tabNotification = Notify.tab(1000, 10);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Visible.isSupported()) {
|
||||||
|
Visible.onChange(function (yes) {
|
||||||
|
if (yes) { Notifier.unnotify(); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Notifier;
|
||||||
|
});
|
@ -1,87 +0,0 @@
|
|||||||
define(['jquery'], 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;
|
|
||||||
onLocal();
|
|
||||||
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);
|
|
||||||
onLocal();
|
|
||||||
};
|
|
||||||
|
|
||||||
// update title: href is optional; if not specified, we use window.location.href
|
|
||||||
exp.updateTitle = function (newTitle, href, cb) {
|
|
||||||
cb = cb || $.noop;
|
|
||||||
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, href, function (err, data) {
|
|
||||||
if (err) {
|
|
||||||
console.log("Couldn't set pad title");
|
|
||||||
console.error(err);
|
|
||||||
updateLocalTitle(oldTitle);
|
|
||||||
return void cb(err);
|
|
||||||
}
|
|
||||||
updateLocalTitle(data);
|
|
||||||
cb(null, 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;
|
|
||||||
});
|
|
||||||
|
|
@ -1,117 +0,0 @@
|
|||||||
define(['json.sortify'], function (Sortify) {
|
|
||||||
var module = {};
|
|
||||||
|
|
||||||
module.create = function (info, onLocal, Cryptget, Cryptpad) {
|
|
||||||
var exp = {};
|
|
||||||
|
|
||||||
var userData = exp.userData = {};
|
|
||||||
var userList = exp.userList = info.userList;
|
|
||||||
var myData = exp.myData = {};
|
|
||||||
exp.myUserName = info.myID;
|
|
||||||
exp.myNetfluxId = info.myID;
|
|
||||||
|
|
||||||
var network = Cryptpad.getNetwork();
|
|
||||||
|
|
||||||
var parsed = Cryptpad.parsePadUrl(window.location.href);
|
|
||||||
var appType = parsed ? parsed.type : undefined;
|
|
||||||
|
|
||||||
var oldUserData = {};
|
|
||||||
var addToUserData = exp.addToUserData = function(data) {
|
|
||||||
var users = userList.users;
|
|
||||||
for (var attrname in data) { userData[attrname] = data[attrname]; }
|
|
||||||
|
|
||||||
if (users && users.length) {
|
|
||||||
for (var userKey in userData) {
|
|
||||||
if (users.indexOf(userKey) === -1) {
|
|
||||||
delete userData[userKey];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(userList && typeof userList.onChange === "function") {
|
|
||||||
// Make sure we don't update the userlist everytime someone makes a change to the pad
|
|
||||||
if (Sortify(oldUserData) === Sortify(userData)) { return; }
|
|
||||||
oldUserData = JSON.parse(JSON.stringify(userData));
|
|
||||||
|
|
||||||
userList.onChange(userData);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exp.getToolbarConfig = function () {
|
|
||||||
return {
|
|
||||||
data: userData,
|
|
||||||
list: userList,
|
|
||||||
userNetfluxId: exp.myNetfluxId
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
var setName = exp.setName = function (newName, cb) {
|
|
||||||
if (typeof(newName) !== 'string') { return; }
|
|
||||||
var myUserNameTemp = newName.trim();
|
|
||||||
if(myUserNameTemp.length > 32) {
|
|
||||||
myUserNameTemp = myUserNameTemp.substr(0, 32);
|
|
||||||
}
|
|
||||||
exp.myUserName = myUserNameTemp;
|
|
||||||
myData = {};
|
|
||||||
myData[exp.myNetfluxId] = {
|
|
||||||
name: exp.myUserName,
|
|
||||||
uid: Cryptpad.getUid(),
|
|
||||||
avatar: Cryptpad.getAvatarUrl(),
|
|
||||||
profile: Cryptpad.getProfileUrl(),
|
|
||||||
curvePublic: Cryptpad.getProxy().curvePublic
|
|
||||||
};
|
|
||||||
addToUserData(myData);
|
|
||||||
/*Cryptpad.setAttribute('username', exp.myUserName, function (err) {
|
|
||||||
if (err) {
|
|
||||||
console.log("Couldn't set username");
|
|
||||||
console.error(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (typeof cb === "function") { cb(); }
|
|
||||||
});*/
|
|
||||||
if (typeof cb === "function") { cb(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
exp.getLastName = function ($changeNameButton, isNew) {
|
|
||||||
Cryptpad.getLastName(function (err, lastName) {
|
|
||||||
if (err) {
|
|
||||||
console.log("Could not get previous name");
|
|
||||||
console.error(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Update the toolbar list:
|
|
||||||
// Add the current user in the metadata
|
|
||||||
if (typeof(lastName) === 'string') {
|
|
||||||
setName(lastName, onLocal);
|
|
||||||
} else {
|
|
||||||
myData[exp.myNetfluxId] = {
|
|
||||||
name: "",
|
|
||||||
uid: Cryptpad.getUid(),
|
|
||||||
avatar: Cryptpad.getAvatarUrl(),
|
|
||||||
profile: Cryptpad.getProfileUrl(),
|
|
||||||
curvePublic: Cryptpad.getProxy().curvePublic
|
|
||||||
};
|
|
||||||
addToUserData(myData);
|
|
||||||
onLocal();
|
|
||||||
$changeNameButton.click();
|
|
||||||
}
|
|
||||||
if (isNew && appType) {
|
|
||||||
Cryptpad.selectTemplate(appType, info.realtime, Cryptget);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Cryptpad.onDisplayNameChanged(function (newName) {
|
|
||||||
setName(newName, onLocal);
|
|
||||||
});
|
|
||||||
|
|
||||||
network.on('reconnect', function (uid) {
|
|
||||||
exp.myNetfluxId = uid;
|
|
||||||
exp.setName(exp.myUserName);
|
|
||||||
});
|
|
||||||
|
|
||||||
return exp;
|
|
||||||
};
|
|
||||||
|
|
||||||
return module;
|
|
||||||
});
|
|
@ -1,112 +0,0 @@
|
|||||||
define(function () {
|
|
||||||
var module = {};
|
|
||||||
|
|
||||||
module.create = function (info, onLocal, Cryptget, Cryptpad) {
|
|
||||||
var exp = {};
|
|
||||||
|
|
||||||
var userData = exp.userData = {};
|
|
||||||
var userList = exp.userList = info.userList;
|
|
||||||
var myData = exp.myData = {};
|
|
||||||
exp.myUserName = info.myID;
|
|
||||||
exp.myNetfluxId = info.myID;
|
|
||||||
|
|
||||||
var network = Cryptpad.getNetwork();
|
|
||||||
|
|
||||||
var parsed = Cryptpad.parsePadUrl(window.location.href);
|
|
||||||
var appType = parsed ? parsed.type : undefined;
|
|
||||||
|
|
||||||
var addToUserData = exp.addToUserData = function(data) {
|
|
||||||
var users = userList.users;
|
|
||||||
for (var attrname in data) { userData[attrname] = data[attrname]; }
|
|
||||||
|
|
||||||
if (users && users.length) {
|
|
||||||
for (var userKey in userData) {
|
|
||||||
if (users.indexOf(userKey) === -1) {
|
|
||||||
delete userData[userKey];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(userList && typeof userList.onChange === "function") {
|
|
||||||
userList.onChange(userData);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exp.getToolbarConfig = function () {
|
|
||||||
return {
|
|
||||||
data: userData,
|
|
||||||
list: userList,
|
|
||||||
userNetfluxId: exp.myNetfluxId
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
var setName = exp.setName = function (newName, cb) {
|
|
||||||
if (typeof(newName) !== 'string') { return; }
|
|
||||||
var myUserNameTemp = newName.trim();
|
|
||||||
if(myUserNameTemp.length > 32) {
|
|
||||||
myUserNameTemp = myUserNameTemp.substr(0, 32);
|
|
||||||
}
|
|
||||||
exp.myUserName = myUserNameTemp;
|
|
||||||
myData = {};
|
|
||||||
myData[exp.myNetfluxId] = {
|
|
||||||
name: exp.myUserName,
|
|
||||||
uid: Cryptpad.getUid(),
|
|
||||||
avatar: Cryptpad.getAvatarUrl(),
|
|
||||||
profile: Cryptpad.getProfileUrl(),
|
|
||||||
curvePublic: Cryptpad.getProxy().curvePublic
|
|
||||||
};
|
|
||||||
addToUserData(myData);
|
|
||||||
/*Cryptpad.setAttribute('username', exp.myUserName, function (err) {
|
|
||||||
if (err) {
|
|
||||||
console.log("Couldn't set username");
|
|
||||||
console.error(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (typeof cb === "function") { cb(); }
|
|
||||||
});*/
|
|
||||||
if (typeof cb === "function") { cb(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
exp.getLastName = function ($changeNameButton, isNew) {
|
|
||||||
Cryptpad.getLastName(function (err, lastName) {
|
|
||||||
if (err) {
|
|
||||||
console.log("Could not get previous name");
|
|
||||||
console.error(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Update the toolbar list:
|
|
||||||
// Add the current user in the metadata
|
|
||||||
if (typeof(lastName) === 'string') {
|
|
||||||
setName(lastName, onLocal);
|
|
||||||
} else {
|
|
||||||
myData[exp.myNetfluxId] = {
|
|
||||||
name: "",
|
|
||||||
uid: Cryptpad.getUid(),
|
|
||||||
avatar: Cryptpad.getAvatarUrl(),
|
|
||||||
profile: Cryptpad.getProfileUrl(),
|
|
||||||
curvePublic: Cryptpad.getProxy().curvePublic
|
|
||||||
};
|
|
||||||
addToUserData(myData);
|
|
||||||
onLocal();
|
|
||||||
$changeNameButton.click();
|
|
||||||
}
|
|
||||||
if (isNew && appType) {
|
|
||||||
Cryptpad.selectTemplate(appType, info.realtime, Cryptget);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Cryptpad.onDisplayNameChanged(function (newName) {
|
|
||||||
setName(newName, onLocal);
|
|
||||||
});
|
|
||||||
|
|
||||||
network.on('reconnect', function (uid) {
|
|
||||||
exp.myNetfluxId = uid;
|
|
||||||
exp.setName(exp.myUserName);
|
|
||||||
});
|
|
||||||
|
|
||||||
return exp;
|
|
||||||
};
|
|
||||||
|
|
||||||
return module;
|
|
||||||
});
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,93 @@
|
|||||||
|
define([
|
||||||
|
'/file/file-crypto.js',
|
||||||
|
'/common/common-hash.js',
|
||||||
|
'/bower_components/tweetnacl/nacl-fast.min.js',
|
||||||
|
], function (FileCrypto, Hash) {
|
||||||
|
var Nacl = window.nacl;
|
||||||
|
var module = {};
|
||||||
|
|
||||||
|
module.upload = function (file, noStore, common, updateProgress, onComplete, onError, onPending) {
|
||||||
|
var u8 = file.blob; // This is not a blob but a uint8array
|
||||||
|
var metadata = file.metadata;
|
||||||
|
|
||||||
|
// if it exists, path contains the new pad location in the drive
|
||||||
|
var path = file.path;
|
||||||
|
|
||||||
|
var key = Nacl.randomBytes(32);
|
||||||
|
var next = FileCrypto.encrypt(u8, metadata, key);
|
||||||
|
|
||||||
|
var estimate = FileCrypto.computeEncryptedSize(u8.length, metadata);
|
||||||
|
|
||||||
|
var sendChunk = function (box, cb) {
|
||||||
|
var enc = Nacl.util.encodeBase64(box);
|
||||||
|
common.rpc.send.unauthenticated('UPLOAD', enc, function (e, msg) {
|
||||||
|
cb(e, msg);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var actual = 0;
|
||||||
|
var again = function (err, box) {
|
||||||
|
if (err) { throw new Error(err); }
|
||||||
|
if (box) {
|
||||||
|
actual += box.length;
|
||||||
|
var progressValue = (actual / estimate * 100);
|
||||||
|
updateProgress(progressValue);
|
||||||
|
|
||||||
|
return void sendChunk(box, function (e) {
|
||||||
|
if (e) { return console.error(e); }
|
||||||
|
next(again);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actual !== estimate) {
|
||||||
|
console.error('Estimated size does not match actual size');
|
||||||
|
}
|
||||||
|
|
||||||
|
// if not box then done
|
||||||
|
common.uploadComplete(function (e, id) {
|
||||||
|
if (e) { return void console.error(e); }
|
||||||
|
var uri = ['', 'blob', id.slice(0,2), id].join('/');
|
||||||
|
console.log("encrypted blob is now available as %s", uri);
|
||||||
|
|
||||||
|
var b64Key = Nacl.util.encodeBase64(key);
|
||||||
|
|
||||||
|
var hash = Hash.getFileHashFromKeys(id, b64Key);
|
||||||
|
var href = '/file/#' + hash;
|
||||||
|
|
||||||
|
var title = metadata.name;
|
||||||
|
|
||||||
|
if (noStore) { return void onComplete(href); }
|
||||||
|
|
||||||
|
common.initialPath = path;
|
||||||
|
common.renamePad(title || "", href, function (err) {
|
||||||
|
if (err) { return void console.error(err); }
|
||||||
|
onComplete(href);
|
||||||
|
common.setPadAttribute('fileType', metadata.type, null, href);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
common.uploadStatus(estimate, function (e, pending) {
|
||||||
|
if (e) {
|
||||||
|
console.error(e);
|
||||||
|
onError(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pending) {
|
||||||
|
return void onPending(function () {
|
||||||
|
// if the user wants to cancel the pending upload to execute that one
|
||||||
|
common.uploadCancel(function (e, res) {
|
||||||
|
if (e) {
|
||||||
|
return void console.error(e);
|
||||||
|
}
|
||||||
|
console.log(res);
|
||||||
|
next(again);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
next(again);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return module;
|
||||||
|
});
|
@ -1,865 +0,0 @@
|
|||||||
define([
|
|
||||||
'jquery',
|
|
||||||
'/customize/application_config.js',
|
|
||||||
'/api/config'
|
|
||||||
], function ($, Config, ApiConfig) {
|
|
||||||
|
|
||||||
var urlArgs = ApiConfig.requireConf.urlArgs;
|
|
||||||
|
|
||||||
var Messages = {};
|
|
||||||
|
|
||||||
var Bar = {
|
|
||||||
constants: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Id of the div containing the user list. */
|
|
||||||
var USER_LIST_CLS = Bar.constants.userlist = 'cryptpad-user-list';
|
|
||||||
|
|
||||||
/** Id of the div containing the lag info. */
|
|
||||||
var LAG_ELEM_CLS = Bar.constants.lag = 'cryptpad-lag';
|
|
||||||
|
|
||||||
var LIMIT_ELEM_CLS = Bar.constants.lag = 'cryptpad-limit';
|
|
||||||
|
|
||||||
/** The toolbar class which contains the user list, debug link and lag. */
|
|
||||||
var TOOLBAR_CLS = Bar.constants.toolbar = 'cryptpad-toolbar';
|
|
||||||
|
|
||||||
var TOP_CLS = Bar.constants.top = 'cryptpad-toolbar-top';
|
|
||||||
var LEFTSIDE_CLS = Bar.constants.leftside = 'cryptpad-toolbar-leftside';
|
|
||||||
var RIGHTSIDE_CLS = Bar.constants.rightside = 'cryptpad-toolbar-rightside';
|
|
||||||
var HISTORY_CLS = Bar.constants.history = 'cryptpad-toolbar-history';
|
|
||||||
|
|
||||||
var SPINNER_CLS = Bar.constants.spinner = 'cryptpad-spinner';
|
|
||||||
|
|
||||||
var STATE_CLS = Bar.constants.state = 'cryptpad-state';
|
|
||||||
|
|
||||||
var USERNAME_CLS = Bar.constants.username = 'cryptpad-toolbar-username';
|
|
||||||
|
|
||||||
var READONLY_CLS = Bar.constants.readonly = 'cryptpad-readonly';
|
|
||||||
|
|
||||||
var USERBUTTONS_CONTAINER_CLS = Bar.constants.userButtonsContainer = "cryptpad-userbuttons-container";
|
|
||||||
var USERLIST_CLS = Bar.constants.userlist = "cryptpad-dropdown-users";
|
|
||||||
var EDITSHARE_CLS = Bar.constants.editShare = "cryptpad-dropdown-editShare";
|
|
||||||
var VIEWSHARE_CLS = Bar.constants.viewShare = "cryptpad-dropdown-viewShare";
|
|
||||||
var SHARE_CLS = Bar.constants.viewShare = "cryptpad-dropdown-share";
|
|
||||||
var DROPDOWN_CONTAINER_CLS = Bar.constants.dropdownContainer = "cryptpad-dropdown-container";
|
|
||||||
var DROPDOWN_CLS = Bar.constants.dropdown = "cryptpad-dropdown";
|
|
||||||
var TITLE_CLS = Bar.constants.title = "cryptpad-title";
|
|
||||||
var USER_CLS = Bar.constants.userAdmin = "cryptpad-user";
|
|
||||||
var USERBUTTON_CLS = Bar.constants.changeUsername = "cryptpad-change-username";
|
|
||||||
|
|
||||||
var SPINNER_DISAPPEAR_TIME = 3000;
|
|
||||||
|
|
||||||
var uid = function () {
|
|
||||||
return 'cryptpad-uid-' + String(Math.random()).substring(2);
|
|
||||||
};
|
|
||||||
|
|
||||||
var $style;
|
|
||||||
|
|
||||||
var connected = false;
|
|
||||||
var firstConnection = true;
|
|
||||||
var lagErrors = 0;
|
|
||||||
|
|
||||||
var styleToolbar = function ($container, href, version) {
|
|
||||||
href = href || '/customize/toolbar.css' + (version?('?' + version): '');
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: href,
|
|
||||||
dataType: 'text',
|
|
||||||
success: function (data) {
|
|
||||||
$container.append($('<style>').text(data));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var createRealtimeToolbar = function ($container, config) {
|
|
||||||
var $toolbar = $('<div>', {
|
|
||||||
'class': TOOLBAR_CLS,
|
|
||||||
id: uid(),
|
|
||||||
})
|
|
||||||
.append($('<div>', {'class': TOP_CLS}))
|
|
||||||
.append($('<div>', {'class': LEFTSIDE_CLS}))
|
|
||||||
.append($('<div>', {'class': RIGHTSIDE_CLS}))
|
|
||||||
.append($('<div>', {'class': HISTORY_CLS}));
|
|
||||||
|
|
||||||
// The 'notitle' class removes the line added for the title with a small screen
|
|
||||||
if (!config || typeof config !== "object") {
|
|
||||||
$toolbar.addClass('notitle');
|
|
||||||
}
|
|
||||||
|
|
||||||
$container.prepend($toolbar);
|
|
||||||
|
|
||||||
if (ApiConfig && ApiConfig.requireConf && ApiConfig.requireConf.urlArgs) {
|
|
||||||
styleToolbar($container, undefined, ApiConfig.requireConf.urlArgs);
|
|
||||||
} else {
|
|
||||||
styleToolbar($container);
|
|
||||||
}
|
|
||||||
return $toolbar;
|
|
||||||
};
|
|
||||||
|
|
||||||
var createSpinner = function ($container, config) {
|
|
||||||
if (config.displayed.indexOf('spinner') !== -1) {
|
|
||||||
var $spin = $('<span>', {'class':SPINNER_CLS});
|
|
||||||
var $spinner = $('<span>', {
|
|
||||||
id: uid(),
|
|
||||||
'class': 'spin fa fa-spinner fa-pulse',
|
|
||||||
}).appendTo($spin).hide();
|
|
||||||
$('<span>', {
|
|
||||||
id: uid(),
|
|
||||||
'class': 'synced fa fa-check',
|
|
||||||
title: Messages.synced
|
|
||||||
}).appendTo($spin);
|
|
||||||
$container.prepend($spin);
|
|
||||||
return $spin[0];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var kickSpinner = function (Cryptpad, realtime, local, spinnerElement) {
|
|
||||||
if (!spinnerElement) { return; }
|
|
||||||
$(spinnerElement).find('.spin').show();
|
|
||||||
$(spinnerElement).find('.synced').hide();
|
|
||||||
var onSynced = function () {
|
|
||||||
if (spinnerElement.timeout) { clearTimeout(spinnerElement.timeout); }
|
|
||||||
spinnerElement.timeout = setTimeout(function () {
|
|
||||||
$(spinnerElement).find('.spin').hide();
|
|
||||||
$(spinnerElement).find('.synced').show();
|
|
||||||
}, local ? 0 : SPINNER_DISAPPEAR_TIME);
|
|
||||||
};
|
|
||||||
if (Cryptpad) {
|
|
||||||
Cryptpad.whenRealtimeSyncs(realtime, onSynced);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onSynced();
|
|
||||||
};
|
|
||||||
|
|
||||||
var createUserButtons = function ($userlistElement, config, readOnly, Cryptpad) {
|
|
||||||
// User list button
|
|
||||||
if (config.displayed.indexOf('userlist') !== -1) {
|
|
||||||
if (!config.userData) {
|
|
||||||
throw new Error("You must provide a `userData` object to display the userlist");
|
|
||||||
}
|
|
||||||
var dropdownConfig = {
|
|
||||||
options: [{
|
|
||||||
tag: 'p',
|
|
||||||
attributes: {'class': USERLIST_CLS},
|
|
||||||
}] // Entries displayed in the menu
|
|
||||||
};
|
|
||||||
var $block = Cryptpad.createDropdown(dropdownConfig);
|
|
||||||
$block.attr('id', 'userButtons');
|
|
||||||
$userlistElement.append($block);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Share button
|
|
||||||
if (config.displayed.indexOf('share') !== -1) {
|
|
||||||
var secret = Cryptpad.find(config, ['share', 'secret']);
|
|
||||||
var channel = Cryptpad.find(config, ['share', 'channel']);
|
|
||||||
if (!secret || !channel) {
|
|
||||||
throw new Error("Unable to display the share button: share.secret and share.channel required");
|
|
||||||
}
|
|
||||||
Cryptpad.getRecentPads(function (err, recent) {
|
|
||||||
var $shareIcon = $('<span>', {'class': 'fa fa-share-alt'});
|
|
||||||
var $span = $('<span>', {'class': 'large'}).append(' ' +Messages.shareButton);
|
|
||||||
var hashes = Cryptpad.getHashes(channel, secret);
|
|
||||||
var options = [];
|
|
||||||
|
|
||||||
// If we have a stronger version in drive, add it and add a redirect button
|
|
||||||
var stronger = recent && Cryptpad.findStronger(null, recent);
|
|
||||||
if (stronger) {
|
|
||||||
var parsed = Cryptpad.parsePadUrl(stronger);
|
|
||||||
hashes.editHash = parsed.hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hashes.editHash) {
|
|
||||||
options.push({
|
|
||||||
tag: 'a',
|
|
||||||
attributes: {title: Messages.editShareTitle, 'class': 'editShare'},
|
|
||||||
content: '<span class="fa fa-users"></span> ' + Messages.editShare
|
|
||||||
});
|
|
||||||
if (stronger) {
|
|
||||||
// We're in view mode, display the "open editing link" button
|
|
||||||
options.push({
|
|
||||||
tag: 'a',
|
|
||||||
attributes: {
|
|
||||||
title: Messages.editOpenTitle,
|
|
||||||
'class': 'editOpen',
|
|
||||||
href: window.location.pathname + '#' + hashes.editHash,
|
|
||||||
target: '_blank'
|
|
||||||
},
|
|
||||||
content: '<span class="fa fa-users"></span> ' + Messages.editOpen
|
|
||||||
});
|
|
||||||
}
|
|
||||||
options.push({tag: 'hr'});
|
|
||||||
}
|
|
||||||
if (hashes.viewHash) {
|
|
||||||
options.push({
|
|
||||||
tag: 'a',
|
|
||||||
attributes: {title: Messages.viewShareTitle, 'class': 'viewShare'},
|
|
||||||
content: '<span class="fa fa-eye"></span> ' + Messages.viewShare
|
|
||||||
});
|
|
||||||
if (hashes.editHash && !stronger) {
|
|
||||||
// We're in edit mode, display the "open readonly" button
|
|
||||||
options.push({
|
|
||||||
tag: 'a',
|
|
||||||
attributes: {
|
|
||||||
title: Messages.viewOpenTitle,
|
|
||||||
'class': 'viewOpen',
|
|
||||||
href: window.location.pathname + '#' + hashes.viewHash,
|
|
||||||
target: '_blank'
|
|
||||||
},
|
|
||||||
content: '<span class="fa fa-eye"></span> ' + Messages.viewOpen
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (hashes.fileHash) {
|
|
||||||
options.push({
|
|
||||||
tag: 'a',
|
|
||||||
attributes: {title: Messages.viewShareTitle, 'class': 'fileShare'},
|
|
||||||
content: '<span class="fa fa-eye"></span> ' + Messages.viewShare
|
|
||||||
});
|
|
||||||
}
|
|
||||||
var dropdownConfigShare = {
|
|
||||||
text: $('<div>').append($shareIcon).append($span).html(),
|
|
||||||
options: options
|
|
||||||
};
|
|
||||||
var $shareBlock = Cryptpad.createDropdown(dropdownConfigShare);
|
|
||||||
$shareBlock.find('button').attr('id', 'shareButton');
|
|
||||||
$shareBlock.find('.dropdown-bar-content').addClass(SHARE_CLS).addClass(EDITSHARE_CLS).addClass(VIEWSHARE_CLS);
|
|
||||||
$userlistElement.append($shareBlock);
|
|
||||||
|
|
||||||
if (hashes.editHash) {
|
|
||||||
$shareBlock.find('a.editShare').click(function () {
|
|
||||||
var url = window.location.origin + window.location.pathname + '#' + hashes.editHash;
|
|
||||||
var success = Cryptpad.Clipboard.copy(url);
|
|
||||||
if (success) { Cryptpad.log(Messages.shareSuccess); }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (hashes.viewHash) {
|
|
||||||
$shareBlock.find('a.viewShare').click(function () {
|
|
||||||
var url = window.location.origin + window.location.pathname + '#' + hashes.viewHash ;
|
|
||||||
var success = Cryptpad.Clipboard.copy(url);
|
|
||||||
if (success) { Cryptpad.log(Messages.shareSuccess); }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (hashes.fileHash) {
|
|
||||||
$shareBlock.find('a.fileShare').click(function () {
|
|
||||||
var url = window.location.origin + window.location.pathname + '#' + hashes.fileHash ;
|
|
||||||
var success = Cryptpad.Clipboard.copy(url);
|
|
||||||
if (success) { Cryptpad.log(Messages.shareSuccess); }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var createUserList = function ($container, config, readOnly, Cryptpad) {
|
|
||||||
if (config.displayed.indexOf('userlist') === -1 && config.displayed.indexOf('share') === -1) { return; }
|
|
||||||
var $userlist = $('<div>', {
|
|
||||||
'class': USER_LIST_CLS,
|
|
||||||
id: uid(),
|
|
||||||
});
|
|
||||||
createUserButtons($userlist, config, readOnly, Cryptpad);
|
|
||||||
$container.append($userlist);
|
|
||||||
return $userlist[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
var getOtherUsers = function(myUserName, userList, userData) {
|
|
||||||
var i = 0; // duplicates counter
|
|
||||||
var list = [];
|
|
||||||
var myUid = userData[myUserName] ? userData[myUserName].uid : undefined;
|
|
||||||
var uids = [];
|
|
||||||
userList.forEach(function(user) {
|
|
||||||
if(user !== myUserName) {
|
|
||||||
var data = (userData) ? (userData[user] || null) : null;
|
|
||||||
var userName = (data) ? data.name : null;
|
|
||||||
var userId = (data) ? data.uid : null;
|
|
||||||
if (userName && uids.indexOf(userId) === -1 && (!myUid || userId !== myUid)) {
|
|
||||||
uids.push(userId);
|
|
||||||
list.push(userName);
|
|
||||||
} else { i++; }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
list: list,
|
|
||||||
duplicates: i
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
var arrayIntersect = function(a, b) {
|
|
||||||
return $.grep(a, function(i) {
|
|
||||||
return $.inArray(i, b) > -1;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var getViewers = function (n) {
|
|
||||||
if (!n || !parseInt(n) || n === 0) { return ''; }
|
|
||||||
if (n === 1) { return '; + ' + Messages.oneViewer; }
|
|
||||||
return '; + ' + Messages._getKey('viewers', [n]);
|
|
||||||
};
|
|
||||||
|
|
||||||
var checkSynchronizing = function (userList, myUserName, $stateElement) {
|
|
||||||
var meIdx = userList.indexOf(myUserName);
|
|
||||||
if (meIdx === -1) {
|
|
||||||
$stateElement.text(Messages.synchronizing);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$stateElement.text('');
|
|
||||||
};
|
|
||||||
|
|
||||||
var updateUserList = function (config, myUserName, userlistElement, userList, userData, readOnly, $userAdminElement) {
|
|
||||||
// Make sure the elements are displayed
|
|
||||||
var $userButtons = $(userlistElement).find("#userButtons");
|
|
||||||
$userButtons.attr('display', 'inline');
|
|
||||||
|
|
||||||
if (config.displayed.indexOf('userlist') !== -1) {
|
|
||||||
var numberOfUsers = userList.length;
|
|
||||||
|
|
||||||
// If we are using old pads (readonly unavailable), only editing users are in userList.
|
|
||||||
// With new pads, we also have readonly users in userList, so we have to intersect with
|
|
||||||
// the userData to have only the editing users. We can't use userData directly since it
|
|
||||||
// may contain data about users that have already left the channel.
|
|
||||||
userList = readOnly === -1 ? userList : arrayIntersect(userList, Object.keys(userData));
|
|
||||||
|
|
||||||
// Names of editing users
|
|
||||||
var others = getOtherUsers(myUserName, userList, userData);
|
|
||||||
var editUsersNames = others.list;
|
|
||||||
var duplicates = others.duplicates;
|
|
||||||
|
|
||||||
var numberOfEditUsers = userList.length - duplicates;
|
|
||||||
var numberOfViewUsers = numberOfUsers - numberOfEditUsers - duplicates;
|
|
||||||
|
|
||||||
// Number of anonymous editing users
|
|
||||||
var anonymous = numberOfEditUsers - editUsersNames.length;
|
|
||||||
|
|
||||||
// Update the userlist
|
|
||||||
var $usersTitle = $('<h2>').text(Messages.users);
|
|
||||||
var $editUsers = $userButtons.find('.' + USERLIST_CLS);
|
|
||||||
$editUsers.html('').append($usersTitle);
|
|
||||||
|
|
||||||
var $editUsersList = $('<pre>');
|
|
||||||
if (readOnly !== 1) { // Yourself - edit
|
|
||||||
$editUsers.append('<span class="yourself">' + Messages.yourself + '</span>');
|
|
||||||
anonymous--;
|
|
||||||
}
|
|
||||||
// Editors
|
|
||||||
$editUsersList.text(editUsersNames.join('\n')); // .text() to avoid XSS
|
|
||||||
$editUsers.append($editUsersList);
|
|
||||||
if (anonymous > 0) { // Anonymous editors
|
|
||||||
var text = anonymous === 1 ? Messages.anonymousUser : Messages.anonymousUsers;
|
|
||||||
$editUsers.append('<span class="anonymous">' + anonymous + ' ' + text + '</span>');
|
|
||||||
}
|
|
||||||
if (numberOfViewUsers > 0) { // Viewers
|
|
||||||
var viewText = '<span class="viewer">';
|
|
||||||
if (numberOfEditUsers > 0) {
|
|
||||||
$editUsers.append('<br>');
|
|
||||||
viewText += Messages.and + ' ';
|
|
||||||
}
|
|
||||||
var viewerText = numberOfViewUsers !== 1 ? Messages.viewers : Messages.viewer;
|
|
||||||
viewText += numberOfViewUsers + ' ' + viewerText + '</span>';
|
|
||||||
$editUsers.append(viewText);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Update the buttons
|
|
||||||
var fa_editusers = '<span class="fa fa-users"></span>';
|
|
||||||
var fa_viewusers = '<span class="fa fa-eye"></span>';
|
|
||||||
var viewersText = numberOfViewUsers !== 1 ? Messages.viewers : Messages.viewer;
|
|
||||||
var editorsText = numberOfEditUsers !== 1 ? Messages.editors : Messages.editor;
|
|
||||||
var $span = $('<span>', {'class': 'large'}).html(fa_editusers + ' ' + numberOfEditUsers + ' ' + editorsText + ' ' + fa_viewusers + ' ' + numberOfViewUsers + ' ' + viewersText);
|
|
||||||
var $spansmall = $('<span>', {'class': 'narrow'}).html(fa_editusers + ' ' + numberOfEditUsers + ' ' + fa_viewusers + ' ' + numberOfViewUsers);
|
|
||||||
$userButtons.find('.buttonTitle').html('').append($span).append($spansmall);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.displayed.indexOf('useradmin') !== -1) {
|
|
||||||
// Change username in useradmin dropdown
|
|
||||||
var $userElement = $userAdminElement.find('.' + USERNAME_CLS);
|
|
||||||
$userElement.show();
|
|
||||||
if (readOnly === 1) {
|
|
||||||
$userElement.addClass(READONLY_CLS).text(Messages.readonly);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var name = userData[myUserName] && userData[myUserName].name;
|
|
||||||
if (!name) {
|
|
||||||
name = Messages.anonymous;
|
|
||||||
}
|
|
||||||
$userElement.removeClass(READONLY_CLS).text(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var createLagElement = function () {
|
|
||||||
var $lag = $('<span>', {
|
|
||||||
'class': LAG_ELEM_CLS,
|
|
||||||
id: uid(),
|
|
||||||
});
|
|
||||||
var $a = $('<span>', {'class': 'cryptpad-lag', id: 'newLag'});
|
|
||||||
$('<span>', {'class': 'bar1'}).appendTo($a);
|
|
||||||
$('<span>', {'class': 'bar2'}).appendTo($a);
|
|
||||||
$('<span>', {'class': 'bar3'}).appendTo($a);
|
|
||||||
$('<span>', {'class': 'bar4'}).appendTo($a);
|
|
||||||
return $a[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
var checkLag = function (getLag, lagElement) {
|
|
||||||
var lag;
|
|
||||||
if(typeof getLag === "function") {
|
|
||||||
lag = getLag();
|
|
||||||
}
|
|
||||||
var lagLight = $('<div>', {
|
|
||||||
'class': 'lag'
|
|
||||||
});
|
|
||||||
var title;
|
|
||||||
var $lag = $(lagElement);
|
|
||||||
if (lag) {
|
|
||||||
$lag.attr('class', 'cryptpad-lag');
|
|
||||||
firstConnection = false;
|
|
||||||
title = Messages.lag + ' : ' + lag + ' ms\n';
|
|
||||||
if (lag > 30000) {
|
|
||||||
$lag.addClass('lag0');
|
|
||||||
title = Messages.redLight;
|
|
||||||
} else if (lag > 5000) {
|
|
||||||
$lag.addClass('lag1');
|
|
||||||
title += Messages.orangeLight;
|
|
||||||
} else if (lag > 1000) {
|
|
||||||
$lag.addClass('lag2');
|
|
||||||
title += Messages.orangeLight;
|
|
||||||
} else if (lag > 300) {
|
|
||||||
$lag.addClass('lag3');
|
|
||||||
title += Messages.greenLight;
|
|
||||||
} else {
|
|
||||||
$lag.addClass('lag4');
|
|
||||||
title += Messages.greenLight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!firstConnection) {
|
|
||||||
$lag.attr('class', 'cryptpad-lag');
|
|
||||||
// Display the red light at the 2nd failed attemp to get the lag
|
|
||||||
lagLight.addClass('lag-red');
|
|
||||||
title = Messages.redLight;
|
|
||||||
}
|
|
||||||
if (title) {
|
|
||||||
$lag.attr('title', title);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var createLinkToMain = function ($topContainer) {
|
|
||||||
var $linkContainer = $('<span>', {
|
|
||||||
'class': "cryptpad-link"
|
|
||||||
}).appendTo($topContainer);
|
|
||||||
var $imgTag = $('<img>', {
|
|
||||||
src: "/customize/cryptofist_mini.png?" + urlArgs,
|
|
||||||
alt: "Cryptpad"
|
|
||||||
});
|
|
||||||
|
|
||||||
// We need to override the "a" tag action here because it is inside the iframe!
|
|
||||||
var $aTagSmall = $('<a>', {
|
|
||||||
href: "/",
|
|
||||||
title: Messages.header_logoTitle,
|
|
||||||
'class': "cryptpad-logo"
|
|
||||||
}).append($imgTag);
|
|
||||||
var $span = $('<span>').text('CryptPad');
|
|
||||||
var $aTagBig = $aTagSmall.clone().addClass('large').append($span);
|
|
||||||
$aTagSmall.addClass('narrow');
|
|
||||||
var onClick = function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
if (e.ctrlKey) {
|
|
||||||
window.open('/');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.location = "/";
|
|
||||||
};
|
|
||||||
|
|
||||||
var onContext = function (e) { e.stopPropagation(); };
|
|
||||||
|
|
||||||
$aTagBig.click(onClick).contextmenu(onContext);
|
|
||||||
$aTagSmall.click(onClick).contextmenu(onContext);
|
|
||||||
|
|
||||||
$linkContainer.append($aTagSmall).append($aTagBig);
|
|
||||||
};
|
|
||||||
|
|
||||||
var createUserAdmin = function ($topContainer, config, readOnly, lagElement, Cryptpad) {
|
|
||||||
var $lag = $(lagElement);
|
|
||||||
|
|
||||||
var $userContainer = $('<span>', {
|
|
||||||
'class': USER_CLS
|
|
||||||
}).appendTo($topContainer);
|
|
||||||
|
|
||||||
if (config.displayed.indexOf('state') !== -1) {
|
|
||||||
var $state = $('<span>', {
|
|
||||||
'class': STATE_CLS
|
|
||||||
}).text(Messages.synchronizing).appendTo($userContainer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.displayed.indexOf('lag') !== -1) {
|
|
||||||
$userContainer.append($lag);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.displayed.indexOf('limit') !== -1 && Config.enablePinning) {
|
|
||||||
var usage;
|
|
||||||
var $limitIcon = $('<span>', {'class': 'fa fa-exclamation-triangle'});
|
|
||||||
var $limit = $('<span>', {
|
|
||||||
'class': LIMIT_ELEM_CLS,
|
|
||||||
'title': Messages.pinLimitReached
|
|
||||||
}).append($limitIcon).hide().appendTo($userContainer);
|
|
||||||
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(message, null, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Cryptpad.isOverPinLimit(todo);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.displayed.indexOf('newpad') !== -1) {
|
|
||||||
var pads_options = [];
|
|
||||||
Config.availablePadTypes.forEach(function (p) {
|
|
||||||
if (p === 'drive') { return; }
|
|
||||||
pads_options.push({
|
|
||||||
tag: 'a',
|
|
||||||
attributes: {
|
|
||||||
'target': '_blank',
|
|
||||||
'href': '/' + p + '/',
|
|
||||||
},
|
|
||||||
content: Messages.type[p]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
var $plusIcon = $('<span>', {'class': 'fa fa-plus'});
|
|
||||||
var $newbig = $('<span>', {'class': 'big'}).append(' ' +Messages.newButton);
|
|
||||||
var $newButton = $('<div>').append($plusIcon).append($newbig);
|
|
||||||
var dropdownConfig = {
|
|
||||||
text: $newButton.html(), // Button initial text
|
|
||||||
options: pads_options, // Entries displayed in the menu
|
|
||||||
left: true, // Open to the left of the button
|
|
||||||
};
|
|
||||||
var $newPadBlock = Cryptpad.createDropdown(dropdownConfig);
|
|
||||||
$newPadBlock.find('button').attr('title', Messages.newButtonTitle);
|
|
||||||
$newPadBlock.find('button').attr('id', 'newdoc');
|
|
||||||
$newPadBlock.appendTo($userContainer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// User dropdown
|
|
||||||
if (config.displayed.indexOf('useradmin') !== -1) {
|
|
||||||
var userMenuCfg = {};
|
|
||||||
if (!config.hideDisplayName) {
|
|
||||||
userMenuCfg = {
|
|
||||||
displayNameCls: USERNAME_CLS,
|
|
||||||
changeNameButtonCls: USERBUTTON_CLS,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (readOnly !== 1) {
|
|
||||||
userMenuCfg.displayName = 1;
|
|
||||||
userMenuCfg.displayChangeName = 1;
|
|
||||||
}
|
|
||||||
var $userAdmin = Cryptpad.createUserAdminMenu(userMenuCfg);
|
|
||||||
|
|
||||||
$userAdmin.attr('id', 'userDropdown');
|
|
||||||
$userContainer.append($userAdmin);
|
|
||||||
|
|
||||||
var $userButton = $userAdmin.find('a.' + USERBUTTON_CLS);
|
|
||||||
var renameAlertOpened;
|
|
||||||
$userButton.click(function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
Cryptpad.getLastName(function (err, lastName) {
|
|
||||||
if (err) { return void console.error("Cannot get last name", err); }
|
|
||||||
Cryptpad.prompt(Messages.changeNamePrompt, lastName || '', function (newName) {
|
|
||||||
if (newName === null && typeof(lastName) === "string") { return; }
|
|
||||||
if (newName === null) { newName = ''; }
|
|
||||||
Cryptpad.changeDisplayName(newName);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Cryptpad.onDisplayNameChanged(function (newName) {
|
|
||||||
Cryptpad.findCancelButton().click();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return $userContainer;
|
|
||||||
};
|
|
||||||
|
|
||||||
var createTitle = function ($container, readOnly, config, Cryptpad) {
|
|
||||||
var $titleContainer = $('<span>', {
|
|
||||||
id: 'toolbarTitle',
|
|
||||||
'class': TITLE_CLS
|
|
||||||
}).appendTo($container);
|
|
||||||
|
|
||||||
if (!config || typeof config !== "object") { return; }
|
|
||||||
|
|
||||||
var callback = config.onRename;
|
|
||||||
var placeholder = config.defaultName;
|
|
||||||
var suggestName = config.suggestName;
|
|
||||||
|
|
||||||
// Buttons
|
|
||||||
var $text = $('<span>', {
|
|
||||||
'class': 'title'
|
|
||||||
}).appendTo($titleContainer);
|
|
||||||
var $pencilIcon = $('<span>', {
|
|
||||||
'class': 'pencilIcon',
|
|
||||||
'title': Messages.clickToEdit
|
|
||||||
});
|
|
||||||
if (readOnly === 1 || typeof(Cryptpad) === "undefined") { return $titleContainer; }
|
|
||||||
var $input = $('<input>', {
|
|
||||||
type: 'text',
|
|
||||||
placeholder: placeholder
|
|
||||||
}).appendTo($titleContainer).hide();
|
|
||||||
if (readOnly !== 1) {
|
|
||||||
$text.attr("title", Messages.clickToEdit);
|
|
||||||
$text.addClass("editable");
|
|
||||||
var $icon = $('<span>', {
|
|
||||||
'class': 'fa fa-pencil readonly',
|
|
||||||
style: 'font-family: FontAwesome;'
|
|
||||||
});
|
|
||||||
$pencilIcon.append($icon).appendTo($titleContainer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Events
|
|
||||||
$input.on('mousedown', function (e) {
|
|
||||||
if (!$input.is(":focus")) {
|
|
||||||
$input.focus();
|
|
||||||
}
|
|
||||||
e.stopPropagation();
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
$input.on('keyup', function (e) {
|
|
||||||
if (e.which === 13 && connected === true) {
|
|
||||||
var name = $input.val().trim();
|
|
||||||
if (name === "") {
|
|
||||||
name = $input.attr('placeholder');
|
|
||||||
}
|
|
||||||
Cryptpad.renamePad(name, function (err, newtitle) {
|
|
||||||
if (err) { return; }
|
|
||||||
$text.text(newtitle);
|
|
||||||
callback(null, newtitle);
|
|
||||||
$input.hide();
|
|
||||||
$text.show();
|
|
||||||
//$pencilIcon.css('display', '');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (e.which === 27) {
|
|
||||||
$input.hide();
|
|
||||||
$text.show();
|
|
||||||
//$pencilIcon.css('display', '');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var displayInput = function () {
|
|
||||||
if (connected === false) { return; }
|
|
||||||
$text.hide();
|
|
||||||
//$pencilIcon.css('display', 'none');
|
|
||||||
var inputVal = suggestName() || "";
|
|
||||||
$input.val(inputVal);
|
|
||||||
$input.show();
|
|
||||||
$input.focus();
|
|
||||||
};
|
|
||||||
$text.on('click', displayInput);
|
|
||||||
$pencilIcon.on('click', displayInput);
|
|
||||||
return $titleContainer;
|
|
||||||
};
|
|
||||||
|
|
||||||
var create = Bar.create = function ($container, myUserName, realtime, getLag, userList, config) {
|
|
||||||
config = config || {};
|
|
||||||
var readOnly = (typeof config.readOnly !== "undefined") ? (config.readOnly ? 1 : 0) : -1;
|
|
||||||
var Cryptpad = config.common;
|
|
||||||
Messages = Cryptpad.Messages;
|
|
||||||
config.displayed = config.displayed || [];
|
|
||||||
|
|
||||||
var toolbar = createRealtimeToolbar($container, config.title);
|
|
||||||
var userListElement = createUserList(toolbar.find('.' + LEFTSIDE_CLS), config, readOnly, Cryptpad);
|
|
||||||
var $titleElement = createTitle(toolbar.find('.' + TOP_CLS), readOnly, config.title, Cryptpad);
|
|
||||||
var $linkElement = createLinkToMain(toolbar.find('.' + TOP_CLS));
|
|
||||||
var lagElement = createLagElement();
|
|
||||||
var $userAdminElement = createUserAdmin(toolbar.find('.' + TOP_CLS), config, readOnly, lagElement, Cryptpad);
|
|
||||||
var spinner = createSpinner($userAdminElement, config);
|
|
||||||
var userData = config.userData;
|
|
||||||
// readOnly = 1 (readOnly enabled), 0 (disabled), -1 (old pad without readOnly mode)
|
|
||||||
var saveElement;
|
|
||||||
var loadElement;
|
|
||||||
var $stateElement = toolbar.find('.' + STATE_CLS);
|
|
||||||
|
|
||||||
if (config.ifrw) {
|
|
||||||
var removeDropdowns = function (e) {
|
|
||||||
$container.find('.cryptpad-dropdown').hide();
|
|
||||||
};
|
|
||||||
var cancelEditTitle = function (e) {
|
|
||||||
// Now we want to apply the title even if we click somewhere else
|
|
||||||
if ($(e.target).parents('.' + TITLE_CLS).length || !$titleElement) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$titleElement.find('input').is(':visible')) { return; }
|
|
||||||
|
|
||||||
var ev = $.Event("keyup");
|
|
||||||
ev.which = 13;
|
|
||||||
$titleElement.find('input').trigger(ev);
|
|
||||||
|
|
||||||
/*
|
|
||||||
$titleElement.find('input').hide();
|
|
||||||
$titleElement.find('span.title').show();
|
|
||||||
$titleElement.find('span.pencilIcon').css('display', '');
|
|
||||||
*/
|
|
||||||
};
|
|
||||||
$(config.ifrw).on('click', removeDropdowns);
|
|
||||||
$(config.ifrw).on('click', cancelEditTitle);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (config.ifrw.$('iframe').length) {
|
|
||||||
var innerIfrw = config.ifrw.$('iframe').each(function (i, el) {
|
|
||||||
$(el.contentWindow).on('click', removeDropdowns);
|
|
||||||
$(el.contentWindow).on('click', cancelEditTitle);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// empty try catch in case this iframe is problematic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update user list
|
|
||||||
if (userList) {
|
|
||||||
if (userData) {
|
|
||||||
userList.change.push(function (newUserData) {
|
|
||||||
var users = userList.users;
|
|
||||||
if (users.indexOf(myUserName) !== -1) { connected = true; }
|
|
||||||
if (!connected) { return; }
|
|
||||||
checkSynchronizing(users, myUserName, $stateElement);
|
|
||||||
updateUserList(config, myUserName, userListElement, users, userData, readOnly, $userAdminElement);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
userList.change.push(function () {
|
|
||||||
var users = userList.users;
|
|
||||||
if (users.indexOf(myUserName) !== -1) { connected = true; }
|
|
||||||
if (!connected) { return; }
|
|
||||||
checkSynchronizing(users, myUserName, $stateElement);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Display notifications when users are joining/leaving the session
|
|
||||||
var oldUserData;
|
|
||||||
if (typeof Cryptpad !== "undefined" && userList) {
|
|
||||||
var notify = function(type, name, oldname) {
|
|
||||||
// type : 1 (+1 user), 0 (rename existing user), -1 (-1 user)
|
|
||||||
if (typeof name === "undefined") { return; }
|
|
||||||
name = (name === "") ? Messages.anonymous : name;
|
|
||||||
switch(type) {
|
|
||||||
case 1:
|
|
||||||
Cryptpad.log(Messages._getKey("notifyJoined", [name]));
|
|
||||||
break;
|
|
||||||
case 0:
|
|
||||||
oldname = (oldname === "") ? Messages.anonymous : oldname;
|
|
||||||
Cryptpad.log(Messages._getKey("notifyRenamed", [oldname, name]));
|
|
||||||
break;
|
|
||||||
case -1:
|
|
||||||
Cryptpad.log(Messages._getKey("notifyLeft", [name]));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.log("Invalid type of notification");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var userPresent = function (id, user, data) {
|
|
||||||
if (!(user && user.uid)) {
|
|
||||||
console.log('no uid');
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (!data) {
|
|
||||||
console.log('no data');
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var count = 0;
|
|
||||||
Object.keys(data).forEach(function (k) {
|
|
||||||
if (data[k] && data[k].uid === user.uid) { count++; }
|
|
||||||
});
|
|
||||||
return count;
|
|
||||||
};
|
|
||||||
|
|
||||||
userList.change.push(function (newdata) {
|
|
||||||
// Notify for disconnected users
|
|
||||||
if (typeof oldUserData !== "undefined") {
|
|
||||||
for (var u in oldUserData) {
|
|
||||||
// if a user's uid is still present after having left, don't notify
|
|
||||||
if (userList.users.indexOf(u) === -1) {
|
|
||||||
var temp = JSON.parse(JSON.stringify(oldUserData[u]));
|
|
||||||
delete oldUserData[u];
|
|
||||||
if (userPresent(u, temp, newdata || oldUserData) < 1) {
|
|
||||||
notify(-1, temp.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Update the "oldUserData" object and notify for new users and names changed
|
|
||||||
if (typeof newdata === "undefined") { return; }
|
|
||||||
if (typeof oldUserData === "undefined") {
|
|
||||||
oldUserData = JSON.parse(JSON.stringify(newdata));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (readOnly === 0 && !oldUserData[myUserName]) {
|
|
||||||
oldUserData = JSON.parse(JSON.stringify(newdata));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (var k in newdata) {
|
|
||||||
if (k !== myUserName && userList.users.indexOf(k) !== -1) {
|
|
||||||
if (typeof oldUserData[k] === "undefined") {
|
|
||||||
// if the same uid is already present in the userdata, don't notify
|
|
||||||
if (!userPresent(k, newdata[k], oldUserData)) {
|
|
||||||
notify(1, newdata[k].name);
|
|
||||||
}
|
|
||||||
} else if (oldUserData[k].name !== newdata[k].name) {
|
|
||||||
notify(0, newdata[k].name, oldUserData[k].name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
oldUserData = JSON.parse(JSON.stringify(newdata));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var ks = function (local) {
|
|
||||||
return function () {
|
|
||||||
if (connected) { kickSpinner(Cryptpad, realtime, local, spinner); }
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
if (realtime) {
|
|
||||||
realtime.onPatch(ks());
|
|
||||||
realtime.onMessage(ks(true));
|
|
||||||
|
|
||||||
checkLag(getLag, lagElement);
|
|
||||||
setInterval(function () {
|
|
||||||
if (!connected) { return; }
|
|
||||||
checkLag(getLag, lagElement);
|
|
||||||
}, 3000);
|
|
||||||
} else { connected = true; }
|
|
||||||
|
|
||||||
var failed = function () {
|
|
||||||
connected = false;
|
|
||||||
$stateElement.text(Messages.disconnected);
|
|
||||||
checkLag(undefined, lagElement);
|
|
||||||
};
|
|
||||||
|
|
||||||
// On log out, remove permanently the realtime elements of the toolbar
|
|
||||||
Cryptpad.onLogout(function () {
|
|
||||||
failed();
|
|
||||||
$userAdminElement.find('#userDropdown').hide();
|
|
||||||
$(userListElement).hide();
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
failed: failed,
|
|
||||||
reconnecting: function (userId) {
|
|
||||||
myUserName = userId;
|
|
||||||
connected = false;
|
|
||||||
$stateElement.text(Messages.reconnecting);
|
|
||||||
checkLag(getLag, lagElement);
|
|
||||||
},
|
|
||||||
connected: function () {
|
|
||||||
connected = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return Bar;
|
|
||||||
});
|
|
File diff suppressed because it is too large
Load Diff
@ -1,16 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html class="cp">
|
|
||||||
<!-- If this file is not called customize.dist/src/template.html, it is generated -->
|
|
||||||
<head>
|
|
||||||
<title data-localization="main_title">CryptPad: Zero Knowledge, Collaborative Real Time Editing</title>
|
|
||||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
|
||||||
<link rel="icon" type="image/png" href="/customize/main-favicon.png" id="favicon"/>
|
|
||||||
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
|
||||||
</head>
|
|
||||||
<body class="html">
|
|
||||||
<noscript>
|
|
||||||
<p><strong>OOPS</strong> In order to do encryption in your browser, Javascript is really <strong>really</strong> required.</p>
|
|
||||||
<p><strong>OUPS</strong> Afin de pouvoir réaliser le chiffrement dans votre navigateur, Javascript est <strong>vraiment</strong> nécessaire.</p>
|
|
||||||
</noscript>
|
|
||||||
</html>
|
|
@ -1,54 +0,0 @@
|
|||||||
@import '/customize/src/less/variables.less';
|
|
||||||
@import '/customize/src/less/mixins.less';
|
|
||||||
@import '/customize/src/less/sidebar-layout.less';
|
|
||||||
|
|
||||||
.cp {
|
|
||||||
#container {
|
|
||||||
#rightSide {
|
|
||||||
.userFeedback, .thumbnails {
|
|
||||||
input[type="checkbox"] {
|
|
||||||
vertical-align: middle;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.languageSelector {
|
|
||||||
button.btn {
|
|
||||||
width: @button-width;
|
|
||||||
background-color: @button-alt-bg;
|
|
||||||
border-color: #adadad;
|
|
||||||
color: black;
|
|
||||||
&:hover {
|
|
||||||
background-color: darken(@button-alt-bg, 15%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.inputBlock {
|
|
||||||
input {
|
|
||||||
border-top-right-radius: 0.25em;
|
|
||||||
border-bottom-right-radius: 0.25em;
|
|
||||||
padding: 5px;
|
|
||||||
padding-left: 15px;
|
|
||||||
&[type="number"] {
|
|
||||||
border-right: 1px solid #adadad;
|
|
||||||
}
|
|
||||||
&[type="checkbox"] {
|
|
||||||
margin-right: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.infoBlock {
|
|
||||||
[type="text"] {
|
|
||||||
width: @button-width;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.backupDrive {
|
|
||||||
button {
|
|
||||||
span.fa {
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,528 +0,0 @@
|
|||||||
define([
|
|
||||||
'jquery',
|
|
||||||
'/common/cryptpad-common.js',
|
|
||||||
'/common/hyperscript.js',
|
|
||||||
'/bower_components/marked/marked.min.js',
|
|
||||||
'/common/media-tag.js',
|
|
||||||
], function ($, Cryptpad, h, Marked, MediaTag) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var UI = {};
|
|
||||||
var Messages = Cryptpad.Messages;
|
|
||||||
|
|
||||||
var m = function (md) {
|
|
||||||
var d = h('div.cp-app-contacts-content');
|
|
||||||
try {
|
|
||||||
d.innerHTML = Marked(md || '');
|
|
||||||
var $d = $(d);
|
|
||||||
// remove potentially malicious elements
|
|
||||||
$d.find('script, iframe, object, applet, video, audio').remove();
|
|
||||||
|
|
||||||
// activate media-tags
|
|
||||||
$d.find('media-tag').each(function (i, e) { MediaTag(e); });
|
|
||||||
} catch (e) {
|
|
||||||
console.error(md);
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
return d;
|
|
||||||
};
|
|
||||||
|
|
||||||
var dataQuery = function (curvePublic) {
|
|
||||||
return '[data-key="' + curvePublic + '"]';
|
|
||||||
};
|
|
||||||
|
|
||||||
var initChannel = function (state, curvePublic, info) {
|
|
||||||
console.log('initializing channel for [%s]', curvePublic);
|
|
||||||
state.channels[curvePublic] = {
|
|
||||||
messages: [],
|
|
||||||
HEAD: info.lastKnownHash,
|
|
||||||
TAIL: null,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
UI.create = function (messenger, $userlist, $messages, common) {
|
|
||||||
var origin = common.getMetadataMgr().getPrivateData().origin;
|
|
||||||
|
|
||||||
var state = window.state = {
|
|
||||||
active: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
state.channels = {};
|
|
||||||
var displayNames = state.displayNames = {};
|
|
||||||
|
|
||||||
var avatars = state.avatars = {};
|
|
||||||
var setActive = function (curvePublic) {
|
|
||||||
state.active = curvePublic;
|
|
||||||
};
|
|
||||||
var isActive = function (curvePublic) {
|
|
||||||
return curvePublic === state.active;
|
|
||||||
};
|
|
||||||
|
|
||||||
var find = {};
|
|
||||||
find.inList = function (curvePublic) {
|
|
||||||
return $userlist.find(dataQuery(curvePublic));
|
|
||||||
};
|
|
||||||
|
|
||||||
var notify = function (curvePublic) {
|
|
||||||
find.inList(curvePublic).addClass('cp-app-contacts-notify');
|
|
||||||
};
|
|
||||||
var unnotify = function (curvePublic) {
|
|
||||||
find.inList(curvePublic).removeClass('cp-app-contacts-notify');
|
|
||||||
};
|
|
||||||
|
|
||||||
var markup = {};
|
|
||||||
markup.message = function (msg) {
|
|
||||||
var curvePublic = msg.author;
|
|
||||||
var name = displayNames[msg.author];
|
|
||||||
return h('div.cp-app-contacts-message', {
|
|
||||||
title: msg.time? new Date(msg.time).toLocaleString(): '?',
|
|
||||||
'data-key': curvePublic,
|
|
||||||
}, [
|
|
||||||
name? h('div.cp-app-contacts-sender', name): undefined,
|
|
||||||
m(msg.text),
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
var getChat = function (curvePublic) {
|
|
||||||
return $messages.find(dataQuery(curvePublic));
|
|
||||||
};
|
|
||||||
|
|
||||||
var normalizeLabels = function ($messagebox) {
|
|
||||||
$messagebox.find('div.cp-app-contacts-message').toArray().reduce(function (a, b) {
|
|
||||||
var $b = $(b);
|
|
||||||
if ($(a).data('key') === $b.data('key')) {
|
|
||||||
$b.find('.cp-app-contacts-sender').hide();
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
return b;
|
|
||||||
}, []);
|
|
||||||
};
|
|
||||||
|
|
||||||
markup.chatbox = function (curvePublic, data) {
|
|
||||||
var moreHistory = h('span.cp-app-contacts-more-history.fa.fa-history', {
|
|
||||||
title: Messages.contacts_fetchHistory,
|
|
||||||
});
|
|
||||||
var displayName = data.displayName;
|
|
||||||
|
|
||||||
var fetching = false;
|
|
||||||
var $moreHistory = $(moreHistory).click(function () {
|
|
||||||
if (fetching) { return; }
|
|
||||||
|
|
||||||
// get oldest known message...
|
|
||||||
var channel = state.channels[curvePublic];
|
|
||||||
|
|
||||||
if (channel.exhausted) {
|
|
||||||
return void $moreHistory.addClass('cp-app-contacts-faded');
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('getting history');
|
|
||||||
var sig = channel.TAIL || channel.HEAD;
|
|
||||||
|
|
||||||
fetching = true;
|
|
||||||
var $messagebox = $(getChat(curvePublic)).find('.cp-app-contacts-messages');
|
|
||||||
messenger.getMoreHistory(curvePublic, sig, 10, function (e, history) {
|
|
||||||
fetching = false;
|
|
||||||
if (e) { return void console.error(e); }
|
|
||||||
|
|
||||||
if (history.length === 0) {
|
|
||||||
channel.exhausted = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
history.forEach(function (msg) {
|
|
||||||
if (channel.exhausted) { return; }
|
|
||||||
if (msg.sig) {
|
|
||||||
if (msg.sig === channel.TAIL) {
|
|
||||||
console.error('No more messages to fetch');
|
|
||||||
channel.exhausted = true;
|
|
||||||
console.log(channel);
|
|
||||||
return void $moreHistory.addClass('cp-app-contacts-faded');
|
|
||||||
} else {
|
|
||||||
channel.TAIL = msg.sig;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return void console.error('expected signature');
|
|
||||||
}
|
|
||||||
if (msg.type !== 'MSG') { return; }
|
|
||||||
|
|
||||||
// FIXME Schlameil the painter (performance does not scale well)
|
|
||||||
if (channel.messages.some(function (old) {
|
|
||||||
return msg.sig === old.sig;
|
|
||||||
})) { return; }
|
|
||||||
|
|
||||||
channel.messages.unshift(msg);
|
|
||||||
var el_message = markup.message(msg);
|
|
||||||
$messagebox.prepend(el_message);
|
|
||||||
});
|
|
||||||
normalizeLabels($messagebox);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var removeHistory = h('span.cp-app-contacts-remove-history.fa.fa-eraser', {
|
|
||||||
title: Messages.contacts_removeHistoryTitle
|
|
||||||
});
|
|
||||||
|
|
||||||
$(removeHistory).click(function () {
|
|
||||||
Cryptpad.confirm(Messages.contacts_confirmRemoveHistory, function (yes) {
|
|
||||||
if (!yes) { return; }
|
|
||||||
Cryptpad.clearOwnedChannel(data.channel, function (e) {
|
|
||||||
if (e) {
|
|
||||||
console.error(e);
|
|
||||||
Cryptpad.alert(Messages.contacts_removeHistoryServerError);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var avatar = h('div.cp-avatar');
|
|
||||||
var header = h('div.cp-app-contacts-header', [
|
|
||||||
avatar,
|
|
||||||
moreHistory,
|
|
||||||
removeHistory,
|
|
||||||
]);
|
|
||||||
var messages = h('div.cp-app-contacts-messages');
|
|
||||||
var input = h('textarea', {
|
|
||||||
placeholder: Messages.contacts_typeHere
|
|
||||||
});
|
|
||||||
var sendButton = h('button.btn.btn-primary.fa.fa-paper-plane', {
|
|
||||||
title: Messages.contacts_send,
|
|
||||||
});
|
|
||||||
|
|
||||||
var rightCol = h('span.cp-app-contacts-right-col', [
|
|
||||||
h('span.cp-app-contacts-name', displayName),
|
|
||||||
]);
|
|
||||||
|
|
||||||
var $avatar = $(avatar);
|
|
||||||
if (data.avatar && avatars[data.avatar]) {
|
|
||||||
$avatar.append(avatars[data.avatar]).append(rightCol);
|
|
||||||
} else {
|
|
||||||
common.displayAvatar($avatar, data.avatar, data.displayName, function ($img) {
|
|
||||||
if (data.avatar && $img) {
|
|
||||||
avatars[data.avatar] = $img[0].outerHTML;
|
|
||||||
}
|
|
||||||
$(rightCol).insertAfter($avatar);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var sending = false;
|
|
||||||
var send = function (content) {
|
|
||||||
if (typeof(content) !== 'string' || !content.trim()) { return; }
|
|
||||||
if (sending) { return false; }
|
|
||||||
sending = true;
|
|
||||||
messenger.sendMessage(curvePublic, content, function (e) {
|
|
||||||
if (e) {
|
|
||||||
// failed to send
|
|
||||||
return void console.error('failed to send');
|
|
||||||
}
|
|
||||||
input.value = '';
|
|
||||||
sending = false;
|
|
||||||
console.log('sent successfully');
|
|
||||||
var $messagebox = $(messages);
|
|
||||||
|
|
||||||
var height = $messagebox[0].scrollHeight;
|
|
||||||
$messagebox.scrollTop(height);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var onKeyDown = function (e) {
|
|
||||||
// ignore anything that isn't 'enter'
|
|
||||||
if (e.keyCode !== 13) { return; }
|
|
||||||
// send unless they're holding a ctrl-key or shift
|
|
||||||
if (!e.ctrlKey && !e.shiftKey) {
|
|
||||||
send(this.value);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert a newline if they're holding either
|
|
||||||
var val = this.value;
|
|
||||||
var start = this.selectionState;
|
|
||||||
var end = this.selectionEnd;
|
|
||||||
|
|
||||||
if (![start,end].some(function (x) {
|
|
||||||
return typeof(x) !== 'number';
|
|
||||||
})) {
|
|
||||||
this.value = val.slice(0, start) + '\n' + val.slice(end);
|
|
||||||
this.selectionStart = this.selectionEnd = start + 1;
|
|
||||||
} else if (document.selection && document.selection.createRange) {
|
|
||||||
this.focus();
|
|
||||||
var range = document.selection.createRange();
|
|
||||||
range.text = '\r\n';
|
|
||||||
range.collapse(false);
|
|
||||||
range.select();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
$(input).on('keydown', onKeyDown);
|
|
||||||
$(sendButton).click(function () { send(input.value); });
|
|
||||||
|
|
||||||
return h('div.cp-app-contacts-chat', {
|
|
||||||
'data-key': curvePublic,
|
|
||||||
}, [
|
|
||||||
header,
|
|
||||||
messages,
|
|
||||||
h('div.cp-app-contacts-input', [
|
|
||||||
input,
|
|
||||||
sendButton,
|
|
||||||
]),
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
var hideInfo = function () {
|
|
||||||
$messages.find('.cp-app-contacts-info').hide();
|
|
||||||
};
|
|
||||||
|
|
||||||
var updateStatus = function (curvePublic) {
|
|
||||||
var $status = find.inList(curvePublic).find('.cp-app-contacts-status');
|
|
||||||
// FIXME this stopped working :(
|
|
||||||
messenger.getStatus(curvePublic, function (e, online) {
|
|
||||||
// if error maybe you shouldn't display this friend...
|
|
||||||
if (e) {
|
|
||||||
find.inList(curvePublic).hide();
|
|
||||||
getChat(curvePublic).hide();
|
|
||||||
|
|
||||||
return void console.error(curvePublic, e);
|
|
||||||
}
|
|
||||||
if (online) {
|
|
||||||
return void $status
|
|
||||||
.removeClass('cp-app-contacts-offline').addClass('cp-app-contacts-online');
|
|
||||||
}
|
|
||||||
$status.removeClass('cp-app-contacts-online').addClass('cp-app-contacts-offline');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var display = function (curvePublic) {
|
|
||||||
var channel = state.channels[curvePublic];
|
|
||||||
var lastMsg = channel.messages.slice(-1)[0];
|
|
||||||
|
|
||||||
if (lastMsg) {
|
|
||||||
channel.HEAD = lastMsg.sig;
|
|
||||||
messenger.setChannelHead(curvePublic, channel.HEAD, function (e) {
|
|
||||||
if (e) { console.error(e); }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setActive(curvePublic);
|
|
||||||
unnotify(curvePublic);
|
|
||||||
var $chat = getChat(curvePublic);
|
|
||||||
hideInfo();
|
|
||||||
$messages.find('div.cp-app-contacts-chat[data-key]').hide();
|
|
||||||
if ($chat.length) {
|
|
||||||
var $chat_messages = $chat.find('div.cp-app-contacts-message');
|
|
||||||
if (!$chat_messages.length) {
|
|
||||||
var $more = $chat.find('.cp-app-contacts-more-history');
|
|
||||||
$more.click();
|
|
||||||
}
|
|
||||||
return void $chat.show();
|
|
||||||
}
|
|
||||||
messenger.getFriendInfo(curvePublic, function (e, info) {
|
|
||||||
if (e) { return void console.error(e); } // FIXME
|
|
||||||
var chatbox = markup.chatbox(curvePublic, info);
|
|
||||||
$messages.append(chatbox);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var removeFriend = function (curvePublic) {
|
|
||||||
messenger.removeFriend(curvePublic, function (e /*, removed */) {
|
|
||||||
if (e) { return void console.error(e); }
|
|
||||||
find.inList(curvePublic).remove();
|
|
||||||
//console.log(removed);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
markup.friend = function (data) {
|
|
||||||
var curvePublic = data.curvePublic;
|
|
||||||
var friend = h('div.cp-app-contacts-friend.cp-avatar', {
|
|
||||||
'data-key': curvePublic,
|
|
||||||
});
|
|
||||||
|
|
||||||
var remove = h('span.cp-app-contacts-remove.fa.fa-user-times', {
|
|
||||||
title: Messages.contacts_remove
|
|
||||||
});
|
|
||||||
var status = h('span.cp-app-contacts-status');
|
|
||||||
var rightCol = h('span.cp-app-contacts-right-col', [
|
|
||||||
h('span.cp-app-contacts-name', [data.displayName]),
|
|
||||||
remove,
|
|
||||||
]);
|
|
||||||
|
|
||||||
var $friend = $(friend)
|
|
||||||
.click(function () {
|
|
||||||
display(curvePublic);
|
|
||||||
})
|
|
||||||
.dblclick(function () {
|
|
||||||
if (data.profile) { window.open(origin + '/profile/#' + data.profile); }
|
|
||||||
});
|
|
||||||
|
|
||||||
$(remove).click(function (e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
Cryptpad.confirm(Messages._getKey('contacts_confirmRemove', [
|
|
||||||
Cryptpad.fixHTML(data.displayName)
|
|
||||||
]), function (yes) {
|
|
||||||
if (!yes) { return; }
|
|
||||||
removeFriend(curvePublic, function (e) {
|
|
||||||
if (e) { return void console.error(e); }
|
|
||||||
});
|
|
||||||
// TODO remove friend from userlist ui
|
|
||||||
// FIXME seems to trigger EJOINED from netflux-websocket (from server);
|
|
||||||
// (tried to join a channel in which you were already present)
|
|
||||||
}, undefined, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data.avatar && avatars[data.avatar]) {
|
|
||||||
$friend.append(avatars[data.avatar]);
|
|
||||||
$friend.append(rightCol);
|
|
||||||
} else {
|
|
||||||
common.displayAvatar($friend, data.avatar, data.displayName, function ($img) {
|
|
||||||
if (data.avatar && $img) {
|
|
||||||
avatars[data.avatar] = $img[0].outerHTML;
|
|
||||||
}
|
|
||||||
$friend.append(rightCol);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
$friend.append(status);
|
|
||||||
return $friend;
|
|
||||||
};
|
|
||||||
|
|
||||||
var isBottomedOut = function ($elem) {
|
|
||||||
return ($elem[0].scrollHeight - $elem.scrollTop() === $elem.outerHeight());
|
|
||||||
};
|
|
||||||
|
|
||||||
var initializing = true;
|
|
||||||
messenger.on('message', function (message) {
|
|
||||||
if (!initializing) { Cryptpad.notify(); }
|
|
||||||
var curvePublic = message.curve;
|
|
||||||
|
|
||||||
var name = displayNames[curvePublic];
|
|
||||||
var chat = getChat(curvePublic, name);
|
|
||||||
|
|
||||||
console.log(message);
|
|
||||||
|
|
||||||
var el_message = markup.message(message);
|
|
||||||
|
|
||||||
state.channels[curvePublic].messages.push(message);
|
|
||||||
|
|
||||||
var $chat = $(chat);
|
|
||||||
|
|
||||||
if (!$chat.length) {
|
|
||||||
console.error("Got a message but the chat isn't open");
|
|
||||||
}
|
|
||||||
|
|
||||||
var $messagebox = $chat.find('.cp-app-contacts-messages');
|
|
||||||
var shouldScroll = isBottomedOut($messagebox);
|
|
||||||
|
|
||||||
$messagebox.append(el_message);
|
|
||||||
|
|
||||||
if (shouldScroll) {
|
|
||||||
$messagebox.scrollTop($messagebox.outerHeight());
|
|
||||||
}
|
|
||||||
normalizeLabels($messagebox);
|
|
||||||
|
|
||||||
var channel = state.channels[curvePublic];
|
|
||||||
if (!channel) {
|
|
||||||
console.error('expected channel [%s] to be open', curvePublic);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isActive(curvePublic)) {
|
|
||||||
channel.HEAD = message.sig;
|
|
||||||
messenger.setChannelHead(curvePublic, message.sig, function (e) {
|
|
||||||
if (e) { return void console.error(e); }
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var lastMsg = channel.messages.slice(-1)[0];
|
|
||||||
if (lastMsg.sig !== channel.HEAD) {
|
|
||||||
return void notify(curvePublic);
|
|
||||||
}
|
|
||||||
unnotify(curvePublic);
|
|
||||||
});
|
|
||||||
|
|
||||||
messenger.on('join', function (curvePublic, channel) {
|
|
||||||
channel = channel;
|
|
||||||
updateStatus(curvePublic);
|
|
||||||
});
|
|
||||||
messenger.on('leave', function (curvePublic, channel) {
|
|
||||||
channel = channel;
|
|
||||||
updateStatus(curvePublic);
|
|
||||||
});
|
|
||||||
|
|
||||||
// change in your friend list
|
|
||||||
messenger.on('update', function (info, curvePublic) {
|
|
||||||
var name = displayNames[curvePublic] = info.displayName;
|
|
||||||
|
|
||||||
// update label in friend list
|
|
||||||
find.inList(curvePublic).find('.cp-app-contacts-name').text(name);
|
|
||||||
|
|
||||||
// update title bar and messages
|
|
||||||
$messages.find(dataQuery(curvePublic) + ' .cp-app-contacts-header ' +
|
|
||||||
'.cp-app-contacts-name, div.cp-app-contacts-message'+
|
|
||||||
dataQuery(curvePublic) + ' div.cp-app-contacts-sender').text(name).text(name);
|
|
||||||
});
|
|
||||||
|
|
||||||
var connectToFriend = function (curvePublic, cb) {
|
|
||||||
messenger.getFriendInfo(curvePublic, function (e, info) {
|
|
||||||
if (e) { return void console.error(e); }
|
|
||||||
var name = displayNames[curvePublic] = info.displayName;
|
|
||||||
initChannel(state, curvePublic, info);
|
|
||||||
|
|
||||||
var chatbox = markup.chatbox(curvePublic, info);
|
|
||||||
$(chatbox).hide();
|
|
||||||
$messages.append(chatbox);
|
|
||||||
|
|
||||||
var friend = markup.friend(info, name);
|
|
||||||
$userlist.append(friend);
|
|
||||||
messenger.openFriendChannel(curvePublic, function (e) {
|
|
||||||
if (e) { return void console.error(e); }
|
|
||||||
cb();
|
|
||||||
updateStatus(curvePublic);
|
|
||||||
// don't add friends that are already in your userlist
|
|
||||||
//if (friendExistsInUserList(k)) { return; }
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
messenger.on('friend', function (curvePublic) {
|
|
||||||
console.log('new friend: ', curvePublic);
|
|
||||||
//console.error("TODO redraw user list");
|
|
||||||
//console.error("TODO connect to new friend");
|
|
||||||
// FIXME this doesn't work right now because the friend hasn't been fully added?
|
|
||||||
connectToFriend(curvePublic, function () {
|
|
||||||
//console.error('connected');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
messenger.on('unfriend', function (curvePublic) {
|
|
||||||
console.log('unfriend', curvePublic);
|
|
||||||
find.inList(curvePublic).remove();
|
|
||||||
console.error('TODO remove chatbox');
|
|
||||||
console.error('TODO show something if that chatbox was active');
|
|
||||||
});
|
|
||||||
|
|
||||||
Cryptpad.onDisplayNameChanged(function () {
|
|
||||||
//messenger.checkNewFriends();
|
|
||||||
messenger.updateMyData();
|
|
||||||
});
|
|
||||||
|
|
||||||
// FIXME dirty hack
|
|
||||||
messenger.getMyInfo(function (e, info) {
|
|
||||||
displayNames[info.curvePublic] = info.displayName;
|
|
||||||
});
|
|
||||||
|
|
||||||
messenger.getFriendList(function (e, keys) {
|
|
||||||
var count = keys.length + 1;
|
|
||||||
var ready = function () {
|
|
||||||
count--;
|
|
||||||
if (count === 0) {
|
|
||||||
initializing = false;
|
|
||||||
Cryptpad.removeLoadingScreen();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
ready();
|
|
||||||
keys.forEach(function (curvePublic) {
|
|
||||||
connectToFriend(curvePublic, ready);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return UI;
|
|
||||||
});
|
|
Loading…
Reference in New Issue