From 95af9018083e82cbc16a52900a94e6d85079dfff Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 18 Aug 2025 18:07:58 +0300 Subject: [PATCH] feat(react/settings): port HTML import tags --- .../widgets/type_widgets/options/other.tsx | 43 +++++ .../options/other/html_import_tags.ts | 176 ------------------ apps/server/src/services/html_sanitizer.ts | 102 +--------- apps/server/src/services/options_init.ts | 5 +- packages/commons/src/index.ts | 1 + packages/commons/src/lib/shared_constants.ts | 98 ++++++++++ 6 files changed, 146 insertions(+), 279 deletions(-) delete mode 100644 apps/client/src/widgets/type_widgets/options/other/html_import_tags.ts create mode 100644 packages/commons/src/lib/shared_constants.ts diff --git a/apps/client/src/widgets/type_widgets/options/other.tsx b/apps/client/src/widgets/type_widgets/options/other.tsx index a8bb605c9..8de6866ea 100644 --- a/apps/client/src/widgets/type_widgets/options/other.tsx +++ b/apps/client/src/widgets/type_widgets/options/other.tsx @@ -6,6 +6,9 @@ import Button from "../../react/Button"; import FormText from "../../react/FormText"; import OptionsSection from "./components/OptionsSection"; import TimeSelector from "./components/TimeSelector"; +import { useMemo } from "preact/hooks"; +import { useTriliumOptionJson } from "../../react/hooks"; +import { SANITIZER_DEFAULT_ALLOWED_TAGS } from "@triliumnext/commons"; export default function OtherSettings() { return ( @@ -13,6 +16,7 @@ export default function OtherSettings() { + ) } @@ -80,4 +84,43 @@ function RevisionSnapshotInterval() { /> ) +} + +function HtmlImportTags() { + const [ allowedHtmlTags, setAllowedHtmlTags ] = useTriliumOptionJson("allowedHtmlTags"); + + const parsedValue = useMemo(() => { + return allowedHtmlTags.join(" "); + }, allowedHtmlTags); + + return ( + + {t("import.html_import_tags.description")} + + - -
- -
-`; - -export default class HtmlImportTagsOptions extends OptionsWidget { - - private $allowedTags!: JQuery; - private $resetButton!: JQuery; - - doRender() { - this.$widget = $(TPL); - this.contentSized(); - - this.$allowedTags = this.$widget.find(".allowed-html-tags"); - this.$resetButton = this.$widget.find(".reset-to-default"); - - this.$allowedTags.on("change", () => this.saveTags()); - this.$resetButton.on("click", () => this.resetToDefault()); - - // Load initial tags - this.refresh(); - } - - async optionsLoaded(options: OptionMap) { - try { - if (options.allowedHtmlTags) { - const tags = JSON.parse(options.allowedHtmlTags); - this.$allowedTags.val(tags.join(" ")); - } else { - // If no tags are set, show the defaults - this.$allowedTags.val(DEFAULT_ALLOWED_TAGS.join(" ")); - } - } catch (e) { - console.error("Could not load HTML tags:", e); - // On error, show the defaults - this.$allowedTags.val(DEFAULT_ALLOWED_TAGS.join(" ")); - } - } - - async saveTags() { - const tagsText = String(this.$allowedTags.val()) || ""; - const tags = tagsText - .split(/[\n,\s]+/) // Split on newlines, commas, or spaces - .map((tag) => tag.trim()) - .filter((tag) => tag.length > 0); - - await this.updateOption("allowedHtmlTags", JSON.stringify(tags)); - } - - async resetToDefault() { - this.$allowedTags.val(DEFAULT_ALLOWED_TAGS.join("\n")); // Use actual newline - await this.saveTags(); - } -} diff --git a/apps/server/src/services/html_sanitizer.ts b/apps/server/src/services/html_sanitizer.ts index da707b7c3..79d80033f 100644 --- a/apps/server/src/services/html_sanitizer.ts +++ b/apps/server/src/services/html_sanitizer.ts @@ -1,6 +1,7 @@ import sanitizeHtml from "sanitize-html"; import { sanitizeUrl } from "@braintree/sanitize-url"; import optionService from "./options.js"; +import { SANITIZER_DEFAULT_ALLOWED_TAGS } from "@triliumnext/commons"; // Be consistent with `ALLOWED_PROTOCOLS` in `src\public\app\services\link.js` // TODO: Deduplicate with client once we can. @@ -12,105 +13,6 @@ export const ALLOWED_PROTOCOLS = [ 'mid' ]; -// Default list of allowed HTML tags -export const DEFAULT_ALLOWED_TAGS = [ - "h1", - "h2", - "h3", - "h4", - "h5", - "h6", - "blockquote", - "p", - "a", - "ul", - "ol", - "li", - "b", - "i", - "strong", - "em", - "strike", - "s", - "del", - "abbr", - "code", - "hr", - "br", - "div", - "table", - "thead", - "caption", - "tbody", - "tfoot", - "tr", - "th", - "td", - "pre", - "section", - "img", - "figure", - "figcaption", - "span", - "label", - "input", - "details", - "summary", - "address", - "aside", - "footer", - "header", - "hgroup", - "main", - "nav", - "dl", - "dt", - "menu", - "bdi", - "bdo", - "dfn", - "kbd", - "mark", - "q", - "time", - "var", - "wbr", - "area", - "map", - "track", - "video", - "audio", - "picture", - "del", - "ins", - "en-media", // for ENEX import - // Additional tags (https://github.com/TriliumNext/Trilium/issues/567) - "acronym", - "article", - "big", - "button", - "cite", - "col", - "colgroup", - "data", - "dd", - "fieldset", - "form", - "legend", - "meter", - "noscript", - "option", - "progress", - "rp", - "samp", - "small", - "sub", - "sup", - "template", - "textarea", - "tt" -] as const; - // intended mainly as protection against XSS via import // secondarily, it (partly) protects against "CSS takeover" // sanitize also note titles, label values etc. - there are so many usages which make it difficult @@ -138,7 +40,7 @@ function sanitize(dirtyHtml: string) { allowedTags = JSON.parse(optionService.getOption("allowedHtmlTags")); } catch (e) { // Fallback to default list if option doesn't exist or is invalid - allowedTags = DEFAULT_ALLOWED_TAGS; + allowedTags = SANITIZER_DEFAULT_ALLOWED_TAGS; } const colorRegex = [/^#(0x)?[0-9a-f]+$/i, /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/, /^hsl\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*\)$/]; diff --git a/apps/server/src/services/options_init.ts b/apps/server/src/services/options_init.ts index dc2b4b268..91fcf25ca 100644 --- a/apps/server/src/services/options_init.ts +++ b/apps/server/src/services/options_init.ts @@ -4,8 +4,7 @@ import { randomSecureToken, isWindows } from "./utils.js"; import log from "./log.js"; import dateUtils from "./date_utils.js"; import keyboardActions from "./keyboard_actions.js"; -import type { KeyboardShortcutWithRequiredActionName, OptionMap, OptionNames } from "@triliumnext/commons"; -import { DEFAULT_ALLOWED_TAGS } from "./html_sanitizer.js"; +import { SANITIZER_DEFAULT_ALLOWED_TAGS, type KeyboardShortcutWithRequiredActionName, type OptionMap, type OptionNames } from "@triliumnext/commons"; function initDocumentOptions() { optionService.createOption("documentId", randomSecureToken(16), false); @@ -187,7 +186,7 @@ const defaultOptions: DefaultOption[] = [ { name: "backgroundEffects", value: "true", isSynced: false }, { name: "allowedHtmlTags", - value: JSON.stringify(DEFAULT_ALLOWED_TAGS), + value: JSON.stringify(SANITIZER_DEFAULT_ALLOWED_TAGS), isSynced: true }, diff --git a/packages/commons/src/index.ts b/packages/commons/src/index.ts index 151924c8f..432990bc0 100644 --- a/packages/commons/src/index.ts +++ b/packages/commons/src/index.ts @@ -7,3 +7,4 @@ export * from "./lib/test-utils.js"; export * from "./lib/mime_type.js"; export * from "./lib/bulk_actions.js"; export * from "./lib/server_api.js"; +export * from "./lib/shared_constants.js"; diff --git a/packages/commons/src/lib/shared_constants.ts b/packages/commons/src/lib/shared_constants.ts new file mode 100644 index 000000000..00b179d33 --- /dev/null +++ b/packages/commons/src/lib/shared_constants.ts @@ -0,0 +1,98 @@ +// Default list of allowed HTML tags +export const SANITIZER_DEFAULT_ALLOWED_TAGS = [ + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "blockquote", + "p", + "a", + "ul", + "ol", + "li", + "b", + "i", + "strong", + "em", + "strike", + "s", + "del", + "abbr", + "code", + "hr", + "br", + "div", + "table", + "thead", + "caption", + "tbody", + "tfoot", + "tr", + "th", + "td", + "pre", + "section", + "img", + "figure", + "figcaption", + "span", + "label", + "input", + "details", + "summary", + "address", + "aside", + "footer", + "header", + "hgroup", + "main", + "nav", + "dl", + "dt", + "menu", + "bdi", + "bdo", + "dfn", + "kbd", + "mark", + "q", + "time", + "var", + "wbr", + "area", + "map", + "track", + "video", + "audio", + "picture", + "del", + "ins", + "en-media", // for ENEX import + // Additional tags (https://github.com/TriliumNext/Trilium/issues/567) + "acronym", + "article", + "big", + "button", + "cite", + "col", + "colgroup", + "data", + "dd", + "fieldset", + "form", + "legend", + "meter", + "noscript", + "option", + "progress", + "rp", + "samp", + "small", + "sub", + "sup", + "template", + "textarea", + "tt" +] as const;