diff --git a/src/components/common/helpers/renderTextWithEntities.tsx b/src/components/common/helpers/renderTextWithEntities.tsx
index 5bed9afab..0bcbcc07b 100644
--- a/src/components/common/helpers/renderTextWithEntities.tsx
+++ b/src/components/common/helpers/renderTextWithEntities.tsx
@@ -502,7 +502,7 @@ function processEntityAsHtml(
case ApiMessageEntityTypes.Code:
return `${renderedContent}`;
case ApiMessageEntityTypes.Pre:
- return `\`\`\`${entity.language || ''}
${renderedContent}
\`\`\`
`;
+ return `\`\`\`${renderText(entity.language || '', ['escape_html'])}
${renderedContent}
\`\`\`
`;
case ApiMessageEntityTypes.Strike:
return `${renderedContent}`;
case ApiMessageEntityTypes.MentionName:
diff --git a/src/lib/hljs-tl/typelanguage.js b/src/lib/hljs-tl/typelanguage.js
new file mode 100644
index 000000000..b413a792a
--- /dev/null
+++ b/src/lib/hljs-tl/typelanguage.js
@@ -0,0 +1,81 @@
+export default (hljs) => {
+ const IDENTIFIER_RE = '[a-zA-Z_0-9]+';
+ const IDENTIFIER_WITH_NAMESPACE_RE = '[a-zA-Z_0-9.]+';
+ const TL = [
+ {
+ className: 'keyword',
+ begin: '---',
+ end: '---',
+ },
+ {
+ className: 'number',
+ begin: '#',
+ end: '\\s',
+ excludeBegin: true,
+ excludeEnd: true,
+ },
+ {
+ className: 'punctuation',
+ match: '[:#?=<>]',
+ },
+ {
+ className: 'symbol',
+ match: 'flags\\d*\\.\\d*', // Flagged parameters
+ },
+ {
+ className: 'built_in',
+ match: 'flags:#', // Flags
+ },
+ {
+ className: 'title.class',
+ match: `^${IDENTIFIER_RE}(?=\\.)`, // Namespace
+ },
+ {
+ className: 'title.function',
+ match: `^${IDENTIFIER_RE}(?=[\\s#])`, // Identifier followed by space or #
+ },
+ {
+ className: 'title.function',
+ match: `(?<=\\.)${IDENTIFIER_RE}(?=[\\s#])`, // Identifier after namespace
+ },
+ {
+ className: 'params',
+ match: `(?<=\\s)${IDENTIFIER_RE}(?=:)`, // Parameter name
+ },
+ {
+ className: 'type',
+ match: `(?<=[:?])${IDENTIFIER_WITH_NAMESPACE_RE}(?=\\s)`, // Parameter type
+ },
+ {
+ className: 'variable.constant',
+ match: `(?<=[:?])${IDENTIFIER_RE}(?=<)`, // Generic type
+ },
+ {
+ className: 'type',
+ match: `(?<=<)${IDENTIFIER_RE}(?=>)`, // Type inside angle brackets
+ },
+ {
+ className: 'title.function.invoke',
+ match: `(?<==\\s)${IDENTIFIER_RE}(?=;)`, // Result identifier
+ },
+ {
+ className: 'title.class',
+ match: `(?<==\\s)${IDENTIFIER_RE}(?=\\.)`, // Result namespace
+ },
+ {
+ className: 'title.function.invoke',
+ match: `(?<==\\s${IDENTIFIER_RE}\\.)${IDENTIFIER_RE}(?=;)`, // Result identifier after namespace
+ },
+ ];
+
+ return {
+ name: 'TypeLanguage',
+ aliases: ['tl'],
+ case_insensitive: false,
+ contains: [
+ hljs.C_LINE_COMMENT_MODE,
+ hljs.C_BLOCK_COMMENT_MODE,
+ hljs.C_NUMBER_MODE,
+ ].concat(TL),
+ };
+};
diff --git a/src/util/highlightCode.ts b/src/util/highlightCode.ts
index a4c2b084a..f6b72af79 100644
--- a/src/util/highlightCode.ts
+++ b/src/util/highlightCode.ts
@@ -38,12 +38,15 @@ const SUPPORTED_LANGUAGES: Record = {
sql: [],
swift: [],
twig: ['craftcms'],
+ typelanguage: ['tl'],
typescript: ['ts', 'tsx'],
xml: ['html', 'xhtml', 'rss', 'atom', 'xjb', 'xsd', 'xsl', 'plist', 'wsf', 'svg'],
yaml: [],
};
-const languagePromises = new Map>();
+const THIRD_PARTY_LANGUAGES = ['typelanguage'];
+
+const languagePromises = new Map>();
export default async function highlightCode(text: string, language: string) {
const lowLang = language.toLowerCase();
@@ -73,13 +76,10 @@ async function ensureLanguage(language: string) {
return true;
}
- // Funky webpack bug https://github.com/webpack/webpack/issues/13865
- const languagePromise = import(
- /* webpackChunkName: "Highlight for [request]" */
- `../../node_modules/highlight.js/lib/languages/${langCode}`
- );
- languagePromises.set(langCode, languagePromise);
- // Allow errors to help debugging wrong language names
+ const languagePromise = THIRD_PARTY_LANGUAGES.includes(langCode)
+ ? loadThirdPartyLanguage(langCode) : loadFirstPartyLanguage(langCode);
+ if (!languagePromise) return false;
+
const syntax = await languagePromise;
lowlight.registerLanguage(langCode, syntax.default);
if (langCode === '1c') {
@@ -88,6 +88,25 @@ async function ensureLanguage(language: string) {
return true;
}
+function loadFirstPartyLanguage(langCode: string) {
+ // Funky webpack bug https://github.com/webpack/webpack/issues/13865
+ const languagePromise = import(
+ /* webpackChunkName: "Highlight for [request]" */
+ `../../node_modules/highlight.js/lib/languages/${langCode}`
+ );
+ languagePromises.set(langCode, languagePromise);
+ return languagePromise;
+}
+
+function loadThirdPartyLanguage(langCode: string) {
+ if (langCode === 'typelanguage') {
+ const langPromise = import('../lib/hljs-tl/typelanguage');
+ languagePromises.set(langCode, langPromise);
+ return langPromise;
+ }
+ return undefined;
+}
+
function treeToElements(tree: Element | Root): TeactNode {
const children = tree.children.map((child) => {
if (child.type === 'text') {