FIX: pretty text allow list (#10977)

Reword whitelist to allowlist in pretty-text.
This library is used by plugins so we need deprecation notice.
This commit is contained in:
Krzysztof Kotlarek 2020-10-28 13:22:06 +11:00 committed by GitHub
parent 632942e697
commit dbec3792b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 315 additions and 272 deletions

View File

@ -268,7 +268,7 @@ export default Controller.extend(ModalFunctionality, {
} else { } else {
const opts = { const opts = {
features: { editHistory: true, historyOneboxes: true }, features: { editHistory: true, historyOneboxes: true },
whiteListed: { allowListed: {
editHistory: { custom: (tag, attr) => attr === "class" }, editHistory: { custom: (tag, attr) => attr === "class" },
historyOneboxes: ["header", "article", "div[style]"], historyOneboxes: ["header", "article", "div[style]"],
}, },

View File

@ -1,7 +1,7 @@
import { getURLWithCDN } from "discourse-common/lib/get-url"; import { getURLWithCDN } from "discourse-common/lib/get-url";
import PrettyText, { buildOptions } from "pretty-text/pretty-text"; import PrettyText, { buildOptions } from "pretty-text/pretty-text";
import { performEmojiUnescape, buildEmojiUrl } from "pretty-text/emoji"; import { performEmojiUnescape, buildEmojiUrl } from "pretty-text/emoji";
import WhiteLister from "pretty-text/white-lister"; import AllowLister from "pretty-text/allow-lister";
import { sanitize as textSanitize } from "pretty-text/sanitizer"; import { sanitize as textSanitize } from "pretty-text/sanitizer";
import loadScript from "discourse/lib/load-script"; import loadScript from "discourse/lib/load-script";
import { formatUsername } from "discourse/lib/utilities"; import { formatUsername } from "discourse/lib/utilities";
@ -49,7 +49,7 @@ export function generateCookFunction(options) {
} }
export function sanitize(text, options) { export function sanitize(text, options) {
return textSanitize(text, new WhiteLister(options)); return textSanitize(text, new AllowLister(options));
} }
export function sanitizeAsync(text, options) { export function sanitizeAsync(text, options) {

View File

@ -1,36 +1,36 @@
import { test, module } from "qunit"; import { test, module } from "qunit";
import WhiteLister from "pretty-text/white-lister"; import AllowLister from "pretty-text/allow-lister";
module("lib:whiteLister"); module("lib:allowLister");
test("whiteLister", (assert) => { test("allowLister", (assert) => {
const whiteLister = new WhiteLister(); const allowLister = new AllowLister();
assert.ok( assert.ok(
Object.keys(whiteLister.getWhiteList().tagList).length > 1, Object.keys(allowLister.getAllowList().tagList).length > 1,
"should have some defaults" "should have some defaults"
); );
whiteLister.disable("default"); allowLister.disable("default");
assert.ok( assert.ok(
Object.keys(whiteLister.getWhiteList().tagList).length === 0, Object.keys(allowLister.getAllowList().tagList).length === 0,
"should have no defaults if disabled" "should have no defaults if disabled"
); );
whiteLister.whiteListFeature("test", [ allowLister.allowListFeature("test", [
"custom.foo", "custom.foo",
"custom.baz", "custom.baz",
"custom[data-*]", "custom[data-*]",
"custom[rel=nofollow]", "custom[rel=nofollow]",
]); ]);
whiteLister.whiteListFeature("test", ["custom[rel=test]"]); allowLister.allowListFeature("test", ["custom[rel=test]"]);
whiteLister.enable("test"); allowLister.enable("test");
assert.deepEqual( assert.deepEqual(
whiteLister.getWhiteList(), allowLister.getAllowList(),
{ {
tagList: { tagList: {
custom: [], custom: [],
@ -46,10 +46,10 @@ test("whiteLister", (assert) => {
"Expecting a correct white list" "Expecting a correct white list"
); );
whiteLister.disable("test"); allowLister.disable("test");
assert.deepEqual( assert.deepEqual(
whiteLister.getWhiteList(), allowLister.getAllowList(),
{ {
tagList: {}, tagList: {},
attrList: {}, attrList: {},

View File

@ -6,6 +6,7 @@
//= require ./pretty-text/addon/emoji //= require ./pretty-text/addon/emoji
//= require ./pretty-text/addon/engines/discourse-markdown-it //= require ./pretty-text/addon/engines/discourse-markdown-it
//= require xss.min //= require xss.min
//= require ./pretty-text/addon/allow-lister
//= require ./pretty-text/addon/white-lister //= require ./pretty-text/addon/white-lister
//= require ./pretty-text/addon/sanitizer //= require ./pretty-text/addon/sanitizer
//= require ./pretty-text/addon/oneboxer //= require ./pretty-text/addon/oneboxer

View File

@ -0,0 +1,246 @@
import deprecated from "discourse-common/lib/deprecated";
// to match:
// abcd
// abcd[test]
// abcd[test=bob]
const ALLOWLIST_REGEX = /([^\[]+)(\[([^=]+)(=(.*))?\])?/;
export default class AllowLister {
constructor(options) {
this._enabled = { default: true };
this._allowedHrefSchemes = (options && options.allowedHrefSchemes) || [];
this._allowedIframes = (options && options.allowedIframes) || [];
this._rawFeatures = [["default", DEFAULT_LIST]];
this._cache = null;
if (options && options.features) {
Object.keys(options.features).forEach((f) => {
if (options.features[f]) {
this._enabled[f] = true;
}
});
}
}
allowListFeature(feature, info) {
this._rawFeatures.push([feature, info]);
}
whiteListFeature(feature, info) {
deprecated("`whiteListFeature` has been replaced with `allowListFeature`", {
since: "2.6.0.beta.4",
dropFrom: "2.7.0",
});
this.allowListFeature(feature, info);
}
disable(feature) {
this._enabled[feature] = false;
this._cache = null;
}
enable(feature) {
this._enabled[feature] = true;
this._cache = null;
}
_buildCache() {
const tagList = {};
const attrList = {};
const custom = [];
this._rawFeatures.forEach(([name, info]) => {
if (!this._enabled[name]) {
return;
}
if (info.custom) {
custom.push(info.custom);
return;
}
if (typeof info === "string") {
info = [info];
}
(info || []).forEach((tag) => {
const classes = tag.split(".");
const tagWithAttr = classes.shift();
const m = ALLOWLIST_REGEX.exec(tagWithAttr);
if (m) {
const [, tagname, , attr, , val] = m;
tagList[tagname] = [];
let attrs = (attrList[tagname] = attrList[tagname] || {});
if (classes.length > 0) {
attrs["class"] = (attrs["class"] || []).concat(classes);
}
if (attr) {
let attrInfo = (attrs[attr] = attrs[attr] || []);
if (val) {
attrInfo.push(val);
} else {
attrs[attr] = ["*"];
}
}
}
});
});
this._cache = { custom, allowList: { tagList, attrList } };
}
_ensureCache() {
if (!this._cache) {
this._buildCache();
}
}
getAllowList() {
this._ensureCache();
return this._cache.allowList;
}
getWhiteList() {
deprecated("`getWhiteList` has been replaced with `getAllowList`", {
since: "2.6.0.beta.4",
dropFrom: "2.7.0",
});
return this.getAllowList();
}
getCustom() {
this._ensureCache();
return this._cache.custom;
}
getAllowedHrefSchemes() {
return this._allowedHrefSchemes;
}
getAllowedIframes() {
return this._allowedIframes;
}
}
// Only add to `default` when you always want your allowlist to occur. In other words,
// don't change this for a plugin or a feature that can be disabled
export const DEFAULT_LIST = [
"a.attachment",
"a.hashtag",
"a.mention",
"a.mention-group",
"a.onebox",
`a.inline-onebox`,
`a.inline-onebox-loading`,
"a[data-bbcode]",
"a[name]",
"a[rel=nofollow]",
"a[rel=ugc]",
"a[target=_blank]",
"a[title]",
"abbr[title]",
"aside.quote",
"aside[data-*]",
"audio",
"audio[controls]",
"audio[preload]",
"b",
"big",
"blockquote",
"br",
"code",
"dd",
"del",
"div",
"div.quote-controls",
"div.title",
"div[align]",
"div[lang]",
"div[data-*]" /* This may seem a bit much but polls does
it anyway and this is needed for themes,
special code in sanitizer handles data-*
nothing exists for data-theme-* and we
don't want to slow sanitize for this case
*/,
"div[dir]",
"dl",
"dt",
"em",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"hr",
"i",
"iframe",
"iframe[frameborder]",
"iframe[height]",
"iframe[marginheight]",
"iframe[marginwidth]",
"iframe[width]",
"iframe[allowfullscreen]",
"img[alt]",
"img[height]",
"img[title]",
"img[width]",
"ins",
"kbd",
"li",
"ol",
"ol[start]",
"p",
"p[lang]",
"picture",
"pre",
"s",
"small",
"span[lang]",
"span.excerpt",
"div.excerpt",
"div.video-container",
"div.onebox-placeholder-container",
"span.placeholder-icon video",
"span.hashtag",
"span.mention",
"strike",
"strong",
"sub",
"sup",
"source[data-orig-src]",
"source[src]",
"source[srcset]",
"source[type]",
"track",
"track[default]",
"track[label]",
"track[kind]",
"track[src]",
"track[srclang]",
"ul",
"video",
"video[autoplay]",
"video[controls]",
"video[controlslist]",
"video[crossorigin]",
"video[height]",
"video[loop]",
"video[muted]",
"video[playsinline]",
"video[poster]",
"video[preload]",
"video[width]",
"ruby",
"ruby[lang]",
"rb",
"rb[lang]",
"rp",
"rt",
"rt[lang]",
];

View File

@ -1,6 +1,7 @@
import WhiteLister from "pretty-text/white-lister"; import AllowLister from "pretty-text/allow-lister";
import { sanitize } from "pretty-text/sanitizer"; import { sanitize } from "pretty-text/sanitizer";
import guid from "pretty-text/guid"; import guid from "pretty-text/guid";
import deprecated from "discourse-common/lib/deprecated";
export const ATTACHMENT_CSS_CLASS = "attachment"; export const ATTACHMENT_CSS_CLASS = "attachment";
@ -23,11 +24,19 @@ function createHelper(
optionCallbacks, optionCallbacks,
pluginCallbacks, pluginCallbacks,
getOptions, getOptions,
whiteListed allowListed
) { ) {
let helper = {}; let helper = {};
helper.markdownIt = true; helper.markdownIt = true;
helper.whiteList = (info) => whiteListed.push([featureName, info]); helper.allowList = (info) => allowListed.push([featureName, info]);
helper.whiteList = (info) => {
deprecated("`whiteList` has been replaced with `allowList`", {
since: "2.6.0.beta.4",
dropFrom: "2.7.0",
});
helper.allowList(info);
};
helper.registerInline = deprecate(featureName, "registerInline"); helper.registerInline = deprecate(featureName, "registerInline");
helper.replaceBlock = deprecate(featureName, "replaceBlock"); helper.replaceBlock = deprecate(featureName, "replaceBlock");
helper.addPreProcessor = deprecate(featureName, "addPreProcessor"); helper.addPreProcessor = deprecate(featureName, "addPreProcessor");
@ -296,7 +305,7 @@ export function setup(opts, siteSettings, state) {
const check = /discourse-markdown\/|markdown-it\//; const check = /discourse-markdown\/|markdown-it\//;
let features = []; let features = [];
let whiteListed = []; let allowListed = [];
Object.keys(require._eak_seen).forEach((entry) => { Object.keys(require._eak_seen).forEach((entry) => {
if (check.test(entry)) { if (check.test(entry)) {
@ -319,13 +328,13 @@ export function setup(opts, siteSettings, state) {
optionCallbacks, optionCallbacks,
pluginCallbacks, pluginCallbacks,
getOptions, getOptions,
whiteListed allowListed
) )
); );
}); });
Object.entries(state.whiteListed || {}).forEach((entry) => { Object.entries(state.allowListed || {}).forEach((entry) => {
whiteListed.push(entry); allowListed.push(entry);
}); });
optionCallbacks.forEach(([, callback]) => { optionCallbacks.forEach(([, callback]) => {
@ -393,14 +402,14 @@ export function setup(opts, siteSettings, state) {
opts.setup = true; opts.setup = true;
if (!opts.discourse.sanitizer || !opts.sanitizer) { if (!opts.discourse.sanitizer || !opts.sanitizer) {
const whiteLister = new WhiteLister(opts.discourse); const allowLister = new AllowLister(opts.discourse);
whiteListed.forEach(([feature, info]) => { allowListed.forEach(([feature, info]) => {
whiteLister.whiteListFeature(feature, info); allowLister.allowListFeature(feature, info);
}); });
opts.sanitizer = opts.discourse.sanitizer = !!opts.discourse.sanitize opts.sanitizer = opts.discourse.sanitizer = !!opts.discourse.sanitize
? (a) => sanitize(a, whiteLister) ? (a) => sanitize(a, allowLister)
: (a) => a; : (a) => a;
} }
} }

View File

@ -71,7 +71,7 @@ export function hrefAllowed(href, extraHrefMatchers) {
} }
} }
export function sanitize(text, whiteLister) { export function sanitize(text, allowLister) {
if (!text) { if (!text) {
return ""; return "";
} }
@ -79,9 +79,9 @@ export function sanitize(text, whiteLister) {
// Allow things like <3 and <_< // Allow things like <3 and <_<
text = text.replace(/<([^A-Za-z\/\!]|$)/g, "&lt;$1"); text = text.replace(/<([^A-Za-z\/\!]|$)/g, "&lt;$1");
const whiteList = whiteLister.getWhiteList(), const allowList = allowLister.getAllowList(),
allowedHrefSchemes = whiteLister.getAllowedHrefSchemes(), allowedHrefSchemes = allowLister.getAllowedHrefSchemes(),
allowedIframes = whiteLister.getAllowedIframes(); allowedIframes = allowLister.getAllowedIframes();
let extraHrefMatchers = null; let extraHrefMatchers = null;
if (allowedHrefSchemes && allowedHrefSchemes.length > 0) { if (allowedHrefSchemes && allowedHrefSchemes.length > 0) {
@ -94,12 +94,12 @@ export function sanitize(text, whiteLister) {
} }
let result = xss(text, { let result = xss(text, {
whiteList: whiteList.tagList, whiteList: allowList.tagList,
stripIgnoreTag: true, stripIgnoreTag: true,
stripIgnoreTagBody: ["script", "table"], stripIgnoreTagBody: ["script", "table"],
onIgnoreTagAttr(tag, name, value) { onIgnoreTagAttr(tag, name, value) {
const forTag = whiteList.attrList[tag]; const forTag = allowList.attrList[tag];
if (forTag) { if (forTag) {
const forAttr = forTag[name]; const forAttr = forTag[name];
if ( if (
@ -134,7 +134,7 @@ export function sanitize(text, whiteLister) {
return attr(name, value); return attr(name, value);
} }
const custom = whiteLister.getCustom(); const custom = allowLister.getCustom();
for (let i = 0; i < custom.length; i++) { for (let i = 0; i < custom.length; i++) {
const fn = custom[i]; const fn = custom[i];
if (fn(tag, name, value)) { if (fn(tag, name, value)) {

View File

@ -1,229 +1,15 @@
// to match: import deprecated from "discourse-common/lib/deprecated";
// abcd import AllowLister from "pretty-text/allow-lister";
// abcd[test] import { DEFAULT_LIST as NEW_DEFAULT_LIST } from "pretty-text/allow-lister";
// abcd[test=bob]
const WHITELIST_REGEX = /([^\[]+)(\[([^=]+)(=(.*))?\])?/;
export default class WhiteLister { export default class WhiteLister extends AllowLister {
constructor(options) { constructor(options) {
this._enabled = { default: true }; deprecated("`WhiteLister` has been replaced with `AllowLister`", {
this._allowedHrefSchemes = (options && options.allowedHrefSchemes) || []; since: "2.6.0.beta.4",
this._allowedIframes = (options && options.allowedIframes) || []; dropFrom: "2.7.0",
this._rawFeatures = [["default", DEFAULT_LIST]];
this._cache = null;
if (options && options.features) {
Object.keys(options.features).forEach((f) => {
if (options.features[f]) {
this._enabled[f] = true;
}
}); });
super(options);
} }
} }
whiteListFeature(feature, info) { export const DEFAULT_LIST = NEW_DEFAULT_LIST;
this._rawFeatures.push([feature, info]);
}
disable(feature) {
this._enabled[feature] = false;
this._cache = null;
}
enable(feature) {
this._enabled[feature] = true;
this._cache = null;
}
_buildCache() {
const tagList = {};
const attrList = {};
const custom = [];
this._rawFeatures.forEach(([name, info]) => {
if (!this._enabled[name]) {
return;
}
if (info.custom) {
custom.push(info.custom);
return;
}
if (typeof info === "string") {
info = [info];
}
(info || []).forEach((tag) => {
const classes = tag.split(".");
const tagWithAttr = classes.shift();
const m = WHITELIST_REGEX.exec(tagWithAttr);
if (m) {
const [, tagname, , attr, , val] = m;
tagList[tagname] = [];
let attrs = (attrList[tagname] = attrList[tagname] || {});
if (classes.length > 0) {
attrs["class"] = (attrs["class"] || []).concat(classes);
}
if (attr) {
let attrInfo = (attrs[attr] = attrs[attr] || []);
if (val) {
attrInfo.push(val);
} else {
attrs[attr] = ["*"];
}
}
}
});
});
this._cache = { custom, whiteList: { tagList, attrList } };
}
_ensureCache() {
if (!this._cache) {
this._buildCache();
}
}
getWhiteList() {
this._ensureCache();
return this._cache.whiteList;
}
getCustom() {
this._ensureCache();
return this._cache.custom;
}
getAllowedHrefSchemes() {
return this._allowedHrefSchemes;
}
getAllowedIframes() {
return this._allowedIframes;
}
}
// Only add to `default` when you always want your allowlist to occur. In other words,
// don't change this for a plugin or a feature that can be disabled
export const DEFAULT_LIST = [
"a.attachment",
"a.hashtag",
"a.mention",
"a.mention-group",
"a.onebox",
`a.inline-onebox`,
`a.inline-onebox-loading`,
"a[data-bbcode]",
"a[name]",
"a[rel=nofollow]",
"a[rel=ugc]",
"a[target=_blank]",
"a[title]",
"abbr[title]",
"aside.quote",
"aside[data-*]",
"audio",
"audio[controls]",
"audio[preload]",
"b",
"big",
"blockquote",
"br",
"code",
"dd",
"del",
"div",
"div.quote-controls",
"div.title",
"div[align]",
"div[lang]",
"div[data-*]" /* This may seem a bit much but polls does
it anyway and this is needed for themes,
special code in sanitizer handles data-*
nothing exists for data-theme-* and we
don't want to slow sanitize for this case
*/,
"div[dir]",
"dl",
"dt",
"em",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"hr",
"i",
"iframe",
"iframe[frameborder]",
"iframe[height]",
"iframe[marginheight]",
"iframe[marginwidth]",
"iframe[width]",
"iframe[allowfullscreen]",
"img[alt]",
"img[height]",
"img[title]",
"img[width]",
"ins",
"kbd",
"li",
"ol",
"ol[start]",
"p",
"p[lang]",
"picture",
"pre",
"s",
"small",
"span[lang]",
"span.excerpt",
"div.excerpt",
"div.video-container",
"div.onebox-placeholder-container",
"span.placeholder-icon video",
"span.hashtag",
"span.mention",
"strike",
"strong",
"sub",
"sup",
"source[data-orig-src]",
"source[src]",
"source[srcset]",
"source[type]",
"track",
"track[default]",
"track[label]",
"track[kind]",
"track[src]",
"track[srclang]",
"ul",
"video",
"video[autoplay]",
"video[controls]",
"video[controlslist]",
"video[crossorigin]",
"video[height]",
"video[loop]",
"video[muted]",
"video[playsinline]",
"video[poster]",
"video[preload]",
"video[width]",
"ruby",
"ruby[lang]",
"rb",
"rb[lang]",
"rp",
"rt",
"rt[lang]",
];

View File

@ -147,7 +147,7 @@ function processBBCode(state, silent) {
} }
export function setup(helper) { export function setup(helper) {
helper.whiteList([ helper.allowList([
"span.bbcode-b", "span.bbcode-b",
"span.bbcode-i", "span.bbcode-i",
"span.bbcode-u", "span.bbcode-u",

View File

@ -41,7 +41,7 @@ export function setup(helper) {
.concat(["auto", "nohighlight"]); .concat(["auto", "nohighlight"]);
}); });
helper.whiteList({ helper.allowList({
custom(tag, name, value) { custom(tag, name, value) {
if (tag === "code" && name === "class") { if (tag === "code" && name === "class") {
const m = /^lang\-(.+)$/.exec(value); const m = /^lang\-(.+)$/.exec(value);

View File

@ -68,5 +68,5 @@ export function setup(helper) {
md.block.bbcode.ruler.push("block-wrap", blockRule); md.block.bbcode.ruler.push("block-wrap", blockRule);
}); });
helper.whiteList([`div.${WRAP_CLASS}`, `span.${WRAP_CLASS}`, "span[data-*]"]); helper.allowList([`div.${WRAP_CLASS}`, `span.${WRAP_CLASS}`, "span[data-*]"]);
} }

View File

@ -339,7 +339,7 @@ export function setup(helper) {
); );
}); });
helper.whiteList([ helper.allowList([
"img[class=emoji]", "img[class=emoji]",
"img[class=emoji emoji-custom]", "img[class=emoji emoji-custom]",
"img[class=emoji emoji-custom only-emoji]", "img[class=emoji emoji-custom only-emoji]",

View File

@ -2,7 +2,7 @@ export function setup(helper) {
const opts = helper.getOptions(); const opts = helper.getOptions();
if (opts.previewing && opts.injectLineNumbersToPreview) { if (opts.previewing && opts.injectLineNumbersToPreview) {
helper.whiteList([ helper.allowList([
"p.preview-sync-line", "p.preview-sync-line",
"p[data-line-number]", "p[data-line-number]",
"h1.preview-sync-line", "h1.preview-sync-line",

View File

@ -168,8 +168,8 @@ export function setup(helper) {
md.block.bbcode.ruler.push("quotes", rule); md.block.bbcode.ruler.push("quotes", rule);
}); });
helper.whiteList(["img[class=avatar]"]); helper.allowList(["img[class=avatar]"]);
helper.whiteList({ helper.allowList({
custom(tag, name, value) { custom(tag, name, value) {
if (tag === "aside" && name === "class") { if (tag === "aside" && name === "class") {
return ( return (

View File

@ -71,7 +71,7 @@ export const priority = 1;
export function setup(helper) { export function setup(helper) {
const opts = helper.getOptions(); const opts = helper.getOptions();
if (opts.previewing) { if (opts.previewing) {
helper.whiteList([ helper.allowList([
"span.image-wrapper", "span.image-wrapper",
"span.button-wrapper", "span.button-wrapper",
"span[class=scale-btn]", "span[class=scale-btn]",

View File

@ -10,7 +10,7 @@ export function setup(helper) {
}); });
// we need a custom callback for style handling // we need a custom callback for style handling
helper.whiteList({ helper.allowList({
custom: function (tag, attr, val) { custom: function (tag, attr, val) {
if (tag !== "th" && tag !== "td") { if (tag !== "th" && tag !== "td") {
return false; return false;
@ -28,7 +28,7 @@ export function setup(helper) {
}, },
}); });
helper.whiteList([ helper.allowList([
"table", "table",
"tbody", "tbody",
"thead", "thead",

View File

@ -91,10 +91,10 @@ function rule(state) {
export function setup(helper) { export function setup(helper) {
const opts = helper.getOptions(); const opts = helper.getOptions();
if (opts.previewing) { if (opts.previewing) {
helper.whiteList(["img.resizable"]); helper.allowList(["img.resizable"]);
} }
helper.whiteList([ helper.allowList([
"img[data-orig-src]", "img[data-orig-src]",
"img[data-base62-sha1]", "img[data-base62-sha1]",
"a[data-orig-href]", "a[data-orig-href]",

View File

@ -90,6 +90,7 @@ module PrettyText
apply_es6_file(ctx, root_path, "discourse-common/addon/lib/get-url") apply_es6_file(ctx, root_path, "discourse-common/addon/lib/get-url")
apply_es6_file(ctx, root_path, "discourse-common/addon/lib/object") apply_es6_file(ctx, root_path, "discourse-common/addon/lib/object")
apply_es6_file(ctx, root_path, "discourse-common/addon/lib/deprecated")
apply_es6_file(ctx, root_path, "discourse/app/lib/to-markdown") apply_es6_file(ctx, root_path, "discourse/app/lib/to-markdown")
apply_es6_file(ctx, root_path, "discourse/app/lib/utilities") apply_es6_file(ctx, root_path, "discourse/app/lib/utilities")

View File

@ -17,7 +17,7 @@ const rule = {
}; };
export function setup(helper) { export function setup(helper) {
helper.whiteList([ helper.allowList([
"summary", "summary",
"summary[title]", "summary[title]",
"details", "details",

View File

@ -138,7 +138,7 @@ function closeBuffer(buffer, state, text) {
} }
export function setup(helper) { export function setup(helper) {
helper.whiteList([ helper.allowList([
"span.discourse-local-date", "span.discourse-local-date",
"span[data-*]", "span[data-*]",
"span[aria-label]", "span[aria-label]",

View File

@ -272,7 +272,7 @@ function newApiInit(helper) {
} }
export function setup(helper) { export function setup(helper) {
helper.whiteList([ helper.allowList([
"div.poll", "div.poll",
"div.poll-info", "div.poll-info",
"div.poll-container", "div.poll-container",