Skip to content

Commit

Permalink
Various changes.
Browse files Browse the repository at this point in the history
- Add docstrings.
- Render only hostname below video element
- Handle no headers being returned
  • Loading branch information
jcbrand committed Jan 12, 2025
1 parent cff07d7 commit 6e4ad8d
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 54 deletions.
2 changes: 1 addition & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

### Github Issues
- #122: Set horizontal layout direction based on the language
- #317: Add the ability to render audio streams. New config option [show_self_in_roster](https://conversejs.org/docs/html/configuration.html#show-self-in-roster)
- #317: Add the ability to render audio streams. New config option [fetch_url_headers](https://conversejs.org/docs/html/configuration.html#fetch-url-headers)
- #698: Add support for MUC private messages
- #1021: Message from non-roster contacts don't appear in fullscreen view_mode
- #1038: Support setting node config manually
Expand Down
19 changes: 0 additions & 19 deletions src/plugins/chatview/tests/message-audio.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,25 +46,6 @@ describe("A Chat Message", function () {
expect(msg.querySelector('audio').src).toEqual(message);
}));

xit("will render audio stream",
mock.initConverse(['chatBoxesFetched'],
{ fetch_url_headers: true },
async function (_converse) {

await mock.waitForRoster(_converse, 'current', 1);
const message = 'https://differentdrumz.radioca.st/stream/1/';

const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
await mock.sendMessage(view, message);
await u.waitUntil(() => view.querySelectorAll('.chat-content audio').length, 1000)
const msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop();
expect(msg.innerHTML.replace(/<!-.*?->/g, '').replace(/(\r\n|\n|\r)/gm, "").trim()).toEqual(
`<audio controls="" src="${message}"></audio>`+
`<a target="_blank" rel="noopener" href="${message}">${message}</a>`);
}));

it("will render Spotify player for Spotify URLs",
mock.initConverse(['chatBoxesFetched'],
{ embed_3rd_party_media_players: true, view_mode: 'fullscreen' },
Expand Down
12 changes: 3 additions & 9 deletions src/plugins/chatview/tests/message-videos.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,13 @@ describe("A chat message containing video URLs", function () {
await mock.sendMessage(view, message);
await u.waitUntil(() => view.querySelectorAll('.chat-content video').length, 1000)
let msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop();
expect(msg.innerHTML.replace(/<!-.*?->/g, '').trim()).toEqual(
`<video controls="" preload="metadata" src="${message}"></video>`+
`<a target="_blank" rel="noopener" href="${message}">${message}</a>`);
expect(msg.querySelector('video').src).toEqual(message);

message += "?param1=val1&param2=val2";
await mock.sendMessage(view, message);
await u.waitUntil(() => view.querySelectorAll('.chat-content video').length === 2, 1000);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop();
expect(msg.innerHTML.replace(/<!-.*?->/g, '').trim()).toEqual(
`<video controls="" preload="metadata" src="${Strophe.xmlescape(message)}"></video>`+
`<a target="_blank" rel="noopener" href="${Strophe.xmlescape(message)}">${Strophe.xmlescape(message)}</a>`);
expect(msg.querySelector('video').src).toEqual(message);
}));

it("will not render videos if render_media is false",
Expand Down Expand Up @@ -64,9 +60,7 @@ describe("A chat message containing video URLs", function () {
await mock.sendMessage(view, message);
await u.waitUntil(() => view.querySelectorAll('.chat-content video').length, 1000)
const msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop();
expect(msg.innerHTML.replace(/<!-.*?->/g, '').trim()).toEqual(
`<video controls="" preload="metadata" src="${message}"></video>`+
`<a target="_blank" rel="noopener" href="${message}">${message}</a>`);
expect(msg.querySelector('video').src).toEqual(message);
}));

it("will allow the user to toggle visibility of rendered videos",
Expand Down
4 changes: 1 addition & 3 deletions src/plugins/chatview/tests/oob.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,7 @@ describe("A Chat Message", function () {
expect(msg.classList.length).toBe(1);
expect(msg.textContent).toEqual('Have you seen this funny video?');
const media = view.querySelector('.chat-msg .chat-msg__media');
expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "").replace(/<!-.*?->/g, '')).toEqual(
`<video controls="" preload="metadata" src="${Strophe.xmlescape(url)}"></video>`+
`<a target="_blank" rel="noopener" href="${Strophe.xmlescape(url)}">${Strophe.xmlescape(url)}</a>`);
expect(media.querySelector('video').getAttribute('src')).toBe(url);

// If the <url> and <body> contents is the same, don't duplicate.
stanza = u.toStanza(`
Expand Down
47 changes: 34 additions & 13 deletions src/shared/directives/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,46 @@ import { getHyperlinkTemplate } from 'utils/html.js';
const { URI } = converse.env;
const { isURLWithImageExtension } = u;


class ImageDirective extends AsyncDirective {

render (src, href, onLoad, onClick) {
return href ?
html`<a href="${href}" class="chat-image__link" target="_blank" rel="noopener">${ this.renderImage(src, href, onLoad, onClick) }</a>` :
this.renderImage(src, href, onLoad, onClick);
/**
* @param {string} src - The source URL of the image.
* @param {string} [href] - The optional hyperlink for the image.
* @param {Function} [onLoad] - Callback function to be called once the image has loaded.
* @param {Function} [onClick] - Callback function to be called once the image has been clicked.
* @returns {import('lit').TemplateResult}
*/
render(src, href, onLoad, onClick) {
return href
? html`<a href="${href}" class="chat-image__link" target="_blank" rel="noopener"
>${this.renderImage(src, href, onLoad, onClick)}</a
>`
: this.renderImage(src, href, onLoad, onClick);
}

renderImage (src, href, onLoad, onClick) {
/**
* @param {string} src - The source URL of the image.
* @param {string} [href] - The optional hyperlink for the image.
* @param {Function} [onLoad] - Callback function to be called once the image has loaded.
* @param {Function} [onClick] - Callback function to be called once the image has been clicked.
* @returns {import('lit').TemplateResult}
*/
renderImage(src, href, onLoad, onClick) {
return html`<img class="chat-image img-thumbnail"
loading="lazy"
src="${src}"
@click=${onClick}
@error=${() => this.onError(src, href, onLoad, onClick)}
@load="${onLoad}"/></a>`;
loading="lazy"
src="${src}"
@click=${onClick}
@error=${() => this.onError(src, href, onLoad, onClick)}
@load="${onLoad}"/></a>`;
}

onError (src, href, onLoad, onClick) {
/**
* Handles errors that occur during image loading.
* @param {string} src - The source URL of the image that failed to load.
* @param {string} [href] - The optional hyperlink for the image.
* @param {Function} [onLoad] - Callback function to be called once the image has loaded.
* @param {Function} [onClick] - Callback function to be called once the image has been clicked.
*/
onError(src, href, onLoad, onClick) {
if (isURLWithImageExtension(src)) {
href && this.setValue(getHyperlinkTemplate(href));
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/shared/texture/texture.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export class Texture extends String {
} else {
if (this.shouldRenderMedia(url_text, 'audio') && api.settings.get('fetch_url_headers')) {
const headers = await getHeaders(url_text);
if (headers.get('content-type')?.startsWith('audio')) {
if (headers?.get('content-type')?.startsWith('audio')) {
template = tplAudio(filtered_url, this.hide_media_urls, headers.get('Icy-Name'));
}
}
Expand Down
11 changes: 7 additions & 4 deletions src/templates/video.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { html } from 'lit';
* @param {string} url
* @param {boolean} [hide_url]
*/
export default (url, hide_url) =>
html`<video controls preload="metadata" src="${url}"></video>${hide_url
? ''
: html`<a target="_blank" rel="noopener" href="${url}">${url}</a>`}`;
export default (url, hide_url) => {
const { hostname } = new URL(url);
return html`<figure>
<video controls preload="metadata" src="${url}"></video>
${hide_url ? '' : html`<a target="_blank" rel="noopener" title="${url}" href="${url}">${hostname}</a>`}
</figure>`;
}
29 changes: 25 additions & 4 deletions src/types/shared/directives/image.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,32 @@
* @param { Function } onLoad - A callback function to be called once the image has loaded.
* @param { Function } onClick - A callback function to be called once the image has been clicked.
*/
export const renderImage: (src?: any, href?: any, onLoad?: any, onClick?: any) => import("lit/async-directive.js").DirectiveResult<typeof ImageDirective>;
export const renderImage: (src: string, href?: string, onLoad?: Function, onClick?: Function) => import("lit/async-directive.js").DirectiveResult<typeof ImageDirective>;
declare class ImageDirective extends AsyncDirective {
render(src: any, href: any, onLoad: any, onClick: any): import("lit").TemplateResult<1>;
renderImage(src: any, href: any, onLoad: any, onClick: any): import("lit").TemplateResult<1>;
onError(src: any, href: any, onLoad: any, onClick: any): void;
/**
* @param {string} src - The source URL of the image.
* @param {string} [href] - The optional hyperlink for the image.
* @param {Function} [onLoad] - Callback function to be called once the image has loaded.
* @param {Function} [onClick] - Callback function to be called once the image has been clicked.
* @returns {import('lit').TemplateResult}
*/
render(src: string, href?: string, onLoad?: Function, onClick?: Function): import("lit").TemplateResult;
/**
* @param {string} src - The source URL of the image.
* @param {string} [href] - The optional hyperlink for the image.
* @param {Function} [onLoad] - Callback function to be called once the image has loaded.
* @param {Function} [onClick] - Callback function to be called once the image has been clicked.
* @returns {import('lit').TemplateResult}
*/
renderImage(src: string, href?: string, onLoad?: Function, onClick?: Function): import("lit").TemplateResult;
/**
* Handles errors that occur during image loading.
* @param {string} src - The source URL of the image that failed to load.
* @param {string} [href] - The optional hyperlink for the image.
* @param {Function} [onLoad] - Callback function to be called once the image has loaded.
* @param {Function} [onClick] - Callback function to be called once the image has been clicked.
*/
onError(src: string, href?: string, onLoad?: Function, onClick?: Function): void;
}
import { AsyncDirective } from 'lit/async-directive.js';
export {};
Expand Down

0 comments on commit 6e4ad8d

Please sign in to comment.