diff --git a/config.example.js b/config.example.js index 2c234aee4..3aace0d4a 100644 --- a/config.example.js +++ b/config.example.js @@ -126,7 +126,8 @@ module.exports = { 'about', 'contact', 'what-is-cryptpad', - 'features' + 'features', + 'faq' ], /* Limits, Donations, Subscriptions and Contact @@ -248,6 +249,14 @@ module.exports = { */ pinPath: './pins', + /* Pads that are not 'pinned' by any registered user can be set to expire + * after a configurable number of days of inactivity (default 90 days). + * The value can be changed or set to false to remove expiration. + * Expired pads can then be removed using a cron job calling the + * `delete-inactive.js` script with node + */ + inactiveTime: 90, // days + /* CryptPad allows logged in users to upload encrypted files. Files/blobs * are stored in a 'blob-store'. Set its location here. */ diff --git a/customize.dist/faq.html b/customize.dist/faq.html new file mode 100644 index 000000000..d485505dd --- /dev/null +++ b/customize.dist/faq.html @@ -0,0 +1,17 @@ + + + + + CryptPad: Zero Knowledge, Collaborative Real Time Editing + + + + + + + + + diff --git a/customize.dist/login.js b/customize.dist/login.js index 5511637e8..fde218d21 100644 --- a/customize.dist/login.js +++ b/customize.dist/login.js @@ -191,8 +191,10 @@ define([ window.location.href = '/drive/'; }; + var hashing; Exports.loginOrRegisterUI = function (uname, passwd, isRegister, shouldImport, testing, test) { - var hashing = true; + if (hashing) { return void console.log("hashing is already in progress"); } + hashing = true; var proceed = function (result) { hashing = false; @@ -275,7 +277,7 @@ define([ proceed(result); }); - }, 0); + }, 500); }, 200); }; diff --git a/customize.dist/pages.js b/customize.dist/pages.js index a9fbeabde..9dd366da2 100644 --- a/customize.dist/pages.js +++ b/customize.dist/pages.js @@ -72,7 +72,7 @@ define([ ]) ]) ]), - h('div.cp-version-footer', "CryptPad v1.26.0 (undefined)") + h('div.cp-version-footer', "CryptPad v1.27.0 (null)") ]); }; @@ -373,6 +373,42 @@ define([ ]); }; + Pages['/faq.html'] = function () { + var categories = []; + var faq = Msg.faq; + Object.keys(faq).forEach(function (c) { + var questions = []; + Object.keys(faq[c]).forEach(function (q) { + var item = faq[c][q]; + if (typeof item !== "object") { return; } + var answer = h('p.cp-faq-questions-a'); + var question = h('p.cp-faq-questions-q'); + $(question).click(function () { + if ($(answer).is(':visible')) { + return void $(answer).slideUp(); + } + $(answer).slideDown(); + }); + questions.push(h('div.cp-faq-questions-items', [ + setHTML(question, item.q), + setHTML(answer, item.a) + ])); + }); + categories.push(h('div.cp-faq-category', [ + h('h3', faq[c].title), + h('div.cp-faq-category-questions', questions) + ])); + }); + return h('div#cp-main', [ + infopageTopbar(), + h('div.container.cp-container', [ + h('center', h('h1', Msg.faq_title)), + h('div.cp-faq-container', categories) + ]), + infopageFooter() + ]); + }; + Pages['/terms.html'] = function () { return h('div#cp-main', [ infopageTopbar(), @@ -720,10 +756,14 @@ define([ Pages['/whiteboard/'] = Pages['/whiteboard/index.html'] = function () { return [ appToolbar(), - h('div#cp-app-whiteboard-canvas-area', h('canvas#cp-app-whiteboard-canvas', { - width: 600, - height: 600 - })), + h('div#cp-app-whiteboard-canvas-area', + h('div#cp-app-whiteboard-container', + h('canvas#cp-app-whiteboard-canvas', { + width: 600, + height: 600 + }) + ) + ), h('div#cp-app-whiteboard-controls', { style: { display: 'block', @@ -783,12 +823,6 @@ define([ appToolbar(), h('div#cp-app-poll-content', [ h('div#cp-app-poll-form', [ - h('div#cp-app-poll-help', [ - h('h1', 'CryptPoll'), - setHTML(h('h2'), Msg.poll_subtitle), - h('p', Msg.poll_p_save), - h('p', Msg.poll_p_encryption) - ]), h('div.cp-app-poll-realtime', [ h('br'), h('div', [ diff --git a/customize.dist/src/less2/include/ckeditor-fix.less b/customize.dist/src/less2/include/ckeditor-fix.less index 96e6d893f..98672bd86 100644 --- a/customize.dist/src/less2/include/ckeditor-fix.less +++ b/customize.dist/src/less2/include/ckeditor-fix.less @@ -3,28 +3,9 @@ .cke_reset_all * { color: inherit; } - #cke_editor1 .cke_inner { - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - display: flex; - flex-flow: column; - } .cke_toolbox_main { display: inline-block; } - #cke_1_contents { - flex: 1; - margin-top: -1px; - display: flex; - overflow: visible; - iframe { - min-height: 100%; - width: 100%; - } - } .cke_toolbox .cp-toolbar-history { input.gotoInput { // TODO padding: 3px 3px; diff --git a/customize.dist/src/less2/include/colortheme.less b/customize.dist/src/less2/include/colortheme.less index c8848d02b..1a2b143e5 100644 --- a/customize.dist/src/less2/include/colortheme.less +++ b/customize.dist/src/less2/include/colortheme.less @@ -44,11 +44,11 @@ @colortheme_pad-bg: #1c4fa0; @colortheme_pad-color: #fff; @colortheme_pad-toolbar-bg: #c1e7ff; -@colortheme_pad-warn: #F83A3A; +@colortheme_pad-warn: #ffae00; @colortheme_slide-bg: #e57614; @colortheme_slide-color: #fff; -@colortheme_slide-warn: #58D697; +@colortheme_slide-warn: #005868; @colortheme_code-bg: #ffae00; @colortheme_code-color: #000; @@ -59,7 +59,7 @@ @colortheme_poll-help-bg: #bbffbb; @colortheme_poll-th-bg: #005bef; @colortheme_poll-th-fg: #fff; -@colortheme_poll-warn: #ffae00; +@colortheme_poll-warn: #ffade3; @colortheme_whiteboard-bg: #800080; @colortheme_whiteboard-color: #fff; diff --git a/customize.dist/src/less2/include/creation.less b/customize.dist/src/less2/include/creation.less index 58d5a53ba..16bce0637 100644 --- a/customize.dist/src/less2/include/creation.less +++ b/customize.dist/src/less2/include/creation.less @@ -25,6 +25,7 @@ text-align: center; font: @colortheme_app-font; width: 100%; + outline: none; & > div { width: 60vw; max-width: 100%; @@ -75,7 +76,7 @@ } } - .cp-creation-create { + .cp-creation-create, .cp-creation-settings { button { .tools_unselectable(); padding: 15px; @@ -84,9 +85,14 @@ margin: 3px 10px; border: none; cursor: pointer; + outline: none; &:hover { background: darken(@colortheme_loading-bg, 5%); } + &.cp-creation-button-selected { + color: darken(@colortheme_loading-bg, 10%); + background: @colortheme_loading-color; + } } } @@ -159,6 +165,9 @@ color: lighten(#0275d8, 10%); } } + &> span.fa { + margin-left: 15px; + } } .cp-creation-deleted { background: #111; diff --git a/customize.dist/src/less2/include/help.less b/customize.dist/src/less2/include/help.less new file mode 100644 index 000000000..905c453fb --- /dev/null +++ b/customize.dist/src/less2/include/help.less @@ -0,0 +1,32 @@ +@import (once) "./colortheme-all.less"; + +.help_main (@color, @bg-color) { + .cp-help-container { + position: relative; + background-color: lighten(@bg-color, 15%); + &.cp-help-hidden { + display: none; + } + + .cp-help-close { + position: absolute; + top: 5px; + right: 5px; + } + .cp-help-text { + color: @color; + margin: 0; + padding: 15px; + h1 { + font-size: 20px; + } + h2 { + font-size: 18px; + } + h3 { + font-size: 16px; + } + ul, ol, p { margin: 0; } + } + } +} diff --git a/customize.dist/src/less2/include/icons.less b/customize.dist/src/less2/include/icons.less index a79bc25f5..03a6af5ab 100644 --- a/customize.dist/src/less2/include/icons.less +++ b/customize.dist/src/less2/include/icons.less @@ -25,6 +25,10 @@ text-overflow: ellipsis; word-wrap: break-word; } + &.cp-icons-element-selected { + background-color: white; + color: #666; + } .fa { display: block; font-size: 64px; diff --git a/customize.dist/src/less2/include/markdown-toolbar.less b/customize.dist/src/less2/include/markdown-toolbar.less new file mode 100644 index 000000000..4fb466525 --- /dev/null +++ b/customize.dist/src/less2/include/markdown-toolbar.less @@ -0,0 +1,20 @@ +@import (once) "./colortheme-all.less"; + +.markdownToolbar_main (@color, @bg-color) { + .cp-markdown-toolbar { + height: @toolbar_line-height; + background-color: lighten(@bg-color, 20%); + display: none; + button { + height: @toolbar_line-height !important; + outline: 0; + color: @color; + .toolbar_button; + font: normal normal normal 14px/1 FontAwesome; + &:hover { + background-color: lighten(@bg-color, 8%); + } + &.cp-markdown-help { float: right; } + } + } +} diff --git a/customize.dist/src/less2/include/toolbar.less b/customize.dist/src/less2/include/toolbar.less index 435476090..a0c01e377 100644 --- a/customize.dist/src/less2/include/toolbar.less +++ b/customize.dist/src/less2/include/toolbar.less @@ -8,6 +8,8 @@ @import (once) "./tools.less"; @import (once) "./icons.less"; @import (once) "./modal.less"; +@import (once) "./markdown-toolbar.less"; +@import (once) "./help.less"; .toolbar_main ( @color: @colortheme_default-color, // Color of the text for the toolbar @@ -24,6 +26,8 @@ .ckeditor_fix(); .history_main(); .iconColors_main(); + .markdownToolbar_main(@color, @bg-color); + .help_main(@color, @bg-color); .cp-toolbar-container { display: flex; @@ -208,6 +212,7 @@ width: auto; margin: 0; padding: 0; + outline: none; } label[for="cp-app-toolbar-creation-advanced"] { margin: 0; @@ -238,23 +243,6 @@ } } - // TODO(cjd) This ought to be in a less file for markdown-based editors - .cp-markdown-toolbar { - height: @toolbar_line-height; - background-color: lighten(@bg-color, 20%); - display: none; - button { - height: @toolbar_line-height !important; - outline: 0; - color: @color; - .toolbar_button; - font: normal normal normal 14px/1 FontAwesome; - &:hover { - background-color: lighten(@bg-color, 8%); - } - &.cp-markdown-help { float: right; } - } - } .cp-toolbar-userlist-drawer { background-color: @bg-color; color: @color; @@ -356,11 +344,6 @@ margin: 0; }*/ - .cp-toolbar-rightside-button { - float: right; - cursor: pointer; - } - select { margin-left: 5px; margin-right: 5px; @@ -422,6 +405,7 @@ font-size: @colortheme_app-font-size; flex: 1; max-width: none; + line-height: calc(@toolbar_line-height - 12px); // padding + border } } } @@ -442,13 +426,6 @@ background-color: @bg-color; } } - .cp-toolbar-rightside { - @media screen and (max-width: @barWidth) { // 450px - flex-wrap: wrap; - height: auto; - width: 100%; - } - } .cp-toolbar-title-hoverable:hover { .cp-toolbar-title-editable, .cp-toolbar-title-edit { cursor: text; @@ -505,6 +482,7 @@ font-size: @colortheme_app-font-size; a { font-size: @colortheme_app-font-size; + font-family: @colortheme_font; font-weight: bold; color: @warn-color; &:hover { @@ -593,7 +571,7 @@ } input { max-width: ~"calc(100% - 40px)"; - flex: 1; + //flex: 1; vertical-align: middle; box-sizing: border-box; cursor: auto; @@ -601,6 +579,7 @@ font-size: 20px; padding: 5px 5px; height: 40px; + line-height: 28px; // padding + border } } .cp-toolbar-link, .cp-toolbar-new { @@ -792,16 +771,41 @@ } .cp-toolbar-share-button { width: 50px; + text-align: center; } } .cp-toolbar-rightside { + display: flex; min-height: @toolbar_line-height; overflow: hidden; + @media screen and (max-width: @barWidth) { // 450px + flex-wrap: wrap; + height: auto; + width: 100%; + } &:empty { min-height: 0; height: 0; } - text-align: right; + + .cp-toolbar-rightside-button { + cursor: pointer; + // UI actions + &.cp-toolbar-icon-toggle { order: 1; } + &.cp-toolbar-icon-preview { order: 2; } + &.cp-toolbar-icon-present { order: 3; } + // Content actions + &.cp-toolbar-icon-mediatag { order: 10; } + order: 11; + // Storage actions + &.cp-toolbar-icon-hashtag { order: 20; } + &.cp-toolbar-icon-template { order: 21; } + &.cp-toolbar-icon-forget { order: 22; } + // Drawer + &.cp-toolbar-drawer-button { order: 30; } + + } + .cp-toolbar-drawer-content:empty ~ .cp-toolbar-drawer-button { display: none; } @@ -820,6 +824,7 @@ font-size: 17px; } &> span { + order: 8; box-sizing: border-box; min-width: 150px; height: @toolbar_line-height; @@ -834,15 +839,36 @@ border: 0; width: 100%; line-height: 1em; + &.cp-toolbar-button-active { + background-color: inherit; + } .cp-toolbar-drawer-element { margin-left: 10px; display: inline; - vertical-align: top; + vertical-align: baseline; + } + &.fa-info-circle, &.fa-history, &.fa-cog { + .cp-toolbar-drawer-element { + margin-left: 11px; + } + } + &.fa-question { + .cp-toolbar-drawer-element { + margin-left: 16px; + } } &:hover { background-color: @colortheme_dropdown-bg-hover !important; color: @colortheme_dropdown-color; } + order: 8; + &.fa-history { order: 1; } + &.fa-download { order: 2; } + &.fa-upload { order: 3; } + &.fa-print { order: 4; } + &.fa-cog { order: 5; } + &.fa-info-circle { order: 6; } + &.fa-help { order: 7; } } } } diff --git a/customize.dist/src/less2/main.less b/customize.dist/src/less2/main.less index c1bb5bba4..dadbef539 100644 --- a/customize.dist/src/less2/main.less +++ b/customize.dist/src/less2/main.less @@ -10,6 +10,7 @@ body.cp-page-what-is-cryptpad { @import "./pages/page-what-is-cryptpad.less"; } body.cp-page-about { @import "./pages/page-about.less"; } body.cp-page-privacy { @import "./pages/page-privacy.less"; } body.cp-page-features { @import "./pages/page-features.less"; } +body.cp-page-faq { @import "./pages/page-faq.less"; } body.cp-page-terms { @import "./pages/page-terms.less"; } // Set the HTML style for the apps which shouldn't have a body scrollbar diff --git a/customize.dist/src/less2/pages/page-faq.less b/customize.dist/src/less2/pages/page-faq.less new file mode 100644 index 000000000..e24dc767c --- /dev/null +++ b/customize.dist/src/less2/pages/page-faq.less @@ -0,0 +1,30 @@ +@import (once) "../include/infopages.less"; +@import (once) "../include/colortheme-all.less"; + +.infopages_main(); +.infopages_topbar(); + +.cp-faq-container { + .cp-faq-questions-q { + color: #3a84b6; + padding: 0; + margin-bottom: 0; + margin-top: 5px; + cursor: pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } + .cp-faq-questions-q:hover { + color: #2e688f; + text-decoration: underline; + } + .cp-faq-questions-a { + display: none; + padding: 0; + } +} + diff --git a/customize.dist/translations/messages.el.js b/customize.dist/translations/messages.el.js index 1e30248ec..36a19da87 100644 --- a/customize.dist/translations/messages.el.js +++ b/customize.dist/translations/messages.el.js @@ -714,13 +714,8 @@ define(function () { '

', 'Αυτό είναι CryptPad, ο συνεργατικός επεξεργαστής πραγματικού χρόνου Zero Knowledge. Τα πάντα αποθηκεύονται καθώς πληκτρολογείτε.', '
', - 'Μοιραστείτε τον σύνδεσμο σε αυτό το pad για να το επεξεργαστείτε με φίλους ή χρησιμοποιήστε το κουμπί  Share  για να μοιραστείτε ένα κείμενο με δικαιώματα read-only link το οποίο επιτρέπει να το αναγνώσει κάποιος αλλά όχι να το επεξεργαστεί.', + 'Μοιραστείτε τον σύνδεσμο σε αυτό το pad για να το επεξεργαστείτε με φίλους ή χρησιμοποιήστε το κουμπί για να μοιραστείτε ένα κείμενο με δικαιώματα read-only link το οποίο επιτρέπει να το αναγνώσει κάποιος αλλά όχι να το επεξεργαστεί.', '

', - - '

', - 'Εμπρός, απλά ξεκινήστε να πληκτρολογείτε...', - '

', - '

 

' ].join(''); out.codeInitialState = [ @@ -732,14 +727,6 @@ define(function () { out.slideInitialState = [ '# CryptSlide\n', - '* Αυτός είναι ένας συνεργατικός επεξεργαστής πραγματικού χρόνου με τεχνολογία zero knowledge.\n', - '* Ό,τι πληκτρολογείτε εδώ είναι κρυπτογραφημένο έτσι ώστε μόνο οι άνθρωποι που έχουν τον σύνδεσμο να μπορούν να έχουν πρόσβαση.\n', - '* Ακόμη κι ο διακομιστής δεν μπορεί να δει τι πληκτρολογείτε.\n', - '* Ό,τι δείτε εδώ, ό,τι ακούσετε εδώ, όταν φύγετε από εδώ, θα παραμείνει εδώ.\n', - '\n', - '---', - '\n', - '# Πως να το χρησιμοποιήσετε\n', '1. Γράψτε τα περιεχόμενα των slides σας χρησιμοποιώντας σύνταξη markdown\n', ' - Μάθετε περισσότερα για την σύνταξη markdown [εδώ](http://www.markdowntutorial.com/)\n', '2. Διαχωρίστε τα slides σας με ---\n', diff --git a/customize.dist/translations/messages.es.js b/customize.dist/translations/messages.es.js index 6df44d691..9e246294f 100644 --- a/customize.dist/translations/messages.es.js +++ b/customize.dist/translations/messages.es.js @@ -294,17 +294,12 @@ define(function () { '

', 'Esto es CryptPad, el editor colaborativo en tiempo real Zero Knowledge. Todo está guardado cuando escribes.', '
', - 'Comparte el enlace a este pad para editar con amigos o utiliza el botón  Compartir  para obtener un enlace sólo lectura que permite leer pero no escribir.', + 'Comparte el enlace a este pad para editar con amigos o utiliza el botón para obtener un enlace sólo lectura que permite leer pero no escribir.', '

', - - '

', - 'Vamos, empieza a escribir...', - '

', - '

 

' ].join(''); out.codeInitialState = "/*\n Esto es CryptPad, el editor colaborativo en tiempo real zero knowledge.\n Lo que escribes aquí está cifrado de manera que sólo las personas con el enlace pueden acceder a ello.\n Incluso el servidor no puede ver lo que escribes.\n Lo que ves aquí, lo que escuchas aquí, cuando sales, se queda aquí\n*/"; - out.slideInitialState = "# CryptSlide\n* Esto es CryptPad, el editor colaborativo en tiempo real zero knowledge.\n* Lo que escribes aquí está cifrado de manera que sólo las personas con el enlace pueden acceder a ello.\n* Incluso el servidor no puede ver lo que escribes.\n* Lo que ves aquí, lo que escuchas aquí, cuando sales, se queda aquí\n\n---\n# Cómo utilizarlo\n1. Escribe tu contenido en Markdown\n - Puedes aprender más sobre Markdown [aquí](http://www.markdowntutorial.com/)\n2. Separa tus diapositivas con ---\n3. Haz clic en \"Presentar\" para ver el resultado - Tus diapositivas se actualizan en tiempo real"; + out.slideInitialState = "# CryptSlide\n1. Escribe tu contenido en Markdown\n - Puedes aprender más sobre Markdown [aquí](http://www.markdowntutorial.com/)\n2. Separa tus diapositivas con ---\n3. Haz clic en \"Presentar\" para ver el resultado - Tus diapositivas se actualizan en tiempo real"; out.driveReadmeTitle = "¿Qué es CryptPad?"; out.readme_welcome = "¡Bienvenido a CryptPad!"; out.readme_p1 = "Bienvenido a CryptPad, aquí podrás anotar cosas solo o con otra gente."; diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 2d21cf9a6..145d50720 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -29,11 +29,12 @@ 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.padNotPinned = 'Ce pad va expirer après 3 mois d\'inactivité, {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.expiredError = "Ce pad a atteint sa date d'expiration est n'est donc plus disponible."; out.expiredErrorCopy = ' Vous pouvez toujours copier son contenu ailleurs en appuyant sur Échap.
Dés que vous aurez quitté la page, il sera impossible de le récupérer.'; out.deletedError = 'Ce pad a été supprimé par son propriétaire et n\'est donc plus disponible.'; + out.inactiveError = 'Ce pad a été supprimé en raison de son inactivité. Appuyez sur Échap pour créer un nouveau pad.'; out.loading = "Chargement..."; out.error = "Erreur"; @@ -217,6 +218,10 @@ define(function () { out.cancelButton = 'Annuler (Échap)'; out.doNotAskAgain = "Ne plus demander (Échap)"; + out.show_help_button = "Afficher l'aide"; + out.hide_help_button = "Cacher l'aide"; + out.help_button = "Aide"; + out.historyText = "Historique"; out.historyButton = "Afficher l'historique du document"; out.history_next = "Voir la version suivante"; @@ -277,9 +282,6 @@ define(function () { out.poll_locked = "Verrouillé"; out.poll_unlocked = "Déverrouillé"; - out.poll_show_help_button = "Afficher l'aide"; - out.poll_hide_help_button = "Cacher l'aide"; - out.poll_bookmark_col = "Marquer cette colonne comme favorite pour qu'elle soit toujours déverouillée et affichée en première position."; out.poll_bookmarked_col = "Voici votre colonne favorite; elle sera toujours dévérouillée et affichée en première position."; out.poll_total = 'TOTAL'; @@ -779,13 +781,8 @@ define(function () { '

', 'Voici CryptPad, l\'éditeur collaboratif en temps-réel Zero Knowledge. Tout est sauvegardé dés que vous le tapez.', '
', - 'Partagez le lien vers ce pad avec des amis ou utilisez le bouton  Partager  pour obtenir le lien de lecture-seule, qui permet la lecture mais non la modification.', + 'Partagez le lien vers ce pad avec des amis ou utilisez le bouton pour obtenir le lien de lecture-seule, qui permet la lecture mais non la modification.', '

', - '

', - '', - 'Lancez-vous, commencez à taper...', - '

', - '

 

' ].join(''); out.codeInitialState = [ @@ -797,14 +794,6 @@ define(function () { out.slideInitialState = [ '# CryptSlide\n', - '* Voici CryptPad, l\'éditeur collaboratif en temps-réel Zero Knowledge.\n', - '* Ce que vous tapez ici est chiffré de manière que seules les personnes avec le lien peuvent y accéder.\n', - '* Même le serveur est incapable de voir ce que vous tapez.\n', - '* Ce que vous voyez ici, ce que vous entendez, quand vous partez, ça reste ici.\n', - '\n', - '---', - '\n', - '# Comment l\'utiliser\n', '1. Écrivez le contenu de votre présentation avec la syntaxe Markdown\n', ' - Apprenez à utiliser markdown en cliquant [ici](http://www.markdowntutorial.com/)\n', '2. Séparez vos slides avec ---\n', @@ -869,6 +858,7 @@ define(function () { out.creation_createFromTemplate = "Depuis un modèle"; out.creation_createFromScratch = "Nouveau pad vide"; out.creation_settings = "Préférences des nouveaux pads"; + out.creation_saveSettings = "Sauver les préférences"; // Properties about creation data out.creation_owners = "Propriétaires"; out.creation_ownedByOther = "Possédé par un autre utilisateur"; @@ -876,7 +866,8 @@ define(function () { 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_newPadModalDescription = "Cliquez sur un type de pad pour le créer. Vous pouvez aussi appuyer sur Tab pour sélectionner un type et appuyer sur Entrée pour valider."; + out.creation_newPadModalDescriptionAdvanced = "Cochez la case si vous souhaitez voir l'écran de création de pads (pour les pads possédés ou à date d'expiration). Vous pouvez appuyer sur Espace pour changer sa valeur."; out.creation_newPadModalAdvanced = "Afficher l'écran de création de pads"; // New share modal diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 956aa2147..92702a339 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -30,11 +30,12 @@ define(function () { out.typeError = "This pad is not compatible with the selected application"; 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.padNotPinned = 'This pad will expire after 3 months of inactivity, {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.expiredError = 'This pad has reached its expiration time and is no longer available.'; out.expiredErrorCopy = ' You can still copy the content to another location by pressing Esc.
Once you leave this page, it will disappear forever!'; out.deletedError = 'This pad has been deleted by its owner and is no longer available.'; + out.inactiveError = 'This pad has been deleted due to inactivity. Press Esc to create a new pad.'; out.loading = "Loading..."; out.error = "Error"; @@ -219,6 +220,10 @@ define(function () { out.cancelButton = 'Cancel (esc)'; out.doNotAskAgain = "Don't ask me again (Esc)"; + out.show_help_button = "Show help"; + out.hide_help_button = "Hide help"; + out.help_button = "Help"; + out.historyText = "History"; out.historyButton = "Display the document history"; out.history_next = "Go to the next version"; @@ -279,9 +284,6 @@ define(function () { out.poll_locked = "Locked"; out.poll_unlocked = "Unlocked"; - out.poll_show_help_button = "Show help"; - out.poll_hide_help_button = "Hide help"; - out.poll_bookmark_col = 'Bookmark this column so that it is always unlocked and displayed at the beginning for you'; out.poll_bookmarked_col = 'This is your bookmarked column. It will always be unlocked and displayed at the beginning for you.'; out.poll_total = 'TOTAL'; @@ -759,6 +761,30 @@ define(function () { out.features_f_storage_anon = "Pads deleted after 3 months"; out.features_f_storage_registered = "Free: 50MB
Premium: 5GB/20GB/50GB"; + // faq.html + + out.faq_link = "FAQ"; + out.faq_title = "Frequently Asked Questions"; + out.faq = {}; + out.faq.cat1 = { + title: 'Category 1', + q1: { + q: 'What is a pad?', + a: 'A realtime collaborative document...' + }, + q2: { + q: 'Question 2?', + a: '42' + } + }; + out.faq.cat2 = { + title: 'Category 2', + q1: { + q: 'A new question?', + a: 'The answer' + } + }; + // terms.html out.tos_title = "CryptPad Terms of Service"; @@ -791,13 +817,8 @@ define(function () { '

', 'This is CryptPad, the Zero Knowledge realtime collaborative editor. Everything is saved as you type.', '
', - 'Share the link to this pad to edit with friends or use the  Share  button to share a read-only link which allows viewing but not editing.', + 'Share the link to this pad to edit with friends or use the button to share a read-only link which allows viewing but not editing.', '

', - - '

', - 'Go ahead, just start typing...', - '

', - '

 

' ].join(''); out.codeInitialState = [ @@ -809,14 +830,6 @@ define(function () { out.slideInitialState = [ '# CryptSlide\n', - '* This is a zero knowledge realtime collaborative editor.\n', - '* What you type here is encrypted so only people who have the link can access it.\n', - '* Even the server cannot see what you type.\n', - '* What you see here, what you hear here, when you leave here, let it stay here.\n', - '\n', - '---', - '\n', - '# How to use\n', '1. Write your slides content using markdown syntax\n', ' - Learn more about markdown syntax [here](http://www.markdowntutorial.com/)\n', '2. Separate your slides with ---\n', @@ -883,6 +896,7 @@ define(function () { out.creation_createFromTemplate = "From template"; out.creation_createFromScratch = "From scratch"; out.creation_settings = "New Pad settings"; + out.creation_saveSettings = "Save settings"; // Properties about creation data out.creation_owners = "Owners"; out.creation_ownedByOther = "Owned by another user"; @@ -890,7 +904,8 @@ define(function () { 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 pads, expiring pads, etc.)."; + out.creation_newPadModalDescription = "Click on a pad type to create it. You can also press Tab to select the type and press Enter to confirm."; + out.creation_newPadModalDescriptionAdvanced = "You can check the box (or press Space to change its value) if you want to display the pad creation screen (for owned pads, expiring pads, etc.)."; out.creation_newPadModalAdvanced = "Display the pad creation screen"; // New share modal diff --git a/customize.dist/translations/messages.pt-br.js b/customize.dist/translations/messages.pt-br.js index 79ba513c3..fbf63d067 100644 --- a/customize.dist/translations/messages.pt-br.js +++ b/customize.dist/translations/messages.pt-br.js @@ -486,13 +486,8 @@ define(function () { '

', 'This is CryptPad, the Zero Knowledge realtime collaborative editor. Everything is saved as you type.', '
', - 'Share the link to this pad to edit with friends or use the  Share  button to share a read-only link which allows viewing but not editing.', + 'Share the link to this pad to edit with friends or use the button to share a read-only link which allows viewing but not editing.', '

', - - '

', - 'Go ahead, just start typing...', - '

', - '

 

' ].join(''); out.codeInitialState = [ @@ -504,14 +499,6 @@ define(function () { out.slideInitialState = [ '# CryptSlide\n', - '* This is a zero knowledge realtime collaborative editor.\n', - '* What you type here is encrypted so only people who have the link can access it.\n', - '* Even the server cannot see what you type.\n', - '* What you see here, what you hear here, when you leave here, let it stay here.\n', - '\n', - '---', - '\n', - '# How to use\n', '1. Write your slides content using markdown syntax\n', ' - Learn more about markdown syntax [here](http://www.markdowntutorial.com/)\n', '2. Separate your slides with ---\n', diff --git a/customize.dist/translations/messages.ro.js b/customize.dist/translations/messages.ro.js index 14a55f8ab..56ec8b05d 100644 --- a/customize.dist/translations/messages.ro.js +++ b/customize.dist/translations/messages.ro.js @@ -4,16 +4,6 @@ define(function () { out.main_title = "CryptPad: Zero Knowledge, Colaborare în timp real"; out.main_slogan = "Puterea stă în cooperare - Colaborarea este cheia"; - out.type = {}; - out.pad = "Rich text"; - out.code = "Code"; - out.poll = "Poll"; - out.slide = "Presentation"; - out.drive = "Drive"; - out.whiteboard = "Whiteboard"; - out.file = "File"; - out.media = "Media"; - out.button_newpad = "Filă Text Nouă"; out.button_newcode = "Filă Cod Nouă"; out.button_newpoll = "Sondaj Nou"; @@ -330,9 +320,9 @@ define(function () { out.header_france = "With \"love\" from \"Franța\"/ by \"XWiki"; out.header_support = " \"OpenPaaS-ng\""; out.header_logoTitle = "Mergi la pagina principală"; - out.initialState = "

Acesta este CryptPad, editorul colaborativ bazat pe tehnologia Zero Knowledge în timp real. Totul este salvat pe măsură ce scrii.
Partajează link-ul către acest pad pentru a edita cu prieteni sau folosește  Share  butonul pentru a partaja read-only link permițând vizualizarea dar nu și editarea.

Îndrăznește, începe să scrii...

 

"; + out.initialState = "

Acesta este CryptPad, editorul colaborativ bazat pe tehnologia Zero Knowledge în timp real. Totul este salvat pe măsură ce scrii.
Partajează link-ul către acest pad pentru a edita cu prieteni sau folosește butonul pentru a partaja read-only link permițând vizualizarea dar nu și editarea.

"; out.codeInitialState = "/*\n Acesta este editorul colaborativ de cod bazat pe tehnologia Zero Knowledge CryptPad.\n Ce scrii aici este criptat, așa că doar oamenii care au link-ul pot să-l acceseze.\n Poți să alegi ce limbaj de programare pus n evidență și schema de culori UI n dreapta sus.\n*/"; - out.slideInitialState = "# CryptSlide\n* Acesta este un editor colaborativ bazat pe tehnologia Zero Knowledge.\n* Ce scrii aici este criptat, așa că doar oamenii care au link-ul pot să-l acceseze.\n* Nici măcar serverele nu au acces la ce scrii tu.\n* Ce vezi aici, ce auzi aici, atunci când pleci, lași aici.\n\n-\n# Cum se folosește\n1. Scrie-ți conținutul slide-urilor folosind sintaxa markdown\n - Află mai multe despre sintaxa markdown [aici](http://www.markdowntutorial.com/)\n2. Separă-ți slide-urile cu -\n3. Click pe butonul \"Play\" pentru a vedea rezultatele - Slide-urile tale sunt actualizate în timp real."; + out.slideInitialState = "# CryptSlide\n1. Scrie-ți conținutul slide-urilor folosind sintaxa markdown\n - Află mai multe despre sintaxa markdown [aici](http://www.markdowntutorial.com/)\n2. Separă-ți slide-urile cu ---\n3. Click pe butonul \"Play\" pentru a vedea rezultatele - Slide-urile tale sunt actualizate în timp real."; out.driveReadmeTitle = "Ce este CryptPad?"; out.readme_welcome = "Bine ai venit n CryptPad !"; out.readme_p1 = "Bine ai venit în CryptPad, acesta este locul unde îți poți lua notițe, singur sau cu prietenii."; diff --git a/customize.dist/translations/messages.zh.js b/customize.dist/translations/messages.zh.js index c7ac7915a..25b7fe8c8 100644 --- a/customize.dist/translations/messages.zh.js +++ b/customize.dist/translations/messages.zh.js @@ -469,13 +469,8 @@ define(function () { '

', '這是 CryptPad, 零知識即時協作編輯平台,當你輸入時一切已即存好。', '
', - '分享這個工作檔案的網址連結給友人或是使用、  分享  按鈕分享唯讀的連結 其只能看不能編寫。', - '

', - - '

', - '來吧, 開始打字輸入吧...', - '

', - '

 

' + '分享這個工作檔案的網址連結給友人或是使用、 按鈕分享唯讀的連結 其只能看不能編寫。', + '

' ].join(''); out.codeInitialState = [ @@ -487,14 +482,6 @@ define(function () { out.slideInitialState = [ '# CryptSlide\n', - '* 它是零知識即時協作編輯平台。\n', - '* 你所輸入的東西會予以加密,僅有知道此網頁連結者可以接取這份文件。\n', - '* 即便是本站伺服器也不知道你輸入了什麼內容。\n', - '* 你在這裏看到的、你在這裏聽到的、當你離開本站時,讓它就留在這裏吧。\n', - '\n', - '---', - '\n', - '# 如何使用\n', '1. 使用 markdown 語法來寫下你的投影片內容\n', ' - 進一步學習 markdown 語法 [here](http://www.markdowntutorial.com/)\n', '2. 利用 --- 來區隔不同的投影片\n', diff --git a/delete-inactive.js b/delete-inactive.js new file mode 100644 index 000000000..16f41da45 --- /dev/null +++ b/delete-inactive.js @@ -0,0 +1,40 @@ +/* jshint esversion: 6, node: true */ +const Fs = require("fs"); +const nThen = require("nthen"); +const Saferphore = require("saferphore"); +const PinnedData = require('./pinneddata'); +let config; +try { + config = require('./config'); +} catch (e) { + config = require('./config.example'); +} + +if (!config.inactiveTime || typeof(config.inactiveTime) !== "number") { return; } + +let inactiveTime = +new Date() - (config.inactiveTime * 24 * 3600 * 1000); +let inactiveConfig = { + unpinned: true, + olderthan: inactiveTime, + blobsolderthan: inactiveTime +}; +let toDelete; +nThen(function (waitFor) { + PinnedData.load(inactiveConfig, waitFor(function (err, data) { + if (err) { + waitFor.abort(); + throw new Error(err); + } + toDelete = data; + })); +}).nThen(function () { + var sem = Saferphore.create(10); + toDelete.forEach(function (f) { + sem.take(function (give) { + Fs.unlink(f.filename, give(function (err) { + if (err) { return void console.error(err + " " + f.filename); } + console.log(f.filename + " " + f.size + " " + (+f.atime) + " " + (+new Date())); + })); + }); + }); +}); diff --git a/expire-channels.js b/expire-channels.js index 4e22dfce4..a42a3af58 100644 --- a/expire-channels.js +++ b/expire-channels.js @@ -7,7 +7,6 @@ var config; try { config = require('./config'); } catch (e) { - console.log("You can customize the configuration by copying config.example.js to config.js"); config = require('./config.example'); } diff --git a/package.json b/package.json index 75f94fb6b..3ab228851 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "cryptpad", "description": "realtime collaborative visual editor with zero knowlege server", - "version": "1.26.0", + "version": "1.27.0", "license": "AGPL-3.0-or-later", "dependencies": { "chainpad-server": "^2.0.0", - "express": "~4.10.1", + "express": "~4.16.0", "nthen": "~0.1.0", "pull-stream": "^3.6.1", "replify": "^1.2.0", diff --git a/pinneddata.js b/pinneddata.js index d7848c7df..378f34ad7 100644 --- a/pinneddata.js +++ b/pinneddata.js @@ -47,103 +47,126 @@ const dsFileStats = {}; const out = []; const pinned = {}; -nThen((waitFor) => { - Fs.readdir('./datastore', waitFor((err, list) => { - if (err) { throw err; } - dirList = list; - })); -}).nThen((waitFor) => { - dirList.forEach((f) => { - sema.take((returnAfter) => { - Fs.readdir('./datastore/' + f, waitFor(returnAfter((err, list2) => { - if (err) { throw err; } - list2.forEach((ff) => { fileList.push('./datastore/' + f + '/' + ff); }); - }))); +module.exports.load = function (config, cb) { + nThen((waitFor) => { + Fs.readdir('./datastore', waitFor((err, list) => { + if (err) { throw err; } + dirList = list; + })); + }).nThen((waitFor) => { + dirList.forEach((f) => { + sema.take((returnAfter) => { + Fs.readdir('./datastore/' + f, waitFor(returnAfter((err, list2) => { + if (err) { throw err; } + list2.forEach((ff) => { fileList.push('./datastore/' + f + '/' + ff); }); + }))); + }); }); - }); -}).nThen((waitFor) => { + }).nThen((waitFor) => { - Fs.readdir('./blob', waitFor((err, list) => { - if (err) { throw err; } - dirList = list; - })); -}).nThen((waitFor) => { - dirList.forEach((f) => { - sema.take((returnAfter) => { - Fs.readdir('./blob/' + f, waitFor(returnAfter((err, list2) => { - if (err) { throw err; } - list2.forEach((ff) => { fileList.push('./blob/' + f + '/' + ff); }); - }))); + Fs.readdir('./blob', waitFor((err, list) => { + if (err) { throw err; } + dirList = list; + })); + }).nThen((waitFor) => { + dirList.forEach((f) => { + sema.take((returnAfter) => { + Fs.readdir('./blob/' + f, waitFor(returnAfter((err, list2) => { + if (err) { throw err; } + list2.forEach((ff) => { fileList.push('./blob/' + f + '/' + ff); }); + }))); + }); }); - }); -}).nThen((waitFor) => { - fileList.forEach((f) => { - sema.take((returnAfter) => { - Fs.stat(f, waitFor(returnAfter((err, st) => { - if (err) { throw err; } - st.filename = f; - dsFileStats[f.replace(/^.*\/([^\/\.]*)(\.ndjson)?$/, (all, a) => (a))] = st; - }))); + }).nThen((waitFor) => { + fileList.forEach((f) => { + sema.take((returnAfter) => { + Fs.stat(f, waitFor(returnAfter((err, st) => { + if (err) { throw err; } + st.filename = f; + dsFileStats[f.replace(/^.*\/([^\/\.]*)(\.ndjson)?$/, (all, a) => (a))] = st; + }))); + }); }); - }); -}).nThen((waitFor) => { - Fs.readdir('./pins', waitFor((err, list) => { - if (err) { throw err; } - dirList = list; - })); -}).nThen((waitFor) => { - fileList.splice(0, fileList.length); - dirList.forEach((f) => { - sema.take((returnAfter) => { - Fs.readdir('./pins/' + f, waitFor(returnAfter((err, list2) => { - if (err) { throw err; } - list2.forEach((ff) => { fileList.push('./pins/' + f + '/' + ff); }); - }))); + }).nThen((waitFor) => { + Fs.readdir('./pins', waitFor((err, list) => { + if (err) { throw err; } + dirList = list; + })); + }).nThen((waitFor) => { + fileList.splice(0, fileList.length); + dirList.forEach((f) => { + sema.take((returnAfter) => { + Fs.readdir('./pins/' + f, waitFor(returnAfter((err, list2) => { + if (err) { throw err; } + list2.forEach((ff) => { fileList.push('./pins/' + f + '/' + ff); }); + }))); + }); }); - }); -}).nThen((waitFor) => { - fileList.forEach((f) => { - sema.take((returnAfter) => { - Fs.readFile(f, waitFor(returnAfter((err, content) => { - if (err) { throw err; } - const hashes = hashesFromPinFile(content.toString('utf8'), f); - const size = sizeForHashes(hashes, dsFileStats); - if (process.argv.indexOf('--unpinned') > -1) { - hashes.forEach((x) => { pinned[x] = 1; }); - } else { - out.push([f, Math.floor(size / (1024 * 1024))]); - } - }))); + }).nThen((waitFor) => { + fileList.forEach((f) => { + sema.take((returnAfter) => { + Fs.readFile(f, waitFor(returnAfter((err, content) => { + if (err) { throw err; } + const hashes = hashesFromPinFile(content.toString('utf8'), f); + const size = sizeForHashes(hashes, dsFileStats); + if (config.unpinned) { + hashes.forEach((x) => { pinned[x] = 1; }); + } else { + out.push([f, Math.floor(size / (1024 * 1024))]); + } + }))); + }); }); - }); -}).nThen(() => { - if (process.argv.indexOf('--unpinned') > -1) { - const ot = process.argv.indexOf('--olderthan'); - let before = Infinity; - if (ot > -1) { - before = new Date(process.argv[ot+1]); - if (isNaN(before)) { - throw new Error('--olderthan error [' + process.argv[ot+1] + '] not a valid date'); + }).nThen(() => { + if (config.unpinned) { + let before = Infinity; + if (config.olderthan) { + before = config.olderthan; + if (isNaN(before)) { + return void cb('--olderthan error [' + config.olderthan + '] not a valid date'); + } } - } - const bot = process.argv.indexOf('--blobsolderthan'); - let blobsbefore = before; - if (bot > -1) { - blobsbefore = new Date(process.argv[bot+1]); - if (isNaN(blobsbefore)) { - throw new Error('--blobsolderthan error [' + process.argv[bot+1] + '] not a valid date'); + let blobsbefore = before; + if (config.blobsolderthan) { + blobsbefore = config.blobsolderthan; + if (isNaN(blobsbefore)) { + return void cb('--blobsolderthan error [' + config.blobsolderthan + '] not a valid date'); + } } + let files = []; + Object.keys(dsFileStats).forEach((f) => { + if (!(f in pinned)) { + const isBlob = dsFileStats[f].filename.indexOf('.ndjson') === -1; + if ((+dsFileStats[f].atime) >= ((isBlob) ? blobsbefore : before)) { return; } + files.push({ + filename: dsFileStats[f].filename, + size: dsFileStats[f].size, + atime: dsFileStats[f].atime + }); + } + }); + cb(null, files); + } else { + out.sort((a,b) => (a[1] - b[1])); + cb(null, out.slice()); } - Object.keys(dsFileStats).forEach((f) => { - if (!(f in pinned)) { - const isBlob = dsFileStats[f].filename.indexOf('.ndjson') === -1; - if ((+dsFileStats[f].mtime) >= ((isBlob) ? blobsbefore : before)) { return; } - console.log(dsFileStats[f].filename + " " + dsFileStats[f].size + " " + - (+dsFileStats[f].mtime)); - } - }); - } else { - out.sort((a,b) => (a[1] - b[1])); - out.forEach((x) => { console.log(x[0] + ' ' + x[1] + ' MB'); }); - } -}); + }); +}; + +if (!module.parent) { + let config = {}; + if (process.argv.indexOf('--unpinned') > -1) { config.unpinned = true; } + const ot = process.argv.indexOf('--olderthan'); + config.olderthan = ot > -1 && new Date(process.argv[ot+1]); + const bot = process.argv.indexOf('--blobsolderthan'); + config.blobsolderthan = bot > -1 && new Date(process.argv[bot+1]); + module.exports.load(config, function (err, data) { + if (err) { throw new Error(err); } + if (!Array.isArray(data)) { return; } + if (config.unpinned) { + data.forEach((f) => { console.log(f.filename + " " + f.size + " " + (+f.atime)); }); + } else { + data.forEach((x) => { console.log(x[0] + ' ' + x[1] + ' MB'); }); + } + }); +} diff --git a/www/code/app-code.less b/www/code/app-code.less index 463bb07a3..f4153e95d 100644 --- a/www/code/app-code.less +++ b/www/code/app-code.less @@ -74,6 +74,24 @@ } } .markdown_main(); + .cp-app-code-preview-empty { + display: none; + } + &.cp-app-code-preview-isempty { + display: flex; + align-items: center; + justify-content: center; + #cp-app-code-preview-content { + display: none; + } + .cp-app-code-preview-empty { + //flex: 1 1 auto; + max-height: 100%; + max-width: 100%; + display: block; + opacity: 0.2; + } + } } #cp-app-code-preview-content { diff --git a/www/code/inner.js b/www/code/inner.js index e7f080c02..66010a485 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -54,6 +54,7 @@ define([ var MEDIA_TAG_MODES = Object.freeze([ 'markdown', + 'gfm', 'html', 'htmlembedded', 'htmlmixed', @@ -63,6 +64,31 @@ define([ 'xml', ]); + var mkMarkdownTb = function (editor, framework) { + var $codeMirrorContainer = $('#cp-app-code-container'); + var markdownTb = framework._.sfCommon.createMarkdownToolbar(editor); + $codeMirrorContainer.prepend(markdownTb.toolbar); + + framework._.toolbar.$rightside.append(markdownTb.button); + + var modeChange = function (mode) { + if (['markdown', 'gfm'].indexOf(mode) !== -1) { return void markdownTb.setState(true); } + markdownTb.setState(false); + }; + + return { + modeChange: modeChange + }; + }; + var mkHelpMenu = function (framework) { + var $codeMirrorContainer = $('#cp-app-code-container'); + var helpMenu = framework._.sfCommon.createHelpMenu(); + $codeMirrorContainer.prepend(helpMenu.menu); + + $(helpMenu.text).html(DiffMd.render(Messages.codeInitialState)); + + framework._.toolbar.$drawer.append(helpMenu.button); + }; var mkPreviewPane = function (editor, CodeMirror, framework, isPresentMode) { var $previewContainer = $('#cp-app-code-preview'); var $preview = $('#cp-app-code-preview-content'); @@ -70,12 +96,20 @@ define([ var $codeMirrorContainer = $('#cp-app-code-container'); var $codeMirror = $('.CodeMirror'); - var markdownTb = framework._.sfCommon.createMarkdownToolbar(editor); - $codeMirrorContainer.prepend(markdownTb.toolbar); + $('', { + src: '/customize/main-favicon.png', + alt: '', + class: 'cp-app-code-preview-empty' + }).appendTo($previewContainer); - var $previewButton = framework._.sfCommon.createButton(null, true); + var $previewButton = framework._.sfCommon.createButton('preview', true); var forceDrawPreview = function () { try { + if (editor.getValue() === '') { + $previewContainer.addClass('cp-app-code-preview-isempty'); + return; + } + $previewContainer.removeClass('cp-app-code-preview-isempty'); DiffMd.apply(DiffMd.render(editor.getValue()), $preview); } catch (e) { console.error(e); } }; @@ -85,12 +119,6 @@ define([ forceDrawPreview(); }, 150); - $previewButton.removeClass('fa-question').addClass('fa-eye'); - window.setTimeout(function () { - // setTimeout needed for tippy (tooltip), otherwise we have the browser's default - // tooltips - $previewButton.attr('title', Messages.previewButtonTitle); - }); var previewTo; $previewButton.click(function () { clearTimeout(previewTo); @@ -118,7 +146,7 @@ define([ } }); - framework._.toolbar.$rightside.append($previewButton).append(markdownTb.button); + framework._.toolbar.$rightside.append($previewButton); $preview.click(function (e) { if (!e.target) { return; } @@ -145,7 +173,6 @@ define([ } } }); - markdownTb.setState(true); return; } $editorContainer.removeClass('cp-app-code-present'); @@ -153,7 +180,6 @@ define([ $previewContainer.hide(); $previewButton.removeClass('active'); $codeMirrorContainer.addClass('cp-app-code-fullpage'); - markdownTb.setState(false); }; var isVisible = function () { @@ -252,8 +278,12 @@ define([ var common = framework._.sfCommon; var previewPane = mkPreviewPane(editor, CodeMirror, framework, isPresentMode); + var markdownTb = mkMarkdownTb(editor, framework); + mkHelpMenu(framework); + var evModeChange = Util.mkEvent(); evModeChange.reg(previewPane.modeChange); + evModeChange.reg(markdownTb.modeChange); mkIndentSettings(editor, framework._.cpNfInner.metadataMgr); CodeMirror.init(framework.localChange, framework._.title, framework._.toolbar); @@ -292,6 +322,8 @@ define([ framework.setTitleRecommender(CodeMirror.getHeadingText); framework.onReady(function (newPad) { + editor.focus(); + if (newPad && !CodeMirror.highlightMode) { CodeMirror.setMode('gfm', evModeChange.fire); //console.log("%s => %s", CodeMirror.highlightMode, CodeMirror.$language.val()); @@ -315,7 +347,7 @@ define([ }); framework.onDefaultContentNeeded(function () { - editor.setValue(Messages.codeInitialState); + editor.setValue(''); //Messages.codeInitialState); }); framework.setFileExporter(CodeMirror.getContentExtension, CodeMirror.fileExporter); @@ -335,13 +367,9 @@ define([ 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 () { diff --git a/www/common/common-interface.js b/www/common/common-interface.js index d0c0e1961..d10b289cd 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -555,8 +555,11 @@ define([ $loading = $('#' + LOADING); //.show(); $loading.css('display', ''); $loading.removeClass('cp-loading-hidden'); + $('.cp-loading-spinner-container').show(); if (loadingText) { $('#' + LOADING).find('p').text(loadingText); + } else { + $('#' + LOADING).find('p').text(''); } $container = $loading.find('.cp-loading-container'); } else { @@ -612,7 +615,10 @@ define([ if (exitable) { $(window).focus(); $(window).keydown(function (e) { - if (e.which === 27) { $('#' + LOADING).hide(); } + if (e.which === 27) { + $('#' + LOADING).hide(); + if (typeof(exitable) === "function") { exitable(); } + } }); } }; @@ -662,7 +668,6 @@ define([ position: 'bottom', distance: 0, performance: true, - dynamicTitle: true, delay: [delay, 0], sticky: true }); @@ -672,6 +677,12 @@ define([ setInterval(UI.clearTooltips, delay); var checkRemoved = function (x) { var out = false; + var xId = $(x).attr('aria-describedby'); + if (xId) { + if (xId.indexOf('tippy-tooltip-') === 0) { + return true; + } + } $(x).find('[aria-describedby]').each(function (i, el) { var id = el.getAttribute('aria-describedby'); if (id.indexOf('tippy-tooltip-') !== 0) { return; } @@ -685,6 +696,9 @@ define([ mutations.forEach(function(mutation) { if (mutation.type === "childList") { for (var i = 0; i < mutation.addedNodes.length; i++) { + if ($(mutation.addedNodes[i]).attr('title')) { + addTippy(0, mutation.addedNodes[i]); + } $(mutation.addedNodes[i]).find('[title]').each(addTippy); } for (var j = 0; j < mutation.removedNodes.length; j++) { diff --git a/www/common/common-thumbnail.js b/www/common/common-thumbnail.js index f0ba27194..f3864e6a3 100644 --- a/www/common/common-thumbnail.js +++ b/www/common/common-thumbnail.js @@ -177,6 +177,7 @@ define([ window.html2canvas = undefined; Thumb.fromDOM = function (opts, cb) { var element = opts.getContainer(); + if (!element) { return; } var todo = function () { if (opts.filter) { opts.filter(element, true); } window.html2canvas(element, { @@ -202,8 +203,8 @@ define([ var mkThumbnail = function () { var content = opts.getContent(); if (content === oldThumbnailState) { return; } + oldThumbnailState = content; Thumb.fromDOM(opts, function (err, b64) { - oldThumbnailState = content; Thumb.setPadThumbnail(common, opts.href, b64); }); }; diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 27c583e08..de550dc0c 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -463,12 +463,11 @@ define([ UIElements.createButton = function (common, type, rightside, data, callback) { var AppConfig = common.getAppConfig(); var button; - var size = "17px"; var sframeChan = common.getSframeChannel(); switch (type) { case 'export': button = $('