Jelajahi Sumber

Various changes.

- Add docstrings.
- Render only hostname below video element
- Handle no headers being returned
JC Brand 5 bulan lalu
induk
melakukan
6e4ad8de3d

+ 1 - 1
CHANGES.md

@@ -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

+ 0 - 19
src/plugins/chatview/tests/message-audio.js

@@ -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' },

+ 3 - 9
src/plugins/chatview/tests/message-videos.js

@@ -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",
@@ -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",

+ 1 - 3
src/plugins/chatview/tests/oob.js

@@ -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(`

+ 34 - 13
src/shared/directives/image.js

@@ -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 {

+ 1 - 1
src/shared/texture/texture.js

@@ -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'));
                 }
             }

+ 7 - 4
src/templates/video.js

@@ -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>`;
+}

+ 25 - 4
src/types/shared/directives/image.d.ts

@@ -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 {};