|
|
|
|
define([
|
|
|
|
|
'jquery',
|
|
|
|
|
'/api/config',
|
|
|
|
|
'/common/cryptpad-common.js',
|
|
|
|
|
'/common/common-util.js',
|
|
|
|
|
'/common/common-hash.js',
|
|
|
|
|
'/common/common-language.js',
|
|
|
|
|
'/common/common-interface.js',
|
|
|
|
|
'/common/media-tag.js',
|
|
|
|
|
|
|
|
|
|
'css!/common/tippy.css',
|
|
|
|
|
], function ($, Config, Cryptpad, Util, Hash, Language, UI, MediaTag) {
|
|
|
|
|
var UIElements = {};
|
|
|
|
|
var Messages = Cryptpad.Messages;
|
|
|
|
|
|
|
|
|
|
UIElements.updateTags = function (common, href) {
|
|
|
|
|
var sframeChan = common.getSframeChannel();
|
|
|
|
|
sframeChan.query('Q_TAGS_GET', href || null, function (err, res) {
|
|
|
|
|
if (err || res.error) {
|
|
|
|
|
if (res.error === 'NO_ENTRY') {
|
|
|
|
|
UI.alert(Messages.tags_noentry);
|
|
|
|
|
}
|
|
|
|
|
return void console.error(err || res.error);
|
|
|
|
|
}
|
|
|
|
|
UI.dialog.tagPrompt(res.data, function (tags) {
|
|
|
|
|
if (!Array.isArray(tags)) { return; }
|
|
|
|
|
sframeChan.event('EV_TAGS_SET', {
|
|
|
|
|
tags: tags,
|
|
|
|
|
href: href,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var importContent = function (type, f, cfg) {
|
|
|
|
|
return function () {
|
|
|
|
|
var $files = $('<input>', {type:"file"});
|
|
|
|
|
if (cfg && cfg.accept) {
|
|
|
|
|
$files.attr('accept', cfg.accept);
|
|
|
|
|
}
|
|
|
|
|
$files.click();
|
|
|
|
|
$files.on('change', function (e) {
|
|
|
|
|
var file = e.target.files[0];
|
|
|
|
|
var reader = new FileReader();
|
|
|
|
|
reader.onload = function (e) { f(e.target.result, file); };
|
|
|
|
|
reader.readAsText(file, type);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
UIElements.createButton = function (common, type, rightside, data, callback) {
|
|
|
|
|
var AppConfig = common.getAppConfig();
|
|
|
|
|
var button;
|
|
|
|
|
var size = "17px";
|
|
|
|
|
var sframeChan = common.getSframeChannel();
|
|
|
|
|
switch (type) {
|
|
|
|
|
case 'export':
|
|
|
|
|
button = $('<button>', {
|
|
|
|
|
'class': 'fa fa-download',
|
|
|
|
|
title: Messages.exportButtonTitle,
|
|
|
|
|
}).append($('<span>', {'class': 'cp-toolbar-drawer-element'}).text(Messages.exportButton));
|
|
|
|
|
|
|
|
|
|
button.click(common.prepareFeedback(type));
|
|
|
|
|
if (callback) {
|
|
|
|
|
button.click(callback);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'import':
|
|
|
|
|
button = $('<button>', {
|
|
|
|
|
'class': 'fa fa-upload',
|
|
|
|
|
title: Messages.importButtonTitle,
|
|
|
|
|
}).append($('<span>', {'class': 'cp-toolbar-drawer-element'}).text(Messages.importButton));
|
|
|
|
|
if (callback) {
|
|
|
|
|
button
|
|
|
|
|
.click(common.prepareFeedback(type))
|
|
|
|
|
.click(importContent('text/plain', function (content, file) {
|
|
|
|
|
callback(content, file);
|
|
|
|
|
}, {accept: data ? data.accept : undefined}));
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'upload':
|
|
|
|
|
button = $('<button>', {
|
|
|
|
|
'class': 'btn btn-primary new',
|
|
|
|
|
title: Messages.uploadButtonTitle,
|
|
|
|
|
}).append($('<span>', {'class':'fa fa-upload'})).append(' '+Messages.uploadButton);
|
|
|
|
|
if (!data.FM) { return; }
|
|
|
|
|
var $input = $('<input>', {
|
|
|
|
|
'type': 'file',
|
|
|
|
|
'style': 'display: none;'
|
|
|
|
|
}).on('change', function (e) {
|
|
|
|
|
var file = e.target.files[0];
|
|
|
|
|
var ev = {
|
|
|
|
|
target: data.target
|
|
|
|
|
};
|
|
|
|
|
if (data.filter && !data.filter(file)) {
|
|
|
|
|
UI.log('Invalid avatar (type or size)');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
data.FM.handleFile(file, ev);
|
|
|
|
|
if (callback) { callback(); }
|
|
|
|
|
});
|
|
|
|
|
if (data.accept) { $input.attr('accept', data.accept); }
|
|
|
|
|
button.click(function () { $input.click(); });
|
|
|
|
|
break;
|
|
|
|
|
case 'template':
|
|
|
|
|
if (!AppConfig.enableTemplates) { return; }
|
|
|
|
|
if (!common.isLoggedIn()) { return; }
|
|
|
|
|
button = $('<button>', {
|
|
|
|
|
title: Messages.saveTemplateButton,
|
|
|
|
|
}).append($('<span>', {'class':'fa fa-bookmark', style: 'font:'+size+' FontAwesome'}));
|
|
|
|
|
if (data.rt) {
|
|
|
|
|
button
|
|
|
|
|
.click(function () {
|
|
|
|
|
var title = data.getTitle() || document.title;
|
|
|
|
|
var todo = function (val) {
|
|
|
|
|
if (typeof(val) !== "string") { return; }
|
|
|
|
|
var toSave = data.rt.getUserDoc();
|
|
|
|
|
if (val.trim()) {
|
|
|
|
|
val = val.trim();
|
|
|
|
|
title = val;
|
|
|
|
|
try {
|
|
|
|
|
var parsed = JSON.parse(toSave);
|
|
|
|
|
var meta;
|
|
|
|
|
if (Array.isArray(parsed) && typeof(parsed[3]) === "object") {
|
|
|
|
|
meta = parsed[3].metadata; // pad
|
|
|
|
|
} else if (parsed.info) {
|
|
|
|
|
meta = parsed.info; // poll
|
|
|
|
|
} else {
|
|
|
|
|
meta = parsed.metadata;
|
|
|
|
|
}
|
|
|
|
|
if (typeof(meta) === "object") {
|
|
|
|
|
meta.title = val;
|
|
|
|
|
meta.defaultTitle = val;
|
|
|
|
|
delete meta.users;
|
|
|
|
|
}
|
|
|
|
|
toSave = JSON.stringify(parsed);
|
|
|
|
|
} catch(e) {
|
|
|
|
|
console.error("Parse error while setting the title", e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
sframeChan.query('Q_SAVE_AS_TEMPLATE', {
|
|
|
|
|
title: title,
|
|
|
|
|
toSave: toSave
|
|
|
|
|
}, function () {
|
|
|
|
|
UI.alert(Messages.templateSaved);
|
|
|
|
|
common.feedback('TEMPLATE_CREATED');
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
UI.prompt(Messages.saveTemplatePrompt, title, todo);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'forget':
|
|
|
|
|
button = $('<button>', {
|
|
|
|
|
id: 'cryptpad-forget',
|
|
|
|
|
title: Messages.forgetButtonTitle,
|
|
|
|
|
'class': "fa fa-trash cryptpad-forget",
|
|
|
|
|
style: 'font:'+size+' FontAwesome'
|
|
|
|
|
});
|
|
|
|
|
if (callback) {
|
|
|
|
|
button
|
|
|
|
|
.click(common.prepareFeedback(type))
|
|
|
|
|
.click(function() {
|
|
|
|
|
var msg = common.isLoggedIn() ? Messages.forgetPrompt : Messages.fm_removePermanentlyDialog;
|
|
|
|
|
UI.confirm(msg, function (yes) {
|
|
|
|
|
if (!yes) { return; }
|
|
|
|
|
sframeChan.query('Q_MOVE_TO_TRASH', null, function (err) {
|
|
|
|
|
if (err) { return void callback(err); }
|
|
|
|
|
var cMsg = common.isLoggedIn() ? Messages.movedToTrash : Messages.deleted;
|
|
|
|
|
UI.alert(cMsg, undefined, true);
|
|
|
|
|
callback();
|
|
|
|
|
return;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'present':
|
|
|
|
|
button = $('<button>', {
|
|
|
|
|
title: Messages.presentButtonTitle,
|
|
|
|
|
'class': "fa fa-play-circle cp-app-slide-present-button", // used in slide.js
|
|
|
|
|
style: 'font:'+size+' FontAwesome'
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
case 'history':
|
|
|
|
|
if (!AppConfig.enableHistory) {
|
|
|
|
|
button = $('<span>');
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
button = $('<button>', {
|
|
|
|
|
title: Messages.historyButton,
|
|
|
|
|
'class': "fa fa-history history",
|
|
|
|
|
}).append($('<span>', {'class': 'cp-toolbar-drawer-element'}).text(Messages.historyText));
|
|
|
|
|
if (data.histConfig) {
|
|
|
|
|
button
|
|
|
|
|
.click(common.prepareFeedback(type))
|
|
|
|
|
.on('click', function () {
|
|
|
|
|
common.getHistory(data.histConfig);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'more':
|
|
|
|
|
button = $('<button>', {
|
|
|
|
|
title: Messages.moreActions,
|
|
|
|
|
'class': "cp-toolbar-drawer-button fa fa-ellipsis-h",
|
|
|
|
|
style: 'font:'+size+' FontAwesome'
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
case 'savetodrive':
|
|
|
|
|
button = $('<button>', {
|
|
|
|
|
'class': 'fa fa-cloud-upload',
|
|
|
|
|
title: Messages.canvas_saveToDrive,
|
|
|
|
|
})
|
|
|
|
|
.click(common.prepareFeedback(type));
|
|
|
|
|
break;
|
|
|
|
|
case 'hashtag':
|
|
|
|
|
button = $('<button>', {
|
|
|
|
|
'class': 'fa fa-hashtag',
|
|
|
|
|
title: Messages.tags_title,
|
|
|
|
|
})
|
|
|
|
|
.click(common.prepareFeedback(type))
|
|
|
|
|
.click(function () { UIElements.updateTags(common, null); });
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
button = $('<button>', {
|
|
|
|
|
'class': "fa fa-question",
|
|
|
|
|
style: 'font:'+size+' FontAwesome'
|
|
|
|
|
})
|
|
|
|
|
.click(common.prepareFeedback(type));
|
|
|
|
|
}
|
|
|
|
|
if (rightside) {
|
|
|
|
|
button.addClass('cp-toolbar-rightside-button');
|
|
|
|
|
}
|
|
|
|
|
return button;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Avatars
|
|
|
|
|
UIElements.displayMediatagImage = function (Common, $tag, cb) {
|
|
|
|
|
if (!$tag.length || !$tag.is('media-tag')) { return void cb('NOT_MEDIATAG'); }
|
|
|
|
|
var observer = new MutationObserver(function(mutations) {
|
|
|
|
|
mutations.forEach(function(mutation) {
|
|
|
|
|
if (mutation.addedNodes.length) {
|
|
|
|
|
if (mutation.addedNodes.length > 1 ||
|
|
|
|
|
mutation.addedNodes[0].nodeName !== 'IMG') {
|
|
|
|
|
return void cb('NOT_IMAGE');
|
|
|
|
|
}
|
|
|
|
|
var $image = $tag.find('img');
|
|
|
|
|
var onLoad = function () {
|
|
|
|
|
var img = new Image();
|
|
|
|
|
img.onload = function () {
|
|
|
|
|
var _cb = cb;
|
|
|
|
|
cb = $.noop;
|
|
|
|
|
_cb(null, $image, img);
|
|
|
|
|
};
|
|
|
|
|
img.src = $image.attr('src');
|
|
|
|
|
};
|
|
|
|
|
if ($image[0].complete) { onLoad(); }
|
|
|
|
|
$image.on('load', onLoad);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
observer.observe($tag[0], {
|
|
|
|
|
attributes: false,
|
|
|
|
|
childList: true,
|
|
|
|
|
characterData: false
|
|
|
|
|
});
|
|
|
|
|
MediaTag($tag[0]);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var emoji_patt = /([\uD800-\uDBFF][\uDC00-\uDFFF])/;
|
|
|
|
|
var isEmoji = function (str) {
|
|
|
|
|
return emoji_patt.test(str);
|
|
|
|
|
};
|
|
|
|
|
var emojiStringToArray = function (str) {
|
|
|
|
|
var split = str.split(emoji_patt);
|
|
|
|
|
var arr = [];
|
|
|
|
|
for (var i=0; i<split.length; i++) {
|
|
|
|
|
var char = split[i];
|
|
|
|
|
if (char !== "") {
|
|
|
|
|
arr.push(char);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return arr;
|
|
|
|
|
};
|
|
|
|
|
var getFirstEmojiOrCharacter = function (str) {
|
|
|
|
|
if (!str || !str.trim()) { return '?'; }
|
|
|
|
|
var emojis = emojiStringToArray(str);
|
|
|
|
|
return isEmoji(emojis[0])? emojis[0]: str[0];
|
|
|
|
|
};
|
|
|
|
|
UIElements.displayAvatar = function (Common, $container, href, name, cb) {
|
|
|
|
|
var displayDefault = function () {
|
|
|
|
|
var text = getFirstEmojiOrCharacter(name);
|
|
|
|
|
var $avatar = $('<span>', {'class': 'cp-avatar-default'}).text(text);
|
|
|
|
|
$container.append($avatar);
|
|
|
|
|
if (cb) { cb(); }
|
|
|
|
|
};
|
|
|
|
|
if (!href) { return void displayDefault(); }
|
|
|
|
|
var parsed = Hash.parsePadUrl(href);
|
|
|
|
|
var secret = Hash.getSecrets('file', parsed.hash);
|
|
|
|
|
if (secret.keys && secret.channel) {
|
|
|
|
|
var cryptKey = secret.keys && secret.keys.fileKeyStr;
|
|
|
|
|
var hexFileName = Util.base64ToHex(secret.channel);
|
|
|
|
|
var src = Hash.getBlobPathFromHex(hexFileName);
|
|
|
|
|
Common.getFileSize(href, function (e, data) {
|
|
|
|
|
if (e) {
|
|
|
|
|
displayDefault();
|
|
|
|
|
return void console.error(e);
|
|
|
|
|
}
|
|
|
|
|
if (typeof data !== "number") { return void displayDefault(); }
|
|
|
|
|
if (Util.bytesToMegabytes(data) > 0.5) { return void displayDefault(); }
|
|
|
|
|
var $img = $('<media-tag>').appendTo($container);
|
|
|
|
|
$img.attr('src', src);
|
|
|
|
|
$img.attr('data-crypto-key', 'cryptpad:' + cryptKey);
|
|
|
|
|
UIElements.displayMediatagImage(Common, $img, function (err, $image, img) {
|
|
|
|
|
if (err) { return void console.error(err); }
|
|
|
|
|
var w = img.width;
|
|
|
|
|
var h = img.height;
|
|
|
|
|
if (w>h) {
|
|
|
|
|
$image.css('max-height', '100%');
|
|
|
|
|
$img.css('flex-direction', 'column');
|
|
|
|
|
if (cb) { cb($img); }
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
$image.css('max-width', '100%');
|
|
|
|
|
$img.css('flex-direction', 'row');
|
|
|
|
|
if (cb) { cb($img); }
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* Create a usage bar which keeps track of how much storage space is used
|
|
|
|
|
by your CryptDrive. The getPinnedUsage RPC is one of the heavier calls,
|
|
|
|
|
so we throttle its usage. Clients will not update more than once per
|
|
|
|
|
LIMIT_REFRESH_RATE. It will be update at least once every three such intervals
|
|
|
|
|
If changes are made to your drive in the interim, they will trigger an
|
|
|
|
|
update.
|
|
|
|
|
*/
|
|
|
|
|
var LIMIT_REFRESH_RATE = 30000; // milliseconds
|
|
|
|
|
UIElements.createUsageBar = function (common, cb) {
|
|
|
|
|
if (!common.isLoggedIn()) { return cb("NOT_LOGGED_IN"); }
|
|
|
|
|
// getPinnedUsage updates common.account.usage, and other values
|
|
|
|
|
// so we can just use those and only check for errors
|
|
|
|
|
var $container = $('<span>', {'class':'cp-limit-container'});
|
|
|
|
|
var todo;
|
|
|
|
|
var updateUsage = Util.notAgainForAnother(function () {
|
|
|
|
|
common.getPinUsage(todo);
|
|
|
|
|
}, LIMIT_REFRESH_RATE);
|
|
|
|
|
|
|
|
|
|
todo = function (err, data) {
|
|
|
|
|
if (err) { return void console.error(err); }
|
|
|
|
|
|
|
|
|
|
var usage = data.usage;
|
|
|
|
|
var limit = data.limit;
|
|
|
|
|
var plan = data.plan;
|
|
|
|
|
$container.html('');
|
|
|
|
|
var unit = Util.magnitudeOfBytes(limit);
|
|
|
|
|
|
|
|
|
|
usage = unit === 'GB'? Util.bytesToGigabytes(usage):
|
|
|
|
|
Util.bytesToMegabytes(usage);
|
|
|
|
|
limit = unit === 'GB'? Util.bytesToGigabytes(limit):
|
|
|
|
|
Util.bytesToMegabytes(limit);
|
|
|
|
|
|
|
|
|
|
var $limit = $('<span>', {'class': 'cp-limit-bar'}).appendTo($container);
|
|
|
|
|
var quota = usage/limit;
|
|
|
|
|
var $usage = $('<span>', {'class': 'cp-limit-usage'}).css('width', quota*100+'%');
|
|
|
|
|
|
|
|
|
|
var urls = common.getMetadataMgr().getPrivateData().accounts;
|
|
|
|
|
var makeDonateButton = function () {
|
|
|
|
|
$('<a>', {
|
|
|
|
|
'class': 'cp-limit-upgrade btn btn-success',
|
|
|
|
|
href: urls.donateURL,
|
|
|
|
|
rel: "noreferrer noopener",
|
|
|
|
|
target: "_blank",
|
|
|
|
|
}).text(Messages.supportCryptpad).appendTo($container);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var makeUpgradeButton = function () {
|
|
|
|
|
$('<a>', {
|
|
|
|
|
'class': 'cp-limit-upgrade btn btn-success',
|
|
|
|
|
href: urls.upgradeURL,
|
|
|
|
|
rel: "noreferrer noopener",
|
|
|
|
|
target: "_blank",
|
|
|
|
|
}).text(Messages.upgradeAccount).appendTo($container);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (!Config.removeDonateButton) {
|
|
|
|
|
if (!common.isLoggedIn() || !Config.allowSubscriptions) {
|
|
|
|
|
// user is not logged in, or subscriptions are disallowed
|
|
|
|
|
makeDonateButton();
|
|
|
|
|
} else if (!plan) {
|
|
|
|
|
// user is logged in and subscriptions are allowed
|
|
|
|
|
// and they don't have one. show upgrades
|
|
|
|
|
makeUpgradeButton();
|
|
|
|
|
} else {
|
|
|
|
|
// they have a plan. show nothing
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var prettyUsage;
|
|
|
|
|
var prettyLimit;
|
|
|
|
|
|
|
|
|
|
if (unit === 'GB') {
|
|
|
|
|
prettyUsage = Messages._getKey('formattedGB', [usage]);
|
|
|
|
|
prettyLimit = Messages._getKey('formattedGB', [limit]);
|
|
|
|
|
} else {
|
|
|
|
|
prettyUsage = Messages._getKey('formattedMB', [usage]);
|
|
|
|
|
prettyLimit = Messages._getKey('formattedMB', [limit]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (quota < 0.8) { $usage.addClass('cp-limit-usage-normal'); }
|
|
|
|
|
else if (quota < 1) { $usage.addClass('cp-limit-usage-warning'); }
|
|
|
|
|
else { $usage.addClass('cp-limit-usage-above'); }
|
|
|
|
|
var $text = $('<span>', {'class': 'cp-limit-usage-text'});
|
|
|
|
|
$text.text(usage + ' / ' + prettyLimit);
|
|
|
|
|
$limit.append($usage).append($text);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
setInterval(function () {
|
|
|
|
|
updateUsage();
|
|
|
|
|
}, LIMIT_REFRESH_RATE * 3);
|
|
|
|
|
|
|
|
|
|
updateUsage();
|
|
|
|
|
cb(null, $container);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Create a button with a dropdown menu
|
|
|
|
|
// input is a config object with parameters:
|
|
|
|
|
// - container (optional): the dropdown container (span)
|
|
|
|
|
// - text (optional): the button text value
|
|
|
|
|
// - options: array of {tag: "", attributes: {}, content: "string"}
|
|
|
|
|
//
|
|
|
|
|
// allowed options tags: ['a', 'hr', 'p']
|
|
|
|
|
UIElements.createDropdown = function (config) {
|
|
|
|
|
if (typeof config !== "object" || !Array.isArray(config.options)) { return; }
|
|
|
|
|
|
|
|
|
|
var allowedTags = ['a', 'p', 'hr'];
|
|
|
|
|
var isValidOption = function (o) {
|
|
|
|
|
if (typeof o !== "object") { return false; }
|
|
|
|
|
if (!o.tag || allowedTags.indexOf(o.tag) === -1) { return false; }
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Container
|
|
|
|
|
var $container = $(config.container);
|
|
|
|
|
var containerConfig = {
|
|
|
|
|
'class': 'cp-dropdown-container'
|
|
|
|
|
};
|
|
|
|
|
if (config.buttonTitle) {
|
|
|
|
|
containerConfig.title = config.buttonTitle;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!config.container) {
|
|
|
|
|
$container = $('<span>', containerConfig);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Button
|
|
|
|
|
var $button = $('<button>', {
|
|
|
|
|
'class': ''
|
|
|
|
|
}).append($('<span>', {'class': 'cp-dropdown-button-title'}).html(config.text || ""));
|
|
|
|
|
/*$('<span>', {
|
|
|
|
|
'class': 'fa fa-caret-down',
|
|
|
|
|
}).appendTo($button);*/
|
|
|
|
|
|
|
|
|
|
// Menu
|
|
|
|
|
var $innerblock = $('<div>', {'class': 'cp-dropdown-content'});
|
|
|
|
|
if (config.left) { $innerblock.addClass('cp-dropdown-left'); }
|
|
|
|
|
|
|
|
|
|
config.options.forEach(function (o) {
|
|
|
|
|
if (!isValidOption(o)) { return; }
|
|
|
|
|
$('<' + o.tag + '>', o.attributes || {}).html(o.content || '').appendTo($innerblock);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$container.append($button).append($innerblock);
|
|
|
|
|
|
|
|
|
|
var value = config.initialValue || '';
|
|
|
|
|
|
|
|
|
|
var setActive = function ($el) {
|
|
|
|
|
if ($el.length !== 1) { return; }
|
|
|
|
|
$innerblock.find('.cp-dropdown-element-active').removeClass('cp-dropdown-element-active');
|
|
|
|
|
$el.addClass('cp-dropdown-element-active');
|
|
|
|
|
var scroll = $el.position().top + $innerblock.scrollTop();
|
|
|
|
|
if (scroll < $innerblock.scrollTop()) {
|
|
|
|
|
$innerblock.scrollTop(scroll);
|
|
|
|
|
} else if (scroll > ($innerblock.scrollTop() + 280)) {
|
|
|
|
|
$innerblock.scrollTop(scroll-270);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var hide = function () {
|
|
|
|
|
window.setTimeout(function () { $innerblock.hide(); }, 0);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var show = function () {
|
|
|
|
|
$innerblock.show();
|
|
|
|
|
$innerblock.find('.cp-dropdown-element-active').removeClass('cp-dropdown-element-active');
|
|
|
|
|
if (config.isSelect && value) {
|
|
|
|
|
var $val = $innerblock.find('[data-value="'+value+'"]');
|
|
|
|
|
setActive($val);
|
|
|
|
|
$innerblock.scrollTop($val.position().top + $innerblock.scrollTop());
|
|
|
|
|
}
|
|
|
|
|
if (config.feedback) { Cryptpad.feedback(config.feedback); }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$container.click(function (e) {
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
var state = $innerblock.is(':visible');
|
|
|
|
|
$('.cp-dropdown-content').hide();
|
|
|
|
|
try {
|
|
|
|
|
$('iframe').each(function (idx, ifrw) {
|
|
|
|
|
$(ifrw).contents().find('.cp-dropdown-content').hide();
|
|
|
|
|
});
|
|
|
|
|
} catch (er) {
|
|
|
|
|
// empty try catch in case this iframe is problematic (cross-origin)
|
|
|
|
|
}
|
|
|
|
|
if (state) {
|
|
|
|
|
hide();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
show();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (config.isSelect) {
|
|
|
|
|
var pressed = '';
|
|
|
|
|
var to;
|
|
|
|
|
$container.keydown(function (e) {
|
|
|
|
|
var $value = $innerblock.find('[data-value].cp-dropdown-element-active');
|
|
|
|
|
if (e.which === 38) { // Up
|
|
|
|
|
if ($value.length) {
|
|
|
|
|
var $prev = $value.prev();
|
|
|
|
|
setActive($prev);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (e.which === 40) { // Down
|
|
|
|
|
if ($value.length) {
|
|
|
|
|
var $next = $value.next();
|
|
|
|
|
setActive($next);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (e.which === 13) { //Enter
|
|
|
|
|
if ($value.length) {
|
|
|
|
|
$value.click();
|
|
|
|
|
hide();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (e.which === 27) { // Esc
|
|
|
|
|
hide();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
$container.keypress(function (e) {
|
|
|
|
|
window.clearTimeout(to);
|
|
|
|
|
var c = String.fromCharCode(e.which);
|
|
|
|
|
pressed += c;
|
|
|
|
|
var $value = $innerblock.find('[data-value^="'+pressed+'"]:first');
|
|
|
|
|
if ($value.length) {
|
|
|
|
|
setActive($value);
|
|
|
|
|
$innerblock.scrollTop($value.position().top + $innerblock.scrollTop());
|
|
|
|
|
}
|
|
|
|
|
to = window.setTimeout(function () {
|
|
|
|
|
pressed = '';
|
|
|
|
|
}, 1000);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$container.setValue = function (val, name) {
|
|
|
|
|
value = val;
|
|
|
|
|
var $val = $innerblock.find('[data-value="'+val+'"]');
|
|
|
|
|
var textValue = name || $val.html() || val;
|
|
|
|
|
$button.find('.cp-dropdown-button-title').html(textValue);
|
|
|
|
|
};
|
|
|
|
|
$container.getValue = function () {
|
|
|
|
|
return value || '';
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $container;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
UIElements.createUserAdminMenu = function (Common, config) {
|
|
|
|
|
var metadataMgr = Common.getMetadataMgr();
|
|
|
|
|
|
|
|
|
|
var displayNameCls = config.displayNameCls || 'displayName';
|
|
|
|
|
var $displayedName = $('<span>', {'class': displayNameCls});
|
|
|
|
|
|
|
|
|
|
var accountName = metadataMgr.getPrivateData().accountName;
|
|
|
|
|
var origin = metadataMgr.getPrivateData().origin;
|
|
|
|
|
var padType = metadataMgr.getMetadata().type;
|
|
|
|
|
|
|
|
|
|
var $userName = $('<span>', {'class': 'userDisplayName'});
|
|
|
|
|
var options = [];
|
|
|
|
|
if (config.displayNameCls) {
|
|
|
|
|
var $userAdminContent = $('<p>');
|
|
|
|
|
if (accountName) {
|
|
|
|
|
var $userAccount = $('<span>', {'class': 'userAccount'}).append(Messages.user_accountName + ': ');
|
|
|
|
|
$userAdminContent.append($userAccount).append(Util.fixHTML(accountName));
|
|
|
|
|
$userAdminContent.append($('<br>'));
|
|
|
|
|
}
|
|
|
|
|
if (config.displayName) {
|
|
|
|
|
// Hide "Display name:" in read only mode
|
|
|
|
|
$userName.append(Messages.user_displayName + ': ');
|
|
|
|
|
$userName.append($displayedName);
|
|
|
|
|
}
|
|
|
|
|
$userAdminContent.append($userName);
|
|
|
|
|
options.push({
|
|
|
|
|
tag: 'p',
|
|
|
|
|
attributes: {'class': 'cp-toolbar-account'},
|
|
|
|
|
content: $userAdminContent.html()
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (padType !== 'drive') {
|
|
|
|
|
options.push({
|
|
|
|
|
tag: 'a',
|
|
|
|
|
attributes: {
|
|
|
|
|
'target': '_blank',
|
|
|
|
|
'href': origin+'/drive/'
|
|
|
|
|
},
|
|
|
|
|
content: Messages.login_accessDrive
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
// Add the change display name button if not in read only mode
|
|
|
|
|
if (config.changeNameButtonCls && config.displayChangeName) {
|
|
|
|
|
options.push({
|
|
|
|
|
tag: 'a',
|
|
|
|
|
attributes: {'class': config.changeNameButtonCls},
|
|
|
|
|
content: Messages.user_rename
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (accountName) {
|
|
|
|
|
options.push({
|
|
|
|
|
tag: 'a',
|
|
|
|
|
attributes: {'class': 'profile'},
|
|
|
|
|
content: Messages.profileButton
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (padType !== 'settings') {
|
|
|
|
|
options.push({
|
|
|
|
|
tag: 'a',
|
|
|
|
|
attributes: {'class': 'settings'},
|
|
|
|
|
content: Messages.settingsButton
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
// Add login or logout button depending on the current status
|
|
|
|
|
if (accountName) {
|
|
|
|
|
options.push({
|
|
|
|
|
tag: 'a',
|
|
|
|
|
attributes: {'class': 'logout'},
|
|
|
|
|
content: Messages.logoutButton
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
options.push({
|
|
|
|
|
tag: 'a',
|
|
|
|
|
attributes: {'class': 'login'},
|
|
|
|
|
content: Messages.login_login
|
|
|
|
|
});
|
|
|
|
|
options.push({
|
|
|
|
|
tag: 'a',
|
|
|
|
|
attributes: {'class': 'register'},
|
|
|
|
|
content: Messages.login_register
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
var $icon = $('<span>', {'class': 'fa fa-user-secret'});
|
|
|
|
|
//var $userbig = $('<span>', {'class': 'big'}).append($displayedName.clone());
|
|
|
|
|
var $userButton = $('<div>').append($icon);//.append($userbig);
|
|
|
|
|
if (accountName) {
|
|
|
|
|
$userButton = $('<div>').append(accountName);
|
|
|
|
|
}
|
|
|
|
|
/*if (account && config.displayNameCls) {
|
|
|
|
|
$userbig.append($('<span>', {'class': 'account-name'}).text('(' + accountName + ')'));
|
|
|
|
|
} else if (account) {
|
|
|
|
|
// If no display name, do not display the parentheses
|
|
|
|
|
$userbig.append($('<span>', {'class': 'account-name'}).text(accountName));
|
|
|
|
|
}*/
|
|
|
|
|
var dropdownConfigUser = {
|
|
|
|
|
text: $userButton.html(), // Button initial text
|
|
|
|
|
options: options, // Entries displayed in the menu
|
|
|
|
|
left: true, // Open to the left of the button
|
|
|
|
|
container: config.$initBlock, // optional
|
|
|
|
|
feedback: "USER_ADMIN",
|
|
|
|
|
};
|
|
|
|
|
var $userAdmin = UIElements.createDropdown(dropdownConfigUser);
|
|
|
|
|
|
|
|
|
|
var $displayName = $userAdmin.find('.'+displayNameCls);
|
|
|
|
|
|
|
|
|
|
var $avatar = $userAdmin.find('.cp-dropdown-button-title');
|
|
|
|
|
var loadingAvatar;
|
|
|
|
|
var to;
|
|
|
|
|
var oldUrl = '';
|
|
|
|
|
var updateButton = function () {
|
|
|
|
|
var myData = metadataMgr.getUserData();
|
|
|
|
|
if (!myData) { return; }
|
|
|
|
|
if (loadingAvatar) {
|
|
|
|
|
// Try again in 200ms
|
|
|
|
|
window.clearTimeout(to);
|
|
|
|
|
to = window.setTimeout(updateButton, 200);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
loadingAvatar = true;
|
|
|
|
|
var newName = myData.name;
|
|
|
|
|
var url = myData.avatar;
|
|
|
|
|
$displayName.text(newName || Messages.anonymous);
|
|
|
|
|
if (accountName && oldUrl !== url) {
|
|
|
|
|
$avatar.html('');
|
|
|
|
|
UIElements.displayAvatar(Common, $avatar, url, newName || Messages.anonymous, function ($img) {
|
|
|
|
|
oldUrl = url;
|
|
|
|
|
if ($img) {
|
|
|
|
|
$userAdmin.find('button').addClass('cp-avatar');
|
|
|
|
|
}
|
|
|
|
|
loadingAvatar = false;
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
loadingAvatar = false;
|
|
|
|
|
};
|
|
|
|
|
metadataMgr.onChange(updateButton);
|
|
|
|
|
updateButton();
|
|
|
|
|
|
|
|
|
|
$userAdmin.find('a.logout').click(function () {
|
|
|
|
|
Common.logout(function () {
|
|
|
|
|
window.parent.location = origin+'/';
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
$userAdmin.find('a.settings').click(function () {
|
|
|
|
|
if (padType) {
|
|
|
|
|
window.open(origin+'/settings/');
|
|
|
|
|
} else {
|
|
|
|
|
window.parent.location = origin+'/settings/';
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
$userAdmin.find('a.profile').click(function () {
|
|
|
|
|
if (padType) {
|
|
|
|
|
window.open(origin+'/profile/');
|
|
|
|
|
} else {
|
|
|
|
|
window.parent.location = origin+'/profile/';
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
$userAdmin.find('a.login').click(function () {
|
|
|
|
|
Common.setLoginRedirect(function () {
|
|
|
|
|
window.parent.location = origin+'/login/';
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
$userAdmin.find('a.register').click(function () {
|
|
|
|
|
Common.setLoginRedirect(function () {
|
|
|
|
|
window.parent.location = origin+'/register/';
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return $userAdmin;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Provide $container if you want to put the generated block in another element
|
|
|
|
|
// Provide $initBlock if you already have the menu block and you want the content inserted in it
|
|
|
|
|
UIElements.createLanguageSelector = function (common, $container, $initBlock) {
|
|
|
|
|
var options = [];
|
|
|
|
|
var languages = Messages._languages;
|
|
|
|
|
var keys = Object.keys(languages).sort();
|
|
|
|
|
keys.forEach(function (l) {
|
|
|
|
|
options.push({
|
|
|
|
|
tag: 'a',
|
|
|
|
|
attributes: {
|
|
|
|
|
'class': 'cp-language-value',
|
|
|
|
|
'data-value': l,
|
|
|
|
|
'href': '#',
|
|
|
|
|
},
|
|
|
|
|
content: languages[l] // Pretty name of the language value
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
var dropdownConfig = {
|
|
|
|
|
text: Messages.language, // Button initial text
|
|
|
|
|
options: options, // Entries displayed in the menu
|
|
|
|
|
//left: true, // Open to the left of the button
|
|
|
|
|
container: $initBlock, // optional
|
|
|
|
|
isSelect: true
|
|
|
|
|
};
|
|
|
|
|
var $block = UIElements.createDropdown(dropdownConfig);
|
|
|
|
|
$block.attr('id', 'cp-language-selector');
|
|
|
|
|
|
|
|
|
|
if ($container) {
|
|
|
|
|
$block.appendTo($container);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Language.initSelector($block, common);
|
|
|
|
|
|
|
|
|
|
return $block;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
UIElements.createModal = function (cfg) {
|
|
|
|
|
var $body = cfg.$body || $('body');
|
|
|
|
|
var $blockContainer = $body.find('#'+cfg.id);
|
|
|
|
|
if (!$blockContainer.length) {
|
|
|
|
|
$blockContainer = $('<div>', {
|
|
|
|
|
'class': 'cp-modal-container',
|
|
|
|
|
'id': cfg.id
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
var hide = function () {
|
|
|
|
|
if (cfg.onClose) { return void cfg.onClose(); }
|
|
|
|
|
$blockContainer.hide();
|
|
|
|
|
};
|
|
|
|
|
$blockContainer.html('').appendTo($body);
|
|
|
|
|
var $block = $('<div>', {'class': 'cp-modal'}).appendTo($blockContainer);
|
|
|
|
|
$('<span>', {
|
|
|
|
|
'class': 'cp-modal-close fa fa-times',
|
|
|
|
|
'title': Messages.filePicker_close
|
|
|
|
|
}).click(hide).appendTo($block);
|
|
|
|
|
$body.click(hide);
|
|
|
|
|
$block.click(function (e) {
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
});
|
|
|
|
|
$body.keydown(function (e) {
|
|
|
|
|
if (e.which === 27) {
|
|
|
|
|
hide();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return $blockContainer;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
UIElements.initFilePicker = function (common, cfg) {
|
|
|
|
|
var onSelect = cfg.onSelect || $.noop;
|
|
|
|
|
var sframeChan = common.getSframeChannel();
|
|
|
|
|
sframeChan.on("EV_FILE_PICKED", function (data) {
|
|
|
|
|
onSelect(data);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
UIElements.openFilePicker = function (common, types) {
|
|
|
|
|
var sframeChan = common.getSframeChannel();
|
|
|
|
|
sframeChan.event("EV_FILE_PICKER_OPEN", types);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
UIElements.openTemplatePicker = function (common) {
|
|
|
|
|
var metadataMgr = common.getMetadataMgr();
|
|
|
|
|
var type = metadataMgr.getMetadataLazy().type;
|
|
|
|
|
var sframeChan = common.getSframeChannel();
|
|
|
|
|
var focus;
|
|
|
|
|
|
|
|
|
|
var pickerCfg = {
|
|
|
|
|
types: [type],
|
|
|
|
|
where: ['template'],
|
|
|
|
|
hidden: true
|
|
|
|
|
};
|
|
|
|
|
var onConfirm = function (yes) {
|
|
|
|
|
if (!yes) {
|
|
|
|
|
if (focus) { focus.focus(); }
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
delete pickerCfg.hidden;
|
|
|
|
|
common.openFilePicker(pickerCfg);
|
|
|
|
|
var first = true; // We can only pick a template once (for a new document)
|
|
|
|
|
var fileDialogCfg = {
|
|
|
|
|
onSelect: function (data) {
|
|
|
|
|
if (data.type === type && first) {
|
|
|
|
|
UI.addLoadingScreen({hideTips: true});
|
|
|
|
|
sframeChan.query('Q_TEMPLATE_USE', data.href, function () {
|
|
|
|
|
first = false;
|
|
|
|
|
UI.removeLoadingScreen();
|
|
|
|
|
common.feedback('TEMPLATE_USED');
|
|
|
|
|
});
|
|
|
|
|
if (focus) { focus.focus(); }
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
common.initFilePicker(fileDialogCfg);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
sframeChan.query("Q_TEMPLATE_EXIST", type, function (err, data) {
|
|
|
|
|
if (data) {
|
|
|
|
|
common.openFilePicker(pickerCfg);
|
|
|
|
|
focus = document.activeElement;
|
|
|
|
|
UI.confirm(Messages.useTemplate, onConfirm, {
|
|
|
|
|
ok: Messages.useTemplateOK,
|
|
|
|
|
cancel: Messages.useTemplateCancel,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return UIElements;
|
|
|
|
|
});
|