diff --git a/CHANGELOG.md b/CHANGELOG.md
index b8514dbd..0ce2626c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@ TODO: tag conversejs livechat branch, and replace commit ID in build-converse.js
* Fix cleanup on channel deletion.
* #416: Deregister prosodyctl interval callback when spawn.stdin disappears.
* #423: Merging video-watch scope into common scope.
+* Rewriting the share chat dialog with more modern code.
## 10.0.2
diff --git a/assets/styles/style.scss b/assets/styles/style.scss
index 3dc28bd6..088f6c9f 100644
--- a/assets/styles/style.scss
+++ b/assets/styles/style.scss
@@ -4,6 +4,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
+@use "style/elements/share-chat.scss";
+
#peertube-plugin-livechat-container {
display: flex;
flex-direction: column;
@@ -143,66 +145,6 @@ table.peertube-plugin-livechat-prosody-list-rooms td {
padding: 4px 5px;
}
-.peertube-plugin-livechat-shareurl-modal {
- & > * {
- margin-top: 10px;
- }
-
- .livechat-shareurl-copy {
- display: flex;
- flex-wrap: wrap;
-
- button {
- white-space: nowrap;
- }
-
- input {
- flex-grow: 2;
- width: auto !important; // must cancel the width: 100% of form-control
- }
- }
-
- .livechat-shareurl-web-options,
- .livechat-shareurl-xmpp-options {
- input[type="checkbox"],
- input[type="radio"] {
- margin-right: 20px;
- }
-
- label {
- display: block;
- }
-
- .livechat-shareurl-web-options-readonly {
- margin-left: 40px;
-
- & > * {
- display: block;
- }
-
- &.livechat-shareurl-web-options-readonly-disabled {
- label {
- color: var(--gray-dark);
- }
- }
- }
- }
-
- .livechat-shareurl-protocol {
- display: flex;
- flex-flow: row wrap;
- column-gap: 30px;
-
- input[type="radio"] {
- margin-right: 10px;
- }
- }
-
- .livechat-shareurl-tips {
- min-height: 60px;
- }
-}
-
livechat-spinner,
.livechat-spinner {
display: flex;
diff --git a/assets/styles/style/elements/share-chat.scss b/assets/styles/style/elements/share-chat.scss
new file mode 100644
index 00000000..e75e5576
--- /dev/null
+++ b/assets/styles/style/elements/share-chat.scss
@@ -0,0 +1,77 @@
+/*
+ * SPDX-FileCopyrightText: 2024 John Livingston
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+livechat-share-chat {
+ display: block;
+
+ & > * {
+ margin-top: 10px;
+ }
+
+ .sub-menu-entry {
+ cursor: pointer;
+ }
+
+ .livechat-shareurl-copy {
+ display: flex;
+ flex-wrap: wrap;
+
+ button {
+ white-space: nowrap;
+ }
+
+ input {
+ flex-grow: 2;
+ width: auto !important; // must cancel the width: 100% of form-control
+ }
+ }
+
+ .livechat-shareurl-block {
+ // To avoid the height to change when switching tabs, we set a height and overflow.
+ height: 300px;
+ overflow-y: scroll;
+ }
+
+ .livechat-shareurl-options {
+ input[type="checkbox"],
+ input[type="radio"] {
+ margin-right: 20px;
+ }
+
+ label {
+ display: block;
+ }
+
+ .livechat-shareurl-suboptions {
+ margin-left: 40px;
+
+ & > * {
+ display: block;
+ }
+
+ &.livechat-shareurl-suboptions-disabled {
+ label {
+ /* stylelint-disable-next-line custom-property-pattern */
+ color: var(--greyForegroundColor);
+ }
+ }
+ }
+ }
+
+ .livechat-shareurl-protocol {
+ display: flex;
+ flex-flow: row wrap;
+ column-gap: 30px;
+
+ input[type="radio"] {
+ margin-right: 10px;
+ }
+ }
+
+ .livechat-shareurl-tips {
+ min-height: 60px;
+ }
+}
diff --git a/client/@types/global.d.ts b/client/@types/global.d.ts
index 02f40a25..b44834c9 100644
--- a/client/@types/global.d.ts
+++ b/client/@types/global.d.ts
@@ -110,3 +110,6 @@ declare const LOC_ACTION_REMOVE_ENTRY: string
declare const LOC_ACTION_REMOVE_ENTRY_CONFIRM: string
declare const LOC_LOADING_ERROR: string
+
+declare const LOC_SHARE_CHAT_EMBED: string
+declare const LOC_SHARE_CHAT_PEERTUBE_TIPS: string
diff --git a/client/common/lib/contexts/peertube.ts b/client/common/lib/contexts/peertube.ts
index 3cbe0a06..0dac9914 100644
--- a/client/common/lib/contexts/peertube.ts
+++ b/client/common/lib/contexts/peertube.ts
@@ -4,6 +4,7 @@
import type { SettingEntries } from '@peertube/peertube-types'
import type { RegisterClientOptions } from '@peertube/peertube-types/client/types'
+import type { ConverseJSTheme } from 'shared/lib/types'
import { logger as loggerFunction } from '../../../utils/logger'
// We precise some of the settings in SettingsEntries, to faciliate some type checking.
@@ -14,6 +15,9 @@ export type LiveChatSettings = SettingEntries & {
'chat-videos-list': string
'federation-no-remote-chat': boolean
'chat-style': string | undefined
+ 'prosody-room-allow-s2s': boolean
+ 'converse-theme': ConverseJSTheme
+ 'prosody-room-type': string
}
export class PtContext {
diff --git a/client/common/videowatch/elements/index.ts b/client/common/videowatch/elements/index.ts
new file mode 100644
index 00000000..5142fd40
--- /dev/null
+++ b/client/common/videowatch/elements/index.ts
@@ -0,0 +1 @@
+import './share-chat'
diff --git a/client/common/videowatch/elements/share-chat.ts b/client/common/videowatch/elements/share-chat.ts
new file mode 100644
index 00000000..49568a5c
--- /dev/null
+++ b/client/common/videowatch/elements/share-chat.ts
@@ -0,0 +1,225 @@
+// SPDX-FileCopyrightText: 2024 John Livingston
+//
+// SPDX-License-Identifier: AGPL-3.0-only
+
+import type { Video } from '@peertube/peertube-types'
+import type { LiveChatSettings } from '../../lib/contexts/peertube'
+import { html, PropertyValues, TemplateResult } from 'lit'
+import { customElement, property } from 'lit/decorators.js'
+import { LivechatElement } from '../../lib/elements/livechat'
+import { tplShareChatCopy, tplShareChatTips, tplShareChatTabs, tplShareChatOptions } from './templates/share-chat'
+import { isAutoColorsAvailable } from 'shared/lib/autocolors'
+import { getIframeUri, getXMPPAddr, UriOptions } from '../uri'
+
+/**
+ * Results of the ShareChatElement.computeUrl method.
+ */
+interface ComputedUrl {
+ /**
+ * The string to share.
+ */
+ shareString: string
+
+ /**
+ * The url to open in a browser window, if relevant (http:// or xmpp://).
+ * Undefined when not a standard uri (for iframes for exemple).
+ * This will be the url used by the "open" button.
+ */
+ openUrl: string | undefined
+}
+
+@customElement('livechat-share-chat')
+export class ShareChatElement extends LivechatElement {
+ /**
+ * The associated video.
+ * Must be given when calling this custom element.
+ */
+ @property({ attribute: false })
+ protected _video!: Video
+
+ /**
+ * The settings.
+ * Must be given when calling this custom element.
+ */
+ @property({ attribute: false })
+ protected _settings!: LiveChatSettings
+
+ /**
+ * The current tab.
+ */
+ @property({ attribute: false })
+ public currentTab: 'peertube' | 'embed' | 'xmpp' = 'peertube'
+
+ /**
+ * Should we render the XMPP tab?
+ */
+ @property({ attribute: false })
+ public xmppUriEnabled: boolean = false
+
+ /**
+ * Can we use autocolors?
+ */
+ @property({ attribute: false })
+ public autocolorsAvailable: boolean = false
+
+ /**
+ * In the Embed tab, should we generated an iframe link.
+ */
+ @property({ attribute: false })
+ public embedIFrame: boolean = false
+
+ /**
+ * In the Embed tab, should we generated a read-only chat link.
+ */
+ @property({ attribute: false })
+ public embedReadOnly: boolean = false
+
+ /**
+ * Read-only, with scrollbar?
+ */
+ @property({ attribute: false })
+ public embedReadOnlyScrollbar: boolean = false
+
+ /**
+ * Read-only, transparent background?
+ */
+ @property({ attribute: false })
+ public embedReadOnlyTransparentBackground: boolean = false
+
+ /**
+ * In the Embed tab, should we use current theme color?
+ */
+ @property({ attribute: false })
+ public embedAutocolors: boolean = false
+
+ protected override firstUpdated (changedProperties: PropertyValues): void {
+ super.firstUpdated(changedProperties)
+ const settings = this._settings
+ this.xmppUriEnabled = !!settings['prosody-room-allow-s2s']
+ this.autocolorsAvailable = isAutoColorsAvailable(settings['converse-theme'])
+
+ this._restorePreviousState()
+ }
+
+ protected override render = (): TemplateResult => {
+ return html`
+ ${tplShareChatTabs(this)}
+ ${tplShareChatCopy(this)}
+
+ ${tplShareChatTips(this)}
+ ${tplShareChatOptions(this)}
+
+ `
+ }
+
+ protected _restorePreviousState (): void {
+ // TODO: restore previous state.
+
+ // Some sanity checks, to not be in an impossible state.
+ if (!this.xmppUriEnabled && this.currentTab === 'xmpp') {
+ this.currentTab = 'peertube'
+ }
+ }
+
+ public computeUrl (): ComputedUrl {
+ switch (this.currentTab) {
+ case 'peertube': return this._computeUrlPeertube()
+ case 'embed': return this._computeUrlEmbed()
+ case 'xmpp': return this._computeUrlXMPP()
+ default:
+ return {
+ shareString: '',
+ openUrl: undefined
+ }
+ }
+ }
+
+ protected _computeUrlPeertube (): ComputedUrl {
+ const url = window.location.protocol +
+ '//' +
+ window.location.host +
+ '/p/livechat/room?room=' +
+ encodeURIComponent(this._video.uuid)
+ return {
+ shareString: url,
+ openUrl: url
+ }
+ }
+
+ protected _computeUrlXMPP (): ComputedUrl {
+ const addr = getXMPPAddr(this.ptContext.ptOptions, this._settings, this._video)
+ return {
+ shareString: addr?.jid ?? '',
+ openUrl: addr?.uri
+ }
+ }
+
+ protected _computeUrlEmbed (): ComputedUrl {
+ const uriOptions: UriOptions = {
+ ignoreAutoColors: this.autocolorsAvailable ? !this.embedAutocolors : true,
+ permanent: true
+ }
+
+ if (this.embedReadOnly) {
+ uriOptions.readonly = this.embedReadOnlyScrollbar ? true : 'noscroll'
+ if (this.embedReadOnlyTransparentBackground) {
+ uriOptions.transparent = true
+ }
+ }
+
+ // Note: for the "embed" case, the url is always the same as the iframe.
+ // So we use getIframeUri to compte, and just change the finale result if we really want the iframe.
+ const url = getIframeUri(this.ptContext.ptOptions, this._settings, this._video, uriOptions)
+ if (!url) {
+ return {
+ shareString: '',
+ openUrl: undefined
+ }
+ }
+
+ if (!this.embedIFrame) {
+ return {
+ shareString: url,
+ openUrl: url
+ }
+ }
+
+ // Actually building the iframe:
+ const iframe = document.createElement('iframe')
+ iframe.setAttribute('src', url)
+ iframe.setAttribute('title', this._video.name)
+ iframe.setAttribute('sandbox', 'allow-same-origin allow-scripts allow-popups allow-forms')
+ iframe.setAttribute('width', '560')
+ iframe.setAttribute('height', '315')
+ iframe.setAttribute('frameborder', '0')
+ const iframeHTML = iframe.outerHTML
+ iframe.remove()
+ return {
+ shareString: iframeHTML,
+ openUrl: undefined
+ }
+ }
+
+ /**
+ * Copy the current url in the clipboard.
+ */
+ public async copyUrl (): Promise {
+ await navigator.clipboard.writeText(this.computeUrl().shareString)
+ this.ptNotifier.success(await this.ptTranslate(LOC_COPIED))
+ }
+
+ /**
+ * Opens the url.
+ */
+ public openUrl (): void {
+ const url = this.computeUrl().openUrl
+ if (!url) {
+ return
+ }
+ window.open(url)
+ }
+
+ public switchTab (tab: ShareChatElement['currentTab']): void {
+ this.currentTab = tab
+ }
+}
diff --git a/client/common/videowatch/elements/templates/share-chat.ts b/client/common/videowatch/elements/templates/share-chat.ts
new file mode 100644
index 00000000..561deab7
--- /dev/null
+++ b/client/common/videowatch/elements/templates/share-chat.ts
@@ -0,0 +1,171 @@
+import type { ShareChatElement } from '../share-chat'
+import { html, TemplateResult } from 'lit'
+import { ptTr } from '../../../lib/directives/translation'
+import { classMap } from 'lit/directives/class-map.js'
+
+export function tplShareChatCopy (el: ShareChatElement): TemplateResult {
+ const computedUrl = el.computeUrl()
+ return html`
+ {
+ const input = ev.target as HTMLInputElement
+ // Select the whole value when entering the input.
+ input.select()
+ input.setSelectionRange(0, 99999) /* For mobile devices */
+ }}
+ />
+
+ ${ptTr(LOC_COPY)}
+
+
+ ${ptTr(LOC_OPEN)}
+
+
+
`
+}
+
+function _tplShareChatTab (
+ el: ShareChatElement,
+ tabName: ShareChatElement['currentTab'],
+ label: string
+): TemplateResult {
+ return html`
+ {
+ ev.preventDefault()
+ el.switchTab(tabName)
+ }}
+ >
+ ${ptTr(label)}
+ `
+}
+
+export function tplShareChatTabs (el: ShareChatElement): TemplateResult {
+ return html`
+ ${_tplShareChatTab(el, 'peertube', LOC_WEB)}
+ ${_tplShareChatTab(el, 'embed', LOC_SHARE_CHAT_EMBED)}
+ ${
+ el.xmppUriEnabled
+ ? _tplShareChatTab(el, 'xmpp', LOC_CONNECT_USING_XMPP)
+ : ''
+ }
+ `
+}
+
+export function tplShareChatTips (el: ShareChatElement): TemplateResult {
+ let label: string | undefined
+ switch (el.currentTab) {
+ case 'peertube':
+ label = LOC_SHARE_CHAT_PEERTUBE_TIPS
+ break
+ case 'embed':
+ label = LOC_TIPS_FOR_STREAMERS
+ break
+ case 'xmpp':
+ label = LOC_CONNECT_USING_XMPP_HELP
+ break
+ }
+ if (!label) {
+ return html``
+ }
+ return html`${ptTr(label)}
`
+}
+
+function _tplShareChatPeertubeOptions (_el: ShareChatElement): TemplateResult {
+ return html``
+}
+
+function _tplShareChatEmbedOptions (el: ShareChatElement): TemplateResult {
+ return html`
+
+ { el.embedIFrame = !el.embedIFrame }}
+ />
+ ${ptTr(LOC_GENERATE_IFRAME)}
+
+
+
+ { el.embedReadOnly = !el.embedReadOnly }}
+ />
+ ${ptTr(LOC_READ_ONLY)}
+
+
+
+ { el.embedReadOnlyScrollbar = !el.embedReadOnlyScrollbar }}
+ />
+ ${ptTr(LOC_SHOW_SCROLLBARR)}
+
+
+ { el.embedReadOnlyTransparentBackground = !el.embedReadOnlyTransparentBackground }}
+ />
+ ${ptTr(LOC_TRANSPARENT_BACKGROUND)}
+
+
+
+ ${
+ el.autocolorsAvailable
+ ? html`
+
+ { el.embedAutocolors = !el.embedAutocolors }}
+ />
+ ${ptTr(LOC_USE_CURRENT_THEME_COLOR)}
+ `
+ : ''
+ }
+ `
+}
+
+function _tplShareChatXMPPOptions (_el: ShareChatElement): TemplateResult {
+ return html``
+}
+
+export function tplShareChatOptions (el: ShareChatElement): TemplateResult {
+ let tpl: TemplateResult
+ switch (el.currentTab) {
+ case 'peertube':
+ tpl = _tplShareChatPeertubeOptions(el)
+ break
+ case 'embed':
+ tpl = _tplShareChatEmbedOptions(el)
+ break
+ case 'xmpp':
+ tpl = _tplShareChatXMPPOptions(el)
+ break
+ default:
+ tpl = html``
+ }
+ return html`${tpl}
`
+}
diff --git a/client/common/videowatch/register.ts b/client/common/videowatch/register.ts
index e8018d6a..4d431d6d 100644
--- a/client/common/videowatch/register.ts
+++ b/client/common/videowatch/register.ts
@@ -5,6 +5,7 @@
import type { Video } from '@peertube/peertube-types'
import { getPtContext } from '../lib/contexts/peertube'
import { initChat } from './chat'
+import './elements' // Import all needed elements.
interface VideoWatchLoadedHookOptions {
videojs: any
diff --git a/client/common/videowatch/share.ts b/client/common/videowatch/share.ts
index c6404a47..c1a2c305 100644
--- a/client/common/videowatch/share.ts
+++ b/client/common/videowatch/share.ts
@@ -4,385 +4,68 @@
import type { RegisterClientOptions } from '@peertube/peertube-types/client'
import type { Video } from '@peertube/peertube-types'
-import { helpButtonSVG } from './buttons'
+import type { LiveChatSettings } from '../lib/contexts/peertube'
import { logger } from '../../utils/logger'
-import { getIframeUri, getXMPPAddr, UriOptions } from './uri'
-import { isAutoColorsAvailable } from 'shared/lib/autocolors'
-import { localizedHelpUrl } from '../../utils/help'
+import { html, render } from 'lit'
-interface ShareForm {
- shareString: HTMLInputElement
- openButton: HTMLButtonElement
- copyButton: HTMLButtonElement
- readonly: HTMLInputElement
- withscroll: HTMLInputElement
- transparent: HTMLInputElement
- readonlyOptions: HTMLElement
- autoColors?: HTMLInputElement
- generateIframe: HTMLInputElement
- divTips: HTMLElement
- radioProtocolWeb?: HTMLInputElement
- radioProtocolXMPP?: HTMLInputElement
- divWebOptions: HTMLDivElement
-}
-
-async function shareChatUrl (registerOptions: RegisterClientOptions, settings: any, video: Video): Promise {
+async function shareChatUrl (
+ registerOptions: RegisterClientOptions,
+ settings: LiveChatSettings,
+ video: Video
+): Promise {
const peertubeHelpers = registerOptions.peertubeHelpers
- const streamersHelpUrl = await localizedHelpUrl(registerOptions, {
- page: 'documentation/user/streamers'
- })
+ const labelShare = await peertubeHelpers.translate(LOC_SHARE_CHAT_LINK)
- const [
- labelShare,
- labelWeb,
- labelXMPP,
- labelXMPPTips,
- labelReadonly,
- labelWithscroll,
- labelTransparent,
- labelOBSTips,
- labelCopy,
- labelCopied,
- labelError,
- labelOpen,
- labelAutocolors,
- labelGenerateIframe,
- labelChatFor,
- labelHelp
- ] = await Promise.all([
- peertubeHelpers.translate(LOC_SHARE_CHAT_LINK),
- peertubeHelpers.translate(LOC_WEB),
- peertubeHelpers.translate(LOC_CONNECT_USING_XMPP),
- peertubeHelpers.translate(LOC_CONNECT_USING_XMPP_HELP),
- peertubeHelpers.translate(LOC_READ_ONLY),
- peertubeHelpers.translate(LOC_SHOW_SCROLLBARR),
- peertubeHelpers.translate(LOC_TRANSPARENT_BACKGROUND),
- peertubeHelpers.translate(LOC_TIPS_FOR_STREAMERS),
- peertubeHelpers.translate(LOC_COPY),
- peertubeHelpers.translate(LOC_LINK_COPIED),
- peertubeHelpers.translate(LOC_ERROR),
- peertubeHelpers.translate(LOC_OPEN),
- peertubeHelpers.translate(LOC_USE_CURRENT_THEME_COLOR),
- peertubeHelpers.translate(LOC_GENERATE_IFRAME),
- peertubeHelpers.translate(LOC_CHAT_FOR_LIVE_STREAM),
- peertubeHelpers.translate(LOC_ONLINE_HELP)
- ])
+ // function save (form: ShareForm): void {
+ // if (!window.localStorage) {
+ // return
+ // }
+ // const v = {
+ // version: 1, // in case we add incompatible values in a near feature
+ // readonly: !!form.readonly.checked,
+ // withscroll: !!form.withscroll.checked,
+ // transparent: !!form.transparent.checked,
+ // autocolors: !!form.autoColors?.checked,
+ // generateIframe: !!form.generateIframe.checked,
+ // protocol: !form.radioProtocolWeb || form.radioProtocolWeb.checked ? 'web' : 'xmpp'
+ // }
+ // window.localStorage.setItem('peertube-plugin-livechat-shareurl', JSON.stringify(v))
+ // }
- const defaultUri = getIframeUri(registerOptions, settings, video)
- if (!defaultUri) {
- return
- }
-
- let form: ShareForm | undefined
- function renderContent (container: HTMLElement): void {
- if (!form) {
- container.childNodes.forEach(child => container.removeChild(child))
-
- container.classList.add('peertube-plugin-livechat-shareurl-modal')
-
- const divShareString = document.createElement('div')
- divShareString.classList.add('livechat-shareurl-copy')
- const shareString = document.createElement('input')
- shareString.setAttribute('type', 'text')
- shareString.setAttribute('readonly', '')
- shareString.setAttribute('autocomplete', 'off')
- shareString.setAttribute('placeholder', '')
- shareString.classList.add('form-control', 'readonly')
- divShareString.append(shareString)
- const copyButton = document.createElement('button')
- copyButton.classList.add('btn', 'btn-outline-secondary', 'text-uppercase')
- copyButton.textContent = labelCopy
- divShareString.append(copyButton)
- const openButton = document.createElement('button')
- openButton.classList.add('btn', 'btn-outline-secondary', 'text-uppercase')
- openButton.textContent = labelOpen
- divShareString.append(openButton)
-
- const helpButton = document.createElement('a')
- helpButton.href = streamersHelpUrl
- helpButton.target = '_blank'
- helpButton.innerHTML = helpButtonSVG()
- helpButton.title = labelHelp
- helpButton.classList.add('orange-button', 'peertube-button-link', 'peertube-plugin-livechat-button')
- divShareString.append(helpButton)
-
- container.append(divShareString)
-
- let radioProtocolWeb
- let radioProtocolXMPP
- if (settings['prosody-room-allow-s2s']) {
- const protocolContainer = document.createElement('div')
- protocolContainer.classList.add('livechat-shareurl-protocol')
-
- radioProtocolWeb = document.createElement('input')
- radioProtocolWeb.setAttribute('type', 'radio')
- radioProtocolWeb.setAttribute('value', 'web')
- radioProtocolWeb.setAttribute('name', 'protocol')
- const radioProtocolWebLabel = document.createElement('label')
- radioProtocolWebLabel.textContent = labelWeb
- radioProtocolWebLabel.prepend(radioProtocolWeb)
- protocolContainer.append(radioProtocolWebLabel)
-
- radioProtocolXMPP = document.createElement('input')
- radioProtocolXMPP.setAttribute('type', 'radio')
- radioProtocolXMPP.setAttribute('value', 'xmpp')
- radioProtocolXMPP.setAttribute('name', 'protocol')
- const radioProtocolXMPPLabel = document.createElement('label')
- radioProtocolXMPPLabel.textContent = labelXMPP
- radioProtocolXMPPLabel.prepend(radioProtocolXMPP)
- protocolContainer.append(radioProtocolXMPPLabel)
-
- container.append(protocolContainer)
- }
-
- const divTips = document.createElement('div')
- divTips.textContent = ''
- divTips.classList.add('livechat-shareurl-tips')
- container.append(divTips)
-
- const divWebOptions = document.createElement('div')
- divWebOptions.classList.add('livechat-shareurl-web-options')
- container.append(divWebOptions)
-
- const readonly = document.createElement('input')
- readonly.setAttribute('type', 'checkbox')
- const readonlyLabelEl = document.createElement('label')
- readonlyLabelEl.textContent = labelReadonly
- readonlyLabelEl.prepend(readonly)
- divWebOptions.append(readonlyLabelEl)
-
- const readonlyOptions = document.createElement('div')
- readonlyOptions.classList.add('livechat-shareurl-web-options-readonly')
- divWebOptions.append(readonlyOptions)
-
- const withscroll = document.createElement('input')
- withscroll.setAttribute('type', 'checkbox')
- const withscrollLabelEl = document.createElement('label')
- withscrollLabelEl.textContent = labelWithscroll
- withscrollLabelEl.prepend(withscroll)
- readonlyOptions.append(withscrollLabelEl)
-
- const transparent = document.createElement('input')
- transparent.setAttribute('type', 'checkbox')
- const transparentLabelEl = document.createElement('label')
- transparentLabelEl.textContent = labelTransparent
- transparentLabelEl.prepend(transparent)
- readonlyOptions.append(transparentLabelEl)
-
- let autoColors
- if (isAutoColorsAvailable(settings['converse-theme'])) {
- const label = document.createElement('label')
- label.innerText = labelAutocolors
- autoColors = document.createElement('input')
- autoColors.setAttribute('type', 'checkbox')
- label.prepend(autoColors)
- divWebOptions.append(label)
- }
-
- const generateIframe = document.createElement('input')
- generateIframe.setAttribute('type', 'checkbox')
- const generateIframeLabelEl = document.createElement('label')
- generateIframeLabelEl.textContent = labelGenerateIframe
- generateIframeLabelEl.prepend(generateIframe)
- divWebOptions.append(generateIframeLabelEl)
-
- if (radioProtocolWeb) {
- radioProtocolWeb.onclick = () => {
- renderContent(container)
- }
- }
- if (radioProtocolXMPP) {
- radioProtocolXMPP.onclick = () => {
- renderContent(container)
- }
- }
-
- readonly.onclick = () => {
- renderContent(container)
- }
- withscroll.onclick = () => {
- renderContent(container)
- }
- transparent.onclick = () => {
- renderContent(container)
- }
- if (autoColors) {
- autoColors.onclick = () => {
- renderContent(container)
- }
- }
- generateIframe.onclick = () => {
- renderContent(container)
- }
-
- shareString.onclick = () => {
- shareString.select()
- shareString.setSelectionRange(0, 99999) /* For mobile devices */
- }
-
- copyButton.onclick = () => {
- shareString.select()
- shareString.setSelectionRange(0, 99999) /* For mobile devices */
- navigator.clipboard.writeText(shareString.value).then(() => {
- peertubeHelpers.notifier.success(labelCopied)
- }, () => {
- peertubeHelpers.notifier.error(labelError)
- })
- }
-
- openButton.onclick = () => {
- const uri = shareString.getAttribute('open-uri') ?? shareString.value
- // Don't open the url if it is an iframe!
- if (uri.startsWith('http') || uri.startsWith('xmpp')) {
- window.open(uri)
- }
- }
-
- form = {
- shareString,
- copyButton,
- openButton,
- readonly,
- withscroll,
- transparent,
- readonlyOptions,
- autoColors,
- generateIframe,
- radioProtocolWeb,
- radioProtocolXMPP,
- divWebOptions,
- divTips
- }
- restore(form)
- }
-
- // Saving the form state, to restore each time the modal is opened.
- save(form)
-
- const uriOptions: UriOptions = {
- ignoreAutoColors: form.autoColors ? !form.autoColors.checked : true,
- permanent: true
- }
- if (form.radioProtocolXMPP?.checked) {
- // To minimize the height gap between the 2 modes,
- // and prevent the dialog to resize and move too much,
- // we use visibility instead of display
- form.divTips.textContent = labelXMPPTips
- form.divWebOptions.style.visibility = 'hidden'
- } else {
- form.divTips.textContent = labelOBSTips
- form.divWebOptions.style.visibility = 'visible'
- }
- if (form.readonly.checked) {
- if (form.withscroll.checked) {
- uriOptions.readonly = true
- } else {
- uriOptions.readonly = 'noscroll'
- }
- if (form.transparent.checked) {
- uriOptions.transparent = true
- }
- form.withscroll.disabled = false
- form.transparent.disabled = false
- form.readonlyOptions.classList.remove('livechat-shareurl-web-options-readonly-disabled')
- } else {
- form.withscroll.disabled = true
- form.transparent.disabled = true
- form.readonlyOptions.classList.add('livechat-shareurl-web-options-readonly-disabled')
- }
- let shareStringValue
- let shareStringOpen: string | undefined
- if (!form.radioProtocolXMPP?.checked) {
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- if (form.readonly?.checked || form.autoColors?.checked || form.generateIframe?.checked) {
- shareStringValue = getIframeUri(registerOptions, settings, video, uriOptions)
- if (form.generateIframe.checked) {
- form.openButton.disabled = true
- if (shareStringValue) {
- // To properly escape all attributes, we are constructing an HTMLIframeElement
- const iframe = document.createElement('iframe')
- iframe.setAttribute('src', shareStringValue)
- iframe.setAttribute('title', labelChatFor + ' ' + video.name)
- iframe.setAttribute('sandbox', 'allow-same-origin allow-scripts allow-popups allow-forms')
- iframe.setAttribute('width', '560')
- iframe.setAttribute('height', '315')
- iframe.setAttribute('frameborder', '0')
- shareStringValue = iframe.outerHTML
- }
- } else {
- form.openButton.disabled = false
- }
- } else {
- shareStringValue = window.location.protocol +
- '//' +
- window.location.host +
- '/p/livechat/room?room=' +
- encodeURIComponent(video.uuid)
- }
- } else {
- // we must generate a XMPP room address
- // form.openButton.disabled = true
- const addr = getXMPPAddr(registerOptions, settings, video)
- shareStringValue = addr?.jid
- shareStringOpen = addr?.uri
- }
- form.shareString.setAttribute('value', shareStringValue ?? '')
- if (shareStringOpen) {
- form.shareString.setAttribute('open-uri', shareStringOpen)
- } else {
- form.shareString.removeAttribute('open-uri')
- }
- }
-
- function save (form: ShareForm): void {
- if (!window.localStorage) {
- return
- }
- const v = {
- version: 1, // in case we add incompatible values in a near feature
- readonly: !!form.readonly.checked,
- withscroll: !!form.withscroll.checked,
- transparent: !!form.transparent.checked,
- autocolors: !!form.autoColors?.checked,
- generateIframe: !!form.generateIframe.checked,
- protocol: !form.radioProtocolWeb || form.radioProtocolWeb.checked ? 'web' : 'xmpp'
- }
- window.localStorage.setItem('peertube-plugin-livechat-shareurl', JSON.stringify(v))
- }
-
- function restore (form: ShareForm): void {
- if (!window.localStorage) {
- return
- }
- const s = window.localStorage.getItem('peertube-plugin-livechat-shareurl')
- if (!s) {
- return
- }
- let v: any
- try {
- v = JSON.parse(s)
- if (!v || (typeof v !== 'object') || v.version !== 1) {
- return
- }
- form.readonly.checked = !!v.readonly
- form.withscroll.checked = !!v.withscroll
- form.transparent.checked = !!v.transparent
- if (form.autoColors) {
- form.autoColors.checked = !!v.autocolors
- }
- form.generateIframe.checked = !!v.generateIframe
- if (form.radioProtocolXMPP && v.protocol === 'xmpp') {
- form.radioProtocolXMPP.checked = true
- } else if (form.radioProtocolWeb) {
- form.radioProtocolWeb.checked = true
- }
- } catch (err) {
- logger.error(err as string)
- }
- }
+ // function restore (form: ShareForm): void {
+ // if (!window.localStorage) {
+ // return
+ // }
+ // const s = window.localStorage.getItem('peertube-plugin-livechat-shareurl')
+ // if (!s) {
+ // return
+ // }
+ // let v: any
+ // try {
+ // v = JSON.parse(s)
+ // if (!v || (typeof v !== 'object') || v.version !== 1) {
+ // return
+ // }
+ // form.readonly.checked = !!v.readonly
+ // form.withscroll.checked = !!v.withscroll
+ // form.transparent.checked = !!v.transparent
+ // if (form.autoColors) {
+ // form.autoColors.checked = !!v.autocolors
+ // }
+ // form.generateIframe.checked = !!v.generateIframe
+ // if (form.radioProtocolXMPP && v.protocol === 'xmpp') {
+ // form.radioProtocolXMPP.checked = true
+ // } else if (form.radioProtocolWeb) {
+ // form.radioProtocolWeb.checked = true
+ // }
+ // } catch (err) {
+ // logger.error(err as string)
+ // }
+ // }
logger.info('Opening the share modal...')
+ // We can't just put in modalContent, Peertube sanitize it...
const observer = new MutationObserver(mutations => {
for (const { addedNodes } of mutations) {
addedNodes.forEach(node => {
@@ -401,7 +84,14 @@ async function shareChatUrl (registerOptions: RegisterClientOptions, settings: a
logger.error('Modal has no body... Dont know how to fill it.')
return
}
- renderContent(modalBodyElem)
+ modalBodyElem.childNodes.forEach(child => modalBodyElem.removeChild(child))
+
+ render(html`
+
+ `, modalBodyElem)
}
})
}
@@ -411,7 +101,7 @@ async function shareChatUrl (registerOptions: RegisterClientOptions, settings: a
})
peertubeHelpers.showModal({
title: labelShare,
- content: `${defaultUri ?? ''}
`, // incase the observer is broken...
+ content: '',
close: true
})
// just in case, remove the observer after a timeout, if not already done...
diff --git a/client/common/videowatch/uri.ts b/client/common/videowatch/uri.ts
index 2cc9bb2b..b130df25 100644
--- a/client/common/videowatch/uri.ts
+++ b/client/common/videowatch/uri.ts
@@ -58,12 +58,17 @@ function getIframeUri (
return iframeUriStr
}
+interface GetXMPPAddrSettings {
+ 'prosody-room-allow-s2s': boolean
+ 'prosody-room-type': string
+}
+
interface XMPPAddr {
uri: string
jid: string
}
function getXMPPAddr (
- registerOptions: RegisterClientOptions, settings: any, video: Video
+ registerOptions: RegisterClientOptions, settings: GetXMPPAddrSettings, video: Video
): XMPPAddr | null {
// returns something like xmpp:256896ac-199a-4dab-bb3a-4fd916140272@room.instance.tdl?join
if (!settings['prosody-room-allow-s2s']) {
diff --git a/languages/en.yml b/languages/en.yml
index 10f51613..801b1554 100644
--- a/languages/en.yml
+++ b/languages/en.yml
@@ -9,8 +9,9 @@ read_only: "Read-only"
show_scrollbarr: "Show the scrollbar"
transparent_background: "Transparent background (for stream integration, with OBS
for example)"
-tips_for_streamers: "Tips for streamers: To add the chat to your OBS, generate a read-only
- link and use it as a browser source."
+tips_for_streamers: |
+ Tips for streamers: To embed the chat in your video stream using OBS for example,
+ generate a read-only link and use it as a browser source.
copy: "Copy"
copied: "Copied"
link_copied: "Link copied"
@@ -518,3 +519,5 @@ action_remove_entry: Remove this entry
action_remove_entry_confirm: Are you sure you want to remove this entry?
loading_error: An error occured while loading data.
+share_chat_embed: Embed
+share_chat_peertube_tips: This link will open the chat within the Peertube interface.