mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-27 21:05:53 +00:00
Merge branch 'master' into fix/emotes-older-vods
This commit is contained in:
commit
3cccebab17
75 changed files with 3760 additions and 1295 deletions
|
@ -785,6 +785,26 @@
|
||||||
"css": "list-bullet",
|
"css": "list-bullet",
|
||||||
"code": 61642,
|
"code": 61642,
|
||||||
"src": "fontawesome"
|
"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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
14
package.json
14
package.json
|
@ -1,20 +1,20 @@
|
||||||
{
|
{
|
||||||
"name": "frankerfacez",
|
"name": "frankerfacez",
|
||||||
"author": "Dan Salvato LLC",
|
"author": "Dan Salvato LLC",
|
||||||
"version": "4.36.1",
|
"version": "4.39.0",
|
||||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"scripts": {
|
"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}\"",
|
"eslint": "eslint \"src/**/*.{js,jsx,vue}\"",
|
||||||
"clean": "rimraf dist",
|
"clean": "rimraf dist",
|
||||||
"dev": "webpack-dev-server --config webpack.web.dev.js",
|
"dev": "cross-env NODE_OPTIONS=--openssl-legacy-provider webpack-dev-server --config webpack.web.dev.js",
|
||||||
"dev:prod": "webpack-dev-server --config webpack.web.dev.prod.js",
|
"dev:prod": "cross-env NODE_OPTIONS=--openssl-legacy-provider webpack-dev-server --config webpack.web.dev.prod.js",
|
||||||
"build": "pnpm build:prod",
|
"build": "pnpm build:prod",
|
||||||
"build:stats": "cross-env NODE_ENV=production webpack --config webpack.web.prod.js --json > stats.json",
|
"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_ENV=production webpack --config webpack.web.prod.js",
|
"build:prod": "cross-env NODE_OPTIONS=--openssl-legacy-provider NODE_ENV=production webpack --config webpack.web.prod.js",
|
||||||
"build:dev": "pnpm clean && webpack --config webpack.web.dev.js",
|
"build:dev": "pnpm clean && cross-env NODE_OPTIONS=--openssl-legacy-provider webpack --config webpack.web.dev.js",
|
||||||
"font": "pnpm font:edit",
|
"font": "pnpm font:edit",
|
||||||
"font:edit": "fontello-cli --cli-config fontello.client.json edit",
|
"font:edit": "fontello-cli --cli-config fontello.client.json edit",
|
||||||
"font:save": "fontello-cli --cli-config fontello.client.json save && pnpm font:update",
|
"font:save": "fontello-cli --cli-config fontello.client.json save && pnpm font:update",
|
||||||
|
|
438
pnpm-lock.yaml
generated
438
pnpm-lock.yaml
generated
|
@ -1,4 +1,4 @@
|
||||||
lockfileVersion: 5.3
|
lockfileVersion: 5.4
|
||||||
|
|
||||||
overrides:
|
overrides:
|
||||||
ansi-regex@>2.1.1 <5.0.1: '>=5.0.1'
|
ansi-regex@>2.1.1 <5.0.1: '>=5.0.1'
|
||||||
|
@ -52,7 +52,7 @@ specifiers:
|
||||||
semver: ^7.3.5
|
semver: ^7.3.5
|
||||||
sortablejs: ^1.14.0
|
sortablejs: ^1.14.0
|
||||||
sourcemapped-stacktrace: ^1.1.11
|
sourcemapped-stacktrace: ^1.1.11
|
||||||
terser-webpack-plugin: '4'
|
terser-webpack-plugin: ^4.2.3
|
||||||
text-diff: ^1.0.1
|
text-diff: ^1.0.1
|
||||||
vue: ^2.6.14
|
vue: ^2.6.14
|
||||||
vue-clickaway: ^2.2.2
|
vue-clickaway: ^2.2.2
|
||||||
|
@ -97,7 +97,7 @@ dependencies:
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@babel/core': 7.16.0
|
'@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-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-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
|
'@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-syntax-dynamic-import': 7.8.3_@babel+core@7.16.0
|
||||||
'@babel/plugin-transform-react-jsx': 7.16.0_@babel+core@7.16.0
|
'@babel/plugin-transform-react-jsx': 7.16.0_@babel+core@7.16.0
|
||||||
'@ffz/fontello-cli': 1.0.4
|
'@ffz/fontello-cli': 1.0.4
|
||||||
'@webpack-cli/serve': 1.6.0_90245a0e5d2744c77d25a53c1b9ef3e7
|
'@webpack-cli/serve': 1.6.0_sasfuds5e5cmo7jfuu6bxhxt44
|
||||||
babel-loader: 8.2.3_1bd60a6cd0f7024f034efd75ae733a3f
|
babel-loader: 8.2.3_dplau3gq64be6a2o7v2244z2h4
|
||||||
clean-webpack-plugin: 3.0.0_webpack@4.46.0
|
clean-webpack-plugin: 3.0.0_webpack@4.46.0
|
||||||
copy-webpack-plugin: 5.1.2_webpack@4.46.0
|
copy-webpack-plugin: 5.1.2_webpack@4.46.0
|
||||||
cross-env: 7.0.3
|
cross-env: 7.0.3
|
||||||
|
@ -123,11 +123,11 @@ devDependencies:
|
||||||
sass-loader: 7.3.1_webpack@4.46.0
|
sass-loader: 7.3.1_webpack@4.46.0
|
||||||
semver: 7.3.5
|
semver: 7.3.5
|
||||||
terser-webpack-plugin: 4.2.3_webpack@4.46.0
|
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
|
vue-template-compiler: 2.6.14
|
||||||
webpack: 4.46.0_webpack-cli@4.9.1
|
webpack: 4.46.0_webpack-cli@4.9.1
|
||||||
webpack-cli: 4.9.1_703ab6c6d02792f100f7002d09038fa7
|
webpack-cli: 4.9.1_oa5lnrwqe6jpcahxaawqsa4pu4
|
||||||
webpack-dev-server: 4.4.0_webpack-cli@4.9.1+webpack@4.46.0
|
webpack-dev-server: 4.4.0_7d675yvzq6xw7ta76a6hebfduq
|
||||||
webpack-manifest-plugin: 4.0.2_webpack@4.46.0
|
webpack-manifest-plugin: 4.0.2_webpack@4.46.0
|
||||||
webpack-merge: 4.2.2
|
webpack-merge: 4.2.2
|
||||||
|
|
||||||
|
@ -174,7 +174,7 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
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==}
|
resolution: {integrity: sha512-c+AsYOHjI+FgCa+ifLd8sDXp4U4mjkfFgL9NdQWhuA731kAUJs0WdJIXET4A14EJAR9Jv9FFF/MzPWJfV9Oirw==}
|
||||||
engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0}
|
engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -366,6 +366,8 @@ packages:
|
||||||
resolution: {integrity: sha512-RUVpT0G2h6rOZwqLDTrKk7ksNv7YpAilTnYe1/Q+eDjxEceRMKVWbCsX7t8h6C1qCFi/1Y8WZjcEPBAFG27GPw==}
|
resolution: {integrity: sha512-RUVpT0G2h6rOZwqLDTrKk7ksNv7YpAilTnYe1/Q+eDjxEceRMKVWbCsX7t8h6C1qCFi/1Y8WZjcEPBAFG27GPw==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/types': 7.16.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@babel/plugin-proposal-class-properties/7.16.0_@babel+core@7.16.0:
|
/@babel/plugin-proposal-class-properties/7.16.0_@babel+core@7.16.0:
|
||||||
|
@ -682,10 +684,10 @@ packages:
|
||||||
source-map: 0.6.1
|
source-map: 0.6.1
|
||||||
dev: true
|
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==}
|
resolution: {integrity: sha512-97sfH2mYNU+2PzGrmK2haqffDpVASuib9/w2/noxiFi31Z54hW+q3izKQXXQZSNhtiUpAI36uSuYepeBe4wpHQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
consolidate: 0.15.1
|
consolidate: 0.15.1_react@17.0.2
|
||||||
hash-sum: 1.0.2
|
hash-sum: 1.0.2
|
||||||
lru-cache: 4.1.5
|
lru-cache: 4.1.5
|
||||||
merge-source-map: 1.1.0
|
merge-source-map: 1.1.0
|
||||||
|
@ -694,7 +696,61 @@ packages:
|
||||||
source-map: 0.6.1
|
source-map: 0.6.1
|
||||||
vue-template-es2015-compiler: 1.9.1
|
vue-template-es2015-compiler: 1.9.1
|
||||||
optionalDependencies:
|
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
|
dev: true
|
||||||
|
|
||||||
/@webassemblyjs/ast/1.9.0:
|
/@webassemblyjs/ast/1.9.0:
|
||||||
|
@ -824,14 +880,14 @@ packages:
|
||||||
'@xtuc/long': 4.2.2
|
'@xtuc/long': 4.2.2
|
||||||
dev: true
|
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==}
|
resolution: {integrity: sha512-ttOkEkoalEHa7RaFYpM0ErK1xc4twg3Am9hfHhL7MVqlHebnkYd2wuI/ZqTDj0cVzZho6PdinY0phFZV3O0Mzg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
webpack: 4.x.x || 5.x.x
|
webpack: 4.x.x || 5.x.x
|
||||||
webpack-cli: 4.x.x
|
webpack-cli: 4.x.x
|
||||||
dependencies:
|
dependencies:
|
||||||
webpack: 4.46.0_webpack-cli@4.9.1
|
webpack: 4.46.0_webpack-cli@4.9.1
|
||||||
webpack-cli: 4.9.1_703ab6c6d02792f100f7002d09038fa7
|
webpack-cli: 4.9.1_oa5lnrwqe6jpcahxaawqsa4pu4
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@webpack-cli/info/1.4.0_webpack-cli@4.9.1:
|
/@webpack-cli/info/1.4.0_webpack-cli@4.9.1:
|
||||||
|
@ -840,10 +896,10 @@ packages:
|
||||||
webpack-cli: 4.x.x
|
webpack-cli: 4.x.x
|
||||||
dependencies:
|
dependencies:
|
||||||
envinfo: 7.8.1
|
envinfo: 7.8.1
|
||||||
webpack-cli: 4.9.1_703ab6c6d02792f100f7002d09038fa7
|
webpack-cli: 4.9.1_oa5lnrwqe6jpcahxaawqsa4pu4
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@webpack-cli/serve/1.6.0_90245a0e5d2744c77d25a53c1b9ef3e7:
|
/@webpack-cli/serve/1.6.0_sasfuds5e5cmo7jfuu6bxhxt44:
|
||||||
resolution: {integrity: sha512-ZkVeqEmRpBV2GHvjjUZqEai2PpUbuq8Bqd//vEYsp63J8WyexI8ppCqVS3Zs0QADf6aWuPdU+0XsPI647PVlQA==}
|
resolution: {integrity: sha512-ZkVeqEmRpBV2GHvjjUZqEai2PpUbuq8Bqd//vEYsp63J8WyexI8ppCqVS3Zs0QADf6aWuPdU+0XsPI647PVlQA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
webpack-cli: 4.x.x
|
webpack-cli: 4.x.x
|
||||||
|
@ -852,8 +908,8 @@ packages:
|
||||||
webpack-dev-server:
|
webpack-dev-server:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
webpack-cli: 4.9.1_703ab6c6d02792f100f7002d09038fa7
|
webpack-cli: 4.9.1_oa5lnrwqe6jpcahxaawqsa4pu4
|
||||||
webpack-dev-server: 4.4.0_webpack-cli@4.9.1+webpack@4.46.0
|
webpack-dev-server: 4.4.0_7d675yvzq6xw7ta76a6hebfduq
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@xtuc/ieee754/1.2.0:
|
/@xtuc/ieee754/1.2.0:
|
||||||
|
@ -892,6 +948,12 @@ packages:
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: 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:
|
/aggregate-error/3.1.0:
|
||||||
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
|
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
@ -979,6 +1041,8 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
micromatch: 3.1.10
|
micromatch: 3.1.10
|
||||||
normalize-path: 2.1.1
|
normalize-path: 2.1.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
@ -1116,7 +1180,7 @@ packages:
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/babel-loader/8.2.3_1bd60a6cd0f7024f034efd75ae733a3f:
|
/babel-loader/8.2.3_dplau3gq64be6a2o7v2244z2h4:
|
||||||
resolution: {integrity: sha512-n4Zeta8NC3QAsuyiizu0GkmRcQ6clkV9WFUnUf1iXP//IeSKbWjofW3UHyZVwlOB4y039YQKefawyTn64Zwbuw==}
|
resolution: {integrity: sha512-n4Zeta8NC3QAsuyiizu0GkmRcQ6clkV9WFUnUf1iXP//IeSKbWjofW3UHyZVwlOB4y039YQKefawyTn64Zwbuw==}
|
||||||
engines: {node: '>= 8.9'}
|
engines: {node: '>= 8.9'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -1221,6 +1285,8 @@ packages:
|
||||||
qs: 6.7.0
|
qs: 6.7.0
|
||||||
raw-body: 2.4.0
|
raw-body: 2.4.0
|
||||||
type-is: 1.6.18
|
type-is: 1.6.18
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/bonjour/3.5.0:
|
/bonjour/3.5.0:
|
||||||
|
@ -1255,6 +1321,8 @@ packages:
|
||||||
snapdragon-node: 2.1.1
|
snapdragon-node: 2.1.1
|
||||||
split-string: 3.1.0
|
split-string: 3.1.0
|
||||||
to-regex: 3.0.2
|
to-regex: 3.0.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/braces/3.0.2:
|
/braces/3.0.2:
|
||||||
|
@ -1328,7 +1396,7 @@ packages:
|
||||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
caniuse-lite: 1.0.30001278
|
caniuse-lite: 1.0.30001387
|
||||||
electron-to-chromium: 1.3.890
|
electron-to-chromium: 1.3.890
|
||||||
escalade: 3.1.1
|
escalade: 3.1.1
|
||||||
node-releases: 2.0.1
|
node-releases: 2.0.1
|
||||||
|
@ -1421,6 +1489,8 @@ packages:
|
||||||
ssri: 8.0.1
|
ssri: 8.0.1
|
||||||
tar: 6.1.11
|
tar: 6.1.11
|
||||||
unique-filename: 1.1.1
|
unique-filename: 1.1.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bluebird
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/cache-base/1.0.1:
|
/cache-base/1.0.1:
|
||||||
|
@ -1455,8 +1525,8 @@ packages:
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/caniuse-lite/1.0.30001278:
|
/caniuse-lite/1.0.30001387:
|
||||||
resolution: {integrity: sha512-mpF9KeH8u5cMoEmIic/cr7PNS+F5LWBk0t2ekGT60lFf0Wq+n9LspAj0g3P+o7DQhD3sUdlMln4YFAWhFYn9jg==}
|
resolution: {integrity: sha512-fKDH0F1KOJvR+mWSOvhj8lVRr/Q/mc5u5nabU2vi1/sgvlSqEsE8dOq0Hy/BqVbDkCYQPRRHB1WRjW6PGB/7PA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/chainsaw/0.1.0:
|
/chainsaw/0.1.0:
|
||||||
|
@ -1484,7 +1554,7 @@ packages:
|
||||||
|
|
||||||
/chokidar/2.1.8:
|
/chokidar/2.1.8:
|
||||||
resolution: {integrity: sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==}
|
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:
|
dependencies:
|
||||||
anymatch: 2.0.0
|
anymatch: 2.0.0
|
||||||
async-each: 1.0.3
|
async-each: 1.0.3
|
||||||
|
@ -1499,6 +1569,8 @@ packages:
|
||||||
upath: 1.2.0
|
upath: 1.2.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 1.2.13
|
fsevents: 1.2.13
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
@ -1518,6 +1590,23 @@ packages:
|
||||||
fsevents: 2.3.2
|
fsevents: 2.3.2
|
||||||
dev: true
|
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:
|
/chownr/1.1.4:
|
||||||
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
|
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -1662,6 +1751,8 @@ packages:
|
||||||
on-headers: 1.0.2
|
on-headers: 1.0.2
|
||||||
safe-buffer: 5.1.2
|
safe-buffer: 5.1.2
|
||||||
vary: 1.1.2
|
vary: 1.1.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/concat-map/0.0.1:
|
/concat-map/0.0.1:
|
||||||
|
@ -1687,11 +1778,173 @@ packages:
|
||||||
resolution: {integrity: sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==}
|
resolution: {integrity: sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/consolidate/0.15.1:
|
/consolidate/0.15.1_react@17.0.2:
|
||||||
resolution: {integrity: sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==}
|
resolution: {integrity: sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==}
|
||||||
engines: {node: '>= 0.10.0'}
|
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:
|
dependencies:
|
||||||
bluebird: 3.7.2
|
bluebird: 3.7.2
|
||||||
|
react: 17.0.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/constants-browserify/1.0.0:
|
/constants-browserify/1.0.0:
|
||||||
|
@ -1872,12 +2125,22 @@ packages:
|
||||||
|
|
||||||
/debug/2.6.9:
|
/debug/2.6.9:
|
||||||
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
|
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
|
||||||
|
peerDependencies:
|
||||||
|
supports-color: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
supports-color:
|
||||||
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.0.0
|
ms: 2.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/debug/3.2.7:
|
/debug/3.2.7:
|
||||||
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
|
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
|
||||||
|
peerDependencies:
|
||||||
|
supports-color: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
supports-color:
|
||||||
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -2435,6 +2698,8 @@ packages:
|
||||||
regex-not: 1.0.2
|
regex-not: 1.0.2
|
||||||
snapdragon: 0.8.2
|
snapdragon: 0.8.2
|
||||||
to-regex: 3.0.2
|
to-regex: 3.0.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/express/4.17.1:
|
/express/4.17.1:
|
||||||
|
@ -2471,6 +2736,8 @@ packages:
|
||||||
type-is: 1.6.18
|
type-is: 1.6.18
|
||||||
utils-merge: 1.0.1
|
utils-merge: 1.0.1
|
||||||
vary: 1.1.2
|
vary: 1.1.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/extend-shallow/2.0.1:
|
/extend-shallow/2.0.1:
|
||||||
|
@ -2500,6 +2767,8 @@ packages:
|
||||||
regex-not: 1.0.2
|
regex-not: 1.0.2
|
||||||
snapdragon: 0.8.2
|
snapdragon: 0.8.2
|
||||||
to-regex: 3.0.2
|
to-regex: 3.0.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/extract-loader/2.0.1:
|
/extract-loader/2.0.1:
|
||||||
|
@ -2609,6 +2878,8 @@ packages:
|
||||||
parseurl: 1.3.3
|
parseurl: 1.3.3
|
||||||
statuses: 1.5.0
|
statuses: 1.5.0
|
||||||
unpipe: 1.0.0
|
unpipe: 1.0.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/find-cache-dir/2.1.0:
|
/find-cache-dir/2.1.0:
|
||||||
|
@ -2743,7 +3014,7 @@ packages:
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
bindings: 1.5.0
|
bindings: 1.5.0
|
||||||
nan: 2.15.0
|
nan: 2.16.0
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
@ -3819,6 +4090,8 @@ packages:
|
||||||
regex-not: 1.0.2
|
regex-not: 1.0.2
|
||||||
snapdragon: 0.8.2
|
snapdragon: 0.8.2
|
||||||
to-regex: 3.0.2
|
to-regex: 3.0.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/micromatch/4.0.4:
|
/micromatch/4.0.4:
|
||||||
|
@ -3996,8 +4269,8 @@ packages:
|
||||||
thunky: 1.1.0
|
thunky: 1.1.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/nan/2.15.0:
|
/nan/2.16.0:
|
||||||
resolution: {integrity: sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==}
|
resolution: {integrity: sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
@ -4017,6 +4290,8 @@ packages:
|
||||||
regex-not: 1.0.2
|
regex-not: 1.0.2
|
||||||
snapdragon: 0.8.2
|
snapdragon: 0.8.2
|
||||||
to-regex: 3.0.2
|
to-regex: 3.0.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/natural-compare/1.4.0:
|
/natural-compare/1.4.0:
|
||||||
|
@ -4483,6 +4758,8 @@ packages:
|
||||||
async: 2.6.3
|
async: 2.6.3
|
||||||
debug: 3.2.7
|
debug: 3.2.7
|
||||||
mkdirp: 0.5.5
|
mkdirp: 0.5.5
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/posix-character-classes/0.1.1:
|
/posix-character-classes/0.1.1:
|
||||||
|
@ -4547,8 +4824,8 @@ packages:
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/prettier/2.4.1:
|
/prettier/2.7.1:
|
||||||
resolution: {integrity: sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==}
|
resolution: {integrity: sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==}
|
||||||
engines: {node: '>=10.13.0'}
|
engines: {node: '>=10.13.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
|
@ -4570,6 +4847,11 @@ packages:
|
||||||
|
|
||||||
/promise-inflight/1.0.1:
|
/promise-inflight/1.0.1:
|
||||||
resolution: {integrity: sha1-mEcocL8igTL8vdhoEputEsPAKeM=}
|
resolution: {integrity: sha1-mEcocL8igTL8vdhoEputEsPAKeM=}
|
||||||
|
peerDependencies:
|
||||||
|
bluebird: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
bluebird:
|
||||||
|
optional: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/prop-types/15.7.2:
|
/prop-types/15.7.2:
|
||||||
|
@ -4715,7 +4997,6 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
loose-envify: 1.4.0
|
loose-envify: 1.4.0
|
||||||
object-assign: 4.1.1
|
object-assign: 4.1.1
|
||||||
dev: false
|
|
||||||
|
|
||||||
/readable-stream/2.3.7:
|
/readable-stream/2.3.7:
|
||||||
resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==}
|
resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==}
|
||||||
|
@ -4744,6 +5025,8 @@ packages:
|
||||||
graceful-fs: 4.2.8
|
graceful-fs: 4.2.8
|
||||||
micromatch: 3.1.10
|
micromatch: 3.1.10
|
||||||
readable-stream: 2.3.7
|
readable-stream: 2.3.7
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
@ -5012,6 +5295,8 @@ packages:
|
||||||
on-finished: 2.3.0
|
on-finished: 2.3.0
|
||||||
range-parser: 1.2.1
|
range-parser: 1.2.1
|
||||||
statuses: 1.5.0
|
statuses: 1.5.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/serialize-javascript/4.0.0:
|
/serialize-javascript/4.0.0:
|
||||||
|
@ -5037,6 +5322,8 @@ packages:
|
||||||
http-errors: 1.6.3
|
http-errors: 1.6.3
|
||||||
mime-types: 2.1.33
|
mime-types: 2.1.33
|
||||||
parseurl: 1.3.3
|
parseurl: 1.3.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/serve-static/1.14.1:
|
/serve-static/1.14.1:
|
||||||
|
@ -5047,6 +5334,8 @@ packages:
|
||||||
escape-html: 1.0.3
|
escape-html: 1.0.3
|
||||||
parseurl: 1.3.3
|
parseurl: 1.3.3
|
||||||
send: 0.17.1
|
send: 0.17.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/set-immediate-shim/1.0.1:
|
/set-immediate-shim/1.0.1:
|
||||||
|
@ -5160,6 +5449,8 @@ packages:
|
||||||
source-map: 0.5.7
|
source-map: 0.5.7
|
||||||
source-map-resolve: 0.5.3
|
source-map-resolve: 0.5.3
|
||||||
use: 3.1.1
|
use: 3.1.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/sockjs/0.3.21:
|
/sockjs/0.3.21:
|
||||||
|
@ -5472,6 +5763,8 @@ packages:
|
||||||
terser: 5.9.0
|
terser: 5.9.0
|
||||||
webpack: 4.46.0_webpack-cli@4.9.1
|
webpack: 4.46.0_webpack-cli@4.9.1
|
||||||
webpack-sources: 1.4.3
|
webpack-sources: 1.4.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bluebird
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/terser/4.8.0:
|
/terser/4.8.0:
|
||||||
|
@ -5479,6 +5772,7 @@ packages:
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
|
acorn: 8.8.0
|
||||||
commander: 2.20.3
|
commander: 2.20.3
|
||||||
source-map: 0.6.1
|
source-map: 0.6.1
|
||||||
source-map-support: 0.5.20
|
source-map-support: 0.5.20
|
||||||
|
@ -5489,6 +5783,7 @@ packages:
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
|
acorn: 8.8.0
|
||||||
commander: 2.20.3
|
commander: 2.20.3
|
||||||
source-map: 0.7.3
|
source-map: 0.7.3
|
||||||
source-map-support: 0.5.20
|
source-map-support: 0.5.20
|
||||||
|
@ -5781,20 +6076,23 @@ packages:
|
||||||
resolution: {integrity: sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==}
|
resolution: {integrity: sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/vue-loader/15.9.8_e15c8784917900cf55a453471769391c:
|
/vue-loader/15.9.8_4jxi6swgjym7ovrhfrafs526hm:
|
||||||
resolution: {integrity: sha512-GwSkxPrihfLR69/dSV3+5CdMQ0D+jXg8Ma1S4nQXKJAznYFX14vHdc/NetQc34Dw+rBbIJyP7JOuVb9Fhprvog==}
|
resolution: {integrity: sha512-GwSkxPrihfLR69/dSV3+5CdMQ0D+jXg8Ma1S4nQXKJAznYFX14vHdc/NetQc34Dw+rBbIJyP7JOuVb9Fhprvog==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
'@vue/compiler-sfc': ^3.0.8
|
||||||
cache-loader: '*'
|
cache-loader: '*'
|
||||||
css-loader: '*'
|
css-loader: '*'
|
||||||
vue-template-compiler: '*'
|
vue-template-compiler: '*'
|
||||||
webpack: ^3.0.0 || ^4.1.0 || ^5.0.0-0
|
webpack: ^3.0.0 || ^4.1.0 || ^5.0.0-0
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
|
'@vue/compiler-sfc':
|
||||||
|
optional: true
|
||||||
cache-loader:
|
cache-loader:
|
||||||
optional: true
|
optional: true
|
||||||
vue-template-compiler:
|
vue-template-compiler:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
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
|
css-loader: 3.6.0_webpack@4.46.0
|
||||||
hash-sum: 1.0.2
|
hash-sum: 1.0.2
|
||||||
loader-utils: 1.4.0
|
loader-utils: 1.4.0
|
||||||
|
@ -5802,6 +6100,60 @@ packages:
|
||||||
vue-style-loader: 4.1.3
|
vue-style-loader: 4.1.3
|
||||||
vue-template-compiler: 2.6.14
|
vue-template-compiler: 2.6.14
|
||||||
webpack: 4.46.0_webpack-cli@4.9.1
|
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
|
dev: true
|
||||||
|
|
||||||
/vue-observe-visibility/1.0.0:
|
/vue-observe-visibility/1.0.0:
|
||||||
|
@ -5841,6 +6193,8 @@ packages:
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
chokidar: 2.1.8
|
chokidar: 2.1.8
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
@ -5850,8 +6204,10 @@ packages:
|
||||||
graceful-fs: 4.2.8
|
graceful-fs: 4.2.8
|
||||||
neo-async: 2.6.2
|
neo-async: 2.6.2
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
chokidar: 3.5.2
|
chokidar: 3.5.3
|
||||||
watchpack-chokidar2: 2.0.1
|
watchpack-chokidar2: 2.0.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/wbuf/1.7.3:
|
/wbuf/1.7.3:
|
||||||
|
@ -5864,7 +6220,7 @@ packages:
|
||||||
resolution: {integrity: sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=}
|
resolution: {integrity: sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/webpack-cli/4.9.1_703ab6c6d02792f100f7002d09038fa7:
|
/webpack-cli/4.9.1_oa5lnrwqe6jpcahxaawqsa4pu4:
|
||||||
resolution: {integrity: sha512-JYRFVuyFpzDxMDB+v/nanUdQYcZtqFPGzmlW4s+UkPMFhSpfRNmf1z4AwYcHJVdvEFAM7FFCQdNTpsBYhDLusQ==}
|
resolution: {integrity: sha512-JYRFVuyFpzDxMDB+v/nanUdQYcZtqFPGzmlW4s+UkPMFhSpfRNmf1z4AwYcHJVdvEFAM7FFCQdNTpsBYhDLusQ==}
|
||||||
engines: {node: '>=10.13.0'}
|
engines: {node: '>=10.13.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
@ -5885,9 +6241,9 @@ packages:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@discoveryjs/json-ext': 0.5.5
|
'@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/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
|
colorette: 2.0.16
|
||||||
commander: 7.2.0
|
commander: 7.2.0
|
||||||
execa: 5.1.1
|
execa: 5.1.1
|
||||||
|
@ -5896,7 +6252,7 @@ packages:
|
||||||
interpret: 2.2.0
|
interpret: 2.2.0
|
||||||
rechoir: 0.7.1
|
rechoir: 0.7.1
|
||||||
webpack: 4.46.0_webpack-cli@4.9.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
|
webpack-merge: 5.8.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
@ -5914,7 +6270,7 @@ packages:
|
||||||
webpack: 4.46.0_webpack-cli@4.9.1
|
webpack: 4.46.0_webpack-cli@4.9.1
|
||||||
dev: true
|
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==}
|
resolution: {integrity: sha512-+S0XRIbsopVjPFjCO8I07FXYBWYqkFmuP56ucGMTs2hA/gV4q2M9xTmNo5Tg4o8ffRR+Nm3AsXnQXxKRyYovrA==}
|
||||||
engines: {node: '>= 12.13.0'}
|
engines: {node: '>= 12.13.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
@ -5949,7 +6305,7 @@ packages:
|
||||||
strip-ansi: 7.0.1
|
strip-ansi: 7.0.1
|
||||||
url: 0.11.0
|
url: 0.11.0
|
||||||
webpack: 4.46.0_webpack-cli@4.9.1
|
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
|
webpack-dev-middleware: 5.2.1_webpack@4.46.0
|
||||||
ws: 8.2.3
|
ws: 8.2.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
|
@ -6042,8 +6398,10 @@ packages:
|
||||||
tapable: 1.1.3
|
tapable: 1.1.3
|
||||||
terser-webpack-plugin: 1.4.5_webpack@4.46.0
|
terser-webpack-plugin: 1.4.5_webpack@4.46.0
|
||||||
watchpack: 1.7.5
|
watchpack: 1.7.5
|
||||||
webpack-cli: 4.9.1_703ab6c6d02792f100f7002d09038fa7
|
webpack-cli: 4.9.1_oa5lnrwqe6jpcahxaawqsa4pu4
|
||||||
webpack-sources: 1.4.3
|
webpack-sources: 1.4.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/websocket-driver/0.7.4:
|
/websocket-driver/0.7.4:
|
||||||
|
|
Binary file not shown.
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" standalone="no"?>
|
<?xml version="1.0" standalone="no"?>
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg">
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
<metadata>Copyright (C) 2021 by original authors @ fontello.com</metadata>
|
<metadata>Copyright (C) 2022 by original authors @ fontello.com</metadata>
|
||||||
<defs>
|
<defs>
|
||||||
<font id="ffz-fontello" horiz-adv-x="1000" >
|
<font id="ffz-fontello" horiz-adv-x="1000" >
|
||||||
<font-face font-family="ffz-fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
|
<font-face font-family="ffz-fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
|
||||||
|
@ -148,6 +148,10 @@
|
||||||
|
|
||||||
<glyph glyph-name="right-open" unicode="" d="M618 361l-414-415q-11-10-25-10t-25 10l-93 93q-11 11-11 25t11 25l296 297-296 296q-11 11-11 25t11 25l93 93q10 11 25 11t25-11l414-414q10-11 10-25t-10-25z" horiz-adv-x="714.3" />
|
<glyph glyph-name="right-open" unicode="" d="M618 361l-414-415q-11-10-25-10t-25 10l-93 93q-11 11-11 25t11 25l296 297-296 296q-11 11-11 25t11 25l93 93q10 11 25 11t25-11l414-414q10-11 10-25t-10-25z" horiz-adv-x="714.3" />
|
||||||
|
|
||||||
|
<glyph glyph-name="mastodon" unicode="" d="M933 629c-14 106-108 190-219 206-18 3-89 13-253 13h-2c-164 0-199-10-218-13-107-15-206-91-230-198-11-53-12-111-10-165 3-77 4-154 11-231 5-51 13-102 25-151 23-93 115-169 205-200 96-33 200-38 299-16 11 3 22 6 32 9 24 7 53 16 73 31 1 0 1 0 1 1 0 0 0 0 0 1v74c0 0 0 1 0 1 0 0 0 0-1 1 0 0 0 0 0 0-1 0-1 0-1 0-64-15-129-22-195-22-112 0-142 53-151 75-7 19-11 38-13 59 0 0 0 0 0 1 0 0 0 0 1 0 0 1 0 1 1 1 0 0 0 0 1 0 62-15 126-23 191-23 15 0 30 0 46 1 65 1 133 5 196 17 2 0 4 1 5 1 100 19 196 79 206 231 0 6 1 62 1 68 0 21 7 150-1 228z m-155-378h-105v256c0 54-23 81-69 81-51 0-76-32-76-96v-140h-105v140c0 64-26 96-76 96-46 0-69-27-69-81v-256h-106v264c0 54 14 96 42 128 29 32 66 48 113 48 54 0 95-21 122-62l26-43 27 43c27 41 68 62 122 62 46 0 84-16 113-48 28-32 42-74 42-128l-1-264z" horiz-adv-x="937" />
|
||||||
|
|
||||||
|
<glyph glyph-name="volume-up" unicode="" d="M0 169l0 360 203 0 307 250 0-858-307 248-203 0z m563 33q62 63 62 151t-62 152l60 65q90-90 92-219 0-125-92-213z m101-105q106 105 106 256t-106 258l66 62q131-133 131-319t-131-321z m100-98q146 147 146 354t-146 353l62 65q82-82 128-190t46-227-46-229-128-190z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
<glyph glyph-name="move" unicode="" d="M1000 350q0-14-11-25l-142-143q-11-11-26-11t-25 11-10 25v72h-215v-215h72q14 0 25-10t11-25-11-25l-143-143q-10-11-25-11t-25 11l-143 143q-11 10-11 25t11 25 25 10h72v215h-215v-72q0-14-10-25t-25-11-25 11l-143 143q-11 11-11 25t11 25l143 143q10 11 25 11t25-11 10-25v-72h215v215h-72q-14 0-25 10t-11 25 11 26l143 142q11 11 25 11t25-11l143-142q11-11 11-26t-11-25-25-10h-72v-215h215v72q0 14 10 25t25 11 26-11l142-143q11-10 11-25z" horiz-adv-x="1000" />
|
<glyph glyph-name="move" unicode="" d="M1000 350q0-14-11-25l-142-143q-11-11-26-11t-25 11-10 25v72h-215v-215h72q14 0 25-10t11-25-11-25l-143-143q-10-11-25-11t-25 11l-143 143q-11 10-11 25t11 25 25 10h72v215h-215v-72q0-14-10-25t-25-11-25 11l-143 143q-11 11-11 25t11 25l143 143q10 11 25 11t25-11 10-25v-72h215v215h-72q-14 0-25 10t-11 25 11 26l143 142q11 11 25 11t25-11l143-142q11-11 11-26t-11-25-25-10h-72v-215h215v72q0 14 10 25t25 11 26-11l142-143q11-10 11-25z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
<glyph glyph-name="link-ext" unicode="" d="M786 332v-178q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h393q7 0 12-5t5-13v-36q0-8-5-13t-12-5h-393q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v178q0 8 5 13t13 5h36q8 0 13-5t5-13z m214 482v-285q0-15-11-25t-25-11-25 11l-98 98-364-364q-5-6-13-6t-12 6l-64 64q-6 5-6 12t6 13l364 364-98 98q-11 11-11 25t11 25 25 11h285q15 0 25-11t11-25z" horiz-adv-x="1000" />
|
<glyph glyph-name="link-ext" unicode="" d="M786 332v-178q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h393q7 0 12-5t5-13v-36q0-8-5-13t-12-5h-393q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v178q0 8 5 13t13 5h36q8 0 13-5t5-13z m214 482v-285q0-15-11-25t-25-11-25 11l-98 98-364-364q-5-6-13-6t-12 6l-64 64q-6 5-6 12t6 13l364 364-98 98q-11 11-11 25t11 25 25 11h285q15 0 25-11t11-25z" horiz-adv-x="1000" />
|
||||||
|
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 44 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -2,7 +2,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
(() => {
|
(() => {
|
||||||
// Don't run on certain sub-domains.
|
// 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;
|
return;
|
||||||
|
|
||||||
const DEBUG = localStorage.ffzDebugMode == 'true' && document.body.classList.contains('ffz-dev'),
|
const DEBUG = localStorage.ffzDebugMode == 'true' && document.body.classList.contains('ffz-dev'),
|
||||||
|
|
|
@ -48,6 +48,8 @@ export default class ExperimentManager extends Module {
|
||||||
constructor(...args) {
|
constructor(...args) {
|
||||||
super(...args);
|
super(...args);
|
||||||
|
|
||||||
|
this.get = this.getAssignment;
|
||||||
|
|
||||||
this.inject('settings');
|
this.inject('settings');
|
||||||
|
|
||||||
this.settings.addUI('experiments', {
|
this.settings.addUI('experiments', {
|
||||||
|
|
|
@ -1,4 +1,12 @@
|
||||||
{
|
{
|
||||||
|
"line_renderer": {
|
||||||
|
"name": "Modular Chat Line Rendering",
|
||||||
|
"description": "Enable a newer, modular chat line renderer.",
|
||||||
|
"groups": [
|
||||||
|
{"value": true, "weight": 0},
|
||||||
|
{"value": false, "weight": 100}
|
||||||
|
]
|
||||||
|
},
|
||||||
"api_load": {
|
"api_load": {
|
||||||
"name": "New API Stress Testing",
|
"name": "New API Stress Testing",
|
||||||
"description": "Send duplicate requests to the new API server for load testing.",
|
"description": "Send duplicate requests to the new API server for load testing.",
|
||||||
|
|
29
src/modules/chat/actions/components/edit-copy.vue
Normal file
29
src/modules/chat/actions/components/edit-copy.vue
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<template lang="html">
|
||||||
|
<div class="tw-flex tw-align-items-start">
|
||||||
|
<label for="edit_format" class="tw-mg-y-05">
|
||||||
|
{{ t('setting.actions.format', 'Format') }}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="tw-full-width">
|
||||||
|
<input
|
||||||
|
id="edit_format"
|
||||||
|
v-model="value.format"
|
||||||
|
:placeholder="defaults.format"
|
||||||
|
class="tw-border-radius-medium tw-font-size-6 tw-full-width ffz-input tw-pd-x-1 tw-pd-y-05 tw-mg-y-05"
|
||||||
|
@input="$emit('input', value)"
|
||||||
|
>
|
||||||
|
|
||||||
|
<div class="tw-c-text-alt-2 tw-mg-b-1">
|
||||||
|
{{ t('setting.actions.variables', 'Available Variables: {vars}', {vars}) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['value', 'defaults', 'vars'],
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
40
src/modules/chat/actions/components/edit-emote.vue
Normal file
40
src/modules/chat/actions/components/edit-emote.vue
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
<template lang="html">
|
||||||
|
<div class="tw-flex tw-align-items-start">
|
||||||
|
<label class="tw-mg-y-05">
|
||||||
|
{{ t('setting.actions.emote', 'Emote') }}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<emote-picker
|
||||||
|
:value="val"
|
||||||
|
class="tw-full-width"
|
||||||
|
@input="change"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['value'],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
val: this.value
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
value() {
|
||||||
|
this.val = this.value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
change(val) {
|
||||||
|
this.val = val;
|
||||||
|
this.$emit('input', this.val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
5
src/modules/chat/actions/components/preview-emote.vue
Normal file
5
src/modules/chat/actions/components/preview-emote.vue
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<template functional>
|
||||||
|
<figure class="mod-icon__image">
|
||||||
|
<img :src="props.data.src">
|
||||||
|
</figure>
|
||||||
|
</template>
|
|
@ -38,6 +38,18 @@ export default class Actions extends Module {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.settings.add('chat.actions.hover-size', {
|
||||||
|
default: 30,
|
||||||
|
ui: {
|
||||||
|
path: 'Chat > Actions > Message Hover >> Appearance',
|
||||||
|
title: 'Action Size',
|
||||||
|
description: "How tall hover actions should be, in pixels. This may be affected by your browser's zoom and font size settings.",
|
||||||
|
component: 'setting-text-box',
|
||||||
|
process: 'to_int',
|
||||||
|
bounds: [1]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.settings.add('chat.actions.reasons', {
|
this.settings.add('chat.actions.reasons', {
|
||||||
default: [
|
default: [
|
||||||
{v: {text: 'One-Man Spam', i18n: 'chat.reasons.spam'}},
|
{v: {text: 'One-Man Spam', i18n: 'chat.reasons.spam'}},
|
||||||
|
@ -67,6 +79,42 @@ export default class Actions extends Module {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.settings.add('chat.actions.hover', {
|
||||||
|
process: (ctx, val) =>
|
||||||
|
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', {
|
this.settings.add('chat.actions.inline', {
|
||||||
// Filter out actions
|
// Filter out actions
|
||||||
process: (ctx, val) =>
|
process: (ctx, val) =>
|
||||||
|
@ -80,8 +128,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: '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: '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: '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: '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: {}}}
|
|
||||||
],
|
],
|
||||||
|
|
||||||
type: 'array_merge',
|
type: 'array_merge',
|
||||||
|
@ -223,8 +270,12 @@ export default class Actions extends Module {
|
||||||
|
|
||||||
this.actions[key] = data;
|
this.actions[key] = data;
|
||||||
|
|
||||||
for(const ctx of this.settings.__contexts)
|
for(const ctx of this.settings.__contexts) {
|
||||||
ctx.update('chat.actions.inline');
|
ctx.update('chat.actions.inline');
|
||||||
|
ctx.update('chat.actions.hover');
|
||||||
|
ctx.update('chat.actions.user-context');
|
||||||
|
ctx.update('chat.actions.room');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -234,8 +285,13 @@ export default class Actions extends Module {
|
||||||
|
|
||||||
this.renderers[key] = data;
|
this.renderers[key] = data;
|
||||||
|
|
||||||
for(const ctx of this.settings.__contexts)
|
for(const ctx of this.settings.__contexts) {
|
||||||
ctx.update('chat.actions.inline');
|
ctx.update('chat.actions.inline');
|
||||||
|
ctx.update('chat.actions.inline');
|
||||||
|
ctx.update('chat.actions.hover');
|
||||||
|
ctx.update('chat.actions.user-context');
|
||||||
|
ctx.update('chat.actions.room');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -501,6 +557,8 @@ export default class Actions extends Module {
|
||||||
if ( ! data )
|
if ( ! data )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
data.ctx = 'room';
|
||||||
|
|
||||||
const type = data.type;
|
const type = data.type;
|
||||||
if ( type ) {
|
if ( type ) {
|
||||||
if ( type === 'new-line' ) {
|
if ( type === 'new-line' ) {
|
||||||
|
@ -545,6 +603,12 @@ export default class Actions extends Module {
|
||||||
if ( maybe_call(act.hidden, this, data, null, current_room, current_user, mod_icons) )
|
if ( maybe_call(act.hidden, this, data, null, current_room, current_user, mod_icons) )
|
||||||
continue;
|
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 ) {
|
if ( act.override_appearance ) {
|
||||||
const out = act.override_appearance.call(this, Object.assign({}, ap), data, null, current_room, current_user, mod_icons);
|
const out = act.override_appearance.call(this, Object.assign({}, ap), data, null, current_room, current_user, mod_icons);
|
||||||
if ( out )
|
if ( out )
|
||||||
|
@ -615,16 +679,17 @@ export default class Actions extends Module {
|
||||||
const u = site.getUser(),
|
const u = site.getUser(),
|
||||||
r = {id: line.props.channelID, login: room};
|
r = {id: line.props.channelID, login: room};
|
||||||
|
|
||||||
const has_replies = line.chatRepliesTreatment ? line.chatRepliesTreatment !== 'control' : false,
|
const has_replies = !!(line.props.hasReply || line.props.reply || ! line.props.replyRestrictedReason),
|
||||||
can_replies = has_replies && ! msg.deleted && ! line.props.disableReplyClick,
|
can_replies = has_replies && msg.message && ! msg.deleted && ! line.props.disableReplyClick,
|
||||||
can_reply = can_replies && u.login !== msg.user?.login && ! msg.reply;
|
can_reply = can_replies && (has_replies || (u && u.login !== msg.user?.login));
|
||||||
|
|
||||||
msg.roomId = r.id;
|
msg.roomId = r.id;
|
||||||
|
|
||||||
if ( u ) {
|
if ( u ) {
|
||||||
u.moderator = line.props.isCurrentUserModerator;
|
u.moderator = line.props.isCurrentUserModerator;
|
||||||
u.staff = line.props.isCurrentUserStaff;
|
u.staff = line.props.isCurrentUserStaff;
|
||||||
u.can_reply = this.parent.context.get('chat.replies.style') === 2 && can_reply;
|
u.reply_mode = this.parent.context.get('chat.replies.style'),
|
||||||
|
u.can_reply = can_reply;
|
||||||
}
|
}
|
||||||
|
|
||||||
const current_level = this.getUserLevel(r, u),
|
const current_level = this.getUserLevel(r, u),
|
||||||
|
@ -649,6 +714,8 @@ export default class Actions extends Module {
|
||||||
if ( ! data )
|
if ( ! data )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
data.ctx = 'user_context';
|
||||||
|
|
||||||
if ( data.type === 'new-line' ) {
|
if ( data.type === 'new-line' ) {
|
||||||
line = null;
|
line = null;
|
||||||
continue;
|
continue;
|
||||||
|
@ -681,11 +748,17 @@ export default class Actions extends Module {
|
||||||
(disp.deleted != null && disp.deleted !== !!msg.deleted) )
|
(disp.deleted != null && disp.deleted !== !!msg.deleted) )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if ( maybe_call(act.hidden, this, data, msg, r, u, mod_icons) )
|
if ( maybe_call(act.hidden, this, data, msg, r, u, mod_icons, chat_line) )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
if ( ap.type === 'dynamic' ) {
|
||||||
|
const out = act.dynamicAppearance && act.dynamicAppearance.call(this, Object.assign({}, ap), data, msg, r, u, mod_icons, chat_line);
|
||||||
|
if ( out )
|
||||||
|
ap = out;
|
||||||
|
}
|
||||||
|
|
||||||
if ( act.override_appearance ) {
|
if ( act.override_appearance ) {
|
||||||
const out = act.override_appearance.call(this, Object.assign({}, ap), data, msg, r, u, mod_icons);
|
const out = act.override_appearance.call(this, Object.assign({}, ap), data, msg, r, u, mod_icons, chat_line);
|
||||||
if ( out )
|
if ( out )
|
||||||
ap = out;
|
ap = out;
|
||||||
}
|
}
|
||||||
|
@ -695,7 +768,7 @@ export default class Actions extends Module {
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const has_color = def.colored && ap.color,
|
const has_color = def.colored && ap.color,
|
||||||
disabled = maybe_call(act.disabled, this, data, msg, r, u, mod_icons) || false,
|
disabled = maybe_call(act.disabled, this, data, msg, r, u, mod_icons, chat_line) || false,
|
||||||
color = has_color && (chat && chat.colors ? chat.colors.process(ap.color) : ap.color),
|
color = has_color && (chat && chat.colors ? chat.colors.process(ap.color) : ap.color),
|
||||||
contents = def.render.call(this, ap, createElement, color);
|
contents = def.render.call(this, ap, createElement, color);
|
||||||
|
|
||||||
|
@ -704,7 +777,7 @@ export default class Actions extends Module {
|
||||||
|
|
||||||
const btn = (<button
|
const btn = (<button
|
||||||
class={`ffz-tooltip ffz-tooltip--no-mouse tw-button tw-button--text${disabled ? ' tw-button--disabled disabled' : ''}`}
|
class={`ffz-tooltip ffz-tooltip--no-mouse tw-button tw-button--text${disabled ? ' tw-button--disabled disabled' : ''}`}
|
||||||
disabled={disabled}
|
//disabled={disabled}
|
||||||
data-tooltip-type="action"
|
data-tooltip-type="action"
|
||||||
data-action={data.action}
|
data-action={data.action}
|
||||||
data-options={data.options ? JSON.stringify(data.options) : null}
|
data-options={data.options ? JSON.stringify(data.options) : null}
|
||||||
|
@ -743,7 +816,95 @@ export default class Actions extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
renderInline(msg, mod_icons, current_user, current_room, createElement) {
|
renderHover(msg, mod_icons, current_user, current_room, createElement, instance = null) {
|
||||||
|
const actions = [];
|
||||||
|
|
||||||
|
const current_level = this.getUserLevel(current_room, current_user),
|
||||||
|
msg_level = this.getUserLevel(current_room, msg.user),
|
||||||
|
is_self = msg.user && current_user && current_user.login === msg.user.login;
|
||||||
|
|
||||||
|
if ( current_level < 3 )
|
||||||
|
mod_icons = false;
|
||||||
|
|
||||||
|
const chat = this.resolve('site.chat');
|
||||||
|
|
||||||
|
let had_action = false;
|
||||||
|
|
||||||
|
for(const data of this.parent.context.get('chat.actions.hover')) {
|
||||||
|
if ( ! data.action || ! data.appearance )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
data.ctx = 'hover';
|
||||||
|
|
||||||
|
let ap = data.appearance || {};
|
||||||
|
const disp = data.display || {},
|
||||||
|
keys = disp.keys,
|
||||||
|
act = this.actions[data.action];
|
||||||
|
|
||||||
|
if ( ! act || disp.disabled ||
|
||||||
|
(disp.mod_icons != null && disp.mod_icons !== !!mod_icons) ||
|
||||||
|
(disp.mod != null && disp.mod !== (current_level > msg_level)) ||
|
||||||
|
(disp.staff != null && disp.staff !== (current_user ? !!current_user.staff : false)) ||
|
||||||
|
(disp.deleted != null && disp.deleted !== !!msg.deleted) )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ( is_self && ! act.can_self )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
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 )
|
||||||
|
ap = out;
|
||||||
|
}
|
||||||
|
|
||||||
|
const def = this.renderers[ap.type];
|
||||||
|
if ( ! def )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const has_color = def.colored && ap.color,
|
||||||
|
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);
|
||||||
|
|
||||||
|
had_action = true;
|
||||||
|
actions.push(<div class={`ffz-hover-action${keys ? ` ffz-has-modifier ffz-modifier-${keys}` : ''}`}>
|
||||||
|
<button
|
||||||
|
class={`ffz-tooltip ffz-mod-icon tw-c-text-alt-2${disabled ? ' disabled' : ''}${has_color ? ' colored' : ''}`}
|
||||||
|
//disabled={disabled}
|
||||||
|
data-tooltip-type="action"
|
||||||
|
data-action={data.action}
|
||||||
|
data-options={data.options ? JSON.stringify(data.options) : null}
|
||||||
|
data-tip={ap.tooltip}
|
||||||
|
onClick={this.handleClick}
|
||||||
|
onContextMenu={this.handleContext}
|
||||||
|
>
|
||||||
|
{contents}
|
||||||
|
</button>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! had_action )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return (<div
|
||||||
|
class={`ffz--hover-actions ffz-action-data tw-mg-r-05`}
|
||||||
|
data-source="line"
|
||||||
|
>
|
||||||
|
{actions}
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
renderInline(msg, mod_icons, current_user, current_room, createElement, instance = null) {
|
||||||
const actions = [];
|
const actions = [];
|
||||||
|
|
||||||
const current_level = this.getUserLevel(current_room, current_user),
|
const current_level = this.getUserLevel(current_room, current_user),
|
||||||
|
@ -762,6 +923,8 @@ export default class Actions extends Module {
|
||||||
if ( ! data.action || ! data.appearance )
|
if ( ! data.action || ! data.appearance )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
data.ctx = 'inline';
|
||||||
|
|
||||||
let ap = data.appearance || {};
|
let ap = data.appearance || {};
|
||||||
const disp = data.display || {},
|
const disp = data.display || {},
|
||||||
keys = disp.keys,
|
keys = disp.keys,
|
||||||
|
@ -778,11 +941,17 @@ export default class Actions extends Module {
|
||||||
if ( is_self && ! act.can_self )
|
if ( is_self && ! act.can_self )
|
||||||
continue;
|
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;
|
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 ) {
|
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 )
|
if ( out )
|
||||||
ap = out;
|
ap = out;
|
||||||
}
|
}
|
||||||
|
@ -792,7 +961,7 @@ export default class Actions extends Module {
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const has_color = def.colored && ap.color,
|
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),
|
color = has_color && (chat && chat.colors ? chat.colors.process(ap.color) : ap.color),
|
||||||
contents = def.render.call(this, ap, createElement, color);
|
contents = def.render.call(this, ap, createElement, color);
|
||||||
|
|
||||||
|
@ -804,7 +973,7 @@ export default class Actions extends Module {
|
||||||
had_action = true;
|
had_action = true;
|
||||||
list.push(<button
|
list.push(<button
|
||||||
class={`ffz-tooltip mod-icon ffz-mod-icon tw-c-text-alt-2${disabled ? ' disabled' : ''}${has_color ? ' colored' : ''}${keys ? ` ffz-modifier-${keys}` : ''}${hover ? ' ffz-hover' : ''}`}
|
class={`ffz-tooltip mod-icon ffz-mod-icon tw-c-text-alt-2${disabled ? ' disabled' : ''}${has_color ? ' colored' : ''}${keys ? ` ffz-modifier-${keys}` : ''}${hover ? ' ffz-hover' : ''}`}
|
||||||
disabled={disabled}
|
//disabled={disabled}
|
||||||
data-tooltip-type="action"
|
data-tooltip-type="action"
|
||||||
data-action={data.action}
|
data-action={data.action}
|
||||||
data-options={data.options ? JSON.stringify(data.options) : null}
|
data-options={data.options ? JSON.stringify(data.options) : null}
|
||||||
|
@ -819,14 +988,6 @@ export default class Actions extends Module {
|
||||||
if ( ! had_action )
|
if ( ! had_action )
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
/*const room = current_room && JSON.stringify(current_room),
|
|
||||||
user = msg.user && JSON.stringify({
|
|
||||||
login: msg.user.login,
|
|
||||||
displayName: msg.user.displayName,
|
|
||||||
id: msg.user.id,
|
|
||||||
type: msg.user.type
|
|
||||||
});*/
|
|
||||||
|
|
||||||
let out = null;
|
let out = null;
|
||||||
if ( actions.length )
|
if ( actions.length )
|
||||||
out = (<div
|
out = (<div
|
||||||
|
|
|
@ -3,6 +3,22 @@
|
||||||
import {load as loadFontAwesome} from 'utilities/font-awesome';
|
import {load as loadFontAwesome} from 'utilities/font-awesome';
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Dynamic
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export const dynamic = {
|
||||||
|
title: 'Dynamic',
|
||||||
|
title_i18n: 'setting.actions.appearance.dynamic',
|
||||||
|
|
||||||
|
colored: true,
|
||||||
|
|
||||||
|
render(data, createElement, color) {
|
||||||
|
return <figure style={{color}} class={`${data.icon||'ffz-i-zreknarf'}`} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Text
|
// Text
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
@ -21,6 +37,21 @@ export const text = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Emote
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export const emote = {
|
||||||
|
title: 'Emote',
|
||||||
|
title_i18n: 'setting.actions.appearance.emote',
|
||||||
|
|
||||||
|
editor: () => import(/* webpackChunkName: 'main-menu' */ './components/edit-emote.vue'),
|
||||||
|
|
||||||
|
component: () => import(/* webpackChunkName: 'main-menu' */ './components/preview-emote.vue'),
|
||||||
|
render(data, createElement) {
|
||||||
|
return <figure class="mod-icon__image"><img src={data.src} /></figure>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Icon
|
// Icon
|
||||||
|
|
|
@ -3,6 +3,73 @@
|
||||||
import {createElement} from 'utilities/dom';
|
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 if you're a moderator.",
|
||||||
|
|
||||||
|
can_self: true,
|
||||||
|
|
||||||
|
tooltip(data) {
|
||||||
|
const pinned = data.line?.props?.pinnedMessage?.message?.id === data.message_id;
|
||||||
|
if (pinned)
|
||||||
|
return this.i18n.t('chat.actions.pin.already', 'This message is already pinned.');
|
||||||
|
|
||||||
|
return this.i18n.t('chat.actions.pin', 'Pin This Message')
|
||||||
|
},
|
||||||
|
|
||||||
|
disabled(data, message, current_room, current_user, mod_icons, instance) {
|
||||||
|
const line = instance ?? data.line,
|
||||||
|
props = line?.props,
|
||||||
|
pinned = props?.pinnedMessage?.message?.id === message.id && message.id != null;
|
||||||
|
|
||||||
|
return pinned;
|
||||||
|
},
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// If the message is empty or deleted, we can't pin it.
|
||||||
|
if ( ! message.message || ! message.message.length || message.deleted )
|
||||||
|
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
|
// Send Reply
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
@ -10,19 +77,37 @@ import {createElement} from 'utilities/dom';
|
||||||
export const reply = {
|
export const reply = {
|
||||||
presets: [{
|
presets: [{
|
||||||
appearance: {
|
appearance: {
|
||||||
type: 'icon',
|
type: 'dynamic'
|
||||||
icon: 'ffz-i-reply'
|
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
|
||||||
required_context: ['message'],
|
required_context: ['message'],
|
||||||
|
supports_dynamic: true,
|
||||||
|
|
||||||
title: 'Reply to Message',
|
title: 'Reply to Message',
|
||||||
description: 'Allows you to directly reply to another user\'s message. Only functions when the Chat Replies Style is "FrankerFaceZ".',
|
description: "Allows you to directly reply to another user's message.",
|
||||||
|
|
||||||
can_self: true,
|
can_self: true,
|
||||||
|
|
||||||
tooltip() {
|
dynamicAppearance(ap, data, message, current_room, current_user, mod_icons, instance) {
|
||||||
|
const line = instance ?? data.line,
|
||||||
|
props = line?.props,
|
||||||
|
has_reply = props?.hasReply || props?.reply;
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'icon',
|
||||||
|
icon: has_reply ? 'ffz-i-threads' : 'ffz-i-reply',
|
||||||
|
color: ap.color
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
tooltip(data) {
|
||||||
|
const props = data.line?.props,
|
||||||
|
has_reply = props?.hasReply || props?.reply;
|
||||||
|
|
||||||
|
if (has_reply)
|
||||||
|
return this.i18n.t('chat.actions.reply.thread', 'Open Thread');
|
||||||
|
|
||||||
return this.i18n.t('chat.actions.reply', 'Reply to Message')
|
return this.i18n.t('chat.actions.reply', 'Reply to Message')
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -31,10 +116,16 @@ export const reply = {
|
||||||
if ( typeof id !== 'string' || ! /^[0-9a-f]+-[0-9a-f]+/.test(id) )
|
if ( typeof id !== 'string' || ! /^[0-9a-f]+-[0-9a-f]+/.test(id) )
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if ( ! message.message || message.deleted || (current_user && current_user.login === message.user?.login) || ! current_user?.can_reply )
|
// Users must be able to reply.
|
||||||
|
if ( ! current_user?.can_reply )
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if ( message?.reply )
|
// If reply mode is set to 0 (Disabled), don't show the action.
|
||||||
|
if ( current_user?.reply_mode === 0 )
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// If the message is empty or deleted, don't show the action.
|
||||||
|
if ( ! message.message || message.deleted )
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -83,6 +174,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 [
|
||||||
|
(<div class="tw-border-b tw-mg-b-05">{ // eslint-disable-line react/jsx-key
|
||||||
|
this.i18n.t('chat.actions.copy_message', 'Copy Message')
|
||||||
|
}</div>),
|
||||||
|
(<div class="tw-align-left">{ // eslint-disable-line react/jsx-key
|
||||||
|
msg
|
||||||
|
}</div>)
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
click(event, data) {
|
||||||
|
const msg = this.replaceVariables(data.options.format, data);
|
||||||
|
navigator.clipboard.writeText(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Open URL
|
// Open URL
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
@ -513,7 +513,7 @@ export default class Emotes extends Module {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const line = fine.searchParent(target, n => n.props && n.props.message),
|
const line = fine.searchParent(target, n => n.props && n.props.message),
|
||||||
opener = fine.searchParent(target, n => n.onShowEmoteCard, 200);
|
opener = fine.searchParent(target, n => n.onShowEmoteCard, 250);
|
||||||
|
|
||||||
if ( ! line || ! opener )
|
if ( ! line || ! opener )
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -20,6 +20,7 @@ import Room from './room';
|
||||||
import User from './user';
|
import User from './user';
|
||||||
import * as TOKENIZERS from './tokenizers';
|
import * as TOKENIZERS from './tokenizers';
|
||||||
import * as RICH_PROVIDERS from './rich_providers';
|
import * as RICH_PROVIDERS from './rich_providers';
|
||||||
|
import * as LINK_PROVIDERS from './link_providers';
|
||||||
|
|
||||||
import Actions from './actions';
|
import Actions from './actions';
|
||||||
import { getFontsList } from 'src/utilities/fonts';
|
import { getFontsList } from 'src/utilities/fonts';
|
||||||
|
@ -99,6 +100,9 @@ export default class Chat extends Module {
|
||||||
this.rich_providers = {};
|
this.rich_providers = {};
|
||||||
this.__rich_providers = [];
|
this.__rich_providers = [];
|
||||||
|
|
||||||
|
this.link_providers = {};
|
||||||
|
this.__link_providers = [];
|
||||||
|
|
||||||
this._hl_reasons = {};
|
this._hl_reasons = {};
|
||||||
this.addHighlightReason('mention', 'Mentioned');
|
this.addHighlightReason('mention', 'Mentioned');
|
||||||
this.addHighlightReason('user', 'Highlight User');
|
this.addHighlightReason('user', 'Highlight User');
|
||||||
|
@ -1241,6 +1245,8 @@ export default class Chat extends Module {
|
||||||
onEnable() {
|
onEnable() {
|
||||||
this.socket = this.resolve('socket');
|
this.socket = this.resolve('socket');
|
||||||
|
|
||||||
|
this.on('site.subpump:pubsub-message', this.onPubSub, this);
|
||||||
|
|
||||||
if ( this.context.get('chat.filtering.color-mentions') )
|
if ( this.context.get('chat.filtering.color-mentions') )
|
||||||
this.createColorCache().then(() => this.emit(':update-line-tokens'));
|
this.createColorCache().then(() => this.emit(':update-line-tokens'));
|
||||||
|
|
||||||
|
@ -1251,6 +1257,47 @@ export default class Chat extends Module {
|
||||||
for(const key in RICH_PROVIDERS)
|
for(const key in RICH_PROVIDERS)
|
||||||
if ( has(RICH_PROVIDERS, key) )
|
if ( has(RICH_PROVIDERS, key) )
|
||||||
this.addRichProvider(RICH_PROVIDERS[key]);
|
this.addRichProvider(RICH_PROVIDERS[key]);
|
||||||
|
|
||||||
|
for(const key in LINK_PROVIDERS)
|
||||||
|
if ( has(LINK_PROVIDERS, key) )
|
||||||
|
this.addLinkProvider(LINK_PROVIDERS[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onPubSub(event) {
|
||||||
|
if ( event.prefix === 'stream-chat-room-v1' && event.message.type === 'chat_rich_embed' ) {
|
||||||
|
const data = event.message.data,
|
||||||
|
url = data.request_url,
|
||||||
|
|
||||||
|
providers = this.__link_providers;
|
||||||
|
|
||||||
|
// Don't re-cache.
|
||||||
|
if ( this._link_info[url] )
|
||||||
|
return;
|
||||||
|
|
||||||
|
for(const provider of providers) {
|
||||||
|
const match = provider.test.call(this, url);
|
||||||
|
if ( match ) {
|
||||||
|
const processed = provider.receive ? provider.receive.call(this, match, data) : data;
|
||||||
|
let result = provider.process.call(this, match, processed);
|
||||||
|
|
||||||
|
if ( !(result instanceof Promise) )
|
||||||
|
result = Promise.resolve(result);
|
||||||
|
|
||||||
|
result.then(value => {
|
||||||
|
// If something is already running, don't override it.
|
||||||
|
let info = this._link_info[url];
|
||||||
|
if ( info )
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Save the value.
|
||||||
|
this._link_info[url] = [true, Date.now() + 120000, value];
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1855,6 +1902,11 @@ export default class Chat extends Module {
|
||||||
|
|
||||||
addTokenizer(tokenizer) {
|
addTokenizer(tokenizer) {
|
||||||
const type = tokenizer.type;
|
const type = tokenizer.type;
|
||||||
|
if ( has(this.tokenizers, type) ) {
|
||||||
|
this.log.warn(`Tried adding tokenizer of type '${type}' when one was already present.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.tokenizers[type] = tokenizer;
|
this.tokenizers[type] = tokenizer;
|
||||||
if ( tokenizer.priority == null )
|
if ( tokenizer.priority == null )
|
||||||
tokenizer.priority = 0;
|
tokenizer.priority = 0;
|
||||||
|
@ -1894,8 +1946,48 @@ export default class Chat extends Module {
|
||||||
return tokenizer;
|
return tokenizer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addLinkProvider(provider) {
|
||||||
|
const type = provider.type;
|
||||||
|
if ( has(this.link_providers, type) ) {
|
||||||
|
this.log.warn(`Tried adding link provider of type '${type}' when one was already present.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.link_providers[type] = provider;
|
||||||
|
if ( provider.priority == null )
|
||||||
|
provider.priority = 0;
|
||||||
|
|
||||||
|
this.__link_providers.push(provider);
|
||||||
|
this.__link_providers.sort((a,b) => {
|
||||||
|
if ( a.priority > b.priority ) return -1;
|
||||||
|
if ( a.priority < b.priority ) return 1;
|
||||||
|
return a.type < b.type;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeLinkProvider(provider) {
|
||||||
|
let type;
|
||||||
|
if ( typeof provider === 'string' ) type = provider;
|
||||||
|
else type = provider.type;
|
||||||
|
|
||||||
|
provider = this.link_providers[type];
|
||||||
|
if ( ! provider )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
const idx = this.__link_providers.indexOf(provider);
|
||||||
|
if ( idx !== -1 )
|
||||||
|
this.__link_providers.splice(idx, 1);
|
||||||
|
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
addRichProvider(provider) {
|
addRichProvider(provider) {
|
||||||
const type = provider.type;
|
const type = provider.type;
|
||||||
|
if ( has(this.rich_providers, type) ) {
|
||||||
|
this.log.warn(`Tried adding rich provider of type '${type}' when one was already present.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.rich_providers[type] = provider;
|
this.rich_providers[type] = provider;
|
||||||
if ( provider.priority == null )
|
if ( provider.priority == null )
|
||||||
provider.priority = 0;
|
provider.priority = 0;
|
||||||
|
@ -2108,6 +2200,17 @@ export default class Chat extends Module {
|
||||||
cbs[success ? 0 : 1](data);
|
cbs[success ? 0 : 1](data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try using a link provider.
|
||||||
|
for(const lp of this.__link_providers) {
|
||||||
|
const match = lp.test.call(this, url);
|
||||||
|
if ( match ) {
|
||||||
|
timeout(lp.process.call(this, match), 15000)
|
||||||
|
.then(data => handle(true, data))
|
||||||
|
.catch(err => handle(false, err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let provider = this.settings.get('debug.link-resolver.source');
|
let provider = this.settings.get('debug.link-resolver.source');
|
||||||
if ( provider == null )
|
if ( provider == null )
|
||||||
provider = this.experiments.getAssignment('api_links') ? 'test' : 'socket';
|
provider = this.experiments.getAssignment('api_links') ? 'test' : 'socket';
|
||||||
|
|
459
src/modules/chat/link_providers.js
Normal file
459
src/modules/chat/link_providers.js
Normal file
|
@ -0,0 +1,459 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Rich Content Providers
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
const CLIP_URL = /^(?:https?:\/\/)?clips\.twitch\.tv\/([a-z0-9-_=]+)(?:\/)?(\w+)?(?:\/edit)?/i;
|
||||||
|
const NEW_CLIP_URL = /^(?:https?:\/\/)?(?:(?:www|m)\.)?twitch\.tv\/\w+\/clip\/([a-z0-9-_=]+)/i;
|
||||||
|
const VIDEO_URL = /^(?:https?:\/\/)?(?:www\.)?twitch\.tv\/(?:\w+\/v|videos)\/(\w+)/;
|
||||||
|
const USER_URL = /^(?:https?:\/\/)?(?:www\.)?twitch\.tv\/([^/]+)$/;
|
||||||
|
|
||||||
|
const BAD_USERS = [
|
||||||
|
'directory', '_deck', 'p', 'downloads', 'jobs', 'turbo', 'settings', 'friends',
|
||||||
|
'subscriptions', 'inventory', 'wallet'
|
||||||
|
];
|
||||||
|
|
||||||
|
import GET_CLIP from './clip_info.gql';
|
||||||
|
import GET_VIDEO from './video_info.gql';
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Clips
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export const Clip = {
|
||||||
|
type: 'clip',
|
||||||
|
|
||||||
|
test(url) {
|
||||||
|
const match = CLIP_URL.exec(url) || NEW_CLIP_URL.exec(url);
|
||||||
|
if ( match && match[1] && match[1] !== 'create' )
|
||||||
|
return match[1];
|
||||||
|
},
|
||||||
|
|
||||||
|
receive(match, data) {
|
||||||
|
const cd = data?.twitch_metadata?.clip_metadata;
|
||||||
|
if ( ! cd )
|
||||||
|
return;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: cd.id,
|
||||||
|
slug: cd.slug,
|
||||||
|
title: data.title,
|
||||||
|
thumbnailURL: data.thumbnail_url,
|
||||||
|
curator: {
|
||||||
|
id: cd.curator_id,
|
||||||
|
displayName: data.author_name
|
||||||
|
},
|
||||||
|
broadcaster: {
|
||||||
|
id: cd.broadcaster_id,
|
||||||
|
displayName: cd.channel_display_name
|
||||||
|
},
|
||||||
|
game: {
|
||||||
|
displayName: cd.game
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async process(match, received) {
|
||||||
|
let clip = received;
|
||||||
|
|
||||||
|
if ( ! clip ) {
|
||||||
|
const apollo = this.resolve('site.apollo');
|
||||||
|
if ( ! apollo )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
const result = await apollo.client.query({
|
||||||
|
query: GET_CLIP,
|
||||||
|
variables: {
|
||||||
|
slug: match
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
clip = result?.data?.clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! clip || ! clip.broadcaster )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
const game = clip.game,
|
||||||
|
game_display = game && game.displayName;
|
||||||
|
|
||||||
|
let user = {
|
||||||
|
type: 'style', weight: 'semibold', color: 'alt-2',
|
||||||
|
content: clip.broadcaster.displayName
|
||||||
|
};
|
||||||
|
|
||||||
|
if ( clip.broadcaster.login )
|
||||||
|
user = {
|
||||||
|
type: 'link', url: `https://www.twitch.tv/${clip.broadcaster.login}`,
|
||||||
|
content: user
|
||||||
|
};
|
||||||
|
|
||||||
|
const subtitle = game_display ? {
|
||||||
|
type: 'i18n', key: 'clip.desc.1.playing', phrase: '{user} playing {game}', content: {
|
||||||
|
user,
|
||||||
|
game: {type: 'style', weight: 'semibold', content: game_display}
|
||||||
|
}
|
||||||
|
} : {type: 'i18n', key: 'clip.desc.1', phrase: 'Clip of {user}', content: {user}};
|
||||||
|
|
||||||
|
let curator = clip.curator ? {
|
||||||
|
type: 'style', color: 'alt-2',
|
||||||
|
content: clip.curator.displayName
|
||||||
|
} : {type: 'i18n', key: 'clip.unknown', phrase: 'Unknown'};
|
||||||
|
|
||||||
|
if ( clip.curator?.login )
|
||||||
|
curator = {
|
||||||
|
type: 'link', url: `https://www.twitch.tv/${clip.curator.login}`,
|
||||||
|
content: curator
|
||||||
|
};
|
||||||
|
|
||||||
|
let extra;
|
||||||
|
|
||||||
|
if ( clip.viewCount > 0 )
|
||||||
|
extra = {
|
||||||
|
type: 'i18n', key: 'clip.desc.2',
|
||||||
|
phrase: 'Clipped by {curator} — {views, plural, one {# View} other {# Views}}',
|
||||||
|
content: {
|
||||||
|
curator,
|
||||||
|
views: clip.viewCount
|
||||||
|
}
|
||||||
|
};
|
||||||
|
else
|
||||||
|
extra = {
|
||||||
|
type: 'i18n', key: 'clip.desc.no-views',
|
||||||
|
phrase: 'Clipped by {curator}',
|
||||||
|
content: {
|
||||||
|
curator
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
accent: '#6441a4',
|
||||||
|
|
||||||
|
short: {
|
||||||
|
type: 'header',
|
||||||
|
image: {type: 'image', url: clip.thumbnailURL, sfw: true, aspect: 16/9},
|
||||||
|
title: clip.title,
|
||||||
|
subtitle,
|
||||||
|
extra
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Users
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export const User = {
|
||||||
|
type: 'user',
|
||||||
|
|
||||||
|
test(url) {
|
||||||
|
const match = USER_URL.exec(url);
|
||||||
|
if ( match && ! BAD_USERS.includes(match[1]) )
|
||||||
|
return match[1];
|
||||||
|
},
|
||||||
|
|
||||||
|
async process(match) {
|
||||||
|
const twitch_data = this.resolve('site.twitch_data'),
|
||||||
|
user = twitch_data ? await twitch_data.getUser(null, match) : null;
|
||||||
|
|
||||||
|
if ( ! user || ! user.id )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
const game = user.broadcastSettings?.game?.displayName,
|
||||||
|
stream_id = user.stream?.id;
|
||||||
|
|
||||||
|
const fragments = {
|
||||||
|
avatar: {
|
||||||
|
type: 'image',
|
||||||
|
url: user.profileImageURL,
|
||||||
|
rounding: -1,
|
||||||
|
aspect: 1
|
||||||
|
},
|
||||||
|
desc: user.description,
|
||||||
|
title: [user.displayName]
|
||||||
|
};
|
||||||
|
|
||||||
|
if ( stream_id && game )
|
||||||
|
fragments.game = {type: 'style', weight: 'semibold', content: game};
|
||||||
|
|
||||||
|
if ( user.displayName.trim().toLowerCase() !== user.login )
|
||||||
|
fragments.title.push({
|
||||||
|
type: 'style', color: 'alt-2',
|
||||||
|
content: [' (', user.login, ')']
|
||||||
|
});
|
||||||
|
|
||||||
|
if ( user.roles?.isPartner )
|
||||||
|
fragments.title.push({
|
||||||
|
type: 'style', color: 'link',
|
||||||
|
content: {type: 'icon', name: 'verified'}
|
||||||
|
});
|
||||||
|
|
||||||
|
const full = [
|
||||||
|
{
|
||||||
|
type: 'header',
|
||||||
|
image: {type: 'ref', name: 'avatar'},
|
||||||
|
title: {type: 'ref', name: 'title'},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'box',
|
||||||
|
'mg-y': 'small',
|
||||||
|
wrap: 'pre-wrap',
|
||||||
|
lines: 5,
|
||||||
|
content: {
|
||||||
|
type: 'ref',
|
||||||
|
name: 'desc'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
if ( stream_id && game ) {
|
||||||
|
const thumb_url = user.stream.previewImageURL
|
||||||
|
? user.stream.previewImageURL
|
||||||
|
.replace('{width}', '320')
|
||||||
|
.replace('{height}', '180')
|
||||||
|
: null;
|
||||||
|
|
||||||
|
full.push({
|
||||||
|
type: 'link',
|
||||||
|
url: `https://www.twitch.tv/${user.login}`,
|
||||||
|
embed: true,
|
||||||
|
interactive: true,
|
||||||
|
tooltip: false,
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'conditional',
|
||||||
|
media: true,
|
||||||
|
content: {
|
||||||
|
type: 'gallery',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'image',
|
||||||
|
url: thumb_url,
|
||||||
|
aspect: 16/9
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'box',
|
||||||
|
'mg-y': 'small',
|
||||||
|
lines: 2,
|
||||||
|
content: user.broadcastSettings.title
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'ref',
|
||||||
|
name: 'game'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
full.push({
|
||||||
|
type: 'header',
|
||||||
|
compact: true,
|
||||||
|
subtitle: [
|
||||||
|
{
|
||||||
|
type: 'icon',
|
||||||
|
name: 'twitch'
|
||||||
|
},
|
||||||
|
' Twitch'
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
v: 5,
|
||||||
|
|
||||||
|
accent: user.primaryColorHex ? `#${user.primaryColorHex}` : null,
|
||||||
|
fragments,
|
||||||
|
|
||||||
|
short: {
|
||||||
|
type: 'header',
|
||||||
|
image: {type: 'ref', name: 'avatar'},
|
||||||
|
title: {type: 'ref', name: 'title'},
|
||||||
|
subtitle: {type: 'ref', name: 'desc'},
|
||||||
|
extra: stream_id ? {
|
||||||
|
type: 'i18n',
|
||||||
|
key: 'cards.user.streaming',
|
||||||
|
phrase: 'streaming {game}',
|
||||||
|
content: {
|
||||||
|
game: {type: 'ref', name: 'game'}
|
||||||
|
}
|
||||||
|
} : null
|
||||||
|
},
|
||||||
|
|
||||||
|
full
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Videos
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export const Video = {
|
||||||
|
type: 'video',
|
||||||
|
|
||||||
|
test(url) {
|
||||||
|
const match = VIDEO_URL.exec(url);
|
||||||
|
if ( match )
|
||||||
|
return match[1];
|
||||||
|
},
|
||||||
|
|
||||||
|
async process(match) {
|
||||||
|
const apollo = this.resolve('site.apollo');
|
||||||
|
if ( ! apollo )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
const result = await apollo.client.query({
|
||||||
|
query: GET_VIDEO,
|
||||||
|
variables: {
|
||||||
|
id: match
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if ( ! result || ! result.data || ! result.data.video || ! result.data.video.owner )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
const video = result.data.video,
|
||||||
|
game = video.game,
|
||||||
|
game_display = game && game.displayName;
|
||||||
|
|
||||||
|
const fragments = {
|
||||||
|
title: video.title,
|
||||||
|
thumbnail: {
|
||||||
|
type: 'image',
|
||||||
|
url: video.previewThumbnailURL,
|
||||||
|
aspect: 16/9
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const user = {
|
||||||
|
type: 'link',
|
||||||
|
url: `https://www.twitch.tv/${video.owner.login}`,
|
||||||
|
content: {
|
||||||
|
type: 'style',
|
||||||
|
weight: 'semibold',
|
||||||
|
color: 'alt-2',
|
||||||
|
content: video.owner.displayName
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fragments.subtitle = video.game?.displayName
|
||||||
|
? {
|
||||||
|
type: 'i18n',
|
||||||
|
key: 'video.desc.1.playing',
|
||||||
|
phrase: 'Video of {user} playing {game}',
|
||||||
|
content: {
|
||||||
|
user,
|
||||||
|
game: {
|
||||||
|
type: 'style',
|
||||||
|
weight: 'semibold',
|
||||||
|
content: video.game.displayName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'i18n',
|
||||||
|
key: 'video.desc.1',
|
||||||
|
phrase: 'Video of {user}',
|
||||||
|
content: {
|
||||||
|
user
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let length = video.lengthSeconds;
|
||||||
|
|
||||||
|
return {
|
||||||
|
v: 5,
|
||||||
|
|
||||||
|
fragments,
|
||||||
|
|
||||||
|
short: {
|
||||||
|
type: 'header',
|
||||||
|
image: {type: 'ref', name: 'thumbnail'},
|
||||||
|
title: {type: 'ref', name: 'title'},
|
||||||
|
subtitle: {type: 'ref', name: 'subtitle'},
|
||||||
|
extra: {
|
||||||
|
type: 'i18n',
|
||||||
|
key: 'video.desc.2',
|
||||||
|
phrase: '{length,duration} — {views,number} Views — {date,datetime}',
|
||||||
|
content: {
|
||||||
|
length,
|
||||||
|
views: video.viewCount,
|
||||||
|
date: video.publishedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
full: [
|
||||||
|
{
|
||||||
|
type: 'header',
|
||||||
|
image: {
|
||||||
|
type: 'image',
|
||||||
|
url: video.owner.profileImageURL,
|
||||||
|
rounding: -1,
|
||||||
|
aspect: 1
|
||||||
|
},
|
||||||
|
title: {type: 'ref', name: 'title'},
|
||||||
|
subtitle: {type: 'ref', name: 'subtitle'}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'box',
|
||||||
|
'mg-y': 'small',
|
||||||
|
lines: 5,
|
||||||
|
wrap: 'pre-wrap',
|
||||||
|
content: video.description
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'conditional',
|
||||||
|
media: true,
|
||||||
|
content: {
|
||||||
|
type: 'gallery',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'overlay',
|
||||||
|
content: {type: 'ref', name: 'thumbnail'},
|
||||||
|
'top-left': {
|
||||||
|
type: 'format',
|
||||||
|
format: 'duration',
|
||||||
|
value: length
|
||||||
|
},
|
||||||
|
'bottom-left': {
|
||||||
|
type: 'i18n',
|
||||||
|
key: 'video.views',
|
||||||
|
phrase: '{views,number} views',
|
||||||
|
content: {
|
||||||
|
views: video.viewCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'header',
|
||||||
|
compact: true,
|
||||||
|
subtitle: [
|
||||||
|
{
|
||||||
|
type: 'icon',
|
||||||
|
name: 'twitch'
|
||||||
|
},
|
||||||
|
" Twitch • ",
|
||||||
|
{
|
||||||
|
type: 'format',
|
||||||
|
format: 'datetime',
|
||||||
|
value: video.publishedAt
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -4,24 +4,6 @@
|
||||||
// Rich Content Providers
|
// Rich Content Providers
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
//const CLIP_URL = /^(?:https?:\/\/)?clips\.twitch\.tv\/(\w+)(?:\/)?(\w+)?(?:\/edit)?/;
|
|
||||||
//const NEW_CLIP_URL = /^(?:https?:\/\/)?(?:www\.)?twitch\.tv\/\w+\/clip\/(\w+)/;
|
|
||||||
const CLIP_URL = /^(?:https?:\/\/)?clips\.twitch\.tv\/([a-z0-9-_=]+)(?:\/)?(\w+)?(?:\/edit)?/i;
|
|
||||||
const NEW_CLIP_URL = /^(?:https?:\/\/)?(?:(?:www|m)\.)?twitch\.tv\/\w+\/clip\/([a-z0-9-_=]+)/i;
|
|
||||||
const VIDEO_URL = /^(?:https?:\/\/)?(?:www\.)?twitch\.tv\/(?:\w+\/v|videos)\/(\w+)/;
|
|
||||||
const USER_URL = /^(?:https?:\/\/)?(?:www\.)?twitch\.tv\/([^/]+)$/;
|
|
||||||
|
|
||||||
const BAD_USERS = [
|
|
||||||
'directory', '_deck', 'p', 'downloads', 'jobs', 'turbo', 'settings', 'friends',
|
|
||||||
'subscriptions', 'inventory', 'wallet'
|
|
||||||
];
|
|
||||||
|
|
||||||
import GET_CLIP from './clip_info.gql';
|
|
||||||
import GET_VIDEO from './video_info.gql';
|
|
||||||
|
|
||||||
import {truncate} from 'utilities/object';
|
|
||||||
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// General Links
|
// General Links
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
@ -32,10 +14,18 @@ export const Links = {
|
||||||
priority: -10,
|
priority: -10,
|
||||||
|
|
||||||
test(token) {
|
test(token) {
|
||||||
if ( ! this.context.get('chat.rich.all-links') && ! token.force_rich )
|
if ( token.type !== 'link' )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return token.type === 'link'
|
const url = token.url;
|
||||||
|
|
||||||
|
// Link providers always result in embeds.
|
||||||
|
for(const provider of this.__link_providers) {
|
||||||
|
if ( provider.test.call(this, url) )
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.context.get('chat.rich.all-links') || token.force_rich;
|
||||||
},
|
},
|
||||||
|
|
||||||
process(token, want_mid) {
|
process(token, want_mid) {
|
||||||
|
@ -70,279 +60,3 @@ export const Links = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Users
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
export const Users = {
|
|
||||||
type: 'user',
|
|
||||||
can_hide_token: true,
|
|
||||||
|
|
||||||
test(token) {
|
|
||||||
if ( token.type !== 'link' || (! this.context.get('chat.rich.all-links') && ! token.force_rich) )
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return USER_URL.test(token.url);
|
|
||||||
},
|
|
||||||
|
|
||||||
process(token) {
|
|
||||||
const match = USER_URL.exec(token.url),
|
|
||||||
twitch_data = this.resolve('site.twitch_data');
|
|
||||||
|
|
||||||
if ( ! twitch_data || ! match || BAD_USERS.includes(match[1]) )
|
|
||||||
return;
|
|
||||||
|
|
||||||
return {
|
|
||||||
url: token.url,
|
|
||||||
|
|
||||||
getData: async () => {
|
|
||||||
const user = await twitch_data.getUser(null, match[1]);
|
|
||||||
if ( ! user || ! user.id )
|
|
||||||
return null;
|
|
||||||
|
|
||||||
const game = user.broadcastSettings?.game?.displayName,
|
|
||||||
stream_id = user.stream?.id;
|
|
||||||
|
|
||||||
let subtitle
|
|
||||||
if ( stream_id && game )
|
|
||||||
subtitle = {
|
|
||||||
type: 'i18n',
|
|
||||||
key: 'cards.user.streaming', phrase: 'streaming {game}', content: {
|
|
||||||
game: {type: 'style', weight: 'semibold', content: game}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const extra = truncate(user.description);
|
|
||||||
const title = [user.displayName];
|
|
||||||
|
|
||||||
if ( user.displayName.trim().toLowerCase() !== user.login )
|
|
||||||
title.push({
|
|
||||||
type: 'style', color: 'alt-2',
|
|
||||||
content: [' (', user.login, ')']
|
|
||||||
});
|
|
||||||
|
|
||||||
if ( user.roles?.isPartner )
|
|
||||||
title.push({
|
|
||||||
type: 'style', color: 'link',
|
|
||||||
content: {type: 'icon', name: 'verified'}
|
|
||||||
});
|
|
||||||
|
|
||||||
/*const full = [{
|
|
||||||
type: 'header',
|
|
||||||
image: {type: 'image', url: user.profileImageURL, rounding: -1, aspect: 1},
|
|
||||||
title,
|
|
||||||
subtitle,
|
|
||||||
extra: stream_id ? extra : null
|
|
||||||
}];
|
|
||||||
|
|
||||||
if ( stream_id ) {
|
|
||||||
full.push({type: 'box', 'mg-y': 'small', lines: 1, content: user.broadcastSettings.title});
|
|
||||||
full.push({type: 'conditional', content: {
|
|
||||||
type: 'gallery', items: [{
|
|
||||||
type: 'image', aspect: 16/9, sfw: false, url: user.stream.previewImageURL
|
|
||||||
}]
|
|
||||||
}});
|
|
||||||
} else
|
|
||||||
full.push({type: 'box', 'mg-y': 'small', wrap: 'pre-wrap', lines: 5, content: truncate(user.description, 1000, undefined, undefined, false)})
|
|
||||||
|
|
||||||
full.push({
|
|
||||||
type: 'fieldset',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: {type: 'i18n', key: 'embed.twitch.views', phrase: 'Views'},
|
|
||||||
value: {type: 'format', format: 'number', value: user.profileViewCount},
|
|
||||||
inline: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: {type: 'i18n', key: 'embed.twitch.followers', phrase: 'Followers'},
|
|
||||||
value: {type: 'format', format: 'number', value: user.followers?.totalCount},
|
|
||||||
inline: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
full.push({
|
|
||||||
type: 'header',
|
|
||||||
subtitle: [{type: 'icon', name: 'twitch'}, ' Twitch']
|
|
||||||
});*/
|
|
||||||
|
|
||||||
return {
|
|
||||||
url: token.url,
|
|
||||||
accent: user.primaryColorHex ? `#${user.primaryColorHex}` : null,
|
|
||||||
short: {
|
|
||||||
type: 'header',
|
|
||||||
image: {type: 'image', url: user.profileImageURL, rounding: -1, aspect: 1},
|
|
||||||
title,
|
|
||||||
subtitle,
|
|
||||||
extra
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Clips
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
export const Clips = {
|
|
||||||
type: 'clip',
|
|
||||||
can_hide_token: true,
|
|
||||||
|
|
||||||
test(token) {
|
|
||||||
if ( token.type !== 'link' )
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return CLIP_URL.test(token.url) || NEW_CLIP_URL.test(token.url);
|
|
||||||
},
|
|
||||||
|
|
||||||
process(token) {
|
|
||||||
let match = CLIP_URL.exec(token.url);
|
|
||||||
if ( ! match )
|
|
||||||
match = NEW_CLIP_URL.exec(token.url);
|
|
||||||
|
|
||||||
const apollo = this.resolve('site.apollo');
|
|
||||||
if ( ! apollo || ! match || match[1] === 'create' )
|
|
||||||
return;
|
|
||||||
|
|
||||||
return {
|
|
||||||
url: token.url,
|
|
||||||
|
|
||||||
getData: async () => {
|
|
||||||
const result = await apollo.client.query({
|
|
||||||
query: GET_CLIP,
|
|
||||||
variables: {
|
|
||||||
slug: match[1]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if ( ! result || ! result.data || ! result.data.clip || ! result.data.clip.broadcaster )
|
|
||||||
return null;
|
|
||||||
|
|
||||||
const clip = result.data.clip,
|
|
||||||
game = clip.game,
|
|
||||||
game_display = game && game.displayName;
|
|
||||||
|
|
||||||
const user = {
|
|
||||||
type: 'link', url: `https://www.twitch.tv/${clip.broadcaster.login}`,
|
|
||||||
content: {
|
|
||||||
type: 'style', weight: 'semibold', color: 'alt-2',
|
|
||||||
content: clip.broadcaster.displayName
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const subtitle = game_display ? {
|
|
||||||
type: 'i18n', key: 'clip.desc.1.playing', phrase: '{user} playing {game}', content: {
|
|
||||||
user,
|
|
||||||
game: {type: 'style', weight: 'semibold', content: game_display}
|
|
||||||
}
|
|
||||||
} : {type: 'i18n', key: 'clip.desc.1', phrase: 'Clip of {user}', content: {user}};
|
|
||||||
|
|
||||||
const curator = clip.curator ? {
|
|
||||||
type: 'link', url: `https://www.twitch.tv/${clip.curator.login}`,
|
|
||||||
content: {
|
|
||||||
type: 'style', color: 'alt-2',
|
|
||||||
content: clip.curator.displayName
|
|
||||||
}
|
|
||||||
} : {type: 'i18n', key: 'clip.unknown', phrase: 'Unknown'};
|
|
||||||
|
|
||||||
const extra = {
|
|
||||||
type: 'i18n', key: 'clip.desc.2',
|
|
||||||
phrase: 'Clipped by {curator} — {views, plural, one {# View} other {# Views}}',
|
|
||||||
content: {
|
|
||||||
curator,
|
|
||||||
views: clip.viewCount
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
url: token.url,
|
|
||||||
accent: '#6441a4',
|
|
||||||
|
|
||||||
short: {
|
|
||||||
type: 'header',
|
|
||||||
image: {type: 'image', url: clip.thumbnailURL, sfw: true, aspect: 16/9},
|
|
||||||
title: clip.title,
|
|
||||||
subtitle,
|
|
||||||
extra
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export const Videos = {
|
|
||||||
type: 'video',
|
|
||||||
can_hide_token: true,
|
|
||||||
|
|
||||||
test(token) {
|
|
||||||
return token.type === 'link' && VIDEO_URL.test(token.url)
|
|
||||||
},
|
|
||||||
|
|
||||||
process(token) {
|
|
||||||
const match = VIDEO_URL.exec(token.url),
|
|
||||||
apollo = this.resolve('site.apollo');
|
|
||||||
|
|
||||||
if ( ! apollo || ! match )
|
|
||||||
return;
|
|
||||||
|
|
||||||
return {
|
|
||||||
getData: async () => {
|
|
||||||
const result = await apollo.client.query({
|
|
||||||
query: GET_VIDEO,
|
|
||||||
variables: {
|
|
||||||
id: match[1]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if ( ! result || ! result.data || ! result.data.video || ! result.data.video.owner )
|
|
||||||
return null;
|
|
||||||
|
|
||||||
const video = result.data.video,
|
|
||||||
game = video.game,
|
|
||||||
game_display = game && game.displayName;
|
|
||||||
|
|
||||||
const user = {
|
|
||||||
type: 'link', url: `https://www.twitch.tv/${video.owner.login}`,
|
|
||||||
content: {
|
|
||||||
type: 'style', weight: 'semibold', color: 'alt-2',
|
|
||||||
content: video.owner.displayName
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const subtitle = game_display ? {
|
|
||||||
type: 'i18n', key: 'clip.desc.1.playing', phrase: '{user} playing {game}', content: {
|
|
||||||
user,
|
|
||||||
game: {type: 'style', weight: 'semibold', content: game_display}
|
|
||||||
}
|
|
||||||
} : {type: 'i18n', key: 'video.desc.1', phrase: 'Video of {user}', content: {user}};
|
|
||||||
|
|
||||||
const extra = {
|
|
||||||
type: 'i18n', key: 'video.desc.2',
|
|
||||||
phrase: '{length,duration} — {views,number} Views — {date,datetime}', content: {
|
|
||||||
length: video.lengthSeconds,
|
|
||||||
views: video.viewCount,
|
|
||||||
date: video.publishedAt
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
url: token.url,
|
|
||||||
short: {
|
|
||||||
type: 'header',
|
|
||||||
image: {type: 'image', url: video.previewThumbnailURL, sfw: true, aspect: 16/9},
|
|
||||||
title: video.title,
|
|
||||||
subtitle,
|
|
||||||
extra
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,7 +2,7 @@ query FFZ_GetVideoInfo($id: ID!) {
|
||||||
video(id: $id) {
|
video(id: $id) {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
previewThumbnailURL(width: 86, height: 45)
|
previewThumbnailURL(width: 320, height: 180)
|
||||||
lengthSeconds
|
lengthSeconds
|
||||||
publishedAt
|
publishedAt
|
||||||
viewCount
|
viewCount
|
||||||
|
@ -14,6 +14,7 @@ query FFZ_GetVideoInfo($id: ID!) {
|
||||||
id
|
id
|
||||||
login
|
login
|
||||||
displayName
|
displayName
|
||||||
|
profileImageURL(width: 50)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -51,6 +51,7 @@
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
v-for="(r, key) in data.renderers"
|
v-for="(r, key) in data.renderers"
|
||||||
|
v-if="supportsRenderer(key)"
|
||||||
:key="key"
|
:key="key"
|
||||||
:value="key"
|
:value="key"
|
||||||
>
|
>
|
||||||
|
@ -330,7 +331,7 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tw-pd-r-1 ffz-checkbox">
|
<div v-if="has_hover_modifier" class="tw-pd-r-1 ffz-checkbox">
|
||||||
<input
|
<input
|
||||||
:id="'key_hover$' + id"
|
:id="'key_hover$' + id"
|
||||||
ref="key_hover"
|
ref="key_hover"
|
||||||
|
@ -388,8 +389,8 @@
|
||||||
|
|
||||||
<div v-if="canPreview" class="tw-mg-l-1 tw-border-l tw-pd-l-1 tw-pd-y-05 tw-flex tw-flex-shrink-0 tw-align-items-start">
|
<div v-if="canPreview" class="tw-mg-l-1 tw-border-l tw-pd-l-1 tw-pd-y-05 tw-flex tw-flex-shrink-0 tw-align-items-start">
|
||||||
<action-preview
|
<action-preview
|
||||||
:act="display"
|
:act="maybeDynamic(display)"
|
||||||
:color="display.appearance && data.color(display.appearance.color)"
|
:process-color="data.color"
|
||||||
:renderers="data.renderers"
|
:renderers="data.renderers"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -458,7 +459,7 @@ import {has, maybe_call, deep_copy} from 'utilities/object';
|
||||||
let id = 0;
|
let id = 0;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['action', 'data', 'inline', 'mod_icons', 'context', 'modifiers'],
|
props: ['vuectx', 'action', 'data', 'inline', 'mod_icons', 'context', 'modifiers', 'hover_modifier'],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -494,6 +495,10 @@ export default {
|
||||||
return this.modifiers
|
return this.modifiers
|
||||||
},
|
},
|
||||||
|
|
||||||
|
has_hover_modifier() {
|
||||||
|
return this.hover_modifier !== false
|
||||||
|
},
|
||||||
|
|
||||||
fmts() {
|
fmts() {
|
||||||
const out = [];
|
const out = [];
|
||||||
|
|
||||||
|
@ -552,6 +557,9 @@ export default {
|
||||||
if ( this.action.t === 'inherit' )
|
if ( this.action.t === 'inherit' )
|
||||||
return this.t('setting.inheritance', 'Inheritance Point');
|
return this.t('setting.inheritance', 'Inheritance Point');
|
||||||
|
|
||||||
|
if ( this.action.t === 'skip' )
|
||||||
|
return this.t('setting.inheritance.skip', 'Not Inheriting');
|
||||||
|
|
||||||
else if ( ! this.display )
|
else if ( ! this.display )
|
||||||
return this.t('setting.unknown', 'Unknown Value');
|
return this.t('setting.unknown', 'Unknown Value');
|
||||||
|
|
||||||
|
@ -585,7 +593,10 @@ export default {
|
||||||
|
|
||||||
description() {
|
description() {
|
||||||
if ( this.action.t === 'inherit' )
|
if ( this.action.t === 'inherit' )
|
||||||
return this.t('setting.inheritance.desc', 'Inherit values from lower priority profiles at this position.');
|
return this.t('setting.inheritance.desc', 'Inherit values from lower priority profiles or the default values at this position.');
|
||||||
|
|
||||||
|
if ( this.action.t === 'skip' )
|
||||||
|
return this.t('setting.inheritance.skip.desc', 'This profile does not inherit values from lower priority profiles or the default values, despite having no values of its own.');
|
||||||
|
|
||||||
const type = this.display && this.display.type;
|
const type = this.display && this.display.type;
|
||||||
|
|
||||||
|
@ -691,7 +702,7 @@ export default {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( disp.hover )
|
if ( this.has_hover_modifier && disp.hover )
|
||||||
out.push(this.t('setting.actions.visible.hover', 'when hovering'));
|
out.push(this.t('setting.actions.visible.hover', 'when hovering'));
|
||||||
|
|
||||||
if ( ! out.length )
|
if ( ! out.length )
|
||||||
|
@ -766,6 +777,29 @@ export default {
|
||||||
this.edit_data = null;
|
this.edit_data = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
maybeDynamic(data) {
|
||||||
|
let ap = data.appearance;
|
||||||
|
if (ap?.type === 'dynamic') {
|
||||||
|
const act = this.action_def,
|
||||||
|
ffz = this.vuectx.getFFZ(),
|
||||||
|
actions = ffz && ffz.resolve('chat.actions');
|
||||||
|
|
||||||
|
const out = actions && act?.dynamicAppearance && act.dynamicAppearance
|
||||||
|
.call(actions, deep_copy(ap), data, null, null, null, null);
|
||||||
|
if ( out )
|
||||||
|
return Object.assign({}, data, {appearance: out});
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
supportsRenderer(key) {
|
||||||
|
if (key !== 'dynamic')
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return this.action_def?.supports_dynamic;
|
||||||
|
},
|
||||||
|
|
||||||
getData() {
|
getData() {
|
||||||
const def = this.display && this.data.actions[this.display.action];
|
const def = this.display && this.data.actions[this.display.action];
|
||||||
if ( ! def )
|
if ( ! def )
|
||||||
|
|
|
@ -19,9 +19,13 @@
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['act', 'color', 'tooltip', 'pad', 'renderers'],
|
props: ['act', 'process-color', 'tooltip', 'pad', 'renderers'],
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
color() {
|
||||||
|
return this['processColor'](this.act.appearance.color);
|
||||||
|
},
|
||||||
|
|
||||||
renderer() {
|
renderer() {
|
||||||
return this.renderers[this.act.appearance.type]
|
return this.renderers[this.act.appearance.type]
|
||||||
}
|
}
|
||||||
|
|
|
@ -207,8 +207,8 @@
|
||||||
<action-preview
|
<action-preview
|
||||||
v-else
|
v-else
|
||||||
:key="act.id"
|
:key="act.id"
|
||||||
:act="act.v"
|
:act="maybeDynamic(act.v)"
|
||||||
:color="color(act.v.appearance.color)"
|
:process-color="color"
|
||||||
:renderers="data.renderers"
|
:renderers="data.renderers"
|
||||||
tooltip="true"
|
tooltip="true"
|
||||||
pad="true"
|
pad="true"
|
||||||
|
@ -291,7 +291,12 @@
|
||||||
<div class="tw-flex-grow-1 tw-mg-r-1">
|
<div class="tw-flex-grow-1 tw-mg-r-1">
|
||||||
{{ preset.title_i18n ? t(preset.title_i18n, preset.title, preset) : preset.title }}
|
{{ preset.title_i18n ? t(preset.title_i18n, preset.title, preset) : preset.title }}
|
||||||
</div>
|
</div>
|
||||||
<action-preview v-if="preset.appearance" :act="preset" :renderers="data.renderers" />
|
<action-preview
|
||||||
|
v-if="preset.appearance"
|
||||||
|
:act="maybeDynamic(preset)"
|
||||||
|
:process-color="color"
|
||||||
|
:renderers="data.renderers"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
@ -300,7 +305,7 @@
|
||||||
</balloon>
|
</balloon>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
v-if="! maybe_clear && val.length"
|
v-if="! maybe_clear && strip_skip_val.length"
|
||||||
class="tw-mg-l-1 tw-button tw-button--text ffz-il-tooltip__container"
|
class="tw-mg-l-1 tw-button tw-button--text ffz-il-tooltip__container"
|
||||||
@click="maybe_clear = true"
|
@click="maybe_clear = true"
|
||||||
>
|
>
|
||||||
|
@ -333,7 +338,7 @@
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="! val.length && has_default"
|
v-if="! strip_skip_val.length && has_default"
|
||||||
class="tw-mg-l-1 tw-button tw-button--text ffz-il-tooltip__container"
|
class="tw-mg-l-1 tw-button tw-button--text ffz-il-tooltip__container"
|
||||||
@click="populate"
|
@click="populate"
|
||||||
>
|
>
|
||||||
|
@ -349,6 +354,20 @@
|
||||||
<div ref="list" class="ffz--action-list">
|
<div ref="list" class="ffz--action-list">
|
||||||
<div v-if="! val.length" class="tw-c-text-alt-2 tw-font-size-4 tw-align-center tw-c-text-alt-2 tw-pd-1">
|
<div v-if="! val.length" class="tw-c-text-alt-2 tw-font-size-4 tw-align-center tw-c-text-alt-2 tw-pd-1">
|
||||||
{{ t('setting.actions.no-actions', 'no actions are defined in this profile') }}
|
{{ t('setting.actions.no-actions', 'no actions are defined in this profile') }}
|
||||||
|
|
||||||
|
<div class="tw-mg-t-1">
|
||||||
|
<button
|
||||||
|
class="tw-button tw-button--text ffz-il-tooltip__container"
|
||||||
|
@click="addSkip"
|
||||||
|
>
|
||||||
|
<span class="tw-button__text ffz-i-block">
|
||||||
|
{{ t('setting.actions.do-not-inherit', 'Do Not Inherit') }}
|
||||||
|
</span>
|
||||||
|
<span class="ffz-il-tooltip ffz-il-tooltip--down ffz-il-tooltip--align-center">
|
||||||
|
{{ t('setting.actions.do-not-inherit.desc', 'Do not inherit values from lower priority profiles or the defaults.') }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<section v-for="act in val" :key="act.id">
|
<section v-for="act in val" :key="act.id">
|
||||||
<action-editor
|
<action-editor
|
||||||
|
@ -357,7 +376,9 @@
|
||||||
:inline="item.inline"
|
:inline="item.inline"
|
||||||
:mod_icons="has_icons"
|
:mod_icons="has_icons"
|
||||||
:context="item.context"
|
:context="item.context"
|
||||||
|
:vuectx="context"
|
||||||
:modifiers="item.modifiers"
|
:modifiers="item.modifiers"
|
||||||
|
:hover_modifier="item.hover_modifier"
|
||||||
@remove="remove(act)"
|
@remove="remove(act)"
|
||||||
@save="save(act, $event)"
|
@save="save(act, $event)"
|
||||||
/>
|
/>
|
||||||
|
@ -409,6 +430,14 @@ export default {
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
hasSkip() {
|
||||||
|
for(const val of this.val)
|
||||||
|
if ( val.t === 'skip' )
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
sample_user() {
|
sample_user() {
|
||||||
return this.has_user ? {
|
return this.has_user ? {
|
||||||
displayName: 'SirStendec',
|
displayName: 'SirStendec',
|
||||||
|
@ -560,6 +589,10 @@ export default {
|
||||||
return out;
|
return out;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
strip_skip_val() {
|
||||||
|
return this.val.filter(x => x.t !== 'skip');
|
||||||
|
},
|
||||||
|
|
||||||
val() {
|
val() {
|
||||||
if ( ! this.has_value )
|
if ( ! this.has_value )
|
||||||
return [];
|
return [];
|
||||||
|
@ -657,8 +690,28 @@ export default {
|
||||||
this.set(deep_copy(this.default_value));
|
this.set(deep_copy(this.default_value));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
addSkip() {
|
||||||
|
const vals = Array.from(this.val);
|
||||||
|
if(vals.length > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
vals.push({
|
||||||
|
t: 'skip'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.set(deep_copy(vals));
|
||||||
|
},
|
||||||
|
|
||||||
add(val) {
|
add(val) {
|
||||||
const vals = Array.from(this.val);
|
const vals = Array.from(this.val);
|
||||||
|
|
||||||
|
// Remove any skip entry.
|
||||||
|
let i = vals.length;
|
||||||
|
while(i--) {
|
||||||
|
if (vals[i]?.t === 'skip')
|
||||||
|
vals.splice(i, 1);
|
||||||
|
}
|
||||||
|
|
||||||
vals.push(val);
|
vals.push(val);
|
||||||
this.set(deep_copy(vals));
|
this.set(deep_copy(vals));
|
||||||
this.add_open = false;
|
this.add_open = false;
|
||||||
|
@ -739,6 +792,22 @@ export default {
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
maybeDynamic(data) {
|
||||||
|
let ap = data.appearance;
|
||||||
|
if (ap?.type === 'dynamic') {
|
||||||
|
const act = this.data.actions[data.action],
|
||||||
|
ffz = this.context.getFFZ(),
|
||||||
|
actions = ffz && ffz.resolve('chat.actions');
|
||||||
|
|
||||||
|
const out = actions && act?.dynamicAppearance && act.dynamicAppearance
|
||||||
|
.call(actions, deep_copy(ap), data, this.sample_message, this.sample_room, this.sample_user, this.with_mod_icons);
|
||||||
|
if ( out )
|
||||||
|
return Object.assign({}, data, {appearance: out});
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
color(input) {
|
color(input) {
|
||||||
if ( ! input )
|
if ( ! input )
|
||||||
return input;
|
return input;
|
||||||
|
|
699
src/modules/main_menu/components/chat-tester.vue
Normal file
699
src/modules/main_menu/components/chat-tester.vue
Normal file
|
@ -0,0 +1,699 @@
|
||||||
|
<template>
|
||||||
|
<div class="ffz--chat-tester">
|
||||||
|
<div v-if="context.exclusive" class="tw-c-background-accent tw-c-text-overlay tw-pd-1 tw-mg-b-2">
|
||||||
|
<h3 class="ffz-i-attention">
|
||||||
|
{{ t('debug.chat-tester.exclusive', "Hey! This won't work here!") }}
|
||||||
|
</h3>
|
||||||
|
<markdown :source="t('debug.chat-tester.exclusive-explain', 'This feature does not work when the FFZ Control Center is popped out. It needs to be used in a window where you can see chat.')" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tw-flex tw-align-items-start">
|
||||||
|
<label for="selector" class="tw-mg-y-05">
|
||||||
|
{{ t('debug.chat-tester.message', 'Test Message') }}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="tw-flex tw-flex-column tw-mg-05 tw-full-width">
|
||||||
|
<select
|
||||||
|
id="selector"
|
||||||
|
ref="selector"
|
||||||
|
class="tw-full-width tw-border-top-left-radius-medium tw-border-top-right-radius-medium tw-font-size-6 ffz-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05"
|
||||||
|
@change="onSelectChange"
|
||||||
|
>
|
||||||
|
<option :selected="is_custom" value="custom">
|
||||||
|
{{ t('setting.combo-box.custom', 'Custom') }}
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
v-for="(sample, idx) in samples"
|
||||||
|
:key="idx"
|
||||||
|
:selected="sample.data === message"
|
||||||
|
:value="sample.data"
|
||||||
|
>
|
||||||
|
{{ sample.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<textarea
|
||||||
|
ref="message"
|
||||||
|
class="tw-block tw-font-size-6 tw-full-width ffz-textarea ffz-mg-t-1p tw-border-bottom-left-radius-medium tw-border-bottom-right-radius-medium"
|
||||||
|
rows="10"
|
||||||
|
@blur="updateMessage"
|
||||||
|
@input="onMessageChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tw-mg-t-1 tw-flex tw-align-items-center">
|
||||||
|
<div class="tw-flex-grow-1" />
|
||||||
|
|
||||||
|
<div class="tw-pd-x-1 ffz-checkbox">
|
||||||
|
<input
|
||||||
|
id="replay_fix"
|
||||||
|
ref="replay_fix"
|
||||||
|
:checked="replay_fix"
|
||||||
|
type="checkbox"
|
||||||
|
class="ffz-checkbox__input"
|
||||||
|
@change="onCheck"
|
||||||
|
>
|
||||||
|
|
||||||
|
<label for="replay_fix" class="ffz-checkbox__label">
|
||||||
|
<span class="tw-mg-l-1">
|
||||||
|
{{ t('debug.chat-tester.replay-fix', 'Fix ID and Channel') }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="tw-mg-l-1 tw-button tw-button--text"
|
||||||
|
@click="playMessage"
|
||||||
|
>
|
||||||
|
<span class="tw-button__text ffz-i-play">
|
||||||
|
{{ t('debug.chat-tester.play', 'Play Message') }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tw-pd-t-1 tw-border-t tw-mg-t-1 tw-flex tw-mg-b-1 tw-align-items-center">
|
||||||
|
<div class="tw-flex-grow-1" />
|
||||||
|
|
||||||
|
<div class="tw-pd-x-1 ffz-checkbox">
|
||||||
|
<input
|
||||||
|
id="capture_chat"
|
||||||
|
ref="capture_chat"
|
||||||
|
:checked="capture_chat"
|
||||||
|
type="checkbox"
|
||||||
|
class="ffz-checkbox__input"
|
||||||
|
@change="onCheck"
|
||||||
|
>
|
||||||
|
|
||||||
|
<label for="capture_chat" class="ffz-checkbox__label">
|
||||||
|
<span class="tw-mg-l-1">
|
||||||
|
{{ t('debug.chat-tester.capture-chat', 'Capture Chat') }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tw-pd-x-1 ffz-checkbox">
|
||||||
|
<input
|
||||||
|
id="ignore_privmsg"
|
||||||
|
ref="ignore_privmsg"
|
||||||
|
:checked="ignore_privmsg"
|
||||||
|
type="checkbox"
|
||||||
|
class="ffz-checkbox__input"
|
||||||
|
@change="onCheck"
|
||||||
|
>
|
||||||
|
|
||||||
|
<label for="ignore_privmsg" class="ffz-checkbox__label">
|
||||||
|
<span class="tw-mg-l-1">
|
||||||
|
{{ t('debug.chat-tester.ignore-privmsg', 'Ignore PRIVMSG') }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tw-pd-x-1 ffz-checkbox">
|
||||||
|
<input
|
||||||
|
id="capture_pubsub"
|
||||||
|
ref="capture_pubsub"
|
||||||
|
:checked="capture_pubsub"
|
||||||
|
type="checkbox"
|
||||||
|
class="ffz-checkbox__input"
|
||||||
|
@change="onCheck"
|
||||||
|
>
|
||||||
|
|
||||||
|
<label for="capture_pubsub" class="ffz-checkbox__label">
|
||||||
|
<span class="tw-mg-l-1">
|
||||||
|
{{ t('debug.chat-tester.capture-pubsub', 'Capture PubSub') }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="tw-mg-l-1 tw-button tw-button--text"
|
||||||
|
@click="clearLog"
|
||||||
|
>
|
||||||
|
<span class="tw-button__text ffz-i-trash">
|
||||||
|
{{ t('debug.chat-tester.clear-log', 'Clear Log') }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-for="item in log"
|
||||||
|
:key="item._id"
|
||||||
|
class="tw-elevation-1 tw-border tw-pd-y-05 tw-pd-r-1 tw-mg-y-05 tw-flex tw-flex-nowrap tw-align-items-center"
|
||||||
|
:class="{'tw-c-background-base': item.pubsub, 'tw-c-background-alt-2': !item.pubsub}"
|
||||||
|
>
|
||||||
|
<time class="tw-mg-l-05 tw-mg-r-1 tw-flex-shrink-0">
|
||||||
|
{{ tTime(item.timestamp, 'HH:mm:ss') }}
|
||||||
|
</time>
|
||||||
|
<div v-if="item.pubsub" class="tw-flex-grow-1">
|
||||||
|
<div class="tw-mg-b-05 tw-border-b tw-pd-b-05">{{ item.topic }}</div>
|
||||||
|
<div v-html="highlightJson(item.data)" />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="item.chat" class="tw-flex-grow-1">
|
||||||
|
<div v-if="item.tags" class="ffz-ct--tags">
|
||||||
|
@<template v-for="(tag, key) in item.tags"><span class="ffz-ct--tag">{{ key }}</span>=<span class="ffz-ct--tag-value">{{ tag }}</span>;</template>
|
||||||
|
</div>
|
||||||
|
<div class="ffz-ct--prefix">
|
||||||
|
<template v-if="item.prefix">:<span v-if="item.user" class="ffz-ct--user">{{ item.user }}</span><span class="ffz-ct--prefix">{{ item.prefix }}</span></template>
|
||||||
|
<span class="ffz-ct--command">{{ item.command }}</span>
|
||||||
|
<template v-if="item.channel">#<span class="ffz-ct--channel">{{ item.channel }}</span></template>
|
||||||
|
</div>
|
||||||
|
<div v-if="item.last_param" class="ffz-ct--params">
|
||||||
|
<span v-for="para in item.params" class="ffz-ct--param">{{ para }}</span>
|
||||||
|
:<span class="ffz-ct--param">{{ item.last_param }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="tw-flex-grow-1">
|
||||||
|
{{ item.data }}
|
||||||
|
</div>
|
||||||
|
<div class="tw-mg-l-1 tw-flex tw-flex-wrap tw-flex-column tw-justify-content-start tw-align-items-start">
|
||||||
|
<button
|
||||||
|
v-if="item.chat || item.pubsub"
|
||||||
|
class="tw-button tw-button--text"
|
||||||
|
@click="replayItem(item)"
|
||||||
|
>
|
||||||
|
<span class="tw-button__text ffz-i-arrows-cw">
|
||||||
|
{{ t('debug.chat-tester.replay', 'Replay') }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="tw-button tw-button--text"
|
||||||
|
@click="copyItem(item)"
|
||||||
|
>
|
||||||
|
<span class="tw-button__text ffz-i-docs">
|
||||||
|
{{ t('setting.copy-json', 'Copy') }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import { sanitize } from 'src/utilities/dom';
|
||||||
|
import { DEBUG, SERVER } from 'utilities/constants';
|
||||||
|
import { deep_copy, generateUUID } from 'utilities/object';
|
||||||
|
import { getBuster } from 'utilities/time';
|
||||||
|
|
||||||
|
import SAMPLES from '../sample-chat-messages.json'; // eslint-disable-line no-unused-vars
|
||||||
|
|
||||||
|
const IGNORE_COMMANDS = [
|
||||||
|
'PONG',
|
||||||
|
'PING',
|
||||||
|
'366',
|
||||||
|
'353'
|
||||||
|
];
|
||||||
|
|
||||||
|
let LOADED_SAMPLES = [
|
||||||
|
{
|
||||||
|
"name": "Ping",
|
||||||
|
"data": "PING :tmi.twitch.tv"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
let has_loaded_samples = false;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['item', 'context'],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
const state = window.history.state;
|
||||||
|
const samples = deep_copy(LOADED_SAMPLES);
|
||||||
|
const message = state?.ffz_ct_message ?? samples[0].data;
|
||||||
|
|
||||||
|
let is_custom = true;
|
||||||
|
for(const item of samples) {
|
||||||
|
if (item.data === message) {
|
||||||
|
is_custom = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
has_client: false,
|
||||||
|
|
||||||
|
samples,
|
||||||
|
is_custom,
|
||||||
|
message,
|
||||||
|
|
||||||
|
replay_fix: state?.ffz_ct_replay ?? true,
|
||||||
|
ignore_privmsg: state?.ffz_ct_privmsg ?? false,
|
||||||
|
capture_chat: state?.ffz_ct_chat ?? false,
|
||||||
|
capture_pubsub: state?.ffz_ct_pubsub ?? false,
|
||||||
|
|
||||||
|
log: [],
|
||||||
|
logi: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
message() {
|
||||||
|
if ( ! this.is_custom )
|
||||||
|
this.$refs.message.value = this.message;
|
||||||
|
},
|
||||||
|
|
||||||
|
capture_chat() {
|
||||||
|
if ( this.capture_chat )
|
||||||
|
this.listenChat();
|
||||||
|
else
|
||||||
|
this.unlistenChat();
|
||||||
|
},
|
||||||
|
|
||||||
|
capture_pubsub() {
|
||||||
|
if ( this.capture_pubsub )
|
||||||
|
this.listenPubsub();
|
||||||
|
else
|
||||||
|
this.unlistenPubsub();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.loadSamples();
|
||||||
|
|
||||||
|
this.chat = this.item.getChat();
|
||||||
|
|
||||||
|
this.client = this.chat.ChatService.first?.client;
|
||||||
|
this.has_client = !!this.client;
|
||||||
|
|
||||||
|
if ( this.capture_chat )
|
||||||
|
this.listenChat();
|
||||||
|
|
||||||
|
if ( this.capture_pubsub )
|
||||||
|
this.listenPubsub();
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.unlistenChat();
|
||||||
|
this.unlistenPubsub();
|
||||||
|
|
||||||
|
this.client = null;
|
||||||
|
this.chat = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.$refs.message.value = this.message;
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
highlightJson(object, depth = 1) {
|
||||||
|
if ( depth > 10 )
|
||||||
|
return `<span class="ffz-ct--obj-literal"><nested>`;
|
||||||
|
|
||||||
|
if (object == null)
|
||||||
|
return `<span class="ffz-ct--literal" depth="${depth}">null</span>`;
|
||||||
|
|
||||||
|
if ( typeof object === 'number' || typeof object === 'boolean' )
|
||||||
|
return `<span class="ffz-ct--literal" depth="${depth}">${object}</span>`;
|
||||||
|
|
||||||
|
if ( typeof object === 'string' )
|
||||||
|
return `<span class=ffz-ct--string depth="${depth}">"${sanitize(object)}"</span>`;
|
||||||
|
|
||||||
|
if ( Array.isArray(object) )
|
||||||
|
return `<span class="ffz-ct--obj-open" depth="${depth}">[</span>`
|
||||||
|
+ object.map(x => this.highlightJson(x, depth + 1)).join(`<span class="ffz-ct--obj-sep" depth="${depth}">, </span>`)
|
||||||
|
+ `<span class="ffz-ct--obj-close" depth="${depth}">]</span>`;
|
||||||
|
|
||||||
|
const out = [];
|
||||||
|
|
||||||
|
for(const [key, val] of Object.entries(object)) {
|
||||||
|
if ( out.length > 0 )
|
||||||
|
out.push(`<span class="ffz-ct--obj-sep" depth="${depth}">, </span>`);
|
||||||
|
|
||||||
|
out.push(`<span class="ffz-ct--obj-key" depth="${depth}">"${sanitize(key)}"</span><span class="ffz-ct--obj-key-sep" depth="${depth}">: </span>`);
|
||||||
|
out.push(this.highlightJson(val, depth + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return `<span class="ffz-ct--obj-open" depth="${depth}">{</span>${out.join('')}<span class="ffz-ct--obj-close" depth="${depth}">}</span>`;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Samples
|
||||||
|
async loadSamples() {
|
||||||
|
if ( has_loaded_samples )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const values = await fetch(DEBUG ? SAMPLES : `${SERVER}/script/sample-chat-messages.json?_=${getBuster()}`).then(r => r.ok ? r.json() : null);
|
||||||
|
if ( Array.isArray(values) && values.length > 0 ) {
|
||||||
|
has_loaded_samples = true;
|
||||||
|
|
||||||
|
for(const item of values) {
|
||||||
|
if ( Array.isArray(item.data) )
|
||||||
|
item.data = item.data.join('\n\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
LOADED_SAMPLES = values;
|
||||||
|
this.samples = deep_copy(values);
|
||||||
|
|
||||||
|
let is_custom = true;
|
||||||
|
for(const item of this.samples) {
|
||||||
|
if (item.data === this.message) {
|
||||||
|
is_custom = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.is_custom = is_custom;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Chat
|
||||||
|
listenChat() {
|
||||||
|
if ( this.listening_chat )
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Ensure we have the chat client.
|
||||||
|
if ( ! this.has_client ) {
|
||||||
|
this.client = this.chat.ChatService.first?.client;
|
||||||
|
this.has_client = !!this.client;
|
||||||
|
|
||||||
|
if ( ! this.has_client )
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hook into the connection.
|
||||||
|
const conn = this.client.connection;
|
||||||
|
|
||||||
|
if ( ! conn.ffzOnSocketMessage )
|
||||||
|
conn.ffzOnSocketMessage = conn.onSocketMessage;
|
||||||
|
|
||||||
|
conn.onSocketMessage = event => {
|
||||||
|
try {
|
||||||
|
this.handleChat(event);
|
||||||
|
} catch(err) {
|
||||||
|
/* no-op */
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn.ffzOnSocketMessage(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( conn.ws )
|
||||||
|
conn.ws.onmessage = conn.onSocketMessage;
|
||||||
|
|
||||||
|
this.addLog("Started capturing chat.");
|
||||||
|
|
||||||
|
this.listening_chat = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
unlistenChat() {
|
||||||
|
if ( ! this.listening_chat )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const conn = this.client.connection;
|
||||||
|
|
||||||
|
conn.onSocketMessage = conn.ffzOnSocketMessage;
|
||||||
|
|
||||||
|
if ( conn.ws )
|
||||||
|
conn.ws.onmessage = conn.onSocketMessage;
|
||||||
|
|
||||||
|
this.addLog("Stopped capturing chat.");
|
||||||
|
|
||||||
|
this.listening_chat = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
handleChat(event) {
|
||||||
|
for(const raw of event.data.split(/\r?\n/g)) {
|
||||||
|
const msg = this.parseChat(raw);
|
||||||
|
if ( msg ) {
|
||||||
|
if ( this.ignore_privmsg && msg.command === 'PRIVMSG' )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ( IGNORE_COMMANDS.includes(msg.command) )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
this.addLog(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
parseChat(raw) {
|
||||||
|
const msg = this.client.parser.msg(raw);
|
||||||
|
msg.chat = true;
|
||||||
|
|
||||||
|
if ( Object.keys(msg.tags).length === 0 )
|
||||||
|
msg.tags = null;
|
||||||
|
|
||||||
|
if ( msg.params.length > 0 && msg.params[0].startsWith('#') )
|
||||||
|
msg.channel = msg.params.shift().slice(1);
|
||||||
|
|
||||||
|
if ( msg.params.length > 0 )
|
||||||
|
msg.last_param = msg.params.pop();
|
||||||
|
|
||||||
|
const idx = msg.prefix ? msg.prefix.indexOf('!') : -1;
|
||||||
|
|
||||||
|
if ( idx === -1 )
|
||||||
|
msg.user = null;
|
||||||
|
else {
|
||||||
|
msg.user = msg.prefix.substr(0, idx);
|
||||||
|
msg.prefix = msg.prefix.substr(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Pubsub
|
||||||
|
listenPubsub() {
|
||||||
|
if ( this.listening_pubsub )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.chat.on('site.subpump:pubsub-message', this.handlePubsub, this);
|
||||||
|
this.addLog("Started capturing PubSub.");
|
||||||
|
|
||||||
|
this.listening_pubsub = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
unlistenPubsub() {
|
||||||
|
if ( ! this.listening_pubsub )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.chat.off('site.subpump:pubsub-message', this.handlePubsub, this);
|
||||||
|
this.addLog("Stopped capturing PubSub.");
|
||||||
|
|
||||||
|
this.listening_pubsub = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
handlePubsub(event) {
|
||||||
|
|
||||||
|
if ( event.prefix === 'video-playback-by-id' )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.addLog({
|
||||||
|
pubsub: true,
|
||||||
|
topic: event.topic,
|
||||||
|
data: deep_copy(event.message)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// State
|
||||||
|
|
||||||
|
saveState() {
|
||||||
|
try {
|
||||||
|
window.history.replaceState({
|
||||||
|
...window.history.state,
|
||||||
|
ffz_ct_replay: this.replay_fix,
|
||||||
|
ffz_ct_message: this.message,
|
||||||
|
ffz_ct_chat: this.capture_chat,
|
||||||
|
ffz_ct_pubsub: this.capture_pubsub,
|
||||||
|
ffz_ct_privmsg: this.ignore_privmsg
|
||||||
|
}, document.title);
|
||||||
|
|
||||||
|
} catch(err) {
|
||||||
|
/* no-op */
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Event Handlers
|
||||||
|
|
||||||
|
onSelectChange() {
|
||||||
|
const raw_value = this.$refs.selector.value;
|
||||||
|
|
||||||
|
if ( raw_value && raw_value !== 'custom' ) {
|
||||||
|
this.message = raw_value;
|
||||||
|
this.is_custom = false;
|
||||||
|
} else
|
||||||
|
this.is_custom = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateMessage() {
|
||||||
|
const value = this.$refs.message.value;
|
||||||
|
|
||||||
|
let is_custom = true;
|
||||||
|
for(const item of this.samples) {
|
||||||
|
if (item.data === value) {
|
||||||
|
is_custom = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.is_custom = is_custom;
|
||||||
|
if ( this.is_custom )
|
||||||
|
this.message = value;
|
||||||
|
},
|
||||||
|
|
||||||
|
onMessageChange() {
|
||||||
|
this.updateMessage();
|
||||||
|
},
|
||||||
|
|
||||||
|
onCheck() {
|
||||||
|
this.replay_fix = this.$refs.replay_fix.checked;
|
||||||
|
this.capture_chat = this.$refs.capture_chat.checked;
|
||||||
|
this.capture_pubsub = this.$refs.capture_pubsub.checked;
|
||||||
|
this.ignore_privmsg = this.$refs.ignore_privmsg.checked;
|
||||||
|
|
||||||
|
this.saveState();
|
||||||
|
},
|
||||||
|
|
||||||
|
// Log
|
||||||
|
|
||||||
|
addLog(msg) {
|
||||||
|
if ( typeof msg !== 'object' )
|
||||||
|
msg = {
|
||||||
|
data: msg
|
||||||
|
};
|
||||||
|
|
||||||
|
msg.timestamp = Date.now();
|
||||||
|
msg._id = this.logi++;
|
||||||
|
|
||||||
|
this.log.unshift(msg);
|
||||||
|
const extra = this.log.length - 100;
|
||||||
|
if ( extra > 0 )
|
||||||
|
this.log.splice(100, extra);
|
||||||
|
},
|
||||||
|
|
||||||
|
clearLog() {
|
||||||
|
this.log = [];
|
||||||
|
this.addLog('Cleared log.');
|
||||||
|
},
|
||||||
|
|
||||||
|
// Item Actions
|
||||||
|
|
||||||
|
copyItem(item) {
|
||||||
|
let value;
|
||||||
|
if ( item.raw )
|
||||||
|
value = item.raw;
|
||||||
|
else if ( item.data )
|
||||||
|
value = item.data;
|
||||||
|
else
|
||||||
|
value = item;
|
||||||
|
|
||||||
|
if ( typeof value !== 'string' )
|
||||||
|
value = JSON.stringify(value);
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(value);
|
||||||
|
},
|
||||||
|
|
||||||
|
playMessage() {
|
||||||
|
const msgs = [];
|
||||||
|
const parts = this.message.split(/\r?\n/g);
|
||||||
|
|
||||||
|
for(const part of parts) {
|
||||||
|
try {
|
||||||
|
if ( part && part.length > 0 )
|
||||||
|
msgs.push(this.parseChat(part));
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
alert("Unable to parse message.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const msg of msgs)
|
||||||
|
this.replayItem(msg);
|
||||||
|
},
|
||||||
|
|
||||||
|
replayItem(item) {
|
||||||
|
if ( item.chat ) {
|
||||||
|
// While building the string, also build colors for the console log.
|
||||||
|
const out = [];
|
||||||
|
const colors = [];
|
||||||
|
|
||||||
|
if ( item.tags ) {
|
||||||
|
out.push('@');
|
||||||
|
colors.push('gray');
|
||||||
|
|
||||||
|
for(const [key, val] of Object.entries(item.tags)) {
|
||||||
|
let v = val;
|
||||||
|
|
||||||
|
// If the tag is "id", return a new id so the message
|
||||||
|
// won't be deduplicated automatically.
|
||||||
|
if ( key === 'id' && this.replay_fix )
|
||||||
|
v = generateUUID();
|
||||||
|
|
||||||
|
out.push(key);
|
||||||
|
out.push('=');
|
||||||
|
out.push(`${v}`);
|
||||||
|
out.push(';');
|
||||||
|
colors.push('orange');
|
||||||
|
colors.push('gray');
|
||||||
|
colors.push('white');
|
||||||
|
colors.push('gray');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( item.user || item.prefix ) {
|
||||||
|
if ( out.length ) {
|
||||||
|
out.push(' ');
|
||||||
|
colors.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
out.push(':');
|
||||||
|
colors.push('gray');
|
||||||
|
if (item.user) {
|
||||||
|
out.push(item.user);
|
||||||
|
colors.push('green');
|
||||||
|
}
|
||||||
|
if (item.prefix) {
|
||||||
|
out.push(item.prefix);
|
||||||
|
colors.push('gray');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( out.length ) {
|
||||||
|
out.push(' ');
|
||||||
|
colors.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
out.push(item.command);
|
||||||
|
colors.push('orange');
|
||||||
|
|
||||||
|
// If there's a channel, use the current channel rather
|
||||||
|
// than the logged channel.
|
||||||
|
if ( item.channel ) {
|
||||||
|
out.push(` #`);
|
||||||
|
colors.push('gray');
|
||||||
|
out.push(this.replay_fix ? this.chat.ChatService.first?.props?.channelLogin ?? item.channel : item.channel);
|
||||||
|
colors.push('green');
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const para of item.params) {
|
||||||
|
out.push(` ${para}`);
|
||||||
|
colors.push('skyblue');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( item.last_param ) {
|
||||||
|
out.push(` :`);
|
||||||
|
colors.push('gray');
|
||||||
|
out.push(item.last_param);
|
||||||
|
colors.push('skyblue');
|
||||||
|
}
|
||||||
|
|
||||||
|
const msg = out.join(''),
|
||||||
|
conn = this.client.connection,
|
||||||
|
handler = conn.ffzOnSocketMessage ?? conn.onSocketMessage;
|
||||||
|
|
||||||
|
const log_msg = out.join('%c'),
|
||||||
|
log_colors = colors.map(x => x?.length ? `color: ${x};` : '');
|
||||||
|
|
||||||
|
this.chat.log.debugColor(`Injecting chat message: %c${log_msg}`, log_colors);
|
||||||
|
|
||||||
|
handler.call(conn, {
|
||||||
|
data: msg
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
|
@ -80,6 +80,7 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
adding: false,
|
adding: false,
|
||||||
|
resetting: false,
|
||||||
editing: this.copyValue()
|
editing: this.copyValue()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -91,9 +92,16 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
|
value() {
|
||||||
|
this.resetting = true;
|
||||||
|
this.editing = this.copyValue();
|
||||||
|
},
|
||||||
|
|
||||||
editing: {
|
editing: {
|
||||||
handler() {
|
handler() {
|
||||||
this.$emit('input', this.editing)
|
if (!this.resetting)
|
||||||
|
this.$emit('input', this.editing)
|
||||||
|
this.resetting = false;
|
||||||
},
|
},
|
||||||
deep: true
|
deep: true
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,23 +45,23 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-if="deleting">
|
<template v-if="deleting">
|
||||||
<button class="tw-button tw-button--text tw-relative ffz-il-tooltip__container" @click="$emit('delete')">
|
|
||||||
<span class="tw-button__text ffz-i-trash" />
|
|
||||||
<div class="ffz-il-tooltip ffz-il-tooltip--down ffz-il-tooltip--align-right">
|
|
||||||
{{ t('setting.delete', 'Delete') }}
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
<button class="tw-button tw-button--text tw-relative ffz-il-tooltip__container" @click="deleting = false">
|
<button class="tw-button tw-button--text tw-relative ffz-il-tooltip__container" @click="deleting = false">
|
||||||
<span class="tw-button__text ffz-i-cancel" />
|
<span class="tw-button__text ffz-i-cancel" />
|
||||||
<div class="ffz-il-tooltip ffz-il-tooltip--down ffz-il-tooltip--align-right">
|
<div class="ffz-il-tooltip ffz-il-tooltip--up ffz-il-tooltip--align-right">
|
||||||
{{ t('setting.cancel', 'Cancel') }}
|
{{ t('setting.cancel', 'Cancel') }}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
<button class="tw-button tw-button--text tw-relative ffz-il-tooltip__container" @click="$emit('delete')">
|
||||||
|
<span class="tw-button__text ffz-i-trash" />
|
||||||
|
<div class="ffz-il-tooltip ffz-il-tooltip--up ffz-il-tooltip--align-right">
|
||||||
|
{{ t('setting.delete', 'Delete') }}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<button class="tw-button tw-button--text tw-relative ffz-il-tooltip__container" @click="deleting = true">
|
<button class="tw-button tw-button--text tw-relative ffz-il-tooltip__container" @click="deleting = true">
|
||||||
<span class="tw-button__text ffz-i-trash" />
|
<span class="tw-button__text ffz-i-trash" />
|
||||||
<div class="ffz-il-tooltip ffz-il-tooltip--down ffz-il-tooltip--align-right">
|
<div class="ffz-il-tooltip ffz-il-tooltip--up ffz-il-tooltip--align-right">
|
||||||
{{ t('setting.delete', 'Delete') }}
|
{{ t('setting.delete', 'Delete') }}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -366,6 +366,11 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
|
if (this.es) {
|
||||||
|
this.es.close();
|
||||||
|
this.es = null;
|
||||||
|
}
|
||||||
|
|
||||||
this.chat.off('chat:update-link-resolver', this.checkRefreshRaw, this);
|
this.chat.off('chat:update-link-resolver', this.checkRefreshRaw, this);
|
||||||
this.settings.off(':changed:debug.link-resolver.source', this.changeProvider, this);
|
this.settings.off(':changed:debug.link-resolver.source', this.changeProvider, this);
|
||||||
this.chat = null;
|
this.chat = null;
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
@focusin="focus"
|
@focusin="focus"
|
||||||
@focusout="blur"
|
@focusout="blur"
|
||||||
>
|
>
|
||||||
<div class="scrollable-area tw-border-b" data-simplebar>
|
<div ref="scroller" class="scrollable-area tw-border-b" data-simplebar>
|
||||||
<div class="simplebar-scroll-content">
|
<div class="simplebar-scroll-content">
|
||||||
<div ref="popup" class="simplebar-content">
|
<div ref="popup" class="simplebar-content">
|
||||||
<div
|
<div
|
||||||
|
@ -111,7 +111,22 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
opened() {
|
||||||
|
if (this.opened)
|
||||||
|
this.$nextTick(() => this.updateScroller());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
updateScroller() {
|
||||||
|
const scroller = this.$refs.scroller;
|
||||||
|
if (!scroller || ! window.ffzSimplebar || scroller.SimpleBar)
|
||||||
|
return;
|
||||||
|
|
||||||
|
new ffzSimplebar(scroller, ffzSimplebar.getElOptions(scroller));
|
||||||
|
},
|
||||||
|
|
||||||
openConfigure() {
|
openConfigure() {
|
||||||
this.hide();
|
this.hide();
|
||||||
this.$emit('navigate', 'data_management.profiles');
|
this.$emit('navigate', 'data_management.profiles');
|
||||||
|
|
|
@ -8,6 +8,59 @@
|
||||||
{{ t('setting.warn-inheritence', 'These values are being overridden by another profile and may not take effect.') }}
|
{{ t('setting.warn-inheritence', 'These values are being overridden by another profile and may not take effect.') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="tw-flex tw-align-items-center tw-pd-b-05">
|
||||||
|
<div class="tw-flex-grow-1">
|
||||||
|
{{ t('setting.filter.drag', 'Drag entries to re-order them.') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
v-if="! maybe_clear && rules.length"
|
||||||
|
class="tw-mg-l-1 tw-button tw-button--text ffz-il-tooltip__container"
|
||||||
|
@click="maybe_clear = true"
|
||||||
|
>
|
||||||
|
<span class="tw-button__text ffz-i-trash">
|
||||||
|
{{ t('setting.delete-all', 'Delete All') }}
|
||||||
|
</span>
|
||||||
|
<span class="ffz-il-tooltip ffz-il-tooltip--down ffz-il-tooltip--align-right">
|
||||||
|
{{ t('setting.filter.delete-all', "Delete all of this profile's entries.") }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="maybe_clear"
|
||||||
|
class="tw-mg-l-1 tw-button tw-button--text ffz-il-tooltip__container"
|
||||||
|
@click="doClear"
|
||||||
|
>
|
||||||
|
<span class="tw-button__text ffz-i-trash">
|
||||||
|
{{ t('setting.delete-all', 'Delete All') }}
|
||||||
|
</span>
|
||||||
|
<span class="ffz-il-tooltip ffz-il-tooltip--down ffz-il-tooltip--align-right">
|
||||||
|
{{ t('setting.filter.delete-all', "Delete all of this profile's entries.") }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="maybe_clear"
|
||||||
|
class="tw-mg-l-1 tw-button tw-button--text ffz-il-tooltip__container"
|
||||||
|
@click="maybe_clear = false"
|
||||||
|
>
|
||||||
|
<span class="tw-button__text ffz-i-cancel">
|
||||||
|
{{ t('setting.cancel', 'Cancel') }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="! rules.length && has_default"
|
||||||
|
class="tw-mg-l-1 tw-button tw-button--text ffz-il-tooltip__container"
|
||||||
|
@click="populate"
|
||||||
|
>
|
||||||
|
<span class="tw-button__text ffz-i-trash">
|
||||||
|
{{ t('setting.filter.add-default', 'Add Defaults') }}
|
||||||
|
</span>
|
||||||
|
<span class="ffz-il-tooltip ffz-il-tooltip--down ffz-il-tooltip--align-right">
|
||||||
|
{{ t('setting.filter.add-default-tip', 'Add all of the default values to this profile.') }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
<filter-editor
|
<filter-editor
|
||||||
:value="rules"
|
:value="rules"
|
||||||
:filters="filters"
|
:filters="filters"
|
||||||
|
@ -15,6 +68,7 @@
|
||||||
:preview="preview"
|
:preview="preview"
|
||||||
@input="onInput"
|
@input="onInput"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -31,26 +85,42 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
filters: this.item.data(),
|
filters: this.item.data(),
|
||||||
|
maybe_clear: false,
|
||||||
test_context: this.item.test_context ? this.item.test_context() : {},
|
test_context: this.item.test_context ? this.item.test_context() : {},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
has_default() {
|
||||||
|
return this.default_value && this.default_value.length
|
||||||
|
},
|
||||||
|
|
||||||
preview() {
|
preview() {
|
||||||
return this.item.preview || false
|
return this.item.preview || false
|
||||||
},
|
},
|
||||||
|
|
||||||
rules() {
|
rules() {
|
||||||
if ( ! Array.isArray(this.value) || this.value.length <= 0 )
|
if ( ! this.has_value || ! Array.isArray(this.value) )
|
||||||
return [];
|
return [];
|
||||||
|
|
||||||
return this.value.filter(x => x.v).map(x => x.v);
|
return this.value.filter(x => x?.v).map(x => x.v);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
doClear() {
|
||||||
|
this.maybe_clear = false;
|
||||||
|
this.clear();
|
||||||
|
},
|
||||||
|
|
||||||
|
populate() {
|
||||||
|
this.set(deep_copy(this.default_value));
|
||||||
|
},
|
||||||
|
|
||||||
onInput(data) {
|
onInput(data) {
|
||||||
const val = deep_copy(data);
|
const val = deep_copy(data).map(x => ({v: x}));
|
||||||
|
if (val.length == 0)
|
||||||
|
val.push({t: 'skip'});
|
||||||
this.set(val);
|
this.set(val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
29
src/modules/main_menu/sample-chat-messages.json
Normal file
29
src/modules/main_menu/sample-chat-messages.json
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Cheer",
|
||||||
|
"data": "@bits=42069;badge-info=subscriber/69;badges=subscriber/3024,bits/100;color=#FF526F;display-name=SirStendec;id=813a1ef5-f8dd-406c-a2dd-f74d99db2799 :sirstendec!sirstendec@sirstendec.tmi.twitch.tv PRIVMSG #sirstendec :Hello. cheer42069"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ping",
|
||||||
|
"data": "PING :tmi.twitch.tv"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Subscribe (Tier 1, No Message)",
|
||||||
|
"data": "@badge-info=subscriber/1;badges=subscriber/0,premium/1;color=#007ECC;display-name=Kerokai;emotes=;flags=;id=80b7174c-830b-487b-8bce-99bab02b6378;login=kerokai;mod=0;msg-id=sub;msg-param-cumulative-months=1;msg-param-months=0;msg-param-multimonth-duration=1;msg-param-multimonth-tenure=0;msg-param-should-share-streak=0;msg-param-sub-plan-name=Channel\\sSubscription\\s(maxisgoatlord);msg-param-sub-plan=1000;msg-param-was-gifted=false;room-id=42490770;subscriber=1;system-msg=Kerokai\\ssubscribed\\sat\\sTier\\s1.;tmi-sent-ts=1671231043209;user-id=32357552;user-type=; :tmi.twitch.tv USERNOTICE #maximum"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Resubscribe (Tier 1, 17 Months, Message)",
|
||||||
|
"data": "@badge-info=subscriber/17;badges=subscriber/12,moments/1;color=#2E8B57;display-name=FaustDaimos;emotes=;flags=;id=cdb378a9-4e56-4933-a78b-f4bcf2a3961a;login=faustdaimos;mod=0;msg-id=resub;msg-param-cumulative-months=17;msg-param-months=0;msg-param-multimonth-duration=0;msg-param-multimonth-tenure=0;msg-param-should-share-streak=0;msg-param-sub-plan-name=Baka\\sBrigade!;msg-param-sub-plan=1000;msg-param-was-gifted=false;room-id=24761645;subscriber=1;system-msg=FaustDaimos\\ssubscribed\\sat\\sTier\\s1.\\sThey've\\ssubscribed\\sfor\\s17\\smonths!;tmi-sent-ts=1671231277242;user-id=37613709;user-type= :tmi.twitch.tv USERNOTICE #cirno_tv :when do we try it out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mass Gift Sub (Tier 1, 5 Total)",
|
||||||
|
"data": [
|
||||||
|
"@badge-info=;badges=premium/1;color=;display-name=HellbirDza;emotes=;flags=;id=d0e4a1d9-75c7-406d-8423-cfa3dfb514b5;login=hellbirdza;mod=0;msg-id=submysterygift;msg-param-mass-gift-count=5;msg-param-origin-id=ee\\se2\\s3f\\s01\\s75\\s45\\se1\\sfa\\s24\\s47\\s29\\s08\\sdb\\sf8\\sab\\se1\\s56\\s27\\s83\\s2e;msg-param-sender-count=15;msg-param-sub-plan=1000;room-id=26261471;subscriber=0;system-msg=HellbirDza\\sis\\sgifting\\s5\\sTier\\s1\\sSubs\\sto\\sAsmongold's\\scommunity!\\sThey've\\sgifted\\sa\\stotal\\sof\\s15\\sin\\sthe\\schannel!;tmi-sent-ts=1671302976346;user-id=28678305;user-type= :tmi.twitch.tv USERNOTICE #asmongold",
|
||||||
|
"@badge-info=;badges=premium/1;color=;display-name=HellbirDza;emotes=;flags=;id=182675e7-db1b-49d3-9650-54c31d938203;login=hellbirdza;mod=0;msg-id=subgift;msg-param-gift-months=1;msg-param-months=1;msg-param-origin-id=ee\\se2\\s3f\\s01\\s75\\s45\\se1\\sfa\\s24\\s47\\s29\\s08\\sdb\\sf8\\sab\\se1\\s56\\s27\\s83\\s2e;msg-param-recipient-display-name=buddyunique1;msg-param-recipient-id=431251927;msg-param-recipient-user-name=buddyunique1;msg-param-sender-count=0;msg-param-sub-plan-name=Channel\\sSubscription\\s(asmongold);msg-param-sub-plan=1000;room-id=26261471;subscriber=0;system-msg=HellbirDza\\sgifted\\sa\\sTier\\s1\\ssub\\sto\\sbuddyunique1!;tmi-sent-ts=1671302976704;user-id=28678305;user-type= :tmi.twitch.tv USERNOTICE #asmongold",
|
||||||
|
"@badge-info=;badges=premium/1;color=;display-name=HellbirDza;emotes=;flags=;id=594ce86d-f956-43cd-8b5d-1b7e8499dca1;login=hellbirdza;mod=0;msg-id=subgift;msg-param-gift-months=1;msg-param-months=1;msg-param-origin-id=ee\\se2\\s3f\\s01\\s75\\s45\\se1\\sfa\\s24\\s47\\s29\\s08\\sdb\\sf8\\sab\\se1\\s56\\s27\\s83\\s2e;msg-param-recipient-display-name=tartarin_e;msg-param-recipient-id=144049812;msg-param-recipient-user-name=tartarin_e;msg-param-sender-count=0;msg-param-sub-plan-name=Channel\\sSubscription\\s(asmongold);msg-param-sub-plan=1000;room-id=26261471;subscriber=0;system-msg=HellbirDza\\sgifted\\sa\\sTier\\s1\\ssub\\sto\\startarin_e!;tmi-sent-ts=1671302976722;user-id=28678305;user-type= :tmi.twitch.tv USERNOTICE #asmongold",
|
||||||
|
"@badge-info=;badges=premium/1;color=;display-name=HellbirDza;emotes=;flags=;id=527abc39-e599-4c1d-a480-e724a9c69823;login=hellbirdza;mod=0;msg-id=subgift;msg-param-gift-months=1;msg-param-months=1;msg-param-origin-id=ee\\se2\\s3f\\s01\\s75\\s45\\se1\\sfa\\s24\\s47\\s29\\s08\\sdb\\sf8\\sab\\se1\\s56\\s27\\s83\\s2e;msg-param-recipient-display-name=haaryho_stracene_vlasy;msg-param-recipient-id=96664018;msg-param-recipient-user-name=haaryho_stracene_vlasy;msg-param-sender-count=0;msg-param-sub-plan-name=Channel\\sSubscription\\s(asmongold);msg-param-sub-plan=1000;room-id=26261471;subscriber=0;system-msg=HellbirDza\\sgifted\\sa\\sTier\\s1\\ssub\\sto\\shaaryho_stracene_vlasy!;tmi-sent-ts=1671302976759;user-id=28678305;user-type= :tmi.twitch.tv USERNOTICE #asmongold",
|
||||||
|
"@badge-info=;badges=premium/1;color=;display-name=HellbirDza;emotes=;flags=;id=f694b0fc-0b5e-4adf-8002-03dae340e9b5;login=hellbirdza;mod=0;msg-id=subgift;msg-param-gift-months=1;msg-param-months=1;msg-param-origin-id=ee\\se2\\s3f\\s01\\s75\\s45\\se1\\sfa\\s24\\s47\\s29\\s08\\sdb\\sf8\\sab\\se1\\s56\\s27\\s83\\s2e;msg-param-recipient-display-name=corette0;msg-param-recipient-id=149790291;msg-param-recipient-user-name=corette0;msg-param-sender-count=0;msg-param-sub-plan-name=Channel\\sSubscription\\s(asmongold);msg-param-sub-plan=1000;room-id=26261471;subscriber=0;system-msg=HellbirDza\\sgifted\\sa\\sTier\\s1\\ssub\\sto\\scorette0!;tmi-sent-ts=1671302976767;user-id=28678305;user-type= :tmi.twitch.tv USERNOTICE #asmongold",
|
||||||
|
"@badge-info=;badges=premium/1;color=;display-name=HellbirDza;emotes=;flags=;id=5893d8a8-5eb3-46d6-9737-f1b2b76400d4;login=hellbirdza;mod=0;msg-id=subgift;msg-param-gift-months=1;msg-param-months=2;msg-param-origin-id=ee\\se2\\s3f\\s01\\s75\\s45\\se1\\sfa\\s24\\s47\\s29\\s08\\sdb\\sf8\\sab\\se1\\s56\\s27\\s83\\s2e;msg-param-recipient-display-name=yo_adg;msg-param-recipient-id=465861822;msg-param-recipient-user-name=yo_adg;msg-param-sender-count=0;msg-param-sub-plan-name=Channel\\sSubscription\\s(asmongold);msg-param-sub-plan=1000;room-id=26261471;subscriber=0;system-msg=HellbirDza\\sgifted\\sa\\sTier\\s1\\ssub\\sto\\syo_adg!;tmi-sent-ts=1671302976798;user-id=28678305;user-type= :tmi.twitch.tv USERNOTICE #asmongold"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
|
@ -75,7 +75,7 @@ export const array_merge = {
|
||||||
default(val) {
|
default(val) {
|
||||||
const values = [];
|
const values = [];
|
||||||
for(const v of val)
|
for(const v of val)
|
||||||
if ( v.t !== 'inherit' && v.v )
|
if ( v.t !== 'inherit' && v.t !== 'skip' && v.v )
|
||||||
values.push(v.v);
|
values.push(v.v);
|
||||||
|
|
||||||
return values;
|
return values;
|
||||||
|
@ -119,6 +119,8 @@ export const array_merge = {
|
||||||
had_value = true;
|
had_value = true;
|
||||||
if ( val.t === 'inherit' )
|
if ( val.t === 'inherit' )
|
||||||
is_trailing = true;
|
is_trailing = true;
|
||||||
|
else if ( val.t === 'skip' )
|
||||||
|
continue;
|
||||||
else if ( is_trailing )
|
else if ( is_trailing )
|
||||||
trail.push(val.v);
|
trail.push(val.v);
|
||||||
else
|
else
|
||||||
|
|
4
src/sites/player/css_tweaks/player-fade-paused.scss
Normal file
4
src/sites/player/css_tweaks/player-fade-paused.scss
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
.video-player__container[data-paused="true"] video,
|
||||||
|
.video-player__container[data-buffering="true"] video {
|
||||||
|
filter: grayscale(0.25) brightness(0.5);
|
||||||
|
}
|
|
@ -1,3 +1,3 @@
|
||||||
.video-player__overlay[data-controls="false"][data-paused="false"][data-ended="false"] {
|
.video-player__container[data-controls="false"][data-paused="false"][data-ended="false"] {
|
||||||
cursor: none;
|
cursor: none;
|
||||||
}
|
}
|
|
@ -94,6 +94,17 @@ export default class PlayerBase extends Module {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.settings.add('player.fade-pause-buffer', {
|
||||||
|
default: false,
|
||||||
|
ui: {
|
||||||
|
path: 'Player > General >> Playback',
|
||||||
|
title: 'Fade the player when paused or buffering to make the UI easier to see.',
|
||||||
|
component: 'setting-check-box'
|
||||||
|
},
|
||||||
|
|
||||||
|
changed: val => this.css_tweaks.toggle('player-fade-paused', val)
|
||||||
|
});
|
||||||
|
|
||||||
if ( HAS_COMPRESSOR ) {
|
if ( HAS_COMPRESSOR ) {
|
||||||
this.settings.add('player.compressor.enable', {
|
this.settings.add('player.compressor.enable', {
|
||||||
default: true,
|
default: true,
|
||||||
|
@ -559,6 +570,15 @@ export default class PlayerBase extends Module {
|
||||||
},
|
},
|
||||||
changed: val => this.css_tweaks.toggle('player-hide-mouse', val)
|
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() {
|
async onEnable() {
|
||||||
|
@ -569,6 +589,7 @@ export default class PlayerBase extends Module {
|
||||||
this.css_tweaks.toggle('player-volume', this.settings.get('player.volume-always-shown'));
|
this.css_tweaks.toggle('player-volume', this.settings.get('player.volume-always-shown'));
|
||||||
this.css_tweaks.toggle('player-ext-mouse', !this.settings.get('player.ext-interaction'));
|
this.css_tweaks.toggle('player-ext-mouse', !this.settings.get('player.ext-interaction'));
|
||||||
this.css_tweaks.toggle('player-hide-mouse', this.settings.get('player.hide-mouse'));
|
this.css_tweaks.toggle('player-hide-mouse', this.settings.get('player.hide-mouse'));
|
||||||
|
this.css_tweaks.toggle('player-fade-paused', this.settings.get('player.fade-pause-buffer'));
|
||||||
|
|
||||||
this.installVisibilityHook();
|
this.installVisibilityHook();
|
||||||
this.updateHideExtensions();
|
this.updateHideExtensions();
|
||||||
|
@ -643,8 +664,6 @@ export default class PlayerBase extends Module {
|
||||||
|
|
||||||
|
|
||||||
onShortcut(e) {
|
onShortcut(e) {
|
||||||
this.log.info('Compressor Hotkey', e);
|
|
||||||
|
|
||||||
for(const inst of this.Player.instances)
|
for(const inst of this.Player.instances)
|
||||||
this.compressPlayer(inst, e);
|
this.compressPlayer(inst, e);
|
||||||
}
|
}
|
||||||
|
@ -699,6 +718,7 @@ export default class PlayerBase extends Module {
|
||||||
|
|
||||||
const events = this.props.playerEvents;
|
const events = this.props.playerEvents;
|
||||||
if ( events ) {
|
if ( events ) {
|
||||||
|
on(events, 'Buffering', this._ffzUpdateState);
|
||||||
on(events, 'Playing', this._ffzUpdateState);
|
on(events, 'Playing', this._ffzUpdateState);
|
||||||
on(events, 'PlayerError', this._ffzUpdateState);
|
on(events, 'PlayerError', this._ffzUpdateState);
|
||||||
on(events, 'PlayerError', this._ffzErrorReset);
|
on(events, 'PlayerError', this._ffzErrorReset);
|
||||||
|
@ -811,6 +831,7 @@ export default class PlayerBase extends Module {
|
||||||
|
|
||||||
ds.ended = state === 'Ended';
|
ds.ended = state === 'Ended';
|
||||||
ds.paused = state === 'Idle';
|
ds.paused = state === 'Idle';
|
||||||
|
ds.buffering = state === 'Buffering';
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.prototype.ffzAttachListeners = function() {
|
cls.prototype.ffzAttachListeners = function() {
|
||||||
|
@ -825,10 +846,14 @@ export default class PlayerBase extends Module {
|
||||||
if ( ! this._ffz_click_handler )
|
if ( ! this._ffz_click_handler )
|
||||||
this._ffz_click_handler = this.ffzClickHandler.bind(this);
|
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 )
|
if ( ! this._ffz_menu_handler )
|
||||||
this._ffz_menu_handler = this.ffzMenuHandler.bind(this);
|
this._ffz_menu_handler = this.ffzMenuHandler.bind(this);
|
||||||
|
|
||||||
on(cont, 'wheel', this._ffz_scroll_handler);
|
on(cont, 'wheel', this._ffz_scroll_handler);
|
||||||
|
on(cont, 'dblclick', this._ffz_dblclick_handler);
|
||||||
on(cont, 'mousedown', this._ffz_click_handler);
|
on(cont, 'mousedown', this._ffz_click_handler);
|
||||||
on(cont, 'contextmenu', this._ffz_menu_handler);
|
on(cont, 'contextmenu', this._ffz_menu_handler);
|
||||||
}
|
}
|
||||||
|
@ -853,23 +878,60 @@ export default class PlayerBase extends Module {
|
||||||
this._ffz_menu_handler = null;
|
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;
|
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) {
|
cls.prototype.ffzClickHandler = function(event) {
|
||||||
if ( ! event )
|
if ( ! event )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const vol_scroll = t.settings.get('player.volume-scroll'),
|
const vol_scroll = t.settings.get('player.volume-scroll'),
|
||||||
gain_scroll = t.settings.get('player.gain.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);
|
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 ) {
|
if ( wants_rmb && event.button === 2 ) {
|
||||||
this.ffz_rmb = true;
|
this.ffz_rmb = true;
|
||||||
this.ffz_scrolled = false;
|
this.ffz_scrolled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Middle Click
|
||||||
if ( ! t.settings.get('player.mute-click') || event.button !== 1 )
|
if ( ! t.settings.get('player.mute-click') || event.button !== 1 )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -1321,7 +1383,7 @@ export default class PlayerBase extends Module {
|
||||||
return;
|
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 ( ! has_comp || this.areControlsDisabled(inst) ) {
|
||||||
if ( cont )
|
if ( cont )
|
||||||
cont.remove();
|
cont.remove();
|
||||||
|
@ -1346,7 +1408,6 @@ export default class PlayerBase extends Module {
|
||||||
<div>
|
<div>
|
||||||
{tip = (<div class="ffz--p-tip" />)}
|
{tip = (<div class="ffz--p-tip" />)}
|
||||||
{extra = (<div class="ffz--p-extra tw-pd-t-05 ffz--tooltip-explain" />)}
|
{extra = (<div class="ffz--p-extra tw-pd-t-05 ffz--tooltip-explain" />)}
|
||||||
{ff_el = IS_FIREFOX ? (<div class="ffz--p-ff tw-pd-t-05 ffz--tooltip-explain" />) : null}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>);
|
</div>);
|
||||||
|
@ -1374,9 +1435,6 @@ export default class PlayerBase extends Module {
|
||||||
if ( can_apply && this._shortcut_bound )
|
if ( can_apply && this._shortcut_bound )
|
||||||
label = `${label} (${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-on', comp_active);
|
||||||
icon.classList.toggle('ffz-i-comp-off', ! comp_active);
|
icon.classList.toggle('ffz-i-comp-off', ! comp_active);
|
||||||
btn.disabled = ! can_apply;
|
btn.disabled = ! can_apply;
|
||||||
|
@ -1543,12 +1601,16 @@ export default class PlayerBase extends Module {
|
||||||
if ( ! video )
|
if ( ! video )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if ( ! video.src && ! video.srcObject )
|
||||||
|
return false;
|
||||||
|
|
||||||
if ( video.src ) {
|
if ( video.src ) {
|
||||||
const url = new URL(video.src);
|
const url = new URL(video.src);
|
||||||
if ( url.protocol !== 'blob:' )
|
if ( url.protocol !== 'blob:' )
|
||||||
return false;
|
return false;
|
||||||
} else
|
}
|
||||||
return false;
|
|
||||||
|
// TODO: Validation for srcObject (if we need it)
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,11 @@ export default class Twilight extends BaseSite {
|
||||||
onEnable() {
|
onEnable() {
|
||||||
this.settings = this.resolve('settings');
|
this.settings = this.resolve('settings');
|
||||||
|
|
||||||
|
this.web_munch.findModule('simplebar').then(sb => {
|
||||||
|
if (! window.ffzSimplebar && sb )
|
||||||
|
window.ffzSimplebar = sb;
|
||||||
|
}).catch(() => {});
|
||||||
|
|
||||||
const thing = this.fine.searchNode(null, n => n?.pendingProps?.store?.getState),
|
const thing = this.fine.searchNode(null, n => n?.pendingProps?.store?.getState),
|
||||||
store = this.store = thing?.pendingProps?.store;
|
store = this.store = thing?.pendingProps?.store;
|
||||||
|
|
||||||
|
@ -249,19 +254,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.use_result = true;
|
||||||
//Twilight.KNOWN_MODULES.core.chunks = 'core';
|
//Twilight.KNOWN_MODULES.core.chunks = 'core';
|
||||||
|
|
||||||
Twilight.KNOWN_MODULES.simplebar.chunks = VEND_CHUNK;
|
Twilight.KNOWN_MODULES.simplebar.chunks = VEND_CORE;
|
||||||
Twilight.KNOWN_MODULES.react.chunks = VEND_CHUNK;
|
Twilight.KNOWN_MODULES.react.chunks = VEND_CORE;
|
||||||
Twilight.KNOWN_MODULES.cookie.chunks = VEND_CHUNK;
|
Twilight.KNOWN_MODULES.cookie.chunks = VEND_CORE;
|
||||||
|
|
||||||
Twilight.KNOWN_MODULES['gql-printer'].use_result = true;
|
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');
|
const CHAT_CHUNK = n => ! n || n.includes('chat');
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,7 @@ export default class Channel extends Module {
|
||||||
changed: () => this.updateLinks()
|
changed: () => this.updateLinks()
|
||||||
});
|
});
|
||||||
|
|
||||||
this.settings.add('channel.hosting.enable', {
|
/*this.settings.add('channel.hosting.enable', {
|
||||||
default: true,
|
default: true,
|
||||||
ui: {
|
ui: {
|
||||||
path: 'Channel > Behavior >> Hosting',
|
path: 'Channel > Behavior >> Hosting',
|
||||||
|
@ -87,8 +87,7 @@ export default class Channel extends Module {
|
||||||
component: 'setting-check-box'
|
component: 'setting-check-box'
|
||||||
},
|
},
|
||||||
changed: val => ! val && this.InfoBar.each(el => this.updateBar(el))
|
changed: val => ! val && this.InfoBar.each(el => this.updateBar(el))
|
||||||
});
|
});*/
|
||||||
|
|
||||||
|
|
||||||
this.ChannelPanels = this.fine.define(
|
this.ChannelPanels = this.fine.define(
|
||||||
'channel-panels',
|
'channel-panels',
|
||||||
|
@ -116,7 +115,7 @@ export default class Channel extends Module {
|
||||||
{childNodes: true, subtree: true}, 1
|
{childNodes: true, subtree: true}, 1
|
||||||
);
|
);
|
||||||
|
|
||||||
const strip_host = resp => {
|
/*const strip_host = resp => {
|
||||||
if ( this.settings.get('channel.hosting.enable') )
|
if ( this.settings.get('channel.hosting.enable') )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -130,7 +129,7 @@ export default class Channel extends Module {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.apollo.registerModifier('UseHosting', strip_host, false);
|
this.apollo.registerModifier('UseHosting', strip_host, false);
|
||||||
this.apollo.registerModifier('PlayerTrackingContextQuery', strip_host, false);
|
this.apollo.registerModifier('PlayerTrackingContextQuery', strip_host, false);*/
|
||||||
}
|
}
|
||||||
|
|
||||||
onEnable() {
|
onEnable() {
|
||||||
|
@ -162,7 +161,7 @@ export default class Channel extends Module {
|
||||||
this.InfoBar.on('unmount', this.removeBar, this);
|
this.InfoBar.on('unmount', this.removeBar, this);
|
||||||
this.InfoBar.each(el => this.updateBar(el));
|
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.router.on(':route', this.checkNavigation, this);
|
||||||
this.checkNavigation();
|
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}`;
|
const topic = `stream-chat-room-v1.${channel_id}`;
|
||||||
|
|
||||||
this.subpump.inject(topic, {
|
this.subpump.inject(topic, {
|
||||||
|
@ -272,7 +271,7 @@ export default class Channel extends Module {
|
||||||
event.message.data.num_viewers = 0;
|
event.message.data.num_viewers = 0;
|
||||||
event.markChanged();
|
event.markChanged();
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
|
|
||||||
updateSubscription(login) {
|
updateSubscription(login) {
|
||||||
|
@ -441,8 +440,8 @@ export default class Channel extends Module {
|
||||||
});
|
});
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
if ( ! this.settings.get('channel.hosting.enable') && props.hostLogin )
|
//if ( ! this.settings.get('channel.hosting.enable') && props.hostLogin )
|
||||||
this.setHost(props.channelID, props.channelLogin, null, null);
|
// this.setHost(props.channelID, props.channelLogin, null, null);
|
||||||
|
|
||||||
this.updateSubscription(props.channelLogin);
|
this.updateSubscription(props.channelLogin);
|
||||||
this.updateMetadata(el);
|
this.updateMetadata(el);
|
||||||
|
@ -492,10 +491,10 @@ export default class Channel extends Module {
|
||||||
live_since: props.liveSince
|
live_since: props.liveSince
|
||||||
},
|
},
|
||||||
props,
|
props,
|
||||||
hosted: {
|
/*hosted: {
|
||||||
login: props.hostLogin,
|
login: props.hostLogin,
|
||||||
display_name: props.hostDisplayName
|
display_name: props.hostDisplayName
|
||||||
},
|
},*/
|
||||||
el,
|
el,
|
||||||
getViewerCount: () => {
|
getViewerCount: () => {
|
||||||
const thing = cont.querySelector('p[data-a-target="animated-channel-viewers-count"]'),
|
const thing = cont.querySelector('p[data-a-target="animated-channel-viewers-count"]'),
|
||||||
|
|
|
@ -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', {
|
this.settings.add('chat.emote-menu.enabled', {
|
||||||
default: true,
|
default: true,
|
||||||
ui: {
|
ui: {
|
||||||
|
@ -760,24 +769,24 @@ export default class EmoteMenu extends Module {
|
||||||
if ( renews > 0 ) {
|
if ( renews > 0 ) {
|
||||||
calendar = {
|
calendar = {
|
||||||
icon: 'calendar',
|
icon: 'calendar',
|
||||||
message: t.i18n.t('emote-menu.sub-renews', 'This sub renews in {seconds,humantime}.', {seconds: renews})
|
message: t.i18n.t('emote-menu.sub-renews', 'This sub renews {seconds,humantime}.', {seconds: renews})
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if ( ends ) {
|
} else if ( ends ) {
|
||||||
if ( data.prime )
|
if ( data.prime )
|
||||||
calendar = {
|
calendar = {
|
||||||
icon: 'crown',
|
icon: 'crown',
|
||||||
message: t.i18n.t('emote-menu.sub-prime', 'This is your free sub with Prime Gaming.\nIt ends in {seconds,humantime}.', {seconds: ends})
|
message: t.i18n.t('emote-menu.sub-prime', 'This is your free sub with Prime Gaming.\nIt ends {seconds,humantime}.', {seconds: ends})
|
||||||
}
|
}
|
||||||
else if ( data.gift )
|
else if ( data.gift )
|
||||||
calendar = {
|
calendar = {
|
||||||
icon: 'gift',
|
icon: 'gift',
|
||||||
message: t.i18n.t('emote-menu.sub-gift-ends', 'This gifted sub ends in {seconds,humantime}.', {seconds: ends})
|
message: t.i18n.t('emote-menu.sub-gift-ends', 'This gifted sub ends {seconds,humantime}.', {seconds: ends})
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
calendar = {
|
calendar = {
|
||||||
icon: 'calendar-empty',
|
icon: 'calendar-empty',
|
||||||
message: t.i18n.t('emote-menu.sub-ends', 'This sub ends in {seconds,humantime}.', {seconds: ends})
|
message: t.i18n.t('emote-menu.sub-ends', 'This sub ends {seconds,humantime}.', {seconds: ends})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1078,6 +1087,7 @@ export default class EmoteMenu extends Module {
|
||||||
reducedPadding: t.chat.context.get('chat.emote-menu.reduced-padding'),
|
reducedPadding: t.chat.context.get('chat.emote-menu.reduced-padding'),
|
||||||
combineTabs: t.chat.context.get('chat.emote-menu.combine-tabs'),
|
combineTabs: t.chat.context.get('chat.emote-menu.combine-tabs'),
|
||||||
showSearch: t.chat.context.get('chat.emote-menu.show-search'),
|
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)
|
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.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.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.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);
|
t.chat.context.on('changed:chat.emote-menu.tall', this.updateSettingState, this);
|
||||||
|
|
||||||
window.ffz_menu = 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.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.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.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);
|
t.chat.context.off('changed:chat.emote-menu.tall', this.updateSettingState, this);
|
||||||
|
|
||||||
if ( window.ffz_menu === 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'),
|
reducedPadding: t.chat.context.get('chat.emote-menu.reduced-padding'),
|
||||||
combineTabs: t.chat.context.get('chat.emote-menu.combine-tabs'),
|
combineTabs: t.chat.context.get('chat.emote-menu.combine-tabs'),
|
||||||
showSearch: t.chat.context.get('chat.emote-menu.show-search'),
|
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')
|
tall: t.chat.context.get('chat.emote-menu.tall')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2309,6 +2322,13 @@ export default class EmoteMenu extends Module {
|
||||||
return;
|
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,
|
const cd = this.props.channel_data,
|
||||||
old_cd = old_props.channel_data,
|
old_cd = old_props.channel_data,
|
||||||
cd_diff = cd?.user !== old_cd?.user || cd?.channel !== old_cd?.channel,
|
cd_diff = cd?.user !== old_cd?.user || cd?.channel !== old_cd?.channel,
|
||||||
|
|
|
@ -20,6 +20,7 @@ import SettingsMenu from './settings_menu';
|
||||||
import EmoteMenu from './emote_menu';
|
import EmoteMenu from './emote_menu';
|
||||||
import Input from './input';
|
import Input from './input';
|
||||||
import ViewerCards from './viewer_card';
|
import ViewerCards from './viewer_card';
|
||||||
|
import { isHighlightedReward } from './points';
|
||||||
|
|
||||||
|
|
||||||
/*const REGEX_EMOTES = {
|
/*const REGEX_EMOTES = {
|
||||||
|
@ -201,7 +202,7 @@ export default class ChatHook extends Module {
|
||||||
|
|
||||||
this.ChatController = this.fine.define(
|
this.ChatController = this.fine.define(
|
||||||
'chat-controller',
|
'chat-controller',
|
||||||
n => n.hostingHandler && n.onRoomStateUpdated,
|
n => n.parseOutgoingMessage && n.onRoomStateUpdated && n.renderNotifications,
|
||||||
Twilight.CHAT_ROUTES
|
Twilight.CHAT_ROUTES
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -275,6 +276,13 @@ export default class ChatHook extends Module {
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
|
|
||||||
|
this.settings.addUI('debug.chat-test', {
|
||||||
|
path: 'Debugging > Chat >> Chat',
|
||||||
|
component: 'chat-tester',
|
||||||
|
getChat: () => this,
|
||||||
|
force_seen: true
|
||||||
|
});
|
||||||
|
|
||||||
this.settings.add('chat.filtering.blocked-types', {
|
this.settings.add('chat.filtering.blocked-types', {
|
||||||
default: [],
|
default: [],
|
||||||
type: 'array_merge',
|
type: 'array_merge',
|
||||||
|
@ -652,7 +660,34 @@ export default class ChatHook extends Module {
|
||||||
default: true,
|
default: true,
|
||||||
ui: {
|
ui: {
|
||||||
path: 'Chat > Input >> Appearance',
|
path: 'Chat > Input >> Appearance',
|
||||||
title: 'Display the Mod View button in relevant channels.',
|
title: 'Allow the "Mod View" button to appear in relevant channels.',
|
||||||
|
component: 'setting-check-box'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.settings.add('chat.input.show-highlight', {
|
||||||
|
default: true,
|
||||||
|
ui: {
|
||||||
|
path: 'Chat > Input >> Appearance',
|
||||||
|
title: 'Allow the "Chat Highlight Settings" button to appear in relevant channels.',
|
||||||
|
component: 'setting-check-box'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.settings.add('chat.input.show-shield', {
|
||||||
|
default: true,
|
||||||
|
ui: {
|
||||||
|
path: 'Chat > Input >> Appearance',
|
||||||
|
title: 'Allow the "Shield Mode" button to appear in relevant channels.',
|
||||||
|
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'
|
component: 'setting-check-box'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -724,11 +759,15 @@ export default class ChatHook extends Module {
|
||||||
|
|
||||||
const width = this.chat.context.get('chat.effective-width'),
|
const width = this.chat.context.get('chat.effective-width'),
|
||||||
action_size = this.chat.context.get('chat.actions.size'),
|
action_size = this.chat.context.get('chat.actions.size'),
|
||||||
|
hover_action_size = this.chat.context.get('chat.actions.hover-size'),
|
||||||
ts_size = this.chat.context.get('chat.timestamp-size'),
|
ts_size = this.chat.context.get('chat.timestamp-size'),
|
||||||
size = this.chat.context.get('chat.font-size'),
|
size = this.chat.context.get('chat.font-size'),
|
||||||
emote_alignment = this.chat.context.get('chat.lines.emote-alignment'),
|
emote_alignment = this.chat.context.get('chat.lines.emote-alignment'),
|
||||||
lh = Math.round((20/12) * size);
|
lh = Math.round((20/12) * size);
|
||||||
|
|
||||||
|
const hover_action_icon = Math.round(hover_action_size * (2/3)),
|
||||||
|
hover_action_padding = hover_action_size - hover_action_icon;
|
||||||
|
|
||||||
let font = this.chat.context.get('chat.font-family') || 'inherit';
|
let font = this.chat.context.get('chat.font-family') || 'inherit';
|
||||||
const [processed, unloader] = useFont(font);
|
const [processed, unloader] = useFont(font);
|
||||||
font = processed;
|
font = processed;
|
||||||
|
@ -747,6 +786,8 @@ export default class ChatHook extends Module {
|
||||||
this.css_tweaks.delete('ts-size');
|
this.css_tweaks.delete('ts-size');
|
||||||
|
|
||||||
this.css_tweaks.setVariable('chat-actions-size', `${action_size/10}rem`);
|
this.css_tweaks.setVariable('chat-actions-size', `${action_size/10}rem`);
|
||||||
|
this.css_tweaks.setVariable('chat-actions-hover-size', `${hover_action_icon/10}rem`);
|
||||||
|
this.css_tweaks.setVariable('chat-actions-hover-padding', `${hover_action_padding/20}rem`);
|
||||||
this.css_tweaks.setVariable('chat-font-size', `${size/10}rem`);
|
this.css_tweaks.setVariable('chat-font-size', `${size/10}rem`);
|
||||||
this.css_tweaks.setVariable('chat-line-height', `${lh/10}rem`);
|
this.css_tweaks.setVariable('chat-line-height', `${lh/10}rem`);
|
||||||
this.css_tweaks.setVariable('chat-font-family', font);
|
this.css_tweaks.setVariable('chat-font-family', font);
|
||||||
|
@ -872,6 +913,7 @@ export default class ChatHook extends Module {
|
||||||
this.chat.context.on('changed:chat.effective-width', this.updateChatCSS, this);
|
this.chat.context.on('changed:chat.effective-width', this.updateChatCSS, this);
|
||||||
this.settings.main_context.on('changed:chat.use-width', this.updateChatCSS, this);
|
this.settings.main_context.on('changed:chat.use-width', this.updateChatCSS, this);
|
||||||
this.chat.context.on('changed:chat.actions.size', this.updateChatCSS, this);
|
this.chat.context.on('changed:chat.actions.size', this.updateChatCSS, this);
|
||||||
|
this.chat.context.on('changed:chat.actions.hover-size', this.updateChatCSS, this);
|
||||||
this.chat.context.on('changed:chat.font-size', this.updateChatCSS, this);
|
this.chat.context.on('changed:chat.font-size', this.updateChatCSS, this);
|
||||||
this.chat.context.on('changed:chat.timestamp-size', this.updateChatCSS, this);
|
this.chat.context.on('changed:chat.timestamp-size', this.updateChatCSS, this);
|
||||||
this.chat.context.on('changed:chat.font-family', this.updateChatCSS, this);
|
this.chat.context.on('changed:chat.font-family', this.updateChatCSS, this);
|
||||||
|
@ -911,7 +953,13 @@ export default class ChatHook extends Module {
|
||||||
this.css_tweaks.toggleHide('last-x-events', ! val));
|
this.css_tweaks.toggleHide('last-x-events', ! val));
|
||||||
|
|
||||||
this.chat.context.getChanges('chat.input.show-mod-view', val =>
|
this.chat.context.getChanges('chat.input.show-mod-view', val =>
|
||||||
this.css_tweaks.toggleHide('mod-view', ! val));
|
this.css_tweaks.toggleHide('ci-mod-view', ! val));
|
||||||
|
|
||||||
|
this.chat.context.getChanges('chat.input.show-highlight', val =>
|
||||||
|
this.css_tweaks.toggleHide('ci-highlight-settings', !val));
|
||||||
|
|
||||||
|
this.chat.context.getChanges('chat.input.show-shield', val =>
|
||||||
|
this.css_tweaks.toggleHide('ci-shield-mode', ! val));
|
||||||
|
|
||||||
this.chat.context.getChanges('chat.lines.padding', val =>
|
this.chat.context.getChanges('chat.lines.padding', val =>
|
||||||
this.css_tweaks.toggle('chat-padding', val));
|
this.css_tweaks.toggle('chat-padding', val));
|
||||||
|
@ -941,6 +989,9 @@ export default class ChatHook extends Module {
|
||||||
this.updateMentionCSS();
|
this.updateMentionCSS();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.chat.context.getChanges('chat.input.show-elevate-your-message', val =>
|
||||||
|
this.css_tweaks.toggleHide('elevate-your-message', ! val));
|
||||||
|
|
||||||
this.updateChatCSS();
|
this.updateChatCSS();
|
||||||
this.updateColors();
|
this.updateColors();
|
||||||
this.updateLineBorders();
|
this.updateLineBorders();
|
||||||
|
@ -1296,6 +1347,7 @@ export default class ChatHook extends Module {
|
||||||
type: this.chat_types.Message,
|
type: this.chat_types.Message,
|
||||||
ffz_type: 'points',
|
ffz_type: 'points',
|
||||||
ffz_reward: reward,
|
ffz_reward: reward,
|
||||||
|
ffz_reward_highlight: isHighlightedReward(reward),
|
||||||
messageParts: [],
|
messageParts: [],
|
||||||
user: {
|
user: {
|
||||||
id: data.user.id,
|
id: data.user.id,
|
||||||
|
@ -2429,6 +2481,7 @@ export default class ChatHook extends Module {
|
||||||
|
|
||||||
out.ffz_type = 'points';
|
out.ffz_type = 'points';
|
||||||
out.ffz_reward = reward;
|
out.ffz_reward = reward;
|
||||||
|
out.ffz_reward_highlight = isHighlightedReward(reward);
|
||||||
|
|
||||||
return i.postMessageToCurrentChannel(e, out);
|
return i.postMessageToCurrentChannel(e, out);
|
||||||
}
|
}
|
||||||
|
@ -2441,7 +2494,7 @@ export default class ChatHook extends Module {
|
||||||
return old_points.call(i, e);
|
return old_points.call(i, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
const old_host = this.onHostingEvent;
|
/*const old_host = this.onHostingEvent;
|
||||||
this.onHostingEvent = function (e, _t) {
|
this.onHostingEvent = function (e, _t) {
|
||||||
t.emit('tmi:host', e, _t);
|
t.emit('tmi:host', e, _t);
|
||||||
return old_host.call(i, e, _t);
|
return old_host.call(i, e, _t);
|
||||||
|
@ -2451,7 +2504,7 @@ export default class ChatHook extends Module {
|
||||||
this.onUnhostEvent = function (e, _t) {
|
this.onUnhostEvent = function (e, _t) {
|
||||||
t.emit('tmi:unhost', e, _t);
|
t.emit('tmi:unhost', e, _t);
|
||||||
return old_unhost.call(i, e, _t);
|
return old_unhost.call(i, e, _t);
|
||||||
}
|
}*/
|
||||||
|
|
||||||
const old_add = this.addMessage;
|
const old_add = this.addMessage;
|
||||||
this.addMessage = function(e) {
|
this.addMessage = function(e) {
|
||||||
|
|
|
@ -722,6 +722,7 @@ export default class Input extends Module {
|
||||||
return {emotes: [], length: 0};
|
return {emotes: [], length: 0};
|
||||||
|
|
||||||
const out = [],
|
const out = [],
|
||||||
|
seen = new Set,
|
||||||
anim = this.chat.context.get('chat.emotes.animated') > 0,
|
anim = this.chat.context.get('chat.emotes.animated') > 0,
|
||||||
hidden_sets = this.settings.provider.get('emote-menu.hidden-sets'),
|
hidden_sets = this.settings.provider.get('emote-menu.hidden-sets'),
|
||||||
has_hidden = Array.isArray(hidden_sets) && hidden_sets.length > 0,
|
has_hidden = Array.isArray(hidden_sets) && hidden_sets.length > 0,
|
||||||
|
@ -760,9 +761,11 @@ export default class Input extends Module {
|
||||||
const id = emote.id,
|
const id = emote.id,
|
||||||
token = KNOWN_CODES[emote.token] || emote.token;
|
token = KNOWN_CODES[emote.token] || emote.token;
|
||||||
|
|
||||||
if ( ! token )
|
if ( ! token || seen.has(token) )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
seen.add(token);
|
||||||
|
|
||||||
const replacement = REPLACEMENTS[id];
|
const replacement = REPLACEMENTS[id];
|
||||||
let srcSet;
|
let srcSet;
|
||||||
|
|
||||||
|
@ -942,18 +945,18 @@ export default class Input extends Module {
|
||||||
|
|
||||||
|
|
||||||
getEmoteSuggestions(input, inst) {
|
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,
|
const user = inst._ffz_user,
|
||||||
channel_id = inst._ffz_channel_id,
|
channel_id = inst._ffz_channel_id,
|
||||||
channel_login = inst._ffz_channel_login;
|
channel_login = inst._ffz_channel_login;
|
||||||
|
|
||||||
if ( ! channel_login ) {
|
if ( ! channel_login )
|
||||||
const parent = this.fine.searchParent(inst, 'chat-input', 50);
|
return [];
|
||||||
if ( parent )
|
|
||||||
this.updateEmoteCompletion(parent, inst);
|
|
||||||
|
|
||||||
if ( ! channel_login )
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
let cache = inst.ffz_ffz_cache;
|
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 )
|
if ( ! cache || cache.user_id !== user?.id || cache.user_login !== user?.login || cache.channel_id !== channel_id || cache.channel_login !== channel_login )
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -108,6 +108,11 @@ export default class Scroller extends Module {
|
||||||
this.use_keys = true;
|
this.use_keys = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
for(const act of this.chat.context.get('chat.actions.hover'))
|
||||||
|
if ( act && act.display && act.display.keys ) {
|
||||||
|
this.use_keys = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if ( this.use_keys !== old_use ) {
|
if ( this.use_keys !== old_use ) {
|
||||||
for(const inst of this.ChatScroller.instances)
|
for(const inst of this.ChatScroller.instances)
|
||||||
|
@ -679,6 +684,16 @@ export default class Scroller extends Module {
|
||||||
this.off('tooltips:mousemove', inst.ffzTooltipHover, inst);
|
this.off('tooltips:mousemove', inst.ffzTooltipHover, inst);
|
||||||
this.off('tooltips:leave', inst.ffzTooltipLeave, inst);
|
this.off('tooltips:leave', inst.ffzTooltipLeave, inst);
|
||||||
|
|
||||||
|
if ( inst._ffz_hover_timer ) {
|
||||||
|
clearInterval(inst._ffz_hover_timer);
|
||||||
|
inst._ffz_hover_timer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( inst._ffz_outside_timer ) {
|
||||||
|
clearTimeout(inst._ffz_outside_timer);
|
||||||
|
inst._ffz_outside_timer = null;
|
||||||
|
}
|
||||||
|
|
||||||
window.removeEventListener('keydown', inst.ffzHandleKey);
|
window.removeEventListener('keydown', inst.ffzHandleKey);
|
||||||
window.removeEventListener('keyup', inst.ffzHandleKey);
|
window.removeEventListener('keyup', inst.ffzHandleKey);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ const CLASSES = {
|
||||||
'modview-hide-info': '.modview-player-widget__hide-stream-info',
|
'modview-hide-info': '.modview-player-widget__hide-stream-info',
|
||||||
|
|
||||||
'community-highlights': '.community-highlight-stack__card',
|
'community-highlights': '.community-highlight-stack__card',
|
||||||
|
'elevate-your-message': '.chat-input__input-icons button[aria-label="ElevatedMessage"]',
|
||||||
|
|
||||||
'prime-offers': '.top-nav__prime',
|
'prime-offers': '.top-nav__prime',
|
||||||
'discover-luna': '.top-nav__external-link[data-a-target="try-presto-link"]',
|
'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-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"])',
|
'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,div[data-test-selector="channel-leaderboard-container"]',
|
||||||
'whispers': 'body .whispers-open-threads,.tw-core-button[data-a-target="whisper-box-button"],.whispers__pill',
|
'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',
|
'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',
|
||||||
|
@ -46,8 +47,12 @@ const CLASSES = {
|
||||||
'not-live-bar': 'div[data-test-selector="non-live-video-banner-layout"]',
|
'not-live-bar': 'div[data-test-selector="non-live-video-banner-layout"]',
|
||||||
'channel-live-ind': '.channel-header__user .tw-channel-status-text-indicator,.channel-info-content .tw-halo__indicator',
|
'channel-live-ind': '.channel-header__user .tw-channel-status-text-indicator,.channel-info-content .tw-halo__indicator',
|
||||||
'celebration': 'body .celebration__overlay',
|
'celebration': 'body .celebration__overlay',
|
||||||
'mod-view': '.chat-input__buttons-container a[href*="/moderator"]',
|
|
||||||
'last-x-events': '.last-x-events_container'
|
'last-x-events': '.last-x-events_container',
|
||||||
|
|
||||||
|
'ci-mod-view': '.chat-input__buttons-container a[href*="/moderator"]',
|
||||||
|
'ci-highlight-settings': '.chat-input__buttons-container button[data-highlight-selector="chat-highlights-shortcut"]',
|
||||||
|
'ci-shield-mode': '.chat-input__buttons-container > div:last-child button[class|="ScCoreButton"]:not([data-highlight-selector]):not([data-a-target])'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
.video-player__container[data-paused="true"] video,
|
||||||
|
.video-player__container[data-buffering="true"] video {
|
||||||
|
filter: grayscale(0.25) brightness(0.5);
|
||||||
|
}
|
|
@ -1,3 +1,3 @@
|
||||||
.video-player__overlay[data-controls="false"][data-paused="false"][data-ended="false"] {
|
.video-player__container[data-controls="false"][data-paused="false"][data-ended="false"] {
|
||||||
cursor: none;
|
cursor: none;
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
.channel-root__scroll-area--theatre-mode {
|
.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;
|
right: 40rem !important;
|
||||||
bottom: calc(10rem + var(--ffz-chat-height)) !important;
|
bottom: calc(10rem + var(--ffz-chat-height)) !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,7 +94,7 @@ body .whispers--theatre-mode.whispers--right-column-expanded-beside {
|
||||||
}
|
}
|
||||||
|
|
||||||
.channel-root__scroll-area--theatre-mode {
|
.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;
|
left: calc(var(--ffz-chat-width) + 5rem) !important;
|
||||||
right: 40rem !important;
|
right: 40rem !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
.channel-root__scroll-area--theatre-mode {
|
.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 {
|
.channel-info-bar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 17rem;
|
bottom: 17rem;
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
.channel-info-content > div:first-child,
|
#live-channel-stream-information, //.channel-info-content > section:first-child,
|
||||||
.channel-info-bar {
|
.channel-info-bar {
|
||||||
background-color: var(--color-background-base);
|
background-color: var(--color-background-base);
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
|
|
|
@ -54,7 +54,7 @@ export default class Dashboard extends Module {
|
||||||
this.settings.updateContext({
|
this.settings.updateContext({
|
||||||
channel: get('props.channelLogin', inst),
|
channel: get('props.channelLogin', inst),
|
||||||
channelID: get('props.channelID', 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({
|
this.settings.updateContext({
|
||||||
channel: null,
|
channel: null,
|
||||||
channelID: null,
|
channelID: null,
|
||||||
hosting: false
|
//hosting: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(<p class="tw-pd-t-05 tw-pd-x-1 tw-c-text-alt-2">
|
|
||||||
{this.i18n.t('directory.hosted', 'Hosted Channel')}
|
|
||||||
</p>);
|
|
||||||
|
|
||||||
// Hosted Channel Content
|
|
||||||
simplebarContentChildren.push(<a
|
|
||||||
class="tw-block tw-border-radius-small tw-full-width ffz-interactable ffz-interactable--default ffz-interactable--hover-enabled tw-interactive"
|
|
||||||
href={`/${inst.props.channelLogin}`}
|
|
||||||
onClick={e => this.parent.hijackUserClick(e, inst.props.channelLogin, this.destroyHostMenu.bind(this))} // eslint-disable-line react/jsx-no-bind
|
|
||||||
>
|
|
||||||
<div class="tw-align-items-center tw-flex tw-flex-row tw-flex-nowrap tw-mg-x-1 tw-mg-y-05">
|
|
||||||
<div class="ffz-channel-avatar">
|
|
||||||
<img src={inst.props.channelImageProps.src} alt={inst.props.channelDisplayName} />
|
|
||||||
</div>
|
|
||||||
<p class="tw-ellipsis tw-flex-grow-1 tw-mg-l-1 tw-font-size-5">
|
|
||||||
{inst.props.channelDisplayName}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</a>);
|
|
||||||
|
|
||||||
// Hosting Channels Header
|
|
||||||
simplebarContentChildren.push(<p class="tw-pd-t-05 tw-pd-x-1 tw-c-text-alt-2">
|
|
||||||
{this.i18n.t('directory.hosting', 'Hosting Channels')}
|
|
||||||
</p>);
|
|
||||||
|
|
||||||
// Hosting Channels Content
|
|
||||||
for (const channel of channels) {
|
|
||||||
simplebarContentChildren.push(<a
|
|
||||||
class="tw-block tw-border-radius-small tw-full-width ffz-interactable ffz-interactable--default ffz-interactable--hover-enabled tw-interactive"
|
|
||||||
href={`/${channel.login}`}
|
|
||||||
onClick={e => this.parent.hijackUserClick(e, channel.login, this.destroyHostMenu.bind(this))} // eslint-disable-line react/jsx-no-bind
|
|
||||||
>
|
|
||||||
<div class="tw-align-items-center tw-flex tw-flex-row tw-flex-nowrap tw-mg-x-1 tw-mg-y-05">
|
|
||||||
<div class="ffz-channel-avatar">
|
|
||||||
<img src={channel.profileImageURL} alt={channel.displayName} />
|
|
||||||
</div>
|
|
||||||
<p class="tw-ellipsis tw-flex-grow-1 tw-mg-l-1 tw-font-size-5">
|
|
||||||
{channel.displayName}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</a>);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hostMenu = (<div class="ffz-host-menu ffz-balloon tw-block">
|
|
||||||
<div class="tw-border tw-elevation-1 tw-border-radius-small tw-c-background-base tw-pd-05">
|
|
||||||
<div class="scrollable-area" data-simplebar>
|
|
||||||
{simplebarContentChildren}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>);
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -48,7 +48,6 @@ export default class Directory extends SiteModule {
|
||||||
this.inject('i18n');
|
this.inject('i18n');
|
||||||
this.inject('settings');
|
this.inject('settings');
|
||||||
|
|
||||||
//this.inject(Following);
|
|
||||||
this.inject(Game);
|
this.inject(Game);
|
||||||
|
|
||||||
this.DirectoryCard = this.elemental.define(
|
this.DirectoryCard = this.elemental.define(
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -172,7 +172,7 @@ export default class Layout extends Module {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.settings.add('layout.portrait-extra-height', {
|
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) {
|
process(ctx) {
|
||||||
let height = 0;
|
let height = 0;
|
||||||
if ( ctx.get('context.ui.theatreModeEnabled') ) {
|
if ( ctx.get('context.ui.theatreModeEnabled') ) {
|
||||||
|
@ -192,8 +192,8 @@ export default class Layout extends Module {
|
||||||
|
|
||||||
height += ctx.get('context.new_channel') ? 1 : 5;
|
height += ctx.get('context.new_channel') ? 1 : 5;
|
||||||
|
|
||||||
if ( ctx.get('context.hosting') )
|
/*if ( ctx.get('context.hosting') )
|
||||||
height += 4;
|
height += 4;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
return height;
|
return height;
|
||||||
|
|
|
@ -196,7 +196,7 @@ export default class Player extends PlayerBase {
|
||||||
|
|
||||||
|
|
||||||
checkCarousel(inst) {
|
checkCarousel(inst) {
|
||||||
if ( this.settings.get('channel.hosting.enable') )
|
/*if ( this.settings.get('channel.hosting.enable') )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if ( inst.props?.playerType === 'channel_home_carousel' ) {
|
if ( inst.props?.playerType === 'channel_home_carousel' ) {
|
||||||
|
@ -211,7 +211,7 @@ export default class Player extends PlayerBase {
|
||||||
events = inst.props.playerEvents;
|
events = inst.props.playerEvents;
|
||||||
|
|
||||||
this.stopPlayer(player, events, inst);
|
this.stopPlayer(player, events, inst);
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -271,6 +271,7 @@ export default class ThemeEngine extends Module {
|
||||||
`--color-background-input-focus:${color.toCSS()};`,
|
`--color-background-input-focus:${color.toCSS()};`,
|
||||||
`--color-background-base:${hsla._l(luma + (dark ? 0.05 : -0.05)).toCSS()};`,
|
`--color-background-base:${hsla._l(luma + (dark ? 0.05 : -0.05)).toCSS()};`,
|
||||||
`--color-background-alt:${hsla._l(luma + (dark ? 0.1 : -0.1)).toCSS()};`,
|
`--color-background-alt:${hsla._l(luma + (dark ? 0.1 : -0.1)).toCSS()};`,
|
||||||
|
`--color-background-float:var(--color-background-alt);`,
|
||||||
`--color-background-alt-2:${hsla._l(luma + (dark ? 0.15 : -0.15)).toCSS()};`
|
`--color-background-alt-2:${hsla._l(luma + (dark ? 0.15 : -0.15)).toCSS()};`
|
||||||
].join('');
|
].join('');
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,6 +89,8 @@ export default class VideoChatHook extends Module {
|
||||||
component: 'setting-check-box'
|
component: 'setting-check-box'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.active_room = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -561,6 +563,7 @@ export default class VideoChatHook extends Module {
|
||||||
if ( ! this.addRoom(chat, props) )
|
if ( ! this.addRoom(chat, props) )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
this.active_room = chat._ffz_room;
|
||||||
this.chat.badges.updateTwitchBadges(get('data.badges', props));
|
this.chat.badges.updateTwitchBadges(get('data.badges', props));
|
||||||
|
|
||||||
this.updateRoomBadges(chat, get('data.video.owner.broadcastBadges', props));
|
this.updateRoomBadges(chat, get('data.video.owner.broadcastBadges', props));
|
||||||
|
@ -607,6 +610,9 @@ export default class VideoChatHook extends Module {
|
||||||
|
|
||||||
|
|
||||||
chatUnmounted(chat) {
|
chatUnmounted(chat) {
|
||||||
|
if (this.active_room === chat._ffz_room)
|
||||||
|
this.active_room = null;
|
||||||
|
|
||||||
this.removeRoom(chat);
|
this.removeRoom(chat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
.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,
|
||||||
.ffz-stat.tw-button--text {
|
.ffz-stat.tw-button--text {
|
||||||
|
|
|
@ -452,4 +452,50 @@
|
||||||
padding: 0.5rem !important;
|
padding: 0.5rem !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ffz--hover-actions {
|
||||||
|
position: absolute;
|
||||||
|
right: 0.5rem;
|
||||||
|
top: -1rem;
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
--ffz-chat-actions-size: var(--ffz-chat-actions-hover-size);
|
||||||
|
--ffz-chat-actions-padding: var(--ffz-chat-actions-hover-padding);
|
||||||
|
|
||||||
|
.ffz-notice-line:hover &,
|
||||||
|
.chat-line__message:hover & {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.ffz-hover-action {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
|
||||||
|
box-shadow: var(--shadow-elevation-1);
|
||||||
|
background-color: var(--color-background-base);
|
||||||
|
border-radius: var(--border-radius-medium);
|
||||||
|
|
||||||
|
figure {
|
||||||
|
line-height: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ffz-has-modifier {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .ffz-mod-icon {
|
||||||
|
padding: var(--ffz-chat-actions-padding);
|
||||||
|
|
||||||
|
background-color: var(--color-background-button-text-default);
|
||||||
|
color: var(--color-fill-button-icon);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-background-button-text-hover);
|
||||||
|
color: var(--color-fill-button-icon-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
@import 'fixes';
|
@import 'fixes';
|
||||||
|
|
||||||
@import 'host_options';
|
//@import 'host_options';
|
||||||
@import 'featured_follow';
|
@import 'featured_follow';
|
||||||
@import 'mod_card';
|
@import 'mod_card';
|
||||||
@import 'easteregg';
|
@import 'easteregg';
|
259
src/std-components/emote-picker.vue
Normal file
259
src/std-components/emote-picker.vue
Normal file
|
@ -0,0 +1,259 @@
|
||||||
|
<template lang="html">
|
||||||
|
<div v-on-clickaway="close" class="ffz--emote-picker tw-relative">
|
||||||
|
<div class="tw-search-input tw-full-width">
|
||||||
|
<label v-if="isOpen" :for="'emote-search$' + id" class="tw-hide-accessible">{{ t('setting.emote.search', 'Search for Emote') }}</label>
|
||||||
|
<div class="tw-relative">
|
||||||
|
<div class="tw-absolute tw-align-items-center tw-c-text-alt-2 tw-flex tw-full-height ffz-input__icon tw-justify-content-center tw-left-0 tw-top-0 tw-z-default">
|
||||||
|
<figure class="tw-mg-y-05 tw-mg-x-05">
|
||||||
|
<img class="ffz-preview-emote" v-if="val.src" :src="val.src" />
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
:id="'emote-search$' + id"
|
||||||
|
ref="input"
|
||||||
|
:placeholder="t('setting.emote.search', 'Search for Emote')"
|
||||||
|
:value="isOpen ? search : valName"
|
||||||
|
:class="[clearable ? 'tw-pd-r-5' : 'tw-pd-r-1']"
|
||||||
|
type="text"
|
||||||
|
class="tw-block tw-border-radius-medium tw-font-size-6 tw-full-width ffz-input tw-pd-l-3 tw-pd-y-05"
|
||||||
|
autocapitalize="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocomplete="off"
|
||||||
|
spellcheck="false"
|
||||||
|
@input="update"
|
||||||
|
@focus="onFocus"
|
||||||
|
@blur="onBlur"
|
||||||
|
@keydown.escape="open = false"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
v-if="clearable"
|
||||||
|
class="tw-absolute tw-right-0 tw-top-0 tw-button tw-button--text ffz-il-tooltip__container"
|
||||||
|
@click="change('', false)"
|
||||||
|
@keydown.escape="open = false"
|
||||||
|
@focus="onFocus(false)"
|
||||||
|
@blur="onBlur"
|
||||||
|
>
|
||||||
|
<span class="tw-button__text ffz-i-trash" />
|
||||||
|
<div class="ffz-il-tooltip ffz-il-tooltip--up ffz-il-tooltip--align-right">
|
||||||
|
{{ t('setting.icon.clear', 'Clear') }}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<balloon v-if="isOpen" :dir="direction" color="background-base">
|
||||||
|
<div ref="list">
|
||||||
|
<simplebar classes="scrollable-area--suppress-scroll-x ffz--emote-picker__list">
|
||||||
|
<div v-if="visible.length" role="radiogroup" class="tw-pd-1 tw-flex tw-flex-wrap tw-justify-content-between">
|
||||||
|
<div
|
||||||
|
v-for="i of visible"
|
||||||
|
:key="`${i.provider}:${i.id}`"
|
||||||
|
:aria-checked="val.provider === i.provider && val.id === i.id"
|
||||||
|
:class="{'ffz-interactable--selected': val.provider === i.provider && val.id === i.id}"
|
||||||
|
:data-provider="i.provider"
|
||||||
|
:data-id="i.id"
|
||||||
|
:data-set="i.set_id"
|
||||||
|
:data-name="i.name"
|
||||||
|
class="ffz-tooltip ffz-icon ffz-interactable ffz-interactable--hover-enabled ffz-interactable--default tw-interactive"
|
||||||
|
role="radio"
|
||||||
|
tabindex="0"
|
||||||
|
data-tooltip-type="emote"
|
||||||
|
@keydown.space.stop.prevent=""
|
||||||
|
@keyup.space="change(i)"
|
||||||
|
@keyup.enter="change(i)"
|
||||||
|
@click="change(i)"
|
||||||
|
@focus="onFocus(false)"
|
||||||
|
@blur="onBlur"
|
||||||
|
>
|
||||||
|
<figure :class="`tw-mg-y-05 tw-mg-x-1`">
|
||||||
|
<img :src="i.src" />
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="! emotes.length" class="tw-align-center tw-pd-1 tw-c-text-alt-2">
|
||||||
|
{{ t('setting.emote.none', 'unable to load emote data') }}
|
||||||
|
<div class="tw-mg-t-05">
|
||||||
|
{{ t('setting.emote.none-about', 'Please make sure you have the FFZ Emote Menu enabled, and that you use this from a page that loads chat.') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="tw-align-center tw-pd-1 tw-c-text-alt-2">
|
||||||
|
{{ t('setting.actions.empty-search', 'no results') }}
|
||||||
|
</div>
|
||||||
|
</simplebar>
|
||||||
|
</div>
|
||||||
|
</balloon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import { debounce } from 'utilities/object';
|
||||||
|
|
||||||
|
let id = 0;
|
||||||
|
|
||||||
|
function readEmoteMenuEmotes(input, out, seen) {
|
||||||
|
if ( Array.isArray(input) ) {
|
||||||
|
for(const item of input)
|
||||||
|
readEmoteMenuEmotes(item, out, seen);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! Array.isArray(input?.emotes) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
for(const emote of input.emotes) {
|
||||||
|
if ( emote.locked || seen.has(emote.name) )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
seen.add(emote.name);
|
||||||
|
|
||||||
|
out.push({
|
||||||
|
provider: emote.provider,
|
||||||
|
id: emote.id,
|
||||||
|
set_id: emote.set_id,
|
||||||
|
name: emote.name,
|
||||||
|
lname: emote.name && emote.name.toLowerCase(),
|
||||||
|
src: emote.src
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
value: Object,
|
||||||
|
alwaysOpen: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
clearable: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
direction: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'down'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
id: id++,
|
||||||
|
open: false,
|
||||||
|
val: this.value,
|
||||||
|
search: '',
|
||||||
|
emotes: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
visible() {
|
||||||
|
if ( ! this.search || ! this.search.length )
|
||||||
|
return this.emotes;
|
||||||
|
|
||||||
|
const search = this.search.toLowerCase();
|
||||||
|
return this.emotes.filter(x => x.lname && x.lname.indexOf(search) !== -1);
|
||||||
|
},
|
||||||
|
|
||||||
|
valName() {
|
||||||
|
return this.val?.name;
|
||||||
|
},
|
||||||
|
|
||||||
|
isOpen() {
|
||||||
|
return this.alwaysOpen || this.open
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
value() {
|
||||||
|
this.val = this.value;
|
||||||
|
},
|
||||||
|
|
||||||
|
isOpen() {
|
||||||
|
if ( ! this.isOpen ) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const ffz = FrankerFaceZ.get();
|
||||||
|
if ( ffz )
|
||||||
|
ffz.emit('tooltips:cleanup');
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.maybeLoadEmotes();
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if ( this.val ) {
|
||||||
|
const root = this.$refs.list,
|
||||||
|
el = root && root.querySelector('.ffz-interactable--selected');
|
||||||
|
|
||||||
|
if ( el )
|
||||||
|
el.scrollIntoViewIfNeeded();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.maybeClose = debounce(this.maybeClose, 10);
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
maybeLoadEmotes() {
|
||||||
|
if ( ! this.emotes || ! this.emotes.length ) {
|
||||||
|
const emotes = [],
|
||||||
|
seen = new Set,
|
||||||
|
menu = window.ffz_menu,
|
||||||
|
state = menu?.state;
|
||||||
|
|
||||||
|
if ( menu ) {
|
||||||
|
menu.loadData();
|
||||||
|
readEmoteMenuEmotes(state?.channel_sets, emotes, seen);
|
||||||
|
readEmoteMenuEmotes(state?.all_sets, emotes, seen);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emotes = emotes;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
update() {
|
||||||
|
if ( this.isOpen )
|
||||||
|
this.search = this.$refs.input.value;
|
||||||
|
},
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.open = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
change(val, close = true) {
|
||||||
|
this.val = {
|
||||||
|
type: 'emote',
|
||||||
|
provider: val.provider,
|
||||||
|
id: val.id,
|
||||||
|
name: val.name,
|
||||||
|
src: val.src
|
||||||
|
},
|
||||||
|
this.$emit('input', this.val);
|
||||||
|
if ( close )
|
||||||
|
this.open = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
onFocus(open = true) {
|
||||||
|
this.focused = true;
|
||||||
|
if ( open )
|
||||||
|
this.open = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
onBlur() {
|
||||||
|
this.focused = false;
|
||||||
|
this.maybeClose();
|
||||||
|
},
|
||||||
|
|
||||||
|
maybeClose() {
|
||||||
|
if ( ! this.focused )
|
||||||
|
this.open = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
|
@ -31,6 +31,14 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
const scroller = this.$refs.scroller;
|
||||||
|
if (!scroller || ! window.ffzSimplebar || scroller.SimpleBar)
|
||||||
|
return;
|
||||||
|
|
||||||
|
new ffzSimplebar(scroller, ffzSimplebar.getElOptions(scroller));
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
onScroll() {
|
onScroll() {
|
||||||
// We do this to avoid the scroll position getting screwed up on
|
// We do this to avoid the scroll position getting screwed up on
|
||||||
|
|
|
@ -40,6 +40,7 @@ export const RERENDER_SETTINGS = [
|
||||||
'chat.subs.show',
|
'chat.subs.show',
|
||||||
'chat.subs.compact',
|
'chat.subs.compact',
|
||||||
'chat.actions.inline',
|
'chat.actions.inline',
|
||||||
|
'chat.actions.hover',
|
||||||
'chat.timestamp-format',
|
'chat.timestamp-format',
|
||||||
'chat.points.allow-highlight',
|
'chat.points.allow-highlight',
|
||||||
'chat.filtering.display-deleted',
|
'chat.filtering.display-deleted',
|
||||||
|
|
|
@ -104,5 +104,7 @@ export default [
|
||||||
"reply",
|
"reply",
|
||||||
"threads",
|
"threads",
|
||||||
"right-open",
|
"right-open",
|
||||||
"list-bullet"
|
"list-bullet",
|
||||||
|
"mastodon",
|
||||||
|
"volume-up"
|
||||||
];
|
];
|
|
@ -69,26 +69,50 @@ export class Logger {
|
||||||
return this.invoke(Logger.VERBOSE, args);
|
return this.invoke(Logger.VERBOSE, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
verboseColor(msg, colors, ...args) {
|
||||||
|
return this.invokeColor(Logger.VERBOSE, msg, colors, args);
|
||||||
|
}
|
||||||
|
|
||||||
debug(...args) {
|
debug(...args) {
|
||||||
return this.invoke(Logger.DEBUG, args);
|
return this.invoke(Logger.DEBUG, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debugColor(msg, colors, ...args) {
|
||||||
|
return this.invokeColor(Logger.DEBUG, msg, colors, args);
|
||||||
|
}
|
||||||
|
|
||||||
info(...args) {
|
info(...args) {
|
||||||
return this.invoke(Logger.INFO, args);
|
return this.invoke(Logger.INFO, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
infoColor(msg, colors, ...args) {
|
||||||
|
return this.invokeColor(Logger.INFO, msg, colors, args);
|
||||||
|
}
|
||||||
|
|
||||||
warn(...args) {
|
warn(...args) {
|
||||||
return this.invoke(Logger.WARN, args);
|
return this.invoke(Logger.WARN, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
warnColor(msg, colors, ...args) {
|
||||||
|
return this.invokeColor(Logger.WARN, msg, colors, args);
|
||||||
|
}
|
||||||
|
|
||||||
warning(...args) {
|
warning(...args) {
|
||||||
return this.invoke(Logger.WARN, args);
|
return this.invoke(Logger.WARN, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
warningColor(msg, colors, ...args) {
|
||||||
|
return this.invokeColor(Logger.WARN, msg, colors, args);
|
||||||
|
}
|
||||||
|
|
||||||
error(...args) {
|
error(...args) {
|
||||||
return this.invoke(Logger.ERROR, args);
|
return this.invoke(Logger.ERROR, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
errorColor(msg, colors, ...args) {
|
||||||
|
return this.invokeColor(Logger.ERROR, msg, colors, args);
|
||||||
|
}
|
||||||
|
|
||||||
crumb(...args) {
|
crumb(...args) {
|
||||||
if ( this.raven )
|
if ( this.raven )
|
||||||
return this.raven.captureBreadcrumb(...args);
|
return this.raven.captureBreadcrumb(...args);
|
||||||
|
@ -107,6 +131,62 @@ export class Logger {
|
||||||
return this.error(...args);
|
return this.error(...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
invokeColor(level, msg, colors, args) {
|
||||||
|
if ( ! this.enabled || level < this.level )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ( ! Array.isArray(colors) )
|
||||||
|
colors = [colors];
|
||||||
|
|
||||||
|
const message = args ? Array.prototype.slice.call(args) : [];
|
||||||
|
|
||||||
|
if ( level !== Logger.VERBOSE ) {
|
||||||
|
const out = msg.replace(/%c/g, '') + ' ' + message.join(' ');
|
||||||
|
|
||||||
|
if ( this.root.init )
|
||||||
|
this.root.captured_init.push({
|
||||||
|
time: Date.now(),
|
||||||
|
category: this.name,
|
||||||
|
message: out,
|
||||||
|
level: RAVEN_LEVELS[level] || level
|
||||||
|
});
|
||||||
|
|
||||||
|
this.crumb({
|
||||||
|
message: out,
|
||||||
|
category: this.name,
|
||||||
|
level: RAVEN_LEVELS[level] || level
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
message.unshift(msg);
|
||||||
|
|
||||||
|
if ( this.name ) {
|
||||||
|
message[0] = `%c${this.root.label} [%c${this.name}%c]:%c ${message[0]}`;
|
||||||
|
colors.unshift('color:#755000; font-weight:bold', '', 'color:#755000; font-weight:bold', '');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
message[0] = `%c${this.root.label}:%c ${message[0]}`;
|
||||||
|
colors.unshift('color:#755000; font-weight:bold', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
message.splice(1, 0, ...colors);
|
||||||
|
|
||||||
|
if ( level === Logger.DEBUG || level === Logger.VERBOSE )
|
||||||
|
console.debug(...message);
|
||||||
|
|
||||||
|
else if ( level === Logger.INFO )
|
||||||
|
console.info(...message);
|
||||||
|
|
||||||
|
else if ( level === Logger.WARN )
|
||||||
|
console.warn(...message);
|
||||||
|
|
||||||
|
else if ( level === Logger.ERROR )
|
||||||
|
console.error(...message);
|
||||||
|
|
||||||
|
else
|
||||||
|
console.log(...message);
|
||||||
|
}
|
||||||
|
|
||||||
/* eslint no-console: "off" */
|
/* eslint no-console: "off" */
|
||||||
invoke(level, args) {
|
invoke(level, args) {
|
||||||
if ( ! this.enabled || level < this.level )
|
if ( ! this.enabled || level < this.level )
|
||||||
|
|
|
@ -802,8 +802,13 @@ function render_image(token, createElement, ctx) {
|
||||||
const image = createElement('img', {
|
const image = createElement('img', {
|
||||||
className: `${token.class || ''} ${round}`,
|
className: `${token.class || ''} ${round}`,
|
||||||
src: token.url,
|
src: token.url,
|
||||||
|
alt: token.alt || token.title || '',
|
||||||
title: token.title || '',
|
title: token.title || '',
|
||||||
onLoad: ctx.onload
|
onLoad: ctx.onload,
|
||||||
|
style: {
|
||||||
|
width: token.width,
|
||||||
|
height: token.height
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if ( ! aspect )
|
if ( ! aspect )
|
||||||
|
|
|
@ -373,7 +373,12 @@
|
||||||
&[data-meta="true"][data-alt="true"][data-ctrl="true"] .ffz-modifier-13,
|
&[data-meta="true"][data-alt="true"][data-ctrl="true"] .ffz-modifier-13,
|
||||||
&[data-meta="true"][data-alt="true"][data-shift="true"] .ffz-modifier-14,
|
&[data-meta="true"][data-alt="true"][data-shift="true"] .ffz-modifier-14,
|
||||||
&[data-meta="true"][data-alt="true"][data-ctrl="true"][data-shift="true"] .ffz-modifier-15 {
|
&[data-meta="true"][data-alt="true"][data-ctrl="true"][data-shift="true"] .ffz-modifier-15 {
|
||||||
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|
||||||
|
&:ffz-hover-action {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,8 @@
|
||||||
.ffz-i-threads:before { content: '\e844'; } /* '' */
|
.ffz-i-threads:before { content: '\e844'; } /* '' */
|
||||||
.ffz-i-volume-off:before { content: '\e845'; } /* '' */
|
.ffz-i-volume-off:before { content: '\e845'; } /* '' */
|
||||||
.ffz-i-right-open:before { content: '\e846'; } /* '' */
|
.ffz-i-right-open:before { content: '\e846'; } /* '' */
|
||||||
|
.ffz-i-mastodon:before { content: '\e847'; } /* '' */
|
||||||
|
.ffz-i-volume-up:before { content: '\e848'; } /* '' */
|
||||||
.ffz-i-move:before { content: '\f047'; } /* '' */
|
.ffz-i-move:before { content: '\f047'; } /* '' */
|
||||||
.ffz-i-link-ext:before { content: '\f08e'; } /* '' */
|
.ffz-i-link-ext:before { content: '\f08e'; } /* '' */
|
||||||
.ffz-i-twitter:before { content: '\f099'; } /* '' */
|
.ffz-i-twitter:before { content: '\f099'; } /* '' */
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -70,6 +70,8 @@
|
||||||
.ffz-i-threads { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
.ffz-i-threads { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
.ffz-i-volume-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
.ffz-i-volume-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
.ffz-i-right-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
.ffz-i-right-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
|
.ffz-i-mastodon { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
|
.ffz-i-volume-up { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
.ffz-i-move { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
.ffz-i-move { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
.ffz-i-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
.ffz-i-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
.ffz-i-twitter { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
.ffz-i-twitter { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
|
|
|
@ -81,6 +81,8 @@
|
||||||
.ffz-i-threads { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
.ffz-i-threads { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
.ffz-i-volume-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
.ffz-i-volume-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
.ffz-i-right-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
.ffz-i-right-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
|
.ffz-i-mastodon { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
|
.ffz-i-volume-up { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
.ffz-i-move { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
.ffz-i-move { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
.ffz-i-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
.ffz-i-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
.ffz-i-twitter { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
.ffz-i-twitter { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'ffz-fontello';
|
font-family: 'ffz-fontello';
|
||||||
src: url('../font/ffz-fontello.eot?79984461');
|
src: url('../font/ffz-fontello.eot?19069837');
|
||||||
src: url('../font/ffz-fontello.eot?79984461#iefix') format('embedded-opentype'),
|
src: url('../font/ffz-fontello.eot?19069837#iefix') format('embedded-opentype'),
|
||||||
url('../font/ffz-fontello.woff2?79984461') format('woff2'),
|
url('../font/ffz-fontello.woff2?19069837') format('woff2'),
|
||||||
url('../font/ffz-fontello.woff?79984461') format('woff'),
|
url('../font/ffz-fontello.woff?19069837') format('woff'),
|
||||||
url('../font/ffz-fontello.ttf?79984461') format('truetype'),
|
url('../font/ffz-fontello.ttf?19069837') format('truetype'),
|
||||||
url('../font/ffz-fontello.svg?79984461#ffz-fontello') format('svg');
|
url('../font/ffz-fontello.svg?19069837#ffz-fontello') format('svg');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'ffz-fontello';
|
font-family: 'ffz-fontello';
|
||||||
src: url('../font/ffz-fontello.svg?79984461#ffz-fontello') format('svg');
|
src: url('../font/ffz-fontello.svg?19069837#ffz-fontello') format('svg');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
@ -125,6 +125,8 @@
|
||||||
.ffz-i-threads:before { content: '\e844'; } /* '' */
|
.ffz-i-threads:before { content: '\e844'; } /* '' */
|
||||||
.ffz-i-volume-off:before { content: '\e845'; } /* '' */
|
.ffz-i-volume-off:before { content: '\e845'; } /* '' */
|
||||||
.ffz-i-right-open:before { content: '\e846'; } /* '' */
|
.ffz-i-right-open:before { content: '\e846'; } /* '' */
|
||||||
|
.ffz-i-mastodon:before { content: '\e847'; } /* '' */
|
||||||
|
.ffz-i-volume-up:before { content: '\e848'; } /* '' */
|
||||||
.ffz-i-move:before { content: '\f047'; } /* '' */
|
.ffz-i-move:before { content: '\f047'; } /* '' */
|
||||||
.ffz-i-link-ext:before { content: '\f08e'; } /* '' */
|
.ffz-i-link-ext:before { content: '\f08e'; } /* '' */
|
||||||
.ffz-i-twitter:before { content: '\f099'; } /* '' */
|
.ffz-i-twitter:before { content: '\f099'; } /* '' */
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
|
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
|
||||||
}
|
}
|
||||||
|
|
||||||
.ffz-mod-icon .ffz-i-reply {
|
.ffz--inline-actions .ffz-mod-icon .ffz-i-reply {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
@import "./widgets/color-picker.scss";
|
@import "./widgets/color-picker.scss";
|
||||||
@import "./widgets/icon-picker.scss";
|
@import "./widgets/icon-picker.scss";
|
||||||
|
|
||||||
|
@import "./widgets/chat-tester.scss";
|
||||||
|
|
||||||
@import "./widgets/check-box.scss";
|
@import "./widgets/check-box.scss";
|
||||||
|
|
||||||
.tw-display-inline { display: inline !important }
|
.tw-display-inline { display: inline !important }
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
.ffz--emote-picker__list,
|
||||||
.ffz--icon-picker__list {
|
.ffz--icon-picker__list {
|
||||||
max-height: 15rem;
|
max-height: 15rem;
|
||||||
font-size: 1.6rem;
|
font-size: 1.6rem;
|
||||||
|
@ -9,4 +10,12 @@
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ffz--emote-picker {
|
||||||
|
.ffz-preview-emote {
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue