Embeddeding chat without iframe besieds videos WIP
This commit is contained in:
parent
ba52d4e3d8
commit
612a9f622d
@ -4,6 +4,10 @@
|
|||||||
|
|
||||||
TODO: replace commit_id by a tag in build-conversejs
|
TODO: replace commit_id by a tag in build-conversejs
|
||||||
|
|
||||||
|
**Breaking changes**:
|
||||||
|
|
||||||
|
* if you were adding custom CSS to livechat iframe, it could be broken, as the livechat is no more included in an iframe. Your custom styles are now added on a `div` element.
|
||||||
|
|
||||||
### New features
|
### New features
|
||||||
|
|
||||||
* #143: User colors: implementing [XEP-0392](https://xmpp.org/extensions/xep-0392.html) to have random colors on users nicknames
|
* #143: User colors: implementing [XEP-0392](https://xmpp.org/extensions/xep-0392.html) to have random colors on users nicknames
|
||||||
|
@ -67,7 +67,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#peertube-plugin-livechat-container iframe {
|
#peertube-plugin-livechat-container converse-root {
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
min-height: 30vh;
|
min-height: 30vh;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -15,7 +15,7 @@ async function register (clientOptions: RegisterClientOptions): Promise<void> {
|
|||||||
console.warn(
|
console.warn(
|
||||||
'[peertube-plugin-livechat navigation-end] ' +
|
'[peertube-plugin-livechat navigation-end] ' +
|
||||||
'It seems that action:router.navigation-end was called after action:video-watch.video.loaded. ' +
|
'It seems that action:router.navigation-end was called after action:video-watch.video.loaded. ' +
|
||||||
'No removing the chat from the DOM.'
|
'Not removing the chat from the DOM.'
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import type { RegisterClientOptions } from '@peertube/peertube-types/client'
|
import type { RegisterClientOptions } from '@peertube/peertube-types/client'
|
||||||
import type { InitConverseJSParams } from 'shared/lib/types'
|
|
||||||
import { renderConfigurationHome } from './templates/home'
|
import { renderConfigurationHome } from './templates/home'
|
||||||
import { renderConfigurationChannel } from './templates/channel'
|
import { renderConfigurationChannel } from './templates/channel'
|
||||||
import { getBaseRoute } from '../../utils/uri'
|
import { displayConverseJS } from '../../utils/conversejs'
|
||||||
import { loadConverseJS } from '../../utils/conversejs'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers stuff related to the user's configuration pages.
|
* Registers stuff related to the user's configuration pages.
|
||||||
@ -29,33 +27,7 @@ async function registerConfiguration (clientOptions: RegisterClientOptions): Pro
|
|||||||
container.classList.add('livechat-embed-fullpage')
|
container.classList.add('livechat-embed-fullpage')
|
||||||
rootEl.append(container)
|
rootEl.append(container)
|
||||||
|
|
||||||
const converseRoot = document.createElement('converse-root')
|
await displayConverseJS(clientOptions, container, roomKey, 'peertube-fullpage')
|
||||||
converseRoot.classList.add('theme-peertube')
|
|
||||||
container.append(converseRoot)
|
|
||||||
|
|
||||||
const spinner = document.createElement('div')
|
|
||||||
spinner.classList.add('livechat-spinner')
|
|
||||||
spinner.setAttribute('id', 'livechat-loading-spinner')
|
|
||||||
spinner.innerHTML = '<div></div>'
|
|
||||||
container.prepend(spinner)
|
|
||||||
// spinner will be removed by a converse plugin
|
|
||||||
|
|
||||||
const authHeader = peertubeHelpers.getAuthHeader()
|
|
||||||
|
|
||||||
const response = await fetch(
|
|
||||||
getBaseRoute(clientOptions) + '/api/configuration/room/' + encodeURIComponent(roomKey),
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
headers: peertubeHelpers.getAuthHeader()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Can\'t get channel configuration options.')
|
|
||||||
}
|
|
||||||
const converseJSParams: InitConverseJSParams = await (response).json()
|
|
||||||
|
|
||||||
await loadConverseJS(converseJSParams)
|
|
||||||
window.initConverse(converseJSParams, 'peertube-fullpage', authHeader ?? null)
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[peertube-plugin-livechat] ' + (err as string))
|
console.error('[peertube-plugin-livechat] ' + (err as string))
|
||||||
// FIXME: do a better error page.
|
// FIXME: do a better error page.
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import type { InitConverseJSParams } from 'shared/lib/types'
|
import type { RegisterClientOptions } from '@peertube/peertube-types/client'
|
||||||
|
import type { InitConverseJSParams, ChatPeertubeIncludeMode } from 'shared/lib/types'
|
||||||
import { computeAutoColors } from './colors'
|
import { computeAutoColors } from './colors'
|
||||||
|
import { getBaseRoute } from './uri'
|
||||||
// FIXME
|
// FIXME
|
||||||
// declare global {
|
// declare global {
|
||||||
// interface Window {
|
// interface Window {
|
||||||
@ -109,6 +110,50 @@ async function loadConverseJS (converseJSParams: InitConverseJSParams): Promise<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
/**
|
||||||
loadConverseJS
|
* Loads the chat in the given container.
|
||||||
|
* @param clientOptions Peertube client options
|
||||||
|
* @param container the dom element where to insert the chat
|
||||||
|
* @param roomKey the room to join
|
||||||
|
* @param chatIncludeMode the include mode
|
||||||
|
*/
|
||||||
|
async function displayConverseJS (
|
||||||
|
clientOptions: RegisterClientOptions,
|
||||||
|
container: HTMLElement,
|
||||||
|
roomKey: string,
|
||||||
|
chatIncludeMode: ChatPeertubeIncludeMode
|
||||||
|
): Promise<void> {
|
||||||
|
const peertubeHelpers = clientOptions.peertubeHelpers
|
||||||
|
|
||||||
|
const converseRoot = document.createElement('converse-root')
|
||||||
|
converseRoot.classList.add('theme-peertube')
|
||||||
|
container.append(converseRoot)
|
||||||
|
|
||||||
|
const spinner = document.createElement('div')
|
||||||
|
spinner.classList.add('livechat-spinner')
|
||||||
|
spinner.setAttribute('id', 'livechat-loading-spinner')
|
||||||
|
spinner.innerHTML = '<div></div>'
|
||||||
|
container.prepend(spinner)
|
||||||
|
// spinner will be removed by a converse plugin
|
||||||
|
|
||||||
|
const authHeader = peertubeHelpers.getAuthHeader()
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
getBaseRoute(clientOptions) + '/api/configuration/room/' + encodeURIComponent(roomKey),
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
headers: peertubeHelpers.getAuthHeader()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Can\'t get channel configuration options.')
|
||||||
|
}
|
||||||
|
const converseJSParams: InitConverseJSParams = await (response).json()
|
||||||
|
|
||||||
|
await loadConverseJS(converseJSParams)
|
||||||
|
window.initConverse(converseJSParams, chatIncludeMode, authHeader ?? null)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
displayConverseJS
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import { logger } from './utils/logger'
|
|||||||
import { closeSVG, openBlankChatSVG, openChatSVG, shareChatUrlSVG, helpButtonSVG } from './videowatch/buttons'
|
import { closeSVG, openBlankChatSVG, openChatSVG, shareChatUrlSVG, helpButtonSVG } from './videowatch/buttons'
|
||||||
import { displayButton, displayButtonOptions } from './videowatch/button'
|
import { displayButton, displayButtonOptions } from './videowatch/button'
|
||||||
import { shareChatUrl } from './videowatch/share'
|
import { shareChatUrl } from './videowatch/share'
|
||||||
import { getIframeUri } from './videowatch/uri'
|
import { displayConverseJS } from './utils/conversejs'
|
||||||
|
|
||||||
interface VideoWatchLoadedHookOptions {
|
interface VideoWatchLoadedHookOptions {
|
||||||
videojs: any
|
videojs: any
|
||||||
@ -68,7 +68,7 @@ function guessIamIModerator (_registerOptions: RegisterClientOptions): boolean {
|
|||||||
|
|
||||||
function register (registerOptions: RegisterClientOptions): void {
|
function register (registerOptions: RegisterClientOptions): void {
|
||||||
const { registerHook, peertubeHelpers } = registerOptions
|
const { registerHook, peertubeHelpers } = registerOptions
|
||||||
let settings: any = {}
|
let settings: any = {} // will be loaded later
|
||||||
|
|
||||||
async function insertChatDom (
|
async function insertChatDom (
|
||||||
container: HTMLElement, video: Video, showOpenBlank: boolean, showShareUrlButton: boolean
|
container: HTMLElement, video: Video, showOpenBlank: boolean, showShareUrlButton: boolean
|
||||||
@ -77,7 +77,7 @@ function register (registerOptions: RegisterClientOptions): void {
|
|||||||
const viewersDocumentationHelpUrl = await localizedHelpUrl(registerOptions, {
|
const viewersDocumentationHelpUrl = await localizedHelpUrl(registerOptions, {
|
||||||
page: 'documentation/user/viewers'
|
page: 'documentation/user/viewers'
|
||||||
})
|
})
|
||||||
const p = new Promise<void>((resolve, reject) => {
|
const p = new Promise<void>((resolve) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
Promise.all([
|
Promise.all([
|
||||||
peertubeHelpers.translate(LOC_OPEN_CHAT),
|
peertubeHelpers.translate(LOC_OPEN_CHAT),
|
||||||
@ -92,11 +92,6 @@ function register (registerOptions: RegisterClientOptions): void {
|
|||||||
const labelShareUrl = labels[3]
|
const labelShareUrl = labels[3]
|
||||||
const labelHelp = labels[4]
|
const labelHelp = labels[4]
|
||||||
|
|
||||||
const iframeUri = getIframeUri(registerOptions, settings, video)
|
|
||||||
if (!iframeUri) {
|
|
||||||
return reject(new Error('No uri, cant display the buttons.'))
|
|
||||||
}
|
|
||||||
|
|
||||||
const buttonContainer = document.createElement('div')
|
const buttonContainer = document.createElement('div')
|
||||||
buttonContainer.classList.add('peertube-plugin-livechat-buttons')
|
buttonContainer.classList.add('peertube-plugin-livechat-buttons')
|
||||||
container.append(buttonContainer)
|
container.append(buttonContainer)
|
||||||
@ -107,7 +102,9 @@ function register (registerOptions: RegisterClientOptions): void {
|
|||||||
buttonContainer,
|
buttonContainer,
|
||||||
name: 'open',
|
name: 'open',
|
||||||
label: labelOpen,
|
label: labelOpen,
|
||||||
callback: () => openChat(video),
|
callback: () => {
|
||||||
|
openChat(video).then(() => {}, () => {})
|
||||||
|
},
|
||||||
icon: openChatSVG,
|
icon: openChatSVG,
|
||||||
additionalClasses: []
|
additionalClasses: []
|
||||||
})
|
})
|
||||||
@ -118,7 +115,7 @@ function register (registerOptions: RegisterClientOptions): void {
|
|||||||
label: labelOpenBlank,
|
label: labelOpenBlank,
|
||||||
callback: () => {
|
callback: () => {
|
||||||
closeChat()
|
closeChat()
|
||||||
window.open(iframeUri)
|
window.open('/p/livechat/room?room=' + encodeURIComponent(video.uuid))
|
||||||
},
|
},
|
||||||
icon: openBlankChatSVG,
|
icon: openBlankChatSVG,
|
||||||
additionalClasses: []
|
additionalClasses: []
|
||||||
@ -176,45 +173,49 @@ function register (registerOptions: RegisterClientOptions): void {
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
function openChat (video: Video): void | boolean {
|
async function openChat (video: Video): Promise<void | false> {
|
||||||
if (!video) {
|
if (!video) {
|
||||||
logger.log('No video.')
|
logger.log('No video.')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`Trying to load the chat for video ${video.uuid}.`)
|
logger.info(`Trying to load the chat for video ${video.uuid}.`)
|
||||||
const iframeUri = getIframeUri(registerOptions, settings, video)
|
// here the room key is always the video uuid, a backend API will translate to channel id if relevant.
|
||||||
if (!iframeUri) {
|
const roomkey = video.uuid
|
||||||
logger.error('Incorrect iframe uri')
|
if (!roomkey) {
|
||||||
|
logger.error('Can\'t get room xmpp addr')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const additionalStyles = settings['chat-style'] || ''
|
const additionalStyles = settings['chat-style'] || ''
|
||||||
|
|
||||||
logger.info('Opening the chat...')
|
logger.info('Opening the chat...')
|
||||||
const container = document.getElementById('peertube-plugin-livechat-container')
|
const container = document.getElementById('peertube-plugin-livechat-container')
|
||||||
if (!container) {
|
|
||||||
logger.error('Cant found the livechat container.')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (container.querySelector('iframe')) {
|
try {
|
||||||
logger.error('Seems that there is already an iframe in the container.')
|
if (!container) {
|
||||||
return false
|
logger.error('Cant found the livechat container.')
|
||||||
}
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Creating the iframe...
|
if (container.querySelector('converse-root')) {
|
||||||
const iframe = document.createElement('iframe')
|
logger.error('Seems that there is already a ConverseJS in the container.')
|
||||||
iframe.setAttribute('src', iframeUri)
|
return false
|
||||||
iframe.setAttribute('sandbox', 'allow-same-origin allow-scripts allow-popups allow-forms')
|
}
|
||||||
iframe.setAttribute('frameborder', '0')
|
|
||||||
if (additionalStyles) {
|
|
||||||
iframe.setAttribute('style', additionalStyles)
|
|
||||||
}
|
|
||||||
container.append(iframe)
|
|
||||||
container.setAttribute('peertube-plugin-livechat-state', 'open')
|
|
||||||
|
|
||||||
// Hacking styles...
|
// Loading converseJS...
|
||||||
hackStyles(true)
|
await displayConverseJS(registerOptions, container, roomkey, 'peertube-video')
|
||||||
|
|
||||||
|
if (additionalStyles) {
|
||||||
|
container.setAttribute('style', additionalStyles)
|
||||||
|
}
|
||||||
|
container.setAttribute('peertube-plugin-livechat-state', 'open')
|
||||||
|
|
||||||
|
// Hacking styles...
|
||||||
|
hackStyles(true)
|
||||||
|
} catch (err) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeChat (): void {
|
function closeChat (): void {
|
||||||
@ -223,8 +224,12 @@ function register (registerOptions: RegisterClientOptions): void {
|
|||||||
logger.error('Cant close livechat, container not found.')
|
logger.error('Cant close livechat, container not found.')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
container.querySelectorAll('iframe')
|
|
||||||
.forEach(dom => dom.remove())
|
// Disconnecting ConverseJS
|
||||||
|
if (window.converse?.livechatDisconnect) { window.converse.livechatDisconnect() }
|
||||||
|
|
||||||
|
// Removing from the DOM
|
||||||
|
container.childNodes.forEach(dom => dom.remove())
|
||||||
|
|
||||||
container.setAttribute('peertube-plugin-livechat-state', 'closed')
|
container.setAttribute('peertube-plugin-livechat-state', 'closed')
|
||||||
|
|
||||||
@ -232,7 +237,7 @@ function register (registerOptions: RegisterClientOptions): void {
|
|||||||
hackStyles(false)
|
hackStyles(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
function initChat (video: Video): void {
|
async function initChat (video: Video): Promise<void> {
|
||||||
if (!video) {
|
if (!video) {
|
||||||
logger.error('No video provided')
|
logger.error('No video provided')
|
||||||
return
|
return
|
||||||
@ -254,15 +259,15 @@ function register (registerOptions: RegisterClientOptions): void {
|
|||||||
container.setAttribute('peertube-plugin-livechat-current-url', window.location.href)
|
container.setAttribute('peertube-plugin-livechat-current-url', window.location.href)
|
||||||
placeholder.append(container)
|
placeholder.append(container)
|
||||||
|
|
||||||
peertubeHelpers.getSettings().then((s: any) => {
|
try {
|
||||||
settings = s
|
settings = await peertubeHelpers.getSettings()
|
||||||
|
|
||||||
logger.log('Checking if this video should have a chat...')
|
logger.log('Checking if this video should have a chat...')
|
||||||
if (settings['chat-no-anonymous'] === true && isAnonymousUser(registerOptions)) {
|
if (settings['chat-no-anonymous'] === true && isAnonymousUser(registerOptions)) {
|
||||||
logger.log('No chat for anonymous users')
|
logger.log('No chat for anonymous users')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!videoHasWebchat(s, video) && !videoHasRemoteWebchat(s, video)) {
|
if (!videoHasWebchat(settings, video) && !videoHasRemoteWebchat(settings, video)) {
|
||||||
logger.log('This video has no webchat')
|
logger.log('This video has no webchat')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -279,18 +284,16 @@ function register (registerOptions: RegisterClientOptions): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
insertChatDom(container as HTMLElement, video, !!settings['chat-open-blank'], showShareUrlButton).then(() => {
|
await insertChatDom(container as HTMLElement, video, !!settings['chat-open-blank'], showShareUrlButton)
|
||||||
if (settings['chat-auto-display']) {
|
if (settings['chat-auto-display']) {
|
||||||
openChat(video)
|
await openChat(video)
|
||||||
} else if (container) {
|
} else if (container) {
|
||||||
container.setAttribute('peertube-plugin-livechat-state', 'closed')
|
container.setAttribute('peertube-plugin-livechat-state', 'closed')
|
||||||
}
|
}
|
||||||
}, () => {
|
} catch (err) {
|
||||||
logger.error('insertChatDom has failed')
|
logger.error('initChat has failed')
|
||||||
})
|
logger.error(err as string)
|
||||||
}, () => {
|
}
|
||||||
logger.error('Cant get settings')
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let savedMyPluginFlexGrow: string | undefined
|
let savedMyPluginFlexGrow: string | undefined
|
||||||
@ -335,7 +338,7 @@ function register (registerOptions: RegisterClientOptions): void {
|
|||||||
logger.info('We are in a playlist, we will not use the webchat')
|
logger.info('We are in a playlist, we will not use the webchat')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
initChat(video)
|
initChat(video).then(() => {}, () => {})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { InitConverseJSParams } from 'shared/lib/types'
|
import type { InitConverseJSParams, ChatIncludeMode } from 'shared/lib/types'
|
||||||
import { inIframe } from './lib/utils'
|
import { inIframe } from './lib/utils'
|
||||||
import { initDom } from './lib/dom'
|
import { initDom } from './lib/dom'
|
||||||
import {
|
import {
|
||||||
@ -54,14 +54,6 @@ function initConversePlugins (peertubeEmbedded: boolean): void {
|
|||||||
}
|
}
|
||||||
window.initConversePlugins = initConversePlugins
|
window.initConversePlugins = initConversePlugins
|
||||||
|
|
||||||
/**
|
|
||||||
* ChatIncludeMode:
|
|
||||||
* - chat-only: the chat is on a full page, without Peertube
|
|
||||||
* - peertube-fullpage: the chat is embedded in Peertube, in a full custom page
|
|
||||||
* - peertube-video: the chat is embedded in Peertube, beside a video
|
|
||||||
*/
|
|
||||||
type ChatIncludeMode = 'chat-only' | 'peertube-fullpage' | 'peertube-video'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Init ConverseJS
|
* Init ConverseJS
|
||||||
* @param initConverseParams ConverseJS init Params
|
* @param initConverseParams ConverseJS init Params
|
||||||
|
@ -2,9 +2,12 @@
|
|||||||
@import "shared/styles/index";
|
@import "shared/styles/index";
|
||||||
@import "./peertubetheme";
|
@import "./peertubetheme";
|
||||||
|
|
||||||
body.livechat-iframe #conversejs .chat-head {
|
peertube-plugin-livechat-container,
|
||||||
// Hidding the chat-head when the plugin is displayed in an iframe.
|
body.livechat-iframe {
|
||||||
display: none;
|
#conversejs .chat-head {
|
||||||
|
// Hidding the chat-head when the plugin is displayed in an iframe or besides a video.
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#conversejs-bg {
|
#conversejs-bg {
|
||||||
|
@ -4,7 +4,7 @@ function initDom ({ forceReadonly, transparent }: InitConverseJSParams, isInIfra
|
|||||||
const body = document.querySelector('body')
|
const body = document.querySelector('body')
|
||||||
if (isInIframe) {
|
if (isInIframe) {
|
||||||
if (body) {
|
if (body) {
|
||||||
body.classList.add('livechat-iframe')
|
body.classList.add('livechat-iframe') // we need to keep this, for embedded chats in external websites
|
||||||
// prevent horizontal scrollbar when in iframe. (don't know why, but does not work if done by CSS)
|
// prevent horizontal scrollbar when in iframe. (don't know why, but does not work if done by CSS)
|
||||||
body.style.overflowX = 'hidden'
|
body.style.overflowX = 'hidden'
|
||||||
}
|
}
|
||||||
|
@ -90,6 +90,16 @@ interface ChannelConfiguration {
|
|||||||
configuration: ChannelConfigurationOptions
|
configuration: ChannelConfigurationOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ChatPeertubeIncludeMode = 'peertube-fullpage' | 'peertube-video'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ChatIncludeMode:
|
||||||
|
* - chat-only: the chat is on a full page, without Peertube
|
||||||
|
* - peertube-fullpage: the chat is embedded in Peertube, in a full custom page
|
||||||
|
* - peertube-video: the chat is embedded in Peertube, beside a video
|
||||||
|
*/
|
||||||
|
type ChatIncludeMode = 'chat-only' | ChatPeertubeIncludeMode
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
ConverseJSTheme,
|
ConverseJSTheme,
|
||||||
InitConverseJSParams,
|
InitConverseJSParams,
|
||||||
@ -98,5 +108,7 @@ export type {
|
|||||||
ProsodyListRoomsResultRoom,
|
ProsodyListRoomsResultRoom,
|
||||||
ChannelInfos,
|
ChannelInfos,
|
||||||
ChannelConfigurationOptions,
|
ChannelConfigurationOptions,
|
||||||
ChannelConfiguration
|
ChannelConfiguration,
|
||||||
|
ChatIncludeMode,
|
||||||
|
ChatPeertubeIncludeMode
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user