Move most of tags allowed to its own file

This commit is contained in:
Henrik Giesel 2021-03-24 21:04:12 +01:00 committed by Damien Elmes
parent 592d73c344
commit 1d4d7fabec
4 changed files with 126 additions and 102 deletions

View File

@ -77,3 +77,7 @@ export function caretToEnd(currentField: EditingArea): void {
selection.removeAllRanges();
selection.addRange(range);
}
export function isNightMode(): boolean {
return document.body.classList.contains("nightMode");
}

View File

@ -2,92 +2,9 @@
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
import { nodeIsElement } from "./helpers";
import { tagsAllowedBasic, tagsAllowedExtended } from "./htmlFilterTagsAllowed";
const allowedTagsBasic = {};
const allowedTagsExtended = {};
let TAGS_WITHOUT_ATTRS = ["P", "DIV", "BR", "SUB", "SUP"];
for (const tag of TAGS_WITHOUT_ATTRS) {
allowedTagsBasic[tag] = { attrs: [] };
}
TAGS_WITHOUT_ATTRS = [
"B",
"BLOCKQUOTE",
"CODE",
"DD",
"DL",
"DT",
"EM",
"H1",
"H2",
"H3",
"I",
"LI",
"OL",
"PRE",
"RP",
"RT",
"RUBY",
"STRONG",
"TABLE",
"U",
"UL",
];
for (const tag of TAGS_WITHOUT_ATTRS) {
allowedTagsExtended[tag] = { attrs: [] };
}
allowedTagsBasic["IMG"] = { attrs: ["SRC"] };
allowedTagsExtended["A"] = { attrs: ["HREF"] };
allowedTagsExtended["TR"] = { attrs: ["ROWSPAN"] };
allowedTagsExtended["TD"] = { attrs: ["COLSPAN", "ROWSPAN"] };
allowedTagsExtended["TH"] = { attrs: ["COLSPAN", "ROWSPAN"] };
allowedTagsExtended["FONT"] = { attrs: ["COLOR"] };
const allowedStyling = {
color: true,
"background-color": true,
"font-weight": true,
"font-style": true,
"text-decoration-line": true,
};
function isNightMode(): boolean {
return document.body.classList.contains("nightMode");
}
function filterExternalSpan(elem: HTMLElement): void {
// filter out attributes
for (const attr of [...elem.attributes]) {
const attrName = attr.name.toUpperCase();
if (attrName !== "STYLE") {
elem.removeAttributeNode(attr);
}
}
// filter styling
for (const name of [...elem.style]) {
const value = elem.style.getPropertyValue(name);
if (
!allowedStyling.hasOwnProperty(name) ||
// google docs adds this unnecessarily
(name === "background-color" && value === "transparent") ||
// ignore coloured text in night mode for now
(isNightMode() && (name === "background-color" || name === "color"))
) {
elem.style.removeProperty(name);
}
}
}
allowedTagsExtended["SPAN"] = filterExternalSpan;
// add basic tags to extended
Object.assign(allowedTagsExtended, allowedTagsBasic);
////////////////////// //////////////////// ////////////////////
function isHTMLElement(elem: Element): elem is HTMLElement {
return elem instanceof HTMLElement;
@ -127,29 +44,16 @@ function filterNode(node: Node, extendedMode: boolean): void {
return;
}
const tag = extendedMode
? allowedTagsExtended[node.tagName]
: allowedTagsBasic[node.tagName];
const tagsAllowed = extendedMode ? tagsAllowedExtended : tagsAllowedBasic;
if (!tag) {
if (tagsAllowed.hasOwnProperty(node.tagName)) {
tagsAllowed[node.tagName](node);
} else {
if (!node.innerHTML || node.tagName === "TITLE") {
node.parentNode.removeChild(node);
} else {
node.outerHTML = node.innerHTML;
}
} else {
if (typeof tag === "function") {
// filtering function provided
tag(node);
} else {
// allowed, filter out attributes
for (const attr of [...node.attributes]) {
const attrName = attr.name.toUpperCase();
if (tag.attrs.indexOf(attrName) === -1) {
node.removeAttributeNode(attr);
}
}
}
}
}

View File

@ -0,0 +1,44 @@
import { isNightMode } from "./helpers";
/* keys are allowed properties, values are blocked values */
const stylingAllowListNightMode = {
"font-weight": [],
"font-style": [],
"text-decoration-line": [],
};
const stylingAllowList = {
color: [],
"background-color": ["transparent"],
...stylingAllowListNightMode,
};
function isStylingAllowed(property: string, value: string): boolean {
const allowList = isNightMode() ? stylingAllowListNightMode : stylingAllowList;
return allowList.hasOwnProperty(property) && !allowList[property].includes(value);
}
const allowedAttrs = ["STYLE"];
export function filterSpan(element: Element): void {
// filter out attributes
for (const attr of [...element.attributes]) {
const attrName = attr.name.toUpperCase();
if (!allowedAttrs.includes(attrName)) {
element.removeAttributeNode(attr);
}
}
// filter styling
const elementStyle = (element as HTMLSpanElement).style;
for (const property of [...elementStyle]) {
const value = elementStyle.getPropertyValue(name);
if (!isStylingAllowed(property, value)) {
elementStyle.removeProperty(property);
}
}
}

View File

@ -0,0 +1,72 @@
import { filterSpan } from "./htmlFilterSpan";
type FilterMethod = (element: Element) => void;
interface TagsAllowed {
[key: string]: FilterMethod;
}
function filterOutAttributes(
attributePredicate: (attributeName: string) => boolean,
element: Element
): void {
for (const attr of [...element.attributes]) {
const attrName = attr.name.toUpperCase();
if (attributePredicate(attrName)) {
element.removeAttributeNode(attr);
}
}
}
function blockExcept(attrs: string[]): FilterMethod {
return (element: Element) =>
filterOutAttributes(
(attributeName: string) => !attrs.includes(attributeName),
element
);
}
function blockAll(element: Element): void {
filterOutAttributes(() => true, element);
}
export const tagsAllowedBasic: TagsAllowed = {
BR: blockAll,
IMG: blockExcept(["SRC"]),
DIV: blockAll,
P: blockAll,
SUB: blockAll,
SUP: blockAll,
};
export const tagsAllowedExtended: TagsAllowed = {
...tagsAllowedBasic,
A: blockExcept(["HREF"]),
B: blockAll,
BLOCKQUOTE: blockAll,
CODE: blockAll,
DD: blockAll,
DL: blockAll,
DT: blockAll,
EM: blockAll,
FONT: blockExcept(["COLOR"]),
H1: blockAll,
H2: blockAll,
H3: blockAll,
I: blockAll,
LI: blockAll,
OL: blockAll,
PRE: blockAll,
RP: blockAll,
RT: blockAll,
RUBY: blockAll,
SPAN: filterSpan,
STRONG: blockAll,
TABLE: blockAll,
TD: blockExcept(["COLSPAN", "ROWSPAN"]),
TH: blockExcept(["COLSPAN", "ROWSPAN"]),
TR: blockExcept(["ROWSPAN"]),
U: blockAll,
UL: blockAll,
};