Render audio from URLs in messages
This commit is contained in:
parent
33b426c79e
commit
095d9b60cd
|
@ -793,6 +793,17 @@ domain_placeholder
|
||||||
The placeholder text shown in the domain input on the registration form.
|
The placeholder text shown in the domain input on the registration form.
|
||||||
|
|
||||||
|
|
||||||
|
embed_audio
|
||||||
|
-----------
|
||||||
|
|
||||||
|
* Default: ``true``
|
||||||
|
|
||||||
|
If set to ``false``, audio files won't be embedded in chats, instead only their links will be shown.
|
||||||
|
|
||||||
|
It also accepts an array strings of whitelisted domain names to only render videos that belong to those domains.
|
||||||
|
E.g. ``['conversejs.org']``
|
||||||
|
|
||||||
|
|
||||||
embed_videos
|
embed_videos
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
|
|
@ -47,9 +47,11 @@ module.exports = function(config) {
|
||||||
{ pattern: "src/plugins/chatview/tests/http-file-upload.js", type: 'module' },
|
{ pattern: "src/plugins/chatview/tests/http-file-upload.js", type: 'module' },
|
||||||
{ pattern: "src/plugins/chatview/tests/markers.js", type: 'module' },
|
{ pattern: "src/plugins/chatview/tests/markers.js", type: 'module' },
|
||||||
{ pattern: "src/plugins/chatview/tests/me-messages.js", type: 'module' },
|
{ pattern: "src/plugins/chatview/tests/me-messages.js", type: 'module' },
|
||||||
|
{ pattern: "src/plugins/chatview/tests/message-audio.js", type: 'module' },
|
||||||
{ pattern: "src/plugins/chatview/tests/message-images.js", type: 'module' },
|
{ pattern: "src/plugins/chatview/tests/message-images.js", type: 'module' },
|
||||||
{ pattern: "src/plugins/chatview/tests/message-videos.js", type: 'module' },
|
{ pattern: "src/plugins/chatview/tests/message-videos.js", type: 'module' },
|
||||||
{ pattern: "src/plugins/chatview/tests/messages.js", type: 'module' },
|
{ pattern: "src/plugins/chatview/tests/messages.js", type: 'module' },
|
||||||
|
{ pattern: "src/plugins/chatview/tests/oob.js", type: 'module' },
|
||||||
{ pattern: "src/plugins/chatview/tests/receipts.js", type: 'module' },
|
{ pattern: "src/plugins/chatview/tests/receipts.js", type: 'module' },
|
||||||
{ pattern: "src/plugins/chatview/tests/spoilers.js", type: 'module' },
|
{ pattern: "src/plugins/chatview/tests/spoilers.js", type: 'module' },
|
||||||
{ pattern: "src/plugins/chatview/tests/xss.js", type: 'module' },
|
{ pattern: "src/plugins/chatview/tests/xss.js", type: 'module' },
|
||||||
|
|
|
@ -37,6 +37,7 @@ converse.plugins.add('converse-chatview', {
|
||||||
'auto_focus': true,
|
'auto_focus': true,
|
||||||
'debounced_content_rendering': true,
|
'debounced_content_rendering': true,
|
||||||
'embed_videos': true,
|
'embed_videos': true,
|
||||||
|
'embed_audio': true,
|
||||||
'filter_url_query_params': null,
|
'filter_url_query_params': null,
|
||||||
'image_urls_regex': null,
|
'image_urls_regex': null,
|
||||||
'message_limit': 0,
|
'message_limit': 0,
|
||||||
|
|
25
src/plugins/chatview/tests/message-audio.js
Normal file
25
src/plugins/chatview/tests/message-audio.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/*global mock, converse */
|
||||||
|
|
||||||
|
const { sizzle, u } = converse.env;
|
||||||
|
|
||||||
|
describe("A Chat Message", function () {
|
||||||
|
|
||||||
|
it("will render audio files from their URLs",
|
||||||
|
mock.initConverse(['chatBoxesFetched'], {},
|
||||||
|
async function (done, _converse) {
|
||||||
|
await mock.waitForRoster(_converse, 'current');
|
||||||
|
const base_url = 'https://conversejs.org';
|
||||||
|
const message = base_url+"/logo/audio.mp3";
|
||||||
|
|
||||||
|
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>`);
|
||||||
|
done();
|
||||||
|
}));
|
||||||
|
});
|
|
@ -1267,167 +1267,4 @@ describe("A Chat Message", function () {
|
||||||
done();
|
done();
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe("which contains an OOB URL", function () {
|
|
||||||
|
|
||||||
it("will render audio from oob mp3 URLs",
|
|
||||||
mock.initConverse(
|
|
||||||
['chatBoxesFetched'], {},
|
|
||||||
async function (done, _converse) {
|
|
||||||
|
|
||||||
await mock.waitForRoster(_converse, 'current', 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);
|
|
||||||
spyOn(view.model, 'sendMessage').and.callThrough();
|
|
||||||
|
|
||||||
let stanza = u.toStanza(`
|
|
||||||
<message from="${contact_jid}"
|
|
||||||
type="chat"
|
|
||||||
to="romeo@montague.lit/orchard">
|
|
||||||
<body>Have you heard this funny audio?</body>
|
|
||||||
<x xmlns="jabber:x:oob"><url>https://montague.lit/audio.mp3</url></x>
|
|
||||||
</message>`)
|
|
||||||
_converse.connection._dataRecv(mock.createRequest(stanza));
|
|
||||||
await new Promise(resolve => view.model.messages.once('rendered', resolve));
|
|
||||||
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg audio').length, 1000);
|
|
||||||
let msg = view.querySelector('.chat-msg .chat-msg__text');
|
|
||||||
expect(msg.classList.length).toEqual(1);
|
|
||||||
expect(u.hasClass('chat-msg__text', msg)).toBe(true);
|
|
||||||
expect(msg.textContent).toEqual('Have you heard this funny audio?');
|
|
||||||
let media = view.querySelector('.chat-msg .chat-msg__media');
|
|
||||||
expect(media.innerHTML.replace(/<!-.*?->/g, '').replace(/(\r\n|\n|\r)/gm, "").trim()).toEqual(
|
|
||||||
`<audio controls="" src="https://montague.lit/audio.mp3"></audio> `+
|
|
||||||
`<a target="_blank" rel="noopener" href="https://montague.lit/audio.mp3">Download audio file "audio.mp3"</a>`);
|
|
||||||
|
|
||||||
// If the <url> and <body> contents is the same, don't duplicate.
|
|
||||||
stanza = u.toStanza(`
|
|
||||||
<message from="${contact_jid}"
|
|
||||||
type="chat"
|
|
||||||
to="romeo@montague.lit/orchard">
|
|
||||||
<body>https://montague.lit/audio.mp3</body>
|
|
||||||
<x xmlns="jabber:x:oob"><url>https://montague.lit/audio.mp3</url></x>
|
|
||||||
</message>`);
|
|
||||||
_converse.connection._dataRecv(mock.createRequest(stanza));
|
|
||||||
await new Promise(resolve => view.model.messages.once('rendered', resolve));
|
|
||||||
msg = view.querySelector('.chat-msg:last-child .chat-msg__text');
|
|
||||||
expect(msg.innerHTML.replace(/<!-.*?->/g, '')).toEqual('Have you heard this funny audio?'); // Emtpy
|
|
||||||
media = view.querySelector('.chat-msg:last-child .chat-msg__media');
|
|
||||||
expect(media.innerHTML.replace(/<!-.*?->/g, '').replace(/(\r\n|\n|\r)/gm, "").trim()).toEqual(
|
|
||||||
`<audio controls="" src="https://montague.lit/audio.mp3"></audio> `+
|
|
||||||
`<a target="_blank" rel="noopener" href="https://montague.lit/audio.mp3">`+
|
|
||||||
`Download audio file "audio.mp3"</a>`);
|
|
||||||
done();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("will render video from oob mp4 URLs",
|
|
||||||
mock.initConverse(
|
|
||||||
['chatBoxesFetched'], {},
|
|
||||||
async function (done, _converse) {
|
|
||||||
|
|
||||||
await mock.waitForRoster(_converse, 'current', 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);
|
|
||||||
spyOn(view.model, 'sendMessage').and.callThrough();
|
|
||||||
|
|
||||||
let stanza = u.toStanza(`
|
|
||||||
<message from="${contact_jid}"
|
|
||||||
type="chat"
|
|
||||||
to="romeo@montague.lit/orchard">
|
|
||||||
<body>Have you seen this funny video?</body>
|
|
||||||
<x xmlns="jabber:x:oob"><url>https://montague.lit/video.mp4</url></x>
|
|
||||||
</message>`);
|
|
||||||
_converse.connection._dataRecv(mock.createRequest(stanza));
|
|
||||||
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg video').length, 2000)
|
|
||||||
let msg = view.querySelector('.chat-msg .chat-msg__text');
|
|
||||||
expect(msg.classList.length).toBe(1);
|
|
||||||
expect(msg.textContent).toEqual('Have you seen this funny video?');
|
|
||||||
let media = view.querySelector('.chat-msg .chat-msg__media');
|
|
||||||
expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "").replace(/<!-.*?->/g, '')).toEqual(
|
|
||||||
`<video controls="" preload="metadata" style="max-height: 50vh" src="https://montague.lit/video.mp4"></video>`);
|
|
||||||
|
|
||||||
|
|
||||||
// If the <url> and <body> contents is the same, don't duplicate.
|
|
||||||
stanza = u.toStanza(`
|
|
||||||
<message from="${contact_jid}"
|
|
||||||
type="chat"
|
|
||||||
to="romeo@montague.lit/orchard">
|
|
||||||
<body>https://montague.lit/video.mp4</body>
|
|
||||||
<x xmlns="jabber:x:oob"><url>https://montague.lit/video.mp4</url></x>
|
|
||||||
</message>`);
|
|
||||||
_converse.connection._dataRecv(mock.createRequest(stanza));
|
|
||||||
await new Promise(resolve => view.model.messages.once('rendered', resolve));
|
|
||||||
msg = view.querySelector('.chat-msg:last-child .chat-msg__text');
|
|
||||||
expect(msg.innerHTML.replace(/<!-.*?->/g, '')).toEqual('Have you seen this funny video?');
|
|
||||||
media = view.querySelector('.chat-msg:last-child .chat-msg__media');
|
|
||||||
expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "").replace(/<!-.*?->/g, '')).toEqual(
|
|
||||||
`<video controls="" preload="metadata" style="max-height: 50vh" src="https://montague.lit/video.mp4"></video>`);
|
|
||||||
done();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("will render download links for files from oob URLs",
|
|
||||||
mock.initConverse(
|
|
||||||
['chatBoxesFetched'], {},
|
|
||||||
async function (done, _converse) {
|
|
||||||
|
|
||||||
await mock.waitForRoster(_converse, 'current', 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);
|
|
||||||
spyOn(view.model, 'sendMessage').and.callThrough();
|
|
||||||
const stanza = u.toStanza(`
|
|
||||||
<message from="${contact_jid}"
|
|
||||||
type="chat"
|
|
||||||
to="romeo@montague.lit/orchard">
|
|
||||||
<body>Have you downloaded this funny file?</body>
|
|
||||||
<x xmlns="jabber:x:oob"><url>https://montague.lit/funny.pdf</url></x>
|
|
||||||
</message>`);
|
|
||||||
_converse.connection._dataRecv(mock.createRequest(stanza));
|
|
||||||
await new Promise(resolve => view.model.messages.once('rendered', resolve));
|
|
||||||
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg a').length, 1000);
|
|
||||||
const msg = view.querySelector('.chat-msg .chat-msg__text');
|
|
||||||
expect(u.hasClass('chat-msg__text', msg)).toBe(true);
|
|
||||||
expect(msg.textContent).toEqual('Have you downloaded this funny file?');
|
|
||||||
const media = view.querySelector('.chat-msg .chat-msg__media');
|
|
||||||
expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "").replace(/<!-.*?->/g, '')).toEqual(
|
|
||||||
`<a target="_blank" rel="noopener" href="https://montague.lit/funny.pdf">Download file "funny.pdf"</a>`);
|
|
||||||
done();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("will render images from oob URLs",
|
|
||||||
mock.initConverse(
|
|
||||||
['chatBoxesFetched'], {},
|
|
||||||
async function (done, _converse) {
|
|
||||||
|
|
||||||
const base_url = 'https://conversejs.org';
|
|
||||||
await mock.waitForRoster(_converse, 'current', 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);
|
|
||||||
spyOn(view.model, 'sendMessage').and.callThrough();
|
|
||||||
const url = base_url+"/logo/conversejs-filled.svg";
|
|
||||||
|
|
||||||
const stanza = u.toStanza(`
|
|
||||||
<message from="${contact_jid}"
|
|
||||||
type="chat"
|
|
||||||
to="romeo@montague.lit/orchard">
|
|
||||||
<body>Have you seen this funny image?</body>
|
|
||||||
<x xmlns="jabber:x:oob"><url>${url}</url></x>
|
|
||||||
</message>`);
|
|
||||||
_converse.connection._dataRecv(mock.createRequest(stanza));
|
|
||||||
_converse.connection._dataRecv(mock.createRequest(stanza));
|
|
||||||
await new Promise(resolve => view.model.messages.once('rendered', resolve));
|
|
||||||
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg a').length, 1000);
|
|
||||||
const msg = view.querySelector('.chat-msg .chat-msg__text');
|
|
||||||
expect(u.hasClass('chat-msg__text', msg)).toBe(true);
|
|
||||||
expect(msg.textContent).toEqual('Have you seen this funny image?');
|
|
||||||
const media = view.querySelector('.chat-msg .chat-msg__media');
|
|
||||||
expect(media.innerHTML.replace(/<!-.*?->/g, '').replace(/(\r\n|\n|\r)/gm, "")).toEqual(
|
|
||||||
`<a target="_blank" rel="noopener" href="${base_url}/logo/conversejs-filled.svg">`+
|
|
||||||
`Download image file "conversejs-filled.svg"</a>`);
|
|
||||||
done();
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
167
src/plugins/chatview/tests/oob.js
Normal file
167
src/plugins/chatview/tests/oob.js
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
/*global mock, converse */
|
||||||
|
|
||||||
|
const { Promise, u } = converse.env;
|
||||||
|
|
||||||
|
describe("A Chat Message", function () {
|
||||||
|
describe("which contains an OOB URL", function () {
|
||||||
|
|
||||||
|
it("will render audio from oob mp3 URLs",
|
||||||
|
mock.initConverse(
|
||||||
|
['chatBoxesFetched'], {},
|
||||||
|
async function (done, _converse) {
|
||||||
|
|
||||||
|
await mock.waitForRoster(_converse, 'current', 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);
|
||||||
|
spyOn(view.model, 'sendMessage').and.callThrough();
|
||||||
|
|
||||||
|
const url = 'https://montague.lit/audio.mp3';
|
||||||
|
let stanza = u.toStanza(`
|
||||||
|
<message from="${contact_jid}"
|
||||||
|
type="chat"
|
||||||
|
to="romeo@montague.lit/orchard">
|
||||||
|
<body>Have you heard this funny audio?</body>
|
||||||
|
<x xmlns="jabber:x:oob"><url>${url}</url></x>
|
||||||
|
</message>`)
|
||||||
|
_converse.connection._dataRecv(mock.createRequest(stanza));
|
||||||
|
await new Promise(resolve => view.model.messages.once('rendered', resolve));
|
||||||
|
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg audio').length, 1000);
|
||||||
|
let msg = view.querySelector('.chat-msg .chat-msg__text');
|
||||||
|
expect(msg.classList.length).toEqual(1);
|
||||||
|
expect(u.hasClass('chat-msg__text', msg)).toBe(true);
|
||||||
|
expect(msg.textContent).toEqual('Have you heard this funny audio?');
|
||||||
|
let media = view.querySelector('.chat-msg .chat-msg__media');
|
||||||
|
expect(media.innerHTML.replace(/<!-.*?->/g, '').replace(/(\r\n|\n|\r)/gm, "").trim()).toEqual(
|
||||||
|
`<audio controls="" src="https://montague.lit/audio.mp3"></audio>`+
|
||||||
|
`<a target="_blank" rel="noopener" href="https://montague.lit/audio.mp3">${url}</a>`);
|
||||||
|
|
||||||
|
// If the <url> and <body> contents is the same, don't duplicate.
|
||||||
|
stanza = u.toStanza(`
|
||||||
|
<message from="${contact_jid}"
|
||||||
|
type="chat"
|
||||||
|
to="romeo@montague.lit/orchard">
|
||||||
|
<body>https://montague.lit/audio.mp3</body>
|
||||||
|
<x xmlns="jabber:x:oob"><url>https://montague.lit/audio.mp3</url></x>
|
||||||
|
</message>`);
|
||||||
|
_converse.connection._dataRecv(mock.createRequest(stanza));
|
||||||
|
await new Promise(resolve => view.model.messages.once('rendered', resolve));
|
||||||
|
msg = view.querySelector('.chat-msg:last-child .chat-msg__text');
|
||||||
|
expect(msg.innerHTML.replace(/<!-.*?->/g, '')).toEqual('Have you heard this funny audio?'); // Emtpy
|
||||||
|
media = view.querySelector('.chat-msg:last-child .chat-msg__media');
|
||||||
|
expect(media.innerHTML.replace(/<!-.*?->/g, '').replace(/(\r\n|\n|\r)/gm, "").trim()).toEqual(
|
||||||
|
`<audio controls="" src="https://montague.lit/audio.mp3"></audio>`+
|
||||||
|
`<a target="_blank" rel="noopener" href="${url}">${url}</a>`);
|
||||||
|
done();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("will render video from oob mp4 URLs",
|
||||||
|
mock.initConverse(
|
||||||
|
['chatBoxesFetched'], {},
|
||||||
|
async function (done, _converse) {
|
||||||
|
|
||||||
|
await mock.waitForRoster(_converse, 'current', 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);
|
||||||
|
spyOn(view.model, 'sendMessage').and.callThrough();
|
||||||
|
|
||||||
|
let stanza = u.toStanza(`
|
||||||
|
<message from="${contact_jid}"
|
||||||
|
type="chat"
|
||||||
|
to="romeo@montague.lit/orchard">
|
||||||
|
<body>Have you seen this funny video?</body>
|
||||||
|
<x xmlns="jabber:x:oob"><url>https://montague.lit/video.mp4</url></x>
|
||||||
|
</message>`);
|
||||||
|
_converse.connection._dataRecv(mock.createRequest(stanza));
|
||||||
|
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg video').length, 2000)
|
||||||
|
let msg = view.querySelector('.chat-msg .chat-msg__text');
|
||||||
|
expect(msg.classList.length).toBe(1);
|
||||||
|
expect(msg.textContent).toEqual('Have you seen this funny video?');
|
||||||
|
let media = view.querySelector('.chat-msg .chat-msg__media');
|
||||||
|
expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "").replace(/<!-.*?->/g, '')).toEqual(
|
||||||
|
`<video controls="" preload="metadata" style="max-height: 50vh" src="https://montague.lit/video.mp4"></video>`);
|
||||||
|
|
||||||
|
|
||||||
|
// If the <url> and <body> contents is the same, don't duplicate.
|
||||||
|
stanza = u.toStanza(`
|
||||||
|
<message from="${contact_jid}"
|
||||||
|
type="chat"
|
||||||
|
to="romeo@montague.lit/orchard">
|
||||||
|
<body>https://montague.lit/video.mp4</body>
|
||||||
|
<x xmlns="jabber:x:oob"><url>https://montague.lit/video.mp4</url></x>
|
||||||
|
</message>`);
|
||||||
|
_converse.connection._dataRecv(mock.createRequest(stanza));
|
||||||
|
await new Promise(resolve => view.model.messages.once('rendered', resolve));
|
||||||
|
msg = view.querySelector('.chat-msg:last-child .chat-msg__text');
|
||||||
|
expect(msg.innerHTML.replace(/<!-.*?->/g, '')).toEqual('Have you seen this funny video?');
|
||||||
|
media = view.querySelector('.chat-msg:last-child .chat-msg__media');
|
||||||
|
expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "").replace(/<!-.*?->/g, '')).toEqual(
|
||||||
|
`<video controls="" preload="metadata" style="max-height: 50vh" src="https://montague.lit/video.mp4"></video>`);
|
||||||
|
done();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("will render download links for files from oob URLs",
|
||||||
|
mock.initConverse(
|
||||||
|
['chatBoxesFetched'], {},
|
||||||
|
async function (done, _converse) {
|
||||||
|
|
||||||
|
await mock.waitForRoster(_converse, 'current', 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);
|
||||||
|
spyOn(view.model, 'sendMessage').and.callThrough();
|
||||||
|
const stanza = u.toStanza(`
|
||||||
|
<message from="${contact_jid}"
|
||||||
|
type="chat"
|
||||||
|
to="romeo@montague.lit/orchard">
|
||||||
|
<body>Have you downloaded this funny file?</body>
|
||||||
|
<x xmlns="jabber:x:oob"><url>https://montague.lit/funny.pdf</url></x>
|
||||||
|
</message>`);
|
||||||
|
_converse.connection._dataRecv(mock.createRequest(stanza));
|
||||||
|
await new Promise(resolve => view.model.messages.once('rendered', resolve));
|
||||||
|
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg a').length, 1000);
|
||||||
|
const msg = view.querySelector('.chat-msg .chat-msg__text');
|
||||||
|
expect(u.hasClass('chat-msg__text', msg)).toBe(true);
|
||||||
|
expect(msg.textContent).toEqual('Have you downloaded this funny file?');
|
||||||
|
const media = view.querySelector('.chat-msg .chat-msg__media');
|
||||||
|
expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "").replace(/<!-.*?->/g, '')).toEqual(
|
||||||
|
`<a target="_blank" rel="noopener" href="https://montague.lit/funny.pdf">Download file "funny.pdf"</a>`);
|
||||||
|
done();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("will render images from oob URLs",
|
||||||
|
mock.initConverse(
|
||||||
|
['chatBoxesFetched'], {},
|
||||||
|
async function (done, _converse) {
|
||||||
|
|
||||||
|
const base_url = 'https://conversejs.org';
|
||||||
|
await mock.waitForRoster(_converse, 'current', 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);
|
||||||
|
spyOn(view.model, 'sendMessage').and.callThrough();
|
||||||
|
const url = base_url+"/logo/conversejs-filled.svg";
|
||||||
|
|
||||||
|
const stanza = u.toStanza(`
|
||||||
|
<message from="${contact_jid}"
|
||||||
|
type="chat"
|
||||||
|
to="romeo@montague.lit/orchard">
|
||||||
|
<body>Have you seen this funny image?</body>
|
||||||
|
<x xmlns="jabber:x:oob"><url>${url}</url></x>
|
||||||
|
</message>`);
|
||||||
|
_converse.connection._dataRecv(mock.createRequest(stanza));
|
||||||
|
_converse.connection._dataRecv(mock.createRequest(stanza));
|
||||||
|
await new Promise(resolve => view.model.messages.once('rendered', resolve));
|
||||||
|
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg a').length, 1000);
|
||||||
|
const msg = view.querySelector('.chat-msg .chat-msg__text');
|
||||||
|
expect(u.hasClass('chat-msg__text', msg)).toBe(true);
|
||||||
|
expect(msg.textContent).toEqual('Have you seen this funny image?');
|
||||||
|
const media = view.querySelector('.chat-msg .chat-msg__media');
|
||||||
|
expect(media.innerHTML.replace(/<!-.*?->/g, '').replace(/(\r\n|\n|\r)/gm, "")).toEqual(
|
||||||
|
`<a target="_blank" rel="noopener" href="${base_url}/logo/conversejs-filled.svg">`+
|
||||||
|
`Download image file "conversejs-filled.svg"</a>`);
|
||||||
|
done();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
|
@ -4,6 +4,8 @@ import renderRichText from 'shared/directives/rich-text.js';
|
||||||
import { CustomElement } from 'shared/components/element.js';
|
import { CustomElement } from 'shared/components/element.js';
|
||||||
import { api } from "@converse/headless/core";
|
import { api } from "@converse/headless/core";
|
||||||
|
|
||||||
|
import './styles/message-body.scss';
|
||||||
|
|
||||||
|
|
||||||
export default class MessageBody extends CustomElement {
|
export default class MessageBody extends CustomElement {
|
||||||
|
|
||||||
|
@ -13,6 +15,7 @@ export default class MessageBody extends CustomElement {
|
||||||
is_me_message: { type: Boolean },
|
is_me_message: { type: Boolean },
|
||||||
show_images: { type: Boolean },
|
show_images: { type: Boolean },
|
||||||
embed_videos: { type: Boolean },
|
embed_videos: { type: Boolean },
|
||||||
|
embed_audio: { type: Boolean },
|
||||||
text: { type: String },
|
text: { type: String },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,12 +34,13 @@ export default class MessageBody extends CustomElement {
|
||||||
const offset = 0;
|
const offset = 0;
|
||||||
const mentions = this.model.get('references');
|
const mentions = this.model.get('references');
|
||||||
const options = {
|
const options = {
|
||||||
|
'embed_audio': this.embed_audio,
|
||||||
|
'embed_videos': this.embed_videos,
|
||||||
'nick': this.model.collection.chatbox.get('nick'),
|
'nick': this.model.collection.chatbox.get('nick'),
|
||||||
'onImgClick': this.onImgClick,
|
'onImgClick': this.onImgClick,
|
||||||
'onImgLoad': () => this.onImgLoad(),
|
'onImgLoad': () => this.onImgLoad(),
|
||||||
'render_styling': !this.model.get('is_unstyled') && api.settings.get('allow_message_styling'),
|
'render_styling': !this.model.get('is_unstyled') && api.settings.get('allow_message_styling'),
|
||||||
'show_images': this.show_images,
|
'show_images': this.show_images,
|
||||||
'embed_videos': this.embed_videos,
|
|
||||||
'show_me_message': true
|
'show_me_message': true
|
||||||
}
|
}
|
||||||
return renderRichText(this.text, offset, mentions, options, callback);
|
return renderRichText(this.text, offset, mentions, options, callback);
|
||||||
|
|
|
@ -240,6 +240,7 @@ export default class Message extends CustomElement {
|
||||||
?is_me_message="${this.model.isMeCommand()}"
|
?is_me_message="${this.model.isMeCommand()}"
|
||||||
?show_images="${api.settings.get('show_images_inline')}"
|
?show_images="${api.settings.get('show_images_inline')}"
|
||||||
?embed_videos="${api.settings.get('embed_videos')}"
|
?embed_videos="${api.settings.get('embed_videos')}"
|
||||||
|
?embed_audio="${api.settings.get('embed_audio')}"
|
||||||
text="${text}"></converse-chat-message-body>
|
text="${text}"></converse-chat-message-body>
|
||||||
${ (this.model.get('received') && !this.model.isMeCommand() && !is_groupchat_message) ? html`<span class="fa fa-check chat-msg__receipt"></span>` : '' }
|
${ (this.model.get('received') && !this.model.isMeCommand() && !is_groupchat_message) ? html`<span class="fa fa-check chat-msg__receipt"></span>` : '' }
|
||||||
${ (this.model.get('edited')) ? html`<i title="${ i18n_edited }" class="fa fa-edit chat-msg__edit-modal" @click=${this.showMessageVersionsModal}></i>` : '' }
|
${ (this.model.get('edited')) ? html`<i title="${ i18n_edited }" class="fa fa-edit chat-msg__edit-modal" @click=${this.showMessageVersionsModal}></i>` : '' }
|
||||||
|
|
5
src/shared/chat/styles/message-body.scss
Normal file
5
src/shared/chat/styles/message-body.scss
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
converse-chat-message-body {
|
||||||
|
audio {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,8 @@ export default class RichText extends CustomElement {
|
||||||
|
|
||||||
static get properties () {
|
static get properties () {
|
||||||
return {
|
return {
|
||||||
|
embed_audio: { type: Boolean },
|
||||||
|
embed_videos: { type: Boolean },
|
||||||
mentions: { type: Array },
|
mentions: { type: Array },
|
||||||
nick: { type: String },
|
nick: { type: String },
|
||||||
offset: { type: Number },
|
offset: { type: Number },
|
||||||
|
@ -13,7 +15,6 @@ export default class RichText extends CustomElement {
|
||||||
onImgLoad: { type: Function },
|
onImgLoad: { type: Function },
|
||||||
render_styling: { type: Boolean },
|
render_styling: { type: Boolean },
|
||||||
show_images: { type: Boolean },
|
show_images: { type: Boolean },
|
||||||
embed_videos: { type: Boolean },
|
|
||||||
show_me_message: { type: Boolean },
|
show_me_message: { type: Boolean },
|
||||||
text: { type: String },
|
text: { type: String },
|
||||||
}
|
}
|
||||||
|
@ -21,22 +22,24 @@ export default class RichText extends CustomElement {
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
super();
|
super();
|
||||||
this.offset = 0;
|
this.embed_audio = false;
|
||||||
|
this.embed_videos = false;
|
||||||
this.mentions = [];
|
this.mentions = [];
|
||||||
|
this.offset = 0;
|
||||||
this.render_styling = false;
|
this.render_styling = false;
|
||||||
this.show_images = false;
|
this.show_images = false;
|
||||||
this.embed_videos = false;
|
|
||||||
this.show_me_message = false;
|
this.show_me_message = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const options = {
|
const options = {
|
||||||
|
embed_audio: this.embed_audio,
|
||||||
|
embed_videos: this.embed_videos,
|
||||||
nick: this.nick,
|
nick: this.nick,
|
||||||
onImgClick: this.onImgClick,
|
onImgClick: this.onImgClick,
|
||||||
onImgLoad: this.onImgLoad,
|
onImgLoad: this.onImgLoad,
|
||||||
render_styling: this.render_styling,
|
render_styling: this.render_styling,
|
||||||
show_images: this.show_images,
|
show_images: this.show_images,
|
||||||
embed_videos: this.embed_videos,
|
|
||||||
show_me_message: this.show_me_message,
|
show_me_message: this.show_me_message,
|
||||||
}
|
}
|
||||||
return renderRichText(this.text, this.offset, this.mentions, options);
|
return renderRichText(this.text, this.offset, this.mentions, options);
|
||||||
|
|
|
@ -14,7 +14,7 @@ class StylingDirective extends Directive {
|
||||||
txt,
|
txt,
|
||||||
offset,
|
offset,
|
||||||
mentions,
|
mentions,
|
||||||
Object.assign(options, { 'show_images': false, 'embed_videos': false })
|
Object.assign(options, { 'show_images': false, 'embed_videos': false, 'embed_audio': false })
|
||||||
);
|
);
|
||||||
return html`${until(transform(t), html`${t}`)}`;
|
return html`${until(transform(t), html`${t}`)}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import URI from 'urijs';
|
import URI from 'urijs';
|
||||||
import log from '@converse/headless/log';
|
import log from '@converse/headless/log';
|
||||||
|
import tpl_audio from 'templates/audio.js';
|
||||||
import tpl_image from 'templates/image.js';
|
import tpl_image from 'templates/image.js';
|
||||||
import tpl_video from '../templates/video.js';
|
import tpl_video from 'templates/video.js';
|
||||||
import { _converse, api } from '@converse/headless/core';
|
import { _converse, api } from '@converse/headless/core';
|
||||||
import { containsDirectives, getDirectiveAndLength, getDirectiveTemplate, isQuoteDirective } from './styling.js';
|
import { containsDirectives, getDirectiveAndLength, getDirectiveTemplate, isQuoteDirective } from './styling.js';
|
||||||
import {
|
import {
|
||||||
|
@ -13,8 +14,11 @@ import {
|
||||||
import {
|
import {
|
||||||
filterQueryParamsFromURL,
|
filterQueryParamsFromURL,
|
||||||
getHyperlinkTemplate,
|
getHyperlinkTemplate,
|
||||||
isImageURL,
|
getURI,
|
||||||
|
isAudioDomainAllowed,
|
||||||
|
isAudioURL,
|
||||||
isImageDomainAllowed,
|
isImageDomainAllowed,
|
||||||
|
isImageURL,
|
||||||
isVideoDomainAllowed,
|
isVideoDomainAllowed,
|
||||||
isVideoURL
|
isVideoURL
|
||||||
} from 'utils/html';
|
} from 'utils/html';
|
||||||
|
@ -66,6 +70,8 @@ export class RichText extends String {
|
||||||
*/
|
*/
|
||||||
constructor (text, offset = 0, mentions = [], options = {}) {
|
constructor (text, offset = 0, mentions = [], options = {}) {
|
||||||
super(text);
|
super(text);
|
||||||
|
this.embed_audio = options?.embed_audio;
|
||||||
|
this.embed_videos = options?.embed_videos;
|
||||||
this.mentions = mentions;
|
this.mentions = mentions;
|
||||||
this.nick = options?.nick;
|
this.nick = options?.nick;
|
||||||
this.offset = offset;
|
this.offset = offset;
|
||||||
|
@ -76,7 +82,6 @@ export class RichText extends String {
|
||||||
this.references = [];
|
this.references = [];
|
||||||
this.render_styling = options?.render_styling;
|
this.render_styling = options?.render_styling;
|
||||||
this.show_images = options?.show_images;
|
this.show_images = options?.show_images;
|
||||||
this.embed_videos = options?.embed_videos;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -114,6 +119,8 @@ export class RichText extends String {
|
||||||
});
|
});
|
||||||
} else if (this.embed_videos && isVideoURL(url_text) && isVideoDomainAllowed(url_text)) {
|
} else if (this.embed_videos && isVideoURL(url_text) && isVideoDomainAllowed(url_text)) {
|
||||||
template = tpl_video({ 'url': filtered_url });
|
template = tpl_video({ 'url': filtered_url });
|
||||||
|
} else if (this.embed_audio && isAudioURL(url_text) && isAudioDomainAllowed(url_text)) {
|
||||||
|
template = tpl_audio(filtered_url);
|
||||||
} else {
|
} else {
|
||||||
template = getHyperlinkTemplate(filtered_url);
|
template = getHyperlinkTemplate(filtered_url);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import { html } from "lit";
|
import { html } from 'lit';
|
||||||
|
|
||||||
|
export default (url) => {
|
||||||
export default (o) => html`
|
return html`<audio controls src="${url}"></audio><a target="_blank" rel="noopener" href="${url}">${url}</a>`;
|
||||||
<audio controls src="${o.url}"></audio>
|
}
|
||||||
<a target="_blank" rel="noopener" href="${o.url}">${o.label_download}</a>
|
|
||||||
`;
|
|
||||||
|
|
|
@ -94,6 +94,20 @@ export function isImageURL (url) {
|
||||||
return regex?.test(url) || isURLWithImageExtension(url);
|
return regex?.test(url) || isURLWithImageExtension(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isAudioDomainAllowed (url) {
|
||||||
|
const embed_audio = api.settings.get('embed_audio');
|
||||||
|
if (!Array.isArray(embed_audio)) {
|
||||||
|
return embed_audio;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const audio_domain = getURI(url).domain();
|
||||||
|
return embed_audio.includes(audio_domain);
|
||||||
|
} catch (error) {
|
||||||
|
log.debug(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function isVideoDomainAllowed (url) {
|
export function isVideoDomainAllowed (url) {
|
||||||
const embed_videos = api.settings.get('embed_videos');
|
const embed_videos = api.settings.get('embed_videos');
|
||||||
if (!Array.isArray(embed_videos)) {
|
if (!Array.isArray(embed_videos)) {
|
||||||
|
@ -122,7 +136,7 @@ export function isImageDomainAllowed (url) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFileName (uri) {
|
export function getFileName (uri) {
|
||||||
try {
|
try {
|
||||||
return decodeURI(uri.filename());
|
return decodeURI(uri.filename());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -131,12 +145,8 @@ function getFileName (uri) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderAudioURL (_converse, uri) {
|
function renderAudioURL (url) {
|
||||||
const { __ } = _converse;
|
return tpl_audio(url);
|
||||||
return tpl_audio({
|
|
||||||
'url': uri.toString(),
|
|
||||||
'label_download': __('Download audio file "%1$s"', getFileName(uri))
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderImageURL (_converse, uri) {
|
function renderImageURL (_converse, uri) {
|
||||||
|
@ -170,7 +180,7 @@ u.getOOBURLMarkup = function (_converse, url) {
|
||||||
if (u.isVideoURL(uri)) {
|
if (u.isVideoURL(uri)) {
|
||||||
return tpl_video({ url });
|
return tpl_video({ url });
|
||||||
} else if (u.isAudioURL(uri)) {
|
} else if (u.isAudioURL(uri)) {
|
||||||
return renderAudioURL(_converse, uri);
|
return renderAudioURL(url);
|
||||||
} else if (u.isImageURL(uri)) {
|
} else if (u.isImageURL(uri)) {
|
||||||
return renderImageURL(_converse, uri);
|
return renderImageURL(_converse, uri);
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user