Add support for rendering unfurls via Prosody's mod_ogp
See here: https://modules.prosody.im/mod_ogp.html
This commit is contained in:
parent
c69eb6e1bf
commit
16edc2954d
@ -17,6 +17,7 @@
|
|||||||
- New configuration setting: [send_chat_markers](https://conversejs.org/docs/html/configuration.html#send-chat-markers)
|
- New configuration setting: [send_chat_markers](https://conversejs.org/docs/html/configuration.html#send-chat-markers)
|
||||||
- #1823: New config options [mam_request_all_pages](https://conversejs.org/docs/html/configuration.html#mam-request-all-pages)
|
- #1823: New config options [mam_request_all_pages](https://conversejs.org/docs/html/configuration.html#mam-request-all-pages)
|
||||||
- Use the MUC stanza id when sending XEP-0333 markers
|
- Use the MUC stanza id when sending XEP-0333 markers
|
||||||
|
- Add support for rendering unfurls via [mod_ogp](https://modules.prosody.im/mod_ogp.html)
|
||||||
|
|
||||||
### Breaking Changes
|
### Breaking Changes
|
||||||
|
|
||||||
|
@ -53,6 +53,7 @@ module.exports = function(config) {
|
|||||||
{ pattern: "spec/markers.js", type: 'module' },
|
{ pattern: "spec/markers.js", type: 'module' },
|
||||||
{ pattern: "spec/rai.js", type: 'module' },
|
{ pattern: "spec/rai.js", type: 'module' },
|
||||||
{ pattern: "spec/muc_messages.js", type: 'module' },
|
{ pattern: "spec/muc_messages.js", type: 'module' },
|
||||||
|
{ pattern: "spec/unfurls.js", type: 'module' },
|
||||||
{ pattern: "spec/muc-mentions.js", type: 'module' },
|
{ pattern: "spec/muc-mentions.js", type: 'module' },
|
||||||
{ pattern: "spec/me-messages.js", type: 'module' },
|
{ pattern: "spec/me-messages.js", type: 'module' },
|
||||||
{ pattern: "spec/mentions.js", type: 'module' },
|
{ pattern: "spec/mentions.js", type: 'module' },
|
||||||
|
@ -9,6 +9,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.message {
|
.message {
|
||||||
|
|
||||||
|
.card--unfurl {
|
||||||
|
margin: 1em 0;
|
||||||
|
max-width: 18rem;
|
||||||
|
}
|
||||||
|
|
||||||
.show-msg-author-modal {
|
.show-msg-author-modal {
|
||||||
color: var(--text-color) !important;
|
color: var(--text-color) !important;
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
@import "bootstrap/scss/input-group";
|
@import "bootstrap/scss/input-group";
|
||||||
@import "bootstrap/scss/custom-forms";
|
@import "bootstrap/scss/custom-forms";
|
||||||
@import "bootstrap/scss/nav";
|
@import "bootstrap/scss/nav";
|
||||||
|
@import "bootstrap/scss/card";
|
||||||
@import "bootstrap/scss/badge";
|
@import "bootstrap/scss/badge";
|
||||||
@import "bootstrap/scss/alert";
|
@import "bootstrap/scss/alert";
|
||||||
@import "bootstrap/scss/media";
|
@import "bootstrap/scss/media";
|
||||||
|
@ -59,7 +59,8 @@ describe("A Groupchat Message", function () {
|
|||||||
mock.initConverse(['chatBoxesFetched'], {}, async function (done, _converse) {
|
mock.initConverse(['chatBoxesFetched'], {}, async function (done, _converse) {
|
||||||
|
|
||||||
const muc_jid = 'lounge@montague.lit';
|
const muc_jid = 'lounge@montague.lit';
|
||||||
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
|
const nick = 'romeo';
|
||||||
|
await mock.openAndEnterChatRoom(_converse, muc_jid, nick);
|
||||||
const view = _converse.api.chatviews.get(muc_jid);
|
const view = _converse.api.chatviews.get(muc_jid);
|
||||||
let presence = u.toStanza(`
|
let presence = u.toStanza(`
|
||||||
<presence xmlns="jabber:client" to="${_converse.jid}" from="${muc_jid}/romeo">
|
<presence xmlns="jabber:client" to="${_converse.jid}" from="${muc_jid}/romeo">
|
||||||
|
103
spec/unfurls.js
Normal file
103
spec/unfurls.js
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
/*global mock, converse */
|
||||||
|
|
||||||
|
const { u } = converse.env;
|
||||||
|
|
||||||
|
describe("A Groupchat Message", function () {
|
||||||
|
|
||||||
|
it("will render an unfurl based on OGP data", mock.initConverse(['chatBoxesFetched'], {}, async function (done, _converse) {
|
||||||
|
const nick = 'romeo';
|
||||||
|
const muc_jid = 'lounge@montague.lit';
|
||||||
|
await mock.openAndEnterChatRoom(_converse, muc_jid, nick);
|
||||||
|
const view = _converse.api.chatviews.get(muc_jid);
|
||||||
|
|
||||||
|
const message_stanza = u.toStanza(`
|
||||||
|
<message xmlns="jabber:client" type="groupchat" from="${muc_jid}/arzu" xml:lang="en" to="${_converse.jid}" id="eda6c790-b4f3-4c07-b5e2-13fff99e6c04">
|
||||||
|
<body>https://www.youtube.com/watch?v=dQw4w9WgXcQ</body>
|
||||||
|
<active xmlns="http://jabber.org/protocol/chatstates"/>
|
||||||
|
<origin-id xmlns="urn:xmpp:sid:0" id="eda6c790-b4f3-4c07-b5e2-13fff99e6c04"/>
|
||||||
|
<stanza-id xmlns="urn:xmpp:sid:0" by="${muc_jid}" id="8f7613cc-27d4-40ca-9488-da25c4baf92a"/>
|
||||||
|
<markable xmlns="urn:xmpp:chat-markers:0"/>
|
||||||
|
</message>`);
|
||||||
|
_converse.connection._dataRecv(mock.createRequest(message_stanza));
|
||||||
|
const el = await u.waitUntil(() => view.querySelector('.chat-msg__text'));
|
||||||
|
expect(el.textContent).toBe('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
|
||||||
|
|
||||||
|
const metadata_stanza = u.toStanza(`
|
||||||
|
<message xmlns="jabber:client" from="${muc_jid}" to="${_converse.jid}">
|
||||||
|
<apply-to xmlns="urn:xmpp:fasten:0" id="eda6c790-b4f3-4c07-b5e2-13fff99e6c04">
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:site_name" content="YouTube" />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:url" content="https://www.youtube.com/watch?v=dQw4w9WgXcQ" />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:title" content="Rick Astley - Never Gonna Give You Up (Video)" />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:image" content="https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg" />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:image:width" content="1280" />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:image:height" content="720" />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:description" content="Rick Astley&#39;s official music video for "Never Gonna Give You Up" Listen to Rick Astley: https://RickAstley.lnk.to/_listenYD Subscribe to the official Rick Ast..." />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:type" content="video.other" />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:video:url" content="https://www.youtube.com/embed/dQw4w9WgXcQ" />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:video:secure_url" content="https://www.youtube.com/embed/dQw4w9WgXcQ" />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:video:type" content="text/html" />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:video:width" content="1280" />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:video:height" content="720" />
|
||||||
|
</apply-to>
|
||||||
|
</message>`);
|
||||||
|
_converse.connection._dataRecv(mock.createRequest(metadata_stanza));
|
||||||
|
|
||||||
|
const unfurl = await u.waitUntil(() => view.querySelector('converse-message-unfurl'));
|
||||||
|
expect(unfurl.querySelector('.card-img-top').getAttribute('src')).toBe('https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg');
|
||||||
|
done();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("will render multiple unfurls based on OGP data", mock.initConverse(['chatBoxesFetched'], {}, async function (done, _converse) {
|
||||||
|
const nick = 'romeo';
|
||||||
|
const muc_jid = 'lounge@montague.lit';
|
||||||
|
await mock.openAndEnterChatRoom(_converse, muc_jid, nick);
|
||||||
|
const view = _converse.api.chatviews.get(muc_jid);
|
||||||
|
|
||||||
|
const message_stanza = u.toStanza(`
|
||||||
|
<message xmlns="jabber:client" type="groupchat" from="${muc_jid}/arzu" xml:lang="en" to="${_converse.jid}" id="eda6c790-b4f3-4c07-b5e2-13fff99e6c04">
|
||||||
|
<body>Check out https://www.youtube.com/watch?v=dQw4w9WgXcQ and https://duckduckgo.com</body>
|
||||||
|
<active xmlns="http://jabber.org/protocol/chatstates"/>
|
||||||
|
<origin-id xmlns="urn:xmpp:sid:0" id="eda6c790-b4f3-4c07-b5e2-13fff99e6c04"/>
|
||||||
|
<stanza-id xmlns="urn:xmpp:sid:0" by="${muc_jid}" id="8f7613cc-27d4-40ca-9488-da25c4baf92a"/>
|
||||||
|
<markable xmlns="urn:xmpp:chat-markers:0"/>
|
||||||
|
</message>`);
|
||||||
|
_converse.connection._dataRecv(mock.createRequest(message_stanza));
|
||||||
|
const el = await u.waitUntil(() => view.querySelector('.chat-msg__text'));
|
||||||
|
expect(el.textContent).toBe('Check out https://www.youtube.com/watch?v=dQw4w9WgXcQ and https://duckduckgo.com');
|
||||||
|
|
||||||
|
let metadata_stanza = u.toStanza(`
|
||||||
|
<message xmlns="jabber:client" from="${muc_jid}" to="${_converse.jid}">
|
||||||
|
<apply-to xmlns="urn:xmpp:fasten:0" id="eda6c790-b4f3-4c07-b5e2-13fff99e6c04">
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:site_name" content="YouTube" />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:url" content="https://www.youtube.com/watch?v=dQw4w9WgXcQ" />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:title" content="Rick Astley - Never Gonna Give You Up (Video)" />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:image" content="https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg" />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:image:width" content="1280" />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:image:height" content="720" />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:description" content="Rick Astley&#39;s official music video for "Never Gonna Give You Up" Listen to Rick Astley: https://RickAstley.lnk.to/_listenYD Subscribe to the official Rick Ast..." />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:type" content="video.other" />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:video:url" content="https://www.youtube.com/embed/dQw4w9WgXcQ" />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:video:secure_url" content="https://www.youtube.com/embed/dQw4w9WgXcQ" />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:video:type" content="text/html" />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:video:width" content="1280" />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:video:height" content="720" />
|
||||||
|
</apply-to>
|
||||||
|
</message>`);
|
||||||
|
_converse.connection._dataRecv(mock.createRequest(metadata_stanza));
|
||||||
|
|
||||||
|
metadata_stanza = u.toStanza(`
|
||||||
|
<message xmlns="jabber:client" from="${muc_jid}" to="${_converse.jid}">
|
||||||
|
<apply-to xmlns="urn:xmpp:fasten:0" id="eda6c790-b4f3-4c07-b5e2-13fff99e6c04">
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:url" content="https://duckduckgo.com/" />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:site_name" content="DuckDuckGo" />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:image" content="https://duckduckgo.com/assets/logo_social-media.png" />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:title" content="DuckDuckGo - Privacy, simplified." />
|
||||||
|
<meta xmlns="http://www.w3.org/1999/xhtml" property="og:description" content="The Internet privacy company that empowers you to seamlessly take control of your personal information online, without any tradeoffs." />
|
||||||
|
</apply-to>
|
||||||
|
</message>`);
|
||||||
|
_converse.connection._dataRecv(mock.createRequest(metadata_stanza));
|
||||||
|
|
||||||
|
await u.waitUntil(() => view.querySelectorAll('converse-message-unfurl').length === 2);
|
||||||
|
done();
|
||||||
|
}));
|
||||||
|
});
|
@ -47,6 +47,7 @@ const tpl_message = (o) => html`
|
|||||||
spoiler_hint=${o.spoiler_hint || ''}
|
spoiler_hint=${o.spoiler_hint || ''}
|
||||||
subject=${o.subject || ''}
|
subject=${o.subject || ''}
|
||||||
time=${o.time}
|
time=${o.time}
|
||||||
|
unfurl_metadata=${o.unfurl_metadata}
|
||||||
username=${o.username}></converse-chat-message>
|
username=${o.username}></converse-chat-message>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -58,6 +58,7 @@ export default class Message extends CustomElement {
|
|||||||
spoiler_hint: { type: String },
|
spoiler_hint: { type: String },
|
||||||
subject: { type: String },
|
subject: { type: String },
|
||||||
time: { type: String },
|
time: { type: String },
|
||||||
|
unfurl_metadata: { type: String },
|
||||||
username: { type: String }
|
username: { type: String }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,7 @@ Strophe.addNamespace('STYLING', 'urn:xmpp:styling:0');
|
|||||||
Strophe.addNamespace('VCARD', 'vcard-temp');
|
Strophe.addNamespace('VCARD', 'vcard-temp');
|
||||||
Strophe.addNamespace('VCARDUPDATE', 'vcard-temp:x:update');
|
Strophe.addNamespace('VCARDUPDATE', 'vcard-temp:x:update');
|
||||||
Strophe.addNamespace('XFORM', 'jabber:x:data');
|
Strophe.addNamespace('XFORM', 'jabber:x:data');
|
||||||
|
Strophe.addNamespace('XHTML', 'http://www.w3.org/1999/xhtml');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom error for indicating timeouts
|
* Custom error for indicating timeouts
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
import ModelWithContact from './model-with-contact.js';
|
import ModelWithContact from './model-with-contact.js';
|
||||||
import filesize from "filesize";
|
import filesize from "filesize";
|
||||||
|
import isMatch from "lodash/isMatch";
|
||||||
|
import isObject from "lodash/isObject";
|
||||||
import log from '@converse/headless/log';
|
import log from '@converse/headless/log';
|
||||||
|
import pick from "lodash/pick";
|
||||||
import { Model } from '@converse/skeletor/src/model.js';
|
import { Model } from '@converse/skeletor/src/model.js';
|
||||||
import { _converse, api, converse } from "../../core.js";
|
import { _converse, api, converse } from "../../core.js";
|
||||||
import { find, isMatch, isObject, pick } from "lodash-es";
|
import { getOpenGraphMetadata } from '@converse/headless/shared/parsers';
|
||||||
import { parseMessage } from './parsers.js';
|
import { parseMessage } from './parsers.js';
|
||||||
import { sendMarker } from '@converse/headless/shared/actions';
|
import { sendMarker } from '@converse/headless/shared/actions';
|
||||||
|
|
||||||
@ -11,6 +14,24 @@ const { Strophe, $msg } = converse.env;
|
|||||||
|
|
||||||
const u = converse.env.utils;
|
const u = converse.env.utils;
|
||||||
|
|
||||||
|
const METADATA_ATTRIBUTES = [
|
||||||
|
"og:description",
|
||||||
|
"og:image",
|
||||||
|
"og:image:height",
|
||||||
|
"og:image:width",
|
||||||
|
"og:site_name",
|
||||||
|
"og:title",
|
||||||
|
"og:type",
|
||||||
|
"og:url",
|
||||||
|
"og:video:height",
|
||||||
|
"og:video:secure_url",
|
||||||
|
"og:video:tag",
|
||||||
|
"og:video:type",
|
||||||
|
"og:video:url",
|
||||||
|
"og:video:width"
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an open/ongoing chat conversation.
|
* Represents an open/ongoing chat conversation.
|
||||||
*
|
*
|
||||||
@ -468,6 +489,24 @@ const ChatBox = ModelWithContact.extend({
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleMetadataFastening (stanza) {
|
||||||
|
const attrs = getOpenGraphMetadata(stanza);
|
||||||
|
if (attrs.ogp_for_id) {
|
||||||
|
if (attrs.ogp_for_id) {
|
||||||
|
const message = this.messages.findWhere({'origin_id': attrs.ogp_for_id});
|
||||||
|
if (message) {
|
||||||
|
const list = message.get('ogp_metadata') || [];
|
||||||
|
list.push(pick(attrs, METADATA_ATTRIBUTES));
|
||||||
|
message.save('ogp_metadata', list);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether the passed in message attributes represent a
|
* Determines whether the passed in message attributes represent a
|
||||||
* message which corrects a previously received message, or an
|
* message which corrects a previously received message, or an
|
||||||
@ -524,7 +563,7 @@ const ChatBox = ModelWithContact.extend({
|
|||||||
this.getMessageBodyQueryAttrs(attrs)
|
this.getMessageBodyQueryAttrs(attrs)
|
||||||
].filter(s => s);
|
].filter(s => s);
|
||||||
const msgs = this.messages.models;
|
const msgs = this.messages.models;
|
||||||
return find(msgs, m => queries.reduce((out, q) => (out || isMatch(m.attributes, q)), false));
|
return msgs.find(m => queries.reduce((out, q) => (out || isMatch(m.attributes, q)), false));
|
||||||
},
|
},
|
||||||
|
|
||||||
getOriginIdQueryAttrs (attrs) {
|
getOriginIdQueryAttrs (attrs) {
|
||||||
|
@ -495,6 +495,7 @@ const ChatRoomMixin = {
|
|||||||
*/
|
*/
|
||||||
async handleMessageStanza (stanza) {
|
async handleMessageStanza (stanza) {
|
||||||
if (stanza.getAttribute('type') !== 'groupchat') {
|
if (stanza.getAttribute('type') !== 'groupchat') {
|
||||||
|
this.handleMetadataFastening(stanza);
|
||||||
this.handleForwardedMentions(stanza);
|
this.handleForwardedMentions(stanza);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import dayjs from 'dayjs';
|
|||||||
import sizzle from 'sizzle';
|
import sizzle from 'sizzle';
|
||||||
import { Strophe } from 'strophe.js/src/strophe';
|
import { Strophe } from 'strophe.js/src/strophe';
|
||||||
import { _converse, api } from '@converse/headless/core';
|
import { _converse, api } from '@converse/headless/core';
|
||||||
|
import { decodeHTMLEntities } from 'shared/utils';
|
||||||
import { rejectMessage } from '@converse/headless/shared/actions';
|
import { rejectMessage } from '@converse/headless/shared/actions';
|
||||||
|
|
||||||
const { NS } = Strophe;
|
const { NS } = Strophe;
|
||||||
@ -120,6 +121,24 @@ export function getCorrectionAttributes (stanza, original_stanza) {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getOpenGraphMetadata (stanza) {
|
||||||
|
const fastening = sizzle(`> apply-to[xmlns="${Strophe.NS.FASTEN}"]`, stanza).pop();
|
||||||
|
if (fastening) {
|
||||||
|
const applies_to_id = fastening.getAttribute('id');
|
||||||
|
const meta = sizzle(`> meta[xmlns="${Strophe.NS.XHTML}"]`, fastening);
|
||||||
|
return meta.reduce((acc, el) => {
|
||||||
|
const property = el.getAttribute('property');
|
||||||
|
if (property) {
|
||||||
|
acc[property] = decodeHTMLEntities(el.getAttribute('content') || '');
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {
|
||||||
|
'ogp_for_id': applies_to_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
export function getSpoilerAttributes (stanza) {
|
export function getSpoilerAttributes (stanza) {
|
||||||
const spoiler = sizzle(`spoiler[xmlns="${Strophe.NS.SPOILER}"]`, stanza).pop();
|
const spoiler = sizzle(`spoiler[xmlns="${Strophe.NS.SPOILER}"]`, stanza).pop();
|
||||||
return {
|
return {
|
||||||
|
15
src/shared/chat/templates/unfurl.js
Normal file
15
src/shared/chat/templates/unfurl.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { html } from 'lit-element';
|
||||||
|
import { converse } from "@converse/headless/core";
|
||||||
|
const u = converse.env.utils;
|
||||||
|
|
||||||
|
export default (o) => {
|
||||||
|
return html`<div class="card card--unfurl">
|
||||||
|
<a href="${o.url}" target="_blank" rel="noopener">
|
||||||
|
<img class="card-img-top" src="${o.image}" @load=${o.onload}/>
|
||||||
|
</a>
|
||||||
|
<div class="card-body">
|
||||||
|
<a href="${o.url}" target="_blank" rel="noopener"><h5 class="card-title">${o.title}</h5></a>
|
||||||
|
<p class="card-text">${u.addHyperlinks(o.description)}</p>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
34
src/shared/chat/unfurl.js
Normal file
34
src/shared/chat/unfurl.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { CustomElement } from 'components/element.js';
|
||||||
|
import { _converse, api } from "@converse/headless/core";
|
||||||
|
import tpl_unfurl from './templates/unfurl.js';
|
||||||
|
|
||||||
|
|
||||||
|
export default class MessageUnfurl extends CustomElement {
|
||||||
|
|
||||||
|
static get properties () {
|
||||||
|
return {
|
||||||
|
description: { type: String },
|
||||||
|
image: { type: String },
|
||||||
|
jid: { type: String },
|
||||||
|
title: { type: String },
|
||||||
|
url: { type: String },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return tpl_unfurl(Object.assign({
|
||||||
|
'onload': () => this.onImageLoad()
|
||||||
|
}, {
|
||||||
|
description: this.description,
|
||||||
|
image: this.image,
|
||||||
|
title: this.title,
|
||||||
|
url: this.url
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
onImageLoad () {
|
||||||
|
_converse.chatboxviews.get(this.getAttribute('jid'))?.scrollDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
api.elements.define('converse-message-unfurl', MessageUnfurl);
|
12
src/shared/utils.js
Normal file
12
src/shared/utils.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import xss from 'xss/dist/xss';
|
||||||
|
|
||||||
|
const element = document.createElement('div');
|
||||||
|
|
||||||
|
export function decodeHTMLEntities (str) {
|
||||||
|
if (str && typeof str === 'string') {
|
||||||
|
element.innerHTML = xss.filterXSS(str);
|
||||||
|
str = element.textContent;
|
||||||
|
element.textContent = '';
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import { html } from "lit-html";
|
import 'shared/chat/unfurl';
|
||||||
import { __ } from '../i18n';
|
import { __ } from '../i18n';
|
||||||
|
import { html } from "lit-html";
|
||||||
import { renderAvatar } from './../templates/directives/avatar';
|
import { renderAvatar } from './../templates/directives/avatar';
|
||||||
|
|
||||||
|
|
||||||
@ -39,6 +40,14 @@ export default (o) => {
|
|||||||
?is_retracted="${o.is_retracted}"
|
?is_retracted="${o.is_retracted}"
|
||||||
message_type="${o.message_type}"></converse-message-actions>
|
message_type="${o.message_type}"></converse-message-actions>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
${ o.model.get('ogp_metadata')?.map(m =>
|
||||||
|
html`<converse-message-unfurl
|
||||||
|
jid="${o.jid}"
|
||||||
|
description="${m['og:description']}"
|
||||||
|
title="${m['og:title']}"
|
||||||
|
image="${m['og:image']}"
|
||||||
|
url="${m['og:url']}"></converse-message-unfurl>`) }
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user