import { html } from 'lit';
import { bracketing_directives, dont_escape, styling_directives, styling_map } from './constants';
/**
* @param {any} s
* @returns {boolean} - Returns true if the input is a string, otherwise false.
*/
export function isString(s) {
return typeof s === 'string';
}
/**
* @param {string} url
* @returns {boolean}
*/
export function isSpotifyTrack(url) {
try {
const { hostname, pathname } = new URL(url);
return hostname === 'open.spotify.com' && pathname.startsWith('/track/');
} catch (e) {
console.warn(`Could not create URL object from ${url}`);
return false;
}
}
/**
* @param {string} url
* @returns {Promise}
*/
export async function getHeaders(url) {
try {
const response = await fetch(url, { method: 'HEAD' });
return response.headers;
} catch (e) {
console.warn(`Error calling HEAD on url ${url}: ${e}`);
return null;
}
}
/**
* We don't render more than two line-breaks, replace extra line-breaks with
* the zero-width whitespace character
* This takes into account other characters that may have been removed by
* being replaced with a zero-width space, such as '> ' in the case of
* multi-line quotes.
* @param {string} text
*/
export function collapseLineBreaks(text) {
return text.replace(/\n(\u200B*\n)+/g, (m) => `\n${'\u200B'.repeat(m.length - 2)}\n`);
}
export const tplMentionWithNick = (o) =>
html`${o.mention}`;
export function tplMention(o) {
return html`${o.mention}`;
}
/**
* 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 {import('./texture').Texture} 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
* @returns {boolean}
*/
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;
}
const is_quote = isQuoteDirective(d);
if (is_quote && i > 0 && text[i - 1] !== '\n') {
// Quote directives must be on newlines
return false;
} else if (bracketing_directives.includes(d) && text[i + 1] === d) {
// Don't consider empty bracketing directives as valid (e.g. **, `` etc.)
return false;
}
} 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;
}
if (bracketing_directives.includes(d) && text[i - 1] === d) {
// Don't consider empty directives as valid (e.g. **, `` etc.)
return false;
}
}
return true;
}
/**
* Given a specific index "i" of "text", return the directive it matches or null otherwise.
* @param {import('./texture').Texture} 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
* @returns {string|null}
*/
function getDirective(text, i, opening = true) {
let d;
if (
/(^```[\s,\u200B]*\n)|(^```[\s,\u200B]*$)/.test(text.slice(i)) &&
(i === 0 || text[i - 1] === '>' || /\n\u200B{0,2}$/.test(text.slice(0, i)))
) {
d = text.slice(i, i + 3);
} else if (styling_directives.includes(text.slice(i, i + 1))) {
d = text.slice(i, i + 1);
if (!isValidDirective(d, text, i, opening)) return null;
} else {
return null;
}
return d;
}
/**
* @param {import('./texture').Texture} text
* @param {number} i
*/
export function getDirectiveAndLength(text, i) {
const d = getDirective(text, i);
const length = d ? getDirectiveLength(d, text, i) : 0;
return length > 0 ? { d, length } : {};
}
/**
* 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 {import('./texture').Texture} 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\u200B*[^>\u200B]/)
.shift().length;
return i - begin;
} else if (styling_map[d].type === 'span') {
const line = text.slice(i).split('\n').shift();
let j = 0;
let idx = line.indexOf(d);
while (idx !== -1) {
if (getDirective(text, i + idx, false) === d) {
return idx + 2 * d.length;
}
idx = line.indexOf(d, j++);
}
return 0;
} else {
// block directives
const substring = text.slice(i + 1);
let j = 0;
let idx = substring.indexOf(d);
while (idx !== -1) {
if (getDirective(text, i + 1 + idx, false) === d) {
return idx + 1 + 2 * d.length;
}
idx = substring.indexOf(d, j++);
}
return 0;
}
}
/**
* @param {string} d
*/
export function isQuoteDirective(d) {
return ['>', '>'].includes(d);
}
/**
* @param {import('./texture').Texture} text
* @returns {boolean}
*/
export function containsDirectives(text) {
for (let i = 0; i < styling_directives.length; i++) {
if (text.includes(styling_directives[i])) {
return true;
}
}
return false;
}