Custom channel emoticons WIP (#130)
This commit is contained in:
parent
893708d93a
commit
a777c7ac8d
@ -431,7 +431,7 @@ livechat-image-file-input {
|
|||||||
|
|
||||||
// width and height are values coming from ConverseJS custom emojis.
|
// 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.
|
// If we want to upload something else, we should add options on the field to customize.
|
||||||
height: 1.5em;
|
max-height: 1.5em;
|
||||||
width: 1.5em;
|
max-width: 1.5em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1
client/@types/global.d.ts
vendored
1
client/@types/global.d.ts
vendored
@ -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_WRONG_FORMAT: string
|
||||||
declare const LOC_INVALID_VALUE_NOT_IN_RANGE: string
|
declare const LOC_INVALID_VALUE_NOT_IN_RANGE: string
|
||||||
declare const LOC_INVALID_VALUE_FILE_TOO_BIG: string
|
declare const LOC_INVALID_VALUE_FILE_TOO_BIG: string
|
||||||
|
declare const LOC_INVALID_VALUE_DUPLICATE: string
|
||||||
|
|
||||||
declare const LOC_CHATROOM_NOT_ACCESSIBLE: string
|
declare const LOC_CHATROOM_NOT_ACCESSIBLE: string
|
||||||
|
|
||||||
|
@ -182,12 +182,17 @@ export class ChannelDetailsService {
|
|||||||
public async validateEmojisConfiguration (channelEmojis: ChannelEmojis): Promise<boolean> {
|
public async validateEmojisConfiguration (channelEmojis: ChannelEmojis): Promise<boolean> {
|
||||||
const propertiesError: ValidationError['properties'] = {}
|
const propertiesError: ValidationError['properties'] = {}
|
||||||
|
|
||||||
|
const seen = new Map<string, true>()
|
||||||
for (const [i, e] of channelEmojis.customEmojis.entries()) {
|
for (const [i, e] of channelEmojis.customEmojis.entries()) {
|
||||||
propertiesError[`emojis.${i}.sn`] = []
|
propertiesError[`emojis.${i}.sn`] = []
|
||||||
if (e.sn === '') {
|
if (e.sn === '') {
|
||||||
propertiesError[`emojis.${i}.sn`].push(ValidationErrorType.Missing)
|
propertiesError[`emojis.${i}.sn`].push(ValidationErrorType.Missing)
|
||||||
} else if (!/^:[\w-]+:$/.test(e.sn)) {
|
} else if (!/^:[\w-]+:$/.test(e.sn)) {
|
||||||
propertiesError[`emojis.${i}.sn`].push(ValidationErrorType.WrongFormat)
|
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`] = []
|
propertiesError[`emojis.${i}.url`] = []
|
||||||
|
@ -617,6 +617,9 @@ export class DynamicTableFormElement extends LivechatElement {
|
|||||||
if (validationErrorTypes.includes(ValidationErrorType.NotInRange)) {
|
if (validationErrorTypes.includes(ValidationErrorType.NotInRange)) {
|
||||||
errorMessages.push(html`${ptTr(LOC_INVALID_VALUE_NOT_IN_RANGE)}`)
|
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>`
|
return html`<div id="${inputId}-feedback" class="invalid-feedback">${errorMessages}</div>`
|
||||||
} else {
|
} else {
|
||||||
|
@ -8,7 +8,6 @@ import { registerClientOptionsContext } from '../contexts/peertube'
|
|||||||
import { html } from 'lit'
|
import { html } from 'lit'
|
||||||
import { customElement, property } from 'lit/decorators.js'
|
import { customElement, property } from 'lit/decorators.js'
|
||||||
import { consume } from '@lit/context'
|
import { consume } from '@lit/context'
|
||||||
import { ifDefined } from 'lit/directives/if-defined.js'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Special element to upload image files.
|
* Special element to upload image files.
|
||||||
@ -40,7 +39,6 @@ export class ImageFileInputElement extends LivechatElement {
|
|||||||
public accept: string[] = ['image/jpg', 'image/png', 'image/gif']
|
public accept: string[] = ['image/jpg', 'image/png', 'image/gif']
|
||||||
|
|
||||||
protected override render = (): unknown => {
|
protected override render = (): unknown => {
|
||||||
// FIXME: limit file size in the upload field.
|
|
||||||
return html`
|
return html`
|
||||||
${this.value
|
${this.value
|
||||||
? html`<img src=${this.value} @click=${(ev: Event) => {
|
? html`<img src=${this.value} @click=${(ev: Event) => {
|
||||||
@ -57,11 +55,6 @@ export class ImageFileInputElement extends LivechatElement {
|
|||||||
style=${this.value ? 'visibility: hidden;' : ''}
|
style=${this.value ? 'visibility: hidden;' : ''}
|
||||||
@change=${async (ev: Event) => this._upload(ev)}
|
@change=${async (ev: Event) => this._upload(ev)}
|
||||||
/>
|
/>
|
||||||
<input
|
|
||||||
type="hidden"
|
|
||||||
name=${ifDefined(this.name)}
|
|
||||||
value=${this.value ?? ''}
|
|
||||||
/>
|
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ export enum ValidationErrorType {
|
|||||||
WrongType,
|
WrongType,
|
||||||
WrongFormat,
|
WrongFormat,
|
||||||
NotInRange,
|
NotInRange,
|
||||||
|
Duplicate
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ValidationError extends Error {
|
export class ValidationError extends Error {
|
||||||
|
@ -119,7 +119,7 @@ function defaultConverseParams (
|
|||||||
params.emoji_categories = Object.assign(
|
params.emoji_categories = Object.assign(
|
||||||
{},
|
{},
|
||||||
customEmojisUrl
|
customEmojisUrl
|
||||||
? { custom: ':xmpp:' } // TODO: put here the default custom emoji
|
? { custom: ':converse:' }
|
||||||
: {},
|
: {},
|
||||||
{
|
{
|
||||||
smileys: ':grinning:',
|
smileys: ':grinning:',
|
||||||
|
@ -33,8 +33,10 @@ export const livechatEmojisPlugin = {
|
|||||||
return json
|
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 ?? {}
|
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 = {}
|
json.custom = {}
|
||||||
|
|
||||||
let defaultDef: CustomEmojiDefinition | undefined
|
let defaultDef: CustomEmojiDefinition | undefined
|
||||||
@ -48,6 +50,15 @@ export const livechatEmojisPlugin = {
|
|||||||
if (def.isCategoryEmoji) {
|
if (def.isCategoryEmoji) {
|
||||||
defaultDef ??= def
|
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) {
|
for (const key in defaultCustom) {
|
||||||
|
@ -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_wrong_format: "Value is in the wrong format."
|
||||||
invalid_value_not_in_range: "Value is not in authorized range."
|
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_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."
|
slow_mode_info: "Slow mode is enabled, users can send a message every %1$s seconds."
|
||||||
|
|
||||||
|
@ -285,11 +285,8 @@ async function _connectionInfos (
|
|||||||
params.forcetype ?? false
|
params.forcetype ?? false
|
||||||
)
|
)
|
||||||
|
|
||||||
if (video?.channelId && await Emojis.singletonSafe()?.channelHasCustomEmojis(video.channelId)) {
|
if (video?.channelId) {
|
||||||
customEmojisUrl = getBaseRouterRoute(options) +
|
customEmojisUrl = await Emojis.singletonSafe()?.channelCustomEmojisUrl(video.channelId)
|
||||||
'emojis/channel/' +
|
|
||||||
encodeURIComponent(video.channelId) +
|
|
||||||
'/definition'
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
options.peertubeHelpers.logger.error(err)
|
options.peertubeHelpers.logger.error(err)
|
||||||
|
@ -63,6 +63,26 @@ export class Emojis {
|
|||||||
return fs.promises.access(filepath, fs.constants.F_OK).then(() => true, () => false)
|
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).
|
* Get the file path for the channel definition JSON file (does not test if the file exists).
|
||||||
* @param channelId channel Id
|
* @param channelId channel Id
|
||||||
@ -286,6 +306,13 @@ export class Emojis {
|
|||||||
customEmojis.push(sanitized)
|
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 = {
|
const result: ChannelEmojis = {
|
||||||
customEmojis: customEmojis
|
customEmojis: customEmojis
|
||||||
}
|
}
|
||||||
|
@ -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 allowedExtensions = ['png', 'jpg', 'jpeg', 'gif']
|
||||||
export const inputFileAccept = ['image/jpg', 'image/png', 'image/gif']
|
export const inputFileAccept = ['image/jpg', 'image/png', 'image/gif']
|
||||||
export const allowedMimeTypes = ['image/jpg', 'image/png', 'image/gif']
|
export const allowedMimeTypes = ['image/jpg', 'image/png', 'image/gif']
|
||||||
|
Loading…
Reference in New Issue
Block a user