2
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2025-09-06 10:50:21 +08:00

DEV: adds initial support for custom blocks using code fencing (#15743)

Allows to write custom code blocks:

```
   ```mermaid height=200,foo=bar
   test
   ```
```

Which will then get converted to:

```
<pre data-code-wrap="mermaid" data-code-height="200" data-code-foo="bar">
  <code class="lang-nohighlight">
    test
  </code>
</pre>
```
This commit is contained in:
Joffrey JAFFEUX 2022-02-09 11:23:44 +01:00 committed by GitHub
parent c38114f0c6
commit b3ecf00c98
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 173 additions and 32 deletions

View file

@ -1,35 +1,78 @@
// we need a custom renderer for code blocks cause we have a slightly non compliant
// format with special handling for text and so on
const TEXT_CODE_CLASSES = ["text", "pre", "plain"];
function extractTokenInfo(info, md) {
if (!info) {
return;
}
info = info.trim();
const matches = info.match(/(^\s*\S*)\s*(.*)/i);
if (!matches) {
return;
}
// ensure the token has only valid chars
// c++, strucuted-text and p91, are all valid
if (!/^[\w+-]*$/i.test(matches[1])) {
return;
}
const ASCII_REGEX = /[^\x00-\x7F]/;
const tag = md.utils.unescapeAll(matches[1].replace(ASCII_REGEX, ""));
const extractedData = { tag, attributes: {} };
if (matches[2]?.length) {
md.utils
.unescapeAll(matches[2].replace(ASCII_REGEX, ""))
.split(",")
.forEach((potentialPair) => {
const [key, value] = potentialPair.trim().split(/\s+/g)[0].split("=");
// invalid pairs would get caught here and not used, eg `foo=`
if (key && value) {
extractedData.attributes[key] = value;
}
});
}
return extractedData;
}
function render(tokens, idx, options, env, slf, md) {
let token = tokens[idx],
info = token.info ? md.utils.unescapeAll(token.info) : "",
langName = md.options.discourse.defaultCodeLang,
className,
escapedContent = md.utils.escapeHtml(token.content);
const token = tokens[idx];
const escapedContent = md.utils.escapeHtml(token.content);
const tokenInfo = extractTokenInfo(token.info, md);
const tag = tokenInfo?.tag || md.options.discourse.defaultCodeLang;
const attributes = tokenInfo?.attributes || {};
if (info) {
// strip off any additional languages
info = info.trim().split(/\s+/g)[0];
let className;
const acceptableCodeClasses =
md.options.discourse.acceptableCodeClasses || [];
if (TEXT_CODE_CLASSES.indexOf(tag) > -1) {
className = "lang-nohighlight";
} else if (acceptableCodeClasses.indexOf(tag) > -1) {
className = `lang-${tag}`;
} else {
className = "lang-nohighlight";
attributes["wrap"] = tag;
}
const acceptableCodeClasses = md.options.discourse.acceptableCodeClasses;
if (
acceptableCodeClasses &&
info &&
acceptableCodeClasses.indexOf(info) !== -1
) {
langName = info;
}
const dataAttributes = Object.keys(attributes)
.map((key) => {
const value = md.utils.escapeHtml(attributes[key]);
key = md.utils.escapeHtml(key);
return `data-code-${key}="${value}"`;
})
.join(" ");
className =
TEXT_CODE_CLASSES.indexOf(info) !== -1
? "lang-nohighlight"
: "lang-" + langName;
return `<pre><code class="${className}">${escapedContent}</code></pre>\n`;
return `<pre${dataAttributes ? ` ${dataAttributes}` : ""}><code${
className ? ` class="${className}"` : ""
}>${escapedContent}</code></pre>\n`;
}
export function setup(helper) {
@ -41,6 +84,8 @@ export function setup(helper) {
.concat(["auto", "nohighlight"]);
});
helper.allowList(["pre[data-code-*]"]);
helper.allowList({
custom(tag, name, value) {
if (tag === "code" && name === "class") {