define([ 'jquery', '/customize/application_config.js', '/api/config', '/api/broadcast', '/common/common-ui-elements.js', '/common/common-interface.js', '/common/common-hash.js', '/common/common-util.js', '/common/common-feedback.js', '/common/inner/common-mediatag.js', '/common/hyperscript.js', '/common/messenger-ui.js', '/customize/messages.js', '/customize/pages.js', ], function ($, Config, ApiConfig, Broadcast, UIElements, UI, Hash, Util, Feedback, MT, h, MessengerUI, Messages, Pages) { var Common; var Bar = { constants: {}, }; var SPINNER_DISAPPEAR_TIME = 1000; // Toolbar parts var TOOLBAR_CLS = Bar.constants.toolbar = 'cp-toolbar'; var TOP_CLS = Bar.constants.top = 'cp-toolbar-top'; var BOTTOM_CLS = Bar.constants.bottom = 'cp-toolbar-bottom'; var BOTTOM_LEFT_CLS = Bar.constants.bottomL = 'cp-toolbar-bottom-left'; var BOTTOM_MID_CLS = Bar.constants.bottomM = 'cp-toolbar-bottom-mid'; var BOTTOM_RIGHT_CLS = Bar.constants.bottomR = 'cp-toolbar-bottom-right'; var LEFTSIDE_CLS = Bar.constants.leftside = 'cp-toolbar-leftside'; var RIGHTSIDE_CLS = Bar.constants.rightside = 'cp-toolbar-rightside'; var FILE_CLS = Bar.constants.file = 'cp-toolbar-file'; var DRAWER_CLS = Bar.constants.drawer = 'cp-toolbar-drawer-content'; var HISTORY_CLS = Bar.constants.history = 'cp-toolbar-history'; var SNAPSHOTS_CLS = Bar.constants.history = 'cp-toolbar-snapshots'; // Userlist var USERLIST_CLS = Bar.constants.userlist = "cp-toolbar-users"; // Top parts var USER_CLS = Bar.constants.userAdmin = "cp-toolbar-user"; var SPINNER_CLS = Bar.constants.spinner = 'cp-toolbar-spinner'; var LIMIT_CLS = Bar.constants.limit = 'cp-toolbar-limit'; var TITLE_CLS = Bar.constants.title = "cp-toolbar-title"; var LINK_CLS = Bar.constants.link = "cp-toolbar-link"; var NOTIFICATIONS_CLS = Bar.constants.user = 'cp-toolbar-notifications'; var MAINTENANCE_CLS = Bar.constants.user = 'cp-toolbar-maintenance'; // User admin menu var USERADMIN_CLS = Bar.constants.user = 'cp-toolbar-user-dropdown'; var USERNAME_CLS = Bar.constants.username = 'cp-toolbar-user-name'; /*var READONLY_CLS = */Bar.constants.readonly = 'cp-toolbar-readonly'; var USERBUTTON_CLS = Bar.constants.changeUsername = "cp-toolbar-user-rename"; // Create the toolbar element var uid = function () { return 'cp-toolbar-uid-' + String(Math.random()).substring(2); }; var createRealtimeToolbar = function (config) { if (!config.$container) { return; } var $container = config.$container; var priv = config.metadataMgr.getPrivateData(); var isEmbed = Bar.isEmbed = priv.isEmbed || (priv.app === 'form' && priv.readOnly && !priv.form_auditorHash); if (isEmbed) { $container.hide(); } var $toolbar = $('<div>', { 'class': TOOLBAR_CLS, id: uid(), }); var $topContainer = $('<div>', {'class': TOP_CLS}); $('<span>', {'class': 'cp-toolbar-top-filler'}).appendTo($topContainer); var $userContainer = $('<span>', { 'class': USER_CLS }).appendTo($topContainer); $('<span>', {'class': LIMIT_CLS}).hide().appendTo($userContainer); $('<span>', {'class': MAINTENANCE_CLS + ' cp-dropdown-container'}).hide().appendTo($userContainer); $('<span>', {'class': NOTIFICATIONS_CLS + ' cp-dropdown-container'}).hide().appendTo($userContainer); $('<span>', {'class': USERADMIN_CLS + ' cp-dropdown-container'}).hide().appendTo($userContainer); $toolbar.append($topContainer); var $bottom = $(h('div.'+BOTTOM_CLS, [ h('div.'+BOTTOM_LEFT_CLS), h('div.'+BOTTOM_MID_CLS), h('div.'+BOTTOM_RIGHT_CLS) ])).appendTo($toolbar); $toolbar.append(h('div.'+HISTORY_CLS)); $toolbar.append(h('div.'+SNAPSHOTS_CLS)); var $file = $toolbar.find('.'+BOTTOM_LEFT_CLS); if (!config.hideDrawer) { var $drawer = $(h('button.' + FILE_CLS, [ h('i.fa.fa-file-o'), h('span.cp-button-name', Messages.toolbar_file) ])).appendTo($file).hide(); var $drawerContent = $('<div>', { 'class': DRAWER_CLS, 'tabindex': 1 }).hide(); UI.createDrawer($drawer, $drawerContent); } // The 'notitle' class removes the line added for the title with a small screen if (!config.title || typeof config.title !== "object") { $toolbar.addClass('cp-toolbar-notitle'); } $container.prepend($toolbar); $container.on('drop dragover', function (e) { e.preventDefault(); e.stopPropagation(); }); return $toolbar; }; // Userlist elements var getOtherUsers = function(config) { //var userList = config.userList.getUserlist(); var userData = config.metadataMgr.getMetadata().users; var i = 0; // duplicates counter var list = []; // Display only one time each user (if he is connected in multiple tabs) var uids = []; Object.keys(userData).forEach(function(user) { //if (user !== userNetfluxId) { var data = userData[user] || {}; var userId = data.uid; if (!userId) { return; } //data.netfluxId = user; if (uids.indexOf(userId) === -1) {// && (!myUid || userId !== myUid)) { uids.push(userId); list.push(data); } else { i++; } //} }); return { list: list, duplicates: i }; }; var editingUserName = { state: false }; var setDisplayName = function (newName) { Common.setDisplayName(newName, function (err) { if (err) { console.log("Couldn't set username"); console.error(err); return; } }); }; var showColors = false; var updateUserList = function (toolbar, config, forceOffline) { if (!config.displayed || config.displayed.indexOf('userlist') === -1) { return; } if (toolbar.isAlone) { return; } // Make sure the elements are displayed var $userButtons = toolbar.userlist; var $userlistContent = toolbar.userlistContent; var metadataMgr = config.metadataMgr; var online = !forceOffline && metadataMgr.isConnected(); var userData = metadataMgr.getMetadata().users; var viewers = metadataMgr.getViewers(); var priv = metadataMgr.getPrivateData(); var origin = priv.origin; var friends = priv.friends; var user = metadataMgr.getUserData(); // If we are using old pads (readonly unavailable), only editing users are in userList. // With new pads, we also have readonly users in userList, so we have to intersect with // the userData to have only the editing users. We can't use userData directly since it // may contain data about users that have already left the channel. //userList = config.readOnly === -1 ? userList : arrayIntersect(userList, Object.keys(userData)); // Names of editing users var others = getOtherUsers(config); var editUsersNames = others.list; var duplicates = others.duplicates; // Number of duplicates editUsersNames.sort(function (a, b) { var na = a.name || Messages.anonymous; var nb = b.name || Messages.anonymous; return na.toLowerCase() > nb.toLowerCase(); }); var numberOfEditUsers = Object.keys(userData).length - duplicates; var numberOfViewUsers = viewers; // If the user was changing his username, do not reste the input, store the current value // and cursor if (editingUserName.state) { var $input = $userlistContent.find('.cp-toolbar-userlist-name-input'); editingUserName.value = $input.val(); editingUserName.select = [$input[0].selectionStart, $input[0].selectionEnd]; } // Update the userlist var $editUsers = $userlistContent.find('.' + USERLIST_CLS).html(''); var $editUsersList = $('<div>', {'class': 'cp-toolbar-userlist-others'}) .appendTo($editUsers); var degradedLimit = Config.degradedLimit || 8; if (toolbar.isDeleted) { $('<em>').text(Messages.deletedFromServer).appendTo($editUsersList); numberOfEditUsers = '?'; numberOfViewUsers = '?'; } else if (!online) { $('<em>').text(Messages.userlist_offline).appendTo($editUsersList); numberOfEditUsers = '?'; numberOfViewUsers = '?'; } else if (metadataMgr.isDegraded() === true) { numberOfEditUsers = Math.max(metadataMgr.getChannelMembers().length - 1, 0); numberOfViewUsers = ''; $('<em>').text(Messages._getKey('toolbar_degraded', [degradedLimit])).appendTo($editUsersList); } // Update the buttons var fa_editusers = '<span class="fa fa-users"></span>'; var fa_viewusers = numberOfViewUsers === '' ? '' : '<span class="fa fa-eye"></span>'; var $spansmall = $('<span>').html(fa_editusers + ' ' + numberOfEditUsers + ' ' + fa_viewusers + ' ' + numberOfViewUsers); $userButtons.find('.cp-dropdown-button-title').html('').append($spansmall); if (!online || toolbar.isDeleted) { return; } if (metadataMgr.isDegraded() === true) { return; } // Display the userlist // Editors var pendingFriends = Common.getPendingFriends(); // Friend requests sent var friendRequests = Common.getFriendRequests(); // Friend requests received editUsersNames.forEach(function (data) { var name = data.name || Messages.anonymous; var safeName = Util.fixHTML(name); var $span = $('<span>', {'class': 'cp-avatar'}); if (data.color && showColors) { $span.css('border-color', data.color); } var $rightCol = $('<span>', {'class': 'cp-toolbar-userlist-rightcol'}); var $nameSpan = $('<span>', {'class': 'cp-toolbar-userlist-name'}).appendTo($rightCol); var $nameValue = $('<span>', { 'class': 'cp-toolbar-userlist-name-value' }).text(name).appendTo($nameSpan); var isMe = data.uid === user.uid; if (isMe && !priv.readOnly) { if (!Config.disableProfile) { var $button = $('<button>', { 'class': 'fa fa-pencil cp-toolbar-userlist-button', title: Messages.user_rename }).appendTo($nameSpan); $button.hover(function (e) { e.preventDefault(); e.stopPropagation(); }); $button.mouseenter(function (e) { e.preventDefault(); e.stopPropagation(); window.setTimeout(function () { $button.parents().mouseleave(); }); }); var $nameInput = $('<input>', { 'class': 'cp-toolbar-userlist-name-input' }).val(name).appendTo($rightCol); $button.click(function (e) { e.stopPropagation(); $nameSpan.hide(); $nameInput.show().focus().select(); editingUserName.state = true; editingUserName.oldName = $nameInput.val(); }); $nameInput.click(function (e) { e.stopPropagation(); }); $nameInput.on('keydown', function (e) { if (e.which === 13 || e.which === 27) { $nameInput.hide(); $nameSpan.show(); $button.show(); editingUserName.state = false; } if (e.which === 13) { var newName = $nameInput.val(); // TODO clean $nameValue.text(newName); setDisplayName(newName); return; } if (e.which === 27) { $nameValue.text(editingUserName.oldName); return; } }); if (editingUserName.state) { $button.click(); $nameInput.val(editingUserName.value); $nameInput[0].setSelectionRange(editingUserName.select[0], editingUserName.select[1]); setTimeout(function () { $nameInput.focus(); }); } } } else if (Common.isLoggedIn() && data.curvePublic && !friends[data.curvePublic] && !priv.readOnly) { if (pendingFriends[data.curvePublic]) { $('<button>', { 'class': 'fa fa-hourglass-half cp-toolbar-userlist-button', 'title': Messages.profile_friendRequestSent }).appendTo($nameSpan); } else if (friendRequests[data.curvePublic]) { $('<button>', { 'class': 'fa fa-bell cp-toolbar-userlist-button', 'data-cptippy-html': true, 'title': Messages._getKey('friendRequest_received', [safeName]), }).appendTo($nameSpan).click(function (e) { e.stopPropagation(); UIElements.displayFriendRequestModal(Common, friendRequests[data.curvePublic]); }); } else { $('<button>', { 'class': 'fa fa-user-plus cp-toolbar-userlist-button', 'data-cptippy-html': true, 'title': Messages._getKey('userlist_addAsFriendTitle', [ safeName, ]) }).appendTo($nameSpan).click(function (e) { e.stopPropagation(); Common.sendFriendRequest(data, function (err, obj) { if (err || (obj && obj.error)) { UI.warn(Messages.error); return void console.error(err || obj.error); } }); }); } } else if (Common.isLoggedIn() && data.curvePublic && friends[data.curvePublic]) { $('<button>', { 'class': 'fa fa-comments-o cp-toolbar-userlist-button', 'title': Messages.contact_chat }).appendTo($nameSpan).click(function (e) { e.stopPropagation(); Common.openURL('/contacts/'); }); } if (data.profile) { // Messages.contacts_info3 "Double-click their icon to view their profile", $span.addClass('cp-userlist-clickable'); $span.attr('title', Messages._getKey('userlist_visitProfile', [name])); $span.click(function () { Common.openURL(origin+'/profile/#' + data.profile); }); } Common.displayAvatar($span, data.avatar, name, function () { $span.append($rightCol); }, data.uid); $span.data('uid', data.uid); $editUsersList.append($span); }); // Viewers if (numberOfViewUsers > 0) { var viewText = '<div class="cp-toolbar-userlist-viewer">'; var viewerText = numberOfViewUsers !== 1 ? Messages.viewers : Messages.viewer; viewText += numberOfViewUsers + ' ' + viewerText + '</div>'; $editUsers.append(viewText); } }; var initUserList = function (toolbar, config) { if (config.metadataMgr) { var metadataMgr = config.metadataMgr; metadataMgr.onChange(function () { if (metadataMgr.isConnected()) {toolbar.connected = true;} if (!toolbar.connected) { return; } updateUserList(toolbar, config); }); setTimeout(function () { updateUserList(toolbar, config, true); }); } }; // Create sub-elements var createUserList = function (toolbar, config) { if (!config.metadataMgr) { throw new Error("You must provide a `metadataMgr` to display the userlist"); } var $content = $('<div>', {'class': 'cp-toolbar-userlist-drawer'}); $content.on('drop dragover', function (e) { e.preventDefault(); e.stopPropagation(); }); //var $closeIcon = $('<span>', {"class": "fa fa-times cp-toolbar-userlist-drawer-close"}).appendTo($content); $('<h2>').text(Messages.users).appendTo($content); $('<p>', {'class': USERLIST_CLS}).appendTo($content); toolbar.userlistContent = $content; var $container = $('<span>', {id: 'cp-toolbar-userlist-drawer-open', title: Messages.userListButton}); var $button = $('<button>').appendTo($container); $('<span>',{'class': 'cp-dropdown-button-title'}).appendTo($button); toolbar.$bottomR.prepend($container); if (config.$contentContainer) { config.$contentContainer.prepend($content); } var hide = function () { $content.hide(); $button.removeClass('cp-toolbar-button-active'); }; var show = function () { if (Bar.isEmbed) { $content.hide(); return; } $content.show(); $button.addClass('cp-toolbar-button-active'); }; /* $closeIcon.click(function () { Common.setAttribute(['toolbar', 'userlist-drawer'], false); hide(); }); */ $button.click(function () { var visible = $content.is(':visible'); if (visible) { hide(); } else { show(); } visible = !visible; Common.setAttribute(['toolbar', 'userlist-drawer'], visible); Feedback.send(visible?'USERLIST_SHOW': 'USERLIST_HIDE'); }); show(); Common.getAttribute(['toolbar', 'userlist-drawer'], function (err, val) { if (val === false || window.innerWidth < 800) { return void hide(); } show(); }); initUserList(toolbar, config); return $container; }; createCollapse = function (toolbar) { var up = h('i.fa.fa-chevron-up', {title: Messages.toolbar_collapse}); var down = h('i.fa.fa-chevron-down', {title: Messages.toolbar_expand}); var $button = $(h('button.cp-toolbar-collapse',[ up, down ])); var $up = $(up); var $down = $(down); toolbar.$bottomR.prepend($button); $down.hide(); $button.click(function () { toolbar.$top.toggleClass('toolbar-hidden'); var hidden = toolbar.$top.hasClass('toolbar-hidden'); $button.toggleClass('cp-toolbar-button-active'); if (hidden) { $up.hide(); $down.show(); } else { $up.show(); $down.hide(); } }); }; var initChat = function (toolbar) { var $container = $('<div>', { id: 'cp-app-contacts-container', 'class': 'cp-app-contacts-inapp' }).prependTo(toolbar.chatContent); MessengerUI.create($container, Common, toolbar); }; var createChat = function (toolbar, config) { if (!config.metadataMgr) { throw new Error("You must provide a `metadataMgr` to display the chat"); } if (Config.availablePadTypes.indexOf('contacts') === -1) { return; } var $content = $('<div>', {'class': 'cp-toolbar-chat-drawer'}); $content.on('drop dragover', function (e) { e.preventDefault(); e.stopPropagation(); }); //var $closeIcon = $('<span>', {"class": "fa fa-times cp-toolbar-chat-drawer-close"}).appendTo($content); //$('<h2>').text(Messages.users).appendTo($content); //$('<p>', {'class': USERLIST_CLS}).appendTo($content); toolbar.chatContent = $content; var $container = $('<span>', {id: 'cp-toolbar-chat-drawer-open'}); var $button = $(h('button', [ h('i.fa.fa-comments'), h('span.cp-button-name', Messages.chatButton) ])).appendTo($container); toolbar.$bottomR.prepend($container); if (config.$contentContainer) { config.$contentContainer.prepend($content); } var hide = function (closed) { if (!closed) { // It means it's the initial state so we're going to make the icon blink $button.addClass('cp-toolbar-notification'); } $content.hide(); $button.removeClass('cp-toolbar-button-active'); config.$contentContainer.removeClass('cp-chat-visible'); }; var show = function () { if (Bar.isEmbed) { $content.hide(); return; } $content.show(); // scroll down chat var $messagebox = $content.find('.cp-app-contacts-messages'); if ($messagebox.length) { $messagebox.scrollTop($messagebox[0].scrollHeight); } $button.addClass('cp-toolbar-button-active'); config.$contentContainer.addClass('cp-chat-visible'); $button.removeClass('cp-toolbar-notification'); }; /* $closeIcon.click(function () { Common.setAttribute(['toolbar', 'chat-drawer'], false); hide(true); }); */ $button.click(function () { var visible = $content.is(':visible'); if (visible) { hide(true); } else { show(); } visible = !visible; Common.setAttribute(['toolbar', 'chat-drawer'], visible); }); show(); Common.getAttribute(['toolbar', 'chat-drawer'], function (err, val) { if (!val || Util.isSmallScreen()) { return void hide(val === false); } show(); }); initChat(toolbar); return $container; }; var createShare = function (toolbar, config) { if (!config.metadataMgr) { throw new Error("You must provide a `metadataMgr` to display the share button"); } var $shareBlock = $(h('button.cp-toolar-share-button', [ h('i.fa.fa-shhare-alt'), h('span.cp-button-name', Messages.shareButton) ])); Common.getSframeChannel().event('EV_SHARE_OPEN', { hidden: true }); $shareBlock.click(function () { if (toolbar.isDeleted) { return void UI.warn(Messages.deletedFromServer); } var privateData = config.metadataMgr.getPrivateData(); var title = (config.title && config.title.getTitle && config.title.getTitle()) || (config.title && config.title.defaultName) || ""; Common.getSframeChannel().event('EV_SHARE_OPEN', { title: title, auditorHash: privateData.form_auditorHash }); }); toolbar.$bottomM.append($shareBlock); toolbar.share = $shareBlock; return "Loading share button"; }; var createAccess = function (toolbar, config) { if (!config.metadataMgr) { throw new Error("You must provide a `metadataMgr` to display the access button"); } var $accessBlock = $(h('button.cp-toolar-access-button', [ h('i.fa.fa-unlock-alt'), h('span.cp-button-name', Messages.accessButton) ])); $accessBlock.click(function () { if (toolbar.isDeleted) { return void UI.warn(Messages.deletedFromServer); } var title = (config.title && config.title.getTitle && config.title.getTitle()) || (config.title && config.title.defaultName) || ""; Common.getSframeChannel().event('EV_ACCESS_OPEN', { title: title }); }); toolbar.$bottomM.append($accessBlock); toolbar.access = $accessBlock; return "Loading access button"; }; var createFileShare = function (toolbar, config) { if (!config.metadataMgr) { throw new Error("You must provide a `metadataMgr` to display the userlist"); } var $shareBlock = $(h('button.cp-toolar-share-button', [ h('i.fa.fa-shhare-alt'), h('span.cp-button-name', Messages.shareButton) ])); Common.getSframeChannel().event('EV_SHARE_OPEN', { hidden: true, file: true }); $shareBlock.click(function () { var title = (config.title && config.title.getTitle && config.title.getTitle()) || ""; Common.getSframeChannel().event('EV_SHARE_OPEN', { title: title, file: true }); }); toolbar.$bottomM.append($shareBlock); return $shareBlock; }; /* var createRequest = function (toolbar, config) { if (!config.metadataMgr) { throw new Error("You must provide a `metadataMgr` to display the request access button"); } // We can only requets more access if we're in read-only mode if (config.readOnly !== 1) { return; } var $requestBlock = $('<button>', { 'class': 'fa fa-lock cp-toolbar-share-button', title: Messages.requestEdit_button }).hide(); // If we have access to the owner's mailbox, display the button and enable it // false => check if we can contact the owner // true ==> send the request Common.getSframeChannel().query('Q_REQUEST_ACCESS', {send:false}, function (err, obj) { if (obj && obj.state) { var locked = false; $requestBlock.show().click(function () { if (locked) { return; } locked = true; Common.getSframeChannel().query('Q_REQUEST_ACCESS', {send:true}, function (err, obj) { if (obj && obj.state) { UI.log(Messages.requestEdit_sent); $requestBlock.hide(); } else { locked = false; } }); }); } }); toolbar.$leftside.append($requestBlock); toolbar.request = $requestBlock; return $requestBlock; }; */ var createTitle = function (toolbar, config) { var $titleContainer = $('<span>', { 'class': TITLE_CLS }).appendTo(toolbar.$top); var $hoverable = $('<span>', {'class': 'cp-toolbar-title-hoverable'}).appendTo($titleContainer); if (typeof config.title !== "object") { console.error("config.title", config); throw new Error("config.title is not an object"); } var updateTitle = config.title.updateTitle; var placeholder = config.title.defaultName; var suggestName = config.title.suggestName; // Buttons var $text = $('<span>', { 'class': 'cp-toolbar-title-value' }).appendTo($hoverable); var $pencilIcon = $('<span>', { 'class': 'cp-toolbar-title-edit', 'title': Messages.clickToEdit }); var $saveIcon = $('<span>', { 'class': 'cp-toolbar-title-save', 'title': Messages.saveTitle }).hide(); if (config.readOnly === 1) { $hoverable.append($('<span>', {'class': 'cp-toolbar-title-readonly'}) .text('('+Messages.readonly+')')); return $titleContainer; } $hoverable.append($('<span>', {'class': 'cp-toolbar-title-readonly cp-toolbar-title-unsync'}) .text('('+Messages.readonly+')')); var $input = $('<input>', { type: 'text', placeholder: placeholder }).appendTo($hoverable).hide(); if (config.readOnly !== 1) { $text.attr("title", Messages.clickToEdit); $text.addClass("cp-toolbar-title-editable"); var $icon = $('<span>', { 'class': 'fa fa-pencil cp-toolbar-title-icon-readonly', style: 'font-family: FontAwesome;' }); $pencilIcon.append($icon).appendTo($hoverable); var $icon2 = $('<span>', { 'class': 'fa fa-check cp-toolbar-title-icon-readonly', style: 'font-family: FontAwesome;' }); $saveIcon.append($icon2).appendTo($hoverable); } // Events $input.on('mousedown', function (e) { if (!$input.is(":focus")) { $input.focus(); } e.stopPropagation(); return true; }); var save = function () { if (toolbar.history) { return; } var name = $input.val().trim(); if (name === "") { name = $input.attr('placeholder'); } updateTitle(name, function (err/*, newtitle*/) { if (err) { return console.error(err); } //$text.text(newtitle); $input.hide(); $text.show(); $pencilIcon.show(); $saveIcon.hide(); }); }; $input.on('keyup', function (e) { if (e.which === 13 && toolbar.connected === true) { save(); } else if (e.which === 27) { $input.hide(); $text.show(); $pencilIcon.show(); $saveIcon.hide(); //$pencilIcon.css('display', ''); } else if (e.which === 32) { e.stopPropagation(); } }); $saveIcon.click(save); var displayInput = function () { if (toolbar.connected === false) { return; } if (toolbar.history) { return; } $input.width(Math.max(($text.width() + 10), 300)+'px'); $text.hide(); //$pencilIcon.css('display', 'none'); var inputVal = suggestName() || ""; $input.val(inputVal); $input.show(); $input.focus(); $pencilIcon.hide(); $saveIcon.show(); }; $text.on('click', displayInput); $pencilIcon.on('click', displayInput); return $titleContainer; }; var createUnpinnedWarning0 = function (toolbar, config) { if (true) { return; } // stub this call since it won't make it into the next release if (Common.isLoggedIn()) { return; } var pd = config.metadataMgr.getPrivateData(); var o = pd.origin; var cid = pd.channel; Common.sendAnonRpcMsg('IS_CHANNEL_PINNED', cid, function (x) { if (x.error || !Array.isArray(x.response)) { return void console.log(x); } if (x.response[0] === true) { $('.cp-pad-not-pinned').remove(); return; } if (typeof(ApiConfig.inactiveTime) !== 'number') { $('.cp-pad-not-pinned').remove(); return; } if ($('.cp-pad-not-pinned').length) { return; } var pnpTitle = Messages._getKey('padNotPinnedVariable', ['','','','', ApiConfig.inactiveTime]); var pnpMsg = Messages._getKey('padNotPinnedVariable', [ '<a href="' + o + '/login" class="cp-pnp-login" target="blank" title>', '</a>', '<a href="' + o + '/register" class="cp-pnp-register" target="blank" title>', '</a>', ApiConfig.inactiveTime ]); var $msg = $('<span>', { 'class': 'cp-pad-not-pinned' }).append([ $('<span>', {'class': 'fa fa-exclamation-triangle', 'title': pnpTitle}), $('<span>', {'class': 'cp-pnp-msg'}).append(pnpMsg) ]); $msg.find('a.cp-pnp-login').click(function (ev) { ev.preventDefault(); Common.setLoginRedirect('login'); }); $msg.find('a.cp-pnp-register').click(function (ev) { ev.preventDefault(); Common.setLoginRedirect('register'); }); $('.cp-toolbar-top').append($msg); //UI.addTooltips(); }); }; var createUnpinnedWarning = function (toolbar, config) { config.metadataMgr.onChange(function () { createUnpinnedWarning0(toolbar, config); }); createUnpinnedWarning0(toolbar, config); }; var createPageTitle = function (toolbar, config) { if (!config.pageTitle) { return; } var $titleContainer = $('<span>', { 'class': TITLE_CLS }).appendTo(toolbar.$top); toolbar.$top.find('.filler').hide(); var $hoverable = $('<span>', {'class': 'cp-toolbar-title-hoverable'}).appendTo($titleContainer); // Buttons $('<span>', { 'class': 'cp-toolbar-title-value cp-toolbar-title-value-page' }).appendTo($hoverable).text(config.pageTitle); }; var createLinkToMain = function (toolbar, config) { var $linkContainer = $('<span>', { 'class': LINK_CLS }).appendTo(toolbar.$top); // We need to override the "a" tag action here because it is inside the iframe! var inDrive = /^\/drive/; var privateData = config.metadataMgr.getPrivateData(); var origin = privateData.origin; var pathname = privateData.pathname; var isAnonSF = privateData.newSharedFolder && !privateData.accountName; var toMain = inDrive.test(pathname) && !isAnonSF; var href = toMain ? origin+'/index.html' : origin+'/drive/'; var buttonTitle = toMain ? Messages.header_homeTitle : Messages.header_logoTitle; var $aTag = $('<a>', { href: href, title: buttonTitle, 'class': "cp-toolbar-link-logo" }).append(UIElements.getSvgLogo()); var onClick = function (e) { e.preventDefault(); if (e.ctrlKey) { Common.openURL(href); return; } Common.gotoURL(href); }; var onContext = function (e) { e.stopPropagation(); }; $aTag.click(onClick).contextmenu(onContext); $linkContainer.append($aTag); return $linkContainer; }; var typing = -1; var kickSpinner = function (toolbar, config/*, local*/) { if (!toolbar.spinner) { return; } if (toolbar.isErrorState) { return; } var $spin = toolbar.spinner; if (typing === -1) { typing = 1; $spin.text(Messages.typing); $spin.interval = window.setInterval(function () { if (toolbar.isErrorState) { return; } var dots = Array(typing+1).join('.'); $spin.text(Messages.typing + dots); typing++; if (typing > 3) { typing = 0; } }, 500); } var onSynced = function () { if ($spin.timeout) { clearTimeout($spin.timeout); } $spin.timeout = setTimeout(function () { if (toolbar.isErrorState) { return; } window.clearInterval($spin.interval); typing = -1; $spin.text(Messages.saved); }, /*local ? 0 :*/ SPINNER_DISAPPEAR_TIME); }; if (config.spinner) { var h = function () { onSynced(); try { config.spinner.onSync.unreg(h); } catch (e) { console.error(e); } }; config.spinner.onSync.reg(h); return; } config.sfCommon.whenRealtimeSyncs(onSynced); }; var ks = function (toolbar, config, local) { return function () { if (toolbar.connected) { kickSpinner(toolbar, config, local); } }; }; var createSpinner = function (toolbar, config) { if (config.readOnly === 1) { return; } var $spin = $('<span>', {'class': SPINNER_CLS}).appendTo(toolbar.title); $spin.text(Messages.synchronizing); if (config.spinner) { config.spinner.onPatch.reg(ks(toolbar, config)); typing = 0; setTimeout(function () { kickSpinner(toolbar, config); }); return $spin; } if (config.realtime) { config.realtime.onPatch(ks(toolbar, config)); config.realtime.onMessage(ks(toolbar, config, true)); } // without this, users in read-only mode say 'synchronizing' until they // receive a patch. typing = 0; config.sfCommon.whenRealtimeSyncs(function () { kickSpinner(toolbar, config); }); return $spin; }; var createLimit = function (toolbar, config) { var $limitIcon = $('<span>', {'class': 'fa fa-exclamation-triangle'}); var $limit = toolbar.$userAdmin.find('.'+LIMIT_CLS).attr({ 'title': Messages.pinLimitReached }).append($limitIcon).hide(); var priv = config.metadataMgr.getPrivateData(); var origin = priv.origin; var l = document.createElement("a"); l.href = origin; var todo = function (e, overLimit) { if (e) { return void console.error("Unable to get the pinned usage", e); } if (overLimit) { $limit.show().click(function () { if (ApiConfig.allowSubscriptions && Config.upgradeURL) { var key = 'pinLimitReachedAlert'; // Msg.pinLimitReachedAlert var msg = Pages.setHTML(h('span'), Messages.pinLimitReachedAlert); $(msg).find('a').attr({ target: '_blank', href: Config.upgradeURL, }); UI.alert(msg); } else { UI.alert(Messages.pinLimitReachedAlertNoAccounts); } }); } }; Common.isOverPinLimit(todo); return $limit; }; var createNewPad = function (toolbar, config) { var $button = Common.createButton('newpad', true); toolbar.$drawer.append($button); return $button; }; var createUserAdmin = function (toolbar, config) { if (!config.metadataMgr) { throw new Error("You must provide a `metadataMgr` to display the user menu"); } var metadataMgr = config.metadataMgr; var $userAdmin = toolbar.$userAdmin.find('.'+USERADMIN_CLS).show(); var userMenuCfg = { $initBlock: $userAdmin, }; if (!config.hideDisplayName) { $.extend(true, userMenuCfg, { displayNameCls: USERNAME_CLS, changeNameButtonCls: USERBUTTON_CLS, }); } if (config.readOnly !== 1) { userMenuCfg.displayName = 1; userMenuCfg.displayChangeName = 1; } Common.createUserAdminMenu(userMenuCfg); $userAdmin.find('> button').attr({ title: Messages.userAccountButton, alt: Messages.userAccountButton, }); return $userAdmin; }; var createMaintenance = function (toolbar, config) { var $notif = toolbar.$top.find('.'+MAINTENANCE_CLS); var button = h('button.cp-maintenance-wrench.fa.fa-wrench'); $notif.append(button); var m = Broadcast.maintenance; $(button).click(function () { if (!m || !m.start || !m.end) { return; } UI.alert(Messages._getKey('broadcast_maintenance', [ new Date(m.start).toLocaleString(), new Date(m.end).toLocaleString(), ]), null, true); }); var to; Common.makeUniversal('broadcast', { onEvent: function (obj) { var cmd = obj.ev; if (cmd !== "MAINTENANCE") { return; } var data = obj.data; if (!data) { return void $notif.hide(); } if ((+new Date()) > data.end) { return void $notif.hide(); } m = data; clearTimeout(to); to = setTimeout(function () { m = undefined; $notif.hide(); }, m.end-(+new Date())); $notif.css('display', ''); } }); if (m && m.start && m.end) { $notif.css('display', ''); to = setTimeout(function () { m = undefined; $notif.hide(); }, m.end-(+new Date())); } else { $notif.hide(); } }; var createNotifications = function (toolbar, config) { var $notif = toolbar.$top.find('.'+NOTIFICATIONS_CLS).show(); var openNotifsApp = h('div.cp-notifications-gotoapp', h('p', Messages.openNotificationsApp || "Open notifications App")); $(openNotifsApp).click(function () { Common.openURL("/notifications/"); }); var div = h('div.cp-notifications-container', [ h('div.cp-notifications-empty', Messages.notifications_empty) ]); var pads_options = [div]; var metadataMgr = config.metadataMgr; var privateData = metadataMgr.getPrivateData(); if (!privateData.notifications) { var allowNotif = h('div.cp-notifications-gotoapp', h('p', Messages.allowNotifications)); pads_options.unshift(h("hr")); pads_options.unshift(allowNotif); var $allow = $(allowNotif).click(function () { Common.getSframeChannel().event('Q_ASK_NOTIFICATION', null, function (e, allow) { if (!allow) { return; } $(allowNotif).remove(); }); }); var onChange = function () { var privateData = metadataMgr.getPrivateData(); if (!privateData.notifications) { return; } $allow.remove(); metadataMgr.off('change', onChange); }; metadataMgr.onChange(onChange); } if (Common.isLoggedIn()) { pads_options.unshift(h("hr")); pads_options.unshift(openNotifsApp); } var dropdownConfig = { text: '', // Button initial text options: pads_options, // Entries displayed in the menu container: $notif, left: true, common: Common }; var $newPadBlock = UIElements.createDropdown(dropdownConfig); var $button = $newPadBlock.find('button'); $button.attr('title', Messages.notificationsPage); $button.addClass('fa fa-bell-o cp-notifications-bell'); var $n = $button.find('.cp-dropdown-button-title').hide(); var $empty = $(div).find('.cp-notifications-empty'); var refresh = function () { updateUserList(toolbar, config); var n = $(div).find('.cp-notification').length; $button.removeClass('fa-bell-o').removeClass('fa-bell'); $n.removeClass('cp-notifications-small'); if (n === 0) { $empty.show(); $n.hide(); return void $button.addClass('fa-bell-o'); } if (n > 99) { n = '99+'; $n.addClass('cp-notifications-small'); } $empty.hide(); $n.text(n).show(); $button.addClass('fa-bell'); }; Common.mailbox.subscribe(['notifications', 'team', 'broadcast', 'reminders'], { onMessage: function (data, el) { if (el) { $(div).prepend(el); } refresh(); }, onViewed: function () { refresh(); } }); return $newPadBlock; }; // Events var initClickEvents = function (toolbar) { var removeDropdowns = function () { window.setTimeout(function () { $('body').find('.cp-dropdown-content').hide(); }); }; var cancelEditTitle = function (e) { // Now we want to apply the title even if we click somewhere else if ($(e.target).parents('.' + TITLE_CLS).length || !toolbar.title) { return; } var $title = toolbar.title; if (!$title.find('input').is(':visible')) { return; } // Press enter var ev = $.Event("keyup"); ev.which = 13; $title.find('input').trigger(ev); }; // Click in the main window var w = window; $(w).on('click', removeDropdowns); $(w).on('click', cancelEditTitle); // Click in iframes try { if (w.$ && w.$('iframe').length) { w.$('iframe').each(function (i, el) { $(el.contentWindow).on('click', removeDropdowns); $(el.contentWindow).on('click', cancelEditTitle); }); } } catch (e) { // empty try catch in case this iframe is problematic } }; var getFancyGuestName = function (name, uid) { name = UI.getDisplayName(name); if (name === Messages.anonymous && uid) { var animal = MT.getPseudorandomAnimal(uid); if (animal) { name = animal + ' ' + name; } } return name; }; // Notifications var initNotifications = function (toolbar, config) { // Display notifications when users are joining/leaving the session var oldUserData; if (!config.metadataMgr) { return; } var metadataMgr = config.metadataMgr; var notify = function(type, name, oldname, uid) { if (toolbar.isAlone) { return; } // type : 1 (+1 user), 0 (rename existing user), -1 (-1 user) if (typeof name === "undefined") { return; } if (Config.disableUserlistNotifications) { return; } name = getFancyGuestName(name, uid); oldname = getFancyGuestName(oldname, uid); switch(type) { case 1: UI.log(Messages._getKey("notifyJoined", [name])); break; case 0: oldname = (!oldname) ? Messages.anonymous : oldname; UI.log(Messages._getKey("notifyRenamed", [oldname, name])); break; case -1: UI.log(Messages._getKey("notifyLeft", [name])); break; default: console.log("Invalid type of notification"); break; } }; var userPresent = function (id, user, data) { if (!(user && user.uid)) { console.log('no uid'); return 0; } if (!data) { console.log('no data'); return 0; } var count = 0; Object.keys(data).forEach(function (k) { if (data[k] && data[k].uid === user.uid) { count++; } }); return count; }; var joined = false; metadataMgr.onChange(function () { var newdata = metadataMgr.getMetadata().users; var netfluxIds = Object.keys(newdata); var userNetfluxId = metadataMgr.getNetfluxId(); // Notify for disconnected users if (typeof oldUserData !== "undefined") { for (var u in oldUserData) { // if a user's uid is still present after having left, don't notify if (netfluxIds.indexOf(u) === -1) { var temp = JSON.parse(JSON.stringify(oldUserData[u])); delete oldUserData[u]; if (temp && newdata[userNetfluxId] && temp.uid === newdata[userNetfluxId].uid) { return; } if (userPresent(u, temp, newdata || oldUserData) < 1) { notify(-1, temp.name, undefined, temp.uid); } } } } // Update the "oldUserData" object and notify for new users and names changed if (typeof newdata === "undefined") { return; } if (typeof oldUserData === "undefined") { oldUserData = JSON.parse(JSON.stringify(newdata)); return; } if (config.readOnly === 0 && !oldUserData[userNetfluxId]) { oldUserData = JSON.parse(JSON.stringify(newdata)); return; } for (var k in newdata) { if (joined && k !== userNetfluxId && netfluxIds.indexOf(k) !== -1) { if (typeof oldUserData[k] === "undefined") { // if the same uid is already present in the userdata, don't notify if (!userPresent(k, newdata[k], oldUserData)) { notify(1, newdata[k].name, undefined, newdata[k].uid); } } else if (oldUserData[k].name !== newdata[k].name) { notify(0, newdata[k].name, oldUserData[k].name, newdata[k].uid); } } } joined = true; oldUserData = JSON.parse(JSON.stringify(newdata)); }); }; // Main Bar.create = function (cfg) { var config = cfg || {}; Common = config.sfCommon; config.readOnly = (typeof config.readOnly !== "undefined") ? (config.readOnly ? 1 : 0) : -1; config.displayed = config.displayed || []; var toolbar = {}; toolbar.connected = false; toolbar.firstConnection = true; var $toolbar = toolbar.$toolbar = createRealtimeToolbar(config); toolbar.$bottom = $toolbar.find('.'+Bar.constants.bottom); toolbar.$bottomL = $toolbar.find('.'+Bar.constants.bottomL); toolbar.$bottomM = $toolbar.find('.'+Bar.constants.bottomM); toolbar.$bottomR = $toolbar.find('.'+Bar.constants.bottomR); toolbar.$leftside = $toolbar.find('.'+Bar.constants.leftside); toolbar.$rightside = $toolbar.find('.'+Bar.constants.rightside); toolbar.$file = $toolbar.find('.'+Bar.constants.file); toolbar.$drawer = $toolbar.find('.'+Bar.constants.drawer); toolbar.$top = $toolbar.find('.'+Bar.constants.top); toolbar.$history = $toolbar.find('.'+Bar.constants.history); toolbar.$userAdmin = $toolbar.find('.'+Bar.constants.userAdmin); // Create the subelements var tb = {}; tb['userlist'] = createUserList; tb['collapse'] = createCollapse; tb['chat'] = createChat; tb['share'] = createShare; tb['access'] = createAccess; tb['fileshare'] = createFileShare; tb['title'] = createTitle; tb['pageTitle'] = createPageTitle; //tb['request'] = createRequest; tb['lag'] = $.noop; tb['spinner'] = createSpinner; tb['state'] = $.noop; tb['limit'] = createLimit; // TODO tb['upgrade'] = $.noop; tb['newpad'] = createNewPad; tb['useradmin'] = createUserAdmin; tb['unpinnedWarning'] = createUnpinnedWarning; tb['notifications'] = createNotifications; tb['maintenance'] = createMaintenance; tb['pad'] = function () { toolbar.$file.show(); addElement([ 'chat', 'collapse', 'userlist', 'title', 'useradmin', 'spinner', 'newpad', 'share', 'access', 'limit', 'unpinnedWarning', 'notifications' ], {}); }; var checkSize = function () { toolbar.$bottom.removeClass('cp-toolbar-small'); var w = $(window).width(); var size = toolbar.$bottomL.width() + toolbar.$bottomM.width() + toolbar.$bottomR.width(); if (size > w) { toolbar.$bottom.addClass('cp-toolbar-small'); } }; $(window).on('resize', checkSize); var addElement = toolbar.addElement = function (arr, additionalCfg, init) { if (typeof additionalCfg === "object") { $.extend(true, config, additionalCfg); } arr.forEach(function (el) { if (typeof el !== "string" || !el.trim()) { return; } if (typeof tb[el] === "function") { if (!init && config.displayed.indexOf(el) !== -1) { return; } // Already done if (!init) { config.displayed.push(el); } toolbar[el] = tb[el](toolbar, config); } }); checkSize(); }; addElement(config.displayed, {}, true); addElement(['maintenance'], {}, true); toolbar['linkToMain'] = createLinkToMain(toolbar, config); if (!config.realtime) { toolbar.connected = true; } initClickEvents(toolbar, config); initNotifications(toolbar, config); var failed = toolbar.failed = function (hideUserList) { toolbar.connected = false; if (toolbar.spinner) { toolbar.spinner.text(Messages.disconnected); } if (hideUserList) { updateUserList(toolbar, config, true); } //checkLag(toolbar, config); }; toolbar.initializing = function (/*userId*/) { if (toolbar.history) { return; } toolbar.connected = false; if (toolbar.spinner) { toolbar.spinner.text(Messages.initializing); } }; toolbar.reconnecting = function (/*userId*/) { if (toolbar.history) { return; } toolbar.connected = false; if (toolbar.spinner) { var state = -1; var interval = window.setInterval(function () { if (toolbar.connected) { clearInterval(interval); } var dots = Array(state+1).join('.'); toolbar.spinner.text(Messages.reconnecting + dots); if (++state > 3) { state = 0; } }, 500); toolbar.spinner.text(Messages.reconnecting); } }; toolbar.ready = function () { toolbar.connected = true; kickSpinner(toolbar, config); }; toolbar.errorState = function (state, error) { toolbar.isErrorState = state; if (state) { toolbar.connected = false; } if (toolbar.spinner) { if (!state) { return void kickSpinner(toolbar, config); } var txt = Messages._getKey('errorState', [error]); toolbar.spinner.text(txt); } }; // When the pad is moved to the trash (forget button) toolbar.forgotten = function (/*userId*/) { toolbar.connected = false; if (toolbar.spinner) { toolbar.spinner.text(Messages.forgotten); } }; // When the pad is deleted from the server toolbar.deleted = function (/*userId*/) { toolbar.isErrorState = true; toolbar.connected = false; toolbar.isDeleted = true; updateUserList(toolbar, config, true); toolbar.title.toggleClass('cp-toolbar-unsync', true); // "read only" next to the title if (toolbar.spinner) { toolbar.spinner.text(Messages.deletedFromServer); } }; // Show user colors in the userlist only if the app is compatible and if the user // wants to see the cursors toolbar.showColors = function () { if (!config.metadataMgr) { return; } var privateData = config.metadataMgr.getPrivateData(); var show = Util.find(privateData, ['settings', 'general', 'cursor', 'show']); if (show === false) { return; } showColors = true; }; // If we had to create a new chainpad instance, reset the one used in the toolbar toolbar.resetChainpad = function (chainpad) { if (config.readOnly === 1) { return; } if (config.realtime !== chainpad) { config.realtime = chainpad; config.realtime.onPatch(ks(toolbar, config)); config.realtime.onMessage(ks(toolbar, config, true)); } }; toolbar.setSnapshot = function (bool) { toolbar.history = bool; toolbar.title.toggleClass('cp-toolbar-unsync', bool); if (bool && toolbar.spinner) { toolbar.spinner.text(Messages.snaphot_title); } else { kickSpinner(toolbar, config); } }; toolbar.setHistory = function (bool) { toolbar.history = bool; toolbar.title.toggleClass('cp-toolbar-unsync', bool); if (bool && toolbar.spinner) { toolbar.spinner.text(Messages.historyText); } else { kickSpinner(toolbar, config); } }; toolbar.offline = function (bool) { toolbar.connected = !bool; // Can't edit title toolbar.history = bool; // Stop "Initializing" state toolbar.isErrorState = bool; // Stop kickSpinner toolbar.title.toggleClass('cp-toolbar-unsync', bool); // "read only" next to the title if (bool && toolbar.spinner) { toolbar.spinner.text(Messages.Offline); } else { kickSpinner(toolbar, config); } }; // disable notification, userlist and chat toolbar.alone = function () { toolbar.userlist.hide(); toolbar.chat.hide(); $('.cp-toolbar-userlist-drawer').remove(); $('.cp-toolbar-chat-drawer').remove(); toolbar.isAlone = true; }; // On log out, remove permanently the realtime elements of the toolbar Common.onLogout(function () { failed(); if (toolbar.useradmin) { toolbar.useradmin.hide(); } if (toolbar.userlist) { toolbar.userlist.hide(); } }); return toolbar; }; return Bar; });