diff --git a/check-accounts.js b/check-accounts.js new file mode 100644 index 000000000..4f37e51fd --- /dev/null +++ b/check-accounts.js @@ -0,0 +1,40 @@ +/* globals Buffer */ +var Https = require('https'); +var Config = require("./config.js"); +var Package = require("./package.json"); + +var body = JSON.stringify({ + domain: Config.myDomain, + adminEmail: Config.adminEmail, + version: Package.version, +}); + +var options = { + host: 'accounts.cryptpad.fr', + path: '/api/getauthorized', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(body) + } +}; + +Https.request(options, function (response) { + if (!('' + response.statusCode).match(/^2\d\d$/)) { + throw new Error('SERVER ERROR ' + response.statusCode); + } + var str = ''; + response.on('data', function (chunk) { + str += chunk; + }); + response.on('end', function () { + try { + var json = JSON.parse(str); + console.log(json); + } catch (e) { + throw new Error(e); + } + }); +}).on('error', function (e) { + console.error(e); +}).end(body); diff --git a/customize.dist/header.js b/customize.dist/header.js deleted file mode 100644 index 5e0dcc881..000000000 --- a/customize.dist/header.js +++ /dev/null @@ -1,54 +0,0 @@ -define([ - 'jquery', - '/customize/application_config.js', - '/common/cryptpad-common.js', - '/api/config', -], function ($, Config, Cryptpad, ApiConfig) { - - window.APP = { - Cryptpad: Cryptpad, - }; - - var Messages = Cryptpad.Messages; - - $(function () { - // Language selector - var $sel = $('#language-selector'); - Cryptpad.createLanguageSelector(undefined, $sel); - $sel.find('button').addClass('btn').addClass('btn-secondary'); - $sel.show(); - - var $upgrade = $('#upgrade'); - - var showUpgrade = function (text, feedback, url) { - if (ApiConfig.removeDonateButton) { return; } - if (localStorage.plan) { return; } - if (!text) { return; } - $upgrade.text(text).show(); - $upgrade.click(function () { - Cryptpad.feedback(feedback); - window.open(url,'_blank'); - }); - }; - - // User admin menu - var $userMenu = $('#user-menu'); - var userMenuCfg = { - $initBlock: $userMenu, - 'static': true - }; - var $userAdmin = Cryptpad.createUserAdminMenu(userMenuCfg); - $userAdmin.find('button').addClass('btn').addClass('btn-secondary'); - - $(window).click(function () { - $('.cp-dropdown-content').hide(); - }); - - if (Cryptpad.isLoggedIn() && ApiConfig.allowSubscriptions) { - showUpgrade(Messages.upgradeAccount, "HOME_UPGRADE_ACCOUNT", Cryptpad.upgradeURL); - } else { - showUpgrade(Messages.supportCryptpad, "HOME_SUPPORT_CRYPTPAD", Cryptpad.donateURL); - } - }); -}); - diff --git a/customize.dist/main.js b/customize.dist/main.js index 5ac45af3a..a110caaa3 100644 --- a/customize.dist/main.js +++ b/customize.dist/main.js @@ -2,15 +2,16 @@ define([ 'jquery', '/customize/application_config.js', '/common/cryptpad-common.js', - '/customize/header.js', -], function ($, Config, Cryptpad) { + '/common/common-interface.js', + '/common/common-realtime.js', + '/common/common-constants.js', + '/customize/messages.js', +], function ($, Config, Cryptpad, UI, Realtime, Constants, Messages) { window.APP = { Cryptpad: Cryptpad, }; - var Messages = Cryptpad.Messages; - $(function () { var $main = $('#mainBlock'); @@ -33,7 +34,7 @@ define([ $main.find('a[href="/drive/"] div.pad-button-text h4') .text(Messages.main_yourCryptDrive); - var name = localStorage[Cryptpad.userNameKey] || sessionStorage[Cryptpad.userNameKey]; + var name = localStorage[Constants.userNameKey] || sessionStorage[Constants.userNameKey]; var $loggedInBlock = $main.find('#loggedIn'); var $hello = $loggedInBlock.find('#loggedInHello'); var $logout = $loggedInBlock.find('#loggedInLogOut'); @@ -58,34 +59,6 @@ define([ $('#name').focus(); } - var displayCreateButtons = function () { - var $parent = $('#buttons'); - var options = []; - var $container = $('
', {'class': 'cp-dropdown-container'}).appendTo($parent); - Config.availablePadTypes.forEach(function (el) { - if (el === 'drive') { return; } - if (!Cryptpad.isLoggedIn() && Config.registeredOnlyTypes && - Config.registeredOnlyTypes.indexOf(el) !== -1) { return; } - options.push({ - tag: 'a', - attributes: { - 'class': 'newdoc', - 'href': '/' + el + '/', - 'target': '_blank' - }, - content: Messages['button_new' + el] // Pretty name of the language value - }); - }); - var dropdownConfig = { - text: Messages.login_makeAPad, // Button initial text - options: options, // Entries displayed in the menu - container: $container - }; - var $block = Cryptpad.createDropdown(dropdownConfig); - $block.find('button').addClass('btn').addClass('btn-primary'); - $block.appendTo($parent); - }; - /* Log in UI */ var Login; // deferred execution to avoid unnecessary asset loading @@ -116,7 +89,7 @@ define([ $('button.login').click(function () { // setTimeout 100ms to remove the keyboard on mobile devices before the loading screen pops up window.setTimeout(function () { - Cryptpad.addLoadingScreen({loadingText: Messages.login_hashing}); + 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 () { @@ -135,7 +108,7 @@ define([ proxy.edPrivate = result.edPrivate; proxy.edPublic = result.edPublic; - Cryptpad.whenRealtimeSyncs(result.realtime, function () { + Realtime.whenRealtimeSyncs(result.realtime, function () { Cryptpad.login(result.userHash, result.userName, function () { document.location.href = '/drive/'; }); @@ -144,22 +117,22 @@ define([ } switch (err) { case 'NO_SUCH_USER': - Cryptpad.removeLoadingScreen(function () { - Cryptpad.alert(Messages.login_noSuchUser); + UI.removeLoadingScreen(function () { + UI.alert(Messages.login_noSuchUser); }); break; case 'INVAL_USER': - Cryptpad.removeLoadingScreen(function () { - Cryptpad.alert(Messages.login_invalUser); + UI.removeLoadingScreen(function () { + UI.alert(Messages.login_invalUser); }); break; case 'INVAL_PASS': - Cryptpad.removeLoadingScreen(function () { - Cryptpad.alert(Messages.login_invalPass); + UI.removeLoadingScreen(function () { + UI.alert(Messages.login_invalPass); }); break; default: // UNHANDLED ERROR - Cryptpad.errorLoadingScreen(Messages.login_unhandledError); + UI.errorLoadingScreen(Messages.login_unhandledError); } }); }); @@ -167,27 +140,6 @@ define([ }, 100); }); /* End Log in UI */ - - var addButtonHandlers = function () { - $('button.register').click(function () { - var username = $('#name').val(); - var passwd = $('#password').val(); - sessionStorage.login_user = username; - sessionStorage.login_pass = passwd; - document.location.href = '/register/'; - }); - $('button.gotodrive').click(function () { - document.location.href = '/drive/'; - }); - - $('button#loggedInLogout').click(function () { - $('#user-menu .logout').click(); - }); - }; - - displayCreateButtons(); - - addButtonHandlers(); console.log("ready"); }); }); diff --git a/customize.dist/messages.js b/customize.dist/messages.js index 7230a719d..7cb43762d 100644 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -30,7 +30,6 @@ if (language && map[language]) { req.push('/customize/translations/messages.' + define(req, function($, Default, Language) { map.en = 'English'; var defaultLanguage = 'en'; -console.log(messages); if (!Language || language === defaultLanguage || !map[language]) { messages = $.extend(true, messages, Default); @@ -39,7 +38,6 @@ console.log(messages); // Add the translated keys to the returned object messages = $.extend(true, messages, Default, Language); } -console.log(messages); messages._languages = map; messages._languageUsed = language; @@ -49,11 +47,13 @@ console.log(messages); var missing = []; var reqs = []; Object.keys(map).forEach(function (code) { + if (code === defaultLanguage) { return; } reqs.push('/customize/translations/messages.' + code + '.js'); }); require(reqs, function () { var langs = arguments; Object.keys(map).forEach(function (code, i) { + if (code === defaultLanguage) { return; } var translation = langs[i]; var updated = {}; Object.keys(Default).forEach(function (k) { diff --git a/customize.dist/pages.js b/customize.dist/pages.js index e342f4f1c..6868418cc 100644 --- a/customize.dist/pages.js +++ b/customize.dist/pages.js @@ -49,11 +49,6 @@ define([ h('p', Msg.main_footerText) ]) ], ''), - /* footerCol(null, [ - footLink('/about.html', 'about'), - footLink('/terms.html', 'terms'), - footLink('/privacy.html', 'privacy'), - ], 'CryptPad'),*/ footerCol('footer_applications', [ footLink('/drive/', 'main_drive'), footLink('/pad/', 'main_richText'), @@ -76,7 +71,7 @@ define([ ]) ]) ]), - h('div.cp-version-footer', "CryptPad v1.19.0 (Tarasque)") + h('div.cp-version-footer', "CryptPad v1.21.0 (Vampire)") ]); }; @@ -690,45 +685,5 @@ define([ ]; }; - Pages['/drive/'] = Pages['/drive/index.html'] = function () { - return loadingScreen(); - }; - - Pages['/file/'] = Pages['/file/index.html'] = function () { - return loadingScreen(); - }; - - Pages['/contacts/'] = Pages['/contacts/index.html'] = function () { - return loadingScreen(); - }; - - Pages['/pad/'] = Pages['/pad/index.html'] = function () { - return loadingScreen(); - }; - - Pages['/code/'] = Pages['/code/index.html'] = function () { - return loadingScreen(); - }; - - Pages['/slide/'] = Pages['/slide/index.html'] = function () { - return loadingScreen(); - }; - - Pages['/invite/'] = Pages['/invite/index.html'] = function () { - return loadingScreen(); - }; - - Pages['/settings/'] = Pages['/settings/index.html'] = function () { - return loadingScreen(); - }; - - Pages['/profile/'] = Pages['/profile/index.html'] = function () { - return loadingScreen(); - }; - - Pages['/todo/'] = Pages['/todo/index.html'] = function () { - return loadingScreen(); - }; - return Pages; }); diff --git a/customize.dist/src/less2/include/dropdown.less b/customize.dist/src/less2/include/dropdown.less index 6593b926e..1aea222bd 100644 --- a/customize.dist/src/less2/include/dropdown.less +++ b/customize.dist/src/less2/include/dropdown.less @@ -43,7 +43,7 @@ display: block; } - a { + & > a, & > span { color: @colortheme_dropdown-color; padding: 5px 16px; text-decoration: none; @@ -75,6 +75,31 @@ color: @colortheme_dropdown-color; } } + &> span { + box-sizing: border-box; + height: 26px; + border-radius: 0; + border: 0; + padding: 0 16px; + .cp-dropdown-content { + margin-top: 26px; + left: 0; + } + button { + padding: 0; + text-align: left; + margin: 0; + border-radius: 0; + border: 0; + width: 100%; + line-height: 1em; + .cp-toolbar-drawer-element { + margin-left: 10px; + display: inline; + vertical-align: top; + } + } + } hr { margin: 5px 0px; diff --git a/customize.dist/src/less2/include/toolbar-history.less b/customize.dist/src/less2/include/toolbar-history.less index 0388f6401..d553caa3f 100644 --- a/customize.dist/src/less2/include/toolbar-history.less +++ b/customize.dist/src/less2/include/toolbar-history.less @@ -4,6 +4,7 @@ .cp-toolbar-history { display: none; text-align: center; + width: 100%; * { font: @colortheme_app-font; } diff --git a/customize.dist/src/less2/include/toolbar.less b/customize.dist/src/less2/include/toolbar.less index d39effaef..e4499d67e 100644 --- a/customize.dist/src/less2/include/toolbar.less +++ b/customize.dist/src/less2/include/toolbar.less @@ -24,6 +24,7 @@ } .cp-toolbar-userlist-drawer { + background-color: @colortheme_default-bg; font: @colortheme_app-font-size @colortheme_font; min-width: 175px; width: 175px; @@ -102,6 +103,32 @@ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + display: flex; + justify-content: space-between; + align-items: center; + } + .cp-toolbar-userlist-name-input { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: none; + border: none; + } + .cp-toolbar-userlist-name-value { + overflow: hidden; + flex: 1; + min-width: 0; + min-height: 0; + text-overflow: ellipsis; + } + .cp-toolbar-userlist-name-edit { + width: 20px; + font-size: 16px; + padding: 0; + border: none; + height: 20px; + cursor: pointer; } .cp-toolbar-userlist-friend { padding: 0; @@ -115,7 +142,7 @@ } } - .addToolbarColors (@color, @bg-color) { + .addToolbarColors (@color, @bg-color, @barWidth: 600px) { .cp-toolbar-userlist-drawer { background-color: @bgcolor; color: @color; @@ -126,6 +153,19 @@ background-color: darken(@bgcolor, 10%); color: @color; } + .cp-toolbar-userlist-name-input { + background-color: darken(@bg-color, 10%); + color: @color; + } + .cp-toolbar-userlist-name-edit { + color: contrast(@color, + lighten(@color, 20%), + darken(@color, 20%)); + background: transparent; + &:hover { + color: @color; + } + } .cp-toolbar-userlist-friend { &:hover { color: darken(@color, 15%); @@ -148,6 +188,13 @@ background-color: @bgcolor; } } + .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; @@ -192,7 +239,7 @@ &.cp-app-slide { @bgcolor: @colortheme_slide-bg; @color: @colortheme_slide-color; - .addToolbarColors(@color, @bgcolor); + .addToolbarColors(@color, @bgcolor, 700px); } &.cp-app-poll { @bgcolor: @colortheme_poll-bg; @@ -236,33 +283,6 @@ } - /* TODO: move to the slide LESS page */ - .cp-app-slide { - @media screen and (max-width: @browser_media-medium-screen) { - .cp-toolbar-leftside { - flex-flow: row wrap; - width: 175px; - height: auto; - .cp-toolbar-spinner { order: 0; } - } - .cp-toolbar-rightside { - height: 2*@toolbar_line-height; - } - } - @media screen and (max-width: 320px) { - .cp-toolbar-leftside { - flex-flow: row wrap; - width: 175px; - height: auto; - padding-top: @toolbar_line-height; - .cp-toolbar-spinner { order: 0; } - } - .cp-toolbar-rightside { - height: auto; - } - } - } - .cp-toolbar { * { outline-width: 0; @@ -275,6 +295,9 @@ box-sizing: border-box; padding: 0px; + display: flex; + flex-wrap: wrap; + justify-content: space-between; //background-color: #BBBBFF; background-color: @colortheme_default-bg; @@ -437,8 +460,9 @@ flex-flow: row; height: @toolbar_top-height; position: relative; + width: 100%; .cp-toolbar-top-filler { - height: 100%; + height: @toolbar_top-height; display: inline-block; order: 4; //flex: 1; @@ -600,7 +624,7 @@ } } .cp-toolbar-user { - height: 100%; + height: @toolbar_top-height; display: inline-flex; order: 5; line-height: @toolbar_top-height; @@ -628,8 +652,9 @@ } .cp-dropdown-content { margin: 0; + overflow: visible; } - button { + & > button { display: flex; justify-content: center; align-items: center; @@ -675,9 +700,10 @@ &:empty { height: 0; } - float: left; display: inline-flex; align-items: center; + max-width: 100%; + flex: 1; //margin-bottom: -1px; .cp-toolbar-users { pre { @@ -720,12 +746,6 @@ height: 0; } text-align: right; - /*&> button { - height: 100%; - margin: 0; - border-radius: 0; - padding: 0 10px; - }*/ .cp-toolbar-drawer-content:empty ~ .cp-toolbar-drawer-button { display: none; } @@ -773,6 +793,11 @@ .cp-toolbar-spinner { line-height: @toolbar_line-height; padding: 0 20px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 200px; + box-sizing: border-box; &> span.fa { height: 20px; width: 20px; diff --git a/customize.dist/src/less2/main.less b/customize.dist/src/less2/main.less index c7b425f8c..ca1c3503e 100644 --- a/customize.dist/src/less2/main.less +++ b/customize.dist/src/less2/main.less @@ -36,4 +36,5 @@ body.cp-app-whiteboard { @import "../../../whiteboard/app-whiteboard.less"; } body.cp-app-todo { @import "../../../todo/app-todo.less"; } body.cp-app-profile { @import "../../../profile/app-profile.less"; } body.cp-app-settings { @import "../../../settings/app-settings.less"; } +body.cp-app-debug { @import "../../../debug/app-debug.less"; } diff --git a/customize.dist/template.js b/customize.dist/template.js index a6cf1797a..2aed683ce 100644 --- a/customize.dist/template.js +++ b/customize.dist/template.js @@ -1,11 +1,10 @@ define([ 'jquery', '/common/hyperscript.js', - '/common/cryptpad-common.js', '/customize/pages.js', 'less!/bower_components/components-font-awesome/css/font-awesome.min.css', -], function ($, h, Cryptpad, Pages) { +], function ($, h, Pages) { $(function () { var $body = $('body'); var isMainApp = function () { diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index a94907da8..2d01fd007 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -201,6 +201,7 @@ define(function () { out.cancel = "Annuler"; out.cancelButton = 'Annuler (Échap)'; + out.doNotAskAgain = "Ne plus demander (Échap)"; out.historyText = "Historique"; out.historyButton = "Afficher l'historique du document"; @@ -440,7 +441,7 @@ define(function () { out.login_invalPass = 'Mot de passe requis'; out.login_unhandledError = "Une erreur inattendue s'est produite :("; - out.register_importRecent = "Importer l'historique (Recommendé)"; + out.register_importRecent = "Importer l'historique (Recommandé)"; out.register_acceptTerms = "J'accepte les conditions d'utilisation"; out.register_passwordsDontMatch = "Les mots de passe doivent être identiques!"; out.register_passwordTooShort = "Les mots de passe doivent contenir au moins {0} caractères."; @@ -488,12 +489,12 @@ define(function () { out.settings_resetTipsButton = "Réinitialiser les astuces visibles dans CryptDrive"; out.settings_resetTipsDone = "Toutes les astuces sont de nouveau visibles."; - out.settings_thumbnails = "Vignettes"; - out.settings_disableThumbnailsAction = "Désactiver la création de vignettes dans CryptDrive"; - out.settings_disableThumbnailsDescription = "Des vignettes de vos pads sont automatiquement créées et stockées dans votre navigateur. Vous pouvez désactiver cette fonctionnalité."; + out.settings_thumbnails = "Miniatures"; + out.settings_disableThumbnailsAction = "Désactiver la création de miniatures dans CryptDrive"; + out.settings_disableThumbnailsDescription = "Des miniatures de vos pads sont automatiquement créées et stockées dans votre navigateur. Vous pouvez désactiver cette fonctionnalité."; out.settings_resetThumbnailsAction = "Nettoyer"; - out.settings_resetThumbnailsDescription = "Nettoyer toutes les vignettes stockées dans votre navigateur."; - out.settings_resetThumbnailsDone = "Toutes les vignettes ont été effacées."; + out.settings_resetThumbnailsDescription = "Nettoyer toutes les miniatures stockées dans votre navigateur."; + out.settings_resetThumbnailsDone = "Toutes les miniatures ont été effacées."; out.settings_importTitle = "Importer les pads récents de ce navigateur dans votre CryptDrive"; out.settings_import = "Importer"; @@ -523,6 +524,9 @@ define(function () { out.settings_codeUseTabs = "Utiliser des tabulations au lieu d'espaces"; 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. "+ + "Ce nom sera permanent et visible par les autres utilisateurs."; out.upload_serverError = "Erreur interne: impossible d'importer le fichier pour l'instant."; out.upload_uploadPending = "Vous avez déjà un fichier en cours d'importation. Souhaitez-vous l'annuler et importer ce nouveau fichier ?"; out.upload_success = "Votre fichier ({0}) a été importé avec succès et ajouté à votre CryptDrive."; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 29735e18f..b70635d48 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -204,6 +204,7 @@ define(function () { out.cancel = "Cancel"; out.cancelButton = 'Cancel (esc)'; + out.doNotAskAgain = "Don't ask me again (Esc)"; out.historyText = "History"; out.historyButton = "Display the document history"; @@ -528,6 +529,9 @@ define(function () { out.settings_codeUseTabs = "Indent using tabs (instead of spaces)"; 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. "+ + "This name will be permanent and visible to other users."; out.upload_serverError = "Server Error: unable to upload your file at this time."; out.upload_uploadPending = "You already have an upload in progress. Cancel it and upload your new file?"; out.upload_success = "Your file ({0}) has been successfully uploaded and added to your drive."; diff --git a/package.json b/package.json index 14bb2c321..7365de287 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cryptpad", "description": "realtime collaborative visual editor with zero knowlege server", - "version": "1.19.0", + "version": "1.21.0", "dependencies": { "chainpad-server": "^1.0.1", "express": "~4.10.1", diff --git a/www/assert/main.js b/www/assert/main.js index 3eb9e5892..bac38a8f2 100644 --- a/www/assert/main.js +++ b/www/assert/main.js @@ -3,12 +3,12 @@ define([ '/bower_components/hyperjson/hyperjson.js', '/bower_components/textpatcher/TextPatcher.amd.js', 'json.sortify', - '/common/cryptpad-common.js', '/drive/tests.js', '/common/test.js', + '/common/common-hash.js', '/common/common-thumbnail.js', '/common/flat-dom.js', -], function ($, Hyperjson, TextPatcher, Sortify, Cryptpad, Drive, Test, Thumb, Flat) { +], function ($, Hyperjson, TextPatcher, Sortify, Drive, Test, Hash, Thumb, Flat) { window.Hyperjson = Hyperjson; window.TextPatcher = TextPatcher; window.Sortify = Sortify; @@ -158,7 +158,7 @@ define([ // check that old hashes parse correctly assert(function (cb) { - var secret = Cryptpad.parsePadUrl('/pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy'); + var secret = Hash.parsePadUrl('/pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy'); return cb(secret.hashData.channel === "67b8385b07352be53e40746d2be6ccd7" && secret.hashData.key === "XAYSuJYYqa9NfmInyHci7LNy" && secret.hashData.version === 0); @@ -166,7 +166,7 @@ define([ // make sure version 1 hashes parse correctly assert(function (cb) { - var secret = Cryptpad.parsePadUrl('/pad/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI'); + var secret = Hash.parsePadUrl('/pad/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI'); return cb(secret.hashData.version === 1 && secret.hashData.mode === "edit" && secret.hashData.channel === "3Ujt4F2Sjnjbis6CoYWpoQ" && @@ -176,7 +176,7 @@ define([ // test support for present mode in hashes assert(function (cb) { - var secret = Cryptpad.parsePadUrl('/pad/#/1/edit/CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G/present'); + var secret = Hash.parsePadUrl('/pad/#/1/edit/CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G/present'); return cb(secret.hashData.version === 1 && secret.hashData.mode === "edit" && secret.hashData.channel === "CmN5+YJkrHFS3NSBg-P7Sg" @@ -186,7 +186,7 @@ define([ // test support for present mode in hashes assert(function (cb) { - var secret = Cryptpad.parsePadUrl('/pad/#/1/edit//CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G//present'); + var secret = Hash.parsePadUrl('/pad/#/1/edit//CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G//present'); return cb(secret.hashData.version === 1 && secret.hashData.mode === "edit" && secret.hashData.channel === "CmN5+YJkrHFS3NSBg-P7Sg" @@ -196,7 +196,7 @@ define([ // test support for present & embed mode in hashes assert(function (cb) { - var secret = Cryptpad.parsePadUrl('/pad/#/1/edit//CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G/embed/present/'); + var secret = Hash.parsePadUrl('/pad/#/1/edit//CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G/embed/present/'); return cb(secret.hashData.version === 1 && secret.hashData.mode === "edit" && secret.hashData.channel === "CmN5+YJkrHFS3NSBg-P7Sg" @@ -207,7 +207,7 @@ define([ // test support for present & embed mode in hashes assert(function (cb) { - var secret = Cryptpad.parsePadUrl('/pad/#/1/edit//CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G/present/embed'); + var secret = Hash.parsePadUrl('/pad/#/1/edit//CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G/present/embed'); return cb(secret.hashData.version === 1 && secret.hashData.mode === "edit" && secret.hashData.channel === "CmN5+YJkrHFS3NSBg-P7Sg" @@ -218,7 +218,7 @@ define([ // test support for embed mode in hashes assert(function (cb) { - var secret = Cryptpad.parsePadUrl('/pad/#/1/edit//CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G///embed//'); + var secret = Hash.parsePadUrl('/pad/#/1/edit//CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G///embed//'); return cb(secret.hashData.version === 1 && secret.hashData.mode === "edit" && secret.hashData.channel === "CmN5+YJkrHFS3NSBg-P7Sg" @@ -229,7 +229,7 @@ define([ // test support for trailing slash assert(function (cb) { - var secret = Cryptpad.parsePadUrl('/pad/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI/'); + var secret = Hash.parsePadUrl('/pad/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI/'); return cb(secret.hashData.version === 1 && secret.hashData.mode === "edit" && secret.hashData.channel === "3Ujt4F2Sjnjbis6CoYWpoQ" && @@ -238,13 +238,24 @@ define([ }, "test support for trailing slashes in version 1 hash failed to parse"); assert(function (cb) { - var secret = Cryptpad.parsePadUrl('/invite/#/1/ilrOtygzDVoUSRpOOJrUuQ/e8jvf36S3chzkkcaMrLSW7PPrz7VDp85lIFNI26dTmr=/'); + var secret = Hash.parsePadUrl('/invite/#/1/ilrOtygzDVoUSRpOOJrUuQ/e8jvf36S3chzkkcaMrLSW7PPrz7VDp85lIFNI26dTmr=/'); var hd = secret.hashData; cb(hd.channel === "ilrOtygzDVoUSRpOOJrUuQ" && hd.pubkey === "e8jvf36S3chzkkcaMrLSW7PPrz7VDp85lIFNI26dTmr=" && hd.type === 'invite'); }, "test support for invite urls"); + assert(function (cb) { + var url = '/pad/?utm_campaign=new_comment&utm_medium=email&utm_source=thread_mailer#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI/'; + var secret = Hash.parsePadUrl(url); + + return cb(secret.hashData.version === 1 && + secret.hashData.mode === "edit" && + secret.hashData.channel === "3Ujt4F2Sjnjbis6CoYWpoQ" && + secret.hashData.key === "usn4+9CqVja8Q7RZOGTfRgqI" && + !secret.hashData.present); + }, "test support for ugly tracking query paramaters in url"); + assert(function (cb) { // TODO return cb(true); diff --git a/www/code/inner.js b/www/code/inner.js index 8e6f213f1..afe9e5059 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -1,12 +1,13 @@ define([ 'jquery', - '/common/cryptpad-common.js', '/common/diffMarked.js', '/bower_components/nthen/index.js', '/common/sframe-common.js', '/common/sframe-app-framework.js', '/common/common-util.js', + '/common/common-hash.js', '/common/modes.js', + '/customize/messages.js', 'cm/lib/codemirror', 'css!cm/lib/codemirror.css', @@ -37,17 +38,17 @@ define([ ], function ( $, - Cryptpad, DiffMd, nThen, SFCommon, Framework, Util, + Hash, Modes, + Messages, CMeditor) { window.CodeMirror = CMeditor; - var Messages = Cryptpad.Messages; var MEDIA_TAG_MODES = Object.freeze([ 'markdown', @@ -80,7 +81,11 @@ define([ }, 150); $previewButton.removeClass('fa-question').addClass('fa-eye'); - $previewButton.attr('title', Messages.previewButtonTitle); + 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); @@ -291,8 +296,8 @@ define([ //var cursor = editor.getCursor(); //var cleanName = data.name.replace(/[\[\]]/g, ''); //var text = '!['+cleanName+']('+data.url+')'; - var parsed = Cryptpad.parsePadUrl(data.url); - var hexFileName = Cryptpad.base64ToHex(parsed.hashData.channel); + var parsed = Hash.parsePadUrl(data.url); + var hexFileName = Util.base64ToHex(parsed.hashData.channel); var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; var mt = ''; editor.replaceSelection(mt); @@ -352,6 +357,7 @@ define([ } $(el).parents().css('overflow', ''); $(el).css('max-height', ''); + editor.refresh(); } } }, waitFor(function (fw) { framework = fw; })); diff --git a/www/common/common-constants.js b/www/common/common-constants.js new file mode 100644 index 000000000..76c70d103 --- /dev/null +++ b/www/common/common-constants.js @@ -0,0 +1,14 @@ +define(function () { + return { + // localStorage + userHashKey: 'User_hash', + userNameKey: 'User_name', + fileHashKey: 'FS_hash', + // sessionStorage + newPadPathKey: "newPadPath", + // Store + displayNameKey: 'cryptpad.username', + oldStorageKey: 'CryptPad_RECENTPADS', + storageKey: 'filesData', + }; +}); diff --git a/www/common/common-file.js b/www/common/common-file.js deleted file mode 100644 index df2f94ee7..000000000 --- a/www/common/common-file.js +++ /dev/null @@ -1,366 +0,0 @@ -define([ - 'jquery', - '/file/file-crypto.js', - '/common/common-thumbnail.js', - '/bower_components/tweetnacl/nacl-fast.min.js', -], function ($, FileCrypto, Thumb) { - var Nacl = window.nacl; - var module = {}; - - var blobToArrayBuffer = module.blobToArrayBuffer = function (blob, cb) { - var reader = new FileReader(); - reader.onloadend = function () { - cb(void 0, this.result); - }; - reader.readAsArrayBuffer(blob); - }; - - var arrayBufferToString = function (AB) { - try { - return Nacl.util.encodeBase64(new Uint8Array(AB)); - } catch (e) { - console.error(e); - return null; - } - }; - - module.upload = function (file, noStore, common, updateProgress, onComplete, onError, onPending) { - var u8 = file.blob; // This is not a blob but a uint8array - var metadata = file.metadata; - - // if it exists, path contains the new pad location in the drive - var path = file.path; - - var key = Nacl.randomBytes(32); - var next = FileCrypto.encrypt(u8, metadata, key); - - var estimate = FileCrypto.computeEncryptedSize(u8.length, metadata); - - var sendChunk = function (box, cb) { - var enc = Nacl.util.encodeBase64(box); - common.rpc.send.unauthenticated('UPLOAD', enc, function (e, msg) { - cb(e, msg); - }); - }; - - var actual = 0; - var again = function (err, box) { - if (err) { throw new Error(err); } - if (box) { - actual += box.length; - var progressValue = (actual / estimate * 100); - updateProgress(progressValue); - - return void sendChunk(box, function (e) { - if (e) { return console.error(e); } - next(again); - }); - } - - if (actual !== estimate) { - console.error('Estimated size does not match actual size'); - } - - // if not box then done - common.uploadComplete(function (e, id) { - if (e) { return void console.error(e); } - var uri = ['', 'blob', id.slice(0,2), id].join('/'); - console.log("encrypted blob is now available as %s", uri); - - var b64Key = Nacl.util.encodeBase64(key); - - var hash = common.getFileHashFromKeys(id, b64Key); - var href = '/file/#' + hash; - - var title = metadata.name; - - if (noStore) { return void onComplete(href); } - - common.initialPath = path; - common.renamePad(title || "", href, function (err) { - if (err) { return void console.error(err); } - onComplete(href); - common.setPadAttribute('fileType', metadata.type, null, href); - }); - }); - }; - - common.uploadStatus(estimate, function (e, pending) { - if (e) { - console.error(e); - onError(e); - return; - } - - if (pending) { - return void onPending(function () { - // if the user wants to cancel the pending upload to execute that one - common.uploadCancel(function (e, res) { - if (e) { - return void console.error(e); - } - console.log(res); - next(again); - }); - }); - } - next(again); - }); - }; - - module.create = function (common, config) { - var File = {}; - - var Messages = common.Messages; - - var queue = File.queue = { - queue: [], - inProgress: false - }; - - var uid = function () { - return 'file-' + String(Math.random()).substring(2); - }; - - var $table = File.$table = $('', { id: 'uploadStatus' }); - var $thead = $('').appendTo($table); - $('', {id: id}).appendTo($table); - - var $cancel = $('', {'class': 'cancel fa fa-times'}).click(function () { - queue.queue = queue.queue.filter(function (el) { return el.id !== id; }); - $cancel.remove(); - $tr.find('.upCancel').text('-'); - $tr.find('.progressValue').text(Messages.upload_cancelled); - }); - - var $link = $('', { - 'class': 'upLink', - 'rel': 'noopener noreferrer' - }).text(obj.metadata.name); - - $('
').text(Messages.upload_name).appendTo($thead); - $('').text(Messages.upload_size).appendTo($thead); - $('').text(Messages.upload_progress).appendTo($thead); - $('').text(Messages.cancel).appendTo($thead); - - var createTableContainer = function ($body) { - File.$container = $('
', { id: 'uploadStatusContainer' }).append($table).appendTo($body); - return File.$container; - }; - - var getData = function (file, href) { - var data = {}; - - data.name = file.metadata.name; - data.url = href; - if (file.metadata.type.slice(0,6) === 'image/') { - data.mediatag = true; - } - - return data; - }; - - var upload = function (file) { - var blob = file.blob; // This is not a blob but an array buffer - var u8 = new Uint8Array(blob); - var metadata = file.metadata; - var id = file.id; - if (queue.inProgress) { return; } - queue.inProgress = true; - - var $row = $table.find('tr[id="'+id+'"]'); - - $row.find('.upCancel').html('-'); - var $pv = $row.find('.progressValue'); - var $pb = $row.find('.progressContainer'); - var $pc = $row.find('.upProgress'); - var $link = $row.find('.upLink'); - - var updateProgress = function (progressValue) { - $pv.text(Math.round(progressValue*100)/100 + '%'); - $pb.css({ - width: (progressValue/100)*$pc.width()+'px' - }); - }; - - var onComplete = function (href) { - $link.attr('href', href) - .click(function (e) { - e.preventDefault(); - window.open($link.attr('href'), '_blank'); - }); - var title = metadata.name; - common.log(Messages._getKey('upload_success', [title])); - common.prepareFeedback('upload')(); - - if (config.onUploaded) { - var data = getData(file, href); - config.onUploaded(file.dropEvent, data); - } - - queue.inProgress = false; - queue.next(); - }; - - var onError = function (e) { - queue.inProgress = false; - queue.next(); - if (e === 'TOO_LARGE') { - // TODO update table to say too big? - return void common.alert(Messages.upload_tooLarge); - } - if (e === 'NOT_ENOUGH_SPACE') { - // TODO update table to say not enough space? - return void common.alert(Messages.upload_notEnoughSpace); - } - console.error(e); - return void common.alert(Messages.upload_serverError); - }; - - var onPending = function (cb) { - common.confirm(Messages.upload_uploadPending, function (yes) { - if (!yes) { return; } - cb(); - }); - }; - - file.blob = u8; - module.upload(file, config.noStore, common, updateProgress, onComplete, onError, onPending); - }; - - var prettySize = function (bytes) { - var kB = common.bytesToKilobytes(bytes); - if (kB < 1024) { return kB + Messages.KB; } - var mB = common.bytesToMegabytes(bytes); - return mB + Messages.MB; - }; - - queue.next = function () { - if (queue.queue.length === 0) { - queue.to = window.setTimeout(function () { - if (config.keepTable) { return; } - File.$container.fadeOut(); - }, 3000); - return; - } - if (queue.inProgress) { return; } - File.$container.show(); - var file = queue.queue.shift(); - upload(file); - }; - queue.push = function (obj) { - var id = uid(); - obj.id = id; - queue.queue.push(obj); - - $table.show(); - var estimate = FileCrypto.computeEncryptedSize(obj.blob.byteLength, obj.metadata); - - var $progressBar = $('
', {'class':'progressContainer'}); - var $progressValue = $('', {'class':'progressValue'}).text(Messages.upload_pending); - - var $tr = $('
').append($link).appendTo($tr); - $('').text(prettySize(estimate)).appendTo($tr); - $('', {'class': 'upProgress'}).append($progressBar).append($progressValue).appendTo($tr); - $('', {'class': 'upCancel'}).append($cancel).appendTo($tr); - - queue.next(); - }; - - var handleFile = File.handleFile = function (file, e, thumbnail) { - var thumb; - var file_arraybuffer; - var finish = function () { - var metadata = { - name: file.name, - type: file.type, - }; - if (thumb) { metadata.thumbnail = thumb; } - queue.push({ - blob: file_arraybuffer, - metadata: metadata, - dropEvent: e - }); - }; - - blobToArrayBuffer(file, function (e, buffer) { - if (e) { console.error(e); } - file_arraybuffer = buffer; - if (thumbnail) { // there is already a thumbnail - return blobToArrayBuffer(thumbnail, function (e, buffer) { - if (e) { console.error(e); } - thumb = arrayBufferToString(buffer); - finish(); - }); - } - - if (!Thumb.isSupportedType(file.type)) { return finish(); } - // make a resized thumbnail from the image.. - Thumb.fromBlob(file, function (e, thumb64) { - if (e) { console.error(e); } - if (!thumb64) { return finish(); } - thumb = thumb64; - finish(); - }); - }); - }; - - var onFileDrop = File.onFileDrop = function (file, e) { - if (!common.isLoggedIn()) { - return common.alert(common.Messages.upload_mustLogin); - } - - Array.prototype.slice.call(file).forEach(function (d) { - handleFile(d, e); - }); - }; - - var createAreaHandlers = File.createDropArea = function ($area, $hoverArea) { - var counter = 0; - if (!$hoverArea) { $hoverArea = $area; } - if (!$area) { return; } - $hoverArea - .on('dragenter', function (e) { - e.preventDefault(); - e.stopPropagation(); - counter++; - $hoverArea.addClass('hovering'); - }) - .on('dragleave', function (e) { - e.preventDefault(); - e.stopPropagation(); - counter--; - if (counter <= 0) { - $hoverArea.removeClass('hovering'); - } - }); - - $area - .on('drag dragstart dragend dragover drop dragenter dragleave', function (e) { - e.preventDefault(); - e.stopPropagation(); - }) - .on('drop', function (e) { - e.stopPropagation(); - - var dropped = e.originalEvent.dataTransfer.files; - counter = 0; - $hoverArea.removeClass('hovering'); - onFileDrop(dropped, e); - }); - }; - - var createUploader = function ($area, $hover, $body) { - if (!config.noHandlers) { - createAreaHandlers($area, null); - } - createTableContainer($body); - }; - - createUploader(config.dropArea, config.hoverArea, config.body); - - return File; - }; - - return module; -}); diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 2aa2155bf..2bbf2c7e2 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -1,9 +1,9 @@ define([ '/common/common-util.js', - '/common/common-interface.js', + '/customize/messages.js', '/bower_components/chainpad-crypto/crypto.js', '/bower_components/tweetnacl/nacl-fast.min.js' -], function (Util, UI, Crypto) { +], function (Util, Messages, Crypto) { var Nacl = window.nacl; var Hash = {}; @@ -35,8 +35,8 @@ define([ var getFileHashFromKeys = Hash.getFileHashFromKeys = function (fileKey, cryptKey) { return '/1/' + hexToBase64(fileKey) + '/' + Crypto.b64RemoveSlashes(cryptKey) + '/'; }; - Hash.getUserHrefFromKeys = function (username, pubkey) { - return window.location.origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-'); + Hash.getUserHrefFromKeys = function (origin, username, pubkey) { + return origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-'); }; var fixDuplicateSlashes = function (s) { @@ -114,6 +114,7 @@ Version 1 if (!href) { return ret; } if (href.slice(-1) !== '/') { href += '/'; } + href = href.replace(/\/\?[^#]+#/, '/#'); var idx; @@ -211,14 +212,12 @@ Version 1 secret.keys = Crypto.createEditCryptor(parsed.key); secret.key = secret.keys.editKeyStr; if (secret.channel.length !== 32 || secret.key.length !== 24) { - UI.alert("The channel key and/or the encryption key is invalid"); throw new Error("The channel key and/or the encryption key is invalid"); } } else if (parsed.mode === 'view') { secret.keys = Crypto.createViewCryptor(parsed.key); if (secret.channel.length !== 32) { - UI.alert("The channel key is invalid"); throw new Error("The channel key is invalid"); } } @@ -363,5 +362,19 @@ Version 1 '/' + curvePublic.replace(/\//g, '-') + '/'; }; + // Create untitled documents when no name is given + var getLocaleDate = function () { + if (window.Intl && window.Intl.DateTimeFormat) { + var options = {weekday: "short", year: "numeric", month: "long", day: "numeric"}; + return new window.Intl.DateTimeFormat(undefined, options).format(new Date()); + } + return new Date().toString().split(' ').slice(0,4).join(' '); + }; + Hash.getDefaultName = function (parsed) { + var type = parsed.type; + var name = (Messages.type)[type] + ' - ' + getLocaleDate(); + return name; + }; + return Hash; }); diff --git a/www/common/common-history.js b/www/common/common-history.js deleted file mode 100644 index 387f349fc..000000000 --- a/www/common/common-history.js +++ /dev/null @@ -1,266 +0,0 @@ -define([ - 'jquery', - '/bower_components/chainpad-crypto/crypto.js', - '/bower_components/chainpad/chainpad.dist.js', -], function ($, Crypto, ChainPad) { - var History = {}; - - var getStates = function (rt) { - var states = []; - var b = rt.getAuthBlock(); - if (b) { states.unshift(b); } - while (b.getParent()) { - b = b.getParent(); - states.unshift(b); - } - return states; - }; - - var loadHistory = function (config, common, cb) { - var network = common.getNetwork(); - var hkn = network.historyKeeper; - - var wcId = common.hrefToHexChannelId(config.href || window.location.href); - - var createRealtime = function () { - return ChainPad.create({ - userName: 'history', - initialState: '', - patchTransformer: ChainPad.NaiveJSONStransformer, - logLevel: 0, - noPrune: true - }); - }; - var realtime = createRealtime(); - - var parsed = config.href ? common.parsePadUrl(config.href) : {}; - var secret = common.getSecrets(parsed.type, parsed.hash); - - History.readOnly = 0; - if (!secret.keys) { - secret.keys = secret.key; - History.readOnly = 0; - } - else if (!secret.keys.validateKey) { - History.readOnly = 1; - } - - var crypto = Crypto.createEncryptor(secret.keys); - - var to = window.setTimeout(function () { - cb('[GET_FULL_HISTORY_TIMEOUT]'); - }, 30000); - - var parse = function (msg) { - try { - return JSON.parse(msg); - } catch (e) { - return null; - } - }; - var onMsg = function (msg) { - var parsed = parse(msg); - if (parsed[0] === 'FULL_HISTORY_END') { - console.log('END'); - window.clearTimeout(to); - cb(null, realtime); - return; - } - if (parsed[0] !== 'FULL_HISTORY') { return; } - if (parsed[1] && parsed[1].validateKey) { // First message - secret.keys.validateKey = parsed[1].validateKey; - return; - } - msg = parsed[1][4]; - if (msg) { - msg = msg.replace(/^cp\|/, ''); - var decryptedMsg = crypto.decrypt(msg, secret.keys.validateKey); - realtime.message(decryptedMsg); - } - }; - - network.on('message', function (msg) { - onMsg(msg); - }); - - network.sendto(hkn, JSON.stringify(['GET_FULL_HISTORY', wcId, secret.keys.validateKey])); - }; - - History.create = function (common, config) { - if (!config.$toolbar) { return void console.error("config.$toolbar is undefined");} - if (History.loading) { return void console.error("History is already being loaded..."); } - History.loading = true; - var $toolbar = config.$toolbar; - - if (!config.applyVal || !config.setHistory || !config.onLocal || !config.onRemote) { - throw new Error("Missing config element: applyVal, onLocal, onRemote, setHistory"); - } - - // config.setHistory(bool, bool) - // - bool1: history value - // - bool2: reset old content? - var render = function (val) { - if (typeof val === "undefined") { return; } - try { - config.applyVal(val); - } catch (e) { - // Probably a parse error - console.error(e); - } - }; - var onClose = function () { config.setHistory(false, true); }; - var onRevert = function () { - config.setHistory(false, false); - config.onLocal(); - config.onRemote(); - }; - var onReady = function () { - config.setHistory(true); - }; - - var Messages = common.Messages; - - var realtime; - - var states = []; - var c = states.length - 1; - - var $hist = $toolbar.find('.cryptpad-toolbar-history'); - var $left = $toolbar.find('.cryptpad-toolbar-leftside'); - var $right = $toolbar.find('.cryptpad-toolbar-rightside'); - var $cke = $toolbar.find('.cke_toolbox_main'); - - $hist.html('').show(); - $left.hide(); - $right.hide(); - $cke.hide(); - - common.spinner($hist).get().show(); - - var onUpdate; - - var update = function () { - if (!realtime) { return []; } - states = getStates(realtime); - if (typeof onUpdate === "function") { onUpdate(); } - return states; - }; - - // Get the content of the selected version, and change the version number - var get = function (i) { - i = parseInt(i); - if (isNaN(i)) { return; } - if (i < 0) { i = 0; } - if (i > states.length - 1) { i = states.length - 1; } - var val = states[i].getContent().doc; - c = i; - if (typeof onUpdate === "function") { onUpdate(); } - $hist.find('.next, .previous').css('visibility', ''); - if (c === states.length - 1) { $hist.find('.next').css('visibility', 'hidden'); } - if (c === 0) { $hist.find('.previous').css('visibility', 'hidden'); } - return val || ''; - }; - - var getNext = function (step) { - return typeof step === "number" ? get(c + step) : get(c + 1); - }; - var getPrevious = function (step) { - return typeof step === "number" ? get(c - step) : get(c - 1); - }; - - // Create the history toolbar - var display = function () { - $hist.html(''); - var $prev =$('