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