mirror of
https://github.com/zadam/trilium.git
synced 2025-12-22 15:24:24 +01:00
feat(script/jsx): compile JSX on server side
This commit is contained in:
parent
883e32f5c9
commit
3619c0c3e4
@ -30,7 +30,8 @@
|
||||
"dependencies": {
|
||||
"better-sqlite3": "12.5.0",
|
||||
"html-to-text": "9.0.5",
|
||||
"node-html-parser": "7.0.1"
|
||||
"node-html-parser": "7.0.1",
|
||||
"sucrase": "3.35.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "0.71.2",
|
||||
|
||||
@ -1,26 +1,25 @@
|
||||
"use strict";
|
||||
|
||||
import protectedSessionService from "../../services/protected_session.js";
|
||||
import log from "../../services/log.js";
|
||||
import sql from "../../services/sql.js";
|
||||
import optionService from "../../services/options.js";
|
||||
import eraseService from "../../services/erase.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import dateUtils from "../../services/date_utils.js";
|
||||
import AbstractBeccaEntity from "./abstract_becca_entity.js";
|
||||
import BRevision from "./brevision.js";
|
||||
import BAttachment from "./battachment.js";
|
||||
import TaskContext from "../../services/task_context.js";
|
||||
import { dayjs } from "@triliumnext/commons";
|
||||
import eventService from "../../services/events.js";
|
||||
import type { AttachmentRow, AttributeType, CloneResponse, NoteRow, NoteType, RevisionRow } from "@triliumnext/commons";
|
||||
import type BBranch from "./bbranch.js";
|
||||
import BAttribute from "./battribute.js";
|
||||
import type { NotePojo } from "../becca-interface.js";
|
||||
import searchService from "../../services/search/services/search.js";
|
||||
import { dayjs } from "@triliumnext/commons";
|
||||
|
||||
import cloningService from "../../services/cloning.js";
|
||||
import noteService from "../../services/notes.js";
|
||||
import dateUtils from "../../services/date_utils.js";
|
||||
import eraseService from "../../services/erase.js";
|
||||
import eventService from "../../services/events.js";
|
||||
import handlers from "../../services/handlers.js";
|
||||
import log from "../../services/log.js";
|
||||
import noteService from "../../services/notes.js";
|
||||
import optionService from "../../services/options.js";
|
||||
import protectedSessionService from "../../services/protected_session.js";
|
||||
import searchService from "../../services/search/services/search.js";
|
||||
import sql from "../../services/sql.js";
|
||||
import TaskContext from "../../services/task_context.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import type { NotePojo } from "../becca-interface.js";
|
||||
import AbstractBeccaEntity from "./abstract_becca_entity.js";
|
||||
import BAttachment from "./battachment.js";
|
||||
import BAttribute from "./battribute.js";
|
||||
import type BBranch from "./bbranch.js";
|
||||
import BRevision from "./brevision.js";
|
||||
|
||||
const LABEL = "label";
|
||||
const RELATION = "relation";
|
||||
@ -296,6 +295,10 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
);
|
||||
}
|
||||
|
||||
isJsx() {
|
||||
return (this.type === "code" && this.mime === "text/jsx");
|
||||
}
|
||||
|
||||
/** @returns true if this note is HTML */
|
||||
isHtml() {
|
||||
return ["code", "file", "render"].includes(this.type) && this.mime === "text/html";
|
||||
@ -355,9 +358,9 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
return this.__attributeCache.filter((attr) => attr.type === type);
|
||||
} else if (name) {
|
||||
return this.__attributeCache.filter((attr) => attr.name === name);
|
||||
} else {
|
||||
return this.__attributeCache;
|
||||
}
|
||||
return this.__attributeCache;
|
||||
|
||||
}
|
||||
|
||||
private __ensureAttributeCacheIsAvailable() {
|
||||
@ -692,9 +695,9 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
return this.ownedAttributes.filter((attr) => attr.type === type);
|
||||
} else if (name) {
|
||||
return this.ownedAttributes.filter((attr) => attr.name === name);
|
||||
} else {
|
||||
return this.ownedAttributes;
|
||||
}
|
||||
return this.ownedAttributes;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -745,9 +748,9 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
return 1;
|
||||
} else if (a.parentNote?.isHiddenCompletely()) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
|
||||
});
|
||||
|
||||
this.parents = this.parentBranches.map((branch) => branch.parentNote).filter((note) => !!note) as BNote[];
|
||||
@ -1178,9 +1181,9 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
return a.isArchived ? 1 : -1;
|
||||
} else if (a.isHidden !== b.isHidden) {
|
||||
return a.isHidden ? 1 : -1;
|
||||
} else {
|
||||
return a.notePath.length - b.notePath.length;
|
||||
}
|
||||
return a.notePath.length - b.notePath.length;
|
||||
|
||||
});
|
||||
|
||||
return notePaths;
|
||||
@ -1257,9 +1260,9 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
} else {
|
||||
new BAttribute({
|
||||
noteId: this.noteId,
|
||||
type: type,
|
||||
name: name,
|
||||
value: value
|
||||
type,
|
||||
name,
|
||||
value
|
||||
}).save();
|
||||
}
|
||||
}
|
||||
@ -1292,11 +1295,11 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
addAttribute(type: AttributeType, name: string, value: string = "", isInheritable: boolean = false, position: number | null = null): BAttribute {
|
||||
return new BAttribute({
|
||||
noteId: this.noteId,
|
||||
type: type,
|
||||
name: name,
|
||||
value: value,
|
||||
isInheritable: isInheritable,
|
||||
position: position
|
||||
type,
|
||||
name,
|
||||
value,
|
||||
isInheritable,
|
||||
position
|
||||
}).save();
|
||||
}
|
||||
|
||||
@ -1470,10 +1473,10 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
role: "image",
|
||||
mime: this.mime,
|
||||
title: this.title,
|
||||
content: content
|
||||
content
|
||||
});
|
||||
|
||||
let parentContent = parentNote.getContent();
|
||||
const parentContent = parentNote.getContent();
|
||||
|
||||
const oldNoteUrl = `api/images/${this.noteId}/`;
|
||||
const newAttachmentUrl = `api/attachments/${attachment.attachmentId}/image/`;
|
||||
@ -1712,14 +1715,14 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
} else if (this.type === "text") {
|
||||
if (this.isFolder()) {
|
||||
return "bx bx-folder";
|
||||
} else {
|
||||
return "bx bx-note";
|
||||
}
|
||||
return "bx bx-note";
|
||||
|
||||
} else if (this.type === "code" && this.mime.startsWith("text/x-sql")) {
|
||||
return "bx bx-data";
|
||||
} else {
|
||||
return NOTE_TYPE_ICONS[this.type];
|
||||
}
|
||||
return NOTE_TYPE_ICONS[this.type];
|
||||
|
||||
}
|
||||
|
||||
// TODO: Deduplicate with fnote
|
||||
@ -1729,7 +1732,7 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
|
||||
// TODO: Deduplicate with fnote
|
||||
getFilteredChildBranches() {
|
||||
let childBranches = this.getChildBranches();
|
||||
const childBranches = this.getChildBranches();
|
||||
|
||||
if (!childBranches) {
|
||||
console.error(`No children for '${this.noteId}'. This shouldn't happen.`);
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import ScriptContext from "./script_context.js";
|
||||
import cls from "./cls.js";
|
||||
import log from "./log.js";
|
||||
import { transform } from "sucrase";
|
||||
|
||||
import becca from "../becca/becca.js";
|
||||
import type BNote from "../becca/entities/bnote.js";
|
||||
import type { ApiParams } from "./backend_script_api_interface.js";
|
||||
import cls from "./cls.js";
|
||||
import log from "./log.js";
|
||||
import ScriptContext from "./script_context.js";
|
||||
|
||||
export interface Bundle {
|
||||
note?: BNote;
|
||||
@ -110,9 +112,9 @@ function getParams(params?: ScriptParams) {
|
||||
.map((p) => {
|
||||
if (typeof p === "string" && p.startsWith("!@#Function: ")) {
|
||||
return p.substr(13);
|
||||
} else {
|
||||
return JSON.stringify(p);
|
||||
}
|
||||
return JSON.stringify(p);
|
||||
|
||||
})
|
||||
.join(",");
|
||||
}
|
||||
@ -145,7 +147,7 @@ export function getScriptBundle(note: BNote, root: boolean = true, scriptEnv: st
|
||||
return;
|
||||
}
|
||||
|
||||
if (!note.isJavaScript() && !note.isHtml()) {
|
||||
if (!(note.isJavaScript() || note.isHtml() || note.isJsx())) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -158,7 +160,7 @@ export function getScriptBundle(note: BNote, root: boolean = true, scriptEnv: st
|
||||
}
|
||||
|
||||
const bundle: Bundle = {
|
||||
note: note,
|
||||
note,
|
||||
script: "",
|
||||
html: "",
|
||||
allNotes: [note]
|
||||
@ -192,12 +194,19 @@ export function getScriptBundle(note: BNote, root: boolean = true, scriptEnv: st
|
||||
// only frontend scripts are async. Backend cannot be async because of transaction management.
|
||||
const isFrontend = scriptEnv === "frontend";
|
||||
|
||||
if (note.isJavaScript()) {
|
||||
if (note.isJsx() || note.isJavaScript()) {
|
||||
let scriptContent = note.getContent();
|
||||
|
||||
if (note.isJsx()) {
|
||||
console.log("GOT JSX!!!");
|
||||
scriptContent = buildJsx(note).code;
|
||||
}
|
||||
|
||||
bundle.script += `
|
||||
apiContext.modules['${note.noteId}'] = { exports: {} };
|
||||
${root ? "return " : ""}${isFrontend ? "await" : ""} ((${isFrontend ? "async" : ""} function(exports, module, require, api${modules.length > 0 ? ", " : ""}${modules.map((child) => sanitizeVariableName(child.title)).join(", ")}) {
|
||||
try {
|
||||
${overrideContent || note.getContent()};
|
||||
${overrideContent || scriptContent};
|
||||
} catch (e) { throw new Error("Load of script note \\"${note.title}\\" (${note.noteId}) failed with: " + e.message); }
|
||||
for (const exportKey in exports) module.exports[exportKey] = exports[exportKey];
|
||||
return module.exports;
|
||||
@ -210,6 +219,16 @@ return module.exports;
|
||||
return bundle;
|
||||
}
|
||||
|
||||
function buildJsx(jsxNote: BNote) {
|
||||
const contentRaw = jsxNote.getContent();
|
||||
const content = Buffer.isBuffer(contentRaw) ? contentRaw.toString("utf-8") : contentRaw;
|
||||
return transform(content, {
|
||||
transforms: ["jsx"],
|
||||
jsxPragma: "h", // for Preact
|
||||
jsxFragmentPragma: "Fragment",
|
||||
});
|
||||
}
|
||||
|
||||
function sanitizeVariableName(str: string) {
|
||||
return str.replace(/[^a-z0-9_]/gim, "");
|
||||
}
|
||||
|
||||
11
pnpm-lock.yaml
generated
11
pnpm-lock.yaml
generated
@ -497,6 +497,9 @@ importers:
|
||||
node-html-parser:
|
||||
specifier: 7.0.1
|
||||
version: 7.0.1
|
||||
sucrase:
|
||||
specifier: 3.35.1
|
||||
version: 3.35.1
|
||||
devDependencies:
|
||||
'@anthropic-ai/sdk':
|
||||
specifier: 0.71.2
|
||||
@ -15257,6 +15260,8 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-core': 47.3.0
|
||||
'@ckeditor/ckeditor5-utils': 47.3.0
|
||||
ckeditor5: 47.3.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-code-block@47.3.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)':
|
||||
dependencies:
|
||||
@ -15474,8 +15479,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-utils': 47.3.0
|
||||
ckeditor5: 47.3.0
|
||||
es-toolkit: 1.39.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-editor-multi-root@47.3.0':
|
||||
dependencies:
|
||||
@ -15498,6 +15501,8 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-table': 47.3.0
|
||||
'@ckeditor/ckeditor5-utils': 47.3.0
|
||||
ckeditor5: 47.3.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-emoji@47.3.0':
|
||||
dependencies:
|
||||
@ -15680,6 +15685,8 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-widget': 47.3.0
|
||||
ckeditor5: 47.3.0
|
||||
es-toolkit: 1.39.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-icons@47.3.0': {}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user