Merge branch 'staging' of github.com:xwiki-labs/cryptpad into staging

This commit is contained in:
ansuz 2017-11-06 15:14:21 +01:00
commit 402fae442d
31 changed files with 1699 additions and 640 deletions

View File

@ -728,7 +728,7 @@ define([
Pages['/profile/'] = Pages['/profile/index.html'] = function () { Pages['/profile/'] = Pages['/profile/index.html'] = function () {
return [ return [
h('div#toolbar'), h('div#cp-toolbar'),
h('div#container'), h('div#container'),
loadingScreen() loadingScreen()
]; ];

View File

@ -102,9 +102,9 @@
@category-bg: #f4f4f4; @category-bg: #f4f4f4;
@button-bg: #3066e5; @button-bg: @colortheme_sidebar-button-bg;
@button-alt-bg: #fff; @button-alt-bg: @colortheme_sidebar-button-alt-bg;
@button-red-bg: #e54e4e; @button-red-bg: @colortheme_sidebar-button-red-bg;
.unselectable () { .unselectable () {
-webkit-touch-callout: none; -webkit-touch-callout: none;

View File

@ -79,7 +79,7 @@
@colortheme_todo-bg: #7bccd1; @colortheme_todo-bg: #7bccd1;
@colortheme_todo-color: #000; @colortheme_todo-color: #000;
// Sidebar layout // Sidebar layout (profile / settings)
@colortheme_sidebar-active: #fff; @colortheme_sidebar-active: #fff;
@colortheme_sidebar-left-bg: #eee; @colortheme_sidebar-left-bg: #eee;
@colortheme_sidebar-left-fg: #000; @colortheme_sidebar-left-fg: #000;
@ -87,7 +87,9 @@
@colortheme_sidebar-right-bg: #fff; @colortheme_sidebar-right-bg: #fff;
@colortheme_sidebar-right-fg: #000; @colortheme_sidebar-right-fg: #000;
@colortheme_sidebar-description: #777; @colortheme_sidebar-description: #777;
@colortheme_sidebar-button-bg: #3066e5;
@colortheme_sidebar-button-red-bg: #e54e4e;
@colortheme_sidebar-button-alt-bg: #fff;
@cryptpad_color_blue: #4591C4; @cryptpad_color_blue: #4591C4;
@cryptpad_color_grey: #999999; @cryptpad_color_grey: #999999;

View File

@ -0,0 +1,99 @@
@import (once) "/customize/src/less2/include/colortheme.less";
@import (once) "/customize/src/less2/include/leftside-menu.less";
@leftside-bg: @colortheme_sidebar-left-bg;
@leftside-color: @colortheme_sidebar-left-fg;
@rightside-color: @colortheme_sidebar-right-fg;
@description-color: @colortheme_sidebar-description;
@button-width: 400px;
.sidebar-layout_main() {
input[type="text"] {
padding-left: 10px;
}
#cp-sidebarlayout-container {
font-size: 16px;
display: flex;
flex: 1;
min-height: 0;
#cp-sidebarlayout-leftside {
color: @leftside-color;
width: 250px;
background: @leftside-bg;
display: flex;
flex-flow: column;
.cp-sidebarlayout-categories {
flex: 1;
.cp-sidebarlayout-category {
.leftside-menu-category_main();
}
}
}
#cp-sidebarlayout-rightside {
flex: 1;
padding: 5px 20px;
color: @rightside-color;
overflow: auto;
// Following rules are only in settings
.element {
label:not(.noTitle), .label {
display: block;
font-weight: bold;
margin-bottom: 0;
}
.description {
display: block;
color: @description-color;
margin-bottom: 5px;
}
margin-bottom: 20px;
}
[type="text"], button {
vertical-align: middle;
height: 40px;
box-sizing: border-box;
}
.inputBlock {
display: inline-flex;
width: @button-width;
input {
flex: 1;
border-radius: 0.25em 0 0 0.25em;
border: 1px solid #adadad;
border-right: 0px;
}
button {
border-radius: 0 0.25em 0.25em 0;
//border: 1px solid #adadad;
border-left: 0px;
}
}
&>div {
margin: 10px 0;
}
button.btn {
@button-bg: @colortheme_sidebar-button-bg;
@button-red-bg: @colortheme_sidebar-button-red-bg;
background-color: @button-bg;
border-color: darken(@button-bg, 10%);
color: white;
&:hover {
background-color: darken(@button-bg, 10%);
}
&.btn-danger {
background-color: @button-red-bg;
border-color: darken(@button-red-bg, 10%);
color: white;
&:hover {
background-color: darken(@button-red-bg, 10%);
}
}
}
}
}
}

View File

@ -34,4 +34,5 @@ body.cp-app-contacts { @import "../../../contacts/app-contacts.less"; }
body.cp-app-poll { @import "../../../poll/app-poll.less"; } body.cp-app-poll { @import "../../../poll/app-poll.less"; }
body.cp-app-whiteboard { @import "../../../whiteboard/app-whiteboard.less"; } body.cp-app-whiteboard { @import "../../../whiteboard/app-whiteboard.less"; }
body.cp-app-todo { @import "../../../todo/app-todo.less"; } body.cp-app-todo { @import "../../../todo/app-todo.less"; }
body.cp-app-profile { @import "../../../profile/app-profile.less"; }

View File

@ -44,6 +44,7 @@ define(function () {
out.typing = "Édition"; out.typing = "Édition";
out.initializing = "Initialisation..."; out.initializing = "Initialisation...";
out.forgotten = 'Déplacé vers la corbeille'; out.forgotten = 'Déplacé vers la corbeille';
out.errorState = 'Erreur critique : {0}';
out.lag = 'Latence'; out.lag = 'Latence';
out.readonly = 'Lecture seule'; out.readonly = 'Lecture seule';
out.anonymous = "Anonyme"; out.anonymous = "Anonyme";
@ -56,6 +57,7 @@ define(function () {
out.viewers = "lecteurs"; out.viewers = "lecteurs";
out.editor = "éditeur"; out.editor = "éditeur";
out.editors = "éditeurs"; out.editors = "éditeurs";
out.userlist_offline = "Vous êtes actuellement hors-ligne, la liste des utilisateurs n'est pas disponible.";
out.language = "Langue"; out.language = "Langue";
@ -486,6 +488,13 @@ define(function () {
out.settings_resetTipsButton = "Réinitialiser les astuces visibles dans CryptDrive"; out.settings_resetTipsButton = "Réinitialiser les astuces visibles dans CryptDrive";
out.settings_resetTipsDone = "Toutes les astuces sont de nouveau visibles."; out.settings_resetTipsDone = "Toutes les astuces sont de nouveau visibles.";
out.settings_thumbnails = "Vignettes";
out.settings_disableThumbnailsAction = "Désactiver la création de vignettes dans CryptDrive";
out.settings_disableThumbnailsDescription = "Des vignettes de vos pads sont automatiquement créées et stockées dans votre navigateur. Vous pouvez désactiver cette fonctionnalité.";
out.settings_resetThumbnailsAction = "Nettoyer";
out.settings_resetThumbnailsDescription = "Nettoyer toutes les vignettes stockées dans votre navigateur.";
out.settings_resetThumbnailsDone = "Toutes les vignettes ont été effacées.";
out.settings_importTitle = "Importer les pads récents de ce navigateur dans votre CryptDrive"; out.settings_importTitle = "Importer les pads récents de ce navigateur dans votre CryptDrive";
out.settings_import = "Importer"; out.settings_import = "Importer";
out.settings_importConfirm = "Êtes-vous sûr de vouloir importer les pads récents de ce navigateur dans le CryptDrive de votre compte utilisateur ?"; out.settings_importConfirm = "Êtes-vous sûr de vouloir importer les pads récents de ce navigateur dans le CryptDrive de votre compte utilisateur ?";

View File

@ -43,9 +43,10 @@ define(function () {
out.disconnected = 'Disconnected'; out.disconnected = 'Disconnected';
out.synchronizing = 'Synchronizing'; out.synchronizing = 'Synchronizing';
out.reconnecting = 'Reconnecting...'; out.reconnecting = 'Reconnecting...';
out.typing = "Typing"; out.typing = "Editing";
out.initializing = "Initializing..."; out.initializing = "Initializing...";
out.forgotten = 'Moved to the trash'; out.forgotten = 'Moved to the trash';
out.errorState = 'Critical error: {0}';
out.lag = 'Lag'; out.lag = 'Lag';
out.readonly = 'Read only'; out.readonly = 'Read only';
out.anonymous = "Anonymous"; out.anonymous = "Anonymous";
@ -58,6 +59,7 @@ define(function () {
out.viewers = "viewers"; out.viewers = "viewers";
out.editor = "editor"; out.editor = "editor";
out.editors = "editors"; out.editors = "editors";
out.userlist_offline = "You're currently offline, the user list is not available.";
out.language = "Language"; out.language = "Language";
@ -491,6 +493,13 @@ define(function () {
out.settings_resetTipsButton = "Reset the available tips in CryptDrive"; out.settings_resetTipsButton = "Reset the available tips in CryptDrive";
out.settings_resetTipsDone = "All the tips are now visible again."; out.settings_resetTipsDone = "All the tips are now visible again.";
out.settings_thumbnails = "Thumbnails";
out.settings_disableThumbnailsAction = "Disable thumbnails creation in your CryptDrive";
out.settings_disableThumbnailsDescription = "Thumbnails are automatically created and stored in your browser when you visit a new pad. You can disable this feature here.";
out.settings_resetThumbnailsAction = "Clean";
out.settings_resetThumbnailsDescription = "Clean all the pads thumbnails stored in your browser.";
out.settings_resetThumbnailsDone = "All the thumbnails have been erased.";
out.settings_importTitle = "Import this browser's recent pads in your CryptDrive"; out.settings_importTitle = "Import this browser's recent pads in your CryptDrive";
out.settings_import = "Import"; out.settings_import = "Import";
out.settings_importConfirm = "Are you sure you want to import recent pads from this browser to your user account's CryptDrive?"; out.settings_importConfirm = "Are you sure you want to import recent pads from this browser to your user account's CryptDrive?";

View File

@ -7,7 +7,6 @@ define([
'/common/sframe-common.js', '/common/sframe-common.js',
'/common/sframe-app-framework.js', '/common/sframe-app-framework.js',
'/common/common-util.js', '/common/common-util.js',
'/common/common-thumbnail.js',
'/common/modes.js', '/common/modes.js',
'cm/lib/codemirror', 'cm/lib/codemirror',
@ -46,7 +45,6 @@ define([
SFCommon, SFCommon,
Framework, Framework,
Util, Util,
Thumb,
Modes, Modes,
CMeditor) CMeditor)
{ {

View File

@ -1,11 +1,17 @@
define([ define([
'/common/common-util.js',
'/common/visible.js',
'/common/common-hash.js',
'/file/file-crypto.js',
'/bower_components/localforage/dist/localforage.min.js',
'/bower_components/tweetnacl/nacl-fast.min.js', '/bower_components/tweetnacl/nacl-fast.min.js',
], function () { ], function (Util, Visible, Hash, FileCrypto, localForage) {
var Nacl = window.nacl; var Nacl = window.nacl;
var Thumb = { var Thumb = {
dimension: 100, dimension: 100,
padDimension: 200, padDimension: 200,
UPDATE_INTERVAL: 5000 UPDATE_INTERVAL: 60000,
UPDATE_FIRST: 5000
}; };
var supportedTypes = [ var supportedTypes = [
@ -189,5 +195,82 @@ define([
require(['/bower_components/html2canvas/build/html2canvas.min.js'], todo); require(['/bower_components/html2canvas/build/html2canvas.min.js'], todo);
}; };
Thumb.initPadThumbnails = function (opts) {
if (!opts.href || !opts.getContent) {
throw new Error("href and getContent are needed for thumbnails");
}
var oldThumbnailState;
var mkThumbnail = function () {
var content = opts.getContent();
if (content === oldThumbnailState) { return; }
Thumb.fromDOM(opts, function (err, b64) {
oldThumbnailState = content;
Thumb.setPadThumbnail(opts.href, b64);
});
};
var nafa = Util.notAgainForAnother(mkThumbnail, Thumb.UPDATE_INTERVAL);
var to;
var tUntil;
var interval = function () {
tUntil = nafa();
if (tUntil) {
window.clearTimeout(to);
to = window.setTimeout(interval, tUntil+1);
return;
}
to = window.setTimeout(interval, Thumb.UPDATE_INTERVAL+1);
};
Visible.onChange(function (v) {
if (v) {
window.clearTimeout(to);
return;
}
interval();
});
if (!Visible.currently()) { to = window.setTimeout(interval, Thumb.UPDATE_FIRST); }
};
var addThumbnail = function (err, thumb, $span, cb) {
var img = new Image();
img.src = thumb.slice(0,5) === 'data:' ? thumb : 'data:;base64,'+thumb;
$span.find('.cp-icon').hide();
$span.prepend(img);
cb($(img));
};
Thumb.setPadThumbnail = function (href, b64, cb) {
cb = cb || function () {};
var k ='thumbnail-' + href;
localForage.setItem(k, b64, cb);
};
Thumb.displayThumbnail = function (href, $container, cb) {
cb = cb || function () {};
var parsed = Hash.parsePadUrl(href);
var k ='thumbnail-' + href;
var whenNewThumb = function () {
var secret = Hash.getSecrets('file', parsed.hash);
var hexFileName = Util.base64ToHex(secret.channel);
var src = Hash.getBlobPathFromHex(hexFileName);
var cryptKey = secret.keys && secret.keys.fileKeyStr;
var key = Nacl.util.decodeBase64(cryptKey);
FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) {
if (!metadata.thumbnail) {
return void localForage.setItem(k, 'EMPTY');
}
localForage.setItem(k, metadata.thumbnail, function (err) {
addThumbnail(err, metadata.thumbnail, $container, cb);
});
});
};
localForage.getItem(k, function (err, v) {
if (!v && parsed.type === 'file') {
// We can only create thumbnails for files here since we can't easily decrypt pads
return void whenNewThumb();
}
if (!v) { return; }
if (v === 'EMPTY') { return; }
addThumbnail(err, v, $container, cb);
});
};
return Thumb; return Thumb;
}); });

View File

@ -129,6 +129,7 @@ define([
var domFromHTML = function (html) { var domFromHTML = function (html) {
var Dom = new DOMParser().parseFromString(html, "text/html"); var Dom = new DOMParser().parseFromString(html, "text/html");
Dom.normalize();
removeForbiddenTags(Dom.body); removeForbiddenTags(Dom.body);
removeListeners(Dom.body); removeListeners(Dom.body);
return Dom; return Dom;
@ -179,6 +180,7 @@ define([
var $div = $('<div>', {id: id}).append(safe_newHtmlFixed); var $div = $('<div>', {id: id}).append(safe_newHtmlFixed);
var Dom = domFromHTML($('<div>').append($div).html()); var Dom = domFromHTML($('<div>').append($div).html());
$content[0].normalize();
var oldDom = domFromHTML($content[0].outerHTML); var oldDom = domFromHTML($content[0].outerHTML);
var patch = makeDiff(oldDom, Dom, id); var patch = makeDiff(oldDom, Dom, id);
if (typeof(patch) === 'string') { if (typeof(patch) === 'string') {

View File

@ -8,7 +8,6 @@ define([
'/common/cryptpad-common.js', '/common/cryptpad-common.js',
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'/common/sframe-common.js', '/common/sframe-common.js',
'/common/sframe-common-interface.js',
'/customize/messages.js', '/customize/messages.js',
'/common/common-util.js', '/common/common-util.js',
'/common/common-thumbnail.js', '/common/common-thumbnail.js',
@ -27,7 +26,6 @@ define([
Cryptpad, Cryptpad,
nThen, nThen,
SFCommon, SFCommon,
SFUI,
Messages, Messages,
Util, Util,
Thumb, Thumb,
@ -268,24 +266,18 @@ define([
Cryptpad.removeLoadingScreen(emitResize); Cryptpad.removeLoadingScreen(emitResize);
if (options.thumbnail) { var privateDat = cpNfInner.metadataMgr.getPrivateData();
var oldThumbnailState; if (options.thumbnail && privateDat.thumbnails) {
var privateDat = cpNfInner.metadataMgr.getPrivateData();
var hash = privateDat.availableHashes.editHash || var hash = privateDat.availableHashes.editHash ||
privateDat.availableHashes.viewHash; privateDat.availableHashes.viewHash;
var href = privateDat.pathname + '#' + hash; if (hash) {
var mkThumbnail = function () { options.thumbnail.href = privateDat.pathname + '#' + hash;
if (!hash) { return; } options.thumbnail.getContent = function () {
if (state !== STATE.READY) { return; } if (!cpNfInner.chainpad) { return; }
if (!cpNfInner.chainpad) { return; } return cpNfInner.chainpad.getUserDoc();
var content = cpNfInner.chainpad.getUserDoc(); };
if (content === oldThumbnailState) { return; } Thumb.initPadThumbnails(options.thumbnail);
Thumb.fromDOM(options.thumbnail, function (err, b64) { }
oldThumbnailState = content;
SFUI.setPadThumbnail(href, b64);
});
};
window.setInterval(mkThumbnail, Thumb.UPDATE_INTERVAL);
} }
if (newPad) { if (newPad) {
@ -422,6 +414,7 @@ define([
textPatcher = TextPatcher.create({ realtime: cpNfInner.chainpad }); textPatcher = TextPatcher.create({ realtime: cpNfInner.chainpad });
var infiniteSpinnerModal = false;
window.setInterval(function () { window.setInterval(function () {
if (state === STATE.DISCONNECTED) { return; } if (state === STATE.DISCONNECTED) { return; }
var l; var l;
@ -432,13 +425,15 @@ define([
} }
if (l.lag < badStateTimeout) { return; } if (l.lag < badStateTimeout) { return; }
if (state === STATE.INFINITE_SPINNER) { return; } if (infiniteSpinnerModal) { return; }
infiniteSpinnerModal = true;
stateChange(STATE.INFINITE_SPINNER); stateChange(STATE.INFINITE_SPINNER);
Cryptpad.confirm(Messages.realtime_unrecoverableError, function (yes) { Cryptpad.confirm(Messages.realtime_unrecoverableError, function (yes) {
if (!yes) { return; } if (!yes) { return; }
common.gotoURL(); common.gotoURL();
}); });
cpNfInner.chainpad.onSettle(function () { cpNfInner.chainpad.onSettle(function () {
infiniteSpinnerModal = false;
Cryptpad.findCancelButton().click(); Cryptpad.findCancelButton().click();
stateChange(STATE.READY); stateChange(STATE.READY);
onRemote(); onRemote();

View File

@ -3,16 +3,13 @@ define([
'/api/config', '/api/config',
'/common/cryptpad-common.js', '/common/cryptpad-common.js',
'/common/common-util.js', '/common/common-util.js',
'/common/common-hash.js',
'/common/media-tag.js', '/common/media-tag.js',
'/common/tippy.min.js', '/common/tippy.min.js',
'/customize/application_config.js', '/customize/application_config.js',
'/file/file-crypto.js',
'/bower_components/localforage/dist/localforage.min.js',
'/bower_components/tweetnacl/nacl-fast.min.js', '/bower_components/tweetnacl/nacl-fast.min.js',
'css!/common/tippy.css', 'css!/common/tippy.css',
], function ($, Config, Cryptpad, Util, Hash, MediaTag, Tippy, AppConfig, FileCrypto, localForage) { ], function ($, Config, Cryptpad, Util, MediaTag, Tippy, AppConfig) {
var UI = {}; var UI = {};
var Messages = Cryptpad.Messages; var Messages = Cryptpad.Messages;
var Nacl = window.nacl; var Nacl = window.nacl;
@ -33,49 +30,6 @@ define([
* - createDropdown * - createDropdown
*/ */
var addThumbnail = function (err, thumb, $span, cb) {
var img = new Image();
img.src = thumb.slice(0,5) === 'data:' ? thumb : 'data:;base64,'+thumb;
$span.find('.cp-icon').hide();
$span.prepend(img);
cb($(img));
};
UI.setPadThumbnail = function (href, b64, cb) {
cb = cb || $.noop;
var k ='thumbnail-' + href;
localForage.setItem(k, b64, cb);
};
localForage.removeItem('thumbnail-/1/edit/lqg6RRnynI76LV0sR8F0YA/Nh1SNXxB5U2UjaADvODfvI5l/');
UI.displayThumbnail = function (href, $container, cb) {
cb = cb || $.noop;
var parsed = Hash.parsePadUrl(href);
var k ='thumbnail-' + href;
var whenNewThumb = function () {
var secret = Hash.getSecrets('file', parsed.hash);
var hexFileName = Util.base64ToHex(secret.channel);
var src = Hash.getBlobPathFromHex(hexFileName);
var cryptKey = secret.keys && secret.keys.fileKeyStr;
var key = Nacl.util.decodeBase64(cryptKey);
FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) {
if (!metadata.thumbnail) {
return void localForage.setItem(k, 'EMPTY');
}
localForage.setItem(k, metadata.thumbnail, function (err) {
addThumbnail(err, metadata.thumbnail, $container, cb);
});
});
};
localForage.getItem(k, function (err, v) {
if (!v && parsed.type === 'file') {
// We can only create thumbnails for files here since we can't easily decrypt pads
return void whenNewThumb();
}
if (!v) { return; }
if (v === 'EMPTY') { return; }
addThumbnail(err, v, $container, cb);
});
};
UI.updateTags = function (common, href) { UI.updateTags = function (common, href) {
var sframeChan = common.getSframeChannel(); var sframeChan = common.getSframeChannel();
sframeChan.query('Q_TAGS_GET', href || null, function (err, res) { sframeChan.query('Q_TAGS_GET', href || null, function (err, res) {
@ -339,6 +293,7 @@ define([
$img.attr('src', src); $img.attr('src', src);
$img.attr('data-crypto-key', 'cryptpad:' + cryptKey); $img.attr('data-crypto-key', 'cryptpad:' + cryptKey);
UI.displayMediatagImage(Common, $img, function (err, $image, img) { UI.displayMediatagImage(Common, $img, function (err, $image, img) {
if (err) { return void console.error(err); }
var w = img.width; var w = img.width;
var h = img.height; var h = img.height;
if (w>h) { if (w>h) {

View File

@ -130,6 +130,7 @@ define([
settings: proxy.settings || {}, settings: proxy.settings || {},
isPresent: parsed.hashData && parsed.hashData.present, isPresent: parsed.hashData && parsed.hashData.present,
isEmbed: parsed.hashData && parsed.hashData.embed, isEmbed: parsed.hashData && parsed.hashData.embed,
thumbnails: !((proxy.settings || {}).general || {}).disableThumbnails,
accounts: { accounts: {
donateURL: Cryptpad.donateURL, donateURL: Cryptpad.donateURL,
upgradeURL: Cryptpad.upgradeURL upgradeURL: Cryptpad.upgradeURL

View File

@ -14,7 +14,8 @@ define([
'/customize/application_config.js', '/customize/application_config.js',
'/common/cryptpad-common.js', '/common/cryptpad-common.js',
'/common/common-realtime.js', '/common/common-realtime.js',
'/common/common-util.js' '/common/common-util.js',
'/common/common-thumbnail.js'
], function ( ], function (
$, $,
nThen, nThen,
@ -30,7 +31,8 @@ define([
AppConfig, AppConfig,
Cryptpad, Cryptpad,
CommonRealtime, CommonRealtime,
Util Util,
Thumb
) { ) {
// Chainpad Netflux Inner // Chainpad Netflux Inner
@ -80,7 +82,9 @@ define([
funcs.createButton = callWithCommon(UI.createButton); funcs.createButton = callWithCommon(UI.createButton);
funcs.createUsageBar = callWithCommon(UI.createUsageBar); funcs.createUsageBar = callWithCommon(UI.createUsageBar);
funcs.updateTags = callWithCommon(UI.updateTags); funcs.updateTags = callWithCommon(UI.updateTags);
funcs.displayThumbnail = UI.displayThumbnail;
// Thumb
funcs.displayThumbnail = Thumb.displayThumbnail;
// History // History
funcs.getHistory = callWithCommon(History.create); funcs.getHistory = callWithCommon(History.create);

View File

@ -168,4 +168,8 @@ define({
// in the drive at registration. // in the drive at registration.
'Q_MERGE_ANON_DRIVE': true, 'Q_MERGE_ANON_DRIVE': true,
// Add or remove the avatar from the profile.
// We have to pin/unpin the avatar and store/remove the value from the user object
'Q_PROFILE_AVATAR_ADD': true,
'Q_PROFILE_AVATAR_REMOVE': true
}); });

View File

@ -154,6 +154,7 @@ define([
var $userlistContent = toolbar.userlistContent; var $userlistContent = toolbar.userlistContent;
var metadataMgr = config.metadataMgr; var metadataMgr = config.metadataMgr;
var online = metadataMgr.isConnected();
var userData = metadataMgr.getMetadata().users; var userData = metadataMgr.getMetadata().users;
var viewers = metadataMgr.getViewers(); var viewers = metadataMgr.getViewers();
var priv = metadataMgr.getPrivateData(); var priv = metadataMgr.getPrivateData();
@ -184,7 +185,23 @@ define([
// Update the userlist // Update the userlist
var $editUsers = $userlistContent.find('.' + USERLIST_CLS).html(''); var $editUsers = $userlistContent.find('.' + USERLIST_CLS).html('');
var $editUsersList = $('<div>', {'class': 'cp-toolbar-userlist-others'}); var $editUsersList = $('<div>', {'class': 'cp-toolbar-userlist-others'})
.appendTo($editUsers);
if (!online) {
$('<em>').text(Messages.userlist_offline).appendTo($editUsersList);
numberOfEditUsers = '?';
numberOfViewUsers = '?';
}
// Update the buttons
var fa_editusers = '<span class="fa fa-users"></span>';
var fa_viewusers = '<span class="fa fa-eye"></span>';
var $spansmall = $('<span>').html(fa_editusers + ' ' + numberOfEditUsers + '&nbsp;&nbsp; ' + fa_viewusers + ' ' + numberOfViewUsers);
$userButtons.find('.cp-dropdown-button-title').html('').append($spansmall);
if (!online) { return; }
// Display the userlist
// Editors // Editors
var pendingFriends = Common.getPendingFriends(); var pendingFriends = Common.getPendingFriends();
@ -237,7 +254,6 @@ define([
$span.data('uid', data.uid); $span.data('uid', data.uid);
$editUsersList.append($span); $editUsersList.append($span);
}); });
$editUsers.append($editUsersList);
// Viewers // Viewers
if (numberOfViewUsers > 0) { if (numberOfViewUsers > 0) {
@ -246,12 +262,6 @@ define([
viewText += numberOfViewUsers + ' ' + viewerText + '</div>'; viewText += numberOfViewUsers + ' ' + viewerText + '</div>';
$editUsers.append(viewText); $editUsers.append(viewText);
} }
// Update the buttons
var fa_editusers = '<span class="fa fa-users"></span>';
var fa_viewusers = '<span class="fa fa-eye"></span>';
var $spansmall = $('<span>').html(fa_editusers + ' ' + numberOfEditUsers + '&nbsp;&nbsp; ' + fa_viewusers + ' ' + numberOfViewUsers);
$userButtons.find('.cp-dropdown-button-title').html('').append($spansmall);
}; };
var initUserList = function (toolbar, config) { var initUserList = function (toolbar, config) {
@ -691,6 +701,7 @@ define([
var typing = -1; var typing = -1;
var kickSpinner = function (toolbar, config/*, local*/) { var kickSpinner = function (toolbar, config/*, local*/) {
if (!toolbar.spinner) { return; } if (!toolbar.spinner) { return; }
if (toolbar.isErrorState) { return; }
var $spin = toolbar.spinner; var $spin = toolbar.spinner;
if (typing === -1) { if (typing === -1) {
@ -1050,6 +1061,17 @@ define([
} }
}; };
toolbar.errorState = function (state, error) {
toolbar.isErrorState = state;
if (toolbar.spinner) {
if (!state) {
return void kickSpinner(toolbar, config);
}
var txt = Messages._getKey('errorState', [error]);
toolbar.spinner.text(txt);
}
};
// When the pad is moved to the trash (forget button) // When the pad is moved to the trash (forget button)
toolbar.forgotten = function (/*userId*/) { toolbar.forgotten = function (/*userId*/) {
toolbar.connected = false; toolbar.connected = false;

View File

@ -47,6 +47,11 @@
align-items: center; align-items: center;
img {
max-width: 100px;
max-height: 100px;
}
.cp-filepicker-content-element-name { .cp-filepicker-content-element-name {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;

20
www/oldprofile/index.html Normal file
View File

@ -0,0 +1,20 @@
<!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>

532
www/oldprofile/main.js Normal file
View File

@ -0,0 +1,532 @@
require.config({
paths: {
cm: '/bower_components/codemirror'
}
});
define([
'jquery',
'/common/cryptpad-common.js',
'/bower_components/chainpad-listmap/chainpad-listmap.js',
'/bower_components/chainpad-crypto/crypto.js',
'/bower_components/marked/marked.min.js',
'/common/toolbar2.js',
'cm/lib/codemirror',
'cm/mode/markdown/markdown',
'less!/profile/main.less',
'less!/customize/src/less/toolbar.less',
'less!/customize/src/less/cryptpad.less',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
], function ($, Cryptpad, Listmap, Crypto, Marked, Toolbar, CodeMirror) {
var APP = window.APP = {
Cryptpad: Cryptpad,
_onRefresh: []
};
$(window.document).on('decryption', function (e) {
var decrypted = e.originalEvent;
if (decrypted.callback) { decrypted.callback(); }
})
.on('decryptionError', function (e) {
var error = e.originalEvent;
Cryptpad.alert(error.message);
});
// Marked
var renderer = new Marked.Renderer();
Marked.setOptions({
renderer: renderer,
sanitize: true
});
// Tasks list
var checkedTaskItemPtn = /^\s*\[x\]\s*/;
var uncheckedTaskItemPtn = /^\s*\[ \]\s*/;
renderer.listitem = function (text) {
var isCheckedTaskItem = checkedTaskItemPtn.test(text);
var isUncheckedTaskItem = uncheckedTaskItemPtn.test(text);
if (isCheckedTaskItem) {
text = text.replace(checkedTaskItemPtn,
'<i class="fa fa-check-square" aria-hidden="true"></i>&nbsp;') + '\n';
}
if (isUncheckedTaskItem) {
text = text.replace(uncheckedTaskItemPtn,
'<i class="fa fa-square-o" aria-hidden="true"></i>&nbsp;') + '\n';
}
var cls = (isCheckedTaskItem || isUncheckedTaskItem) ? ' class="todo-list-item"' : '';
return '<li'+ cls + '>' + text + '</li>\n';
};
/*renderer.image = function (href, title, text) {
if (href.slice(0,6) === '/file/') {
var parsed = Cryptpad.parsePadUrl(href);
var hexFileName = Cryptpad.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '">';
mt += '</media-tag>';
return mt;
}
var out = '<img src="' + href + '" alt="' + text + '"';
if (title) {
out += ' title="' + title + '"';
}
out += this.options.xhtml ? '/>' : '>';
return out;
};*/
var Messages = Cryptpad.Messages;
var DISPLAYNAME_ID = "displayName";
var LINK_ID = "link";
var AVATAR_ID = "avatar";
var DESCRIPTION_ID = "description";
var PUBKEY_ID = "pubKey";
var CREATE_ID = "createProfile";
var HEADER_ID = "header";
var HEADER_RIGHT_ID = "rightside";
var CREATE_INVITE_BUTTON = 'inviteButton'; /* jshint ignore: line */
var VIEW_PROFILE_BUTTON = 'viewProfileButton';
var createEditableInput = function ($block, name, ph, getValue, setValue, realtime, fallbackValue) {
fallbackValue = fallbackValue || ''; // don't ever display 'null' or 'undefined'
var lastVal;
getValue(function (value) {
lastVal = value;
var $input = $('<input>', {
'id': name+'Input',
placeholder: ph
}).val(value);
var $icon = $('<span>', {'class': 'fa fa-pencil edit'});
var editing = false;
var todo = function () {
if (editing) { return; }
editing = true;
var newVal = $input.val().trim();
if (newVal === lastVal) {
editing = false;
return;
}
setValue(newVal, function (err) {
if (err) { return void console.error(err); }
Cryptpad.whenRealtimeSyncs(realtime, function () {
lastVal = newVal;
Cryptpad.log(Messages._getKey('profile_fieldSaved', [newVal || fallbackValue]));
editing = false;
});
});
};
$input.on('keyup', function (e) {
if (e.which === 13) { return void todo(); }
if (e.which === 27) {
$input.val(lastVal);
}
});
$icon.click(function () { $input.focus(); });
$input.focus(function () {
$input.width('');
});
$input.focusout(todo);
$block.append($input).append($icon);
});
};
/* jshint ignore:start */
var isFriend = function (proxy, edKey) {
var friends = Cryptpad.find(proxy, ['friends']);
return typeof(edKey) === 'string' && friends && (edKey in friends);
};
var addCreateInviteLinkButton = function ($container) {
return;
var obj = APP.lm.proxy;
var proxy = Cryptpad.getProxy();
var userViewHash = Cryptpad.find(proxy, ['profile', 'view']);
var edKey = obj.edKey;
var curveKey = obj.curveKey;
if (!APP.readOnly || !curveKey || !edKey || userViewHash === window.location.hash.slice(1) || isFriend(proxy, edKey)) {
//console.log("edit mode or missing curve key, or you're viewing your own profile");
return;
}
// sanitize user inputs
var unsafeName = obj.name || '';
console.log(unsafeName);
var name = Cryptpad.fixHTML(unsafeName) || Messages.anonymous;
console.log(name);
console.log("Creating invite button");
$("<button>", {
id: CREATE_INVITE_BUTTON,
title: Messages.profile_inviteButtonTitle,
})
.addClass('btn btn-success')
.text(Messages.profile_inviteButton)
.click(function () {
Cryptpad.confirm(Messages._getKey('profile_inviteExplanation', [name]), function (yes) {
if (!yes) { return; }
console.log(obj.curveKey);
Cryptpad.alert("TODO");
// TODO create a listmap object using your curve keys
// TODO fill the listmap object with your invite data
// TODO generate link to invite object
// TODO copy invite link to clipboard
}, null, true);
})
.appendTo($container);
};
/* jshint ignore:end */
var addViewButton = function ($container) {
if (!Cryptpad.isLoggedIn() || window.location.hash) {
return;
}
var hash = Cryptpad.find(Cryptpad.getProxy(), ['profile', 'view']);
var url = '/profile/#' + hash;
var $button = $('<button>', {
'class': 'btn btn-success',
id: VIEW_PROFILE_BUTTON,
})
.text(Messages.profile_viewMyProfile)
.click(function () {
window.open(url, '_blank');
});
$container.append($button);
};
var addDisplayName = function ($container) {
var $block = $('<div>', {id: DISPLAYNAME_ID}).appendTo($container);
var getValue = function (cb) {
cb(APP.lm.proxy.name);
};
var placeholder = Messages.profile_namePlaceholder;
if (APP.readOnly) {
var $span = $('<span>', {'class': DISPLAYNAME_ID}).appendTo($block);
getValue(function (value) {
$span.text(value || Messages.anonymous);
});
//addCreateInviteLinkButton($block);
return;
}
var setValue = function (value, cb) {
APP.lm.proxy.name = value;
cb();
};
var rt = Cryptpad.getStore().getProxy().info.realtime;
createEditableInput($block, DISPLAYNAME_ID, placeholder, getValue, setValue, rt, Messages.anonymous);
};
var addLink = function ($container) {
var $block = $('<div>', {id: LINK_ID}).appendTo($container);
var getValue = function (cb) {
cb(APP.lm.proxy.url);
};
if (APP.readOnly) {
var $a = $('<a>', {
'class': LINK_ID,
target: '_blank',
rel: 'noreferrer noopener'
}).appendTo($block);
getValue(function (value) {
if (!value) {
return void $a.hide();
}
$a.attr('href', value).text(value);
});
return;
}
var setValue = function (value, cb) {
APP.lm.proxy.url = value;
cb();
};
var rt = APP.lm.realtime;
var placeholder = Messages.profile_urlPlaceholder;
createEditableInput($block, LINK_ID, placeholder, getValue, setValue, rt);
};
var addAvatar = function ($container) {
var $block = $('<div>', {id: AVATAR_ID}).appendTo($container);
var $span = $('<span>').appendTo($block);
var allowedMediaTypes = Cryptpad.avatarAllowedTypes;
var displayAvatar = function () {
$span.html('');
if (!APP.lm.proxy.avatar) {
$('<img>', {
src: '/customize/images/avatar.png',
title: Messages.profile_avatar,
alt: 'Avatar'
}).appendTo($span);
return;
}
Cryptpad.displayAvatar($span, APP.lm.proxy.avatar);
if (APP.readOnly) { return; }
var $delButton = $('<button>', {
'class': 'delete btn btn-danger fa fa-times',
title: Messages.fc_delete
});
$span.append($delButton);
$delButton.click(function () {
var oldChanId = Cryptpad.hrefToHexChannelId(APP.lm.proxy.avatar);
Cryptpad.unpinPads([oldChanId], function (e) {
if (e) { Cryptpad.log(e); }
delete APP.lm.proxy.avatar;
delete Cryptpad.getProxy().profile.avatar;
Cryptpad.whenRealtimeSyncs(APP.lm.realtime, function () {
var driveRt = Cryptpad.getStore().getProxy().info.realtime;
Cryptpad.whenRealtimeSyncs(driveRt, function () {
displayAvatar();
});
});
});
});
};
displayAvatar();
if (APP.readOnly) { return; }
var fmConfig = {
noHandlers: true,
noStore: true,
body: $('body'),
onUploaded: function (ev, data) {
var chanId = Cryptpad.hrefToHexChannelId(data.url);
var profile = Cryptpad.getProxy().profile;
var old = profile.avatar;
var todo = function () {
Cryptpad.pinPads([chanId], function (e) {
if (e) { return void Cryptpad.log(e); }
APP.lm.proxy.avatar = data.url;
Cryptpad.getProxy().profile.avatar = data.url;
Cryptpad.whenRealtimeSyncs(APP.lm.realtime, function () {
var driveRt = Cryptpad.getStore().getProxy().info.realtime;
Cryptpad.whenRealtimeSyncs(driveRt, function () {
displayAvatar();
});
});
});
};
if (old) {
var oldChanId = Cryptpad.hrefToHexChannelId(old);
Cryptpad.unpinPads([oldChanId], function (e) {
if (e) { Cryptpad.log(e); }
todo();
});
return;
}
todo();
}
};
APP.FM = Cryptpad.createFileManager(fmConfig);
var data = {
FM: APP.FM,
filter: function (file) {
var sizeMB = Cryptpad.bytesToMegabytes(file.size);
var type = file.type;
return sizeMB <= 0.5 && allowedMediaTypes.indexOf(type) !== -1;
},
accept: ".gif,.jpg,.jpeg,.png"
};
var $upButton = Cryptpad.createButton('upload', false, data);
$upButton.text(Messages.profile_upload);
$upButton.prepend($('<span>', {'class': 'fa fa-upload'}));
$block.append($upButton);
};
var addDescription = function ($container) {
var $block = $('<div>', {id: DESCRIPTION_ID}).appendTo($container);
if (APP.readOnly) {
if (!(APP.lm.proxy.description || "").trim()) { return void $block.hide(); }
var $div = $('<div>', {'class': 'rendered'}).appendTo($block);
var val = Marked(APP.lm.proxy.description);
$div.html(val);
return;
}
$('<h3>').text(Messages.profile_description).insertBefore($block);
var $ok = $('<span>', {'class': 'ok fa fa-check', title: Messages.saved}).appendTo($block);
var $spinner = $('<span>', {'class': 'spin fa fa-spinner fa-pulse'}).appendTo($block);
var $textarea = $('<textarea>').val(APP.lm.proxy.description || '');
$block.append($textarea);
var editor = APP.editor = CodeMirror.fromTextArea($textarea[0], {
lineNumbers: true,
lineWrapping: true,
styleActiveLine : true,
mode: "markdown",
});
var onLocal = function () {
$ok.hide();
$spinner.show();
var val = editor.getValue();
APP.lm.proxy.description = val;
Cryptpad.whenRealtimeSyncs(APP.lm.realtime, function () {
$ok.show();
$spinner.hide();
});
};
editor.on('change', onLocal);
};
var addPublicKey = function ($container) {
var $block = $('<div>', {id: PUBKEY_ID});
$container.append($block);
};
var createLeftside = function () {
var $categories = $('<div>', {'class': 'categories'}).appendTo(APP.$leftside);
APP.$usage = $('<div>', {'class': 'usage'}).appendTo(APP.$leftside);
var $category = $('<div>', {'class': 'category'}).appendTo($categories);
$category.append($('<span>', {'class': 'fa fa-user'}));
$category.addClass('active');
$category.append(Messages.profileButton);
};
var createToolbar = function () {
var displayed = ['useradmin', 'newpad', 'limit', 'upgrade', 'pageTitle'];
var configTb = {
displayed: displayed,
ifrw: window,
common: Cryptpad,
$container: APP.$toolbar,
pageTitle: Messages.profileButton
};
var toolbar = APP.toolbar = Toolbar.create(configTb);
toolbar.$rightside.html(''); // Remove the drawer if we don't use it to hide the toolbar
};
var onReady = function () {
APP.$container.find('#'+CREATE_ID).remove();
var obj = APP.lm && APP.lm.proxy;
if (!APP.readOnly) {
var pubKeys = Cryptpad.getPublicKeys();
if (pubKeys && pubKeys.curve) {
obj.curveKey = pubKeys.curve;
obj.edKey = pubKeys.ed;
}
}
if (!APP.initialized) {
var $header = $('<div>', {id: HEADER_ID}).appendTo(APP.$rightside);
addAvatar($header);
var $rightside = $('<div>', {id: HEADER_RIGHT_ID}).appendTo($header);
addDisplayName($rightside);
addLink($rightside);
addDescription(APP.$rightside);
addViewButton(APP.$rightside); //$rightside);
addPublicKey(APP.$rightside);
APP.initialized = true;
createLeftside();
}
Cryptpad.removeLoadingScreen();
};
var onInit = function () {
};
var onDisconnect = function () {};
var onChange = function () {};
var andThen = function (profileHash) {
var secret = Cryptpad.getSecrets('profile', profileHash);
var readOnly = APP.readOnly = secret.keys && !secret.keys.editKeyStr;
var listmapConfig = {
data: {},
websocketURL: Cryptpad.getWebsocketURL(),
channel: secret.channel,
readOnly: readOnly,
validateKey: secret.keys.validateKey || undefined,
crypto: Crypto.createEncryptor(secret.keys),
userName: 'profile',
logLevel: 1,
};
var lm = APP.lm = Listmap.create(listmapConfig);
lm.proxy.on('create', onInit)
.on('ready', onReady)
.on('disconnect', onDisconnect)
.on('change', [], onChange);
};
var getOrCreateProfile = function () {
var obj = Cryptpad.getStore().getProxy().proxy;
if (obj.profile && obj.profile.view && obj.profile.edit) {
return void andThen(obj.profile.edit);
}
// If the user doesn't have a public profile, ask them if they want to create one
var todo = function () {
var secret = Cryptpad.getSecrets();
obj.profile = {};
var channel = Cryptpad.createChannelId();
Cryptpad.pinPads([channel], function (e) {
if (e) {
if (e === 'E_OVER_LIMIT') {
Cryptpad.alert(Messages.pinLimitNotPinned, null, true);
}
return void Cryptpad.log(Messages._getKey('profile_error', [e]));
}
obj.profile.edit = Cryptpad.getEditHashFromKeys(channel, secret.keys);
obj.profile.view = Cryptpad.getViewHashFromKeys(channel, secret.keys);
andThen(obj.profile.edit);
});
};
Cryptpad.removeLoadingScreen();
if (!Cryptpad.isLoggedIn()) {
var $p = $('<p>', {id: CREATE_ID}).append(Messages.profile_register);
var $a = $('<a>', {
href: '/register/'
});
$('<button>', {
'class': 'btn btn-success',
}).text(Messages.login_register).appendTo($a);
$p.append($('<br>')).append($a);
APP.$rightside.append($p);
return;
}
// make an empty profile for the user on their first visit
todo();
};
var onCryptpadReady = function () {
APP.$leftside = $('<div>', {id: 'leftSide'}).appendTo(APP.$container);
APP.$rightside = $('<div>', {id: 'rightSide'}).appendTo(APP.$container);
createToolbar();
if (window.location.hash) {
return void andThen(window.location.hash.slice(1));
}
getOrCreateProfile();
};
$(function () {
$(window).click(function () {
$('.cp-dropdown-content').hide();
});
APP.$container = $('#container');
APP.$toolbar = $('#toolbar');
Cryptpad.ready(function () {
Cryptpad.reportAppUsage();
onCryptpadReady();
});
});
});

View File

@ -557,6 +557,7 @@ define([
getContainer: function () { return $('iframe').contents().find('html')[0]; }, getContainer: function () { return $('iframe').contents().find('html')[0]; },
filter: function (el, before) { filter: function (el, before) {
if (before) { if (before) {
module.cursor.update();
$(el).parents().css('overflow', 'visible'); $(el).parents().css('overflow', 'visible');
$(el).css('max-width', '1200px'); $(el).css('max-width', '1200px');
$(el).css('max-height', Math.max(600, $(el).width()) + 'px'); $(el).css('max-height', Math.max(600, $(el).width()) + 'px');
@ -569,6 +570,9 @@ define([
$(el).css('max-height', ''); $(el).css('max-height', '');
$(el).css('overflow', ''); $(el).css('overflow', '');
$(el).find('body').css('background-color', '#fff'); $(el).find('body').css('background-color', '#fff');
var sel = module.cursor.makeSelection();
var range = module.cursor.makeRange();
module.cursor.fixSelection(sel, range);
} }
} }
}, waitFor(function (fw) { window.APP.framework = framework = fw; })); }, waitFor(function (fw) { window.APP.framework = framework = fw; }));

View File

@ -22,6 +22,7 @@ define([
'cm/mode/markdown/markdown', 'cm/mode/markdown/markdown',
'css!cm/lib/codemirror.css', 'css!cm/lib/codemirror.css',
'/bower_components/file-saver/FileSaver.min.js', '/bower_components/file-saver/FileSaver.min.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
@ -686,6 +687,8 @@ define([
var getCommentId = Render.Uid('c'); var getCommentId = Render.Uid('c');
var removeComment = function (uid) { var removeComment = function (uid) {
var idx = APP.proxy.commentsOrder.indexOf(uid);
if (idx !== -1) { APP.proxy.commentsOrder.splice(idx, 1); }
delete APP.proxy.comments[uid]; delete APP.proxy.comments[uid];
APP.updateComments(); APP.updateComments();
}; };
@ -695,6 +698,7 @@ define([
var avatars = {}; var avatars = {};
var updateComments = APP.updateComments = function () { var updateComments = APP.updateComments = function () {
if (!APP.proxy.comments) { APP.proxy.comments = {}; } if (!APP.proxy.comments) { APP.proxy.comments = {}; }
if (!APP.proxy.commentsOrder) { APP.proxy.commentsOrder = []; }
var profile; var profile;
if (common.isLoggedIn()) { if (common.isLoggedIn()) {
@ -703,10 +707,9 @@ define([
var $comments = APP.$comments.html(''); var $comments = APP.$comments.html('');
var comments = APP.proxy.comments; var comments = APP.proxy.comments;
Object.keys(copyObject(comments)).sort(function (a, b) { APP.proxy.commentsOrder.forEach(function (k) {
return comments[a].time > comments[b].time;
}).forEach(function (k) {
var c = comments[k]; var c = comments[k];
if (!c) { return; }
var name = c.name || Messages.anonymous; var name = c.name || Messages.anonymous;
var $c = $('<div>', { var $c = $('<div>', {
'class': 'cp-app-poll-comments-list-el' 'class': 'cp-app-poll-comments-list-el'
@ -770,6 +773,7 @@ define([
}; };
var addComment = function () { var addComment = function () {
if (!APP.proxy.comments) { APP.proxy.comments = {}; } if (!APP.proxy.comments) { APP.proxy.comments = {}; }
if (!APP.proxy.commentsOrder) { APP.proxy.commentsOrder = []; }
var name = APP.$addComment.find('.cp-app-poll-comments-add-name').val().trim(); var name = APP.$addComment.find('.cp-app-poll-comments-add-name').val().trim();
var msg = APP.$addComment.find('.cp-app-poll-comments-add-msg').val().trim(); var msg = APP.$addComment.find('.cp-app-poll-comments-add-msg').val().trim();
var time = +new Date(); var time = +new Date();
@ -783,6 +787,7 @@ define([
} }
var uid = getCommentId(); var uid = getCommentId();
APP.proxy.commentsOrder.push(uid);
APP.proxy.comments[uid] = { APP.proxy.comments[uid] = {
msg: msg, msg: msg,
name: name, name: name,
@ -797,16 +802,20 @@ define([
var initThumbnails = function () { var initThumbnails = function () {
var oldThumbnailState; var oldThumbnailState;
var privateDat = metadataMgr.getPrivateData(); var privateDat = metadataMgr.getPrivateData();
if (!privateDat.thumbnails) { return; } // Thumbnails are disabled
var hash = privateDat.availableHashes.editHash || var hash = privateDat.availableHashes.editHash ||
privateDat.availableHashes.viewHash; privateDat.availableHashes.viewHash;
if (!hash) { return; }
var href = privateDat.pathname + '#' + hash; var href = privateDat.pathname + '#' + hash;
var $el = $('.cp-app-poll-realtime'); var $el = $('.cp-app-poll-realtime');
//var $el = $('#cp-app-poll-table'); //var $el = $('#cp-app-poll-table');
var scrollTop;
var options = { var options = {
getContainer: function () { return $el[0]; }, getContainer: function () { return $el[0]; },
filter: function (el, before) { filter: function (el, before) {
if (before) { if (before) {
$el.parents().css('overflow', 'visible'); $el.parents().css('overflow', 'visible');
scrollTop = $('#cp-app-poll-form').scrollTop();
$el.css('max-height', Math.max(600, $(el).width()) + 'px'); $el.css('max-height', Math.max(600, $(el).width()) + 'px');
$el.find('tr td:first-child, tr td:last-child, tr td:nth-last-child(2)') $el.find('tr td:first-child, tr td:last-child, tr td:nth-last-child(2)')
.css('position', 'static'); .css('position', 'static');
@ -824,19 +833,12 @@ define([
$el.find('#cp-app-poll-table-scroll').css('max-width', ''); $el.find('#cp-app-poll-table-scroll').css('max-width', '');
$el.find('tr td:first-child, tr td:last-child, tr td:nth-last-child(2)') $el.find('tr td:first-child, tr td:last-child, tr td:nth-last-child(2)')
.css('position', ''); .css('position', '');
} $('#cp-app-poll-form').scrollTop(scrollTop);
},
href: href,
getContent: function () { return JSON.stringify(APP.proxy.content); }
}; };
var mkThumbnail = function () { Thumb.initPadThumbnails(options);
if (!hash) { return; }
if (!APP.proxy) { return; }
var content = JSON.stringify(APP.proxy.content);
if (content === oldThumbnailState) { return; }
Thumb.fromDOM(options, function (err, b64) {
oldThumbnailState = content;
SFUI.setPadThumbnail(href, b64);
});
};
window.setInterval(mkThumbnail, Thumb.UPDATE_INTERVAL);
}; };
var checkDeletedCells = function () { var checkDeletedCells = function () {
@ -859,17 +861,23 @@ define([
if (!isNew) { if (!isNew) {
if (proxy.info) { if (proxy.info) {
// Migration?? // Migration
proxy.metadata = proxy.info; proxy.metadata = proxy.info;
delete proxy.info; delete proxy.info;
} }
if (proxy.table) { if (proxy.table) {
// Migration?? // Migration
proxy.content = proxy.table; proxy.content = proxy.table;
delete proxy.table; delete proxy.table;
} }
checkDeletedCells(); checkDeletedCells();
if (proxy.comments && !proxy.commentsOrder) { // Migration
proxy.commentsOrder = Object.keys(copyObject(proxy.comments)).sort(function (a, b) {
return proxy.comments[a].time > proxy.comments[b].time;
});
}
if (proxy && proxy.metadata) { if (proxy && proxy.metadata) {
metadataMgr.updateMetadata(proxy.metadata); metadataMgr.updateMetadata(proxy.metadata);
} }
@ -1199,6 +1207,17 @@ define([
mode: "markdown", mode: "markdown",
}); });
APP.$descriptionPublished.click(function (e) {
if (!e.target) { return; }
var $t = $(e.target);
if ($t.is('a') || $t.parents('a').length) {
e.preventDefault();
var $a = $t.is('a') ? $t : $t.parents('a').first();
var href = $a.attr('href');
if (!href) { return; }
window.open(href);
}
});
var listmapConfig = { var listmapConfig = {
data: {}, data: {},

View File

@ -0,0 +1,160 @@
@import (once) "../../customize/src/less2/include/browser.less";
@import (once) "../../customize/src/less2/include/toolbar.less";
@import (once) "../../customize/src/less2/include/markdown.less";
@import (once) '../../customize/src/less2/include/fileupload.less';
@import (once) '../../customize/src/less2/include/alertify.less';
//@import (once) '../../customize/src/less/mixins.less';
//@import (once) '../../customize/src/less/variables.less";
@import (once) '../../customize/src/less2/include/avatar.less';
@import (once) '../../customize/src/less2/include/sidebar-layout.less';
.toolbar_main();
.fileupload_main();
.alertify_main();
.sidebar-layout_main();
// body
&.cp-app-profile {
display: flex;
flex-flow: column;
#cp-app-profile-header {
display: flex;
#cp-app-profile-rightside {
flex: 1;
display: flex;
flex-flow: column;
}
}
#cp-app-profile-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: 4px;
overflow: hidden;
position: relative;
.cp-app-profile-avatar-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: 1;
}
}
button {
height: 40px;
margin: 5px;
}
}
#cp-app-profile-displayname, #cp-app-profile-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;
}
.cp-app-profile-input-edit {
position: absolute;
margin-left: -25px;
margin-top: 8px;
}
.cp-app-profile-input-temp {
font-weight: 400;
font-family: sans-serif;
}
.cp-app-profile-displayname {
font-weight: bold;
font-size: 30px;
}
.cp-app-profile-link {
font-size: 25px;
}
.cp-app-profile-displayname, .cp-app-profile-link {
line-height: 40px;
}
}
// I tried using flexbox but messed with how the pencil icon was displayed
#cp-app-profile-invite-button {
float: right;
}
#cp-app-profile-viewprofile-button {
margin-bottom: 20px;
float: right;
}
#cp-app-profile-description {
position: relative;
font-size: 16px;
border: 1px solid #DDD;
margin-bottom: 20px;
.cp-app-profile-description-rendered {
padding: 0 15px;
}
.cp-app-profile-description-ok, .cp-app-profile-description-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;
}
}
}
#cp-app-profile-create {
height: 100%;
display: flex;
flex-flow: column;
align-items: center;
justify-content: center;
}
}

View File

@ -1,20 +1,38 @@
<!DOCTYPE html> <!DOCTYPE html>
<html class="cp"> <html>
<!-- If this file is not called customize.dist/src/template.html, it is generated -->
<head> <head>
<title data-localization="main_title">CryptPad: Zero Knowledge, Collaborative Real Time Editing</title> <title>CryptPad</title>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/> <meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/png" href="/customize/main-favicon.png" id="favicon"/> <meta name="referrer" content="no-referrer" />
<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> <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>
<style>
<link rel="stylesheet" href="/bower_components/codemirror/lib/codemirror.css"> html, body {
<link rel="stylesheet" href="/bower_components/codemirror/addon/dialog/dialog.css"> margin: 0px;
<link rel="stylesheet" href="/bower_components/codemirror/addon/fold/foldgutter.css" /> padding: 0px;
}
#sbox-iframe {
position:fixed;
top:0px;
left:0px;
bottom:0px;
right:0px;
width:100%;
height:100%;
border:none;
margin:0;
padding:0;
overflow:hidden;
}
#sbox-filePicker-iframe {
position: fixed;
top:0; left:0;
bottom:0; right:0;
width:100%;
height: 100%;
border: 0;
}
</style>
</head> </head>
<body class="html"> <body>
<noscript> <iframe id="sbox-iframe">
<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>

18
www/profile/inner.html Normal file
View 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="/profile/inner.js" data-main="/common/sframe-boot.js?ver=1.4" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden { display: none; }
</style>
</head>
<body class="cp-app-profile">
<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>

479
www/profile/inner.js Normal file
View File

@ -0,0 +1,479 @@
define([
'jquery',
'/bower_components/chainpad-crypto/crypto.js',
'/common/sframe-chainpad-listmap.js',
'/common/toolbar3.js',
'/common/cryptpad-common.js',
'/bower_components/nthen/index.js',
'/common/sframe-common.js',
'/bower_components/marked/marked.min.js',
'cm/lib/codemirror',
'cm/mode/markdown/markdown',
'css!/bower_components/codemirror/lib/codemirror.css',
'css!/bower_components/codemirror/addon/dialog/dialog.css',
'css!/bower_components/codemirror/addon/fold/foldgutter.css',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less2/main.less',
], function (
$,
Crypto,
Listmap,
Toolbar,
Cryptpad,
nThen,
SFCommon,
Marked,
CodeMirror
)
{
var Messages = Cryptpad.Messages;
var APP = window.APP = {
Cryptpad: Cryptpad,
_onRefresh: []
};
var onConnectError = function () {
Cryptpad.errorLoadingScreen(Messages.websocketError);
};
// Decryption event for avatar mediatag (TODO not needed anymore?)
$(window.document).on('decryption', function (e) {
var decrypted = e.originalEvent;
if (decrypted.callback) { decrypted.callback(); }
})
.on('decryptionError', function (e) {
var error = e.originalEvent;
Cryptpad.alert(error.message);
});
$(window).click(function () {
$('.cp-dropdown-content').hide();
});
// Marked
var renderer = new Marked.Renderer();
Marked.setOptions({
renderer: renderer,
sanitize: true
});
// Tasks list
var checkedTaskItemPtn = /^\s*\[x\]\s*/;
var uncheckedTaskItemPtn = /^\s*\[ \]\s*/;
renderer.listitem = function (text) {
var isCheckedTaskItem = checkedTaskItemPtn.test(text);
var isUncheckedTaskItem = uncheckedTaskItemPtn.test(text);
if (isCheckedTaskItem) {
text = text.replace(checkedTaskItemPtn,
'<i class="fa fa-check-square" aria-hidden="true"></i>&nbsp;') + '\n';
}
if (isUncheckedTaskItem) {
text = text.replace(uncheckedTaskItemPtn,
'<i class="fa fa-square-o" aria-hidden="true"></i>&nbsp;') + '\n';
}
var cls = (isCheckedTaskItem || isUncheckedTaskItem) ? ' class="todo-list-item"' : '';
return '<li'+ cls + '>' + text + '</li>\n';
};
var DISPLAYNAME_ID = "cp-app-profile-displayname";
var LINK_ID = "cp-app-profile-link";
var AVATAR_ID = "cp-app-profile-avatar";
var DESCRIPTION_ID = "cp-app-profile-description";
var PUBKEY_ID = "cp-app-profile-pubkey";
var CREATE_ID = "cp-app-profile-create";
var HEADER_ID = "cp-app-profile-header";
var HEADER_RIGHT_ID = "cp-app-profile-rightside";
var CREATE_INVITE_BUTTON = 'cp-app-profile-invite-button'; /* jshint ignore: line */
var VIEW_PROFILE_BUTTON = 'cp-app-profile-viewprofile-button';
var common;
var sFrameChan;
var createEditableInput = function ($block, name, ph, getValue, setValue, fallbackValue) {
fallbackValue = fallbackValue || ''; // don't ever display 'null' or 'undefined'
var lastVal;
getValue(function (value) {
lastVal = value;
var $input = $('<input>', {
'id': name+'Input',
placeholder: ph
}).val(value);
var $icon = $('<span>', {'class': 'fa fa-pencil cp-app-profile-input-edit'});
var editing = false;
var todo = function () {
if (editing) { return; }
editing = true;
var newVal = $input.val().trim();
if (newVal === lastVal) {
editing = false;
return;
}
setValue(newVal, function (err) {
if (err) { return void console.error(err); }
lastVal = newVal;
Cryptpad.log(Messages._getKey('profile_fieldSaved', [newVal || fallbackValue]));
editing = false;
});
};
$input.on('keyup', function (e) {
if (e.which === 13) { return void todo(); }
if (e.which === 27) {
$input.val(lastVal);
}
});
$icon.click(function () { $input.focus(); });
$input.focus(function () {
$input.width('');
});
$input.focusout(todo);
$block.append($input).append($icon);
});
};
/* jshint ignore:start */
var isFriend = function (proxy, edKey) {
var friends = Cryptpad.find(proxy, ['friends']);
return typeof(edKey) === 'string' && friends && (edKey in friends);
};
var addCreateInviteLinkButton = function ($container) {
return;
var obj = APP.lm.proxy;
var proxy = Cryptpad.getProxy();
var userViewHash = Cryptpad.find(proxy, ['profile', 'view']);
var edKey = obj.edKey;
var curveKey = obj.curveKey;
if (!APP.readOnly || !curveKey || !edKey || userViewHash === window.location.hash.slice(1) || isFriend(proxy, edKey)) {
//console.log("edit mode or missing curve key, or you're viewing your own profile");
return;
}
// sanitize user inputs
var unsafeName = obj.name || '';
console.log(unsafeName);
var name = Cryptpad.fixHTML(unsafeName) || Messages.anonymous;
console.log(name);
console.log("Creating invite button");
$("<button>", {
id: CREATE_INVITE_BUTTON,
title: Messages.profile_inviteButtonTitle,
})
.addClass('btn btn-success')
.text(Messages.profile_inviteButton)
.click(function () {
Cryptpad.confirm(Messages._getKey('profile_inviteExplanation', [name]), function (yes) {
if (!yes) { return; }
console.log(obj.curveKey);
Cryptpad.alert("TODO");
// TODO create a listmap object using your curve keys
// TODO fill the listmap object with your invite data
// TODO generate link to invite object
// TODO copy invite link to clipboard
}, null, true);
})
.appendTo($container);
};
/* jshint ignore:end */
var addViewButton = function ($container) {
if (APP.readOnly) {
return;
}
var hash = common.getMetadataMgr().getPrivateData().availableHashes.viewHash;
var url = APP.origin + '/profile/#' + hash;
var $button = $('<button>', {
'class': 'btn btn-success',
id: VIEW_PROFILE_BUTTON,
})
.text(Messages.profile_viewMyProfile)
.click(function () {
window.open(url, '_blank');
});
$container.append($button);
};
var addDisplayName = function ($container) {
var $block = $('<div>', {id: DISPLAYNAME_ID}).appendTo($container);
var getValue = function (cb) {
cb(APP.lm.proxy.name);
};
var placeholder = Messages.profile_namePlaceholder;
if (APP.readOnly) {
var $span = $('<span>', {'class': DISPLAYNAME_ID}).appendTo($block);
getValue(function (value) {
$span.text(value || Messages.anonymous);
});
//addCreateInviteLinkButton($block);
return;
}
var setValue = function (value, cb) {
APP.lm.proxy.name = value;
Cryptpad.whenRealtimeSyncs(APP.lm.realtime, cb);
};
createEditableInput($block, DISPLAYNAME_ID, placeholder, getValue, setValue, Messages.anonymous);
};
var addLink = function ($container) {
var $block = $('<div>', {id: LINK_ID}).appendTo($container);
var getValue = function (cb) {
cb(APP.lm.proxy.url);
};
if (APP.readOnly) {
var $a = $('<a>', {
'class': LINK_ID,
target: '_blank',
rel: 'noreferrer noopener'
}).appendTo($block);
getValue(function (value) {
if (!value) {
return void $a.hide();
}
$a.attr('href', value).text(value);
});
return;
}
var setValue = function (value, cb) {
APP.lm.proxy.url = value;
Cryptpad.whenRealtimeSyncs(APP.lm.realtime, cb);
};
var placeholder = Messages.profile_urlPlaceholder;
createEditableInput($block, LINK_ID, placeholder, getValue, setValue);
};
var addAvatar = function ($container) {
var $block = $('<div>', {id: AVATAR_ID}).appendTo($container);
var $span = $('<span>').appendTo($block);
var allowedMediaTypes = Cryptpad.avatarAllowedTypes;
var sframeChan = common.getSframeChannel();
var displayAvatar = function () {
$span.html('');
if (!APP.lm.proxy.avatar) {
$('<img>', {
src: '/customize/images/avatar.png',
title: Messages.profile_avatar,
alt: 'Avatar'
}).appendTo($span);
return;
}
common.displayAvatar($span, APP.lm.proxy.avatar);
if (APP.readOnly) { return; }
var $delButton = $('<button>', {
'class': 'cp-app-profile-avatar-delete btn btn-danger fa fa-times',
title: Messages.fc_delete
});
$span.append($delButton);
$delButton.click(function () {
var old = common.getMetadataMgr().getUserData().avatar;
sframeChan.query("Q_PROFILE_AVATAR_REMOVE", old, function (err, err2) {
if (err || err2) { return void Cryptpad.log(err || err2); }
delete APP.lm.proxy.avatar;
displayAvatar();
});
});
};
displayAvatar();
if (APP.readOnly) { return; }
var fmConfig = {
noHandlers: true,
noStore: true,
body: $('body'),
onUploaded: function (ev, data) {
var old = common.getMetadataMgr().getUserData().avatar;
var todo = function () {
sframeChan.query("Q_PROFILE_AVATAR_ADD", data.url, function (err, err2) {
if (err || err2) { return void Cryptpad.log(err || err2); }
APP.lm.proxy.avatar = data.url;
displayAvatar();
});
};
if (old) {
sframeChan.query("Q_PROFILE_AVATAR_REMOVE", old, function (err, err2) {
if (err || err2) { return void Cryptpad.log(err || err2); }
todo();
});
return;
}
todo();
}
};
APP.FM = common.createFileManager(fmConfig);
var data = {
FM: APP.FM,
filter: function (file) {
var sizeMB = Cryptpad.bytesToMegabytes(file.size);
var type = file.type;
return sizeMB <= 0.5 && allowedMediaTypes.indexOf(type) !== -1;
},
accept: ".gif,.jpg,.jpeg,.png"
};
var $upButton = common.createButton('upload', false, data);
$upButton.text(Messages.profile_upload);
$upButton.prepend($('<span>', {'class': 'fa fa-upload'}));
$block.append($upButton);
};
var addDescription = function ($container) {
var $block = $('<div>', {id: DESCRIPTION_ID}).appendTo($container);
if (APP.readOnly) {
if (!(APP.lm.proxy.description || "").trim()) { return void $block.hide(); }
var $div = $('<div>', {'class': 'cp-app-profile-description-rendered'}).appendTo($block);
var val = Marked(APP.lm.proxy.description);
$div.html(val);
return;
}
$('<h3>').text(Messages.profile_description).insertBefore($block);
var $ok = $('<span>', {
'class': 'cp-app-profile-description-ok fa fa-check',
title: Messages.saved
}).appendTo($block);
var $spinner = $('<span>', {
'class': 'cp-app-profile-description-spin fa fa-spinner fa-pulse'
}).appendTo($block);
var $textarea = $('<textarea>').val(APP.lm.proxy.description || '');
$block.append($textarea);
var editor = APP.editor = CodeMirror.fromTextArea($textarea[0], {
lineNumbers: true,
lineWrapping: true,
styleActiveLine : true,
mode: "markdown",
});
var onLocal = function () {
$ok.hide();
$spinner.show();
var val = editor.getValue();
APP.lm.proxy.description = val;
Cryptpad.whenRealtimeSyncs(APP.lm.realtime, function () {
$ok.show();
$spinner.hide();
});
};
editor.on('change', onLocal);
};
var addPublicKey = function ($container) {
var $block = $('<div>', {id: PUBKEY_ID});
$container.append($block);
};
var createLeftside = function () {
var $categories = $('<div>', {'class': 'cp-sidebarlayout-categories'}).appendTo(APP.$leftside);
var $category = $('<div>', {'class': 'cp-sidebarlayout-category'}).appendTo($categories);
$category.append($('<span>', {'class': 'fa fa-user'}));
$category.addClass('cp-leftside-active');
$category.append(Messages.profileButton);
};
var onReady = function () {
APP.$container.find('#'+CREATE_ID).remove();
var obj = APP.lm && APP.lm.proxy;
if (!APP.readOnly) {
var pubKeys = Cryptpad.getPublicKeys();
if (pubKeys && pubKeys.curve) {
obj.curveKey = pubKeys.curve;
obj.edKey = pubKeys.ed;
}
}
if (!APP.initialized) {
var $header = $('<div>', {id: HEADER_ID}).appendTo(APP.$rightside);
addAvatar($header);
var $rightside = $('<div>', {id: HEADER_RIGHT_ID}).appendTo($header);
addDisplayName($rightside);
addLink($rightside);
addDescription(APP.$rightside);
addViewButton(APP.$rightside);
addPublicKey(APP.$rightside);
APP.initialized = true;
createLeftside();
}
Cryptpad.removeLoadingScreen();
};
var createToolbar = function () {
var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle'];
var configTb = {
displayed: displayed,
common: Cryptpad,
sfCommon: common,
$container: APP.$toolbar,
pageTitle: Messages.profileButton,
metadataMgr: common.getMetadataMgr(),
};
APP.toolbar = Toolbar.create(configTb);
APP.toolbar.$rightside.hide();
};
nThen(function (waitFor) {
$(waitFor(Cryptpad.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*/) {
Cryptpad.onError(function (info) {
if (info && info.type === "store") {
onConnectError();
}
});
createToolbar();
var metadataMgr = common.getMetadataMgr();
var privateData = metadataMgr.getPrivateData();
APP.origin = privateData.origin;
APP.readOnly = privateData.readOnly;
// If not logged in, you can only view other users's profile
if (!privateData.readOnly && !common.isLoggedIn()) {
Cryptpad.removeLoadingScreen();
var $p = $('<p>', {id: CREATE_ID}).append(Messages.profile_register);
var $a = $('<a>', {
href: APP.origin + '/register/'
});
$('<button>', {
'class': 'btn btn-success',
}).text(Messages.login_register).appendTo($a);
$p.append($('<br>')).append($a);
APP.$rightside.append($p);
return;
}
var listmapConfig = {
data: {},
common: common,
userName: 'profile',
logLevel: 1
};
var lm = APP.lm = Listmap.create(listmapConfig);
lm.proxy.on('ready', onReady);
});
});

View File

@ -1,532 +1,95 @@
require.config({ // Load #1, load as little as possible because we are in a race to get the loading screen up.
paths: {
cm: '/bower_components/codemirror'
}
});
define([ define([
'/bower_components/nthen/index.js',
'/api/config',
'jquery', 'jquery',
'/common/cryptpad-common.js', '/common/requireconfig.js',
'/bower_components/chainpad-listmap/chainpad-listmap.js', '/common/sframe-common-outer.js'
'/bower_components/chainpad-crypto/crypto.js', ], function (nThen, ApiConfig, $, RequireConfig, SFCommonO) {
'/bower_components/marked/marked.min.js', var requireConfig = RequireConfig();
'/common/toolbar2.js',
'cm/lib/codemirror',
'cm/mode/markdown/markdown',
'less!/profile/main.less',
'less!/customize/src/less/toolbar.less',
'less!/customize/src/less/cryptpad.less',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
], function ($, Cryptpad, Listmap, Crypto, Marked, Toolbar, CodeMirror) {
var APP = window.APP = { // Loaded in load #2
Cryptpad: Cryptpad, nThen(function (waitFor) {
_onRefresh: [] $(waitFor());
}; }).nThen(function (waitFor) {
var req = {
$(window.document).on('decryption', function (e) { cfg: requireConfig,
var decrypted = e.originalEvent; req: [ '/common/loading.js' ],
if (decrypted.callback) { decrypted.callback(); } pfx: window.location.origin
})
.on('decryptionError', function (e) {
var error = e.originalEvent;
Cryptpad.alert(error.message);
});
// Marked
var renderer = new Marked.Renderer();
Marked.setOptions({
renderer: renderer,
sanitize: true
});
// Tasks list
var checkedTaskItemPtn = /^\s*\[x\]\s*/;
var uncheckedTaskItemPtn = /^\s*\[ \]\s*/;
renderer.listitem = function (text) {
var isCheckedTaskItem = checkedTaskItemPtn.test(text);
var isUncheckedTaskItem = uncheckedTaskItemPtn.test(text);
if (isCheckedTaskItem) {
text = text.replace(checkedTaskItemPtn,
'<i class="fa fa-check-square" aria-hidden="true"></i>&nbsp;') + '\n';
}
if (isUncheckedTaskItem) {
text = text.replace(uncheckedTaskItemPtn,
'<i class="fa fa-square-o" aria-hidden="true"></i>&nbsp;') + '\n';
}
var cls = (isCheckedTaskItem || isUncheckedTaskItem) ? ' class="todo-list-item"' : '';
return '<li'+ cls + '>' + text + '</li>\n';
};
/*renderer.image = function (href, title, text) {
if (href.slice(0,6) === '/file/') {
var parsed = Cryptpad.parsePadUrl(href);
var hexFileName = Cryptpad.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '">';
mt += '</media-tag>';
return mt;
}
var out = '<img src="' + href + '" alt="' + text + '"';
if (title) {
out += ' title="' + title + '"';
}
out += this.options.xhtml ? '/>' : '>';
return out;
};*/
var Messages = Cryptpad.Messages;
var DISPLAYNAME_ID = "displayName";
var LINK_ID = "link";
var AVATAR_ID = "avatar";
var DESCRIPTION_ID = "description";
var PUBKEY_ID = "pubKey";
var CREATE_ID = "createProfile";
var HEADER_ID = "header";
var HEADER_RIGHT_ID = "rightside";
var CREATE_INVITE_BUTTON = 'inviteButton'; /* jshint ignore: line */
var VIEW_PROFILE_BUTTON = 'viewProfileButton';
var createEditableInput = function ($block, name, ph, getValue, setValue, realtime, fallbackValue) {
fallbackValue = fallbackValue || ''; // don't ever display 'null' or 'undefined'
var lastVal;
getValue(function (value) {
lastVal = value;
var $input = $('<input>', {
'id': name+'Input',
placeholder: ph
}).val(value);
var $icon = $('<span>', {'class': 'fa fa-pencil edit'});
var editing = false;
var todo = function () {
if (editing) { return; }
editing = true;
var newVal = $input.val().trim();
if (newVal === lastVal) {
editing = false;
return;
}
setValue(newVal, function (err) {
if (err) { return void console.error(err); }
Cryptpad.whenRealtimeSyncs(realtime, function () {
lastVal = newVal;
Cryptpad.log(Messages._getKey('profile_fieldSaved', [newVal || fallbackValue]));
editing = false;
});
});
};
$input.on('keyup', function (e) {
if (e.which === 13) { return void todo(); }
if (e.which === 27) {
$input.val(lastVal);
}
});
$icon.click(function () { $input.focus(); });
$input.focus(function () {
$input.width('');
});
$input.focusout(todo);
$block.append($input).append($icon);
});
};
/* jshint ignore:start */
var isFriend = function (proxy, edKey) {
var friends = Cryptpad.find(proxy, ['friends']);
return typeof(edKey) === 'string' && friends && (edKey in friends);
};
var addCreateInviteLinkButton = function ($container) {
return;
var obj = APP.lm.proxy;
var proxy = Cryptpad.getProxy();
var userViewHash = Cryptpad.find(proxy, ['profile', 'view']);
var edKey = obj.edKey;
var curveKey = obj.curveKey;
if (!APP.readOnly || !curveKey || !edKey || userViewHash === window.location.hash.slice(1) || isFriend(proxy, edKey)) {
//console.log("edit mode or missing curve key, or you're viewing your own profile");
return;
}
// sanitize user inputs
var unsafeName = obj.name || '';
console.log(unsafeName);
var name = Cryptpad.fixHTML(unsafeName) || Messages.anonymous;
console.log(name);
console.log("Creating invite button");
$("<button>", {
id: CREATE_INVITE_BUTTON,
title: Messages.profile_inviteButtonTitle,
})
.addClass('btn btn-success')
.text(Messages.profile_inviteButton)
.click(function () {
Cryptpad.confirm(Messages._getKey('profile_inviteExplanation', [name]), function (yes) {
if (!yes) { return; }
console.log(obj.curveKey);
Cryptpad.alert("TODO");
// TODO create a listmap object using your curve keys
// TODO fill the listmap object with your invite data
// TODO generate link to invite object
// TODO copy invite link to clipboard
}, null, true);
})
.appendTo($container);
};
/* jshint ignore:end */
var addViewButton = function ($container) {
if (!Cryptpad.isLoggedIn() || window.location.hash) {
return;
}
var hash = Cryptpad.find(Cryptpad.getProxy(), ['profile', 'view']);
var url = '/profile/#' + hash;
var $button = $('<button>', {
'class': 'btn btn-success',
id: VIEW_PROFILE_BUTTON,
})
.text(Messages.profile_viewMyProfile)
.click(function () {
window.open(url, '_blank');
});
$container.append($button);
};
var addDisplayName = function ($container) {
var $block = $('<div>', {id: DISPLAYNAME_ID}).appendTo($container);
var getValue = function (cb) {
cb(APP.lm.proxy.name);
}; };
var placeholder = Messages.profile_namePlaceholder; window.rc = requireConfig;
if (APP.readOnly) { window.apiconf = ApiConfig;
var $span = $('<span>', {'class': DISPLAYNAME_ID}).appendTo($block); $('#sbox-iframe').attr('src',
getValue(function (value) { ApiConfig.httpSafeOrigin + '/profile/inner.html?' + requireConfig.urlArgs +
$span.text(value || Messages.anonymous); '#' + encodeURIComponent(JSON.stringify(req)));
});
//addCreateInviteLinkButton($block); // This is a cheap trick to avoid loading sframe-channel in parallel with the
return; // loading screen setup.
} var done = waitFor();
var setValue = function (value, cb) { var onMsg = function (msg) {
APP.lm.proxy.name = value; var data = JSON.parse(msg.data);
cb(); if (data.q !== 'READY') { return; }
window.removeEventListener('message', onMsg);
var _done = done;
done = function () { };
_done();
}; };
var rt = Cryptpad.getStore().getProxy().info.realtime; window.addEventListener('message', onMsg);
createEditableInput($block, DISPLAYNAME_ID, placeholder, getValue, setValue, rt, Messages.anonymous); }).nThen(function (/*waitFor*/) {
}; var getSecrets = function (Cryptpad) {
// 1st case: visiting someone else's profile with hash in the URL
var addLink = function ($container) { if (window.location.hash) {
var $block = $('<div>', {id: LINK_ID}).appendTo($container); return Cryptpad.getSecrets('profile', window.location.hash.slice(1));
var getValue = function (cb) {
cb(APP.lm.proxy.url);
};
if (APP.readOnly) {
var $a = $('<a>', {
'class': LINK_ID,
target: '_blank',
rel: 'noreferrer noopener'
}).appendTo($block);
getValue(function (value) {
if (!value) {
return void $a.hide();
}
$a.attr('href', value).text(value);
});
return;
}
var setValue = function (value, cb) {
APP.lm.proxy.url = value;
cb();
};
var rt = APP.lm.realtime;
var placeholder = Messages.profile_urlPlaceholder;
createEditableInput($block, LINK_ID, placeholder, getValue, setValue, rt);
};
var addAvatar = function ($container) {
var $block = $('<div>', {id: AVATAR_ID}).appendTo($container);
var $span = $('<span>').appendTo($block);
var allowedMediaTypes = Cryptpad.avatarAllowedTypes;
var displayAvatar = function () {
$span.html('');
if (!APP.lm.proxy.avatar) {
$('<img>', {
src: '/customize/images/avatar.png',
title: Messages.profile_avatar,
alt: 'Avatar'
}).appendTo($span);
return;
} }
Cryptpad.displayAvatar($span, APP.lm.proxy.avatar); // 2nd case: visiting our own existing profile
var obj = Cryptpad.getProxy();
if (APP.readOnly) { return; } if (obj.profile && obj.profile.view && obj.profile.edit) {
return Cryptpad.getSecrets('profile', obj.profile.edit);
var $delButton = $('<button>', {
'class': 'delete btn btn-danger fa fa-times',
title: Messages.fc_delete
});
$span.append($delButton);
$delButton.click(function () {
var oldChanId = Cryptpad.hrefToHexChannelId(APP.lm.proxy.avatar);
Cryptpad.unpinPads([oldChanId], function (e) {
if (e) { Cryptpad.log(e); }
delete APP.lm.proxy.avatar;
delete Cryptpad.getProxy().profile.avatar;
Cryptpad.whenRealtimeSyncs(APP.lm.realtime, function () {
var driveRt = Cryptpad.getStore().getProxy().info.realtime;
Cryptpad.whenRealtimeSyncs(driveRt, function () {
displayAvatar();
});
});
});
});
};
displayAvatar();
if (APP.readOnly) { return; }
var fmConfig = {
noHandlers: true,
noStore: true,
body: $('body'),
onUploaded: function (ev, data) {
var chanId = Cryptpad.hrefToHexChannelId(data.url);
var profile = Cryptpad.getProxy().profile;
var old = profile.avatar;
var todo = function () {
Cryptpad.pinPads([chanId], function (e) {
if (e) { return void Cryptpad.log(e); }
APP.lm.proxy.avatar = data.url;
Cryptpad.getProxy().profile.avatar = data.url;
Cryptpad.whenRealtimeSyncs(APP.lm.realtime, function () {
var driveRt = Cryptpad.getStore().getProxy().info.realtime;
Cryptpad.whenRealtimeSyncs(driveRt, function () {
displayAvatar();
});
});
});
};
if (old) {
var oldChanId = Cryptpad.hrefToHexChannelId(old);
Cryptpad.unpinPads([oldChanId], function (e) {
if (e) { Cryptpad.log(e); }
todo();
});
return;
}
todo();
} }
}; // 3rd case: profile creation (create a new random hash, store it later if needed)
APP.FM = Cryptpad.createFileManager(fmConfig); if (!Cryptpad.isLoggedIn()) { return; }
var data = { var hash = Cryptpad.createRandomHash();
FM: APP.FM, var secret = Cryptpad.getSecrets('profile', hash);
filter: function (file) { Cryptpad.pinPads([secret.channel], function (e) {
var sizeMB = Cryptpad.bytesToMegabytes(file.size);
var type = file.type;
return sizeMB <= 0.5 && allowedMediaTypes.indexOf(type) !== -1;
},
accept: ".gif,.jpg,.jpeg,.png"
};
var $upButton = Cryptpad.createButton('upload', false, data);
$upButton.text(Messages.profile_upload);
$upButton.prepend($('<span>', {'class': 'fa fa-upload'}));
$block.append($upButton);
};
var addDescription = function ($container) {
var $block = $('<div>', {id: DESCRIPTION_ID}).appendTo($container);
if (APP.readOnly) {
if (!(APP.lm.proxy.description || "").trim()) { return void $block.hide(); }
var $div = $('<div>', {'class': 'rendered'}).appendTo($block);
var val = Marked(APP.lm.proxy.description);
$div.html(val);
return;
}
$('<h3>').text(Messages.profile_description).insertBefore($block);
var $ok = $('<span>', {'class': 'ok fa fa-check', title: Messages.saved}).appendTo($block);
var $spinner = $('<span>', {'class': 'spin fa fa-spinner fa-pulse'}).appendTo($block);
var $textarea = $('<textarea>').val(APP.lm.proxy.description || '');
$block.append($textarea);
var editor = APP.editor = CodeMirror.fromTextArea($textarea[0], {
lineNumbers: true,
lineWrapping: true,
styleActiveLine : true,
mode: "markdown",
});
var onLocal = function () {
$ok.hide();
$spinner.show();
var val = editor.getValue();
APP.lm.proxy.description = val;
Cryptpad.whenRealtimeSyncs(APP.lm.realtime, function () {
$ok.show();
$spinner.hide();
});
};
editor.on('change', onLocal);
};
var addPublicKey = function ($container) {
var $block = $('<div>', {id: PUBKEY_ID});
$container.append($block);
};
var createLeftside = function () {
var $categories = $('<div>', {'class': 'categories'}).appendTo(APP.$leftside);
APP.$usage = $('<div>', {'class': 'usage'}).appendTo(APP.$leftside);
var $category = $('<div>', {'class': 'category'}).appendTo($categories);
$category.append($('<span>', {'class': 'fa fa-user'}));
$category.addClass('active');
$category.append(Messages.profileButton);
};
var createToolbar = function () {
var displayed = ['useradmin', 'newpad', 'limit', 'upgrade', 'pageTitle'];
var configTb = {
displayed: displayed,
ifrw: window,
common: Cryptpad,
$container: APP.$toolbar,
pageTitle: Messages.profileButton
};
var toolbar = APP.toolbar = Toolbar.create(configTb);
toolbar.$rightside.html(''); // Remove the drawer if we don't use it to hide the toolbar
};
var onReady = function () {
APP.$container.find('#'+CREATE_ID).remove();
var obj = APP.lm && APP.lm.proxy;
if (!APP.readOnly) {
var pubKeys = Cryptpad.getPublicKeys();
if (pubKeys && pubKeys.curve) {
obj.curveKey = pubKeys.curve;
obj.edKey = pubKeys.ed;
}
}
if (!APP.initialized) {
var $header = $('<div>', {id: HEADER_ID}).appendTo(APP.$rightside);
addAvatar($header);
var $rightside = $('<div>', {id: HEADER_RIGHT_ID}).appendTo($header);
addDisplayName($rightside);
addLink($rightside);
addDescription(APP.$rightside);
addViewButton(APP.$rightside); //$rightside);
addPublicKey(APP.$rightside);
APP.initialized = true;
createLeftside();
}
Cryptpad.removeLoadingScreen();
};
var onInit = function () {
};
var onDisconnect = function () {};
var onChange = function () {};
var andThen = function (profileHash) {
var secret = Cryptpad.getSecrets('profile', profileHash);
var readOnly = APP.readOnly = secret.keys && !secret.keys.editKeyStr;
var listmapConfig = {
data: {},
websocketURL: Cryptpad.getWebsocketURL(),
channel: secret.channel,
readOnly: readOnly,
validateKey: secret.keys.validateKey || undefined,
crypto: Crypto.createEncryptor(secret.keys),
userName: 'profile',
logLevel: 1,
};
var lm = APP.lm = Listmap.create(listmapConfig);
lm.proxy.on('create', onInit)
.on('ready', onReady)
.on('disconnect', onDisconnect)
.on('change', [], onChange);
};
var getOrCreateProfile = function () {
var obj = Cryptpad.getStore().getProxy().proxy;
if (obj.profile && obj.profile.view && obj.profile.edit) {
return void andThen(obj.profile.edit);
}
// If the user doesn't have a public profile, ask them if they want to create one
var todo = function () {
var secret = Cryptpad.getSecrets();
obj.profile = {};
var channel = Cryptpad.createChannelId();
Cryptpad.pinPads([channel], function (e) {
if (e) { if (e) {
if (e === 'E_OVER_LIMIT') { if (e === 'E_OVER_LIMIT') {
Cryptpad.alert(Messages.pinLimitNotPinned, null, true); // TODO
} }
return void Cryptpad.log(Messages._getKey('profile_error', [e])); return;
//return void Cryptpad.log(Messages._getKey('profile_error', [e])) // TODO
} }
obj.profile.edit = Cryptpad.getEditHashFromKeys(channel, secret.keys); obj.profile = {};
obj.profile.view = Cryptpad.getViewHashFromKeys(channel, secret.keys); obj.profile.edit = Cryptpad.getEditHashFromKeys(secret.channel, secret.keys);
andThen(obj.profile.edit); obj.profile.view = Cryptpad.getViewHashFromKeys(secret.channel, secret.keys);
});
return secret;
};
var addRpc = function (sframeChan, Cryptpad) {
// Adding a new avatar from the profile: pin it and store it in the object
sframeChan.on('Q_PROFILE_AVATAR_ADD', function (data, cb) {
var chanId = Cryptpad.hrefToHexChannelId(data);
Cryptpad.pinPads([chanId], function (e) {
if (e) { return void cb(e); }
Cryptpad.getProxy().profile.avatar = data;
Cryptpad.whenRealtimeSyncs(Cryptpad.getRealtime(), function () {
cb();
});
});
});
// Removing the avatar from the profile: unpin it
sframeChan.on('Q_PROFILE_AVATAR_REMOVE', function (data, cb) {
var chanId = Cryptpad.hrefToHexChannelId(data);
Cryptpad.unpinPads([chanId], function (e) {
delete Cryptpad.getProxy().profile.avatar;
cb(e);
});
}); });
}; };
SFCommonO.start({
Cryptpad.removeLoadingScreen(); getSecrets: getSecrets,
noHash: true, // Don't add the hash in the URL if it doesn't already exist
if (!Cryptpad.isLoggedIn()) { addRpc: addRpc,
var $p = $('<p>', {id: CREATE_ID}).append(Messages.profile_register); noRealtime: !localStorage.User_hash
var $a = $('<a>', {
href: '/register/'
});
$('<button>', {
'class': 'btn btn-success',
}).text(Messages.login_register).appendTo($a);
$p.append($('<br>')).append($a);
APP.$rightside.append($p);
return;
}
// make an empty profile for the user on their first visit
todo();
};
var onCryptpadReady = function () {
APP.$leftside = $('<div>', {id: 'leftSide'}).appendTo(APP.$container);
APP.$rightside = $('<div>', {id: 'rightSide'}).appendTo(APP.$container);
createToolbar();
if (window.location.hash) {
return void andThen(window.location.hash.slice(1));
}
getOrCreateProfile();
};
$(function () {
$(window).click(function () {
$('.cp-dropdown-content').hide();
});
APP.$container = $('#container');
APP.$toolbar = $('#toolbar');
Cryptpad.ready(function () {
Cryptpad.reportAppUsage();
onCryptpadReady();
}); });
}); });
}); });

View File

@ -42,6 +42,7 @@ define([
'languageSelector', 'languageSelector',
'logoutEverywhere', 'logoutEverywhere',
'resetTips', 'resetTips',
'thumbnails',
'userFeedback' 'userFeedback'
], ],
'drive': [ 'drive': [
@ -220,6 +221,60 @@ define([
return $div; return $div;
}; };
var createThumbnails = function (obj) {
var $div = $('<div>', {'class': 'thumbnails element'});
$('<label>').text(Messages.settings_thumbnails).appendTo($div);
// Disable
$('<span>', {'class': 'description'})
.text(Messages.settings_disableThumbnailsDescription).appendTo($div);
var $label = $('<label>', { 'for': 'disableThumbnails', 'class': 'noTitle' })
.text(Messages.settings_disableThumbnailsAction);
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved});
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'});
var $checkbox = $('<input>', {
'type': 'checkbox',
id: 'disableThumbnails'
}).on('change', function () {
$spinner.show();
$ok.hide();
if (!obj.proxy.settings) { obj.proxy.settings = {}; }
if (!obj.proxy.settings.general) { obj.proxy.settings.general = {}; }
obj.proxy.settings.general.disableThumbnails = $checkbox.is(':checked') || false;
Cryptpad.whenRealtimeSyncs(obj.info.realtime, function () {
$spinner.hide();
$ok.show();
});
});
$checkbox.appendTo($div);
$label.appendTo($div);
$ok.hide().appendTo($div);
$spinner.hide().appendTo($div);
if (obj.proxy.settings && obj.proxy.settings.general
&& obj.proxy.settings.general.disableThumbnails) {
$checkbox[0].checked = true;
}
// Reset
/*$('<span>', {'class': 'description'})
.text(Messages.settings_resetThumbnailsDescription).appendTo($div);
var $button = $('<button>', {'id': 'resetThumbnails', 'class': 'btn btn-primary'})
.text(Messages.settings_resetThumbnailsAction).appendTo($div);
$button.click(function () {
// TODO we need to have access to the sandbox localforage first...
Cryptpad.alert(Messages.settings_resetThumbnailsDone);
});*/
return $div;
};
var createBackupDrive = function (store) { var createBackupDrive = function (store) {
var obj = store.proxy; var obj = store.proxy;
var $div = $('<div>', {'class': 'backupDrive element'}); var $div = $('<div>', {'class': 'backupDrive element'});
@ -467,6 +522,7 @@ define([
$rightside.append(createLogoutEverywhere(obj)); $rightside.append(createLogoutEverywhere(obj));
} }
$rightside.append(createResetTips()); $rightside.append(createResetTips());
$rightside.append(createThumbnails(obj));
$rightside.append(createBackupDrive(obj)); $rightside.append(createBackupDrive(obj));
$rightside.append(createImportLocalPads(obj)); $rightside.append(createImportLocalPads(obj));
$rightside.append(createResetDrive(obj)); $rightside.append(createResetDrive(obj));

View File

@ -5,8 +5,8 @@
.cp { .cp {
#container { #container {
#rightSide { #rightSide {
.userFeedback { .userFeedback, .thumbnails {
input { input[type="checkbox"] {
vertical-align: middle; vertical-align: middle;
margin-right: 5px; margin-right: 5px;
} }

View File

@ -402,7 +402,6 @@ define([
}); });
framework.setContentGetter(function () { framework.setContentGetter(function () {
console.log(framework._.title.title);
var content = CodeMirror.getContent(); var content = CodeMirror.getContent();
Slide.update(content.content); Slide.update(content.content);
return content; return content;

View File

@ -377,6 +377,7 @@ define([
var initThumbnails = function () { var initThumbnails = function () {
var oldThumbnailState; var oldThumbnailState;
var privateDat = metadataMgr.getPrivateData(); var privateDat = metadataMgr.getPrivateData();
if (!privateDat.thumbnails) { return; }
var hash = privateDat.availableHashes.editHash || var hash = privateDat.availableHashes.editHash ||
privateDat.availableHashes.viewHash; privateDat.availableHashes.viewHash;
var href = privateDat.pathname + '#' + hash; var href = privateDat.pathname + '#' + hash;
@ -389,10 +390,11 @@ define([
var D = Thumb.getResizedDimensions($canvas[0], 'pad'); var D = Thumb.getResizedDimensions($canvas[0], 'pad');
Thumb.fromCanvas($canvas[0], D, function (err, b64) { Thumb.fromCanvas($canvas[0], D, function (err, b64) {
oldThumbnailState = content; oldThumbnailState = content;
SFUI.setPadThumbnail(href, b64); Thumb.setPadThumbnail(href, b64);
}); });
}; };
window.setInterval(mkThumbnail, Thumb.UPDATE_INTERVAL); window.setInterval(mkThumbnail, Thumb.UPDATE_INTERVAL);
window.setTimeout(mkThumbnail, Thumb.UPDATE_FIRST);
}; };
config.onInit = function (info) { config.onInit = function (info) {