From bc0eab4409f09beeae1974c1c4550f8366996cff Mon Sep 17 00:00:00 2001 From: SirStendec Date: Thu, 1 Sep 2022 15:36:47 -0400 Subject: [PATCH 01/11] 4.36.2 * Fixed: Channel Metadata not appearing over the player in theater mode when hovering with the relevant setting enabled. * Fixed: Chat on Videos not appearing correctly for some videos due to incorrect channel IDs associated with messages. * Fixed: When in theater mode, hide the secondary player controls that Twitch displays by mistake. * Fixed: Certain Twitch badges not appearing correctly for users with high-DPI displays or increased zoom levels. (Note that this may require you to perform a hard refresh due to browser caching.) * Removed: Don't display a message about the Audio Compressor preventing playback rate changes for Firefox users. Firefox fixed that issue a while back and no users should experience it at this time. --- package.json | 8 +- pnpm-lock.yaml | 438 ++++++++++++++++-- src/sites/shared/player.jsx | 6 +- .../styles/portrait-metadata-top.scss | 2 +- .../css_tweaks/styles/swap-sidebars.scss | 2 +- .../css_tweaks/styles/theatre-metadata.scss | 4 +- .../modules/video_chat/index.jsx | 8 +- src/sites/twitch-twilight/styles/channel.scss | 4 + 8 files changed, 418 insertions(+), 54 deletions(-) diff --git a/package.json b/package.json index 7c916977..22f2ce56 100755 --- a/package.json +++ b/package.json @@ -1,16 +1,16 @@ { "name": "frankerfacez", "author": "Dan Salvato LLC", - "version": "4.36.1", + "version": "4.36.2", "description": "FrankerFaceZ is a Twitch enhancement suite.", "private": true, "license": "Apache-2.0", "scripts": { - "start": "webpack-dev-server --config webpack.web.dev.js", + "start": "cross-env NODE_OPTIONS=--openssl-legacy-provider webpack-dev-server --config webpack.web.dev.js", "eslint": "eslint \"src/**/*.{js,jsx,vue}\"", "clean": "rimraf dist", - "dev": "webpack-dev-server --config webpack.web.dev.js", - "dev:prod": "webpack-dev-server --config webpack.web.dev.prod.js", + "dev": "cross-env NODE_OPTIONS=--openssl-legacy-provider webpack-dev-server --config webpack.web.dev.js", + "dev:prod": "cross-env NODE_OPTIONS=--openssl-legacy-provider webpack-dev-server --config webpack.web.dev.prod.js", "build": "pnpm build:prod", "build:stats": "cross-env NODE_ENV=production webpack --config webpack.web.prod.js --json > stats.json", "build:prod": "cross-env NODE_ENV=production webpack --config webpack.web.prod.js", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f2e77e77..3c46c7db 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: 5.3 +lockfileVersion: 5.4 overrides: ansi-regex@>2.1.1 <5.0.1: '>=5.0.1' @@ -52,7 +52,7 @@ specifiers: semver: ^7.3.5 sortablejs: ^1.14.0 sourcemapped-stacktrace: ^1.1.11 - terser-webpack-plugin: '4' + terser-webpack-plugin: ^4.2.3 text-diff: ^1.0.1 vue: ^2.6.14 vue-clickaway: ^2.2.2 @@ -97,7 +97,7 @@ dependencies: devDependencies: '@babel/core': 7.16.0 - '@babel/eslint-parser': 7.16.0_@babel+core@7.16.0+eslint@7.32.0 + '@babel/eslint-parser': 7.16.0_q3hdp35agahhlc67sgxrhsgj5a '@babel/plugin-proposal-class-properties': 7.16.0_@babel+core@7.16.0 '@babel/plugin-proposal-nullish-coalescing-operator': 7.16.0_@babel+core@7.16.0 '@babel/plugin-proposal-object-rest-spread': 7.16.0_@babel+core@7.16.0 @@ -105,8 +105,8 @@ devDependencies: '@babel/plugin-syntax-dynamic-import': 7.8.3_@babel+core@7.16.0 '@babel/plugin-transform-react-jsx': 7.16.0_@babel+core@7.16.0 '@ffz/fontello-cli': 1.0.4 - '@webpack-cli/serve': 1.6.0_90245a0e5d2744c77d25a53c1b9ef3e7 - babel-loader: 8.2.3_1bd60a6cd0f7024f034efd75ae733a3f + '@webpack-cli/serve': 1.6.0_sasfuds5e5cmo7jfuu6bxhxt44 + babel-loader: 8.2.3_dplau3gq64be6a2o7v2244z2h4 clean-webpack-plugin: 3.0.0_webpack@4.46.0 copy-webpack-plugin: 5.1.2_webpack@4.46.0 cross-env: 7.0.3 @@ -123,11 +123,11 @@ devDependencies: sass-loader: 7.3.1_webpack@4.46.0 semver: 7.3.5 terser-webpack-plugin: 4.2.3_webpack@4.46.0 - vue-loader: 15.9.8_e15c8784917900cf55a453471769391c + vue-loader: 15.9.8_4jxi6swgjym7ovrhfrafs526hm vue-template-compiler: 2.6.14 webpack: 4.46.0_webpack-cli@4.9.1 - webpack-cli: 4.9.1_703ab6c6d02792f100f7002d09038fa7 - webpack-dev-server: 4.4.0_webpack-cli@4.9.1+webpack@4.46.0 + webpack-cli: 4.9.1_oa5lnrwqe6jpcahxaawqsa4pu4 + webpack-dev-server: 4.4.0_7d675yvzq6xw7ta76a6hebfduq webpack-manifest-plugin: 4.0.2_webpack@4.46.0 webpack-merge: 4.2.2 @@ -174,7 +174,7 @@ packages: - supports-color dev: true - /@babel/eslint-parser/7.16.0_@babel+core@7.16.0+eslint@7.32.0: + /@babel/eslint-parser/7.16.0_q3hdp35agahhlc67sgxrhsgj5a: resolution: {integrity: sha512-c+AsYOHjI+FgCa+ifLd8sDXp4U4mjkfFgL9NdQWhuA731kAUJs0WdJIXET4A14EJAR9Jv9FFF/MzPWJfV9Oirw==} engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} peerDependencies: @@ -366,6 +366,8 @@ packages: resolution: {integrity: sha512-RUVpT0G2h6rOZwqLDTrKk7ksNv7YpAilTnYe1/Q+eDjxEceRMKVWbCsX7t8h6C1qCFi/1Y8WZjcEPBAFG27GPw==} engines: {node: '>=6.0.0'} hasBin: true + dependencies: + '@babel/types': 7.16.0 dev: true /@babel/plugin-proposal-class-properties/7.16.0_@babel+core@7.16.0: @@ -682,10 +684,10 @@ packages: source-map: 0.6.1 dev: true - /@vue/component-compiler-utils/3.3.0: + /@vue/component-compiler-utils/3.3.0_react@17.0.2: resolution: {integrity: sha512-97sfH2mYNU+2PzGrmK2haqffDpVASuib9/w2/noxiFi31Z54hW+q3izKQXXQZSNhtiUpAI36uSuYepeBe4wpHQ==} dependencies: - consolidate: 0.15.1 + consolidate: 0.15.1_react@17.0.2 hash-sum: 1.0.2 lru-cache: 4.1.5 merge-source-map: 1.1.0 @@ -694,7 +696,61 @@ packages: source-map: 0.6.1 vue-template-es2015-compiler: 1.9.1 optionalDependencies: - prettier: 2.4.1 + prettier: 2.7.1 + transitivePeerDependencies: + - arc-templates + - atpl + - babel-core + - bracket-template + - coffee-script + - dot + - dust + - dustjs-helpers + - dustjs-linkedin + - eco + - ect + - ejs + - haml-coffee + - hamlet + - hamljs + - handlebars + - hogan.js + - htmling + - jade + - jazz + - jqtpl + - just + - liquid-node + - liquor + - lodash + - marko + - mote + - mustache + - nunjucks + - plates + - pug + - qejs + - ractive + - razor-tmpl + - react + - react-dom + - slm + - squirrelly + - swig + - swig-templates + - teacup + - templayed + - then-jade + - then-pug + - tinyliquid + - toffee + - twig + - twing + - underscore + - vash + - velocityjs + - walrus + - whiskers dev: true /@webassemblyjs/ast/1.9.0: @@ -824,14 +880,14 @@ packages: '@xtuc/long': 4.2.2 dev: true - /@webpack-cli/configtest/1.1.0_webpack-cli@4.9.1+webpack@4.46.0: + /@webpack-cli/configtest/1.1.0_7d675yvzq6xw7ta76a6hebfduq: resolution: {integrity: sha512-ttOkEkoalEHa7RaFYpM0ErK1xc4twg3Am9hfHhL7MVqlHebnkYd2wuI/ZqTDj0cVzZho6PdinY0phFZV3O0Mzg==} peerDependencies: webpack: 4.x.x || 5.x.x webpack-cli: 4.x.x dependencies: webpack: 4.46.0_webpack-cli@4.9.1 - webpack-cli: 4.9.1_703ab6c6d02792f100f7002d09038fa7 + webpack-cli: 4.9.1_oa5lnrwqe6jpcahxaawqsa4pu4 dev: true /@webpack-cli/info/1.4.0_webpack-cli@4.9.1: @@ -840,10 +896,10 @@ packages: webpack-cli: 4.x.x dependencies: envinfo: 7.8.1 - webpack-cli: 4.9.1_703ab6c6d02792f100f7002d09038fa7 + webpack-cli: 4.9.1_oa5lnrwqe6jpcahxaawqsa4pu4 dev: true - /@webpack-cli/serve/1.6.0_90245a0e5d2744c77d25a53c1b9ef3e7: + /@webpack-cli/serve/1.6.0_sasfuds5e5cmo7jfuu6bxhxt44: resolution: {integrity: sha512-ZkVeqEmRpBV2GHvjjUZqEai2PpUbuq8Bqd//vEYsp63J8WyexI8ppCqVS3Zs0QADf6aWuPdU+0XsPI647PVlQA==} peerDependencies: webpack-cli: 4.x.x @@ -852,8 +908,8 @@ packages: webpack-dev-server: optional: true dependencies: - webpack-cli: 4.9.1_703ab6c6d02792f100f7002d09038fa7 - webpack-dev-server: 4.4.0_webpack-cli@4.9.1+webpack@4.46.0 + webpack-cli: 4.9.1_oa5lnrwqe6jpcahxaawqsa4pu4 + webpack-dev-server: 4.4.0_7d675yvzq6xw7ta76a6hebfduq dev: true /@xtuc/ieee754/1.2.0: @@ -892,6 +948,12 @@ packages: hasBin: true dev: true + /acorn/8.8.0: + resolution: {integrity: sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + /aggregate-error/3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} @@ -979,6 +1041,8 @@ packages: dependencies: micromatch: 3.1.10 normalize-path: 2.1.1 + transitivePeerDependencies: + - supports-color dev: true optional: true @@ -1116,7 +1180,7 @@ packages: hasBin: true dev: true - /babel-loader/8.2.3_1bd60a6cd0f7024f034efd75ae733a3f: + /babel-loader/8.2.3_dplau3gq64be6a2o7v2244z2h4: resolution: {integrity: sha512-n4Zeta8NC3QAsuyiizu0GkmRcQ6clkV9WFUnUf1iXP//IeSKbWjofW3UHyZVwlOB4y039YQKefawyTn64Zwbuw==} engines: {node: '>= 8.9'} peerDependencies: @@ -1221,6 +1285,8 @@ packages: qs: 6.7.0 raw-body: 2.4.0 type-is: 1.6.18 + transitivePeerDependencies: + - supports-color dev: true /bonjour/3.5.0: @@ -1255,6 +1321,8 @@ packages: snapdragon-node: 2.1.1 split-string: 3.1.0 to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color dev: true /braces/3.0.2: @@ -1328,7 +1396,7 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001278 + caniuse-lite: 1.0.30001387 electron-to-chromium: 1.3.890 escalade: 3.1.1 node-releases: 2.0.1 @@ -1421,6 +1489,8 @@ packages: ssri: 8.0.1 tar: 6.1.11 unique-filename: 1.1.1 + transitivePeerDependencies: + - bluebird dev: true /cache-base/1.0.1: @@ -1455,8 +1525,8 @@ packages: engines: {node: '>=6'} dev: true - /caniuse-lite/1.0.30001278: - resolution: {integrity: sha512-mpF9KeH8u5cMoEmIic/cr7PNS+F5LWBk0t2ekGT60lFf0Wq+n9LspAj0g3P+o7DQhD3sUdlMln4YFAWhFYn9jg==} + /caniuse-lite/1.0.30001387: + resolution: {integrity: sha512-fKDH0F1KOJvR+mWSOvhj8lVRr/Q/mc5u5nabU2vi1/sgvlSqEsE8dOq0Hy/BqVbDkCYQPRRHB1WRjW6PGB/7PA==} dev: true /chainsaw/0.1.0: @@ -1484,7 +1554,7 @@ packages: /chokidar/2.1.8: resolution: {integrity: sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==} - deprecated: Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies. + deprecated: Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies dependencies: anymatch: 2.0.0 async-each: 1.0.3 @@ -1499,6 +1569,8 @@ packages: upath: 1.2.0 optionalDependencies: fsevents: 1.2.13 + transitivePeerDependencies: + - supports-color dev: true optional: true @@ -1518,6 +1590,23 @@ packages: fsevents: 2.3.2 dev: true + /chokidar/3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + requiresBuild: true + dependencies: + anymatch: 3.1.2 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.2 + dev: true + optional: true + /chownr/1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} dev: true @@ -1662,6 +1751,8 @@ packages: on-headers: 1.0.2 safe-buffer: 5.1.2 vary: 1.1.2 + transitivePeerDependencies: + - supports-color dev: true /concat-map/0.0.1: @@ -1687,11 +1778,173 @@ packages: resolution: {integrity: sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==} dev: true - /consolidate/0.15.1: + /consolidate/0.15.1_react@17.0.2: resolution: {integrity: sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==} engines: {node: '>= 0.10.0'} + peerDependencies: + arc-templates: ^0.5.3 + atpl: '>=0.7.6' + babel-core: ^6.26.3 + bracket-template: ^1.1.5 + coffee-script: ^1.12.7 + dot: ^1.1.3 + dust: ^0.3.0 + dustjs-helpers: ^1.7.4 + dustjs-linkedin: ^2.7.5 + eco: ^1.1.0-rc-3 + ect: ^0.5.9 + ejs: ^3.1.5 + haml-coffee: ^1.14.1 + hamlet: ^0.3.3 + hamljs: ^0.6.2 + handlebars: ^4.7.6 + hogan.js: ^3.0.2 + htmling: ^0.0.8 + jade: ^1.11.0 + jazz: ^0.0.18 + jqtpl: ~1.1.0 + just: ^0.1.8 + liquid-node: ^3.0.1 + liquor: ^0.0.5 + lodash: ^4.17.20 + marko: ^3.14.4 + mote: ^0.2.0 + mustache: ^3.0.0 + nunjucks: ^3.2.2 + plates: ~0.4.11 + pug: ^3.0.0 + qejs: ^3.0.5 + ractive: ^1.3.12 + razor-tmpl: ^1.3.1 + react: ^16.13.1 + react-dom: ^16.13.1 + slm: ^2.0.0 + squirrelly: ^5.1.0 + swig: ^1.4.2 + swig-templates: ^2.0.3 + teacup: ^2.0.0 + templayed: '>=0.2.3' + then-jade: '*' + then-pug: '*' + tinyliquid: ^0.2.34 + toffee: ^0.3.6 + twig: ^1.15.2 + twing: ^5.0.2 + underscore: ^1.11.0 + vash: ^0.13.0 + velocityjs: ^2.0.1 + walrus: ^0.10.1 + whiskers: ^0.4.0 + peerDependenciesMeta: + arc-templates: + optional: true + atpl: + optional: true + babel-core: + optional: true + bracket-template: + optional: true + coffee-script: + optional: true + dot: + optional: true + dust: + optional: true + dustjs-helpers: + optional: true + dustjs-linkedin: + optional: true + eco: + optional: true + ect: + optional: true + ejs: + optional: true + haml-coffee: + optional: true + hamlet: + optional: true + hamljs: + optional: true + handlebars: + optional: true + hogan.js: + optional: true + htmling: + optional: true + jade: + optional: true + jazz: + optional: true + jqtpl: + optional: true + just: + optional: true + liquid-node: + optional: true + liquor: + optional: true + lodash: + optional: true + marko: + optional: true + mote: + optional: true + mustache: + optional: true + nunjucks: + optional: true + plates: + optional: true + pug: + optional: true + qejs: + optional: true + ractive: + optional: true + razor-tmpl: + optional: true + react: + optional: true + react-dom: + optional: true + slm: + optional: true + squirrelly: + optional: true + swig: + optional: true + swig-templates: + optional: true + teacup: + optional: true + templayed: + optional: true + then-jade: + optional: true + then-pug: + optional: true + tinyliquid: + optional: true + toffee: + optional: true + twig: + optional: true + twing: + optional: true + underscore: + optional: true + vash: + optional: true + velocityjs: + optional: true + walrus: + optional: true + whiskers: + optional: true dependencies: bluebird: 3.7.2 + react: 17.0.2 dev: true /constants-browserify/1.0.0: @@ -1872,12 +2125,22 @@ packages: /debug/2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true dependencies: ms: 2.0.0 dev: true /debug/3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true dependencies: ms: 2.1.3 dev: true @@ -2435,6 +2698,8 @@ packages: regex-not: 1.0.2 snapdragon: 0.8.2 to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color dev: true /express/4.17.1: @@ -2471,6 +2736,8 @@ packages: type-is: 1.6.18 utils-merge: 1.0.1 vary: 1.1.2 + transitivePeerDependencies: + - supports-color dev: true /extend-shallow/2.0.1: @@ -2500,6 +2767,8 @@ packages: regex-not: 1.0.2 snapdragon: 0.8.2 to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color dev: true /extract-loader/2.0.1: @@ -2609,6 +2878,8 @@ packages: parseurl: 1.3.3 statuses: 1.5.0 unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color dev: true /find-cache-dir/2.1.0: @@ -2743,7 +3014,7 @@ packages: requiresBuild: true dependencies: bindings: 1.5.0 - nan: 2.15.0 + nan: 2.16.0 dev: true optional: true @@ -3819,6 +4090,8 @@ packages: regex-not: 1.0.2 snapdragon: 0.8.2 to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color dev: true /micromatch/4.0.4: @@ -3996,8 +4269,8 @@ packages: thunky: 1.1.0 dev: true - /nan/2.15.0: - resolution: {integrity: sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==} + /nan/2.16.0: + resolution: {integrity: sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==} requiresBuild: true dev: true optional: true @@ -4017,6 +4290,8 @@ packages: regex-not: 1.0.2 snapdragon: 0.8.2 to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color dev: true /natural-compare/1.4.0: @@ -4483,6 +4758,8 @@ packages: async: 2.6.3 debug: 3.2.7 mkdirp: 0.5.5 + transitivePeerDependencies: + - supports-color dev: true /posix-character-classes/0.1.1: @@ -4547,8 +4824,8 @@ packages: engines: {node: '>= 0.8.0'} dev: true - /prettier/2.4.1: - resolution: {integrity: sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==} + /prettier/2.7.1: + resolution: {integrity: sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==} engines: {node: '>=10.13.0'} hasBin: true requiresBuild: true @@ -4570,6 +4847,11 @@ packages: /promise-inflight/1.0.1: resolution: {integrity: sha1-mEcocL8igTL8vdhoEputEsPAKeM=} + peerDependencies: + bluebird: '*' + peerDependenciesMeta: + bluebird: + optional: true dev: true /prop-types/15.7.2: @@ -4715,7 +4997,6 @@ packages: dependencies: loose-envify: 1.4.0 object-assign: 4.1.1 - dev: false /readable-stream/2.3.7: resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==} @@ -4744,6 +5025,8 @@ packages: graceful-fs: 4.2.8 micromatch: 3.1.10 readable-stream: 2.3.7 + transitivePeerDependencies: + - supports-color dev: true optional: true @@ -5012,6 +5295,8 @@ packages: on-finished: 2.3.0 range-parser: 1.2.1 statuses: 1.5.0 + transitivePeerDependencies: + - supports-color dev: true /serialize-javascript/4.0.0: @@ -5037,6 +5322,8 @@ packages: http-errors: 1.6.3 mime-types: 2.1.33 parseurl: 1.3.3 + transitivePeerDependencies: + - supports-color dev: true /serve-static/1.14.1: @@ -5047,6 +5334,8 @@ packages: escape-html: 1.0.3 parseurl: 1.3.3 send: 0.17.1 + transitivePeerDependencies: + - supports-color dev: true /set-immediate-shim/1.0.1: @@ -5160,6 +5449,8 @@ packages: source-map: 0.5.7 source-map-resolve: 0.5.3 use: 3.1.1 + transitivePeerDependencies: + - supports-color dev: true /sockjs/0.3.21: @@ -5472,6 +5763,8 @@ packages: terser: 5.9.0 webpack: 4.46.0_webpack-cli@4.9.1 webpack-sources: 1.4.3 + transitivePeerDependencies: + - bluebird dev: true /terser/4.8.0: @@ -5479,6 +5772,7 @@ packages: engines: {node: '>=6.0.0'} hasBin: true dependencies: + acorn: 8.8.0 commander: 2.20.3 source-map: 0.6.1 source-map-support: 0.5.20 @@ -5489,6 +5783,7 @@ packages: engines: {node: '>=10'} hasBin: true dependencies: + acorn: 8.8.0 commander: 2.20.3 source-map: 0.7.3 source-map-support: 0.5.20 @@ -5781,20 +6076,23 @@ packages: resolution: {integrity: sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==} dev: true - /vue-loader/15.9.8_e15c8784917900cf55a453471769391c: + /vue-loader/15.9.8_4jxi6swgjym7ovrhfrafs526hm: resolution: {integrity: sha512-GwSkxPrihfLR69/dSV3+5CdMQ0D+jXg8Ma1S4nQXKJAznYFX14vHdc/NetQc34Dw+rBbIJyP7JOuVb9Fhprvog==} peerDependencies: + '@vue/compiler-sfc': ^3.0.8 cache-loader: '*' css-loader: '*' vue-template-compiler: '*' webpack: ^3.0.0 || ^4.1.0 || ^5.0.0-0 peerDependenciesMeta: + '@vue/compiler-sfc': + optional: true cache-loader: optional: true vue-template-compiler: optional: true dependencies: - '@vue/component-compiler-utils': 3.3.0 + '@vue/component-compiler-utils': 3.3.0_react@17.0.2 css-loader: 3.6.0_webpack@4.46.0 hash-sum: 1.0.2 loader-utils: 1.4.0 @@ -5802,6 +6100,60 @@ packages: vue-style-loader: 4.1.3 vue-template-compiler: 2.6.14 webpack: 4.46.0_webpack-cli@4.9.1 + transitivePeerDependencies: + - arc-templates + - atpl + - babel-core + - bracket-template + - coffee-script + - dot + - dust + - dustjs-helpers + - dustjs-linkedin + - eco + - ect + - ejs + - haml-coffee + - hamlet + - hamljs + - handlebars + - hogan.js + - htmling + - jade + - jazz + - jqtpl + - just + - liquid-node + - liquor + - lodash + - marko + - mote + - mustache + - nunjucks + - plates + - pug + - qejs + - ractive + - razor-tmpl + - react + - react-dom + - slm + - squirrelly + - swig + - swig-templates + - teacup + - templayed + - then-jade + - then-pug + - tinyliquid + - toffee + - twig + - twing + - underscore + - vash + - velocityjs + - walrus + - whiskers dev: true /vue-observe-visibility/1.0.0: @@ -5841,6 +6193,8 @@ packages: requiresBuild: true dependencies: chokidar: 2.1.8 + transitivePeerDependencies: + - supports-color dev: true optional: true @@ -5850,8 +6204,10 @@ packages: graceful-fs: 4.2.8 neo-async: 2.6.2 optionalDependencies: - chokidar: 3.5.2 + chokidar: 3.5.3 watchpack-chokidar2: 2.0.1 + transitivePeerDependencies: + - supports-color dev: true /wbuf/1.7.3: @@ -5864,7 +6220,7 @@ packages: resolution: {integrity: sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=} dev: true - /webpack-cli/4.9.1_703ab6c6d02792f100f7002d09038fa7: + /webpack-cli/4.9.1_oa5lnrwqe6jpcahxaawqsa4pu4: resolution: {integrity: sha512-JYRFVuyFpzDxMDB+v/nanUdQYcZtqFPGzmlW4s+UkPMFhSpfRNmf1z4AwYcHJVdvEFAM7FFCQdNTpsBYhDLusQ==} engines: {node: '>=10.13.0'} hasBin: true @@ -5885,9 +6241,9 @@ packages: optional: true dependencies: '@discoveryjs/json-ext': 0.5.5 - '@webpack-cli/configtest': 1.1.0_webpack-cli@4.9.1+webpack@4.46.0 + '@webpack-cli/configtest': 1.1.0_7d675yvzq6xw7ta76a6hebfduq '@webpack-cli/info': 1.4.0_webpack-cli@4.9.1 - '@webpack-cli/serve': 1.6.0_90245a0e5d2744c77d25a53c1b9ef3e7 + '@webpack-cli/serve': 1.6.0_sasfuds5e5cmo7jfuu6bxhxt44 colorette: 2.0.16 commander: 7.2.0 execa: 5.1.1 @@ -5896,7 +6252,7 @@ packages: interpret: 2.2.0 rechoir: 0.7.1 webpack: 4.46.0_webpack-cli@4.9.1 - webpack-dev-server: 4.4.0_webpack-cli@4.9.1+webpack@4.46.0 + webpack-dev-server: 4.4.0_7d675yvzq6xw7ta76a6hebfduq webpack-merge: 5.8.0 dev: true @@ -5914,7 +6270,7 @@ packages: webpack: 4.46.0_webpack-cli@4.9.1 dev: true - /webpack-dev-server/4.4.0_webpack-cli@4.9.1+webpack@4.46.0: + /webpack-dev-server/4.4.0_7d675yvzq6xw7ta76a6hebfduq: resolution: {integrity: sha512-+S0XRIbsopVjPFjCO8I07FXYBWYqkFmuP56ucGMTs2hA/gV4q2M9xTmNo5Tg4o8ffRR+Nm3AsXnQXxKRyYovrA==} engines: {node: '>= 12.13.0'} hasBin: true @@ -5949,7 +6305,7 @@ packages: strip-ansi: 7.0.1 url: 0.11.0 webpack: 4.46.0_webpack-cli@4.9.1 - webpack-cli: 4.9.1_703ab6c6d02792f100f7002d09038fa7 + webpack-cli: 4.9.1_oa5lnrwqe6jpcahxaawqsa4pu4 webpack-dev-middleware: 5.2.1_webpack@4.46.0 ws: 8.2.3 transitivePeerDependencies: @@ -6042,8 +6398,10 @@ packages: tapable: 1.1.3 terser-webpack-plugin: 1.4.5_webpack@4.46.0 watchpack: 1.7.5 - webpack-cli: 4.9.1_703ab6c6d02792f100f7002d09038fa7 + webpack-cli: 4.9.1_oa5lnrwqe6jpcahxaawqsa4pu4 webpack-sources: 1.4.3 + transitivePeerDependencies: + - supports-color dev: true /websocket-driver/0.7.4: diff --git a/src/sites/shared/player.jsx b/src/sites/shared/player.jsx index bf6abe96..20425591 100644 --- a/src/sites/shared/player.jsx +++ b/src/sites/shared/player.jsx @@ -1321,7 +1321,7 @@ export default class PlayerBase extends Module { return; } - let icon, tip, extra, ff_el, btn, cont = container.querySelector('.ffz--player-comp'); + let icon, tip, extra, btn, cont = container.querySelector('.ffz--player-comp'); if ( ! has_comp || this.areControlsDisabled(inst) ) { if ( cont ) cont.remove(); @@ -1346,7 +1346,6 @@ export default class PlayerBase extends Module {
{tip = (
)} {extra = (
)} - {ff_el = IS_FIREFOX ? (
) : null}
); @@ -1374,9 +1373,6 @@ export default class PlayerBase extends Module { if ( can_apply && this._shortcut_bound ) label = `${label} (${this._shortcut_bound})`; - if ( ff_el ) - ff_el.textContent += `\n${this.i18n.t('player.comp_button.firefox', 'Playback Speed controls will not function for Firefox users when the Compressor has been enabled.')}`; - icon.classList.toggle('ffz-i-comp-on', comp_active); icon.classList.toggle('ffz-i-comp-off', ! comp_active); btn.disabled = ! can_apply; diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/portrait-metadata-top.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/portrait-metadata-top.scss index eda91ad8..40197292 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/styles/portrait-metadata-top.scss +++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/portrait-metadata-top.scss @@ -1,5 +1,5 @@ .channel-root__scroll-area--theatre-mode { - .channel-info-content > div:first-child { + #live-channel-stream-information { //.channel-info-content > div:first-child { right: 40rem !important; bottom: calc(10rem + var(--ffz-chat-height)) !important; } diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/swap-sidebars.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/swap-sidebars.scss index 3c48fe80..e9ccd7a5 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/styles/swap-sidebars.scss +++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/swap-sidebars.scss @@ -94,7 +94,7 @@ body .whispers--theatre-mode.whispers--right-column-expanded-beside { } .channel-root__scroll-area--theatre-mode { - .channel-info-content > div:first-child, .channel-info-bar { + #live-channel-stream-information, .channel-info-bar { // .channel-info-content > div:first-child, .channel-info-bar { left: calc(var(--ffz-chat-width) + 5rem) !important; right: 40rem !important; } diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/theatre-metadata.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/theatre-metadata.scss index bdeca9ac..5855df74 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/styles/theatre-metadata.scss +++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/theatre-metadata.scss @@ -1,5 +1,5 @@ .channel-root__scroll-area--theatre-mode { - .channel-info-content > div:first-child, + #live-channel-stream-information, //.channel-info-content > section:first-child, .channel-info-bar { position: fixed; bottom: 17rem; @@ -24,7 +24,7 @@ } &:hover { - .channel-info-content > div:first-child, + #live-channel-stream-information, //.channel-info-content > section:first-child, .channel-info-bar { background-color: var(--color-background-base); opacity: 0.75; diff --git a/src/sites/twitch-twilight/modules/video_chat/index.jsx b/src/sites/twitch-twilight/modules/video_chat/index.jsx index 08948848..4991b277 100644 --- a/src/sites/twitch-twilight/modules/video_chat/index.jsx +++ b/src/sites/twitch-twilight/modules/video_chat/index.jsx @@ -89,6 +89,8 @@ export default class VideoChatHook extends Module { component: 'setting-check-box' } }); + + this.active_room = null; } @@ -472,7 +474,7 @@ export default class VideoChatHook extends Module { if ( comment._ffz_message ) return comment._ffz_message; - const room = this.chat.getRoom(comment.channelId, null, true, true), + const room = this.active_room || this.chat.getRoom(comment.channelId, null, true, true), params = comment.message.userNoticeParams, msg_id = params && params['msg-id']; @@ -561,6 +563,7 @@ export default class VideoChatHook extends Module { if ( ! this.addRoom(chat, props) ) return; + this.active_room = chat._ffz_room; this.chat.badges.updateTwitchBadges(get('data.badges', props)); this.updateRoomBadges(chat, get('data.video.owner.broadcastBadges', props)); @@ -605,6 +608,9 @@ export default class VideoChatHook extends Module { chatUnmounted(chat) { + if (this.active_room === chat._ffz_room) + this.active_room = null; + this.removeRoom(chat); } diff --git a/src/sites/twitch-twilight/styles/channel.scss b/src/sites/twitch-twilight/styles/channel.scss index 862824c5..aabe02b6 100644 --- a/src/sites/twitch-twilight/styles/channel.scss +++ b/src/sites/twitch-twilight/styles/channel.scss @@ -34,6 +34,10 @@ } }*/ +.video-player__container--theatre .stream-display-ad__wrapper .player-controls__right-control-group { + display: none !important; +} + .tw-root--theme-ffz, .tw-root--theme-ffz.tw-root--theme-dark, .tw-root--theme-dark, body { .ffz-stat > .tw-button--text, .ffz-stat.tw-button--text { From 8cd6545556fbfaaf663b7c383552dc0f77b655bf Mon Sep 17 00:00:00 2001 From: SirStendec Date: Fri, 7 Oct 2022 15:12:15 -0400 Subject: [PATCH 02/11] 4.37.0 * Added: "Copy Message" chat action for copying a message to your clipboard. * Added: Setting to pause the player by clicking on it. This is disabled by default, and the pause happens after half a second to avoid pausing as part of a double-click. * Added: Setting to clear the emote menu's search when closing it. * Added: Setting to hide the "Elevate Your Message" button in the chat input field. * Changed: Remove code related to channel hosting. * Fixed: Do not attempt to load FFZ on `gql` or `passport` subdomains. * Fixed: Channel leader-boards not being hidden on channels within a specific experiment. --- package.json | 8 +- src/entry.js | 2 +- .../chat/actions/components/edit-copy.vue | 29 ++ src/modules/chat/actions/types.jsx | 45 +++ src/sites/shared/player.jsx | 52 ++- src/sites/twitch-twilight/modules/channel.jsx | 23 +- .../modules/chat/emote_menu.jsx | 20 + .../twitch-twilight/modules/chat/index.js | 18 +- .../modules/css_tweaks/index.js | 3 +- .../twitch-twilight/modules/dashboard.js | 4 +- .../modules/directory/following.jsx | 249 ------------ .../modules/directory/index.jsx | 1 - .../modules/host_button/index.js | 379 ------------------ src/sites/twitch-twilight/modules/layout.js | 6 +- src/sites/twitch-twilight/modules/player.jsx | 4 +- src/sites/twitch-twilight/styles/main.scss | 2 +- 16 files changed, 185 insertions(+), 660 deletions(-) create mode 100644 src/modules/chat/actions/components/edit-copy.vue delete mode 100644 src/sites/twitch-twilight/modules/directory/following.jsx delete mode 100644 src/sites/twitch-twilight/modules/host_button/index.js diff --git a/package.json b/package.json index 22f2ce56..39e62d68 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "frankerfacez", "author": "Dan Salvato LLC", - "version": "4.36.2", + "version": "4.37.0", "description": "FrankerFaceZ is a Twitch enhancement suite.", "private": true, "license": "Apache-2.0", @@ -12,9 +12,9 @@ "dev": "cross-env NODE_OPTIONS=--openssl-legacy-provider webpack-dev-server --config webpack.web.dev.js", "dev:prod": "cross-env NODE_OPTIONS=--openssl-legacy-provider webpack-dev-server --config webpack.web.dev.prod.js", "build": "pnpm build:prod", - "build:stats": "cross-env NODE_ENV=production webpack --config webpack.web.prod.js --json > stats.json", - "build:prod": "cross-env NODE_ENV=production webpack --config webpack.web.prod.js", - "build:dev": "pnpm clean && webpack --config webpack.web.dev.js", + "build:stats": "cross-env NODE_OPTIONS=--openssl-legacy-provider NODE_ENV=production webpack --config webpack.web.prod.js --json > stats.json", + "build:prod": "cross-env NODE_OPTIONS=--openssl-legacy-provider NODE_ENV=production webpack --config webpack.web.prod.js", + "build:dev": "pnpm clean && cross-env NODE_OPTIONS=--openssl-legacy-provider webpack --config webpack.web.dev.js", "font": "pnpm font:edit", "font:edit": "fontello-cli --cli-config fontello.client.json edit", "font:save": "fontello-cli --cli-config fontello.client.json save && pnpm font:update", diff --git a/src/entry.js b/src/entry.js index ca7b8c21..7b3ae680 100644 --- a/src/entry.js +++ b/src/entry.js @@ -2,7 +2,7 @@ 'use strict'; (() => { // Don't run on certain sub-domains. - if ( /^(?:localhost\.rig|blog|im|chatdepot|tmi|api|brand|dev)\./.test(location.hostname) ) + if ( /^(?:localhost\.rig|blog|im|chatdepot|tmi|api|brand|dev|gql|passport)\./.test(location.hostname) ) return; const DEBUG = localStorage.ffzDebugMode == 'true' && document.body.classList.contains('ffz-dev'), diff --git a/src/modules/chat/actions/components/edit-copy.vue b/src/modules/chat/actions/components/edit-copy.vue new file mode 100644 index 00000000..1dfbedfd --- /dev/null +++ b/src/modules/chat/actions/components/edit-copy.vue @@ -0,0 +1,29 @@ + + + \ No newline at end of file diff --git a/src/modules/chat/actions/types.jsx b/src/modules/chat/actions/types.jsx index 4de79288..da02fa29 100644 --- a/src/modules/chat/actions/types.jsx +++ b/src/modules/chat/actions/types.jsx @@ -83,6 +83,51 @@ export const edit_overrides = { } +// ============================================================================ +// Copy to Clipboard +// ============================================================================ + +export const copy_message = { + presets: [{ + appearance: { + type: 'icon', + icon: 'ffz-i-docs' + } + }], + + defaults: { + format: '{{user.displayName}}: {{message.text}}' + }, + + editor: () => import(/* webpackChunkName: 'main-menu' */ './components/edit-copy.vue'), + + required_context: ['user', 'message'], + + title: 'Copy Message', + description: 'Allows you to quickly copy a chat message to your clipboard.', + + can_self: true, + + tooltip(data) { + const msg = this.replaceVariables(data.options.format, data); + + return [ + (
{ // eslint-disable-line react/jsx-key + this.i18n.t('chat.actions.copy_message', 'Copy Message') + }
), + (
{ // eslint-disable-line react/jsx-key + msg + }
) + ]; + }, + + click(event, data) { + const msg = this.replaceVariables(data.options.format, data); + navigator.clipboard.writeText(msg); + } +} + + // ============================================================================ // Open URL // ============================================================================ diff --git a/src/sites/shared/player.jsx b/src/sites/shared/player.jsx index 20425591..f3ec0a95 100644 --- a/src/sites/shared/player.jsx +++ b/src/sites/shared/player.jsx @@ -559,6 +559,15 @@ export default class PlayerBase extends Module { }, changed: val => this.css_tweaks.toggle('player-hide-mouse', val) }); + + this.settings.add('player.single-click-pause', { + default: false, + ui: { + path: 'Player > General >> Playback', + title: "Pause/Unpause the player by clicking.", + component: 'setting-check-box' + } + }); } async onEnable() { @@ -643,8 +652,6 @@ export default class PlayerBase extends Module { onShortcut(e) { - this.log.info('Compressor Hotkey', e); - for(const inst of this.Player.instances) this.compressPlayer(inst, e); } @@ -825,10 +832,14 @@ export default class PlayerBase extends Module { if ( ! this._ffz_click_handler ) this._ffz_click_handler = this.ffzClickHandler.bind(this); + if ( ! this._ffz_dblclick_handler ) + this._ffz_dblclick_handler = this.ffzDblClickHandler.bind(this); + if ( ! this._ffz_menu_handler ) this._ffz_menu_handler = this.ffzMenuHandler.bind(this); on(cont, 'wheel', this._ffz_scroll_handler); + on(cont, 'dblclick', this._ffz_dblclick_handler); on(cont, 'mousedown', this._ffz_click_handler); on(cont, 'contextmenu', this._ffz_menu_handler); } @@ -853,23 +864,60 @@ export default class PlayerBase extends Module { this._ffz_menu_handler = null; } + if ( this._ffz_dblclick_handler ) { + off(cont, 'dblclick', this._ffz_dblclick_handler); + this._ffz_dblclick_handler = null; + } + this._ffz_listeners = false; } + cls.prototype.ffzDelayPause = function() { + if ( this._ffz_pause_timer ) + clearTimeout(this._ffz_pause_timer); + + const player = this.props?.mediaPlayerInstance; + if (! player.isPaused()) + this._ffz_pause_timer = setTimeout(() => { + const player = this.props?.mediaPlayerInstance; + if (!player.isPaused()) + player.pause(); + }, 500); + } + + cls.prototype.ffzDblClickHandler = function(event) { + if ( ! event ) + return; + + if ( this._ffz_pause_timer ) + clearTimeout(this._ffz_pause_timer); + } + cls.prototype.ffzClickHandler = function(event) { if ( ! event ) return; const vol_scroll = t.settings.get('player.volume-scroll'), gain_scroll = t.settings.get('player.gain.scroll'), + click_pause = t.settings.get('player.single-click-pause'), wants_rmb = wantsRMB(vol_scroll) || wantsRMB(gain_scroll); + // Left Click + if (click_pause && event.button === 0) { + if (! event.target || ! event.target.classList.contains('click-handler')) + return; + + this.ffzDelayPause(); + } + + // Right Click if ( wants_rmb && event.button === 2 ) { this.ffz_rmb = true; this.ffz_scrolled = false; } + // Middle Click if ( ! t.settings.get('player.mute-click') || event.button !== 1 ) return; diff --git a/src/sites/twitch-twilight/modules/channel.jsx b/src/sites/twitch-twilight/modules/channel.jsx index 3751ae8b..af212516 100644 --- a/src/sites/twitch-twilight/modules/channel.jsx +++ b/src/sites/twitch-twilight/modules/channel.jsx @@ -79,7 +79,7 @@ export default class Channel extends Module { changed: () => this.updateLinks() }); - this.settings.add('channel.hosting.enable', { + /*this.settings.add('channel.hosting.enable', { default: true, ui: { path: 'Channel > Behavior >> Hosting', @@ -87,8 +87,7 @@ export default class Channel extends Module { component: 'setting-check-box' }, changed: val => ! val && this.InfoBar.each(el => this.updateBar(el)) - }); - + });*/ this.ChannelPanels = this.fine.define( 'channel-panels', @@ -116,7 +115,7 @@ export default class Channel extends Module { {childNodes: true, subtree: true}, 1 ); - const strip_host = resp => { + /*const strip_host = resp => { if ( this.settings.get('channel.hosting.enable') ) return; @@ -130,7 +129,7 @@ export default class Channel extends Module { }; this.apollo.registerModifier('UseHosting', strip_host, false); - this.apollo.registerModifier('PlayerTrackingContextQuery', strip_host, false); + this.apollo.registerModifier('PlayerTrackingContextQuery', strip_host, false);*/ } onEnable() { @@ -162,7 +161,7 @@ export default class Channel extends Module { this.InfoBar.on('unmount', this.removeBar, this); this.InfoBar.each(el => this.updateBar(el)); - this.subpump.on(':pubsub-message', this.onPubSub, this); + //this.subpump.on(':pubsub-message', this.onPubSub, this); this.router.on(':route', this.checkNavigation, this); this.checkNavigation(); @@ -230,7 +229,7 @@ export default class Channel extends Module { } } - setHost(channel_id, channel_login, target_id, target_login) { + /*setHost(channel_id, channel_login, target_id, target_login) { const topic = `stream-chat-room-v1.${channel_id}`; this.subpump.inject(topic, { @@ -272,7 +271,7 @@ export default class Channel extends Module { event.message.data.num_viewers = 0; event.markChanged(); } - } + }*/ updateSubscription(login) { @@ -441,8 +440,8 @@ export default class Channel extends Module { }); }*/ - if ( ! this.settings.get('channel.hosting.enable') && props.hostLogin ) - this.setHost(props.channelID, props.channelLogin, null, null); + //if ( ! this.settings.get('channel.hosting.enable') && props.hostLogin ) + // this.setHost(props.channelID, props.channelLogin, null, null); this.updateSubscription(props.channelLogin); this.updateMetadata(el); @@ -492,10 +491,10 @@ export default class Channel extends Module { live_since: props.liveSince }, props, - hosted: { + /*hosted: { login: props.hostLogin, display_name: props.hostDisplayName - }, + },*/ el, getViewerCount: () => { const thing = cont.querySelector('p[data-a-target="animated-channel-viewers-count"]'), diff --git a/src/sites/twitch-twilight/modules/chat/emote_menu.jsx b/src/sites/twitch-twilight/modules/chat/emote_menu.jsx index 0da82e64..6e6be4a7 100644 --- a/src/sites/twitch-twilight/modules/chat/emote_menu.jsx +++ b/src/sites/twitch-twilight/modules/chat/emote_menu.jsx @@ -188,6 +188,15 @@ export default class EmoteMenu extends Module { } }); + this.settings.add('chat.emote-menu.clear-search', { + default: false, + ui: { + path: 'Chat > Emote Menu >> General', + title: 'Reset search when closing the Emote Menu.', + component: 'setting-check-box' + } + }); + this.settings.add('chat.emote-menu.enabled', { default: true, ui: { @@ -1078,6 +1087,7 @@ export default class EmoteMenu extends Module { reducedPadding: t.chat.context.get('chat.emote-menu.reduced-padding'), combineTabs: t.chat.context.get('chat.emote-menu.combine-tabs'), showSearch: t.chat.context.get('chat.emote-menu.show-search'), + clearSearch: t.chat.context.get('chat.emote-menu.clear-search'), tone: t.settings.provider.get('emoji-tone', null) } @@ -1226,6 +1236,7 @@ export default class EmoteMenu extends Module { t.chat.context.on('changed:chat.emote-menu.show-heading', this.updateSettingState, this); t.chat.context.on('changed:chat.emote-menu.combine-tabs', this.updateSettingState, this); t.chat.context.on('changed:chat.emote-menu.show-search', this.updateSettingState, this); + t.chat.context.on('changed:chat.emote-menu.clear-search', this.updateSettingState, this); t.chat.context.on('changed:chat.emote-menu.tall', this.updateSettingState, this); window.ffz_menu = this; @@ -1241,6 +1252,7 @@ export default class EmoteMenu extends Module { t.chat.context.off('changed:chat.emote-menu.reduced-padding', this.updateSettingState, this); t.chat.context.off('changed:chat.emote-menu.combine-tabs', this.updateSettingState, this); t.chat.context.off('changed:chat.emote-menu.show-search', this.updateSettingState, this); + t.chat.context.off('changed:chat.emote-menu.clear-search', this.updateSettingState, this); t.chat.context.off('changed:chat.emote-menu.tall', this.updateSettingState, this); if ( window.ffz_menu === this ) @@ -1256,6 +1268,7 @@ export default class EmoteMenu extends Module { reducedPadding: t.chat.context.get('chat.emote-menu.reduced-padding'), combineTabs: t.chat.context.get('chat.emote-menu.combine-tabs'), showSearch: t.chat.context.get('chat.emote-menu.show-search'), + clearSearch: t.chat.context.get('chat.emote-menu.clear-search'), tall: t.chat.context.get('chat.emote-menu.tall') }); } @@ -2309,6 +2322,13 @@ export default class EmoteMenu extends Module { return; } + if ( ! this.props.visible && old_props.visible ) { + if ( this.state.clearSearch ) { + this.setState(this.filterState('', this.state)); + return; + } + } + const cd = this.props.channel_data, old_cd = old_props.channel_data, cd_diff = cd?.user !== old_cd?.user || cd?.channel !== old_cd?.channel, diff --git a/src/sites/twitch-twilight/modules/chat/index.js b/src/sites/twitch-twilight/modules/chat/index.js index 1b43f35c..62d5f28b 100644 --- a/src/sites/twitch-twilight/modules/chat/index.js +++ b/src/sites/twitch-twilight/modules/chat/index.js @@ -201,7 +201,7 @@ export default class ChatHook extends Module { this.ChatController = this.fine.define( 'chat-controller', - n => n.hostingHandler && n.onRoomStateUpdated, + n => n.parseOutgoingMessage && n.onRoomStateUpdated && n.renderNotifications, Twilight.CHAT_ROUTES ); @@ -656,6 +656,15 @@ export default class ChatHook extends Module { component: 'setting-check-box' } }); + + this.settings.add('chat.input.show-elevate-your-message', { + default: true, + ui: { + path: 'Chat > Input >> Appearance', + title: 'Allow the "Elevate Your Message" button to be displayed.', + component: 'setting-check-box' + } + }); } get currentChat() { @@ -941,6 +950,9 @@ export default class ChatHook extends Module { this.updateMentionCSS(); }); + this.chat.context.getChanges('chat.input.show-elevate-your-message', val => + this.css_tweaks.toggleHide('elevate-your-message', ! val)); + this.updateChatCSS(); this.updateColors(); this.updateLineBorders(); @@ -2441,7 +2453,7 @@ export default class ChatHook extends Module { return old_points.call(i, e); } - const old_host = this.onHostingEvent; + /*const old_host = this.onHostingEvent; this.onHostingEvent = function (e, _t) { t.emit('tmi:host', e, _t); return old_host.call(i, e, _t); @@ -2451,7 +2463,7 @@ export default class ChatHook extends Module { this.onUnhostEvent = function (e, _t) { t.emit('tmi:unhost', e, _t); return old_unhost.call(i, e, _t); - } + }*/ const old_add = this.addMessage; this.addMessage = function(e) { diff --git a/src/sites/twitch-twilight/modules/css_tweaks/index.js b/src/sites/twitch-twilight/modules/css_tweaks/index.js index da1cbf50..323eb3bc 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/index.js +++ b/src/sites/twitch-twilight/modules/css_tweaks/index.js @@ -26,6 +26,7 @@ const CLASSES = { 'modview-hide-info': '.modview-player-widget__hide-stream-info', 'community-highlights': '.community-highlight-stack__card', + 'elevate-your-message': '.chat-input__input-icons button[aria-label="ElevatedMessage"]', 'prime-offers': '.top-nav__prime', 'discover-luna': '.top-nav__external-link[data-a-target="try-presto-link"]', @@ -38,7 +39,7 @@ const CLASSES = { 'player-event-bar': '.channel-root .live-event-banner-ui__header', 'player-rerun-bar': '.channel-root__player-container div.tw-c-text-overlay:not([data-a-target="hosting-ui-header"])', - 'pinned-cheer': '.pinned-cheer,.pinned-cheer-v2,.channel-leaderboard', + 'pinned-cheer': '.pinned-cheer,.pinned-cheer-v2,.channel-leaderboard,.channel-leaderboard-marquee', 'whispers': 'body .whispers-open-threads,.tw-core-button[data-a-target="whisper-box-button"],.whispers__pill', 'dir-live-ind': '.live-channel-card[data-ffz-type="live"] .tw-channel-status-text-indicator, article[data-ffz-type="live"] .tw-channel-status-text-indicator', diff --git a/src/sites/twitch-twilight/modules/dashboard.js b/src/sites/twitch-twilight/modules/dashboard.js index 9e68298d..2c1cdd95 100644 --- a/src/sites/twitch-twilight/modules/dashboard.js +++ b/src/sites/twitch-twilight/modules/dashboard.js @@ -54,7 +54,7 @@ export default class Dashboard extends Module { this.settings.updateContext({ channel: get('props.channelLogin', inst), channelID: get('props.channelID', inst), - hosting: !! inst.props?.hostedChannel?.id + //hosting: !! inst.props?.hostedChannel?.id }); } @@ -62,7 +62,7 @@ export default class Dashboard extends Module { this.settings.updateContext({ channel: null, channelID: null, - hosting: false + //hosting: false }); } diff --git a/src/sites/twitch-twilight/modules/directory/following.jsx b/src/sites/twitch-twilight/modules/directory/following.jsx deleted file mode 100644 index 99ce9efc..00000000 --- a/src/sites/twitch-twilight/modules/directory/following.jsx +++ /dev/null @@ -1,249 +0,0 @@ -'use strict'; - -// ============================================================================ -// Following Page -// ============================================================================ - -import {SiteModule} from 'utilities/module'; -import {createElement} from 'utilities/dom'; -import {get} from 'utilities/object'; - -import {createPopper} from '@popperjs/core'; -import {makeReference} from 'utilities/tooltip'; - -export default class Following extends SiteModule { - constructor(...args) { - super(...args); - - this.inject('site.fine'); - this.inject('site.router'); - this.inject('site.apollo'); - this.inject('site.css_tweaks'); - - this.inject('i18n'); - this.inject('settings'); - - this.settings.add('directory.following.group-hosts', { - default: true, - - ui: { - path: 'Directory > Following @{"description": "**Note:** These settings do not currently work due to changes made by Twitch to how the directory works."} >> Hosts', - title: 'Group Hosts', - description: 'Only show a given hosted channel once in the directory.', - component: 'setting-check-box' - }, - - changed: () => { - this.apollo.maybeRefetch('FollowedIndex_CurrentUser'); - this.apollo.maybeRefetch('FollowingHosts_CurrentUser'); - } - }); - - this.settings.add('directory.following.host-menus', { - default: 1, - - ui: { - path: 'Directory > Following >> Hosts', - title: 'Hosted Channel Menus', - description: 'Display a menu to select which channel to visit when clicking a hosted channel in the directory.', - - component: 'setting-select-box', - - data: [ - {value: 0, title: 'Disabled'}, - {value: 1, title: 'When Multiple are Hosting'}, - {value: 2, title: 'Always'} - ] - }, - - changed: () => this.parent.DirectoryCard.forceUpdate() - }); - - this.hosts = new WeakMap; - } - - modifyLiveUsers(res, path = 'followedLiveUsers') { - const followed_live = get(`data.currentUser.${path}`, res); - if ( ! followed_live ) - return; - - if ( followed_live.nodes ) - followed_live.nodes = this.parent.processNodes(followed_live.nodes); - - else if ( followed_live.edges ) - followed_live.edges = this.parent.processNodes(followed_live.edges); - - return res; - } - - modifyLiveHosts(res) { - const blocked_games = this.settings.provider.get('directory.game.blocked-games', []), - do_grouping = this.settings.get('directory.following.group-hosts'), - edges = get('data.currentUser.followedHosts.nodes', res); - - if ( ! edges || ! edges.length ) - return res; - - this.hosts = new WeakMap(); - const out = []; - - for(const edge of edges) { - if ( ! edge ) - continue; - - const node = edge.node || edge, - hosted = node.hosting, - stream = hosted && hosted.stream; - - if ( ! stream || stream.game && blocked_games.includes(stream.game.name) ) - continue; - - if ( ! stream.viewersCount ) { - if ( ! do_grouping || ! this.hosts[hosted.login] ) - out.push(edge); - continue; - } - - const store = stream.viewersCount = new Number(stream.viewersCount || 0); - - store.createdAt = stream.createdAt; - store.title = stream.title; - //store.game = stream.game; - - if ( do_grouping ) { - const host_nodes = this.hosts[hosted.login]; - if ( host_nodes ) { - host_nodes.push(node); - this.hosts.set(store, host_nodes); - - } else { - this.hosts.set(store, this.hosts[hosted.login] = [node]); - out.push(edge); - } - - } else - out.push(edge); - } - - res.data.currentUser.followedHosts.nodes = out; - return res; - } - - onEnable() { - document.body.addEventListener('click', this.destroyHostMenu.bind(this)); - } - - destroyHostMenu(event) { - if (!event || ! this.hostMenu || event && event.target && event.target.closest('.ffz-channel-selector-outer') === null && Date.now() > this.hostMenuBuffer) { - this.hostMenuPopper && this.hostMenuPopper.destroy(); - this.hostMenu && this.hostMenu.remove(); - this.hostMenuPopper = this.hostMenu = undefined; - } - } - - showHostMenu(inst, channels, event) { - if (this.settings.get('directory.following.host-menus') === 0 || this.settings.get('directory.following.host-menus') === 1 && channels.length < 2) return; - - event.preventDefault(); - event.stopPropagation(); - - this.hostMenuPopper && this.hostMenuPopper.destroy(); - - this.hostMenu && this.hostMenu.remove(); - - const simplebarContentChildren = []; - - // Hosted Channel Header - simplebarContentChildren.push(

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

); - - // Hosted Channel Content - simplebarContentChildren.push( this.parent.hijackUserClick(e, inst.props.channelLogin, this.destroyHostMenu.bind(this))} // eslint-disable-line react/jsx-no-bind - > -
-
- {inst.props.channelDisplayName} -
-

- {inst.props.channelDisplayName} -

-
-
); - - // Hosting Channels Header - simplebarContentChildren.push(

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

); - - // Hosting Channels Content - for (const channel of channels) { - simplebarContentChildren.push( this.parent.hijackUserClick(e, channel.login, this.destroyHostMenu.bind(this))} // eslint-disable-line react/jsx-no-bind - > -
-
- {channel.displayName} -
-

- {channel.displayName} -

-
-
); - } - - this.hostMenu = (
-
-
- {simplebarContentChildren} -
-
-
); - - const root = (document.body.querySelector('#root>div') || document.body); - root.appendChild(this.hostMenu); - - this.hostMenuPopper = createPopper( - makeReference(event.clientX - 60, event.clientY - 60), - this.hostMenu, - { - placement: 'bottom-start', - modifiers: { - flip: { - enabled: false - } - } - } - ); - - this.hostMenuBuffer = Date.now() + 50; - } - - updateChannelCard(inst) { - const card = this.fine.getChildNode(inst); - - if ( ! card ) - return; - - const login = inst.props.channelLogin, - hosting = inst.props.channelLinkTo && inst.props.channelLinkTo.state.content === 'live_host' && this.hosts && this.hosts[login]; - - if ( hosting && this.settings.get('directory.following.group-hosts') ) { - const host_data = this.hosts[login]; - - const title_link = card.querySelector('a[data-test-selector="preview-card-titles__primary-link"]'), - thumbnail_link = card.querySelector('a[data-a-target="preview-card-image-link"]'); - - if ( title_link ) - title_link.addEventListener('click', this.showHostMenu.bind(this, inst, host_data)); - - if ( thumbnail_link ) - thumbnail_link.addEventListener('click', this.showHostMenu.bind(this, inst, host_data)); - } - } -} diff --git a/src/sites/twitch-twilight/modules/directory/index.jsx b/src/sites/twitch-twilight/modules/directory/index.jsx index 354916bf..9ed24f67 100644 --- a/src/sites/twitch-twilight/modules/directory/index.jsx +++ b/src/sites/twitch-twilight/modules/directory/index.jsx @@ -48,7 +48,6 @@ export default class Directory extends SiteModule { this.inject('i18n'); this.inject('settings'); - //this.inject(Following); this.inject(Game); this.DirectoryCard = this.elemental.define( diff --git a/src/sites/twitch-twilight/modules/host_button/index.js b/src/sites/twitch-twilight/modules/host_button/index.js deleted file mode 100644 index f854509c..00000000 --- a/src/sites/twitch-twilight/modules/host_button/index.js +++ /dev/null @@ -1,379 +0,0 @@ -'use strict'; - -// ============================================================================ -// Host Button -// ============================================================================ - -import Module from 'utilities/module'; -import {get} from 'utilities/object'; -import {createElement} from 'utilities/dom'; - -const HOST_ERRORS = { - COMMAND_EXECUTION: { - key: 'command-execution', - text: 'There was an error executing the host command. Please try again later.', - }, - CHAT_CONNECTION: { - key: 'chat-connection', - text: 'There was an issue connecting to chat. Please try again later.', - } -}; - -export default class HostButton extends Module { - constructor(...args) { - super(...args); - - this.should_enable = true; - - this.inject('site'); - this.inject('site.fine'); - this.inject('site.chat'); - this.inject('site.twitch_data'); - this.inject('i18n'); - this.inject('metadata'); - this.inject('settings'); - - this.settings.add('metadata.host-button', { - default: true, - - ui: { - path: 'Channel > Metadata >> Player', - title: 'Host Button', - description: 'Show a host button with the current hosted channel in the tooltip.', - component: 'setting-check-box' - }, - - changed: () => { - const ffz_user = this.site.getUser(), - userLogin = ffz_user && ffz_user.login; - - if (userLogin) - this.joinChannel(userLogin); - - this.metadata.updateMetadata('host'); - } - }); - } - - isChannelHosted(channelLogin) { - return this._last_hosted_channel === channelLogin; - } - - sendHostUnhostCommand(channel) { - if (!this._chat_con) { - this._host_error = HOST_ERRORS.CHAT_CONNECTION; - this._host_updating = false; - return; - } - - const ffz_user = this.site.getUser(), - userLogin = ffz_user && ffz_user.login; - - const commandData = {channel: userLogin, username: channel}; - - this._host_updating = true; - this.metadata.updateMetadata('host'); - - this._host_feedback = setTimeout(() => { - if (this._last_hosted_channel === null) { - this._host_error = HOST_ERRORS.COMMAND_EXECUTION; - this._host_updating = false; - this.metadata.updateMetadata('host'); - } - }, 3000); - - if (this.isChannelHosted(channel)) { - this._chat_con.commands.unhost.execute(commandData); - } else { - this._chat_con.commands.host.execute(commandData); - } - } - - joinChannel(channel) { - if (this._chat_con) { - if (this.settings.get('metadata.host-button') && !this._chat_con.session.channelstate[`#${channel}`]) { - this._chat_con.joinChannel(channel); - } - } - } - - hookIntoChatConnection(inst) { - const userLogin = inst.props.currentUserLogin; - - if (this._chat_con) { - this.joinChannel(userLogin); - return; - } - - this.on('tmi:host', e => { - if (e.channel.substring(1) !== userLogin) return; - - clearTimeout(this._host_feedback); - this._host_error = false; - this._last_hosted_channel = e.target; - - this._host_updating = false; - this.metadata.updateMetadata('host'); - }); - - this.on('tmi:unhost', e => { - if (e.channel.substring(1) !== userLogin) return; - - clearTimeout(this._host_feedback); - this._host_error = false; - this._last_hosted_channel = null; - - this._host_updating = false; - this.metadata.updateMetadata('host'); - }); - - const chatServiceClient = inst.client; - - this._chat_con = chatServiceClient; - if (this.settings.get('metadata.host-button')) - this.joinChannel(userLogin); - } - - onEnable() { - this.on('i18n:update', () => this.metadata.updateMetadata('host')); - - this.metadata.definitions.host = { - order: 150, - border: true, - button: true, - fade_in: true, - modview: true, - - disabled: () => this._host_updating || this._host_error, - - click: data => { - if ( data.channel ) - this.sendHostUnhostCommand(data.channel.login); - }, - - popup: async (data, tip) => { - const vue = this.resolve('vue'), - _host_options_vue = import(/* webpackChunkName: "host-options" */ './host-options.vue'), - _autoHosts = this.fetchAutoHosts(), - _autoHostSettings = this.fetchAutoHostSettings(); - - const [, host_options_vue, autoHosts, autoHostSettings] = await Promise.all([vue.enable(), _host_options_vue, _autoHosts, _autoHostSettings]); - - this._auto_host_tip = tip; - tip.element.classList.remove('tw-pd-1'); - tip.element.classList.add('ffz-balloon--lg'); - vue.component('host-options', host_options_vue.default); - return this.buildAutoHostMenu(vue, autoHosts, autoHostSettings, data.channel); - }, - - label: data => { - const ffz_user = this.site.getUser(); - - if ( ! this.settings.get('metadata.host-button') || ! ffz_user || ! data.channel || data.channel.login === ffz_user.login ) - return; - - if ( data.channel.video && ! this.isChannelHosted(data.channel.login) ) - return; - - if ( this._host_updating ) - return this.i18n.t('metadata.host-button.updating', 'Updating...'); - - return (this._last_hosted_channel && this.isChannelHosted(data.channel && data.channel.login)) - ? this.i18n.t('metadata.host-button.unhost', 'Unhost') - : this.i18n.t('metadata.host-button.host', 'Host'); - }, - - tooltip: () => { - if (this._host_error) { - return this.i18n.t( - `metadata.host-button.tooltip.error.${this._host_error.key}`, - this._host_error.text); - } else { - return this.i18n.t('metadata.host-button.tooltip', - 'Currently hosting: {channel}', - { - channel: this._last_hosted_channel || this.i18n.t('metadata.host-button.tooltip.none', 'None') - }); - } - } - }; - - this.metadata.updateMetadata('host'); - - this.chat.ChatService.ready((cls, instances) => { - for(const inst of instances) - this.hookIntoChatConnection(inst); - }) - - this.chat.ChatService.on('mount', this.hookIntoChatConnection, this); - } - - buildAutoHostMenu(vue, hosts, autoHostSettings, data) { - this._current_channel_id = data.id; - this.activeTab = this.activeTab || 'auto-host'; - - const vueEl = new vue.Vue({ - el: createElement('div'), - render: h => this.vueHostMenu = h('host-options', { - hosts, - autoHostSettings, - activeTab: this.activeTab, - - addedToHosts: this.currentRoomInHosts(), - addToAutoHosts: () => this.addCurrentRoomToHosts(), - rearrangeHosts: event => this.rearrangeHosts(event.oldIndex, event.newIndex), - removeFromHosts: event => this.removeUserFromHosts(event), - setActiveTab: tab => { - this.vueHostMenu.data.activeTab = this.activeTab = tab; - }, - updatePopper: () => { - if (this._auto_host_tip) this._auto_host_tip.update(); - }, - updateCheckbox: e => { - const t = e.target; - let setting = t.dataset.setting, - state = t.checked; - - if ( setting === 'enabled' ) - setting = 'isEnabled'; - else if ( setting === 'teamHost' ) - setting = 'willAutohostTeam'; - else if ( setting === 'strategy' ) - state = state ? 'RANDOM' : 'ORDERED'; - else if ( setting === 'deprioritizeVodcast' ) { - setting = 'willPrioritizeAutohost'; - } - - this.updateAutoHostSetting(setting, state); - } - }) - }); - - return vueEl.$el; - } - - async fetchAutoHosts() { - const user = this.site.getUser(); - if ( ! user ) - return; - - const result = await this.twitch_data.queryApollo( - await import(/* webpackChunkName: 'host-options' */ './autohost_list.gql'), - { - id: user.id - }, - { - fetchPolicy: 'network-only' - } - ); - - return this.autoHosts = get('data.user.autohostChannels.nodes', result); - } - - async fetchAutoHostSettings() { - const user = this.site.getUser(); - if ( ! user ) - return; - - const result = await this.twitch_data.queryApollo( - await import(/* webpackChunkName: 'host-options' */ './autohost_settings.gql'), - { - id: user.id - }, - { - fetchPolicy: 'network-only' - } - ); - - return this.autoHostSettings = get('data.user.autohostSettings', result); - } - - queueHostUpdate() { - if (this._host_update_timer) clearTimeout(this._host_update_timer); - - this._host_update_timer = setTimeout(() => { - this._host_update_timer = undefined; - this.updateAutoHosts(this.autoHosts); - }, 1000); - } - - rearrangeHosts(oldIndex, newIndex) { - const host = this.autoHosts.splice(oldIndex, 1)[0]; - this.autoHosts.splice(newIndex, 0, host); - - this.queueHostUpdate(); - } - - currentRoomInHosts() { - return this.getAutoHostIDs(this.autoHosts).includes(this._current_channel_id); - } - - addCurrentRoomToHosts() { - const newHosts = this.autoHosts.slice(0); - newHosts.push({ id: this._current_channel_id}); - - this.updateAutoHosts(newHosts); - } - - removeUserFromHosts(event) { - const id = event.target.closest('.ffz--host-user').dataset.id; - - const newHosts = []; - for (let i = 0; i < this.autoHosts.length; i++) { - if (this.autoHosts[i].id != id) newHosts.push(this.autoHosts[i]); - } - - this.updateAutoHosts(newHosts); - } - - getAutoHostIDs(hosts) { // eslint-disable-line class-methods-use-this - const ids = []; - if (hosts) { - for (let i = 0; i < hosts.length; i++) { - ids.push(hosts[i].id); - } - } - return ids; - } - - async updateAutoHosts(newHosts) { - const user = this.site.getUser(); - if ( ! user ) - return; - - const autoHosts = this.getAutoHostIDs(newHosts); - - const result = await this.twitch_data.mutate({ - mutation: await import(/* webpackChunkName: 'host-options' */ './autohost_list_mutate.gql'), - variables: { - userID: user.id, - channelIDs: autoHosts - } - }); - - this.autoHosts = get('data.setAutohostChannels.user.autohostChannels.nodes', result); - if (this.vueHostMenu) { - this.vueHostMenu.data.hosts = this.autoHosts; - this.vueHostMenu.data.addedToHosts = this.currentRoomInHosts(); - } - } - - async updateAutoHostSetting(setting, newValue) { - const user = this.site.getUser(); - if ( ! user ) - return; - - const result = await this.twitch_data.mutate({ - mutation: await import(/* webpackChunkName: 'host-options' */ './autohost_settings_mutate.gql'), - variables: { - userID: user.id, - [setting]: newValue - } - }); - - this.autoHostSettings = get('data.updateAutohostSettings.user.autohostSettings', result); - if (this.vueHostMenu) { - this.vueHostMenu.data.autoHostSettings = this.autoHostSettings; - } - } -} \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/layout.js b/src/sites/twitch-twilight/modules/layout.js index 79eade88..6615f71d 100644 --- a/src/sites/twitch-twilight/modules/layout.js +++ b/src/sites/twitch-twilight/modules/layout.js @@ -172,7 +172,7 @@ export default class Layout extends Module { }); this.settings.add('layout.portrait-extra-height', { - requires: ['context.new_channel', 'context.squad_bar', 'context.hosting', 'context.ui.theatreModeEnabled', 'player.theatre.no-whispers', 'whispers.show', 'layout.minimal-navigation'], + requires: ['context.new_channel', 'context.squad_bar', /*'context.hosting',*/ 'context.ui.theatreModeEnabled', 'player.theatre.no-whispers', 'whispers.show', 'layout.minimal-navigation'], process(ctx) { let height = 0; if ( ctx.get('context.ui.theatreModeEnabled') ) { @@ -192,8 +192,8 @@ export default class Layout extends Module { height += ctx.get('context.new_channel') ? 1 : 5; - if ( ctx.get('context.hosting') ) - height += 4; + /*if ( ctx.get('context.hosting') ) + height += 4;*/ } return height; diff --git a/src/sites/twitch-twilight/modules/player.jsx b/src/sites/twitch-twilight/modules/player.jsx index 8e0a323e..1fdf17ec 100644 --- a/src/sites/twitch-twilight/modules/player.jsx +++ b/src/sites/twitch-twilight/modules/player.jsx @@ -196,7 +196,7 @@ export default class Player extends PlayerBase { checkCarousel(inst) { - if ( this.settings.get('channel.hosting.enable') ) + /*if ( this.settings.get('channel.hosting.enable') ) return; if ( inst.props?.playerType === 'channel_home_carousel' ) { @@ -211,7 +211,7 @@ export default class Player extends PlayerBase { events = inst.props.playerEvents; this.stopPlayer(player, events, inst); - } + }*/ } diff --git a/src/sites/twitch-twilight/styles/main.scss b/src/sites/twitch-twilight/styles/main.scss index 0f3a0321..bdefc036 100644 --- a/src/sites/twitch-twilight/styles/main.scss +++ b/src/sites/twitch-twilight/styles/main.scss @@ -9,7 +9,7 @@ @import 'fixes'; -@import 'host_options'; +//@import 'host_options'; @import 'featured_follow'; @import 'mod_card'; @import 'easteregg'; \ No newline at end of file From 9017dd644f62b31e58c24894a6a191dd4549c377 Mon Sep 17 00:00:00 2001 From: SirStendec Date: Fri, 14 Oct 2022 17:23:45 -0400 Subject: [PATCH 03/11] 4.37.1 * Added: "Pin This Message" chat action to allow moderators to pin messages as a quick fix until I can implement the normal vanilla pin button. * Fixed: FrankerFaceZ failing to load correctly on moderation view pages. --- package.json | 2 +- src/modules/chat/actions/index.jsx | 8 +-- src/modules/chat/actions/types.jsx | 51 +++++++++++++++++++ src/sites/twitch-twilight/index.js | 13 ++--- .../twitch-twilight/modules/chat/line.js | 50 +++++++++++------- 5 files changed, 96 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 39e62d68..7be8243c 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "frankerfacez", "author": "Dan Salvato LLC", - "version": "4.37.0", + "version": "4.37.1", "description": "FrankerFaceZ is a Twitch enhancement suite.", "private": true, "license": "Apache-2.0", diff --git a/src/modules/chat/actions/index.jsx b/src/modules/chat/actions/index.jsx index 10ff20f0..4ed509df 100644 --- a/src/modules/chat/actions/index.jsx +++ b/src/modules/chat/actions/index.jsx @@ -743,7 +743,7 @@ export default class Actions extends Module { } - renderInline(msg, mod_icons, current_user, current_room, createElement) { + renderInline(msg, mod_icons, current_user, current_room, createElement, instance = null) { const actions = []; const current_level = this.getUserLevel(current_room, current_user), @@ -778,11 +778,11 @@ export default class Actions extends Module { if ( is_self && ! act.can_self ) continue; - if ( maybe_call(act.hidden, this, data, msg, current_room, current_user, mod_icons) ) + if ( maybe_call(act.hidden, this, data, msg, current_room, current_user, mod_icons, instance) ) continue; if ( act.override_appearance ) { - const out = act.override_appearance.call(this, Object.assign({}, ap), data, msg, current_room, current_user, mod_icons); + const out = act.override_appearance.call(this, Object.assign({}, ap), data, msg, current_room, current_user, mod_icons, instance); if ( out ) ap = out; } @@ -792,7 +792,7 @@ export default class Actions extends Module { continue; const has_color = def.colored && ap.color, - disabled = maybe_call(act.disabled, this, data, msg, current_room, current_user, mod_icons) || false, + disabled = maybe_call(act.disabled, this, data, msg, current_room, current_user, mod_icons, instance) || false, color = has_color && (chat && chat.colors ? chat.colors.process(ap.color) : ap.color), contents = def.render.call(this, ap, createElement, color); diff --git a/src/modules/chat/actions/types.jsx b/src/modules/chat/actions/types.jsx index da02fa29..ded79ae1 100644 --- a/src/modules/chat/actions/types.jsx +++ b/src/modules/chat/actions/types.jsx @@ -3,6 +3,57 @@ import {createElement} from 'utilities/dom'; +// ============================================================================ +// Pin Message +// ============================================================================ + +export const pin = { + presets: [{ + appearance: { + type: 'icon', + icon: 'ffz-i-pin' + } + }], + + required_context: ['message'], + + title: 'Pin This Message', + description: 'Allows you to pin a chat message. Only functions in channels that have access to Twitch\'s pinned messages experiment.', + + can_self: true, + + tooltip() { + return this.i18n.t('chat.actions.pin', 'Pin This Message') + }, + + hidden(data, message, current_room, current_user, mod_icons, instance) { + let line = instance; + + if ( ! line ) + return true; + + if ( ! line.props.isPinnable || ! line.onPinMessageClick ) + return true; + }, + + click(event, data) { + let line = data.line; + if ( ! line ) { + const fine = this.resolve('site.fine'); + line = fine ? fine.searchParent(event.target, n => n.setMessageTray && n.props && n.props.message) : null; + } + + if ( ! line || ! line.props.isPinnable || ! line.onPinMessageClick ) + return; + + if ( line.props.pinnedMessage?.message?.id === data.message_id ) + return; + + line.onPinMessageClick(); + } +} + + // ============================================================================ // Send Reply // ============================================================================ diff --git a/src/sites/twitch-twilight/index.js b/src/sites/twitch-twilight/index.js index c63f0bbe..f8e55c24 100644 --- a/src/sites/twitch-twilight/index.js +++ b/src/sites/twitch-twilight/index.js @@ -249,19 +249,20 @@ Twilight.KNOWN_MODULES = { } } -const VEND_CHUNK = n => ! n || n.includes('vendor'); +//const VEND_CHUNK = n => ! n || n.includes('vendor'); +const VEND_CORE = n => ! n || n.includes('vendor') || n.includes('core'); Twilight.KNOWN_MODULES.core.use_result = true; //Twilight.KNOWN_MODULES.core.chunks = 'core'; -Twilight.KNOWN_MODULES.simplebar.chunks = VEND_CHUNK; -Twilight.KNOWN_MODULES.react.chunks = VEND_CHUNK; -Twilight.KNOWN_MODULES.cookie.chunks = VEND_CHUNK; +Twilight.KNOWN_MODULES.simplebar.chunks = VEND_CORE; +Twilight.KNOWN_MODULES.react.chunks = VEND_CORE; +Twilight.KNOWN_MODULES.cookie.chunks = VEND_CORE; Twilight.KNOWN_MODULES['gql-printer'].use_result = true; -Twilight.KNOWN_MODULES['gql-printer'].chunks = VEND_CHUNK; +Twilight.KNOWN_MODULES['gql-printer'].chunks = VEND_CORE; -Twilight.KNOWN_MODULES.mousetrap.chunks = VEND_CHUNK; +Twilight.KNOWN_MODULES.mousetrap.chunks = VEND_CORE; const CHAT_CHUNK = n => ! n || n.includes('chat'); diff --git a/src/sites/twitch-twilight/modules/chat/line.js b/src/sites/twitch-twilight/modules/chat/line.js index b2825f39..ccb65cb1 100644 --- a/src/sites/twitch-twilight/modules/chat/line.js +++ b/src/sites/twitch-twilight/modules/chat/line.js @@ -741,7 +741,7 @@ other {# messages were deleted by a moderator.} Object.assign(user_props.style, msg.ffz_user_style); const user_bits = [ - t.actions.renderInline(msg, this.props.showModerationIcons, u, r, e), + t.actions.renderInline(msg, this.props.showModerationIcons, u, r, e, this), this.renderInlineHighlight ? this.renderInlineHighlight() : null, e('span', { className: 'chat-line__message--badges' @@ -876,7 +876,7 @@ other {# messages were deleted by a moderator.} out ? null : extra_ts && (this.props.showTimestamps || this.props.isHistorical) && e('span', { className: 'chat-line__timestamp' }, t.chat.formatTime(msg.timestamp)), - (out || msg.sub_anon) ? null : t.actions.renderInline(msg, this.props.showModerationIcons, u, r, e), + (out || msg.sub_anon) ? null : t.actions.renderInline(msg, this.props.showModerationIcons, u, r, e, this), sub_msg ]), mystery ? e('div', { @@ -949,7 +949,7 @@ other {# messages were deleted by a moderator.} out ? null : extra_ts && (this.props.showTimestamps || this.props.isHistorical) && e('span', { className: 'chat-line__timestamp' }, t.chat.formatTime(msg.timestamp)), - (out || msg.sub_anon) ? null : t.actions.renderInline(msg, this.props.showModerationIcons, u, r, e), + (out || msg.sub_anon) ? null : t.actions.renderInline(msg, this.props.showModerationIcons, u, r, e, this), sub_msg ]) ]), @@ -1014,7 +1014,7 @@ other {# messages were deleted by a moderator.} out ? null : extra_ts && (this.props.showTimestamps || this.props.isHistorical) && e('span', { className: 'chat-line__timestamp' }, t.chat.formatTime(msg.timestamp)), - out ? null : t.actions.renderInline(msg, this.props.showModerationIcons, u, r, e), + out ? null : t.actions.renderInline(msg, this.props.showModerationIcons, u, r, e, this), sub_msg ]) ]), @@ -1073,7 +1073,7 @@ other {# messages were deleted by a moderator.} out ? null : extra_ts && (this.props.showTimestamps || this.props.isHistorical) && e('span', { className: 'chat-line__timestamp' }, t.chat.formatTime(msg.timestamp)), - out ? null : t.actions.renderInline(msg, this.props.showModerationIcons, u, r, e), + out ? null : t.actions.renderInline(msg, this.props.showModerationIcons, u, r, e, this), out ? t.i18n.tList('chat.points.redeemed', 'Redeemed {reward} {cost}', {reward, cost}) : t.i18n.tList('chat.points.user-redeemed', '{user} redeemed {reward} {cost}', { @@ -1102,7 +1102,7 @@ other {# messages were deleted by a moderator.} out ? null : extra_ts && (this.props.showTimestamps || this.props.isHistorical) && e('span', { className: 'chat-line__timestamp' }, t.chat.formatTime(msg.timestamp)), - out ? null : t.actions.renderInline(msg, this.props.showModerationIcons, u, r, e), + out ? null : t.actions.renderInline(msg, this.props.showModerationIcons, u, r, e, this), t.i18n.tList('chat.bits-message', 'Cheered {count, plural, one {# Bit} other {# Bits}}', {count: msg.bits || 0}) ]), out && e('div', { @@ -1140,17 +1140,33 @@ other {# messages were deleted by a moderator.} this.props.repliesAppearancePreference && this.props.repliesAppearancePreference === 'expanded' ? this.renderReplyLine() : null, out ]), - e('div', { - className: 'chat-line__reply-icon tw-absolute tw-border-radius-medium tw-c-background-base tw-elevation-1' - }, e('button', { - className: 'tw-align-items-center tw-align-middle tw-border-bottom-left-radius-medium tw-border-bottom-right-radius-medium tw-border-top-left-radius-medium tw-border-top-right-radius-medium tw-button-icon ffz-core-button tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative ffz-tooltip ffz-tooltip--no-mouse', - 'data-test-selector': 'chat-reply-button', - 'aria-label': title, - 'data-title': title, - onClick: this.ffz_open_reply - }, e('span', { - className: 'tw-button-icon__icon' - }, icon))) + /*e('div', { + className: 'chat-line__icons' + }, [*/ + /*e('div', { + className: 'chat-line__pin-icon tw-absolute tw-border-radius-medium tw-c-background-base tw-elevation-1' + }, e('button', { + className: 'tw-align-items-center tw-align-middle tw-border-bottom-left-radius-medium tw-border-bottom-right-radius-medium tw-border-top-left-radius-medium tw-border-top-right-radius-medium tw-button-icon ffz-core-button tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative ffz-tooltip ffz-tooltip--no-mouse', + 'data-test-selector': 'chat-pin-button', + 'aria-label': 'Pin This Message', + 'data-title': 'Pin This Message', + onClick: this.onPinMessageClick + }, e('span', { + className: 'tw-button-icon__icon' + }, e('figure', {className: 'ffz-i-'})))), + //this.renderPinButton(),*/ + e('div', { + className: 'chat-line__reply-icon tw-absolute tw-border-radius-medium tw-c-background-base tw-elevation-1' + }, e('button', { + className: 'tw-align-items-center tw-align-middle tw-border-bottom-left-radius-medium tw-border-bottom-right-radius-medium tw-border-top-left-radius-medium tw-border-top-right-radius-medium tw-button-icon ffz-core-button tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative ffz-tooltip ffz-tooltip--no-mouse', + 'data-test-selector': 'chat-reply-button', + 'aria-label': title, + 'data-title': title, + onClick: this.ffz_open_reply + }, e('span', { + className: 'tw-button-icon__icon' + }, icon))) + //]) ]; } From 09066933d07586adbeb3b525a99af75ac2dd243a Mon Sep 17 00:00:00 2001 From: Conor Finegan Date: Wed, 30 Nov 2022 19:48:44 -0500 Subject: [PATCH 04/11] getEmoteSuggestions no longer uses stale channel info --- src/sites/twitch-twilight/modules/chat/input.jsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/sites/twitch-twilight/modules/chat/input.jsx b/src/sites/twitch-twilight/modules/chat/input.jsx index 7cb95b04..eea54d8a 100644 --- a/src/sites/twitch-twilight/modules/chat/input.jsx +++ b/src/sites/twitch-twilight/modules/chat/input.jsx @@ -942,18 +942,18 @@ export default class Input extends Module { getEmoteSuggestions(input, inst) { + if ( ! inst._ffz_channel_login ) { + const parent = this.fine.searchParent(inst, 'chat-input', 50); + if ( parent ) + this.updateEmoteCompletion(parent, inst); + } + const user = inst._ffz_user, channel_id = inst._ffz_channel_id, channel_login = inst._ffz_channel_login; - if ( ! channel_login ) { - const parent = this.fine.searchParent(inst, 'chat-input', 50); - if ( parent ) - this.updateEmoteCompletion(parent, inst); - - if ( ! channel_login ) - return []; - } + if ( ! channel_login ) + return []; let cache = inst.ffz_ffz_cache; if ( ! cache || cache.user_id !== user?.id || cache.user_login !== user?.login || cache.channel_id !== channel_id || cache.channel_login !== channel_login ) From 9ab9f69583ca99e0b87e514b1195d0bfec631363 Mon Sep 17 00:00:00 2001 From: SirStendec Date: Wed, 7 Dec 2022 16:52:07 -0500 Subject: [PATCH 05/11] 4.38.0 * Added: Options to hide the "Chat Highlight Settings" and "Shield Mode" buttons in the chat input. * Added: Option to fade the video player when paused or buffering. (Closes #1289) * Added: Message Hover chat actions! Now you can add custom actions in the style of Twitch's native "Pin Message" and "Reply to Message" buttons. This change also adds default actions for those that behave similarly to Twitch's native behavior. (Note: The Pin Message action is, by default, only visible if you have your mod icons displayed.) (Closes #1284. Closes #1293.) * Changed: Remove the Reply action from the defaults for In-Line chat actions. * Fixed: Duplicate words in *in* certain localized strings with human friendly relative times. (Closes #1292) * Fixed: Bug where the link testing debug component would not collect its event source when being destroyed. * Fixed: Popup UI elements not appearing with the correct colors. (Closes #1285) * Fixed: Elements in the FFZ Control Center sometimes failing to display scroll-bars correctly after a Twitch update. * Fixed: The mouse cursor not hiding correctly when positioned over the player with controls not visible. * Fixed: Tab-completion sometimes failing to include emotes from add-ons due to improperly cached data. (Closes #1299. Thanks cfinegan) * Fixed: Rich token rendering not setting alt text or width and height on images. --- fontello.config.json | 20 +++ package.json | 2 +- res/font/ffz-fontello.eot | Bin 33276 -> 33876 bytes res/font/ffz-fontello.svg | 6 +- res/font/ffz-fontello.ttf | Bin 33092 -> 33692 bytes res/font/ffz-fontello.woff | Bin 20448 -> 20904 bytes res/font/ffz-fontello.woff2 | Bin 17092 -> 17424 bytes src/modules/chat/actions/index.jsx | 163 ++++++++++++++++-- src/modules/chat/actions/renderers.jsx | 16 ++ src/modules/chat/actions/types.jsx | 52 +++++- .../main_menu/components/action-editor.vue | 38 +++- .../main_menu/components/action-preview.vue | 6 +- .../main_menu/components/chat-actions.vue | 29 +++- .../main_menu/components/link-tester.vue | 5 + .../main_menu/components/profile-selector.vue | 17 +- .../player/css_tweaks/player-fade-paused.scss | 4 + .../player/css_tweaks/player-hide-mouse.scss | 2 +- src/sites/shared/player.jsx | 14 ++ src/sites/twitch-twilight/index.js | 5 + .../modules/chat/emote_menu.jsx | 8 +- .../twitch-twilight/modules/chat/index.js | 28 ++- .../twitch-twilight/modules/chat/line.js | 81 ++++----- .../twitch-twilight/modules/chat/scroller.js | 5 + .../modules/css_tweaks/index.js | 10 +- .../css_tweaks/styles/player-fade-paused.scss | 4 + .../css_tweaks/styles/player-hide-mouse.scss | 2 +- .../twitch-twilight/modules/theme/index.js | 1 + src/sites/twitch-twilight/styles/chat.scss | 40 +++++ src/std-components/simplebar.vue | 8 + src/utilities/constants.js | 1 + src/utilities/ffz-icons.js | 4 +- src/utilities/rich_tokens.js | 7 +- styles/chat.scss | 5 + styles/fontello/ffz-fontello-codes.scss | 2 + styles/fontello/ffz-fontello-embedded.scss | 14 +- styles/fontello/ffz-fontello-ie7-codes.scss | 2 + styles/fontello/ffz-fontello-ie7.scss | 2 + styles/fontello/ffz-fontello.scss | 16 +- 38 files changed, 509 insertions(+), 110 deletions(-) create mode 100644 src/sites/player/css_tweaks/player-fade-paused.scss create mode 100644 src/sites/twitch-twilight/modules/css_tweaks/styles/player-fade-paused.scss diff --git a/fontello.config.json b/fontello.config.json index 28cc0f55..4e800df2 100644 --- a/fontello.config.json +++ b/fontello.config.json @@ -785,6 +785,26 @@ "css": "list-bullet", "code": 61642, "src": "fontawesome" + }, + { + "uid": "7655b7161cf9660beeb1af4036db1198", + "css": "mastodon", + "code": 59463, + "src": "custom_icons", + "selected": true, + "svg": { + "path": "M932.9 220.7C918.5 114.6 825 31 714.2 14.8 695.5 12 624.7 2.1 460.6 2.1H459.4C295.3 2.1 260.1 12 241.4 14.8 133.7 30.5 35.3 105.6 11.4 213 0 265.9-1.3 324.5 0.9 378.2 3.9 455.3 4.5 532.3 11.6 609.1 16.5 660.1 25 710.7 37.1 760.5 59.8 852.5 151.6 929 241.6 960.3 337.9 992.8 441.5 998.2 540.8 975.9 551.7 973.4 562.5 970.4 573.2 967.1 597.3 959.5 625.6 951.1 646.3 936.2 646.6 935.9 646.9 935.7 647 935.4 647.2 935.1 647.3 934.7 647.3 934.4V860C647.3 859.6 647.2 859.3 647.1 859 646.9 858.7 646.7 858.5 646.4 858.3 646.2 858.1 645.9 857.9 645.6 857.8 645.2 857.8 644.9 857.8 644.6 857.8 581 872.9 515.8 880.4 450.4 880.3 337.9 880.3 307.6 827.5 299 805.5 292 786.5 287.6 766.7 285.8 746.5 285.8 746.2 285.9 745.8 286 745.5 286.1 745.2 286.3 744.9 286.6 744.7 286.9 744.5 287.2 744.4 287.5 744.3 287.9 744.2 288.2 744.2 288.5 744.3 351.1 759.2 415.2 766.8 479.5 766.8 495 766.8 510.4 766.8 525.9 766.3 590.6 764.6 658.8 761.3 722.4 749 724 748.7 725.6 748.4 727 748 827.4 728.9 922.9 669.1 932.7 517.5 933 511.5 933.9 455 933.9 448.8 934 427.7 940.8 299.5 932.9 220.7ZM778.4 598.9H672.8V343.1C672.8 289.3 650.1 261.8 604 261.8 553.2 261.8 527.8 294.3 527.8 358.5V498.5H422.9V358.5C422.9 294.3 397.4 261.8 346.7 261.8 300.8 261.8 277.9 289.3 277.9 343.1V598.9H172.4V335.4C172.4 281.5 186.3 238.7 214.1 207 242.7 175.4 280.4 159.1 327.1 159.1 381.1 159.1 421.9 179.7 449.2 220.8L475.4 264.4 501.7 220.8C529 179.7 569.8 159.1 623.8 159.1 670.4 159.1 708 175.4 736.8 207 764.6 238.7 778.5 281.5 778.5 335.4L778.4 598.9Z", + "width": 937 + }, + "search": [ + "logo-black" + ] + }, + { + "uid": "012ff5762ccb18c16bdfdd6baf187406", + "css": "volume-up", + "code": 59464, + "src": "elusive" } ] } \ No newline at end of file diff --git a/package.json b/package.json index 7be8243c..8bc3e3e1 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "frankerfacez", "author": "Dan Salvato LLC", - "version": "4.37.1", + "version": "4.38.0", "description": "FrankerFaceZ is a Twitch enhancement suite.", "private": true, "license": "Apache-2.0", diff --git a/res/font/ffz-fontello.eot b/res/font/ffz-fontello.eot index b84fe1d0ea578fa76147e7387f366460dfa9bd41..f14c114d42872bd8fb48687dddc980010160c2af 100644 GIT binary patch delta 1504 zcmYk6ZERCj7=YjR+>f>cx)!!hH@0;xtkmv{wri1%QigMbRcr^;F$QZl%G%3H3o8Us zS8x!~;0G%|CK!nZG(=<2CL|_?5Cgd2H}HcWsL5i?{z!&F1Z2$ja{R%4^W5jW=l0w; z_k8rh2)lNO$?FaPyf<=0_K0KUj>;qcat0CZF&G8vCWw^%bByjQppAnB*v zygizXEnC@phw=yjn@*>OG8e;NOar7T7hg*6jHQRqHk}0+f!rC~h_B~nvy^=TkVXUE z0O3)qhFn17CqPs`6x?725sD_;b0^t}`U~)QTmm)5AB)St<2!g1xt+p~!T`kcesK=! zrqn`o#q(h)EMzC!092rz{6DY?V2b)wU^V$I4s?*;ULeosKo{A?feqvq4s??v z9H4c&73Dw=S)uy36#B|dr#g;N?|ueY?WLt(v7o69twNR@TA zUSy)AH5!&Jd1tJ{>@G&c&9PNkdug#i&E80bArq9sE}w>pA0%1>i9o9oZVlN>%j}_N z*Rx&juBBR0lte*7ZKJ)$-O|5wXlZBb$|Y{6)~G8qU;s_!bG>H75@dyFa5g74z4`D0 zLMU?ha3nMXs0~JZ4u>y7dy|eA%zQ3P3bV2xfgVhB;%fa*=@0<}Wu^1x76>w|Hkat=Cg+_|EJ7ZdqFq6($cGt!MTMs3tz3)+Zt@w7BwuoZr^9W@31%y)U?;!uI;PaTOX`H zRR38+K||)zpHd=!ZX3;qGdDhX37z@3lL}%YcYpg2A|_DDkEa^hJZ&;Mlu2z#ZP)Ei V4emwf_Zyq|Y{||ZQhjah{ delta 926 zcmYk4TSyd97{~u-cD=H=5fwp_v+jCHZA~f*HITHj?4o5`X{qbF7hKn!)=exTFZHlN z2r~5$8bSC_)PpqPTPRT?5osmji#|kz37H0FH??o~>%sH!o8S4)%sJo8IWr!T{$7`& z|8@Yy9o^Bn`axr7RCi%d+b|%?0Mt5sc4h8f-xtdJftUeDOHih9YJppHNpLA{U!C>W z70P=lHMs4~3XQL${02GJ-FVrRlecjai1Pr|V;-lyK55pf0iL~dKi5M8v6}8U<>7f2 zk1yEjowTh^(7DuNG z0bx)=?qYD5e3!uyau0)2@+$^M$*&n4BTq4)O`M-*aDx1aK^gfogL3lM2yMVg>dY}X z1uU#*P(j|z;50drfsMSA!5Q*?24~5&49=153@XVD4CufX9y2&ko}u~+GxYTr$V&{W z$v+v?kpD0UFTTux7XQA5K^=LFfrI>xK|T3B11I?d0~gh!b6@=?jZ4~r_>p`qr6k6j z7}aSUtT7fW!l(%TrE%ELVZmr#G656B21W}}H{gLF7E96;I$kcC|NDi5c z%}*_|r6om5wWN-uS<>F52h-;=Hf6MC7G%z3*|s-Xi>+6!53DQMp2)xE)>eFRLgc8i f{$Y`$jrtpeaI1M2-)e@yZb_>M77FdorNM-M`Ogow diff --git a/res/font/ffz-fontello.svg b/res/font/ffz-fontello.svg index 029fe910..0e2424a3 100644 --- a/res/font/ffz-fontello.svg +++ b/res/font/ffz-fontello.svg @@ -1,7 +1,7 @@ -Copyright (C) 2021 by original authors @ fontello.com +Copyright (C) 2022 by original authors @ fontello.com @@ -148,6 +148,10 @@ + + + + diff --git a/res/font/ffz-fontello.ttf b/res/font/ffz-fontello.ttf index b65ee43f0a5abd85f808f39bcf1a624b628e1afb..ef0757aeeb35b5df9e40ca246e0045a995763505 100644 GIT binary patch delta 1498 zcmYk6YiyHM7=YjRe3$kc*tNhq-C*llSgGBOwri0=8N%5NN4Oa6lc>&Uk_upZ#f3=V|Ik8#uY0hYKcL&PS0u;n*WP$~eiGB@AimXWT zUXVpa76-H_3W_KUfWQ=i4FD9OB7qVIMnB&Gj9~RJ=5>j_i2#vQJ*OxKA_l(Yu@ zl7)wlwVGTdh`2erDrYMz5vbY~uFz)#QpoAmF!7zZ&mZ^u5+PsER#t8cKDD0hbhR(i zilQV65^5W5HLjMPMg5D~e9ITQ99n}?tVcf@=gjb!5KEC2qu#MNzUj4ziwME+;9xj7 z38)Q(y>`1dOlOmB8ccpHj0)3oAb}2ybopwVA6F0o{pDpdXA}uCtTq{SqKI@o5DwDP z;x6hafn*S92^$U0d4^hayR05#=`2mDTTc}|cLmOpHD94~;v0;4y^A|or!A3a*}va> zv-wKc3T|lwt5dv3?4G)@x+m3Z8vWX2MTUqk!5zJ zea^A2;>CYKu|TI3;Fr9e?i+W$Gv>i~;b^Q1r8AS`!f4@K+<+_It+-qHf{B@SRE4YV znU9)3Gmlt~&JE1{d|u7GebqW^gZ0MzhWR&bduOwr@p@W1^r>H?o=9RHQ$f-jd#?>`EthrlPew PGX>xFW-J`oaY6SVV$Xz_ delta 907 zcmYk4T}TvB6vzK}c1sO$CHjISXWdOrZA~hRG>8=1JekzGkbdQ9)9z?=g!=FX6~KH4QZrP z>alc;0-_o~z0HPFd4$qbkJ75rcsk|R z$;qDPOYWjQYbSsdFHk$?bt#RR(>4w8?xy?2UK&W&3`Z%~=UKh}Pc?aB^%uzV3~I>>4C=^#80d>HGN8qOZe-9v z9%JAnzhlrye$T)~{=mRZ_2|@>en=lB?RHAEMDtfBSu$31nuh93rE@SD^f$uf)3&Vt zwuF0pyM#xJ`piO$H>HU(bvGv>j7J3tZnn$bfrwCCLt`3ZfB5i%pCxj>5slL(d7k5fp Nq;;RrCPPDM{{Y$@3w{6q diff --git a/res/font/ffz-fontello.woff b/res/font/ffz-fontello.woff index d3da1da101be9c740dd6ced02896676442489170..e77f24ad3824296e8fb965888645038bf8ae2f6a 100644 GIT binary patch delta 18761 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 Ed=S)vX8-^I delta 18299 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 literal 17092 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 + val.filter(x => x.appearance && + this.renderers[x.appearance.type] && + (! this.renderers[x.appearance.type].load || this.renderers[x.appearance.type].load(x.appearance)) && + (! x.action || this.actions[x.action]) + ), + + default: [ + {v: {action: 'pin', appearance: {type: 'icon', icon: 'ffz-i-pin'}, options: {}, display: {mod_icons: true}}}, + {v: {action: 'reply', appearance: {type: 'dynamic'}, options: {}, display: {}}} + ], + + type: 'array_merge', + inherit_default: true, + + ui: { + path: 'Chat > Actions > Message Hover @{"description": "Here, you can define custom actions that will appear on top of messages in chat when you hover over them. If you aren\'t seeing an action you\'ve defined here in chat, please make sure that you have enabled Mod Icons in the chat settings menu."}', + component: 'chat-actions', + context: ['user', 'room', 'message'], + inline: true, + modifiers: true, + hover_modifier: false, + + data: () => { + const chat = this.resolve('site.chat'); + + return { + color: val => chat && chat.colors ? chat.colors.process(val) : val, + actions: deep_copy(this.actions), + renderers: deep_copy(this.renderers) + } + } + } + }); + this.settings.add('chat.actions.inline', { // Filter out actions process: (ctx, val) => @@ -80,8 +116,7 @@ export default class Actions extends Module { {v: {action: 'ban', appearance: {type: 'icon', icon: 'ffz-i-block'}, options: {}, display: {mod: true, mod_icons: true, deleted: false}}}, {v: {action: 'unban', appearance: {type: 'icon', icon: 'ffz-i-ok'}, options: {}, display: {mod: true, mod_icons: true, deleted: true}}}, {v: {action: 'timeout', appearance: {type: 'icon', icon: 'ffz-i-clock'}, display: {mod: true, mod_icons: true}}}, - {v: {action: 'msg_delete', appearance: {type: 'icon', icon: 'ffz-i-trash'}, options: {}, display: {mod: true, mod_icons: true}}}, - {v: {action: 'reply', appearance: {type: 'icon', icon: 'ffz-i-reply'}, options: {}, display: {}}} + {v: {action: 'msg_delete', appearance: {type: 'icon', icon: 'ffz-i-trash'}, options: {}, display: {mod: true, mod_icons: true}}} ], type: 'array_merge', @@ -501,6 +536,8 @@ export default class Actions extends Module { if ( ! data ) continue; + data.ctx = 'room'; + const type = data.type; if ( type ) { if ( type === 'new-line' ) { @@ -545,6 +582,12 @@ export default class Actions extends Module { if ( maybe_call(act.hidden, this, data, null, current_room, current_user, mod_icons) ) continue; + if ( ap.type === 'dynamic' ) { + const out = act.dynamicAppearance && act.dynamicAppearance.call(this, Object.assign({}, ap), data, null, current_room, current_user, mod_icons); + if ( out ) + ap = out; + } + if ( act.override_appearance ) { const out = act.override_appearance.call(this, Object.assign({}, ap), data, null, current_room, current_user, mod_icons); if ( out ) @@ -649,6 +692,8 @@ export default class Actions extends Module { if ( ! data ) continue; + data.ctx = 'user_context'; + if ( data.type === 'new-line' ) { line = null; continue; @@ -684,6 +729,12 @@ export default class Actions extends Module { if ( maybe_call(act.hidden, this, data, msg, r, u, mod_icons) ) continue; + if ( ap.type === 'dynamic' ) { + const out = act.dynamicAppearance && act.dynamicAppearance.call(this, Object.assign({}, ap), data, msg, r, u, mod_icons); + if ( out ) + ap = out; + } + if ( act.override_appearance ) { const out = act.override_appearance.call(this, Object.assign({}, ap), data, msg, r, u, mod_icons); if ( out ) @@ -704,7 +755,7 @@ export default class Actions extends Module { const btn = ( +
); + } + + if ( ! had_action ) + return null; + + return (
+ {actions} +
); + } + + renderInline(msg, mod_icons, current_user, current_room, createElement, instance = null) { const actions = []; @@ -762,6 +901,8 @@ export default class Actions extends Module { if ( ! data.action || ! data.appearance ) continue; + data.ctx = 'inline'; + let ap = data.appearance || {}; const disp = data.display || {}, keys = disp.keys, @@ -781,6 +922,12 @@ export default class Actions extends Module { if ( maybe_call(act.hidden, this, data, msg, current_room, current_user, mod_icons, instance) ) continue; + if ( ap.type === 'dynamic' ) { + const out = act.dynamicAppearance && act.dynamicAppearance.call(this, Object.assign({}, ap), data, msg, current_room, current_user, mod_icons, instance); + if ( out ) + ap = out; + } + if ( act.override_appearance ) { const out = act.override_appearance.call(this, Object.assign({}, ap), data, msg, current_room, current_user, mod_icons, instance); if ( out ) @@ -804,7 +951,7 @@ export default class Actions extends Module { had_action = true; list.push(