diff --git a/TestSelenium.js b/TestSelenium.js index 59bdd427a..352b293eb 100644 --- a/TestSelenium.js +++ b/TestSelenium.js @@ -36,7 +36,10 @@ var nt = nThen(function (waitFor) { }).nThen; [ + // login test must happen after register test ['/register/', {}], + ['/login/', {}], + ['/assert/', {}], ['/auth/', {}], @@ -49,6 +52,9 @@ var nt = nThen(function (waitFor) { ['/slide/#/1/edit/uwKqgj8Ezh2dRaFUWSlrRQ/JkJtAb-hNzfESZEHreAeULU1/', {}], ['/slide/#/1/view/uwKqgj8Ezh2dRaFUWSlrRQ/Xa8jXl+jWMpwep41mlrhkqbRuVKGxlueH80Pbgeu5Go/', {}], + ['/poll/#/1/edit/lHhnKHSs0HBsl2UGfSJoLw/ZXSsAq4BORIixuFaLVBFcxoq/', {}], + ['/poll/#/1/view/lHhnKHSs0HBsl2UGfSJoLw/TGul8PhswwLh1klHpBto6yEntWtKES2+tetYrrYec4M/', {}] + ].forEach(function (x) { if (failed) { return; } var url = 'http://localhost:3000' + x[0]; diff --git a/customize.dist/application_config.js b/customize.dist/application_config.js index 0762b5b36..2320bf9af 100644 --- a/customize.dist/application_config.js +++ b/customize.dist/application_config.js @@ -68,5 +68,7 @@ define(function() { config.displayCreationScreen = false; + config.disableAnonymousStore = false; + return config; }); diff --git a/www/common/credential.js b/customize.dist/credential.js similarity index 100% rename from www/common/credential.js rename to customize.dist/credential.js diff --git a/www/common/login.js b/customize.dist/login.js similarity index 99% rename from www/common/login.js rename to customize.dist/login.js index 847ca74a7..b7a9d4e84 100644 --- a/www/common/login.js +++ b/customize.dist/login.js @@ -4,7 +4,7 @@ define([ '/bower_components/chainpad-crypto/crypto.js', '/common/common-util.js', '/common/outer/network-config.js', - '/common/credential.js', + '/customize/credential.js', '/bower_components/chainpad/chainpad.dist.js', '/bower_components/tweetnacl/nacl-fast.min.js', diff --git a/customize.dist/main.js b/customize.dist/main.js index 7aaa77675..008226211 100644 --- a/customize.dist/main.js +++ b/customize.dist/main.js @@ -1,25 +1,12 @@ define([ 'jquery', - '/customize/application_config.js', - '/common/cryptpad-common.js', - '/common/common-interface.js', - '/common/common-realtime.js', - '/common/common-constants.js', '/common/outer/local-store.js', '/customize/messages.js', -], function ($, Config, Cryptpad, UI, Realtime, Constants, LocalStore, Messages) { - - window.APP = { - Cryptpad: Cryptpad, - }; +], function ($, LocalStore, Messages) { $(function () { var $main = $('#mainBlock'); - $(window).click(function () { - $('.cp-dropdown-content').hide(); - }); - // main block is hidden in case javascript is disabled $main.removeClass('hidden'); @@ -34,113 +21,9 @@ define([ $main.find('a[href="/drive/"] div.pad-button-text h4') .text(Messages.main_yourCryptDrive); - - var name = localStorage[Constants.userNameKey] || sessionStorage[Constants.userNameKey]; - var $loggedInBlock = $main.find('#loggedIn'); - var $hello = $loggedInBlock.find('#loggedInHello'); - var $logout = $loggedInBlock.find('#loggedInLogOut'); - - if (name) { - $hello.text(Messages._getKey('login_hello', [name])); - } else { - $hello.text(Messages.login_helloNoName); - } - $('#buttons').find('.nologin').hide(); - - $logout.click(function () { - LocalStore.logout(function () { - window.location.reload(); - }); - }); - - $loggedInBlock.removeClass('hidden'); } - else { - $main.find('#userForm').removeClass('hidden'); - $('#name').focus(); - } - - /* Log in UI */ - var Login; - // deferred execution to avoid unnecessary asset loading - var loginReady = function (cb) { - if (Login) { - if (typeof(cb) === 'function') { cb(); } - return; - } - require([ - '/common/login.js', - ], function (_Login) { - Login = Login || _Login; - if (typeof(cb) === 'function') { cb(); } - }); - }; - - var $uname = $('#name').on('focus', loginReady); - - var $passwd = $('#password') - // background loading of login assets - .on('focus', loginReady) - // enter key while on password field clicks signup - .on('keyup', function (e) { - if (e.which !== 13) { return; } // enter - $('button.login').click(); + $(window).click(function () { + $('.cp-dropdown-content').hide(); }); - - $('button.login').click(function () { - // setTimeout 100ms to remove the keyboard on mobile devices before the loading screen pops up - window.setTimeout(function () { - UI.addLoadingScreen({loadingText: Messages.login_hashing}); - // We need a setTimeout(cb, 0) otherwise the loading screen is only displayed after hashing the password - window.setTimeout(function () { - loginReady(function () { - var uname = $uname.val(); - var passwd = $passwd.val(); - Login.loginOrRegister(uname, passwd, false, function (err, result) { - if (!err) { - var proxy = result.proxy; - - // successful validation and user already exists - // set user hash in localStorage and redirect to drive - if (proxy && !proxy.login_name) { - proxy.login_name = result.userName; - } - - proxy.edPrivate = result.edPrivate; - proxy.edPublic = result.edPublic; - - Realtime.whenRealtimeSyncs(result.realtime, function () { - LocalStore.login(result.userHash, result.userName, function () { - document.location.href = '/drive/'; - }); - }); - return; - } - switch (err) { - case 'NO_SUCH_USER': - UI.removeLoadingScreen(function () { - UI.alert(Messages.login_noSuchUser); - }); - break; - case 'INVAL_USER': - UI.removeLoadingScreen(function () { - UI.alert(Messages.login_invalUser); - }); - break; - case 'INVAL_PASS': - UI.removeLoadingScreen(function () { - UI.alert(Messages.login_invalPass); - }); - break; - default: // UNHANDLED ERROR - UI.errorLoadingScreen(Messages.login_unhandledError); - } - }); - }); - }, 0); - }, 100); - }); - /* End Log in UI */ - console.log("ready"); }); }); diff --git a/customize.dist/pages.js b/customize.dist/pages.js index f977c956c..f120beef1 100644 --- a/customize.dist/pages.js +++ b/customize.dist/pages.js @@ -2,8 +2,9 @@ define([ '/api/config', '/common/hyperscript.js', '/customize/messages.js', - 'jquery' -], function (Config, h, Msg, $) { + 'jquery', + '/customize/application_config.js', +], function (Config, h, Msg, $, AppConfig) { var Pages = {}; var urlArgs = Config.requireConf.urlArgs; @@ -71,7 +72,7 @@ define([ ]) ]) ]), - h('div.cp-version-footer', "CryptPad v1.23.0 (Xenomorph)") + h('div.cp-version-footer', "CryptPad v1.24.0 (Yeti)") ]); }; @@ -374,8 +375,61 @@ define([ ]); }; + var isAvailableType = function (x) { + if (!Array.isArray(AppConfig.availablePadTypes)) { return true; } + return AppConfig.availablePadTypes.some(function (type) { + return x.indexOf(type) > -1; + }); + }; + Pages['/'] = Pages['/index.html'] = function () { var showingMore = false; + + var icons = [ + [ 'pad', '/pad/', Msg.main_richTextPad, 'fa-file-word-o' ], + [ 'code', '/code/', Msg.main_codePad, 'fa-file-code-o' ], + [ 'slide', '/slide/', Msg.main_slidePad, 'fa-file-powerpoint-o' ], + [ 'poll', '/poll/', Msg.main_pollPad, 'fa-calendar' ], + [ 'whiteboard', '/whiteboard/', Msg.main_whiteboardPad, 'fa-paint-brush' ], + [ 'recent', '/drive/', Msg.main_localPads, 'fa-hdd-o' ] + ].filter(function (x) { + return isAvailableType(x[1]); + }) + .map(function (x, i) { + var s = 'div.bs-callout.cp-callout-' + x[0]; + if (i > 2) { s += '.cp-more.cp-hidden'; } + return h('a', [ + { href: x[1] }, + h(s, [ + h('i.fa.' + x[3]), + h('div.pad-button-text', [ h('h4', x[2]) ]) + ]) + ]); + }); + + var more = icons.length < 4? undefined: h('div.bs-callout.cp-callout-more', [ + h('div.cp-callout-more-lessmsg.cp-hidden', [ + "see less ", + h('i.fa.fa-caret-up') + ]), + h('div.cp-callout-more-moremsg', [ + "see more ", + h('i.fa.fa-caret-down') + ]), + { + onclick: function () { + if (showingMore) { + $('.cp-more, .cp-callout-more-lessmsg').addClass('cp-hidden'); + $('.cp-callout-more-moremsg').removeClass('cp-hidden'); + } else { + $('.cp-more, .cp-callout-more-lessmsg').removeClass('cp-hidden'); + $('.cp-callout-more-moremsg').addClass('cp-hidden'); + } + showingMore = !showingMore; + } + } + ]); + return [ h('div#cp-main', [ infopageTopbar(), @@ -387,44 +441,8 @@ define([ h('p', Msg.main_catch_phrase) ]), h('div.col-12.col-sm-6', [ - [ - [ 'pad', '/pad/', Msg.main_richTextPad, 'fa-file-word-o' ], - [ 'code', '/code/', Msg.main_codePad, 'fa-file-code-o' ], - [ 'slide', '/slide/', Msg.main_slidePad, 'fa-file-powerpoint-o' ], - [ 'poll.cp-more.cp-hidden', '/poll/', Msg.main_pollPad, 'fa-calendar' ], - [ 'whiteboard.cp-more.cp-hidden', '/whiteboard/', Msg.main_whiteboardPad, 'fa-paint-brush' ], - [ 'recent.cp-more.cp-hidden', '/drive/', Msg.main_localPads, 'fa-hdd-o' ] - ].map(function (x) { - return h('a', [ - { href: x[1] }, - h('div.bs-callout.cp-callout-' + x[0], [ - h('i.fa.' + x[3]), - h('div.pad-button-text', [ h('h4', x[2]) ]) - ]) - ]); - }), - h('div.bs-callout.cp-callout-more', [ - h('div.cp-callout-more-lessmsg.cp-hidden', [ - "see less ", - h('i.fa.fa-caret-up') - ]), - h('div.cp-callout-more-moremsg', [ - "see more ", - h('i.fa.fa-caret-down') - ]), - { - onclick: function () { - if (showingMore) { - $('.cp-more, .cp-callout-more-lessmsg').addClass('cp-hidden'); - $('.cp-callout-more-moremsg').removeClass('cp-hidden'); - } else { - $('.cp-more, .cp-callout-more-lessmsg').removeClass('cp-hidden'); - $('.cp-callout-more-moremsg').addClass('cp-hidden'); - } - showingMore = !showingMore; - } - } - ]) + icons, + more ]) ]) ]), @@ -552,6 +570,19 @@ define([ 'name': 'password', placeholder: Msg.login_password, }), + h('div.checkbox-container', [ + h('input#import-recent', { + name: 'import-recent', + type: 'checkbox', + checked: true + }), + // hscript doesn't generate for on label for some + // reason... use jquery as a temporary fallback + setHTML($('')[0], Msg.register_importRecent) + /*h('label', { + 'for': 'import-recent', + }, Msg.register_importRecent),*/ + ]), h('div.extra', [ h('button.login.first.btn', Msg.login_login) ]) diff --git a/customize.dist/src/less2/include/alertify.less b/customize.dist/src/less2/include/alertify.less index a75a0bb07..06f3ad58a 100644 --- a/customize.dist/src/less2/include/alertify.less +++ b/customize.dist/src/less2/include/alertify.less @@ -125,6 +125,8 @@ width: 100%; } } + display: flex; + flex-flow: column; } width: 100%; @@ -132,6 +134,8 @@ position: relative; top: 50%; transform: translateY(-50%); + max-height: 100%; + display: flex; > * { width: 100%; @@ -148,6 +152,42 @@ padding: @alertify_padding-base; margin-bottom: @alertify_padding-base; margin: 0; + overflow: auto; + .alertify-tabs { + .alertify-tabs-titles { + height: 30px; + display: flex; + border-bottom: 1px solid @alertify-fore; + margin-bottom: 20px; + box-sizing: content-box; + span { + font-size: 20px; + height: 30px; + line-height: 30px; + box-sizing: border-box; + padding: 0 15px; + border-left: 1px solid lighten(@alertify-base, 10%); + border-right: 1px solid lighten(@alertify-base, 10%); + cursor: pointer; + } + span.alertify-tabs-active { + background-color: @alertify-fore; + border-left: 1px solid @alertify-fore; + border-right: 1px solid @alertify-fore; + color: @alertify-base; + font-weight: bold; + cursor: default; + } + } + .alertify-tabs-contents { + & > div { + display: none; + } + & > div.alertify-tabs-content-active { + display: block; + } + } + } } input:not(.form-control), textarea { diff --git a/customize.dist/src/less2/include/ckeditor-fix.less b/customize.dist/src/less2/include/ckeditor-fix.less index bd44c11e1..96e6d893f 100644 --- a/customize.dist/src/less2/include/ckeditor-fix.less +++ b/customize.dist/src/less2/include/ckeditor-fix.less @@ -14,7 +14,6 @@ } .cke_toolbox_main { display: inline-block; - margin-bottom: -3px; } #cke_1_contents { flex: 1; diff --git a/customize.dist/src/less2/include/icons.less b/customize.dist/src/less2/include/icons.less new file mode 100644 index 000000000..a79bc25f5 --- /dev/null +++ b/customize.dist/src/less2/include/icons.less @@ -0,0 +1,39 @@ +.icons_main() { + li { + display: inline-block; + margin: 10px 10px; + width: 140px; + height: 140px; + text-align: center; + vertical-align: top; + overflow: hidden; + text-overflow: ellipsis; + padding-top: 5px; + padding-bottom: 5px; + border: 1px solid white; + + .cp-icons-name { + width: 100%; + height: 24px; + margin: 0; + display: inline-block; + font-size: 14px; + //align-items: center; + //justify-content: center; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + word-wrap: break-word; + } + .fa { + display: block; + font-size: 64px; + margin: 18px 0; + text-align: center; + &.listonly { + display: none; + } + } + } +} + diff --git a/customize.dist/src/less2/include/sidebar-layout.less b/customize.dist/src/less2/include/sidebar-layout.less index 89d23d946..ede03942e 100644 --- a/customize.dist/src/less2/include/sidebar-layout.less +++ b/customize.dist/src/less2/include/sidebar-layout.less @@ -48,6 +48,9 @@ display: block; color: @description-color; margin-bottom: 5px; + p { + margin-bottom: 0; + } } margin-bottom: 20px; } diff --git a/customize.dist/src/less2/include/toolbar.less b/customize.dist/src/less2/include/toolbar.less index 9335058b5..178a922fb 100644 --- a/customize.dist/src/less2/include/toolbar.less +++ b/customize.dist/src/less2/include/toolbar.less @@ -6,6 +6,8 @@ @import (once) "./toolbar-history.less"; @import (once) "./icon-colors.less"; @import (once) "./tools.less"; +@import (once) "./icons.less"; +@import (once) "./modal.less"; .toolbar_main ( @color: @colortheme_default-color, // Color of the text for the toolbar @@ -173,6 +175,68 @@ } } + #cp-app-toolbar-creation-dialog.cp-modal-container { + .icons_main(); + + li:hover { + border: 1px solid white; + } + .cp-modal { + display: flex; + flex-flow: column; + li, li .fa { + cursor: pointer; + } + &> p { + margin: 50px; + } + &> div { + display: flex; + flex-wrap: wrap; + justify-content: center; + align-content: center; + overflow-y: auto; + } + } + + .cp-creation-icons-name { + white-space: nowrap; + } + + #cp-app-toolbar-creation-advanced { + width: auto; + margin: 0; + padding: 0; + } + label[for="cp-app-toolbar-creation-advanced"] { + margin: 0; + margin-left: 5px; + } + + @media screen and (max-height: @browser_media-not-big) { + .cp-modal { + & > p { + display: none; + } + & > div { + align-content: unset; + li { + height: 40px; + width: 200px; + display: flex; + align-items: center; + .fa { + font-size: 32px; + } + .cp-icons-name { + height: auto; + } + } + } + } + } + } + // TODO(cjd) This ought to be in a less file for markdown-based editors .cp-markdown-toolbar { height: @toolbar_line-height; diff --git a/customize.dist/store.js b/customize.dist/store.js deleted file mode 100644 index 5b82921ed..000000000 --- a/customize.dist/store.js +++ /dev/null @@ -1,97 +0,0 @@ -define(function () { - /* - This module uses localStorage, which is synchronous, but exposes an - asyncronous API. This is so that we can substitute other storage - methods. - - To override these methods, create another file at: - /customize/storage.js - */ - - var Store = {}; - - // Store uses nodebacks... - Store.set = function (key, val, cb) { - localStorage.setItem(key, JSON.stringify(val)); - cb(); - }; - - // implement in alternative store - Store.setBatch = function (map, cb) { - Object.keys(map).forEach(function (key) { - localStorage.setItem(key, JSON.stringify(map[key])); - }); - cb(void 0, map); - }; - - var safeGet = window.safeGet = function (key) { - var val = localStorage.getItem(key); - try { - return JSON.parse(val); - } catch (err) { - console.log(val); - console.error(err); - return val; - } - }; - - Store.get = function (key, cb) { - cb(void 0, safeGet(key)); - }; - - // implement in alternative store - Store.getBatch = function (keys, cb) { - var res = {}; - keys.forEach(function (key) { - res[key] = safeGet(key); - }); - cb(void 0, res); - }; - - Store.remove = function (key, cb) { - localStorage.removeItem(key); - cb(); - }; - - // implement in alternative store - Store.removeBatch = function (keys, cb) { - keys.forEach(function (key) { - localStorage.removeItem(key); - }); - cb(); - }; - - Store.keys = function (cb) { - cb(void 0, Object.keys(localStorage)); - }; - - Store.ready = function (f) { - if (typeof(f) === 'function') { - f(void 0, Store); - } - }; - - var changeHandlers = Store.changeHandlers = []; - - Store.change = function (f) { - if (typeof(f) !== 'function') { - throw new Error('[Store.change] callback must be a function'); - } - changeHandlers.push(f); - - if (changeHandlers.length === 1) { - // start listening for changes - window.addEventListener('storage', function (e) { - changeHandlers.forEach(function (f) { - f({ - key: e.key, - oldValue: e.oldValue, - newValue: e.newValue, - }); - }); - }); - } - }; - - return Store; -}); diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 58babab3f..869064a8b 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -29,6 +29,8 @@ define(function () { out.typeError = "Ce pad n'est pas compatible avec l'application sélectionnée"; out.onLogout = 'Vous êtes déconnecté de votre compte utilisateur, cliquez ici pour vous authentifier
ou appuyez sur Échap pour accéder au pad en mode lecture seule.'; out.wrongApp = "Impossible d'afficher le contenu de ce document temps-réel dans votre navigateur. Vous pouvez essayer de recharger la page."; + out.padNotPinned = 'Ce pad va expirer dans 3 mois, {0}connectez-vous{1} ou {2}enregistrez-vous{3} pour le préserver.'; + out.anonymousStoreDisabled = "L'administrateur de cette instance de CryptPad a désactivé le drive pour les utilisateurs non enregistrés. Vous devez vous connecter pour pouvoir utiliser CryptDrive."; out.loading = "Chargement..."; out.error = "Erreur"; @@ -144,6 +146,9 @@ define(function () { out.backgroundButtonTitle = 'Changer la couleur de fond de la présentation'; out.colorButtonTitle = 'Changer la couleur du texte en mode présentation'; + out.propertiesButton = "Propriétés"; + out.propertiesButtonTitle = 'Voir les propriétés de ce pad'; + out.printText = "Imprimer"; out.printButton = "Imprimer (Entrée)"; out.printButtonTitle = "Imprimer votre présentation ou l'enregistrer au format PDF"; @@ -153,6 +158,11 @@ define(function () { out.printTitle = "Afficher le titre du pad"; out.printCSS = "Personnaliser l'apparence (CSS):"; out.printTransition = "Activer les animations de transition"; + out.printBackground = "Utiliser une image d'arrière-plan"; + out.printBackgroundButton = "Choisir une image"; + out.printBackgroundValue = "Arrière-plan actuel: {0}"; + out.printBackgroundNoValue = "Aucun arrière-plan affiché"; + out.printBackgroundRemove = "Supprimer cet arrière-plan"; out.filePickerButton = "Intégrer un fichier stocké dans CryptDrive"; out.filePicker_close = "Fermer"; @@ -345,6 +355,7 @@ define(function () { out.fm_templateName = "Modèles"; out.fm_searchName = "Recherche"; out.fm_recentPadsName = "Pads récents"; + out.fm_ownedPadsName = "Possédés"; out.fm_searchPlaceholder = "Rechercher..."; out.fm_newButton = "Nouveau"; out.fm_newButtonTitle = "Créer un nouveau pad ou un dossier, importer un fichier dans le dossier courant"; @@ -366,6 +377,7 @@ define(function () { 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 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_deleteOwnedPads = "Êtes-vous sûr de vouloir supprimer définitivement ce pad du serveur ?"; 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 ?"; @@ -382,6 +394,7 @@ define(function () { out.fm_info_allFiles = 'Contient tous les fichiers de "Documents", "Fichiers non triés" et "Corbeille". Vous ne pouvez pas supprimer ou déplacer des fichiers depuis cet endroit.'; // Same here out.fm_info_anonymous = 'Vous n\'êtes pas connecté, ces pads risquent donc d\'être supprimés (découvrez pourquoi). ' + 'Inscrivez-vous ou connectez-vous pour les maintenir en vie.'; + out.fm_info_owned = "Vous êtes propriétaire des pads affichés dans cette catégorie. Cela signifie que vous pouvez choisir de les supprimer définitivement du serveur à n'importe quel moment. Ils seront alors inaccessibles pour tous les autres utilisateurs."; out.fm_alert_backupUrl = "Lien de secours pour ce CryptDrive.
" + "Il est fortement recommandé de garder ce lien pour vous-même.
" + "Il vous servira en cas de perte des données de votre navigateur afin de retrouver vos fichiers.
" + @@ -400,12 +413,15 @@ define(function () { out.fm_burnThisDriveButton = "Effacer toutes les informations stockées par CryptPad dans votre navigateur"; out.fm_burnThisDrive = "Êtes-vous sûr de vouloir supprimmer tout ce qui est stocké par CryptPad dans votre navigateur ?
" + "Cette action supprimera votre CryptDrive et son historique de votre navigateur, mais les pads existeront toujours (de manière chiffrée) sur notre serveur."; + out.fm_padIsOwned = "Vous êtes le propriétaire de ce pad"; + out.fm_padIsOwnedOther = "Ce pad est la propriété d'un autre utilisateur"; // File - Context menu out.fc_newfolder = "Nouveau dossier"; out.fc_rename = "Renommer"; out.fc_open = "Ouvrir"; out.fc_open_ro = "Ouvrir (lecture seule)"; out.fc_delete = "Déplacer vers la corbeille"; + out.fc_delete_owned = "Supprimer du serveur"; out.fc_restore = "Restaurer"; out.fc_remove = "Supprimer de votre CryptDrive"; out.fc_empty = "Vider la corbeille"; @@ -473,6 +489,7 @@ define(function () { out.settings_cat_drive = "CryptDrive"; out.settings_cat_code = "Code"; out.settings_cat_pad = "Documents texte"; + out.settings_cat_creation = "Nouveau pad"; out.settings_title = "Préférences"; out.settings_save = "Sauver"; @@ -533,6 +550,14 @@ define(function () { out.settings_padWidthHint = "L'éditeur de documents texte occupe toute la largeur de l'écran disponible par défaut, ce qui peut rendre le texte difficile à lire. Vous pouvez ici réduire la largeur de l'éditeur."; out.settings_padWidthLabel = "Réduire la largeur de l'éditeur"; + out.settings_creationSkip = "Passer l'écran de création de pad"; + out.settings_creationSkipHint = "L'écran de création de pad offre de nouvelles options pour créer un pad, permettant d'avoir plus de contrôle et de sécurité concernant vos données. Toutefois, il peut ralentir votre travail en ajoutant une étape supplémentaire et donc, ici, vous avez la possibilité de choisir de passer cet écran et d'utiliser les paramètres par défaut choisis au-dessus."; + out.settings_creationSkipTrue = "Passer"; + out.settings_creationSkipFalse = "Afficher"; + + out.settings_templateSkip = "Passer la fenêtre de choix d'un modèle"; + out.settings_templateSkipHint = "Quand vous créez un nouveau pad, et si vous possédez des modèles pour ce type de pad, une fenêtre peut apparaître pour demander si vous souhaitez importer un modèle. Ici vous pouvez choisir de ne jamais montrer cette fenêtre et donc de ne jamais utiliser de modèle."; + out.upload_title = "Hébergement de fichiers"; out.upload_rename = "Souhaitez-vous renommer {0} avant son stockage en ligne ?
" + "L'extension du fichier ({1}) sera ajoutée automatiquement. "+ @@ -776,5 +801,16 @@ define(function () { out.feedback_privacy = "Nous prenons au sérieux le respect de votre vie privée, et en même temps nous souhaitons rendre CryptPad très simple à utiliser. Nous utilisons cette page pour comprendre quelles fonctionnalités dans l'interface comptent le plus pour les utilisateurs, en l'appelant avec un paramètre spécifiant quelle action a été réalisée."; out.feedback_optout = "Si vous le souhaitez, vous pouvez désactiver ces requêtes en vous rendant dans votre page de préférences, où vous trouverez une case à cocher pour désactiver le retour d'expérience."; + // Creation page + // Properties about creation data + out.creation_owners = "Propriétaires"; + out.creation_ownedByOther = "Possédé par un autre utilisateur"; + out.creation_noOwner = "Pas de propriétaire"; + out.creation_expiration = "Date d'expiration"; + out.creation_propertiesTitle = "Disponibilité"; + out.creation_appMenuName = "Mode avancé (Ctrl + E)"; + out.creation_newPadModalDescription = "Cliquez sur un type de pad pour le créer. Vous pouvez cocher la case pour afficher l'écran de création de pads"; + out.creation_newPadModalAdvanced = "Afficher l'écran de création de pads"; + return out; }); diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index ecc4060ec..6ac63c4ef 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -32,6 +32,7 @@ define(function () { out.onLogout = 'You are logged out, click here to log in
or press Escape to access your pad in read-only mode.'; out.wrongApp = "Unable to display the content of that realtime session in your browser. Please try to reload that page."; out.padNotPinned = 'This pad will expire in 3 months, {0}login{1} or {2}register{3} to preserve it.'; + out.anonymousStoreDisabled = "The webmaster of this CryptPad instance has disabled the store for anonymous users. You have to log in to be able to use CryptDrive."; out.loading = "Loading..."; out.error = "Error"; @@ -147,6 +148,9 @@ define(function () { out.backgroundButtonTitle = 'Change the background color in the presentation'; out.colorButtonTitle = 'Change the text color in presentation mode'; + out.propertiesButton = "Properties"; + out.propertiesButtonTitle = 'Get pad properties'; + out.printText = "Print"; out.printButton = "Print (enter)"; out.printButtonTitle = "Print your slides or export them as a PDF file"; @@ -156,6 +160,11 @@ define(function () { out.printTitle = "Display the pad title"; out.printCSS = "Custom style rules (CSS):"; out.printTransition = "Enable transition animations"; + out.printBackground = "Use a background image"; + out.printBackgroundButton = "Pick an image"; + out.printBackgroundValue = "Current background: {0}"; + out.printBackgroundNoValue = "No background image displayed"; + out.printBackgroundRemove = "Remove this background image"; out.filePickerButton = "Embed a file stored in CryptDrive"; out.filePicker_close = "Close"; @@ -348,6 +357,7 @@ define(function () { out.fm_templateName = "Templates"; out.fm_searchName = "Search"; out.fm_recentPadsName = "Recent pads"; + out.fm_ownedPadsName = "Owned"; out.fm_searchPlaceholder = "Search..."; out.fm_newButton = "New"; out.fm_newButtonTitle = "Create a new pad or folder, import a file in the current folder"; @@ -371,6 +381,7 @@ define(function () { 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_deleteOwnedPads = "Are you sure you want to remove permanently this pad from the server?"; out.fm_restoreDialog = "Are you sure you want to restore {0} to its previous location?"; out.fm_unknownFolderError = "The selected or last visited directory no longer exist. Opening the parent folder..."; out.fm_contextMenuError = "Unable to open the context menu for that element. If the problem persist, try to reload the page."; @@ -385,6 +396,7 @@ define(function () { out.fm_info_allFiles = 'Contains all the files from "Documents", "Unsorted" and "Trash". You can\'t move or remove files from here.'; // Same here out.fm_info_anonymous = 'You are not logged in so your pads will expire after 3 months (find out more). ' + 'Sign up or Log in to keep them alive.'; + out.fm_info_owned = "You are the owner of the pads displayed here. This means you can remove them permanently from the server whenever you want. If you do so, other users won't be able to access them anymore."; out.fm_alert_backupUrl = "Backup link for this drive.
" + "It is highly recommended that you keep it secret.
" + "You can use it to retrieve all your files in case your browser memory got erased.
" + @@ -403,12 +415,15 @@ define(function () { out.fm_burnThisDriveButton = "Erase all information stored by CryptPad in your browser"; out.fm_burnThisDrive = "Are you sure you want to remove everything stored by CryptPad in your browser?
" + "This will remove your CryptDrive and its history from your browser, but your pads will still exist (encrypted) on our server."; + out.fm_padIsOwned = "You are the owner of this pad"; + out.fm_padIsOwnedOther = "This pad is owned by another user"; // File - Context menu out.fc_newfolder = "New folder"; out.fc_rename = "Rename"; out.fc_open = "Open"; out.fc_open_ro = "Open (read-only)"; out.fc_delete = "Move to trash"; + out.fc_delete_owned = "Delete from the server"; out.fc_restore = "Restore"; out.fc_remove = "Remove from your CryptDrive"; out.fc_empty = "Empty the trash"; @@ -479,6 +494,7 @@ define(function () { out.settings_cat_drive = "CryptDrive"; out.settings_cat_code = "Code"; out.settings_cat_pad = "Rich text"; + out.settings_cat_creation = "New pad"; out.settings_title = "Settings"; out.settings_save = "Save"; @@ -539,6 +555,14 @@ define(function () { out.settings_padWidthHint = "Rich text pads use by default the maximum available width on your screen and it can be difficult to read. You can reduce the editor's width here."; out.settings_padWidthLabel = "Reduce the editor's width"; + out.settings_creationSkip = "Skip the pad creation screen"; + out.settings_creationSkipHint = "The pad creation screen offers new options to create a pad, providing you more control and security over your data. However, it may slow down your workflow by adding one additionnal step so, here, you have the option to skip this screen and use the default settings selected above."; + out.settings_creationSkipTrue = "Skip"; + out.settings_creationSkipFalse = "Display"; + + out.settings_templateSkip = "Skip the template selection modal"; + out.settings_templateSkipHint = "When you create a new empty pad, if you have stored templates for this type of pad, a modal appears to ask if you want to use a template. Here you can choose to never show this modal and so to never use a template."; + out.upload_title = "File upload"; out.upload_rename = "Do you want to rename {0} before uploading it to the server?
" + "The file extension ({1}) will be added automatically. "+ @@ -805,11 +829,20 @@ define(function () { out.creation_expireHours = "Hours"; out.creation_expireDays = "Days"; out.creation_expireMonths = "Months"; - out.creation_expire1 = "By default, a pad stored by a registered users will never be removed from the server, unless it is requested by its owner."; + out.creation_expire1 = "By default, a pad stored by a registered user will never be removed from the server, unless it is requested by its owner."; out.creation_expire2 = "If you prefer, you can set a life time to make sure the pad will be permanently deleted from the server and unavailable after the specified date."; out.creation_createTitle = "Create a pad"; out.creation_createFromTemplate = "From template"; out.creation_createFromScratch = "From scratch"; + // Properties about creation data + out.creation_owners = "Owners"; + out.creation_ownedByOther = "Owned by another user"; + out.creation_noOwner = "No owner"; + out.creation_expiration = "Expiration time"; + out.creation_propertiesTitle = "Availability"; + out.creation_appMenuName = "Advanced mode (Ctrl + E)"; + out.creation_newPadModalDescription = "Click on a pad type to create it. You can check the box if you want to display the pad creation screen (for owned pad, expiration pad, etc.)."; + out.creation_newPadModalAdvanced = "Display the pad creation screen"; return out; }); diff --git a/package.json b/package.json index c58ec7e63..e6862775b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cryptpad", "description": "realtime collaborative visual editor with zero knowlege server", - "version": "1.23.0", + "version": "1.24.0", "dependencies": { "chainpad-server": "^1.0.1", "express": "~4.10.1", diff --git a/runtests.js b/runtests.js index bb7b49f5c..6d275d5d6 100644 --- a/runtests.js +++ b/runtests.js @@ -70,6 +70,8 @@ run('npm', ['install'], () => { ' | awk \'{print $2}\' | while read x; do kill $x; done' ], waitFor()); + run('bash', ['-c', 'rm -rf ./blob ./blobstage ./datastore'], waitFor()); + run('bash', ['-c', 'caffeinate -u -t 2'], waitFor()); } }).nThen((waitFor) => { diff --git a/storage/file.js b/storage/file.js index 31b58aa94..46f8a83f5 100644 --- a/storage/file.js +++ b/storage/file.js @@ -45,7 +45,7 @@ var getChannelMetadata = function (Env, channelId, cb) { }; var closeChannel = function (env, channelName, cb) { - if (!env.channels[channelName]) { return; } + if (!env.channels[channelName]) { return void cb(); } try { env.channels[channelName].writeStream.close(); delete env.channels[channelName]; @@ -177,13 +177,13 @@ var getChannel = function (env, id, callback) { }, env.channelExpirationMs / 2); }); } - + var path = mkPath(env, id); var channel = env.channels[id] = { atime: +new Date(), - messages: [], writeStream: undefined, whenLoaded: [ callback ], - onError: [ ] + onError: [ ], + path: path }; var complete = function (err) { var whenLoaded = channel.whenLoaded; @@ -195,7 +195,6 @@ var getChannel = function (env, id, callback) { } whenLoaded.forEach(function (wl) { wl(err, (err) ? undefined : channel); }); }; - var path = mkPath(env, id); var fileExists; var errorState; nThen(function (waitFor) { @@ -207,17 +206,6 @@ var getChannel = function (env, id, callback) { } fileExists = exists; })); - }).nThen(function (waitFor) { - if (errorState) { return; } - if (!fileExists) { return; } - readMessages(path, function (msg) { - channel.messages.push(msg); - }, waitFor(function (err) { - if (err) { - errorState = true; - complete(err); - } - })); }).nThen(function (waitFor) { if (errorState) { return; } var stream = channel.writeStream = Fs.createWriteStream(path, { flags: 'a' }); @@ -255,7 +243,7 @@ var message = function (env, chanName, msg, cb) { chan.writeStream.write(msg + '\n', function () { chan.onError.splice(chan.onError.indexOf(complete) - 1, 1); if (!cb) { return; } - chan.messages.push(msg); + //chan.messages.push(msg); chan.atime = +new Date(); complete(); }); @@ -268,19 +256,25 @@ var getMessages = function (env, chanName, handler, cb) { cb(err); return; } + var errorState = false; try { - chan.messages - .forEach(function (message) { - if (!message) { return; } - handler(message); - }); + readMessages(chan.path, function (msg) { + if (!msg || errorState) { return; } + //console.log(msg); + handler(msg); + }, function (err) { + if (err) { + errorState = true; + return void cb(err); + } + chan.atime = +new Date(); + cb(); + }); } catch (err2) { console.error(err2); cb(err2); return; } - chan.atime = +new Date(); - cb(); }); }; diff --git a/www/assert/main.js b/www/assert/main.js index c792c780c..eb470eb35 100644 --- a/www/assert/main.js +++ b/www/assert/main.js @@ -134,12 +134,12 @@ define([ // check that old hashes parse correctly assert(function (cb) { - if (1) { return cb(true); } // TODO(cjd): This is a test failure which is a known bug + //if (1) { return cb(true); } // TODO(cjd): This is a test failure which is a known bug var secret = Hash.parsePadUrl('/pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy'); return cb(secret.hashData.channel === "67b8385b07352be53e40746d2be6ccd7" && secret.hashData.key === "XAYSuJYYqa9NfmInyHci7LNy" && secret.hashData.version === 0 && - typeof(secret.hashData.getURL) === 'function'); + typeof(secret.getUrl) === 'function'); }, "Old hash failed to parse"); // make sure version 1 hashes parse correctly diff --git a/www/code/inner.js b/www/code/inner.js index 7417206c4..d1e402aaa 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -268,11 +268,11 @@ define([ //// framework.onContentUpdate(function (newContent) { - CodeMirror.contentUpdate(newContent); var highlightMode = newContent.highlightMode; if (highlightMode && highlightMode !== CodeMirror.highlightMode) { CodeMirror.setMode(highlightMode, evModeChange.fire); } + CodeMirror.contentUpdate(newContent); previewPane.draw(); }); @@ -357,7 +357,7 @@ define([ getContainer: getThumbnailContainer, filter: function (el, before) { if (before) { - $(el).parents().css('overflow', 'visible'); + //$(el).parents().css('overflow', 'visible'); $(el).css('max-height', Math.max(600, $(el).width()) + 'px'); return; } diff --git a/www/common/common-constants.js b/www/common/common-constants.js index 044319069..494267ed3 100644 --- a/www/common/common-constants.js +++ b/www/common/common-constants.js @@ -11,5 +11,6 @@ define(function () { oldStorageKey: 'CryptPad_RECENTPADS', storageKey: 'filesData', tokenKey: 'loginToken', + displayPadCreationScreen: 'displayPadCreationScreen' }; }); diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 5cbcaa5f5..14cb3e53a 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -128,6 +128,38 @@ define([ ]); }; + /** + * tabs is an array containing objects + * each object must have the following attributes: + * - title: String + * - content: DOMElement + */ + dialog.tabs = function (tabs) { + var contents = []; + var titles = []; + tabs.forEach(function (tab) { + if (!tab.content || !tab.title) { return; } + var content = tab.content; + var title = h('span.alertify-tabs-title', tab.title); + $(title).click(function () { + titles.forEach(function (t) { $(t).removeClass('alertify-tabs-active'); }); + contents.forEach(function (c) { $(c).removeClass('alertify-tabs-content-active'); }); + $(title).addClass('alertify-tabs-active'); + $(content).addClass('alertify-tabs-content-active'); + }); + titles.push(title); + contents.push(content); + }); + if (contents.length) { + $(contents[0]).addClass('alertify-tabs-content-active'); + $(titles[0]).addClass('alertify-tabs-active'); + } + return h('div.alertify-tabs', [ + h('div.alertify-tabs-titles', titles), + h('div.alertify-tabs-contents', contents), + ]); + }; + UI.tokenField = function (target) { var t = { element: target || h('input'), @@ -464,6 +496,7 @@ define([ UI.removeLoadingScreen = function (cb) { // Release the test blocker, hopefully every test has been registered. // This test is created in sframe-boot2.js + cb = cb || function () {}; if (Test.__ASYNC_BLOCKER__) { Test.__ASYNC_BLOCKER__.pass(); } $('#' + LOADING).addClass("cp-loading-hidden"); @@ -476,9 +509,9 @@ define([ 'opacity': 0, 'pointer-events': 'none', }); - setTimeout(function () { - $tip.remove(); - }, 3750); + window.setTimeout(function () { + $tip.remove(); + }, 3750); // jquery.fadeout can get stuck }; UI.errorLoadingScreen = function (error, transparent) { diff --git a/www/common/common-thumbnail.js b/www/common/common-thumbnail.js index 18e5dff52..f0ba27194 100644 --- a/www/common/common-thumbnail.js +++ b/www/common/common-thumbnail.js @@ -183,8 +183,10 @@ define([ allowTaint: true, onrendered: function (canvas) { if (opts.filter) { opts.filter(element, false); } - var D = getResizedDimensions(canvas, 'pad'); - Thumb.fromCanvas(canvas, D, cb); + setTimeout(function () { + var D = getResizedDimensions(canvas, 'pad'); + Thumb.fromCanvas(canvas, D, cb); + }, 10); } }); }; diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index c426a0272..aad840259 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -5,13 +5,17 @@ define([ '/common/common-hash.js', '/common/common-language.js', '/common/common-interface.js', + '/common/common-constants.js', '/common/common-feedback.js', '/common/hyperscript.js', '/common/media-tag.js', '/customize/messages.js', + '/customize/application_config.js', + '/bower_components/nthen/index.js', 'css!/common/tippy.css', -], function ($, Config, Util, Hash, Language, UI, Feedback, h, MediaTag, Messages) { +], function ($, Config, Util, Hash, Language, UI, Constants, Feedback, h, MediaTag, Messages, + AppConfig, NThen) { var UIElements = {}; // Configure MediaTags to use our local viewer @@ -54,6 +58,174 @@ define([ }; }; + var getPropertiesData = function (common, cb) { + var data = {}; + NThen(function (waitFor) { + common.getPadAttribute('href', waitFor(function (err, val) { + var base = common.getMetadataMgr().getPrivateData().origin; + + var parsed = Hash.parsePadUrl(val); + if (parsed.hashData.mode === "view") { + data.roHref = base + val; + return; + } + + // We're not in a read-only pad + data.href = base + val; + // Get Read-only href + if (parsed.hashData.type !== "pad") { return; } + var i = data.href.indexOf('#') + 1; + var hBase = data.href.slice(0, i); + var hrefsecret = Hash.getSecrets(parsed.type, parsed.hash); + if (!hrefsecret.keys) { return; } + var viewHash = Hash.getViewHashFromKeys(hrefsecret.channel, hrefsecret.keys); + data.roHref = hBase + viewHash; + })); + common.getPadAttribute('atime', waitFor(function (err, val) { + data.atime = val; + })); + common.getPadAttribute('ctime', waitFor(function (err, val) { + data.ctime = val; + })); + common.getPadAttribute('tags', waitFor(function (err, val) { + data.tags = val; + })); + common.getPadAttribute('owners', waitFor(function (err, val) { + data.owners = val; + })); + common.getPadAttribute('expire', waitFor(function (err, val) { + data.expire = val; + })); + }).nThen(function () { + cb(void 0, data); + }); + }; + var getRightsProperties = function (common, data, cb) { + var $d = $('
'); + if (!data) { return void cb(void 0, $d); } + + $('