2020-10-16 09:02:12 +02:00
|
|
|
/**
|
|
|
|
* @copyright 2020, the Converse.js contributors
|
|
|
|
* @license Mozilla Public License (MPLv2)
|
|
|
|
* @description Utility functions to help with parsing XEP-393 message styling hints
|
|
|
|
* @todo Other parsing helpers can be made more abstract and placed here.
|
|
|
|
*/
|
2021-04-14 22:56:59 +02:00
|
|
|
import { html } from 'lit';
|
2021-03-24 11:59:09 +01:00
|
|
|
import { renderStylingDirectiveBody } from 'shared/directives/styling.js';
|
2020-10-16 09:02:12 +02:00
|
|
|
|
|
|
|
|
2021-06-22 16:52:50 +02:00
|
|
|
const bracketing_directives = ['*', '_', '~', '`'];
|
|
|
|
const styling_directives = [...bracketing_directives, '```', '>'];
|
2020-10-16 09:02:12 +02:00
|
|
|
const styling_map = {
|
|
|
|
'*': {'name': 'strong', 'type': 'span'},
|
|
|
|
'_': {'name': 'emphasis', 'type': 'span'},
|
|
|
|
'~': {'name': 'strike', 'type': 'span'},
|
|
|
|
'`': {'name': 'preformatted', 'type': 'span'},
|
|
|
|
'```': {'name': 'preformatted_block', 'type': 'block'},
|
|
|
|
'>': {'name': 'quote', 'type': 'block'}
|
|
|
|
};
|
|
|
|
|
|
|
|
const dont_escape = ['_', '>', '`', '~'];
|
|
|
|
|
|
|
|
const styling_templates = {
|
|
|
|
// m is the chatbox model
|
|
|
|
// i is the offset of this directive relative to the start of the original message
|
2021-09-09 16:20:33 +02:00
|
|
|
'emphasis': (txt, i, options) => html`<span class="styling-directive">_</span><i>${renderStylingDirectiveBody(txt, i, options)}</i><span class="styling-directive">_</span>`,
|
2020-10-16 09:02:12 +02:00
|
|
|
'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>`,
|
2021-09-09 16:20:33 +02:00
|
|
|
'quote': (txt, i, options) => html`<blockquote>${renderStylingDirectiveBody(txt, i, options)}</blockquote>`,
|
|
|
|
'strike': (txt, i, options) => html`<span class="styling-directive">~</span><del>${renderStylingDirectiveBody(txt, i, options)}</del><span class="styling-directive">~</span>`,
|
|
|
|
'strong': (txt, i, options) => html`<span class="styling-directive">*</span><b>${renderStylingDirectiveBody(txt, i, options)}</b><span class="styling-directive">*</span>`,
|
2020-10-16 09:02:12 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks whether a given character "d" at index "i" of "text" is a valid opening or closing directive.
|
|
|
|
* @param { String } d - The potential directive
|
|
|
|
* @param { String } text - The text in which the directive appears
|
|
|
|
* @param { Number } i - The directive index
|
|
|
|
* @param { Boolean } opening - Check for a valid opening or closing directive
|
|
|
|
*/
|
|
|
|
function isValidDirective (d, text, i, opening) {
|
|
|
|
// Ignore directives that are parts of words
|
|
|
|
// More info on the Regexes used here: https://javascript.info/regexp-unicode#unicode-properties-p
|
|
|
|
if (opening) {
|
|
|
|
const regex = RegExp(dont_escape.includes(d) ? `^(\\p{L}|\\p{N})${d}` : `^(\\p{L}|\\p{N})\\${d}`, 'u');
|
|
|
|
if (i > 1 && regex.test(text.slice(i-1))) {
|
|
|
|
return false;
|
|
|
|
}
|
2020-12-09 14:19:31 +01:00
|
|
|
const is_quote = isQuoteDirective(d);
|
|
|
|
if (is_quote && i > 0 && text[i-1] !== '\n') {
|
|
|
|
// Quote directives must be on newlines
|
|
|
|
return false;
|
2021-06-22 16:52:50 +02:00
|
|
|
} else if (bracketing_directives.includes(d) && (text[i+1] === d)) {
|
|
|
|
// Don't consider empty bracketing directives as valid (e.g. **, `` etc.)
|
2020-12-09 14:19:31 +01:00
|
|
|
return false;
|
|
|
|
}
|
2020-10-16 09:02:12 +02:00
|
|
|
} else {
|
|
|
|
const regex = RegExp(dont_escape.includes(d) ? `^${d}(\\p{L}|\\p{N})` : `^\\${d}(\\p{L}|\\p{N})`, 'u');
|
|
|
|
if (i < text.length-1 && regex.test(text.slice(i))) {
|
|
|
|
return false;
|
|
|
|
}
|
2021-06-22 16:52:50 +02:00
|
|
|
if (bracketing_directives.includes(d) && (text[i-1] === d)) {
|
|
|
|
// Don't consider empty directives as valid (e.g. **, `` etc.)
|
|
|
|
return false;
|
|
|
|
}
|
2020-10-16 09:02:12 +02:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Given a specific index "i" of "text", return the directive it matches or
|
|
|
|
* null otherwise.
|
|
|
|
* @param { String } text - The text in which the directive appears
|
|
|
|
* @param { Number } i - The directive index
|
|
|
|
* @param { Boolean } opening - Whether we're looking for an opening or closing directive
|
|
|
|
*/
|
|
|
|
function getDirective (text, i, opening=true) {
|
|
|
|
let d;
|
|
|
|
if ((/(^```\s*\n|^```\s*$)/).test(text.slice(i)) && (i === 0 || text[i-1] === '\n' || text[i-1] === '>')) {
|
|
|
|
d = text.slice(i, i+3);
|
2020-12-09 14:19:31 +01:00
|
|
|
} else if (styling_directives.includes(text.slice(i, i+1))) {
|
2020-10-16 09:02:12 +02:00
|
|
|
d = text.slice(i, i+1);
|
|
|
|
if (!isValidDirective(d, text, i, opening)) return null;
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return d;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Given a directive "d", which occurs in "text" at index "i", check that it
|
|
|
|
* has a valid closing directive and return the length from start to end of the
|
|
|
|
* directive.
|
|
|
|
* @param { String } d -The directive
|
|
|
|
* @param { Number } i - The directive index
|
|
|
|
* @param { String } text -The text in which the directive appears
|
|
|
|
*/
|
|
|
|
function getDirectiveLength (d, text, i) {
|
|
|
|
if (!d) { return 0; }
|
|
|
|
const begin = i;
|
|
|
|
i += d.length;
|
|
|
|
if (isQuoteDirective(d)) {
|
|
|
|
i += text.slice(i).split(/\n[^>]/).shift().length;
|
|
|
|
return i-begin;
|
|
|
|
} else if (styling_map[d].type === 'span') {
|
2021-06-22 16:52:50 +02:00
|
|
|
const line = text.slice(i).split('\n').shift();
|
2020-10-16 09:02:12 +02:00
|
|
|
let j = 0;
|
|
|
|
let idx = line.indexOf(d);
|
|
|
|
while (idx !== -1) {
|
2021-06-22 16:52:50 +02:00
|
|
|
if (getDirective(text, i+idx, false) === d) {
|
|
|
|
return idx+2*d.length;
|
|
|
|
}
|
2020-10-16 09:02:12 +02:00
|
|
|
idx = line.indexOf(d, j++);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
} else {
|
2021-06-22 16:52:50 +02:00
|
|
|
// block directives
|
2020-10-16 09:02:12 +02:00
|
|
|
const substring = text.slice(i+1);
|
2021-04-15 13:30:22 +02:00
|
|
|
let j = 0;
|
2020-10-16 09:02:12 +02:00
|
|
|
let idx = substring.indexOf(d);
|
|
|
|
while (idx !== -1) {
|
2021-06-22 16:52:50 +02:00
|
|
|
if (getDirective(text, i+1+idx, false) === d) {
|
|
|
|
return idx+1+2*d.length;
|
|
|
|
}
|
2020-10-16 09:02:12 +02:00
|
|
|
idx = substring.indexOf(d, j++);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function getDirectiveAndLength (text, i) {
|
|
|
|
const d = getDirective(text, i);
|
|
|
|
const length = d ? getDirectiveLength(d, text, i) : 0;
|
|
|
|
return length > 0 ? { d, length } : {};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const isQuoteDirective = (d) => ['>', '>'].includes(d);
|
|
|
|
|
|
|
|
|
2021-09-09 16:20:33 +02:00
|
|
|
export function getDirectiveTemplate (d, text, offset, options) {
|
2020-10-16 09:02:12 +02:00
|
|
|
const template = styling_templates[styling_map[d].name];
|
|
|
|
if (isQuoteDirective(d)) {
|
2020-12-09 15:11:54 +01:00
|
|
|
const newtext = text
|
|
|
|
.replace(/\n>/g, '\n') // Don't show the directive itself
|
2020-12-09 15:58:09 +01:00
|
|
|
.replace(/\n$/, ''); // Trim line-break at the end
|
2021-09-09 16:20:33 +02:00
|
|
|
return template(newtext, offset, options);
|
2020-10-16 09:02:12 +02:00
|
|
|
} else {
|
2021-09-09 16:20:33 +02:00
|
|
|
return template(text, offset, options);
|
2020-10-16 09:02:12 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function containsDirectives (text) {
|
|
|
|
for (let i=0; i<styling_directives.length; i++) {
|
|
|
|
if (text.includes(styling_directives[i])) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|