From 5f05adc856859fcf0f2e3c57a487e6f9f1ff02af Mon Sep 17 00:00:00 2001 From: Guangcong Luo Date: Sun, 4 Nov 2018 13:54:02 -0600 Subject: [PATCH] Split battle-log.ts off from battle-dex.ts This splits battle-dex.ts up into: - `battle-dex.ts` - dex data access, misc tools - `battle-log.ts` - manipulating HTML, especially in battle logs This turned out to be a pretty significant portion of what was previously battle-dex. --- .eslintignore | 1 + .eslintrc.js | 4 +- .gitignore | 1 + build-tools/update | 3 +- js/client-battle-tooltips.js | 6 +- js/client-battle.js | 32 +- js/client-chat-tournament.js | 18 +- js/client-chat.js | 54 +- js/client-ladder.js | 12 +- js/client-mainmenu.js | 42 +- js/client-rooms.js | 16 +- js/client-teambuilder.js | 52 +- js/client-topbar.js | 42 +- js/client.js | 20 +- js/replay-embed.js | 2 +- js/search.js | 40 +- js/storage.js | 6 +- src/battle-animations.ts | 6 +- src/battle-dex.ts | 496 +----------------- ...{battle-dex-misc.js => battle-log-misc.js} | 8 +- src/battle-log.ts | 486 +++++++++++++++++ src/battle.ts | 110 ++-- src/globals.d.ts | 2 +- test/battle-test.mocha.js | 1 + 24 files changed, 732 insertions(+), 728 deletions(-) rename src/{battle-dex-misc.js => battle-log-misc.js} (97%) create mode 100644 src/battle-log.ts diff --git a/.eslintignore b/.eslintignore index 5ca096b50..4572b0642 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,6 +4,7 @@ node_modules/ /js/battle.js /js/battledata.js +/js/battle-log.js /js/battle-dex.js /js/battle-dex-data.js /js/battle-animations-moves.js diff --git a/.eslintrc.js b/.eslintrc.js index 9620a04ef..d2dde77f4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -18,7 +18,7 @@ module.exports = { "fs": false, "gui": false, "ga": false, "macgap": false, "nwWindow": false, "webkitNotifications": false, // Battle stuff - "Battle": true, "Pokemon": true, "BattleSound": true, "BattleTooltips": true, + "Battle": true, "Pokemon": true, "BattleSound": true, "BattleTooltips": true, "BattleLog": true, "BattleAbilities": false, "BattleAliases": false, "BattleBackdrops": false, "BattleBackdropsFive": false, "BattleBackdropsFour": false, "BattleBackdropsThree": false, "BattleEffects": false, "BattleFormats": false, "BattleFormatsData": false, "BattleLearnsets": false, "BattleItems": false, "BattleMoveAnims": false, "BattleMovedex": false, "BattleNatures": false, "BattleOtherAnims": false, "BattlePokedex": false,"BattlePokemonSprites": false, "BattlePokemonSpritesBW": false, "BattleSearchCountIndex": false, "BattleSearchIndex": false, "BattleArticleTitles": false, @@ -26,7 +26,7 @@ module.exports = { // Generic global variables "Config": false, "BattleSearch": false, "soundManager": false, "Storage": false, "Tools": false, - "app": false, "toId": false, "toRoomid": false, "toUserid": false, "toName": false, "getString": false, "hashColor": false, "MD5": false, + "app": false, "toId": false, "toRoomid": false, "toUserid": false, "toName": false, "getString": false, "MD5": false, "ChatHistory": false, "Topbar": false, "UserList": false, // Rooms diff --git a/.gitignore b/.gitignore index 6bc408368..60d45e047 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ package-lock.json /js/battle.js /js/battledata.js +/js/battle-log.js /js/battle-dex.js /js/battle-dex-data.js /js/battle-animations-moves.js diff --git a/build-tools/update b/build-tools/update index 744d6b1e1..199f0a0a8 100755 --- a/build-tools/update +++ b/build-tools/update @@ -52,7 +52,8 @@ fs.writeFileSync( 'js/battledata.js', fs.readFileSync('js/battle-dex.js') + '\n\n' + fs.readFileSync('js/battle-dex-data.js') + '\n\n' + - fs.readFileSync('src/battle-dex-misc.js') + fs.readFileSync('js/battle-log.js') + '\n\n' + + fs.readFileSync('src/battle-log-misc.js') ); if (!ignoreGraphics) { diff --git a/js/client-battle-tooltips.js b/js/client-battle-tooltips.js index 7041bb1e6..d4112fe8a 100644 --- a/js/client-battle-tooltips.js +++ b/js/client-battle-tooltips.js @@ -99,12 +99,12 @@ var BattleTooltips = (function () { BattleTooltips.prototype.tooltipAttrs = function (thing, type, ownHeight) { var roomid = this.room.id; return ' onclick="BattleTooltips._handleClickFor(event)"' + - ' ontouchstart="BattleTooltips._handleTouchStartFor(event, \'' + roomid + '\', \'' + Tools.escapeHTML('' + thing, true) + '\',\'' + type + '\', this, ' + (ownHeight ? 'true' : 'false') + ')"' + + ' ontouchstart="BattleTooltips._handleTouchStartFor(event, \'' + roomid + '\', \'' + BattleLog.escapeHTML('' + thing, true) + '\',\'' + type + '\', this, ' + (ownHeight ? 'true' : 'false') + ')"' + ' ontouchend="BattleTooltips._handleTouchEndFor(event, this)"' + ' ontouchleave="BattleTooltips._handleTouchLeaveFor(event)"' + ' ontouchcancel="BattleTooltips._handleTouchLeaveFor(event)"' + - ' onmouseover="BattleTooltips.showTooltipFor(\'' + roomid + '\', \'' + Tools.escapeHTML('' + thing, true) + '\',\'' + type + '\', this, ' + (ownHeight ? 'true' : 'false') + ')"' + - ' onfocus="BattleTooltips.showTooltipFor(\'' + roomid + '\', \'' + Tools.escapeHTML('' + thing, true) + '\',\'' + type + '\', this, ' + (ownHeight ? 'true' : 'false') + ')"' + + ' onmouseover="BattleTooltips.showTooltipFor(\'' + roomid + '\', \'' + BattleLog.escapeHTML('' + thing, true) + '\',\'' + type + '\', this, ' + (ownHeight ? 'true' : 'false') + ')"' + + ' onfocus="BattleTooltips.showTooltipFor(\'' + roomid + '\', \'' + BattleLog.escapeHTML('' + thing, true) + '\',\'' + type + '\', this, ' + (ownHeight ? 'true' : 'false') + ')"' + ' onmouseout="BattleTooltips.hideTooltip()"' + ' onblur="BattleTooltips.hideTooltip()"' + ' onmouseup="BattleTooltips._handleMouseUpFor()"' + diff --git a/js/client-battle.js b/js/client-battle.js index 08348eb48..bcd23efd4 100644 --- a/js/client-battle.js +++ b/js/client-battle.js @@ -191,7 +191,7 @@ switch (args[0]) { case 'trapped': requestData.trapped = true; - var pokeName = pokemon.side.n === 0 ? Tools.escapeHTML(pokemon.name) : "The opposing " + (this.battle.ignoreOpponent || this.battle.ignoreNicks ? pokemon.species : Tools.escapeHTML(pokemon.name)); + var pokeName = pokemon.side.n === 0 ? BattleLog.escapeHTML(pokemon.name) : "The opposing " + (this.battle.ignoreOpponent || this.battle.ignoreNicks ? pokemon.species : BattleLog.escapeHTML(pokemon.name)); this.battle.activityQueue.push('|message|' + pokeName + ' is trapped and cannot switch!'); break; case 'cant': @@ -554,7 +554,7 @@ } else if (!pokemon || pokemon.fainted) { targetMenus[0] += ' '; } else { - targetMenus[0] += ' '; + targetMenus[0] += ' '; } } for (var i = 0; i < myActive.length; i++) { @@ -573,7 +573,7 @@ } else if (!pokemon || pokemon.fainted) { targetMenus[1] += ' '; } else { - targetMenus[1] += ' '; + targetMenus[1] += ' '; } } @@ -587,7 +587,7 @@ } else { // Move chooser var hpBar = 'HP ' + switchables[pos].hp + '/' + switchables[pos].maxhp + ''; - requestTitle += ' What will ' + Tools.escapeHTML(switchables[pos].name) + ' do? ' + hpBar; + requestTitle += ' What will ' + BattleLog.escapeHTML(switchables[pos].name) + ' do? ' + hpBar; var hasMoves = false; var moveMenu = ''; @@ -605,7 +605,7 @@ if (moveData.disabled) { movebuttons += ' '; @@ -620,7 +620,7 @@ var move = Tools.getMove(moveData.move); var moveType = this.tooltips.getMoveType(move, this.battle.mySide.active[pos] || this.myPokemon[pos]); if (canZMove[i]) { - movebuttons += ' '; } else { movebuttons += ''; @@ -662,9 +662,9 @@ var pokemon = switchables[i]; pokemon.name = pokemon.ident.substr(4); if (pokemon.fainted || i < this.battle.mySide.active.length || this.choice.switchFlags[i]) { - switchMenu += ' '; + switchMenu += ' '; } else { - switchMenu += ' '; + switchMenu += ' '; } } if (this.finalDecisionSwitch && this.battle.gen > 2) { @@ -711,11 +711,11 @@ for (var i = 0; i < myActive.length; i++) { var pokemon = this.myPokemon[i]; if (pokemon && !pokemon.fainted || this.choice.switchOutFlags[i]) { - controls += ' '; + controls += ' '; } else if (!pokemon) { controls += ' '; } else { - controls += ' '; + controls += ' '; } } controls += ''; @@ -729,18 +729,18 @@ if (this.choice.freedomDegrees >= 1) { requestTitle += "Choose a Pokémon to send to battle!"; } else { - requestTitle += "Switch " + Tools.escapeHTML(switchables[pos].name) + " to:"; + requestTitle += "Switch " + BattleLog.escapeHTML(switchables[pos].name) + " to:"; } var switchMenu = ''; for (var i = 0; i < switchables.length; i++) { var pokemon = switchables[i]; if (pokemon.fainted || i < this.battle.mySide.active.length || this.choice.switchFlags[i]) { - switchMenu += ' '; + switchMenu += '' + BattleLog.escapeHTML(pokemon.name) + (!pokemon.fainted ? '' + (pokemon.status ? '' : '') : '') + ' '; } var controls = ( @@ -774,9 +774,9 @@ var oIndex = this.choice.teamPreview[i] - 1; var pokemon = switchables[oIndex]; if (i < this.choice.done) { - switchMenu += ' '; + switchMenu += ' '; } else { - switchMenu += ' '; + switchMenu += ' '; } } @@ -987,7 +987,7 @@ filename += '-' + toId(this.battle.p1.name); filename += '-' + toId(this.battle.p2.name); - e.currentTarget.href = Tools.createReplayFileHref(this); + e.currentTarget.href = BattleLog.createReplayFileHref(this); e.currentTarget.download = filename + '.html'; e.stopPropagation(); diff --git a/js/client-chat-tournament.js b/js/client-chat-tournament.js index 7be4da66e..f0bcbdcd9 100644 --- a/js/client-chat-tournament.js +++ b/js/client-chat-tournament.js @@ -271,7 +271,7 @@ case 'create': var formatName = window.BattleFormats && BattleFormats[data[0]] ? BattleFormats[data[0]].name : data[0]; var type = data[1]; - this.room.$chat.append("
" + Tools.escapeHTML(formatName) + " " + Tools.escapeHTML(type) + " Tournament created.
"); + this.room.$chat.append("
" + BattleLog.escapeHTML(formatName) + " " + BattleLog.escapeHTML(type) + " Tournament created.
"); this.room.notifyOnce("Tournament created", "Room: " + this.room.title + "\nFormat: " + formatName + "\nType: " + type, 'tournament-create'); this.curTeamIndex = 0; this.updateTeams(); @@ -317,7 +317,7 @@ break; case 'disqualify': - this.room.$chat.append("
" + Tools.escapeHTML(data[0]) + " has been disqualified from the tournament.
"); + this.room.$chat.append("
" + BattleLog.escapeHTML(data[0]) + " has been disqualified from the tournament.
"); break; case 'autodq': @@ -448,7 +448,7 @@ case 'battlestart': var roomid = toRoomid(data[2]).toLowerCase(); this.room.$chat.append('
' + - "Tournament battle between " + Tools.escapeHTML(data[0]) + " and " + Tools.escapeHTML(data[1]) + " started." + + "Tournament battle between " + BattleLog.escapeHTML(data[0]) + " and " + BattleLog.escapeHTML(data[1]) + " started." + '
'); break; @@ -458,7 +458,7 @@ result = "won"; else if (data[2] === 'loss') result = "lost"; - var message = Tools.escapeHTML(data[0]) + " has " + result + " the match " + Tools.escapeHTML(data[3].split(',').join(' - ')) + " against " + Tools.escapeHTML(data[1]) + + var message = BattleLog.escapeHTML(data[0]) + " has " + result + " the match " + BattleLog.escapeHTML(data[3].split(',').join(' - ')) + " against " + BattleLog.escapeHTML(data[1]) + (data[4] === 'fail' ? " but the tournament does not support drawing, so it did not count" : "") + "."; var $battleMessage = data[5] ? this.room.$chat.find('.tournament-' + toRoomid(data[5]).toLowerCase()) : ''; if ($battleMessage && $battleMessage.length) { @@ -483,9 +483,9 @@ } var type = endData.generator; - this.room.$chat.append("
Congratulations to " + Tools.escapeHTML(arrayToPhrase(endData.results[0])) + " for winning the " + Tools.escapeFormat(endData.format) + " " + Tools.escapeHTML(type) + " Tournament!
"); + this.room.$chat.append("
Congratulations to " + BattleLog.escapeHTML(arrayToPhrase(endData.results[0])) + " for winning the " + BattleLog.escapeFormat(endData.format) + " " + BattleLog.escapeHTML(type) + " Tournament!
"); if (endData.results[1]) - this.room.$chat.append("
Runner-up" + (endData.results[1].length > 1 ? "s" : "") + ": " + Tools.escapeHTML(arrayToPhrase(endData.results[1])) + "
"); + this.room.$chat.append("
Runner-up" + (endData.results[1].length > 1 ? "s" : "") + ": " + BattleLog.escapeHTML(arrayToPhrase(endData.results[1])) + "
"); // Fallthrough @@ -510,7 +510,7 @@ case 'error': var appendError = function (message) { - this.room.$chat.append("
" + Tools.sanitizeHTML(message) + "
"); + this.room.$chat.append("
" + BattleLog.sanitizeHTML(message) + "
"); }.bind(this); switch (data[0]) { @@ -586,7 +586,7 @@ if (!data.rootNode) { if (!('users' in data)) return; var users = data.users.length; - if (users) $div.html('' + users + ' user' + (users !== 1 ? 's' : '') + ':
' + Tools.escapeHTML(data.users.join(", "))); + if (users) $div.html('' + users + ' user' + (users !== 1 ? 's' : '') + ':
' + BattleLog.escapeHTML(data.users.join(", "))); return $div; } @@ -803,7 +803,7 @@ var UserPopup = this.Popup.extend({ initialize: function (data) { this.$el.html(''); }, diff --git a/js/client-chat.js b/js/client-chat.js index 7aabd497f..430e08f79 100644 --- a/js/client-chat.js +++ b/js/client-chat.js @@ -28,7 +28,7 @@ var name = app.user.get('name'); var userid = app.user.get('userid'); if (this.expired) { - this.$chatAdd.html(this.expired === true ? 'This room is expired' : Tools.sanitizeHTML(this.expired)); + this.$chatAdd.html(this.expired === true ? 'This room is expired' : BattleLog.sanitizeHTML(this.expired)); this.$chatbox = null; } else if (!name) { this.$chatAdd.html('Connecting...'); @@ -37,7 +37,7 @@ this.$chatAdd.html('
'); this.$chatbox = null; } else { - this.$chatAdd.html('
'); + this.$chatAdd.html('
'); this.$chatbox = this.$chatAdd.find('textarea'); this.$chatbox.autoResize({ animate: false, @@ -479,7 +479,7 @@ if (target === 'extractteams') { app.addPopup(Popup, { type: 'modal', - htmlMessage: "Extracted team data:
" + htmlMessage: "Extracted team data:
" }); } else { this.add('|error|Unknown debug command.'); @@ -795,7 +795,7 @@ buffer += ''; } else { buffer += ''; - hiddenFormats.push(Tools.escapeFormat(formatId)); + hiddenFormats.push(BattleLog.escapeFormat(formatId)); } // Validate all the numerical data @@ -804,7 +804,7 @@ if (typeof values[j] !== 'number' && typeof values[j] !== 'string' || isNaN(values[j])) return self.add('|raw|Error: corrupted ranking data'); } - buffer += '' + Tools.escapeFormat(formatId) + '' + Math.round(row.elo) + ''; + buffer += '' + BattleLog.escapeFormat(formatId) + '' + Math.round(row.elo) + ''; if (row.rprd > 100) { // High rating deviation. Provisional rating. buffer += '–'; @@ -1231,7 +1231,7 @@ if (!matches) { return; // bogus room ID could be used to inject JavaScript } - var format = Tools.escapeFormat(matches[1]); + var format = BattleLog.escapeFormat(matches[1]); if (silent && !Tools.prefs('showbattles')) return; @@ -1241,7 +1241,7 @@ battletype = format + ' battle'; if (format === 'Random Battle') battletype = 'Random Battle'; } - this.$chat.append('
' + battletype + ' started between ' + Tools.escapeHTML(name) + ' and ' + Tools.escapeHTML(name2) + '.
'); + this.$chat.append('
' + battletype + ' started between ' + BattleLog.escapeHTML(name) + ' and ' + BattleLog.escapeHTML(name2) + '.
'); break; case 'j': @@ -1281,7 +1281,7 @@ case 'raw': case 'html': - this.$chat.append('
' + Tools.sanitizeHTML(row.slice(1).join('|')) + '
'); + this.$chat.append('
' + BattleLog.sanitizeHTML(row.slice(1).join('|')) + '
'); break; case 'notify': @@ -1307,7 +1307,7 @@ break; case 'error': - this.$chat.append('
' + Tools.escapeHTML(row.slice(1).join('|')) + '
'); + this.$chat.append('
' + BattleLog.escapeHTML(row.slice(1).join('|')) + '
'); break; case 'uhtml': @@ -1317,12 +1317,12 @@ if (!html) { $elements.remove(); } else if (!$elements.length) { - this.$chat.append('
' + Tools.sanitizeHTML(html) + '
'); + this.$chat.append('
' + BattleLog.sanitizeHTML(html) + '
'); } else if (row[0] === 'uhtmlchange') { - $elements.html(Tools.sanitizeHTML(html)); + $elements.html(BattleLog.sanitizeHTML(html)); } else { $elements.remove(); - this.$chat.append('
' + Tools.sanitizeHTML(html) + '
'); + this.$chat.append('
' + BattleLog.sanitizeHTML(html) + '
'); } break; @@ -1349,7 +1349,7 @@ case 'tournaments': if (Tools.prefs('notournaments')) { if (row[1] === 'create') { - this.$chat.append('
' + Tools.escapeFormat(row[2]) + ' ' + Tools.escapeHTML(row[3]) + ' tournament created (and hidden because you have tournaments disabled).
'); + this.$chat.append('
' + BattleLog.escapeFormat(row[2]) + ' ' + BattleLog.escapeHTML(row[3]) + ' tournament created (and hidden because you have tournaments disabled).
'); } else if (row[1] === 'start') { this.$chat.append('
Tournament started.
'); } else if (row[1] === 'forceend') { @@ -1364,11 +1364,11 @@ // fallthrough in case of unparsed message case '': - this.$chat.append('
' + Tools.escapeHTML(row.slice(1).join('|')) + '
'); + this.$chat.append('
' + BattleLog.escapeHTML(row.slice(1).join('|')) + '
'); break; default: - this.$chat.append('
|' + Tools.escapeHTML(row.join('|')) + '
'); + this.$chat.append('
|' + BattleLog.escapeHTML(row.join('|')) + '
'); break; } } @@ -1465,7 +1465,7 @@ message += ', '; } } - message += Tools.escapeHTML(list[j]); + message += BattleLog.escapeHTML(list[j]); } message += ' joined'; } @@ -1494,7 +1494,7 @@ message += ', '; } } - message += Tools.escapeHTML(list[j]); + message += BattleLog.escapeHTML(list[j]); } message += ' left
'; } @@ -1519,11 +1519,11 @@ if (pm) { var pmuserid = toUserid(pm); var oName = pmuserid === app.user.get('userid') ? name : pm; - var clickableName = '' + Tools.escapeHTML(name.substr(1)) + ''; + var clickableName = '' + BattleLog.escapeHTML(name.substr(1)) + ''; this.$chat.append( '
' + ChatRoom.getTimestamp('lobby', msgTime) + - '' + clickableName + ':' + - '(Private to ' + Tools.escapeHTML(pm) + ') ' + Tools.parseMessage(message) + '' + + '' + clickableName + ':' + + '(Private to ' + BattleLog.escapeHTML(pm) + ') ' + BattleLog.parseMessage(message) + '' + '
' ); return; // PMs independently notify in the main menu; no need to make them notify again with `inchatpm`. @@ -1546,7 +1546,7 @@ } var isHighlighted = userid !== app.user.get('userid') && this.getHighlight(message); - var parsedMessage = Tools.parseChatMessage(message, name, ChatRoom.getTimestamp('chat', msgTime), isHighlighted); + var parsedMessage = BattleLog.parseChatMessage(message, name, ChatRoom.getTimestamp('chat', msgTime), isHighlighted); if (!$.isArray(parsedMessage)) parsedMessage = [parsedMessage]; for (var i = 0; i < parsedMessage.length; i++) { if (!parsedMessage[i]) continue; @@ -1687,17 +1687,17 @@ var text = ''; // Sanitising the `userid` here is probably unnecessary, because // IDs can't contain anything dangerous. - text += ''; - text += ''; text += ''; diff --git a/js/client-ladder.js b/js/client-ladder.js index a932badfb..6fa485197 100644 --- a/js/client-ladder.js +++ b/js/client-ladder.js @@ -48,14 +48,14 @@ break; case 'pagehtml': - this.$el.html(Tools.sanitizeHTML(row[1])); + this.$el.html(BattleLog.sanitizeHTML(row[1])); this.subtleNotifyOnce(); break; case 'selectorhtml': var pipeIndex2 = row[1].indexOf('|'); if (pipeIndex2 < 0) return; - this.$(row[1].slice(0, pipeIndex2)).html(Tools.sanitizeHTML(row[1].slice(pipeIndex2 + 1))); + this.$(row[1].slice(0, pipeIndex2)).html(BattleLog.sanitizeHTML(row[1].slice(pipeIndex2 + 1))); this.subtleNotifyOnce(); break; @@ -96,7 +96,7 @@ return; } if (this.curFormat !== data[0]) return; - buf += Tools.sanitizeHTML(data[1]) + ''; + buf += BattleLog.sanitizeHTML(data[1]) + ''; this.$el.html(buf); }, this); }, @@ -119,9 +119,9 @@ if (!format.rated || !format.searchShow) continue; if (format.section && format.section !== curSection) { curSection = format.section; - buf += '

' + Tools.escapeHTML(curSection) + '

' + BattleLog.escapeHTML(curSection) + '

'; this.$el.html(buf); @@ -141,7 +141,7 @@ }, function (data) { if (self.curFormat !== format) return; var buf = '

'; - buf += '

' + Tools.escapeFormat(format) + ' Top 500

'; + buf += '

' + BattleLog.escapeFormat(format) + ' Top 500

'; buf += data + '
'; self.$el.html(buf); }, 'html'); diff --git a/js/client-mainmenu.js b/js/client-mainmenu.js index 65ee131e9..dbf33779c 100644 --- a/js/client-mainmenu.js +++ b/js/client-mainmenu.js @@ -143,7 +143,7 @@ var autoscroll = ($chatFrame.scrollTop() + 60 >= $chat.height() - $chatFrame.height()); - var parsedMessage = Tools.parseChatMessage(message, name, ChatRoom.getTimestamp('pms'), false, $chat); + var parsedMessage = BattleLog.parseChatMessage(message, name, ChatRoom.getTimestamp('pms'), false, $chat); if (!$.isArray(parsedMessage)) parsedMessage = [parsedMessage]; for (var i = 0; i < parsedMessage.length; i++) { if (!parsedMessage[i]) continue; @@ -176,12 +176,12 @@ if (group === ' ') { group = ''; } else { - group = '' + Tools.escapeHTML(group) + ''; + group = '' + BattleLog.escapeHTML(group) + ''; } var buf = '
'; buf += '

'; buf += ''; - buf += group + Tools.escapeHTML(name.substr(1)) + '

'; + buf += group + BattleLog.escapeHTML(name.substr(1)) + ''; buf += '
'; buf += '
'; $pmWindow = $(buf).prependTo(this.$pmBox); @@ -569,7 +569,7 @@ for (var roomid in this.games) { var name = this.games[roomid]; if (name.slice(-1) === '*') name = name.slice(0, -1); - buf += '
' + Tools.escapeHTML(name) + '
'; + buf += '
' + BattleLog.escapeHTML(name) + '
'; } buf += ''; if (!$searchGroup.is(':visible')) buf += '

'; @@ -606,7 +606,7 @@ var userid = toId(name); var $challenge = this.openChallenge(name); - var buf = '

Waiting for ' + Tools.escapeHTML(name) + '...

'; + var buf = '

Waiting for ' + BattleLog.escapeHTML(name) + '...

'; buf += '

' + this.renderFormats(challenge.format, true) + '

'; buf += '

'; @@ -623,10 +623,10 @@ if (data.challengesFrom[userid]) { var format = data.challengesFrom[userid]; if (!$pmWindow.find('.challenge').length) { - self.notifyOnce("Challenge from " + name, "Format: " + Tools.escapeFormat(format), 'challenge:' + userid); + self.notifyOnce("Challenge from " + name, "Format: " + BattleLog.escapeFormat(format), 'challenge:' + userid); } var $challenge = self.openChallenge(name, $pmWindow); - var buf = '

' + Tools.escapeHTML(name) + ' wants to battle!

'; + var buf = '

' + BattleLog.escapeHTML(name) + ' wants to battle!

'; buf += '

' + self.renderFormats(format, true) + '

'; buf += '

' + self.renderTeams(format) + '

'; buf += '

'; @@ -729,7 +729,7 @@ } $challenge = this.openChallenge(name); - var buf = '

Challenge ' + Tools.escapeHTML(name) + '?

'; + var buf = '

Challenge ' + BattleLog.escapeHTML(name) + '?

'; buf += '

' + this.renderFormats(format) + '

'; buf += '

' + this.renderTeams(format, teamIndex) + '

'; buf += '

'; @@ -773,7 +773,7 @@ return; } - var buf = '

Challenging ' + Tools.escapeHTML(name) + '...

'; + var buf = '

Challenging ' + BattleLog.escapeHTML(name) + '...

'; buf += '

' + this.renderFormats(format, true) + '

'; buf += '

'; @@ -802,7 +802,7 @@ curFormat: '', renderFormats: function (formatid, noChoice) { if (!window.BattleFormats) { - return ''; + return ''; } if (_.isEmpty(BattleFormats)) { return ''; @@ -820,7 +820,7 @@ } formatid = this.curFormat; } - return ''; + return ''; }, curTeamFormat: '', curTeamIndex: 0, @@ -957,9 +957,9 @@ if (!bufs[curBuf]) { bufs[curBuf] = ''; } - bufs[curBuf] += '
  • ' + Tools.escapeHTML(curSection) + '

  • '; + bufs[curBuf] += '
  • ' + BattleLog.escapeHTML(curSection) + '

  • '; } - var formatName = Tools.escapeFormat(format.id); + var formatName = BattleLog.escapeFormat(format.id); if (formatName.charAt(0) !== '[') formatName = '[Gen 6] ' + formatName; formatName = formatName.replace('[Gen 7] ', ''); bufs[curBuf] += '
  • '; @@ -989,7 +989,7 @@ var $teamButton = this.sourceEl.closest('form').find('button[name=team]'); if ($teamButton.length) $teamButton.replaceWith(app.rooms[''].renderTeams(format)); } - this.sourceEl.val(format).html(Tools.escapeFormat(format) || '(Select a format)'); + this.sourceEl.val(format).html(BattleLog.escapeFormat(format) || '(Select a format)'); this.close(); } @@ -1023,22 +1023,22 @@ if (!teams.length) { bufs[curBuf] = '
  • You have no teams

  • '; - bufs[curBuf] += '
  • '; + bufs[curBuf] += '
  • '; } else { var curTeam = (data.team === '' ? -1 : +data.team); var count = 0; if (teamFormat) { - bufs[curBuf] = '
  • ' + Tools.escapeFormat(teamFormat) + ' teams

  • '; + bufs[curBuf] = '
  • ' + BattleLog.escapeFormat(teamFormat) + ' teams

  • '; for (var i = 0; i < teams.length; i++) { if ((!teams[i].format && !teamFormat) || teams[i].format === teamFormat) { var selected = (i === curTeam); - bufs[curBuf] += '
  • '; + bufs[curBuf] += '
  • '; count++; if (count % bufBoundary == 0 && curBuf < 4) curBuf++; } } - if (!count) bufs[curBuf] += '
  • You have no ' + Tools.escapeFormat(teamFormat) + ' teams

  • '; - bufs[curBuf] += '
  • '; + if (!count) bufs[curBuf] += '
  • You have no ' + BattleLog.escapeFormat(teamFormat) + ' teams

  • '; + bufs[curBuf] += '
  • '; bufs[curBuf] += '
  • Other teams

  • '; } else { bufs[curBuf] = '
  • '; @@ -1049,7 +1049,7 @@ for (var i = 0; i < teams.length; i++) { if (teamFormat && teams[i].format === teamFormat) continue; var selected = (i === curTeam); - bufs[curBuf] += '
  • '; + bufs[curBuf] += '
  • '; count++; if (count % bufBoundary == 0 && curBuf < 4) curBuf++; } @@ -1112,7 +1112,7 @@ } var team = Storage.teams[i]; if (!team) return 'Error: Corrupted team'; - var buf = '' + Tools.escapeHTML(team.name) + ''; + var buf = '' + BattleLog.escapeHTML(team.name) + ''; buf += Storage.getTeamIcons(team) + ''; return buf; } diff --git a/js/client-rooms.js b/js/client-rooms.js index d70471785..5db1fb6fc 100644 --- a/js/client-rooms.js +++ b/js/client-rooms.js @@ -59,12 +59,12 @@ }, renderRoomBtn: function (roomData) { var id = toId(roomData.title); - var buf = '
    (' + Number(roomData.userCount) + ' users) ' + Tools.escapeHTML(roomData.title) + '
    ' + Tools.escapeHTML(roomData.desc || ''); + var buf = '
    (' + Number(roomData.userCount) + ' users) ' + BattleLog.escapeHTML(roomData.title) + '
    ' + BattleLog.escapeHTML(roomData.desc || ''); if (roomData.subRooms && roomData.subRooms.length) { buf += '
    Subrooms: '; for (var i = 0; i < roomData.subRooms.length; i++) { if (i) buf += ', '; - buf += ' ' + Tools.escapeHTML(roomData.subRooms[i]); + buf += ' ' + BattleLog.escapeHTML(roomData.subRooms[i]); } buf += ''; } @@ -167,14 +167,14 @@ renderRoomBtn: function (id, roomData, matches) { var format = (matches[1] || ''); var formatBuf = ''; - if (roomData.minElo) formatBuf += '(' + (typeof roomData.minElo === 'number' ? 'rated: ' : '') + Tools.escapeHTML(roomData.minElo) + ')'; - formatBuf += (format ? '[' + Tools.escapeFormat(format) + ']
    ' : ''); - var roomDesc = formatBuf + '' + Tools.escapeHTML(roomData.p1) + ' vs. ' + Tools.escapeHTML(roomData.p2) + ''; + if (roomData.minElo) formatBuf += '(' + (typeof roomData.minElo === 'number' ? 'rated: ' : '') + BattleLog.escapeHTML(roomData.minElo) + ')'; + formatBuf += (format ? '[' + BattleLog.escapeFormat(format) + ']
    ' : ''); + var roomDesc = formatBuf + '' + BattleLog.escapeHTML(roomData.p1) + ' vs. ' + BattleLog.escapeHTML(roomData.p2) + ''; if (!roomData.p1) { matches = id.match(/[^0-9]([0-9]*)$/); roomDesc = formatBuf + 'empty room ' + matches[1]; } else if (!roomData.p2) { - roomDesc = formatBuf + '' + Tools.escapeHTML(roomData.p1) + ''; + roomDesc = formatBuf + '' + BattleLog.escapeHTML(roomData.p1) + ''; } return '
    '; }, @@ -204,8 +204,8 @@ buf.push(this.renderRoomBtn(id, roomData, matches)); } - if (!buf.length) return this.$list.html('

    No ' + Tools.escapeFormat(this.format) + ' battles are going on right now.

    '); - return this.$list.html('

    ' + buf.length + (buf.length === 100 ? '+' : '') + ' ' + Tools.escapeFormat(this.format) + ' ' + (buf.length === 1 ? 'battle' : 'battles') + '

    ' + buf.join("")); + if (!buf.length) return this.$list.html('

    No ' + BattleLog.escapeFormat(this.format) + ' battles are going on right now.

    '); + return this.$list.html('

    ' + buf.length + (buf.length === 100 ? '+' : '') + ' ' + BattleLog.escapeFormat(this.format) + ' ' + (buf.length === 1 ? 'battle' : 'battles') + '

    ' + buf.join("")); }, refresh: function () { var elofilter = ''; diff --git a/js/client-teambuilder.js b/js/client-teambuilder.js index 3edb62bd7..025b1b668 100644 --- a/js/client-teambuilder.js +++ b/js/client-teambuilder.js @@ -149,14 +149,14 @@ if (this.exportMode) { if (this.curFolder) { buf = '
    '; - buf += '
    '; + buf += '
    '; } else { buf = '
    '; buf += '
    '; } @@ -291,7 +291,7 @@ formatName = '(uncategorized)'; format = '/'; } else { - formatName = Tools.escapeHTML(formatName); + formatName = BattleLog.escapeHTML(formatName); } buf += '
    ' : '">') + '
    ' + formatName + '
    ' + (this.curFolder === format ? '
    ' : ''); continue; @@ -347,7 +347,7 @@ var newButtonText = "New Team"; if (filterFolder) newButtonText = "New Team in folder"; if (filterFormat && filterFormat !== 'gen7') { - newButtonText = "New " + Tools.escapeFormat(filterFormat) + " Team"; + newButtonText = "New " + BattleLog.escapeFormat(filterFormat) + " Team"; } buf += '

    '; @@ -399,7 +399,7 @@ // teams are
    s rather than '; } @@ -1019,10 +1019,10 @@ var buf = ''; if (this.exportMode) { - buf = '
    '; - buf += '
    '; + buf = '
    '; + buf += '
    '; } else { - buf = '
    '; + buf = '
    '; buf += '
    '; buf += '
      '; buf += '
    1. ' + this.clipboardHTML() + '
    2. '; @@ -1038,7 +1038,7 @@ }; if (exports.BattleFormats) { buf += '
    3. '; - buf += ''; + buf += ''; var btnClass = 'button' + (!this.curSetList.length ? ' disabled' : ''); buf += '
    4. '; } @@ -1081,7 +1081,7 @@ } buf += '
      '; buf += '
      '; - buf += ''; + buf += ''; buf += '
      '; buf += '
      '; @@ -1092,7 +1092,7 @@ } else { buf += '
      '; } - buf += '
      '; + buf += '
    '; // details buf += '
    '; @@ -1131,17 +1131,17 @@ buf += '
    '; buf += '
    '; - if (this.curTeam.gen > 1) buf += '
    '; - if (this.curTeam.gen > 2) buf += '
    '; + if (this.curTeam.gen > 1) buf += '
    '; + if (this.curTeam.gen > 2) buf += '
    '; buf += '
    '; // moves if (!set.moves) set.moves = []; buf += '
    '; - buf += '
    '; - buf += '
    '; - buf += '
    '; - buf += '
    '; + buf += '
    '; + buf += '
    '; + buf += '
    '; + buf += '
    '; buf += '
    '; // stats @@ -1533,9 +1533,9 @@ buf += ' '; isAdd = true; } else if (i == this.curSetLoc) { - buf += ' '; + buf += ' '; } else { - buf += ' '; + buf += ' '; } } if (this.curSetList.length < 6 && !isAdd) { @@ -1902,7 +1902,7 @@ buf += '
    '; for (var i in stats) { if (i === 'spd' && this.curTeam.gen === 1) continue; - buf += '
    '; + buf += '
    '; } buf += '
    '; @@ -1912,7 +1912,7 @@ for (var i in stats) { if (set.ivs[i] === undefined || isNaN(set.ivs[i])) set.ivs[i] = 31; var val = '' + (set.ivs[i]); - buf += '
    '; + buf += '
    '; } var hpType = ''; if (set.moves) { @@ -2032,7 +2032,7 @@ for (var i in stats) { if (set.ivs[i] === undefined || isNaN(set.ivs[i])) set.ivs[i] = 31; var val = '' + Math.floor(set.ivs[i] / 2); - buf += '
    '; + buf += '
    '; } buf += '
    '; } @@ -2304,7 +2304,7 @@ buf += '

    Details

    '; buf += '
    '; - buf += '
    '; + buf += '
    '; if (this.curTeam.gen > 1) { buf += '
    '; @@ -3354,7 +3354,7 @@ if (i !== data.i && i !== data.i + 1) { buf += '
  • '; } - buf += ' ' + Tools.escapeHTML(set.name || set.species) + ''; + buf += ' ' + BattleLog.escapeHTML(set.name || set.species) + ''; } if (i !== data.i && i !== data.i + 1) { buf += '
  • '; @@ -3387,7 +3387,7 @@ initialize: function (data) { this.room = data.room; this.folder = data.folder; - var buf = '

    Remove "' + data.folder.slice(0, -1) + '"?

    '; + var buf = '

    Remove "' + data.folder.slice(0, -1) + '"?

    '; buf += '

    '; this.$el.html(buf); }, diff --git a/js/client-topbar.js b/js/client-topbar.js index f2c8f528d..66e0545ef 100644 --- a/js/client-topbar.js +++ b/js/client-topbar.js @@ -30,11 +30,11 @@ updateUserbar: function () { var buf = ''; var name = ' ' + app.user.get('name'); - var color = hashColor(app.user.get('userid')); + var color = BattleLog.hashColor(app.user.get('userid')); if (!app.user.loaded) { buf = ''; } else if (app.user.get('named')) { - buf = ' ' + Tools.escapeHTML(name) + ''; + buf = ' ' + BattleLog.escapeHTML(name) + ''; } else { buf = ''; } @@ -74,7 +74,7 @@ if (room.notifications[tag].title) title += room.notifications[tag].title + '\n'; if (room.notifications[tag].body) title += room.notifications[tag].body + '\n'; } - if (title) buf += ' title="' + Tools.escapeHTML(title) + '"'; + if (title) buf += ' title="' + BattleLog.escapeHTML(title) + '"'; } switch (room ? room.type : id) { case '': @@ -89,7 +89,7 @@ case 'rooms': return buf + ' aria-label="Join chatroom">  '; case 'battle': - var name = Tools.escapeHTML(room.title); + var name = BattleLog.escapeHTML(room.title); var idChunks = id.substr(7).split('-'); var formatid; if (idChunks.length <= 1) { @@ -101,25 +101,25 @@ var p1 = (room.battle && room.battle.p1 && room.battle.p1.name) || ''; var p2 = (room.battle && room.battle.p2 && room.battle.p2.name) || ''; if (p1 && p2) { - name = '' + Tools.escapeHTML(p1) + ' v. ' + Tools.escapeHTML(p2); + name = '' + BattleLog.escapeHTML(p1) + ' v. ' + BattleLog.escapeHTML(p2); } else if (p1 || p2) { - name = '' + Tools.escapeHTML(p1) + Tools.escapeHTML(p2); + name = '' + BattleLog.escapeHTML(p1) + BattleLog.escapeHTML(p2); } else { name = '(empty room)'; } } - return buf + ' draggable="true">' + Tools.escapeFormat(formatid) + '' + name + '

    '; if (app.user.get('named')) { var registered = app.user.get('registered'); @@ -776,7 +776,7 @@ var buf = '
    '; if (data.error) { - buf += '

    ' + Tools.escapeHTML(data.error) + '

    '; + buf += '

    ' + BattleLog.escapeHTML(data.error) + '

    '; if (data.error.indexOf('inappropriate') >= 0) { buf += '

    Keep in mind these rules:

    '; buf += '
      '; @@ -786,14 +786,14 @@ buf += '
    '; } } else if (data.reason) { - buf += '

    ' + Tools.parseMessage(data.reason) + '

    '; + buf += '

    ' + BattleLog.parseMessage(data.reason) + '

    '; } else if (!data.force) { var noRenameGames = ''; if (app.rooms[''].games) { for (var roomid in app.rooms[''].games) { var title = app.rooms[''].games[roomid]; if (title.slice(-1) === '*') { - noRenameGames += '
  • ' + Tools.escapeHTML(title.slice(0, -1)) + '
  • '; + noRenameGames += '
  • ' + BattleLog.escapeHTML(title.slice(0, -1)) + '
  • '; } } } @@ -810,7 +810,7 @@ var name = (data.name || ''); if (!name && app.user.get('named')) name = app.user.get('name'); - buf += '

    '; + buf += '

    '; if (name) { buf += '

    (Others will be able to see your name change. To change name privately, use "Log out")

    '; } @@ -825,7 +825,7 @@ updateColor: function (e) { var name = e.currentTarget.value; var preview = this.$('.preview'); - var css = hashColor(toUserid(name)).slice(6, -1); + var css = BattleLog.hashColor(toUserid(name)).slice(6, -1); preview.css('color', css); }, force: function () { @@ -890,11 +890,11 @@ } else { buf += '

    Register your account:

    '; } - buf += '

    '; + buf += '

    '; buf += '

    '; buf += '

    '; buf += '

    '; - buf += '

    '; + buf += '

    '; buf += '

    '; this.$el.html(buf); }, @@ -933,7 +933,7 @@ var buf = '
    '; if (data.error) { - buf += '

    ' + Tools.escapeHTML(data.error) + '

    '; + buf += '

    ' + BattleLog.escapeHTML(data.error) + '

    '; if (data.error.indexOf(' forced you to change ') >= 0) { buf += '

    Keep in mind these rules:

    '; buf += '
      '; @@ -943,13 +943,13 @@ buf += '
    '; } } else if (data.reason) { - buf += '

    ' + Tools.escapeHTML(data.reason) + '

    '; + buf += '

    ' + BattleLog.escapeHTML(data.reason) + '

    '; } else { buf += '

    The name you chose is registered.

    '; } buf += '

    If this is your account:

    '; - buf += '

    '; + buf += '

    '; if (data.special === '@gmail') { buf += '
    [loading Google log-in button]
    '; buf += '

    '; diff --git a/js/client.js b/js/client.js index 6c85f0855..2df8fa223 100644 --- a/js/client.js +++ b/js/client.js @@ -831,7 +831,7 @@ var replayLink = 'https://replay.pokemonshowdown.com/' + replayid; $.ajax(replayLink + '.json', {dataType: 'json'}).done(function (replay) { if (replay) { - var title = Tools.escapeHTML(replay.p1) + ' vs. ' + Tools.escapeHTML(replay.p2); + var title = BattleLog.escapeHTML(replay.p1) + ' vs. ' + BattleLog.escapeHTML(replay.p2); app.receive('>battle-' + replayid + '\n|init|battle\n|title|' + title + '\n' + replay.log); app.receive('>battle-' + replayid + '\n|expire|Open replay in new tab'); } else { @@ -980,7 +980,7 @@ app.addPopup(Popup, { type: type, maxWidth: maxWidth, - htmlMessage: Tools.sanitizeHTML(data) + htmlMessage: BattleLog.sanitizeHTML(data) }); } else { app.addPopup(Popup, { @@ -993,7 +993,7 @@ break; case 'disconnect': - app.trigger('init:socketclosed', Tools.sanitizeHTML(data.substr(12))); + app.trigger('init:socketclosed', BattleLog.sanitizeHTML(data.substr(12))); break; case 'pm': @@ -1060,7 +1060,7 @@ if (!groupName) Config.defaultGroup = symbol; groups[symbol] = { - name: groupName ? Tools.escapeHTML(groupName + ' (' + symbol + ')') : null, + name: groupName ? BattleLog.escapeHTML(groupName + ' (' + symbol + ')') : null, type: groupType, order: i + 1 }; @@ -2231,7 +2231,7 @@ }, initialize: function (data) { if (!this.type) this.type = 'semimodal'; - this.$el.html('

    ' + (data.htmlMessage || Tools.parseMessage(data.message)) + '

    ' + (data.buttons || '') + '

    ').css('max-width', data.maxWidth || 480); + this.$el.html('

    ' + (data.htmlMessage || BattleLog.parseMessage(data.message)) + '

    ' + (data.buttons || '') + '

    ').css('max-width', data.maxWidth || 480); }, dispatchClickButton: function (e) { @@ -2284,7 +2284,7 @@ var buf = '
    '; buf += '

    '; + buf += '

    '; buf += '

    '; buf += '
    '; @@ -2403,7 +2403,7 @@ var buf = '
    '; if (avatar) buf += ''; - buf += '' + Tools.escapeHTML(name) + '
    '; + buf += '' + BattleLog.escapeHTML(name) + '
    '; buf += '' + (group || ' ') + ''; if (globalgroup) buf += '
    ' + globalgroup + ''; if (data.rooms) { @@ -2421,7 +2421,7 @@ var p1 = data.rooms[i].p1.substr(1); var p2 = data.rooms[i].p2.substr(1); var ownBattle = (ownUserid === toUserid(p1) || ownUserid === toUserid(p2)); - var room = '' + '' + roomrank + roomid.substr(7) + ''; + var room = '' + '' + roomrank + roomid.substr(7) + ''; if (data.rooms[i].isPrivate) { if (!privatebuf) privatebuf = '
    Private rooms: '; else privatebuf += ', '; @@ -2524,7 +2524,7 @@ } var $pm = $('.pm-window-' + this.userid); if ($pm.length && $pm.css('display') !== 'none') { - $pm.find('.inner').append('
    ' + Tools.escapeHTML(buf) + '
    '); + $pm.find('.inner').append('
    ' + BattleLog.escapeHTML(buf) + '
    '); } else { var room = (app.curRoom && app.curRoom.add ? app.curRoom : app.curSideRoom); if (!room || !room.add) { @@ -2619,7 +2619,7 @@ var warning = ('warning' in data); var buf = ''; if (warning) { - buf += '

    ' + (Tools.escapeHTML(data.warning) || 'You have been warned for breaking the rules.') + '

    '; + buf += '

    ' + (BattleLog.escapeHTML(data.warning) || 'You have been warned for breaking the rules.') + '

    '; } buf += '

    Pokémon Showdown Rules

    '; buf += 'Global

    1. Be nice to people. Respect people. Don\'t be rude or mean to people.

    2. Follow US laws (PS is based in the US). No porn (minors use PS), don\'t distribute pirated material, and don\'t slander others.

    3. No sex. Don\'t discuss anything sexually explicit, not even in private messages, not even if you\'re both adults.

    4. No cheating. Don\'t exploit bugs to gain an unfair advantage. Don\'t game the system (by intentionally losing against yourself or a friend in a ladder match, by timerstalling, etc). Don\'t impersonate staff if you\'re not.

    5. Moderators have discretion to punish any behaviour they deem inappropriate, whether or not it\'s on this list. If you disagree with a moderator ruling, appeal to a leader (a user with & next to their name) or Discipline Appeals.

    (Note: The First Amendment does not apply to PS, since PS is not a government organization.)

    '; diff --git a/js/replay-embed.js b/js/replay-embed.js index 8b0d6d670..a9516dbb5 100644 --- a/js/replay-embed.js +++ b/js/replay-embed.js @@ -125,7 +125,7 @@ var Replays = { var replayid = this.$('input[name=replayid]').val(); var m = /^([a-z0-9]+)-[a-z0-9]+-[0-9]+$/.exec(replayid); if (m) { - this.battle.log('
    This replay was uploaded from a third-party server (' + Tools.escapeHTML(m[1]) + '). It contains errors and cannot be viewed.
    Replays uploaded from third-party servers can contain errors if the server is running custom code, or the server operator has otherwise incorrectly configured their server.
    ', true); + this.battle.log('
    This replay was uploaded from a third-party server (' + BattleLog.escapeHTML(m[1]) + '). It contains errors and cannot be viewed.
    Replays uploaded from third-party servers can contain errors if the server is running custom code, or the server operator has otherwise incorrectly configured their server.
    ', true); this.battle.pause(); } }, diff --git a/js/search.js b/js/search.js index 24c882f22..b78e418c8 100644 --- a/js/search.js +++ b/js/search.js @@ -541,7 +541,7 @@ var text = this.filters[i][1]; if (this.filters[i][0] === 'move') text = Tools.getMove(text).name; if (this.filters[i][0] === 'pokemon') text = Tools.getTemplate(text).name; - buf += ' '; + buf += ' '; } if (!q) buf += '(backspace = delete filter)'; return buf + '

    '; @@ -878,7 +878,7 @@ template = Tools.getTemplate(set.species); var abilitySet = [['header', "Abilities"]]; if (template.isMega) { - abilitySet.unshift(['html', '

    Will be ' + Tools.escapeHTML(template.abilities['0']) + ' after Mega Evolving.

    ']); + abilitySet.unshift(['html', '

    Will be ' + BattleLog.escapeHTML(template.abilities['0']) + ' after Mega Evolving.

    ']); template = Tools.getTemplate(template.baseSpecies); } var abilitiesInThisGen = Tools.getAbilitiesFor(template.id, this.gen); @@ -898,7 +898,7 @@ template = Tools.getTemplate(set.species); var abilities = []; if (template.isMega) { - if (format === 'almostanyability') abilitySet.unshift(['html', '

    Will be ' + Tools.escapeHTML(template.abilities['0']) + ' after Mega Evolving.

    ']); + if (format === 'almostanyability') abilitySet.unshift(['html', '

    Will be ' + BattleLog.escapeHTML(template.abilities['0']) + ' after Mega Evolving.

    ']); // template is unused after this, so no need to replace } for (var i in BattleAbilities) { @@ -1290,7 +1290,7 @@ if (!pokemon) return '
  • Unrecognized pokemon
  • '; var id = toId(pokemon.species); if (Search.urlRoot) attrs += ' href="' + Search.urlRoot + 'pokemon/' + id + '" data-target="push"'; - var buf = '
  • '; + var buf = '
  • '; // number // buf += '' + (pokemon.num >= 0 ? pokemon.num : 'CAP') + ' '; @@ -1414,7 +1414,7 @@ Search.prototype.renderTaggedPokemonRowInner = function (pokemon, tag, errorMessage) { var attrs = ''; if (Search.urlRoot) attrs = ' href="' + Search.urlRoot + 'pokemon/' + toId(pokemon.species) + '" data-target="push"'; - var buf = ''; + var buf = ''; // tag buf += '' + tag + ' '; @@ -1485,7 +1485,7 @@ if (!item) return '
  • Unrecognized item
  • '; var id = toId(item.name); if (Search.urlRoot) attrs += ' href="' + Search.urlRoot + 'items/' + id + '" data-target="push"'; - var buf = '
  • '; + var buf = '
  • '; // icon buf += ''; @@ -1515,7 +1515,7 @@ } } } - buf += '' + Tools.escapeHTML(desc) + ' '; + buf += '' + BattleLog.escapeHTML(desc) + ' '; buf += '
  • '; @@ -1526,7 +1526,7 @@ if (!ability) return '
  • Unrecognized ability
  • '; var id = toId(ability.name); if (Search.urlRoot) attrs += ' href="' + Search.urlRoot + 'abilities/' + id + '" data-target="push"'; - var buf = '
  • '; + var buf = '
  • '; // name var name = ability.name; @@ -1541,7 +1541,7 @@ return buf; } - buf += '' + Tools.escapeHTML(ability.shortDesc || ability.desc) + ' '; + buf += '' + BattleLog.escapeHTML(ability.shortDesc || ability.desc) + ' '; buf += '
  • '; @@ -1552,7 +1552,7 @@ if (!move) return '
  • Unrecognized move
  • '; var id = toId(move.name); if (Search.urlRoot) attrs += ' href="' + Search.urlRoot + 'moves/' + id + '" data-target="push"'; - var buf = '
  • '; + var buf = '
  • '; // name var name = move.name; @@ -1614,7 +1614,7 @@ } } } - buf += '' + Tools.escapeHTML(desc) + ' '; + buf += '' + BattleLog.escapeHTML(desc) + ' '; buf += '
  • '; @@ -1623,7 +1623,7 @@ Search.prototype.renderMoveRowInner = function (move, errorMessage) { var attrs = ''; if (Search.urlRoot) attrs = ' href="' + Search.urlRoot + 'moves/' + toId(move.name) + '" data-target="push"'; - var buf = ''; + var buf = ''; // name var name = move.name; @@ -1649,7 +1649,7 @@ buf += 'PP
    ' + (move.pp === 1 || move.noPPBoosts ? move.pp : move.pp * 8 / 5) + '
    '; // desc - buf += '' + Tools.escapeHTML(move.shortDesc || move.desc) + ' '; + buf += '' + BattleLog.escapeHTML(move.shortDesc || move.desc) + ' '; buf += ''; @@ -1658,7 +1658,7 @@ Search.prototype.renderTaggedMoveRow = function (move, tag, errorMessage) { var attrs = ''; if (Search.urlRoot) attrs = ' href="' + Search.urlRoot + 'moves/' + toId(move.name) + '" data-target="push"'; - var buf = '
  • '; + var buf = '
  • '; // tag buf += '' + tag + ' '; @@ -1686,7 +1686,7 @@ buf += 'PP
    ' + (move.pp !== 1 ? move.pp * 8 / 5 : move.pp) + '
    '; // desc - buf += '' + Tools.escapeHTML(move.shortDesc || move.desc) + ' '; + buf += '' + BattleLog.escapeHTML(move.shortDesc || move.desc) + ' '; buf += '
  • '; @@ -1696,7 +1696,7 @@ Search.prototype.renderTypeRow = function (type, matchStart, matchLength, errorMessage) { var attrs = ''; if (Search.urlRoot) attrs = ' href="' + Search.urlRoot + 'types/' + toId(type.name) + '" data-target="push"'; - var buf = '
  • '; + var buf = '
  • '; // name var name = type.name; @@ -1723,7 +1723,7 @@ Search.prototype.renderCategoryRow = function (category, matchStart, matchLength, errorMessage) { var attrs = ''; if (Search.urlRoot) attrs = ' href="' + Search.urlRoot + 'categories/' + category.id + '" data-target="push"'; - var buf = '
  • '; + var buf = '
  • '; // name var name = category.name; @@ -1752,7 +1752,7 @@ if (Search.urlRoot) attrs = ' href="' + Search.urlRoot + 'articles/' + article.id + '" data-target="push"'; var isSearchType = (article.id === 'pokemon' || article.id === 'moves'); if (isSearchType) attrs = ' href="' + article.id + '/" data-target="replace"'; - var buf = '
  • '; + var buf = '
  • '; // name var name = article.name; @@ -1781,7 +1781,7 @@ Search.prototype.renderEggGroupRow = function (egggroup, matchStart, matchLength, errorMessage) { var attrs = ''; if (Search.urlRoot) attrs = ' href="' + Search.urlRoot + 'egggroups/' + toId(egggroup.name) + '" data-target="push"'; - var buf = '
  • '; + var buf = '
  • '; // name var name = egggroup.name; @@ -1803,7 +1803,7 @@ Search.prototype.renderTierRow = function (tier, matchStart, matchLength, errorMessage) { var attrs = ''; if (Search.urlRoot) attrs = ' href="' + Search.urlRoot + 'tiers/' + toId(tier.name) + '" data-target="push"'; - var buf = '
  • '; + var buf = '
  • '; // name var name = tier.name; diff --git a/js/storage.js b/js/storage.js index 55047837f..c616348d3 100644 --- a/js/storage.js +++ b/js/storage.js @@ -486,10 +486,10 @@ Storage.initTestClient = function () { if (uri[0] === '/') { //relative URI uri = Tools.resourcePrefix + uri.substr(1); } - var src = '
    '; + var src = ''; src += ''; for (var i in data) { - src += ''; + src += ''; } src += '
    '; app.addPopup(ProxyPopup, {uri: "data:text/html;charset=UTF-8," + encodeURIComponent(src), callback: callback}); @@ -529,7 +529,7 @@ Storage.loadPackedTeams = function (buffer) { Storage.whenAppLoaded(function (app) { app.addPopup(Popup, { type: 'modal', - htmlMessage: "Your teams are corrupt and could not be loaded. :( We may be able to recover a team from this data:
    " + htmlMessage: "Your teams are corrupt and could not be loaded. :( We may be able to recover a team from this data:
    " }); }); } diff --git a/src/battle-animations.ts b/src/battle-animations.ts index 54cbf97a4..14ea1f576 100644 --- a/src/battle-animations.ts +++ b/src/battle-animations.ts @@ -657,7 +657,7 @@ class BattleScene { pokemonhtml = '
    ' + pokemonhtml + '
    '; const $sidebar = (side.n ? this.$rightbar : this.$leftbar); if (side.name) { - $sidebar.html('
    ' + Tools.escapeHTML(side.name) + '
    ' + pokemonhtml + '
    '); + $sidebar.html('
    ' + BattleLog.escapeHTML(side.name) + '
    ' + pokemonhtml + '
    '); $sidebar.find('.trainer').css('opacity', 1); } else { $sidebar.find('.trainer').css('opacity', 0.4); @@ -729,7 +729,7 @@ class BattleScene { } side.totalPokemon = side.pokemon.length; if (textBuf) { - this.log('
    ' + Tools.escapeHTML(side.name) + '\'s team: ' + Tools.escapeHTML(textBuf) + '
    '); + this.log('
    ' + BattleLog.escapeHTML(side.name) + '\'s team: ' + BattleLog.escapeHTML(textBuf) + '
    '); } this.$sprites[siden].html(buf + buf2); @@ -2270,7 +2270,7 @@ class PokemonSprite extends Sprite { getStatbarHTML(pokemon: Pokemon) { let buf = '
  • '; - let template = Tools.getTemplate(target); - if (!template.abilities || !template.baseStats) return '[not supported in replays]'; - buf += '' + (template.tier || Tools.getTemplate(template.baseSpecies).tier) + ' '; - buf += ' '; - buf += '' + template.species + ' '; - buf += ''; - if (template.types) for (let i = 0; i < template.types.length; i++) { - buf += Tools.getTypeIcon(template.types[i]); - } - buf += ' '; - buf += ''; - if (template.abilities['1']) { - buf += '' + template.abilities['0'] + '
    ' + template.abilities['1'] + '
    '; - } else { - buf += '' + template.abilities['0'] + ''; - } - if (template.abilities['S']) { - buf += '' + template.abilities['H'] + '
    ' + template.abilities['S'] + '
    '; - } else if (template.abilities['H']) { - buf += '' + template.abilities['H'] + ''; - } else { - buf += ''; - } - buf += '
    '; - buf += ''; - buf += 'HP
    ' + template.baseStats.hp + '
    '; - buf += 'Atk
    ' + template.baseStats.atk + '
    '; - buf += 'Def
    ' + template.baseStats.def + '
    '; - buf += 'SpA
    ' + template.baseStats.spa + '
    '; - buf += 'SpD
    ' + template.baseStats.spd + '
    '; - buf += 'Spe
    ' + template.baseStats.spe + '
    '; - let bst = 0; - for (const i in template.baseStats) bst += template.baseStats[i as StatName]; - buf += 'BST
    ' + bst + '
    '; - buf += '
    '; - buf += '
  • '; - return '
      ' + buf + '
    '; - case 'data-item': - if (!window.BattleSearch) return '[not supported in replays]'; - return '
      ' + BattleSearch.renderItemRow(Tools.getItem(target), 0, 0) + '
    '; - case 'data-ability': - if (!window.BattleSearch) return '[not supported in replays]'; - return '
      ' + BattleSearch.renderAbilityRow(Tools.getAbility(target), 0, 0) + '
    '; - case 'data-move': - if (!window.BattleSearch) return '[not supported in replays]'; - return '
      ' + BattleSearch.renderMoveRow(Tools.getMove(target), 0, 0) + '
    '; - case 'text': - return '
    ' + Tools.parseMessage(target) + '
    '; - case 'error': - return '
    ' + Tools.escapeHTML(target) + '
    '; - case 'html': - return '
    ' + timestamp + '' + clickableName + ': ' + Tools.sanitizeHTML(target) + '
    '; - case 'uhtml': - case 'uhtmlchange': - let parts = target.split(','); - let $elements = $chatElem.find('div.uhtml-' + toId(parts[0])); - let html = parts.slice(1).join(','); - if (!html) { - $elements.remove(); - } else if (!$elements.length) { - $chatElem.append('
    ' + Tools.sanitizeHTML(html) + '
    '); - } else if (cmd === 'uhtmlchange') { - $elements.html(Tools.sanitizeHTML(html)); - } else { - $elements.remove(); - $chatElem.append('
    ' + Tools.sanitizeHTML(html) + '
    '); - } - return ''; - case 'raw': - return '
    ' + Tools.sanitizeHTML(target) + '
    '; - default: - // Not a command or unsupported. Parsed as a normal chat message. - if (!name) { - return '
    ' + timestamp + '' + Tools.parseMessage(message) + '
    '; - } - return '
    ' + timestamp + '' + clickableName + ': ' + Tools.parseMessage(message) + '
    '; - } - }, - - parseMessage(str: string) { - // Don't format console commands (>>). - if (str.substr(0, 3) === '>> ' || str.substr(0, 4) === '>>> ') return Tools.escapeHTML(str); - // Don't format console results (<<). - if (str.substr(0, 3) === '<< ') return Tools.escapeHTML(str); - str = formatText(str); - - let options = Tools.prefs('chatformatting') || {}; - - if (options.hidelinks) { - str = str.replace(/]*>/g, '').replace(/<\/a>/g, ''); - } - if (options.hidespoiler) { - str = str.replace(//g, ''); - } - if (options.hidegreentext) { - str = str.replace(//g, ''); - } - - return str; - }, - - escapeHTML(str: string, jsEscapeToo?: boolean) { - str = getString(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); - if (jsEscapeToo) str = str.replace(/\\/g, '\\\\').replace(/'/g, '\\\''); - return str; - }, - - unescapeHTML(str: string) { - str = (str ? '' + str : ''); - return str.replace(/"/g, '"').replace(/>/g, '>').replace(/</g, '<').replace(/&/g, '&'); - }, - - escapeRegExp(str: string) { - return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); - }, - - sanitizeHTML: (function () { - if (!('html4' in window)) { - return function () { - throw new Error('sanitizeHTML requires caja'); - }; - } - // Add to the whitelist. - Object.assign(html4.ELEMENTS, { - 'marquee': 0, - 'blink': 0, - 'psicon': html4.eflags['OPTIONAL_ENDTAG'] | html4.eflags['EMPTY'] - }); - Object.assign(html4.ATTRIBS, { - // See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/marquee - 'marquee::behavior': 0, - 'marquee::bgcolor': 0, - 'marquee::direction': 0, - 'marquee::height': 0, - 'marquee::hspace': 0, - 'marquee::loop': 0, - 'marquee::scrollamount': 0, - 'marquee::scrolldelay': 0, - 'marquee::truespeed': 0, - 'marquee::vspace': 0, - 'marquee::width': 0, - 'psicon::pokemon': 0, - 'psicon::item': 0 - }); - - let uriRewriter = function (urlData: any) { - if (urlData.scheme_ === 'geo' || urlData.scheme_ === 'sms' || urlData.scheme_ === 'tel') return null; - return urlData; - }; - let tagPolicy = function (tagName: string, attribs: string[]) { - if (html4.ELEMENTS[tagName] & html4.eflags['UNSAFE']) { - return; - } - let targetIdx = 0, srcIdx = 0; - if (tagName === 'a') { - // Special handling of tags. - - for (let i = 0; i < attribs.length - 1; i += 2) { - switch (attribs[i]) { - case 'target': - targetIdx = i + 1; - break; - } - } - } - let dataUri = ''; - if (tagName === 'img') { - for (let i = 0; i < attribs.length - 1; i += 2) { - if (attribs[i] === 'src' && attribs[i + 1].substr(0, 11) === 'data:image/') { - srcIdx = i; - dataUri = attribs[i + 1]; - } - if (attribs[i] === 'src' && attribs[i + 1].substr(0, 2) === '//') { - if (location.protocol !== 'http:' && location.protocol !== 'https:') { - attribs[i + 1] = 'http:' + attribs[i + 1]; - } - } - } - } else if (tagName === 'psicon') { - // is a custom element which supports a set of mutually incompatible attributes: - // and - let classValueIndex = -1; - let styleValueIndex = -1; - let iconAttrib = null; - for (let i = 0; i < attribs.length - 1; i += 2) { - if (attribs[i] === 'pokemon' || attribs[i] === 'item') { - // If declared more than once, use the later. - iconAttrib = attribs.slice(i, i + 2); - } else if (attribs[i] === 'class') { - classValueIndex = i + 1; - } else if (attribs[i] === 'style') { - styleValueIndex = i + 1; - } - } - tagName = 'span'; - - if (iconAttrib) { - if (classValueIndex < 0) { - attribs.push('class', ''); - classValueIndex = attribs.length - 1; - } - if (styleValueIndex < 0) { - attribs.push('style', ''); - styleValueIndex = attribs.length - 1; - } - - // Prepend all the classes and styles associated to the custom element. - if (iconAttrib[0] === 'pokemon') { - attribs[classValueIndex] = attribs[classValueIndex] ? 'picon ' + attribs[classValueIndex] : 'picon'; - attribs[styleValueIndex] = attribs[styleValueIndex] ? Tools.getPokemonIcon(iconAttrib[1]) + '; ' + attribs[styleValueIndex] : Tools.getPokemonIcon(iconAttrib[1]); - } else if (iconAttrib[0] === 'item') { - attribs[classValueIndex] = attribs[classValueIndex] ? 'itemicon ' + attribs[classValueIndex] : 'itemicon'; - attribs[styleValueIndex] = attribs[styleValueIndex] ? Tools.getItemIcon(iconAttrib[1]) + '; ' + attribs[styleValueIndex] : Tools.getItemIcon(iconAttrib[1]); - } - } - } - - if (attribs[targetIdx] === 'replace') { - targetIdx = -targetIdx; - } - attribs = html.sanitizeAttribs(tagName, attribs, uriRewriter); - if (targetIdx < 0) { - targetIdx = -targetIdx; - attribs[targetIdx - 1] = 'data-target'; - attribs[targetIdx] = 'replace'; - targetIdx = 0; - } - - if (dataUri && tagName === 'img') { - attribs[srcIdx + 1] = dataUri; - } - if (tagName === 'a' || tagName === 'form') { - if (targetIdx) { - attribs[targetIdx] = '_blank'; - } else { - attribs.push('target'); - attribs.push('_blank'); - } - if (tagName === 'a') { - attribs.push('rel'); - attribs.push('noopener'); - } - } - return {tagName: tagName, attribs: attribs}; - }; - let localizeTime = function (full: string, date: string, time: string, timezone?: string) { - let parsedTime = new Date(date + 'T' + time + (timezone || 'Z').toUpperCase()); - // Very old (pre-ES5) web browsers may be incapable of parsing ISO 8601 - // dates. In such a case, gracefully continue without replacing the date - // format. - if (!parsedTime.getTime()) return full; - - let formattedTime; - // Try using Intl API if it exists - if (window.Intl && Intl.DateTimeFormat) { - formattedTime = new Intl.DateTimeFormat(undefined, {month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric'}).format(parsedTime); - } else { - // toLocaleString even exists in ECMAScript 1, so no need to check - // if it exists. - formattedTime = parsedTime.toLocaleString(); - } - return ''; - }; - return function (input: any) { - //
  • '; + let template = Tools.getTemplate(target); + if (!template.abilities || !template.baseStats) return '[not supported in replays]'; + buf += '' + (template.tier || Tools.getTemplate(template.baseSpecies).tier) + ' '; + buf += ' '; + buf += '' + template.species + ' '; + buf += ''; + if (template.types) for (let i = 0; i < template.types.length; i++) { + buf += Tools.getTypeIcon(template.types[i]); + } + buf += ' '; + buf += ''; + if (template.abilities['1']) { + buf += '' + template.abilities['0'] + '
    ' + template.abilities['1'] + '
    '; + } else { + buf += '' + template.abilities['0'] + ''; + } + if (template.abilities['S']) { + buf += '' + template.abilities['H'] + '
    ' + template.abilities['S'] + '
    '; + } else if (template.abilities['H']) { + buf += '' + template.abilities['H'] + ''; + } else { + buf += ''; + } + buf += '
    '; + buf += ''; + buf += 'HP
    ' + template.baseStats.hp + '
    '; + buf += 'Atk
    ' + template.baseStats.atk + '
    '; + buf += 'Def
    ' + template.baseStats.def + '
    '; + buf += 'SpA
    ' + template.baseStats.spa + '
    '; + buf += 'SpD
    ' + template.baseStats.spd + '
    '; + buf += 'Spe
    ' + template.baseStats.spe + '
    '; + let bst = 0; + for (const i in template.baseStats) bst += template.baseStats[i as StatName]; + buf += 'BST
    ' + bst + '
    '; + buf += '
    '; + buf += '
  • '; + return '
      ' + buf + '
    '; + case 'data-item': + if (!window.BattleSearch) return '[not supported in replays]'; + return '
      ' + BattleSearch.renderItemRow(Tools.getItem(target), 0, 0) + '
    '; + case 'data-ability': + if (!window.BattleSearch) return '[not supported in replays]'; + return '
      ' + BattleSearch.renderAbilityRow(Tools.getAbility(target), 0, 0) + '
    '; + case 'data-move': + if (!window.BattleSearch) return '[not supported in replays]'; + return '
      ' + BattleSearch.renderMoveRow(Tools.getMove(target), 0, 0) + '
    '; + case 'text': + return '
    ' + this.parseMessage(target) + '
    '; + case 'error': + return '
    ' + this.escapeHTML(target) + '
    '; + case 'html': + return '
    ' + timestamp + '' + clickableName + ': ' + BattleLog.sanitizeHTML(target) + '
    '; + case 'uhtml': + case 'uhtmlchange': + let parts = target.split(','); + let $elements = $chatElem.find('div.uhtml-' + toId(parts[0])); + let html = parts.slice(1).join(','); + if (!html) { + $elements.remove(); + } else if (!$elements.length) { + $chatElem.append('
    ' + BattleLog.sanitizeHTML(html) + '
    '); + } else if (cmd === 'uhtmlchange') { + $elements.html(BattleLog.sanitizeHTML(html)); + } else { + $elements.remove(); + $chatElem.append('
    ' + BattleLog.sanitizeHTML(html) + '
    '); + } + return ''; + case 'raw': + return '
    ' + BattleLog.sanitizeHTML(target) + '
    '; + default: + // Not a command or unsupported. Parsed as a normal chat message. + if (!name) { + return '
    ' + timestamp + '' + this.parseMessage(message) + '
    '; + } + return '
    ' + timestamp + '' + clickableName + ': ' + this.parseMessage(message) + '
    '; + } + } + + static parseMessage(str: string) { + // Don't format console commands (>>). + if (str.substr(0, 3) === '>> ' || str.substr(0, 4) === '>>> ') return this.escapeHTML(str); + // Don't format console results (<<). + if (str.substr(0, 3) === '<< ') return this.escapeHTML(str); + str = formatText(str); + + let options = Tools.prefs('chatformatting') || {}; + + if (options.hidelinks) { + str = str.replace(/]*>/g, '').replace(/<\/a>/g, ''); + } + if (options.hidespoiler) { + str = str.replace(//g, ''); + } + if (options.hidegreentext) { + str = str.replace(//g, ''); + } + + return str; + } + + static interstice = (() => { + const whitelist: string[] = (window.Config && Config.whitelist) ? Config.whitelist : []; + const patterns = whitelist.map(entry => new RegExp( + `^(https?:)?//([A-Za-z0-9-]*\\.)?${entry}(/.*)?`, + 'i')); + return { + isWhitelisted(uri: string) { + if (uri[0] === '/' && uri[1] !== '/') { + // domain-relative URIs are safe + return true; + } + for (const pattern of patterns) { + if (pattern.test(uri)) return true; + } + return false; + }, + getURI(uri: string) { + return 'http://pokemonshowdown.com/interstice?uri=' + encodeURIComponent(uri); + }, + }; + })(); + + static tagPolicy: (tagName: string, attribs: string[]) => any = null!; + static initSanitizeHTML() { + if (this.tagPolicy) return; + if (!('html4' in window)) { + throw new Error('sanitizeHTML requires caja'); + } + // Add to the whitelist. + Object.assign(html4.ELEMENTS, { + 'marquee': 0, + 'blink': 0, + 'psicon': html4.eflags['OPTIONAL_ENDTAG'] | html4.eflags['EMPTY'] + }); + Object.assign(html4.ATTRIBS, { + // See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/marquee + 'marquee::behavior': 0, + 'marquee::bgcolor': 0, + 'marquee::direction': 0, + 'marquee::height': 0, + 'marquee::hspace': 0, + 'marquee::loop': 0, + 'marquee::scrollamount': 0, + 'marquee::scrolldelay': 0, + 'marquee::truespeed': 0, + 'marquee::vspace': 0, + 'marquee::width': 0, + 'psicon::pokemon': 0, + 'psicon::item': 0 + }); + + this.tagPolicy = (tagName: string, attribs: string[]) => { + if (html4.ELEMENTS[tagName] & html4.eflags['UNSAFE']) { + return; + } + let targetIdx = 0, srcIdx = 0; + if (tagName === 'a') { + // Special handling of tags. + + for (let i = 0; i < attribs.length - 1; i += 2) { + switch (attribs[i]) { + case 'target': + targetIdx = i + 1; + break; + } + } + } + let dataUri = ''; + if (tagName === 'img') { + for (let i = 0; i < attribs.length - 1; i += 2) { + if (attribs[i] === 'src' && attribs[i + 1].substr(0, 11) === 'data:image/') { + srcIdx = i; + dataUri = attribs[i + 1]; + } + if (attribs[i] === 'src' && attribs[i + 1].substr(0, 2) === '//') { + if (location.protocol !== 'http:' && location.protocol !== 'https:') { + attribs[i + 1] = 'http:' + attribs[i + 1]; + } + } + } + } else if (tagName === 'psicon') { + // is a custom element which supports a set of mutually incompatible attributes: + // and + let classValueIndex = -1; + let styleValueIndex = -1; + let iconAttrib = null; + for (let i = 0; i < attribs.length - 1; i += 2) { + if (attribs[i] === 'pokemon' || attribs[i] === 'item') { + // If declared more than once, use the later. + iconAttrib = attribs.slice(i, i + 2); + } else if (attribs[i] === 'class') { + classValueIndex = i + 1; + } else if (attribs[i] === 'style') { + styleValueIndex = i + 1; + } + } + tagName = 'span'; + + if (iconAttrib) { + if (classValueIndex < 0) { + attribs.push('class', ''); + classValueIndex = attribs.length - 1; + } + if (styleValueIndex < 0) { + attribs.push('style', ''); + styleValueIndex = attribs.length - 1; + } + + // Prepend all the classes and styles associated to the custom element. + if (iconAttrib[0] === 'pokemon') { + attribs[classValueIndex] = attribs[classValueIndex] ? 'picon ' + attribs[classValueIndex] : 'picon'; + attribs[styleValueIndex] = attribs[styleValueIndex] ? Tools.getPokemonIcon(iconAttrib[1]) + '; ' + attribs[styleValueIndex] : Tools.getPokemonIcon(iconAttrib[1]); + } else if (iconAttrib[0] === 'item') { + attribs[classValueIndex] = attribs[classValueIndex] ? 'itemicon ' + attribs[classValueIndex] : 'itemicon'; + attribs[styleValueIndex] = attribs[styleValueIndex] ? Tools.getItemIcon(iconAttrib[1]) + '; ' + attribs[styleValueIndex] : Tools.getItemIcon(iconAttrib[1]); + } + } + } + + if (attribs[targetIdx] === 'replace') { + targetIdx = -targetIdx; + } + attribs = html.sanitizeAttribs(tagName, attribs, (urlData: any) => { + if (urlData.scheme_ === 'geo' || urlData.scheme_ === 'sms' || urlData.scheme_ === 'tel') return null; + return urlData; + }); + if (targetIdx < 0) { + targetIdx = -targetIdx; + attribs[targetIdx - 1] = 'data-target'; + attribs[targetIdx] = 'replace'; + targetIdx = 0; + } + + if (dataUri && tagName === 'img') { + attribs[srcIdx + 1] = dataUri; + } + if (tagName === 'a' || tagName === 'form') { + if (targetIdx) { + attribs[targetIdx] = '_blank'; + } else { + attribs.push('target'); + attribs.push('_blank'); + } + if (tagName === 'a') { + attribs.push('rel'); + attribs.push('noopener'); + } + } + return {tagName: tagName, attribs: attribs}; + }; + } + static localizeTime(full: string, date: string, time: string, timezone?: string) { + let parsedTime = new Date(date + 'T' + time + (timezone || 'Z').toUpperCase()); + // Very old (pre-ES5) web browsers may be incapable of parsing ISO 8601 + // dates. In such a case, gracefully continue without replacing the date + // format. + if (!parsedTime.getTime()) return full; + + let formattedTime; + // Try using Intl API if it exists + if (window.Intl && Intl.DateTimeFormat) { + formattedTime = new Intl.DateTimeFormat(undefined, {month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric'}).format(parsedTime); + } else { + // toLocaleString even exists in ECMAScript 1, so no need to check + // if it exists. + formattedTime = parsedTime.toLocaleString(); + } + return ''; + } + static sanitizeHTML(input: string) { + this.initSanitizeHTML(); + const sanitized = html.sanitizeWithPolicy(getString(input), this.tagPolicy) as string; + //
    ' + Tools.escapeHTML(args[1]) + ': (Private to ' + Tools.escapeHTML(args[3]) + ') ' + Tools.parseMessage(args[4]) + ''); + this.scene.log('
    ' + BattleLog.escapeHTML(args[1]) + ': (Private to ' + BattleLog.escapeHTML(args[3]) + ') ' + BattleLog.parseMessage(args[4]) + ''); break; } case 'askreg': { - this.scene.log('
    Register an account to protect your ladder rating!
    '); + this.scene.log('
    Register an account to protect your ladder rating!
    '); break; } case 'inactive': { if (!this.kickingInactive) this.kickingInactive = true; @@ -4469,11 +4469,11 @@ class Battle { this.kickingInactive = parseInt(args[1].slice(hasIndex + 5), 10) || true; } } - this.scene.log('
    ' + Tools.escapeHTML(args[1]) + '
    ', preempt); + this.scene.log('
    ' + BattleLog.escapeHTML(args[1]) + '
    ', preempt); break; } case 'inactiveoff': { this.kickingInactive = false; - this.scene.log('
    ' + Tools.escapeHTML(args[1]) + '
    ', preempt); + this.scene.log('
    ' + BattleLog.escapeHTML(args[1]) + '
    ', preempt); break; } case 'timer': { break; @@ -4490,7 +4490,7 @@ class Battle { room.userList.updateNoUsersOnline(); } if (!this.ignoreSpects) { - this.scene.log('
    ' + Tools.escapeHTML(args[1]) + ' joined.
    ', preempt); + this.scene.log('
    ' + BattleLog.escapeHTML(args[1]) + ' joined.
    ', preempt); } break; } case 'leave': case 'l': { @@ -4505,7 +4505,7 @@ class Battle { room.userList.updateNoUsersOnline(); } if (!this.ignoreSpects) { - this.scene.log('
    ' + Tools.escapeHTML(args[1]) + ' left.
    ', preempt); + this.scene.log('
    ' + BattleLog.escapeHTML(args[1]) + ' left.
    ', preempt); } break; } case 'J': case 'L': case 'N': case 'n': case 'spectator': case 'spectatorleave': { @@ -4637,16 +4637,16 @@ class Battle { this.cantUseMove(poke, effect, move, kwargs); break; } case 'message': { - this.message(Tools.escapeHTML(args[1])); + this.message(BattleLog.escapeHTML(args[1])); break; } case 'bigerror': { - this.message('
    ' + Tools.escapeHTML(args[1]).replace(/\|/g, '
    ') + '
    '); + this.message('
    ' + BattleLog.escapeHTML(args[1]).replace(/\|/g, '
    ') + '
    '); break; } case 'done': case '': { if (this.ended || this.endPrevAction()) return; break; } case 'warning': { - this.message('Warning: ' + Tools.escapeHTML(args[1])); + this.message('Warning: ' + BattleLog.escapeHTML(args[1])); this.message('Bug? Report it to
    the replay viewer\'s Smogon thread'); this.scene.wait(1000); break; @@ -4661,7 +4661,7 @@ class Battle { } case 'debug': { args.shift(); const name = args.join(' '); - this.scene.log('
    [DEBUG] ' + Tools.escapeHTML(name) + '.
    ', preempt); + this.scene.log('
    [DEBUG] ' + BattleLog.escapeHTML(name) + '.
    ', preempt); break; } case 'seed': case 'choice': { break; @@ -4671,13 +4671,13 @@ class Battle { break; } case 'fieldhtml': { this.playbackState = Playback.Seeking; // force seeking to prevent controls etc - this.scene.setFrameHTML(Tools.sanitizeHTML(args[1])); + this.scene.setFrameHTML(BattleLog.sanitizeHTML(args[1])); break; } case 'controlshtml': { - this.scene.setControlsHTML(Tools.sanitizeHTML(args[1])); + this.scene.setControlsHTML(BattleLog.sanitizeHTML(args[1])); break; } default: { - this.scene.log('
    Unknown command: ' + Tools.escapeHTML(args[0]) + '
    '); + this.scene.log('
    Unknown command: ' + BattleLog.escapeHTML(args[0]) + '
    '); if (this.errorCallback) this.errorCallback(this); break; }} @@ -4691,7 +4691,7 @@ class Battle { if (!str) return; if (str.charAt(0) !== '|' || str.substr(0, 2) === '||') { if (str.charAt(0) === '|') str = str.substr(2); - this.scene.log('
    ' + Tools.escapeHTML(str) + '
    ', preempt); + this.scene.log('
    ' + BattleLog.escapeHTML(str) + '
    ', preempt); return; } let args = ['done']; @@ -4751,9 +4751,9 @@ class Battle { this.runMajor(args, kwargs, preempt); } } catch (e) { - this.scene.log('
    Error parsing: ' + Tools.escapeHTML(str) + ' (' + Tools.escapeHTML('' + e) + ')
    ', preempt); + this.scene.log('
    Error parsing: ' + BattleLog.escapeHTML(str) + ' (' + BattleLog.escapeHTML('' + e) + ')
    ', preempt); if (e.stack) { - let stack = Tools.escapeHTML('' + e.stack).split('\n'); + let stack = BattleLog.escapeHTML('' + e.stack).split('\n'); for (let i = 0; i < stack.length; i++) { if (/\brun\b/.test(stack[i])) { stack.length = i; diff --git a/src/globals.d.ts b/src/globals.d.ts index 17d67c8a6..8c649bbb8 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -29,7 +29,7 @@ declare var BattleStatuses: any; declare var BattlePokemonSprites: any; declare var BattlePokemonSpritesBW: any; -// defined in battle-dex-misc +// defined in battle-log-misc declare function MD5(input: string): string; declare function formatText(input: string): string; diff --git a/test/battle-test.mocha.js b/test/battle-test.mocha.js index 964be3237..f3c62d686 100644 --- a/test/battle-test.mocha.js +++ b/test/battle-test.mocha.js @@ -8,6 +8,7 @@ window = global; // Without making these modules, the best we can do is directly include them into this workspace. eval('' + fs.readFileSync(`js/battle-scene-stub.js`)); +eval('' + fs.readFileSync(`js/battle-log.js`)); eval('' + fs.readFileSync(`js/battle-dex.js`)); eval('' + fs.readFileSync(`js/battle-dex-data.js`)); eval('' + fs.readFileSync(`js/battle.js`));