${headerCellData.colName}
@@ -244,10 +253,8 @@ export class DynamicTableFormElement extends LivechatElement {
switch (propertySchema.default?.constructor) {
case String:
+ propertySchema.inputType ??= 'text'
switch (propertySchema.inputType) {
- case undefined:
- propertySchema.inputType = 'text'
-
case 'text':
case 'color':
case 'date':
@@ -298,14 +305,24 @@ export class DynamicTableFormElement extends LivechatElement {
${feedback}
`
break
+
+ case 'image-file':
+ formElement = html`${this._renderImageFileInput(rowId,
+ inputId,
+ inputName,
+ propertyName,
+ propertySchema,
+ propertyValue?.toString(),
+ originalIndex)}
+ ${feedback}
+ `
+ break
}
break
case Date:
+ propertySchema.inputType ??= 'datetime'
switch (propertySchema.inputType) {
- case undefined:
- propertySchema.inputType = 'datetime'
-
case 'date':
case 'datetime':
case 'datetime-local':
@@ -324,10 +341,8 @@ export class DynamicTableFormElement extends LivechatElement {
break
case Number:
+ propertySchema.inputType ??= 'number'
switch (propertySchema.inputType) {
- case undefined:
- propertySchema.inputType = 'number'
-
case 'number':
case 'range':
formElement = html`${this._renderInput(rowId,
@@ -344,10 +359,8 @@ export class DynamicTableFormElement extends LivechatElement {
break
case Boolean:
+ propertySchema.inputType ??= 'checkbox'
switch (propertySchema.inputType) {
- case undefined:
- propertySchema.inputType = 'checkbox'
-
case 'checkbox':
formElement = html`${this._renderCheckbox(rowId,
inputId,
@@ -363,10 +376,8 @@ export class DynamicTableFormElement extends LivechatElement {
break
case Array:
+ propertySchema.inputType ??= 'text'
switch (propertySchema.inputType) {
- case undefined:
- propertySchema.inputType = 'text'
-
case 'text':
case 'color':
case 'date':
@@ -549,6 +560,23 @@ export class DynamicTableFormElement extends LivechatElement {
`
}
+ _renderImageFileInput = (rowId: number,
+ inputId: string,
+ inputName: string,
+ propertyName: string,
+ propertySchema: CellDataSchema,
+ propertyValue: string,
+ originalIndex: number
+ ): TemplateResult => {
+ return html`
this._updatePropertyFromValue(event, propertyName, propertySchema, rowId)}
+ .value=${propertyValue}>`
+ }
+
_getInputValidationClass = (propertyName: string,
originalIndex: number): { [key: string]: boolean } => {
const validationErrorTypes: ValidationErrorType[] | undefined =
diff --git a/client/common/lib/elements/help-button.ts b/client/common/lib/elements/help-button.ts
index 01c50dac..ebac17ed 100644
--- a/client/common/lib/elements/help-button.ts
+++ b/client/common/lib/elements/help-button.ts
@@ -12,7 +12,7 @@ import type { RegisterClientOptions } from '@peertube/peertube-types/client'
import { Task } from '@lit/task'
import { localizedHelpUrl } from '../../../utils/help'
import { ptTr } from '../directives/translation'
-import { DirectiveResult } from 'lit/directive'
+import type { DirectiveResult } from 'lit/directive'
import { LivechatElement } from './livechat'
@customElement('livechat-help-button')
diff --git a/client/common/lib/elements/image-file-input.ts b/client/common/lib/elements/image-file-input.ts
new file mode 100644
index 00000000..c1bff073
--- /dev/null
+++ b/client/common/lib/elements/image-file-input.ts
@@ -0,0 +1,89 @@
+// SPDX-FileCopyrightText: 2024 John Livingston
+//
+// SPDX-License-Identifier: AGPL-3.0-only
+
+import { LivechatElement } from './livechat'
+import { html } from 'lit'
+import { customElement, property, state } from 'lit/decorators.js'
+import { ifDefined } from 'lit/directives/if-defined.js'
+
+/**
+ * Special element to upload image files.
+ * If no current value, displays an input type="file" field.
+ * When there is already an image, it is displayed.
+ * Clicking on the image triggers a new upload, that will replace the image.
+ *
+ * The value can be either:
+ * * an url (when the image is already saved for example)
+ * * a base64 representation (for image to upload for exemple)
+ *
+ * Doing so, we just have to set the img.src to the value to display the image.
+ */
+@customElement('livechat-image-file-input')
+export class ImageFileInputElement extends LivechatElement {
+ @property({ attribute: false })
+ public name?: string
+
+ @property({ reflect: true })
+ public value: string | undefined
+
+ protected override render = (): unknown => {
+ // FIXME: limit file size in the upload field.
+ return html`
+ ${this.value
+ ? html`
{
+ ev.preventDefault()
+ const upload: HTMLInputElement | null | undefined = this.parentElement?.querySelector('input[type="file"]')
+ upload?.click()
+ }} />`
+ : ''
+ }
+
this._upload(ev)}
+ />
+
+ `
+ }
+
+ private async _upload (ev: Event): Promise
{
+ ev.preventDefault()
+ const target = ev.target
+ const file = (target as HTMLInputElement).files?.[0]
+ if (!file) {
+ this.value = ''
+ return
+ }
+
+ try {
+ const base64 = await new Promise((resolve, reject) => {
+ const fileReader = new FileReader()
+ fileReader.readAsDataURL(file)
+ fileReader.onload = () => {
+ if (fileReader.result === null) {
+ reject(new Error('Empty result'))
+ return
+ }
+ if (fileReader.result instanceof ArrayBuffer) {
+ reject(new Error('Result is an ArrayBuffer, this was not intended'))
+ } else {
+ resolve(fileReader.result)
+ }
+ }
+ fileReader.onerror = reject
+ })
+
+ this.value = base64
+ } catch (err) {
+ // FIXME: use peertube notifier?
+ console.error(err)
+ }
+ }
+}
diff --git a/client/common/lib/elements/index.js b/client/common/lib/elements/index.js
index a41a0f4e..56ea7912 100644
--- a/client/common/lib/elements/index.js
+++ b/client/common/lib/elements/index.js
@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: 2024 Mehdi Benadel
+// SPDX-FileCopyrightText: 2024 John Livingston
//
// SPDX-License-Identifier: AGPL-3.0-only
@@ -7,3 +8,4 @@ import './help-button'
import './dynamic-table-form'
import './configuration-row'
import './tags-input'
+import './image-file-input'
diff --git a/languages/en.yml b/languages/en.yml
index c0b276e6..454a1a90 100644
--- a/languages/en.yml
+++ b/languages/en.yml
@@ -476,3 +476,11 @@ task_list_pick_message: |
promote: 'Become moderator'
livechat_configuration_channel_emojis_title: 'Channel emojis'
+livechat_emojis_shortname: 'Short name'
+livechat_emojis_shortname_desc: |
+ You can use emojis using ":shortname:".
+ The short name can only contain alphanumerical characters, underscores and hyphens.
+livechat_emojis_file: 'File'
+livechat_emojis_file_desc: |
+ The maximum file size must be 32px by 32px.
+ Accepted formats: png, jpg, gif.
diff --git a/server/lib/emojis/emojis.ts b/server/lib/emojis/emojis.ts
index 20550561..60d41955 100644
--- a/server/lib/emojis/emojis.ts
+++ b/server/lib/emojis/emojis.ts
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2024 John Livingston
+//
+// SPDX-License-Identifier: AGPL-3.0-only
+
import type { ChannelEmojis, CustomEmojiDefinition } from '../../../shared/lib/types'
import { RegisterServerOptions } from '@peertube/peertube-types'
import { getBaseRouterRoute } from '../helpers'
diff --git a/server/lib/emojis/index.ts b/server/lib/emojis/index.ts
index 4626f5ee..18eefae8 100644
--- a/server/lib/emojis/index.ts
+++ b/server/lib/emojis/index.ts
@@ -1,2 +1,6 @@
+// SPDX-FileCopyrightText: 2024 John Livingston
+//
+// SPDX-License-Identifier: AGPL-3.0-only
+
import './emojis'
export * from './emojis'