Merge branch 'main' of https://github.com/JohnXLivingston/peertube-plugin-livechat
This commit is contained in:
commit
a4bf37d534
@ -1,14 +0,0 @@
|
||||
{
|
||||
"root": true,
|
||||
"env": {},
|
||||
"extends": [],
|
||||
"globals": {},
|
||||
"plugins": [],
|
||||
"ignorePatterns": [
|
||||
"node_modules/", "dist/", "webpack.config.js",
|
||||
"build/",
|
||||
"vendor/",
|
||||
"support/documentation",
|
||||
"build-*js"],
|
||||
"rules": {}
|
||||
}
|
40
.github/dependabot.yml
vendored
Normal file
40
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
# SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 10
|
||||
|
||||
groups:
|
||||
minor-and-patch:
|
||||
applies-to: version-updates
|
||||
update-types:
|
||||
- "patch"
|
||||
- "minor"
|
||||
|
||||
versioning-strategy: increase
|
||||
|
||||
ignore:
|
||||
- dependency-name: typescript
|
||||
versions:
|
||||
- ">=5.6.0" # linting libs are not ready for 5.6
|
||||
- dependency-name: "@types/node"
|
||||
versions:
|
||||
- ">=17.0.0" # must be set to the Peertube required version.
|
||||
- dependency-name: "@peertube/peertube-types"
|
||||
versions:
|
||||
- ">5.2.0" # must be set to the Peertube required version.
|
||||
- dependency-name: eslint
|
||||
versions:
|
||||
- ">=9.0.0" # not ready for v9, missing dependencies.
|
||||
- dependency-name: got
|
||||
versions:
|
||||
- ">=12.0.0" # breaking changes, must adapt code.
|
||||
- dependency-name: "@typescript-eslint/parser"
|
||||
versions:
|
||||
- ">=8.5.0" # for now 8.5.0 is broken because of the lack of ./tsconfig.json file. Must fix conf.
|
34
.github/workflows/gh-build.yml
vendored
Normal file
34
.github/workflows/gh-build.yml
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
# SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
name: github build and lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
types: [assigned, opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: false # Fetch Hugo themes (true OR recursive)
|
||||
fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
- name: Install build dependencies
|
||||
run: sudo apt update && sudo apt install wget reuse -y
|
||||
|
||||
- name: Build
|
||||
run: npm install
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
@ -2,7 +2,7 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
'use strict';
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
extends: [
|
||||
@ -14,7 +14,7 @@ module.exports = {
|
||||
// extending the kebab-case to accept ConverseJS class names.
|
||||
'^([a-z][a-z0-9]*)(-[a-z0-9]+)*((__|--)[a-z]+(-[a-z0-9]+)*)?$',
|
||||
{
|
||||
message: 'Expected class selector to be kebab-case, or ConverseJS-style.',
|
||||
message: 'Expected class selector to be kebab-case, or ConverseJS-style.'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
29
CHANGELOG.md
29
CHANGELOG.md
@ -1,5 +1,34 @@
|
||||
# Changelog
|
||||
|
||||
## 12.0.0
|
||||
|
||||
### Importante Notes
|
||||
|
||||
This version requires Peertube 5.2.0 or superior.
|
||||
It also requires NodeJS 16 or superior (same as Peertube 5.2.0.).
|
||||
|
||||
If you use the "system Prosody", you should update to Prosody 0.12.4, and Lua 5.4.
|
||||
|
||||
### New features
|
||||
|
||||
* #131: Emoji only mode.
|
||||
* #516: new option for the moderation bot: forbid duplicate messages.
|
||||
* #517: new option for the moderation bot: forbid messages with too many special characters.
|
||||
* #518: moderators can send announcements and highlighted messages.
|
||||
* #610: compatibility with PeerTube v7
|
||||
|
||||
### Minor changes and fixes
|
||||
|
||||
* Updating ConverseJS (v11 WIP) with latest fixes.
|
||||
* Updating Prosody AppImage to Prosody 0.12.4 + Lua 5.4.
|
||||
* Various translation updates.
|
||||
* Using Typescript 5.5.4, and Eslint 8.57.0 (with new ruleset).
|
||||
* Fix race condition in bot/ctl.
|
||||
* Various type improvements.
|
||||
* Update dependencies.
|
||||
* Fix emoji picker colors and size.
|
||||
* Fix: moderation delay max value was not correctly handled.
|
||||
|
||||
## 11.0.1
|
||||
|
||||
### Minor changes and fixes
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
/* See Peertube sub-menu-h1 mixin */
|
||||
font-size: 1.3rem;
|
||||
border-bottom: 2px solid var(--greyBackgroundColor);
|
||||
border-bottom: 2px solid var(--bg-secondary-400, var(--greyBackgroundColor));
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
@ -42,45 +42,49 @@
|
||||
|
||||
input[type="submit"],
|
||||
button[type="submit"] {
|
||||
// Peertube orange-button mixin
|
||||
&,
|
||||
&:active,
|
||||
&.active,
|
||||
&:focus {
|
||||
color: #fff;
|
||||
background-color: var(--mainColor);
|
||||
color: var(--on-primary, #fff);
|
||||
background-color: var(--primary, var(--mainColor));
|
||||
border: 1px solid var(--primary, var(--mainColor));
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background-color: var(--mainHoverColor);
|
||||
color: var(--on-primary, #fff);
|
||||
background-color: var(--primary-400, var(--mainHoverColor));
|
||||
}
|
||||
|
||||
&[disabled],
|
||||
&.disabled {
|
||||
cursor: default;
|
||||
color: #fff;
|
||||
background-color: var(--inputBorderColor);
|
||||
&[disabled] {
|
||||
pointer-events: none;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="reset"],
|
||||
button[type="reset"] {
|
||||
// Peertube grey-button mixin
|
||||
background-color: var(--greyBackgroundColor);
|
||||
color: var(--greyForegroundColor);
|
||||
color: var(--fg, var(--mainForegroundColor));
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--bg-secondary-500, var(--inputBorderColor)) !important;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&.active,
|
||||
&:focus,
|
||||
&[disabled],
|
||||
&.disabled {
|
||||
color: var(--greyForegroundColor);
|
||||
background-color: var(--greySecondaryBackgroundColor);
|
||||
&:focus-visible {
|
||||
color: var(--fg, var(--mainForegroundColor));
|
||||
background-color: var(--bg-secondary-500, var(--inputBorderColor));
|
||||
border-color: var(--bg-secondary-500, var(--inputBorderColor));
|
||||
}
|
||||
|
||||
&[disabled],
|
||||
&.disabled {
|
||||
cursor: default;
|
||||
&:hover {
|
||||
color: var(--fg, var(--mainForegroundColor));
|
||||
background-color: var(--bg-secondary-450, var(--inputBorderColor));
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
pointer-events: none;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ $small-view: 800px;
|
||||
|
||||
/* See Peertube sub-menu-h1 mixin */
|
||||
font-size: 1.3rem;
|
||||
border-bottom: 2px solid var(--greyBackgroundColor);
|
||||
border-bottom: 2px solid var(--bg-secondary-400, var(--greyBackgroundColor));
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ $small-view: 800px;
|
||||
&.peertube-plugin-livechat-configuration-channel {
|
||||
.peertube-plugin-livechat-configuration-channel-info {
|
||||
/* stylelint-disable-next-line value-keyword-case */
|
||||
color: var(--mainForegroundColor);
|
||||
color: var(--fg, var(--mainForegroundColor));
|
||||
|
||||
span:first-child {
|
||||
/* See Peertube .video-channel-display-name */
|
||||
@ -48,7 +48,7 @@ $small-view: 800px;
|
||||
h2 {
|
||||
// See Peertube settings-big-title mixin
|
||||
text-transform: uppercase;
|
||||
color: var(--mainColor);
|
||||
color: var(--primary, var(--mainColor));
|
||||
font-weight: variables.$font-bold;
|
||||
font-size: 1rem;
|
||||
margin-bottom: 10px;
|
||||
@ -82,35 +82,35 @@ $small-view: 800px;
|
||||
&:active,
|
||||
&:focus {
|
||||
color: #fff;
|
||||
background-color: var(--mainColor);
|
||||
background-color: var(--primary, var(--mainColor));
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background-color: var(--mainHoverColor);
|
||||
background-color: var(--fg-400, var(--mainHoverColor));
|
||||
}
|
||||
|
||||
&[disabled],
|
||||
&.disabled {
|
||||
cursor: default;
|
||||
color: #fff;
|
||||
background-color: var(--inputBorderColor);
|
||||
background-color: var(--input-border-color, var(--inputBorderColor));
|
||||
}
|
||||
}
|
||||
|
||||
input[type="reset"],
|
||||
button[type="reset"] {
|
||||
// Peertube grey-button mixin
|
||||
background-color: var(--greyBackgroundColor);
|
||||
color: var(--greyForegroundColor);
|
||||
background-color: var(--bg-secondary-400, var(--greyBackgroundColor));
|
||||
color: var(--fg-400, var(--greyForegroundColor));
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus,
|
||||
&[disabled],
|
||||
&.disabled {
|
||||
color: var(--greyForegroundColor);
|
||||
background-color: var(--greySecondaryBackgroundColor);
|
||||
color: var(--fg-400, var(--greyForegroundColor));
|
||||
background-color: var(--bg-secondary-300, var(--greySecondaryBackgroundColor));
|
||||
}
|
||||
|
||||
&[disabled],
|
||||
@ -174,6 +174,12 @@ $small-view: 800px;
|
||||
|
||||
a {
|
||||
/* See Peertube .video-channel-names */
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
/* stylelint-disable-next-line value-keyword-case */
|
||||
color: var(--fg, var(--mainForegroundColor));
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
@ -184,12 +190,6 @@ $small-view: 800px;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
/* stylelint-disable-next-line value-keyword-case */
|
||||
color: var(--mainForegroundColor);
|
||||
|
||||
div:first-child {
|
||||
/* See Peertube .video-channel-display-name */
|
||||
font-weight: variables.$font-semibold;
|
||||
|
@ -56,7 +56,7 @@ livechat-share-chat {
|
||||
&.livechat-shareurl-suboptions-disabled {
|
||||
label {
|
||||
/* stylelint-disable-next-line custom-property-pattern */
|
||||
color: var(--greyForegroundColor);
|
||||
color: var(--fg-400, var(--greyForegroundColor));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,9 +15,9 @@ livechat-spinner,
|
||||
height: 48px;
|
||||
margin: 20px;
|
||||
/* stylelint-disable-next-line custom-property-pattern */
|
||||
border: 5px solid var(--greyBackgroundColor) !important; // !important is required for it to work in ConverseJS
|
||||
border: 5px solid var(--bg-secondary-400, var(--greyBackgroundColor)) !important; // !important is required for it to work in ConverseJS
|
||||
/* stylelint-disable-next-line custom-property-pattern */
|
||||
border-bottom-color: var(--mainColor) !important; // !important is required for it to work in ConverseJS
|
||||
border-bottom-color: var(--primary, var(--mainColor)) !important; // !important is required for it to work in ConverseJS
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
|
@ -51,14 +51,14 @@ livechat-tags-input {
|
||||
transition-duration: 0.3s;
|
||||
|
||||
@supports (scrollbar-width: auto) {
|
||||
scrollbar-color: var(--greyForegroundColor) transparent;
|
||||
scrollbar-color: var(--fg-400, var(--greyForegroundColor)) transparent;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
}
|
||||
|
||||
.livechat-tags-container,
|
||||
.livechat-tags-searched {
|
||||
border-bottom: 1px dashed var(--greyForegroundColor);
|
||||
border-bottom: 1px dashed var(--fg-400, var(--greyForegroundColor));
|
||||
|
||||
&.livechat-empty {
|
||||
height: 0;
|
||||
@ -104,7 +104,7 @@ livechat-tags-input {
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
margin-left: var(--tag-padding-horizontal);
|
||||
color: var(--mainColor);
|
||||
color: var(--primary, var(--mainColor));
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
@ -118,19 +118,19 @@ livechat-tags-input {
|
||||
&:active,
|
||||
&:focus {
|
||||
color: #fff;
|
||||
background-color: var(--mainColor);
|
||||
background-color: var(--primary, var(--mainColor));
|
||||
|
||||
.livechat-tag-close {
|
||||
color: var(--mainColor);
|
||||
color: var(--primary, var(--mainColor));
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background-color: var(--mainHoverColor);
|
||||
background-color: var(--fg-400, var(--mainHoverColor));
|
||||
|
||||
.livechat-tag-close {
|
||||
color: var(--mainHoverColor);
|
||||
color: var(--fg-400, var(--mainHoverColor));
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,10 +138,10 @@ livechat-tags-input {
|
||||
&.disabled {
|
||||
cursor: default;
|
||||
color: #fff;
|
||||
background-color: var(--inputBorderColor);
|
||||
background-color: var(--input-border-color, var(--inputBorderColor));
|
||||
|
||||
.livechat-tag-close {
|
||||
color: var(--inputBorderColor);
|
||||
color: var(--input-border-color, var(--inputBorderColor));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,10 +9,10 @@
|
||||
|
||||
livechat-token-list {
|
||||
table {
|
||||
@include tables.data-table;
|
||||
|
||||
width: 100%;
|
||||
|
||||
@include tables.data-table;
|
||||
|
||||
tr th:first-child,
|
||||
tr th:last-child {
|
||||
width: 50px;
|
||||
|
@ -19,7 +19,7 @@ table.peertube-plugin-livechat-prosody-list-rooms tr:nth-child(even) {
|
||||
|
||||
table.peertube-plugin-livechat-prosody-list-rooms th {
|
||||
/* stylelint-disable-next-line custom-property-pattern */
|
||||
background-color: var(--mainHoverColor);
|
||||
background-color: var(--fg-400, var(--mainHoverColor));
|
||||
border: 1px solid black;
|
||||
/* stylelint-disable-next-line custom-property-pattern */
|
||||
color: var(--mainBackgroundColor);
|
||||
|
@ -54,7 +54,7 @@ $bs-green: #39cc0b;
|
||||
&.disabled {
|
||||
cursor: default;
|
||||
color: #fff;
|
||||
background-color: var(--inputBorderColor);
|
||||
background-color: var(--input-border-color, var(--inputBorderColor));
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,7 +67,7 @@ $bs-green: #39cc0b;
|
||||
&:active,
|
||||
&:focus {
|
||||
color: #fff;
|
||||
background-color: var(--mainColor);
|
||||
background-color: var(--primary, var(--mainColor));
|
||||
}
|
||||
|
||||
&:focus,
|
||||
@ -77,13 +77,13 @@ $bs-green: #39cc0b;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background-color: var(--mainHoverColor);
|
||||
background-color: var(--fg-400, var(--mainHoverColor));
|
||||
}
|
||||
|
||||
&[disabled],
|
||||
&.disabled {
|
||||
cursor: default;
|
||||
color: #fff;
|
||||
background-color: var(--inputBorderColor);
|
||||
background-color: var(--input-border-color, var(--inputBorderColor));
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
text-align: center;
|
||||
|
||||
tr {
|
||||
border: 1px var(--greyBackgroundColor) solid;
|
||||
border: 1px var(--bg-secondary-400, var(--greyBackgroundColor)) solid;
|
||||
}
|
||||
|
||||
td,
|
||||
@ -34,6 +34,6 @@
|
||||
}
|
||||
|
||||
tbody tr:nth-child(odd) {
|
||||
background-color: var(--greySecondaryBackgroundColor);
|
||||
background-color: var(--bg-secondary-300, var(--greySecondaryBackgroundColor));
|
||||
}
|
||||
}
|
||||
|
@ -10,4 +10,4 @@
|
||||
@use "video";
|
||||
@use "configuration/configuration";
|
||||
@use "admin/firewall/firewall";
|
||||
@use "list-rooms/list-rooms.scss";
|
||||
@use "list-rooms/list-rooms";
|
||||
|
@ -34,7 +34,7 @@
|
||||
min-height: max(30vh, 300px);
|
||||
}
|
||||
|
||||
@media screen and (orientation: portrait) and (max-width: 767px) {
|
||||
@media screen and (orientation: portrait) and (width <= 767px) {
|
||||
/* On small screen, and when portrait mode, we are giving the chat more vertical space.
|
||||
It should go under the video.
|
||||
*/
|
||||
|
@ -15,7 +15,7 @@ const clientFiles = [
|
||||
'admin-plugin-client-plugin'
|
||||
]
|
||||
|
||||
function loadLocs() {
|
||||
function loadLocs(globalFile) {
|
||||
// Loading english strings, so we can inject them as constants.
|
||||
const refFile = path.resolve(__dirname, 'dist', 'languages', 'en.reference.json')
|
||||
if (!fs.existsSync(refFile)) {
|
||||
@ -25,7 +25,6 @@ function loadLocs() {
|
||||
|
||||
// Reading client/@types/global.d.ts, to have a list of needed localized strings.
|
||||
const r = {}
|
||||
const globalFile = path.resolve(__dirname, 'client', '@types', 'global.d.ts')
|
||||
const globalFileContent = '' + fs.readFileSync(globalFile)
|
||||
const matches = globalFileContent.matchAll(/^declare const LOC_(\w+)\b/gm)
|
||||
for (const match of matches) {
|
||||
@ -41,7 +40,7 @@ function loadLocs() {
|
||||
const define = Object.assign({
|
||||
PLUGIN_CHAT_PACKAGE_NAME: JSON.stringify(packagejson.name),
|
||||
PLUGIN_CHAT_SHORT_NAME: JSON.stringify(packagejson.name.replace(/^peertube-plugin-/, ''))
|
||||
}, loadLocs())
|
||||
}, loadLocs(path.resolve(__dirname, 'client', '@types', 'global.d.ts')))
|
||||
|
||||
const configs = clientFiles.map(f => ({
|
||||
entryPoints: [ path.resolve(__dirname, 'client', f + '.ts') ],
|
||||
@ -59,8 +58,14 @@ const configs = clientFiles.map(f => ({
|
||||
outfile: path.resolve(__dirname, 'dist/client', f + '.js'),
|
||||
}))
|
||||
|
||||
const defineBuiltin = Object.assign(
|
||||
{},
|
||||
loadLocs(path.resolve(__dirname, 'conversejs', 'lib', '@types', 'global.d.ts'))
|
||||
)
|
||||
|
||||
configs.push({
|
||||
entryPoints: ["./conversejs/builtin.ts"],
|
||||
define: defineBuiltin,
|
||||
bundle: true,
|
||||
minify: true,
|
||||
sourcemap,
|
||||
|
@ -10,12 +10,12 @@ set -euo pipefail
|
||||
# This script download the Prosody AppImage from the https://github.com/JohnXLivingston/prosody-appimage project.
|
||||
|
||||
repo_base_url='https://github.com/JohnXLivingston/prosody-appimage/releases/download'
|
||||
wanted_release='v0.12.3-1'
|
||||
wanted_release='v0.12.4-3'
|
||||
|
||||
x86_64_filename='prosody-x86_64.AppImage'
|
||||
x86_64_sha256sum='f4af9bfefa2f804ad7e8b03a68f04194abb801f070ae620b3d4bcedb144e8523'
|
||||
x86_64_sha256sum='83a583ac7036387514bed17afab257dab4161ccdd0ab7453818c78b51f830357'
|
||||
aarch64_filename='prosody-aarch64.AppImage'
|
||||
aarch64_sha256sum='878c5be719e1e36a84d637fd2bd44e3059aa91ddb6906ad05f1dd0334078df09'
|
||||
aarch64_sha256sum='7b7e6bf30d4498fc99a40022232c3065707ee4f4df24dc17947b007621634304'
|
||||
|
||||
download_dir="$(pwd)/vendor/prosody-appimage"
|
||||
dist_dir="$(pwd)/dist/server/prosody"
|
||||
|
@ -1,41 +0,0 @@
|
||||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true
|
||||
},
|
||||
"extends": [
|
||||
"standard-with-typescript",
|
||||
"plugin:lit/recommended"
|
||||
],
|
||||
"globals": {},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018,
|
||||
"project": [
|
||||
"./client/tsconfig.json"
|
||||
]
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"ignorePatterns": [],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-unused-vars": [2, {"argsIgnorePattern": "^_"}],
|
||||
"@typescript-eslint/no-floating-promises": "error",
|
||||
"@typescript-eslint/no-misused-promises": "error",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"@typescript-eslint/strict-boolean-expressions": "off",
|
||||
"@typescript-eslint/return-await": [2, "in-try-catch"], // FIXME: correct?
|
||||
"@typescript-eslint/no-invalid-void-type": "off",
|
||||
"@typescript-eslint/triple-slash-reference": "off",
|
||||
"max-len": [
|
||||
"error",
|
||||
{
|
||||
"code": 120,
|
||||
"comments": 120
|
||||
}
|
||||
],
|
||||
"no-unused-vars": "off"
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
24
client/@types/global.d.ts
vendored
24
client/@types/global.d.ts
vendored
@ -57,12 +57,12 @@ declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_BOT_OPTIONS_TITLE: string
|
||||
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_LABEL: string
|
||||
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_DESC: string
|
||||
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_DESC2: string
|
||||
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_REASON_LABEL: string
|
||||
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_REASON_DESC: string
|
||||
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_RETRACTATION_REASON_LABEL: string
|
||||
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_RETRACTATION_REASON_DESC: string
|
||||
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_REGEXP_LABEL: string
|
||||
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_REGEXP_DESC: string
|
||||
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_APPLYTOMODERATORS_LABEL: string
|
||||
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_APPLYTOMODERATORS_DESC: string
|
||||
declare const LOC_LIVECHAT_CONFIGURATION_APPLYTOMODERATORS_LABEL: string
|
||||
declare const LOC_LIVECHAT_CONFIGURATION_APPLYTOMODERATORS_DESC: string
|
||||
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_LABEL_LABEL: string
|
||||
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_LABEL_DESC: string
|
||||
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_COMMENTS_LABEL: string
|
||||
@ -144,3 +144,19 @@ declare const LOC_PROSODY_FIREWALL_FILE_ENABLED: string
|
||||
declare const LOC_PROSODY_FIREWALL_NAME: string
|
||||
declare const LOC_PROSODY_FIREWALL_NAME_DESC: string
|
||||
declare const LOC_PROSODY_FIREWALL_CONTENT: string
|
||||
|
||||
declare const LOC_EMOJI_ONLY_MODE_TITLE: string
|
||||
declare const LOC_EMOJI_ONLY_MODE_DESC_1: string
|
||||
declare const LOC_EMOJI_ONLY_MODE_DESC_2: string
|
||||
declare const LOC_EMOJI_ONLY_MODE_DESC_3: string
|
||||
declare const LOC_EMOJI_ONLY_ENABLE_ALL_ROOMS: string
|
||||
|
||||
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_SPECIAL_CHARS_LABEL: string
|
||||
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_SPECIAL_CHARS_DESC: string
|
||||
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_SPECIAL_CHARS_TOLERANCE_LABEL: string
|
||||
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_SPECIAL_CHARS_TOLERANCE_DESC: string
|
||||
|
||||
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_NO_DUPLICATE_LABEL: string
|
||||
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_NO_DUPLICATE_DESC: string
|
||||
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_NO_DUPLICATE_DELAY_LABEL: string
|
||||
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_NO_DUPLICATE_DELAY_DESC: string
|
||||
|
@ -143,7 +143,7 @@ function register (clientOptions: RegisterClientOptions): void {
|
||||
lastActivityEl.textContent = date.toLocaleDateString() + ' ' + date.toLocaleTimeString()
|
||||
}
|
||||
const promoteButton = document.createElement('a')
|
||||
promoteButton.classList.add('orange-button', 'peertube-button-link')
|
||||
promoteButton.classList.add('primary-button', 'orange-button', 'peertube-button-link')
|
||||
promoteButton.style.margin = '5px'
|
||||
promoteButton.onclick = async () => {
|
||||
await fetch(
|
||||
@ -243,7 +243,10 @@ function register (clientOptions: RegisterClientOptions): void {
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(error)
|
||||
peertubeHelpers.notifier.error(error.toString(), await peertubeHelpers.translate(LOC_LOADING_ERROR))
|
||||
peertubeHelpers.notifier.error(
|
||||
(error as Error).toString(),
|
||||
await peertubeHelpers.translate(LOC_LOADING_ERROR)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -46,7 +46,7 @@ async function register (clientOptions: RegisterClientOptions): Promise<void> {
|
||||
])
|
||||
const webchatFieldOptions: RegisterClientFormFieldOptions = {
|
||||
name: 'livechat-active',
|
||||
label: label,
|
||||
label,
|
||||
descriptionHTML: description,
|
||||
type: 'input-checkbox',
|
||||
default: true,
|
||||
|
@ -22,7 +22,7 @@ export class AdminFirewallElement extends LivechatElement {
|
||||
public validationError?: ValidationError
|
||||
|
||||
@state()
|
||||
public actionDisabled: boolean = false
|
||||
public actionDisabled = false
|
||||
|
||||
private _asyncTaskRender: Task
|
||||
|
||||
@ -101,9 +101,9 @@ export class AdminFirewallElement extends LivechatElement {
|
||||
})
|
||||
}
|
||||
|
||||
public readonly getInputValidationClass = (propertyName: string): { [key: string]: boolean } => {
|
||||
public readonly getInputValidationClass = (propertyName: string): Record<string, boolean> => {
|
||||
const validationErrorTypes: ValidationErrorType[] | undefined =
|
||||
this.validationError?.properties[`${propertyName}`]
|
||||
this.validationError?.properties[propertyName]
|
||||
return validationErrorTypes ? (validationErrorTypes.length ? { 'is-invalid': true } : { 'is-valid': true }) : {}
|
||||
}
|
||||
|
||||
@ -111,7 +111,7 @@ export class AdminFirewallElement extends LivechatElement {
|
||||
propertyName: string): TemplateResult | typeof nothing => {
|
||||
const errorMessages: TemplateResult[] = []
|
||||
const validationErrorTypes: ValidationErrorType[] | undefined =
|
||||
this.validationError?.properties[`${propertyName}`] ?? undefined
|
||||
this.validationError?.properties[propertyName] ?? undefined
|
||||
|
||||
// FIXME: this code is duplicated in dymamic table form
|
||||
if (validationErrorTypes && validationErrorTypes.length !== 0) {
|
||||
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import type { AdminFirewallElement } from '../elements/admin-firewall'
|
||||
import type { TemplateResult } from 'lit'
|
||||
import type { DynamicFormHeader, DynamicFormSchema } from '../../../lib/elements/dynamic-table-form'
|
||||
@ -64,7 +67,7 @@ export function tplAdminFirewall (el: AdminFirewallElement): TemplateResult {
|
||||
.maxLines=${maxFirewallFiles}
|
||||
.validation=${el.validationError?.properties}
|
||||
.validationPrefix=${'files'}
|
||||
.rows=${el.firewallConfiguration?.files}
|
||||
.rows=${el.firewallConfiguration?.files ?? []}
|
||||
@update=${(e: CustomEvent) => {
|
||||
el.resetValidation(e)
|
||||
if (el.firewallConfiguration) {
|
||||
|
@ -32,7 +32,7 @@ export class ChannelConfigurationElement extends LivechatElement {
|
||||
public validationError?: ValidationError
|
||||
|
||||
@state()
|
||||
public actionDisabled: boolean = false
|
||||
public actionDisabled = false
|
||||
|
||||
private _asyncTaskRender: Task
|
||||
|
||||
@ -113,9 +113,9 @@ export class ChannelConfigurationElement extends LivechatElement {
|
||||
}
|
||||
}
|
||||
|
||||
public readonly getInputValidationClass = (propertyName: string): { [key: string]: boolean } => {
|
||||
public readonly getInputValidationClass = (propertyName: string): Record<string, boolean> => {
|
||||
const validationErrorTypes: ValidationErrorType[] | undefined =
|
||||
this.validationError?.properties[`${propertyName}`]
|
||||
this.validationError?.properties[propertyName]
|
||||
return validationErrorTypes ? (validationErrorTypes.length ? { 'is-invalid': true } : { 'is-valid': true }) : {}
|
||||
}
|
||||
|
||||
@ -123,7 +123,7 @@ export class ChannelConfigurationElement extends LivechatElement {
|
||||
propertyName: string): TemplateResult | typeof nothing => {
|
||||
const errorMessages: TemplateResult[] = []
|
||||
const validationErrorTypes: ValidationErrorType[] | undefined =
|
||||
this.validationError?.properties[`${propertyName}`] ?? undefined
|
||||
this.validationError?.properties[propertyName] ?? undefined
|
||||
|
||||
// FIXME: this code is duplicated in dymamic table form
|
||||
if (validationErrorTypes && validationErrorTypes.length !== 0) {
|
||||
|
@ -30,7 +30,7 @@ export class ChannelEmojisElement extends LivechatElement {
|
||||
public validationError?: ValidationError
|
||||
|
||||
@state()
|
||||
public actionDisabled: boolean = false
|
||||
public actionDisabled = false
|
||||
|
||||
private _asyncTaskRender: Task
|
||||
|
||||
@ -192,7 +192,7 @@ export class ChannelEmojisElement extends LivechatElement {
|
||||
throw new Error('Invalid data')
|
||||
}
|
||||
|
||||
const url = await this._convertImageToDataUrl(entry.url)
|
||||
const url = await this._convertImageToDataUrl(entry.url as string)
|
||||
const sn = entry.sn as string
|
||||
|
||||
const item: ChannelEmojisConfiguration['emojis']['customEmojis'][0] = {
|
||||
@ -211,7 +211,7 @@ export class ChannelEmojisElement extends LivechatElement {
|
||||
await this.ptTranslate(LOC_ACTION_IMPORT_EMOJIS_INFO)
|
||||
)
|
||||
} catch (err: any) {
|
||||
this.ptNotifier.error(err.toString(), await this.ptTranslate(LOC_ERROR))
|
||||
this.ptNotifier.error((err as Error).toString(), await this.ptTranslate(LOC_ERROR))
|
||||
} finally {
|
||||
this.actionDisabled = false
|
||||
}
|
||||
@ -250,12 +250,27 @@ export class ChannelEmojisElement extends LivechatElement {
|
||||
a.remove()
|
||||
} catch (err: any) {
|
||||
this.logger.error(err)
|
||||
this.ptNotifier.error(err.toString())
|
||||
this.ptNotifier.error((err as Error).toString())
|
||||
} finally {
|
||||
this.actionDisabled = false
|
||||
}
|
||||
}
|
||||
|
||||
public async enableEmojisOnlyModeOnAllRooms (ev: Event): Promise<void> {
|
||||
ev.preventDefault()
|
||||
if (!this._channelDetailsService || !this.channelId) {
|
||||
this.ptNotifier.error(await this.ptTranslate(LOC_ERROR))
|
||||
return
|
||||
}
|
||||
try {
|
||||
await this._channelDetailsService.enableEmojisOnlyModeOnAllRooms(this.channelId)
|
||||
this.ptNotifier.info(await this.ptTranslate(LOC_SUCCESSFULLY_SAVED))
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
this.ptNotifier.error(await this.ptTranslate(LOC_ERROR))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an url (or dataUrl), download the image, and converts to dataUrl.
|
||||
* @param url the url
|
||||
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import { html } from 'lit'
|
||||
import { customElement, state } from 'lit/decorators.js'
|
||||
import { ptTr } from '../../lib/directives/translation'
|
||||
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import { LivechatElement } from '../../lib/elements/livechat'
|
||||
import { ptTr } from '../../lib/directives/translation'
|
||||
import { html, TemplateResult } from 'lit'
|
||||
|
@ -2,14 +2,18 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import type { ChannelConfigurationElement } from '../channel-configuration'
|
||||
import type { DynamicFormHeader, DynamicFormSchema } from '../../../lib/elements/dynamic-table-form'
|
||||
import { ptTr } from '../../../lib/directives/translation'
|
||||
import { html, TemplateResult } from 'lit'
|
||||
import { classMap } from 'lit/directives/class-map.js'
|
||||
import { noDuplicateMaxDelay, forbidSpecialCharsMaxTolerance } from 'shared/lib/constants'
|
||||
|
||||
export function tplChannelConfiguration (el: ChannelConfigurationElement): TemplateResult {
|
||||
const tableHeaderList: {[key: string]: DynamicFormHeader} = {
|
||||
const tableHeaderList: Record<string, DynamicFormHeader> = {
|
||||
forbiddenWords: {
|
||||
entries: {
|
||||
colName: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_LABEL),
|
||||
@ -20,16 +24,16 @@ export function tplChannelConfiguration (el: ChannelConfigurationElement): Templ
|
||||
description: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_REGEXP_DESC)
|
||||
},
|
||||
applyToModerators: {
|
||||
colName: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_APPLYTOMODERATORS_LABEL),
|
||||
description: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_APPLYTOMODERATORS_DESC)
|
||||
colName: ptTr(LOC_LIVECHAT_CONFIGURATION_APPLYTOMODERATORS_LABEL),
|
||||
description: ptTr(LOC_LIVECHAT_CONFIGURATION_APPLYTOMODERATORS_DESC)
|
||||
},
|
||||
label: {
|
||||
colName: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_LABEL_LABEL),
|
||||
description: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_LABEL_DESC)
|
||||
},
|
||||
reason: {
|
||||
colName: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_REASON_LABEL),
|
||||
description: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_REASON_DESC)
|
||||
colName: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_RETRACTATION_REASON_LABEL),
|
||||
description: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_RETRACTATION_REASON_DESC)
|
||||
},
|
||||
comments: {
|
||||
colName: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_COMMENTS_LABEL),
|
||||
@ -57,7 +61,7 @@ export function tplChannelConfiguration (el: ChannelConfigurationElement): Templ
|
||||
}
|
||||
}
|
||||
}
|
||||
const tableSchema: {[key: string]: DynamicFormSchema} = {
|
||||
const tableSchema: Record<string, DynamicFormSchema> = {
|
||||
forbiddenWords: {
|
||||
entries: {
|
||||
inputType: 'tags',
|
||||
@ -337,6 +341,246 @@ export function tplChannelConfiguration (el: ChannelConfigurationElement): Templ
|
||||
${el.renderFeedback('peertube-livechat-bot-nickname-feedback', 'bot.nickname')}
|
||||
</div>
|
||||
|
||||
<livechat-configuration-section-header
|
||||
.label=${ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_SPECIAL_CHARS_LABEL)}
|
||||
.description=${ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_SPECIAL_CHARS_DESC)}
|
||||
.helpPage=${'documentation/user/streamers/bot/special_chars'}>
|
||||
</livechat-configuration-section-header>
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="forbid_special_chars"
|
||||
id="peertube-livechat-forbid-special-chars"
|
||||
@input=${(event: InputEvent) => {
|
||||
if (event?.target && el.channelConfiguration) {
|
||||
el.channelConfiguration.configuration.bot.forbidSpecialChars.enabled =
|
||||
(event.target as HTMLInputElement).checked
|
||||
}
|
||||
el.requestUpdate('channelConfiguration')
|
||||
}
|
||||
}
|
||||
value="1"
|
||||
?checked=${el.channelConfiguration?.configuration.bot.forbidSpecialChars.enabled}
|
||||
/>
|
||||
${ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_SPECIAL_CHARS_LABEL)}
|
||||
</label>
|
||||
</div>
|
||||
${!el.channelConfiguration?.configuration.bot.forbidSpecialChars.enabled
|
||||
? ''
|
||||
: html`
|
||||
<div class="form-group">
|
||||
<label>
|
||||
${ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_SPECIAL_CHARS_TOLERANCE_LABEL)}
|
||||
<input
|
||||
type="number"
|
||||
name="special_chars_tolerance"
|
||||
class=${classMap(
|
||||
Object.assign(
|
||||
{ 'form-control': true },
|
||||
el.getInputValidationClass('bot.forbidSpecialChars.tolerance')
|
||||
)
|
||||
)}
|
||||
min="0"
|
||||
max="${forbidSpecialCharsMaxTolerance}"
|
||||
id="peertube-livechat-forbid-special-chars-tolerance"
|
||||
aria-describedby="peertube-livechat-forbid-special-chars-tolerance-feedback"
|
||||
@input=${(event: InputEvent) => {
|
||||
if (event?.target && el.channelConfiguration) {
|
||||
el.channelConfiguration.configuration.bot.forbidSpecialChars.tolerance =
|
||||
Number((event.target as HTMLInputElement).value)
|
||||
}
|
||||
el.requestUpdate('channelConfiguration')
|
||||
}
|
||||
}
|
||||
value="${el.channelConfiguration?.configuration.bot.forbidSpecialChars.tolerance ?? '0'}"
|
||||
/>
|
||||
</label>
|
||||
<small class="form-text text-muted">
|
||||
${ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_SPECIAL_CHARS_TOLERANCE_DESC)}
|
||||
</small>
|
||||
${el.renderFeedback('peertube-livechat-forbid-special-chars-tolerance-feedback',
|
||||
'bot.forbidSpecialChars.tolerance')
|
||||
}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>
|
||||
${ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_RETRACTATION_REASON_LABEL)}
|
||||
<input
|
||||
type="text"
|
||||
name="special_chars_reason"
|
||||
class=${classMap(
|
||||
Object.assign(
|
||||
{ 'form-control': true },
|
||||
el.getInputValidationClass('bot.forbidSpecialChars.reason')
|
||||
)
|
||||
)}
|
||||
id="peertube-livechat-forbid-special-chars-reason"
|
||||
aria-describedby="peertube-livechat-forbid-special-chars-reason-feedback"
|
||||
@input=${(event: InputEvent) => {
|
||||
if (event?.target && el.channelConfiguration) {
|
||||
el.channelConfiguration.configuration.bot.forbidSpecialChars.reason =
|
||||
(event.target as HTMLInputElement).value
|
||||
}
|
||||
el.requestUpdate('channelConfiguration')
|
||||
}
|
||||
}
|
||||
value="${el.channelConfiguration?.configuration.bot.forbidSpecialChars.reason ?? ''}"
|
||||
/>
|
||||
</label>
|
||||
<small class="form-text text-muted">
|
||||
${ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_RETRACTATION_REASON_DESC)}
|
||||
</small>
|
||||
${el.renderFeedback('peertube-livechat-forbid-special-chars-reason-feedback',
|
||||
'bot.forbidSpecialChars.reason')
|
||||
}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="forbid_special_chars_applyToModerators"
|
||||
id="peertube-livechat-forbid-special-chars-applyToModerators"
|
||||
@input=${(event: InputEvent) => {
|
||||
if (event?.target && el.channelConfiguration) {
|
||||
el.channelConfiguration.configuration.bot.forbidSpecialChars.applyToModerators =
|
||||
(event.target as HTMLInputElement).checked
|
||||
}
|
||||
el.requestUpdate('channelConfiguration')
|
||||
}
|
||||
}
|
||||
value="1"
|
||||
?checked=${el.channelConfiguration?.configuration.bot.forbidSpecialChars.applyToModerators}
|
||||
/>
|
||||
${ptTr(LOC_LIVECHAT_CONFIGURATION_APPLYTOMODERATORS_LABEL)}
|
||||
</label>
|
||||
<small class="form-text text-muted">
|
||||
${ptTr(LOC_LIVECHAT_CONFIGURATION_APPLYTOMODERATORS_DESC)}
|
||||
</small>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
<livechat-configuration-section-header
|
||||
.label=${ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_NO_DUPLICATE_LABEL)}
|
||||
.description=${ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_NO_DUPLICATE_DESC)}
|
||||
.helpPage=${'documentation/user/streamers/bot/no_duplicate'}>
|
||||
</livechat-configuration-section-header>
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="no_duplicate"
|
||||
id="peertube-livechat-no-duplicate"
|
||||
@input=${(event: InputEvent) => {
|
||||
if (event?.target && el.channelConfiguration) {
|
||||
el.channelConfiguration.configuration.bot.noDuplicate.enabled =
|
||||
(event.target as HTMLInputElement).checked
|
||||
}
|
||||
el.requestUpdate('channelConfiguration')
|
||||
}
|
||||
}
|
||||
value="1"
|
||||
?checked=${el.channelConfiguration?.configuration.bot.noDuplicate.enabled}
|
||||
/>
|
||||
${ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_NO_DUPLICATE_LABEL)}
|
||||
</label>
|
||||
</div>
|
||||
${!el.channelConfiguration?.configuration.bot.noDuplicate.enabled
|
||||
? ''
|
||||
: html`
|
||||
<div class="form-group">
|
||||
<label>
|
||||
${ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_NO_DUPLICATE_DELAY_LABEL)}
|
||||
<input
|
||||
type="number"
|
||||
name="no_duplicate_delay"
|
||||
class=${classMap(
|
||||
Object.assign(
|
||||
{ 'form-control': true },
|
||||
el.getInputValidationClass('bot.noDuplicate.delay')
|
||||
)
|
||||
)}
|
||||
min="0"
|
||||
max="${noDuplicateMaxDelay.toString()}"
|
||||
id="peertube-livechat-no-duplicate-delay"
|
||||
aria-describedby="peertube-livechat-no-duplicate-delay-feedback"
|
||||
@input=${(event: InputEvent) => {
|
||||
if (event?.target && el.channelConfiguration) {
|
||||
el.channelConfiguration.configuration.bot.noDuplicate.delay =
|
||||
Number((event.target as HTMLInputElement).value)
|
||||
}
|
||||
el.requestUpdate('channelConfiguration')
|
||||
}
|
||||
}
|
||||
value="${el.channelConfiguration?.configuration.bot.noDuplicate.delay ?? '0'}"
|
||||
/>
|
||||
</label>
|
||||
<small class="form-text text-muted">
|
||||
${ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_NO_DUPLICATE_DELAY_DESC)}
|
||||
</small>
|
||||
${el.renderFeedback('peertube-livechat-no-duplicate-delay-feedback',
|
||||
'bot.noDuplicate.delay')
|
||||
}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>
|
||||
${ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_RETRACTATION_REASON_LABEL)}
|
||||
<input
|
||||
type="text"
|
||||
name="no_duplicate_reason"
|
||||
class=${classMap(
|
||||
Object.assign(
|
||||
{ 'form-control': true },
|
||||
el.getInputValidationClass('bot.noDuplicate.reason')
|
||||
)
|
||||
)}
|
||||
id="peertube-livechat-no-duplicate-reason"
|
||||
aria-describedby="peertube-livechat-no-duplicate-reason-feedback"
|
||||
@input=${(event: InputEvent) => {
|
||||
if (event?.target && el.channelConfiguration) {
|
||||
el.channelConfiguration.configuration.bot.noDuplicate.reason =
|
||||
(event.target as HTMLInputElement).value
|
||||
}
|
||||
el.requestUpdate('channelConfiguration')
|
||||
}
|
||||
}
|
||||
value="${el.channelConfiguration?.configuration.bot.noDuplicate.reason ?? ''}"
|
||||
/>
|
||||
</label>
|
||||
<small class="form-text text-muted">
|
||||
${ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_RETRACTATION_REASON_DESC)}
|
||||
</small>
|
||||
${el.renderFeedback('peertube-livechat-no-duplicate-reason-feedback',
|
||||
'bot.noDuplicate.reason')
|
||||
}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="no_duplicate_applyToModerators"
|
||||
id="peertube-livechat-no-duplicate-applyToModerators"
|
||||
@input=${(event: InputEvent) => {
|
||||
if (event?.target && el.channelConfiguration) {
|
||||
el.channelConfiguration.configuration.bot.noDuplicate.applyToModerators =
|
||||
(event.target as HTMLInputElement).checked
|
||||
}
|
||||
el.requestUpdate('channelConfiguration')
|
||||
}
|
||||
}
|
||||
value="1"
|
||||
?checked=${el.channelConfiguration?.configuration.bot.noDuplicate.applyToModerators}
|
||||
/>
|
||||
${ptTr(LOC_LIVECHAT_CONFIGURATION_APPLYTOMODERATORS_LABEL)}
|
||||
</label>
|
||||
<small class="form-text text-muted">
|
||||
${ptTr(LOC_LIVECHAT_CONFIGURATION_APPLYTOMODERATORS_DESC)}
|
||||
</small>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
<livechat-configuration-section-header
|
||||
.label=${ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_LABEL)}
|
||||
.description=${ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_DESC)}
|
||||
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import type { ChannelEmojisElement } from '../channel-emojis'
|
||||
import type { DynamicFormHeader, DynamicFormSchema } from '../../../lib/elements/dynamic-table-form'
|
||||
import { maxEmojisPerChannel } from 'shared/lib/emojis'
|
||||
@ -45,13 +48,14 @@ export function tplChannelEmojis (el: ChannelEmojisElement): TemplateResult {
|
||||
|
||||
<livechat-channel-tabs .active=${'emojis'} .channelId=${el.channelId}></livechat-channel-tabs>
|
||||
|
||||
<h2>${ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_EMOJIS_TITLE)}</h2>
|
||||
|
||||
<p>
|
||||
${ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_EMOJIS_DESC)}
|
||||
<livechat-help-button .page=${'documentation/user/streamers/emojis'}>
|
||||
</livechat-help-button>
|
||||
</p>
|
||||
|
||||
|
||||
<form role="form" @submit=${el.saveEmojis} @change=${el.resetValidation}>
|
||||
<div class="peertube-plugin-livechat-configuration-actions">
|
||||
${
|
||||
@ -86,7 +90,7 @@ export function tplChannelEmojis (el: ChannelEmojisElement): TemplateResult {
|
||||
.maxLines=${maxEmojisPerChannel}
|
||||
.validation=${el.validationError?.properties}
|
||||
.validationPrefix=${'emojis'}
|
||||
.rows=${el.channelEmojisConfiguration?.emojis.customEmojis}
|
||||
.rows=${el.channelEmojisConfiguration?.emojis.customEmojis ?? []}
|
||||
@update=${(e: CustomEvent) => {
|
||||
el.resetValidation(e)
|
||||
if (el.channelEmojisConfiguration) {
|
||||
@ -106,5 +110,23 @@ export function tplChannelEmojis (el: ChannelEmojisElement): TemplateResult {
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<h2>${ptTr(LOC_EMOJI_ONLY_MODE_TITLE)}</h2>
|
||||
|
||||
<p>
|
||||
${ptTr(LOC_EMOJI_ONLY_MODE_DESC_1, true)}
|
||||
</p>
|
||||
<p>
|
||||
${ptTr(LOC_EMOJI_ONLY_MODE_DESC_2, true)}
|
||||
</p>
|
||||
<p>
|
||||
${ptTr(LOC_EMOJI_ONLY_MODE_DESC_3, true)}
|
||||
</p>
|
||||
|
||||
<div class="peertube-plugin-livechat-configuration-actions">
|
||||
<button type="button" @click=${el.enableEmojisOnlyModeOnAllRooms}>
|
||||
${ptTr(LOC_EMOJI_ONLY_ENABLE_ALL_ROOMS)}
|
||||
</button>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
|
@ -66,15 +66,16 @@ async function registerConfiguration (clientOptions: RegisterClientOptions): Pro
|
||||
for (const link of links) {
|
||||
if (typeof link !== 'object') { continue }
|
||||
if (!('key' in link)) { continue }
|
||||
if (link.key !== 'in-my-library') { continue }
|
||||
myLibraryLinks = link
|
||||
break
|
||||
if (link.key === 'in-my-library' || link.key === 'my-video-space') {
|
||||
myLibraryLinks = link
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!myLibraryLinks) { return links }
|
||||
if (!Array.isArray(myLibraryLinks.links)) { return links }
|
||||
|
||||
const label = await peertubeHelpers.translate(LOC_MENU_CONFIGURATION_LABEL)
|
||||
myLibraryLinks.links.push({
|
||||
myLibraryLinks.links.unshift({
|
||||
label,
|
||||
shortLabel: label,
|
||||
path: '/p/livechat/configuration',
|
||||
|
@ -11,7 +11,7 @@ import type {
|
||||
import { ValidationError, ValidationErrorType } from '../../lib/models/validation'
|
||||
import { getBaseRoute } from '../../../utils/uri'
|
||||
import { maxEmojisPerChannel } from 'shared/lib/emojis'
|
||||
import { channelTermsMaxLength } from 'shared/lib/constants'
|
||||
import { channelTermsMaxLength, noDuplicateMaxDelay, forbidSpecialCharsMaxTolerance } from 'shared/lib/constants'
|
||||
|
||||
export class ChannelDetailsService {
|
||||
public _registerClientOptions: RegisterClientOptions
|
||||
@ -67,11 +67,43 @@ export class ChannelDetailsService {
|
||||
// The backend will ignore those values.
|
||||
if (botConf.enabled) {
|
||||
propertiesError['bot.nickname'] = []
|
||||
propertiesError['bot.forbidSpecialChars.tolerance'] = []
|
||||
propertiesError['bot.noDuplicate.delay'] = []
|
||||
|
||||
if (/[^\p{L}\p{N}\p{Z}_-]/u.test(botConf.nickname ?? '')) {
|
||||
propertiesError['bot.nickname'].push(ValidationErrorType.WrongFormat)
|
||||
}
|
||||
|
||||
if (botConf.forbidSpecialChars.enabled) {
|
||||
const forbidSpecialCharsTolerance = channelConfigurationOptions.bot.forbidSpecialChars.tolerance
|
||||
if (
|
||||
(typeof forbidSpecialCharsTolerance !== 'number') ||
|
||||
isNaN(forbidSpecialCharsTolerance)
|
||||
) {
|
||||
propertiesError['bot.forbidSpecialChars.tolerance'].push(ValidationErrorType.WrongType)
|
||||
} else if (
|
||||
forbidSpecialCharsTolerance < 0 ||
|
||||
forbidSpecialCharsTolerance > forbidSpecialCharsMaxTolerance
|
||||
) {
|
||||
propertiesError['bot.forbidSpecialChars.tolerance'].push(ValidationErrorType.NotInRange)
|
||||
}
|
||||
}
|
||||
|
||||
if (botConf.noDuplicate.enabled) {
|
||||
const noDuplicateDelay = channelConfigurationOptions.bot.noDuplicate.delay
|
||||
if (
|
||||
(typeof noDuplicateDelay !== 'number') ||
|
||||
isNaN(noDuplicateDelay)
|
||||
) {
|
||||
propertiesError['bot.noDuplicate.delay'].push(ValidationErrorType.WrongType)
|
||||
} else if (
|
||||
noDuplicateDelay < 0 ||
|
||||
noDuplicateDelay > noDuplicateMaxDelay
|
||||
) {
|
||||
propertiesError['bot.noDuplicate.delay'].push(ValidationErrorType.NotInRange)
|
||||
}
|
||||
}
|
||||
|
||||
for (const [i, fw] of botConf.forbiddenWords.entries()) {
|
||||
for (const v of fw.entries) {
|
||||
propertiesError[`bot.forbiddenWords.${i}.entries`] = []
|
||||
@ -146,7 +178,8 @@ export class ChannelDetailsService {
|
||||
}
|
||||
|
||||
for (const channel of channels.data) {
|
||||
channel.livechatConfigurationUri = '/p/livechat/configuration/channel?channelId=' + encodeURIComponent(channel.id)
|
||||
channel.livechatConfigurationUri =
|
||||
'/p/livechat/configuration/channel?channelId=' + encodeURIComponent(channel.id as string | number)
|
||||
|
||||
// Note: since Peertube v6.0.0, channel.avatar is dropped, and we have to use channel.avatars.
|
||||
// So, if !channel.avatar, we will search a suitable one in channel.avatars, and fill channel.avatar.
|
||||
@ -180,10 +213,11 @@ export class ChannelDetailsService {
|
||||
}
|
||||
|
||||
public async fetchEmojisConfiguration (channelId: number): Promise<ChannelEmojisConfiguration> {
|
||||
const url = getBaseRoute(this._registerClientOptions) +
|
||||
'/api/configuration/channel/emojis/' +
|
||||
encodeURIComponent(channelId)
|
||||
const response = await fetch(
|
||||
getBaseRoute(this._registerClientOptions) +
|
||||
'/api/configuration/channel/emojis/' +
|
||||
encodeURIComponent(channelId),
|
||||
url,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: this._headers
|
||||
@ -295,10 +329,11 @@ export class ChannelDetailsService {
|
||||
channelId: number,
|
||||
channelEmojis: ChannelEmojis
|
||||
): Promise<ChannelEmojisConfiguration> {
|
||||
const url = getBaseRoute(this._registerClientOptions) +
|
||||
'/api/configuration/channel/emojis/' +
|
||||
encodeURIComponent(channelId)
|
||||
const response = await fetch(
|
||||
getBaseRoute(this._registerClientOptions) +
|
||||
'/api/configuration/channel/emojis/' +
|
||||
encodeURIComponent(channelId),
|
||||
url,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: this._headers,
|
||||
@ -312,4 +347,24 @@ export class ChannelDetailsService {
|
||||
|
||||
return response.json()
|
||||
}
|
||||
|
||||
public async enableEmojisOnlyModeOnAllRooms (channelId: number): Promise<void> {
|
||||
const url = getBaseRoute(this._registerClientOptions) +
|
||||
'/api/configuration/channel/emojis/' +
|
||||
encodeURIComponent(channelId) +
|
||||
'/enable_emoji_only'
|
||||
const response = await fetch(
|
||||
url,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: this._headers
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Can\'t enable Emojis Only Mode on all rooms.')
|
||||
}
|
||||
|
||||
return response.json()
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// This content comes from the file assets/images/plus-square.svg, from the Feather icons set https://feathericons.com/
|
||||
export const AddSVG: string =
|
||||
export const AddSVG =
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
@ -14,7 +14,7 @@ export const AddSVG: string =
|
||||
</svg>`
|
||||
|
||||
// This content comes from the file assets/images/x-square.svg, from the Feather icons set https://feathericons.com/
|
||||
export const RemoveSVG: string =
|
||||
export const RemoveSVG =
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
|
@ -13,8 +13,8 @@ import { getPtContext } from '../contexts/peertube'
|
||||
export class TranslationDirective extends AsyncDirective {
|
||||
private readonly _peertubeHelpers: RegisterClientHelpers
|
||||
|
||||
private _translatedValue: string = ''
|
||||
private _localizationId: string = ''
|
||||
private _translatedValue = ''
|
||||
private _localizationId = ''
|
||||
|
||||
private _allowUnsafeHTML = false
|
||||
|
||||
@ -25,7 +25,7 @@ export class TranslationDirective extends AsyncDirective {
|
||||
this._asyncUpdateTranslation().then(() => {}, () => {})
|
||||
}
|
||||
|
||||
public override render = (locId: string, allowHTML: boolean = false): TemplateResult | string => {
|
||||
public override render = (locId: string, allowHTML = false): TemplateResult | string => {
|
||||
this._localizationId = locId // TODO Check current component for context (to infer the prefix)
|
||||
|
||||
this._allowUnsafeHTML = allowHTML
|
||||
|
@ -3,6 +3,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import { ptTr } from '../directives/translation'
|
||||
import { html } from 'lit'
|
||||
import { customElement, property } from 'lit/decorators.js'
|
||||
|
@ -3,6 +3,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import type { TagsInputElement } from './tags-input'
|
||||
import type { DirectiveResult } from 'lit/directive'
|
||||
import { ValidationErrorType } from '../models/validation'
|
||||
@ -20,26 +23,26 @@ import { AddSVG, RemoveSVG } from '../buttons'
|
||||
type DynamicTableAcceptedTypes = number | string | boolean | Date | Array<number | string>
|
||||
|
||||
type DynamicTableAcceptedInputTypes = 'textarea'
|
||||
| 'select'
|
||||
| 'checkbox'
|
||||
| 'range'
|
||||
| 'color'
|
||||
| 'date'
|
||||
| 'datetime'
|
||||
| 'datetime-local'
|
||||
| 'email'
|
||||
| 'file'
|
||||
| 'image'
|
||||
| 'month'
|
||||
| 'number'
|
||||
| 'password'
|
||||
| 'tel'
|
||||
| 'text'
|
||||
| 'time'
|
||||
| 'url'
|
||||
| 'week'
|
||||
| 'tags'
|
||||
| 'image-file'
|
||||
| 'select'
|
||||
| 'checkbox'
|
||||
| 'range'
|
||||
| 'color'
|
||||
| 'date'
|
||||
| 'datetime'
|
||||
| 'datetime-local'
|
||||
| 'email'
|
||||
| 'file'
|
||||
| 'image'
|
||||
| 'month'
|
||||
| 'number'
|
||||
| 'password'
|
||||
| 'tel'
|
||||
| 'text'
|
||||
| 'time'
|
||||
| 'url'
|
||||
| 'week'
|
||||
| 'tags'
|
||||
| 'image-file'
|
||||
|
||||
interface CellDataSchema {
|
||||
min?: number
|
||||
@ -47,7 +50,7 @@ interface CellDataSchema {
|
||||
minlength?: number
|
||||
maxlength?: number
|
||||
size?: number
|
||||
options?: { [key: string]: string }
|
||||
options?: Record<string, string>
|
||||
datalist?: DynamicTableAcceptedTypes[]
|
||||
separator?: string
|
||||
inputType?: DynamicTableAcceptedInputTypes
|
||||
@ -59,7 +62,7 @@ interface CellDataSchema {
|
||||
interface DynamicTableRowData {
|
||||
_id: number
|
||||
_originalIndex: number
|
||||
row: { [key: string]: DynamicTableAcceptedTypes }
|
||||
row: Record<string, DynamicTableAcceptedTypes>
|
||||
}
|
||||
|
||||
interface DynamicFormHeaderCellData {
|
||||
@ -68,10 +71,8 @@ interface DynamicFormHeaderCellData {
|
||||
headerClassList?: string[]
|
||||
}
|
||||
|
||||
export interface DynamicFormHeader {
|
||||
[key: string]: DynamicFormHeaderCellData
|
||||
}
|
||||
export interface DynamicFormSchema { [key: string]: CellDataSchema }
|
||||
export type DynamicFormHeader = Record<string, DynamicFormHeaderCellData>
|
||||
export type DynamicFormSchema = Record<string, CellDataSchema>
|
||||
|
||||
@customElement('livechat-dynamic-table-form')
|
||||
export class DynamicTableFormElement extends LivechatElement {
|
||||
@ -85,19 +86,19 @@ export class DynamicTableFormElement extends LivechatElement {
|
||||
public maxLines?: number = undefined
|
||||
|
||||
@property()
|
||||
public validation?: {[key: string]: ValidationErrorType[] }
|
||||
public validation?: Record<string, ValidationErrorType[]>
|
||||
|
||||
@property({ attribute: false })
|
||||
public validationPrefix: string = ''
|
||||
public validationPrefix = ''
|
||||
|
||||
@property({ attribute: false })
|
||||
public rows: Array<{ [key: string]: DynamicTableAcceptedTypes }> = []
|
||||
public rows: Array<Record<string, DynamicTableAcceptedTypes>> = []
|
||||
|
||||
@state()
|
||||
public _rowsById: DynamicTableRowData[] = []
|
||||
|
||||
@property({ attribute: false })
|
||||
public formName: string = ''
|
||||
public formName = ''
|
||||
|
||||
@state()
|
||||
private _lastRowId = 1
|
||||
@ -112,7 +113,7 @@ export class DynamicTableFormElement extends LivechatElement {
|
||||
}
|
||||
}
|
||||
|
||||
private readonly _getDefaultRow = (): { [key: string]: DynamicTableAcceptedTypes } => {
|
||||
private readonly _getDefaultRow = (): Record<string, DynamicTableAcceptedTypes> => {
|
||||
this._updateLastRowId()
|
||||
return Object.fromEntries([...Object.entries(this.schema).map((entry) => [entry[0], entry[1].default ?? ''])])
|
||||
}
|
||||
@ -245,11 +246,11 @@ export class DynamicTableFormElement extends LivechatElement {
|
||||
|
||||
return html`<tr id=${inputId}>
|
||||
${Object.keys(this.header)
|
||||
.sort((k1, k2) => this.columnOrder.indexOf(k1) - this.columnOrder.indexOf(k2))
|
||||
.map(key => this.renderDataCell(key,
|
||||
rowData.row[key] ?? this.schema[key].default,
|
||||
rowData._id,
|
||||
rowData._originalIndex))}
|
||||
.sort((k1, k2) => this.columnOrder.indexOf(k1) - this.columnOrder.indexOf(k2))
|
||||
.map(key => this.renderDataCell(key,
|
||||
rowData.row[key] ?? this.schema[key].default,
|
||||
rowData._id,
|
||||
rowData._originalIndex))}
|
||||
<td class="form-group">
|
||||
<button type="button"
|
||||
class="dynamic-table-remove-row"
|
||||
@ -457,8 +458,7 @@ export class DynamicTableFormElement extends LivechatElement {
|
||||
inputTitle,
|
||||
propertyName,
|
||||
propertySchema,
|
||||
(propertyValue)?.join(propertySchema.separator ?? ',') ??
|
||||
propertyValue ?? propertySchema.default ?? '',
|
||||
(propertyValue)?.join(propertySchema.separator ?? ',') ?? propertyValue ?? propertySchema.default ?? '',
|
||||
originalIndex)}
|
||||
${feedback}
|
||||
`
|
||||
@ -473,8 +473,7 @@ export class DynamicTableFormElement extends LivechatElement {
|
||||
inputTitle,
|
||||
propertyName,
|
||||
propertySchema,
|
||||
(propertyValue)?.join(propertySchema.separator ?? ',') ??
|
||||
propertyValue ?? propertySchema.default ?? '',
|
||||
(propertyValue)?.join(propertySchema.separator ?? ',') ?? propertyValue ?? propertySchema.default ?? '',
|
||||
originalIndex)}
|
||||
${feedback}
|
||||
`
|
||||
@ -498,8 +497,10 @@ export class DynamicTableFormElement extends LivechatElement {
|
||||
}
|
||||
|
||||
if (!formElement) {
|
||||
this.logger.warn(`value type '${(propertyValue.constructor.toString())}' is incompatible` +
|
||||
`with field type '${propertySchema.inputType as string}' for form entry '${propertyName.toString()}'.`)
|
||||
this.logger.warn(
|
||||
`value type '${(propertyValue.constructor.toString())}' is incompatible` +
|
||||
`with field type '${propertySchema.inputType as string}' for form entry '${propertyName.toString()}'.`
|
||||
)
|
||||
}
|
||||
|
||||
const classList = ['form-group']
|
||||
@ -678,7 +679,7 @@ export class DynamicTableFormElement extends LivechatElement {
|
||||
}
|
||||
|
||||
_getInputValidationClass = (propertyName: string,
|
||||
originalIndex: number): { [key: string]: boolean } => {
|
||||
originalIndex: number): Record<string, boolean> => {
|
||||
const validationErrorTypes: ValidationErrorType[] | undefined =
|
||||
this.validation?.[`${this.validationPrefix}.${originalIndex}.${propertyName}`]
|
||||
|
||||
|
@ -18,7 +18,7 @@ export class HelpButtonElement extends LivechatElement {
|
||||
public buttonTitle: string | DirectiveResult = ptTr(LOC_ONLINE_HELP)
|
||||
|
||||
@property({ attribute: false })
|
||||
public page: string = ''
|
||||
public page = ''
|
||||
|
||||
@state()
|
||||
public url: URL = new URL('https://lmddgtfy.net/')
|
||||
@ -38,7 +38,7 @@ export class HelpButtonElement extends LivechatElement {
|
||||
href="${this.url.href}"
|
||||
target=_blank
|
||||
title="${this.buttonTitle}"
|
||||
class="orange-button peertube-button-link"
|
||||
class="primary-button orange-button peertube-button-link"
|
||||
>${unsafeHTML(helpButtonSVG())}</a>`
|
||||
})
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import { LivechatElement } from './livechat'
|
||||
import { html } from 'lit'
|
||||
import type { DirectiveResult } from 'lit/directive'
|
||||
|
@ -26,7 +26,7 @@ export class LivechatElement extends LitElement {
|
||||
this.logger = this.ptContext.logger.createLogger(this.tagName.toLowerCase())
|
||||
}
|
||||
|
||||
protected override createRenderRoot = (): Element | ShadowRoot => {
|
||||
protected override createRenderRoot = (): HTMLElement | DocumentFragment => {
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import { LivechatElement } from './livechat'
|
||||
import { ptTr } from '../directives/translation'
|
||||
import { html } from 'lit'
|
||||
@ -21,10 +24,11 @@ import type { DirectiveResult } from 'lit/directive'
|
||||
// Then replace the main color by «currentColor»
|
||||
const copySVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 4.233 4.233">
|
||||
<g style="stroke-width:1.00021;stroke-miterlimit:4;stroke-dasharray:none">` +
|
||||
// eslint-disable-next-line max-len
|
||||
// eslint-disable-next-line max-len, @stylistic/indent-binary-ops
|
||||
'<path style="opacity:.998;fill:none;fill-opacity:1;stroke:currentColor;stroke-width:1.17052;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m4.084 4.046-.616.015-.645-.004a.942.942 0 0 1-.942-.942v-4.398a.94.94 0 0 1 .942-.943H7.22a.94.94 0 0 1 .942.943l-.006.334-.08.962" transform="matrix(.45208 0 0 .45208 -.528 1.295)"/>' +
|
||||
// eslint-disable-next-line max-len
|
||||
'<path style="opacity:.998;fill:none;fill-opacity:1;stroke:currentColor;stroke-width:1.17052;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M8.434 5.85c-.422.009-1.338.009-1.76.01-.733.004-2.199 0-2.199 0a.94.94 0 0 1-.942-.941V.52a.94.94 0 0 1 .942-.942h4.398a.94.94 0 0 1 .943.942s.004 1.466 0 2.2c-.003.418-.019 1.251-.006 1.67.024.812-.382 1.439-1.376 1.46z" transform="matrix(.45208 0 0 .45208 -.528 1.295)"/>' +
|
||||
// eslint-disable-next-line @stylistic/indent-binary-ops
|
||||
`</g>
|
||||
</svg>`
|
||||
|
||||
@ -64,10 +68,10 @@ export class TagsInputElement extends LivechatElement {
|
||||
private readonly _isPressingKey: string[] = []
|
||||
|
||||
@property({ attribute: false })
|
||||
public separator: string = '\n'
|
||||
public separator = '\n'
|
||||
|
||||
@property({ attribute: false })
|
||||
public animDuration: number = 200
|
||||
public animDuration = 200
|
||||
|
||||
/**
|
||||
* Overloading the standard focus method.
|
||||
@ -245,8 +249,9 @@ export class TagsInputElement extends LivechatElement {
|
||||
if (!this._isPressingKey.includes(e.key)) {
|
||||
this._isPressingKey.push(e.key)
|
||||
|
||||
if ((target.selectionStart === target.selectionEnd) &&
|
||||
target.selectionStart === 0) {
|
||||
if (
|
||||
(target.selectionStart === target.selectionEnd) && target.selectionStart === 0
|
||||
) {
|
||||
this._handleDeleteTag((this._searchedTagsIndex.length)
|
||||
? this._searchedTagsIndex.slice(-1)[0]
|
||||
: (this.value.length - 1))
|
||||
@ -259,8 +264,9 @@ export class TagsInputElement extends LivechatElement {
|
||||
if (!this._isPressingKey.includes(e.key)) {
|
||||
this._isPressingKey.push(e.key)
|
||||
|
||||
if ((target.selectionStart === target.selectionEnd) &&
|
||||
target.selectionStart === target.value.length) {
|
||||
if (
|
||||
(target.selectionStart === target.selectionEnd) && target.selectionStart === target.value.length
|
||||
) {
|
||||
this._handleDeleteTag((this._searchedTagsIndex.length)
|
||||
? this._searchedTagsIndex[0]
|
||||
: 0)
|
||||
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import type { LivechatTokenListElement } from '../token-list'
|
||||
import { html, TemplateResult } from 'lit'
|
||||
import { unsafeHTML } from 'lit/directives/unsafe-html.js'
|
||||
@ -23,11 +26,11 @@ export function tplTokenList (el: LivechatTokenListElement): TemplateResult {
|
||||
<tbody>
|
||||
${
|
||||
repeat(el.tokenList ?? [], (token) => token.id, (token) => {
|
||||
let dateStr: string = ''
|
||||
let dateStr = ''
|
||||
try {
|
||||
const date = new Date(token.date)
|
||||
dateStr = date.toLocaleDateString() + ' ' + date.toLocaleTimeString()
|
||||
} catch (err) {}
|
||||
} catch (_err) {}
|
||||
return html`<tr>
|
||||
<td>${
|
||||
el.mode === 'select'
|
||||
|
@ -27,7 +27,7 @@ export class LivechatTokenListElement extends LivechatElement {
|
||||
public currentSelectedToken?: LivechatToken
|
||||
|
||||
@property({ attribute: false })
|
||||
public actionDisabled: boolean = false
|
||||
public actionDisabled = false
|
||||
|
||||
private readonly _tokenListService: TokenListService
|
||||
private readonly _asyncTaskRender: Task
|
||||
@ -83,7 +83,7 @@ export class LivechatTokenListElement extends LivechatElement {
|
||||
this.dispatchEvent(new CustomEvent('update', {}))
|
||||
} catch (err: any) {
|
||||
this.logger.error(err)
|
||||
this.ptNotifier.error(err.toString(), await this.ptTranslate(LOC_ERROR))
|
||||
this.ptNotifier.error((err as Error).toString(), await this.ptTranslate(LOC_ERROR))
|
||||
} finally {
|
||||
this.actionDisabled = false
|
||||
}
|
||||
@ -102,7 +102,7 @@ export class LivechatTokenListElement extends LivechatElement {
|
||||
this.dispatchEvent(new CustomEvent('update', {}))
|
||||
} catch (err: any) {
|
||||
this.logger.error(err)
|
||||
this.ptNotifier.error(err.toString(), await this.ptTranslate(LOC_ERROR))
|
||||
this.ptNotifier.error((err as Error).toString(), await this.ptTranslate(LOC_ERROR))
|
||||
} finally {
|
||||
this.actionDisabled = false
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ export enum ValidationErrorType {
|
||||
}
|
||||
|
||||
export class ValidationError extends Error {
|
||||
properties: {[key: string]: ValidationErrorType[] } = {}
|
||||
properties: Record<string, ValidationErrorType[]> = {}
|
||||
|
||||
constructor (name: string, message: string | undefined, properties: ValidationError['properties']) {
|
||||
super(message)
|
||||
|
@ -27,7 +27,7 @@ type displayButtonOptions = displayButtonOptionsCallback | displayButtonOptionsH
|
||||
function displayButton (dbo: displayButtonOptions): void {
|
||||
const button = document.createElement('a')
|
||||
button.classList.add(
|
||||
'orange-button', 'peertube-button-link',
|
||||
'primary-button', 'orange-button', 'peertube-button-link',
|
||||
'peertube-plugin-livechat-button',
|
||||
'peertube-plugin-livechat-button-' + dbo.name
|
||||
)
|
||||
|
@ -60,8 +60,8 @@ async function initChat (video: Video): Promise<void> {
|
||||
return
|
||||
}
|
||||
|
||||
let showShareUrlButton: boolean = false
|
||||
let showPromote: boolean = false
|
||||
let showShareUrlButton = false
|
||||
let showPromote = false
|
||||
if (video.isLocal) { // No need for shareButton on remote chats.
|
||||
const chatShareUrl = settings['chat-share-url'] ?? ''
|
||||
if (chatShareUrl === 'everyone') {
|
||||
@ -187,9 +187,10 @@ async function _insertChatDom (
|
||||
callback: async () => {
|
||||
try {
|
||||
// First we must get the room JID (can be video.uuid@ or channel.id@)
|
||||
const url = getBaseRoute(ptContext.ptOptions) + '/api/configuration/room/' +
|
||||
encodeURIComponent(video.uuid)
|
||||
const response = await fetch(
|
||||
getBaseRoute(ptContext.ptOptions) + '/api/configuration/room/' +
|
||||
encodeURIComponent(video.uuid),
|
||||
url,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: peertubeHelpers.getAuthHeader()
|
||||
@ -303,7 +304,7 @@ async function _openChat (video: Video): Promise<void | false> {
|
||||
|
||||
// Loading converseJS...
|
||||
await displayConverseJS(ptContext.ptOptions, container, roomkey, 'peertube-video', false)
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
// Displaying an error page.
|
||||
if (container) {
|
||||
const message = document.createElement('div')
|
||||
|
@ -14,7 +14,7 @@ import { getIframeUri, getXMPPAddr, UriOptions } from '../uri'
|
||||
import { isAnonymousUser } from '../../../utils/user'
|
||||
|
||||
// First is default tab.
|
||||
const validTabNames = ['embed', 'dock', 'peertube', 'xmpp'] as const
|
||||
const validTabNames: string[] = ['embed', 'dock', 'peertube', 'xmpp'] as const
|
||||
|
||||
type ValidTabNames = typeof validTabNames[number]
|
||||
|
||||
@ -61,49 +61,49 @@ export class ShareChatElement extends LivechatElement {
|
||||
* Should we render the XMPP tab?
|
||||
*/
|
||||
@property({ attribute: false })
|
||||
public xmppUriEnabled: boolean = false
|
||||
public xmppUriEnabled = false
|
||||
|
||||
/**
|
||||
* Should we render the Dock tab?
|
||||
*/
|
||||
@property({ attribute: false })
|
||||
public dockEnabled: boolean = false
|
||||
public dockEnabled = false
|
||||
|
||||
/**
|
||||
* Can we use autocolors?
|
||||
*/
|
||||
@property({ attribute: false })
|
||||
public autocolorsAvailable: boolean = false
|
||||
public autocolorsAvailable = false
|
||||
|
||||
/**
|
||||
* In the Embed tab, should we generated an iframe link.
|
||||
*/
|
||||
@property({ attribute: false })
|
||||
public embedIFrame: boolean = false
|
||||
public embedIFrame = false
|
||||
|
||||
/**
|
||||
* In the Embed tab, should we generated a read-only chat link.
|
||||
*/
|
||||
@property({ attribute: false })
|
||||
public embedReadOnly: boolean = false
|
||||
public embedReadOnly = false
|
||||
|
||||
/**
|
||||
* Read-only, with scrollbar?
|
||||
*/
|
||||
@property({ attribute: false })
|
||||
public embedReadOnlyScrollbar: boolean = false
|
||||
public embedReadOnlyScrollbar = false
|
||||
|
||||
/**
|
||||
* Read-only, transparent background?
|
||||
*/
|
||||
@property({ attribute: false })
|
||||
public embedReadOnlyTransparentBackground: boolean = false
|
||||
public embedReadOnlyTransparentBackground = false
|
||||
|
||||
/**
|
||||
* In the Embed tab, should we use current theme color?
|
||||
*/
|
||||
@property({ attribute: false })
|
||||
public embedAutocolors: boolean = false
|
||||
public embedAutocolors = false
|
||||
|
||||
protected override firstUpdated (changedProperties: PropertyValues): void {
|
||||
super.firstUpdated(changedProperties)
|
||||
@ -156,7 +156,7 @@ export class ShareChatElement extends LivechatElement {
|
||||
return
|
||||
}
|
||||
this.logger.log('Restoring previous state')
|
||||
if (validTabNames.includes(v.currentTab)) {
|
||||
if (validTabNames.includes(v.currentTab as string)) {
|
||||
this.currentTab = v.currentTab
|
||||
}
|
||||
this.embedIFrame = !!v.embedIFrame
|
||||
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import type { ShareChatElement } from '../share-chat'
|
||||
import { html, TemplateResult } from 'lit'
|
||||
import { ptTr } from '../../../lib/directives/translation'
|
||||
|
@ -71,8 +71,7 @@ async function shareChatUrl (
|
||||
addedNodes.forEach(node => {
|
||||
if ((node as HTMLElement).localName === 'ngb-modal-window') {
|
||||
logger.info('Detecting a new modal, checking if this is the good one...')
|
||||
if (!(node as HTMLElement).querySelector) { return }
|
||||
const title = (node as HTMLElement).querySelector('.modal-title')
|
||||
const title = (node as HTMLElement).querySelector?.('.modal-title')
|
||||
if (!(title?.textContent === labelShare)) {
|
||||
return
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
import type { RegisterClientOptions } from '@peertube/peertube-types/client'
|
||||
import type { Video } from '@peertube/peertube-types'
|
||||
import type { LiveChatSettings } from '../lib/contexts/peertube'
|
||||
import { AutoColors, isAutoColorsAvailable } from 'shared/lib/autocolors'
|
||||
import { getBaseRoute } from '../../utils/uri'
|
||||
import { logger } from '../../utils/logger'
|
||||
@ -17,7 +18,7 @@ interface UriOptions {
|
||||
}
|
||||
|
||||
function getIframeUri (
|
||||
registerOptions: RegisterClientOptions, settings: any, video: Video, uriOptions: UriOptions = {}
|
||||
registerOptions: RegisterClientOptions, settings: LiveChatSettings, video: Video, uriOptions: UriOptions = {}
|
||||
): string | null {
|
||||
if (!settings) {
|
||||
logger.error('Settings are not initialized, too soon to compute the iframeUri')
|
||||
|
@ -156,12 +156,12 @@ function launchTests (): void {
|
||||
'content-type': 'application/json;charset=UTF-8'
|
||||
}),
|
||||
body: JSON.stringify({
|
||||
test: test
|
||||
test
|
||||
})
|
||||
})
|
||||
if (!response.ok) {
|
||||
return {
|
||||
test: test,
|
||||
test,
|
||||
messages: [response.statusText ?? 'Unknown error'],
|
||||
ok: false
|
||||
}
|
||||
@ -169,7 +169,7 @@ function launchTests (): void {
|
||||
const data = await response.json()
|
||||
if ((typeof data) !== 'object') {
|
||||
return {
|
||||
test: test,
|
||||
test,
|
||||
messages: ['Incorrect reponse type: ' + (typeof data)],
|
||||
ok: false
|
||||
}
|
||||
@ -190,6 +190,7 @@ function launchTests (): void {
|
||||
waiting.innerHTML = '<i>Testing...</i>'
|
||||
ul.append(waiting)
|
||||
if ((typeof result.next) === 'function') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||
const r: Result = (result.next as Function)()
|
||||
waiting.remove()
|
||||
await machine(r)
|
||||
|
@ -24,18 +24,41 @@ function computeAutoColors (): AutoColors | null {
|
||||
const buttonStyles = window.getComputedStyle(button)
|
||||
|
||||
const autocolors: AutoColors = {
|
||||
mainForeground: styles.getPropertyValue('--mainForegroundColor').trim(),
|
||||
mainBackground: styles.getPropertyValue('--mainBackgroundColor').trim(),
|
||||
greyForeground: styles.getPropertyValue('--greyForegroundColor').trim(),
|
||||
greyBackground: styles.getPropertyValue('--greyBackgroundColor').trim(),
|
||||
menuForeground: styles.getPropertyValue('--menuForegroundColor').trim(),
|
||||
menuBackground: styles.getPropertyValue('--menuBackgroundColor').trim(),
|
||||
inputForeground: styles.getPropertyValue('--inputForegroundColor').trim(),
|
||||
inputBackground: styles.getPropertyValue('--inputBackgroundColor').trim(),
|
||||
buttonForeground: buttonStyles.color.trim(),
|
||||
buttonBackground: styles.getPropertyValue('--mainColor').trim(),
|
||||
link: styles.getPropertyValue('--mainForegroundColor').trim(),
|
||||
linkHover: styles.getPropertyValue('--mainForegroundColor').trim()
|
||||
mainForeground: styles.getPropertyValue('--fg').trim() ||
|
||||
styles.getPropertyValue('--mainForegroundColor').trim(),
|
||||
|
||||
mainBackground: styles.getPropertyValue('--bg').trim() ||
|
||||
styles.getPropertyValue('--mainBackgroundColor').trim(),
|
||||
|
||||
greyForeground: styles.getPropertyValue('--fg-300').trim() ||
|
||||
styles.getPropertyValue('--greyForegroundColor').trim(),
|
||||
|
||||
greyBackground: styles.getPropertyValue('--bg-secondary-300').trim() ||
|
||||
styles.getPropertyValue('--greyBackgroundColor').trim(),
|
||||
|
||||
menuForeground: styles.getPropertyValue('--fg').trim() ||
|
||||
styles.getPropertyValue('--menuForegroundColor').trim(),
|
||||
|
||||
menuBackground: styles.getPropertyValue('--bg-secondary-400').trim() ||
|
||||
styles.getPropertyValue('--menuBackgroundColor').trim(),
|
||||
|
||||
inputForeground: styles.getPropertyValue('--input-fg').trim() ||
|
||||
styles.getPropertyValue('--inputForegroundColor').trim(),
|
||||
|
||||
inputBackground: styles.getPropertyValue('--input-bg').trim() ||
|
||||
styles.getPropertyValue('--inputBackgroundColor').trim(),
|
||||
|
||||
buttonForeground: styles.getPropertyValue('--on-primary').trim() ||
|
||||
buttonStyles.color.trim(),
|
||||
|
||||
buttonBackground: styles.getPropertyValue('--primary').trim() ||
|
||||
styles.getPropertyValue('--mainColor').trim(),
|
||||
|
||||
link: styles.getPropertyValue('--fg').trim() ||
|
||||
styles.getPropertyValue('--mainForegroundColor').trim(),
|
||||
|
||||
linkHover: styles.getPropertyValue('--fg-400').trim() ||
|
||||
styles.getPropertyValue('--mainForegroundColor').trim()
|
||||
}
|
||||
const autoColorsTest = areAutoColorsValid(autocolors)
|
||||
if (autoColorsTest !== true) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
|
||||
|
||||
import type { RegisterClientOptions } from '@peertube/peertube-types/client'
|
||||
import type { InitConverseJSParams, ChatPeertubeIncludeMode } from 'shared/lib/types'
|
||||
@ -17,7 +18,7 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
let pollListenerInitiliazed: boolean = false
|
||||
let pollListenerInitiliazed = false
|
||||
|
||||
/**
|
||||
* load the ConverseJS CSS.
|
||||
@ -152,10 +153,11 @@ async function displayConverseJS (
|
||||
|
||||
const authHeader = peertubeHelpers.getAuthHeader()
|
||||
|
||||
const url = getBaseRoute(clientOptions) + '/api/configuration/room/' +
|
||||
encodeURIComponent(roomKey) +
|
||||
(forceType ? '?forcetype=1' : '')
|
||||
const response = await fetch(
|
||||
getBaseRoute(clientOptions) + '/api/configuration/room/' +
|
||||
encodeURIComponent(roomKey) +
|
||||
(forceType ? '?forcetype=1' : ''),
|
||||
url,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: authHeader
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
import type { RegisterClientOptions } from '@peertube/peertube-types/client'
|
||||
|
||||
function getBaseRoute ({ peertubeHelpers }: RegisterClientOptions, permanent: boolean = false): string {
|
||||
function getBaseRoute ({ peertubeHelpers }: RegisterClientOptions, permanent = false): string {
|
||||
if (permanent) {
|
||||
return '/plugins/livechat/router'
|
||||
}
|
||||
|
@ -1,40 +0,0 @@
|
||||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true
|
||||
},
|
||||
"extends": [
|
||||
"standard-with-typescript"
|
||||
],
|
||||
"globals": {},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018,
|
||||
"project": [
|
||||
"./conversejs/tsconfig.json"
|
||||
]
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"ignorePatterns": [],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-unused-vars": [2, {"argsIgnorePattern": "^_"}],
|
||||
"@typescript-eslint/no-floating-promises": "error",
|
||||
"@typescript-eslint/no-misused-promises": "error",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"@typescript-eslint/strict-boolean-expressions": "off",
|
||||
"@typescript-eslint/return-await": [2, "in-try-catch"], // FIXME: correct?
|
||||
"@typescript-eslint/no-invalid-void-type": "off",
|
||||
"@typescript-eslint/triple-slash-reference": "off",
|
||||
"max-len": [
|
||||
"error",
|
||||
{
|
||||
"code": 120,
|
||||
"comments": 120
|
||||
}
|
||||
],
|
||||
"no-unused-vars": "off"
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
@ -4,6 +4,7 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const YAML = require('yaml')
|
||||
|
@ -18,8 +18,10 @@ set -x
|
||||
CONVERSE_VERSION="v11.0.0"
|
||||
CONVERSE_REPO="https://github.com/conversejs/converse.js.git"
|
||||
# You can eventually set CONVERSE_COMMIT to a specific commit ID, if you want to apply some patches.
|
||||
# 2024-09-02: using Converse upstream (v11 WIP).
|
||||
CONVERSE_COMMIT="9952046d580bc2930e29833f4c9987a3d4c95bc2"
|
||||
# 2024-09-17: using Converse upstream (v11 WIP).
|
||||
CONVERSE_COMMIT="07dc6f4f5da5890b02a46a8a2f2d0498649786bc"
|
||||
# 2024-12-03: using Converse upstream (v11 WIP).
|
||||
CONVERSE_COMMIT="8f32df723e3aa392db02326dc6a3279c9497b6fb"
|
||||
|
||||
# It is possible to use another repository, if we want some customization that are not upstream (yet):
|
||||
# CONVERSE_VERSION="livechat"
|
||||
@ -29,8 +31,8 @@ CONVERSE_COMMIT="9952046d580bc2930e29833f4c9987a3d4c95bc2"
|
||||
|
||||
# 2024-09-03: include badges short label and quick fix for sendMessage button
|
||||
CONVERSE_REPO="https://github.com/JohnXLivingston/converse.js"
|
||||
CONVERSE_VERSION="livechat-11.0.1"
|
||||
CONVERSE_COMMIT=""
|
||||
CONVERSE_VERSION="livechat-12.0.0"
|
||||
# CONVERSE_COMMIT=""
|
||||
|
||||
rootdir="$(pwd)"
|
||||
src_dir="$rootdir/conversejs"
|
||||
@ -40,6 +42,7 @@ if [ -n "$CONVERSE_COMMIT" ]; then
|
||||
fi
|
||||
converse_build_dir="$rootdir/build/conversejs"
|
||||
converse_destination_dir="$rootdir/dist/client/conversejs"
|
||||
converse_emoji_destination="$rootdir/dist/converse-emoji.json"
|
||||
|
||||
if [[ ! -d $src_dir ]]; then
|
||||
echo "$0 must be called from the plugin livechat root dir."
|
||||
@ -119,6 +122,9 @@ cd $rootdir
|
||||
echo "Copying ConverseJS dist files..."
|
||||
mkdir -p "$converse_destination_dir" && cp -r $converse_build_dir/dist/* "$converse_destination_dir/"
|
||||
|
||||
echo "Copying ConverseJS original emoji.json file..." # this is needed for some backend code.
|
||||
cp "$converse_build_dir/src/headless/plugins/emoji/emoji.json" "$converse_emoji_destination"
|
||||
|
||||
echo "ConverseJS OK."
|
||||
|
||||
exit 0
|
||||
|
@ -2,6 +2,8 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
|
||||
|
||||
import type { InitConverseJSParams, ChatIncludeMode, ExternalAuthResult } from 'shared/lib/types'
|
||||
import { inIframe } from './lib/utils'
|
||||
import { initDom } from './lib/dom'
|
||||
@ -21,6 +23,7 @@ import { livechatViewerModePlugin } from './lib/plugins/livechat-viewer-mode'
|
||||
import { livechatMiniMucHeadPlugin } from './lib/plugins/livechat-mini-muc-head'
|
||||
import { livechatEmojisPlugin } from './lib/plugins/livechat-emojis'
|
||||
import { moderationDelayPlugin } from './lib/plugins/moderation-delay'
|
||||
import { livechatAnnouncementsPlugin } from './lib/plugins/livechat-announcements'
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@ -35,11 +38,17 @@ declare global {
|
||||
html: Function
|
||||
sizzle: Function
|
||||
dayjs: Function
|
||||
__: Function
|
||||
u: {
|
||||
hasClass: Function
|
||||
addClass: Function
|
||||
removeClass: Function
|
||||
}
|
||||
}
|
||||
}
|
||||
initConversePlugins: typeof initConversePlugins
|
||||
initConverse: typeof initConverse
|
||||
reconnectConverse?: (room: string) => void
|
||||
reconnectConverse?: (params: any) => void
|
||||
externalAuthGetResult?: (data: ExternalAuthResult) => void
|
||||
}
|
||||
}
|
||||
@ -74,6 +83,8 @@ function initConversePlugins (peertubeEmbedded: boolean): void {
|
||||
converse.plugins.add('livechatViewerModePlugin', livechatViewerModePlugin)
|
||||
|
||||
converse.plugins.add('converse-moderation-delay', moderationDelayPlugin)
|
||||
|
||||
converse.plugins.add('livechatAnnouncementsPlugin', livechatAnnouncementsPlugin)
|
||||
}
|
||||
window.initConversePlugins = initConversePlugins
|
||||
|
||||
@ -86,7 +97,7 @@ window.initConversePlugins = initConversePlugins
|
||||
async function initConverse (
|
||||
initConverseParams: InitConverseJSParams,
|
||||
chatIncludeMode: ChatIncludeMode = 'chat-only',
|
||||
peertubeAuthHeader?: { [header: string]: string } | null
|
||||
peertubeAuthHeader?: Record<string, string> | null
|
||||
): Promise<void> {
|
||||
// First, fixing relative websocket urls.
|
||||
if (initConverseParams.localWebsocketServiceUrl?.startsWith('/')) {
|
||||
@ -121,9 +132,9 @@ async function initConverse (
|
||||
params.view_mode = chatIncludeMode === 'chat-only' ? 'fullscreen' : 'embedded'
|
||||
params.allow_url_history_change = chatIncludeMode === 'chat-only'
|
||||
|
||||
let isAuthenticated: boolean = false
|
||||
let isAuthenticatedWithExternalAccount: boolean = false
|
||||
let isRemoteWithNicknameSet: boolean = false
|
||||
let isAuthenticated = false
|
||||
let isAuthenticatedWithExternalAccount = false
|
||||
let isRemoteWithNicknameSet = false
|
||||
|
||||
// OIDC (OpenID Connect):
|
||||
const tryOIDC = (initConverseParams.externalAuthOIDC?.length ?? 0) > 0
|
||||
|
@ -66,6 +66,7 @@ CORE_PLUGINS.push('livechat-converse-mam-search')
|
||||
// We must also add our custom ROOM_FEATURES, so that they correctly resets
|
||||
// (see headless/plugins/muc, getDiscoInfoFeatures, which loops on this const)
|
||||
ROOM_FEATURES.push('x_peertubelivechat_mute_anonymous')
|
||||
ROOM_FEATURES.push('x_peertubelivechat_emoji_only_mode')
|
||||
|
||||
_converse.exports.CustomElement = CustomElement
|
||||
|
||||
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import { converseLocalizedHelpUrl } from '../../../shared/lib/help'
|
||||
import { tplMUCApp } from '../../../shared/components/muc-app/templates/muc-app.js'
|
||||
import { html } from 'lit'
|
||||
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import { html } from 'lit'
|
||||
|
||||
/**
|
||||
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import { html } from 'lit'
|
||||
import { api } from '@converse/headless'
|
||||
import { getAuthorStyle } from '../../../../src/utils/color.js'
|
||||
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import { converseLocalizedHelpUrl } from '../../../shared/lib/help'
|
||||
import { tplMUCApp } from '../../../shared/components/muc-app/templates/muc-app.js'
|
||||
import { html } from 'lit'
|
||||
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import { html } from 'lit'
|
||||
import { api } from '@converse/headless'
|
||||
import { getAuthorStyle } from '../../../../src/utils/color.js'
|
||||
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import { api } from '@converse/headless'
|
||||
import { html } from 'lit'
|
||||
import { __ } from 'i18n'
|
||||
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import { html } from 'lit'
|
||||
import { repeat } from 'lit/directives/repeat.js'
|
||||
import { __ } from 'i18n'
|
||||
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import { __ } from 'i18n'
|
||||
import BaseModal from 'plugins/modal/modal.js'
|
||||
import { api } from '@converse/headless'
|
||||
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import { converseLocalizedHelpUrl } from '../../../shared/lib/help'
|
||||
import { html } from 'lit'
|
||||
import { __ } from 'i18n'
|
||||
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import { html } from 'lit'
|
||||
import { repeat } from 'lit/directives/repeat.js'
|
||||
import { __ } from 'i18n'
|
||||
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import { html } from 'lit'
|
||||
import { repeat } from 'lit/directives/repeat.js'
|
||||
import { __ } from 'i18n'
|
||||
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import { converseLocalizedHelpUrl } from '../../../shared/lib/help'
|
||||
import { tplMUCApp } from '../../../shared/components/muc-app/templates/muc-app.js'
|
||||
import { html } from 'lit'
|
||||
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import { html } from 'lit'
|
||||
import { repeat } from 'lit/directives/repeat.js'
|
||||
import { __ } from 'i18n'
|
||||
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import { html } from 'lit'
|
||||
import { repeat } from 'lit/directives/repeat.js'
|
||||
import { __ } from 'i18n'
|
||||
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import { html } from 'lit'
|
||||
import { __ } from 'i18n'
|
||||
|
||||
@ -20,7 +23,8 @@ export function tplMucTask (el, task) {
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
.checked=${done === true}
|
||||
@click=${(_ev) => {
|
||||
@click=${(ev) => {
|
||||
ev?.preventDefault()
|
||||
task.set('done', !done)
|
||||
task.saveItem()
|
||||
}}
|
||||
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import { CustomElement } from 'shared/components/element.js'
|
||||
import { api } from '@converse/headless'
|
||||
import { html } from 'lit'
|
||||
|
59
conversejs/custom/shared/styles/_announcements.scss
Normal file
59
conversejs/custom/shared/styles/_announcements.scss
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
// FIXME: this should be with the livechat-announcement plugin.
|
||||
// But for now, there is no way to build scss from there.
|
||||
|
||||
#conversejs {
|
||||
.message.chat-msg {
|
||||
&.livechat-announcement {
|
||||
--livechat-announcement-color: #000;
|
||||
--livechat-announcement-background-color: #dbf2d8;
|
||||
--livechat-announcement-border-color: #2ab218;
|
||||
}
|
||||
|
||||
&.livechat-highlight {
|
||||
--livechat-announcement-color: #000;
|
||||
--livechat-announcement-background-color: #dce8fa;
|
||||
--livechat-announcement-border-color: #3075e5;
|
||||
}
|
||||
|
||||
&.livechat-warning {
|
||||
--livechat-announcement-color: #000;
|
||||
--livechat-announcement-background-color: #fadede;
|
||||
--livechat-announcement-border-color: #e03e3e;
|
||||
}
|
||||
|
||||
&.livechat-announcement,
|
||||
&.livechat-highlight,
|
||||
&.livechat-warning {
|
||||
converse-chat-message-body {
|
||||
border: 2px solid var(--livechat-announcement-border-color);
|
||||
color: var(--livechat-announcement-color);
|
||||
background-color: var(--livechat-announcement-background-color);
|
||||
min-width: 50%;
|
||||
padding: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.livechat-announcements-form {
|
||||
label {
|
||||
// only for screen readers
|
||||
border: 0 !important;
|
||||
clip: rect(1px, 1px, 1px, 1px) !important;
|
||||
/* stylelint-disable-next-line property-no-vendor-prefix */
|
||||
-webkit-clip-path: inset(50%) !important;
|
||||
clip-path: inset(50%) !important;
|
||||
height: 1px !important;
|
||||
overflow: hidden !important;
|
||||
padding: 0 !important;
|
||||
position: absolute !important;
|
||||
width: 1px !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
}
|
||||
}
|
@ -35,6 +35,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Emoji only info box
|
||||
.livechat-emoji-only-info-box {
|
||||
border: 1px dashed var(--peertube-menu-background);
|
||||
color: var(--peertube-main-foreground);
|
||||
background-color: var(--peertube-main-background);
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
converse-chat-toolbar {
|
||||
border-top: none !important; // removing border, to avoid confusing the toolbar with an input field.
|
||||
color: var(--peertube-main-foreground);
|
||||
@ -43,6 +51,10 @@
|
||||
|
||||
// Fixing emoji colors for some emoji like «motorcycle»
|
||||
converse-emoji-picker {
|
||||
// Must set display block. Without this, Converse defined max-width will not apply.
|
||||
// Don't really know why it is working in pure ConverseJs and not in livechat.
|
||||
display: block;
|
||||
|
||||
.emoji-picker {
|
||||
.insert-emoji {
|
||||
a {
|
||||
@ -52,13 +64,17 @@
|
||||
}
|
||||
|
||||
.emoji-picker__header {
|
||||
color: var(--peertube-main-background);
|
||||
background-color: var(--peertube-main-foreground);
|
||||
background-color: var(--peertube-main-background);
|
||||
color: var(--peertube-main-foreground);
|
||||
|
||||
.emoji-search {
|
||||
color: currentcolor;
|
||||
}
|
||||
|
||||
ul {
|
||||
.emoji-category {
|
||||
color: var(--peertube-main-background);
|
||||
background-color: var(--peertube-main-foreground);
|
||||
background-color: var(--peertube-main-background);
|
||||
color: var(--peertube-main-foreground);
|
||||
|
||||
a {
|
||||
color: currentcolor;
|
||||
@ -190,7 +206,7 @@
|
||||
}
|
||||
|
||||
// Bigger occupants sidebar when width is not big enough.
|
||||
@media screen and (max-width: 576px) {
|
||||
@media screen and (width <= 576px) {
|
||||
.chatroom .box-flyout .chatroom-body .occupants {
|
||||
min-width: 50%;
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
@import "./variables";
|
||||
@import "shared/styles/index";
|
||||
@import "./peertubetheme";
|
||||
@import "./announcements";
|
||||
|
||||
body.livechat-iframe {
|
||||
#conversejs .chat-head {
|
||||
@ -21,7 +22,7 @@ body.livechat-iframe {
|
||||
}
|
||||
|
||||
#conversejs .livechat-mini-muc-bar-buttons {
|
||||
a.orange-button {
|
||||
a.primary-button {
|
||||
// force these colors...
|
||||
color: var(--peertube-button-foreground);
|
||||
background-color: var(--peertube-button-background);
|
||||
@ -202,6 +203,7 @@ body.converse-embedded {
|
||||
// This margin-left trick is to align the button on the right.
|
||||
margin-left: auto !important;
|
||||
order: 99;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -229,3 +231,27 @@ body.converse-embedded {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When livechat has not many height, must reduce the emoji picker height.
|
||||
/* stylelint-disable-next-line no-duplicate-selectors */
|
||||
#conversejs {
|
||||
&[livechat-converse-root-height="small"] {
|
||||
converse-emoji-picker {
|
||||
converse-emoji-picker-content {
|
||||
.emoji-picker__lists {
|
||||
height: 2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&[livechat-converse-root-height="medium"] {
|
||||
converse-emoji-picker {
|
||||
converse-emoji-picker-content {
|
||||
.emoji-picker__lists {
|
||||
height: 4em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import { _converse, api } from '@converse/headless'
|
||||
import { __ } from 'i18n'
|
||||
import { html } from 'lit'
|
||||
@ -28,8 +31,9 @@ function externalLoginClickHandler (ev, el, externalAuthOIDCUrl) {
|
||||
|
||||
console.log('Received an external authentication result...', data)
|
||||
if (!data.ok) {
|
||||
// eslint-disable-next-line no-undef
|
||||
el.external_auth_oidc_alert_message = __(LOC_login_external_auth_alert_message) +
|
||||
el.external_auth_oidc_alert_message =
|
||||
// eslint-disable-next-line no-undef
|
||||
__(LOC_login_external_auth_alert_message) +
|
||||
(data.message ? ` (${data.message})` : '')
|
||||
return
|
||||
}
|
||||
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import { __ } from 'i18n'
|
||||
import { _converse, api } from '@converse/headless'
|
||||
import { html } from 'lit'
|
||||
@ -83,6 +86,20 @@ const tplSlowMode = (o) => {
|
||||
return html`<livechat-slow-mode jid=${o.model.get('jid')}>`
|
||||
}
|
||||
|
||||
const tplEmojiOnly = (o) => {
|
||||
if (!o.can_post) { return html`` }
|
||||
if (!o.model.features?.get?.('x_peertubelivechat_emoji_only_mode')) {
|
||||
return ''
|
||||
}
|
||||
return html`<div class="livechat-emoji-only-info-box">
|
||||
<converse-icon class="fa fa-info-circle" size="1.2em"></converse-icon>
|
||||
${
|
||||
// eslint-disable-next-line no-undef
|
||||
__(LOC_emoji_only_info)
|
||||
}
|
||||
</div>`
|
||||
}
|
||||
|
||||
const tplViewerMode = (o) => {
|
||||
if (!api.settings.get('livechat_enable_viewer_mode')) {
|
||||
return html``
|
||||
@ -145,6 +162,7 @@ export default (o) => {
|
||||
return html`
|
||||
${tplViewerMode(o)}
|
||||
${tplSlowMode(o)}
|
||||
${tplEmojiOnly(o)}
|
||||
${
|
||||
mutedAnonymousMessage
|
||||
? html`<span class="muc-bottom-panel muc-bottom-panel--muted">${mutedAnonymousMessage}</span>`
|
||||
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import { api } from '@converse/headless'
|
||||
import tplMUCChatarea from '../../src/plugins/muc-views/templates/muc-chatarea.js'
|
||||
import { html } from 'lit'
|
||||
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
import { html } from 'lit'
|
||||
import { api } from '@converse/headless'
|
||||
import { until } from 'lit/directives/until.js'
|
||||
|
@ -4,6 +4,9 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
// Must import the original muc.js, because it imports some custom elements files.
|
||||
import '../../src/plugins/muc-views/templates/muc.js'
|
||||
import { getChatRoomBodyTemplate } from '../../src/plugins/muc-views/utils.js'
|
||||
|
11
conversejs/lib/@types/global.d.ts
vendored
Normal file
11
conversejs/lib/@types/global.d.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Important note: loc segments that are declared here must also be in loc.keys.js (for now).
|
||||
|
||||
declare const LOC_ANNOUNCEMENTS_MESSAGE_TYPE: string
|
||||
declare const LOC_ANNOUNCEMENTS_MESSAGE_TYPE_STANDARD: string
|
||||
declare const LOC_ANNOUNCEMENTS_MESSAGE_TYPE_ANNOUNCEMENT: string
|
||||
declare const LOC_ANNOUNCEMENTS_MESSAGE_TYPE_HIGHLIGHT: string
|
||||
declare const LOC_ANNOUNCEMENTS_MESSAGE_TYPE_WARNING: string
|
@ -4,7 +4,7 @@
|
||||
|
||||
import type { ProsodyAuthentInfos } from 'shared/lib/types'
|
||||
|
||||
interface AuthHeader { [key: string]: string }
|
||||
type AuthHeader = Record<string, string>
|
||||
|
||||
async function getLocalAuthentInfos (
|
||||
authenticationUrl: string,
|
||||
@ -66,7 +66,7 @@ async function getLocalAuthentInfos (
|
||||
{
|
||||
'content-type': 'application/json;charset=UTF-8'
|
||||
}
|
||||
)
|
||||
) as HeadersInit
|
||||
)
|
||||
})
|
||||
|
||||
@ -104,7 +104,7 @@ async function getLocalAuthentInfos (
|
||||
function getLivechatTokenAuthInfos (): ProsodyAuthentInfos | undefined {
|
||||
try {
|
||||
const hash = window.location.hash
|
||||
if (!hash || !hash.startsWith('#?')) { return undefined }
|
||||
if (!hash?.startsWith('#?')) { return undefined }
|
||||
// We try to read the hash as a queryString.
|
||||
const u = new URL('http://localhost' + hash.substring(1))
|
||||
const jid = u.searchParams.get('j')
|
||||
|
@ -86,7 +86,8 @@ function defaultConverseParams (
|
||||
'livechatDisconnectOnUnloadPlugin',
|
||||
'converse-slow-mode',
|
||||
'livechatEmojis',
|
||||
'converse-moderation-delay'
|
||||
'converse-moderation-delay',
|
||||
'livechatAnnouncementsPlugin'
|
||||
],
|
||||
show_retraction_warning: false, // No need to use this warning (except if we open to external clients?)
|
||||
muc_show_info_messages: mucShowInfoMessages,
|
||||
|
249
conversejs/lib/plugins/livechat-announcements.ts
Normal file
249
conversejs/lib/plugins/livechat-announcements.ts
Normal file
@ -0,0 +1,249 @@
|
||||
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
interface Current {
|
||||
announcementType: string | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* livechat announcements ConverseJS plugin:
|
||||
* with this plugin, moderators can send highlighted/announcements messages.
|
||||
*
|
||||
* Moderators will have a special select field in the chat toolbar, so that they can choose a messaging style.
|
||||
* These special messages will have a first line with a generated title (for XMPP compatibility).
|
||||
* They will also have a special attribute on the body tag.
|
||||
* This attribute will be used to apply some CSS with this plugin.
|
||||
*/
|
||||
export const livechatAnnouncementsPlugin = {
|
||||
dependencies: ['converse-muc', 'converse-muc-views'],
|
||||
initialize: function (this: any) {
|
||||
const _converse = this._converse
|
||||
|
||||
// This is a closure variable, to get the current form status when sending a message.
|
||||
const current: Current = {
|
||||
announcementType: undefined
|
||||
}
|
||||
|
||||
overrideMUCMessageForm(_converse, current)
|
||||
|
||||
_converse.api.listen.on('getToolbarButtons', getToolbarButtons.bind(this))
|
||||
|
||||
_converse.api.listen.on('chatRoomInitialized', (muc: any) => onAffiliationChange(_converse, muc))
|
||||
|
||||
_converse.api.listen.on('getOutgoingMessageAttributes', (chatbox: any, attrs: any) => {
|
||||
return onGetOutgoingMessageAttributes(current, _converse, chatbox, attrs)
|
||||
})
|
||||
|
||||
_converse.api.listen.on('createMessageStanza', createMessageStanza)
|
||||
|
||||
_converse.api.listen.on('parseMUCMessage', parseMUCMessage)
|
||||
|
||||
overrideMessage(_converse)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloads the MUCMessageForm to handle the announcement type field (if present) when sending a message.
|
||||
*
|
||||
* Also hides the announcement type field if we are correcting a previous message.
|
||||
*/
|
||||
function overrideMUCMessageForm (_converse: any, current: Current): void {
|
||||
const MUCMessageForm = _converse.api.elements.registry['converse-muc-message-form']
|
||||
if (MUCMessageForm) {
|
||||
class MUCMessageFormloaded extends MUCMessageForm {
|
||||
async onFormSubmitted (ev?: Event): Promise<void> {
|
||||
const announcementSelect = this.querySelector('[name=livechat-announcements]')
|
||||
current.announcementType = announcementSelect?.selectedOptions?.[0]?.value || undefined
|
||||
try {
|
||||
await super.onFormSubmitted(ev)
|
||||
if (announcementSelect) { announcementSelect.selectedIndex = 0 } // set back to default
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
current.announcementType = undefined
|
||||
}
|
||||
|
||||
insertIntoTextArea (...args: any[]): void {
|
||||
super.insertIntoTextArea(...args)
|
||||
try {
|
||||
// FIXME: doing this here is not very clean.
|
||||
// But that's how ConverseJS adds or removes the 'correction' class to the textarea.
|
||||
const textarea = this.querySelector('.chat-textarea')
|
||||
if (!textarea) { return }
|
||||
const correcting = window.converse.env.u.hasClass('correcting', textarea)
|
||||
const announcementForm = this.querySelector('.livechat-announcements-form')
|
||||
const announcementSelect = this.querySelector('[name=livechat-announcements]')
|
||||
if (correcting) {
|
||||
if (announcementSelect) { announcementSelect.selectedIndex = 0 }
|
||||
if (announcementForm) { announcementForm.style.display = 'none' }
|
||||
} else {
|
||||
if (announcementForm) { announcementForm.style.display = 'block' }
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
_converse.api.elements.define('converse-muc-message-form', MUCMessageFormloaded)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the announcement selector in the toolbar for owner/admin.
|
||||
* @param this the plugin
|
||||
* @param toolbarEl the toolbar element
|
||||
* @param buttons the button list
|
||||
* @returns the updated "button" list
|
||||
*/
|
||||
function getToolbarButtons (this: any, toolbarEl: any, buttons: any[]): Parameters<typeof getToolbarButtons>[1] {
|
||||
const _converse = this._converse
|
||||
const mucModel = toolbarEl.model
|
||||
if (!toolbarEl.is_groupchat) {
|
||||
return buttons
|
||||
}
|
||||
const myself = mucModel.getOwnOccupant()
|
||||
if (!myself || !['admin', 'owner'].includes(myself.get('affiliation') as string)) {
|
||||
return buttons
|
||||
}
|
||||
|
||||
const { __ } = _converse
|
||||
const { html } = window.converse.env
|
||||
|
||||
const i18n = __(LOC_ANNOUNCEMENTS_MESSAGE_TYPE)
|
||||
const i18nStandard = __(LOC_ANNOUNCEMENTS_MESSAGE_TYPE_STANDARD)
|
||||
const i18nAnnouncement = __(LOC_ANNOUNCEMENTS_MESSAGE_TYPE_ANNOUNCEMENT)
|
||||
const i18nHighlight = __(LOC_ANNOUNCEMENTS_MESSAGE_TYPE_HIGHLIGHT)
|
||||
const i18nWarning = __(LOC_ANNOUNCEMENTS_MESSAGE_TYPE_WARNING)
|
||||
|
||||
const select = html`<span class="livechat-announcements-form form-inline">
|
||||
<label for="livechat-announcements-select">${i18n}</label>
|
||||
<select
|
||||
name="livechat-announcements"
|
||||
id="livechat-announcements-select"
|
||||
class="form-control form-control-sm"
|
||||
title=${i18n}
|
||||
>
|
||||
<option value="">${i18nStandard}</option>
|
||||
<option value="highlight">${i18nHighlight}</option>
|
||||
<option value="announcement">${i18nAnnouncement}</option>
|
||||
<option value="warning">${i18nWarning}</option>
|
||||
</select>
|
||||
</span>`
|
||||
|
||||
if (_converse.api.settings.get('visible_toolbar_buttons').emoji) {
|
||||
// Emojis should be the first entry, so adding select in second place.
|
||||
buttons = [
|
||||
buttons.shift(),
|
||||
select,
|
||||
...buttons
|
||||
]
|
||||
} else {
|
||||
// Adding the select in first place.
|
||||
buttons.unshift(select)
|
||||
}
|
||||
return buttons
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshed the toolbar when current user affiliation changes.
|
||||
* @param _converse _converse object
|
||||
* @param muc the current muc
|
||||
*/
|
||||
function onAffiliationChange (_converse: any, muc: any): void {
|
||||
muc.occupants.on('change:affiliation', (occupant: any) => {
|
||||
if (occupant.get('jid') !== _converse.bare_jid) { // only for myself
|
||||
return
|
||||
}
|
||||
document.querySelectorAll('converse-chat-toolbar').forEach(e => (e as any).requestUpdate?.())
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* For outgoing message, adding the announcement type if there is a current one.
|
||||
* @param current current object
|
||||
* @param _converse _converse object
|
||||
* @param chatbox the chatbox
|
||||
* @param attrs message attributes
|
||||
* @returns
|
||||
*/
|
||||
function onGetOutgoingMessageAttributes (
|
||||
current: Current,
|
||||
_converse: any,
|
||||
chatbox: any,
|
||||
attrs: any
|
||||
): Parameters<typeof onGetOutgoingMessageAttributes>[3] {
|
||||
if (!current.announcementType) { return attrs }
|
||||
|
||||
const { __ } = _converse
|
||||
attrs.livechat_announcement_type = current.announcementType
|
||||
if (current.announcementType === 'announcement') {
|
||||
attrs.body = '* ' + __(LOC_ANNOUNCEMENTS_MESSAGE_TYPE_ANNOUNCEMENT) + ' * \n' + attrs.body
|
||||
} else if (current.announcementType === 'warning') {
|
||||
attrs.body = '* ' + __(LOC_ANNOUNCEMENTS_MESSAGE_TYPE_WARNING) + ' *\n' + attrs.body
|
||||
}
|
||||
return attrs
|
||||
}
|
||||
|
||||
/**
|
||||
* Outgoing messages: adding an attribute on body for announcements.
|
||||
* @param chat
|
||||
* @param data
|
||||
*/
|
||||
async function createMessageStanza (
|
||||
chat: any,
|
||||
data: any
|
||||
): Promise<Parameters<typeof createMessageStanza>[1]> {
|
||||
const { message, stanza } = data
|
||||
const announcementType = message.get('livechat_announcement_type')
|
||||
if (!announcementType) {
|
||||
return data
|
||||
}
|
||||
|
||||
stanza.tree().querySelector('message body')?.setAttribute('x-livechat-announcement-type', announcementType)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Incoming messages: checking if there is an announcement attribute, and adding it in computed attributes.
|
||||
* @param stanza
|
||||
* @param attrs
|
||||
*/
|
||||
function parseMUCMessage (stanza: any, attrs: any): Parameters<typeof parseMUCMessage>[1] {
|
||||
const { sizzle } = window.converse.env
|
||||
const body = sizzle('message body', stanza)?.[0]
|
||||
if (!body) { return attrs }
|
||||
|
||||
const announcementType = body.getAttribute('x-livechat-announcement-type')
|
||||
if (!announcementType) { return attrs }
|
||||
|
||||
// Note: we don't check the value here. Will be done in getExtraMessageClasses.
|
||||
// Moreover, the backend server will ensure that only admins/owners can send this attribute.
|
||||
attrs.livechat_announcement_type = announcementType
|
||||
return attrs
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloading the Message class to add CSS for announcements.
|
||||
* @param _converse
|
||||
*/
|
||||
function overrideMessage (_converse: any): void {
|
||||
const Message = _converse.api.elements.registry['converse-chat-message']
|
||||
if (Message) {
|
||||
class MessageOverloaded extends Message {
|
||||
getExtraMessageClasses (this: any): string {
|
||||
// Adding CSS class if the message is an announcement.
|
||||
let extraClasses = super.getExtraMessageClasses() ?? ''
|
||||
const announcementType: string | undefined = this.model.get('livechat_announcement_type')
|
||||
if (!announcementType) {
|
||||
return extraClasses
|
||||
}
|
||||
if (['announcement', 'highlight', 'warning'].includes(announcementType)) {
|
||||
extraClasses += ' livechat-' + announcementType
|
||||
}
|
||||
return extraClasses
|
||||
}
|
||||
}
|
||||
_converse.api.elements.define('converse-chat-message', MessageOverloaded)
|
||||
}
|
||||
}
|
@ -17,19 +17,19 @@ export const livechatEmojisPlugin = {
|
||||
livechat_custom_emojis_url: false
|
||||
})
|
||||
|
||||
_converse.api.listen.on('loadEmojis', async (_context: Object, json: any) => {
|
||||
const url = _converse.api.settings.get('livechat_custom_emojis_url')
|
||||
_converse.api.listen.on('loadEmojis', async (_context: object, json: Record<string, Record<string, unknown>>) => {
|
||||
const url = _converse.api.settings.get('livechat_custom_emojis_url') as string | undefined
|
||||
if (!url) {
|
||||
return json
|
||||
}
|
||||
|
||||
let customs
|
||||
let customs: CustomEmojiDefinition[] | undefined
|
||||
try {
|
||||
customs = await loadCustomEmojis(url)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
if (customs === undefined || !customs?.length) {
|
||||
if (!customs?.length) {
|
||||
return json
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ export const livechatEmojisPlugin = {
|
||||
|
||||
// We must also remove any existing emojis in category other than custom
|
||||
for (const type of Object.keys(json)) {
|
||||
const v: {[key: string]: any} = json[type]
|
||||
const v: Record<string, any> = json[type]
|
||||
if (type !== 'custom' && type !== 'modifiers' && (def.sn in v)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete v[def.sn]
|
||||
|
@ -9,6 +9,7 @@ import { chatRoomOverrides } from './livechat-specific/chatroom'
|
||||
import { chatRoomMessageOverrides } from './livechat-specific/chatroom-message'
|
||||
import { customizeMessageAction } from './livechat-specific/message-action'
|
||||
import { customizeProfileModal } from './livechat-specific/profile'
|
||||
import { customizeMUCBottomPanel } from './livechat-specific/muc-bottom-panel'
|
||||
|
||||
export const livechatSpecificsPlugin = {
|
||||
dependencies: ['converse-muc', 'converse-muc-views'],
|
||||
@ -26,6 +27,7 @@ export const livechatSpecificsPlugin = {
|
||||
customizeToolbar(this)
|
||||
customizeMessageAction(this)
|
||||
customizeProfileModal(this)
|
||||
customizeMUCBottomPanel(this)
|
||||
|
||||
_converse.api.listen.on('chatRoomViewInitialized', function (this: any, _model: any): void {
|
||||
// Remove the spinner if present...
|
||||
|
@ -2,7 +2,8 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
export function chatRoomMessageOverrides (): {[key: string]: Function} {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||
export function chatRoomMessageOverrides (): Record<string, Function> {
|
||||
return {
|
||||
/* By default, ConverseJS groups messages from the same users for a 10 minutes period.
|
||||
* This make no sense in a livechat room. So we override isFollowup to ignore. */
|
||||
|
@ -2,7 +2,8 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
export function chatRoomOverrides (): {[key: string]: Function} {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||
export function chatRoomOverrides (): Record<string, Function> {
|
||||
return {
|
||||
getActionInfoMessage: function getActionInfoMessage (this: any, code: string, nick: string, actor: any): any {
|
||||
if (code === '303') {
|
||||
@ -27,8 +28,9 @@ export function chatRoomOverrides (): {[key: string]: Function} {
|
||||
initOccupants: function initOccupants (this: any) {
|
||||
const r = this.__super__.initOccupants()
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||
const originalComparatorFunction: Function = this.occupants.comparator
|
||||
this.occupants.comparator = function (this: any, occupant1: any, occupant2: any): Number {
|
||||
this.occupants.comparator = function (this: any, occupant1: any, occupant2: any): number {
|
||||
// Overriding Occupants comparators, to display anonymous users at the end of the list.
|
||||
const nick1: string = occupant1.getDisplayName()
|
||||
const nick2: string = occupant2.getDisplayName()
|
||||
|
@ -19,7 +19,7 @@ export function customizeMessageAction (plugin: any): void {
|
||||
try {
|
||||
txt += this.model.getDisplayName() as string
|
||||
txt += ' - '
|
||||
const date = new Date(this.model.get('edited') || this.model.get('time'))
|
||||
const date = new Date((this.model.get('edited') || this.model.get('time')) as string)
|
||||
txt += date.toLocaleDateString() + ' ' + date.toLocaleTimeString()
|
||||
txt += '\n'
|
||||
} catch {}
|
||||
|
23
conversejs/lib/plugins/livechat-specific/muc-bottom-panel.ts
Normal file
23
conversejs/lib/plugins/livechat-specific/muc-bottom-panel.ts
Normal file
@ -0,0 +1,23 @@
|
||||
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
/**
|
||||
* Override the MUCBottomPanel custom element
|
||||
*/
|
||||
export function customizeMUCBottomPanel (plugin: any): void {
|
||||
const _converse = plugin._converse
|
||||
const MUCBottomPanel = _converse.api.elements.registry['converse-muc-bottom-panel']
|
||||
if (MUCBottomPanel) {
|
||||
class MUCBottomPanelOverloaded extends MUCBottomPanel {
|
||||
async initialize (): Promise<any> {
|
||||
await super.initialize()
|
||||
// We must refresh the bottom panel when these features changes (to display the infobox)
|
||||
// FIXME: the custom muc-bottom-panel template should be used here, in an overloaded render method, instead
|
||||
// of using webpack to overload the original file.
|
||||
this.listenTo(this.model.features, 'change:x_peertubelivechat_emoji_only_mode', () => this.requestUpdate())
|
||||
}
|
||||
}
|
||||
_converse.api.elements.define('converse-muc-bottom-panel', MUCBottomPanelOverloaded)
|
||||
}
|
||||
}
|
@ -2,6 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// FIXME: @stylistic/indent is buggy with strings literrals.
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
/**
|
||||
* Do some customization on the toolbar:
|
||||
* * change the appearance of the toggle occupants button
|
||||
|
@ -25,7 +25,7 @@ export function getOpenPromise (): any {
|
||||
promise.isResolved = false
|
||||
promise.isPending = false
|
||||
promise.isRejected = true
|
||||
throw (e)
|
||||
throw (e as Error)
|
||||
}
|
||||
)
|
||||
return promise
|
||||
|
@ -22,11 +22,11 @@ export const livechatViewerModePlugin = {
|
||||
console.error('[livechatViewerModePlugin] getDefaultMUCNickname is not initialized.')
|
||||
} else {
|
||||
Object.assign(_converse.exports, {
|
||||
getDefaultMUCNickname: function (this: any): any {
|
||||
getDefaultMUCNickname: function (this: any, ...args: any[]): any {
|
||||
if (!_converse.api.settings.get('livechat_enable_viewer_mode')) {
|
||||
return originalGetDefaultMUCNickname.apply(this, arguments)
|
||||
return originalGetDefaultMUCNickname.apply(this, args)
|
||||
}
|
||||
return originalGetDefaultMUCNickname.apply(this, arguments) ??
|
||||
return originalGetDefaultMUCNickname.apply(this, args) ??
|
||||
getPreviousAnonymousNick() ??
|
||||
randomNick('Anonymous')
|
||||
}
|
||||
@ -72,8 +72,8 @@ export const livechatViewerModePlugin = {
|
||||
// Note: when previousNickname is set, model.get('nick') has not the nick yet...
|
||||
// It will only come after receiving a presence stanza.
|
||||
// So we use previousNickname before trying to read the model.
|
||||
const nick = getPreviousAnonymousNick() ?? (model?.get ? model.get('nick') : '')
|
||||
refreshViewerMode(nick && !/^Anonymous /.test(nick))
|
||||
const nick = getPreviousAnonymousNick() ?? (model?.get ? model.get('nick') as string : '')
|
||||
refreshViewerMode(!!nick && !/^Anonymous /.test(nick))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,13 @@ export const slowModePlugin = {
|
||||
return
|
||||
}
|
||||
|
||||
const slowModeDuration = parseInt(chatbox?.config?.get('slow_mode_duration'))
|
||||
const slowModeDurationRaw = chatbox?.config?.get('slow_mode_duration') ?? NaN
|
||||
const slowModeDuration =
|
||||
typeof slowModeDurationRaw === 'string'
|
||||
? parseInt(slowModeDurationRaw)
|
||||
: typeof slowModeDurationRaw === 'number'
|
||||
? Math.trunc(slowModeDurationRaw)
|
||||
: NaN
|
||||
if (!(slowModeDuration > 0)) { // undefined, NaN, ... are not considered > 0.
|
||||
return
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
function inIframe (): boolean {
|
||||
try {
|
||||
return window.self !== window.top
|
||||
} catch (e) {
|
||||
} catch (_err) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +62,13 @@ const locKeys = [
|
||||
'moderator_note_original_nick',
|
||||
'search_occupant_message',
|
||||
'message_search',
|
||||
'message_search_original_nick'
|
||||
'message_search_original_nick',
|
||||
'emoji_only_info',
|
||||
'announcements_message_type',
|
||||
'announcements_message_type_standard',
|
||||
'announcements_message_type_announcement',
|
||||
'announcements_message_type_highlight',
|
||||
'announcements_message_type_warning'
|
||||
]
|
||||
|
||||
module.exports = locKeys
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user