diff --git a/apps/client/src/services/attributes.ts b/apps/client/src/services/attributes.ts index fd8a8b0f10..0ead073567 100644 --- a/apps/client/src/services/attributes.ts +++ b/apps/client/src/services/attributes.ts @@ -173,6 +173,8 @@ function isAffecting(attrRow: AttributeRow, affectedNote: FNote | null | undefin * * Note that this work for non-dangerous attributes as well. * + * If there are multiple attributes with the same name, all of them will be toggled at the same time. + * * @param note the note whose attribute to change. * @param type the type of dangerous attribute (label or relation). * @param name the name of the dangerous attribute. @@ -180,19 +182,24 @@ function isAffecting(attrRow: AttributeRow, affectedNote: FNote | null | undefin * @returns a promise that will resolve when the request to the server completes. */ async function toggleDangerousAttribute(note: FNote, type: "label" | "relation", name: string, willEnable: boolean) { - const attr = note.getOwnedAttribute(type, name) ?? note.getOwnedAttribute(type, `disabled:${name}`); - if (!attr) return; - const baseName = getNameWithoutDangerousPrefix(attr.name); - const newName = willEnable ? baseName : `disabled:${baseName}`; - if (newName === attr.name) return; + const attrs = [ + ...note.getOwnedAttributes(type, name), + ...note.getOwnedAttributes(type, `disabled:${name}`) + ]; - // We are adding and removing afterwards to avoid a flicker (because for a moment there would be no active content attribute anymore) because the operations are done in sequence and not atomically. - if (attr.type === "label") { - await setLabel(note.noteId, newName, attr.value); - } else { - await setRelation(note.noteId, newName, attr.value); + for (const attr of attrs) { + const baseName = getNameWithoutDangerousPrefix(attr.name); + const newName = willEnable ? baseName : `disabled:${baseName}`; + if (newName === attr.name) return; + + // We are adding and removing afterwards to avoid a flicker (because for a moment there would be no active content attribute anymore) because the operations are done in sequence and not atomically. + if (attr.type === "label") { + await setLabel(note.noteId, newName, attr.value); + } else { + await setRelation(note.noteId, newName, attr.value); + } + await removeAttributeById(note.noteId, attr.attributeId); } - await removeAttributeById(note.noteId, attr.attributeId); } /** diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index b90f79da4b..d2892456de 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1070,7 +1070,9 @@ "setup_title": "Display custom HTML or Preact JSX inside this note", "setup_create_sample_preact": "Create sample note with Preact", "setup_create_sample_html": "Create sample note with HTML", - "setup_sample_created": "A sample note was created as a child note." + "setup_sample_created": "A sample note was created as a child note.", + "disabled_description": "This render notes comes from an external source. To protect you from malicious content, it is not enabled by default. Make sure you trust the source before enabling it.", + "disabled_button_enable": "Enable render note" }, "web_view_setup": { "title": "Create a live view of a webpage directly into Trilium", diff --git a/apps/client/src/widgets/type_widgets/Render.tsx b/apps/client/src/widgets/type_widgets/Render.tsx index 88fc75cd75..563a2342c6 100644 --- a/apps/client/src/widgets/type_widgets/Render.tsx +++ b/apps/client/src/widgets/type_widgets/Render.tsx @@ -8,7 +8,7 @@ import { t } from "../../services/i18n"; import note_create from "../../services/note_create"; import render from "../../services/render"; import toast from "../../services/toast"; -import { SplitButton } from "../react/Button"; +import Button, { SplitButton } from "../react/Button"; import FormGroup from "../react/FormGroup"; import { FormListItem } from "../react/FormList"; import { useNoteRelation, useTriliumEvent } from "../react/hooks"; @@ -17,6 +17,8 @@ import { refToJQuerySelector } from "../react/react_utils"; import SetupForm from "./helpers/SetupForm"; import { TypeWidgetProps } from "./type_widget"; +const HELP_PAGE = "HcABDtFCkbFN"; + const PREACT_SAMPLE = /*js*/`\ export default function() { return

Hello world.

; @@ -30,6 +32,11 @@ const HTML_SAMPLE = /*html*/`\ export default function Render(props: TypeWidgetProps) { const { note } = props; const [ renderNote ] = useNoteRelation(note, "renderNote"); + const [ disabledRenderNote ] = useNoteRelation(note, "disabled:renderNote"); + + if (disabledRenderNote) { + return ; + } if (!renderNote) { return ; @@ -76,11 +83,28 @@ function RenderContent({ note, noteContext, ntxId }: TypeWidgetProps) { return
; } +function DisabledRender({ note }: TypeWidgetProps) { + return ( + +

{t("render.disabled_description")}

+