diff --git a/bower.json b/bower.json index 0cabd2b5f..2b9c05687 100644 --- a/bower.json +++ b/bower.json @@ -43,7 +43,8 @@ "nthen": "^0.1.5", "open-sans-fontface": "^1.4.2", "bootstrap-tokenfield": "^0.12.1", - "localforage": "^1.5.2" + "localforage": "^1.5.2", + "html2canvas": "^0.4.1" }, "resolutions": { "bootstrap": "v4.0.0-alpha.6" diff --git a/customize.dist/application_config.js b/customize.dist/application_config.js index 9cf29d565..079c36eab 100644 --- a/customize.dist/application_config.js +++ b/customize.dist/application_config.js @@ -12,6 +12,7 @@ define(function() { * You can change their duration here (measured in milliseconds) */ config.notificationTimeout = 5000; + config.disableUserlistNotifications = false; config.enablePinning = true; diff --git a/customize.dist/pages.js b/customize.dist/pages.js index 793f2133e..cc11f1be2 100644 --- a/customize.dist/pages.js +++ b/customize.dist/pages.js @@ -76,7 +76,7 @@ define([ ]) ]) ]), - h('div.cp-version-footer', "CryptPad v1.18.0 (Snallygaster)") + h('div.cp-version-footer', "CryptPad v1.19.0 (Tarasque)") ]); }; @@ -728,7 +728,7 @@ define([ Pages['/profile/'] = Pages['/profile/index.html'] = function () { return [ - h('div#toolbar'), + h('div#cp-toolbar'), h('div#container'), loadingScreen() ]; diff --git a/customize.dist/src/less/variables.less b/customize.dist/src/less/variables.less index 4d804abea..1d3415d89 100644 --- a/customize.dist/src/less/variables.less +++ b/customize.dist/src/less/variables.less @@ -102,9 +102,9 @@ @category-bg: #f4f4f4; -@button-bg: #3066e5; -@button-alt-bg: #fff; -@button-red-bg: #e54e4e; +@button-bg: @colortheme_sidebar-button-bg; +@button-alt-bg: @colortheme_sidebar-button-alt-bg; +@button-red-bg: @colortheme_sidebar-button-red-bg; .unselectable () { -webkit-touch-callout: none; diff --git a/customize.dist/src/less2/include/colortheme.less b/customize.dist/src/less2/include/colortheme.less index e5e57cc74..4893f5fa2 100644 --- a/customize.dist/src/less2/include/colortheme.less +++ b/customize.dist/src/less2/include/colortheme.less @@ -41,6 +41,7 @@ @colortheme_pad-bg: #1c4fa0; @colortheme_pad-color: #fff; +@colortheme_pad-toolbar-bg: #c1e7ff; @colortheme_slide-bg: #e57614; @colortheme_slide-color: #fff; @@ -50,6 +51,9 @@ @colortheme_poll-bg: #006304; @colortheme_poll-color: #fff; +@colortheme_poll-help-bg: #bbffbb; +@colortheme_poll-th-bg: #005bef; +@colortheme_poll-th-fg: #fff; @colortheme_whiteboard-bg: #800080; @colortheme_whiteboard-color: #fff; @@ -60,7 +64,7 @@ @colortheme_file-bg: #cd2532; @colortheme_file-color: #fff; -@colortheme_friends-bg: #607B8D; +@colortheme_friends-bg: #607b8d; @colortheme_friends-color: #fff; @colortheme_default-bg: #ddd; @@ -75,7 +79,7 @@ @colortheme_todo-bg: #7bccd1; @colortheme_todo-color: #000; -// Sidebar layout +// Sidebar layout (profile / settings) @colortheme_sidebar-active: #fff; @colortheme_sidebar-left-bg: #eee; @colortheme_sidebar-left-fg: #000; @@ -83,7 +87,9 @@ @colortheme_sidebar-right-bg: #fff; @colortheme_sidebar-right-fg: #000; @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_grey: #999999; diff --git a/customize.dist/src/less2/include/icon-colors.less b/customize.dist/src/less2/include/icon-colors.less index 345a48a9a..db9de2a14 100644 --- a/customize.dist/src/less2/include/icon-colors.less +++ b/customize.dist/src/less2/include/icon-colors.less @@ -13,5 +13,18 @@ .cp-icon-color-profile { color: @colortheme_settings-bg; } .cp-icon-color-default { color: @colortheme_default-bg; } .cp-icon-color-todo { color:@colortheme_todo-bg; } + + .cp-border-color-pad { border-color: @colortheme_pad-bg !important; } + .cp-border-color-code { border-color: @colortheme_code-bg !important; } + .cp-border-color-slide { border-color: @colortheme_slide-bg !important; } + .cp-border-color-poll { border-color: @colortheme_poll-bg !important; } + .cp-border-color-file { border-color: @colortheme_file-bg !important; } + .cp-border-color-contacts { border-color: @colortheme_friends-bg !important; } + .cp-border-color-whiteboard { border-color: @colortheme_whiteboard-bg !important; } + .cp-border-color-drive { border-color: @colortheme_drive-bg !important; } + .cp-border-color-settings { border-color: @colortheme_settings-bg !important; } + .cp-border-color-profile { border-color: @colortheme_settings-bg !important; } + .cp-border-color-default { border-color: @colortheme_default-bg !important; } + .cp-border-color-todo { border-color:@colortheme_todo-bg !important; } } diff --git a/customize.dist/src/less2/include/sidebar-layout.less b/customize.dist/src/less2/include/sidebar-layout.less new file mode 100644 index 000000000..e0bec9dcf --- /dev/null +++ b/customize.dist/src/less2/include/sidebar-layout.less @@ -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%); + } + } + } + } + } +} + + diff --git a/customize.dist/src/less2/main.less b/customize.dist/src/less2/main.less index 85a906902..38c242b10 100644 --- a/customize.dist/src/less2/main.less +++ b/customize.dist/src/less2/main.less @@ -33,4 +33,6 @@ body.cp-app-filepicker { @import "../../../filepicker/app-filepicker.less"; } body.cp-app-contacts { @import "../../../contacts/app-contacts.less"; } body.cp-app-poll { @import "../../../poll/app-poll.less"; } body.cp-app-whiteboard { @import "../../../whiteboard/app-whiteboard.less"; } +body.cp-app-todo { @import "../../../todo/app-todo.less"; } +body.cp-app-profile { @import "../../../profile/app-profile.less"; } diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 06fb7f13b..a94907da8 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -44,6 +44,7 @@ define(function () { out.typing = "Édition"; out.initializing = "Initialisation..."; out.forgotten = 'Déplacé vers la corbeille'; + out.errorState = 'Erreur critique : {0}'; out.lag = 'Latence'; out.readonly = 'Lecture seule'; out.anonymous = "Anonyme"; @@ -56,6 +57,7 @@ define(function () { out.viewers = "lecteurs"; out.editor = "éditeur"; out.editors = "éditeurs"; + out.userlist_offline = "Vous êtes actuellement hors-ligne, la liste des utilisateurs n'est pas disponible."; out.language = "Langue"; @@ -359,8 +361,8 @@ define(function () { out.fm_openParent = "Montrer dans le dossier"; out.fm_noname = "Document sans titre"; out.fm_emptyTrashDialog = "Êtes-vous sûr de vouloir vider la corbeille ?"; - out.fm_removeSeveralPermanentlyDialog = "Êtes-vous sûr de vouloir supprimer ces {0} éléments de manière permanente ?"; - out.fm_removePermanentlyDialog = "Êtes-vous sûr de vouloir supprimer cet élément de manière permanente ?"; + out.fm_removeSeveralPermanentlyDialog = "Êtes-vous sûr de vouloir supprimer ces {0} éléments de votre CryptDrive de manière permanente ?"; + out.fm_removePermanentlyDialog = "Êtes-vous sûr de vouloir supprimer cet élément de votre CryptDrive de manière permanente ?"; out.fm_restoreDialog = "Êtes-vous sûr de vouloir restaurer {0} à son emplacement précédent ?"; out.fm_removeSeveralDialog = "Êtes-vous sûr de vouloir déplacer ces {0} éléments vers la corbeille ?"; out.fm_removeDialog = "Êtes-vous sûr de vouloir déplacer {0} vers la corbeille ?"; @@ -397,9 +399,9 @@ define(function () { out.fc_rename = "Renommer"; out.fc_open = "Ouvrir"; out.fc_open_ro = "Ouvrir (lecture seule)"; - out.fc_delete = "Supprimer"; + out.fc_delete = "Déplacer vers la corbeille"; out.fc_restore = "Restaurer"; - out.fc_remove = "Supprimer définitivement"; + out.fc_remove = "Supprimer de votre CryptDrive"; out.fc_empty = "Vider la corbeille"; out.fc_prop = "Propriétés"; out.fc_hashtag = "Mots-clés"; @@ -486,6 +488,13 @@ define(function () { out.settings_resetTipsButton = "Réinitialiser les astuces visibles dans CryptDrive"; 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_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 ?"; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 9e9531627..29735e18f 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -43,9 +43,10 @@ define(function () { out.disconnected = 'Disconnected'; out.synchronizing = 'Synchronizing'; out.reconnecting = 'Reconnecting...'; - out.typing = "Typing"; + out.typing = "Editing"; out.initializing = "Initializing..."; out.forgotten = 'Moved to the trash'; + out.errorState = 'Critical error: {0}'; out.lag = 'Lag'; out.readonly = 'Read only'; out.anonymous = "Anonymous"; @@ -58,6 +59,7 @@ define(function () { out.viewers = "viewers"; out.editor = "editor"; out.editors = "editors"; + out.userlist_offline = "You're currently offline, the user list is not available."; out.language = "Language"; @@ -361,8 +363,8 @@ define(function () { out.fm_openParent = "Show in folder"; out.fm_noname = "Untitled Document"; out.fm_emptyTrashDialog = "Are you sure you want to empty the trash?"; - out.fm_removeSeveralPermanentlyDialog = "Are you sure you want to remove these {0} elements from the trash permanently?"; - out.fm_removePermanentlyDialog = "Are you sure you want to remove that element permanently?"; + out.fm_removeSeveralPermanentlyDialog = "Are you sure you want to remove these {0} elements from your CryptDrive permanently?"; + out.fm_removePermanentlyDialog = "Are you sure you want to remove that element from your CryptDrive permanently?"; out.fm_removeSeveralDialog = "Are you sure you want to move these {0} elements to the trash?"; out.fm_removeDialog = "Are you sure you want to move {0} to the trash?"; out.fm_restoreDialog = "Are you sure you want to restore {0} to its previous location?"; @@ -399,9 +401,9 @@ define(function () { out.fc_rename = "Rename"; out.fc_open = "Open"; out.fc_open_ro = "Open (read-only)"; - out.fc_delete = "Delete"; + out.fc_delete = "Move to trash"; out.fc_restore = "Restore"; - out.fc_remove = "Delete permanently"; + out.fc_remove = "Remove from your CryptDrive"; out.fc_empty = "Empty the trash"; out.fc_prop = "Properties"; out.fc_hashtag = "Tags"; @@ -491,6 +493,13 @@ define(function () { out.settings_resetTipsButton = "Reset the available tips in CryptDrive"; 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_import = "Import"; out.settings_importConfirm = "Are you sure you want to import recent pads from this browser to your user account's CryptDrive?"; diff --git a/package.json b/package.json index 3cc3a4873..14bb2c321 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cryptpad", "description": "realtime collaborative visual editor with zero knowlege server", - "version": "1.18.0", + "version": "1.19.0", "dependencies": { "chainpad-server": "^1.0.1", "express": "~4.10.1", diff --git a/www/assert/main.js b/www/assert/main.js index 0913674ae..3eb9e5892 100644 --- a/www/assert/main.js +++ b/www/assert/main.js @@ -7,7 +7,8 @@ define([ '/drive/tests.js', '/common/test.js', '/common/common-thumbnail.js', -], function ($, Hyperjson, TextPatcher, Sortify, Cryptpad, Drive, Test, Thumb) { + '/common/flat-dom.js', +], function ($, Hyperjson, TextPatcher, Sortify, Cryptpad, Drive, Test, Thumb, Flat) { window.Hyperjson = Hyperjson; window.TextPatcher = TextPatcher; window.Sortify = Sortify; @@ -236,11 +237,20 @@ define([ !secret.hashData.present); }, "test support for trailing slashes in version 1 hash failed to parse"); + assert(function (cb) { + var secret = Cryptpad.parsePadUrl('/invite/#/1/ilrOtygzDVoUSRpOOJrUuQ/e8jvf36S3chzkkcaMrLSW7PPrz7VDp85lIFNI26dTmr=/'); + var hd = secret.hashData; + cb(hd.channel === "ilrOtygzDVoUSRpOOJrUuQ" && + hd.pubkey === "e8jvf36S3chzkkcaMrLSW7PPrz7VDp85lIFNI26dTmr=" && + hd.type === 'invite'); + }, "test support for invite urls"); + assert(function (cb) { // TODO return cb(true); }, "version 2 hash failed to parse correctly"); +/* assert(function (cb) { var getBlob = function (url, cb) { var xhr = new XMLHttpRequest(); @@ -266,9 +276,21 @@ define([ }); }); }); +*/ Drive.test(assert); + assert(function (cb) { + // extract dom elements into a flattened JSON representation + var flat = Flat.fromDOM(document.body); + // recreate a _mostly_ equivalent DOM + var dom = Flat.toDOM(flat); + // assume we don't care about comments + var bodyText = document.body.outerHTML.replace(//g, ''); + // check for equality + cb(dom.outerHTML === bodyText); + }); + var swap = function (str, dict) { return str.replace(/\{\{(.*?)\}\}/g, function (all, key) { return typeof dict[key] !== 'undefined'? dict[key] : all; diff --git a/www/code/inner.html b/www/code/inner.html index 5d914b6c8..327363fd0 100644 --- a/www/code/inner.html +++ b/www/code/inner.html @@ -6,6 +6,7 @@ diff --git a/www/code/inner.js b/www/code/inner.js index d5d7edfdd..5f2cf3df1 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -145,6 +145,10 @@ define([ $codeMirror.addClass('cp-app-code-fullpage'); }; + var isVisible = function () { + return $previewContainer.is(':visible'); + }; + framework.onReady(function () { // add the splitter var splitter = $('
', { @@ -184,7 +188,8 @@ define([ return { forceDraw: forceDrawPreview, draw: drawPreview, - modeChange: modeChange + modeChange: modeChange, + isVisible: isVisible }; }; @@ -317,6 +322,17 @@ define([ framework.start(); }; + var getThumbnailContainer = function () { + var $preview = $('#cp-app-code-preview-content'); + var $codeMirror = $('.CodeMirror'); + if ($preview.length && $preview.is(':visible')) { + return $preview[0]; + } + if ($codeMirror.length) { + return $codeMirror[0]; + } + }; + var main = function () { var CodeMirror; var editor; @@ -327,7 +343,19 @@ define([ Framework.create({ toolbarContainer: '#cme_toolbox', - contentContainer: '#cp-app-code-editor' + contentContainer: '#cp-app-code-editor', + thumbnail: { + getContainer: getThumbnailContainer, + filter: function (el, before) { + if (before) { + $(el).parents().css('overflow', 'visible'); + $(el).css('max-height', Math.max(600, $(el).width()) + 'px'); + return; + } + $(el).parents().css('overflow', ''); + $(el).css('max-height', ''); + } + } }, waitFor(function (fw) { framework = fw; })); nThen(function (waitFor) { diff --git a/www/common/common-file.js b/www/common/common-file.js index dfe9bbe94..df2f94ee7 100644 --- a/www/common/common-file.js +++ b/www/common/common-file.js @@ -296,18 +296,11 @@ define([ if (!Thumb.isSupportedType(file.type)) { return finish(); } // make a resized thumbnail from the image.. - Thumb.fromBlob(file, function (e, thumb_blob) { + Thumb.fromBlob(file, function (e, thumb64) { if (e) { console.error(e); } - if (!thumb_blob) { return finish(); } - - blobToArrayBuffer(thumb_blob, function (e, buffer) { - if (e) { - console.error(e); - return finish(); - } - thumb = arrayBufferToString(buffer); - finish(); - }); + if (!thumb64) { return finish(); } + thumb = thumb64; + finish(); }); }); }; diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 644efded2..f03b4b481 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -526,7 +526,8 @@ define([ // them. var win; $('.tippy-popper').each(function (i, el) { - win = win || $('#pad-iframe')[0].contentWindow; + win = win || $('#pad-iframe').length? $('#pad-iframe')[0].contentWindow: undefined; + if (!win) { return; } if (win.$('[aria-describedby=' + el.getAttribute('id') + ']').length === 0) { el.remove(); } diff --git a/www/common/common-thumbnail.js b/www/common/common-thumbnail.js index 00ee33115..5f5a8106a 100644 --- a/www/common/common-thumbnail.js +++ b/www/common/common-thumbnail.js @@ -1,9 +1,18 @@ define([ + 'jquery', + '/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', -], function () { +], function ($, Util, Visible, Hash, FileCrypto, localForage) { var Nacl = window.nacl; var Thumb = { dimension: 100, + padDimension: 200, + UPDATE_INTERVAL: 60000, + UPDATE_FIRST: 5000 }; var supportedTypes = [ @@ -11,7 +20,8 @@ define([ 'image/jpeg', 'image/jpg', 'image/gif', - 'video/' + 'video/', + 'application/pdf' ]; Thumb.isSupportedType = function (type) { @@ -41,22 +51,32 @@ define([ } }; - var getResizedDimensions = function (img, type) { + var getResizedDimensions = Thumb.getResizedDimensions = function (img, type) { var h = type === 'video' ? img.videoHeight : img.height; var w = type === 'video' ? img.videoWidth : img.width; - var dim = Thumb.dimension; + var dim = type === 'pad' ? Thumb.padDimension : Thumb.dimension; + // if the image is too small, don't bother making a thumbnail - if (h <= dim && w <= dim) { return null; } + /*if (h <= dim && w <= dim) { + return { + x: Math.floor((dim - w) / 2), + w: w, + y: Math.floor((dim - h) / 2), + h : h + }; + }*/ // the image is taller than it is wide, so scale to that. var r = dim / (h > w? h: w); // ratio + if (h <= dim && w <= dim) { r = 1; } var d; if (h > w) { var newW = Math.floor(w*r); d = Math.floor((dim - newW) / 2); return { + dim: dim, x: d, w: newW, y: 0, @@ -66,6 +86,7 @@ define([ var newH = Math.floor(h*r); d = Math.floor((dim - newH) / 2); return { + dim: dim, x: 0, w: dim, y: d, @@ -76,18 +97,16 @@ define([ // assumes that your canvas is square // nodeback returning blob - Thumb.fromCanvas = Thumb.fromImage = function (canvas, D, cb) { + Thumb.fromCanvas = function (canvas, D, cb) { var c2 = document.createElement('canvas'); - if (!D) { return void cb('TOO_SMALL'); } + if (!D) { return void cb('ERROR'); } - c2.width = Thumb.dimension; - c2.height = Thumb.dimension; + c2.width = D.dim; + c2.height = D.dim; var ctx = c2.getContext('2d'); ctx.drawImage(canvas, D.x, D.y, D.w, D.h); - c2.toBlob(function (blob) { - cb(void 0, blob); - }); + cb(void 0, c2.toDataURL()); }; Thumb.fromImageBlob = function (blob, cb) { @@ -96,10 +115,7 @@ define([ img.onload = function () { var D = getResizedDimensions(img, 'image'); - Thumb.fromImage(img, D, function (err, t) { - if (err === 'TOO_SMALL') { return void cb(void 0, blob); } - cb(err, t); - }); + Thumb.fromCanvas(img, D, cb); }; img.onerror = function () { cb('ERROR'); @@ -123,15 +139,138 @@ define([ cb('ERROR'); }); }; + Thumb.fromPdfBlob = function (blob, cb) { + require.config({paths: {'pdfjs-dist': '/common/pdfjs'}}); + require(['pdfjs-dist/build/pdf'], function (PDFJS) { + var url = URL.createObjectURL(blob); + var makeThumb = function (page) { + var vp = page.getViewport(1); + var canvas = document.createElement("canvas"); + canvas.width = canvas.height = Thumb.dimension; + var scale = Math.min(canvas.width / vp.width, canvas.height / vp.height); + canvas.width = Math.floor(vp.width * scale); + canvas.height = Math.floor(vp.height * scale); + return page.render({ + canvasContext: canvas.getContext("2d"), + viewport: page.getViewport(scale) + }).promise.then(function () { + return canvas; + }); + }; + PDFJS.getDocument(url).promise + .then(function (doc) { + return doc.getPage(1).then(makeThumb).then(function (canvas) { + var D = getResizedDimensions(canvas, 'pdf'); + Thumb.fromCanvas(canvas, D, cb); + }); + }).catch(function () { + cb('ERROR'); + }); + }); + }; Thumb.fromBlob = function (blob, cb) { if (blob.type.indexOf('video/') !== -1) { return void Thumb.fromVideoBlob(blob, cb); } + if (blob.type.indexOf('application/pdf') !== -1) { + return void Thumb.fromPdfBlob(blob, cb); + } Thumb.fromImageBlob(blob, cb); }; - Thumb.fromVideo = function (video, cb) { - cb = cb; // WIP + window.html2canvas = undefined; + Thumb.fromDOM = function (opts, cb) { + var element = opts.getContainer(); + var todo = function () { + if (opts.filter) { opts.filter(element, true); } + window.html2canvas(element, { + allowTaint: true, + onrendered: function (canvas) { + if (opts.filter) { opts.filter(element, false); } + var D = getResizedDimensions(canvas, 'pad'); + Thumb.fromCanvas(canvas, D, cb); + } + }); + }; + if (window.html2canvas) { return void 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; diff --git a/www/common/diffMarked.js b/www/common/diffMarked.js index bf446fe37..abd5248ec 100644 --- a/www/common/diffMarked.js +++ b/www/common/diffMarked.js @@ -45,9 +45,7 @@ define([ var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; var mt = ''; if (mediaMap[src]) { - mediaMap[src].forEach(function (n) { - mt += n.outerHTML; - }); + mt += mediaMap[src]; } mt += ''; return mt; @@ -129,6 +127,7 @@ define([ var domFromHTML = function (html) { var Dom = new DOMParser().parseFromString(html, "text/html"); + Dom.normalize(); removeForbiddenTags(Dom.body); removeListeners(Dom.body); return Dom; @@ -167,11 +166,7 @@ define([ var unsafe_newHtmlFixed = newHtml.replace(pattern, function (all, tag, src) { var mt = tag; - if (mediaMap[src]) { - mediaMap[src].forEach(function (n) { - mt += n.outerHTML; - }); - } + if (mediaMap[src]) { mt += mediaMap[src]; } return mt + ''; }); @@ -179,6 +174,7 @@ define([ var $div = $('
', {id: id}).append(safe_newHtmlFixed); var Dom = domFromHTML($('
').append($div).html()); + $content[0].normalize(); var oldDom = domFromHTML($content[0].outerHTML); var patch = makeDiff(oldDom, Dom, id); if (typeof(patch) === 'string') { @@ -191,9 +187,11 @@ define([ var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.type === 'childList') { - //console.log(el.outerHTML); - var list_values = [].slice.call(el.children); - mediaMap[el.getAttribute('src')] = list_values; + var list_values = [].slice.call(mutation.target.children) + .map(function (el) { return el.outerHTML; }) + .join(''); + mediaMap[mutation.target.getAttribute('src')] = list_values; + observer.disconnect(); } }); }); diff --git a/www/common/flat-dom.js b/www/common/flat-dom.js new file mode 100644 index 000000000..51c3d07b3 --- /dev/null +++ b/www/common/flat-dom.js @@ -0,0 +1,83 @@ +define([], function () { + var Flat = {}; + + var slice = function (coll) { + return Array.prototype.slice.call(coll); + }; + + var getAttrs = function (el) { + var i = 0; + var l = el.attributes.length; + var attr; + var data = {}; + for (;i < l;i++) { + attr = el.attributes[i]; + if (attr.name && attr.value) { data[attr.name] = attr.value; } + } + return data; + }; + + var identity = function (x) { return x; }; + Flat.fromDOM = function (dom) { + var data = { + map: {}, + }; + + var i = 1; // start from 1 so we're always truthey + var uid = function () { return i++; }; + + var process = function (el) { + var id; + if (!el.tagName && el.nodeType === Node.TEXT_NODE) { + id = uid(); + data.map[id] = el.textContent; + return id; + } + if (!el || !el.attributes) { return void console.error(el); } + id = uid(); + data.map[id] = [ + el.tagName, + getAttrs(el), + slice(el.childNodes).map(function (e) { + return process(e); + }).filter(identity) + ]; + return id; + }; + + data.root = process(dom); + return data; + }; + + Flat.toDOM = function (data) { + var visited = {}; + var process = function (key) { + if (!key) { return; } // ignore falsey keys + if (visited[key]) { + // TODO handle this more gracefully. + throw new Error('duplicate id or loop detected'); + } + visited[key] = true; // mark paths as visited. + + var hj = data.map[key]; + if (typeof(hj) === 'string') { return document.createTextNode(hj); } + if (typeof(hj) === 'undefined') { return; } + if (!Array.isArray(hj)) { console.error(hj); throw new Error('expected array'); } + + var e = document.createElement(hj[0]); + for (var x in hj[1]) { e.setAttribute(x, hj[1][x]); } + var child; + for (var i = 0; i < hj[2].length; i++) { + child = process(hj[2][i]); + if (child) { + e.appendChild(child); + } + } + return e; + }; + + return process(data.root); + }; + + return Flat; +}); diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 2bb6090aa..d58710e1b 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -10,6 +10,7 @@ define([ '/common/sframe-common.js', '/customize/messages.js', '/common/common-util.js', + '/common/common-thumbnail.js', '/customize/application_config.js', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', @@ -27,6 +28,7 @@ define([ SFCommon, Messages, Util, + Thumb, AppConfig) { var SaveAs = window.saveAs; @@ -264,6 +266,20 @@ define([ Cryptpad.removeLoadingScreen(emitResize); + var privateDat = cpNfInner.metadataMgr.getPrivateData(); + if (options.thumbnail && privateDat.thumbnails) { + var hash = privateDat.availableHashes.editHash || + privateDat.availableHashes.viewHash; + if (hash) { + options.thumbnail.href = privateDat.pathname + '#' + hash; + options.thumbnail.getContent = function () { + if (!cpNfInner.chainpad) { return; } + return cpNfInner.chainpad.getUserDoc(); + }; + Thumb.initPadThumbnails(options.thumbnail); + } + } + if (newPad) { common.openTemplatePicker(); } @@ -362,10 +378,7 @@ define([ }).nThen(function (waitFor) { cpNfInner = common.startRealtime({ // really basic operational transform - transformFunction: options.transformFunction || JsonOT.validate, - - // This one causes a big mess. - //patchTransformer: options.patchTransformer || JsonOT.patchTransformer, + transformFunction: options.transformFunction || JsonOT.transform, // cryptpad debug logging (default is 1) // logLevel: 0, @@ -399,6 +412,7 @@ define([ textPatcher = TextPatcher.create({ realtime: cpNfInner.chainpad }); + var infiniteSpinnerModal = false; window.setInterval(function () { if (state === STATE.DISCONNECTED) { return; } var l; @@ -409,13 +423,15 @@ define([ } if (l.lag < badStateTimeout) { return; } - if (state === STATE.INFINITE_SPINNER) { return; } + if (infiniteSpinnerModal) { return; } + infiniteSpinnerModal = true; stateChange(STATE.INFINITE_SPINNER); Cryptpad.confirm(Messages.realtime_unrecoverableError, function (yes) { if (!yes) { return; } common.gotoURL(); }); cpNfInner.chainpad.onSettle(function () { + infiniteSpinnerModal = false; Cryptpad.findCancelButton().click(); stateChange(STATE.READY); onRemote(); diff --git a/www/common/sframe-boot2.js b/www/common/sframe-boot2.js index dd0370ca8..9b0f055b3 100644 --- a/www/common/sframe-boot2.js +++ b/www/common/sframe-boot2.js @@ -13,7 +13,8 @@ define(['/common/requireconfig.js'], function (RequireConfig) { var mkFakeStore = function () { var fakeStorage = { getItem: function (k) { return fakeStorage[k]; }, - setItem: function (k, v) { fakeStorage[k] = v; return v; } + setItem: function (k, v) { fakeStorage[k] = v; return v; }, + removeItem: function (k) { delete fakeStorage[k]; } }; return fakeStorage; }; diff --git a/www/common/sframe-chainpad-listmap.js b/www/common/sframe-chainpad-listmap.js index 22b7e6216..a4c63641d 100644 --- a/www/common/sframe-chainpad-listmap.js +++ b/www/common/sframe-chainpad-listmap.js @@ -685,7 +685,9 @@ define([ }); }; + var ready = false; realtimeOptions.onReady = function (info) { + if (ready) { return; } // create your patcher if (realtime !== info.realtime) { realtime = rt.realtime = info.realtime; @@ -709,6 +711,7 @@ define([ DeepProxy.checkLocalChange(proxy, onLocal); initializing = false; + ready = true; }; realtimeOptions.onAbort = function (info) { diff --git a/www/common/sframe-common-file.js b/www/common/sframe-common-file.js index 7654b0ef3..c8b1b7d7c 100644 --- a/www/common/sframe-common-file.js +++ b/www/common/sframe-common-file.js @@ -249,18 +249,11 @@ define([ if (!Thumb.isSupportedType(file.type)) { return finish(); } // make a resized thumbnail from the image.. - Thumb.fromBlob(file, function (e, thumb_blob) { + Thumb.fromBlob(file, function (e, thumb64) { if (e) { console.error(e); } - if (!thumb_blob) { return finish(); } - - blobToArrayBuffer(thumb_blob, function (e, buffer) { - if (e) { - console.error(e); - return finish(); - } - thumb = arrayBufferToString(buffer); - finish(); - }); + if (!thumb64) { return finish(); } + thumb = thumb64; + finish(); }); }); }; diff --git a/www/common/sframe-common-interface.js b/www/common/sframe-common-interface.js index e53db847d..d4a0e01d3 100644 --- a/www/common/sframe-common-interface.js +++ b/www/common/sframe-common-interface.js @@ -3,19 +3,14 @@ define([ '/api/config', '/common/cryptpad-common.js', '/common/common-util.js', - '/common/common-hash.js', '/common/media-tag.js', '/common/tippy.min.js', '/customize/application_config.js', - '/file/file-crypto.js', - '/bower_components/localforage/dist/localforage.min.js', - '/bower_components/tweetnacl/nacl-fast.min.js', 'css!/common/tippy.css', -], function ($, Config, Cryptpad, Util, Hash, MediaTag, Tippy, AppConfig, FileCrypto, localForage) { +], function ($, Config, Cryptpad, Util, MediaTag, Tippy, AppConfig) { var UI = {}; var Messages = Cryptpad.Messages; - var Nacl = window.nacl; /** * Requirements from cryptpad-common.js @@ -33,40 +28,6 @@ define([ * - createDropdown */ - var addThumbnail = function (err, thumb, $span, cb) { - var img = new Image(); - img.src = 'data:;base64,'+thumb; - $span.find('.cp-icon').hide(); - $span.prepend(img); - cb($(img)); - }; - UI.displayThumbnail = function (href, $container, cb) { - cb = cb || $.noop; - var parsed = Hash.parsePadUrl(href); - if (parsed.type !== 'file') { return; } - 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) { return void whenNewThumb(); } - if (v === 'EMPTY') { return; } - addThumbnail(err, v, $container, cb); - }); - }; - UI.updateTags = function (common, href) { var sframeChan = common.getSframeChannel(); sframeChan.query('Q_TAGS_GET', href || null, function (err, res) { @@ -330,6 +291,7 @@ define([ $img.attr('src', src); $img.attr('data-crypto-key', 'cryptpad:' + cryptKey); UI.displayMediatagImage(Common, $img, function (err, $image, img) { + if (err) { return void console.error(err); } var w = img.width; var h = img.height; if (w>h) { diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index e1ca45c3c..0f07ac8a3 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -130,6 +130,7 @@ define([ settings: proxy.settings || {}, isPresent: parsed.hashData && parsed.hashData.present, isEmbed: parsed.hashData && parsed.hashData.embed, + thumbnails: !((proxy.settings || {}).general || {}).disableThumbnails, accounts: { donateURL: Cryptpad.donateURL, upgradeURL: Cryptpad.upgradeURL diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 19506e038..2a539e88e 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -14,7 +14,8 @@ define([ '/customize/application_config.js', '/common/cryptpad-common.js', '/common/common-realtime.js', - '/common/common-util.js' + '/common/common-util.js', + '/common/common-thumbnail.js' ], function ( $, nThen, @@ -30,7 +31,8 @@ define([ AppConfig, Cryptpad, CommonRealtime, - Util + Util, + Thumb ) { // Chainpad Netflux Inner @@ -80,7 +82,9 @@ define([ funcs.createButton = callWithCommon(UI.createButton); funcs.createUsageBar = callWithCommon(UI.createUsageBar); funcs.updateTags = callWithCommon(UI.updateTags); - funcs.displayThumbnail = UI.displayThumbnail; + + // Thumb + funcs.displayThumbnail = Thumb.displayThumbnail; // History funcs.getHistory = callWithCommon(History.create); diff --git a/www/common/sframe-protocol.js b/www/common/sframe-protocol.js index d568592ed..846502120 100644 --- a/www/common/sframe-protocol.js +++ b/www/common/sframe-protocol.js @@ -168,4 +168,8 @@ define({ // in the drive at registration. '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 }); diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index fdebae133..0b48799d2 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -154,6 +154,7 @@ define([ var $userlistContent = toolbar.userlistContent; var metadataMgr = config.metadataMgr; + var online = metadataMgr.isConnected(); var userData = metadataMgr.getMetadata().users; var viewers = metadataMgr.getViewers(); var priv = metadataMgr.getPrivateData(); @@ -184,7 +185,23 @@ define([ // Update the userlist var $editUsers = $userlistContent.find('.' + USERLIST_CLS).html(''); - var $editUsersList = $('
', {'class': 'cp-toolbar-userlist-others'}); + var $editUsersList = $('
', {'class': 'cp-toolbar-userlist-others'}) + .appendTo($editUsers); + + if (!online) { + $('').text(Messages.userlist_offline).appendTo($editUsersList); + numberOfEditUsers = '?'; + numberOfViewUsers = '?'; + } + + // Update the buttons + var fa_editusers = ''; + var fa_viewusers = ''; + var $spansmall = $('').html(fa_editusers + ' ' + numberOfEditUsers + '   ' + fa_viewusers + ' ' + numberOfViewUsers); + $userButtons.find('.cp-dropdown-button-title').html('').append($spansmall); + + if (!online) { return; } + // Display the userlist // Editors var pendingFriends = Common.getPendingFriends(); @@ -237,7 +254,6 @@ define([ $span.data('uid', data.uid); $editUsersList.append($span); }); - $editUsers.append($editUsersList); // Viewers if (numberOfViewUsers > 0) { @@ -246,12 +262,6 @@ define([ viewText += numberOfViewUsers + ' ' + viewerText + '
'; $editUsers.append(viewText); } - - // Update the buttons - var fa_editusers = ''; - var fa_viewusers = ''; - var $spansmall = $('').html(fa_editusers + ' ' + numberOfEditUsers + '   ' + fa_viewusers + ' ' + numberOfViewUsers); - $userButtons.find('.cp-dropdown-button-title').html('').append($spansmall); }; var initUserList = function (toolbar, config) { @@ -691,6 +701,7 @@ define([ var typing = -1; var kickSpinner = function (toolbar, config/*, local*/) { if (!toolbar.spinner) { return; } + if (toolbar.isErrorState) { return; } var $spin = toolbar.spinner; if (typing === -1) { @@ -886,6 +897,7 @@ define([ // type : 1 (+1 user), 0 (rename existing user), -1 (-1 user) if (typeof name === "undefined") { return; } name = name || Messages.anonymous; + if (Config.disableUserlistNotifications) { return; } switch(type) { case 1: Cryptpad.log(Messages._getKey("notifyJoined", [name])); @@ -920,6 +932,7 @@ define([ return count; }; + var joined = false; metadataMgr.onChange(function () { var newdata = metadataMgr.getMetadata().users; var netfluxIds = Object.keys(newdata); @@ -948,7 +961,7 @@ define([ return; } for (var k in newdata) { - if (k !== userNetfluxId && netfluxIds.indexOf(k) !== -1) { + if (joined && k !== userNetfluxId && netfluxIds.indexOf(k) !== -1) { if (typeof oldUserData[k] === "undefined") { // if the same uid is already present in the userdata, don't notify if (!userPresent(k, newdata[k], oldUserData)) { @@ -959,6 +972,7 @@ define([ } } } + joined = true; oldUserData = JSON.parse(JSON.stringify(newdata)); }); } @@ -1047,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) toolbar.forgotten = function (/*userId*/) { toolbar.connected = false; diff --git a/www/contacts/messenger-ui.js b/www/contacts/messenger-ui.js index fddad4497..b5c2f0c06 100644 --- a/www/contacts/messenger-ui.js +++ b/www/contacts/messenger-ui.js @@ -3,10 +3,9 @@ define([ '/common/cryptpad-common.js', '/common/hyperscript.js', '/bower_components/marked/marked.min.js', -], function ($, Cryptpad, h, Marked) { + '/common/media-tag.js', +], function ($, Cryptpad, h, Marked, MediaTag) { 'use strict'; - // TODO use our fancy markdown and support media-tags - Marked.setOptions({ sanitize: true, }); var UI = {}; var Messages = Cryptpad.Messages; @@ -15,6 +14,12 @@ define([ var d = h('div.cp-app-contacts-content'); try { d.innerHTML = Marked(md || ''); + var $d = $(d); + // remove potentially malicious elements + $d.find('script, iframe, object, applet, video, audio').remove(); + + // activate media-tags + $d.find('media-tag').each(function (i, e) { MediaTag(e); }); } catch (e) { console.error(md); console.error(e); diff --git a/www/drive/app-drive.less b/www/drive/app-drive.less index 8def8763a..2cc04be9c 100644 --- a/www/drive/app-drive.less +++ b/www/drive/app-drive.less @@ -58,13 +58,15 @@ min-height: auto; } .cp-app-drive-element-name { width: 100%; - height: 48px; - margin: 8px 0; + height: 24px; + margin: 0; display: inline-block; + font-size: 14px; //align-items: center; //justify-content: center; overflow: hidden; - //text-overflow: ellipsis; + white-space: nowrap; + text-overflow: ellipsis; word-wrap: break-word; } .cp-app-drive-element-truncated { @@ -83,8 +85,8 @@ min-height: auto; .fa { display: block; margin: auto; - font-size: 48px; - margin: 8px 0; + font-size: 64px; + margin: 18px 0; text-align: center; &.listonly { display: none; @@ -518,10 +520,15 @@ span { } } .cp-app-drive-element-thumbnail { - max-width: 64px; - max-height: 64px; + max-width: 100px; + max-height: 100px; & ~ .fa { - display: none; + display: inline; + font-size: 17px; + position: absolute; + top: 3px; + left: 3px; + margin: 0; } } } diff --git a/www/drive/inner.js b/www/drive/inner.js index fe06dca69..864b2038d 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -1145,6 +1145,10 @@ define([ if (!data) { return void logError("No data for the file", element); } var hrefData = Cryptpad.parsePadUrl(data.href); + if (hrefData.type) { + $span.addClass('cp-border-color-'+hrefData.type); + } + var $state = $('', {'class': 'cp-app-drive-element-state'}); if (hrefData.hashData && hrefData.hashData.mode === 'view') { var $ro = $readonlyIcon.clone().appendTo($state); @@ -1161,6 +1165,7 @@ define([ var $name = $('', {'class': 'cp-app-drive-element-name'}).text(name); $span.append($name); $span.append($state); + $span.attr('title', name); var type = Messages.type[hrefData.type] || hrefData.type; common.displayThumbnail(data.href, $span, function ($thumb) { @@ -1199,6 +1204,7 @@ define([ var $files = $('', { 'class': 'cp-app-drive-element-files cp-app-drive-element-list' }).text(files); + $span.attr('title', key); $span.append($name).append($state).append($subfolders).append($files); }; @@ -1512,7 +1518,7 @@ define([ if (isInRoot) { options.push({ tag: 'a', - attributes: {'class': 'cp-app-drive-new-older'}, + attributes: {'class': 'cp-app-drive-new-folder'}, content: $('
').append($folderIcon.clone()).html() + Messages.fm_folder }); options.push({tag: 'hr'}); @@ -2197,7 +2203,7 @@ define([ } $content.append($info).append($dirContent); - var $truncated = $('', {'class': 'cp-app-drive-element-truncated'}).text('...'); + /*var $truncated = $('', {'class': 'cp-app-drive-element-truncated'}).text('...'); $content.find('.cp-app-drive-element').each(function (idx, el) { var $name = $(el).find('.cp-app-drive-element-name'); if ($name.length === 0) { return; } @@ -2206,7 +2212,7 @@ define([ $tr.attr('title', $name.text()); $(el).append($tr); } - }); + });*/ $content.scrollTop(s); appStatus.ready(true); diff --git a/www/filepicker/app-filepicker.less b/www/filepicker/app-filepicker.less index 69e92d69d..a28ec8008 100644 --- a/www/filepicker/app-filepicker.less +++ b/www/filepicker/app-filepicker.less @@ -47,6 +47,11 @@ align-items: center; + img { + max-width: 100px; + max-height: 100px; + } + .cp-filepicker-content-element-name { overflow: hidden; text-overflow: ellipsis; diff --git a/www/oldprofile/index.html b/www/oldprofile/index.html new file mode 100644 index 000000000..5200564ce --- /dev/null +++ b/www/oldprofile/index.html @@ -0,0 +1,20 @@ + + + + + CryptPad: Zero Knowledge, Collaborative Real Time Editing + + + + + + + + + + + + diff --git a/www/oldprofile/main.js b/www/oldprofile/main.js new file mode 100644 index 000000000..ef2ac9a81 --- /dev/null +++ b/www/oldprofile/main.js @@ -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, + ' ') + '\n'; + } + if (isUncheckedTaskItem) { + text = text.replace(uncheckedTaskItemPtn, + ' ') + '\n'; + } + var cls = (isCheckedTaskItem || isUncheckedTaskItem) ? ' class="todo-list-item"' : ''; + return '' + text + '\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 = ''; + mt += ''; + return mt; + } + var out = '' + text + '' : '>'; + 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 = $('', { + 'id': name+'Input', + placeholder: ph + }).val(value); + var $icon = $('', {'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"); + $("