feat(client/render_note): warning for disabled render note

This commit is contained in:
Elian Doran 2026-02-15 16:54:34 +02:00
parent 197b769838
commit ec8b0a3801
No known key found for this signature in database
5 changed files with 50 additions and 14 deletions

View File

@ -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);
}
/**

View File

@ -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",

View File

@ -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 <p>Hello world.</p>;
@ -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 <DisabledRender {...props} />;
}
if (!renderNote) {
return <SetupRenderContent {...props} />;
@ -76,11 +83,28 @@ function RenderContent({ note, noteContext, ntxId }: TypeWidgetProps) {
return <div ref={contentRef} className="note-detail-render-content" />;
}
function DisabledRender({ note }: TypeWidgetProps) {
return (
<SetupForm
icon="bx bx-extension"
inAppHelpPage={HELP_PAGE}
>
<p>{t("render.disabled_description")}</p>
<Button
text={t("render.disabled_button_enable")}
icon="bx bx-check-shield"
onClick={() => attributes.toggleDangerousAttribute(note, "relation", "renderNote", true)}
primary
/>
</SetupForm>
);
}
function SetupRenderContent({ note }: TypeWidgetProps) {
return (
<SetupForm
icon="bx bx-extension"
inAppHelpPage="HcABDtFCkbFN"
inAppHelpPage={HELP_PAGE}
>
<FormGroup name="render-target-note" label={t("render.setup_title")}>
<NoteAutocomplete noteIdChanged={noteId => {

View File

@ -1,6 +1,8 @@
.setup-form {
height: 100%;
display: flex;
max-width: 600px;
margin: auto;
flex-direction: column;
justify-content: center;
padding-inline: 40px;

View File

@ -80,6 +80,7 @@ type Relations = [
// Active content
"renderNote",
"disabled:renderNote",
// Launcher-specific
"target",