You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
senzapaura_es/src/lib/content/extracto.ts

119 lines
4.0 KiB

/**
* Extracto de la letra para las tarjetas de canción.
*
* Las tarjetas enseñan los primeros versos de un tema, que es lo que invita a
* entrar en su ficha. Esos versos están en el cuerpo del Markdown, y el cuerpo
* se compila a un componente de Svelte: para cuando el listado tiene los datos,
* el texto ya no existe como texto.
*
* La solución es sacarlo antes de compilar. `conExtracto` se ejecuta como
* preprocesador —antes que mdsvex— y escribe el fragmento en el propio
* frontmatter, de donde lo recoge el esquema como un campo más. Así el extracto
* no se mantiene a mano y nunca se desincroniza de la letra.
*
* Este modulo no importa nada del proyecto (ni alias `$lib`) a proposito:
* tambien lo carga `vite.config.ts`, que se evalua fuera de la aplicacion.
*/
export interface OpcionesDeExtracto {
/** Versos como mucho. */
maxLineas?: number;
/** Caracteres como mucho, contando el total. */
maxCaracteres?: number;
}
/** Marcas de Markdown que no deben verse en una tarjeta. */
function limpiar(linea: string): string {
return linea
.replace(/^\s{0,3}#{1,6}\s+/, '') // encabezados
.replace(/^\s{0,3}>\s?/, '') // citas
.replace(/^\s{0,3}[-*+]\s+/, '') // listas
.replace(/\*\*(.+?)\*\*/g, '$1')
.replace(/(^|\W)[*_](\S(?:.*?\S)?)[*_](\W|$)/g, '$1$2$3')
.replace(/`(.+?)`/g, '$1')
.replace(/\[(.+?)\]\([^)]*\)/g, '$1')
.trim();
}
/**
* Primeros versos de un cuerpo de Markdown.
*
* Se corta en la primera línea en blanco: la primera estrofa es una unidad de
* sentido, y partir por la mitad de la segunda queda peor que enseñar menos.
*/
export function extraerLineas(cuerpo: string, opciones: OpcionesDeExtracto = {}): string[] {
const { maxLineas = 4, maxCaracteres = 180 } = opciones;
const lineas = cuerpo.replace(/\r\n/g, '\n').split('\n');
const resultado: string[] = [];
let total = 0;
let empezado = false;
for (let i = 0; i < lineas.length; i++) {
const linea = limpiar(lineas[i]);
if (linea === '') {
// Las lineas en blanco de antes del texto se ignoran; la primera de
// despues cierra la estrofa.
if (empezado) break;
continue;
}
empezado = true;
if (total + linea.length > maxCaracteres) {
// Cabe algo de esta linea: se corta por la ultima palabra entera.
const espacio = maxCaracteres - total;
if (espacio > 24) {
const recortada = linea.slice(0, espacio);
const corte = recortada.lastIndexOf(' ');
resultado.push(`${(corte > 0 ? recortada.slice(0, corte) : recortada).trim()}…`);
} else if (resultado.length > 0) {
resultado[resultado.length - 1] += '…';
}
return resultado;
}
resultado.push(linea);
total += linea.length;
if (resultado.length >= maxLineas) {
// Los puntos suspensivos solo si se corta la estrofa por la mitad. Que
// el tema siga después no cuenta: la estrofa ha terminado entera.
if (limpiar(lineas[i + 1] ?? '') !== '') {
resultado[resultado.length - 1] += '…';
}
return resultado;
}
}
return resultado;
}
const DELIMITADOR = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
/**
* Devuelve el archivo con `extracto` añadido al frontmatter.
*
* Se respeta un `extracto` escrito a mano, y se deja el archivo intacto si no
* tiene frontmatter o si el cuerpo está vacío.
*/
export function conExtracto(fuente: string, opciones: OpcionesDeExtracto = {}): string {
const encabezado = DELIMITADOR.exec(fuente);
if (!encabezado) return fuente;
const frontmatter = encabezado[1];
if (/^extracto\s*:/m.test(frontmatter)) return fuente;
const cuerpo = fuente.slice(encabezado[0].length);
const lineas = extraerLineas(cuerpo, opciones);
if (lineas.length === 0) return fuente;
// JSON es YAML válido en forma de secuencia, así que `JSON.stringify` se
// encarga del escapado sin tener que reimplementar las reglas de YAML.
const inyectado = `${frontmatter}\nextracto: ${JSON.stringify(lineas)}`;
// Con función en vez de cadena: el contenido puede llevar `$&` o `$1`, que
// en una cadena de reemplazo se interpretarían como referencias.
return fuente.replace(DELIMITADOR, () => `---\n${inyectado}\n---\n`);
}

Powered by TurnKey Linux.