diff --git a/apps/client/package.json b/apps/client/package.json index 16cb5fb4bd..163f6297d8 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -22,9 +22,12 @@ "@mind-elixir/node-menu": "1.0.5", "@popperjs/core": "2.11.8", "@triliumnext/ckeditor5": "workspace:*", - "@triliumnext/commons": "workspace:*", "@triliumnext/codemirror": "workspace:*", + "@triliumnext/commons": "workspace:*", + "@triliumnext/highlightjs": "workspace:*", + "autocomplete.js": "0.38.1", "bootstrap": "5.3.6", + "boxicons": "2.1.4", "dayjs": "1.11.13", "dayjs-plugin-utc": "0.1.2", "debounce": "2.2.0", @@ -37,6 +40,7 @@ "jquery-hotkeys": "0.2.2", "jquery.fancytree": "2.38.5", "jsplumb": "2.15.6", + "katex": "0.16.22", "knockout": "3.5.1", "leaflet": "1.9.4", "leaflet-gpx": "2.2.0", @@ -44,6 +48,7 @@ "marked": "15.0.12", "mermaid": "11.6.0", "mind-elixir": "4.5.2", + "normalize.css": "8.0.1", "panzoom": "9.4.3", "react": "19.1.0", "react-dom": "19.1.0", @@ -57,11 +62,13 @@ "@types/jquery": "3.5.32", "@types/leaflet": "1.9.18", "@types/leaflet-gpx": "1.3.7", + "@types/mark.js": "8.11.12", "@types/react": "19.1.4", "@types/react-dom": "19.1.5", "copy-webpack-plugin": "13.0.0", "happy-dom": "17.4.7", - "script-loader": "0.7.2" + "script-loader": "0.7.2", + "vite-plugin-static-copy": "3.0.0" }, "nx": { "name": "client" diff --git a/apps/client/src/desktop.ts b/apps/client/src/desktop.ts index 51c69aa704..1a0f7e8a99 100644 --- a/apps/client/src/desktop.ts +++ b/apps/client/src/desktop.ts @@ -11,6 +11,9 @@ import options from "./services/options.js"; import type ElectronRemote from "@electron/remote"; import type Electron from "electron"; import "./stylesheets/bootstrap.scss"; +import "boxicons/css/boxicons.min.css"; +import "jquery-hotkeys"; +import "autocomplete.js/index_jquery.js"; await appContext.earlyInit(); diff --git a/apps/client/src/mobile.ts b/apps/client/src/mobile.ts index 5d88ec1c41..805ffe2764 100644 --- a/apps/client/src/mobile.ts +++ b/apps/client/src/mobile.ts @@ -2,6 +2,8 @@ import appContext from "./components/app_context.js"; import noteAutocompleteService from "./services/note_autocomplete.js"; import glob from "./services/glob.js"; import "./stylesheets/bootstrap.scss"; +import "boxicons/css/boxicons.min.css"; +import "autocomplete.js/index_jquery.js"; glob.setupGlobs(); diff --git a/apps/client/src/runtime.ts b/apps/client/src/runtime.ts new file mode 100644 index 0000000000..50c385778f --- /dev/null +++ b/apps/client/src/runtime.ts @@ -0,0 +1,5 @@ +import $ from "jquery"; +(window as any).$ = $; +(window as any).jQuery = $; + +$("body").show(); diff --git a/apps/client/src/services/content_renderer.ts b/apps/client/src/services/content_renderer.ts index 6366e147d7..0664f6a5cf 100644 --- a/apps/client/src/services/content_renderer.ts +++ b/apps/client/src/services/content_renderer.ts @@ -1,7 +1,6 @@ import renderService from "./render.js"; import protectedSessionService from "./protected_session.js"; import protectedSessionHolder from "./protected_session_holder.js"; -import libraryLoader from "./library_loader.js"; import openService from "./open.js"; import froca from "./froca.js"; import utils from "./utils.js"; @@ -15,6 +14,7 @@ import { loadElkIfNeeded, postprocessMermaidSvg } from "./mermaid.js"; import renderDoc from "./doc_renderer.js"; import { t } from "../services/i18n.js"; import WheelZoom from 'vanilla-js-wheel-zoom'; +import { renderMathInElement } from "./math.js"; import { normalizeMimeTypeForCKEditor } from "@triliumnext/commons"; let idCounter = 1; @@ -94,8 +94,6 @@ async function renderText(note: FNote | FAttachment, $renderedContent: JQuery').html(blob.content)); if ($renderedContent.find("span.math-tex").length > 0) { - await libraryLoader.requireLibrary(libraryLoader.KATEX); - renderMathInElement($renderedContent[0], { trust: true }); } diff --git a/apps/client/src/services/doc_renderer.ts b/apps/client/src/services/doc_renderer.ts index c8dac0ac05..4192caf8c6 100644 --- a/apps/client/src/services/doc_renderer.ts +++ b/apps/client/src/services/doc_renderer.ts @@ -48,5 +48,6 @@ function getUrl(docNameValue: string, language: string) { // Cannot have spaces in the URL due to how JQuery.load works. docNameValue = docNameValue.replaceAll(" ", "%20"); - return `${window.glob.appPath}/doc_notes/${language}/${docNameValue}.html`; + const basePath = window.glob.isDev ? new URL(window.glob.assetPath).pathname : window.glob.assetPath; + return `${basePath}/doc_notes/${language}/${docNameValue}.html`; } diff --git a/apps/client/src/services/glob.ts b/apps/client/src/services/glob.ts index a1666b0410..75ed6fd556 100644 --- a/apps/client/src/services/glob.ts +++ b/apps/client/src/services/glob.ts @@ -1,7 +1,6 @@ import utils from "./utils.js"; import appContext from "../components/app_context.js"; import server from "./server.js"; -import libraryLoader from "./library_loader.js"; import ws from "./ws.js"; import froca from "./froca.js"; import linkService from "./link.js"; @@ -17,7 +16,6 @@ function setupGlobs() { // required for ESLint plugin and CKEditor window.glob.getActiveContextNote = () => appContext.tabManager.getActiveContextNote(); - window.glob.requireLibrary = libraryLoader.requireLibrary; window.glob.appContext = appContext; // for debugging window.glob.froca = froca; window.glob.treeCache = froca; // compatibility for CKEditor builds for a while @@ -64,7 +62,7 @@ function setupGlobs() { }); for (const appCssNoteId of glob.appCssNoteIds || []) { - libraryLoader.requireCss(`api/notes/download/${appCssNoteId}`, false); + requireCss(`api/notes/download/${appCssNoteId}`, false); } utils.initHelpButtons($(window)); @@ -76,6 +74,18 @@ function setupGlobs() { }); } +async function requireCss(url: string, prependAssetPath = true) { + const cssLinks = Array.from(document.querySelectorAll("link")).map((el) => el.href); + + if (!cssLinks.some((l) => l.endsWith(url))) { + if (prependAssetPath) { + url = `${window.glob.assetPath}/${url}`; + } + + $("head").append($('').attr("href", url)); + } +} + export default { setupGlobs }; diff --git a/apps/client/src/services/library_loader.ts b/apps/client/src/services/library_loader.ts deleted file mode 100644 index 431a8e2f9d..0000000000 --- a/apps/client/src/services/library_loader.ts +++ /dev/null @@ -1,68 +0,0 @@ -export interface Library { - js?: string[] | (() => string[]); - css?: string[]; -} - -const KATEX: Library = { - js: ["node_modules/katex/dist/katex.min.js", "node_modules/katex/dist/contrib/mhchem.min.js", "node_modules/katex/dist/contrib/auto-render.min.js"], - css: ["node_modules/katex/dist/katex.min.css"] -}; - -async function requireLibrary(library: Library) { - if (library.css) { - library.css.map((cssUrl) => requireCss(cssUrl)); - } - - if (library.js) { - for (const scriptUrl of await unwrapValue(library.js)) { - await requireScript(scriptUrl); - } - } -} - -async function unwrapValue(value: T | (() => T) | Promise) { - if (value && typeof value === "object" && "then" in value) { - return (await (value as Promise<() => T>))(); - } - - if (typeof value === "function") { - return (value as () => T)(); - } - - return value; -} - -// we save the promises in case of the same script being required concurrently multiple times -const loadedScriptPromises: Record = {}; - -async function requireScript(url: string) { - url = `${window.glob.assetPath}/${url}`; - - if (!loadedScriptPromises[url]) { - loadedScriptPromises[url] = $.ajax({ - url: url, - dataType: "script", - cache: true - }); - } - - await loadedScriptPromises[url]; -} - -async function requireCss(url: string, prependAssetPath = true) { - const cssLinks = Array.from(document.querySelectorAll("link")).map((el) => el.href); - - if (!cssLinks.some((l) => l.endsWith(url))) { - if (prependAssetPath) { - url = `${window.glob.assetPath}/${url}`; - } - - $("head").append($('').attr("href", url)); - } -} - -export default { - requireCss, - requireLibrary, - KATEX -}; diff --git a/apps/client/src/services/math.ts b/apps/client/src/services/math.ts new file mode 100644 index 0000000000..2a5fc45b22 --- /dev/null +++ b/apps/client/src/services/math.ts @@ -0,0 +1,5 @@ +import katex from "katex"; +import "katex/contrib/mhchem"; +import "katex/dist/katex.min.css"; +export { default as renderMathInElement } from "katex/contrib/auto-render"; +export default katex; diff --git a/apps/client/src/setup.ts b/apps/client/src/setup.ts index 2e0dae151c..033bb4f424 100644 --- a/apps/client/src/setup.ts +++ b/apps/client/src/setup.ts @@ -1,3 +1,5 @@ +import "jquery"; +import "jquery-hotkeys"; import utils from "./services/utils.js"; import ko from "knockout"; import "./stylesheets/bootstrap.scss"; diff --git a/apps/client/src/share.ts b/apps/client/src/share.ts index 0281e6ed4e..6e221b1f1d 100644 --- a/apps/client/src/share.ts +++ b/apps/client/src/share.ts @@ -1,4 +1,5 @@ -import "./stylesheets/bootstrap.scss"; +import "normalize.css"; +import "@triliumnext/ckeditor5/content.css"; /** * Fetch note with given ID from backend diff --git a/apps/client/src/types-assets.d.ts b/apps/client/src/types-assets.d.ts index 61242b04e3..ffe5456fdb 100644 --- a/apps/client/src/types-assets.d.ts +++ b/apps/client/src/types-assets.d.ts @@ -3,9 +3,7 @@ declare module "*.png" { export default path; } -declare module "*.json?external" { +declare module "@triliumnext/ckeditor5/emoji_definitions/en.json?url" { var path: string; export default path; } - -declare module "script-loader!mark.js/dist/jquery.mark.min.js"; diff --git a/apps/client/src/types-lib.d.ts b/apps/client/src/types-lib.d.ts index 57b810f76b..a19bffa9d9 100644 --- a/apps/client/src/types-lib.d.ts +++ b/apps/client/src/types-lib.d.ts @@ -24,3 +24,10 @@ declare module "draggabilly" { declare module "@mind-elixir/node-menu" { export default mindmap; } + +declare module "katex/contrib/auto-render" { + var renderMathInElement: (element: HTMLElement, options: { + trust: boolean; + }) => void; + export default renderMathInElement; +} diff --git a/apps/client/src/types.d.ts b/apps/client/src/types.d.ts index fad0fed168..9970b99d3c 100644 --- a/apps/client/src/types.d.ts +++ b/apps/client/src/types.d.ts @@ -22,7 +22,6 @@ interface CustomGlobals { getReferenceLinkTitle: (href: string) => Promise; getReferenceLinkTitleSync: (href: string) => string; getActiveContextNote: () => FNote | null; - requireLibrary: typeof library_loader.requireLibrary; ESLINT: Library; appContext: AppContext; froca: Froca; @@ -123,17 +122,6 @@ declare global { var require: RequireMethod; var __non_webpack_require__: RequireMethod | undefined; - // Libraries - var renderMathInElement: (element: HTMLElement, options: { - trust: boolean; - }) => void; - - var katex: { - renderToString(text: string, opts: { - throwOnError: boolean - }); - } - /* * Panzoom */ diff --git a/apps/client/src/widgets/dialogs/revisions.ts b/apps/client/src/widgets/dialogs/revisions.ts index c97864e22c..8083dd9358 100644 --- a/apps/client/src/widgets/dialogs/revisions.ts +++ b/apps/client/src/widgets/dialogs/revisions.ts @@ -3,7 +3,6 @@ import utils from "../../services/utils.js"; import server from "../../services/server.js"; import toastService from "../../services/toast.js"; import appContext from "../../components/app_context.js"; -import libraryLoader from "../../services/library_loader.js"; import openService from "../../services/open.js"; import protectedSessionHolder from "../../services/protected_session_holder.js"; import BasicWidget from "../basic_widget.js"; @@ -12,6 +11,7 @@ import options from "../../services/options.js"; import type FNote from "../../entities/fnote.js"; import type { NoteType } from "../../entities/fnote.js"; import { Dropdown, Modal } from "bootstrap"; +import { renderMathInElement } from "../../services/math.js"; const TPL = /*html*/`