Update the MesageText class to not require a Model object

This allows us to use it to transform any piece of text and not just
text from a chat message.
This commit is contained in:
JC Brand 2021-03-24 09:59:26 +01:00
parent 16e7133e31
commit 1fd3e3676a
5 changed files with 43 additions and 42 deletions

View File

@ -83,7 +83,7 @@ const BaseModal = View.extend({
if (ev) { if (ev) {
ev.preventDefault(); ev.preventDefault();
this.trigger_el = ev.target; this.trigger_el = ev.target;
this.trigger_el.classList.add('selected'); !u.hasClass('chat-image', this.trigger_el) && u.addClass('selected', this.trigger_el);
} }
this.modal.show(); this.modal.show();
} }

View File

@ -23,12 +23,12 @@ const dont_escape = ['_', '>', '`', '~'];
const styling_templates = { const styling_templates = {
// m is the chatbox model // m is the chatbox model
// i is the offset of this directive relative to the start of the original message // i is the offset of this directive relative to the start of the original message
'emphasis': (txt, m, i) => html`<span class="styling-directive">_</span><i>${renderStylingDirectiveBody(txt, m, i)}</i><span class="styling-directive">_</span>`, 'emphasis': (txt, i, mentions, options) => html`<span class="styling-directive">_</span><i>${renderStylingDirectiveBody(txt, i, mentions, options)}</i><span class="styling-directive">_</span>`,
'preformatted': txt => html`<span class="styling-directive">\`</span><code>${txt}</code><span class="styling-directive">\`</span>`, 'preformatted': txt => html`<span class="styling-directive">\`</span><code>${txt}</code><span class="styling-directive">\`</span>`,
'preformatted_block': txt => html`<div class="styling-directive">\`\`\`</div><code class="block">${txt}</code><div class="styling-directive">\`\`\`</div>`, 'preformatted_block': txt => html`<div class="styling-directive">\`\`\`</div><code class="block">${txt}</code><div class="styling-directive">\`\`\`</div>`,
'quote': (txt, m, i) => html`<blockquote>${renderStylingDirectiveBody(txt, m, i)}</blockquote>`, 'quote': (txt, i, mentions, options) => html`<blockquote>${renderStylingDirectiveBody(txt, i, mentions, options)}</blockquote>`,
'strike': (txt, m, i) => html`<span class="styling-directive">~</span><del>${renderStylingDirectiveBody(txt, m, i)}</del><span class="styling-directive">~</span>`, 'strike': (txt, i, mentions, options) => html`<span class="styling-directive">~</span><del>${renderStylingDirectiveBody(txt, i, mentions, options)}</del><span class="styling-directive">~</span>`,
'strong': (txt, m, i) => html`<span class="styling-directive">*</span><b>${renderStylingDirectiveBody(txt, m, i)}</b><span class="styling-directive">*</span>`, 'strong': (txt, i, mentions, options) => html`<span class="styling-directive">*</span><b>${renderStylingDirectiveBody(txt, i, mentions, options)}</b><span class="styling-directive">*</span>`,
}; };
@ -145,15 +145,15 @@ export function getDirectiveAndLength (text, i) {
export const isQuoteDirective = (d) => ['>', '&gt;'].includes(d); export const isQuoteDirective = (d) => ['>', '&gt;'].includes(d);
export function getDirectiveTemplate (d, text, model, offset) { export function getDirectiveTemplate (d, text, offset, mentions, options) {
const template = styling_templates[styling_map[d].name]; const template = styling_templates[styling_map[d].name];
if (isQuoteDirective(d)) { if (isQuoteDirective(d)) {
const newtext = text const newtext = text
.replace(/\n>/g, '\n') // Don't show the directive itself .replace(/\n>/g, '\n') // Don't show the directive itself
.replace(/\n$/, ''); // Trim line-break at the end .replace(/\n$/, ''); // Trim line-break at the end
return template(newtext, model, offset); return template(newtext, offset, mentions, options);
} else { } else {
return template(text, model, offset); return template(text, offset, mentions, options);
} }
} }

View File

@ -39,25 +39,30 @@ export class MessageText extends String {
/** /**
* Create a new {@link MessageText} instance. * Create a new {@link MessageText} instance.
* @param { String } text - The text to be annotated * @param { String } text - The text to be annotated
* @param { Message } model - The model representing the message to which
* this MessageText instance belongs
* @param { Integer } offset - The offset of this particular piece of text * @param { Integer } offset - The offset of this particular piece of text
* from the start of the original message text. This is necessary because * from the start of the original message text. This is necessary because
* MessageText instances can be nested when templates call directives * MessageText instances can be nested when templates call directives
* which create new MessageText instances (as happens with XEP-393 styling directives). * which create new MessageText instances (as happens with XEP-393 styling directives).
* @param { Boolean } show_images - Whether image URLs should be rendered as <img> tags. * @param { Array } mentions - An array of mention references
* @param { Function } onImgLoad - Callback for when an inline rendered image has been loaded * @param { Object } options
* @param { Function } onImgClick - Callback for when an inline rendered image has been clicked * @param { Object } options.nick - The current user's nickname (only relevant if the message is in a XEP-0045 MUC)
* @param { Boolean } options.render_styling - Whether XEP-0393 message styling should be applied to the message
* @param { Boolean } options.show_images - Whether image URLs should be rendered as <img> tags.
* @param { Function } options.onImgClick - Callback for when an inline rendered image has been clicked
* @param { Function } options.onImgLoad - Callback for when an inline rendered image has been loaded
*/ */
constructor (text, model, offset=0, show_images, onImgLoad, onImgClick) { constructor (text, offset=0, mentions=[], options={}) {
super(text); super(text);
this.model = model; this.mentions = mentions;
this.nick = options?.nick;
this.offset = offset; this.offset = offset;
this.onImgClick = onImgClick; this.onImgClick = options?.onImgClick;
this.onImgLoad = onImgLoad; this.onImgLoad = options?.onImgLoad;
this.references = []; this.options = options;
this.show_images = show_images;
this.payload = []; this.payload = [];
this.references = [];
this.render_styling = options?.render_styling;
this.show_images = options?.show_images;
} }
/** /**
@ -136,21 +141,14 @@ export class MessageText extends String {
*/ */
addMentions (text, local_offset) { addMentions (text, local_offset) {
const full_offset = local_offset+this.offset; const full_offset = local_offset+this.offset;
if (!this.model.collection) { this.mentions?.forEach(ref => {
// This model doesn't belong to a collection anymore, so it must be
// have been removed in the meantime and can be ignored.
log.debug('addMentions: ignoring dangling model');
return;
}
const nick = this.model.collection.chatbox.get('nick');
this.model.get('references')?.forEach(ref => {
const begin = Number(ref.begin)-full_offset; const begin = Number(ref.begin)-full_offset;
if (begin < 0 || begin >= full_offset+text.length) { if (begin < 0 || begin >= full_offset+text.length) {
return; return;
} }
const end = Number(ref.end)-full_offset; const end = Number(ref.end)-full_offset;
const mention = text.slice(begin, end); const mention = text.slice(begin, end);
if (mention === nick) { if (mention === this.nick) {
this.addTemplateResult( this.addTemplateResult(
begin+local_offset, begin+local_offset,
end+local_offset, end+local_offset,
@ -171,9 +169,6 @@ export class MessageText extends String {
* them. * them.
*/ */
addStyling () { addStyling () {
if (this.model.get('is_unstyled') || !api.settings.get('allow_message_styling')) {
return;
}
let i = 0; let i = 0;
const references = []; const references = [];
if (containsDirectives(this)) { if (containsDirectives(this)) {
@ -192,7 +187,7 @@ export class MessageText extends String {
const text = this.slice(slice_begin, slice_end); const text = this.slice(slice_begin, slice_end);
references.push({ references.push({
'begin': i, 'begin': i,
'template': getDirectiveTemplate(d, text, this.model, offset), 'template': getDirectiveTemplate(d, text, offset, this.mentions, this.options),
end, end,
}); });
i = end; i = end;
@ -254,7 +249,7 @@ export class MessageText extends String {
*/ */
await api.trigger('beforeMessageBodyTransformed', this, {'Synchronous': true}); await api.trigger('beforeMessageBodyTransformed', this, {'Synchronous': true});
this.addStyling(); this.render_styling && this.addStyling();
this.addAnnotations(this.addMentions); this.addAnnotations(this.addMentions);
this.addAnnotations(this.addHyperlinks); this.addAnnotations(this.addHyperlinks);
this.addAnnotations(this.addMapURLs); this.addAnnotations(this.addMapURLs);

View File

@ -18,14 +18,19 @@ class MessageBodyRenderer {
async transform () { async transform () {
const show_images = api.settings.get('show_images_inline'); const show_images = api.settings.get('show_images_inline');
const render_styling = !this.model.get('is_unstyled') && api.settings.get('allow_message_styling');
const offset = 0; const offset = 0;
const text = new MessageText( const text = new MessageText(
this.text, this.text,
this.model,
offset, offset,
show_images, this.model.get('references'),
() => this.onImageLoaded(), {
ev => this.component.showImageModal(ev) 'nick': this.model.collection.chatbox.get('nick'),
'onImgClick': () => this.onImageLoaded(),
'onImgLoad': ev => this.component.showImageModal(ev),
render_styling,
show_images,
}
); );
await text.addTemplates(); await text.addTemplates();
return text.payload; return text.payload;

View File

@ -1,16 +1,17 @@
import { MessageText } from '../../shared/message/text.js'; import { MessageText } from '../../shared/message/text.js';
import { directive, html } from "lit-html"; import { directive, html } from 'lit-html';
import { until } from 'lit-html/directives/until.js'; import { until } from 'lit-html/directives/until.js';
async function transform (t) { async function transform (t) {
await t.addTemplates(); await t.addTemplates();
return t.payload; return t.payload;
} }
function renderer (text, model, offset) { function renderer (text, offset, mentions, options) {
const t = new MessageText(text, model, offset, false); const t = new MessageText(text, offset, mentions, Object.assign(options, { 'show_images': false }));
return html`${until(transform(t), html`${t}`)}`; return html`${until(transform(t), html`${t}`)}`;
} }
export const renderStylingDirectiveBody = directive((text, model, offset) => p => p.setValue(renderer(text, model, offset))); export const renderStylingDirectiveBody = directive((txt, offset, mentions, options) =>
p => p.setValue(renderer(txt, offset, mentions, options))
);