Merge branch 'staging' of github.com:xwiki-labs/cryptpad into staging
commit
b2b1f08d01
@ -0,0 +1,299 @@
|
|||||||
|
define([
|
||||||
|
'jquery',
|
||||||
|
'/common/modes.js',
|
||||||
|
'/common/themes.js',
|
||||||
|
'/bower_components/file-saver/FileSaver.min.js'
|
||||||
|
], function ($, Modes, Themes) {
|
||||||
|
var saveAs = window.saveAs;
|
||||||
|
var module = {};
|
||||||
|
|
||||||
|
module.create = function (CMeditor, ifrw, Cryptpad) {
|
||||||
|
var exp = {};
|
||||||
|
|
||||||
|
var Messages = Cryptpad.Messages;
|
||||||
|
|
||||||
|
var CodeMirror = exp.CodeMirror = CMeditor;
|
||||||
|
CodeMirror.modeURL = "/bower_components/codemirror/mode/%N/%N.js";
|
||||||
|
|
||||||
|
var $pad = $('#pad-iframe');
|
||||||
|
var $textarea = exp.$textarea = $pad.contents().find('#editor1');
|
||||||
|
|
||||||
|
var Title;
|
||||||
|
var onLocal = function () {};
|
||||||
|
var $rightside;
|
||||||
|
exp.init = function (local, title, toolbar) {
|
||||||
|
if (typeof local === "function") {
|
||||||
|
onLocal = local;
|
||||||
|
}
|
||||||
|
Title = title;
|
||||||
|
$rightside = toolbar.$rightside;
|
||||||
|
};
|
||||||
|
|
||||||
|
var editor = exp.editor = CMeditor.fromTextArea($textarea[0], {
|
||||||
|
lineNumbers: true,
|
||||||
|
lineWrapping: true,
|
||||||
|
autoCloseBrackets: true,
|
||||||
|
matchBrackets : true,
|
||||||
|
showTrailingSpace : true,
|
||||||
|
styleActiveLine : true,
|
||||||
|
search: true,
|
||||||
|
highlightSelectionMatches: {showToken: /\w+/},
|
||||||
|
extraKeys: {"Shift-Ctrl-R": undefined},
|
||||||
|
foldGutter: true,
|
||||||
|
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
|
||||||
|
mode: "javascript",
|
||||||
|
readOnly: true
|
||||||
|
});
|
||||||
|
editor.setValue(Messages.codeInitialState);
|
||||||
|
|
||||||
|
var setMode = exp.setMode = function (mode) {
|
||||||
|
exp.highlightMode = mode;
|
||||||
|
if (mode === 'text') {
|
||||||
|
editor.setOption('mode', 'text');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CMeditor.autoLoadMode(editor, mode);
|
||||||
|
editor.setOption('mode', mode);
|
||||||
|
if (exp.$language) {
|
||||||
|
var name = exp.$language.find('a[data-value="' + mode + '"]').text() || 'Mode';
|
||||||
|
exp.$language.setValue(name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var setTheme = exp.setTheme = (function () {
|
||||||
|
var path = '/common/theme/';
|
||||||
|
|
||||||
|
var $head = $(ifrw.document.head);
|
||||||
|
|
||||||
|
var themeLoaded = exp.themeLoaded = function (theme) {
|
||||||
|
return $head.find('link[href*="'+theme+'"]').length;
|
||||||
|
};
|
||||||
|
|
||||||
|
var loadTheme = exp.loadTheme = function (theme) {
|
||||||
|
$head.append($('<link />', {
|
||||||
|
rel: 'stylesheet',
|
||||||
|
href: path + theme + '.css',
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
return function (theme, $select) {
|
||||||
|
if (!theme) {
|
||||||
|
editor.setOption('theme', 'default');
|
||||||
|
} else {
|
||||||
|
if (!themeLoaded(theme)) {
|
||||||
|
loadTheme(theme);
|
||||||
|
}
|
||||||
|
editor.setOption('theme', theme);
|
||||||
|
}
|
||||||
|
if ($select) {
|
||||||
|
$select.setValue(theme || 'Theme');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}());
|
||||||
|
|
||||||
|
exp.getHeadingText = function () {
|
||||||
|
var lines = editor.getValue().split(/\n/);
|
||||||
|
|
||||||
|
var text = '';
|
||||||
|
lines.some(function (line) {
|
||||||
|
// lisps?
|
||||||
|
var lispy = /^\s*(;|#\|)(.*?)$/;
|
||||||
|
if (lispy.test(line)) {
|
||||||
|
line.replace(lispy, function (a, one, two) {
|
||||||
|
text = two;
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// lines beginning with a hash are potentially valuable
|
||||||
|
// works for markdown, python, bash, etc.
|
||||||
|
var hash = /^#(.*?)$/;
|
||||||
|
if (hash.test(line)) {
|
||||||
|
line.replace(hash, function (a, one) {
|
||||||
|
text = one;
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// lines including a c-style comment are also valuable
|
||||||
|
var clike = /^\s*(\/\*|\/\/)(.*)?(\*\/)*$/;
|
||||||
|
if (clike.test(line)) {
|
||||||
|
line.replace(clike, function (a, one, two) {
|
||||||
|
if (!(two && two.replace)) { return; }
|
||||||
|
text = two.replace(/\*\/\s*$/, '').trim();
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO make one more pass for multiline comments
|
||||||
|
});
|
||||||
|
|
||||||
|
return text.trim();
|
||||||
|
};
|
||||||
|
|
||||||
|
exp.configureLanguage = function (cb) {
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
console.log('here');
|
||||||
|
var $block = exp.$language = Cryptpad.createDropdown(dropdownConfig);
|
||||||
|
console.log(exp);
|
||||||
|
$block.find('a').click(function () {
|
||||||
|
setMode($(this).attr('data-value'), $block);
|
||||||
|
onLocal();
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($rightside) { $rightside.append($block); }
|
||||||
|
cb();
|
||||||
|
};
|
||||||
|
|
||||||
|
exp.configureTheme = function () {
|
||||||
|
/* Remember the user's last choice of theme using localStorage */
|
||||||
|
var themeKey = 'CRYPTPAD_CODE_THEME';
|
||||||
|
var lastTheme = localStorage.getItem(themeKey) || 'default';
|
||||||
|
|
||||||
|
var options = [];
|
||||||
|
Themes.forEach(function (l) {
|
||||||
|
options.push({
|
||||||
|
tag: 'a',
|
||||||
|
attributes: {
|
||||||
|
'data-value': l.name,
|
||||||
|
'href': '#',
|
||||||
|
},
|
||||||
|
content: l.name // Pretty name of the language value
|
||||||
|
});
|
||||||
|
});
|
||||||
|
var dropdownConfig = {
|
||||||
|
text: 'Theme', // Button initial text
|
||||||
|
options: options, // Entries displayed in the menu
|
||||||
|
left: true, // Open to the left of the button
|
||||||
|
isSelect: true,
|
||||||
|
initialValue: lastTheme
|
||||||
|
};
|
||||||
|
var $block = exp.$theme = Cryptpad.createDropdown(dropdownConfig);
|
||||||
|
|
||||||
|
setTheme(lastTheme, $block);
|
||||||
|
|
||||||
|
$block.find('a').click(function () {
|
||||||
|
var theme = $(this).attr('data-value');
|
||||||
|
setTheme(theme, $block);
|
||||||
|
localStorage.setItem(themeKey, theme);
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($rightside) { $rightside.append($block); }
|
||||||
|
};
|
||||||
|
|
||||||
|
exp.exportText = function () {
|
||||||
|
var text = editor.getValue();
|
||||||
|
|
||||||
|
var ext = Modes.extensionOf(exp.highlightMode);
|
||||||
|
|
||||||
|
var title = Cryptpad.fixFileName(Title ? Title.suggestTitle('cryptpad') : "?") + (ext || '.txt');
|
||||||
|
|
||||||
|
Cryptpad.prompt(Messages.exportPrompt, title, function (filename) {
|
||||||
|
if (filename === null) { return; }
|
||||||
|
var blob = new Blob([text], {
|
||||||
|
type: 'text/plain;charset=utf-8'
|
||||||
|
});
|
||||||
|
saveAs(blob, filename);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
exp.importText = function (content, file) {
|
||||||
|
var $bar = ifrw.$('#cme_toolbox');
|
||||||
|
var mode;
|
||||||
|
var mime = CodeMirror.findModeByMIME(file.type);
|
||||||
|
|
||||||
|
if (!mime) {
|
||||||
|
var ext = /.+\.([^.]+)$/.exec(file.name);
|
||||||
|
if (ext[1]) {
|
||||||
|
mode = CMeditor.findModeByExtension(ext[1]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mode = mime && mime.mode || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode && Modes.list.some(function (o) { return o.mode === mode; })) {
|
||||||
|
setMode(mode);
|
||||||
|
$bar.find('#language-mode').val(mode);
|
||||||
|
} else {
|
||||||
|
console.log("Couldn't find a suitable highlighting mode: %s", mode);
|
||||||
|
setMode('text');
|
||||||
|
$bar.find('#language-mode').val('text');
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.setValue(content);
|
||||||
|
onLocal();
|
||||||
|
};
|
||||||
|
|
||||||
|
var cursorToPos = function(cursor, oldText) {
|
||||||
|
var cLine = cursor.line;
|
||||||
|
var cCh = cursor.ch;
|
||||||
|
var pos = 0;
|
||||||
|
var textLines = oldText.split("\n");
|
||||||
|
for (var line = 0; line <= cLine; line++) {
|
||||||
|
if(line < cLine) {
|
||||||
|
pos += textLines[line].length+1;
|
||||||
|
}
|
||||||
|
else if(line === cLine) {
|
||||||
|
pos += cCh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pos;
|
||||||
|
};
|
||||||
|
|
||||||
|
var posToCursor = function(position, newText) {
|
||||||
|
var cursor = {
|
||||||
|
line: 0,
|
||||||
|
ch: 0
|
||||||
|
};
|
||||||
|
var textLines = newText.substr(0, position).split("\n");
|
||||||
|
cursor.line = textLines.length - 1;
|
||||||
|
cursor.ch = textLines[cursor.line].length;
|
||||||
|
return cursor;
|
||||||
|
};
|
||||||
|
|
||||||
|
exp.setValueAndCursor = function (oldDoc, remoteDoc, TextPatcher) {
|
||||||
|
var scroll = editor.getScrollInfo();
|
||||||
|
//get old cursor here
|
||||||
|
var oldCursor = {};
|
||||||
|
oldCursor.selectionStart = cursorToPos(editor.getCursor('from'), oldDoc);
|
||||||
|
oldCursor.selectionEnd = cursorToPos(editor.getCursor('to'), oldDoc);
|
||||||
|
|
||||||
|
editor.setValue(remoteDoc);
|
||||||
|
editor.save();
|
||||||
|
|
||||||
|
var op = TextPatcher.diff(oldDoc, remoteDoc);
|
||||||
|
var selects = ['selectionStart', 'selectionEnd'].map(function (attr) {
|
||||||
|
return TextPatcher.transformCursor(oldCursor[attr], op);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(selects[0] === selects[1]) {
|
||||||
|
editor.setCursor(posToCursor(selects[0], remoteDoc));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
editor.setSelection(posToCursor(selects[0], remoteDoc), posToCursor(selects[1], remoteDoc));
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.scrollTo(scroll.left, scroll.top);
|
||||||
|
};
|
||||||
|
|
||||||
|
return exp;
|
||||||
|
};
|
||||||
|
|
||||||
|
return module;
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,51 @@
|
|||||||
|
define(function () {
|
||||||
|
var module = {};
|
||||||
|
|
||||||
|
module.create = function (UserList, Title, cfg) {
|
||||||
|
var exp = {};
|
||||||
|
|
||||||
|
exp.update = function (shjson) {
|
||||||
|
// Extract the user list (metadata) from the hyperjson
|
||||||
|
var json = (!shjson || typeof shjson !== "string") ? "" : JSON.parse(shjson);
|
||||||
|
var titleUpdated = false;
|
||||||
|
var metadata;
|
||||||
|
if (Array.isArray(json)) {
|
||||||
|
metadata = json[3] && json[3].metadata;
|
||||||
|
} else {
|
||||||
|
metadata = json.metadata;
|
||||||
|
}
|
||||||
|
if (typeof metadata === "object") {
|
||||||
|
if (metadata.users) {
|
||||||
|
var userData = metadata.users;
|
||||||
|
// Update the local user data
|
||||||
|
UserList.addToUserData(userData);
|
||||||
|
}
|
||||||
|
if (metadata.defaultTitle) {
|
||||||
|
Title.updateDefaultTitle(metadata.defaultTitle);
|
||||||
|
}
|
||||||
|
if (typeof metadata.title !== "undefined") {
|
||||||
|
Title.updateTitle(metadata.title || Title.defaultTitle);
|
||||||
|
titleUpdated = true;
|
||||||
|
}
|
||||||
|
if (metadata.slideOptions && cfg.slideOptions) {
|
||||||
|
cfg.slideOptions(metadata.slideOptions);
|
||||||
|
}
|
||||||
|
if (metadata.color && cfg.slideColors) {
|
||||||
|
cfg.slideColors(metadata.color, metadata.backColor);
|
||||||
|
}
|
||||||
|
if (typeof(metadata.palette) !== 'undefined' && cfg.updatePalette) {
|
||||||
|
cfg.updatePalette(metadata.palette);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!titleUpdated) {
|
||||||
|
Title.updateTitle(Title.defaultTitle);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return exp;
|
||||||
|
};
|
||||||
|
|
||||||
|
return module;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
@ -0,0 +1,84 @@
|
|||||||
|
define(function () {
|
||||||
|
var module = {};
|
||||||
|
|
||||||
|
module.create = function (cfg, onLocal, Cryptpad) {
|
||||||
|
var exp = {};
|
||||||
|
|
||||||
|
var parsed = exp.parsedHref = Cryptpad.parsePadUrl(window.location.href);
|
||||||
|
exp.defaultTitle = Cryptpad.getDefaultName(parsed);
|
||||||
|
|
||||||
|
exp.title = document.title; // TOOD slides
|
||||||
|
|
||||||
|
cfg = cfg || {};
|
||||||
|
|
||||||
|
var getHeadingText = cfg.getHeadingText || function () { return; };
|
||||||
|
var updateLocalTitle = function (newTitle) {
|
||||||
|
exp.title = newTitle;
|
||||||
|
if (typeof cfg.updateLocalTitle === "function") {
|
||||||
|
cfg.updateLocalTitle(newTitle);
|
||||||
|
} else {
|
||||||
|
document.title = newTitle;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var $title;
|
||||||
|
exp.setToolbar = function (toolbar) {
|
||||||
|
$title = toolbar && toolbar.title;
|
||||||
|
};
|
||||||
|
|
||||||
|
exp.getTitle = function () { return exp.title; };
|
||||||
|
var isDefaultTitle = exp.isDefaultTitle = function (){return exp.title === exp.defaultTitle;};
|
||||||
|
|
||||||
|
var suggestTitle = exp.suggestTitle = function (fallback) {
|
||||||
|
if (isDefaultTitle()) {
|
||||||
|
return getHeadingText() || fallback || "";
|
||||||
|
} else {
|
||||||
|
return exp.title || getHeadingText() || exp.defaultTitle;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var renameCb = function (err, newTitle) {
|
||||||
|
if (err) { return; }
|
||||||
|
updateLocalTitle(newTitle);
|
||||||
|
console.log('here');
|
||||||
|
onLocal();
|
||||||
|
};
|
||||||
|
|
||||||
|
exp.updateTitle = function (newTitle) {
|
||||||
|
if (newTitle === exp.title) { return; }
|
||||||
|
// Change the title now, and set it back to the old value if there is an error
|
||||||
|
var oldTitle = exp.title;
|
||||||
|
Cryptpad.renamePad(newTitle, function (err, data) {
|
||||||
|
if (err) {
|
||||||
|
console.log("Couldn't set pad title");
|
||||||
|
console.error(err);
|
||||||
|
updateLocalTitle(oldTitle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateLocalTitle(data);
|
||||||
|
if (!$title) { return; }
|
||||||
|
$title.find('span.title').text(data);
|
||||||
|
$title.find('input').val(data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exp.updateDefaultTitle = function (newDefaultTitle) {
|
||||||
|
exp.defaultTitle = newDefaultTitle;
|
||||||
|
if (!$title) { return; }
|
||||||
|
$title.find('input').attr("placeholder", exp.defaultTitle);
|
||||||
|
};
|
||||||
|
|
||||||
|
exp.getTitleConfig = function () {
|
||||||
|
return {
|
||||||
|
onRename: renameCb,
|
||||||
|
suggestName: suggestTitle,
|
||||||
|
defaultName: exp.defaultTitle
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return exp;
|
||||||
|
};
|
||||||
|
|
||||||
|
return module;
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,105 @@
|
|||||||
|
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(),
|
||||||
|
};
|
||||||
|
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(); }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
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(),
|
||||||
|
};
|
||||||
|
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 one or more lines are too long
@ -0,0 +1,905 @@
|
|||||||
|
define([
|
||||||
|
'jquery',
|
||||||
|
'/customize/application_config.js',
|
||||||
|
'/api/config'
|
||||||
|
], function ($, Config, ApiConfig) {
|
||||||
|
var Messages = {};
|
||||||
|
var Cryptpad;
|
||||||
|
|
||||||
|
var Bar = {
|
||||||
|
constants: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
var SPINNER_DISAPPEAR_TIME = 3000;
|
||||||
|
|
||||||
|
// Toolbar parts
|
||||||
|
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';
|
||||||
|
|
||||||
|
// Userlist
|
||||||
|
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";
|
||||||
|
|
||||||
|
// Top parts
|
||||||
|
var USER_CLS = Bar.constants.userAdmin = "cryptpad-user";
|
||||||
|
var SPINNER_CLS = Bar.constants.spinner = 'cryptpad-spinner';
|
||||||
|
var STATE_CLS = Bar.constants.state = 'cryptpad-state';
|
||||||
|
var LAG_CLS = Bar.constants.lag = 'cryptpad-lag';
|
||||||
|
var LIMIT_CLS = Bar.constants.lag = 'cryptpad-limit';
|
||||||
|
var TITLE_CLS = Bar.constants.title = "cryptpad-title";
|
||||||
|
var NEWPAD_CLS = Bar.constants.newpad = "cryptpad-newpad";
|
||||||
|
|
||||||
|
// User admin menu
|
||||||
|
var USERADMIN_CLS = Bar.constants.user = 'cryptpad-user-dropdown';
|
||||||
|
var USERNAME_CLS = Bar.constants.username = 'cryptpad-toolbar-username';
|
||||||
|
var READONLY_CLS = Bar.constants.readonly = 'cryptpad-readonly';
|
||||||
|
var USERBUTTON_CLS = Bar.constants.changeUsername = "cryptpad-change-username";
|
||||||
|
|
||||||
|
// Create the toolbar element
|
||||||
|
|
||||||
|
var uid = function () {
|
||||||
|
return 'cryptpad-uid-' + String(Math.random()).substring(2);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (config) {
|
||||||
|
if (!config.$container) { return; }
|
||||||
|
var $container = config.$container;
|
||||||
|
var $toolbar = $('<div>', {
|
||||||
|
'class': TOOLBAR_CLS,
|
||||||
|
id: uid(),
|
||||||
|
});
|
||||||
|
|
||||||
|
var $topContainer = $('<div>', {'class': TOP_CLS});
|
||||||
|
var $userContainer = $('<span>', {
|
||||||
|
'class': USER_CLS
|
||||||
|
}).appendTo($topContainer);
|
||||||
|
$('<span>', {'class': SPINNER_CLS}).hide().appendTo($userContainer);
|
||||||
|
$('<span>', {'class': STATE_CLS}).hide().appendTo($userContainer);
|
||||||
|
$('<span>', {'class': LAG_CLS}).hide().appendTo($userContainer);
|
||||||
|
$('<span>', {'class': LIMIT_CLS}).hide().appendTo($userContainer);
|
||||||
|
$('<span>', {'class': NEWPAD_CLS + ' dropdown-bar'}).hide().appendTo($userContainer);
|
||||||
|
$('<span>', {'class': USERADMIN_CLS + ' dropdown-bar'}).hide().appendTo($userContainer);
|
||||||
|
|
||||||
|
$toolbar.append($topContainer)
|
||||||
|
.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.title || typeof config.title !== "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;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Userlist elements
|
||||||
|
|
||||||
|
var checkSynchronizing = function (toolbar, config) {
|
||||||
|
if (!toolbar.state) { return; }
|
||||||
|
var userList = config.userList.list.users;
|
||||||
|
var userNetfluxId = config.userList.userNetfluxId;
|
||||||
|
var meIdx = userList.indexOf(userNetfluxId);
|
||||||
|
if (meIdx === -1) {
|
||||||
|
toolbar.state.text(Messages.synchronizing);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toolbar.state.text('');
|
||||||
|
};
|
||||||
|
var getOtherUsers = function(config) {
|
||||||
|
var userList = config.userList.list.users;
|
||||||
|
var userData = config.userList.data;
|
||||||
|
var userNetfluxId = config.userList.userNetfluxId;
|
||||||
|
|
||||||
|
var i = 0; // duplicates counter
|
||||||
|
var list = [];
|
||||||
|
|
||||||
|
// Display only one time each user (if he is connected in multiple tabs)
|
||||||
|
var myUid = userData[userNetfluxId] ? userData[userNetfluxId].uid : undefined;
|
||||||
|
var uids = [];
|
||||||
|
userList.forEach(function(user) {
|
||||||
|
if (user !== userNetfluxId) {
|
||||||
|
var data = userData[user] || {};
|
||||||
|
var userName = data.name;
|
||||||
|
var userId = data.uid;
|
||||||
|
if (userName && uids.indexOf(userId) === -1 && (!myUid || userId !== myUid)) {
|
||||||
|
uids.push(userId);
|
||||||
|
list.push(userName);
|
||||||
|
} else if (userName) { i++; }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
list: list,
|
||||||
|
duplicates: i
|
||||||
|
};
|
||||||
|
};
|
||||||
|
var arrayIntersect = function(a, b) {
|
||||||
|
return $.grep(a, function(i) {
|
||||||
|
return $.inArray(i, b) > -1;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var updateUserList = function (toolbar, config) {
|
||||||
|
// Make sure the elements are displayed
|
||||||
|
var $userButtons = toolbar.userlist;
|
||||||
|
|
||||||
|
var userList = config.userList.list.users;
|
||||||
|
var userData = config.userList.data;
|
||||||
|
var userNetfluxId = config.userList.userNetfluxId;
|
||||||
|
|
||||||
|
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 = config.readOnly === -1 ? userList : arrayIntersect(userList, Object.keys(userData));
|
||||||
|
|
||||||
|
// Names of editing users
|
||||||
|
var others = getOtherUsers(config);
|
||||||
|
var editUsersNames = others.list;
|
||||||
|
var duplicates = others.duplicates; // Number of duplicates
|
||||||
|
|
||||||
|
var numberOfEditUsers = userList.length - duplicates;
|
||||||
|
var numberOfViewUsers = numberOfUsers - userList.length;
|
||||||
|
|
||||||
|
// 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>');
|
||||||
|
// Yourself (edit only)
|
||||||
|
if (config.readOnly !== 1) {
|
||||||
|
$editUsers.append('<span class="yourself">' + Messages.yourself + '</span>');
|
||||||
|
anonymous--;
|
||||||
|
}
|
||||||
|
// Editors
|
||||||
|
$editUsersList.text(editUsersNames.join('\n')); // .text() to avoid XSS
|
||||||
|
$editUsers.append($editUsersList);
|
||||||
|
// Anonymous editors
|
||||||
|
if (anonymous > 0) {
|
||||||
|
var text = anonymous === 1 ? Messages.anonymousUser : Messages.anonymousUsers;
|
||||||
|
$editUsers.append('<span class="anonymous">' + anonymous + ' ' + text + '</span>');
|
||||||
|
}
|
||||||
|
// Viewers
|
||||||
|
if (numberOfViewUsers > 0) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Change username in useradmin dropdown
|
||||||
|
if (config.displayed.indexOf('useradmin') !== -1) {
|
||||||
|
var $userAdminElement = toolbar.$userAdmin;
|
||||||
|
var $userElement = $userAdminElement.find('.' + USERNAME_CLS);
|
||||||
|
$userElement.show();
|
||||||
|
if (config.readOnly === 1) {
|
||||||
|
$userElement.addClass(READONLY_CLS).text(Messages.readonly);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var name = userData[userNetfluxId] && userData[userNetfluxId].name;
|
||||||
|
if (!name) {
|
||||||
|
name = Messages.anonymous;
|
||||||
|
}
|
||||||
|
$userElement.removeClass(READONLY_CLS).text(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var initUserList = function (toolbar, config) {
|
||||||
|
if (config.userList && config.userList.list && config.userList.userNetfluxId) {
|
||||||
|
var userList = config.userList.list;
|
||||||
|
userList.change.push(function () {
|
||||||
|
var users = userList.users;
|
||||||
|
if (users.indexOf(config.userList.userNetfluxId) !== -1) {toolbar.connected = true;}
|
||||||
|
if (!toolbar.connected) { return; }
|
||||||
|
checkSynchronizing(toolbar, config);
|
||||||
|
if (config.userList.data) {
|
||||||
|
updateUserList(toolbar, config);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Create sub-elements
|
||||||
|
|
||||||
|
var createUserList = function (toolbar, config) {
|
||||||
|
if (!config.userList || !config.userList.list ||
|
||||||
|
!config.userList.data || !config.userList.userNetfluxId) {
|
||||||
|
throw new Error("You must provide a `userList` object to display the userlist");
|
||||||
|
}
|
||||||
|
var dropdownConfig = {
|
||||||
|
options: [{
|
||||||
|
tag: 'p',
|
||||||
|
attributes: {'class': USERLIST_CLS},
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
var $block = Cryptpad.createDropdown(dropdownConfig);
|
||||||
|
$block.attr('id', 'userButtons');
|
||||||
|
toolbar.$leftside.prepend($block);
|
||||||
|
|
||||||
|
return $block;
|
||||||
|
};
|
||||||
|
|
||||||
|
var createShare = function (toolbar, config) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
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); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toolbar.$leftside.append($shareBlock);
|
||||||
|
toolbar.share = $shareBlock;
|
||||||
|
});
|
||||||
|
|
||||||
|
return "Loading share button";
|
||||||
|
};
|
||||||
|
|
||||||
|
var createFileShare = function (toolbar) {
|
||||||
|
if (!window.location.hash) {
|
||||||
|
throw new Error("Unable to display the share button: hash required in the URL");
|
||||||
|
}
|
||||||
|
var $shareIcon = $('<span>', {'class': 'fa fa-share-alt'});
|
||||||
|
var $span = $('<span>', {'class': 'large'}).append(' ' +Messages.shareButton);
|
||||||
|
var $button = $('<button>', {'id': 'shareButton'}).append($shareIcon).append($span);
|
||||||
|
$button.click(function () {
|
||||||
|
var url = window.location.href;
|
||||||
|
var success = Cryptpad.Clipboard.copy(url);
|
||||||
|
if (success) { Cryptpad.log(Messages.shareSuccess); }
|
||||||
|
});
|
||||||
|
|
||||||
|
toolbar.$leftside.append($button);
|
||||||
|
return $button;
|
||||||
|
};
|
||||||
|
|
||||||
|
var createTitle = function (toolbar, config) {
|
||||||
|
var $titleContainer = $('<span>', {
|
||||||
|
id: 'toolbarTitle',
|
||||||
|
'class': TITLE_CLS
|
||||||
|
}).appendTo(toolbar.$top);
|
||||||
|
|
||||||
|
// TODO: move these functions to toolbar or common?
|
||||||
|
if (typeof config.title !== "object") {
|
||||||
|
console.error("config.title", config);
|
||||||
|
throw new Error("config.title is not an object");
|
||||||
|
}
|
||||||
|
var callback = config.title.onRename;
|
||||||
|
var placeholder = config.title.defaultName;
|
||||||
|
var suggestName = config.title.suggestName;
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
var $text = $('<span>', {
|
||||||
|
'class': 'title'
|
||||||
|
}).appendTo($titleContainer);
|
||||||
|
var $pencilIcon = $('<span>', {
|
||||||
|
'class': 'pencilIcon',
|
||||||
|
'title': Messages.clickToEdit
|
||||||
|
});
|
||||||
|
if (config.readOnly === 1 || typeof(Cryptpad) === "undefined") { return $titleContainer; }
|
||||||
|
var $input = $('<input>', {
|
||||||
|
type: 'text',
|
||||||
|
placeholder: placeholder
|
||||||
|
}).appendTo($titleContainer).hide();
|
||||||
|
if (config.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 && toolbar.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 (toolbar.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 createLinkToMain = function (toolbar) {
|
||||||
|
var $linkContainer = $('<span>', {
|
||||||
|
'class': "cryptpad-link"
|
||||||
|
}).appendTo(toolbar.$top);
|
||||||
|
var $imgTag = $('<img>', {
|
||||||
|
src: "/customize/cryptofist_mini.png",
|
||||||
|
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);
|
||||||
|
|
||||||
|
return $linkContainer;
|
||||||
|
};
|
||||||
|
|
||||||
|
var checkLag = function (toolbar, config, $lagEl) {
|
||||||
|
var lag;
|
||||||
|
var $lag = $lagEl || toolbar.lag;
|
||||||
|
if (!$lag) { return; }
|
||||||
|
var getLag = config.network.getLag;
|
||||||
|
if(typeof getLag === "function") {
|
||||||
|
lag = getLag();
|
||||||
|
}
|
||||||
|
var lagLight = $('<div>', {
|
||||||
|
'class': 'lag'
|
||||||
|
});
|
||||||
|
var title;
|
||||||
|
if (lag && toolbar.connected) {
|
||||||
|
$lag.attr('class', LAG_CLS);
|
||||||
|
toolbar.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 (!toolbar.firstConnection) {
|
||||||
|
$lag.attr('class', LAG_CLS);
|
||||||
|
// 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 createLag = function (toolbar, config) {
|
||||||
|
var $a = toolbar.$userAdmin.find('.'+LAG_CLS).show();
|
||||||
|
$('<span>', {'class': 'bar1'}).appendTo($a);
|
||||||
|
$('<span>', {'class': 'bar2'}).appendTo($a);
|
||||||
|
$('<span>', {'class': 'bar3'}).appendTo($a);
|
||||||
|
$('<span>', {'class': 'bar4'}).appendTo($a);
|
||||||
|
if (config.realtime) {
|
||||||
|
checkLag(toolbar, config, $a);
|
||||||
|
setInterval(function () {
|
||||||
|
if (!toolbar.connected) { return; }
|
||||||
|
checkLag(toolbar, config);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
return $a;
|
||||||
|
};
|
||||||
|
|
||||||
|
var kickSpinner = function (toolbar, config, local) {
|
||||||
|
if (!toolbar.spinner) { return; }
|
||||||
|
var $spin = toolbar.spinner;
|
||||||
|
$spin.find('.spin').show();
|
||||||
|
$spin.find('.synced').hide();
|
||||||
|
var onSynced = function () {
|
||||||
|
if ($spin.timeout) { clearTimeout($spin.timeout); }
|
||||||
|
$spin.timeout = setTimeout(function () {
|
||||||
|
$spin.find('.spin').hide();
|
||||||
|
$spin.find('.synced').show();
|
||||||
|
}, local ? 0 : SPINNER_DISAPPEAR_TIME);
|
||||||
|
};
|
||||||
|
if (Cryptpad) {
|
||||||
|
Cryptpad.whenRealtimeSyncs(config.realtime, onSynced);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onSynced();
|
||||||
|
};
|
||||||
|
var ks = function (toolbar, config, local) {
|
||||||
|
return function () {
|
||||||
|
if (toolbar.connected) { kickSpinner(toolbar, config, local); }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
var createSpinner = function (toolbar, config) {
|
||||||
|
var $spin = toolbar.$userAdmin.find('.'+SPINNER_CLS).show();
|
||||||
|
$('<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);
|
||||||
|
toolbar.$userAdmin.prepend($spin);
|
||||||
|
if (config.realtime) {
|
||||||
|
config.realtime.onPatch(ks(toolbar, config));
|
||||||
|
config.realtime.onMessage(ks(toolbar, config, true));
|
||||||
|
}
|
||||||
|
return $spin;
|
||||||
|
};
|
||||||
|
|
||||||
|
var createState = function (toolbar) {
|
||||||
|
return toolbar.$userAdmin.find('.'+STATE_CLS).text(Messages.synchronizing).show();
|
||||||
|
};
|
||||||
|
|
||||||
|
var createLimit = function (toolbar) {
|
||||||
|
if (!Config.enablePinning) { return; }
|
||||||
|
var $limitIcon = $('<span>', {'class': 'fa fa-exclamation-triangle'});
|
||||||
|
var $limit = toolbar.$userAdmin.find('.'+LIMIT_CLS).attr({
|
||||||
|
'title': Messages.pinLimitReached
|
||||||
|
}).append($limitIcon).hide();
|
||||||
|
var todo = function (e, overLimit) {
|
||||||
|
if (e) { return void console.error("Unable to get the pinned usage"); }
|
||||||
|
if (overLimit) {
|
||||||
|
$limit.show().click(function () {
|
||||||
|
Cryptpad.alert(Messages.pinLimitReachedAlert, null, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Cryptpad.isOverPinLimit(todo);
|
||||||
|
return $limit;
|
||||||
|
};
|
||||||
|
|
||||||
|
var createNewPad = function (toolbar) {
|
||||||
|
var $newPad = toolbar.$userAdmin.find('.'+NEWPAD_CLS).show();
|
||||||
|
|
||||||
|
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,
|
||||||
|
container: $newPad
|
||||||
|
};
|
||||||
|
var $newPadBlock = Cryptpad.createDropdown(dropdownConfig);
|
||||||
|
$newPadBlock.find('button').attr('title', Messages.newButtonTitle);
|
||||||
|
$newPadBlock.find('button').attr('id', 'newdoc');
|
||||||
|
return $newPadBlock;
|
||||||
|
};
|
||||||
|
|
||||||
|
var createUserAdmin = function (toolbar, config) {
|
||||||
|
var $userAdmin = toolbar.$userAdmin.find('.'+USERADMIN_CLS).show();
|
||||||
|
var userMenuCfg = {
|
||||||
|
$initBlock: $userAdmin
|
||||||
|
};
|
||||||
|
if (!config.hideDisplayName) { // TODO: config.userAdmin.hideDisplayName?
|
||||||
|
$.extend(true, userMenuCfg, {
|
||||||
|
displayNameCls: USERNAME_CLS,
|
||||||
|
changeNameButtonCls: USERBUTTON_CLS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (config.readOnly !== 1) {
|
||||||
|
userMenuCfg.displayName = 1;
|
||||||
|
userMenuCfg.displayChangeName = 1;
|
||||||
|
}
|
||||||
|
Cryptpad.createUserAdminMenu(userMenuCfg);
|
||||||
|
|
||||||
|
var $userButton = toolbar.$userNameButton = $userAdmin.find('a.' + USERBUTTON_CLS);
|
||||||
|
$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 () {
|
||||||
|
Cryptpad.findCancelButton().click();
|
||||||
|
});
|
||||||
|
|
||||||
|
return $userAdmin;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Events
|
||||||
|
var initClickEvents = function (toolbar, config) {
|
||||||
|
var removeDropdowns = function () {
|
||||||
|
toolbar.$toolbar.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 || !toolbar.title) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var $title = toolbar.title;
|
||||||
|
if (!$title.find('input').is(':visible')) { return; }
|
||||||
|
|
||||||
|
// Press enter
|
||||||
|
var ev = $.Event("keyup");
|
||||||
|
ev.which = 13;
|
||||||
|
$title.find('input').trigger(ev);
|
||||||
|
};
|
||||||
|
// Click in the main window
|
||||||
|
var w = config.ifrw || window;
|
||||||
|
$(w).on('click', removeDropdowns);
|
||||||
|
$(w).on('click', cancelEditTitle);
|
||||||
|
// Click in iframes
|
||||||
|
try {
|
||||||
|
if (w.$ && w.$('iframe').length) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Notifications
|
||||||
|
var initNotifications = function (toolbar, config) {
|
||||||
|
// Display notifications when users are joining/leaving the session
|
||||||
|
var oldUserData;
|
||||||
|
if (!config.userList || !config.userList.list || !config.userList.userNetfluxId) { return; }
|
||||||
|
var userList = config.userList.list;
|
||||||
|
var userNetfluxId = config.userList.userNetfluxId;
|
||||||
|
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;
|
||||||
|
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 (config.readOnly === 0 && !oldUserData[userNetfluxId]) {
|
||||||
|
oldUserData = JSON.parse(JSON.stringify(newdata));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (var k in newdata) {
|
||||||
|
if (k !== userNetfluxId && 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));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Main
|
||||||
|
|
||||||
|
Bar.create = function (cfg) {
|
||||||
|
var config = cfg || {};
|
||||||
|
Cryptpad = config.common;
|
||||||
|
Messages = Cryptpad.Messages;
|
||||||
|
config.readOnly = (typeof config.readOnly !== "undefined") ? (config.readOnly ? 1 : 0) : -1;
|
||||||
|
config.displayed = config.displayed || [];
|
||||||
|
config.network = cfg.network || Cryptpad.getNetwork();
|
||||||
|
|
||||||
|
var toolbar = {};
|
||||||
|
|
||||||
|
toolbar.connected = false;
|
||||||
|
toolbar.firstConnection = true;
|
||||||
|
|
||||||
|
var $toolbar = toolbar.$toolbar = createRealtimeToolbar(config);
|
||||||
|
toolbar.$leftside = $toolbar.find('.'+Bar.constants.leftside);
|
||||||
|
toolbar.$rightside = $toolbar.find('.'+Bar.constants.rightside);
|
||||||
|
toolbar.$top = $toolbar.find('.'+Bar.constants.top);
|
||||||
|
toolbar.$history = $toolbar.find('.'+Bar.constants.history);
|
||||||
|
|
||||||
|
toolbar.$userAdmin = $toolbar.find('.'+Bar.constants.userAdmin);
|
||||||
|
|
||||||
|
// Create the subelements
|
||||||
|
var tb = {};
|
||||||
|
tb['userlist'] = createUserList;
|
||||||
|
tb['share'] = createShare;
|
||||||
|
tb['fileshare'] = createFileShare;
|
||||||
|
tb['title'] = createTitle;
|
||||||
|
tb['lag'] = createLag;
|
||||||
|
tb['spinner'] = createSpinner;
|
||||||
|
tb['state'] = createState;
|
||||||
|
tb['limit'] = createLimit;
|
||||||
|
tb['newpad'] = createNewPad;
|
||||||
|
tb['useradmin'] = createUserAdmin;
|
||||||
|
|
||||||
|
|
||||||
|
var addElement = toolbar.addElement = function (arr, additionnalCfg, init) {
|
||||||
|
if (typeof additionnalCfg === "object") { $.extend(true, config, additionnalCfg); }
|
||||||
|
arr.forEach(function (el) {
|
||||||
|
if (typeof el !== "string" || !el.trim()) { return; }
|
||||||
|
if (typeof tb[el] === "function") {
|
||||||
|
if (!init && config.displayed.indexOf(el) !== -1) { return; } // Already done
|
||||||
|
toolbar[el] = tb[el](toolbar, config);
|
||||||
|
if (!init) { config.displayed.push(el); }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
addElement(config.displayed, {}, true);
|
||||||
|
initUserList(toolbar, config);
|
||||||
|
|
||||||
|
toolbar['linkToMain'] = createLinkToMain(toolbar, config);
|
||||||
|
|
||||||
|
if (!config.realtime) { toolbar.connected = true; }
|
||||||
|
|
||||||
|
initClickEvents(toolbar, config);
|
||||||
|
initNotifications(toolbar, config);
|
||||||
|
|
||||||
|
var failed = toolbar.failed = function () {
|
||||||
|
toolbar.connected = false;
|
||||||
|
if (toolbar.state) {
|
||||||
|
toolbar.state.text(Messages.disconnected);
|
||||||
|
}
|
||||||
|
checkLag(toolbar, config);
|
||||||
|
};
|
||||||
|
toolbar.reconnecting = function (userId) {
|
||||||
|
if (config.userList) { config.userList.userNetfluxId = userId; }
|
||||||
|
toolbar.connected = false;
|
||||||
|
if (toolbar.state) {
|
||||||
|
toolbar.state.text(Messages.reconnecting);
|
||||||
|
}
|
||||||
|
checkLag(toolbar, config);
|
||||||
|
};
|
||||||
|
|
||||||
|
// On log out, remove permanently the realtime elements of the toolbar
|
||||||
|
Cryptpad.onLogout(function () {
|
||||||
|
failed();
|
||||||
|
if (toolbar.useradmin) { toolbar.useradmin.hide(); }
|
||||||
|
if (toolbar.userlist) { toolbar.userlist.hide(); }
|
||||||
|
});
|
||||||
|
|
||||||
|
return toolbar;
|
||||||
|
};
|
||||||
|
|
||||||
|
return Bar;
|
||||||
|
});
|
File diff suppressed because it is too large
Load Diff
@ -1,52 +0,0 @@
|
|||||||
define([
|
|
||||||
'jquery',
|
|
||||||
'/bower_components/chainpad-crypto/crypto.js',
|
|
||||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
|
||||||
'/common/toolbar.js',
|
|
||||||
'/common/cryptpad-common.js',
|
|
||||||
'/common/visible.js',
|
|
||||||
'/common/notify.js',
|
|
||||||
'/bower_components/tweetnacl/nacl-fast.min.js',
|
|
||||||
], function ($, Crypto, realtimeInput, Toolbar, Cryptpad, Visible, Notify) {
|
|
||||||
var Messages = Cryptpad.Messages;
|
|
||||||
window.Nacl = window.nacl;
|
|
||||||
$(function () {
|
|
||||||
|
|
||||||
var ifrw = $('#pad-iframe')[0].contentWindow;
|
|
||||||
var $iframe = $('#pad-iframe').contents();
|
|
||||||
|
|
||||||
Cryptpad.addLoadingScreen();
|
|
||||||
|
|
||||||
var andThen = function () {
|
|
||||||
var $bar = $iframe.find('.toolbar-container');
|
|
||||||
var secret = Cryptpad.getSecrets();
|
|
||||||
var readOnly = secret.keys && !secret.keys.editKeyStr;
|
|
||||||
if (!secret.keys) {
|
|
||||||
secret.keys = secret.key;
|
|
||||||
}
|
|
||||||
|
|
||||||
var $mt = $iframe.find('#encryptedFile');
|
|
||||||
$mt.attr('src', './assets/image.png-encrypted');
|
|
||||||
$mt.attr('data-crypto-key', 'TBo77200c0e/FdldQFcnQx4Y');
|
|
||||||
$mt.attr('data-type', 'image/png');
|
|
||||||
require(['/common/media-tag.js'], function (MediaTag) {
|
|
||||||
MediaTag($mt[0]);
|
|
||||||
Cryptpad.removeLoadingScreen();
|
|
||||||
var configTb = {
|
|
||||||
displayed: ['useradmin', 'newpad'],
|
|
||||||
ifrw: ifrw,
|
|
||||||
common: Cryptpad
|
|
||||||
};
|
|
||||||
toolbar = Toolbar.create($bar, null, null, null, null, configTb);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
Cryptpad.ready(function (err, anv) {
|
|
||||||
andThen();
|
|
||||||
Cryptpad.reportAppUsage();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,77 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
||||||
<script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
|
|
||||||
<style>
|
|
||||||
html, body{
|
|
||||||
padding: 0px;
|
|
||||||
margin: 0px;
|
|
||||||
overflow: hidden;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
textarea{
|
|
||||||
position: absolute;
|
|
||||||
top: 5vh;
|
|
||||||
left: 0px;
|
|
||||||
border: 0px;
|
|
||||||
|
|
||||||
padding-top: 15px;
|
|
||||||
width: 100%;
|
|
||||||
height: 95vh;
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 100vh;
|
|
||||||
|
|
||||||
font-size: 30px;
|
|
||||||
background-color: #073642;
|
|
||||||
color: #839496;
|
|
||||||
|
|
||||||
overflow-x: hidden;
|
|
||||||
|
|
||||||
/* disallow textarea resizes */
|
|
||||||
resize: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea[disabled] {
|
|
||||||
background-color: #275662;
|
|
||||||
color: #637476;
|
|
||||||
}
|
|
||||||
|
|
||||||
#panel {
|
|
||||||
position: fixed;
|
|
||||||
top: 0px;
|
|
||||||
right: 0px;
|
|
||||||
width: 100%;
|
|
||||||
height: 5vh;
|
|
||||||
z-index: 95;
|
|
||||||
background-color: #777;
|
|
||||||
/* min-height: 75px; */
|
|
||||||
}
|
|
||||||
#run {
|
|
||||||
display: block;
|
|
||||||
float: right;
|
|
||||||
height: 100%;
|
|
||||||
width: 10vw;
|
|
||||||
z-index: 100;
|
|
||||||
line-height: 5vw;
|
|
||||||
font-size: 1.5em;
|
|
||||||
background-color: #222;
|
|
||||||
color: #CCC;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 5%;
|
|
||||||
border: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<textarea></textarea>
|
|
||||||
<div id="panel">
|
|
||||||
<!-- TODO update this element when new users join -->
|
|
||||||
<span id="users"></span>
|
|
||||||
<!-- what else should go in the panel? -->
|
|
||||||
<a href="#" id="run">RUN</a>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,161 +0,0 @@
|
|||||||
define([
|
|
||||||
'jquery',
|
|
||||||
'/api/config',
|
|
||||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
|
||||||
'/bower_components/chainpad-crypto/crypto.js',
|
|
||||||
'/bower_components/textpatcher/TextPatcher.amd.js',
|
|
||||||
'/common/cryptpad-common.js'
|
|
||||||
], function ($, Config, Realtime, Crypto, TextPatcher, Cryptpad) {
|
|
||||||
|
|
||||||
var secret = Cryptpad.getSecrets();
|
|
||||||
|
|
||||||
var $textarea = $('textarea'),
|
|
||||||
$run = $('#run');
|
|
||||||
|
|
||||||
var module = {};
|
|
||||||
|
|
||||||
var config = {
|
|
||||||
initialState: '',
|
|
||||||
websocketURL: Config.websocketURL,
|
|
||||||
channel: secret.channel,
|
|
||||||
crypto: Crypto.createEncryptor(secret.key),
|
|
||||||
};
|
|
||||||
var initializing = true;
|
|
||||||
|
|
||||||
var setEditable = function (bool) { $textarea.attr('disabled', !bool); };
|
|
||||||
var canonicalize = function (text) { return text.replace(/\r\n/g, '\n'); };
|
|
||||||
|
|
||||||
setEditable(false);
|
|
||||||
|
|
||||||
var onInit = config.onInit = function (info) {
|
|
||||||
window.location.hash = info.channel + secret.key;
|
|
||||||
$(window).on('hashchange', function() { window.location.reload(); });
|
|
||||||
};
|
|
||||||
|
|
||||||
var onRemote = config.onRemote = function (info) {
|
|
||||||
if (initializing) { return; }
|
|
||||||
|
|
||||||
var userDoc = info.realtime.getUserDoc();
|
|
||||||
var current = canonicalize($textarea.val());
|
|
||||||
|
|
||||||
var op = TextPatcher.diff(current, userDoc);
|
|
||||||
|
|
||||||
var elem = $textarea[0];
|
|
||||||
|
|
||||||
var selects = ['selectionStart', 'selectionEnd'].map(function (attr) {
|
|
||||||
return TextPatcher.transformCursor(elem[attr], op);
|
|
||||||
});
|
|
||||||
|
|
||||||
$textarea.val(userDoc);
|
|
||||||
elem.selectionStart = selects[0];
|
|
||||||
elem.selectionEnd = selects[1];
|
|
||||||
|
|
||||||
// TODO do something on external messages
|
|
||||||
// http://webdesign.tutsplus.com/tutorials/how-to-display-update-notifications-in-the-browser-tab--cms-23458
|
|
||||||
};
|
|
||||||
|
|
||||||
var onReady = config.onReady = function (info) {
|
|
||||||
module.patchText = TextPatcher.create({
|
|
||||||
realtime: info.realtime
|
|
||||||
// logging: true
|
|
||||||
});
|
|
||||||
initializing = false;
|
|
||||||
setEditable(true);
|
|
||||||
$textarea.val(info.realtime.getUserDoc());
|
|
||||||
};
|
|
||||||
|
|
||||||
var onAbort = config.onAbort = function (info) {
|
|
||||||
setEditable(false);
|
|
||||||
window.alert("Server Connection Lost");
|
|
||||||
};
|
|
||||||
|
|
||||||
var onLocal = config.onLocal = function () {
|
|
||||||
if (initializing) { return; }
|
|
||||||
module.patchText(canonicalize($textarea.val()));
|
|
||||||
};
|
|
||||||
|
|
||||||
var rt = window.rt = Realtime.start(config);
|
|
||||||
|
|
||||||
var splice = function (str, index, chars) {
|
|
||||||
var count = chars.length;
|
|
||||||
return str.slice(0, index) + chars + str.slice((index -1) + count);
|
|
||||||
};
|
|
||||||
|
|
||||||
var setSelectionRange = function (input, start, end) {
|
|
||||||
if (input.setSelectionRange) {
|
|
||||||
input.focus();
|
|
||||||
input.setSelectionRange(start, end);
|
|
||||||
} else if (input.createTextRange) {
|
|
||||||
var range = input.createTextRange();
|
|
||||||
range.collapse(true);
|
|
||||||
range.moveEnd('character', end);
|
|
||||||
range.moveStart('character', start);
|
|
||||||
range.select();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var setCursor = function (el, pos) {
|
|
||||||
setSelectionRange(el, pos, pos);
|
|
||||||
};
|
|
||||||
|
|
||||||
var state = {};
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
$textarea.on('keydown', function (e) {
|
|
||||||
// track when control keys are pushed down
|
|
||||||
//switch (e.key) { }
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
$textarea.on('keyup', function (e) {
|
|
||||||
// track when control keys are released
|
|
||||||
});
|
|
||||||
|
|
||||||
//$textarea.on('change', onLocal);
|
|
||||||
$textarea.on('keypress', function (e) {
|
|
||||||
onLocal();
|
|
||||||
switch (e.key) {
|
|
||||||
case 'Tab':
|
|
||||||
// insert a tab wherever the cursor is...
|
|
||||||
var start = $textarea.prop('selectionStart');
|
|
||||||
var end = $textarea.prop('selectionEnd');
|
|
||||||
if (typeof start !== 'undefined') {
|
|
||||||
if (start === end) {
|
|
||||||
$textarea.val(function (i, val) {
|
|
||||||
return splice(val, start, "\t");
|
|
||||||
});
|
|
||||||
setCursor($textarea[0], start +1);
|
|
||||||
} else {
|
|
||||||
// indentation?? this ought to be fun.
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// simulate a keypress so the event goes through..
|
|
||||||
// prevent default behaviour for tab
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
onLocal();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
['cut', 'paste', 'change', 'keyup', 'keydown', 'select', 'textInput']
|
|
||||||
.forEach(function (evt) {
|
|
||||||
$textarea.on(evt, onLocal);
|
|
||||||
});
|
|
||||||
|
|
||||||
$run.click(function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
var content = $textarea.val();
|
|
||||||
|
|
||||||
try {
|
|
||||||
eval(content); // jshint ignore:line
|
|
||||||
} catch (err) {
|
|
||||||
// FIXME don't use alert, make an errorbox
|
|
||||||
window.alert(err.message);
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,75 +0,0 @@
|
|||||||
define([
|
|
||||||
'jquery',
|
|
||||||
'/common/cryptget.js',
|
|
||||||
'/bower_components/chainpad-crypto/crypto.js'
|
|
||||||
], function ($, Crypt, Crypto) {
|
|
||||||
var Nacl = window.nacl;
|
|
||||||
|
|
||||||
var key = Nacl.randomBytes(32);
|
|
||||||
|
|
||||||
var handleFile = function (body) {
|
|
||||||
//console.log("plaintext");
|
|
||||||
//console.log(body);
|
|
||||||
|
|
||||||
/*
|
|
||||||
0 && Crypt.put(body, function (e, out) {
|
|
||||||
if (e) { return void console.error(e); }
|
|
||||||
if (out) {
|
|
||||||
console.log(out);
|
|
||||||
}
|
|
||||||
}); */
|
|
||||||
|
|
||||||
var data = {};
|
|
||||||
|
|
||||||
(function () {
|
|
||||||
var cyphertext = data.payload = Crypto.encrypt(body, key);
|
|
||||||
console.log("encrypted");
|
|
||||||
console.log(cyphertext);
|
|
||||||
|
|
||||||
console.log(data);
|
|
||||||
|
|
||||||
var decrypted = Crypto.decrypt(cyphertext, key);
|
|
||||||
//console.log('decrypted');
|
|
||||||
//console.log(decrypted);
|
|
||||||
|
|
||||||
|
|
||||||
if (decrypted !== body) {
|
|
||||||
throw new Error("failed to maintain integrity with round trip");
|
|
||||||
}
|
|
||||||
|
|
||||||
// finding... files are entirely too large.
|
|
||||||
|
|
||||||
|
|
||||||
console.log(data.payload.length, body.length); // 1491393, 588323
|
|
||||||
console.log(body.length / data.payload.length); // 0.3944788529918003
|
|
||||||
console.log(data.payload.length / body.length); // 2.534990132971174
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
http://stackoverflow.com/questions/19959072/sending-binary-data-in-javascript-over-http
|
|
||||||
|
|
||||||
// Since we deal with Firefox and Chrome only
|
|
||||||
var bytesToSend = [253, 0, 128, 1];
|
|
||||||
var bytesArray = new Uint8Array(bytesToSend);
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: '%your_service_url%',
|
|
||||||
type: 'POST',
|
|
||||||
contentType: 'application/octet-stream',
|
|
||||||
data: bytesArray,
|
|
||||||
processData: false
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
})();
|
|
||||||
};
|
|
||||||
|
|
||||||
var $file = $('input[type="file"]');
|
|
||||||
$file.on('change', function (e) {
|
|
||||||
var file = e.target.files[0];
|
|
||||||
var reader = new FileReader();
|
|
||||||
reader.onload = function (e) {
|
|
||||||
handleFile(e.target.result);
|
|
||||||
};
|
|
||||||
reader.readAsText(file);
|
|
||||||
});
|
|
||||||
});
|
|
@ -0,0 +1,188 @@
|
|||||||
|
define([
|
||||||
|
'/bower_components/tweetnacl/nacl-fast.min.js',
|
||||||
|
], function () {
|
||||||
|
var Nacl = window.nacl;
|
||||||
|
var PARANOIA = true;
|
||||||
|
|
||||||
|
var plainChunkLength = 128 * 1024;
|
||||||
|
var cypherChunkLength = 131088;
|
||||||
|
|
||||||
|
var encodePrefix = function (p) {
|
||||||
|
return [
|
||||||
|
65280, // 255 << 8
|
||||||
|
255,
|
||||||
|
].map(function (n, i) {
|
||||||
|
return (p & n) >> ((1 - i) * 8);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var decodePrefix = function (A) {
|
||||||
|
return (A[0] << 8) | A[1];
|
||||||
|
};
|
||||||
|
|
||||||
|
var slice = function (A) {
|
||||||
|
return Array.prototype.slice.call(A);
|
||||||
|
};
|
||||||
|
|
||||||
|
var createNonce = function () {
|
||||||
|
return new Uint8Array(new Array(24).fill(0));
|
||||||
|
};
|
||||||
|
|
||||||
|
var increment = function (N) {
|
||||||
|
var l = N.length;
|
||||||
|
while (l-- > 1) {
|
||||||
|
if (PARANOIA) {
|
||||||
|
if (typeof(N[l]) !== 'number') {
|
||||||
|
throw new Error('E_UNSAFE_TYPE');
|
||||||
|
}
|
||||||
|
if (N[l] > 255) {
|
||||||
|
throw new Error('E_OUT_OF_BOUNDS');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* jshint probably suspects this is unsafe because we lack types
|
||||||
|
but as long as this is only used on nonces, it should be safe */
|
||||||
|
if (N[l] !== 255) { return void N[l]++; } // jshint ignore:line
|
||||||
|
N[l] = 0;
|
||||||
|
|
||||||
|
// you don't need to worry about this running out.
|
||||||
|
// you'd need a REAAAALLY big file
|
||||||
|
if (l === 0) {
|
||||||
|
throw new Error('E_NONCE_TOO_LARGE');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var joinChunks = function (chunks) {
|
||||||
|
return new Uint8Array(chunks.reduce(function (A, B) {
|
||||||
|
return slice(A).concat(slice(B));
|
||||||
|
}, []));
|
||||||
|
};
|
||||||
|
|
||||||
|
var decrypt = function (u8, key, cb) {
|
||||||
|
var fail = function (e) {
|
||||||
|
cb(e || "DECRYPTION_ERROR");
|
||||||
|
};
|
||||||
|
|
||||||
|
var nonce = createNonce();
|
||||||
|
var i = 0;
|
||||||
|
|
||||||
|
var prefix = u8.subarray(0, 2);
|
||||||
|
var metadataLength = decodePrefix(prefix);
|
||||||
|
|
||||||
|
var res = {
|
||||||
|
metadata: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
var metaBox = new Uint8Array(u8.subarray(2, 2 + metadataLength));
|
||||||
|
|
||||||
|
var metaChunk = Nacl.secretbox.open(metaBox, nonce, key);
|
||||||
|
increment(nonce);
|
||||||
|
|
||||||
|
try {
|
||||||
|
res.metadata = JSON.parse(Nacl.util.encodeUTF8(metaChunk));
|
||||||
|
} catch (e) {
|
||||||
|
return fail('E_METADATA_DECRYPTION');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!res.metadata) {
|
||||||
|
return void setTimeout(function () {
|
||||||
|
cb('NO_METADATA');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var takeChunk = function () {
|
||||||
|
var start = i * cypherChunkLength + 2 + metadataLength;
|
||||||
|
var end = start + cypherChunkLength;
|
||||||
|
i++;
|
||||||
|
var box = new Uint8Array(u8.subarray(start, end));
|
||||||
|
|
||||||
|
// decrypt the chunk
|
||||||
|
var plaintext = Nacl.secretbox.open(box, nonce, key);
|
||||||
|
increment(nonce);
|
||||||
|
return plaintext;
|
||||||
|
};
|
||||||
|
|
||||||
|
var chunks = [];
|
||||||
|
// decrypt file contents
|
||||||
|
var chunk;
|
||||||
|
for (;i * cypherChunkLength < u8.length;) {
|
||||||
|
chunk = takeChunk();
|
||||||
|
if (!chunk) {
|
||||||
|
return window.setTimeout(fail);
|
||||||
|
}
|
||||||
|
chunks.push(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
// send chunks
|
||||||
|
res.content = joinChunks(chunks);
|
||||||
|
|
||||||
|
cb(void 0, res);
|
||||||
|
};
|
||||||
|
|
||||||
|
// metadata
|
||||||
|
/* { filename: 'raccoon.jpg', type: 'image/jpeg' } */
|
||||||
|
var encrypt = function (u8, metadata, key) {
|
||||||
|
var nonce = createNonce();
|
||||||
|
|
||||||
|
// encode metadata
|
||||||
|
var metaBuffer = Array.prototype.slice
|
||||||
|
.call(Nacl.util.decodeUTF8(JSON.stringify(metadata)));
|
||||||
|
|
||||||
|
var plaintext = new Uint8Array(metaBuffer);
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
0: metadata
|
||||||
|
1: u8
|
||||||
|
2: done
|
||||||
|
*/
|
||||||
|
|
||||||
|
var state = 0;
|
||||||
|
|
||||||
|
var next = function (cb) {
|
||||||
|
var start;
|
||||||
|
var end;
|
||||||
|
var part;
|
||||||
|
var box;
|
||||||
|
|
||||||
|
// DONE
|
||||||
|
if (state === 2) { return void cb(); }
|
||||||
|
|
||||||
|
if (state === 0) { // metadata...
|
||||||
|
part = new Uint8Array(plaintext);
|
||||||
|
box = Nacl.secretbox(part, nonce, key);
|
||||||
|
increment(nonce);
|
||||||
|
|
||||||
|
if (box.length > 65535) {
|
||||||
|
return void cb('METADATA_TOO_LARGE');
|
||||||
|
}
|
||||||
|
var prefixed = new Uint8Array(encodePrefix(box.length)
|
||||||
|
.concat(slice(box)));
|
||||||
|
state++;
|
||||||
|
return void cb(void 0, prefixed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// encrypt the rest of the file...
|
||||||
|
start = i * plainChunkLength;
|
||||||
|
end = start + plainChunkLength;
|
||||||
|
|
||||||
|
part = u8.subarray(start, end);
|
||||||
|
box = Nacl.secretbox(part, nonce, key);
|
||||||
|
increment(nonce);
|
||||||
|
i++;
|
||||||
|
|
||||||
|
// regular data is done
|
||||||
|
if (i * plainChunkLength >= u8.length) { state = 2; }
|
||||||
|
|
||||||
|
return void cb(void 0, box);
|
||||||
|
};
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
decrypt: decrypt,
|
||||||
|
encrypt: encrypt,
|
||||||
|
joinChunks: joinChunks,
|
||||||
|
};
|
||||||
|
});
|
@ -0,0 +1,65 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||||
|
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css">
|
||||||
|
<script src="/bower_components/jquery/dist/jquery.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="/bower_components/bootstrap/dist/css/bootstrap.min.css">
|
||||||
|
<style>
|
||||||
|
html, body {
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
.cryptpad-toolbar {
|
||||||
|
margin-bottom: 1px;
|
||||||
|
padding: 0px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
#file {
|
||||||
|
display: block;
|
||||||
|
height: 300px;
|
||||||
|
width: 300px;
|
||||||
|
border: 2px solid black;
|
||||||
|
margin: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputfile {
|
||||||
|
width: 0.1px;
|
||||||
|
height: 0.1px;
|
||||||
|
opacity: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block {
|
||||||
|
display: block;
|
||||||
|
height: 500px;
|
||||||
|
width: 500px;
|
||||||
|
}
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.inputfile + label {
|
||||||
|
border: 2px solid black;
|
||||||
|
background-color: rgba(50, 50, 50, .10);
|
||||||
|
margin: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputfile:focus + label,
|
||||||
|
.inputfile + label:hover {
|
||||||
|
background-color: rgba(50, 50, 50, 0.30);
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="toolbar" class="toolbar-container"></div>
|
||||||
|
<div id="upload-form" style="display: none;">
|
||||||
|
<input type="file" name="file" id="file" class="inputfile" />
|
||||||
|
<label for="file" class="block">Choose a file</label>
|
||||||
|
</div>
|
||||||
|
<div id="feedback" class="block hidden">
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,236 @@
|
|||||||
|
define([
|
||||||
|
'jquery',
|
||||||
|
'/bower_components/chainpad-crypto/crypto.js',
|
||||||
|
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||||
|
'/common/toolbar2.js',
|
||||||
|
'/common/cryptpad-common.js',
|
||||||
|
'/common/visible.js',
|
||||||
|
'/common/notify.js',
|
||||||
|
'/file/file-crypto.js',
|
||||||
|
'/bower_components/tweetnacl/nacl-fast.min.js',
|
||||||
|
'/bower_components/file-saver/FileSaver.min.js',
|
||||||
|
], function ($, Crypto, realtimeInput, Toolbar, Cryptpad, Visible, Notify, FileCrypto) {
|
||||||
|
var Messages = Cryptpad.Messages;
|
||||||
|
var saveAs = window.saveAs;
|
||||||
|
var Nacl = window.nacl;
|
||||||
|
|
||||||
|
var APP = {};
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
|
||||||
|
var ifrw = $('#pad-iframe')[0].contentWindow;
|
||||||
|
var $iframe = $('#pad-iframe').contents();
|
||||||
|
var $form = $iframe.find('#upload-form');
|
||||||
|
|
||||||
|
Cryptpad.addLoadingScreen();
|
||||||
|
|
||||||
|
var Title;
|
||||||
|
|
||||||
|
var myFile;
|
||||||
|
var myDataType;
|
||||||
|
|
||||||
|
var upload = function (blob, metadata) {
|
||||||
|
console.log(metadata);
|
||||||
|
var u8 = new Uint8Array(blob);
|
||||||
|
|
||||||
|
var key = Nacl.randomBytes(32);
|
||||||
|
var next = FileCrypto.encrypt(u8, metadata, key);
|
||||||
|
|
||||||
|
var chunks = [];
|
||||||
|
|
||||||
|
var sendChunk = function (box, cb) {
|
||||||
|
var enc = Nacl.util.encodeBase64(box);
|
||||||
|
|
||||||
|
chunks.push(box);
|
||||||
|
Cryptpad.rpc.send('UPLOAD', enc, function (e, msg) {
|
||||||
|
cb(e, msg);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var again = function (err, box) {
|
||||||
|
if (err) { throw new Error(err); }
|
||||||
|
if (box) {
|
||||||
|
return void sendChunk(box, function (e) {
|
||||||
|
if (e) { return console.error(e); }
|
||||||
|
next(again);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// if not box then done
|
||||||
|
Cryptpad.rpc.send('UPLOAD_COMPLETE', '', function (e, res) {
|
||||||
|
if (e) { return void console.error(e); }
|
||||||
|
var id = res[0];
|
||||||
|
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);
|
||||||
|
window.location.hash = Cryptpad.getFileHashFromKeys(id, b64Key);
|
||||||
|
|
||||||
|
$form.hide();
|
||||||
|
|
||||||
|
APP.toolbar.addElement(['fileshare'], {});
|
||||||
|
|
||||||
|
// check if the uploaded file can be decrypted
|
||||||
|
var newU8 = FileCrypto.joinChunks(chunks);
|
||||||
|
FileCrypto.decrypt(newU8, key, function (e, res) {
|
||||||
|
if (e) { return console.error(e); }
|
||||||
|
var title = document.title = res.metadata.name;
|
||||||
|
myFile = res.content;
|
||||||
|
myDataType = res.metadata.type;
|
||||||
|
|
||||||
|
var defaultName = Cryptpad.getDefaultName(Cryptpad.parsePadUrl(window.location.href));
|
||||||
|
Title.updateTitle(title || defaultName);
|
||||||
|
APP.toolbar.title.show();
|
||||||
|
Cryptpad.alert("successfully uploaded: " + title);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Cryptpad.rpc.send('UPLOAD_STATUS', '', function (e, pending) {
|
||||||
|
if (e) {
|
||||||
|
console.error(e);
|
||||||
|
return void Cryptpad.alert("something went wrong");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pending[0]) {
|
||||||
|
return void Cryptpad.confirm('upload pending, abort?', function (yes) {
|
||||||
|
if (!yes) { return; }
|
||||||
|
Cryptpad.rpc.send('UPLOAD_CANCEL', '', function (e, res) {
|
||||||
|
if (e) { return void console.error(e); }
|
||||||
|
console.log(res);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
next(again);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var uploadMode = false;
|
||||||
|
|
||||||
|
var andThen = function () {
|
||||||
|
var $bar = $iframe.find('.toolbar-container');
|
||||||
|
|
||||||
|
var secret;
|
||||||
|
var hexFileName;
|
||||||
|
if (window.location.hash) {
|
||||||
|
secret = Cryptpad.getSecrets();
|
||||||
|
if (!secret.keys) { throw new Error("You need a hash"); } // TODO
|
||||||
|
hexFileName = Cryptpad.base64ToHex(secret.channel);
|
||||||
|
} else {
|
||||||
|
uploadMode = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var getTitle = function () {
|
||||||
|
var pad = Cryptpad.getRelativeHref(window.location.href);
|
||||||
|
var fo = Cryptpad.getStore().getProxy().fo;
|
||||||
|
var data = fo.getFileData(pad);
|
||||||
|
return data ? data.title : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
var exportFile = function () {
|
||||||
|
var suggestion = document.title;
|
||||||
|
Cryptpad.prompt(Messages.exportPrompt,
|
||||||
|
Cryptpad.fixFileName(suggestion), function (filename) {
|
||||||
|
if (!(typeof(filename) === 'string' && filename)) { return; }
|
||||||
|
var blob = new Blob([myFile], {type: myDataType});
|
||||||
|
saveAs(blob, filename);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Title = Cryptpad.createTitle({}, function(){}, Cryptpad);
|
||||||
|
|
||||||
|
var displayed = ['title', 'useradmin', 'newpad', 'limit'];
|
||||||
|
if (secret && hexFileName) {
|
||||||
|
displayed.push('fileshare');
|
||||||
|
}
|
||||||
|
|
||||||
|
var configTb = {
|
||||||
|
displayed: displayed,
|
||||||
|
ifrw: ifrw,
|
||||||
|
common: Cryptpad,
|
||||||
|
title: Title.getTitleConfig(),
|
||||||
|
hideDisplayName: true,
|
||||||
|
$container: $bar
|
||||||
|
};
|
||||||
|
var toolbar = APP.toolbar = Toolbar.create(configTb);
|
||||||
|
|
||||||
|
Title.setToolbar(toolbar);
|
||||||
|
|
||||||
|
if (uploadMode) { toolbar.title.hide(); }
|
||||||
|
|
||||||
|
var $rightside = toolbar.$rightside;
|
||||||
|
|
||||||
|
var $export = Cryptpad.createButton('export', true, {}, exportFile);
|
||||||
|
$rightside.append($export);
|
||||||
|
|
||||||
|
Title.updateTitle(Cryptpad.initialName || getTitle() || Title.defaultTitle);
|
||||||
|
|
||||||
|
if (!uploadMode) {
|
||||||
|
var src = Cryptpad.getBlobPathFromHex(hexFileName);
|
||||||
|
return Cryptpad.fetch(src, function (e, u8) {
|
||||||
|
// now decrypt the u8
|
||||||
|
if (e) { return window.alert('error'); }
|
||||||
|
var cryptKey = secret.keys && secret.keys.fileKeyStr;
|
||||||
|
var key = Nacl.util.decodeBase64(cryptKey);
|
||||||
|
|
||||||
|
FileCrypto.decrypt(u8, key, function (e, data) {
|
||||||
|
if (e) {
|
||||||
|
Cryptpad.removeLoadingScreen();
|
||||||
|
return console.error(e);
|
||||||
|
}
|
||||||
|
console.log(data);
|
||||||
|
var title = document.title = data.metadata.name;
|
||||||
|
myFile = data.content;
|
||||||
|
myDataType = data.metadata.type;
|
||||||
|
Title.updateTitle(title || Title.defaultTitle);
|
||||||
|
Cryptpad.removeLoadingScreen();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Cryptpad.isLoggedIn()) {
|
||||||
|
return Cryptpad.alert("You must be logged in to upload files");
|
||||||
|
}
|
||||||
|
|
||||||
|
$form.css({
|
||||||
|
display: 'block',
|
||||||
|
});
|
||||||
|
|
||||||
|
var handleFile = function (file) {
|
||||||
|
console.log(file);
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onloadend = function () {
|
||||||
|
upload(this.result, {
|
||||||
|
name: file.name,
|
||||||
|
type: file.type,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
$form.find("#file").on('change', function (e) {
|
||||||
|
var file = e.target.files[0];
|
||||||
|
handleFile(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
$form
|
||||||
|
.on('drag dragstart dragend dragover dragenter dragleave drop', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
})
|
||||||
|
.on('drop', function (e) {
|
||||||
|
var dropped = e.originalEvent.dataTransfer.files;
|
||||||
|
handleFile(dropped[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// we're in upload mode
|
||||||
|
Cryptpad.removeLoadingScreen();
|
||||||
|
};
|
||||||
|
|
||||||
|
Cryptpad.ready(function () {
|
||||||
|
andThen();
|
||||||
|
Cryptpad.reportAppUsage();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
@ -1,28 +1,16 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html class="cp pad">
|
||||||
<head>
|
<head>
|
||||||
|
<title>CryptPad</title>
|
||||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css">
|
||||||
<script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
|
<script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
|
||||||
<link rel="icon" type="image/png"
|
<link rel="icon" type="image/png"
|
||||||
href="/customize/main-favicon.png"
|
href="/customize/main-favicon.png"
|
||||||
|
|
||||||
data-main-favicon="/customize/main-favicon.png"
|
data-main-favicon="/customize/main-favicon.png"
|
||||||
data-alt-favicon="/customize/alt-favicon.png"
|
data-alt-favicon="/customize/alt-favicon.png"
|
||||||
id="favicon" />
|
id="favicon" />
|
||||||
<style>
|
<link rel="stylesheet" href="/customize/main.css" />
|
||||||
input {
|
|
||||||
width: 50vw;
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
pre {
|
|
||||||
max-width: 90vw;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Upload</h1>
|
|
||||||
|
|
||||||
<input type="file">
|
|
@ -0,0 +1,87 @@
|
|||||||
|
define([
|
||||||
|
'jquery',
|
||||||
|
'/bower_components/chainpad-crypto/crypto.js',
|
||||||
|
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||||
|
'/common/toolbar.js',
|
||||||
|
'/common/cryptpad-common.js',
|
||||||
|
'/common/visible.js',
|
||||||
|
'/common/notify.js',
|
||||||
|
'/file/file-crypto.js',
|
||||||
|
'/bower_components/tweetnacl/nacl-fast.min.js',
|
||||||
|
'/bower_components/file-saver/FileSaver.min.js',
|
||||||
|
], function ($, Crypto, realtimeInput, Toolbar, Cryptpad, Visible, Notify, FileCrypto) {
|
||||||
|
var Nacl = window.nacl;
|
||||||
|
$(function () {
|
||||||
|
|
||||||
|
var filesAreSame = function (a, b) {
|
||||||
|
var l = a.length;
|
||||||
|
if (l !== b.length) { return false; }
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
for (; i < l; i++) { if (a[i] !== b[i]) { return false; } }
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
var metadataIsSame = function (A, B) {
|
||||||
|
return !Object.keys(A).some(function (k) {
|
||||||
|
return A[k] !== B[k];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var upload = function (blob, metadata) {
|
||||||
|
var u8 = new Uint8Array(blob);
|
||||||
|
var key = Nacl.randomBytes(32);
|
||||||
|
|
||||||
|
var next = FileCrypto.encrypt(u8, metadata, key);
|
||||||
|
|
||||||
|
var chunks = [];
|
||||||
|
var sendChunk = function (box, cb) {
|
||||||
|
chunks.push(box);
|
||||||
|
cb();
|
||||||
|
};
|
||||||
|
|
||||||
|
var again = function (err, box) {
|
||||||
|
if (err) { throw new Error(err); }
|
||||||
|
|
||||||
|
if (box) {
|
||||||
|
return void sendChunk(box, function (e) {
|
||||||
|
if (e) {
|
||||||
|
console.error(e);
|
||||||
|
return Cryptpad.alert('Something went wrong');
|
||||||
|
}
|
||||||
|
next(again);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// check if the uploaded file can be decrypted
|
||||||
|
var newU8 = FileCrypto.joinChunks(chunks);
|
||||||
|
|
||||||
|
console.log('encrypted file with metadata is %s uint8s', newU8.length);
|
||||||
|
FileCrypto.decrypt(newU8, key, function (e, res) {
|
||||||
|
if (e) { return Cryptpad.alert(e); }
|
||||||
|
|
||||||
|
if (filesAreSame(blob, res.content) &&
|
||||||
|
metadataIsSame(res.metadata, metadata)) {
|
||||||
|
Cryptpad.alert("successfully uploaded");
|
||||||
|
} else {
|
||||||
|
Cryptpad.alert('encryption failure!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
next(again);
|
||||||
|
};
|
||||||
|
|
||||||
|
var andThen = function () {
|
||||||
|
var src = '/customize/cryptofist_mini.png';
|
||||||
|
Cryptpad.fetch(src, function (e, file) {
|
||||||
|
console.log('original file is %s uint8s', file.length);
|
||||||
|
upload(file, {
|
||||||
|
pew: 'pew',
|
||||||
|
bang: 'bang',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
andThen();
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
Binary file not shown.
@ -0,0 +1,47 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html class="cp pad">
|
||||||
|
<head>
|
||||||
|
<title>CryptPad</title>
|
||||||
|
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css">
|
||||||
|
<script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
|
||||||
|
<link rel="icon" type="image/png"
|
||||||
|
href="/customize/main-favicon.png"
|
||||||
|
data-main-favicon="/customize/main-favicon.png"
|
||||||
|
data-alt-favicon="/customize/alt-favicon.png"
|
||||||
|
id="favicon" />
|
||||||
|
<link rel="stylesheet" href="/customize/main.css" />
|
||||||
|
<style>
|
||||||
|
html, body {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
#pad-iframe {
|
||||||
|
position:fixed;
|
||||||
|
top:0px;
|
||||||
|
left:0px;
|
||||||
|
bottom:0px;
|
||||||
|
right:0px;
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
border:none;
|
||||||
|
margin:0;
|
||||||
|
padding:0;
|
||||||
|
overflow:hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<iframe id="pad-iframe"></iframe><script src="/common/noscriptfix.js"></script>
|
||||||
|
<div id="loading">
|
||||||
|
<div class="loadingContainer">
|
||||||
|
<img class="cryptofist" src="/customize/cryptofist_small.png" />
|
||||||
|
<div class="spinnerContainer">
|
||||||
|
<span class="fa fa-spinner fa-pulse fa-4x fa-fw"></span>
|
||||||
|
</div>
|
||||||
|
<p data-localization="loading"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,106 @@
|
|||||||
|
define([
|
||||||
|
'jquery',
|
||||||
|
'/bower_components/chainpad-crypto/crypto.js',
|
||||||
|
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||||
|
'/common/toolbar.js',
|
||||||
|
'/common/cryptpad-common.js',
|
||||||
|
//'/common/visible.js',
|
||||||
|
//'/common/notify.js',
|
||||||
|
'/bower_components/tweetnacl/nacl-fast.min.js',
|
||||||
|
'/bower_components/file-saver/FileSaver.min.js',
|
||||||
|
], function ($, Crypto, realtimeInput, Toolbar, Cryptpad /*, Visible, Notify*/) {
|
||||||
|
//var Messages = Cryptpad.Messages;
|
||||||
|
//var saveAs = window.saveAs;
|
||||||
|
//window.Nacl = window.nacl;
|
||||||
|
$(function () {
|
||||||
|
|
||||||
|
var ifrw = $('#pad-iframe')[0].contentWindow;
|
||||||
|
var $iframe = $('#pad-iframe').contents();
|
||||||
|
|
||||||
|
Cryptpad.addLoadingScreen();
|
||||||
|
|
||||||
|
var andThen = function () {
|
||||||
|
var $bar = $iframe.find('.toolbar-container');
|
||||||
|
var secret = Cryptpad.getSecrets();
|
||||||
|
|
||||||
|
if (!secret.keys) { throw new Error("You need a hash"); } // TODO
|
||||||
|
|
||||||
|
var cryptKey = secret.keys && secret.keys.fileKeyStr;
|
||||||
|
var fileId = secret.channel;
|
||||||
|
var hexFileName = Cryptpad.base64ToHex(fileId);
|
||||||
|
var type = "image/png";
|
||||||
|
|
||||||
|
var parsed = Cryptpad.parsePadUrl(window.location.href);
|
||||||
|
var defaultName = Cryptpad.getDefaultName(parsed);
|
||||||
|
|
||||||
|
var getTitle = function () {
|
||||||
|
var pad = Cryptpad.getRelativeHref(window.location.href);
|
||||||
|
var fo = Cryptpad.getStore().getProxy().fo;
|
||||||
|
var data = fo.getFileData(pad);
|
||||||
|
return data ? data.title : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
var updateTitle = function (newTitle) {
|
||||||
|
Cryptpad.renamePad(newTitle, function (err, data) {
|
||||||
|
if (err) {
|
||||||
|
console.log("Couldn't set pad title");
|
||||||
|
console.error(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.title = newTitle;
|
||||||
|
$bar.find('.' + Toolbar.constants.title).find('span.title').text(data);
|
||||||
|
$bar.find('.' + Toolbar.constants.title).find('input').val(data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var suggestName = function () {
|
||||||
|
return document.title || getTitle() || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
var renameCb = function (err, title) {
|
||||||
|
document.title = title;
|
||||||
|
};
|
||||||
|
|
||||||
|
var $mt = $iframe.find('#encryptedFile');
|
||||||
|
$mt.attr('src', '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName);
|
||||||
|
$mt.attr('data-crypto-key', 'cryptpad:'+cryptKey);
|
||||||
|
$mt.attr('data-type', type);
|
||||||
|
|
||||||
|
window.onMediaMetadata = function (metadata) {
|
||||||
|
if (!metadata || metadata.name !== defaultName) { return; }
|
||||||
|
var title = document.title = metadata.name;
|
||||||
|
updateTitle(title || defaultName);
|
||||||
|
};
|
||||||
|
|
||||||
|
require(['/common/media-tag.js'], function (MediaTag) {
|
||||||
|
var configTb = {
|
||||||
|
displayed: ['useradmin', 'share', 'newpad'],
|
||||||
|
ifrw: ifrw,
|
||||||
|
common: Cryptpad,
|
||||||
|
title: {
|
||||||
|
onRename: renameCb,
|
||||||
|
defaultName: defaultName,
|
||||||
|
suggestName: suggestName
|
||||||
|
},
|
||||||
|
share: {
|
||||||
|
secret: secret,
|
||||||
|
channel: hexFileName
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Toolbar.create($bar, null, null, null, null, configTb);
|
||||||
|
|
||||||
|
updateTitle(Cryptpad.initialName || getTitle() || defaultName);
|
||||||
|
|
||||||
|
MediaTag($mt[0]);
|
||||||
|
|
||||||
|
Cryptpad.removeLoadingScreen();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Cryptpad.ready(function () {
|
||||||
|
andThen();
|
||||||
|
Cryptpad.reportAppUsage();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue