From edbdbd3749587ca1d973dddc4d46c5b34dedae49 Mon Sep 17 00:00:00 2001 From: Guangcong Luo Date: Thu, 15 Nov 2018 23:17:53 -0600 Subject: [PATCH] Use BattleLog for chat logs --- src/battle-log.ts | 2 - src/panel-chat.tsx | 207 +------------------------------------------ testclient-beta.html | 3 + 3 files changed, 7 insertions(+), 205 deletions(-) diff --git a/src/battle-log.ts b/src/battle-log.ts index 764bd31452..bc0fd9075f 100644 --- a/src/battle-log.ts +++ b/src/battle-log.ts @@ -763,5 +763,3 @@ class BattleLog { return 'data:text/plain;base64,' + encodeURIComponent(btoa(unescape(encodeURIComponent(this.createReplayFile(room))))); } } - -exports.BattleLog = BattleLog; diff --git a/src/panel-chat.tsx b/src/panel-chat.tsx index 1a0d1a9351..7f8a80661b 100644 --- a/src/panel-chat.tsx +++ b/src/panel-chat.tsx @@ -31,44 +31,15 @@ class ChatPanel extends preact.Component<{style: {}, room: PSRoom}> { } class ChatLog extends preact.Component<{class: string, room: PSRoom}> { - atBottom = true; - innerElem: HTMLDivElement = null!; - onScroll = (e: UIEvent) => { - const distanceFromBottom = this.base!.scrollHeight - this.base!.scrollTop - this.base!.clientHeight; - this.atBottom = (distanceFromBottom < 30); - }; + log: BattleLog = null!; componentDidMount() { - this.innerElem = this.base!.childNodes[0] as HTMLDivElement; + this.log = new BattleLog(this.base! as HTMLDivElement); this.props.room.subscribe(msg => { if (!msg) return; - if (!msg.startsWith('|')) msg = '||' + msg; const tokens = PS.lineParse(msg); - switch (tokens[0]) { - case 'raw': - case 'html': - const el = document.createElement('div'); - el.className = 'notice'; - el.innerHTML = PSHTML(tokens[1]); - this.appendNode(el); - break; - default: - this.append(
{msg}
); - break; - } + this.log.add(tokens); }); } - appendNode(node: HTMLElement) { - this.innerElem.appendChild(node); - if (this.atBottom) { - this.base!.scrollTop = this.base!.scrollHeight; - } - } - append(nodes: preact.ComponentChild) { - preact.render(nodes, this.innerElem); - if (this.atBottom) { - this.base!.scrollTop = this.base!.scrollHeight; - } - } shouldComponentUpdate(props: {class: string}) { if (props.class !== this.props.class) { this.base!.className = props.class; @@ -76,180 +47,10 @@ class ChatLog extends preact.Component<{class: string, room: PSRoom}> { return false; } render() { - return
-
-
; + return
; } } -const PSHTML = (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: string) { - //