Compare commits
96 Commits
15ea3c8306
...
36934ed56f
Author | SHA1 | Date | |
---|---|---|---|
36934ed56f | |||
b5e18faaaa | |||
|
4a29b68d57 | ||
|
c94f23fbe5 | ||
|
abfdb0f08d | ||
|
84b21dde6d | ||
|
1b53a6ec2d | ||
|
583f581192 | ||
|
3da491ea49 | ||
|
04768347f2 | ||
|
261d7e0506 | ||
|
f98013f2be | ||
|
c426f0c8c3 | ||
|
a63240ca25 | ||
|
05b1f0f645 | ||
|
baf08ae321 | ||
|
149bab2fc5 | ||
|
80274ecee3 | ||
|
e08e59d625 | ||
|
98eb12104c | ||
|
3c93aad053 | ||
|
0cc87e95e9 | ||
|
75dcd3ab1c | ||
|
aaea13a2fc | ||
|
e940fdc2d3 | ||
|
d7e98642f5 | ||
|
ee37e8893b | ||
|
8fa17b050b | ||
|
b336dbbc78 | ||
|
a68a2d0e30 | ||
|
99371bcdec | ||
|
1bdd7df712 | ||
|
3ec4482043 | ||
|
9a30958979 | ||
|
538ec28da9 | ||
|
9824435b6e | ||
|
4e436c00f0 | ||
|
f0088671ea | ||
|
d92bf9073e | ||
|
8944bb95d8 | ||
|
b357619f7a | ||
|
5e754b0103 | ||
|
7e2f6ede65 | ||
|
91d6782a3c | ||
|
35c486035e | ||
|
b66b0aa315 | ||
|
0b196805b2 | ||
|
33be9b3fc5 | ||
|
1d0a7a97d2 | ||
|
89f1f42e7a | ||
|
c2430856b4 | ||
|
4c84146cff | ||
|
6f479d26c5 | ||
|
5225257bb5 | ||
|
651641f63c | ||
|
8dcd3ef488 | ||
|
2d5fec25c7 | ||
|
5566f4b6cf | ||
|
5018d04b78 | ||
|
c65995e5fa | ||
|
071ee9f6b4 | ||
|
3e23d2751f | ||
|
9731835c31 | ||
|
e6381c7bba | ||
|
ace8ad72f5 | ||
|
50309c6ba9 | ||
|
0c220b2fc4 | ||
|
1ce68eec7e | ||
|
5f4cc7c46e | ||
|
7330729ac1 | ||
|
d5a25af6c8 | ||
|
634e894522 | ||
|
4f2fbfc228 | ||
|
4b5f83c45f | ||
|
c561851bb6 | ||
|
edca1be70a | ||
|
fd27105c2c | ||
|
c010758164 | ||
|
7b3d93b290 | ||
|
64a9c7be21 | ||
|
b5990f0c1d | ||
|
f15d3ed542 | ||
|
b6028ef740 | ||
|
e61db352c7 | ||
|
b3d74a3816 | ||
|
122ab9725b | ||
|
ac64c27c3e | ||
|
8ad762eaa2 | ||
|
acce1c5a5c | ||
|
e57d5b0f30 | ||
|
b45ab79c0e | ||
|
91cddfa8d8 | ||
|
08017ac2bb | ||
|
b115c28ee7 | ||
|
5db4f46421 | ||
|
1a75b30c50 |
@ -1,14 +0,0 @@
|
||||
{
|
||||
"root": true,
|
||||
"env": {},
|
||||
"extends": [],
|
||||
"globals": {},
|
||||
"plugins": [],
|
||||
"ignorePatterns": [
|
||||
"node_modules/", "dist/", "webpack.config.js",
|
||||
"build/",
|
||||
"vendor/",
|
||||
"support/documentation",
|
||||
"build-*js"],
|
||||
"rules": {}
|
||||
}
|
31
.github/dependabot.yml
vendored
Normal file
31
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
# 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
|
||||
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.
|
@ -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.'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
32
CHANGELOG.md
32
CHANGELOG.md
@ -1,5 +1,37 @@
|
||||
# Changelog
|
||||
|
||||
## 12.0.0 (Not Released Yet)
|
||||
|
||||
TODO Before releasing:
|
||||
* update ConverseJS with latest merges (there are currently some known bugs).
|
||||
* as the Prosody version changes, check these stress test https://github.com/JohnXLivingston/livechat-perf-test/tree/main/tests/33-prosody-gc and apply the correct gc parameter. Also see https://github.com/JohnXLivingston/peertube-plugin-livechat/issues/300
|
||||
|
||||
### 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.
|
||||
|
||||
### 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
|
||||
|
@ -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(--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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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,7 +101,7 @@ 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}`]
|
||||
return validationErrorTypes ? (validationErrorTypes.length ? { 'is-invalid': true } : { 'is-valid': true }) : {}
|
||||
|
@ -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,7 +113,7 @@ 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}`]
|
||||
return validationErrorTypes ? (validationErrorTypes.length ? { 'is-invalid': true } : { 'is-valid': true }) : {}
|
||||
|
@ -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>`
|
||||
}
|
||||
|
@ -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/')
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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,8 @@ 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"
|
||||
|
||||
# It is possible to use another repository, if we want some customization that are not upstream (yet):
|
||||
# CONVERSE_VERSION="livechat"
|
||||
@ -29,8 +29,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 +40,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 +120,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 {
|
||||
@ -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
|
||||
|
199
eslint.config.mjs
Normal file
199
eslint.config.mjs
Normal file
@ -0,0 +1,199 @@
|
||||
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import love from 'eslint-config-love'
|
||||
import eslint from '@eslint/js'
|
||||
import tseslint from 'typescript-eslint'
|
||||
import typescriptParser from '@typescript-eslint/parser'
|
||||
import stylistic from '@stylistic/eslint-plugin'
|
||||
import eslintLit from 'eslint-plugin-lit'
|
||||
import globals from 'globals'
|
||||
|
||||
export default tseslint.config(
|
||||
{
|
||||
ignores: [
|
||||
'node_modules/', 'dist/', 'webpack.config.js',
|
||||
'build/',
|
||||
'vendor/',
|
||||
'support/documentation', 'support',
|
||||
'build-*js'
|
||||
]
|
||||
},
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
{
|
||||
...love,
|
||||
files: ['**/*.ts']
|
||||
},
|
||||
stylistic.configs.customize({
|
||||
quotes: 'single',
|
||||
semi: false,
|
||||
commaDangle: 'never',
|
||||
blockSpacing: true,
|
||||
braceStyle: '1tbs',
|
||||
indent: 2,
|
||||
quoteProps: 'as-needed'
|
||||
}),
|
||||
{
|
||||
rules: {
|
||||
'@stylistic/space-before-function-paren': ['error', { anonymous: 'always', asyncArrow: 'always', named: 'always' }],
|
||||
'@stylistic/arrow-parens': 'off',
|
||||
'@stylistic/operator-linebreak': ['error', 'after', { overrides: { '?': 'before', ':': 'before' } }],
|
||||
'@stylistic/max-statements-per-line': ['error', { max: 2 }]
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-empty-function': ['error', { allow: ['arrowFunctions'] }],
|
||||
'no-unused-vars': ['error', { argsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' }],
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' }],
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
'@typescript-eslint/no-misused-promises': ['error', { checksVoidReturn: false }],
|
||||
'@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',
|
||||
'@typescript-eslint/no-explicit-any': 'off', // FIXME: should be "error", and we should use 'unknown' in the code.
|
||||
'init-declarations': 'off',
|
||||
'@typescript-eslint/init-declarations': 'off',
|
||||
'@typescript-eslint/consistent-type-imports': 'off',
|
||||
'@typescript-eslint/consistent-type-exports': 'off',
|
||||
'@typescript-eslint/no-require-imports': 'off',
|
||||
'@typescript-eslint/unbound-method': 'off',
|
||||
'@typescript-eslint/prefer-promise-reject-errors': 'off',
|
||||
'max-params': 'off',
|
||||
'@typescript-eslint/max-params': ['error', { max: 8 }], // FIXME: this rules should use the default max value.
|
||||
'@typescript-eslint/explicit-function-return-type': 'error',
|
||||
'@typescript-eslint/no-confusing-void-expression': 'off',
|
||||
'@typescript-eslint/class-methods-use-this': 'off',
|
||||
'@typescript-eslint/non-nullable-type-assertion-style': 'off',
|
||||
'@typescript-eslint/no-magic-numbers': 'off',
|
||||
'max-len': [
|
||||
'error',
|
||||
{
|
||||
code: 120,
|
||||
comments: 120
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['**/*.js'],
|
||||
rules: {
|
||||
// Disabling typescript-eslint rules for JS files (did not find a simplier way...)
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-floating-promises': 'off',
|
||||
'@typescript-eslint/no-misused-promises': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'@typescript-eslint/strict-boolean-expressions': 'off',
|
||||
'@typescript-eslint/return-await': 'off',
|
||||
'@typescript-eslint/no-invalid-void-type': 'off',
|
||||
'@typescript-eslint/triple-slash-reference': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/init-declarations': 'off',
|
||||
'@typescript-eslint/consistent-type-imports': 'off',
|
||||
'@typescript-eslint/consistent-type-exports': 'off',
|
||||
'@typescript-eslint/no-require-imports': 'off',
|
||||
'@typescript-eslint/unbound-method': 'off',
|
||||
'@typescript-eslint/prefer-promise-reject-errors': 'off',
|
||||
'@typescript-eslint/max-params': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/no-confusing-void-expression': 'off',
|
||||
'@typescript-eslint/class-methods-use-this': 'off',
|
||||
'@typescript-eslint/non-nullable-type-assertion-style': 'off',
|
||||
'@typescript-eslint/no-magic-numbers': 'off',
|
||||
|
||||
'max-params': 'off',
|
||||
'init-declarations': 'off',
|
||||
'no-unused-vars': ['error', { argsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' }],
|
||||
'max-len': [
|
||||
'error',
|
||||
{
|
||||
code: 120,
|
||||
comments: 120
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['.stylelintrc.js'],
|
||||
languageOptions: {
|
||||
globals: globals.node
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['server/**/*.js', 'server/**/*.ts'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 6,
|
||||
globals: {
|
||||
...globals.node
|
||||
},
|
||||
parser: typescriptParser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
project: './server/tsconfig.json',
|
||||
projectService: true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['shared/**/*.js', 'shared/**/*.ts'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 6,
|
||||
globals: {
|
||||
...globals.es2016,
|
||||
...globals.browser
|
||||
},
|
||||
parser: typescriptParser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
project: [
|
||||
'./server/tsconfig.json'
|
||||
// './client/tsconfig.json' // FIXME: dont really know what is necessary here.
|
||||
],
|
||||
// FIXME: how to make projectService work?
|
||||
projectService: false
|
||||
// projectService: {
|
||||
// allowDefaultProject: ['shared/lib/*.js', 'shared/lib/*.ts'],
|
||||
// defaultProject: './server/tsconfig.json'
|
||||
// }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['client/**/*.js', 'client/**/*.ts', 'conversejs/**/*.js', 'conversejs/**/*.ts'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 6,
|
||||
globals: {
|
||||
...globals.browser
|
||||
},
|
||||
parser: typescriptParser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
project: [
|
||||
'./client/tsconfig.json',
|
||||
'./conversejs/tsconfig.json'
|
||||
]
|
||||
}
|
||||
},
|
||||
// FIXME: not sure elintLit works.
|
||||
plugins: {
|
||||
...eslintLit.configs['flat/recommended'].plugins
|
||||
},
|
||||
rules: {
|
||||
...eslintLit.configs['flat/recommended'].rules
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['conversejs/build-*.js', 'conversejs/loc.keys.js', 'conversejs/custom/webpack.livechat.js'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
@ -46,7 +46,7 @@ livechat_configuration_channel_forbidden_words_label_label: الملصقة
|
||||
avatar_set_option_none: لا شيء
|
||||
successfully_saved: تم الحفظ بنجاح
|
||||
livechat_configuration_channel_slow_mode_label: الوضع البطيء
|
||||
livechat_configuration_channel_forbidden_words_reason_label: السبب
|
||||
livechat_configuration_channel_retractation_reason_label: السبب
|
||||
livechat_configuration_channel_bot_options_title: خيارات روبوت الإشراف
|
||||
livechat_configuration_channel_forbidden_words_comments_label: التعليقات
|
||||
livechat_configuration_channel_command_message_label: رسالة
|
||||
|
@ -302,8 +302,8 @@ livechat_configuration_channel_desc: Sie können hier einige Optionen für diese
|
||||
einstellen (Moderationsrichtlinien, ...).
|
||||
livechat_configuration_channel_bot_options_title: Optionen für den Moderationsbot
|
||||
livechat_configuration_channel_forbidden_words_label: Verbotene Wörter oder Ausdrücke
|
||||
livechat_configuration_channel_forbidden_words_reason_label: Grund
|
||||
livechat_configuration_channel_forbidden_words_reason_desc: Anzuzeigender Grund anstelle
|
||||
livechat_configuration_channel_retractation_reason_label: Grund
|
||||
livechat_configuration_channel_retractation_reason_desc: Anzuzeigender Grund anstelle
|
||||
der gelöschen Nachricht
|
||||
livechat_configuration_channel_forbidden_words_regexp_desc: Wenn Sie diese Option
|
||||
aktivieren, können Sie reguläre Ausdrücke (regex) verwenden.
|
||||
@ -353,11 +353,7 @@ livechat_configuration_channel_quote_delay_desc: "Der Chatbot wird die Nachricht
|
||||
livechat_configuration_channel_command_desc: "Sie können den Chatbot so konfigurieren,
|
||||
dass er auf Befehle reagiert.\nEin Befehl ist eine Nachricht, die mit einem \"!\"\
|
||||
\ beginnt, wie zum Beispiel \"!help\" den Befehl \"help\" aufruft.\n"
|
||||
livechat_configuration_channel_forbidden_words_applytomoderators_label: Auch Nachrichten
|
||||
von Moderatoren moderieren
|
||||
livechat_configuration_channel_forbidden_words_applytomoderators_desc: "Standardmäßig
|
||||
werden Nachrichten von Moderatoren nicht gelöscht, wenn sie verbotene Wörter enthalten.\n
|
||||
Wenn Sie diese Option aktivieren, werden auch Nachrichten von Moderatoren gelöscht.\n"
|
||||
livechat_configuration_applytomoderators_label: Auch Nachrichten von Moderatoren moderieren
|
||||
invalid_value: Ungültiger Wert.
|
||||
livechat_configuration_channel_forbidden_words_comments_label: Kommentare
|
||||
livechat_configuration_channel_forbidden_words_comments_desc: "Sie können hier einige
|
||||
@ -601,3 +597,45 @@ prosody_firewall_name_desc: "Darf nur folgende Zeichen enthalten: alphanumerisch
|
||||
geladen.\n"
|
||||
prosody_firewall_content: Dateiinhalt
|
||||
chat: Chat
|
||||
emoji_only_info: Wenn der Modus "Nur Emoji" aktiviert ist, können Sie nur Emoji in
|
||||
Ihren Nachrichten verwenden.
|
||||
emoji_only_mode_title: Nur Emojis-Modus
|
||||
emoji_only_mode_desc_1: "Sie können in Ihren Chaträumen einen \"Nur-Emoji-Modus\"
|
||||
aktivieren.\nWenn dieser Modus aktiviert ist, können die Teilnehmer nur Emojis (Standard-
|
||||
oder kanalangepasste Emojis) senden.\nModeratoren sind von dieser Einschränkung
|
||||
nicht betroffen.\n"
|
||||
emoji_only_mode_desc_3: "Um diesen Modus zu aktivieren oder zu deaktivieren, können
|
||||
Sie das Raumkonfigurationsformular verwenden.\nWenn Sie ihn für alle Ihre Chaträume
|
||||
auf einmal aktivieren möchten, können Sie die Schaltfläche unten verwenden.\n"
|
||||
emoji_only_enable_all_rooms: Aktiviere den Modus "Nur Emoji" in allen Chaträumen des
|
||||
Kanals
|
||||
emoji_only_mode_desc_2: "Dieser Modus kann zum Beispiel nützlich sein:\n<ul>\n <li>um
|
||||
Spam oder beleidigende Nachrichten zu vermeiden, wenn Sie nicht hier sind, um zu
|
||||
moderieren.</li>\n <li>wenn es zu viele sprechende Teilnehmer gibt, und Sie nicht
|
||||
mehr richtig moderieren können.</li>\n</ul>\n"
|
||||
livechat_configuration_applytomoderators_desc: "Standardmäßig sind Nachrichten von
|
||||
Moderatoren von dieser Funktion nicht betroffen.\nWenn Sie diese Option aktivieren,
|
||||
werden auch Nachrichten von Moderatoren gelöscht.\n"
|
||||
livechat_configuration_channel_special_chars_label: Sonderzeichen verbieten
|
||||
livechat_configuration_channel_special_chars_tolerance_label: Toleranz
|
||||
livechat_configuration_channel_special_chars_tolerance_desc: Anzahl der Sonderzeichen,
|
||||
die in einer Nachricht akzeptiert werden, ohne sie zu löschen.
|
||||
livechat_configuration_channel_special_chars_desc: "Wenn Sie diese Option aktivieren,
|
||||
wird der Moderationsbot automatisch Nachrichten löschen, die mehr als X Sonderzeichen
|
||||
enthalten.\nSonderzeichen sind Zeichen, die nicht in eine der folgenden Kategorien
|
||||
passen: Buchstaben, Zahlen, Satzzeichen, Währungssymbole, Emojis.\n"
|
||||
feature_comes_with: Diese Funktion wird mit dem Livechatplugin Version X.X.X verfügbar
|
||||
sein.
|
||||
livechat_configuration_channel_no_duplicate_label: Keine doppelten Nachrichten
|
||||
livechat_configuration_channel_no_duplicate_desc: "Wenn Sie diese Option aktivieren,
|
||||
wird der Chatbot automatisch doppelte Nachrichten moderieren.\nDas heißt, wenn ein
|
||||
Benutzer die gleiche Nachricht zweimal innerhalb von X Sekunden sendet, wird die
|
||||
zweite Nachricht gelöscht.\n"
|
||||
livechat_configuration_channel_no_duplicate_delay_label: Zeitintervall
|
||||
livechat_configuration_channel_no_duplicate_delay_desc: "Das Intervall in Sekunden,
|
||||
in dem ein Benutzer die gleiche Nachricht nicht erneut senden kann.\n"
|
||||
announcements_message_type: Art der Nachricht
|
||||
announcements_message_type_standard: Standard
|
||||
announcements_message_type_announcement: Ankündigung
|
||||
announcements_message_type_highlight: Highlight
|
||||
announcements_message_type_warning: Warnung
|
||||
|
@ -410,8 +410,8 @@ livechat_configuration_channel_forbidden_words_desc: |
|
||||
Several examples are provided on the documentation page.
|
||||
livechat_configuration_channel_forbidden_words_desc2: |
|
||||
One word or expression per line. If you put multiple words on one line, it will only match messages containing the whole sequence.
|
||||
livechat_configuration_channel_forbidden_words_reason_label: "Reason"
|
||||
livechat_configuration_channel_forbidden_words_reason_desc: "Reason to display besides
|
||||
livechat_configuration_channel_retractation_reason_label: "Reason"
|
||||
livechat_configuration_channel_retractation_reason_desc: "Reason to display besides
|
||||
deleted messages"
|
||||
livechat_configuration_channel_forbidden_words_regexp_label: "Consider as regular
|
||||
expressions"
|
||||
@ -420,10 +420,9 @@ livechat_configuration_channel_forbidden_words_regexp_desc: "By checking this op
|
||||
livechat_configuration_channel_forbidden_words_label_label: "Label"
|
||||
livechat_configuration_channel_forbidden_words_label_desc: "Label for this forbidden
|
||||
words rule"
|
||||
livechat_configuration_channel_forbidden_words_applytomoderators_label: "Also moderate
|
||||
messages from moderators"
|
||||
livechat_configuration_channel_forbidden_words_applytomoderators_desc: |
|
||||
By default, moderator messages will not be deleted when containing forbidden words.
|
||||
livechat_configuration_applytomoderators_label: "Also moderate messages from moderators"
|
||||
livechat_configuration_applytomoderators_desc: |
|
||||
By default, moderator messages will not be affected by this feature.
|
||||
By checking this option, messages from moderators will also be deleted.
|
||||
livechat_configuration_channel_forbidden_words_comments_label: "Comments"
|
||||
livechat_configuration_channel_forbidden_words_comments_desc: |
|
||||
@ -637,3 +636,44 @@ prosody_firewall_name_desc: |
|
||||
Can only contain: alphanumerical characters, underscores and hyphens.
|
||||
Scripts will be loaded in alphabetical order.
|
||||
prosody_firewall_content: File content
|
||||
|
||||
emoji_only_info: Emoji only mode is enabled, you can only use emoji in your messages.
|
||||
emoji_only_mode_title: Emojis only mode
|
||||
emoji_only_mode_desc_1: |
|
||||
You can enable an "Emojy only mode" in your chatrooms.
|
||||
When this mode is enabled, participants can only send emojis (standard, or channel custom emojis).
|
||||
Moderators are not affected by this limitation.
|
||||
emoji_only_mode_desc_2: |
|
||||
This mode can be usefull for example:
|
||||
<ul>
|
||||
<li>To avoid spam or offensive message when you are not here to moderate.</li>
|
||||
<li>When there are too many speaking participants, and you can't no more moderate correctly.</li>
|
||||
</ul>
|
||||
emoji_only_mode_desc_3: |
|
||||
To enable or disable this mode, you can use the room configuration form.
|
||||
If you want to enable it for all your chatrooms at once, you can use the button bellow.
|
||||
emoji_only_enable_all_rooms: Enable the emoji only mode on all channel's chatrooms
|
||||
|
||||
livechat_configuration_channel_special_chars_label: "Forbid special characters"
|
||||
livechat_configuration_channel_special_chars_desc: |
|
||||
By enabling this option, the moderation bot will automatically delete messages containing more than X special characters.
|
||||
Special characters are those that don't fit into one of these categories: letters, numbers, punctuation symbols, currency symbols, emojis.
|
||||
livechat_configuration_channel_special_chars_tolerance_label: Tolerance
|
||||
livechat_configuration_channel_special_chars_tolerance_desc: Number of special characters
|
||||
to accept in a message without deleting it.
|
||||
|
||||
feature_comes_with: This feature comes with the livechat plugin version X.X.X.
|
||||
|
||||
livechat_configuration_channel_no_duplicate_label: "No duplicate message"
|
||||
livechat_configuration_channel_no_duplicate_desc: |
|
||||
By enabling this options, the moderation bot will automatically moderate duplicate messages.
|
||||
That means if a user send the same message twice within X seconds, the second message will be deleted.
|
||||
livechat_configuration_channel_no_duplicate_delay_label: Time interval
|
||||
livechat_configuration_channel_no_duplicate_delay_desc: |
|
||||
The interval, in seconds, during which a user can't send again the same message.
|
||||
|
||||
announcements_message_type: Message type
|
||||
announcements_message_type_standard: Standard
|
||||
announcements_message_type_announcement: Announcement
|
||||
announcements_message_type_highlight: Highlight
|
||||
announcements_message_type_warning: Warning
|
||||
|
@ -261,18 +261,15 @@ livechat_configuration_channel_forbidden_words_label: Palabras o expresiones pro
|
||||
livechat_configuration_channel_forbidden_words_desc2: "Una palabra o expresión por
|
||||
línea. Si pones varias palabras en una línea, solo coincidirá con los mensajes que
|
||||
contengan la secuencia completa.\n"
|
||||
livechat_configuration_channel_forbidden_words_reason_label: Motivo
|
||||
livechat_configuration_channel_forbidden_words_reason_desc: Motivo para mostrar además
|
||||
livechat_configuration_channel_retractation_reason_label: Motivo
|
||||
livechat_configuration_channel_retractation_reason_desc: Motivo para mostrar además
|
||||
de eliminar los mensajes
|
||||
livechat_configuration_channel_forbidden_words_regexp_label: Considéralo como expresiones
|
||||
regulares
|
||||
livechat_configuration_channel_forbidden_words_regexp_desc: Marcando esta opción,
|
||||
puedes usar expresiones regulares.
|
||||
livechat_configuration_channel_forbidden_words_applytomoderators_label: Moderar también
|
||||
livechat_configuration_applytomoderators_label: Moderar también
|
||||
los mensajes de los moderadores
|
||||
livechat_configuration_channel_forbidden_words_applytomoderators_desc: "Por defecto,
|
||||
los mensajes de los moderadores no se borrarán cuando contengan palabras prohibidas.\n
|
||||
Al marcar esta opción, los mensajes de los moderadores también se eliminarán.\n"
|
||||
livechat_configuration_channel_forbidden_words_comments_label: Comentarios
|
||||
livechat_configuration_channel_quote_label: Temporizador
|
||||
livechat_configuration_channel_quote_desc: "Puedes configurar varios temporizadores
|
||||
|
@ -315,15 +315,15 @@ livechat_configuration_channel_forbidden_words_label: Mots ou expressions interd
|
||||
livechat_configuration_channel_forbidden_words_desc2: "Un mot ou une expression par
|
||||
ligne. Si vous mettez plusieurs mots sur une même ligne, seuls les messages contenant
|
||||
la séquence entière seront supprimés.\n"
|
||||
livechat_configuration_channel_forbidden_words_reason_label: Raison
|
||||
livechat_configuration_channel_forbidden_words_reason_desc: Raison à affiche à côté
|
||||
des messages supprimés
|
||||
livechat_configuration_channel_retractation_reason_label: Raison
|
||||
livechat_configuration_channel_retractation_reason_desc: Raison à affiche à côté des
|
||||
messages supprimés
|
||||
livechat_configuration_channel_forbidden_words_regexp_label: Considérer comme une
|
||||
expression régulière
|
||||
livechat_configuration_channel_forbidden_words_regexp_desc: En cochant cette option,
|
||||
vous pouvez utiliser des expressions régulières.
|
||||
livechat_configuration_channel_forbidden_words_applytomoderators_label: Également
|
||||
modérer les messages des modérateur⋅rices
|
||||
livechat_configuration_applytomoderators_label: Également modérer les messages des
|
||||
modérateur⋅rices
|
||||
livechat_configuration_channel_quote_label: Timer
|
||||
livechat_configuration_channel_quote_desc: "Vous pouvez configurer quelques timers
|
||||
qui enverrons des messages à intervalle régulier.\nCes messages seront envoyés par
|
||||
@ -351,9 +351,9 @@ livechat_configuration_channel_forbidden_words_desc: "Vous pouvez configurer que
|
||||
mots seront instantanément supprimés).\nVous pouvez aussi ajouter une raison optionnelle,
|
||||
qui sera affichée à la place des messages supprimés.\nQuelques exemples de configuration
|
||||
sont fournis sur la page de documentation.\n"
|
||||
livechat_configuration_channel_forbidden_words_applytomoderators_desc: "Par défaut,
|
||||
les messages des modérateur⋅rices ne seront pas supprimés quand ils contiennent
|
||||
des mots interdits.\nEn cochant cette option, leur messages seront également supprimé.\n"
|
||||
livechat_configuration_applytomoderators_desc: "Par défaut, les messages des modérateur⋅rices
|
||||
ne seront pas affectés par cette fonctionnalité.\nEn cochant cette option, leur
|
||||
messages seront également supprimés.\n"
|
||||
livechat_configuration_channel_command_desc: "Vous pouvez configurer le bot pour répondre
|
||||
à des commandes.\nUne commande est un message qui commence par un \"!\", comme par
|
||||
exemple \"!help\" qui appellera la commande \"help\".\n"
|
||||
@ -620,3 +620,36 @@ prosody_firewall_name_desc: "Ne peut contenir que des caractères alphanumériqu
|
||||
ordre alphabétique.\n"
|
||||
prosody_firewall_content: Contenu du fichier
|
||||
chat: Tchat
|
||||
feature_comes_with: Cette fonctionnalité arrive avec le plugin livechat version X.X.X.
|
||||
emoji_only_mode_title: Mode Émojis Uniquement
|
||||
emoji_only_mode_desc_3: "Pour activer ou désactiver ce mode, vous pouvez utiliser
|
||||
le formulaire de configuration du salon.\nSi vous souhaitez l'activer pour tous
|
||||
vos salons de discussion en même temps, vous pouvez utiliser le bouton ci-dessous.\n"
|
||||
livechat_configuration_channel_special_chars_label: Interdire les caractères spéciaux
|
||||
livechat_configuration_channel_special_chars_desc: "En activant cette option, le robot
|
||||
de modération supprimera automatiquement les messages contenant plus de X caractères
|
||||
spéciaux.\nLes caractères spéciaux sont ceux qui n'entrent pas dans l'une de ces
|
||||
catégories : lettres, chiffres, symboles de ponctuation, symboles monétaires, émojis.\n"
|
||||
livechat_configuration_channel_special_chars_tolerance_desc: Nombre de caractères
|
||||
spéciaux à accepter dans un message avant de le supprimer.
|
||||
livechat_configuration_channel_special_chars_tolerance_label: Tolérance
|
||||
livechat_configuration_channel_no_duplicate_delay_label: Intervalle de temps
|
||||
livechat_configuration_channel_no_duplicate_delay_desc: "Intervalle, en secondes,
|
||||
pendant lequel un⋅e utilisateur⋅rice ne peut pas renvoyer le même message.\n"
|
||||
emoji_only_info: Le mode Émoji Uniquement est activé, vous ne pouvez utiliser que
|
||||
des émoji dans vos messages.
|
||||
emoji_only_mode_desc_1: "Vous pouvez activer un mode \"Émoji Uniquement\" dans vos
|
||||
salons de discussion.\nLorsque ce mode est activé, les participant⋅es ne peuvent
|
||||
envoyer que des émojis (standards ou émojis personnalisés de votre chaîne).\nLes
|
||||
modérateur⋅rices ne sont pas concerné⋅es par cette limitation.\n"
|
||||
emoji_only_mode_desc_2: "Ce mode peut être utile par exemple :\n<ul>\n <li>Pour éviter
|
||||
le spam ou les messages offensants lorsque vous n'êtes pas là pour modérer.</li>\n\
|
||||
\ <li>Lorsqu'il y a trop de participant⋅es qui parlent, et que vous ne pouvez plus
|
||||
modérer correctement.</li>\n</ul>\n"
|
||||
emoji_only_enable_all_rooms: Activer le mode Émoji Uniquement sur tous les salons
|
||||
de discussion de la chaîne
|
||||
livechat_configuration_channel_no_duplicate_label: Pas de message en double
|
||||
livechat_configuration_channel_no_duplicate_desc: "En activant cette option, le robot
|
||||
de modération modérera automatiquement les messages en double.\nCela signifie que
|
||||
si un⋅e utilisateur⋅rice envoie deux fois le même message en l'espace de X secondes,
|
||||
le deuxième message sera supprimé.\n"
|
||||
|
@ -54,17 +54,13 @@ livechat_configuration_channel_forbidden_words_regexp_label: Tretiraj kao regula
|
||||
izraze
|
||||
livechat_configuration_channel_forbidden_words_regexp_desc: Označavanjem ove opcije
|
||||
možeš koristiti regularne izraze.
|
||||
livechat_configuration_channel_forbidden_words_applytomoderators_label: Također moderiraj
|
||||
porukama moderatora
|
||||
livechat_configuration_applytomoderators_label: Također moderiraj porukama moderatora
|
||||
livechat_configuration_channel_quote_label: Timer
|
||||
livechat_configuration_channel_forbidden_words_desc: "Možeš konfigurirati neke riječi
|
||||
koje će bot automatski moderirati (poruke koje sadrže takve riječi će se odmah izbrisati).\n
|
||||
Možeš dodati i opcionalni razlog koji će se prikazati na mjestu izbrisanih poruka.\n
|
||||
Stranica dokumentacije sadrži nekoliko primjera.\n"
|
||||
livechat_configuration_channel_forbidden_words_reason_label: Razlog
|
||||
livechat_configuration_channel_forbidden_words_applytomoderators_desc: "Poruke moderatora
|
||||
se standardno neće izbrisati ako sadrže zabranjene riječi.\nOznačavanjem ove opcije
|
||||
će se poruke moderatora također izbrisati.\n"
|
||||
livechat_configuration_channel_retractation_reason_label: Razlog
|
||||
slow_mode_info: Spori način rada je omogućen. Korisnici mogu slati poruku svakih %1$s
|
||||
sekundi.
|
||||
open_chat: Otvori chat
|
||||
@ -344,3 +340,88 @@ poll_choice_invalid: Ovaj izbor nije valjan.
|
||||
poll_anonymous_vote_ok: Tvoje se glasanje uzima u obzir. Glasanja su anonimna, neće
|
||||
se prikazati drugim sudionicima.
|
||||
poll_vote_ok: Tvoje se glasanje uzima u obzir. Brojači će se uskoro aktualizirati.
|
||||
feature_comes_with: Ova je funkcija dostupna s dodatkom za chat uživo verzije X.X.X.
|
||||
emoji_only_info: Kad je modus „Samo emoji” aktiviran, u svojim porukama možeš koristiti
|
||||
samo emojije.
|
||||
emoji_only_mode_title: „Samo emoji” modus
|
||||
emoji_only_mode_desc_1: "U svojim chat sobama možeš aktivirati „Samo emoji” modus.\n
|
||||
Kad je taj modus aktiviran, sudionici mogu slati samo emojije (standardne ili prilagođene
|
||||
emojije kanala).\nOvo ograničenje ne vrijedi za moderatore.\n"
|
||||
emoji_only_mode_desc_3: "Za aktiviranje ili deaktiviranje ovog modusa možeš koristiti
|
||||
obrazac za konfiguraciju sobe.\nAko ga želiš aktivirati za sve svoje chat sobe odjednom
|
||||
koristi donji gumb.\n"
|
||||
emoji_only_enable_all_rooms: Aktiviraj „Samo emoji” modus u svim chat sobama kanala
|
||||
task_list_pick_message: "Nakon što odabereš popis zadataka, stvorit će se novi zadatak.\n
|
||||
Za prikaz zadatka otvori aplikaciju zadataka koristeći gornji izbornik.\nViše informacija
|
||||
u dokumentaciji dodatka za chat uživo.\n"
|
||||
livechat_configuration_channel_retractation_reason_desc: Razlog koji se prikazuje
|
||||
pored izbrisanih poruka
|
||||
livechat_configuration_applytomoderators_desc: "Ova funkcija standardno neće utjecati
|
||||
na poruke moderatora.\nOznačavanjem ove opcije će se izbrisati i poruke moderatora.\n"
|
||||
livechat_configuration_channel_special_chars_label: Zabrani posebne znakove
|
||||
livechat_configuration_channel_special_chars_tolerance_label: Tolerancija
|
||||
livechat_configuration_channel_special_chars_tolerance_desc: Dozvoljen broj posebnih
|
||||
znakova u poruci bez da se poruka izbriše.
|
||||
livechat_configuration_channel_no_duplicate_label: Bez duplih poruka
|
||||
livechat_configuration_channel_no_duplicate_delay_label: Vremenski interval
|
||||
livechat_configuration_channel_no_duplicate_delay_desc: "Interval u sekundama, tijekom
|
||||
kojeg korisnik ne može ponovo poslati istu poruku.\n"
|
||||
livechat_configuration_channel_no_duplicate_desc: "Aktiviranjem ove opcije, bot za
|
||||
moderiranje će automatski moderirati duple poruke.\nTo znači da ako korisnik pošalje
|
||||
istu poruku dva puta unutar X sekundi, druga će se poruka izbrisati.\n"
|
||||
livechat_configuration_channel_anonymize_moderation_label: Anonimiziraj radnje moderiranja
|
||||
livechat_configuration_channel_anonymize_moderation_desc: "Standardna vrijednost za
|
||||
anonimiziranje radnji moderiranja za nove sobe.\nKada je ovo aktivirano, radnje
|
||||
moderiranja bit će anonimizirane, kako bi se izbjeglo otkrivanje tko isključuje/izbacuje/…
|
||||
sudionike.\n"
|
||||
announcements_message_type: Vrsta poruke
|
||||
announcements_message_type_standard: Standardna
|
||||
announcements_message_type_announcement: Najava
|
||||
announcements_message_type_highlight: Istaknuto
|
||||
announcements_message_type_warning: Upozorenje
|
||||
moderator_notes_create_error: Greška prilikom spremanja bilješke
|
||||
moderator_note_create: Stvori novu bilješku
|
||||
moderator_note_delete: Izbriši bilješku
|
||||
moderator_note_description: Opis
|
||||
moderator_note_delete_confirm: Stvarno želiš izbrisati ovu bilješku?
|
||||
moderator_note_create_for_participant: Stvori novu bilješku
|
||||
moderator_note_search_for_participant: Pretraži bilješke
|
||||
moderator_note_filters: Filtri pretrage
|
||||
moderator_note_original_nick: Nadimak sudionika u trenutku izrade bilješke
|
||||
avatar_set_option_none: Bez
|
||||
search_occupant_message: Pretraži sve poruke
|
||||
message_search: Pretraživanje poruka
|
||||
moderator_notes: Bilješke moderiranja
|
||||
livechat_configuration_channel_mute_anonymous_desc: "Standardna vrijednost za nove
|
||||
chat sobe.\nZa postojeće chat sobe funkciju možeš promijeniti u obrascu za konfiguraciju
|
||||
sobe.\nKada je ova funkcija aktivirana, anonimni korisnici mogu samo čitati chat,
|
||||
ali ne mogu slati poruke.\n"
|
||||
emoji_only_mode_desc_2: "Ovaj modus može biti koristan na primjer:\n<ul>\n <li>za
|
||||
izbjegavanje neželjenih ili uvredljivih poruka kada nisi tu da moderiraš.</li>\n\
|
||||
\ <li>kada postoji previše sudionika koji govore i ne možeš više ispravno moderirati.</li>\n\
|
||||
</ul>\n"
|
||||
livechat_configuration_channel_special_chars_desc: "Aktiviranjem ove opcije, bot za
|
||||
moderiranje će automatski izbrisati poruke koje sadrže više od X posebnih znakova.\n
|
||||
Posebni znakovi su znakovi koji se ne uklapaju u nijednu od ovih kategorija: slova,
|
||||
brojevi, znakovi interpunkcije, znakovi valuta, emojiji.\n"
|
||||
message_search_original_nick: Nadimak sudionika kada je poruka poslana
|
||||
prosody_firewall_label: Aktiviraj Prosody mod_firewall
|
||||
prosody_firewall_description: "<a href=\"https://modules.prosody.im/mod_firewall\"\
|
||||
\ target=\"_blank\">mod_firewall</a> možeš aktivirati na svom Prosody serveru.\n
|
||||
Za više informacija pročitaj <a href=\"https://livingston.frama.io/peertube-plugin-livechat/documentation/admin/mod_firewall/\"\
|
||||
\ target=\"_blank\">dokumentaciju</a>.\n"
|
||||
prosody_firewall_configure_button: "<a class=\"peertube-button-link orange-button\"\
|
||||
\ href=\"/p/livechat/admin/firewall\" target=\"_blank\">Kofiguriraj mod_firewall</a>\n"
|
||||
prosody_firewall_configuration: Prosody mod_firewall konfiguracija
|
||||
prosody_firewall_configuration_help: "Ovdje možeš konfigurirati modul Prosody <a href=\"\
|
||||
https://modules.prosody.im/mod_firewall\" target=\"_blank\">mod_firewall</a>.\n
|
||||
U nastavku možeš stvoriti više konfiguracijskih datoteka i promijeniti njihov redoslijed.\n
|
||||
Nemoj se ustručavati dijeliti svoje konfiguracije sa zajednicom (na primjer dodavanjem
|
||||
nekih primjera u dokumentaciju dodatka).\n"
|
||||
prosody_firewall_file_enabled: Aktivirano
|
||||
prosody_firewall_name: Ime
|
||||
prosody_firewall_content: Sadržaj datoteke
|
||||
share_chat_dock: Dock
|
||||
chat: Chat
|
||||
prosody_firewall_name_desc: "Smije sadržati samo alfanumeričke znakove, podvlake i
|
||||
crtice.\nSkripta će se učitavati abecednim redom.\n"
|
||||
|
@ -154,8 +154,6 @@ auto_ban_anonymous_ip_description: "このオプションを有効にすると
|
||||
livechat_configuration_channel_desc: このチャンネルの設定を開始できます(モデレートポリシーなど)。
|
||||
livechat_configuration_channel_forbidden_words_desc: "Botにより自動的にモデレーションする単語を設定できます(単語が含まれるメッセージは即座に削除されます)。\n\
|
||||
削除されたメッセージの箇所に表示する、削除された理由を設定することもできます。\nいくつかの例をドキュメントで提供していますので、必要があれば確認してください。\n"
|
||||
livechat_configuration_channel_forbidden_words_applytomoderators_desc: "デフォルトでは、モデレーターのメッセージは禁止単語・語句が含まれていても削除されません。\n\
|
||||
このオプションを有効にすると、モデレーターからのメッセージも削除されるようになります。\n"
|
||||
save: 保存
|
||||
prosody_components_interfaces_description: "外部コンポーネント接続のためにリッスンするネットワークインターフェイスです。<br>\n
|
||||
リッスンするIPのリストをコンマ区切りで入力してください。(スペースは除かれます)<br>\n全IPv4インタフェースでリッスンする場合は、 «*»、IPv6の場合は、«::»
|
||||
@ -177,11 +175,11 @@ livechat_configuration_channel_enable_bot_label: モデレーターBotを有効
|
||||
livechat_configuration_channel_bot_options_title: モデレーターBotの設定
|
||||
livechat_configuration_channel_forbidden_words_label: 禁止単語または語句
|
||||
livechat_configuration_channel_forbidden_words_desc2: "単語または語句を1行ごとに記載してください。複数の単語を1行に記載した場合、記載したすべての内容を含むメッセージのみが一致するようになります。\n"
|
||||
livechat_configuration_channel_forbidden_words_reason_label: 理由
|
||||
livechat_configuration_channel_forbidden_words_reason_desc: 削除理由
|
||||
livechat_configuration_channel_retractation_reason_label: 理由
|
||||
livechat_configuration_channel_retractation_reason_desc: 削除理由
|
||||
livechat_configuration_channel_forbidden_words_regexp_label: 正規表現
|
||||
livechat_configuration_channel_forbidden_words_regexp_desc: このオプションを有効にすると、正規表現として処理します。
|
||||
livechat_configuration_channel_forbidden_words_applytomoderators_label: モデレーターからのメッセージも対象にする
|
||||
livechat_configuration_applytomoderators_label: モデレーターからのメッセージも対象にする
|
||||
livechat_configuration_channel_quote_label: タイマー
|
||||
livechat_configuration_channel_quote_desc2: "メッセージ1つに対して1行で入力してください。\n複数のメッセージが入力されている場合、X分おきにランダムに選択されます。\n"
|
||||
livechat_configuration_channel_command_message_label: メッセージ
|
||||
@ -246,7 +244,7 @@ login_remote_peertube_url: あなたのPeerTubeインスタンスURL
|
||||
login_remote_peertube_url_invalid: 無効なPeerTube URLです。
|
||||
login_remote_peertube_no_livechat: このPeerTubeインスタンスには、ライブチャットプラグインがインストールされていません。
|
||||
login_remote_peertube_video_not_found_try_anyway_button: PeerTubeインスタンスで動画を開く
|
||||
livechat_configuration_channel_anonymize_moderation_label: モデレーション操作を匿名か
|
||||
livechat_configuration_channel_anonymize_moderation_label: モデレーション操作を匿名化
|
||||
moderator_notes_create_error: ノートを保存中にエラーが発生しました
|
||||
moderator_note_create: 新規ノートを作成
|
||||
moderator_note_description: 概要
|
||||
@ -377,3 +375,29 @@ invalid_value_duplicate: 重複した値です
|
||||
livechat_configuration_channel_emojis_desc: "チャンネルのカスタム絵文字を設定できます。\nこれらの絵文字は、絵文字ピッカーから利用できます。\n\
|
||||
絵文字は、短縮名でも使用できます。(例えば\":shortname:\")\n"
|
||||
chat: チャット
|
||||
emoji_only_mode_title: 絵文字のみモード
|
||||
emoji_only_mode_desc_1: "チャットルームで\"絵文字のみモード\"を有効にできます。\nこのモードが有効な時は、参加者は絵文字(標準またはチャンネル専用のカスタム絵文字)のみを送信できます。\n\
|
||||
モデレーターはこの制限の影響を受けません。\n"
|
||||
emoji_only_enable_all_rooms: 絵文字のみモードを全てのチャンネルのチャットルームに適用する
|
||||
emoji_only_info: 絵文字のみモードが有効な場合、メッセージ内で絵文字のみ使用できます。
|
||||
emoji_only_mode_desc_2: "このモードは次のような場合に効果があります:\n<ul>\n <li>スパムまたはモデレーターがいない場合に攻撃的なメッセージを抑止するため。</li>\n\
|
||||
\ <li>多数の参加者が存在し、正確なモデレーションが実施できない場合のため。</li>\n</ul>\n"
|
||||
livechat_configuration_channel_special_chars_tolerance_label: 許容範囲
|
||||
emoji_only_mode_desc_3: "この機能の設定切り替えは、ルーム設定フォームから可能です。\n一度にあなたの全てのチャットルームにこの設定を適用する場合は、以下のボタンをクリックしてください。\n"
|
||||
livechat_configuration_channel_special_chars_desc: "このオプションを有効にすると、モデレーションBotは、X文字以上の特殊文字が含まれるメッセージを自動的に削除します。\n\
|
||||
特殊文字は、文字、数字、句読点記号、通貨記号、絵文字のいずれにも当てはまらないものを指します。\n"
|
||||
livechat_configuration_applytomoderators_desc: "初期状態では、モデレーターメッセージはこの機能の影響を受けません。\n\
|
||||
このオプションを有効にすると、モデレーターからのメッセージも削除されるようになります。\n"
|
||||
livechat_configuration_channel_special_chars_label: 特殊文字を禁止する
|
||||
livechat_configuration_channel_special_chars_tolerance_desc: 削除しない特殊文字の文字数を指定してください。
|
||||
feature_comes_with: この機能は、livechatプラグイン バージョン X.X.Xにて登場します。
|
||||
livechat_configuration_channel_no_duplicate_delay_label: 間隔
|
||||
livechat_configuration_channel_no_duplicate_delay_desc: "ユーザーが同じメッセージを再度送信するために空けなければいけない時間を秒単位で指定してください。\n"
|
||||
livechat_configuration_channel_no_duplicate_label: 重複したメッセージはありません
|
||||
livechat_configuration_channel_no_duplicate_desc: "このオプションを有効にすると、モデレーションBotが重複したメッセージに自動的に対応します。\n\
|
||||
これは、もしユーザーが同じメッセージX秒以内に2回送信した場合に、2回目のメッセージを削除します。\n"
|
||||
announcements_message_type: メッセージタイプ
|
||||
announcements_message_type_standard: 標準
|
||||
announcements_message_type_announcement: アナウンス
|
||||
announcements_message_type_highlight: ハイライト
|
||||
announcements_message_type_warning: 警告
|
||||
|
@ -61,7 +61,7 @@ task_name: Nazwa zadania
|
||||
avatar_set_option_cat: Koty
|
||||
task_description: Opis
|
||||
task_delete: Usuń zadanie
|
||||
livechat_configuration_channel_forbidden_words_reason_label: Powód
|
||||
livechat_configuration_channel_retractation_reason_label: Powód
|
||||
livechat_configuration_channel_forbidden_words_label_label: Etykieta
|
||||
copied: Skopiowano
|
||||
autocolors_label: Automatyczne wykrywanie kolorów
|
||||
@ -72,3 +72,52 @@ poll: Ankieta
|
||||
poll_question: Pytanie
|
||||
poll_anonymous_results: Anonimowe wyniki
|
||||
poll_choice_n: 'Wybór {{N}}:'
|
||||
external_auth_custom_oidc_title: <h4>OpenID Connect</h4>
|
||||
external_auth_custom_oidc_label: Użycie dostawcy OpenID Connect
|
||||
external_auth_custom_oidc_button_label_label: Etykieta przycisku połączenia
|
||||
federation_dont_publish_remotely_label: Nie publikuj informacji o czacie
|
||||
federation_no_remote_chat_description: "Zaznaczenie tego ustawienia spowoduje, że
|
||||
instancja nigdy nie będzie wyświetlać czatów ze zdalnych filmów.\n"
|
||||
transparent_background: Przezroczyste tło (do integracji z transmisją, na przykład
|
||||
z OBS)
|
||||
external_auth_custom_oidc_button_label_description: Ta etykieta będzie wyświetlana
|
||||
użytkownikom jako etykieta przycisku do uwierzytelniania u tego dostawcy OIDC.
|
||||
chat: Czat
|
||||
external_auth_description: "<h3>Uwierzytelnianie zewnętrzne</h3>\nDla użytkowników,
|
||||
którzy nie mają konta Peertube, można włączyć różne tryby uwierzytelniania w oparciu
|
||||
o zdalnych dostawców uwierzytelniania.\n"
|
||||
external_auth_custom_oidc_description: "Można skonfigurować zewnętrznego dostawcę
|
||||
OpenID Connect, który może być używany do logowania się do czatu.\nWięcej informacji
|
||||
można znaleźć w dokumentacji:\n<a href=\"https://livingston.frama.io/peertube-plugin-livechat/documentation/admin/settings/\"\
|
||||
\ target=\"_blank\">Ustawienia</a>.\n"
|
||||
chat_terms_label: Regulamin
|
||||
chat_terms_description: "Poniższy regulamin będzie wyświetlany wszystkim użytkownikom
|
||||
po dołączeniu do pokojów rozmów.\nStreamerzy mogą również skonfigurować swoje regulaminy
|
||||
dla swoich kanałów, które będą wyświetlane zaraz po tych globalnych zasadach i warunkach.\n"
|
||||
use_current_theme_color: Użyj bieżących kolorów motywu
|
||||
federation_dont_publish_remotely_description: "Zaznaczenie tego ustawienia spowoduje,
|
||||
że Twoja instancja nie będzie publikować informacji o czacie na fediwersum.\nZdalne
|
||||
instancje Peertube nie będą wiedziały, że są czatami powiązanymi z Twoimi filmami.<br>\n
|
||||
<b>Uwaga</b>: jeśli już istnieją czaty, możliwe, że informacje zostały już opublikowane.\n
|
||||
Będziesz musiał poczekać na następną aktualizację wideo, zanim informacje zostaną
|
||||
cofnięte.\nPonadto, jeśli wyłączysz to ustawienie, będziesz musiał poczekać na aktualizację
|
||||
filmów, zanim informacje\nzostaną ponownie opublikowane. Aktualizacja ta następuje
|
||||
między innymi po wznowieniu lub zakończeniu transmisji na żywo.<br>\n<b>Uwaga</b>:
|
||||
to ustawienie wpływa tylko na publikację informacji za pośrednictwem protokołu ActivityPub.\n
|
||||
Nie zapobiegnie ono wykryciu obecności czatu i próby połączenia się z nim przez
|
||||
zdalną aplikację.\n"
|
||||
tips_for_streamers: "Wskazówki dla streamerów: Aby osadzić czat w swojej transmisji,
|
||||
np. w OBS,\nwygeneruj link tylko do odczytu i użyj go jako źródła przeglądarki.\n"
|
||||
generate_iframe: Generuj kod iframe, aby osadzić czat na stronie internetowej
|
||||
chat_for_live_stream: 'Czat transmisji:'
|
||||
web: Przeglądarka
|
||||
connect_using_xmpp_help: Z pokojem można połączyć się za pomocą zewnętrznego konta
|
||||
XMPP i ulubionego klienta XMPP.
|
||||
important_note_text: "Dokumentację wtyczki można znaleźć tutaj:\n<a href=\"https://livingston.frama.io/peertube-plugin-livechat/\"\
|
||||
\ target=\"_blank\">\n Peertube Plugin Livechat documentation\n</a>.\n"
|
||||
diagnostic: "Przed zwróceniem się o pomoc, skorzystaj z narzędzia diagnostycznego:\n
|
||||
<a class=\"peertube-plugin-livechat-launch-diagnostic\">Uruchom diagnostykę</a>\n
|
||||
(jeśli ten przycisk nie otwiera nowego okna, spróbuj odświeżyć stronę).\n"
|
||||
federation_no_remote_chat_label: Nie wyświetlaj zdalnych czatów
|
||||
federation_description: "<h3>Federacja</h3>\nPoniższe ustawienia dotyczą federacji
|
||||
z innymi instancjami Peertube,\ni innym oprogramowaniem fediwersum.\n"
|
||||
|
@ -87,7 +87,7 @@ prosody_muc_expiration_description: "Këtu mund të zgjidhni se për sa kohë mb
|
||||
të ruhet për 1 <b>vit</b>. 1-shin mund ta zëvendësoni me çfarëdo vlere numër të
|
||||
plotë.</li>\n <li><b>never</b>: lënda nuk skadon kurrë dhe do të mbahet përgjithnjë.</li>\n\
|
||||
</ul>\n"
|
||||
livechat_configuration_channel_forbidden_words_reason_label: Arsye
|
||||
livechat_configuration_channel_retractation_reason_label: Arsye
|
||||
livechat_configuration_channel_forbidden_words_comments_label: Komente
|
||||
livechat_configuration_channel_quote_label2: Mesazhe
|
||||
livechat_configuration_channel_command_message_desc: Mesazhi për t’u dërguar.
|
||||
@ -246,3 +246,7 @@ poll_anonymous_vote_ok: Vota juaj është marrë parasysh. Votat janë anonime,
|
||||
poll_vote_ok: Vota juaj është marrë parasysh, numëratorët do të përditësohen në çast.
|
||||
moderator_notes: Shënime moderimi
|
||||
chat: Fjalosje
|
||||
converse_theme_option_cyberpunk: Temë ConverseJS Cyberpunk
|
||||
livechat_configuration_channel_special_chars_tolerance_desc: Numër shenjash speciale
|
||||
për t’u pranuar në një mesazh, pa e fshirë atë.
|
||||
announcements_message_type: Lloj mesazhi
|
||||
|
@ -37,7 +37,7 @@ menu_configuration_label: Chattrum
|
||||
livechat_configuration_title: Anpassa din sändnings chattrum
|
||||
livechat_configuration_channel_title: Kanalinställningar
|
||||
livechat_configuration_channel_forbidden_words_label: Förbjudna ord och uttryck
|
||||
livechat_configuration_channel_forbidden_words_reason_label: Anledning
|
||||
livechat_configuration_channel_retractation_reason_label: Anledning
|
||||
livechat_configuration_channel_quote_label: Timer
|
||||
livechat_configuration_channel_quote_delay_label: Skicka var X:e minut
|
||||
livechat_configuration_channel_command_message_desc: Meddelandet som ska skickas.
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user