Using ChannelConfigurationElement + Adding TranslationDirective

and a lots of fixes
This commit is contained in:
Mehdi Benadel 2024-05-13 16:40:36 +02:00
parent 0fe9fb4dca
commit 9ea26dfd26
11 changed files with 420 additions and 117 deletions

View File

@ -61,6 +61,8 @@ declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_REGEXP_LABEL: s
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_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
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_COMMENTS_DESC: string
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_COMMAND_LABEL: string

View File

@ -4,8 +4,8 @@
import type { RegisterClientOptions } from '@peertube/peertube-types/client'
import { renderConfigurationHome } from './templates/home'
import { renderConfigurationChannel } from './templates/channel'
import { render } from 'lit'
import './templates/ChannelConfigurationElement'
import { html, render } from 'lit'
/**
* Registers stuff related to the user's configuration pages.
@ -29,7 +29,7 @@ async function registerConfiguration (clientOptions: RegisterClientOptions): Pro
onMount: async ({ rootEl }) => {
const urlParams = new URLSearchParams(window.location.search)
const channelId = urlParams.get('channelId') ?? ''
render(await renderConfigurationChannel(clientOptions, channelId, rootEl), rootEl)
render(html`<channel-configuration .registerClientOptions=${clientOptions}></channel-configuration>`, rootEl)
}
})

View File

@ -1,42 +1,146 @@
import { RegisterClientOptions } from '@peertube/peertube-types/client'
import { html, LitElement } from 'lit'
import { repeat } from 'lit-html/directives/repeat.js'
import { customElement, property } from 'lit/decorators.js'
import { ptTr } from './TranslationDirective'
import { localizedHelpUrl } from '../../../utils/help'
import './DynamicTableFormElement'
import './PluginConfigurationRow'
import { until } from 'async'
import { Task } from '@lit/task';
@customElement('channel-configuration')
export class ChannelConfigurationElement extends LitElement {
@property()
public list: string[] = ["foo", "bar", "baz"]
@property()
public newEl: string = 'change_me'
@property({ attribute: false })
public registerClientOptions: RegisterClientOptions | undefined
createRenderRoot = () => {
return this
}
render() {
return html`
<ul>
${repeat(this.list, (el: string, index) => html`<li>${el}<button @click=${this._removeFromList(index)}>remove</button></li>`
)}
<li><input .value=${this.newEl}/><button @click=${this._addToList(this.newEl)}>add</button></li>
</ul>
`
}
private _asyncTaskRender = new Task(this, {
private _addToList(newEl: string) {
return () => {
this.list.push(newEl)
this.requestUpdate('list')
}
}
task: async ([registerClientOptions], {signal}) => {
let link = registerClientOptions ? await localizedHelpUrl(registerClientOptions, { page: 'documentation/user/streamers/bot/forbidden_words' }) : '';
private _removeFromList(index: number) {
return () => {
this.list.splice(index, 1)
this.requestUpdate('list')
return {
url : new URL(link),
title: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_DESC)
}
},
args: () => [this.registerClientOptions]
});
render = () => {
let tableHeader = {
words: {
colName: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_LABEL),
description: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_DESC2)
},
regex: {
colName: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_REGEXP_LABEL),
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)
},
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)
},
comments: {
colName: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_COMMENTS_LABEL),
description: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_COMMENTS_DESC)
}
}
let tableSchema = {
words: {
inputType: 'text',
default: 'helloqwesad'
},
regex: {
inputType: 'text',
default: 'helloaxzca'
},
applyToModerators: {
inputType: 'checkbox',
default: true
},
label: {
inputType: 'text',
default: 'helloasx'
},
reason: {
inputType: 'select',
default: 'transphobia',
label: 'choose your poison',
options: {'racism': 'Racism', 'sexism': 'Sexism', 'transphobia': 'Transphobia', 'bigotry': 'Bigotry'}
},
comments: {
inputType: 'textarea',
default: `Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit anim id est laborum.`
},
}
let tableRows = [
{
words: 'teweqwst',
regex: 'tesdgst',
applyToModerators: false,
label: 'teswet',
reason: 'sexism',
comments: 'tsdaswest',
},
{
words: 'tedsadst',
regex: 'tezxccst',
applyToModerators: true,
label: 'tewest',
reason: 'racism',
comments: 'tesxzct',
},
{
words: 'tesadsdxst',
regex: 'dsfsdf',
applyToModerators: false,
label: 'tesdadst',
reason: 'bigotry',
comments: 'tsadest',
},
]
return this._asyncTaskRender.render({
complete: (helpLink) => html`
<div class="container">
<channel-configuration></channel-configuration>
<plugin-configuration-row
.title=${ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_LABEL)}
.description=${ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_DESC)}
.helpLink=${helpLink}
>
<dynamic-table-form
.header=${tableHeader}
.schema=${tableSchema}
.rows=${tableRows}
.formName=${'forbidden-words'}
>
</dynamic-table-form>
</plugin-configuration-row>
</div>`
})
}
}

View File

@ -1,6 +1,9 @@
import { html, LitElement, TemplateResult } from 'lit'
import { repeat } from 'lit/directives/repeat.js'
import { customElement, property, state } from 'lit/decorators.js'
import { unsafeHTML } from 'lit/directives/unsafe-html.js'
import { ifDefined } from 'lit/directives/if-defined.js'
import { StaticValue, unsafeStatic } from 'lit/static-html.js'
type DynamicTableAcceptedTypes = number | string | boolean | Date
@ -41,7 +44,7 @@ interface CellDataSchema {
export class DynamicTableFormElement extends LitElement {
@property({ attribute: false })
public header: { [key : string]: TemplateResult<1> } = {}
public header: { [key : string]: { colName: TemplateResult, description: TemplateResult } } = {}
@property({ attribute: false })
@ -87,55 +90,51 @@ export class DynamicTableFormElement extends LitElement {
render = () => {
const inputId = `peertube-livechat-${this.formName.replaceAll('_','-')}-table`
for(let row of this.rows) {
if (!row._id) {
row._id = this._lastRowId++
}
}
return html`
<div class="row mt-5">
<div class="col-12 col-lg-4 col-xl-3">
<h2>Bot command #1</h2>
<p>You can configure the bot to respond to commands. A command is a message starting with a "!", like for example "!help" that calls the "help" command. For more information about how to configure this feature, please refer to the documentation by clicking on the help button.</p>
<a href="https://livingston.frama.io/peertube-plugin-livechat/documentation/user/streamers/bot/commands/" target="_blank" title="Online help" class="orange-button peertube-button-link">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 4.233 4.233">
<path style="display:inline;opacity:.998;fill:none;fill-opacity:1;stroke:currentColor;stroke-width:.529167;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M1.48 1.583V.86c0-.171.085-.31.19-.31h.893c.106 0 .19.139.19.31v.838c0 .171-.107.219-.19.284l-.404.314c-.136.106-.219.234-.221.489l-.003.247"></path>
<path style="display:inline;fill:currentColor;stroke-width:.235169" d="M1.67 3.429h.529v.597H1.67z"></path>
</svg>
</a>
</div>
<div class="col-12 col-lg-8 col-xl-9">
<table class="table table-striped table-hover table-sm" id=${inputId}>
${this._renderHeader()}
<tbody>
${repeat(this.rows, this._renderDataRow)}
</tbody>
<tfoot>
<tr><td><button @click=${this._addRow}>Add Row</button></td></tr>
</tfoot>
</table>
</div>
</div>
${JSON.stringify(this.rows)}
<table class="table table-striped table-hover table-sm" id=${inputId}>
${this._renderHeader()}
<tbody>
${repeat(this.rows,(row) => row._id, this._renderDataRow)}
</tbody>
<tfoot>
<tr><td><button @click=${this._addRow}>Add Row</button></td></tr>
</tfoot>
</table>
`
}
private _renderHeader = () => {
return html`<thead><tr><th scope="col">#</th>${Object.values(this.header).map(this._renderHeaderCell)}<th scope="col">Remove Row</th></tr></thead>`
return html`<thead>
<tr>
<!-- <th scope="col">#</th> -->
${Object.values(this.header).map(this._renderHeaderCell)}
<th scope="col">Remove Row</th>
</tr>
</thead>`
}
private _renderHeaderCell = (headerCellData: TemplateResult<1> | any) => {
return html`<th scope="col">${headerCellData}</th>`
private _renderHeaderCell = (headerCellData: { colName: TemplateResult, description: TemplateResult }) => {
return html`<th scope="col">
<div data-toggle="tooltip" data-placement="bottom" data-html="true" title="${headerCellData.description}">${headerCellData.colName}</div>
</th>`
}
private _renderDataRow = (rowData: { _id: number; [key : string]: DynamicTableAcceptedTypes }) => {
if (!rowData._id) {
rowData._id = this._lastRowId++
}
const inputId = `peertube-livechat-${this.formName.replaceAll('_','-')}-row-${rowData._id}`
return html`<tr id=${inputId}><td class="form-group">${rowData._id}</td>${repeat(Object.entries(rowData).filter(([k,v]) => k != '_id'), (data) => this.renderDataCell(data, rowData._id))}<td class="form-group"><button @click=${() => this._removeRow(rowData._id)}>Remove</button></td></tr>`
return html`<tr id=${inputId}>
<!-- <td class="form-group">${rowData._id}</td> -->
${Object.entries(rowData).filter(([k,v]) => k != '_id').map((data) => this.renderDataCell(data, rowData._id))}
<td class="form-group"><button @click=${() => this._removeRow(rowData._id)}>Remove</button></td>
</tr>`
}
@ -175,12 +174,12 @@ export class DynamicTableFormElement extends LitElement {
name=${inputName}
class="form-control"
id=${inputId}
min=${propertySchema?.min}
max=${propertySchema?.max}
minlength=${propertySchema?.minlength}
maxlength=${propertySchema?.maxlength}
@oninput=${(event: InputEvent) => this._updatePropertyFromValue(event, propertyName, rowId)}
.value=${propertyValue}
min=${ifDefined(propertySchema?.min)}
max=${ifDefined(propertySchema?.max)}
minlength=${ifDefined(propertySchema?.minlength)}
maxlength=${ifDefined(propertySchema?.maxlength)}
@input=${(event: InputEvent) => this._updatePropertyFromValue(event, propertyName, rowId)}
.value=${propertyValue as string}
/>`
break
@ -189,19 +188,26 @@ export class DynamicTableFormElement extends LitElement {
name=${inputName}
class="form-control"
id=${inputId}
min=${propertySchema?.min}
max=${propertySchema?.max}
minlength=${propertySchema?.minlength}
maxlength=${propertySchema?.maxlength}
@oninput=${(event: InputEvent) => this._updatePropertyFromValue(event, propertyName, rowId)}
.value=${propertyValue}
min=${ifDefined(propertySchema?.min)}
max=${ifDefined(propertySchema?.max)}
minlength=${ifDefined(propertySchema?.minlength)}
maxlength=${ifDefined(propertySchema?.maxlength)}
@input=${(event: InputEvent) => this._updatePropertyFromValue(event, propertyName, rowId)}
.value=${propertyValue as string}
></textarea>`
break
case 'select':
formElement = html`<select class="form-select" aria-label="Default select example">
formElement = html`<select
class="form-select"
aria-label="Default select example"
@change=${(event: InputEvent) => this._updatePropertyFromValue(event, propertyName, rowId)}
>
<option ?selected=${!propertyValue}>${propertySchema?.label ?? 'Choose your option'}</option>
${Object.entries(propertySchema?.options ?? {})?.map(([value,name]) => html`<option ?selected=${propertyValue === value} value=${value}>${name}</option>`)}
${Object.entries(propertySchema?.options ?? {})
?.map(([value,name]) =>
html`<option ?selected=${propertyValue === value} value=${value}>${name}</option>`
)}
</select>`
break
@ -222,12 +228,12 @@ export class DynamicTableFormElement extends LitElement {
name=${inputName}
class="form-control"
id=${inputId}
min=${propertySchema?.min}
max=${propertySchema?.max}
minlength=${propertySchema?.minlength}
maxlength=${propertySchema?.maxlength}
@oninput=${(event: InputEvent) => this._updatePropertyFromValue(event, propertyName, rowId)}
.value=${propertyValue}
min=${ifDefined(propertySchema?.min)}
max=${ifDefined(propertySchema?.max)}
minlength=${ifDefined(propertySchema?.minlength)}
maxlength=${ifDefined(propertySchema?.maxlength)}
@input=${(event: InputEvent) => this._updatePropertyFromValue(event, propertyName, rowId)}
.value=${(propertyValue as Date).toISOString()}
/>`
break
@ -246,12 +252,12 @@ export class DynamicTableFormElement extends LitElement {
name=${inputName}
class="form-control"
id=${inputId}
min=${propertySchema?.min}
max=${propertySchema?.max}
minlength=${propertySchema?.minlength}
maxlength=${propertySchema?.maxlength}
@oninput=${(event: InputEvent) => this._updatePropertyFromValue(event, propertyName, rowId)}
.value=${propertyValue}
min=${ifDefined(propertySchema?.min)}
max=${ifDefined(propertySchema?.max)}
minlength=${ifDefined(propertySchema?.minlength)}
maxlength=${ifDefined(propertySchema?.maxlength)}
@input=${(event: InputEvent) => this._updatePropertyFromValue(event, propertyName, rowId)}
.value=${propertyValue as String}
/>`
break
@ -269,9 +275,9 @@ export class DynamicTableFormElement extends LitElement {
name=${inputName}
class="form-check-input"
id=${inputId}
@oninput=${(event: InputEvent) => this._updatePropertyFromValue(event, propertyName, rowId)}
value=""
?checked=${propertyValue}
@input=${(event: InputEvent) => this._updatePropertyFromValue(event, propertyName, rowId)}
.value=${propertyValue as String}
?checked=${propertyValue as Boolean}
/>`
break
@ -281,24 +287,26 @@ export class DynamicTableFormElement extends LitElement {
}
if (!formElement) {
console.warn(`value type '${propertyValue.constructor}' is incompatible with field type '${propertySchema.inputType}' for form entry '${propertyName.toString()}'.`)
console.warn(`value type '${propertyValue.constructor}' is incompatible`
+ `with field type '${propertySchema.inputType}' for form entry '${propertyName.toString()}'.`)
}
console.log
return html`<td class="form-group">${formElement}</td>`
}
_updatePropertyFromValue(event: InputEvent, propertyName: string, rowId : number) {
_updatePropertyFromValue(event: Event, propertyName: string, rowId : number) {
let target = event?.target as (HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement)
let value = (target && target instanceof HTMLInputElement && target.type == "checkbox") ? !!(target?.checked) : target?.value
if(target?.value) {
if(value !== undefined) {
for(let row of this.rows) {
if(row._id === rowId) {
row[propertyName] = target?.value
row[propertyName] = value
this.requestUpdate('rows')
return
}

View File

@ -0,0 +1,41 @@
import { html, LitElement } from 'lit'
import { customElement, property } from 'lit/decorators.js'
import { unsafeSVG } from 'lit/directives/unsafe-svg.js'
import { StaticValue } from 'lit/static-html.js'
import { helpButtonSVG } from '../../../videowatch/buttons'
@customElement('plugin-configuration-row')
export class PLuginConfigurationRow extends LitElement {
@property({ attribute: false })
public title: string = `title`
@property({ attribute: false })
public description: string = `Here's a description`
@property({ attribute: false })
public helpLink: { url: URL, title: string } = { url : new URL('https://lmddgtfy.net/'), title: 'Online Help'}
createRenderRoot = () => {
return this
}
render() {
return html`
<div class="row mt-5">
<div class="col-12 col-lg-4 col-xl-3">
<h2>${this.title}</h2>
<p>${this.description}</p>
<a
href="${this.helpLink.url.href}"
target=_blank
title="${this.helpLink.title}"
class="orange-button peertube-button-link"
>${unsafeSVG(helpButtonSVG())}</a>
</div>
<div class="col-12 col-lg-8 col-xl-9">
<slot><p>Nothing in this row.</p></slot>
</div>
</div>`
}
}

View File

@ -0,0 +1,41 @@
import { PartInfo, directive } from 'lit/directive.js'
import { AsyncDirective } from 'lit/async-directive.js'
import { RegisterClientHelpers } from '@peertube/peertube-types/client';
export class TranslationDirective extends AsyncDirective {
private _peertubeHelpers?: RegisterClientHelpers
private _translatedValue : string = ''
private _localizationId : string = ''
constructor(partInfo: PartInfo) {
super(partInfo);
//_peertubeOptionsPromise.then((options) => this._peertubeHelpers = options.peertubeHelpers)
}
override render = (locId: string) => {
this._localizationId = locId // TODO Check current component for context (to infer the prefix)
if (this._translatedValue === '') {
this._translatedValue = locId
}
this._asyncUpdateTranslation()
return this._translatedValue
}
_asyncUpdateTranslation = async () => {
let newValue = await this._peertubeHelpers?.translate(this._localizationId) ?? ''
if (newValue !== '' && newValue !== this._translatedValue) {
this._translatedValue = newValue
this.setValue(newValue)
}
}
}
export const ptTr = directive(TranslationDirective)

View File

@ -2,16 +2,20 @@
//
// SPDX-License-Identifier: AGPL-3.0-only
import type { RegisterClientOptions } from '@peertube/peertube-types/client'
import type { RegisterClientHelpers, RegisterClientOptions } from '@peertube/peertube-types/client'
import { localizedHelpUrl } from '../../../utils/help'
import { helpButtonSVG } from '../../../videowatch/buttons'
import { getConfigurationChannelViewData } from './logic/channel'
import { TemplateResult, html } from 'lit'
import { unsafeHTML } from 'lit/directives/unsafe-html.js'
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
// Must use require for mustache, import seems buggy.
const Mustache = require('mustache')
import './DynamicTableFormElement'
import './ChannelConfigurationElement'
import './PluginConfigurationRow'
import { ptTr } from './TranslationDirective'
/**
* Renders the configuration settings page for a given channel,
@ -26,6 +30,8 @@ async function renderConfigurationChannel (
channelId: string,
rootEl: HTMLElement
): Promise<TemplateResult> {
const peertubeHelpers = registerClientOptions.peertubeHelpers
try {
const view : {[key: string] : any} = await getConfigurationChannelViewData(registerClientOptions, channelId)
await fillViewHelpButtons(registerClientOptions, view)
@ -34,12 +40,30 @@ async function renderConfigurationChannel (
//await vivifyConfigurationChannel(registerClientOptions, rootEl, channelId)
let tableHeader = {
words: html`${view.forbiddenWords}<div data-toggle="tooltip" data-placement="bottom" data-html="true" title=${view.forbiddenWordsDesc}></div>`,
regex: html`${view.forbiddenWordsRegexp}<div data-toggle="tooltip" data-placement="bottom" data-html="true" title=${view.forbiddenWordsRegexpDesc}></div>`,
applyToModerators: html`${view.forbiddenWordsApplyToModerators}<div data-toggle="tooltip" data-placement="bottom" data-html="true" title=${view.forbiddenWordsApplyToModeratorsDesc}></div>`,
label: html`${view.forbiddenWordsLabel}<div data-toggle="tooltip" data-placement="bottom" data-html="true" title=${view.forbiddenWordsLabelDesc}></div>`,
reason: html`${view.forbiddenWordsReason}<div data-toggle="tooltip" data-placement="bottom" data-html="true" title=${view.forbiddenWordsReasonDesc}></div>`,
comments: html`${view.forbiddenWordsComments}<div data-toggle="tooltip" data-placement="bottom" data-html="true" title=${view.forbiddenWordsCommentsDesc}></div>`,
words: {
colName: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_LABEL),
description: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_DESC2)
},
regex: {
colName: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_REGEXP_LABEL),
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)
},
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)
},
comments: {
colName: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_COMMENTS_LABEL),
description: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_COMMENTS_DESC)
}
}
let tableSchema = {
words: {
@ -102,19 +126,30 @@ async function renderConfigurationChannel (
},
]
return html`${unsafeHTML(Mustache.render(MUSTACHE_CONFIGURATION_CHANNEL, view))}
let helpLink = {
url : new URL(await localizedHelpUrl(registerClientOptions, { page: 'documentation/user/streamers/bot/forbidden_words' })),
title: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_DESC)
}
return html`
<div class="container">
<channel-configuration></channel-configuration>
<dynamic-table-form
.header=${tableHeader}
.schema=${tableSchema}
.rows=${tableRows}
.formName=${'dynamic-table-form'}
<plugin-configuration-row
.title=${ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_LABEL)}
.description=${ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_DESC)}
.helpLink=${helpLink}
>
</dynamic-table-form>
</div>${JSON.stringify(tableRows)}`
<dynamic-table-form
.header=${tableHeader}
.schema=${tableSchema}
.rows=${tableRows}
.formName=${'forbidden-words'}
>
</dynamic-table-form>
</plugin-configuration-row>
</div>`
} catch (err: any) {
registerClientOptions.peertubeHelpers.notifier.error(err.toString())
peertubeHelpers.notifier.error(err.toString())
return html``
}
}
@ -150,7 +185,8 @@ async function fillLabels (
registerClientOptions: RegisterClientOptions,
view: {[key: string] : string}
): Promise<void> {
const { peertubeHelpers } = registerClientOptions
const peertubeHelpers = registerClientOptions.peertubeHelpers
view.title = await peertubeHelpers.translate(LOC_LIVECHAT_CONFIGURATION_CHANNEL_TITLE)
view.description = await peertubeHelpers.translate(LOC_LIVECHAT_CONFIGURATION_CHANNEL_DESC)

View File

@ -5,7 +5,10 @@
import type { RegisterClientOptions } from '@peertube/peertube-types/client'
import { localizedHelpUrl } from '../../../utils/help'
import { helpButtonSVG } from '../../../videowatch/buttons'
import { TemplateResult, html } from 'lit';
import { TemplateResult, html } from 'lit'
import { unsafeHTML } from 'lit/directives/unsafe-html.js'
import { ptTr } from './TranslationDirective'
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
interface HomeViewData {
title: string
@ -86,9 +89,9 @@ async function _fillViewHelpButtons ( // TODO: refactor with the similar functio
return html`<a
href="${helpUrl}"
target=_blank
title="${title}"
title="${ptTr(LOC_ONLINE_HELP)}"
class="orange-button peertube-button-link"
>${helpIcon}</a>`
>${unsafeHTML(helpIcon)}</a>`
}
return button('documentation/user/streamers/channel')

View File

@ -398,6 +398,8 @@ livechat_configuration_channel_forbidden_words_reason_label: "Reason"
livechat_configuration_channel_forbidden_words_reason_desc: "Reason to display besides deleted messages"
livechat_configuration_channel_forbidden_words_regexp_label: "Consider as regular expressions"
livechat_configuration_channel_forbidden_words_regexp_desc: "By checking this option, you can use regular expressions."
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.

63
package-lock.json generated
View File

@ -9,6 +9,8 @@
"version": "10.0.2",
"license": "AGPL-3.0",
"dependencies": {
"@lit/context": "^1.1.1",
"@lit/task": "^1.0.0",
"@xmpp/jid": "^0.13.1",
"async": "^3.2.2",
"decache": "^4.6.0",
@ -18,6 +20,7 @@
"lit": "^3.1.3",
"log-rotate": "^0.2.8",
"openid-client": "^5.6.5",
"rxjs": "^7.8.1",
"validate-color": "^2.2.1",
"xmppjs-chat-bot": "^0.3.0"
},
@ -2670,6 +2673,14 @@
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz",
"integrity": "sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g=="
},
"node_modules/@lit/context": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@lit/context/-/context-1.1.1.tgz",
"integrity": "sha512-q/Rw7oWSJidUP43f/RUPwqZ6f5VlY8HzinTWxL/gW1Hvm2S5q2hZvV+qM8WFcC+oLNNknc3JKsd5TwxLk1hbdg==",
"dependencies": {
"@lit/reactive-element": "^1.6.2 || ^2.0.0"
}
},
"node_modules/@lit/reactive-element": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz",
@ -2678,6 +2689,14 @@
"@lit-labs/ssr-dom-shim": "^1.2.0"
}
},
"node_modules/@lit/task": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@lit/task/-/task-1.0.0.tgz",
"integrity": "sha512-7jocGBh3yGlo3kKxQggZph2txK4X5GYNWp2FAsmV9u2spzUypwrzRzXe8I72icAb02B00+k2nlvxVcrQB6vyrw==",
"dependencies": {
"@lit/reactive-element": "^1.0.0 || ^2.0.0"
}
},
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.2.tgz",
@ -10516,6 +10535,19 @@
"integrity": "sha512-cLgakCUf6PedEu15t8kbsjnwIFFR2D4RfL+W3iWFJ4iac7z4B0ZI8fxy4R3J956kAI68HclCFGL8MPoUVC3qVA==",
"dev": true
},
"node_modules/rxjs": {
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/rxjs/node_modules/tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
"node_modules/safe-array-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz",
@ -14289,6 +14321,14 @@
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz",
"integrity": "sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g=="
},
"@lit/context": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@lit/context/-/context-1.1.1.tgz",
"integrity": "sha512-q/Rw7oWSJidUP43f/RUPwqZ6f5VlY8HzinTWxL/gW1Hvm2S5q2hZvV+qM8WFcC+oLNNknc3JKsd5TwxLk1hbdg==",
"requires": {
"@lit/reactive-element": "^1.6.2 || ^2.0.0"
}
},
"@lit/reactive-element": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz",
@ -14297,6 +14337,14 @@
"@lit-labs/ssr-dom-shim": "^1.2.0"
}
},
"@lit/task": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@lit/task/-/task-1.0.0.tgz",
"integrity": "sha512-7jocGBh3yGlo3kKxQggZph2txK4X5GYNWp2FAsmV9u2spzUypwrzRzXe8I72icAb02B00+k2nlvxVcrQB6vyrw==",
"requires": {
"@lit/reactive-element": "^1.0.0 || ^2.0.0"
}
},
"@msgpackr-extract/msgpackr-extract-darwin-arm64": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.2.tgz",
@ -20289,6 +20337,21 @@
"integrity": "sha512-cLgakCUf6PedEu15t8kbsjnwIFFR2D4RfL+W3iWFJ4iac7z4B0ZI8fxy4R3J956kAI68HclCFGL8MPoUVC3qVA==",
"dev": true
},
"rxjs": {
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"requires": {
"tslib": "^2.1.0"
},
"dependencies": {
"tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
}
}
},
"safe-array-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz",

View File

@ -33,6 +33,8 @@
"dist/assets/styles/configuration.css"
],
"dependencies": {
"@lit/context": "^1.1.1",
"@lit/task": "^1.0.0",
"@xmpp/jid": "^0.13.1",
"async": "^3.2.2",
"decache": "^4.6.0",
@ -42,6 +44,7 @@
"lit": "^3.1.3",
"log-rotate": "^0.2.8",
"openid-client": "^5.6.5",
"rxjs": "^7.8.1",
"validate-color": "^2.2.1",
"xmppjs-chat-bot": "^0.3.0"
},