diff --git a/apps/server/package.json b/apps/server/package.json index 32023eb0e..f207a5485 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -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", diff --git a/apps/server/src/becca/entities/bnote.ts b/apps/server/src/becca/entities/bnote.ts index 006a7c3d4..5e6f3ec58 100644 --- a/apps/server/src/becca/entities/bnote.ts +++ b/apps/server/src/becca/entities/bnote.ts @@ -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 { ); } + 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 { 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 { 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 { 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 { 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 { } else { new BAttribute({ noteId: this.noteId, - type: type, - name: name, - value: value + type, + name, + value }).save(); } } @@ -1292,11 +1295,11 @@ class BNote extends AbstractBeccaEntity { 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 { 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 { } 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 { // 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.`); diff --git a/apps/server/src/services/script.ts b/apps/server/src/services/script.ts index 3e4adab2b..8893fd26b 100644 --- a/apps/server/src/services/script.ts +++ b/apps/server/src/services/script.ts @@ -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, ""); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d0087ce5b..2430dd7d5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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': {}