Merge branch 'staging' into responsiveContextmenu
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
@import (reference) '../../customize/src/less2/include/framework.less';
|
||||
@import (reference) '../../customize/src/less2/include/sidebar-layout.less';
|
||||
@import (reference) '../../customize/src/less2/include/support.less';
|
||||
|
||||
&.cp-app-admin {
|
||||
|
||||
@@ -9,6 +10,11 @@
|
||||
@color: @colortheme_admin-color
|
||||
);
|
||||
.sidebar-layout_main();
|
||||
.support_main();
|
||||
|
||||
.cp-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
|
||||
@@ -9,6 +9,8 @@ define([
|
||||
'/customize/messages.js',
|
||||
'/common/common-interface.js',
|
||||
'/common/common-util.js',
|
||||
'/common/common-hash.js',
|
||||
'/support/ui.js',
|
||||
|
||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
@@ -23,7 +25,9 @@ define([
|
||||
h,
|
||||
Messages,
|
||||
UI,
|
||||
Util
|
||||
Util,
|
||||
Hash,
|
||||
Support
|
||||
)
|
||||
{
|
||||
var APP = {};
|
||||
@@ -41,6 +45,10 @@ define([
|
||||
'cp-admin-active-pads',
|
||||
'cp-admin-registered',
|
||||
'cp-admin-disk-usage',
|
||||
],
|
||||
'support': [
|
||||
'cp-admin-support-list',
|
||||
'cp-admin-support-init'
|
||||
]
|
||||
};
|
||||
|
||||
@@ -94,7 +102,6 @@ define([
|
||||
sFrameChan.query('Q_ADMIN_RPC', {
|
||||
cmd: 'ACTIVE_SESSIONS',
|
||||
}, function (e, data) {
|
||||
console.log(e, data);
|
||||
var total = data[0];
|
||||
var ips = data[1];
|
||||
$div.append(h('pre', total + ' (' + ips + ')'));
|
||||
@@ -160,6 +167,108 @@ define([
|
||||
return $div;
|
||||
};
|
||||
|
||||
var supportKey = ApiConfig.supportMailbox;
|
||||
create['support-list'] = function () {
|
||||
if (!supportKey || !APP.privateKey) { return; }
|
||||
var $div = makeBlock('support-list');
|
||||
$div.addClass('cp-support-container');
|
||||
var hashesById = {};
|
||||
|
||||
// Register to the "support" mailbox
|
||||
common.mailbox.subscribe(['supportadmin'], {
|
||||
onMessage: function (data) {
|
||||
/*
|
||||
Get ID of the ticket
|
||||
If we already have a div for this ID
|
||||
Push the message to the end of the ticket
|
||||
If it's a new ticket ID
|
||||
Make a new div for this ID
|
||||
*/
|
||||
var msg = data.content.msg;
|
||||
var hash = data.content.hash;
|
||||
var content = msg.content;
|
||||
var id = content.id;
|
||||
var $ticket = $div.find('.cp-support-list-ticket[data-id="'+id+'"]');
|
||||
|
||||
hashesById[id] = hashesById[id] || [];
|
||||
if (hashesById[id].indexOf(hash) === -1) {
|
||||
hashesById[id].push(data);
|
||||
}
|
||||
|
||||
if (msg.type === 'CLOSE') {
|
||||
// A ticket has been closed by the admins...
|
||||
if (!$ticket.length) { return; }
|
||||
$ticket.addClass('cp-support-list-closed');
|
||||
$ticket.append(APP.support.makeCloseMessage(content, hash));
|
||||
return;
|
||||
}
|
||||
if (msg.type !== 'TICKET') { return; }
|
||||
|
||||
if (!$ticket.length) {
|
||||
$ticket = APP.support.makeTicket($div, content, function () {
|
||||
var error = false;
|
||||
hashesById[id].forEach(function (d) {
|
||||
common.mailbox.dismiss(d, function (err) {
|
||||
if (err) {
|
||||
error = true;
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (!error) { $ticket.remove(); }
|
||||
});
|
||||
}
|
||||
$ticket.append(APP.support.makeMessage(content, hash));
|
||||
}
|
||||
});
|
||||
return $div;
|
||||
};
|
||||
|
||||
var checkAdminKey = function (priv) {
|
||||
if (!supportKey) { return; }
|
||||
return Hash.checkBoxKeyPair(priv, supportKey);
|
||||
};
|
||||
|
||||
create['support-init'] = function () {
|
||||
var $div = makeBlock('support-init');
|
||||
if (!supportKey) {
|
||||
$div.append(h('p', Messages.admin_supportInitHelp));
|
||||
return $div;
|
||||
}
|
||||
if (!APP.privateKey || !checkAdminKey(APP.privateKey)) {
|
||||
$div.append(h('p', Messages.admin_supportInitPrivate));
|
||||
|
||||
var error = h('div.cp-admin-support-error');
|
||||
var input = h('input.cp-admin-add-private-key');
|
||||
var button = h('button.btn.btn-primary', Messages.admin_supportAddKey);
|
||||
|
||||
if (APP.privateKey && !checkAdminKey(APP.privateKey)) {
|
||||
$(error).text(Messages.admin_supportAddError);
|
||||
}
|
||||
|
||||
$div.append(h('div', [
|
||||
error,
|
||||
input,
|
||||
button
|
||||
]));
|
||||
|
||||
$(button).click(function () {
|
||||
var key = $(input).val();
|
||||
if (!checkAdminKey(key)) {
|
||||
$(input).val('');
|
||||
return void $(error).text(Messages.admin_supportAddError);
|
||||
}
|
||||
sFrameChan.query("Q_ADMIN_MAILBOX", key, function () {
|
||||
APP.privateKey = key;
|
||||
$('.cp-admin-support-init').hide();
|
||||
APP.$rightside.append(create['support-list']());
|
||||
});
|
||||
});
|
||||
return $div;
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
var hideCategories = function () {
|
||||
APP.$rightside.find('> div').hide();
|
||||
};
|
||||
@@ -180,6 +289,7 @@ define([
|
||||
var $category = $('<div>', {'class': 'cp-sidebarlayout-category'}).appendTo($categories);
|
||||
if (key === 'general') { $category.append($('<span>', {'class': 'fa fa-user-o'})); }
|
||||
if (key === 'stats') { $category.append($('<span>', {'class': 'fa fa-hdd-o'})); }
|
||||
if (key === 'support') { $category.append($('<span>', {'class': 'fa fa-life-ring'})); }
|
||||
|
||||
if (key === active) {
|
||||
$category.addClass('cp-leftside-active');
|
||||
@@ -236,8 +346,10 @@ define([
|
||||
return void UI.errorLoadingScreen(Messages.admin_authError || '403 Forbidden');
|
||||
}
|
||||
|
||||
APP.privateKey = privateData.supportPrivateKey;
|
||||
APP.origin = privateData.origin;
|
||||
APP.readOnly = privateData.readOnly;
|
||||
APP.support = Support.create(common, true);
|
||||
|
||||
// Content
|
||||
var $rightside = APP.$rightside;
|
||||
|
||||
@@ -38,6 +38,9 @@ define([
|
||||
}).nThen(function (/*waitFor*/) {
|
||||
var addRpc = function (sframeChan, Cryptpad/*, Utils*/) {
|
||||
// Adding a new avatar from the profile: pin it and store it in the object
|
||||
sframeChan.on('Q_ADMIN_MAILBOX', function (data, cb) {
|
||||
Cryptpad.addAdminMailbox(data, cb);
|
||||
});
|
||||
sframeChan.on('Q_ADMIN_RPC', function (data, cb) {
|
||||
Cryptpad.adminRpc(data, cb);
|
||||
});
|
||||
|
||||
@@ -371,6 +371,20 @@ define([
|
||||
return cb(true);
|
||||
}, "version 2 hash failed to parse correctly");
|
||||
|
||||
assert(function (cb) {
|
||||
var x;
|
||||
var set_x = function (v) {
|
||||
x = v;
|
||||
};
|
||||
|
||||
Util.mkAsync(set_x)(7);
|
||||
set_x(5);
|
||||
|
||||
Util.mkAsync(function (expected) {
|
||||
cb(x === expected);
|
||||
})(7);
|
||||
}, "test mkAsync");
|
||||
|
||||
assert(function (cb) {
|
||||
Wire.create({
|
||||
constructor: function (cb) {
|
||||
|
||||
@@ -8,7 +8,7 @@ define([
|
||||
module.main = function (userDoc, cb) {
|
||||
var mode = userDoc.highlightMode || 'gfm';
|
||||
var content = userDoc.content;
|
||||
module.type = SFCodeMirror.getContentExtension(mode);
|
||||
module.ext = SFCodeMirror.getContentExtension(mode);
|
||||
cb(SFCodeMirror.fileExporter(content));
|
||||
};
|
||||
|
||||
|
||||
@@ -363,7 +363,15 @@ define([
|
||||
});
|
||||
|
||||
framework.setFileExporter(CodeMirror.getContentExtension, CodeMirror.fileExporter);
|
||||
framework.setFileImporter({}, CodeMirror.fileImporter);
|
||||
framework.setFileImporter({}, function () {
|
||||
/* setFileImporter currently takes a function with the following signature:
|
||||
(content, file) => {}
|
||||
I used 'apply' with 'arguments' to avoid breaking things if this API ever changes.
|
||||
*/
|
||||
var ret = CodeMirror.fileImporter.apply(null, Array.prototype.slice.call(arguments));
|
||||
previewPane.modeChange(ret.mode);
|
||||
return ret;
|
||||
});
|
||||
|
||||
framework.setNormalizer(function (c) {
|
||||
return {
|
||||
|
||||
@@ -20,7 +20,7 @@ define(function() {
|
||||
* users and these users will be redirected to the login page if they still try to access
|
||||
* the app
|
||||
*/
|
||||
config.registeredOnlyTypes = ['file', 'contacts', 'oodoc', 'ooslide', 'sheet'];
|
||||
config.registeredOnlyTypes = ['file', 'contacts', 'oodoc', 'ooslide', 'sheet', 'notifications'];
|
||||
|
||||
/* CryptPad is available is multiple languages, but only English and French are maintained
|
||||
* by the developers. The other languages may be outdated, and any missing string for a langauge
|
||||
|
||||
@@ -17,6 +17,6 @@ define(function () {
|
||||
// Sub
|
||||
plan: 'CryptPad_plan',
|
||||
// Apps
|
||||
criticalApps: ['profile', 'settings', 'debug', 'admin']
|
||||
criticalApps: ['profile', 'settings', 'debug', 'admin', 'support', 'notifications']
|
||||
};
|
||||
});
|
||||
|
||||
@@ -85,6 +85,34 @@ define([
|
||||
return id;
|
||||
};
|
||||
|
||||
/* Given a base64-encoded public key, deterministically derive a channel id
|
||||
Used for support mailboxes
|
||||
*/
|
||||
Hash.getChannelIdFromKey = function (publicKey) {
|
||||
if (!publicKey) { return; }
|
||||
return uint8ArrayToHex(Hash.decodeBase64(publicKey).subarray(0,16));
|
||||
};
|
||||
|
||||
/* Given a base64-encoded asymmetric private key
|
||||
derive the corresponding public key
|
||||
*/
|
||||
Hash.getBoxPublicFromSecret = function (priv) {
|
||||
if (!priv) { return; }
|
||||
var u8_priv = Hash.decodeBase64(priv);
|
||||
var pair = Nacl.box.keyPair.fromSecretKey(u8_priv);
|
||||
return Hash.encodeBase64(pair.publicKey);
|
||||
};
|
||||
|
||||
/* Given a base64-encoded private key and public key
|
||||
check that the keys are part of a valid keypair
|
||||
*/
|
||||
Hash.checkBoxKeyPair = function (priv, pub) {
|
||||
if (!pub || !priv) { return false; }
|
||||
var u8_priv = Hash.decodeBase64(priv);
|
||||
var pair = Nacl.box.keyPair.fromSecretKey(u8_priv);
|
||||
return pub === Hash.encodeBase64(pair.publicKey);
|
||||
};
|
||||
|
||||
Hash.createRandomHash = function (type, password) {
|
||||
var cryptor;
|
||||
if (type === 'file') {
|
||||
|
||||
@@ -799,6 +799,11 @@ define([
|
||||
// forever, this is a solution which just searches for tooltips which have no corrisponding element and removes
|
||||
// them.
|
||||
$('.tippy-popper').each(function (i, el) {
|
||||
if (el._tippy && el._tippy.reference && document.body.contains(el._tippy.reference)) {
|
||||
el._tippy.destroy();
|
||||
el.remove();
|
||||
return;
|
||||
}
|
||||
if ($('[aria-describedby=' + el.getAttribute('id') + ']').length === 0) {
|
||||
el.remove();
|
||||
}
|
||||
@@ -849,6 +854,9 @@ define([
|
||||
mutations.forEach(function(mutation) {
|
||||
if (mutation.type === "childList") {
|
||||
for (var i = 0; i < mutation.addedNodes.length; i++) {
|
||||
if ($(mutation.addedNodes[i]).attr('title')) {
|
||||
addTippy(0, mutation.addedNodes[i]);
|
||||
}
|
||||
$(mutation.addedNodes[i]).find('[title]').each(addTippy);
|
||||
}
|
||||
|
||||
|
||||
@@ -451,9 +451,7 @@ define([
|
||||
var txid = parsed[1];
|
||||
var req = getRangeRequest(txid);
|
||||
var type = parsed[0];
|
||||
if (!req) {
|
||||
return void console.error("received response to unknown request");
|
||||
}
|
||||
if (!req) { return; }
|
||||
|
||||
if (!req.cb) {
|
||||
// This is the initial history for a pad chat
|
||||
|
||||
@@ -196,8 +196,8 @@ define([
|
||||
};
|
||||
|
||||
Thumb.initPadThumbnails = function (common, opts) {
|
||||
if (!opts.href || !opts.getContent) {
|
||||
throw new Error("href and getContent are needed for thumbnails");
|
||||
if (!opts.type || !opts.getContent) {
|
||||
throw new Error("type and getContent are needed for thumbnails");
|
||||
}
|
||||
var oldThumbnailState;
|
||||
var mkThumbnail = function () {
|
||||
@@ -230,9 +230,15 @@ define([
|
||||
if (!Visible.currently()) { to = window.setTimeout(interval, Thumb.UPDATE_FIRST); }
|
||||
};
|
||||
|
||||
|
||||
var addThumbnail = function (err, thumb, $span, cb) {
|
||||
var u8 = Nacl.util.decodeBase64(thumb.split(',')[1]);
|
||||
var blob = new Blob([u8], {
|
||||
type: 'image/png'
|
||||
});
|
||||
var url = URL.createObjectURL(blob);
|
||||
var img = new Image();
|
||||
img.src = thumb.slice(0,5) === 'data:' ? thumb : 'data:image/png;base64,'+thumb;
|
||||
img.src = url;
|
||||
$span.find('.cp-icon').hide();
|
||||
$span.prepend(img);
|
||||
cb($(img));
|
||||
|
||||
@@ -119,15 +119,32 @@ define([
|
||||
$('<label>', {'for': 'cp-app-prop-owners'}).text(Messages.creation_owners)
|
||||
.appendTo($d);
|
||||
var owners = Messages.creation_noOwner;
|
||||
var edPublic = common.getMetadataMgr().getPrivateData().edPublic;
|
||||
var priv = common.getMetadataMgr().getPrivateData();
|
||||
var edPublic = priv.edPublic;
|
||||
var owned = false;
|
||||
if (data.owners && data.owners.length) {
|
||||
if (data.owners.indexOf(edPublic) !== -1) {
|
||||
owners = Messages.yourself;
|
||||
owned = true;
|
||||
} else {
|
||||
owners = Messages.creation_ownedByOther;
|
||||
}
|
||||
var names = [];
|
||||
var strangers = 0;
|
||||
data.owners.forEach(function (ed) {
|
||||
// If a friend is an owner, add their name to the list
|
||||
// otherwise, increment the list of strangers
|
||||
if (!Object.keys(priv.friends || {}).some(function (c) {
|
||||
var friend = priv.friends[c] || {};
|
||||
if (friend.edPublic !== ed) { return; }
|
||||
var name = c === 'me' ? Messages.yourself : friend.displayName;
|
||||
names.push(name);
|
||||
return true;
|
||||
})) {
|
||||
strangers++;
|
||||
}
|
||||
});
|
||||
if (strangers) {
|
||||
names.push(Messages._getKey('properties_unknownUser', [strangers]));
|
||||
}
|
||||
owners = names.join(', ');
|
||||
}
|
||||
$d.append(UI.dialog.selectable(owners, {
|
||||
id: 'cp-app-prop-owners',
|
||||
@@ -162,7 +179,7 @@ define([
|
||||
}
|
||||
|
||||
var parsed = Hash.parsePadUrl(data.href || data.roHref);
|
||||
if (!data.noEditPassword && owned && parsed.hashData.type === 'pad') {
|
||||
if (!data.noEditPassword && owned && parsed.hashData.type === 'pad' && parsed.type !== "sheet") { // FIXME SHEET fix password change for sheets
|
||||
var sframeChan = common.getSframeChannel();
|
||||
var changePwTitle = Messages.properties_changePassword;
|
||||
var changePwConfirm = Messages.properties_confirmChange;
|
||||
@@ -289,7 +306,7 @@ define([
|
||||
id: 'cp-app-prop-size',
|
||||
}));
|
||||
|
||||
if (data.sharedFolder) { // XXX debug
|
||||
if (data.sharedFolder) {
|
||||
$('<label>', {'for': 'cp-app-prop-channel'}).text('Channel ID').appendTo($d);
|
||||
if (AppConfig.pinBugRecovery) { $d.append(h('p', AppConfig.pinBugRecovery)); }
|
||||
$d.append(UI.dialog.selectable(data.channel, {
|
||||
@@ -325,7 +342,7 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
var getFriendsList = function (config) {
|
||||
var getFriendsList = function (config, onShare) {
|
||||
var common = config.common;
|
||||
var title = config.title;
|
||||
var friends = config.friends;
|
||||
@@ -412,6 +429,8 @@ define([
|
||||
if (!friend.notifications || !friend.curvePublic) { return; }
|
||||
common.mailbox.sendTo("SHARE_PAD", {
|
||||
href: href,
|
||||
password: config.password,
|
||||
isTemplate: config.isTemplate,
|
||||
name: myName,
|
||||
title: title
|
||||
}, {
|
||||
@@ -435,6 +454,9 @@ define([
|
||||
return smallCurves.indexOf(curve) !== -1;
|
||||
});
|
||||
common.setAttribute(['general', 'share-friends'], order);
|
||||
if (onShare) {
|
||||
onShare.fire();
|
||||
}
|
||||
});
|
||||
$nav.append(button);
|
||||
}
|
||||
@@ -511,8 +533,10 @@ define([
|
||||
|
||||
// Share link tab
|
||||
var hasFriends = Object.keys(config.friends || {}).length !== 0;
|
||||
var friendsList = hasFriends ? getFriendsList(config) : undefined;
|
||||
var onFriendShare = Util.mkEvent();
|
||||
var friendsList = hasFriends ? getFriendsList(config, onFriendShare) : undefined;
|
||||
var friendsUIClass = hasFriends ? '.cp-share-columns' : '';
|
||||
|
||||
var link = h('div.cp-share-modal' + friendsUIClass, [
|
||||
h('div.cp-share-column', [
|
||||
hasFriends ? h('p', Messages.share_description) : undefined,
|
||||
@@ -546,11 +570,12 @@ define([
|
||||
present: present
|
||||
});
|
||||
};
|
||||
onFriendShare.reg(saveValue);
|
||||
var getLinkValue = function (initValue) {
|
||||
var val = initValue || {};
|
||||
var edit = initValue ? val.edit : Util.isChecked($(link).find('#cp-share-editable-true'));
|
||||
var embed = initValue ? val.embed : Util.isChecked($(link).find('#cp-share-embed'));
|
||||
var present = initValue ? val.present : Util.isChecked($(link).find('#cp-share-present'));
|
||||
var edit = val.edit !== undefined ? val.edit : Util.isChecked($(link).find('#cp-share-editable-true'));
|
||||
var embed = val.embed !== undefined ? val.embed : Util.isChecked($(link).find('#cp-share-embed'));
|
||||
var present = val.present !== undefined ? val.present : Util.isChecked($(link).find('#cp-share-present'));
|
||||
|
||||
var hash = (!hashes.viewHash || (edit && hashes.editHash)) ? hashes.editHash : hashes.viewHash;
|
||||
var href = origin + pathname + '#' + hash;
|
||||
@@ -700,7 +725,10 @@ define([
|
||||
},
|
||||
keys: [13]
|
||||
}];
|
||||
var frameLink = UI.dialog.customModal(link, {buttons: linkButtons});
|
||||
var frameLink = UI.dialog.customModal(link, {
|
||||
buttons: linkButtons,
|
||||
onClose: config.onClose,
|
||||
});
|
||||
|
||||
// Embed tab
|
||||
var embed = h('div.cp-share-modal', [
|
||||
@@ -727,7 +755,10 @@ define([
|
||||
},
|
||||
keys: [13]
|
||||
}];
|
||||
var frameEmbed = UI.dialog.customModal(embed, { buttons: embedButtons});
|
||||
var frameEmbed = UI.dialog.customModal(embed, {
|
||||
buttons: embedButtons,
|
||||
onClose: config.onClose,
|
||||
});
|
||||
|
||||
// Create modal
|
||||
var tabs = [{
|
||||
@@ -1737,16 +1768,20 @@ define([
|
||||
var pressed = '';
|
||||
var to;
|
||||
$container.keydown(function (e) {
|
||||
var $value = $innerblock.find('[data-value].cp-dropdown-element-active');
|
||||
var $value = $innerblock.find('[data-value].cp-dropdown-element-active:visible');
|
||||
if (e.which === 38) { // Up
|
||||
if ($value.length) {
|
||||
$value.mouseleave();
|
||||
var $prev = $value.prev();
|
||||
$prev.mouseenter();
|
||||
setActive($prev);
|
||||
}
|
||||
}
|
||||
if (e.which === 40) { // Down
|
||||
if ($value.length) {
|
||||
$value.mouseleave();
|
||||
var $next = $value.next();
|
||||
$next.mouseenter();
|
||||
setActive($next);
|
||||
}
|
||||
}
|
||||
@@ -1757,6 +1792,7 @@ define([
|
||||
}
|
||||
}
|
||||
if (e.which === 27) { // Esc
|
||||
$value.mouseleave();
|
||||
hide();
|
||||
}
|
||||
});
|
||||
@@ -1861,6 +1897,13 @@ define([
|
||||
content: h('span', Messages.adminPage || 'Admin')
|
||||
});
|
||||
}
|
||||
if (padType !== 'support' && accountName && Config.supportMailbox) {
|
||||
options.push({
|
||||
tag: 'a',
|
||||
attributes: {'class': 'cp-toolbar-menu-support fa fa-life-ring'},
|
||||
content: h('span', Messages.supportPage || 'Support')
|
||||
});
|
||||
}
|
||||
// Add login or logout button depending on the current status
|
||||
if (accountName) {
|
||||
options.push({
|
||||
@@ -1956,6 +1999,13 @@ define([
|
||||
window.parent.location = origin+'/settings/';
|
||||
}
|
||||
});
|
||||
$userAdmin.find('a.cp-toolbar-menu-support').click(function () {
|
||||
if (padType) {
|
||||
window.open(origin+'/support/');
|
||||
} else {
|
||||
window.parent.location = origin+'/support/';
|
||||
}
|
||||
});
|
||||
$userAdmin.find('a.cp-toolbar-menu-admin').click(function () {
|
||||
if (padType) {
|
||||
window.open(origin+'/admin/');
|
||||
@@ -2054,6 +2104,9 @@ define([
|
||||
};
|
||||
|
||||
UIElements.createNewPadModal = function (common) {
|
||||
// if in drive, show new pad modal instead
|
||||
if ($("body.cp-app-drive").length !== 0) { return void $(".cp-app-drive-element-row.cp-app-drive-new-ghost").click(); }
|
||||
|
||||
var $modal = UIElements.createModal({
|
||||
id: 'cp-app-toolbar-creation-dialog',
|
||||
$body: $('body')
|
||||
@@ -2716,8 +2769,12 @@ define([
|
||||
UIElements.displayCrowdfunding(common);
|
||||
modal.delete();
|
||||
});
|
||||
var waitingForStoringCb = false;
|
||||
$(store).click(function () {
|
||||
if (waitingForStoringCb) { return; }
|
||||
waitingForStoringCb = true;
|
||||
common.getSframeChannel().query("Q_AUTOSTORE_STORE", null, function (err, obj) {
|
||||
waitingForStoringCb = false;
|
||||
var error = err || (obj && obj.error);
|
||||
if (error) {
|
||||
if (error === 'E_OVER_LIMIT') {
|
||||
@@ -2804,11 +2861,27 @@ define([
|
||||
'aria-labelledBy': 'dropdownMenu',
|
||||
'style': 'display:block;position:static;margin-bottom:5px;'
|
||||
}, [
|
||||
h('li', h('a.dropdown-item', {
|
||||
h('li', h('a.cp-app-code-context-saveindrive.dropdown-item', {
|
||||
'tabindex': '-1',
|
||||
}, Messages.pad_mediatagImport))
|
||||
'data-icon': "fa-cloud-upload",
|
||||
}, Messages.pad_mediatagImport)),
|
||||
h('li', h('a.cp-app-code-context-download.dropdown-item', {
|
||||
'tabindex': '-1',
|
||||
'data-icon': "fa-download",
|
||||
}, Messages.download_mt_button)),
|
||||
])
|
||||
]);
|
||||
// create the icon for each contextmenu option
|
||||
$(menu).find("li a.dropdown-item").each(function (i, el) {
|
||||
var $icon = $("<span>");
|
||||
if ($(el).attr('data-icon')) {
|
||||
var font = $(el).attr('data-icon').indexOf('cptools') === 0 ? 'cptools' : 'fa';
|
||||
$icon.addClass(font).addClass($(el).attr('data-icon'));
|
||||
} else {
|
||||
$icon.text($(el).text());
|
||||
}
|
||||
$(el).prepend($icon);
|
||||
});
|
||||
var m = createContextMenu(menu);
|
||||
|
||||
mediatagContextMenu = m;
|
||||
@@ -2818,7 +2891,13 @@ define([
|
||||
e.stopPropagation();
|
||||
m.hide();
|
||||
var $mt = $menu.data('mediatag');
|
||||
common.importMediaTag($mt);
|
||||
if ($(this).hasClass("cp-app-code-context-saveindrive")) {
|
||||
common.importMediaTag($mt);
|
||||
}
|
||||
else if ($(this).hasClass("cp-app-code-context-download")) {
|
||||
var media = $mt[0]._mediaObject;
|
||||
window.saveAs(media._blob.content, media.name);
|
||||
}
|
||||
});
|
||||
|
||||
return m;
|
||||
|
||||
@@ -2,6 +2,15 @@
|
||||
define([], function () {
|
||||
var Util = window.CryptPad_Util = {};
|
||||
|
||||
Util.mkAsync = function (f) {
|
||||
return function () {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
setTimeout(function () {
|
||||
f.apply(null, args);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
// If once is true, after the event has been fired, any further handlers which are
|
||||
// registered will fire immediately, and this type of event cannot be fired twice.
|
||||
Util.mkEvent = function (once) {
|
||||
@@ -310,6 +319,12 @@ define([], function () {
|
||||
return window.innerHeight < 800 || window.innerWidth < 800;
|
||||
};
|
||||
|
||||
Util.stripTags = function (text) {
|
||||
var div = document.createElement("div");
|
||||
div.innerHTML = text;
|
||||
return div.innerText;
|
||||
};
|
||||
|
||||
return Util;
|
||||
});
|
||||
}(self));
|
||||
|
||||
@@ -620,6 +620,9 @@ define([
|
||||
common.adminRpc = function (data, cb) {
|
||||
postMessage("ADMIN_RPC", data, cb);
|
||||
};
|
||||
common.addAdminMailbox = function (data, cb) {
|
||||
postMessage("ADMIN_ADD_MAILBOX", data, cb);
|
||||
};
|
||||
|
||||
// Network
|
||||
common.onNetworkDisconnect = Util.mkEvent();
|
||||
@@ -690,6 +693,13 @@ define([
|
||||
pad.onConnectEvent = Util.mkEvent();
|
||||
pad.onErrorEvent = Util.mkEvent();
|
||||
|
||||
pad.requestAccess = function (data, cb) {
|
||||
postMessage("REQUEST_PAD_ACCESS", data, cb);
|
||||
};
|
||||
pad.giveAccess = function (data, cb) {
|
||||
postMessage("GIVE_PAD_ACCESS", data, cb);
|
||||
};
|
||||
|
||||
common.changePadPassword = function (Crypt, href, newPassword, edPublic, cb) {
|
||||
if (!href) { return void cb({ error: 'EINVAL_HREF' }); }
|
||||
var parsed = Hash.parsePadUrl(href);
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
define([
|
||||
'/common/curve.js',
|
||||
'/bower_components/chainpad-listmap/chainpad-listmap.js',
|
||||
], function (Curve, Listmap) {
|
||||
var Edit = {};
|
||||
|
||||
Edit.create = function (config, cb) { //network, channel, theirs, mine, cb) {
|
||||
var network = config.network;
|
||||
var channel = config.channel;
|
||||
var keys = config.keys;
|
||||
|
||||
try {
|
||||
var encryptor = Curve.createEncryptor(keys);
|
||||
var lm = Listmap.create({
|
||||
network: network,
|
||||
data: {},
|
||||
channel: channel,
|
||||
readOnly: false,
|
||||
validateKey: keys.validateKey || undefined,
|
||||
crypto: encryptor,
|
||||
userName: 'lol',
|
||||
logLevel: 1,
|
||||
});
|
||||
|
||||
var done = function () {
|
||||
// TODO make this abort and disconnect the session after the
|
||||
// user has finished making changes to the object, and they
|
||||
// have propagated.
|
||||
};
|
||||
|
||||
lm.proxy
|
||||
.on('create', function () {
|
||||
console.log('created');
|
||||
})
|
||||
.on('ready', function () {
|
||||
console.log('ready');
|
||||
cb(lm, done);
|
||||
})
|
||||
.on('disconnect', function () {
|
||||
console.log('disconnected');
|
||||
})
|
||||
.on('change', [], function (o, n, p) {
|
||||
console.log(o, n, p);
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
return Edit;
|
||||
});
|
||||
@@ -15,6 +15,7 @@ define([
|
||||
|
||||
var DiffDOM = window.diffDOM;
|
||||
var renderer = new Marked.Renderer();
|
||||
var restrictedRenderer = new Marked.Renderer();
|
||||
|
||||
var Mermaid = {
|
||||
init: function () {}
|
||||
@@ -61,13 +62,18 @@ define([
|
||||
return h('div.cp-md-toc', content).outerHTML;
|
||||
};
|
||||
|
||||
DiffMd.render = function (md, sanitize) {
|
||||
DiffMd.render = function (md, sanitize, restrictedMd) {
|
||||
Marked.setOptions({
|
||||
renderer: restrictedMd ? restrictedRenderer : renderer,
|
||||
});
|
||||
var r = Marked(md, {
|
||||
sanitize: sanitize
|
||||
});
|
||||
|
||||
// Add Table of Content
|
||||
r = r.replace(/<div class="cp-md-toc"><\/div>/g, getTOC());
|
||||
if (!restrictedMd) {
|
||||
r = r.replace(/<div class="cp-md-toc"><\/div>/g, getTOC());
|
||||
}
|
||||
toc = [];
|
||||
|
||||
return r;
|
||||
@@ -83,6 +89,7 @@ define([
|
||||
return defaultCode.apply(renderer, arguments);
|
||||
}
|
||||
};
|
||||
restrictedRenderer.code = renderer.code;
|
||||
|
||||
renderer.heading = function (text, level) {
|
||||
var i = 0;
|
||||
@@ -99,10 +106,13 @@ define([
|
||||
toc.push({
|
||||
level: level,
|
||||
id: id,
|
||||
title: text
|
||||
title: Util.stripTags(text)
|
||||
});
|
||||
return "<h" + level + " id=\"" + id + "\"><a href=\"#" + id + "\" class=\"anchor\"></a>" + text + "</h" + level + ">";
|
||||
};
|
||||
restrictedRenderer.heading = function (text) {
|
||||
return text;
|
||||
};
|
||||
|
||||
// Tasks list
|
||||
var checkedTaskItemPtn = /^\s*(<p>)?\[[xX]\](<\/p>)?\s*/;
|
||||
@@ -122,16 +132,23 @@ define([
|
||||
}
|
||||
if (!isCheckedTaskItem && !isUncheckedTaskItem && hasBogusInput) {
|
||||
if (/checked/.test(text)) {
|
||||
text = text.replace(bogusCheckPtn,
|
||||
text = text.replace(bogusCheckPtn,
|
||||
'<i class="fa fa-check-square" aria-hidden="true"></i>') + '\n';
|
||||
} else if (/disabled/.test(text)) {
|
||||
text = text.replace(bogusCheckPtn,
|
||||
text = text.replace(bogusCheckPtn,
|
||||
'<i class="fa fa-square-o" aria-hidden="true"></i>') + '\n';
|
||||
}
|
||||
}
|
||||
var cls = (isCheckedTaskItem || isUncheckedTaskItem || hasBogusInput) ? ' class="todo-list-item"' : '';
|
||||
return '<li'+ cls + '>' + text + '</li>\n';
|
||||
};
|
||||
restrictedRenderer.listitem = function (text) {
|
||||
if (bogusCheckPtn.test(text)) {
|
||||
text = text.replace(bogusCheckPtn, '');
|
||||
}
|
||||
return '<li>' + text + '</li>\n';
|
||||
};
|
||||
|
||||
renderer.image = function (href, title, text) {
|
||||
if (href.slice(0,6) === '/file/') {
|
||||
// DEPRECATED
|
||||
@@ -156,12 +173,19 @@ define([
|
||||
out += this.options.xhtml ? '/>' : '>';
|
||||
return out;
|
||||
};
|
||||
restrictedRenderer.image = renderer.image;
|
||||
|
||||
var renderParagraph = function (p) {
|
||||
return /<media\-tag[\s\S]*>/i.test(p)? p + '\n': '<p>' + p + '</p>\n';
|
||||
};
|
||||
renderer.paragraph = function (p) {
|
||||
if (p === '[TOC]') {
|
||||
return '<p><div class="cp-md-toc"></div></p>';
|
||||
}
|
||||
return /<media\-tag[\s\S]*>/i.test(p)? p + '\n': '<p>' + p + '</p>\n';
|
||||
return renderParagraph(p);
|
||||
};
|
||||
restrictedRenderer.paragraph = function (p) {
|
||||
return renderParagraph(p);
|
||||
};
|
||||
|
||||
var MutationObserver = window.MutationObserver;
|
||||
|
||||
@@ -30,9 +30,22 @@
|
||||
};
|
||||
|
||||
|
||||
var isplainTextFile = function (metadata) {
|
||||
// does its type begins with "text/"
|
||||
if (metadata.type.indexOf("text/") === 0) { return true; }
|
||||
// no type and no file extension -> let's guess it's plain text
|
||||
var parsedName = /^(\.?.+?)(\.[^.]+)?$/.exec(metadata.name) || [];
|
||||
if (!metadata.type && !parsedName[2]) { return true; }
|
||||
// other exceptions
|
||||
if (metadata.type === 'application/x-javascript') { return true; }
|
||||
if (metadata.type === 'application/xml') { return true; }
|
||||
return false;
|
||||
};
|
||||
|
||||
// Default config, can be overriden per media-tag call
|
||||
var config = {
|
||||
allowed: [
|
||||
'text/plain',
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/jpg',
|
||||
@@ -53,6 +66,23 @@
|
||||
text: "Download"
|
||||
},
|
||||
Plugins: {
|
||||
/**
|
||||
* @param {object} metadataObject {name, metadatatype, owners} containing metadata of the file
|
||||
* @param {strint} url Url of the blob object
|
||||
* @param {Blob} content Blob object containing the data of the file
|
||||
* @param {object} cfg Object {Plugins, allowed, download, pdf} containing infos about plugins
|
||||
* @param {function} cb Callback function: (err, pluginElement) => {}
|
||||
*/
|
||||
text: function (metadata, url, content, cfg, cb) {
|
||||
var plainText = document.createElement('div');
|
||||
plainText.className = "plain-text-reader";
|
||||
var reader = new FileReader();
|
||||
reader.addEventListener('loadend', function (e) {
|
||||
plainText.innerText = e.srcElement.result;
|
||||
cb(void 0, plainText);
|
||||
});
|
||||
reader.readAsText(content);
|
||||
},
|
||||
image: function (metadata, url, content, cfg, cb) {
|
||||
var img = document.createElement('img');
|
||||
img.setAttribute('src', url);
|
||||
@@ -271,6 +301,9 @@
|
||||
var blob = decrypted.content;
|
||||
|
||||
var mediaType = getType(mediaObject, metadata, cfg);
|
||||
if (isplainTextFile(metadata)) {
|
||||
mediaType = "text";
|
||||
}
|
||||
|
||||
if (mediaType === 'application') {
|
||||
mediaType = mediaObject.extension;
|
||||
|
||||
@@ -49,7 +49,7 @@ define([
|
||||
// We want to merge an edit pad: check if we have the same channel
|
||||
// but read-only and upgrade it in that case
|
||||
datas.forEach(function (pad) {
|
||||
if (!pad.href) { data.href = pad.href; }
|
||||
if (pad.data && !pad.data.href) { pad.data.href = data.href; }
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ define([
|
||||
});
|
||||
try {
|
||||
var $d = $(d);
|
||||
DiffMd.apply(DiffMd.render(md || '', true), $d, common);
|
||||
DiffMd.apply(DiffMd.render(md || '', true, true), $d, common);
|
||||
$d.addClass("cp-app-contacts-content");
|
||||
|
||||
// override link clicking, because we're in an iframe
|
||||
@@ -197,7 +197,7 @@ define([
|
||||
var getChat = function (id) {
|
||||
return $messages.find(dataQuery(id));
|
||||
};
|
||||
|
||||
|
||||
var scrollChatToBottom = function () {
|
||||
var $messagebox = $('.cp-app-contacts-messages');
|
||||
$messagebox.scrollTop($messagebox[0].scrollHeight);
|
||||
|
||||
@@ -191,9 +191,6 @@ define([
|
||||
userObject.version = version = 8;
|
||||
}
|
||||
}).nThen(function () {
|
||||
if (!AppConfig.migrateFriends) { return; } // XXX
|
||||
|
||||
|
||||
// Migration 9: send our mailbox channel to existing friends
|
||||
var migrateFriends = function () {
|
||||
var network = store.network;
|
||||
|
||||
@@ -6,141 +6,141 @@ define([
|
||||
// mode language (extension)
|
||||
var list = Modes.list = [
|
||||
"APL apl .apl",
|
||||
"ASCII-Armor asciiarmor",
|
||||
"ASN.1 asn.1",
|
||||
"ASCII-Armor asciiarmor .asc",
|
||||
"ASN.1 asn.1 .asn1",
|
||||
"Asterisk asterisk",
|
||||
"Brainfuck brainfuck .b",
|
||||
"C text/x-csrc .c",
|
||||
"C text/x-c++src .cpp",
|
||||
"C-like clike",
|
||||
"Clojure clojure",
|
||||
"CMake cmake",
|
||||
"COBOL cobol",
|
||||
"CoffeeScript coffeescript",
|
||||
"Common_Lisp commonlisp",
|
||||
"Crystal crystal",
|
||||
"C-like clike .c",
|
||||
"Clojure clojure .clj",
|
||||
"CMake cmake _", /* no extension */
|
||||
"COBOL cobol .cbl",
|
||||
"CoffeeScript coffeescript .coffee",
|
||||
"Common_Lisp commonlisp .lisp",
|
||||
"Crystal crystal .cr",
|
||||
"CSS css .css",
|
||||
"Cypher cypher",
|
||||
"D d",
|
||||
"Dart dart",
|
||||
"Diff diff",
|
||||
"Django django",
|
||||
"Dockerfile dockerfile",
|
||||
"DTD dtd",
|
||||
"Dylan dylan",
|
||||
"EBNF ebnf",
|
||||
"ECL ecl",
|
||||
"Eiffel eiffel",
|
||||
"Cypher cypher .cypher",
|
||||
"D d .d",
|
||||
"Dart dart .dart",
|
||||
"Diff diff .diff",
|
||||
"Django django .py",
|
||||
"Dockerfile dockerfile _", /* no extension */
|
||||
"DTD dtd .dtd",
|
||||
"Dylan dylan .dylan",
|
||||
"EBNF ebnf .ebnf",
|
||||
"ECL ecl .ecl",
|
||||
"Eiffel eiffel .e",
|
||||
"Elm elm .elm",
|
||||
"Erlang erlang",
|
||||
"Factor factor",
|
||||
"FCL fcl",
|
||||
"Forth forth",
|
||||
"Fortran fortran",
|
||||
"GAS gas",
|
||||
"Gherkin gherkin",
|
||||
"Go go",
|
||||
"Groovy groovy",
|
||||
"Haml haml",
|
||||
"Handlebars handlebars",
|
||||
"Erlang erlang .erl",
|
||||
"Factor factor .factor",
|
||||
"FCL fcl .fcl",
|
||||
"Forth forth .fs",
|
||||
"Fortran fortran .f90",
|
||||
"GAS gas .gas",
|
||||
"Gherkin gherkin .feature",
|
||||
"Go go .go",
|
||||
"Groovy groovy .groovy",
|
||||
"Haml haml .haml",
|
||||
"Handlebars handlebars .hbs",
|
||||
"Haskell haskell .hs",
|
||||
"Haskell-Literate haskell-literate",
|
||||
"Haxe haxe",
|
||||
"Haskell-Literate haskell-literate .lhs",
|
||||
"Haxe haxe .hx",
|
||||
"HTML htmlmixed .html",
|
||||
"HTTP http",
|
||||
"IDL idl",
|
||||
"JADE jade",
|
||||
"HTTP http _", /* no extension */
|
||||
"IDL idl .idl",
|
||||
"JADE jade .jade",
|
||||
"Java text/x-java .java",
|
||||
"JavaScript javascript .js",
|
||||
"Jinja2 jinja2",
|
||||
"Jinja2 jinja2 .j2",
|
||||
"JSX jsx .jsx",
|
||||
"Julia julia",
|
||||
"LiveScript livescript",
|
||||
"Lua lua",
|
||||
"Julia julia .jl",
|
||||
"LiveScript livescript .ls",
|
||||
"Lua lua .lua",
|
||||
"Markdown gfm .md",
|
||||
//"markdown markdown .md",
|
||||
"Mathematica mathematica",
|
||||
"mIRC mirc",
|
||||
"ML mllike",
|
||||
"Modelica modelica",
|
||||
"MscGen mscgen",
|
||||
"MUMPS mumps",
|
||||
"Nginx nginx",
|
||||
"NSIS nsis",
|
||||
"N-Triples ntriples",
|
||||
"Mathematica mathematica .nb",
|
||||
"mIRC mirc .irc",
|
||||
"ML mllike _", /* no extension */
|
||||
"Modelica modelica .mo",
|
||||
"MscGen mscgen .mscgen",
|
||||
"MUMPS mumps .m",
|
||||
"Nginx nginx .conf",
|
||||
"NSIS nsis .nsi",
|
||||
"N-Triples ntriples .nq",
|
||||
"Objective-C text/x-objectivec .m",
|
||||
"Octave octave",
|
||||
"Octave octave .m",
|
||||
"Org-mode orgmode .org",
|
||||
"Oz oz",
|
||||
"Pascal pascal",
|
||||
"PEG.js pegjs",
|
||||
"Perl perl",
|
||||
"PHP php",
|
||||
"Pig pig",
|
||||
"PowerShell powershell",
|
||||
"Properties properties",
|
||||
"Protocol_Buffers protobuf",
|
||||
"Puppet puppet",
|
||||
"Oz oz .oz",
|
||||
"Pascal pascal .pas",
|
||||
"PEG.js pegjs .pegjs",
|
||||
"Perl perl .pl",
|
||||
"PHP php .php",
|
||||
"Pig pig .pig",
|
||||
"PowerShell powershell .ps1",
|
||||
"Properties properties .properties",
|
||||
"Protocol_Buffers protobuf .proto",
|
||||
"Puppet puppet .pp",
|
||||
"Python python .py",
|
||||
"Q q",
|
||||
"R r",
|
||||
"RPM rpm",
|
||||
"RST rst",
|
||||
"Ruby ruby",
|
||||
"Rust rust",
|
||||
"Sass sass",
|
||||
"Q q .q",
|
||||
"R r .r",
|
||||
"RPM rpm .rpm",
|
||||
"RST rst .rst",
|
||||
"Ruby ruby .rb",
|
||||
"Rust rust .rs",
|
||||
"Sass sass .sass",
|
||||
"Scheme scheme .scm",
|
||||
"Shell shell .sh",
|
||||
"Sieve sieve",
|
||||
"Slim slim",
|
||||
"Smalltalk smalltalk",
|
||||
"Smarty smarty",
|
||||
"Solr solr",
|
||||
"Soy soy",
|
||||
"SPARQL sparql",
|
||||
"Spreadsheet spreadsheet",
|
||||
"SQL sql",
|
||||
"sTeX stex",
|
||||
"Stylus stylus",
|
||||
"Swift swift",
|
||||
"Tcl tcl",
|
||||
"Sieve sieve .sieve",
|
||||
"Slim slim .slim",
|
||||
"Smalltalk smalltalk _", /* no extension */
|
||||
"Smarty smarty _", /* no extension */
|
||||
"Solr solr _", /* no extension */
|
||||
"Soy soy .soy",
|
||||
"SPARQL sparql .rq",
|
||||
"Spreadsheet spreadsheet .xls",
|
||||
"SQL sql .sql",
|
||||
"sTeX stex .stex",
|
||||
"Stylus stylus .styl",
|
||||
"Swift swift .swift",
|
||||
"Tcl tcl .tcl",
|
||||
"Text text .txt",
|
||||
"Textile textile",
|
||||
"TiddlyWiki tiddlywiki",
|
||||
"Tiki tiki",
|
||||
"TOML toml",
|
||||
"Tornado tornado",
|
||||
"troff troff",
|
||||
"Textile textile .textile",
|
||||
"TiddlyWiki tiddlywiki .tw",
|
||||
"Tiki tiki _", /* no extension */
|
||||
"TOML toml .toml",
|
||||
"Tornado tornado .tornado",
|
||||
"troff troff .troff",
|
||||
"TTCN ttcn",
|
||||
"TTCN-cfg ttcn-cfg",
|
||||
"Turtle turtle",
|
||||
"Twig twig",
|
||||
"Visual_Basic vb",
|
||||
"VBScript vbscript",
|
||||
"Velocity velocity",
|
||||
"Verilog verilog",
|
||||
"VHDL vhdl",
|
||||
"Vue vue",
|
||||
"XML xml",
|
||||
"Turtle turtle .ttl",
|
||||
"Twig twig .twig",
|
||||
"Visual_Basic vb .vb",
|
||||
"VBScript vbscript .vbs",
|
||||
"Velocity velocity .vm",
|
||||
"Verilog verilog .v",
|
||||
"VHDL vhdl .vhdl",
|
||||
"Vue vue .vue",
|
||||
"XML xml .xml",
|
||||
//"xwiki xwiki21",
|
||||
"XQuery xquery",
|
||||
"XQuery xquery .xquery",
|
||||
"YAML yaml .yaml",
|
||||
"YAML_Frontmatter yaml-frontmatter",
|
||||
"Z80 z80"
|
||||
"YAML_Frontmatter yaml-frontmatter _", /* no extension */
|
||||
"Z80 z80 .z80"
|
||||
].map(function (line) {
|
||||
var kv = line.split(/\s/);
|
||||
return {
|
||||
language: kv[0].replace(/_/g, ' '),
|
||||
mode: kv[1],
|
||||
ext: kv[2],
|
||||
ext: kv[2] === '_' ? '' : kv[2],
|
||||
};
|
||||
});
|
||||
|
||||
Modes.extensionOf = function (mode) {
|
||||
var ext = '';
|
||||
var ext;
|
||||
list.some(function (o) {
|
||||
if (o.mode !== mode) { return; }
|
||||
ext = o.ext || '';
|
||||
ext = o.ext;
|
||||
return true;
|
||||
});
|
||||
return ext;
|
||||
|
||||
@@ -2,74 +2,209 @@ define([
|
||||
'jquery',
|
||||
'/common/hyperscript.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/common-interface.js',
|
||||
'/common/common-ui-elements.js',
|
||||
'/common/common-constants.js',
|
||||
'/customize/messages.js',
|
||||
], function ($, h, Hash, UIElements, Messages) {
|
||||
'/bower_components/nthen/index.js'
|
||||
], function ($, h, Hash, UI, UIElements, Constants, Messages, nThen) {
|
||||
|
||||
var handlers = {};
|
||||
|
||||
var defaultDismiss = function (common, data) {
|
||||
return function (e) {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
common.mailbox.dismiss(data, function (err) {
|
||||
if (err) { return void console.error(err); }
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
// Friend request
|
||||
|
||||
handlers['FRIEND_REQUEST'] = function (common, data, el) {
|
||||
handlers['FRIEND_REQUEST'] = function (common, data) {
|
||||
var content = data.content;
|
||||
var msg = content.msg;
|
||||
|
||||
// Display the notification
|
||||
content.getFormatText = function () {
|
||||
return Messages._getKey('friendRequest_notification', [msg.content.displayName || Messages.anonymous]);
|
||||
};
|
||||
|
||||
// Check authenticity
|
||||
if (msg.author !== msg.content.curvePublic) { return; }
|
||||
|
||||
common.addFriendRequest(data);
|
||||
|
||||
// Display the notification
|
||||
$(el).find('.cp-notification-content p')
|
||||
.html(Messages._getKey('friendRequest_notification', [msg.content.displayName || Messages.anonymous]));
|
||||
$(el).find('.cp-notification-content').addClass("cp-clickable")
|
||||
.click(function () {
|
||||
// if not archived, add handlers
|
||||
if (!content.archived) {
|
||||
content.handler = function () {
|
||||
UIElements.displayFriendRequestModal(common, data);
|
||||
});
|
||||
};
|
||||
common.addFriendRequest(data);
|
||||
}
|
||||
};
|
||||
|
||||
handlers['FRIEND_REQUEST_ACCEPTED'] = function (common, data, el) {
|
||||
handlers['FRIEND_REQUEST_ACCEPTED'] = function (common, data) {
|
||||
var content = data.content;
|
||||
var msg = content.msg;
|
||||
$(el).find('.cp-notification-content p')
|
||||
.html(Messages._getKey('friendRequest_accepted', [msg.content.name || Messages.anonymous]));
|
||||
$(el).find('.cp-notification-dismiss').css('display', 'flex');
|
||||
content.getFormatText = function () {
|
||||
return Messages._getKey('friendRequest_accepted', [msg.content.name || Messages.anonymous]);
|
||||
};
|
||||
if (!content.archived) {
|
||||
content.dismissHandler = defaultDismiss(common, data);
|
||||
}
|
||||
};
|
||||
|
||||
handlers['FRIEND_REQUEST_DECLINED'] = function (common, data, el) {
|
||||
handlers['FRIEND_REQUEST_DECLINED'] = function (common, data) {
|
||||
var content = data.content;
|
||||
var msg = content.msg;
|
||||
$(el).find('.cp-notification-content p')
|
||||
.html(Messages._getKey('friendRequest_declined', [msg.content.name || Messages.anonymous]));
|
||||
$(el).find('.cp-notification-dismiss').css('display', 'flex');
|
||||
content.getFormatText = function () {
|
||||
return Messages._getKey('friendRequest_declined', [msg.content.name || Messages.anonymous]);
|
||||
};
|
||||
if (!content.archived) {
|
||||
content.dismissHandler = defaultDismiss(common, data);
|
||||
}
|
||||
};
|
||||
|
||||
// Share pad
|
||||
|
||||
handlers['SHARE_PAD'] = function (common, data, el) {
|
||||
handlers['SHARE_PAD'] = function (common, data) {
|
||||
var content = data.content;
|
||||
var msg = content.msg;
|
||||
var type = Hash.parsePadUrl(msg.content.href).type;
|
||||
var key = type === 'drive' ? 'notification_folderShared' :
|
||||
(type === 'file' ? 'notification_fileShared' :
|
||||
'notification_padShared');
|
||||
$(el).find('.cp-notification-content p')
|
||||
.html(Messages._getKey(key, [msg.content.name || Messages.anonymous, msg.content.title]));
|
||||
$(el).find('.cp-notification-content').addClass("cp-clickable")
|
||||
.click(function () {
|
||||
'notification_padShared');
|
||||
content.getFormatText = function () {
|
||||
return Messages._getKey(key, [msg.content.name || Messages.anonymous, msg.content.title]);
|
||||
};
|
||||
content.handler = function () {
|
||||
var todo = function () {
|
||||
common.openURL(msg.content.href);
|
||||
defaultDismiss(common, data)();
|
||||
};
|
||||
nThen(function (waitFor) {
|
||||
if (msg.content.isTemplate) {
|
||||
common.sessionStorage.put(Constants.newPadPathKey, ['template'], waitFor());
|
||||
}
|
||||
if (msg.content.password) {
|
||||
common.sessionStorage.put('newPadPassword', msg.content.password, waitFor());
|
||||
}
|
||||
}).nThen(function () {
|
||||
todo();
|
||||
});
|
||||
};
|
||||
if (!content.archived) {
|
||||
content.dismissHandler = defaultDismiss(common, data);
|
||||
}
|
||||
};
|
||||
|
||||
// New support message from the admins
|
||||
handlers['SUPPORT_MESSAGE'] = function (common, data) {
|
||||
var content = data.content;
|
||||
content.getFormatText = function () {
|
||||
return Messages.support_notification;
|
||||
};
|
||||
content.handler = function () {
|
||||
common.openURL('/support/');
|
||||
defaultDismiss(common, data)();
|
||||
};
|
||||
};
|
||||
|
||||
handlers['REQUEST_PAD_ACCESS'] = function (common, data) {
|
||||
var content = data.content;
|
||||
var msg = content.msg;
|
||||
|
||||
// Check authenticity
|
||||
if (msg.author !== msg.content.user.curvePublic) { return; }
|
||||
|
||||
// Display the notification
|
||||
content.getFormatText = function () {
|
||||
return Messages._getKey('requestEdit_request', [msg.content.title, msg.content.user.displayName]);
|
||||
};
|
||||
|
||||
// if not archived, add handlers
|
||||
content.handler = function () {
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
var priv = metadataMgr.getPrivateData();
|
||||
|
||||
var link = h('a', {
|
||||
href: '#'
|
||||
}, Messages.requestEdit_viewPad);
|
||||
var verified = h('p');
|
||||
var $verified = $(verified);
|
||||
|
||||
if (priv.friends && priv.friends[msg.author]) {
|
||||
$verified.addClass('cp-notifications-requestedit-verified');
|
||||
var f = priv.friends[msg.author];
|
||||
$verified.append(h('span.fa.fa-certificate'));
|
||||
var $avatar = $(h('span.cp-avatar')).appendTo($verified);
|
||||
$verified.append(h('p', Messages._getKey('requestEdit_fromFriend', [f.displayName])));
|
||||
common.displayAvatar($avatar, f.avatar, f.displayName);
|
||||
} else {
|
||||
$verified.append(Messages._getKey('requestEdit_fromStranger', [msg.content.user.displayName]));
|
||||
}
|
||||
|
||||
var div = h('div', [
|
||||
UI.setHTML(h('p'), Messages._getKey('requestEdit_confirm', [msg.content.title, msg.content.user.displayName])),
|
||||
verified,
|
||||
link
|
||||
]);
|
||||
$(link).click(function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
common.openURL(msg.content.href);
|
||||
});
|
||||
$(el).find('.cp-notification-dismiss').css('display', 'flex');
|
||||
UI.confirm(div, function (yes) {
|
||||
if (!yes) { return; }
|
||||
common.getSframeChannel().event('EV_GIVE_ACCESS', {
|
||||
channel: msg.content.channel,
|
||||
user: msg.content.user
|
||||
});
|
||||
defaultDismiss(common, data)();
|
||||
}, {
|
||||
ok: Messages.friendRequest_accept,
|
||||
cancel: Messages.later
|
||||
});
|
||||
};
|
||||
|
||||
if (!content.archived) {
|
||||
content.dismissHandler = defaultDismiss(common, data);
|
||||
}
|
||||
};
|
||||
|
||||
handlers['GIVE_PAD_ACCESS'] = function (common, data) {
|
||||
var content = data.content;
|
||||
var msg = content.msg;
|
||||
|
||||
// Check authenticity
|
||||
if (msg.author !== msg.content.user.curvePublic) { return; }
|
||||
|
||||
if (!msg.content.href) { return; }
|
||||
|
||||
// Display the notification
|
||||
content.getFormatText = function () {
|
||||
return Messages._getKey('requestEdit_accepted', [msg.content.title, msg.content.user.displayName]);
|
||||
};
|
||||
|
||||
// if not archived, add handlers
|
||||
content.handler = function () {
|
||||
common.openURL(msg.content.href);
|
||||
defaultDismiss(common, data)();
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
add: function (common, data, el) {
|
||||
add: function (common, data) {
|
||||
var type = data.content.msg.type;
|
||||
|
||||
if (handlers[type]) {
|
||||
handlers[type](common, data, el);
|
||||
} else {
|
||||
$(el).find('.cp-notification-dismiss').css('display', 'flex');
|
||||
handlers[type](common, data);
|
||||
// add getters to access simply some informations
|
||||
data.content.isClickable = typeof data.content.handler === "function";
|
||||
data.content.isDismissible = typeof data.content.dismissHandler === "function";
|
||||
}
|
||||
},
|
||||
remove: function (common, data) {
|
||||
|
||||
@@ -44,6 +44,7 @@ body.cp-app-sheet, body.cp-app-oodoc, body.cp-app-ooslide {
|
||||
height: 100%;
|
||||
background-color: lightgrey;
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
}
|
||||
#cp-app-oo-editor {
|
||||
flex: 1;
|
||||
|
||||
@@ -102,9 +102,12 @@ define([
|
||||
Cryptpad.onlyoffice.onEvent.reg(function (obj) {
|
||||
if (obj.ev === 'MESSAGE' && !/^cp\|/.test(obj.data)) {
|
||||
try {
|
||||
var validateKey = obj.data.validateKey || true;
|
||||
var skipCheck = validateKey === true;
|
||||
var msg = obj.data.msg;
|
||||
obj.data = {
|
||||
msg: JSON.parse(Utils.crypto.decrypt(obj.data, Utils.secret.keys.validateKey)),
|
||||
hash: obj.data.slice(0,64)
|
||||
msg: JSON.parse(Utils.crypto.decrypt(msg, validateKey, skipCheck)),
|
||||
hash: msg.slice(0,64)
|
||||
};
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
@@ -8077,9 +8077,6 @@ jQuery.fn.extend({
|
||||
prop = jQuery.extend( {}, prop );
|
||||
|
||||
function doAnimation() {
|
||||
// XXX 'this' does not always have a nodeName when running the
|
||||
// test suite
|
||||
|
||||
if ( optall.queue === false ) {
|
||||
jQuery._mark( this );
|
||||
}
|
||||
|
||||
@@ -478,7 +478,9 @@ define([
|
||||
settings: store.proxy.settings,
|
||||
thumbnails: disableThumbnails === false,
|
||||
isDriveOwned: Boolean(Util.find(store, ['driveMetadata', 'owners'])),
|
||||
pendingFriends: store.proxy.friends_pending || {}
|
||||
support: Util.find(store.proxy, ['mailboxes', 'support', 'channel']),
|
||||
pendingFriends: store.proxy.friends_pending || {},
|
||||
supportPrivateKey: Util.find(store.proxy, ['mailboxes', 'supportadmin', 'keys', 'curvePrivate'])
|
||||
}
|
||||
};
|
||||
cb(JSON.parse(JSON.stringify(metadata)));
|
||||
@@ -1059,6 +1061,27 @@ define([
|
||||
cb(res);
|
||||
});
|
||||
};
|
||||
Store.addAdminMailbox = function (clientId, data, cb) {
|
||||
var priv = data;
|
||||
var pub = Hash.getBoxPublicFromSecret(priv);
|
||||
if (!priv || !pub) { return void cb({error: 'EINVAL'}); }
|
||||
var channel = Hash.getChannelIdFromKey(pub);
|
||||
var mailboxes = store.proxy.mailboxes = store.proxy.mailboxes || {};
|
||||
var box = mailboxes.supportadmin = {
|
||||
channel: channel,
|
||||
viewed: [],
|
||||
lastKnownHash: '',
|
||||
keys: {
|
||||
curvePublic: pub,
|
||||
curvePrivate: priv
|
||||
}
|
||||
};
|
||||
Store.pinPads(null, [channel], function () {});
|
||||
store.mailbox.open('supportadmin', box, function () {
|
||||
console.log('ready');
|
||||
});
|
||||
onSync(cb);
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
/////////////////////// PAD //////////////////////////////////////
|
||||
@@ -1067,6 +1090,7 @@ define([
|
||||
var channels = Store.channels = store.channels = {};
|
||||
|
||||
Store.joinPad = function (clientId, data) {
|
||||
console.log('joining', data.channel);
|
||||
var isNew = typeof channels[data.channel] === "undefined";
|
||||
var channel = channels[data.channel] = channels[data.channel] || {
|
||||
queue: [],
|
||||
@@ -1220,6 +1244,97 @@ define([
|
||||
channel.sendMessage(msg, clientId, cb);
|
||||
};
|
||||
|
||||
Store.requestPadAccess = function (clientId, data, cb) {
|
||||
// Get owners from pad metadata
|
||||
// Try to find an owner in our friend list
|
||||
// Mailbox...
|
||||
var channel = channels[data.channel];
|
||||
if (!data.send && channel && (!channel.data || !channel.data.channel)) {
|
||||
var i = 0;
|
||||
var it = setInterval(function () {
|
||||
if (channel.data && channel.data.channel) {
|
||||
clearInterval(it);
|
||||
Store.requestPadAccess(clientId, data, cb);
|
||||
return;
|
||||
}
|
||||
if (i >= 300) { // One minute timeout
|
||||
clearInterval(it);
|
||||
}
|
||||
i++;
|
||||
}, 200);
|
||||
return;
|
||||
}
|
||||
var fData = channel.data || {};
|
||||
if (fData.owners) {
|
||||
var friends = store.proxy.friends || {};
|
||||
if (Object.keys(friends).length > 1) {
|
||||
var owner;
|
||||
fData.owners.some(function (edPublic) {
|
||||
return Object.keys(friends).some(function (curve) {
|
||||
if (curve === "me") { return; }
|
||||
if (edPublic === friends[curve].edPublic &&
|
||||
friends[curve].notifications) {
|
||||
owner = friends[curve];
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
if (owner) {
|
||||
if (data.send) {
|
||||
var myData = Messaging.createData(store.proxy);
|
||||
delete myData.channel;
|
||||
store.mailbox.sendTo('REQUEST_PAD_ACCESS', {
|
||||
channel: data.channel,
|
||||
user: myData
|
||||
}, {
|
||||
channel: owner.notifications,
|
||||
curvePublic: owner.curvePublic
|
||||
}, function () {
|
||||
cb({state: true});
|
||||
});
|
||||
return;
|
||||
}
|
||||
return void cb({state: true});
|
||||
}
|
||||
}
|
||||
}
|
||||
cb({sent: false});
|
||||
};
|
||||
Store.givePadAccess = function (clientId, data, cb) {
|
||||
var edPublic = store.proxy.edPublic;
|
||||
var channel = data.channel;
|
||||
var res = store.manager.findChannel(channel);
|
||||
|
||||
if (!data.user || !data.user.notifications || !data.user.curvePublic) {
|
||||
return void cb({error: 'EINVAL'});
|
||||
}
|
||||
|
||||
var href, title;
|
||||
|
||||
if (!res.some(function (obj) {
|
||||
if (obj.data &&
|
||||
Array.isArray(obj.data.owners) && obj.data.owners.indexOf(edPublic) !== -1 &&
|
||||
obj.data.href) {
|
||||
href = obj.data.href;
|
||||
title = obj.data.title;
|
||||
return true;
|
||||
}
|
||||
})) { return void cb({error: 'ENOTFOUND'}); }
|
||||
|
||||
var myData = Messaging.createData(store.proxy);
|
||||
delete myData.channel;
|
||||
store.mailbox.sendTo("GIVE_PAD_ACCESS", {
|
||||
channel: channel,
|
||||
href: href,
|
||||
title: title,
|
||||
user: myData
|
||||
}, {
|
||||
channel: data.user.notifications,
|
||||
curvePublic: data.user.curvePublic
|
||||
});
|
||||
cb();
|
||||
};
|
||||
|
||||
// GET_FULL_HISTORY from sframe-common-outer
|
||||
Store.getFullHistory = function (clientId, data, cb) {
|
||||
var network = store.network;
|
||||
@@ -1683,7 +1798,7 @@ define([
|
||||
state: (2 + (version / 10)),
|
||||
progress: progress
|
||||
});
|
||||
});
|
||||
}, store);
|
||||
}).nThen(function (waitFor) {
|
||||
postMessage(clientId, 'LOADING_DRIVE', {
|
||||
state: 3
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
define([
|
||||
'/common/common-messaging.js',
|
||||
], function (Messaging) {
|
||||
'/common/common-hash.js',
|
||||
], function (Messaging, Hash) {
|
||||
|
||||
var getRandomTimeout = function (ctx) {
|
||||
var lag = ctx.store.realtime.getLag().lag || 0;
|
||||
@@ -156,6 +157,108 @@ define([
|
||||
cb(true);
|
||||
};
|
||||
|
||||
// Hide duplicates when receiving a SHARE_PAD notification:
|
||||
// Keep only one notification per channel: the stronger and more recent one
|
||||
var channels = {};
|
||||
handlers['SHARE_PAD'] = function (ctx, box, data, cb) {
|
||||
var msg = data.msg;
|
||||
var hash = data.hash;
|
||||
var content = msg.content;
|
||||
// content.name, content.title, content.href, content.password
|
||||
|
||||
var channel = Hash.hrefToHexChannelId(content.href, content.password);
|
||||
var parsed = Hash.parsePadUrl(content.href);
|
||||
var mode = parsed.hashData && parsed.hashData.mode || 'n/a';
|
||||
|
||||
var old = channels[channel];
|
||||
var toRemove;
|
||||
if (old) {
|
||||
// New hash is weaker, ignore
|
||||
if (old.mode === 'edit' && mode === 'view') {
|
||||
return void cb(true);
|
||||
}
|
||||
// New hash is not weaker, clear the old one
|
||||
toRemove = old.data;
|
||||
}
|
||||
|
||||
// Update the data
|
||||
channels[channel] = {
|
||||
mode: mode,
|
||||
data: {
|
||||
type: box.type,
|
||||
hash: hash
|
||||
}
|
||||
};
|
||||
|
||||
cb(false, toRemove);
|
||||
};
|
||||
removeHandlers['SHARE_PAD'] = function (ctx, box, data, hash) {
|
||||
var content = data.content;
|
||||
var channel = Hash.hrefToHexChannelId(content.href, content.password);
|
||||
var old = channels[channel];
|
||||
if (old && old.data && old.data.hash === hash) {
|
||||
delete channels[channel];
|
||||
}
|
||||
};
|
||||
|
||||
// Hide duplicates when receiving a SUPPORT_MESSAGE notification
|
||||
var supportMessage = false;
|
||||
handlers['SUPPORT_MESSAGE'] = function (ctx, box, data, cb) {
|
||||
if (supportMessage) { return void cb(true); }
|
||||
supportMessage = true;
|
||||
cb();
|
||||
};
|
||||
|
||||
// Incoming edit rights request: add data before sending it to inner
|
||||
handlers['REQUEST_PAD_ACCESS'] = function (ctx, box, data, cb) {
|
||||
var msg = data.msg;
|
||||
var content = msg.content;
|
||||
|
||||
if (msg.author !== content.user.curvePublic) { return void cb(true); }
|
||||
|
||||
var channel = content.channel;
|
||||
var res = ctx.store.manager.findChannel(channel);
|
||||
|
||||
if (!res.length) { return void cb(true); }
|
||||
|
||||
var edPublic = ctx.store.proxy.edPublic;
|
||||
var title, href;
|
||||
if (!res.some(function (obj) {
|
||||
if (obj.data &&
|
||||
Array.isArray(obj.data.owners) && obj.data.owners.indexOf(edPublic) !== -1 &&
|
||||
obj.data.href) {
|
||||
href = obj.data.href;
|
||||
title = obj.data.filename || obj.data.title;
|
||||
return true;
|
||||
}
|
||||
})) { return void cb(true); }
|
||||
|
||||
content.title = title;
|
||||
content.href = href;
|
||||
cb(false);
|
||||
};
|
||||
|
||||
handlers['GIVE_PAD_ACCESS'] = function (ctx, box, data, cb) {
|
||||
var msg = data.msg;
|
||||
var content = msg.content;
|
||||
|
||||
if (msg.author !== content.user.curvePublic) { return void cb(true); }
|
||||
|
||||
var channel = content.channel;
|
||||
var res = ctx.store.manager.findChannel(channel);
|
||||
|
||||
var title;
|
||||
res.forEach(function (obj) {
|
||||
if (obj.data && !obj.data.href) {
|
||||
if (!title) { title = obj.data.filename || obj.data.title; }
|
||||
obj.data.href = content.href;
|
||||
}
|
||||
});
|
||||
|
||||
content.title = title || content.title;
|
||||
cb(false);
|
||||
};
|
||||
|
||||
return {
|
||||
add: function (ctx, box, data, cb) {
|
||||
/**
|
||||
|
||||
@@ -10,6 +10,8 @@ define([
|
||||
|
||||
var TYPES = [
|
||||
'notifications',
|
||||
'supportadmin',
|
||||
'support'
|
||||
];
|
||||
var BLOCKING_TYPES = [
|
||||
];
|
||||
@@ -25,6 +27,16 @@ define([
|
||||
if (res.error) { console.error(res); }
|
||||
});
|
||||
}
|
||||
if (!mailboxes['support']) {
|
||||
mailboxes.support = {
|
||||
channel: Hash.createChannelId(),
|
||||
lastKnownHash: '',
|
||||
viewed: []
|
||||
};
|
||||
ctx.pinPads([mailboxes.support.channel], function (res) {
|
||||
if (res.error) { console.error(res); }
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -76,15 +88,32 @@ proxy.mailboxes = {
|
||||
var crypto = Crypto.Mailbox.createEncryptor(keys);
|
||||
var network = ctx.store.network;
|
||||
|
||||
var ciphertext = crypto.encrypt(JSON.stringify({
|
||||
var text = JSON.stringify({
|
||||
type: type,
|
||||
content: msg
|
||||
}), user.curvePublic);
|
||||
});
|
||||
var ciphertext = crypto.encrypt(text, user.curvePublic);
|
||||
|
||||
network.join(user.channel).then(function (wc) {
|
||||
wc.bcast(ciphertext).then(function () {
|
||||
cb();
|
||||
wc.leave();
|
||||
|
||||
// If we've just sent a message to one of our mailboxes, we have to trigger the handler manually
|
||||
// (the server won't send back our message to us)
|
||||
// If it isn't one of our mailboxes, we can close it now
|
||||
var box;
|
||||
if (Object.keys(ctx.boxes).some(function (t) {
|
||||
var _box = ctx.boxes[t];
|
||||
if (_box.channel === user.channel) {
|
||||
box = _box;
|
||||
return true;
|
||||
}
|
||||
})) {
|
||||
var hash = ciphertext.slice(0, 64);
|
||||
box.onMessage(text, null, null, null, hash, user.curvePublic);
|
||||
} else {
|
||||
wc.leave();
|
||||
}
|
||||
});
|
||||
}, function (err) {
|
||||
cb({error: err});
|
||||
@@ -157,6 +186,8 @@ proxy.mailboxes = {
|
||||
|
||||
var openChannel = function (ctx, type, m, onReady) {
|
||||
var box = ctx.boxes[type] = {
|
||||
channel: m.channel,
|
||||
type: type,
|
||||
queue: [], // Store the messages to send when the channel is ready
|
||||
history: [], // All the hashes loaded from the server in corretc order
|
||||
content: {}, // Content of the messages that should be displayed
|
||||
@@ -173,9 +204,10 @@ proxy.mailboxes = {
|
||||
if (!Crypto.Mailbox) {
|
||||
return void console.error("chainpad-crypto is outdated and doesn't support mailboxes.");
|
||||
}
|
||||
var keys = getMyKeys(ctx);
|
||||
var keys = m.keys || getMyKeys(ctx);
|
||||
if (!keys) { return void console.error("missing asymmetric encryption keys"); }
|
||||
var crypto = Crypto.Mailbox.createEncryptor(keys);
|
||||
box.encryptor = crypto;
|
||||
var cfg = {
|
||||
network: ctx.store.network,
|
||||
channel: m.channel,
|
||||
@@ -213,8 +245,11 @@ proxy.mailboxes = {
|
||||
});
|
||||
box.queue = [];
|
||||
};
|
||||
cfg.onMessage = function (msg, user, vKey, isCp, hash, author) {
|
||||
var lastReceivedHash; // Don't send a duplicate of the last known hash on reconnect
|
||||
box.onMessage = cfg.onMessage = function (msg, user, vKey, isCp, hash, author) {
|
||||
if (hash === m.lastKnownHash) { return; }
|
||||
if (hash === lastReceivedHash) { return; }
|
||||
lastReceivedHash = hash;
|
||||
try {
|
||||
msg = JSON.parse(msg);
|
||||
} catch (e) {
|
||||
@@ -228,8 +263,8 @@ proxy.mailboxes = {
|
||||
msg: msg,
|
||||
hash: hash
|
||||
};
|
||||
Handlers.add(ctx, box, message, function (toDismiss) {
|
||||
if (toDismiss) {
|
||||
Handlers.add(ctx, box, message, function (dismissed, toDismiss) {
|
||||
if (dismissed) { // This message should be removed
|
||||
dismiss(ctx, {
|
||||
type: type,
|
||||
hash: hash
|
||||
@@ -238,6 +273,11 @@ proxy.mailboxes = {
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (toDismiss) { // List of other messages to remove
|
||||
dismiss(ctx, toDismiss, '', function () {
|
||||
console.log('Notification handled automatically');
|
||||
});
|
||||
}
|
||||
box.content[hash] = msg;
|
||||
showMessage(ctx, type, message);
|
||||
});
|
||||
@@ -293,6 +333,63 @@ proxy.mailboxes = {
|
||||
CpNetflux.start(cfg);
|
||||
};
|
||||
|
||||
var initializeHistory = function (ctx) {
|
||||
var network = ctx.store.network;
|
||||
network.on('message', function (msg, sender) {
|
||||
if (sender !== network.historyKeeper) { return; }
|
||||
var parsed = JSON.parse(msg);
|
||||
if (!/HISTORY_RANGE/.test(parsed[0])) { return; }
|
||||
|
||||
var txid = parsed[1];
|
||||
var req = ctx.req[txid];
|
||||
if (!req) { return; }
|
||||
var type = parsed[0];
|
||||
var _msg = parsed[2];
|
||||
var box = req.box;
|
||||
|
||||
if (type === 'HISTORY_RANGE') {
|
||||
if (!Array.isArray(_msg)) { return; }
|
||||
var message;
|
||||
try {
|
||||
var decrypted = box.encryptor.decrypt(_msg[4]);
|
||||
message = JSON.parse(decrypted.content);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
ctx.emit('HISTORY', {
|
||||
txid: txid,
|
||||
time: _msg[5],
|
||||
message: message,
|
||||
hash: _msg[4].slice(0,64)
|
||||
}, [req.cId]);
|
||||
} else if (type === 'HISTORY_RANGE_END') {
|
||||
ctx.emit('HISTORY', {
|
||||
txid: txid,
|
||||
complete: true
|
||||
}, [req.cId]);
|
||||
delete ctx.req[txid];
|
||||
}
|
||||
});
|
||||
};
|
||||
var loadHistory = function (ctx, clientId, data, cb) {
|
||||
var box = ctx.boxes[data.type];
|
||||
if (!box) { return void cb({error: 'ENOENT'}); }
|
||||
var msg = [ 'GET_HISTORY_RANGE', box.channel, {
|
||||
from: data.lastKnownHash,
|
||||
count: data.count,
|
||||
txid: data.txid
|
||||
}
|
||||
];
|
||||
ctx.req[data.txid] = {
|
||||
cId: clientId,
|
||||
box: box
|
||||
};
|
||||
var network = ctx.store.network;
|
||||
network.sendto(network.historyKeeper, JSON.stringify(msg)).then(function () {
|
||||
}, function (err) {
|
||||
console.error(err);
|
||||
});
|
||||
};
|
||||
|
||||
var subscribe = function (ctx, data, cId, cb) {
|
||||
// Get existing notifications
|
||||
@@ -327,12 +424,14 @@ proxy.mailboxes = {
|
||||
updateMetadata: cfg.updateMetadata,
|
||||
emit: emit,
|
||||
clients: [],
|
||||
boxes: {}
|
||||
boxes: {},
|
||||
req: {}
|
||||
};
|
||||
|
||||
var mailboxes = store.proxy.mailboxes = store.proxy.mailboxes || {};
|
||||
|
||||
initializeMailboxes(ctx, mailboxes);
|
||||
initializeHistory(ctx);
|
||||
|
||||
Object.keys(mailboxes).forEach(function (key) {
|
||||
if (TYPES.indexOf(key) === -1) { return; }
|
||||
@@ -359,6 +458,11 @@ proxy.mailboxes = {
|
||||
});
|
||||
};
|
||||
|
||||
mailbox.open = function (key, m, cb) {
|
||||
if (TYPES.indexOf(key) === -1) { return; }
|
||||
openChannel(ctx, key, m, cb);
|
||||
};
|
||||
|
||||
mailbox.dismiss = function (data, cb) {
|
||||
dismiss(ctx, data, '', cb);
|
||||
};
|
||||
@@ -382,6 +486,9 @@ proxy.mailboxes = {
|
||||
if (cmd === 'SENDTO') {
|
||||
return void sendTo(ctx, data.type, data.msg, data.user, cb);
|
||||
}
|
||||
if (cmd === 'LOAD_HISTORY') {
|
||||
return void loadHistory(ctx, clientId, data, cb);
|
||||
}
|
||||
};
|
||||
|
||||
return mailbox;
|
||||
|
||||
@@ -26,7 +26,10 @@ define([
|
||||
if (!c.id) { c.id = chan.wc.myID + '-' + client; }
|
||||
|
||||
chan.history.forEach(function (msg) {
|
||||
ctx.emit('MESSAGE', msg, [client]);
|
||||
ctx.emit('MESSAGE', {
|
||||
msg: msg,
|
||||
validateKey: chan.validateKey
|
||||
}, [client]);
|
||||
});
|
||||
|
||||
// ==> And push the new tab to the list
|
||||
@@ -37,7 +40,8 @@ define([
|
||||
var onOpen = function (wc) {
|
||||
|
||||
ctx.channels[channel] = ctx.channels[channel] || {
|
||||
history: []
|
||||
history: [],
|
||||
validateKey: obj.validateKey
|
||||
};
|
||||
|
||||
chan = ctx.channels[channel];
|
||||
@@ -61,7 +65,10 @@ define([
|
||||
});
|
||||
wc.on('message', function (msg) {
|
||||
chan.history.push(msg);
|
||||
ctx.emit('MESSAGE', msg, chan.clients);
|
||||
ctx.emit('MESSAGE', {
|
||||
msg: msg,
|
||||
validateKey: chan.validateKey
|
||||
}, chan.clients);
|
||||
});
|
||||
|
||||
chan.wc = wc;
|
||||
@@ -101,6 +108,7 @@ define([
|
||||
};
|
||||
|
||||
network.on('message', function (msg, sender) {
|
||||
if (!ctx.channels[channel]) { return; }
|
||||
var hk = network.historyKeeper;
|
||||
if (sender !== hk) { return; }
|
||||
|
||||
@@ -115,7 +123,12 @@ define([
|
||||
// Keep only metadata messages for the current channel
|
||||
if (parsed.channel && parsed.channel !== channel) { return; }
|
||||
// Ignore the metadata message
|
||||
if (parsed.validateKey && parsed.channel) { return; }
|
||||
if (parsed.validateKey && parsed.channel) {
|
||||
if (!chan.validateKey) {
|
||||
chan.validateKey = parsed.validateKey;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// End of history: emit READY
|
||||
if (parsed.state && parsed.state === 1 && parsed.channel) {
|
||||
ctx.emit('READY', '', chan.clients);
|
||||
@@ -132,7 +145,9 @@ define([
|
||||
if (hash === chan.lastKnownHash || hash === chan.lastCpHash) { return; }
|
||||
|
||||
chan.lastKnownHash = hash;
|
||||
ctx.emit('MESSAGE', msg, chan.clients);
|
||||
ctx.emit('MESSAGE', {
|
||||
msg: msg,
|
||||
}, chan.clients);
|
||||
chan.history.push(msg);
|
||||
});
|
||||
|
||||
@@ -176,7 +191,9 @@ define([
|
||||
return void chan.sendMsg(data.isCp, cb);
|
||||
}
|
||||
chan.sendMsg(data.msg, cb);
|
||||
ctx.emit('MESSAGE', data.msg, chan.clients.filter(function (cl) {
|
||||
ctx.emit('MESSAGE', {
|
||||
msg: data.msg
|
||||
}, chan.clients.filter(function (cl) {
|
||||
return cl !== clientId;
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -78,12 +78,15 @@ define([
|
||||
GET_FULL_HISTORY: Store.getFullHistory,
|
||||
GET_HISTORY_RANGE: Store.getHistoryRange,
|
||||
IS_NEW_CHANNEL: Store.isNewChannel,
|
||||
REQUEST_PAD_ACCESS: Store.requestPadAccess,
|
||||
GIVE_PAD_ACCESS: Store.givePadAccess,
|
||||
// Drive
|
||||
DRIVE_USEROBJECT: Store.userObjectCommand,
|
||||
// Settings,
|
||||
DELETE_ACCOUNT: Store.deleteAccount,
|
||||
// Admin
|
||||
ADMIN_RPC: Store.adminRpc,
|
||||
ADMIN_ADD_MAILBOX: Store.addAdminMailbox,
|
||||
};
|
||||
|
||||
Rpc.query = function (cmd, data, cb) {
|
||||
|
||||
@@ -506,8 +506,14 @@ define([
|
||||
var fixRoot = function (elem) {
|
||||
if (typeof(files[ROOT]) !== "object") { debug("ROOT was not an object"); files[ROOT] = {}; }
|
||||
var element = elem || files[ROOT];
|
||||
if (!element) { return console.error("Invalid element in root"); }
|
||||
var nbMetadataFolders = 0;
|
||||
for (var el in element) {
|
||||
if (element[el] === null) {
|
||||
console.error('element[%s] is null', el);
|
||||
delete element[el];
|
||||
continue;
|
||||
}
|
||||
if (exp.isFolderData(element[el])) {
|
||||
if (nbMetadataFolders !== 0) {
|
||||
debug("Multiple metadata files in folder");
|
||||
@@ -625,6 +631,11 @@ define([
|
||||
var root = exp.find([ROOT]);
|
||||
var toClean = [];
|
||||
for (var id in fd) {
|
||||
if (String(id) !== String(Number(id))) {
|
||||
debug("Invalid file ID in filesData.", id);
|
||||
toClean.push(id);
|
||||
continue;
|
||||
}
|
||||
id = Number(id);
|
||||
var el = fd[id];
|
||||
|
||||
|
||||
@@ -252,6 +252,9 @@ define([
|
||||
var obj = Env.folders[el].proxy.metadata || {};
|
||||
if (obj) { key = obj.title; }
|
||||
} else {
|
||||
try {
|
||||
el = JSON.parse(JSON.stringify(el));
|
||||
} catch (e) { return undefined; }
|
||||
userObject.getFilesRecursively(el, files);
|
||||
}
|
||||
|
||||
@@ -342,7 +345,7 @@ define([
|
||||
});
|
||||
|
||||
// Remove the elements from the old location (without unpinning)
|
||||
Env.user.userObject.delete(resolved.main, waitFor());
|
||||
Env.user.userObject.delete(resolved.main, waitFor()); // FIXME waitFor() is called synchronously
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -369,7 +372,7 @@ define([
|
||||
if (copy) { return; }
|
||||
|
||||
// Remove the elements from the old location (without unpinning)
|
||||
uoFrom.delete(paths, waitFor());
|
||||
uoFrom.delete(paths, waitFor()); // FIXME waitFor() is called synchronously
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -707,6 +710,7 @@ define([
|
||||
if (type === 'expirable') {
|
||||
return function (fileId) {
|
||||
var data = userObject.getFileData(fileId);
|
||||
if (!data) { return; }
|
||||
// Don't push duplicates
|
||||
if (result.indexOf(data.channel) !== -1) { return; }
|
||||
// Return pads owned by someone else or expired by time
|
||||
@@ -718,6 +722,7 @@ define([
|
||||
if (type === 'owned') {
|
||||
return function (fileId) {
|
||||
var data = userObject.getFileData(fileId);
|
||||
if (!data) { return; }
|
||||
// Don't push duplicates
|
||||
if (result.indexOf(data.channel) !== -1) { return; }
|
||||
// Return owned pads
|
||||
@@ -729,6 +734,7 @@ define([
|
||||
if (type === "pin") {
|
||||
return function (fileId) {
|
||||
var data = userObject.getFileData(fileId);
|
||||
if (!data) { return; }
|
||||
// Don't pin pads owned by someone else
|
||||
if (_ownedByOther(Env, data.owners)) { return; }
|
||||
// Don't push duplicates
|
||||
|
||||
@@ -401,7 +401,7 @@ define([
|
||||
var ext = (typeof(extension) === 'function') ? extension() : extension;
|
||||
var suggestion = title.suggestTitle('cryptpad-document');
|
||||
UI.prompt(Messages.exportPrompt,
|
||||
Util.fixFileName(suggestion) + '.' + ext, function (filename)
|
||||
Util.fixFileName(suggestion) + ext, function (filename)
|
||||
{
|
||||
if (!(typeof(filename) === 'string' && filename)) { return; }
|
||||
if (async) {
|
||||
@@ -454,7 +454,7 @@ define([
|
||||
return;
|
||||
}
|
||||
if (!mediaTagEmbedder) { console.log('mediaTagEmbedder missing'); return; }
|
||||
if (data.type !== 'file') { console.log('unhandled embed type ' + data.type); return; }
|
||||
if (data.type !== 'file') { console.log('unhandled embed type ' + data.type); return; }
|
||||
var privateDat = cpNfInner.metadataMgr.getPrivateData();
|
||||
var origin = privateDat.fileHost || privateDat.origin;
|
||||
var src = data.src = origin + data.src;
|
||||
@@ -603,6 +603,7 @@ define([
|
||||
'newpad',
|
||||
'share',
|
||||
'limit',
|
||||
'request',
|
||||
'unpinnedWarning',
|
||||
'notifications'
|
||||
],
|
||||
|
||||
@@ -39,7 +39,8 @@ define([
|
||||
};
|
||||
|
||||
module.getContentExtension = function (mode) {
|
||||
return (Modes.extensionOf(mode) || '.txt').slice(1);
|
||||
var ext = Modes.extensionOf(mode);
|
||||
return ext !== undefined ? ext : '.txt';
|
||||
};
|
||||
module.fileExporter = function (content) {
|
||||
return new Blob([ content ], { type: 'text/plain;charset=utf-8' });
|
||||
@@ -98,9 +99,17 @@ define([
|
||||
// lines beginning with a hash are potentially valuable
|
||||
// works for markdown, python, bash, etc.
|
||||
var hash = /^#+(.*?)$/;
|
||||
var hashAndLink = /^#+\s*\[(.*?)\]\(.*\)\s*$/;
|
||||
if (hash.test(line)) {
|
||||
// test for link inside the title, and set text just to the name of the link
|
||||
if (hashAndLink.test(line)) {
|
||||
line.replace(hashAndLink, function (a, one) {
|
||||
text = Util.stripTags(one);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
line.replace(hash, function (a, one) {
|
||||
text = one;
|
||||
text = Util.stripTags(one);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -231,7 +240,7 @@ define([
|
||||
};
|
||||
var $block = exp.$language = UIElements.createDropdown(dropdownConfig);
|
||||
$block.find('button').attr('title', Messages.languageButtonTitle);
|
||||
|
||||
|
||||
var isHovering = false;
|
||||
var $aLanguages = $block.find('a');
|
||||
$aLanguages.mouseenter(function () {
|
||||
@@ -304,7 +313,7 @@ define([
|
||||
setTheme(theme, $block);
|
||||
Common.setAttribute(themeKey, theme);
|
||||
});
|
||||
|
||||
|
||||
if ($drawer) { $drawer.append($block); }
|
||||
if (cb) { cb(); }
|
||||
};
|
||||
@@ -323,7 +332,7 @@ define([
|
||||
var mode;
|
||||
if (!mime) {
|
||||
var ext = /.+\.([^.]+)$/.exec(file.name);
|
||||
if (ext[1]) {
|
||||
if (ext && ext[1]) {
|
||||
mode = CMeditor.findModeByExtension(ext[1]);
|
||||
mode = mode && mode.mode || null;
|
||||
}
|
||||
@@ -339,7 +348,8 @@ define([
|
||||
exp.setMode('text');
|
||||
$toolbarContainer.find('#language-mode').val('text');
|
||||
}
|
||||
return { content: content };
|
||||
// return the mode so that the code editor can decide how to display the new content
|
||||
return { content: content, mode: mode };
|
||||
};
|
||||
|
||||
exp.setValueAndCursor = function (oldDoc, remoteDoc) {
|
||||
@@ -385,21 +395,32 @@ define([
|
||||
exp.mkIndentSettings = function (metadataMgr) {
|
||||
var setIndentation = function (units, useTabs, fontSize, spellcheck) {
|
||||
if (typeof(units) !== 'number') { return; }
|
||||
var doc = editor.getDoc();
|
||||
editor.setOption('indentUnit', units);
|
||||
editor.setOption('tabSize', units);
|
||||
editor.setOption('indentWithTabs', useTabs);
|
||||
editor.setOption('spellcheck', spellcheck);
|
||||
if (!useTabs) {
|
||||
editor.setOption("extraKeys", {
|
||||
Tab: function() {
|
||||
editor.replaceSelection(Array(units + 1).join(" "));
|
||||
editor.setOption("extraKeys", {
|
||||
Tab: function() {
|
||||
if (doc.somethingSelected()) {
|
||||
editor.execCommand("indentMore");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
editor.setOption("extraKeys", {
|
||||
Tab: undefined,
|
||||
});
|
||||
}
|
||||
else {
|
||||
if (!useTabs) { editor.execCommand("insertSoftTab"); }
|
||||
else { editor.execCommand("insertTab"); }
|
||||
}
|
||||
},
|
||||
"Shift-Tab": function () {
|
||||
editor.execCommand("indentLess");
|
||||
},
|
||||
"Backspace": function () {
|
||||
var cursor = doc.getCursor();
|
||||
var line = doc.getLine(cursor.line);
|
||||
if (line.substring(0, cursor.ch).trim() === "") { editor.execCommand("indentLess"); }
|
||||
else { editor.execCommand("delCharBefore"); }
|
||||
|
||||
},
|
||||
});
|
||||
$('.CodeMirror').css('font-size', fontSize+'px');
|
||||
};
|
||||
|
||||
|
||||
@@ -47,30 +47,29 @@ define([
|
||||
var formatData = function (data) {
|
||||
return JSON.stringify(data.content.msg.content);
|
||||
};
|
||||
var createElement = function (data) {
|
||||
var createElement = mailbox.createElement = function (data) {
|
||||
var notif;
|
||||
var dismissIcon = h('span.fa.fa-times');
|
||||
var dismiss = h('div.cp-notification-dismiss', {
|
||||
title: Messages.notifications_dismiss
|
||||
}, dismissIcon);
|
||||
dismiss.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
mailbox.dismiss(data, function (err) {
|
||||
if (err) { return void console.error(err); }
|
||||
/*if (notif && notif.parentNode) {
|
||||
try {
|
||||
notif.parentNode.removeChild(notif);
|
||||
} catch (e) { console.error(e); }
|
||||
}*/
|
||||
});
|
||||
});
|
||||
notif = h('div.cp-notification', {
|
||||
'data-hash': data.content.hash
|
||||
}, [
|
||||
h('div.cp-notification-content', h('p', formatData(data))),
|
||||
dismiss
|
||||
]);
|
||||
}, [h('div.cp-notification-content', h('p', formatData(data)))]);
|
||||
|
||||
if (data.content.getFormatText) {
|
||||
$(notif).find('.cp-notification-content p').html(data.content.getFormatText());
|
||||
}
|
||||
|
||||
if (data.content.isClickable) {
|
||||
$(notif).find('.cp-notification-content').addClass("cp-clickable")
|
||||
.click(data.content.handler);
|
||||
}
|
||||
if (data.content.isDismissible) {
|
||||
var dismissIcon = h('span.fa.fa-times');
|
||||
var dismiss = h('div.cp-notification-dismiss', {
|
||||
title: Messages.notifications_dismiss
|
||||
}, dismissIcon);
|
||||
$(dismiss).addClass("cp-clickable")
|
||||
.click(data.content.dismissHandler);
|
||||
$(notif).append(dismiss);
|
||||
}
|
||||
return notif;
|
||||
};
|
||||
|
||||
@@ -80,7 +79,7 @@ define([
|
||||
|
||||
onViewedHandlers.push(function (data) {
|
||||
var hash = data.hash.replace(/"/g, '\\\"');
|
||||
var $notif = $('.cp-notification[data-hash="'+hash+'"]');
|
||||
var $notif = $('.cp-notification[data-hash="'+hash+'"]:not(.cp-app-notification-archived)');
|
||||
if ($notif.length) {
|
||||
$notif.remove();
|
||||
}
|
||||
@@ -90,8 +89,11 @@ define([
|
||||
var pushMessage = function (data, handler) {
|
||||
var todo = function (f) {
|
||||
try {
|
||||
var el = createElement(data);
|
||||
Notifications.add(Common, data, el);
|
||||
var el;
|
||||
if (data.type === 'notifications') {
|
||||
Notifications.add(Common, data);
|
||||
el = createElement(data);
|
||||
}
|
||||
f(data, el);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
@@ -108,7 +110,9 @@ define([
|
||||
onViewedHandlers.forEach(function (f) {
|
||||
try {
|
||||
f(data);
|
||||
Notifications.remove(Common, data);
|
||||
if (data.type === 'notifications') {
|
||||
Notifications.remove(Common, data);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
@@ -118,7 +122,7 @@ define([
|
||||
|
||||
var onMessage = function (data) {
|
||||
// data = { type: 'type', content: {msg: 'msg', hash: 'hash'} }
|
||||
console.log(data.content);
|
||||
console.log(data.type, data.content);
|
||||
pushMessage(data);
|
||||
if (!history[data.type]) { history[data.type] = []; }
|
||||
history[data.type].push(data.content);
|
||||
@@ -139,18 +143,25 @@ define([
|
||||
var subscribed = false;
|
||||
|
||||
// Get all existing notifications + the new ones when they come
|
||||
mailbox.subscribe = function (cfg) {
|
||||
mailbox.subscribe = function (types, cfg) {
|
||||
if (!subscribed) {
|
||||
execCommand('SUBSCRIBE', null, function () {});
|
||||
subscribed = true;
|
||||
}
|
||||
if (typeof(cfg.onViewed) === "function") {
|
||||
onViewedHandlers.push(cfg.onViewed);
|
||||
onViewedHandlers.push(function (data) {
|
||||
if (types.indexOf(data.type) === -1) { return; }
|
||||
cfg.onViewed(data);
|
||||
});
|
||||
}
|
||||
if (typeof(cfg.onMessage) === "function") {
|
||||
onMessageHandlers.push(cfg.onMessage);
|
||||
onMessageHandlers.push(function (data, el) {
|
||||
if (types.indexOf(data.type) === -1) { return; }
|
||||
cfg.onMessage(data, el);
|
||||
});
|
||||
}
|
||||
Object.keys(history).forEach(function (type) {
|
||||
if (types.indexOf(type) === -1) { return; }
|
||||
history[type].forEach(function (data) {
|
||||
pushMessage({
|
||||
type: type,
|
||||
@@ -160,6 +171,52 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
var historyState = false;
|
||||
var onHistory = function () {};
|
||||
mailbox.getMoreHistory = function (type, count, lastKnownHash, cb) {
|
||||
if (historyState) { return void cb("ALREADY_CALLED"); }
|
||||
historyState = true;
|
||||
var txid = Util.uid();
|
||||
execCommand('LOAD_HISTORY', {
|
||||
type: type,
|
||||
count: lastKnownHash ? count + 1 : count,
|
||||
txid: txid,
|
||||
lastKnownHash: lastKnownHash
|
||||
}, function (err, obj) {
|
||||
if (obj && obj.error) { console.error(obj.error); }
|
||||
});
|
||||
var messages = [];
|
||||
onHistory = function (data) {
|
||||
if (data.txid !== txid) { return; }
|
||||
if (data.complete) {
|
||||
onHistory = function () {};
|
||||
var end = messages.length < count;
|
||||
cb(null, messages, end);
|
||||
historyState = false;
|
||||
return;
|
||||
}
|
||||
if (data.hash !== lastKnownHash) {
|
||||
messages.push({
|
||||
type: type,
|
||||
content: {
|
||||
msg: data.message,
|
||||
time: data.time,
|
||||
hash: data.hash
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
mailbox.getNotificationsHistory = function (type, count, lastKnownHash, cb) {
|
||||
mailbox.getMoreHistory(type, count, lastKnownHash, function (err, messages, end) {
|
||||
if (!Array.isArray(messages)) { return void cb(err); }
|
||||
messages.forEach(function (data) {
|
||||
data.content.archived = true;
|
||||
Notifications.add(Common, data);
|
||||
});
|
||||
cb(err, messages, end);
|
||||
});
|
||||
};
|
||||
|
||||
// CHANNEL WITH WORKER
|
||||
|
||||
@@ -167,6 +224,9 @@ define([
|
||||
// obj = { ev: 'type', data: obj }
|
||||
var ev = obj.ev;
|
||||
var data = obj.data;
|
||||
if (ev === 'HISTORY') {
|
||||
return void onHistory(data);
|
||||
}
|
||||
if (ev === 'MESSAGE') {
|
||||
return void onMessage(data);
|
||||
}
|
||||
|
||||
@@ -223,6 +223,11 @@ define([
|
||||
sframeChan.event("EV_PAD_PASSWORD");
|
||||
};
|
||||
|
||||
if (!val && sessionStorage.newPadPassword) {
|
||||
val = sessionStorage.newPadPassword;
|
||||
delete sessionStorage.newPadPassword;
|
||||
}
|
||||
|
||||
if (val) {
|
||||
password = val;
|
||||
Cryptpad.getFileSize(window.location.href, password, waitFor(function (e, size) {
|
||||
@@ -267,23 +272,25 @@ define([
|
||||
var parsed = Utils.Hash.parsePadUrl(window.location.href);
|
||||
if (!parsed.type) { throw new Error(); }
|
||||
var defaultTitle = Utils.Hash.getDefaultName(parsed);
|
||||
var edPublic;
|
||||
var edPublic, isTemplate;
|
||||
var forceCreationScreen = cfg.useCreationScreen &&
|
||||
sessionStorage[Utils.Constants.displayPadCreationScreen];
|
||||
delete sessionStorage[Utils.Constants.displayPadCreationScreen];
|
||||
var updateMeta = function () {
|
||||
//console.log('EV_METADATA_UPDATE');
|
||||
var metaObj, isTemplate;
|
||||
var metaObj;
|
||||
nThen(function (waitFor) {
|
||||
Cryptpad.getMetadata(waitFor(function (err, m) {
|
||||
if (err) { console.log(err); }
|
||||
metaObj = m;
|
||||
edPublic = metaObj.priv.edPublic; // needed to create an owned pad
|
||||
}));
|
||||
Cryptpad.isTemplate(window.location.href, waitFor(function (err, t) {
|
||||
if (err) { console.log(err); }
|
||||
isTemplate = t;
|
||||
}));
|
||||
if (typeof(isTemplate) === "undefined") {
|
||||
Cryptpad.isTemplate(window.location.href, waitFor(function (err, t) {
|
||||
if (err) { console.log(err); }
|
||||
isTemplate = t;
|
||||
}));
|
||||
}
|
||||
}).nThen(function (/*waitFor*/) {
|
||||
metaObj.doc = {
|
||||
defaultTitle: defaultTitle,
|
||||
@@ -730,6 +737,7 @@ define([
|
||||
var initShareModal = function (cfg) {
|
||||
cfg.hashes = hashes;
|
||||
cfg.password = password;
|
||||
cfg.isTemplate = isTemplate;
|
||||
// cfg.hidden means pre-loading the filepicker while keeping it hidden.
|
||||
// if cfg.hidden is true and the iframe already exists, do nothing
|
||||
if (!ShareModal.$iframe) {
|
||||
@@ -936,6 +944,19 @@ define([
|
||||
sframeChan.event('EV_WORKER_TIMEOUT');
|
||||
});
|
||||
|
||||
sframeChan.on('EV_GIVE_ACCESS', function (data, cb) {
|
||||
Cryptpad.padRpc.giveAccess(data, cb);
|
||||
});
|
||||
sframeChan.on('Q_REQUEST_ACCESS', function (data, cb) {
|
||||
if (readOnly && hashes.editHash) {
|
||||
return void cb({error: 'ALREADYKNOWN'});
|
||||
}
|
||||
Cryptpad.padRpc.requestAccess({
|
||||
send: data,
|
||||
channel: secret.channel
|
||||
}, cb);
|
||||
});
|
||||
|
||||
if (cfg.messaging) {
|
||||
Notifier.getPermission();
|
||||
|
||||
|
||||
@@ -79,7 +79,8 @@ define([
|
||||
return {
|
||||
updateTitle: exp.updateTitle,
|
||||
suggestName: suggestTitle,
|
||||
defaultName: exp.defaultTitle
|
||||
defaultName: exp.defaultTitle,
|
||||
getTitle: exp.getTitle
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -534,8 +534,11 @@ MessengerUI, Messages) {
|
||||
hidden: true
|
||||
});
|
||||
$shareBlock.click(function () {
|
||||
var title = (config.title && config.title.getTitle && config.title.getTitle())
|
||||
|| (config.title && config.title.defaultName)
|
||||
|| "";
|
||||
Common.getSframeChannel().event('EV_SHARE_OPEN', {
|
||||
title: Common.getMetadataMgr().getMetadata().title
|
||||
title: title
|
||||
});
|
||||
});
|
||||
|
||||
@@ -559,7 +562,10 @@ MessengerUI, Messages) {
|
||||
file: true
|
||||
});
|
||||
$shareBlock.click(function () {
|
||||
var title = (config.title && config.title.getTitle && config.title.getTitle())
|
||||
|| "";
|
||||
Common.getSframeChannel().event('EV_SHARE_OPEN', {
|
||||
title: title,
|
||||
file: true
|
||||
});
|
||||
});
|
||||
@@ -568,6 +574,48 @@ MessengerUI, Messages) {
|
||||
return $shareBlock;
|
||||
};
|
||||
|
||||
var createRequest = function (toolbar, config) {
|
||||
console.error('test');
|
||||
if (!config.metadataMgr) {
|
||||
throw new Error("You must provide a `metadataMgr` to display the request access button");
|
||||
}
|
||||
|
||||
// We can only requets more access if we're in read-only mode
|
||||
if (config.readOnly !== 1) { return; }
|
||||
|
||||
var $requestBlock = $('<button>', {
|
||||
'class': 'fa fa-lock cp-toolbar-share-button',
|
||||
title: Messages.requestEdit_button
|
||||
}).hide();
|
||||
|
||||
// If we have access to the owner's mailbox, display the button and enable it
|
||||
// false => check if we can contact the owner
|
||||
// true ==> send the request
|
||||
Common.getSframeChannel().query('Q_REQUEST_ACCESS', false, function (err, obj) {
|
||||
if (obj && obj.state) {
|
||||
var locked = false;
|
||||
$requestBlock.show().click(function () {
|
||||
if (locked) { return; }
|
||||
locked = true;
|
||||
Common.getSframeChannel().query('Q_REQUEST_ACCESS', true, function (err, obj) {
|
||||
if (obj && obj.state) {
|
||||
UI.log(Messages.requestEdit_sent);
|
||||
$requestBlock.hide();
|
||||
} else {
|
||||
locked = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
toolbar.$leftside.append($requestBlock);
|
||||
toolbar.request = $requestBlock;
|
||||
|
||||
return $requestBlock;
|
||||
};
|
||||
|
||||
var createTitle = function (toolbar, config) {
|
||||
var $titleContainer = $('<span>', {
|
||||
'class': TITLE_CLS
|
||||
@@ -724,7 +772,7 @@ MessengerUI, Messages) {
|
||||
};
|
||||
|
||||
var createPageTitle = function (toolbar, config) {
|
||||
if (config.title || !config.pageTitle) { return; }
|
||||
if (!config.pageTitle) { return; }
|
||||
var $titleContainer = $('<span>', {
|
||||
'class': TITLE_CLS
|
||||
}).appendTo(toolbar.$top);
|
||||
@@ -832,11 +880,17 @@ MessengerUI, Messages) {
|
||||
return $spin;
|
||||
};
|
||||
|
||||
var createLimit = function (toolbar) {
|
||||
var createLimit = function (toolbar, config) {
|
||||
var $limitIcon = $('<span>', {'class': 'fa fa-exclamation-triangle'});
|
||||
var $limit = toolbar.$userAdmin.find('.'+LIMIT_CLS).attr({
|
||||
'title': Messages.pinLimitReached
|
||||
}).append($limitIcon).hide();
|
||||
|
||||
var priv = config.metadataMgr.getPrivateData();
|
||||
var origin = priv.origin;
|
||||
var l = document.createElement("a");
|
||||
l.href = origin;
|
||||
|
||||
var todo = function (e, overLimit) {
|
||||
if (e) { return void console.error("Unable to get the pinned usage", e); }
|
||||
if (overLimit) {
|
||||
@@ -845,7 +899,7 @@ MessengerUI, Messages) {
|
||||
key = 'pinLimitReachedAlertNoAccounts';
|
||||
}
|
||||
$limit.show().click(function () {
|
||||
UI.alert(Messages._getKey(key, [encodeURIComponent(window.location.hostname)]), null, true);
|
||||
UI.alert(Messages._getKey(key, [encodeURIComponent(l.hostname)]), null, true);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -944,10 +998,18 @@ MessengerUI, Messages) {
|
||||
|
||||
var createNotifications = function (toolbar, config) {
|
||||
var $notif = toolbar.$top.find('.'+NOTIFICATIONS_CLS).show();
|
||||
var openNotifsApp = h('div.cp-notifications-gotoapp', h('p', Messages.openNotificationsApp || "Open notifications App"));
|
||||
$(openNotifsApp).click(function () {
|
||||
Common.openURL("/notifications/");
|
||||
});
|
||||
var div = h('div.cp-notifications-container', [
|
||||
h('div.cp-notifications-empty', Messages.notifications_empty)
|
||||
]);
|
||||
var pads_options = [div];
|
||||
if (Common.isLoggedIn()) {
|
||||
pads_options.unshift(h("hr"));
|
||||
pads_options.unshift(openNotifsApp);
|
||||
}
|
||||
var dropdownConfig = {
|
||||
text: '', // Button initial text
|
||||
options: pads_options, // Entries displayed in the menu
|
||||
@@ -983,10 +1045,10 @@ MessengerUI, Messages) {
|
||||
$button.addClass('fa-bell');
|
||||
};
|
||||
|
||||
Common.mailbox.subscribe({
|
||||
Common.mailbox.subscribe(['notifications'], {
|
||||
onMessage: function (data, el) {
|
||||
if (el) {
|
||||
div.appendChild(el);
|
||||
$(div).prepend(el);
|
||||
}
|
||||
refresh();
|
||||
},
|
||||
@@ -1158,6 +1220,7 @@ MessengerUI, Messages) {
|
||||
tb['fileshare'] = createFileShare;
|
||||
tb['title'] = createTitle;
|
||||
tb['pageTitle'] = createPageTitle;
|
||||
tb['request'] = createRequest;
|
||||
tb['lag'] = $.noop;
|
||||
tb['spinner'] = createSpinner;
|
||||
tb['state'] = $.noop;
|
||||
|
||||
@@ -565,7 +565,7 @@
|
||||
"download_step1": "Laden...",
|
||||
"download_step2": "Entschlüsselung...",
|
||||
"todo_title": "CryptTodo",
|
||||
"todo_newTodoNamePlaceholder": "Beschreibe deine Aufgabe...",
|
||||
"todo_newTodoNamePlaceholder": "Beschreibe deine Aufgabe…",
|
||||
"todo_newTodoNameTitle": "Diese Aufgabe zu deiner ToDo-Liste hinzufügen",
|
||||
"todo_markAsCompleteTitle": "Diese Aufgabe als erledigt markieren",
|
||||
"todo_markAsIncompleteTitle": "Diese Aufgabe als nicht erledigt markieren",
|
||||
@@ -830,7 +830,7 @@
|
||||
"generic": {
|
||||
"more": "Erfahre mehr über die Nutzung von CryptPad, indem du unsere <a href=\"/faq.html\" target=\"_blank\">FAQ</a> liest",
|
||||
"share": "Benutze das Teilen-Menü (<span class=\"fa fa-share-alt\"></span>), um Links zu generieren, die Mitarbeiter zum Lesen oder Bearbeiten einladen",
|
||||
"save": "Alle Änderungen werden automatisch synchronisiert. Du misst sie also nicht speichern"
|
||||
"save": "Alle Änderungen werden automatisch synchronisiert. Du musst sie also nicht selbst speichern"
|
||||
},
|
||||
"text": {
|
||||
"formatting": "Du kannst die Werkzeugleiste anzeigen oder verbergen, indem du auf <span class=\"fa fa-caret-down\"></span> oder <span class=\"fa fa-caret-up\"></span> klickst",
|
||||
@@ -1054,7 +1054,7 @@
|
||||
"friendRequest_accepted": "<b>{0}</b> hat deine Freundschaftsanfrage akzeptiert",
|
||||
"friendRequest_received": "<b>{0}</b> möchte mit dir befreundet sein",
|
||||
"friendRequest_notification": "<b>{0}</b> hat dir eine Freundschaftsanfrage geschickt",
|
||||
"notifications_empty": "Du hast keine neuen Benachrichtigungen",
|
||||
"notifications_empty": "Keine Benachrichtigungen verfügbar",
|
||||
"notifications_title": "Du hast ungelesene Benachrichtigungen",
|
||||
"profile_addDescription": "Beschreibung hinzufügen",
|
||||
"profile_editDescription": "Deine Beschreibung bearbeiten",
|
||||
@@ -1074,5 +1074,54 @@
|
||||
"fm_info_sharedFolderHistory": "Dies ist nur der Verlauf deines geteilten Ordners: <b>{0}</b><br/>Dein CryptDrive bleibt beim Navigieren im Nur-Lesen-Modus.",
|
||||
"share_description": "Wähle aus, was du teilen möchtest. Dir wird dann ein entsprechender Link anzeigt. Du kannst es auch direkt an deine Freunde in CryptPad senden.",
|
||||
"fc_expandAll": "Alle ausklappen",
|
||||
"fc_collapseAll": "Alle einklappen"
|
||||
"fc_collapseAll": "Alle einklappen",
|
||||
"fc_color": "Farbe ändern",
|
||||
"supportPage": "Support",
|
||||
"admin_cat_support": "Support",
|
||||
"admin_supportInitHelp": "Dein Server ist noch nicht für die Verwendung eines Support-Postfaches konfiguriert. Wenn du ein Support-Postfach verwenden möchtest, um Nachrichten von Benutzern zu empfangen, bitte deinen Server-Administrator das Skript in \"./scripts/generate-admin-keys.js\" auszuführen, den öffentlichen Schlüssel in der Datei \"config.js\" zu speichern und dir den privaten Schlüssel zuzusenden.",
|
||||
"admin_supportInitPrivate": "Deine CryptPad-Instanz ist für die Verwendung eines Support-Postfaches konfiguriert. Allerdings verfügt dein Account nicht über den für den Zugriff benötigten privaten Schlüssel. Bitte benutze folgendes Formular, um den privaten Schlüssel zu deinem Account hinzuzufügen oder zu aktualisieren.",
|
||||
"admin_supportAddKey": "Privaten Schlüssel hinzufügen",
|
||||
"admin_supportAddError": "Privater Schlüssel ist ungültig",
|
||||
"admin_supportInitTitle": "Einrichtung des Support-Postfaches",
|
||||
"admin_supportInitHint": "Du kannst ein Support-Postfach einrichten, damit dich deine Nutzer bei Problemen auf einem sicheren Weg kontaktieren können.",
|
||||
"admin_supportListTitle": "Support-Postfach",
|
||||
"admin_supportListHint": "Hier ist die Liste der an das Support-Postfach gesendeten Tickets. Alle Administratoren können die Nachrichten und Antworten sehen. Ein geschlossenes Ticket kann nicht wieder geöffnet werden. Du kannst geschlossene Tickets nur ausblenden, sie bleiben aber für die andere Administratoren sichtbar.",
|
||||
"support_disabledTitle": "Support ist nicht aktiviert",
|
||||
"support_disabledHint": "Diese CryptPad-Instanz wurde noch nicht für die Verwendung eines Support-Formulars konfiguriert.",
|
||||
"support_cat_new": "Neues Ticket",
|
||||
"support_formTitle": "Titel des Tickets",
|
||||
"support_formHint": "Mit diesem Formular kann ein neues Support-Ticket eröffnet werden. Es erlaubt die sichere Kontaktaufnahme mit den Administratoren zur Lösung von Problemen oder Beantwortung von Fragen. Bitte eröffne kein neues Ticket, wenn du bereits ein offenes Ticket bezüglich des gleichen Problems hast. Verwende stattdessen die Antworten-Schaltfläche, um weitere Informationen hinzuzufügen.",
|
||||
"support_formButton": "Absenden",
|
||||
"support_formTitleError": "Fehler: Titel ist leer",
|
||||
"support_formContentError": "Fehler: Inhalt ist leer",
|
||||
"support_formMessage": "Gib deine Nachricht ein...",
|
||||
"support_cat_tickets": "Vorhandene Tickets",
|
||||
"support_listTitle": "Support-Tickets",
|
||||
"support_listHint": "Hier ist die Liste der an die Administratoren gesendeten Tickets und der dazugehörigen Antworten. Ein geschlossenes Ticket kann nicht wieder geöffnet werden, du musst ein Ticket eröffnen. Du kannst geschlossene Tickets ausblenden, aber sie werden weiterhin für die Administratoren sichtbar sein.",
|
||||
"support_answer": "Antworten",
|
||||
"support_close": "Ticket schließen",
|
||||
"support_remove": "Ticket entfernen",
|
||||
"support_showData": "Benutzerdaten anzeigen/verbergen",
|
||||
"support_from": "<b>Von:</b> {0}",
|
||||
"support_closed": "Dieses Ticket wurde geschlossen",
|
||||
"fc_noAction": "Keine Aktion verfügbar",
|
||||
"notificationsPage": "Benachrichtigungen",
|
||||
"openNotificationsApp": "Benachrichtigungspanel öffnen",
|
||||
"notifications_cat_all": "Alle",
|
||||
"notifications_cat_friends": "Freundschaftsanfragen",
|
||||
"notifications_cat_pads": "Mit mir geteilt",
|
||||
"notifications_cat_archived": "Verlauf",
|
||||
"notifications_dismissAll": "Alle verbergen",
|
||||
"support_notification": "",
|
||||
"requestEdit_button": "",
|
||||
"requestEdit_dialog": "",
|
||||
"requestEdit_confirm": "",
|
||||
"requestEdit_fromFriend": "",
|
||||
"requestEdit_fromStranger": "",
|
||||
"requestEdit_viewPad": "",
|
||||
"later": "",
|
||||
"requestEdit_request": "",
|
||||
"requestEdit_accepted": "",
|
||||
"requestEdit_sent": "",
|
||||
"uploadFolderButton": ""
|
||||
}
|
||||
|
||||
@@ -1056,7 +1056,7 @@
|
||||
"friendRequest_accepted": "<b>{0}</b> a accepté votre demande d'ami",
|
||||
"friendRequest_received": "<b>{0}</b> souhaite être votre ami",
|
||||
"friendRequest_notification": "<b>{0}</b> vous a envoyé une demande d'ami",
|
||||
"notifications_empty": "Vous n'avez pas de nouvelle notification",
|
||||
"notifications_empty": "Pas de nouvelle notification",
|
||||
"notifications_title": "Vous avez des notifications non lues",
|
||||
"profile_addDescription": "Ajouter une description",
|
||||
"profile_editDescription": "Modifier votre description",
|
||||
@@ -1074,5 +1074,42 @@
|
||||
"share_withFriends": "Partager",
|
||||
"notifications_dismiss": "Cacher",
|
||||
"fm_info_sharedFolderHistory": "Vous regardez l'historique de votre dossier partagé <b>{0}</b><br/>Votre CryptDrive restera en lecture seule pendant la navigation.",
|
||||
"share_description": "Choisissez ce que vous souhaitez partager puis obtenez le lien ou envoyez-le directement à vos amis CryptPad."
|
||||
"share_description": "Choisissez ce que vous souhaitez partager puis obtenez le lien ou envoyez-le directement à vos amis CryptPad.",
|
||||
"fc_color": "Changer la couleur",
|
||||
"supportPage": "Support",
|
||||
"admin_cat_support": "Support",
|
||||
"admin_supportAddKey": "Ajouter la clé",
|
||||
"admin_supportAddError": "Clé privée non valide",
|
||||
"admin_supportInitTitle": "Initialisation du support",
|
||||
"admin_supportListTitle": "Messagerie du support",
|
||||
"support_disabledTitle": "Le support n'est pas activé",
|
||||
"support_disabledHint": "Cette instance de CryptPad n'est pas encore configurée pour utiliser le formulaire de support.",
|
||||
"support_cat_new": "Nouveau ticket",
|
||||
"support_formTitle": "Titre du ticket",
|
||||
"support_formButton": "Envoyer",
|
||||
"support_formTitleError": "Erreur : le titre est vide",
|
||||
"support_formContentError": "Erreur : le contenu est vide",
|
||||
"support_formMessage": "Taper votre message...",
|
||||
"support_cat_tickets": "Tickets existants",
|
||||
"support_listTitle": "Tickets de support",
|
||||
"support_answer": "Répondre",
|
||||
"support_close": "Fermer le ticket",
|
||||
"support_remove": "Supprimer le ticket",
|
||||
"support_showData": "Afficher/cacher les données de l'utilisateur",
|
||||
"support_from": "<b>De :</b> {0}",
|
||||
"support_closed": "Ce ticket a été fermé",
|
||||
"fc_noAction": "Pas d'action disponible",
|
||||
"notificationsPage": "Notifications",
|
||||
"openNotificationsApp": "Ouvrir le panneau de notifications",
|
||||
"notifications_cat_all": "Toutes",
|
||||
"notifications_cat_friends": "Demandes d'ami",
|
||||
"notifications_cat_pads": "Partagé avec moi",
|
||||
"notifications_cat_archived": "Historique",
|
||||
"notifications_dismissAll": "Tout cacher",
|
||||
"admin_supportInitHelp": "Votre serveur n'est pas configuré pour avoir une messagerie de support. Si vous souhaitez activer cette messagerie pour recevoir des messages des utilisateurs, vous devez demander à l'administrateur du serveur d'exécuter le script situé dans \"./scripts/generate-admin-keys.js\", de stocker la clé publique générée dans \"config.js\" puis de vous envoyer la clé privée.",
|
||||
"admin_supportInitPrivate": "Votre instance de CryptPad est configurée pour utiliser la messagerie de support mais votre compte utilisateur ne connaît pas la bonne clé privée nécessaire pour y avoir accès. Veuillez utiliser le formulaire suivant pour ajouter ou mettre à jour la clé dans votre compte.",
|
||||
"admin_supportInitHint": "Vous pouvez configurer une messagerie de support afin de fournir aux utilisateurs de votre instance CryptPad un moyen de vous contacter de manière sécurisée en cas de problème avec leur compte.",
|
||||
"admin_supportListHint": "Voici la liste des tickets envoyés par les utilisateurs au support. Tous les administrateurs peuvent voir les tickets et leurs réponses. Un ticket fermé ne peut pas être ré-ouvert. Vous ne pouvez supprimer (ou cacher) que les tickets fermés, et les tickets supprimés restent visible par les autres administrateurs.",
|
||||
"support_formHint": "Ce formulaire peut être utilisé pour créer un nouveau ticket de support. Utilisez-le pour contacter les administrateurs de manière sécurisée afin de résoudre un problème ou d'obtenir des renseignements. Merci de ne pas créer de nouveau ticket si vous avez déjà un ticket ouvert concernant le même problème, vous pouvez utiliser le bouton \"Répondre\" dans ce cas.",
|
||||
"support_listHint": "Voici la liste des tickets envoyés au support, ainsi que les réponses. Un ticket fermé ne peut pas être ré-ouvert, mais il est possible d'en créer un nouveau. Vous pouvez cacher les tickets qui ont été fermés."
|
||||
}
|
||||
|
||||
@@ -1057,7 +1057,7 @@
|
||||
"friendRequest_accepted": "<b>{0}</b> accepted your friend request",
|
||||
"friendRequest_received": "<b>{0}</b> would like to be your friend",
|
||||
"friendRequest_notification": "<b>{0}</b> sent you a friend request",
|
||||
"notifications_empty": "You have no new notifications",
|
||||
"notifications_empty": "No notifications available",
|
||||
"notifications_title": "You have unread notifications",
|
||||
"profile_addDescription": "Add a description",
|
||||
"profile_editDescription": "Edit your description",
|
||||
@@ -1075,5 +1075,54 @@
|
||||
"share_withFriends": "Share",
|
||||
"notifications_dismiss": "Dismiss",
|
||||
"fm_info_sharedFolderHistory": "This is only the history of your shared folder: <b>{0}</b><br/>Your CryptDrive will stay in read-only mode while you navigate.",
|
||||
"share_description": "Choose what you'd like to share and either get the link or send it directly to your CryptPad friends."
|
||||
"share_description": "Choose what you'd like to share and either get the link or send it directly to your CryptPad friends.",
|
||||
"supportPage": "Support",
|
||||
"admin_cat_support": "Support",
|
||||
"admin_supportInitHelp": "Your server is not yet configured to have a support mailbox. If you want a support mailbox to receive messages from your users, you should ask your server administrator to run the script located in \"./scripts/generate-admin-keys.js\", then store the public key in the \"config.js\" file and send you the private key.",
|
||||
"admin_supportInitPrivate": "Your CryptPad instance is configured to use a support mailbox but your account doesn't have the correct private key to access it. Please use the following form to add or update the private key to your account.",
|
||||
"admin_supportAddKey": "Add private key",
|
||||
"admin_supportAddError": "Invalid private key",
|
||||
"admin_supportInitTitle": "Support mailbox initialization",
|
||||
"admin_supportInitHint": "You can configure a support mailbox in order to give users of your CryptPad instance a way to contact you securely if they have an issue with their account.",
|
||||
"admin_supportListTitle": "Support mailbox",
|
||||
"admin_supportListHint": "Here is the list of tickets sent by users to the support mailbox. All the administrators can see the messages and their answers. A closed ticket cannot be re-opened. You can only remove (hide) closed tickets, and the removed tickets are still visible by other administrators.",
|
||||
"support_disabledTitle": "Support is not enabled",
|
||||
"support_disabledHint": "This CryptPad instance is not yet configured to use a support form.",
|
||||
"support_cat_new": "New ticket",
|
||||
"support_formTitle": "Ticket title",
|
||||
"support_formHint": "This form can be used to create a new support ticket. Use it to contact the administrators to solve issues or ask any question in a secure way. Please don't create a new ticket if you already have an open ticket about the same issue, but use the reply button to provide more information.",
|
||||
"support_formButton": "Send",
|
||||
"support_formTitleError": "Error: title is empty",
|
||||
"support_formContentError": "Error: content is empty",
|
||||
"support_formMessage": "Type your message...",
|
||||
"support_cat_tickets": "Existing tickets",
|
||||
"support_listTitle": "Support tickets",
|
||||
"support_listHint": "Here is the list of tickets sent to the administrators and their answers. A closed ticket cannot be re-opened but you can make a new one. You can hide tickets that have been closed.",
|
||||
"support_answer": "Reply",
|
||||
"support_close": "Close the ticket",
|
||||
"support_remove": "Remove the ticket",
|
||||
"support_showData": "Show/hide user data",
|
||||
"support_from": "<b>From:</b> {0}",
|
||||
"support_closed": "This ticket has been closed",
|
||||
"fc_noAction": "No action available",
|
||||
"notificationsPage": "Notifications",
|
||||
"openNotificationsApp": "Open notifications panel",
|
||||
"notifications_cat_all": "All",
|
||||
"notifications_cat_friends": "Friend requests",
|
||||
"notifications_cat_pads": "Shared with me",
|
||||
"notifications_cat_archived": "History",
|
||||
"notifications_dismissAll": "Dismiss all",
|
||||
"support_notification": "An administrator has responded to your support ticket",
|
||||
"requestEdit_button": "Request edit rights",
|
||||
"requestEdit_dialog": "Are you sure you'd like to ask the owner of this pad for the ability to edit?",
|
||||
"requestEdit_confirm": "{1} has asked for the ability to edit the pad <b>{0}</b>. Would you like to grant them access?",
|
||||
"requestEdit_fromFriend": "You are friends with {0}",
|
||||
"requestEdit_fromStranger": "You are <b>not</b> friends with {0}",
|
||||
"requestEdit_viewPad": "Open the pad in a new tab",
|
||||
"later": "Decide later",
|
||||
"requestEdit_request": "{1} wants to edit the pad <b>{0}</b>",
|
||||
"requestEdit_accepted": "{1} granted you edit rights for the pad <b>{0}</b>",
|
||||
"requestEdit_sent": "Request sent",
|
||||
"uploadFolderButton": "Upload folder",
|
||||
"properties_unknownUser": "{0} unknown user(s)"
|
||||
}
|
||||
|
||||
@@ -389,5 +389,64 @@
|
||||
"fc_empty": "Удалить корзину",
|
||||
"fc_prop": "Свойства",
|
||||
"fc_hashtag": "Теги",
|
||||
"fc_sizeInKilobytes": "Размер в килобайтах"
|
||||
"fc_sizeInKilobytes": "Размер в килобайтах",
|
||||
"poll_title": "Приватный выбор даты",
|
||||
"fm_moveNestedSF": "Нельзя помещать одну общую папку в другую. Папка {0} не была перемещена.",
|
||||
"fc_color": "Изменить цвет",
|
||||
"fc_expandAll": "Расширить все",
|
||||
"fc_collapseAll": "Скрыть все",
|
||||
"fc_remove": "Удалить из вашего CryptDrive",
|
||||
"fo_moveUnsortedError": "Вы не можете переместить папку в список черновиков",
|
||||
"fo_existingNameError": "Это имя уже используется в данной директории. Пожалуйста выберите другое.",
|
||||
"fo_unableToRestore": "Невозможно восстановить этот файл в исходное местоположение. Вы можете попытаться переместить его в другое место.",
|
||||
"login_login": "Войти",
|
||||
"login_makeAPad": "Создать анонимный пэд",
|
||||
"login_nologin": "Просмотреть локальные пэды",
|
||||
"login_register": "Зарегистрироваться",
|
||||
"logoutButton": "Выйти",
|
||||
"settingsButton": "Настройки",
|
||||
"login_username": "Имя пользователя",
|
||||
"login_password": "Пароль",
|
||||
"login_confirm": "Подтвердите ваш пароль",
|
||||
"login_remember": "Запомнить меня",
|
||||
"login_hashing": "Ваш пароль хэшируется, это может занять некое время.",
|
||||
"login_hello": "Привет {0},",
|
||||
"login_helloNoName": "Привет,",
|
||||
"fm_info_sharedFolder": "Это общая папка. Вы не вошли в систему, поэтому можете получить к ней доступ только в режиме только для чтения.<br><a href=\"/register/\">Sign up</a> или <a href=\"/login/\">Log in</a> для импорта на CryptDrive и его изменения.",
|
||||
"fo_moveFolderToChildError": "Вы не можете переместить папку в одну из нее следующую",
|
||||
"fo_unavailableName": "Файл или папка с таким же именем уже существуют в новом месте. Переименуйте элемент и повторите попытку.",
|
||||
"fs_migration": "Ваш CryptDrive обновляется до новой версии. В результате, текущая страница должна быть перезагружена.<br><strong> перезагрузите эту страницу, чтобы продолжить ей пользоваться.</strong>.",
|
||||
"login_accessDrive": "Доступ к хранилищу",
|
||||
"login_orNoLogin": "или",
|
||||
"login_noSuchUser": "Неверный логин или пароль. Попробуйте еще раз или зарегистрируйтесь",
|
||||
"login_invalUser": "Неоьходимо имя пользователя",
|
||||
"login_invalPass": "Необходим пароль",
|
||||
"login_unhandledError": "Произошла неожиданная ошибка :(",
|
||||
"register_importRecent": "Импортировать пэды из вашей анонимной сессии",
|
||||
"register_passwordsDontMatch": "Пароли не совпадают!",
|
||||
"register_passwordTooShort": "Длина пароля должна составлять не менее {0} символов.",
|
||||
"register_mustAcceptTerms": "Вы должны принять условия пользования.",
|
||||
"register_mustRememberPass": "Мы не сможем сбросить ваш пароль, если вы его забудете. Очень важно, чтобы вы его запомнили! Пожалуйста, отметьте флажок для подтверждения.",
|
||||
"register_whyRegister": "Почему стоит зарегистрироваться?",
|
||||
"register_header": "Добро пожаловать в CryptPad",
|
||||
"register_writtenPassword": "Я записал свое имя пользователя и пароль, продолжить",
|
||||
"register_cancel": "Назад",
|
||||
"register_alreadyRegistered": "Этот пользователь уже существует, вы хотите войти?",
|
||||
"settings_cat_account": "Учетная запись",
|
||||
"settings_cat_drive": "КриптДрайв",
|
||||
"settings_cat_cursor": "курсор",
|
||||
"settings_cat_code": "Код",
|
||||
"settings_cat_pad": "Текст с форматированием",
|
||||
"settings_cat_creation": "новый пэд",
|
||||
"settings_cat_subscription": "Подписка",
|
||||
"settings_title": "Настройки",
|
||||
"settings_save": "Сохранить",
|
||||
"settings_backupHint": "Резервное копирование или восстановление всего содержимого CryptDrive. Он не будет содержать содержимое ваших пэдов, только ключи для доступа к ним.",
|
||||
"settings_restore": "Восстановить",
|
||||
"settings_exportDescription": "Пожалуйста, подождите, пока мы загружаем и расшифровываем ваши документы. Это может занять несколько минут. Закрытие вкладки прервет процесс.",
|
||||
"settings_exportFailed": "Если загрузка пэда занимает более 1 минуты, он не будет включен в экспорт. Отображается ссылка на любой блокнот, который не был экспортирован.",
|
||||
"settings_exportWarning": "Примечание: этот инструмент все еще находится в бета-версии и может иметь проблемы со масштабируемостью. Для повышения производительности рекомендуется оставить данную вкладку сфокусированной.",
|
||||
"settings_exportCancel": "Вы уверены, что хотите отменить экспорт? В следующий раз вам придется начинать все сначала.",
|
||||
"settings_export_reading": "Читаем ваше хранилище...",
|
||||
"settings_export_download": "Скачиваем и расшифровываем ваши документы..."
|
||||
}
|
||||
|
||||
@@ -311,12 +311,12 @@ define([
|
||||
_getFiles[FILES_DATA] = function () {
|
||||
var ret = [];
|
||||
if (!files[FILES_DATA]) { return ret; }
|
||||
return Object.keys(files[FILES_DATA]).map(Number);
|
||||
return Object.keys(files[FILES_DATA]).map(Number).filter(Boolean);
|
||||
};
|
||||
_getFiles[SHARED_FOLDERS] = function () {
|
||||
var ret = [];
|
||||
if (!files[SHARED_FOLDERS]) { return ret; }
|
||||
return Object.keys(files[SHARED_FOLDERS]).map(Number);
|
||||
return Object.keys(files[SHARED_FOLDERS]).map(Number).filter(Boolean);
|
||||
};
|
||||
var getFiles = exp.getFiles = function (categories) {
|
||||
var ret = [];
|
||||
@@ -519,26 +519,17 @@ define([
|
||||
var resFolders = [];
|
||||
var findFoldersRec = function (folder, path) {
|
||||
for (var key in folder) {
|
||||
if (isFolder(folder[key])) {
|
||||
if (isSharedFolder(folder[key])) {
|
||||
// var name = getSharedFolderData(folder[key]).title || "";
|
||||
// if (name.toLowerCase().indexOf(lValue) !== -1) {
|
||||
// resFolders.push(path.concat([key, ROOT]));
|
||||
// }
|
||||
findFoldersRec(folder[key], path.concat([key, ROOT]));
|
||||
}
|
||||
else {
|
||||
if (key.toLowerCase().indexOf(lValue) !== -1) {
|
||||
resFolders.push({
|
||||
id: null,
|
||||
paths: [path.concat(key)],
|
||||
data: {
|
||||
title: key
|
||||
}
|
||||
});
|
||||
}
|
||||
findFoldersRec(folder[key], path.concat(key));
|
||||
if (isFolder(folder[key]) && !isSharedFolder(folder[key])) {
|
||||
if (key.toLowerCase().indexOf(lValue) !== -1) {
|
||||
resFolders.push({
|
||||
id: null,
|
||||
paths: [path.concat(key)],
|
||||
data: {
|
||||
title: key
|
||||
}
|
||||
});
|
||||
}
|
||||
findFoldersRec(folder[key], path.concat(key));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
@drive_content-bg-ro: darken(@drive_content-bg, 10%);
|
||||
|
||||
@drive_selected-bg: #888;
|
||||
@drive_droppable-bg: #FE9A2E;
|
||||
|
||||
|
||||
/* PAGE */
|
||||
@@ -107,7 +108,7 @@
|
||||
|
||||
.cp-app-drive-container {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
@@ -121,6 +122,7 @@
|
||||
#cp-app-drive-tree {
|
||||
resize: none;
|
||||
width: 100% !important;
|
||||
min-width: unset;
|
||||
max-width: unset;
|
||||
max-height: unset;
|
||||
border-bottom: 1px solid @drive_mobile-tree-border-col;
|
||||
@@ -156,7 +158,7 @@
|
||||
}
|
||||
|
||||
.cp-app-drive-element-droppable {
|
||||
background-color: #FE9A2E;
|
||||
background-color: @drive_droppable-bg;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
@@ -239,7 +241,6 @@
|
||||
max-height: 100%;
|
||||
.cp-app-drive-tree-categories-container {
|
||||
flex: 1;
|
||||
max-width: 500px;
|
||||
overflow: auto;
|
||||
}
|
||||
img.cp-app-drive-icon {
|
||||
@@ -438,13 +439,13 @@
|
||||
flex: 1;
|
||||
// Needed to avoid the folder's path to overflows
|
||||
// https://stackoverflow.com/questions/38223879/white-space-nowrap-breaks-flexbox-layout
|
||||
min-width: 0;
|
||||
// min-width: 0;
|
||||
}
|
||||
#cp-app-drive-content {
|
||||
box-sizing: border-box;
|
||||
background: @drive_content-bg;
|
||||
color: @drive_content-fg;
|
||||
overflow: auto;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
@@ -939,6 +940,7 @@
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
transition: all 0.15s;
|
||||
cursor: pointer;
|
||||
|
||||
&:first-child {
|
||||
flex-shrink: 1;
|
||||
@@ -946,17 +948,20 @@
|
||||
|
||||
&.cp-app-drive-path-separator {
|
||||
color: #ccc;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&.cp-app-drive-path-collapse {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&.cp-app-drive-element-droppable {
|
||||
background-color: @drive_droppable-bg;
|
||||
}
|
||||
&:not(.cp-app-drive-element-droppable):hover {
|
||||
&:not(.cp-app-drive-path-separator) {
|
||||
background-color: darken(@colortheme_drive-bg, 15%);
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
& ~ .cp-app-drive-path-element {
|
||||
background-color: darken(@colortheme_drive-bg, 15%);
|
||||
|
||||
@@ -17,8 +17,6 @@ define([
|
||||
'/bower_components/chainpad-listmap/chainpad-listmap.js',
|
||||
'/customize/messages.js',
|
||||
|
||||
'/common/jscolor.js',
|
||||
|
||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'less!/drive/app-drive.less',
|
||||
@@ -43,7 +41,10 @@ define([
|
||||
{
|
||||
var APP = window.APP = {
|
||||
editable: false,
|
||||
mobile: function () { return $('body').width() <= 600; }, // Menu and content area are not inline-block anymore for mobiles
|
||||
mobile: function () {
|
||||
if (window.matchMedia) { return !window.matchMedia('(any-pointer:fine)').matches; }
|
||||
else { return $('body').width() <= 600; }
|
||||
},
|
||||
isMac: navigator.platform === "MacIntel",
|
||||
};
|
||||
|
||||
@@ -77,6 +78,8 @@ define([
|
||||
var faFolderOpen = 'cptools-folder-open';
|
||||
var faSharedFolder = 'cptools-shared-folder';
|
||||
var faSharedFolderOpen = 'cptools-shared-folder-open';
|
||||
var faExpandAll = 'fa-plus-square-o';
|
||||
var faCollapseAll = 'fa-minus-square-o';
|
||||
var faShared = 'fa-shhare-alt';
|
||||
var faReadOnly = 'fa-eye';
|
||||
var faRename = 'fa-pencil';
|
||||
@@ -297,6 +300,33 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
APP.selectedFiles = [];
|
||||
|
||||
var isElementSelected = function ($element) {
|
||||
var elementId = $element.data("path").slice(-1)[0];
|
||||
return APP.selectedFiles.indexOf(elementId) !== -1;
|
||||
};
|
||||
var selectElement = function ($element) {
|
||||
var elementId = $element.data("path").slice(-1)[0];
|
||||
if (APP.selectedFiles.indexOf(elementId) === -1) {
|
||||
APP.selectedFiles.push(elementId);
|
||||
}
|
||||
$element.addClass("cp-app-drive-element-selected");
|
||||
};
|
||||
var unselectElement = function ($element) {
|
||||
var elementId = $element.data("path").slice(-1)[0];
|
||||
var index = APP.selectedFiles.indexOf(elementId);
|
||||
if (index !== -1) {
|
||||
APP.selectedFiles.splice(index, 1);
|
||||
}
|
||||
$element.removeClass("cp-app-drive-element-selected");
|
||||
};
|
||||
var findSelectedElements = function () {
|
||||
return $(".cp-app-drive-element-selected");
|
||||
};
|
||||
|
||||
|
||||
var createContextMenu = function () {
|
||||
var menu = h('div.cp-contextmenu.dropdown.cp-unselectable', [
|
||||
h('ul.dropdown-menu', {
|
||||
@@ -316,11 +346,11 @@ define([
|
||||
$separator.clone()[0],
|
||||
h('li', h('a.cp-app-drive-context-expandall.dropdown-item', {
|
||||
'tabindex': '-1',
|
||||
'data-icon': "expandAll",
|
||||
'data-icon': faExpandAll,
|
||||
}, Messages.fc_expandAll)),
|
||||
h('li', h('a.cp-app-drive-context-collapseall.dropdown-item', {
|
||||
'tabindex': '-1',
|
||||
'data-icon': "collapseAll",
|
||||
'data-icon': faCollapseAll,
|
||||
}, Messages.fc_collapseAll)),
|
||||
$separator.clone()[0],
|
||||
h('li', h('a.cp-app-drive-context-color.dropdown-item.cp-app-drive-context-editable', {
|
||||
@@ -543,13 +573,14 @@ define([
|
||||
// Tags used: display Tags category
|
||||
if (Object.keys(manager.getTagsList()).length) { displayedCategories.push(TAGS); }
|
||||
|
||||
var virtualCategories = [SEARCH, RECENT, OWNED, TAGS, SHARED_FOLDER];
|
||||
var virtualCategories = [SEARCH, RECENT, OWNED, TAGS];
|
||||
|
||||
if (!APP.loggedIn) {
|
||||
$tree.hide();
|
||||
if (APP.newSharedFolder) {
|
||||
// ANON_SHARED_FOLDER
|
||||
displayedCategories = [SHARED_FOLDER];
|
||||
virtualCategories.push(SHARED_FOLDER);
|
||||
currentPath = [SHARED_FOLDER, ROOT];
|
||||
} else {
|
||||
displayedCategories = [FILES_DATA];
|
||||
@@ -595,7 +626,8 @@ define([
|
||||
var sel = {};
|
||||
|
||||
var removeSelected = function (keepObj) {
|
||||
$('.cp-app-drive-element-selected').removeClass("cp-app-drive-element-selected");
|
||||
APP.selectedFiles = [];
|
||||
findSelectedElements().removeClass("cp-app-drive-element-selected");
|
||||
var $container = $driveToolbar.find('#cp-app-drive-toolbar-contextbuttons');
|
||||
if (!$container.length) { return; }
|
||||
$container.html('');
|
||||
@@ -705,7 +737,9 @@ define([
|
||||
delete sel.move;
|
||||
$content.find('.cp-app-drive-element-selected-tmp')
|
||||
.removeClass('cp-app-drive-element-selected-tmp')
|
||||
.addClass('cp-app-drive-element-selected');
|
||||
.each(function (idx, element) {
|
||||
selectElement($(element));
|
||||
});
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
@@ -740,7 +774,9 @@ define([
|
||||
// Ctrl+A select all
|
||||
if (e.which === 65 && (e.ctrlKey || (e.metaKey && APP.isMac))) {
|
||||
$content.find('.cp-app-drive-element:not(.cp-app-drive-element-selected)')
|
||||
.addClass('cp-app-drive-element-selected');
|
||||
.each(function (idx, element) {
|
||||
selectElement($(element));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -753,7 +789,7 @@ define([
|
||||
APP.onElementClick(ev, $(el));
|
||||
};
|
||||
|
||||
var $selection = $content.find('.cp-app-drive-element.cp-app-drive-element-selected');
|
||||
var $selection = findSelectedElements();
|
||||
if ($selection.length === 0) { return void click($elements.first()[0]); }
|
||||
|
||||
var lastIndex = typeof sel.endSelected === "number" ? sel.endSelected :
|
||||
@@ -872,12 +908,12 @@ define([
|
||||
return;
|
||||
}
|
||||
removeInput();
|
||||
removeSelected();
|
||||
var $name = $element.find('.cp-app-drive-element-name');
|
||||
if (!$name.length) {
|
||||
$name = $element.find('> .cp-app-drive-element');
|
||||
}
|
||||
$name.hide();
|
||||
var isFolder = $element.is(".cp-app-drive-element-folder:not(.cp-app-drive-element-sharedf)");
|
||||
var el = manager.find(path);
|
||||
var name = manager.isFile(el) ? manager.getTitle(el) : path[path.length - 1];
|
||||
if (manager.isSharedFolder(el)) {
|
||||
@@ -899,14 +935,21 @@ define([
|
||||
var newName = $input.val();
|
||||
if (JSON.stringify(path) === JSON.stringify(currentPath)) {
|
||||
manager.rename(path, $input.val(), function () {
|
||||
renameFoldersOpened(path, newName);
|
||||
path[path.length - 1] = newName;
|
||||
if (isFolder) {
|
||||
renameFoldersOpened(path, newName);
|
||||
path[path.length - 1] = newName;
|
||||
}
|
||||
APP.displayDirectory(path);
|
||||
});
|
||||
}
|
||||
else {
|
||||
manager.rename(path, $input.val(), function () {
|
||||
renameFoldersOpened(path, newName);
|
||||
if (isFolder) {
|
||||
renameFoldersOpened(path, newName);
|
||||
unselectElement($element);
|
||||
$element.data("path", $element.data("path").slice(0, -1).concat(newName));
|
||||
selectElement($element);
|
||||
}
|
||||
refresh();
|
||||
});
|
||||
}
|
||||
@@ -927,7 +970,6 @@ define([
|
||||
|
||||
// We don't want to open the file/folder when clicking on the input
|
||||
$input.on('click dblclick', function (e) {
|
||||
removeSelected();
|
||||
e.stopPropagation();
|
||||
});
|
||||
// Remove the browser ability to drag text from the input to avoid
|
||||
@@ -1147,8 +1189,9 @@ define([
|
||||
|
||||
var getSelectedPaths = function ($element) {
|
||||
var paths = [];
|
||||
if ($('.cp-app-drive-element-selected').length > 1) {
|
||||
var $selected = $('.cp-app-drive-element-selected');
|
||||
if (!$element || $element.length === 0) { return paths; }
|
||||
if (findSelectedElements().length > 1) {
|
||||
var $selected = findSelectedElements();
|
||||
$selected.each(function (idx, elmt) {
|
||||
var ePath = $(elmt).data('path');
|
||||
if (ePath) {
|
||||
@@ -1177,7 +1220,7 @@ define([
|
||||
} else {
|
||||
$driveToolbar.find('cp-app-drive-toolbar-emptytrash').hide();
|
||||
}
|
||||
var $li = $content.find('.cp-app-drive-element-selected');
|
||||
var $li = findSelectedElements();
|
||||
if ($li.length === 0) {
|
||||
$li = findDataHolder($tree.find('.cp-app-drive-element-active'));
|
||||
}
|
||||
@@ -1244,6 +1287,7 @@ define([
|
||||
if (pos+eh <= h && pos >= 0) { return; }
|
||||
$content.scrollTop(v);
|
||||
};
|
||||
|
||||
// Add the "selected" class to the "li" corresponding to the clicked element
|
||||
var onElementClick = APP.onElementClick = function (e, $element) {
|
||||
// If "Ctrl" is pressed, do not remove the current selection
|
||||
@@ -1280,23 +1324,23 @@ define([
|
||||
var $el;
|
||||
removeSelected(true);
|
||||
sel.oldSelection.forEach(function (el) {
|
||||
if (!$(el).hasClass("cp-app-drive-element-selected")) {
|
||||
$(el).addClass("cp-app-drive-element-selected");
|
||||
if (!isElementSelected($(el))) {
|
||||
selectElement($(el));
|
||||
}
|
||||
});
|
||||
for (var i = Math.min(sel.startSelected, sel.endSelected);
|
||||
i <= Math.max(sel.startSelected, sel.endSelected);
|
||||
i++) {
|
||||
$el = $($elements.get(i));
|
||||
if (!$el.hasClass("cp-app-drive-element-selected")) {
|
||||
$el.addClass("cp-app-drive-element-selected");
|
||||
if (!isElementSelected($el)) {
|
||||
selectElement($el);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!$element.hasClass("cp-app-drive-element-selected")) {
|
||||
$element.addClass("cp-app-drive-element-selected");
|
||||
if (!isElementSelected($element)) {
|
||||
selectElement($element);
|
||||
} else {
|
||||
$element.removeClass("cp-app-drive-element-selected");
|
||||
unselectElement($element);
|
||||
}
|
||||
}
|
||||
updateContextButton();
|
||||
@@ -1340,7 +1384,14 @@ define([
|
||||
});
|
||||
// show contextmenu at cursor position
|
||||
$menu.css({ display: "block" });
|
||||
if (APP.mobile()) { return; }
|
||||
if (APP.mobile()) {
|
||||
$menu.css({
|
||||
top: ($("#cp-app-drive-toolbar-context-mobile").offset().top + 32) + 'px',
|
||||
right: '0px',
|
||||
left: ''
|
||||
});
|
||||
return;
|
||||
}
|
||||
var h = $menu.outerHeight();
|
||||
var w = $menu.outerWidth();
|
||||
var wH = window.innerHeight;
|
||||
@@ -1393,6 +1444,17 @@ define([
|
||||
} else {
|
||||
var $element = findDataHolder($(e.target));
|
||||
|
||||
// if clicked from tree
|
||||
var fromTree = $element.closest("#cp-app-drive-tree").length;
|
||||
if (fromTree) {
|
||||
removeSelected();
|
||||
}
|
||||
|
||||
// if clicked on non selected element
|
||||
if (!isElementSelected($element)) {
|
||||
removeSelected();
|
||||
}
|
||||
|
||||
if (type === 'trash' && !$element.data('path')) { return; }
|
||||
|
||||
if (!$element.length) {
|
||||
@@ -1401,8 +1463,8 @@ define([
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$element.hasClass('cp-app-drive-element-selected')) {
|
||||
onElementClick(undefined, $element);
|
||||
if (!isElementSelected($element)) {
|
||||
selectElement($element);
|
||||
}
|
||||
|
||||
paths = getSelectedPaths($element);
|
||||
@@ -1472,6 +1534,7 @@ define([
|
||||
if (!res) { return; }
|
||||
manager.delete(pathsList, function () {
|
||||
pathsList.forEach(removeFoldersOpened);
|
||||
removeSelected();
|
||||
refresh();
|
||||
});
|
||||
}, null, true);
|
||||
@@ -1482,7 +1545,7 @@ define([
|
||||
var paths = [];
|
||||
var $element = findDataHolder($(ev.target));
|
||||
if ($element.hasClass('cp-app-drive-element-selected')) {
|
||||
var $selected = $('.cp-app-drive-element-selected');
|
||||
var $selected = findSelectedElements();
|
||||
$selected.each(function (idx, elmt) {
|
||||
var ePath = $(elmt).data('path');
|
||||
if (ePath) {
|
||||
@@ -1499,7 +1562,7 @@ define([
|
||||
});
|
||||
} else {
|
||||
removeSelected();
|
||||
$element.addClass('cp-app-drive-element-selected');
|
||||
selectElement($element);
|
||||
var val = manager.find(path);
|
||||
if (!val) { return; } // The element is not in the object
|
||||
paths = [{
|
||||
@@ -1518,7 +1581,13 @@ define([
|
||||
|
||||
var findDropPath = function (target) {
|
||||
var $target = $(target);
|
||||
var $el = findDataHolder($target);
|
||||
var $el;
|
||||
if ($target.is(".cp-app-drive-path-element")) {
|
||||
$el = $target;
|
||||
}
|
||||
else {
|
||||
$el = findDataHolder($target);
|
||||
}
|
||||
var newPath = $el.data('path');
|
||||
var dropEl = newPath && manager.find(newPath);
|
||||
if (newPath && manager.isSharedFolder(dropEl)) {
|
||||
@@ -1646,7 +1715,8 @@ define([
|
||||
$owner.attr('title', Messages.fm_padIsOwnedOther);
|
||||
}
|
||||
};
|
||||
var addFileData = function (element, $span) {
|
||||
var thumbsUrls = {};
|
||||
var addFileData = function (element, $element) {
|
||||
if (!manager.isFile(element)) { return; }
|
||||
|
||||
var data = manager.getFileData(element);
|
||||
@@ -1655,7 +1725,7 @@ define([
|
||||
|
||||
var hrefData = Hash.parsePadUrl(href);
|
||||
if (hrefData.type) {
|
||||
$span.addClass('cp-border-color-'+hrefData.type);
|
||||
$element.addClass('cp-border-color-'+hrefData.type);
|
||||
}
|
||||
|
||||
var $state = $('<span>', {'class': 'cp-app-drive-element-state'});
|
||||
@@ -1675,25 +1745,38 @@ define([
|
||||
var $expire = $expirableIcon.clone().appendTo($state);
|
||||
$expire.attr('title', Messages._getKey('fm_expirablePad', [new Date(data.expire).toLocaleString()]));
|
||||
}
|
||||
_addOwnership($span, $state, data);
|
||||
_addOwnership($element, $state, data);
|
||||
|
||||
var name = manager.getTitle(element);
|
||||
|
||||
// The element with the class '.name' is underlined when the 'li' is hovered
|
||||
var $name = $('<span>', {'class': 'cp-app-drive-element-name'}).text(name);
|
||||
$span.append($name);
|
||||
$span.append($state);
|
||||
$span.attr('title', name);
|
||||
$element.append($name);
|
||||
$element.append($state);
|
||||
$element.attr('title', name);
|
||||
|
||||
// display the thumbnail
|
||||
// if the thumbnail has already been displayed once, do not reload it, keep the same url
|
||||
if (thumbsUrls[element]) {
|
||||
var img = new Image();
|
||||
img.src = thumbsUrls[element];
|
||||
$element.find('.cp-icon').addClass('cp-app-drive-element-list');
|
||||
$element.prepend(img);
|
||||
$(img).addClass('cp-app-drive-element-grid cp-app-drive-element-thumbnail');
|
||||
$(img).attr("draggable", false);
|
||||
}
|
||||
else {
|
||||
common.displayThumbnail(href || data.roHref, data.channel, data.password, $element, function ($thumb) {
|
||||
// Called only if the thumbnail exists
|
||||
// Remove the .hide() added by displayThumnail() because it hides the icon in list mode too
|
||||
$element.find('.cp-icon').removeAttr('style').addClass('cp-app-drive-element-list');
|
||||
$thumb.addClass('cp-app-drive-element-grid cp-app-drive-element-thumbnail');
|
||||
$thumb.attr("draggable", false);
|
||||
thumbsUrls[element] = $thumb[0].src;
|
||||
});
|
||||
}
|
||||
|
||||
var type = Messages.type[hrefData.type] || hrefData.type;
|
||||
common.displayThumbnail(href || data.roHref, data.channel, data.password, $span, function ($thumb) {
|
||||
// Called only if the thumbnail exists
|
||||
// Remove the .hide() added by displayThumnail() because it hides the icon in
|
||||
// list mode too
|
||||
$span.find('.cp-icon').removeAttr('style').addClass('cp-app-drive-element-list');
|
||||
$thumb.addClass('cp-app-drive-element-grid')
|
||||
.addClass('cp-app-drive-element-thumbnail');
|
||||
});
|
||||
var $type = $('<span>', {
|
||||
'class': 'cp-app-drive-element-type cp-app-drive-element-list'
|
||||
}).text(type);
|
||||
@@ -1703,7 +1786,7 @@ define([
|
||||
var $cdate = $('<span>', {
|
||||
'class': 'cp-app-drive-element-ctime cp-app-drive-element-list'
|
||||
}).text(getDate(data.ctime));
|
||||
$span.append($type).append($adate).append($cdate);
|
||||
$element.append($type).append($adate).append($cdate);
|
||||
};
|
||||
|
||||
var addFolderData = function (element, key, $span) {
|
||||
@@ -1779,12 +1862,9 @@ define([
|
||||
draggable: true,
|
||||
'class': 'cp-app-drive-element-row'
|
||||
});
|
||||
if (!isFolder && Array.isArray(APP.selectedFiles)) {
|
||||
var idx = APP.selectedFiles.indexOf(element);
|
||||
if (idx !== -1) {
|
||||
$element.addClass('cp-app-drive-element-selected');
|
||||
APP.selectedFiles.splice(idx, 1);
|
||||
}
|
||||
$element.data('path', newPath);
|
||||
if (isElementSelected($element)) {
|
||||
selectElement($element);
|
||||
}
|
||||
$element.prepend($icon).dblclick(function () {
|
||||
if (isFolder) {
|
||||
@@ -1800,11 +1880,10 @@ define([
|
||||
addFileData(element, $element);
|
||||
}
|
||||
$element.addClass(liClass);
|
||||
$element.data('path', newPath);
|
||||
addDragAndDropHandlers($element, newPath, isFolder, !isTrash);
|
||||
$element.click(function(e) {
|
||||
e.stopPropagation();
|
||||
onElementClick(e, $element, newPath);
|
||||
onElementClick(e, $element);
|
||||
});
|
||||
if (!isTrash) {
|
||||
$element.contextmenu(openContextMenu('tree'));
|
||||
@@ -1955,6 +2034,8 @@ define([
|
||||
} else if (idx > 0 && manager.isFile(el)) {
|
||||
name = getElementName(path);
|
||||
}
|
||||
$span.data("path", path.slice(0, idx + 1));
|
||||
addDragAndDropHandlers($span, path.slice(0, idx), true, true);
|
||||
|
||||
if (idx === 0) { name = p === SHARED_FOLDER ? name : getPrettyName(p); }
|
||||
else {
|
||||
@@ -2265,15 +2346,22 @@ define([
|
||||
var data = manager.getSharedFolderData(id);
|
||||
var parsed = Hash.parsePadUrl(data.href);
|
||||
if (!parsed || !parsed.hash) { return void console.error("Invalid href: "+data.href); }
|
||||
var friends = common.getFriends();
|
||||
var modal = UIElements.createSFShareModal({
|
||||
origin: APP.origin,
|
||||
pathname: "/drive/",
|
||||
friends: friends,
|
||||
title: data.title,
|
||||
password: data.password,
|
||||
common: common,
|
||||
hashes: {
|
||||
editHash: parsed.hash
|
||||
}
|
||||
});
|
||||
$shareBlock.click(function () {
|
||||
UI.openCustomModal(modal);
|
||||
UI.openCustomModal(modal, {
|
||||
wide: Object.keys(friends).length !== 0
|
||||
});
|
||||
});
|
||||
$container.append($shareBlock);
|
||||
};
|
||||
@@ -2592,22 +2680,19 @@ define([
|
||||
'class': 'cp-app-drive-element cp-app-drive-element-file cp-app-drive-element-row' + roClass,
|
||||
draggable: draggable
|
||||
});
|
||||
if (Array.isArray(APP.selectedFiles)) {
|
||||
var sidx = APP.selectedFiles.indexOf(id);
|
||||
if (sidx !== -1) {
|
||||
$element.addClass('cp-app-drive-element-selected');
|
||||
APP.selectedFiles.splice(sidx, 1);
|
||||
}
|
||||
|
||||
var path = [rootName, idx];
|
||||
$element.data('path', path);
|
||||
if (isElementSelected($element)) {
|
||||
selectElement($element);
|
||||
}
|
||||
$element.prepend($icon).dblclick(function () {
|
||||
openFile(id);
|
||||
});
|
||||
addFileData(id, $element);
|
||||
var path = [rootName, idx];
|
||||
$element.data('path', path);
|
||||
$element.click(function(e) {
|
||||
e.stopPropagation();
|
||||
onElementClick(e, $element, path);
|
||||
onElementClick(e, $element);
|
||||
});
|
||||
$element.contextmenu(openContextMenu('default'));
|
||||
$element.data('context', 'default');
|
||||
@@ -2734,8 +2819,8 @@ define([
|
||||
e.preventDefault();
|
||||
if (manager.isInTrashRoot(parentPath)) { parentPath = [TRASH]; }
|
||||
else { parentPath.pop(); }
|
||||
APP.selectedFiles = [r.id];
|
||||
APP.displayDirectory(parentPath);
|
||||
APP.selectedFiles = path.slice(-1);
|
||||
}).appendTo($openDir);
|
||||
}
|
||||
$('<a>').text(Messages.fc_prop).click(function () {
|
||||
@@ -2748,7 +2833,7 @@ define([
|
||||
else {
|
||||
$icon.append($folderIcon.clone());
|
||||
$type.text(Messages.fm_folder);
|
||||
$('<a>').text(Messages.fm_OpenFolder || "Open folder").click(function (e) {
|
||||
$('<a>').text(Messages.fc_open).click(function (e) {
|
||||
e.preventDefault();
|
||||
APP.displayDirectory(path);
|
||||
}).appendTo($openDir);
|
||||
@@ -2826,7 +2911,7 @@ define([
|
||||
$element.data('path', path);
|
||||
$element.click(function(e) {
|
||||
e.stopPropagation();
|
||||
onElementClick(e, $element, path);
|
||||
onElementClick(e, $element);
|
||||
});
|
||||
$element.contextmenu(openContextMenu('default'));
|
||||
$element.data('context', 'default');
|
||||
@@ -3050,7 +3135,7 @@ define([
|
||||
$context.click(function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
var $li = $content.find('.cp-app-drive-element-selected');
|
||||
var $li = findSelectedElements();
|
||||
if ($li.length !== 1) {
|
||||
$li = findDataHolder($tree.find('.cp-app-drive-element-active'));
|
||||
}
|
||||
@@ -3060,11 +3145,6 @@ define([
|
||||
return;
|
||||
}
|
||||
// Open the menu
|
||||
$('.cp-contextmenu').css({
|
||||
top: ($context.offset().top + 32) + 'px',
|
||||
right: '0px',
|
||||
left: ''
|
||||
});
|
||||
$li.contextmenu();
|
||||
});
|
||||
} else {
|
||||
@@ -3132,7 +3212,7 @@ define([
|
||||
}
|
||||
});*/
|
||||
|
||||
var $sel = $content.find('.cp-app-drive-element-selected');
|
||||
var $sel = findSelectedElements();
|
||||
if ($sel.length) {
|
||||
$sel[0].scrollIntoView();
|
||||
} else {
|
||||
@@ -3144,6 +3224,9 @@ define([
|
||||
if (history.isHistoryMode) {
|
||||
return void _displayDirectory(path, force);
|
||||
}
|
||||
if (!manager.comparePath(currentPath, path)) {
|
||||
removeSelected();
|
||||
}
|
||||
updateObject(sframeChan, proxy, function () {
|
||||
copyObjectValue(files, proxy.drive);
|
||||
updateSharedFolders(sframeChan, manager, files, folders, function () {
|
||||
@@ -3470,7 +3553,9 @@ define([
|
||||
//data.noPassword = true;
|
||||
data.noEditPassword = true;
|
||||
data.noExpiration = true;
|
||||
data.sharedFolder = true; // XXX debug
|
||||
// this is here to allow users to check the channel id of a shared folder
|
||||
// we should remove it at some point
|
||||
data.sharedFolder = true;
|
||||
}
|
||||
|
||||
UIElements.getProperties(common, data, cb);
|
||||
@@ -3492,6 +3577,7 @@ define([
|
||||
if (!res) { return; }
|
||||
manager.delete(pathsList, function () {
|
||||
pathsList.forEach(removeFoldersOpened);
|
||||
removeSelected();
|
||||
refresh();
|
||||
});
|
||||
});
|
||||
@@ -3608,6 +3694,7 @@ define([
|
||||
friends: friends,
|
||||
title: data.title,
|
||||
common: common,
|
||||
password: data.password,
|
||||
hashes: {
|
||||
editHash: parsed.hash
|
||||
}
|
||||
@@ -3621,6 +3708,7 @@ define([
|
||||
origin: APP.origin,
|
||||
pathname: "/" + padType + "/",
|
||||
friends: friends,
|
||||
password: data.password,
|
||||
hashes: {
|
||||
editHash: parsed.hash,
|
||||
viewHash: roParsed.hash,
|
||||
@@ -3630,6 +3718,7 @@ define([
|
||||
hash: parsed.hash,
|
||||
password: data.password
|
||||
},
|
||||
isTemplate: paths[0].path[0] === 'template',
|
||||
title: data.title,
|
||||
common: common
|
||||
};
|
||||
@@ -3735,16 +3824,15 @@ define([
|
||||
var parentPath = paths[0].path.slice();
|
||||
if (manager.isInTrashRoot(parentPath)) { parentPath = [TRASH]; }
|
||||
else { parentPath.pop(); }
|
||||
el = manager.find(paths[0].path);
|
||||
APP.selectedFiles = [el];
|
||||
APP.displayDirectory(parentPath);
|
||||
APP.selectedFiles = paths[0].path.slice(-1);
|
||||
}
|
||||
APP.hideMenu();
|
||||
});
|
||||
|
||||
$content.on("keydown", function (e) {
|
||||
if (e.which === 113) {
|
||||
var paths = $contextMenu.data('paths');
|
||||
$(window).on("keydown", function (e) {
|
||||
if (e.which === 113) { // if F2 key pressed
|
||||
var paths = getSelectedPaths(findSelectedElements().first());
|
||||
if (paths.length !== 1) { return; }
|
||||
displayRenameInput(paths[0].element, paths[0].path);
|
||||
}
|
||||
@@ -3756,11 +3844,9 @@ define([
|
||||
e.preventDefault();
|
||||
});
|
||||
$appContainer.on('mouseup', function (e) {
|
||||
//if (sel.down) { return; }
|
||||
if (e.which !== 1) { return ; }
|
||||
if ($(e.target).is(".dropdown-submenu a, .dropdown-submenu a span")) { return; } // if we click on dropdown-submenu, don't close menu
|
||||
APP.hideMenu(e);
|
||||
//removeSelected(e);
|
||||
});
|
||||
$appContainer.on('click', function (e) {
|
||||
if (e.which !== 1) { return ; }
|
||||
@@ -3779,7 +3865,7 @@ define([
|
||||
if (manager.isPathIn(currentPath, [FILES_DATA]) && APP.loggedIn) {
|
||||
return; // We can't remove elements directly from filesData
|
||||
}
|
||||
var $selected = $('.cp-app-drive-element-selected');
|
||||
var $selected = findSelectedElements();
|
||||
if (!$selected.length) { return; }
|
||||
var paths = [];
|
||||
var isTrash = manager.isPathIn(currentPath, [TRASH]);
|
||||
|
||||
@@ -52,6 +52,16 @@
|
||||
max-width: 100%;
|
||||
max-height: ~"calc(100vh - 96px)";
|
||||
}
|
||||
.plain-text-reader {
|
||||
align-self: flex-start;
|
||||
width: 90vw;
|
||||
height: 100%;
|
||||
padding: 2em;
|
||||
background-color: white;
|
||||
overflow-y: auto;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
|
||||
#cp-app-file-upload-form, #cp-app-file-download-form {
|
||||
|
||||
@@ -123,7 +123,10 @@ define([
|
||||
common.setPadAttribute('fileType', metadata.type);
|
||||
}
|
||||
|
||||
toolbar.addElement(['pageTitle'], {pageTitle: title});
|
||||
toolbar.addElement(['pageTitle'], {
|
||||
pageTitle: title,
|
||||
title: Title.getTitleConfig(),
|
||||
});
|
||||
toolbar.$rightside.append(common.createButton('forget', true));
|
||||
toolbar.$rightside.append(common.createButton('properties', true));
|
||||
if (common.isLoggedIn()) {
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="cp">
|
||||
<!-- If this file is not called customize.dist/src/template.html, it is generated -->
|
||||
<head>
|
||||
<title data-localization="main_title">CryptPad: Zero Knowledge, Collaborative Real Time Editing</title>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<link rel="icon" type="image/png" href="/customize/main-favicon.png" id="favicon"/>
|
||||
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
|
||||
<link rel="stylesheet" href="/bower_components/codemirror/lib/codemirror.css">
|
||||
<link rel="stylesheet" href="/bower_components/codemirror/addon/dialog/dialog.css">
|
||||
<link rel="stylesheet" href="/bower_components/codemirror/addon/fold/foldgutter.css" />
|
||||
</head>
|
||||
<body class="html">
|
||||
<noscript>
|
||||
<p><strong>OOPS</strong> In order to do encryption in your browser, Javascript is really <strong>really</strong> required.</p>
|
||||
<p><strong>OUPS</strong> Afin de pouvoir réaliser le chiffrement dans votre navigateur, Javascript est <strong>vraiment</strong> nécessaire.</p>
|
||||
</noscript>
|
||||
</html>
|
||||
@@ -1,88 +0,0 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/common-interface.js',
|
||||
//'/common/common-hash.js',
|
||||
//'/bower_components/chainpad-listmap/chainpad-listmap.js',
|
||||
//'/common/curve.js',
|
||||
'less!/invite/main.less',
|
||||
], function ($, Cryptpad, UI/*, Hash , Listmap, Curve*/) {
|
||||
var Messages = Cryptpad.Messages;
|
||||
var comingSoon = function () {
|
||||
return $('<div>', {
|
||||
'class': 'coming-soon',
|
||||
})
|
||||
.text(Messages.comingSoon)
|
||||
.append('<br>');
|
||||
};
|
||||
|
||||
$(function () {
|
||||
UI.removeLoadingScreen();
|
||||
console.log("wut");
|
||||
$('body #mainBlock').append(comingSoon());
|
||||
});
|
||||
return;
|
||||
|
||||
/* jshint ignore:start */
|
||||
var APP = window.APP = {};
|
||||
|
||||
//var Messages = Cryptpad.Messages;
|
||||
var onInit = function () {};
|
||||
|
||||
var onDisconnect = function () {};
|
||||
var onChange = function () {};
|
||||
|
||||
var andThen = function () {
|
||||
var hash = window.location.hash.slice(1);
|
||||
|
||||
var info = Hash.parseTypeHash('invite', hash);
|
||||
console.log(info);
|
||||
|
||||
if (!info.pubkey) {
|
||||
UI.removeLoadingScreen();
|
||||
UI.alert('invalid invite');
|
||||
return;
|
||||
}
|
||||
|
||||
var proxy = Cryptpad.getProxy();
|
||||
var mySecret = proxy.curvePrivate;
|
||||
|
||||
var keys = Curve.deriveKeys(info.pubkey, mySecret);
|
||||
var encryptor = Curve.createEncryptor(keys);
|
||||
|
||||
UI.removeLoadingScreen();
|
||||
|
||||
var listmapConfig = {
|
||||
data: {},
|
||||
network: Cryptpad.getNetwork(),
|
||||
channel: info.channel,
|
||||
readOnly: false,
|
||||
validateKey: keys.validateKey,
|
||||
crypto: encryptor,
|
||||
userName: 'profile',
|
||||
logLevel: 1,
|
||||
};
|
||||
var lm = APP.lm = Listmap.create(listmapConfig);
|
||||
lm.proxy.on('create', onInit)
|
||||
.on('ready', function () {
|
||||
APP.initialized = true;
|
||||
console.log(JSON.stringify(lm.proxy));
|
||||
})
|
||||
.on('disconnect', onDisconnect)
|
||||
.on('change', [], onChange);
|
||||
};
|
||||
|
||||
$(function () {
|
||||
var $main = $('#mainBlock');
|
||||
|
||||
// main block is hidden in case javascript is disabled
|
||||
$main.removeClass('hidden');
|
||||
|
||||
APP.$container = $('#container');
|
||||
|
||||
Cryptpad.ready(function () {
|
||||
andThen();
|
||||
});
|
||||
});
|
||||
/* jshint ignore:end */
|
||||
});
|
||||
@@ -1,150 +0,0 @@
|
||||
/*
|
||||
.cp {
|
||||
#mainBlock {
|
||||
z-index: 1;
|
||||
width: 1000px;
|
||||
max-width: 90%;
|
||||
margin: auto;
|
||||
#container {
|
||||
font-size: 25px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
#header {
|
||||
display: flex;
|
||||
#rightside {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
}
|
||||
#avatar {
|
||||
width: 300px;
|
||||
//height: 350px;
|
||||
margin: 10px;
|
||||
margin-right: 20px;
|
||||
text-align: center;
|
||||
&> span {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
height: 300px;
|
||||
width: 300px;
|
||||
border: 1px solid black;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
.delete {
|
||||
right: 0;
|
||||
position: absolute;
|
||||
opacity: 0.7;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
vertical-align: top;
|
||||
}
|
||||
media-tag {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
img {
|
||||
min-width: 100%;
|
||||
min-height: 100%;
|
||||
max-width: none;
|
||||
max-height: none;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
button {
|
||||
height: 40px;
|
||||
margin: 5px;
|
||||
}
|
||||
}
|
||||
#displayName, #link {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
margin: 10px 0;
|
||||
input {
|
||||
width: 100%;
|
||||
font-size: 20px;
|
||||
box-sizing: border-box;
|
||||
padding-right: 30px;
|
||||
}
|
||||
input:focus ~ .edit {
|
||||
display: none;
|
||||
}
|
||||
.edit {
|
||||
position: absolute;
|
||||
margin-left: -25px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.temp {
|
||||
font-weight: 400;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
.displayName {
|
||||
font-weight: bold;
|
||||
font-size: 30px;
|
||||
}
|
||||
.displayName, .link {
|
||||
line-height: 40px;
|
||||
}
|
||||
}
|
||||
#description {
|
||||
position: relative;
|
||||
font-size: 16px;
|
||||
border: 1px solid #DDD;
|
||||
margin-bottom: 20px;
|
||||
.rendered {
|
||||
padding: 0 15px;
|
||||
}
|
||||
.ok, .spin {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
display: none;
|
||||
z-index: 1000;
|
||||
}
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
}
|
||||
.CodeMirror {
|
||||
border: 1px solid #DDD;
|
||||
font-family: monospace;
|
||||
font-size: 16px;
|
||||
line-height: initial;
|
||||
pre {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
#createProfile {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
.coming-soon {
|
||||
text-align: center;
|
||||
font-size: 25px;
|
||||
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
@@ -2,7 +2,9 @@
|
||||
// Pads from the code app will be exported using this format instead of plain text.
|
||||
define([
|
||||
], function () {
|
||||
var module = {};
|
||||
var module = {
|
||||
ext: '.json'
|
||||
};
|
||||
|
||||
module.main = function (userDoc, cb) {
|
||||
var content = userDoc.content;
|
||||
|
||||
@@ -367,7 +367,7 @@ define([
|
||||
});
|
||||
}
|
||||
|
||||
framework.setFileExporter('json', function () {
|
||||
framework.setFileExporter('.json', function () {
|
||||
return new Blob([JSON.stringify(kanban.getBoardsJSON(), 0, 2)], {
|
||||
type: 'application/json',
|
||||
});
|
||||
|
||||
142
www/notifications/app-notifications.less
Normal file
142
www/notifications/app-notifications.less
Normal file
@@ -0,0 +1,142 @@
|
||||
@import (reference) '../../customize/src/less2/include/framework.less';
|
||||
@import (reference) '../../customize/src/less2/include/sidebar-layout.less';
|
||||
|
||||
&.cp-app-notifications {
|
||||
|
||||
.framework_min_main(
|
||||
@bg-color: @colortheme_notifications-bg,
|
||||
@warn-color: @colortheme_notifications-warn,
|
||||
@color: @colortheme_notifications-color
|
||||
);
|
||||
.sidebar-layout_main();
|
||||
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
|
||||
.cp-clickable {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: rgba(0,0,0,0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.cp-app-notifications-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
width: 100%;
|
||||
|
||||
.cp-app-notifications-panel-titlebar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
// border-radius: 5px 5px 0 0;
|
||||
background-color: #888;
|
||||
color: #fff;
|
||||
|
||||
.cp-app-notifications-panel-title {
|
||||
flex-grow: 1;
|
||||
margin: 1rem 1rem;
|
||||
}
|
||||
|
||||
.cp-app-notifications-panel-titlebar-buttons {
|
||||
align-self: stretch;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: stretch;
|
||||
|
||||
.cp-app-notifications-dismissall {
|
||||
align-self: stretch;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 3rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cp-app-notifications-panel-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
border: 1px solid #ccc;
|
||||
border-top: none;
|
||||
// border-radius: 0 0 5px 5px;
|
||||
overflow: hidden;
|
||||
|
||||
.cp-notification {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
background-color: #ffffff;
|
||||
&.no-notifications {
|
||||
display: none;
|
||||
padding: 1rem 1rem;
|
||||
font-style: italic;
|
||||
color: #777;
|
||||
&:only-child {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
&.cp-app-notification-archived {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
&:not(:first-child) {
|
||||
border-top: 1px solid #ccc;
|
||||
}
|
||||
&.dismissed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cp-notification-content {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
p {
|
||||
display: inline-block;
|
||||
margin: 1rem 1rem;
|
||||
}
|
||||
.notification-time {
|
||||
margin: 1rem 1rem;
|
||||
color: grey;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.cp-notification-dismiss {
|
||||
align-self: stretch;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-left: 1px solid #ccc;
|
||||
width: 3rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cp-app-notification-loadmore {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #ccc;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
12
www/notifications/index.html
Normal file
12
www/notifications/index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<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">
|
||||
<meta name="referrer" content="no-referrer" />
|
||||
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<link href="/customize/src/outer.css" rel="stylesheet" type="text/css">
|
||||
</head>
|
||||
<body>
|
||||
<iframe id="sbox-iframe">
|
||||
18
www/notifications/inner.html
Normal file
18
www/notifications/inner.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="cp-app-noscroll">
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<script async data-bootload="/notifications/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
.loading-hidden { display: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="cp-app-notifications">
|
||||
<div id="cp-toolbar" class="cp-toolbar-container"></div>
|
||||
<div id="cp-sidebarlayout-container"></div>
|
||||
<noscript>
|
||||
<p><strong>OOPS</strong> In order to do encryption in your browser, Javascript is really <strong>really</strong> required.</p>
|
||||
<p><strong>OUPS</strong> Afin de pouvoir réaliser le chiffrement dans votre navigateur, Javascript est <strong>vraiment</strong> nécessaire.</p>
|
||||
</noscript>
|
||||
</body>
|
||||
|
||||
265
www/notifications/inner.js
Normal file
265
www/notifications/inner.js
Normal file
@@ -0,0 +1,265 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/api/config',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/common/toolbar3.js',
|
||||
'/bower_components/nthen/index.js',
|
||||
'/common/sframe-common.js',
|
||||
'/common/hyperscript.js',
|
||||
'/customize/messages.js',
|
||||
'/common/common-interface.js',
|
||||
|
||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'less!/notifications/app-notifications.less',
|
||||
], function (
|
||||
$,
|
||||
ApiConfig,
|
||||
Crypto,
|
||||
Toolbar,
|
||||
nThen,
|
||||
SFCommon,
|
||||
h,
|
||||
Messages,
|
||||
UI
|
||||
)
|
||||
{
|
||||
var APP = {};
|
||||
|
||||
var common;
|
||||
var sFrameChan;
|
||||
|
||||
var categories = {
|
||||
'all': [
|
||||
'cp-notifications-all',
|
||||
],
|
||||
'friends': [
|
||||
'cp-notifications-friends',
|
||||
],
|
||||
'pads': [
|
||||
'cp-notifications-pads',
|
||||
],
|
||||
'archived': [
|
||||
'cp-notifications-archived',
|
||||
],
|
||||
};
|
||||
|
||||
var notifsAllowedTypes = ["FRIEND_REQUEST", "FRIEND_REQUEST_ACCEPTED", "FRIEND_REQUEST_DECLINED", "SHARE_PAD", "REQUEST_PAD_ACCESS"];
|
||||
|
||||
var create = {};
|
||||
|
||||
var unreadData;
|
||||
|
||||
// create the list of notifications
|
||||
// show only notifs with type in filterTypes array
|
||||
var makeNotificationList = function (key, filterTypes) {
|
||||
filterTypes = filterTypes || [];
|
||||
var safeKey = key.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); });
|
||||
var categoryName = Messages['notifications_cat_' + safeKey] || safeKey;
|
||||
|
||||
var notifsData = [];
|
||||
if (key === "all") {
|
||||
unreadData = notifsData;
|
||||
}
|
||||
var $div = $('<div>', {'class': 'cp-notifications-' + key + ' cp-sidebarlayout-element'});
|
||||
var notifsPanel, notifsList, dismissAll;
|
||||
notifsPanel = h("div.cp-app-notifications-panel", [
|
||||
h('div.cp-app-notifications-panel-titlebar', [
|
||||
h("span.cp-app-notifications-panel-title",
|
||||
(Messages.notificationsPage || "Notifications") + " - " + categoryName),
|
||||
h("div.cp-app-notifications-panel-titlebar-buttons", [
|
||||
dismissAll = h("div.cp-app-notifications-dismissall.cp-clickable", { title: Messages.notifications_dismissAll || "Dismiss All" }, h("span.fa.fa-trash")),
|
||||
]),
|
||||
]),
|
||||
notifsList = h("div.cp-app-notifications-panel-list", [
|
||||
h("div.cp-notification.no-notifications", Messages.notifications_empty),
|
||||
]),
|
||||
]);
|
||||
|
||||
// add notification
|
||||
var addNotification = function (data, el) {
|
||||
// if the type of notification correspond
|
||||
if (filterTypes.indexOf(data.content.msg.type) !== -1) {
|
||||
notifsData.push(data);
|
||||
$(notifsList).prepend(el);
|
||||
}
|
||||
};
|
||||
var addArchivedNotification = function (data) {
|
||||
// if the type is allowed
|
||||
if (data.content.archived && notifsAllowedTypes.indexOf(data.content.msg.type) !== -1) {
|
||||
var isDataUnread = unreadData.some(function (ud) {
|
||||
return ud.content.hash === data.content.hash;
|
||||
});
|
||||
notifsData.push(data);
|
||||
var el = common.mailbox.createElement(data);
|
||||
var time = new Date(data.content.time);
|
||||
$(el).find(".cp-notification-content").append(h("span.notification-time", time.toLocaleString()));
|
||||
$(el).addClass("cp-app-notification-archived");
|
||||
$(el).toggle(!isDataUnread);
|
||||
$(notifsList).append(el);
|
||||
}
|
||||
};
|
||||
|
||||
$div.append(notifsPanel);
|
||||
|
||||
if (key === "archived") {
|
||||
var loadmore;
|
||||
var lastKnownHash;
|
||||
$(dismissAll).remove();
|
||||
loadmore = h("div.cp-app-notification-loadmore.cp-clickable", Messages.history_loadMore);
|
||||
$(loadmore).click(function () {
|
||||
common.mailbox.getNotificationsHistory('notifications', 10, lastKnownHash, function (err, messages, end) {
|
||||
if (!Array.isArray(messages)) { return; }
|
||||
// display archived notifs from most recent to oldest
|
||||
for (var i = messages.length - 1 ; i >= 0 ; i--) {
|
||||
var data = messages[i];
|
||||
data.content.archived = true;
|
||||
addArchivedNotification(data);
|
||||
}
|
||||
if (end) {
|
||||
$(loadmore).hide();
|
||||
}
|
||||
else {
|
||||
lastKnownHash = messages[0].content.hash;
|
||||
}
|
||||
$('#cp-sidebarlayout-rightside').scrollTop($('#cp-sidebarlayout-rightside')[0].scrollHeight);
|
||||
});
|
||||
});
|
||||
notifsList.after(loadmore);
|
||||
$(loadmore).click();
|
||||
}
|
||||
|
||||
common.mailbox.subscribe(["notifications"], {
|
||||
onMessage: function (data, el) {
|
||||
addNotification(data, el);
|
||||
},
|
||||
onViewed: function (data) {
|
||||
$('.cp-app-notification-archived[data-hash="' + data.hash + '"]').show();
|
||||
}
|
||||
});
|
||||
|
||||
$(dismissAll).click(function () {
|
||||
notifsData.forEach(function (data) {
|
||||
if (data.content.isDismissible) {
|
||||
data.content.dismissHandler();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return $div;
|
||||
};
|
||||
create['all'] = function () {
|
||||
var key = 'all';
|
||||
return makeNotificationList(key, notifsAllowedTypes);
|
||||
};
|
||||
|
||||
create['friends'] = function () {
|
||||
var key = 'friends';
|
||||
var filter = ["FRIEND_REQUEST", "FRIEND_REQUEST_ACCEPTED", "FRIEND_REQUEST_DECLINED"];
|
||||
return makeNotificationList(key, filter);
|
||||
};
|
||||
|
||||
create['pads'] = function () {
|
||||
var key = 'pads';
|
||||
var filter = ["SHARE_PAD"];
|
||||
return makeNotificationList(key, filter);
|
||||
};
|
||||
|
||||
create['archived'] = function () {
|
||||
var key = 'archived';
|
||||
var filter = [];
|
||||
return makeNotificationList(key, filter);
|
||||
};
|
||||
|
||||
|
||||
var hideCategories = function () {
|
||||
APP.$rightside.find('> div').hide();
|
||||
};
|
||||
var showCategories = function (cat) {
|
||||
hideCategories();
|
||||
cat.forEach(function (c) {
|
||||
APP.$rightside.find('.'+c).show();
|
||||
});
|
||||
};
|
||||
var createLeftside = function () {
|
||||
var $categories = $('<div>', {'class': 'cp-sidebarlayout-categories'})
|
||||
.appendTo(APP.$leftside);
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
var privateData = metadataMgr.getPrivateData();
|
||||
var active = privateData.category || 'all';
|
||||
common.setHash(active);
|
||||
Object.keys(categories).forEach(function (key) {
|
||||
var $category = $('<div>', {'class': 'cp-sidebarlayout-category'}).appendTo($categories);
|
||||
if (key === 'all') { $category.append($('<span>', {'class': 'fa fa-bars'})); }
|
||||
if (key === 'friends') { $category.append($('<span>', {'class': 'fa fa-user'})); }
|
||||
if (key === 'pads') { $category.append($('<span>', {'class': 'cptools cptools-pad'})); }
|
||||
if (key === 'archived') { $category.append($('<span>', {'class': 'fa fa-archive'})); }
|
||||
|
||||
if (key === active) {
|
||||
$category.addClass('cp-leftside-active');
|
||||
}
|
||||
|
||||
$category.click(function () {
|
||||
if (!Array.isArray(categories[key]) && categories[key].onClick) {
|
||||
categories[key].onClick();
|
||||
return;
|
||||
}
|
||||
active = key;
|
||||
common.setHash(key);
|
||||
$categories.find('.cp-leftside-active').removeClass('cp-leftside-active');
|
||||
$category.addClass('cp-leftside-active');
|
||||
showCategories(categories[key]);
|
||||
});
|
||||
|
||||
$category.append(Messages['notifications_cat_'+key] || key);
|
||||
});
|
||||
showCategories(categories[active]);
|
||||
};
|
||||
|
||||
var createToolbar = function () {
|
||||
var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle', 'notifications'];
|
||||
var configTb = {
|
||||
displayed: displayed,
|
||||
sfCommon: common,
|
||||
$container: APP.$toolbar,
|
||||
pageTitle: Messages.notificationsPage || 'Notifications',
|
||||
metadataMgr: common.getMetadataMgr(),
|
||||
};
|
||||
APP.toolbar = Toolbar.create(configTb);
|
||||
APP.toolbar.$rightside.hide();
|
||||
};
|
||||
|
||||
nThen(function (waitFor) {
|
||||
$(waitFor(UI.addLoadingScreen));
|
||||
SFCommon.create(waitFor(function (c) { APP.common = common = c; }));
|
||||
}).nThen(function (waitFor) {
|
||||
APP.$container = $('#cp-sidebarlayout-container');
|
||||
APP.$toolbar = $('#cp-toolbar');
|
||||
APP.$leftside = $('<div>', {id: 'cp-sidebarlayout-leftside'}).appendTo(APP.$container);
|
||||
APP.$rightside = $('<div>', {id: 'cp-sidebarlayout-rightside'}).appendTo(APP.$container);
|
||||
sFrameChan = common.getSframeChannel();
|
||||
sFrameChan.onReady(waitFor());
|
||||
}).nThen(function (/*waitFor*/) {
|
||||
createToolbar();
|
||||
common.setTabTitle(Messages.notificationsPage || 'Notifications');
|
||||
|
||||
|
||||
// Content
|
||||
var $rightside = APP.$rightside;
|
||||
var addItem = function (cssClass) {
|
||||
var item = cssClass.slice(17); // remove 'cp-notifications-'
|
||||
if (typeof (create[item]) === "function") {
|
||||
$rightside.append(create[item]());
|
||||
}
|
||||
};
|
||||
for (var cat in categories) {
|
||||
if (!Array.isArray(categories[cat])) { continue; }
|
||||
categories[cat].forEach(addItem);
|
||||
}
|
||||
|
||||
createLeftside();
|
||||
|
||||
UI.removeLoadingScreen();
|
||||
|
||||
});
|
||||
});
|
||||
52
www/notifications/main.js
Normal file
52
www/notifications/main.js
Normal file
@@ -0,0 +1,52 @@
|
||||
// Load #1, load as little as possible because we are in a race to get the loading screen up.
|
||||
define([
|
||||
'/bower_components/nthen/index.js',
|
||||
'/api/config',
|
||||
'/common/dom-ready.js',
|
||||
'/common/requireconfig.js',
|
||||
'/common/sframe-common-outer.js',
|
||||
], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
|
||||
var requireConfig = RequireConfig();
|
||||
|
||||
// Loaded in load #2
|
||||
nThen(function (waitFor) {
|
||||
DomReady.onReady(waitFor());
|
||||
}).nThen(function (waitFor) {
|
||||
var req = {
|
||||
cfg: requireConfig,
|
||||
req: [ '/common/loading.js' ],
|
||||
pfx: window.location.origin
|
||||
};
|
||||
window.rc = requireConfig;
|
||||
window.apiconf = ApiConfig;
|
||||
document.getElementById('sbox-iframe').setAttribute('src',
|
||||
ApiConfig.httpSafeOrigin + '/notifications/inner.html?' + requireConfig.urlArgs +
|
||||
'#' + encodeURIComponent(JSON.stringify(req)));
|
||||
|
||||
// This is a cheap trick to avoid loading sframe-channel in parallel with the
|
||||
// loading screen setup.
|
||||
var done = waitFor();
|
||||
var onMsg = function (msg) {
|
||||
var data = JSON.parse(msg.data);
|
||||
if (data.q !== 'READY') { return; }
|
||||
window.removeEventListener('message', onMsg);
|
||||
var _done = done;
|
||||
done = function () { };
|
||||
_done();
|
||||
};
|
||||
window.addEventListener('message', onMsg);
|
||||
}).nThen(function (/*waitFor*/) {
|
||||
var category;
|
||||
if (window.location.hash) {
|
||||
category = window.location.hash.slice(1);
|
||||
window.location.hash = '';
|
||||
}
|
||||
var addData = function (obj) {
|
||||
if (category) { obj.category = category; }
|
||||
};
|
||||
SFCommonO.start({
|
||||
noRealtime: true,
|
||||
addData: addData
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -5,7 +5,7 @@ define([
|
||||
'/bower_components/nthen/index.js',
|
||||
], function ($, Util, Hyperjson, nThen) {
|
||||
var module = {
|
||||
type: 'html'
|
||||
ext: '.html'
|
||||
};
|
||||
|
||||
var exportMediaTags = function (inner, cb) {
|
||||
|
||||
@@ -786,7 +786,7 @@ define([
|
||||
});
|
||||
}, true);
|
||||
|
||||
framework.setFileExporter(Exporter.type, function (cb) {
|
||||
framework.setFileExporter(Exporter.ext, function (cb) {
|
||||
Exporter.main(inner, cb);
|
||||
}, true);
|
||||
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
define([
|
||||
'/customize/messages.js',
|
||||
], function (Messages) {
|
||||
var module = {};
|
||||
var module = {
|
||||
ext: '.csv'
|
||||
};
|
||||
|
||||
var copyObject = function (obj) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
|
||||
@@ -559,7 +559,7 @@ define([
|
||||
lm.proxy.on('ready', function () {
|
||||
updateValues(lm.proxy);
|
||||
UI.removeLoadingScreen();
|
||||
common.mailbox.subscribe({
|
||||
common.mailbox.subscribe(["notifications"], {
|
||||
onMessage: function () {
|
||||
refreshFriendRequest(lm.proxy);
|
||||
},
|
||||
|
||||
@@ -111,8 +111,14 @@
|
||||
vertical-align: middle;
|
||||
margin-right: 5px;
|
||||
}
|
||||
input[type="color"] {
|
||||
width: 100px;
|
||||
.cp-settings-cursor-color-picker {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
height: 25px;
|
||||
width: 70px;
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
border: 1px solid black;
|
||||
}
|
||||
.cp-settings-language-selector {
|
||||
button.btn {
|
||||
|
||||
@@ -15,6 +15,7 @@ define([
|
||||
'/settings/make-backup.js',
|
||||
'/common/common-feedback.js',
|
||||
|
||||
'/common/jscolor.js',
|
||||
'/bower_components/file-saver/FileSaver.min.js',
|
||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
@@ -1191,13 +1192,13 @@ define([
|
||||
|
||||
var $inputBlock = $('<div>').appendTo($div);
|
||||
|
||||
var $colorPicker = $("<div>", { class: "cp-settings-cursor-color-picker"});
|
||||
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved});
|
||||
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'});
|
||||
|
||||
var $input = $('<input>', {
|
||||
type: 'color',
|
||||
}).on('change', function () {
|
||||
var val = $input.val();
|
||||
// when jscolor picker value change
|
||||
var onchange = function (colorL) {
|
||||
var val = "#" + colorL.toString();
|
||||
if (!/^#[0-9a-fA-F]{6}$/.test(val)) { return; }
|
||||
$spinner.show();
|
||||
$ok.hide();
|
||||
@@ -1205,15 +1206,25 @@ define([
|
||||
$spinner.hide();
|
||||
$ok.show();
|
||||
});
|
||||
}).appendTo($inputBlock);
|
||||
};
|
||||
|
||||
// jscolor picker
|
||||
var jscolorL = new window.jscolor($colorPicker[0],{showOnClick: false, onFineChange: onchange, valueElement:undefined});
|
||||
$colorPicker.click(function () {
|
||||
jscolorL.show();
|
||||
});
|
||||
|
||||
// set default color
|
||||
common.getAttribute(['general', 'cursor', 'color'], function (e, val) {
|
||||
if (e) { return void console.error(e); }
|
||||
val = val || "#000";
|
||||
jscolorL.fromString(val);
|
||||
});
|
||||
|
||||
$colorPicker.appendTo($inputBlock);
|
||||
$ok.hide().appendTo($inputBlock);
|
||||
$spinner.hide().appendTo($inputBlock);
|
||||
|
||||
common.getAttribute(['general', 'cursor', 'color'], function (e, val) {
|
||||
if (e) { return void console.error(e); }
|
||||
$input.val(val || '');
|
||||
});
|
||||
return $div;
|
||||
};
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ define([
|
||||
var path = '/' + type + '/export.js';
|
||||
require([path], function (Exporter) {
|
||||
Exporter.main(json, function (data) {
|
||||
result.ext = '.' + Exporter.type;
|
||||
result.ext = Exporter.ext || '';
|
||||
result.data = data;
|
||||
cb(result);
|
||||
});
|
||||
@@ -163,12 +163,12 @@ define([
|
||||
var existingNames = [];
|
||||
Object.keys(root).forEach(function (k) {
|
||||
var el = root[k];
|
||||
if (typeof el === "object") {
|
||||
if (typeof el === "object" && el.metadata !== true) { // if folder
|
||||
var fName = getUnique(sanitize(k), '', existingNames);
|
||||
existingNames.push(fName.toLowerCase());
|
||||
return void makeFolder(ctx, el, zip.folder(fName), fd);
|
||||
}
|
||||
if (ctx.data.sharedFolders[el]) {
|
||||
if (ctx.data.sharedFolders[el]) { // if shared folder
|
||||
var sfData = ctx.sf[el].metadata;
|
||||
var sfName = getUnique(sanitize(sfData.title || 'Folder'), '', existingNames);
|
||||
existingNames.push(sfName.toLowerCase());
|
||||
|
||||
@@ -17,7 +17,11 @@ define([
|
||||
{
|
||||
var APP = window.APP = {};
|
||||
|
||||
var init = false;
|
||||
var andThen = function (common) {
|
||||
if (init) { return; }
|
||||
init = true;
|
||||
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
var sframeChan = common.getSframeChannel();
|
||||
|
||||
@@ -38,6 +42,8 @@ define([
|
||||
var modal = f({
|
||||
origin: origin,
|
||||
pathname: pathname,
|
||||
password: priv.password,
|
||||
isTemplate: priv.isTemplate,
|
||||
hashes: hashes,
|
||||
common: common,
|
||||
title: data.title,
|
||||
@@ -50,7 +56,7 @@ define([
|
||||
password: priv.password
|
||||
}
|
||||
});
|
||||
UI.findCancelButton().click();
|
||||
$('button.cancel').click(); // Close any existing alertify
|
||||
UI.openCustomModal(UI.dialog.tabs(modal), {
|
||||
wide: Object.keys(friends).length !== 0
|
||||
});
|
||||
|
||||
@@ -91,6 +91,7 @@ define([
|
||||
feedbackAllowed: Utils.Feedback.state,
|
||||
hashes: config.data.hashes,
|
||||
password: config.data.password,
|
||||
isTemplate: config.data.isTemplate,
|
||||
file: config.data.file,
|
||||
};
|
||||
for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; }
|
||||
|
||||
@@ -4,7 +4,7 @@ define([
|
||||
'/common/sframe-common-codemirror.js',
|
||||
], function (SFCodeMirror) {
|
||||
var module = {
|
||||
type: 'md'
|
||||
ext: '.md'
|
||||
};
|
||||
|
||||
module.main = function (userDoc, cb) {
|
||||
|
||||
@@ -538,7 +538,7 @@ define([
|
||||
|
||||
editor.on('change', framework.localChange);
|
||||
|
||||
framework.setFileExporter(CodeMirror.getContentExtension, CodeMirror.fileExporter);
|
||||
framework.setFileExporter(".md", CodeMirror.fileExporter);
|
||||
framework.setFileImporter({}, CodeMirror.fileImporter);
|
||||
|
||||
framework.start();
|
||||
|
||||
21
www/support/app-support.less
Normal file
21
www/support/app-support.less
Normal file
@@ -0,0 +1,21 @@
|
||||
@import (reference) '../../customize/src/less2/include/framework.less';
|
||||
@import (reference) '../../customize/src/less2/include/sidebar-layout.less';
|
||||
@import (reference) '../../customize/src/less2/include/support.less';
|
||||
|
||||
&.cp-app-support {
|
||||
.framework_min_main(
|
||||
@bg-color: @colortheme_support-bg,
|
||||
@warn-color: @colortheme_support-warn,
|
||||
@color: @colortheme_support-color
|
||||
);
|
||||
.sidebar-layout_main();
|
||||
.support_main();
|
||||
|
||||
.cp-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
12
www/support/index.html
Normal file
12
www/support/index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<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">
|
||||
<meta name="referrer" content="no-referrer" />
|
||||
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<link href="/customize/src/outer.css" rel="stylesheet" type="text/css">
|
||||
</head>
|
||||
<body>
|
||||
<iframe id="sbox-iframe">
|
||||
18
www/support/inner.html
Normal file
18
www/support/inner.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="cp-app-noscroll">
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<script async data-bootload="/support/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
.loading-hidden { display: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="cp-app-support">
|
||||
<div id="cp-toolbar" class="cp-toolbar-container"></div>
|
||||
<div id="cp-sidebarlayout-container"></div>
|
||||
<noscript>
|
||||
<p><strong>OOPS</strong> In order to do encryption in your browser, Javascript is really <strong>really</strong> required.</p>
|
||||
<p><strong>OUPS</strong> Afin de pouvoir réaliser le chiffrement dans votre navigateur, Javascript est <strong>vraiment</strong> nécessaire.</p>
|
||||
</noscript>
|
||||
</body>
|
||||
</html>
|
||||
267
www/support/inner.js
Normal file
267
www/support/inner.js
Normal file
@@ -0,0 +1,267 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/common/toolbar3.js',
|
||||
'/bower_components/nthen/index.js',
|
||||
'/common/sframe-common.js',
|
||||
'/common/common-interface.js',
|
||||
'/common/common-ui-elements.js',
|
||||
'/common/common-util.js',
|
||||
'/common/common-hash.js',
|
||||
'/customize/messages.js',
|
||||
'/common/hyperscript.js',
|
||||
'/support/ui.js',
|
||||
'/api/config',
|
||||
|
||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'less!/support/app-support.less',
|
||||
], function (
|
||||
$,
|
||||
Toolbar,
|
||||
nThen,
|
||||
SFCommon,
|
||||
UI,
|
||||
UIElements,
|
||||
Util,
|
||||
Hash,
|
||||
Messages,
|
||||
h,
|
||||
Support,
|
||||
ApiConfig
|
||||
)
|
||||
{
|
||||
var APP = window.APP = {};
|
||||
|
||||
var common;
|
||||
var metadataMgr;
|
||||
var privateData;
|
||||
|
||||
var categories = {
|
||||
'tickets': [
|
||||
'cp-support-list',
|
||||
],
|
||||
'new': [
|
||||
'cp-support-form',
|
||||
],
|
||||
};
|
||||
|
||||
var supportKey = ApiConfig.supportMailbox;
|
||||
var supportChannel = Hash.getChannelIdFromKey(supportKey);
|
||||
if (!supportKey || !supportChannel) {
|
||||
categories = {
|
||||
'tickets': [
|
||||
'cp-support-disabled'
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
var create = {};
|
||||
|
||||
var makeBlock = function (key, addButton) {
|
||||
// Convert to camlCase for translation keys
|
||||
var safeKey = key.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); });
|
||||
|
||||
var $div = $('<div>', {'class': 'cp-support-' + key + ' cp-sidebarlayout-element'});
|
||||
$('<label>').text(Messages['support_'+safeKey+'Title'] || key).appendTo($div);
|
||||
$('<span>', {'class': 'cp-sidebarlayout-description'})
|
||||
.text(Messages['support_'+safeKey+'Hint'] || 'Coming soon...').appendTo($div);
|
||||
if (addButton) {
|
||||
$('<button>', {
|
||||
'class': 'btn btn-primary'
|
||||
}).text(Messages['support_'+safeKey+'Button'] || safeKey).appendTo($div);
|
||||
}
|
||||
return $div;
|
||||
};
|
||||
|
||||
|
||||
|
||||
// List existing (open?) tickets
|
||||
create['list'] = function () {
|
||||
var key = 'list';
|
||||
var $div = makeBlock(key);
|
||||
$div.addClass('cp-support-container');
|
||||
var hashesById = {};
|
||||
|
||||
// Register to the "support" mailbox
|
||||
common.mailbox.subscribe(['support'], {
|
||||
onMessage: function (data) {
|
||||
/*
|
||||
Get ID of the ticket
|
||||
If we already have a div for this ID
|
||||
Push the message to the end of the ticket
|
||||
If it's a new ticket ID
|
||||
Make a new div for this ID
|
||||
*/
|
||||
var msg = data.content.msg;
|
||||
var hash = data.content.hash;
|
||||
var content = msg.content;
|
||||
var id = content.id;
|
||||
var $ticket = $div.find('.cp-support-list-ticket[data-id="'+id+'"]');
|
||||
|
||||
hashesById[id] = hashesById[id] || [];
|
||||
if (hashesById[id].indexOf(hash) === -1) {
|
||||
hashesById[id].push(data);
|
||||
}
|
||||
|
||||
if (msg.type === 'CLOSE') {
|
||||
// A ticket has been closed by the admins...
|
||||
if (!$ticket.length) { return; }
|
||||
$ticket.addClass('cp-support-list-closed');
|
||||
$ticket.append(APP.support.makeCloseMessage(content, hash));
|
||||
return;
|
||||
}
|
||||
if (msg.type !== 'TICKET') { return; }
|
||||
|
||||
if (!$ticket.length) {
|
||||
$ticket = APP.support.makeTicket($div, content, function () {
|
||||
var error = false;
|
||||
hashesById[id].forEach(function (d) {
|
||||
common.mailbox.dismiss(d, function (err) {
|
||||
if (err) {
|
||||
error = true;
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (!error) { $ticket.remove(); }
|
||||
});
|
||||
}
|
||||
$ticket.append(APP.support.makeMessage(content, hash));
|
||||
}
|
||||
});
|
||||
return $div;
|
||||
};
|
||||
|
||||
// Create a new tickets
|
||||
create['form'] = function () {
|
||||
var key = 'form';
|
||||
var $div = makeBlock(key, true);
|
||||
|
||||
var form = APP.support.makeForm();
|
||||
|
||||
$div.find('button').before(form);
|
||||
|
||||
var id = Util.uid();
|
||||
|
||||
$div.find('button').click(function () {
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
var privateData = metadataMgr.getPrivateData();
|
||||
var user = metadataMgr.getUserData();
|
||||
var sent = APP.support.sendForm(id, form, {
|
||||
channel: privateData.support,
|
||||
curvePublic: user.curvePublic
|
||||
});
|
||||
id = Util.uid();
|
||||
if (sent) {
|
||||
$('.cp-sidebarlayout-category[data-category="tickets"]').click();
|
||||
}
|
||||
});
|
||||
return $div;
|
||||
};
|
||||
|
||||
// Support is disabled...
|
||||
create['disabled'] = function () {
|
||||
var key = 'disabled';
|
||||
var $div = makeBlock(key);
|
||||
return $div;
|
||||
};
|
||||
|
||||
|
||||
var hideCategories = function () {
|
||||
APP.$rightside.find('> div').hide();
|
||||
};
|
||||
var showCategories = function (cat) {
|
||||
hideCategories();
|
||||
cat.forEach(function (c) {
|
||||
APP.$rightside.find('.'+c).show();
|
||||
});
|
||||
};
|
||||
|
||||
var createLeftside = function () {
|
||||
var $categories = $('<div>', {'class': 'cp-sidebarlayout-categories'})
|
||||
.appendTo(APP.$leftside);
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
var privateData = metadataMgr.getPrivateData();
|
||||
var active = privateData.category || 'tickets';
|
||||
common.setHash(active);
|
||||
Object.keys(categories).forEach(function (key) {
|
||||
var $category = $('<div>', {
|
||||
'class': 'cp-sidebarlayout-category',
|
||||
'data-category': key
|
||||
}).appendTo($categories);
|
||||
if (key === 'tickets') { $category.append($('<span>', {'class': 'fa fa-envelope-o'})); }
|
||||
if (key === 'new') { $category.append($('<span>', {'class': 'fa fa-life-ring'})); }
|
||||
|
||||
if (key === active) {
|
||||
$category.addClass('cp-leftside-active');
|
||||
}
|
||||
|
||||
$category.click(function () {
|
||||
if (!Array.isArray(categories[key]) && categories[key].onClick) {
|
||||
categories[key].onClick();
|
||||
return;
|
||||
}
|
||||
active = key;
|
||||
common.setHash(key);
|
||||
$categories.find('.cp-leftside-active').removeClass('cp-leftside-active');
|
||||
$category.addClass('cp-leftside-active');
|
||||
showCategories(categories[key]);
|
||||
});
|
||||
|
||||
$category.append(Messages['support_cat_'+key] || key);
|
||||
});
|
||||
showCategories(categories[active]);
|
||||
};
|
||||
|
||||
var createToolbar = function () {
|
||||
var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle', 'notifications'];
|
||||
var configTb = {
|
||||
displayed: displayed,
|
||||
sfCommon: common,
|
||||
$container: APP.$toolbar,
|
||||
pageTitle: Messages.supportPage,
|
||||
metadataMgr: common.getMetadataMgr(),
|
||||
};
|
||||
APP.toolbar = Toolbar.create(configTb);
|
||||
APP.toolbar.$rightside.hide();
|
||||
};
|
||||
|
||||
nThen(function (waitFor) {
|
||||
$(waitFor(UI.addLoadingScreen));
|
||||
SFCommon.create(waitFor(function (c) { APP.common = common = c; }));
|
||||
}).nThen(function (waitFor) {
|
||||
APP.$container = $('#cp-sidebarlayout-container');
|
||||
APP.$toolbar = $('#cp-toolbar');
|
||||
APP.$leftside = $('<div>', {id: 'cp-sidebarlayout-leftside'}).appendTo(APP.$container);
|
||||
APP.$rightside = $('<div>', {id: 'cp-sidebarlayout-rightside'}).appendTo(APP.$container);
|
||||
var sFrameChan = common.getSframeChannel();
|
||||
sFrameChan.onReady(waitFor());
|
||||
}).nThen(function (/*waitFor*/) {
|
||||
createToolbar();
|
||||
metadataMgr = common.getMetadataMgr();
|
||||
privateData = metadataMgr.getPrivateData();
|
||||
common.setTabTitle(Messages.supportPage);
|
||||
|
||||
APP.origin = privateData.origin;
|
||||
APP.readOnly = privateData.readOnly;
|
||||
APP.support = Support.create(common, false);
|
||||
|
||||
// Content
|
||||
var $rightside = APP.$rightside;
|
||||
var addItem = function (cssClass) {
|
||||
var item = cssClass.slice(11); // remove 'cp-support-'
|
||||
if (typeof (create[item]) === "function") {
|
||||
$rightside.append(create[item]());
|
||||
}
|
||||
};
|
||||
for (var cat in categories) {
|
||||
if (!Array.isArray(categories[cat])) { continue; }
|
||||
categories[cat].forEach(addItem);
|
||||
}
|
||||
|
||||
createLeftside();
|
||||
|
||||
UI.removeLoadingScreen();
|
||||
|
||||
});
|
||||
});
|
||||
52
www/support/main.js
Normal file
52
www/support/main.js
Normal file
@@ -0,0 +1,52 @@
|
||||
// Load #1, load as little as possible because we are in a race to get the loading screen up.
|
||||
define([
|
||||
'/bower_components/nthen/index.js',
|
||||
'/api/config',
|
||||
'/common/dom-ready.js',
|
||||
'/common/requireconfig.js',
|
||||
'/common/sframe-common-outer.js'
|
||||
], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
|
||||
var requireConfig = RequireConfig();
|
||||
|
||||
// Loaded in load #2
|
||||
nThen(function (waitFor) {
|
||||
DomReady.onReady(waitFor());
|
||||
}).nThen(function (waitFor) {
|
||||
var req = {
|
||||
cfg: requireConfig,
|
||||
req: [ '/common/loading.js' ],
|
||||
pfx: window.location.origin
|
||||
};
|
||||
window.rc = requireConfig;
|
||||
window.apiconf = ApiConfig;
|
||||
document.getElementById('sbox-iframe').setAttribute('src',
|
||||
ApiConfig.httpSafeOrigin + '/support/inner.html?' + requireConfig.urlArgs +
|
||||
'#' + encodeURIComponent(JSON.stringify(req)));
|
||||
|
||||
// This is a cheap trick to avoid loading sframe-channel in parallel with the
|
||||
// loading screen setup.
|
||||
var done = waitFor();
|
||||
var onMsg = function (msg) {
|
||||
var data = JSON.parse(msg.data);
|
||||
if (data.q !== 'READY') { return; }
|
||||
window.removeEventListener('message', onMsg);
|
||||
var _done = done;
|
||||
done = function () { };
|
||||
_done();
|
||||
};
|
||||
window.addEventListener('message', onMsg);
|
||||
}).nThen(function (/*waitFor*/) {
|
||||
var category;
|
||||
if (window.location.hash) {
|
||||
category = window.location.hash.slice(1);
|
||||
window.location.hash = '';
|
||||
}
|
||||
var addData = function (obj) {
|
||||
if (category) { obj.category = category; }
|
||||
};
|
||||
SFCommonO.start({
|
||||
noRealtime: true,
|
||||
addData: addData
|
||||
});
|
||||
});
|
||||
});
|
||||
229
www/support/ui.js
Normal file
229
www/support/ui.js
Normal file
@@ -0,0 +1,229 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/api/config',
|
||||
'/common/hyperscript.js',
|
||||
'/common/common-interface.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/common-util.js',
|
||||
'/customize/messages.js',
|
||||
], function ($, ApiConfig, h, UI, Hash, Util, Messages) {
|
||||
|
||||
var send = function (ctx, id, type, data, dest) {
|
||||
var common = ctx.common;
|
||||
var supportKey = ApiConfig.supportMailbox;
|
||||
var supportChannel = Hash.getChannelIdFromKey(supportKey);
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
var user = metadataMgr.getUserData();
|
||||
var privateData = metadataMgr.getPrivateData();
|
||||
|
||||
data = data || {};
|
||||
|
||||
data.sender = {
|
||||
name: user.name,
|
||||
channel: privateData.support,
|
||||
curvePublic: user.curvePublic,
|
||||
edPublic: privateData.edPublic,
|
||||
notifications: user.notifications,
|
||||
};
|
||||
data.id = id;
|
||||
data.time = +new Date();
|
||||
|
||||
if (!ctx.isAdmin) {
|
||||
data.sender.userAgent = window.navigator && window.navigator.userAgent;
|
||||
}
|
||||
|
||||
// Send the message to the admin mailbox and to the user mailbox
|
||||
common.mailbox.sendTo(type, data, {
|
||||
channel: supportChannel,
|
||||
curvePublic: supportKey
|
||||
});
|
||||
common.mailbox.sendTo(type, data, {
|
||||
channel: dest.channel,
|
||||
curvePublic: dest.curvePublic
|
||||
});
|
||||
|
||||
if (ctx.isAdmin) {
|
||||
common.mailbox.sendTo('SUPPORT_MESSAGE', {}, {
|
||||
channel: dest.notifications,
|
||||
curvePublic: dest.curvePublic
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var sendForm = function (ctx, id, form, dest) {
|
||||
var $title = $(form).find('.cp-support-form-title');
|
||||
var $content = $(form).find('.cp-support-form-msg');
|
||||
|
||||
var title = $title.val().trim();
|
||||
if (!title) {
|
||||
return void UI.alert(Messages.support_formTitleError);
|
||||
}
|
||||
var content = $content.val().trim();
|
||||
if (!content) {
|
||||
return void UI.alert(Messages.support_formContentError);
|
||||
}
|
||||
$content.val('');
|
||||
$title.val('');
|
||||
|
||||
send(ctx, id, 'TICKET', {
|
||||
title: title,
|
||||
message: content,
|
||||
}, dest);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
var makeForm = function (cb, title) {
|
||||
var button;
|
||||
|
||||
if (typeof(cb) === "function") {
|
||||
button = h('button.btn.btn-primary.cp-support-list-send', Messages.contacts_send);
|
||||
$(button).click(cb);
|
||||
}
|
||||
|
||||
var cancel = title ? h('button.btn.btn-secondary', Messages.cancel) : undefined;
|
||||
|
||||
var content = [
|
||||
h('hr'),
|
||||
h('input.cp-support-form-title' + (title ? '.cp-hidden' : ''), {
|
||||
placeholder: Messages.support_formTitle,
|
||||
type: 'text',
|
||||
value: title || ''
|
||||
}),
|
||||
cb ? undefined : h('br'),
|
||||
h('textarea.cp-support-form-msg', {
|
||||
placeholder: Messages.support_formMessage
|
||||
}),
|
||||
h('hr'),
|
||||
button,
|
||||
cancel
|
||||
];
|
||||
|
||||
var form = h('div.cp-support-form-container', content);
|
||||
|
||||
$(cancel).click(function () {
|
||||
$(form).closest('.cp-support-list-ticket').find('.cp-support-list-actions').show();
|
||||
$(form).remove();
|
||||
});
|
||||
|
||||
return form;
|
||||
};
|
||||
|
||||
var makeTicket = function (ctx, $div, content, onHide) {
|
||||
var ticketTitle = content.title + ' (#' + content.id + ')';
|
||||
var answer = h('button.btn.btn-primary.cp-support-answer', Messages.support_answer);
|
||||
var close = h('button.btn.btn-danger.cp-support-close', Messages.support_close);
|
||||
var hide = h('button.btn.btn-danger.cp-support-hide', Messages.support_remove);
|
||||
|
||||
var actions = h('div.cp-support-list-actions', [
|
||||
answer,
|
||||
close,
|
||||
hide
|
||||
]);
|
||||
|
||||
var $ticket = $(h('div.cp-support-list-ticket', {
|
||||
'data-id': content.id
|
||||
}, [
|
||||
h('h2', ticketTitle),
|
||||
actions
|
||||
]));
|
||||
|
||||
$(close).click(function () {
|
||||
send(ctx, content.id, 'CLOSE', {}, content.sender);
|
||||
});
|
||||
|
||||
$(hide).click(function () {
|
||||
if (typeof(onHide) !== "function") { return; }
|
||||
onHide();
|
||||
});
|
||||
|
||||
$(answer).click(function () {
|
||||
$ticket.find('.cp-support-form-container').remove();
|
||||
$(actions).hide();
|
||||
var form = makeForm(function () {
|
||||
var sent = sendForm(ctx, content.id, form, content.sender);
|
||||
if (sent) {
|
||||
$(actions).show();
|
||||
$(form).remove();
|
||||
}
|
||||
}, content.title);
|
||||
$ticket.append(form);
|
||||
});
|
||||
|
||||
$div.append($ticket);
|
||||
return $ticket;
|
||||
};
|
||||
|
||||
var makeMessage = function (ctx, content, hash) {
|
||||
var common = ctx.common;
|
||||
var isAdmin = ctx.isAdmin;
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
var privateData = metadataMgr.getPrivateData();
|
||||
|
||||
// Check content.sender to see if it comes from us or from an admin
|
||||
var fromMe = content.sender && content.sender.edPublic === privateData.edPublic;
|
||||
|
||||
var userData = h('div.cp-support-showdata', [
|
||||
Messages.support_showData,
|
||||
h('pre.cp-support-message-data', JSON.stringify(content.sender, 0, 2))
|
||||
]);
|
||||
$(userData).click(function () {
|
||||
$(userData).find('pre').toggle();
|
||||
});
|
||||
|
||||
return h('div.cp-support-list-message', {
|
||||
'data-hash': hash
|
||||
}, [
|
||||
h('div.cp-support-message-from' + (fromMe ? '.cp-support-fromme' : ''), [
|
||||
UI.setHTML(h('span'), Messages._getKey('support_from', [content.sender.name])),
|
||||
h('span.cp-support-message-time', content.time ? new Date(content.time).toLocaleString() : '')
|
||||
]),
|
||||
h('pre.cp-support-message-content', content.message),
|
||||
isAdmin ? userData : undefined,
|
||||
]);
|
||||
};
|
||||
|
||||
var makeCloseMessage = function (ctx, content, hash) {
|
||||
var common = ctx.common;
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
var privateData = metadataMgr.getPrivateData();
|
||||
var fromMe = content.sender && content.sender.edPublic === privateData.edPublic;
|
||||
|
||||
return h('div.cp-support-list-message', {
|
||||
'data-hash': hash
|
||||
}, [
|
||||
h('div.cp-support-message-from' + (fromMe ? '.cp-support-fromme' : ''), [
|
||||
UI.setHTML(h('span'), Messages._getKey('support_from', [content.sender.name])),
|
||||
h('span.cp-support-message-time', content.time ? new Date(content.time).toLocaleString() : '')
|
||||
]),
|
||||
h('pre.cp-support-message-content', Messages.support_closed)
|
||||
]);
|
||||
};
|
||||
|
||||
var create = function (common, isAdmin) {
|
||||
var ui = {};
|
||||
var ctx = {
|
||||
common: common,
|
||||
isAdmin: isAdmin
|
||||
};
|
||||
|
||||
ui.sendForm = function (id, form, dest) {
|
||||
return sendForm(ctx, id, form, dest);
|
||||
};
|
||||
ui.makeForm = makeForm;
|
||||
ui.makeTicket = function ($div, content, onHide) {
|
||||
return makeTicket(ctx, $div, content, onHide);
|
||||
};
|
||||
ui.makeMessage = function (content, hash) {
|
||||
return makeMessage(ctx, content, hash);
|
||||
};
|
||||
ui.makeCloseMessage = function (content, hash) {
|
||||
return makeCloseMessage(ctx, content, hash);
|
||||
};
|
||||
return ui;
|
||||
};
|
||||
|
||||
return {
|
||||
create: create
|
||||
};
|
||||
});
|
||||
@@ -14,7 +14,7 @@ define([
|
||||
var canvas = new Fabric.Canvas(canvas_node);
|
||||
var content = userDoc.content;
|
||||
canvas.loadFromJSON(content, function () {
|
||||
module.type = 'svg';
|
||||
module.ext = '.svg';
|
||||
cb(canvas.toSVG());
|
||||
});
|
||||
};
|
||||
|
||||
@@ -415,11 +415,11 @@ define([
|
||||
setEditable(!locked);
|
||||
});
|
||||
|
||||
framework.setFileExporter('png', function (cb) {
|
||||
framework.setFileExporter('.png', function (cb) {
|
||||
$canvas[0].toBlob(function (blob) {
|
||||
cb(blob);
|
||||
});
|
||||
});
|
||||
}, true);
|
||||
|
||||
framework.setNormalizer(function (c) {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user