Preserve background-color when pasting external content in light mode

Closes #1964
This commit is contained in:
Damien Elmes 2022-08-19 12:31:26 +10:00
parent 92171e25e6
commit 0809812c1d
2 changed files with 71 additions and 53 deletions

View file

@ -9,4 +9,35 @@ describe("filterHTML", () => {
expect(filterHTML("", true, false)).toBe("");
expect(filterHTML("", false, false)).toBe("");
});
test("internal filtering", () => {
// font-size is filtered, weight is not
expect(
filterHTML(
'<div style="font-weight: bold; font-size: 10px;"></div>',
true,
true,
),
).toBe('<div style="font-weight: bold;"></div>');
});
test("background color", () => {
// transparent is stripped, other colors are not
expect(
filterHTML(
'<span style="background-color: transparent;"></span>',
false,
true,
),
).toBe('<span style=""></span>');
expect(
filterHTML('<span style="background-color: blue;"></span>', false, true),
).toBe('<span style="background-color: blue;"></span>');
// except if extended mode is off
expect(
filterHTML('<span style="background-color: blue;">x</span>', false, false),
).toBe("x");
// or if it's an internal paste
expect(
filterHTML('<span style="background-color: blue;"></span>', true, true),
).toBe('<span style=""></span>');
});
});

View file

@ -1,63 +1,50 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
interface AllowPropertiesBlockValues {
[property: string]: string[];
}
type BlockProperties = string[];
/// Keep property if true.
type StylingPredicate = (property: string, value: string) => boolean;
const stylingNightMode: AllowPropertiesBlockValues = {
"font-weight": [],
"font-style": [],
"text-decoration-line": [],
};
const keep = (_key: string, _value: string) => true;
const discard = (_key: string, _value: string) => false;
const stylingLightMode: AllowPropertiesBlockValues = {
color: [],
"background-color": ["transparent"],
...stylingNightMode,
};
const stylingInternal: BlockProperties = [
"background-color",
"font-size",
"font-family",
"width",
"height",
"max-width",
"max-height",
];
const allowPropertiesBlockValues =
(allowBlock: AllowPropertiesBlockValues): StylingPredicate =>
(property: string, value: string): boolean =>
Object.prototype.hasOwnProperty.call(allowBlock, property) &&
!allowBlock[property].includes(value);
const blockProperties =
(block: BlockProperties): StylingPredicate =>
(property: string): boolean =>
!block.includes(property);
const filterStyling =
(predicate: (property: string, value: string) => boolean) =>
(element: HTMLElement): void => {
for (const property of [...element.style]) {
const value = element.style.getPropertyValue(property);
if (!predicate(property, value)) {
element.style.removeProperty(property);
/// Return a function that filters out certain styles.
/// - If the style is listed in `exceptions`, the provided predicate is used.
/// - If the style is not listed, the default predicate is used instead.
function filterStyling(
defaultPredicate: StylingPredicate,
exceptions: Record<string, StylingPredicate>,
): (element: HTMLElement) => void {
return (element: HTMLElement): void => {
// jsdom does not support @@iterator, so manually iterate
for (let i = 0; i < element.style.length; i++) {
const key = element.style.item(i);
const value = element.style.getPropertyValue(key);
const predicate = exceptions[key] ?? defaultPredicate;
if (!predicate(key, value)) {
element.style.removeProperty(key);
}
}
};
}
export const filterStylingNightMode = filterStyling(
allowPropertiesBlockValues(stylingNightMode),
);
export const filterStylingLightMode = filterStyling(
allowPropertiesBlockValues(stylingLightMode),
);
export const filterStylingInternal = filterStyling(blockProperties(stylingInternal));
const nightModeExceptions = {
"font-weight": keep,
"font-style": keep,
"text-decoration-line": keep,
};
export const filterStylingNightMode = filterStyling(discard, nightModeExceptions);
export const filterStylingLightMode = filterStyling(discard, {
color: keep,
"background-color": (_key: string, value: string) => value != "transparent",
...nightModeExceptions,
});
export const filterStylingInternal = filterStyling(keep, {
"background-color": discard,
"font-size": discard,
"font-family": discard,
width: discard,
height: discard,
"max-width": discard,
"max-height": discard,
});