diff --git a/.babelrc b/.babelrc index 650f4ad2..f1e1eb33 100644 --- a/.babelrc +++ b/.babelrc @@ -1,3 +1,8 @@ { - "plugins": ["syntax-dynamic-import"] + "plugins": [ + "syntax-dynamic-import", + ["transform-react-jsx", { + "pragma": "createElement" + }] + ] } \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 722fe5a8..03deba01 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -7,11 +7,22 @@ module.exports = { "eslint:recommended", "plugin:vue/recommended" ], - "plugins": ["vue"], + "plugins": [ + "vue", + "react" + ], "parserOptions": { "parser": "babel-eslint", "ecmaVersion": 8, - "sourceType": "module" + "sourceType": "module", + "ecmaFeatures": { + "jsx": true + } + }, + "settings": { + "react": { + "pragma": "createElement" + } }, "globals": { "import": false, @@ -95,6 +106,7 @@ module.exports = { "allowTemplateLiterals": true } ], + "vue/html-indent": [ "warn", "tab" @@ -108,6 +120,32 @@ module.exports = { "singleline": "never", "multiline": "always" } - ] + ], + + "jsx-quotes": ["error", "prefer-double"], + "react/jsx-boolean-value": "error", + "react/jsx-closing-bracket-location": ["error", "line-aligned"], + //"react/jsx-closing-tag-location": "error" -- stupid rule that doesn't allow line-aligned + "react/jsx-equals-spacing": "error", + "react/jsx-filename-extension": "error", + "react/jsx-first-prop-new-line": ["error", "multiline-multiprop"], + "react/jsx-indent": ["warn", "tab"], + "react/jsx-indent-props": ["warn", "tab"], + "react/jsx-key": "warn", + "react/jsx-no-bind": "error", + "react/jsx-no-comment-textnodes": "error", + "react/jsx-no-duplicate-props": "error", + "react/jsx-no-literals": ["warn"], + "react/jsx-no-target-blank": "error", + "react/jsx-sort-props": ["error", { + "callbacksLast": true, + "reservedFirst": true, + "noSortAlphabetically": true + }], + "react/jsx-tag-spacing": ["error", { + "beforeClosing": "never" + }], + "react/jsx-uses-react": "error", + "react/jsx-wrap-multilines": "error" } }; \ No newline at end of file diff --git a/README.md b/README.md index b9f1158a..9516bb29 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ and add the following to your workspace settings: { "eslint.validate": [ "javascript", + "javascriptreact", "vue" ] }``` \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a97c842c..9f0a9d5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -431,8 +431,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", - "dev": true, - "optional": true + "dev": true }, "asn1": { "version": "0.2.3", @@ -637,6 +636,17 @@ "babel-types": "6.26.0" } }, + "babel-helper-builder-react-jsx": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz", + "integrity": "sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "esutils": "2.0.2" + } + }, "babel-helper-call-delegate": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", @@ -823,6 +833,12 @@ "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", "dev": true }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", + "dev": true + }, "babel-plugin-syntax-trailing-function-commas": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", @@ -1098,6 +1114,17 @@ "babel-runtime": "6.26.0" } }, + "babel-plugin-transform-react-jsx": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz", + "integrity": "sha1-hAoCjn30YN/DotKfDA2R9jduZqM=", + "dev": true, + "requires": { + "babel-helper-builder-react-jsx": "6.26.0", + "babel-plugin-syntax-jsx": "6.18.0", + "babel-runtime": "6.26.0" + } + }, "babel-plugin-transform-regenerator": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", @@ -2755,6 +2782,15 @@ "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", "dev": true }, + "encoding": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "dev": true, + "requires": { + "iconv-lite": "0.4.19" + } + }, "end-of-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", @@ -3037,6 +3073,18 @@ } } }, + "eslint-plugin-react": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.7.0.tgz", + "integrity": "sha512-KC7Snr4YsWZD5flu6A5c0AcIZidzW3Exbqp7OT67OaD2AppJtlBr/GuPrW/vaQM/yfZotEvKAdrxrO+v8vwYJA==", + "dev": true, + "requires": { + "doctrine": "2.1.0", + "has": "1.0.1", + "jsx-ast-utils": "2.0.1", + "prop-types": "15.6.1" + } + }, "eslint-plugin-vue": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-4.4.0.tgz", @@ -3525,6 +3573,29 @@ "websocket-driver": "0.7.0" } }, + "fbjs": { + "version": "0.8.16", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz", + "integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=", + "dev": true, + "requires": { + "core-js": "1.2.7", + "isomorphic-fetch": "2.2.1", + "loose-envify": "1.3.1", + "object-assign": "4.1.1", + "promise": "7.3.1", + "setimmediate": "1.0.5", + "ua-parser-js": "0.7.17" + }, + "dependencies": { + "core-js": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=", + "dev": true + } + } + }, "figures": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", @@ -5004,6 +5075,16 @@ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "dev": true }, + "isomorphic-fetch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", + "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", + "dev": true, + "requires": { + "node-fetch": "1.7.3", + "whatwg-fetch": "2.0.4" + } + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -5133,6 +5214,15 @@ "verror": "1.10.0" } }, + "jsx-ast-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz", + "integrity": "sha1-6AGxs5mF4g//yHtA43SAgOLcrH8=", + "dev": true, + "requires": { + "array-includes": "3.0.3" + } + }, "killable": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.0.tgz", @@ -5866,6 +5956,16 @@ "integrity": "sha512-nJmSswG4As/MkRq7QZFuH/sf/yuv8ODdMZrY4Bedjp77a5MK4A6s7YbBB64c9u79EBUOfXUXBvArmvzTD0X+6g==", "dev": true }, + "node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "dev": true, + "requires": { + "encoding": "0.1.12", + "is-stream": "1.1.0" + } + }, "node-forge": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.1.tgz", @@ -7184,7 +7284,6 @@ "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", "dev": true, - "optional": true, "requires": { "asap": "2.0.6" } @@ -7195,6 +7294,17 @@ "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", "dev": true }, + "prop-types": { + "version": "15.6.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.1.tgz", + "integrity": "sha512-4ec7bY1Y66LymSUOH/zARVYObB23AT2h8cf6e/O6ZALB/N0sqZFEx7rq6EYPX2MkOdKORuooI/H5k9TlR4q7kQ==", + "dev": true, + "requires": { + "fbjs": "0.8.16", + "loose-envify": "1.3.1", + "object-assign": "4.1.1" + } + }, "proxy-addr": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", @@ -9018,6 +9128,12 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, + "ua-parser-js": { + "version": "0.7.17", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz", + "integrity": "sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g==", + "dev": true + }, "uglify-es": { "version": "3.3.9", "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", @@ -10067,6 +10183,12 @@ "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", "dev": true }, + "whatwg-fetch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", + "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==", + "dev": true + }, "whet.extend": { "version": "0.9.9", "resolved": "https://registry.npmjs.org/whet.extend/-/whet.extend-0.9.9.tgz", diff --git a/package.json b/package.json index e6f6a979..f59ab63a 100755 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "main": "script.js", "scripts": { "start": "webpack-dev-server --config webpack.web.dev.js", - "eslint": "eslint \"src/**/*.{js,vue}\"", + "eslint": "eslint \"src/**/*.{js,jsx,vue}\"", "build": "webpack --config webpack.web.prod.js --define process.env.NODE_ENV='production'", "build:babel": "webpack --config webpack.web.babel.js --define process.env.NODE_ENV='production'", "build:prod": "webpack --config webpack.web.prod.js --define process.env.NODE_ENV='production'", @@ -17,12 +17,14 @@ "babel-eslint": "^8.2.2", "babel-loader": "^7.1.4", "babel-plugin-syntax-dynamic-import": "^6.18.0", + "babel-plugin-transform-react-jsx": "^6.24.1", "babel-plugin-transform-runtime": "^6.23.0", "babel-preset-env": "^1.6.1", "clean-webpack-plugin": "^0.1.19", "copy-webpack-plugin": "^4.5.1", "css-loader": "^0.28.10", "eslint": "^4.18.2", + "eslint-plugin-react": "^7.7.0", "eslint-plugin-vue": "^4.4.0", "extract-loader": "^1.0.2", "file-loader": "^1.1.11", diff --git a/src/main.js b/src/main.js index af78a561..4ffa7703 100644 --- a/src/main.js +++ b/src/main.js @@ -66,7 +66,7 @@ class FrankerFaceZ extends Module { // ======================================================================== discoverModules() { - const ctx = require.context('src/modules', true, /(?:^(?:\.\/)?[^/]+|index)\.js$/), + const ctx = require.context('src/modules', true, /(?:^(?:\.\/)?[^/]+|index)\.jsx?$/), modules = this.populate(ctx, this.core_log); this.core_log.info(`Loaded descriptions of ${Object.keys(modules).length} modules.`); diff --git a/src/modules/chat/badges.js b/src/modules/chat/badges.jsx similarity index 96% rename from src/modules/chat/badges.js rename to src/modules/chat/badges.jsx index f675d3e6..14a74392 100644 --- a/src/modules/chat/badges.js +++ b/src/modules/chat/badges.jsx @@ -6,7 +6,7 @@ import {API_SERVER, IS_WEBKIT, WEBKIT_CSS as WEBKIT} from 'utilities/constants'; -import {createElement as e, ManagedStyle} from 'utilities/dom'; +import {createElement, ManagedStyle} from 'utilities/dom'; import {has} from 'utilities/object'; import Module from 'utilities/module'; @@ -270,16 +270,32 @@ export default class Badges extends Module { if ( ! bd ) continue; - out.push(e('div', {className: 'ffz-badge-tip'}, [ + out.push(
+ {show_previews && } + {bd.title} +
); + + /*out.push(e('div', {className: 'ffz-badge-tip'}, [ show_previews && e('img', { className: 'preview-image ffz-badge', src: bd.image4x }), bd.title - ])); + ]));*/ } else if ( p === 'ffz' ) { - out.push(e('div', {className: 'ffz-badge-tip'}, [ + out.push(
+ {show_previews &&
} + {d.title} +
); + + /*out.push(e('div', {className: 'ffz-badge-tip'}, [ show_previews && e('div', { className: 'preview-image ffz-badge', style: { @@ -288,7 +304,7 @@ export default class Badges extends Module { } }), d.title - ])); + ]));*/ } } @@ -297,7 +313,7 @@ export default class Badges extends Module { } - render(msg, e) { // eslint-disable-line class-methods-use-this + render(msg, createElement) { // eslint-disable-line class-methods-use-this const hidden_badges = this.parent.context.get('chat.badges.hidden') || {}, badge_style = this.parent.context.get('chat.badges.style'), custom_mod = this.parent.context.get('chat.badges.custom-mod'), @@ -444,7 +460,7 @@ export default class Badges extends Module { if ( data.replaced ) props['data-replaced'] = data.replaced; - out.push(e('span', props)); + out.push(createElement('span', props)); } return out; diff --git a/src/modules/chat/emotes.js b/src/modules/chat/emotes.js index c25897e3..52b29b30 100644 --- a/src/modules/chat/emotes.js +++ b/src/modules/chat/emotes.js @@ -171,6 +171,7 @@ export default class Emotes extends Module { // ======================================================================== addDefaultSet(provider, set_id, data) { + const had_set = this.default_sets.includes(set_id); if ( ! this.default_sets.sourceIncludes(provider, set_id) ) { this.default_sets.push(provider, set_id); this.refSet(set_id); @@ -178,13 +179,20 @@ export default class Emotes extends Module { if ( data ) this.loadSetData(set_id, data); + + if ( ! had_set ) + this.emit(':update-default-sets'); } removeDefaultSet(provider, set_id) { + const had_set = this.default_sets.includes(set_id); if ( this.default_sets.sourceIncludes(provider, set_id) ) { this.default_sets.remove(provider, set_id); this.unrefSet(set_id); } + + if ( had_set && ! this.default_sets.includes(set_id) ) + this.emit(':update-default-sets'); } refSet(set_id) { diff --git a/src/modules/chat/tokenizers.js b/src/modules/chat/tokenizers.jsx similarity index 87% rename from src/modules/chat/tokenizers.js rename to src/modules/chat/tokenizers.jsx index 5ad70cca..893dfb3e 100644 --- a/src/modules/chat/tokenizers.js +++ b/src/modules/chat/tokenizers.jsx @@ -4,7 +4,7 @@ // Default Tokenizers // ============================================================================ -import {sanitize, createElement as e} from 'utilities/dom'; +import {sanitize, createElement} from 'utilities/dom'; import {has, split_chars} from 'utilities/object'; const EMOTE_CLASS = 'chat-line__message--emote', @@ -40,17 +40,16 @@ export const Links = { type: 'link', priority: 50, - render(token, e) { - return e('a', { - className: 'ffz-tooltip', - 'data-tooltip-type': 'link', - 'data-url': token.url, - 'data-is-mail': token.is_mail, - - rel: 'noopener', - target: '_blank', - href: token.url - }, token.text); + render(token, createElement) { + return ({token.text}); }, tooltip(target, tip) { @@ -209,10 +208,10 @@ export const Mentions = { type: 'mention', priority: 40, - render(token, e) { - return e('strong', { - className: `chat-line__message-mention${token.me ? ' ffz--mention-me' : ''}` - }, `${token.text}`); + render(token, createElement) { + return ( + {token.text} + ); }, process(tokens, msg, user) { @@ -279,16 +278,16 @@ export const Mentions = { export const CheerEmotes = { type: 'cheer', - render(token, e) { - return e('span', { - className: `ffz-cheer ffz-tooltip`, - 'data-tooltip-type': 'cheer', - 'data-prefix': token.prefix, - 'data-amount': this.i18n.formatNumber(token.amount), - 'data-tier': token.tier, - 'data-individuals': token.individuals ? JSON.stringify(token.individuals) : 'null', - alt: token.text - }); + render(token, createElement) { + return (); }, tooltip(target) { @@ -300,16 +299,16 @@ export const CheerEmotes = { length = individuals && individuals.length; const out = [ - this.context.get('tooltip.emote-images') && e('div', { - className: 'preview-image ffz-cheer-preview', - 'data-prefix': prefix, - 'data-tier': tier - }), + this.context.get('tooltip.emote-images') && (
), this.i18n.t('tooltip.bits', '%{count|number} Bits', amount), ]; if ( length > 1 ) { - out.push(e('br')); + out.push(
); individuals.sort(i => -i[0]); @@ -319,11 +318,11 @@ export const CheerEmotes = { amount, prefix, tier - }, e)); + }, createElement)); } if ( length > 12 ) { - out.push(e('br')); + out.push(
); out.push(this.i18n.t('tooltip.bits.more', '(and %{count} more)', length-12)); } } @@ -448,33 +447,33 @@ export const CheerEmotes = { export const AddonEmotes = { type: 'emote', - render(token, e) { + render(token, createElement) { const mods = token.modifiers || [], ml = mods.length, - emote = e('img', { - className: `${EMOTE_CLASS} ffz-tooltip${token.provider === 'ffz' ? ' ffz-emote' : ''}`, - src: token.src, - srcSet: token.srcSet, - alt: token.text, - 'data-tooltip-type': 'emote', - 'data-provider': token.provider, - 'data-id': token.id, - 'data-set': token.set, - 'data-modifiers': ml ? mods.map(x => x.id).join(' ') : null, - 'data-modifier-info': ml ? JSON.stringify(mods.map(x => [x.set, x.id])) : null - }); + emote = ({token.text} x.id).join(' ') : null} + data-modifier-info={ml ? JSON.stringify(mods.map(x => [x.set, x.id])) : null} + />); if ( ! ml ) return emote; - return e('span', { - className: `${EMOTE_CLASS} modified-emote`, - 'data-provider': token.provider, - 'data-id': token.id, - 'data-set': token.set - }, [ - emote, - mods.map(t => e('span', null, this.tokenizers.emote.render(t, e))) - ]); + return ( + {emote} + {mods.map(t => {this.tokenizers.emote.render(t, createElement)})} + ); }, tooltip(target, tip) { @@ -489,10 +488,10 @@ export const AddonEmotes = { emote = emote_set && emote_set.emotes[emote_id]; if ( emote ) - return e('span', null, [ - this.tokenizers.emote.render(emote.token, e), - ` - ${emote.hidden ? '???' : emote.name}` - ]); + return ( + {this.tokenizers.emote.render(emote.token, createElement)} + {` - ${emote.hidden ? '???' : emote.name}`} + ); }) } @@ -540,25 +539,23 @@ export const AddonEmotes = { } return [ - preview && this.context.get('tooltip.emote-images') && e('img', { - className: 'preview-image', - src: preview, - onLoad: tip.update - }), + preview && this.context.get('tooltip.emote-images') && (), this.i18n.t('tooltip.emote', 'Emote: %{code}', {code: target.alt}), - source && this.context.get('tooltip.emote-sources') && e('div', { - className: 'pd-t-05', - }, source), + source && this.context.get('tooltip.emote-sources') && (
+ {source} +
), - owner && this.context.get('tooltip.emote-sources') && e('div', { - className: 'pd-t-05' - }, owner), + owner && this.context.get('tooltip.emote-sources') && (
+ {owner} +
), - mods && e('div', { - className: 'pd-t-1' - }, mods) + mods && (
{mods}
) ]; }, diff --git a/src/modules/main_menu/index.js b/src/modules/main_menu/index.js index ee125e2d..cff46ddc 100644 --- a/src/modules/main_menu/index.js +++ b/src/modules/main_menu/index.js @@ -5,7 +5,7 @@ // ============================================================================ import Module from 'utilities/module'; -import {createElement as e} from 'utilities/dom'; +import {createElement} from 'utilities/dom'; import {has, deep_copy} from 'utilities/object'; import {parse_path} from 'src/settings'; @@ -547,7 +547,7 @@ export default class MainMenu extends Module { return; this._vue = new this.vue.Vue({ - el: e('div'), + el: createElement('div'), render: h => h('main-menu', this.getData()) }); diff --git a/src/modules/metadata.js b/src/modules/metadata.jsx similarity index 88% rename from src/modules/metadata.js rename to src/modules/metadata.jsx index 10b628c6..0b434128 100644 --- a/src/modules/metadata.js +++ b/src/modules/metadata.jsx @@ -4,7 +4,7 @@ // Channel Metadata // ============================================================================ -import {createElement as e, ClickOutside} from 'utilities/dom'; +import {createElement, ClickOutside} from 'utilities/dom'; import {maybe_call} from 'utilities/object'; import {duration_to_string} from 'utilities/time'; @@ -277,35 +277,37 @@ export default class Metadata extends Module { const fix = cls === 'tw-button--text'; if ( typeof icon === 'string' ) - icon = e('span', 'tw-button__icon tw-button__icon--left', e('figure', icon)); + icon = (
); if ( def.popup && def.click ) { - el = e('span', { - className: `ffz-stat${fix ? ' ffz-fix-padding--left' : ''}`, - 'data-key': key, - tip_content: tooltip - }, [ - btn = e('button', `tw-button ${cls}`, [ - icon, - stat = e('span', 'ffz-stat-text tw-button__text') - ]), - popup = e('button', `tw-button ${cls} ffz-stat-arrow`, - e('span', 'tw-button__icon tw-pd-x-0', - e('figure', 'ffz-i-down-dir') - ) - ) - ]); + el = ( + {btn = ()} + {popup = ()} + ); } else - btn = popup = el = e('button', { - className: `ffz-stat${fix ? ' ffz-fix-padding' : ''} tw-button ${cls}`, - 'data-key': key, - tip_content: tooltip - }, [ - icon, - stat = e('span', 'ffz-stat-text tw-button__text'), - def.popup && e('span', 'tw-button__icon tw-button__icon--right', e('figure', 'ffz-i-down-dir')) - ]); + btn = popup = el = (); if ( def.click ) btn.addEventListener('click', e => { @@ -370,16 +372,16 @@ export default class Metadata extends Module { } else { if ( typeof icon === 'string' ) - icon = e('span', 'tw-stat__icon', e('figure', icon)); + icon = (
); - el = e('div', { - className: 'ffz-stat tw-stat', - 'data-key': key, - tip_content: tooltip - }, [ - icon, - stat = e('span', 'ffz-stat-text tw-stat__value') - ]); + el = (
+ {icon} + {stat = } +
) } el._ffz_order = order; diff --git a/src/modules/tooltips.js b/src/modules/tooltips.js index c955d49f..105da040 100644 --- a/src/modules/tooltips.js +++ b/src/modules/tooltips.js @@ -4,7 +4,7 @@ // Tooltip Handling // ============================================================================ -import {createElement as e} from 'utilities/dom'; +import {createElement} from 'utilities/dom'; import {has, maybe_call} from 'utilities/object'; import Tooltip from 'utilities/tooltip'; @@ -20,8 +20,8 @@ export default class TooltipProvider extends Module { this.types.json = target => { const title = target.dataset.title; return [ - title && e('strong', null, title), - e('code', { + title && createElement('strong', null, title), + createElement('code', { className: `block${title ? ' pd-t-05 border-t mg-t-05' : ''}`, style: { fontFamily: 'monospace', @@ -96,8 +96,8 @@ export default class TooltipProvider extends Module { if ( ! handler ) return [ - e('strong', null, 'Unhandled Tooltip Type'), - e('code', { + createElement('strong', null, 'Unhandled Tooltip Type'), + createElement('code', { className: 'block pd-t-05 border-t mg-t-05', style: { fontFamily: 'monospace', diff --git a/src/modules/translation_ui/nondex.js b/src/modules/translation_ui/nondex.js index 741c2f11..4c237e57 100644 --- a/src/modules/translation_ui/nondex.js +++ b/src/modules/translation_ui/nondex.js @@ -5,7 +5,7 @@ // ============================================================================ import Module from 'utilities/module'; -import {createElement as e} from 'utilities/dom'; +import {createElement} from 'utilities/dom'; export default class TranslationUI extends Module { constructor(...args) { diff --git a/src/sites/base.js b/src/sites/base.js index 791eb52a..44fe8501 100644 --- a/src/sites/base.js +++ b/src/sites/base.js @@ -16,7 +16,7 @@ export default class BaseSite extends Module { } populateModules() { - const ctx = require.context('site/modules', true, /(?:^(?:\.\/)?[^/]+|index)\.js$/); + const ctx = require.context('site/modules', true, /(?:^(?:\.\/)?[^/]+|index)\.jsx?$/); const modules = this.populate(ctx, this.log); this.log.info(`Loaded descriptions of ${Object.keys(modules).length} modules.`); } diff --git a/src/sites/twitch-twilight/index.js b/src/sites/twitch-twilight/index.js index a1980460..ff199f1a 100644 --- a/src/sites/twitch-twilight/index.js +++ b/src/sites/twitch-twilight/index.js @@ -11,7 +11,7 @@ import Fine from 'utilities/compat/fine'; import FineRouter from 'utilities/compat/fine-router'; import Apollo from 'utilities/compat/apollo'; -import {createElement as e} from 'utilities/dom'; +import {createElement} from 'utilities/dom'; import MAIN_URL from 'site/styles/main.scss'; @@ -56,7 +56,7 @@ export default class Twilight extends BaseSite { const current = this.router.current; this.fine.route(current && current.name); - document.head.appendChild(e('link', { + document.head.appendChild(createElement('link', { href: MAIN_URL, rel: 'stylesheet', type: 'text/css' diff --git a/src/sites/twitch-twilight/modules/chat/index.js b/src/sites/twitch-twilight/modules/chat/index.js index 70d22b24..de32cccc 100644 --- a/src/sites/twitch-twilight/modules/chat/index.js +++ b/src/sites/twitch-twilight/modules/chat/index.js @@ -376,12 +376,12 @@ export default class ChatHook extends Module { cls.prototype.render = function() { if ( this.state.ffz_errors > 0 ) { const React = t.web_munch.getModule('react'), - e = React && React.createElement; + createElement = React && React.createElement; - if ( ! e ) + if ( ! createElement ) return null; - return e('div', { + return createElement('div', { className: 'tw-border-l tw-c-background-alt-2 tw-c-text tw-full-width tw-full-height tw-align-items-center tw-flex tw-flex-column tw-justify-content-center tw-relative' }, 'There was an error displaying chat.'); diff --git a/src/sites/twitch-twilight/modules/chat/scroller.js b/src/sites/twitch-twilight/modules/chat/scroller.js index ec23d3b4..390b5d48 100644 --- a/src/sites/twitch-twilight/modules/chat/scroller.js +++ b/src/sites/twitch-twilight/modules/chat/scroller.js @@ -4,7 +4,7 @@ // Chat Scroller // ============================================================================ -import {createElement as e} from 'utilities/dom'; +import {createElement} from 'utilities/dom'; import Twilight from 'site'; import Module from 'utilities/module'; @@ -95,7 +95,7 @@ export default class Scroller extends Module { let timer; const auto = this.state.ffz_total_errors < 10, React = t.web_munch.getModule('react'), - e = React && React.createElement, + createElement = React && React.createElement, handler = () => { clearTimeout(timer); this.ffzZeroErrors(); @@ -104,17 +104,17 @@ export default class Scroller extends Module { if ( auto ) timer = setTimeout(handler, 250); - if ( ! e ) + if ( ! createElement ) return null; - return e('div', { + return createElement('div', { className: 'tw-border-l tw-c-background-alt-2 tw-c-text tw-full-width tw-full-height tw-align-items-center tw-flex tw-flex-column tw-justify-content-center tw-relative' }, [ - e('div', {className: 'tw-mg-b-1'}, 'There was an error displaying chat.'), - ! auto && e('button', { + createElement('div', {className: 'tw-mg-b-1'}, 'There was an error displaying chat.'), + ! auto && createElement('button', { className: 'tw-button', onClick: handler - }, e('span', {className: 'tw-button__text'}, 'Try Again')) + }, createElement('span', {className: 'tw-button__text'}, 'Try Again')) ]); } else @@ -176,9 +176,9 @@ export default class Scroller extends Module { node.classList.add('tw-full-height'); - el = this._ffz_freeze_indicator = e('div', { + el = this._ffz_freeze_indicator = createElement('div', { className: 'ffz--freeze-indicator chat-list__more-messages-placeholder tw-relative tw-mg-x-2' - }, e('div', { + }, createElement('div', { className: 'chat-list__more-messages tw-bottom-0 tw-full-width tw-align-items-center tw-flex tw-justify-content-center tw-absolute tw-pd-05' })); diff --git a/src/sites/twitch-twilight/modules/chat/settings_menu.js b/src/sites/twitch-twilight/modules/chat/settings_menu.jsx similarity index 73% rename from src/sites/twitch-twilight/modules/chat/settings_menu.js rename to src/sites/twitch-twilight/modules/chat/settings_menu.jsx index a334a5dd..1145892a 100644 --- a/src/sites/twitch-twilight/modules/chat/settings_menu.js +++ b/src/sites/twitch-twilight/modules/chat/settings_menu.jsx @@ -32,27 +32,36 @@ export default class SettingsMenu extends Module { if ( ! React ) return; - const e = React.createElement; + const createElement = React.createElement; - this.SettingsMenu.ready(cls => { + this.SettingsMenu.ready((cls, instances) => { const old_universal = cls.prototype.renderUniversalOptions; cls.prototype.renderUniversalOptions = function() { const val = old_universal.call(this); - val.props.children.push(e('div', { - className: 'tw-mg-t-1' - }, e('button', { - onClick: () => t.click(this) - }, t.i18n.t('site.menu_button', 'FrankerFaceZ Control Center')) - )); - window.menu = this; + val.props.children.push(
+ +
); return val; } + for(const inst of instances) + inst.ffzSettingsClick = e => t.click(inst, e); + this.SettingsMenu.forceUpdate(); }); + + this.SettingsMenu.on('mount', inst => { + inst.ffzSettingsClick = e => t.click(inst, e) + }); + + this.SettingsMenu.on('unmount', inst => { + inst.ffzSettingsClick = null; + }); } click(inst) { @@ -68,6 +77,7 @@ export default class SettingsMenu extends Module { } else { this.emit('site.menu_button:clicked'); } + const parent = this.fine.searchParent(inst, n => n.toggleBalloonId); parent && parent.handleButtonClick(); } diff --git a/src/sites/twitch-twilight/modules/directory/following.js b/src/sites/twitch-twilight/modules/directory/following.jsx similarity index 84% rename from src/sites/twitch-twilight/modules/directory/following.js rename to src/sites/twitch-twilight/modules/directory/following.jsx index b7202c2c..9bba47c8 100644 --- a/src/sites/twitch-twilight/modules/directory/following.js +++ b/src/sites/twitch-twilight/modules/directory/following.jsx @@ -5,7 +5,7 @@ // ============================================================================ import {SiteModule} from 'utilities/module'; -import {createElement as e} from 'utilities/dom'; +import {createElement} from 'utilities/dom'; import {get} from 'utilities/object'; import Popper from 'popper.js'; @@ -236,83 +236,61 @@ export default class Following extends SiteModule { const simplebarContentChildren = []; // Hosted Channel Header - simplebarContentChildren.push( - e('p', { - className: 'tw-pd-t-05 tw-pd-x-1 tw-c-text-alt-2', - textContent: this.i18n.t('directory.hosted', 'Hosted Channel') - }) - ); + simplebarContentChildren.push(

+ {this.i18n.t('directory.hosted', 'Hosted Channel')} +

); // Hosted Channel Content - simplebarContentChildren.push( - e('a', { - className: 'tw-interactable', - href: `/${hostData.channel}`, - onclick: event => - this.parent.hijackUserClick( - event, - hostData.channel, - this.destroyHostMenu.bind(this) - ) - }, e('div', 'tw-align-items-center tw-flex tw-flex-row tw-flex-nowrap tw-mg-x-1 tw-mg-y-05', - [ - e('div', { - className: 'ffz-channel-avatar', - }, e('img', { - src: inst.props.viewerCount.profileImageURL, - alt: inst.props.channelName - })), - e('p', { - className: 'tw-ellipsis tw-flex-grow-1 tw-mg-l-1 tw-font-size-5', - textContent: inst.props.channelName - }) - ] - )) - ); + simplebarContentChildren.push( this.parent.hijackUserClick(e, hostData.channel, this.destroyHostMenu.bind(this))} // eslint-disable-line react/jsx-no-bind + > +
+
+ {inst.props.channelName} +
+

+ {inst.props.channelName} +

+
+
); // Hosting Channels Header - simplebarContentChildren.push( - e('p', { - className: 'tw-pd-t-05 tw-pd-x-1 tw-c-text-alt-2', - textContent: this.i18n.t('directory.hosting', 'Hosting Channels') - }) - ); + simplebarContentChildren.push(

+ {this.i18n.t('directory.hosting', 'Hosting Channels')} +

); // Hosting Channels Content for (let i = 0; i < hostData.nodes.length; i++) { const node = hostData.nodes[i]; - simplebarContentChildren.push( - e('a', { - className: 'tw-interactable', - href: `/${node.login}`, - onclick: event => this.parent.hijackUserClick(event, node.login, this.destroyHostMenu.bind(this)) - }, e('div', 'tw-align-items-center tw-flex tw-flex-row tw-flex-nowrap tw-mg-x-1 tw-mg-y-05', - [ - e('div', { - className: 'ffz-channel-avatar', - }, e('img', { - src: node.profileImageURL, - alt: node.displayName - })), - e('p', { - className: 'tw-ellipsis tw-flex-grow-1 tw-mg-l-1 tw-font-size-5', - textContent: node.displayName - }) - ] - )) - ); + simplebarContentChildren.push( this.parent.hijackUserClick(e, node.login, this.destroyHostMenu.bind(this))} // eslint-disable-line react/jsx-no-bind + > +
+
+ {node.displayName} +
+

+ {node.displayName} +

+
+
); } - this.hostMenu = e('div', 'ffz-host-menu tw-balloon tw-block', - e('div', 'tw-border tw-elevation-1 tw-border-radius-small tw-c-background', - e('div', { - className: 'scrollable-area', - 'data-simplebar': true, - }, e('div', 'simplebar-scroll-content', - e('div', 'simplebar-content', simplebarContentChildren) - )) - ) - ); + this.hostMenu = (
+
+
+
+
+ {simplebarContentChildren} +
+
+
+
+
); const root = (document.body.querySelector('.twilight-root') || document.body); root.appendChild(this.hostMenu); diff --git a/src/sites/twitch-twilight/modules/directory/game.js b/src/sites/twitch-twilight/modules/directory/game.jsx similarity index 80% rename from src/sites/twitch-twilight/modules/directory/game.js rename to src/sites/twitch-twilight/modules/directory/game.jsx index 9f29f64f..3f61a227 100644 --- a/src/sites/twitch-twilight/modules/directory/game.js +++ b/src/sites/twitch-twilight/modules/directory/game.jsx @@ -5,7 +5,7 @@ // ============================================================================ import {SiteModule} from 'utilities/module'; -import {createElement as e} from 'utilities/dom'; +import {createElement} from 'utilities/dom'; import GAME_QUERY from './game.gql'; @@ -66,10 +66,12 @@ export default class Game extends SiteModule { } - block_btn = e('button', { - className: 'tw-mg-l-1 tw-button ffz-directory-toggle-block', - onClick: this.generateClickHandler('directory.game.blocked-games', game, update_block) - }, block_label = e('span', 'tw-button__text')); + block_btn = (); update_block(); @@ -86,17 +88,19 @@ export default class Game extends SiteModule { this.parent.ChannelCard.forceUpdate(); } - hidden_btn = e('button', { - className: 'tw-mg-l-1 tw-button ffz-directory-toggle-thumbnail', - onClick: this.generateClickHandler('directory.game.hidden-thumbnails', game, update_hidden) - }, hidden_label = e('span', 'tw-button__text')); + hidden_btn = () update_hidden(); - buttons.appendChild(e('div', 'ffz-buttons', [ - block_btn, - hidden_btn - ])); + buttons.appendChild(
+ {block_btn} + {hidden_btn} +
); } generateClickHandler(setting, game, update_func) { diff --git a/src/sites/twitch-twilight/modules/directory/index.js b/src/sites/twitch-twilight/modules/directory/index.jsx similarity index 87% rename from src/sites/twitch-twilight/modules/directory/index.js rename to src/sites/twitch-twilight/modules/directory/index.jsx index bf9e8d1d..5d4bd1cd 100644 --- a/src/sites/twitch-twilight/modules/directory/index.js +++ b/src/sites/twitch-twilight/modules/directory/index.jsx @@ -6,7 +6,7 @@ import {SiteModule} from 'utilities/module'; import {duration_to_string} from 'utilities/time'; -import {createElement as e} from 'utilities/dom'; +import {createElement} from 'utilities/dom'; import {get} from 'utilities/object'; import Following from './following'; @@ -244,15 +244,17 @@ export default class Directory extends SiteModule { const up_text = duration_to_string(uptime, false, false, false, setting === 1); if ( ! inst.ffz_uptime_el || card.querySelector('.ffz-uptime-element') === undefined ) { - card.appendChild(inst.ffz_uptime_el = e('div', - 'video-preview-card__preview-overlay-stat tw-c-background-overlay tw-c-text-overlay tw-font-size-6 tw-top-0 tw-right-0 tw-z-default tw-inline-flex tw-absolute tw-mg-05 ffz-uptime-element', - e('div', 'tw-tooltip-wrapper tw-inline-flex', [ - e('div', 'tw-stat', [ - e('span', 'tw-c-text-live tw-stat__icon', e('figure', 'ffz-i-clock')), - inst.ffz_uptime_span = e('span', 'tw-stat__value') - ]), - inst.ffz_uptime_tt = e('div', 'tw-tooltip tw-tooltip--down tw-tooltip--align-center') - ]))); + card.appendChild(inst.ffz_uptime_el = (
+
+
+ +
+ + {inst.ffz_uptime_span = } +
+ {inst.ffz_uptime_tt =
} +
+
)); } if ( ! inst.ffz_update_timer ) @@ -306,29 +308,29 @@ export default class Directory extends SiteModule { if ( setting === 1 ) { const body = card.querySelector('.tw-card-body .tw-flex'), - avatar = e('a', { - className: 'ffz-channel-avatar tw-mg-r-05 tw-mg-t-05', - href: `/${data.login}`, - title: data.displayName, - onclick: event => this.hijackUserClick(event, data.login) - }, e('img', { - src: data.profileImageURL - })); + avatar = ( this.hijackUserClick(e, data.login)} // eslint-disable-line react/jsx-no-bind + > + + ); body.insertBefore(avatar, body.firstElementChild); } else if ( setting === 2 || setting === 3 ) { - const avatar_el = e('a', { - className: 'ffz-channel-avatar', - href: `/${data.login}`, - onclick: event => this.hijackUserClick(event, data.login) - }, e('div', 'live-channel-card__boxart tw-bottom-0 tw-absolute', - e('figure', 'tw-aspect tw-aspect--align-top', - e('img', { - title: data.displayName, - src: data.profileImageURL - }))) - ); + const avatar_el = ( this.hijackUserClick(e, data.login)} // eslint-disable-line react/jsx-no-bind + > +
+
+ +
+
+
); const cont = card.querySelector('figure.tw-aspect > div'); if ( cont ) diff --git a/src/sites/twitch-twilight/modules/host_button.js b/src/sites/twitch-twilight/modules/host_button.js index d77200bd..3ccb50d7 100644 --- a/src/sites/twitch-twilight/modules/host_button.js +++ b/src/sites/twitch-twilight/modules/host_button.js @@ -5,7 +5,7 @@ // ============================================================================ import Module from 'utilities/module'; -import {createElement as e} from 'utilities/dom'; +import {createElement} from 'utilities/dom'; const HOST_ERRORS = { COMMAND_EXECUTION: { @@ -212,7 +212,7 @@ export default class HostButton extends Module { this.activeTab = this.activeTab || 'auto-host'; this.vueEl = new vue.Vue({ - el: e('div'), + el: createElement('div'), render: h => h('host-options', { hosts, autoHostSettings, diff --git a/src/sites/twitch-twilight/modules/menu_button.js b/src/sites/twitch-twilight/modules/menu_button.jsx similarity index 65% rename from src/sites/twitch-twilight/modules/menu_button.js rename to src/sites/twitch-twilight/modules/menu_button.jsx index 1bd74c24..66e227e6 100644 --- a/src/sites/twitch-twilight/modules/menu_button.js +++ b/src/sites/twitch-twilight/modules/menu_button.jsx @@ -5,7 +5,7 @@ // ============================================================================ import {SiteModule} from 'utilities/module'; -import {createElement as e} from 'utilities/dom'; +import {createElement, setChildren} from 'utilities/dom'; export default class MenuButton extends SiteModule { constructor(...args) { @@ -29,7 +29,7 @@ export default class MenuButton extends SiteModule { set pill(val) { this._pill_content = val; if ( this._pill ) - this._pill.innerHTML = this.formatPill(); + setChildren(this._pill, this.formatPill(), true); } formatPill() { @@ -46,28 +46,26 @@ export default class MenuButton extends SiteModule { user_menu = container.querySelector('.top-nav__nav-items-container:last-child'); - this._el = e('div', - 'ffz-top-nav tw-align-self-center tw-flex-grow-0 tw-flex-shrink-0 tw-flex-nowrap tw-pd-r-1 tw-pd-l-05', - this._btn = e('button', - { - className: 'tw-button-icon tw-button-icon--overlay tw-button-icon--large', - onClick: e => this.emit(':clicked', e) - }, - e('div', 'tw-tooltip-wrapper', [ - e('span', 'tw-button-icon__icon', - e('figure', 'ffz-i-zreknarf') - ), + this._el = (
+ {this._btn = ()} +
); - e('div', 'ffz-menu__pill absolute', - e('div', 'tw-animation tw-animation--animate tw-animation--duration-medium tw-animation--timing-ease-in tw-animation--bounce-in', - this._pill = e('span', 'tw-pill tw-pill--notification', this.formatPill(), true) - ) - ), - - this._tip = e('div', 'tw-tooltip tw-tooltip--down tw-tooltip--align-center') - ]) - ) - ); + setChildren(this._pill, this.formatPill(), true); this.onTranslate(); diff --git a/src/sites/twitch-twilight/modules/player.js b/src/sites/twitch-twilight/modules/player.jsx similarity index 96% rename from src/sites/twitch-twilight/modules/player.js rename to src/sites/twitch-twilight/modules/player.jsx index 191c422a..a2bb4fae 100644 --- a/src/sites/twitch-twilight/modules/player.js +++ b/src/sites/twitch-twilight/modules/player.jsx @@ -5,7 +5,7 @@ // ============================================================================ import Module from 'utilities/module'; -import {createElement as e} from 'utilities/dom'; +import {createElement} from 'utilities/dom'; export default class Player extends Module { @@ -348,14 +348,13 @@ export default class Player extends Module { let tip = container.querySelector('.ffz--player-reset .player-tip'); if ( ! tip ) - container.insertBefore( - e('button', { - className: 'player-button player-button--reset ffz--player-reset ffz-i-cancel', - type: 'button', - onDblClick: t.resetPlayer.bind(t, inst) - }, tip = e('span', 'player-tip js-control-tip')), - el.nextSibling - ); + container.insertBefore(, el.nextSibling); tip.dataset.tip = this.i18n.t('player.reset_button', 'Double-Click to Reset Player'); } diff --git a/src/sites/twitch-twilight/modules/sub_button.js b/src/sites/twitch-twilight/modules/sub_button.jsx similarity index 81% rename from src/sites/twitch-twilight/modules/sub_button.js rename to src/sites/twitch-twilight/modules/sub_button.jsx index fbedb3c9..1bc0f2b3 100644 --- a/src/sites/twitch-twilight/modules/sub_button.js +++ b/src/sites/twitch-twilight/modules/sub_button.jsx @@ -5,7 +5,7 @@ // ============================================================================ import Module from 'utilities/module'; -import {createElement as e} from 'utilities/dom'; +import {createElement} from 'utilities/dom'; export default class SubButton extends Module { constructor(...args) { @@ -59,16 +59,13 @@ export default class SubButton extends Module { icon = btn.querySelector('.ffz--can-prime'); if ( should_show && ! icon ) { - btn.insertBefore( - e('span', 'tw-button__icon tw-button__icon--left ffz--can-prime', - e('figure', { - className: 'ffz-i-crown ffz-tooltip', - 'data-tooltip-type': 'html', - 'data-title': this.i18n.t('sub-button.prime', 'Your free channel sub with Prime is available.') - }) - ), - btn.firstElementChild - ); + btn.insertBefore( +
+ , btn.firstElementChild); } else if ( ! should_show && icon ) icon.remove(); diff --git a/src/sites/twitch-twilight/modules/theme/index.js b/src/sites/twitch-twilight/modules/theme/index.js index d99a892e..f7ed233f 100644 --- a/src/sites/twitch-twilight/modules/theme/index.js +++ b/src/sites/twitch-twilight/modules/theme/index.js @@ -5,7 +5,7 @@ // ============================================================================ import Module from 'utilities/module'; -import {createElement as e} from 'utilities/dom'; +import {createElement} from 'utilities/dom'; import THEME_CSS_URL from 'site/styles/theme.scss'; @@ -58,7 +58,7 @@ export default class ThemeEngine extends Module { if ( ! enable ) return; - this._style = e('link', { + this._style = createElement('link', { rel: 'stylesheet', type: 'text/css', href: THEME_CSS_URL diff --git a/src/utilities/dom.js b/src/utilities/dom.js index 25de3965..ca30cdf1 100644 --- a/src/utilities/dom.js +++ b/src/utilities/dom.js @@ -30,9 +30,12 @@ function camelCase(name) { } -export function createElement(tag, props, children, no_sanitize) { +export function createElement(tag, props, ...children) { const el = document.createElement(tag); + if ( children.length === 1) + children = children[0]; + if ( typeof props === 'string' ) el.className = props; else if ( props ) @@ -73,7 +76,7 @@ export function createElement(tag, props, children, no_sanitize) { } if ( children ) - setChildren(el, children, no_sanitize); + setChildren(el, children); return el; } diff --git a/src/utilities/module.js b/src/utilities/module.js index 440fde54..856fc475 100644 --- a/src/utilities/module.js +++ b/src/utilities/module.js @@ -521,7 +521,9 @@ export class Module extends EventEmitter { for(const raw_path of ctx.keys()) { const raw_module = ctx(raw_path), module = raw_module.module || raw_module.default, - name = raw_path.slice(2, raw_path.length - (raw_path.endsWith('/index.js') ? 9 : 3)); + name = raw_path.slice(2, raw_path.length - (raw_path.endsWith('/index.jsx') ? 10 : raw_path.endsWith('/index.js') ? 9 : raw_path.endsWith('.jsx') ? 4 : 3)); + + // TODO: rewrite the name code to not have like 4 endsWith in it. try { added[name] = this.register(name, module); diff --git a/src/utilities/tooltip.js b/src/utilities/tooltip.js index d79e7d3d..892b46c8 100644 --- a/src/utilities/tooltip.js +++ b/src/utilities/tooltip.js @@ -8,7 +8,7 @@ // Better because they aren't hidden by parents with overflow: hidden; // ============================================================================ -import {createElement as e, setChildren} from 'utilities/dom'; +import {createElement, setChildren} from 'utilities/dom'; import {maybe_call} from 'utilities/object'; import Popper from 'popper.js'; @@ -208,17 +208,17 @@ export class Tooltip { return; // Build the DOM. - const arrow = e('div', opts.arrowClass), - inner = tip.element = e('div', opts.innerClass), + const arrow = createElement('div', opts.arrowClass), + inner = tip.element = createElement('div', opts.innerClass), - el = tip.outer = e('div', { + el = tip.outer = createElement('div', { className: opts.tooltipClass }, [inner, arrow]); arrow.setAttribute('x-arrow', true); if ( opts.arrowInner ) - arrow.appendChild(e('div', opts.arrowInner)); + arrow.appendChild(createElement('div', opts.arrowInner)); if ( tip.add_class ) { inner.classList.add(tip.add_class); diff --git a/styles/widgets.scss b/styles/widgets.scss index b238c0a6..1306a0c5 100644 --- a/styles/widgets.scss +++ b/styles/widgets.scss @@ -46,7 +46,9 @@ } +.ffz--chat-actions, .ffz--profile-manager { + .ffz--action, .ffz--profile { outline: none; diff --git a/webpack.common.js b/webpack.common.js index 2bb89f2c..39fefdd4 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -8,6 +8,7 @@ module.exports = { avalon: './src/main.js' }, resolve: { + extensions: ['.js', '.jsx'], alias: { res: path.resolve(__dirname, 'res/'), styles: path.resolve(__dirname, 'styles/'), @@ -45,6 +46,11 @@ module.exports = { } }] }, + { + test: /\.jsx$/, + exclude: /node_modules/, + loader: 'babel-loader' + }, { test: /\.(graphql|gql)$/, exclude: /node_modules/,