Styling: Take offset into consideration when adding templates

This commit is contained in:
JC Brand 2020-11-27 10:58:38 +01:00
parent 30e784b8ec
commit 35db01d316
3 changed files with 83 additions and 23 deletions

View File

@ -5,7 +5,7 @@ const u = converse.env.utils;
const original_timeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; const original_timeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
fdescribe("Emojis", function () { describe("Emojis", function () {
describe("The emoji picker", function () { describe("The emoji picker", function () {
beforeEach(() => (jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000)); beforeEach(() => (jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000));

View File

@ -300,6 +300,36 @@ describe("An incoming chat Message", function () {
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
'<blockquote> ```\n (println "Hello, world!")</blockquote>\n'+ '<blockquote> ```\n (println "Hello, world!")</blockquote>\n'+
'The entire blockquote is a preformatted text block, but this line is plaintext!'); 'The entire blockquote is a preformatted text block, but this line is plaintext!');
msg_text = '> Also, icons.js is loaded from /dist, instead of dist.\nhttps://conversejs.org/docs/html/configuration.html#assets-path'
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
'<blockquote> Also, icons.js is loaded from /dist, instead of dist.</blockquote>'+
'<a target="_blank" rel="noopener" href="https://conversejs.org/docs/html/configuration.html#assets-path">https://conversejs.org/docs/html/configuration.html#assets-path</a>');
msg_text = '> Where is it located?\ngeo:37.786971,-122.399677';
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
'<blockquote> Where is it located?</blockquote>'+
'<a target="_blank" rel="noopener" '+
'href="https://www.openstreetmap.org/?mlat=37.786971&amp;mlon=-122.399677#map=18/37.786971/-122.399677">https://www.openstreetmap.org/?mlat=37.786971&amp;mlon=-122.399677#map=18/37.786971/-122.399677</a>');
msg_text = '> What do you think of it?\n :poop:';
msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
'<blockquote> What do you think of it?</blockquote> <span title=":poop:">💩</span>');
done(); done();
})); }));
}); });

View File

@ -57,7 +57,13 @@ export class MessageText extends String {
this.payload = []; this.payload = [];
} }
addHyperlinks (text) { /**
* Look for `http` URIs and return templates that render them as URL links
* @param { String } text
* @param { Integer } offset - The index of the passed in text relative to
* the start of the message body text.
*/
addHyperlinks (text, offset) {
const objs = []; const objs = [];
try { try {
const parse_options = { 'start': /\b(?:([a-z][a-z0-9.+-]*:\/\/)|xmpp:|mailto:|www\.)/gi }; const parse_options = { 'start': /\b(?:([a-z][a-z0-9.+-]*:\/\/)|xmpp:|mailto:|www\.)/gi };
@ -73,8 +79,8 @@ export class MessageText extends String {
const url_text = text.slice(url_obj.start, url_obj.end); const url_text = text.slice(url_obj.start, url_obj.end);
const filtered_url = u.filterQueryParamsFromURL(url_text); const filtered_url = u.filterQueryParamsFromURL(url_text);
this.addTemplateResult( this.addTemplateResult(
url_obj.start, url_obj.start+offset,
url_obj.end, url_obj.end+offset,
this.show_images && u.isImageURL(url_text) && u.isImageDomainAllowed(url_text) ? this.show_images && u.isImageURL(url_text) && u.isImageDomainAllowed(url_text) ?
u.convertToImageTag(filtered_url, this.onImgLoad, this.onImgClick) : u.convertToImageTag(filtered_url, this.onImgLoad, this.onImgClick) :
u.convertUrlToHyperlink(filtered_url), u.convertUrlToHyperlink(filtered_url),
@ -82,35 +88,54 @@ export class MessageText extends String {
}); });
} }
addMapURLs (text) { /**
* Look for `geo` URIs and return templates that render them as URL links
* @param { String } text
* @param { Integer } offset - The index of the passed in text relative to
* the start of the message body text.
*/
addMapURLs (text, offset) {
const regex = /geo:([\-0-9.]+),([\-0-9.]+)(?:,([\-0-9.]+))?(?:\?(.*))?/g; const regex = /geo:([\-0-9.]+),([\-0-9.]+)(?:,([\-0-9.]+))?(?:\?(.*))?/g;
const matches = text.matchAll(regex); const matches = text.matchAll(regex);
for (const m of matches) { for (const m of matches) {
this.addTemplateResult( this.addTemplateResult(
m.index, m.index+offset,
m.index+m.input.length, m.index+m.input.length+offset,
u.convertUrlToHyperlink(m.input.replace(regex, _converse.geouri_replacement)) u.convertUrlToHyperlink(m.input.replace(regex, _converse.geouri_replacement))
); );
} }
} }
async addEmojis (text) { /**
* Look for emojis (shortnames or unicode) and add templates for rendering them.
* @param { String } text
* @param { Integer } offset - The index of the passed in text relative to
* the start of the message body text.
*/
async addEmojis (text, offset) {
await api.emojis.initialize(); await api.emojis.initialize();
const references = [...getShortnameReferences(text.toString()), ...getCodePointReferences(text.toString())]; const references = [...getShortnameReferences(text.toString()), ...getCodePointReferences(text.toString())];
references.forEach(e => { references.forEach(e => {
this.addTemplateResult( this.addTemplateResult(
e.begin, e.begin+offset,
e.end, e.end+offset,
getEmojiMarkup(e, {'add_title_wrapper': true}) getEmojiMarkup(e, {'add_title_wrapper': true})
); );
}); });
} }
addMentionReferences (text, offset) { /**
* Look for mentions included as XEP-0372 references and add templates for
* rendering them.
* @param { String } text
* @param { Integer } offset - The index of the passed in text relative to
* the start of the message body text.
*/
addMentions (text, offset) {
if (!this.model.collection) { if (!this.model.collection) {
// This model doesn't belong to a collection anymore, so it must be // This model doesn't belong to a collection anymore, so it must be
// have been removed in the meantime and can be ignored. // have been removed in the meantime and can be ignored.
log.debug('addMentionReferences: ignoring dangling model'); log.debug('addMentions: ignoring dangling model');
return; return;
} }
const nick = this.model.collection.chatbox.get('nick'); const nick = this.model.collection.chatbox.get('nick');
@ -129,7 +154,11 @@ export class MessageText extends String {
}); });
} }
addStylingReferences () { /**
* Look for XEP-0393 styling directives and add templates for rendering
* them.
*/
addStyling () {
if (this.model.get('is_unstyled') || !api.settings.get('allow_message_styling')) { if (this.model.get('is_unstyled') || !api.settings.get('allow_message_styling')) {
return; return;
} }
@ -184,19 +213,20 @@ export class MessageText extends String {
*/ */
await api.trigger('beforeMessageBodyTransformed', this, {'Synchronous': true}); await api.trigger('beforeMessageBodyTransformed', this, {'Synchronous': true});
this.addStylingReferences(); this.addStyling();
const payload = this.marshall(); const payload = this.marshall();
let idx = 0; // The text index of the element in the payload
let offset = this.offset;
for (const text of payload) { for (const text of payload) {
if (isString(text)) { if (!text) {
this.addHyperlinks(text); continue
this.addMapURLs(text); } else if (isString(text)) {
await this.addEmojis(text); this.addHyperlinks(text, idx);
this.addMentionReferences(text, offset); this.addMapURLs(text, idx);
offset += text.length; await this.addEmojis(text, idx);
this.addMentions(text, this.offset+idx);
idx += text.length;
} else { } else {
offset += text.begin; idx += text.end;
} }
} }