(function ($) { var ConsoleRoom = this.ConsoleRoom = Room.extend({ type: 'chat', title: '', constructor: function () { if (!this.events) this.events = {}; if (!this.events['click .username']) this.events['click .username'] = 'clickUsername'; if (!this.events['submit form']) this.events['submit form'] = 'submit'; if (!this.events['keydown textarea']) this.events['keydown textarea'] = 'keyPress'; if (!this.events['focus textarea']) this.events['focus textarea'] = 'focusText'; if (!this.events['blur textarea']) this.events['blur textarea'] = 'blurText'; if (!this.events['click .spoiler']) this.events['click .spoiler'] = 'clickSpoiler'; if (!this.events['click .message-pm i']) this.events['click .message-pm i'] = 'openPM'; this.initializeTabComplete(); // create up/down history for this room this.chatHistory = new ChatHistory(); // this MUST set up this.$chatAdd Room.apply(this, arguments); app.user.on('change', this.updateUser, this); this.updateUser(); }, updateUser: function () { var name = app.user.get('name'); var userid = app.user.get('userid'); if (this.expired) { this.$chatAdd.html('This room is expired'); this.$chatbox = null; } else if (!name) { this.$chatAdd.html('Connecting...'); this.$chatbox = null; } else if (!app.user.get('named')) { this.$chatAdd.html('
'); this.$chatbox = null; } else { this.$chatAdd.html(''); this.$chatbox = this.$chatAdd.find('textarea'); this.$chatbox.autoResize({ animate: false, extraSpace: 0 }); if (this === app.curSideRoom || this === app.curRoom) { this.$chatbox.focus(); } } }, focus: function () { if (this.$chatbox) { this.$chatbox.focus(); } else { this.$('button[name=login]').focus(); } }, focusText: function () { if (this.$chatbox) { var rooms = app.roomList.concat(app.sideRoomList); var roomIndex = rooms.indexOf(this); var roomLeft = rooms[roomIndex - 1]; var roomRight = rooms[roomIndex + 1]; if (roomLeft || roomRight) { this.$chatbox.attr('placeholder', " " + (roomLeft ? "\u2190 " + roomLeft.title : '') + (app.arrowKeysUsed ? " | " : " (use arrow keys) ") + (roomRight ? roomRight.title + " \u2192" : '')); } else { this.$chatbox.attr('placeholder', ""); } } }, blurText: function () { if (this.$chatbox) { this.$chatbox.attr('placeholder', ""); } }, clickSpoiler: function (e) { $(e.currentTarget).toggleClass('spoiler-shown'); }, login: function () { app.addPopup(LoginPopup); }, submit: function (e) { e.preventDefault(); e.stopPropagation(); var text; if ((text = this.$chatbox.val())) { if (!$.trim(text)) { this.$chatbox.val(''); return; } this.tabComplete.reset(); this.chatHistory.push(text); text = this.parseCommand(text); if (text) { this.send(text); } this.$chatbox.val(''); } }, keyPress: function (e) { var cmdKey = (((e.cmdKey || e.metaKey) ? 1 : 0) + (e.ctrlKey ? 1 : 0) === 1) && !e.altKey && !e.shiftKey; var textbox = e.currentTarget; if (e.keyCode === 13 && !e.shiftKey) { // Enter key this.submit(e); } else if (e.keyCode === 73 && cmdKey) { // Ctrl + I key if (ConsoleRoom.toggleFormatChar(textbox, '_')) { e.preventDefault(); e.stopPropagation(); } } else if (e.keyCode === 66 && cmdKey) { // Ctrl + B key if (ConsoleRoom.toggleFormatChar(textbox, '*')) { e.preventDefault(); e.stopPropagation(); } } else if (e.keyCode === 33) { // Pg Up key this.$chatFrame.scrollTop(this.$chatFrame.scrollTop() - this.$chatFrame.height() + 60); } else if (e.keyCode === 34) { // Pg Dn key this.$chatFrame.scrollTop(this.$chatFrame.scrollTop() + this.$chatFrame.height() - 60); } else if (e.keyCode === 9 && !e.shiftKey && !e.ctrlKey) { // Tab key if (this.handleTabComplete(this.$chatbox)) { e.preventDefault(); e.stopPropagation(); } } else if (e.keyCode === 38 && !e.shiftKey && !e.altKey) { // Up key if (this.chatHistoryUp(this.$chatbox, e)) { e.preventDefault(); e.stopPropagation(); } } else if (e.keyCode === 40 && !e.shiftKey && !e.altKey) { // Down key if (this.chatHistoryDown(this.$chatbox, e)) { e.preventDefault(); e.stopPropagation(); } } }, clickUsername: function (e) { e.stopPropagation(); e.preventDefault(); var position; if (e.currentTarget.className === 'userbutton username') { position = 'right'; } var name = $(e.currentTarget).data('name') || $(e.currentTarget).text(); app.addPopup(UserPopup, {name: name, sourceEl: e.currentTarget, position: position}); }, openPM: function (e) { e.preventDefault(); e.stopPropagation(); app.focusRoom(''); app.rooms[''].focusPM($(e.currentTarget).data('name')); }, clear: function () { if (this.$chat) this.$chat.html(''); }, // support for buttons that can be sent by the server: joinRoom: function (room) { app.joinRoom(room); }, avatars: function () { app.addPopup(AvatarsPopup); }, openSounds: function () { app.addPopup(SoundsPopup, {type: 'semimodal'}); }, openOptions: function () { app.addPopup(OptionsPopup, {type: 'semimodal'}); }, // highlight getHighlight: function (message) { var highlights = Tools.prefs('highlights') || []; if (!app.highlightRegExp) { try { this.updateHighlightRegExp(highlights); } catch (e) { // If the expression above is not a regexp, we'll get here. // Don't throw an exception because that would prevent the chat // message from showing up, or, when the lobby is initialising, // it will prevent the initialisation from completing. return false; } } if (!Tools.prefs('noselfhighlight') && app.user.nameRegExp) { if (app.user.nameRegExp.test(message)) return true; } return ((highlights.length > 0) && app.highlightRegExp.test(message)); }, updateHighlightRegExp: function (highlights) { // Enforce boundary for match sides, if a letter on match side is // a word character. For example, regular expression "a" matches // "a", but not "abc", while regular expression "!" matches // "!" and "!abc". app.highlightRegExp = new RegExp('(?:\\b|(?!\\w))(?:' + highlights.join('|') + ')(?:\\b|\\B(?!\\w))', 'i'); }, // chat history chatHistory: null, chatHistoryUp: function ($textbox, e) { var idx = +$textbox.prop('selectionStart'); var line = $textbox.val(); if (e && !e.ctrlKey && idx !== 0 && idx !== line.length) return false; if (this.chatHistory.index === 0) return false; $textbox.val(this.chatHistory.up(line)); return true; }, chatHistoryDown: function ($textbox, e) { var idx = +$textbox.prop('selectionStart'); var line = $textbox.val(); if (e && !e.ctrlKey && idx !== 0 && idx !== line.length) return false; $textbox.val(this.chatHistory.down(line)); return true; }, // tab completion initializeTabComplete: function () { this.tabComplete = { candidates: null, index: 0, prefix: null, cursor: null, reset: function () { this.cursor = null; } }; this.userActivity = []; }, markUserActive: function (userid) { var idx = this.userActivity.indexOf(userid); if (idx !== -1) { this.userActivity.splice(idx, 1); } this.userActivity.push(userid); if (this.userActivity.length > 100) { // Prune the list. this.userActivity.splice(0, 20); } }, tabComplete: null, userActivity: null, handleTabComplete: function ($textbox) { // Don't tab complete at the start of the text box. var idx = $textbox.prop('selectionStart'); if (idx === 0) return false; var users = this.users || (app.rooms['lobby'] ? app.rooms['lobby'].users : {}); var text = $textbox.val(); if (this.tabComplete.cursor !== null && text.substr(0, idx) === this.tabComplete.cursor) { // The user is cycling through the candidate names. if (++this.tabComplete.index >= this.tabComplete.candidates.length) { this.tabComplete.index = 0; } } else { // This is a new tab completion. // There needs to be non-whitespace to the left of the cursor. var m1 = /^(.*?)([A-Za-z0-9][^, ]*)$/.exec(text.substr(0, idx)); var m2 = /^(.*?)([A-Za-z0-9][^, ]* [^, ]*)$/.exec(text.substr(0, idx)); if (!m1 && !m2) return true; this.tabComplete.prefix = text; var idprefix = (m1 ? toId(m1[2]) : ''); var spaceprefix = (m2 ? m2[2].replace(/[^A-Za-z0-9 ]+/g, '').toLowerCase() : ''); var candidates = []; // array of [candidate userid, prefix length] // don't include command names in autocomplete if (m2 && (m2[0] === '/' || m2[0] === '!')) spaceprefix = ''; for (var i in users) { if (spaceprefix && users[i].substr(1).replace(/[^A-Za-z0-9 ]+/g, '').toLowerCase().substr(0, spaceprefix.length) === spaceprefix) { candidates.push([i, m2[1].length]); } else if (idprefix && i.substr(0, idprefix.length) === idprefix) { candidates.push([i, m1[1].length]); } } // Sort by most recent to speak in the chat, or, in the case of a tie, // in alphabetical order. var self = this; candidates.sort(function (a, b) { if (a[1] !== b[1]) { // shorter prefix length comes first return a[1] - b[1]; } var aidx = self.userActivity.indexOf(a[0]); var bidx = self.userActivity.indexOf(b[0]); if (aidx !== -1) { if (bidx !== -1) { return bidx - aidx; } return -1; // a comes first } else if (bidx != -1) { return 1; // b comes first } return (a[0] < b[0]) ? -1 : 1; // alphabetical order }); this.tabComplete.candidates = candidates; this.tabComplete.index = 0; if (!candidates.length) { this.tabComplete.cursor = null; return true; } } // Substitute in the tab-completed name. var candidate = this.tabComplete.candidates[this.tabComplete.index]; var substituteUserId = candidate[0]; if (!users[substituteUserId]) return true; var name = users[substituteUserId].substr(1); name = name.replace(/[^A-Za-z0-9]+$/, ''); var fullPrefix = this.tabComplete.prefix.substr(0, candidate[1]) + name; $textbox.val(fullPrefix + text.substr(idx)); var pos = fullPrefix.length; $textbox[0].setSelectionRange(pos, pos); this.tabComplete.cursor = fullPrefix; return true; }, // command parsing parseCommand: function (text) { var cmd = ''; var target = ''; if (text.substr(0, 2) !== '//' && text.substr(0, 1) === '/') { var spaceIndex = text.indexOf(' '); if (spaceIndex > 0) { cmd = text.substr(1, spaceIndex - 1); target = text.substr(spaceIndex + 1); } else { cmd = text.substr(1); target = ''; } } switch (cmd.toLowerCase()) { case 'chall': case 'challenge': var targets = target.split(',').map($.trim); var self = this; var challenge = function (targets) { target = toId(targets[0]); self.challengeData = {userid: target, format: targets[1] || '', team: targets[2] || ''}; app.on('response:userdetails', self.challengeUserdetails, self); app.send('/cmd userdetails ' + target); }; if (!targets[0]) { app.addPopupPrompt("Who would you like to challenge?", "Challenge user", function (target) { if (!target) return; challenge([target]); }); return false; } challenge(targets); return false; case 'accept': var userid = toId(target); if (userid) { var $challenge = $('.pm-window').filter('div[data-userid="' + userid + '"]').find('button[name="acceptChallenge"]'); if (!$challenge.length) { this.add("You do not have any pending challenge from '" + toName(target) + "' to accept."); return false; } $challenge[0].click(); return false; } var $challenges = $('.challenge').find('button[name=acceptChallenge]'); if (!$challenges.length) { this.add('You do not have any pending challenges to accept.'); return false; } if ($challenges.length > 1) { this.add('You need to specify a user if you have more than one pending challenge to accept.'); this.parseCommand('/help accept'); return false; } $challenges[0].click(); return false; case 'reject': var userid = toId(target); if (userid) { var $challenge = $('.pm-window').filter('div[data-userid="' + userid + '"]').find('button[name="rejectChallenge"]'); if (!$challenge.length) { this.add("You do not have any pending challenge from '" + toName(target) + "' to reject."); return false; } $challenge[0].click(); return false; } var $challenges = $('.challenge').find('button[name="rejectChallenge"]'); if (!$challenges.length) { this.add('You do not have any pending challenges to reject.'); this.parseCommand('/help reject'); return false; } if ($challenges.length > 1) { this.add('You need to specify a user if you have more than one pending challenge to reject.'); this.parseCommand('/help reject'); return false; } $challenges[0].click(); return false; case 'user': case 'open': var openUser = function (target) { app.addPopup(UserPopup, {name: target}); }; target = toName(target); if (!target) { app.addPopupPrompt("Username", "Open", function (target) { if (!target) return; openUser(target); }); return false; } openUser(target); return false; case 'debug': if (target === 'extractteams') { app.addPopup(Popup, { type: 'modal', htmlMessage: "Extracted team data:| User: ' + toName(targets[0]) + ' | ||||||||||||||||
| This user has not played any ladder games yet. | ||||||||||||||||
| Format | Elo | GXE | Glicko-1 | COIL | W | L | T | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ' + row.formatid + ' | ' + Math.round(row.elo) + ' | ' + Math.round(row.gxe, 1) + ' | '; if (row.rprd > 100) { buffer += '' + Math.round(row.rpr) + ' ± ' + Math.round(row.rprd) + ' (provisional)'; } else { buffer += '' + Math.round(row.rpr) + ' ± ' + Math.round(row.rprd) + ''; } var N = parseInt(row.w, 10) + parseInt(row.l, 10) + parseInt(row.t, 10); if (row.formatid === 'oususpecttest') { buffer += ' | ' + Math.round(40.0 * parseFloat(row.gxe) * Math.pow(2.0, -17.0 / N), 0) + ' | '; } else if (row.formatid === 'uberssuspecttest') { buffer += '' + Math.round(40.0 * parseFloat(row.gxe) * Math.pow(2.0, -29.0 / N), 0) + ' | '; } else if (row.formatid === 'uususpecttest') { buffer += '' + Math.round(40.0 * parseFloat(row.gxe) * Math.pow(2.0, -20.0 / N), 0) + ' | '; } else if (row.formatid === 'rususpecttest') { buffer += '' + Math.round(40.0 * parseFloat(row.gxe) * Math.pow(2.0, -9.0 / N), 0) + ' | '; } else if (row.formatid === 'nususpecttest') { buffer += '' + Math.round(40.0 * parseFloat(row.gxe) * Math.pow(2.0, -20.0 / N), 0) + ' | '; } else if (row.formatid === 'pususpecttest') { buffer += '' + Math.round(40.0 * parseFloat(row.gxe) * Math.pow(2.0, -9.0 / N), 0) + ' | '; } else if (row.formatid === 'lcsuspecttest') { buffer += '' + Math.round(40.0 * parseFloat(row.gxe) * Math.pow(2.0, -9.0 / N), 0) + ' | '; } else if (row.formatid === 'doublesoucurrent' || row.formatid === 'doublesoususpecttest') { buffer += '' + Math.round(40.0 * parseFloat(row.gxe) * Math.pow(2.0, -12.0 / N), 0) + ' | '; } else if (row.formatid === 'monotype') { buffer += '' + Math.round(40.0 * parseFloat(row.gxe) * Math.pow(2.0, -16.0 / N), 0) + ' | '; } else { buffer += '-- | '; } buffer += '' + row.w + ' | ' + row.l + ' | ' + row.t + ' |
| This user has not played any ladder games that match the format targeting. | ||||||||||||||||
|' + Tools.escapeHTML(row.join('|')) + '