From a777c7ac8d302920c02e15ca0582e03532189233 Mon Sep 17 00:00:00 2001 From: John Livingston Date: Thu, 6 Jun 2024 15:03:12 +0200 Subject: [PATCH] Custom channel emoticons WIP (#130) --- assets/styles/configuration.scss | 4 +-- client/@types/global.d.ts | 1 + .../configuration/services/channel-details.ts | 5 ++++ .../common/lib/elements/dynamic-table-form.ts | 3 +++ .../common/lib/elements/image-file-input.ts | 7 ----- client/common/lib/models/validation.ts | 1 + conversejs/lib/converse-params.ts | 2 +- conversejs/lib/plugins/livechat-emojis.ts | 13 ++++++++- languages/en.yml | 1 + server/lib/conversejs/params.ts | 7 ++--- server/lib/emojis/emojis.ts | 27 +++++++++++++++++++ shared/lib/emojis.ts | 2 +- 12 files changed, 56 insertions(+), 17 deletions(-) diff --git a/assets/styles/configuration.scss b/assets/styles/configuration.scss index d3923cdf..fb916c30 100644 --- a/assets/styles/configuration.scss +++ b/assets/styles/configuration.scss @@ -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; } } diff --git a/client/@types/global.d.ts b/client/@types/global.d.ts index 8127800a..9f9eba94 100644 --- a/client/@types/global.d.ts +++ b/client/@types/global.d.ts @@ -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 diff --git a/client/common/configuration/services/channel-details.ts b/client/common/configuration/services/channel-details.ts index f40965a0..19bb5403 100644 --- a/client/common/configuration/services/channel-details.ts +++ b/client/common/configuration/services/channel-details.ts @@ -182,12 +182,17 @@ export class ChannelDetailsService { public async validateEmojisConfiguration (channelEmojis: ChannelEmojis): Promise { const propertiesError: ValidationError['properties'] = {} + const seen = new Map() 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`] = [] diff --git a/client/common/lib/elements/dynamic-table-form.ts b/client/common/lib/elements/dynamic-table-form.ts index 6ed90de5..8dd0c73c 100644 --- a/client/common/lib/elements/dynamic-table-form.ts +++ b/client/common/lib/elements/dynamic-table-form.ts @@ -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`
${errorMessages}
` } else { diff --git a/client/common/lib/elements/image-file-input.ts b/client/common/lib/elements/image-file-input.ts index fd1d0f5e..8db9f917 100644 --- a/client/common/lib/elements/image-file-input.ts +++ b/client/common/lib/elements/image-file-input.ts @@ -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` { @@ -57,11 +55,6 @@ export class ImageFileInputElement extends LivechatElement { style=${this.value ? 'visibility: hidden;' : ''} @change=${async (ev: Event) => this._upload(ev)} /> - ` } diff --git a/client/common/lib/models/validation.ts b/client/common/lib/models/validation.ts index 95cee64f..da146ab0 100644 --- a/client/common/lib/models/validation.ts +++ b/client/common/lib/models/validation.ts @@ -7,6 +7,7 @@ export enum ValidationErrorType { WrongType, WrongFormat, NotInRange, + Duplicate } export class ValidationError extends Error { diff --git a/conversejs/lib/converse-params.ts b/conversejs/lib/converse-params.ts index 742be895..eb079fa0 100644 --- a/conversejs/lib/converse-params.ts +++ b/conversejs/lib/converse-params.ts @@ -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:', diff --git a/conversejs/lib/plugins/livechat-emojis.ts b/conversejs/lib/plugins/livechat-emojis.ts index ee7eeee6..f95bc8a1 100644 --- a/conversejs/lib/plugins/livechat-emojis.ts +++ b/conversejs/lib/plugins/livechat-emojis.ts @@ -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) { diff --git a/languages/en.yml b/languages/en.yml index 39ea6072..698227ec 100644 --- a/languages/en.yml +++ b/languages/en.yml @@ -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." diff --git a/server/lib/conversejs/params.ts b/server/lib/conversejs/params.ts index 6919f1e6..c9b7b388 100644 --- a/server/lib/conversejs/params.ts +++ b/server/lib/conversejs/params.ts @@ -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) diff --git a/server/lib/emojis/emojis.ts b/server/lib/emojis/emojis.ts index c196ce5c..c6be5510 100644 --- a/server/lib/emojis/emojis.ts +++ b/server/lib/emojis/emojis.ts @@ -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 { + 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 } diff --git a/shared/lib/emojis.ts b/shared/lib/emojis.ts index dce5f813..bc6011ab 100644 --- a/shared/lib/emojis.ts +++ b/shared/lib/emojis.ts @@ -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']