Custom channel emoticons WIP (#130)

This commit is contained in:
John Livingston 2024-06-06 15:03:12 +02:00
parent 893708d93a
commit a777c7ac8d
No known key found for this signature in database
GPG Key ID: B17B5640CE66CDBC
12 changed files with 56 additions and 17 deletions

View File

@ -431,7 +431,7 @@ livechat-image-file-input {
// width and height are values coming from ConverseJS custom emojis.
// If we want to upload something else, we should add options on the field to customize.
height: 1.5em;
width: 1.5em;
max-height: 1.5em;
max-width: 1.5em;
}
}

View File

@ -88,6 +88,7 @@ declare const LOC_INVALID_VALUE_WRONG_TYPE: string
declare const LOC_INVALID_VALUE_WRONG_FORMAT: string
declare const LOC_INVALID_VALUE_NOT_IN_RANGE: string
declare const LOC_INVALID_VALUE_FILE_TOO_BIG: string
declare const LOC_INVALID_VALUE_DUPLICATE: string
declare const LOC_CHATROOM_NOT_ACCESSIBLE: string

View File

@ -182,12 +182,17 @@ export class ChannelDetailsService {
public async validateEmojisConfiguration (channelEmojis: ChannelEmojis): Promise<boolean> {
const propertiesError: ValidationError['properties'] = {}
const seen = new Map<string, true>()
for (const [i, e] of channelEmojis.customEmojis.entries()) {
propertiesError[`emojis.${i}.sn`] = []
if (e.sn === '') {
propertiesError[`emojis.${i}.sn`].push(ValidationErrorType.Missing)
} else if (!/^:[\w-]+:$/.test(e.sn)) {
propertiesError[`emojis.${i}.sn`].push(ValidationErrorType.WrongFormat)
} else if (seen.has(e.sn)) {
propertiesError[`emojis.${i}.sn`].push(ValidationErrorType.Duplicate)
} else {
seen.set(e.sn, true)
}
propertiesError[`emojis.${i}.url`] = []

View File

@ -617,6 +617,9 @@ export class DynamicTableFormElement extends LivechatElement {
if (validationErrorTypes.includes(ValidationErrorType.NotInRange)) {
errorMessages.push(html`${ptTr(LOC_INVALID_VALUE_NOT_IN_RANGE)}`)
}
if (validationErrorTypes.includes(ValidationErrorType.Duplicate)) {
errorMessages.push(html`${ptTr(LOC_INVALID_VALUE_DUPLICATE)}`)
}
return html`<div id="${inputId}-feedback" class="invalid-feedback">${errorMessages}</div>`
} else {

View File

@ -8,7 +8,6 @@ import { registerClientOptionsContext } from '../contexts/peertube'
import { html } from 'lit'
import { customElement, property } from 'lit/decorators.js'
import { consume } from '@lit/context'
import { ifDefined } from 'lit/directives/if-defined.js'
/**
* Special element to upload image files.
@ -40,7 +39,6 @@ export class ImageFileInputElement extends LivechatElement {
public accept: string[] = ['image/jpg', 'image/png', 'image/gif']
protected override render = (): unknown => {
// FIXME: limit file size in the upload field.
return html`
${this.value
? html`<img src=${this.value} @click=${(ev: Event) => {
@ -57,11 +55,6 @@ export class ImageFileInputElement extends LivechatElement {
style=${this.value ? 'visibility: hidden;' : ''}
@change=${async (ev: Event) => this._upload(ev)}
/>
<input
type="hidden"
name=${ifDefined(this.name)}
value=${this.value ?? ''}
/>
`
}

View File

@ -7,6 +7,7 @@ export enum ValidationErrorType {
WrongType,
WrongFormat,
NotInRange,
Duplicate
}
export class ValidationError extends Error {

View File

@ -119,7 +119,7 @@ function defaultConverseParams (
params.emoji_categories = Object.assign(
{},
customEmojisUrl
? { custom: ':xmpp:' } // TODO: put here the default custom emoji
? { custom: ':converse:' }
: {},
{
smileys: ':grinning:',

View File

@ -33,8 +33,10 @@ export const livechatEmojisPlugin = {
return json
}
// We will put default emojis at the end
// We will put default emojis at the end, so keeping a copy
const defaultCustom = json.custom ?? {}
// Now we must clone json, to avoid side effects when navigating between several videos.
json = JSON.parse(JSON.stringify(json))
json.custom = {}
let defaultDef: CustomEmojiDefinition | undefined
@ -48,6 +50,15 @@ export const livechatEmojisPlugin = {
if (def.isCategoryEmoji) {
defaultDef ??= def
}
// 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]
if (type !== 'custom' && type !== 'modifiers' && (def.sn in v)) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete v[def.sn]
}
}
}
for (const key in defaultCustom) {

View File

@ -441,6 +441,7 @@ invalid_value_wrong_type: "Value is of the wrong type."
invalid_value_wrong_format: "Value is in the wrong format."
invalid_value_not_in_range: "Value is not in authorized range."
invalid_value_file_too_big: "File size is too big (max size: %s)"
invalid_value_duplicate: "Duplicate value"
slow_mode_info: "Slow mode is enabled, users can send a message every %1$s seconds."

View File

@ -285,11 +285,8 @@ async function _connectionInfos (
params.forcetype ?? false
)
if (video?.channelId && await Emojis.singletonSafe()?.channelHasCustomEmojis(video.channelId)) {
customEmojisUrl = getBaseRouterRoute(options) +
'emojis/channel/' +
encodeURIComponent(video.channelId) +
'/definition'
if (video?.channelId) {
customEmojisUrl = await Emojis.singletonSafe()?.channelCustomEmojisUrl(video.channelId)
}
} catch (err) {
options.peertubeHelpers.logger.error(err)

View File

@ -63,6 +63,26 @@ export class Emojis {
return fs.promises.access(filepath, fs.constants.F_OK).then(() => true, () => false)
}
/**
* Gets the public url for the channel emojis definition, if there are custom emojis.
* @param channelId channel Id
*/
public async channelCustomEmojisUrl (channelId: number): Promise<string | undefined> {
if (!await this.channelHasCustomEmojis(channelId)) {
return undefined
}
return canonicalizePluginUri(
this.options,
getBaseRouterRoute(this.options) +
'emojis/channel/' +
encodeURIComponent(channelId) +
'/definition',
{
removePluginVersion: true
}
)
}
/**
* Get the file path for the channel definition JSON file (does not test if the file exists).
* @param channelId channel Id
@ -286,6 +306,13 @@ export class Emojis {
customEmojis.push(sanitized)
}
// For now, the frontend does not implement isCategoryEmoji.
// if there is no isCategoryEmoji, we will take the first value.
// TODO: remove this when the frontend will be able to set this.
if (!categoryEmojiFound && customEmojis.length) {
customEmojis[0].isCategoryEmoji = true
}
const result: ChannelEmojis = {
customEmojis: customEmojis
}

View File

@ -1,4 +1,4 @@
export const maxSize: number = 20 * 1024
export const maxSize: number = 30 * 1024
export const allowedExtensions = ['png', 'jpg', 'jpeg', 'gif']
export const inputFileAccept = ['image/jpg', 'image/png', 'image/gif']
export const allowedMimeTypes = ['image/jpg', 'image/png', 'image/gif']