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..f0d310e67 100644 --- a/customize.dist/main.js +++ b/customize.dist/main.js @@ -2,15 +2,15 @@ define([ 'jquery', '/customize/application_config.js', '/common/cryptpad-common.js', - '/customize/header.js', -], function ($, Config, Cryptpad) { + '/common/common-interface.js', + '/common/common-realtime.js', + '/customize/messages.js', +], function ($, Config, Cryptpad, UI, Realtime, Messages) { window.APP = { Cryptpad: Cryptpad, }; - var Messages = Cryptpad.Messages; - $(function () { var $main = $('#mainBlock'); @@ -58,34 +58,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 +88,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 +107,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 +116,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 +139,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..e1142e2df 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; 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.less b/customize.dist/src/less2/include/toolbar.less index d39effaef..99c0cdf15 100644 --- a/customize.dist/src/less2/include/toolbar.less +++ b/customize.dist/src/less2/include/toolbar.less @@ -102,6 +102,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 +141,7 @@ } } - .addToolbarColors (@color, @bg-color) { + .addToolbarColors (@color, @bg-color, @barWidth: 600px) { .cp-toolbar-userlist-drawer { background-color: @bgcolor; color: @color; @@ -126,6 +152,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 +187,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 +238,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 +282,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 +294,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 +459,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 +623,7 @@ } } .cp-toolbar-user { - height: 100%; + height: @toolbar_top-height; display: inline-flex; order: 5; line-height: @toolbar_top-height; @@ -628,8 +651,9 @@ } .cp-dropdown-content { margin: 0; + overflow: visible; } - button { + & > button { display: flex; justify-content: center; align-items: center; @@ -675,9 +699,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 +745,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 +792,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/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 64edebce9..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"; @@ -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/www/assert/main.js b/www/assert/main.js index 09a5a8279..f6dff1ec2 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,7 +238,7 @@ 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=" && diff --git a/www/code/inner.js b/www/code/inner.js index 5f2cf3df1..73708d1c7 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -1,13 +1,14 @@ define([ 'jquery', '/bower_components/textpatcher/TextPatcher.js', - '/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', @@ -39,17 +40,17 @@ define([ ], function ( $, TextPatcher, - Cryptpad, DiffMd, nThen, SFCommon, Framework, Util, + Hash, Modes, + Messages, CMeditor) { window.CodeMirror = CMeditor; - var Messages = Cryptpad.Messages; var MEDIA_TAG_MODES = Object.freeze([ 'markdown', @@ -82,7 +83,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); @@ -293,8 +298,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); diff --git a/www/common/common-codemirror.js b/www/common/common-codemirror.js deleted file mode 100644 index a32d89af1..000000000 --- a/www/common/common-codemirror.js +++ /dev/null @@ -1,308 +0,0 @@ -define([ - 'jquery', - '/common/modes.js', - '/common/themes.js', - - '/bower_components/file-saver/FileSaver.min.js' -], function ($, Modes, Themes) { - var saveAs = window.saveAs; - var module = {}; - - module.create = function (ifrw, Cryptpad, defaultMode, CMeditor) { - var exp = {}; - var Messages = Cryptpad.Messages; - - var CodeMirror = exp.CodeMirror = CMeditor; - CodeMirror.modeURL = "cm/mode/%N/%N"; - - var $pad = $('#pad-iframe'); - var $textarea = exp.$textarea = $('#editor1'); - if (!$textarea.length) { $textarea = exp.$textarea = $pad.contents().find('#editor1'); } - - var Title; - var onLocal = function () {}; - var $rightside; - var $drawer; - exp.init = function (local, title, toolbar) { - if (typeof local === "function") { - onLocal = local; - } - Title = title; - $rightside = toolbar.$rightside; - $drawer = toolbar.$drawer; - }; - - var editor = exp.editor = CMeditor.fromTextArea($textarea[0], { - lineNumbers: true, - lineWrapping: true, - autoCloseBrackets: true, - matchBrackets : true, - showTrailingSpace : true, - styleActiveLine : true, - search: true, - highlightSelectionMatches: {showToken: /\w+/}, - extraKeys: {"Shift-Ctrl-R": undefined}, - foldGutter: true, - gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], - mode: defaultMode || "javascript", - readOnly: true - }); - editor.setValue(Messages.codeInitialState); - - var setMode = exp.setMode = function (mode, cb) { - exp.highlightMode = mode; - if (mode !== "text") { - CMeditor.autoLoadMode(editor, mode); - } - editor.setOption('mode', mode); - if (exp.$language) { - var name = exp.$language.find('a[data-value="' + mode + '"]').text() || undefined; - name = name ? Messages.languageButton + ' ('+name+')' : Messages.languageButton; - exp.$language.setValue(mode, name); - } - if(cb) { cb(mode); } - }; - - var setTheme = exp.setTheme = (function () { - var path = '/common/theme/'; - - var $head = $(ifrw.document.head); - - var themeLoaded = exp.themeLoaded = function (theme) { - return $head.find('link[href*="'+theme+'"]').length; - }; - - var loadTheme = exp.loadTheme = function (theme) { - $head.append($('', { - rel: 'stylesheet', - href: path + theme + '.css', - })); - }; - - return function (theme, $select) { - if (!theme) { - editor.setOption('theme', 'default'); - } else { - if (!themeLoaded(theme)) { - loadTheme(theme); - } - editor.setOption('theme', theme); - } - if ($select) { - var name = theme || undefined; - name = name ? Messages.themeButton + ' ('+theme+')' : Messages.themeButton; - $select.setValue(theme, name); - } - }; - }()); - - exp.getHeadingText = function () { - var lines = editor.getValue().split(/\n/); - - var text = ''; - lines.some(function (line) { - // lines including a c-style comment are also valuable - var clike = /^\s*(\/\*|\/\/)(.*)?(\*\/)*$/; - if (clike.test(line)) { - line.replace(clike, function (a, one, two) { - if (!(two && two.replace)) { return; } - text = two.replace(/\*\/\s*$/, '').trim(); - }); - return true; - } - - // lisps? - var lispy = /^\s*(;|#\|)+(.*?)$/; - if (lispy.test(line)) { - line.replace(lispy, function (a, one, two) { - text = two; - }); - return true; - } - - // lines beginning with a hash are potentially valuable - // works for markdown, python, bash, etc. - var hash = /^#+(.*?)$/; - if (hash.test(line)) { - line.replace(hash, function (a, one) { - text = one; - }); - return true; - } - - // TODO make one more pass for multiline comments - }); - - return text.trim(); - }; - - exp.configureLanguage = function (cb, onModeChanged) { - var options = []; - Modes.list.forEach(function (l) { - options.push({ - tag: 'a', - attributes: { - 'data-value': l.mode, - 'href': '#', - }, - content: l.language // Pretty name of the language value - }); - }); - var dropdownConfig = { - text: 'Mode', // Button initial text - options: options, // Entries displayed in the menu - left: true, // Open to the left of the button - isSelect: true, - feedback: 'CODE_LANGUAGE', - }; - var $block = exp.$language = Cryptpad.createDropdown(dropdownConfig); - $block.find('button').attr('title', Messages.languageButtonTitle); - $block.find('a').click(function () { - setMode($(this).attr('data-value'), onModeChanged); - onLocal(); - }); - - if ($drawer) { $drawer.append($block); } - if (cb) { cb(); } - }; - - exp.configureTheme = function (cb) { - /* Remember the user's last choice of theme using localStorage */ - var themeKey = 'CRYPTPAD_CODE_THEME'; - var lastTheme = localStorage.getItem(themeKey) || 'default'; - - var options = []; - Themes.forEach(function (l) { - options.push({ - tag: 'a', - attributes: { - 'data-value': l.name, - 'href': '#', - }, - content: l.name // Pretty name of the language value - }); - }); - var dropdownConfig = { - text: 'Theme', // Button initial text - options: options, // Entries displayed in the menu - left: true, // Open to the left of the button - isSelect: true, - initialValue: lastTheme, - feedback: 'CODE_THEME', - }; - var $block = exp.$theme = Cryptpad.createDropdown(dropdownConfig); - $block.find('button').attr('title', Messages.themeButtonTitle); - - setTheme(lastTheme, $block); - - $block.find('a').click(function () { - var theme = $(this).attr('data-value'); - setTheme(theme, $block); - localStorage.setItem(themeKey, theme); - }); - - if ($drawer) { $drawer.append($block); } - if (cb) { cb(); } - }; - - exp.exportText = function () { - var text = editor.getValue(); - - var ext = Modes.extensionOf(exp.highlightMode); - - var title = Cryptpad.fixFileName(Title ? Title.suggestTitle('cryptpad') : "?") + (ext || '.txt'); - - Cryptpad.prompt(Messages.exportPrompt, title, function (filename) { - if (filename === null) { return; } - var blob = new Blob([text], { - type: 'text/plain;charset=utf-8' - }); - saveAs(blob, filename); - }); - }; - exp.importText = function (content, file) { - var $bar = ifrw.$('#cme_toolbox'); - var mode; - var mime = CodeMirror.findModeByMIME(file.type); - - if (!mime) { - var ext = /.+\.([^.]+)$/.exec(file.name); - if (ext[1]) { - mode = CMeditor.findModeByExtension(ext[1]); - mode = mode && mode.mode || null; - } - } else { - mode = mime && mime.mode || null; - } - - if (mode && Modes.list.some(function (o) { return o.mode === mode; })) { - setMode(mode); - $bar.find('#language-mode').val(mode); - } else { - console.log("Couldn't find a suitable highlighting mode: %s", mode); - setMode('text'); - $bar.find('#language-mode').val('text'); - } - - editor.setValue(content); - onLocal(); - }; - - var cursorToPos = function(cursor, oldText) { - var cLine = cursor.line; - var cCh = cursor.ch; - var pos = 0; - var textLines = oldText.split("\n"); - for (var line = 0; line <= cLine; line++) { - if(line < cLine) { - pos += textLines[line].length+1; - } - else if(line === cLine) { - pos += cCh; - } - } - return pos; - }; - - var posToCursor = function(position, newText) { - var cursor = { - line: 0, - ch: 0 - }; - var textLines = newText.substr(0, position).split("\n"); - cursor.line = textLines.length - 1; - cursor.ch = textLines[cursor.line].length; - return cursor; - }; - - exp.setValueAndCursor = function (oldDoc, remoteDoc, TextPatcher) { - var scroll = editor.getScrollInfo(); - //get old cursor here - var oldCursor = {}; - oldCursor.selectionStart = cursorToPos(editor.getCursor('from'), oldDoc); - oldCursor.selectionEnd = cursorToPos(editor.getCursor('to'), oldDoc); - - editor.setValue(remoteDoc); - editor.save(); - - var op = TextPatcher.diff(oldDoc, remoteDoc); - var selects = ['selectionStart', 'selectionEnd'].map(function (attr) { - return TextPatcher.transformCursor(oldCursor[attr], op); - }); - - if(selects[0] === selects[1]) { - editor.setCursor(posToCursor(selects[0], remoteDoc)); - } - else { - editor.setSelection(posToCursor(selects[0], remoteDoc), posToCursor(selects[1], remoteDoc)); - } - - editor.scrollTo(scroll.left, scroll.top); - }; - - return exp; - }; - - return module; -}); - 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 d53c6718d..bcaf3b7f8 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -1,9 +1,8 @@ define([ '/common/common-util.js', - '/common/common-interface.js', '/bower_components/chainpad-crypto/crypto.js', '/bower_components/tweetnacl/nacl-fast.min.js' -], function (Util, UI, Crypto) { +], function (Util, Crypto) { var Nacl = window.nacl; var Hash = {}; @@ -35,8 +34,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) { @@ -212,14 +211,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"); } } diff --git a/www/common/common-history.js b/www/common/common-history.js deleted file mode 100644 index 6dea29ab7..000000000 --- a/www/common/common-history.js +++ /dev/null @@ -1,268 +0,0 @@ -define([ - 'jquery', - '/bower_components/chainpad-json-validator/json-ot.js', - '/bower_components/chainpad-crypto/crypto.js', - '/bower_components/chainpad/chainpad.dist.js', -], function ($, JsonOT, Crypto) { - var ChainPad = window.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: '', - transformFunction: JsonOT.validate, - 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 =$('