@fedify/markdown-it-mention
Version:
A markdown-it plugin that parses and renders Mastodon-style @mentions.
97 lines (96 loc) • 3.22 kB
JavaScript
;
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
Object.defineProperty(exports, "__esModule", { value: true });
exports.escape = escape;
exports.unescape = unescape;
const rawToEntityEntries = [
["&", "&"],
["<", "<"],
[">", ">"],
['"', """],
["'", "'"],
];
const defaultEntityList = Object.fromEntries([
...rawToEntityEntries.map(([raw, entity]) => [entity, raw]),
["'", "'"],
[" ", "\xa0"],
]);
const rawToEntity = new Map(rawToEntityEntries);
const rawRe = new RegExp(`[${[...rawToEntity.keys()].join("")}]`, "g");
/**
* Escapes text for safe interpolation into HTML text content and quoted attributes.
*
* @example Usage
* ```ts
* import { escape } from "@std/html/entities";
* import { assertEquals } from "@std/assert/assert-equals";
*
* assertEquals(escape("<>'&AA"), "<>'&AA");
*
* // Characters that don't need to be escaped will be left alone,
* // even if named HTML entities exist for them.
* assertEquals(escape("þð"), "þð");
* ```
*
* @param str The string to escape.
* @returns The escaped string.
*/
function escape(str) {
return str.replaceAll(rawRe, (m) => rawToEntity.get(m));
}
const defaultUnescapeOptions = {
entityList: defaultEntityList,
};
const MAX_CODE_POINT = 0x10ffff;
const RX_DEC_ENTITY = /&#([0-9]+);/g;
const RX_HEX_ENTITY = /&#x(\p{AHex}+);/gu;
const entityListRegexCache = new WeakMap();
/**
* Unescapes HTML entities in text.
*
* Default options only handle `&<>'"` and numeric entities.
*
* @example Basic usage
* ```ts
* import { unescape } from "@std/html/entities";
* import { assertEquals } from "@std/assert/assert-equals";
*
* assertEquals(unescape("<>'&AA"), "<>'&AA");
* assertEquals(unescape("þð"), "þð");
* ```
*
* @example Using a custom entity list
*
* This uses the full named entity list from the HTML spec (~47K un-minified)
*
* ```ts
* import { unescape } from "@std/html/entities";
* import entityList from "@std/html/named-entity-list.json" with { type: "json" };
* import { assertEquals } from "@std/assert/assert-equals";
*
* assertEquals(unescape("<>'&AA", { entityList }), "<>'&AA");
* ```
*
* @param str The string to unescape.
* @param options Options for unescaping.
* @returns The unescaped string.
*/
function unescape(str, options = {}) {
const { entityList } = { ...defaultUnescapeOptions, ...options };
let entityRe = entityListRegexCache.get(entityList);
if (!entityRe) {
entityRe = new RegExp(`(${Object.keys(entityList)
.sort((a, b) => b.length - a.length)
.join("|")})`, "g");
entityListRegexCache.set(entityList, entityRe);
}
return str
.replaceAll(entityRe, (m) => entityList[m])
.replaceAll(RX_DEC_ENTITY, (_, dec) => codePointStrToChar(dec, 10))
.replaceAll(RX_HEX_ENTITY, (_, hex) => codePointStrToChar(hex, 16));
}
function codePointStrToChar(codePointStr, radix) {
const codePoint = parseInt(codePointStr, radix);
return codePoint > MAX_CODE_POINT ? "�" : String.fromCodePoint(codePoint);
}