Merge pull request #1956 from TriliumNext/client_vite

Port client to Vite
This commit is contained in:
Elian Doran 2025-05-20 20:34:50 +03:00 committed by GitHub
commit a0a5a2c90d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
43 changed files with 426 additions and 924 deletions

View File

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

View File

@ -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();

View File

@ -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();

View File

@ -0,0 +1,5 @@
import $ from "jquery";
(window as any).$ = $;
(window as any).jQuery = $;
$("body").show();

View File

@ -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<HT
$renderedContent.append($('<div class="ck-content">').html(blob.content));
if ($renderedContent.find("span.math-tex").length > 0) {
await libraryLoader.requireLibrary(libraryLoader.KATEX);
renderMathInElement($renderedContent[0], { trust: true });
}

View File

@ -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`;
}

View File

@ -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($('<link rel="stylesheet" type="text/css" />').attr("href", url));
}
}
export default {
setupGlobs
};

View File

@ -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<T>(value: T | (() => T) | Promise<T>) {
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<string, JQuery.jqXHR> = {};
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($('<link rel="stylesheet" type="text/css" />').attr("href", url));
}
}
export default {
requireCss,
requireLibrary,
KATEX
};

View File

@ -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;

View File

@ -1,3 +1,5 @@
import "jquery";
import "jquery-hotkeys";
import utils from "./services/utils.js";
import ko from "knockout";
import "./stylesheets/bootstrap.scss";

View File

@ -1,4 +1,5 @@
import "./stylesheets/bootstrap.scss";
import "normalize.css";
import "@triliumnext/ckeditor5/content.css";
/**
* Fetch note with given ID from backend

View File

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

View File

@ -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;
}

View File

@ -22,7 +22,6 @@ interface CustomGlobals {
getReferenceLinkTitle: (href: string) => Promise<string>;
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
*/

View File

@ -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*/`
<div class="revisions-dialog modal fade mx-auto" tabindex="-1" role="dialog">
@ -315,8 +315,6 @@ export default class RevisionsDialog extends BasicWidget {
this.$content.html(`<div class="ck-content">${fullRevision.content}</div>`);
if (this.$content.find("span.math-tex").length > 0) {
await libraryLoader.requireLibrary(libraryLoader.KATEX);
renderMathInElement(this.$content[0], { trust: true });
}
} else if (revisionItem.type === "code") {

View File

@ -21,7 +21,7 @@ export default class FindInHtml {
}
async performFind(searchTerm: string, matchCase: boolean, wholeWord: boolean) {
await import("script-loader!mark.js/dist/jquery.mark.min.js");
await import("mark.js");
const $content = await this.parent?.noteContext?.getContentElement();
@ -42,7 +42,7 @@ export default class FindInHtml {
const containerTop = scrollingContainer?.getBoundingClientRect().top ?? 0;
const closestIndex = this.$results.toArray().findIndex(el => el.getBoundingClientRect().top >= containerTop);
this.currentIndex = closestIndex >= 0 ? closestIndex : 0;
await this.jumpTo();
res({

View File

@ -11,8 +11,8 @@ import RightPanelWidget from "./right_panel_widget.js";
import options from "../services/options.js";
import OnClickButtonWidget from "./buttons/onclick_button.js";
import appContext, { type EventData } from "../components/app_context.js";
import libraryLoader from "../services/library_loader.js";
import type FNote from "../entities/fnote.js";
import katex from "../services/math.js";
const TPL = /*html*/`<div class="highlights-list-widget">
<style>
@ -175,7 +175,6 @@ export default class HighlightsListWidget extends RightPanelWidget {
} catch (e) {
if (e instanceof ReferenceError && e.message.includes("katex is not defined")) {
// Load KaTeX if it is not already loaded
await libraryLoader.requireLibrary(libraryLoader.KATEX);
try {
rendered = katex.renderToString(latexCode, {
throwOnError: false

View File

@ -27,6 +27,11 @@ import type { AttributeRow, BranchRow } from "../services/load_results.js";
import type { SetNoteOpts } from "../components/note_context.js";
import type { TouchBarItem } from "../components/touch_bar.js";
import type { TreeCommandNames } from "../menus/tree_context_menu.js";
import "jquery.fancytree";
import "jquery.fancytree/dist/modules/jquery.fancytree.dnd5.js";
import "jquery.fancytree/dist/modules/jquery.fancytree.clones.js";
import "jquery.fancytree/dist/modules/jquery.fancytree.filter.js";
import "../stylesheets/tree.css";
const TPL = /*html*/`
<div class="tree-wrapper">

View File

@ -19,7 +19,7 @@ import RightPanelWidget from "./right_panel_widget.js";
import options from "../services/options.js";
import OnClickButtonWidget from "./buttons/onclick_button.js";
import appContext, { type EventData } from "../components/app_context.js";
import libraryLoader from "../services/library_loader.js";
import katex from "../services/math.js";
import type FNote from "../entities/fnote.js";
const TPL = /*html*/`<div class="toc-widget">
@ -55,7 +55,7 @@ const TPL = /*html*/`<div class="toc-widget">
display: flex;
position: relative;
list-style: none;
align-items: center;
align-items: center;
padding-left: 7px;
cursor: pointer;
text-align: justify;
@ -254,7 +254,6 @@ export default class TocWidget extends RightPanelWidget {
} catch (e) {
if (e instanceof ReferenceError && e.message.includes("katex is not defined")) {
// Load KaTeX if it is not already loaded
await libraryLoader.requireLibrary(libraryLoader.KATEX);
try {
rendered = katex.renderToString(latexCode, {
throwOnError: false
@ -297,13 +296,13 @@ export default class TocWidget extends RightPanelWidget {
let curLevel = 2;
const $ols = [$toc];
let $previousLi: JQuery<HTMLElement> | undefined;
if (!(this.noteContext?.viewScope?.tocCollapsedHeadings instanceof Set)) {
this.noteContext!.viewScope!.tocCollapsedHeadings = new Set<string>();
}
const tocCollapsedHeadings = this.noteContext!.viewScope!.tocCollapsedHeadings as Set<string>;
const validHeadingKeys = new Set<string>(); // Used to clean up obsolete entries in tocCollapsedHeadings
let headingCount = 0;
for (let m = null, headingIndex = 0; (m = headingTagsRegex.exec(html)) !== null; headingIndex++) {
//
@ -426,7 +425,7 @@ export default class TocWidget extends RightPanelWidget {
const willCollapse = !$previousLi.hasClass("collapsed");
$previousLi.addClass("animating");
if (willCollapse) { // Collapse
if (willCollapse) { // Collapse
$ol.css("maxHeight", `${$ol.prop("scrollHeight")}px`);
requestAnimationFrame(() => {
requestAnimationFrame(() => {

View File

@ -132,7 +132,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
// currently required by excalidraw, in order to allows self-hosting fonts locally.
// this avoids making excalidraw load the fonts from an external CDN.
(window as any).EXCALIDRAW_ASSET_PATH = `${window.location.origin}/${asset_path}/app-dist/excalidraw/`;
(window as any).EXCALIDRAW_ASSET_PATH = `${new URL(import.meta.url).origin}/node_modules/@excalidraw/excalidraw/dist/prod`;
// temporary vars
this.currentNoteId = "";

View File

@ -4,7 +4,7 @@ import { getHighlightJsNameForMime } from "../../../services/mime_types.js";
import options from "../../../services/options.js";
import { ensureMimeTypesForHighlighting, isSyntaxHighlightEnabled } from "../../../services/syntax_highlight.js";
import utils from "../../../services/utils.js";
import emojiDefinitionsUrl from "@triliumnext/ckeditor5/emoji_definitions/en.json?external";
import emojiDefinitionsUrl from "@triliumnext/ckeditor5/emoji_definitions/en.json?url";
const TEXT_FORMATTING_GROUP = {
label: "Text formatting",
@ -100,7 +100,7 @@ export function buildConfig() {
allowedProtocols: ALLOWED_PROTOCOLS
},
emoji: {
definitionsUrl: emojiDefinitionsUrl
definitionsUrl: new URL(import.meta.url).origin + emojiDefinitionsUrl
},
syntaxHighlighting: {
loadHighlightJs: async () => {

View File

@ -1,5 +1,4 @@
import { t } from "../../services/i18n.js";
import libraryLoader from "../../services/library_loader.js";
import noteAutocompleteService, { type Suggestion } from "../../services/note_autocomplete.js";
import mimeTypesService from "../../services/mime_types.js";
import utils, { hasTouchBar } from "../../services/utils.js";
@ -310,7 +309,9 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
math: {
engine: "katex",
outputType: "span", // or script
lazyLoad: async () => await libraryLoader.requireLibrary(libraryLoader.KATEX),
lazyLoad: async () => {
(window as any).katex = (await import("../../services/math.js")).default
},
forceOutputType: false, // forces output to use outputType
enablePreview: true // Enable preview view
},

View File

@ -1,11 +1,11 @@
import AbstractTextTypeWidget from "./abstract_text_type_widget.js";
import libraryLoader from "../../services/library_loader.js";
import { applySyntaxHighlight } from "../../services/syntax_highlight.js";
import type FNote from "../../entities/fnote.js";
import type { CommandListenerData, EventData } from "../../components/app_context.js";
import { getLocaleById } from "../../services/i18n.js";
import appContext from "../../components/app_context.js";
import { getMermaidConfig } from "../../services/mermaid.js";
import { renderMathInElement } from "../../services/math.js";
const TPL = /*html*/`
<div class="note-detail-readonly-text note-detail-printable" tabindex="100">
@ -121,8 +121,6 @@ export default class ReadOnlyTextTypeWidget extends AbstractTextTypeWidget {
});
if (this.$content.find("span.math-tex").length > 0) {
await libraryLoader.requireLibrary(libraryLoader.KATEX);
renderMathInElement(this.$content[0], { trust: true });
}

View File

@ -215,7 +215,7 @@ class ListOrGridView extends ViewMode {
const highlightedTokens = this.parentNote.highlightedTokens || [];
if (highlightedTokens.length > 0) {
await import("script-loader!mark.js/dist/jquery.mark.min.js");
await import("mark.js");
const regex = highlightedTokens.map((token) => utils.escapeRegExp(token)).join("|");

View File

@ -1,21 +1,94 @@
/// <reference types='vitest' />
import { join, resolve } from 'path';
import { defineConfig } from 'vite';
import { viteStaticCopy } from 'vite-plugin-static-copy'
import asset_path from './src/asset_path';
const assets = [ "assets", "stylesheets", "libraries", "fonts", "translations" ];
export default defineConfig(() => ({
root: __dirname,
cacheDir: '../../node_modules/.vite/apps/client',
plugins: [],
test: {
watch: false,
globals: true,
setupFiles: ["./src/test/setup.ts"],
environment: "happy-dom",
include: ["src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
reporters: ["default"],
coverage: {
reportsDirectory: './test-output/vitest/coverage',
provider: 'v8' as const,
reporter: [ "text", "html" ]
base: "/" + asset_path,
server: {
port: 4200,
host: 'localhost',
},
preview: {
port: 4300,
host: 'localhost',
},
plugins: [
viteStaticCopy({
targets: assets.map((asset) => ({
src: `src/${asset}/**/*`,
dest: asset
}))
}),
viteStaticCopy({
structured: true,
targets: [
{
src: "node_modules/@excalidraw/excalidraw/dist/prod/fonts/*",
dest: "",
}
]
})
],
resolve: {
alias: [
// Force the use of dist in development mode because upstream ESM is broken (some hybrid between CJS and ESM, will be improved in upcoming versions).
{
find: "@triliumnext/highlightjs",
replacement: resolve(__dirname, "node_modules/@triliumnext/highlightjs/dist")
}
]
},
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
build: {
target: "esnext",
outDir: './dist',
emptyOutDir: true,
reportCompressedSize: true,
rollupOptions: {
input: {
desktop: join(__dirname, "src", "desktop.ts"),
mobile: join(__dirname, "src", "mobile.ts"),
login: join(__dirname, "src", "login.ts"),
setup: join(__dirname, "src", "setup.ts"),
share: join(__dirname, "src", "share.ts"),
set_password: join(__dirname, "src", "set_password.ts"),
runtime: join(__dirname, "src", "runtime.ts")
},
output: {
entryFileNames: "src/[name].js",
chunkFileNames: "src/[name].js",
assetFileNames: "src/[name].[ext]"
},
onwarn(warning, rollupWarn) {
if (warning.code === "MODULE_LEVEL_DIRECTIVE") {
return;
}
rollupWarn(warning);
}
}
},
optimizeDeps: {
exclude: [
"@triliumnext/highlightjs"
]
},
css: {
preprocessorOptions: {
scss: {
quietDeps: true
}
}
},
commonjsOptions: {
transformMixedEsModules: true,
}
}));

View File

@ -1,128 +0,0 @@
const { composePlugins, withNx, withWeb } = require('@nx/webpack');
const { join } = require('path');
const CopyPlugin = require('copy-webpack-plugin');
module.exports = composePlugins(
withNx({
tsConfig: join(__dirname, './tsconfig.app.json'),
compiler: "tsc",
main: join(__dirname, "./src/index.ts"),
additionalEntryPoints: [
{
entryName: "desktop",
entryPath: join(__dirname, "./src/desktop.ts")
},
{
entryName: "mobile",
entryPath: join(__dirname, "./src/mobile.ts")
},
{
entryName: "login",
entryPath: join(__dirname, "./src/login.ts")
},
{
entryName: "setup",
entryPath: join(__dirname, "./src/setup.ts")
},
{
entryName: "share",
entryPath: join(__dirname, "./src/share.ts")
},
{
// TriliumNextTODO: integrate set_password into setup entry point/view
entryName: "set_password",
entryPath: join(__dirname, "./src/set_password.ts")
}
],
externalDependencies: [
"electron"
],
baseHref: '/',
outputHashing: false,
optimization: process.env['NODE_ENV'] === 'production'
}),
withWeb({
styles: [],
stylePreprocessorOptions: {
sassOptions: {
quietDeps: true
}
},
}),
(config) => {
config.output = {
path: join(__dirname, 'dist')
};
config.devServer = {
port: 4200,
client: {
overlay: {
errors: true,
warnings: false,
runtimeErrors: true
}
}
}
config.resolve.fallback = {
path: false,
fs: false,
util: false
};
const assets = [ "assets", "stylesheets", "libraries", "fonts", "translations" ]
config.plugins.push(new CopyPlugin({
patterns: assets.map((asset) => ({
from: join(__dirname, "src", asset),
to: asset
}))
}));
inlineCss(config);
inlineSvg(config);
externalJson(config);
return config;
}
);
function inlineSvg(config) {
if (!config.module?.rules) {
return;
}
// Alter Nx's asset rule to avoid inlining SVG if they have ?raw prepended.
const existingRule = config.module.rules.find((r) => r.test.toString() === /\.svg$/.toString());
existingRule.resourceQuery = { not: [/raw/] };
// Add a rule for prepending ?raw SVGs.
config.module.rules.push({
resourceQuery: /raw/,
type: 'asset/source',
});
}
function inlineCss(config) {
if (!config.module?.rules) {
return;
}
// Alter Nx's asset rule to avoid inlining SVG if they have ?raw prepended.
console.log(config.module.rules.map((r) => r.test.toString()));
const existingRule = config.module.rules.find((r) => r.test.toString().startsWith("/\\.css"));
existingRule.resourceQuery = { not: [/raw/] };
}
function externalJson(config) {
if (!config.module?.rules) {
return;
}
// Add a rule for prepending ?external.
config.module.rules.push({
resourceQuery: /external/,
type: 'asset/resource',
});
}

View File

@ -1,2 +1,3 @@
TRILIUM_ENV=production
TRILIUM_DATA_DIR=./apps/server/data
TRILIUM_DATA_DIR=./apps/server/data
TRILIUM_PORT=8082

View File

@ -4,13 +4,10 @@
"description": "The server-side component of TriliumNext, which exposes the client via the web, allows for sync and provides a REST API for both internal and external use.",
"private": true,
"dependencies": {
"better-sqlite3": "11.10.0",
"jquery.fancytree": "2.38.5",
"jquery-hotkeys": "0.2.2"
"better-sqlite3": "11.10.0"
},
"devDependencies": {
"@electron/remote": "2.1.2",
"@excalidraw/excalidraw": "0.18.0",
"@types/archiver": "6.0.3",
"@types/better-sqlite3": "7.6.13",
"@types/cls-hooked": "4.3.9",
@ -41,12 +38,7 @@
"@types/turndown": "5.0.5",
"@types/ws": "8.18.1",
"@types/xml2js": "0.4.14",
"autocomplete.js": "0.38.1",
"boxicons": "2.1.4",
"express-http-proxy": "2.1.1",
"jquery": "3.7.1",
"katex": "0.16.22",
"normalize.css": "8.0.1",
"@anthropic-ai/sdk": "0.51.0",
"@braintree/sanitize-url": "7.1.1",
"@triliumnext/commons": "workspace:*",

View File

@ -5,7 +5,7 @@ import cookieParser from "cookie-parser";
import helmet from "helmet";
import compression from "compression";
import config from "./services/config.js";
import utils, { getResourceDir } from "./services/utils.js";
import utils, { getResourceDir, isDev } from "./services/utils.js";
import assets from "./routes/assets.js";
import routes from "./routes/routes.js";
import custom from "./routes/custom.js";
@ -63,7 +63,7 @@ export default async function buildApp() {
console.log("Database not initialized yet. LLM features will be initialized after setup.");
}
const publicDir = path.join(getResourceDir(), "public");
const publicDir = isDev ? path.join(getResourceDir(), "../dist/public") : path.join(getResourceDir(), "public");
const publicAssetsDir = path.join(publicDir, "assets");
const assetsDir = RESOURCE_DIR;

View File

@ -7,8 +7,6 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover" />
<link rel="manifest" crossorigin="use-credentials" href="manifest.webmanifest">
<% // TriliumNextTODO: move the css file to ${assetPath}/stylesheets/ %>
<link rel="stylesheet" href="<%= appPath %>/desktop.css">
<title>TriliumNext Notes</title>
</head>
<body class="desktop heading-style-<%= headingStyle %> layout-<%= layoutOrientation %> platform-<%= platform %> <%= isElectron ? 'electron' : '' %> <%= hasNativeTitleBar ? 'native-titlebar' : '' %> <%= hasBackgroundEffects ? 'background-effects' : '' %>">
@ -34,17 +32,7 @@
<!-- Required for correct loading of scripts in Electron -->
<script>if (typeof module === 'object') {window.module = module; module = undefined;}</script>
<script src="<%= assetPath %>/node_modules/jquery/dist/jquery.min.js"></script>
<!-- Include Fancytree library and skip -->
<link href="<%= assetPath %>/stylesheets/tree.css" rel="stylesheet">
<script src="<%= assetPath %>/node_modules/jquery.fancytree/dist/jquery.fancytree-all-deps.min.js"></script>
<script src="<%= assetPath %>/node_modules/jquery-hotkeys/jquery-hotkeys.js"></script>
<script src="<%= assetPath %>/node_modules/autocomplete.js/dist/autocomplete.jquery.min.js"></script>
<link href="<%= appPath %>/bootstrap.css" rel="stylesheet">
<link href="<%= assetPath %>/stylesheets/ckeditor-theme.css" rel="stylesheet">
<link href="api/fonts" rel="stylesheet">
<link href="<%= assetPath %>/stylesheets/theme-light.css" rel="stylesheet">
@ -64,14 +52,8 @@
<link href="<%= assetPath %>/stylesheets/style.css" rel="stylesheet">
<link href="<%= assetPath %>/stylesheets/print.css" rel="stylesheet" media="print">
<script>
$("body").show();
</script>
<script src="<%= appPath %>/runtime.js" crossorigin type="module"></script>
<script src="<%= appPath %>/desktop.js" crossorigin type="module"></script>
<link rel="stylesheet" type="text/css" href="<%= assetPath %>/node_modules/boxicons/css/boxicons.min.css">
</body>
</html>

View File

@ -13,7 +13,7 @@
}
</style>
<% // TriliumNextTODO: move the css file to ${assetPath}/stylesheets/ %>
<link rel="stylesheet" href="<%= appPath %>/login.css">
<link rel="stylesheet" href="<%= appPath %>/bootstrap.css">
<link rel="stylesheet" href="<%= assetPath %>/stylesheets/theme-light.css">
<link rel="stylesheet" href="<%= assetPath %>/stylesheets/theme-next.css">
<link rel="stylesheet" href="<%= assetPath %>/stylesheets/style.css">

View File

@ -10,9 +10,6 @@
<title>TriliumNext Notes</title>
<link rel="manifest" crossorigin="use-credentials" href="manifest.webmanifest">
<% // TriliumNextTODO: move the css file to ${assetPath}/stylesheets/ %>
<link rel="stylesheet" href="<%= appPath %>/mobile.css">
<style>
.lds-roller {
display: inline-block;
@ -111,17 +108,11 @@
<%- include("./partials/windowGlobal.ejs", locals) %>
<script src="<%= assetPath %>/node_modules/jquery/dist/jquery.min.js"></script>
<script src="<%= assetPath %>/node_modules/autocomplete.js/dist/autocomplete.jquery.min.js"></script>
<link href="<%= assetPath %>/stylesheets/tree.css" rel="stylesheet">
<script src="<%= assetPath %>/node_modules/jquery.fancytree/dist/jquery.fancytree-all-deps.min.js"></script>
<script src="<%= appPath %>/runtime.js" crossorigin type="module"></script>
<script src="<%= appPath %>/mobile.js" crossorigin type="module"></script>
<link href="api/fonts" rel="stylesheet">
<link rel="stylesheet" href="<%= appPath %>/bootstrap.css">
<link href="<%= assetPath %>/stylesheets/ckeditor-theme.css" rel="stylesheet">
<link href="<%= assetPath %>/stylesheets/theme-light.css" rel="stylesheet">
<% if (themeCssUrl) { %>
@ -130,7 +121,5 @@
<link href="<%= assetPath %>/stylesheets/style.css" rel="stylesheet">
<link href="<%= assetPath %>/stylesheets/print.css" rel="stylesheet" media="print">
<link rel="stylesheet" type="text/css" href="<%= assetPath %>/node_modules/boxicons/css/boxicons.min.css">
</body>
</html>

View File

@ -7,7 +7,7 @@
<link rel="apple-touch-icon" sizes="180x180" href="<%= assetPath %>/images/app-icons/ios/apple-touch-icon.png">
<link rel="shortcut icon" href="favicon.ico">
<% // TriliumNextTODO: move the css file to ${assetPath}/stylesheets/ %>
<link rel="stylesheet" href="<%= appPath %>/set_password.css">
<link rel="stylesheet" href="<%= appPath %>/bootstrap.css">
<link rel="stylesheet" href="<%= assetPath %>/stylesheets/theme-light.css">
<link rel="stylesheet" href="<%= assetPath %>/stylesheets/theme-next.css">
<link rel="stylesheet" href="<%= assetPath %>/stylesheets/style.css">

View File

@ -7,7 +7,7 @@
<title><%= t("setup.title") %></title>
<% // TriliumNextTODO: move the css file to ${assetPath}/stylesheets/ %>
<link rel="stylesheet" href="<%= appPath %>/setup.css">
<link rel="stylesheet" href="<%= appPath %>/bootstrap.css">
<style>
.lds-ring {
@ -168,9 +168,6 @@
<!-- Required for correct loading of scripts in Electron -->
<script>if (typeof module === 'object') {window.module = module; module = undefined;}</script>
<script src="<%= assetPath %>/node_modules/jquery/dist/jquery.min.js"></script>
<script src="<%= assetPath %>/node_modules/jquery-hotkeys/jquery-hotkeys.js"></script>
<script src="<%= appPath %>/runtime.js" crossorigin type="module"></script>
<script src="<%= appPath %>/setup.js" crossorigin type="module"></script>
<link href="<%= assetPath %>/stylesheets/theme-light.css" rel="stylesheet" />

View File

@ -12,13 +12,9 @@
<% } else { %>
<link rel="shortcut icon" href="../favicon.ico">
<% } %>
<script src="../<%= appPath %>/share.js"></script>
<script src="<%= appPath %>/share.js" type="module"></script>
<% if (!note.isLabelTruthy("shareOmitDefaultCss")) { %>
<link href="../<%= assetPath %>/node_modules/normalize.css/normalize.css" rel="stylesheet">
<link href="../<%= assetPath %>/stylesheets/share.css" rel="stylesheet">
<% } %>
<% if (note.type === 'text' || note.type === 'book') { %>
<link href="../<%= assetPath %>/libraries/ckeditor/ckeditor-content.css" rel="stylesheet">
<link href="<%= assetPath %>/stylesheets/share.css" rel="stylesheet">
<% } %>
<% for (const cssRelation of note.getRelations("shareCss")) { %>
<link href="api/notes/<%= cssRelation.value %>/download" rel="stylesheet">

View File

@ -1,11 +1,10 @@
import assetPath from "../services/asset_path.js";
import { assetUrlFragment } from "../services/asset_path.js";
import path from "path";
import { fileURLToPath } from "url";
import express from "express";
import { getResourceDir, isDev } from "../services/utils.js";
import type serveStatic from "serve-static";
import proxy from "express-http-proxy";
import contentCss from "@triliumnext/ckeditor5/content.css";
const persistentCacheStatic = (root: string, options?: serveStatic.ServeStaticOptions<express.Response<unknown, Record<string, unknown>>>) => {
if (!isDev) {
@ -21,75 +20,29 @@ async function register(app: express.Application) {
const srcRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), "..");
const resourceDir = getResourceDir();
app.use(`/${assetPath}/libraries/ckeditor/ckeditor-content.css`, (req, res) => res.contentType("text/css").send(contentCss));
if (isDev) {
const publicUrl = process.env.TRILIUM_PUBLIC_SERVER;
if (!publicUrl) {
throw new Error("Missing TRILIUM_PUBLIC_SERVER");
}
const clientProxy = proxy(publicUrl);
app.use(`/${assetPath}/app/doc_notes`, persistentCacheStatic(path.join(srcRoot, "assets", "doc_notes")));
app.use(`/${assetPath}/app`, clientProxy);
app.use(`/${assetPath}/app-dist`, clientProxy);
app.use(`/${assetPath}/stylesheets`, proxy(publicUrl, {
proxyReqPathResolver: (req) => "/stylesheets" + req.url
app.use("/" + assetUrlFragment + `/@fs`, proxy(publicUrl, {
proxyReqPathResolver: (req) => "/" + assetUrlFragment + `/@fs` + req.url
}));
app.use(`/${assetPath}/libraries`, proxy(publicUrl, {
proxyReqPathResolver: (req) => "/libraries" + req.url
}));
app.use(`/${assetPath}/fonts`, proxy(publicUrl, {
proxyReqPathResolver: (req) => "/fonts" + req.url
}));
app.use(`/${assetPath}/translations`, proxy(publicUrl, {
proxyReqPathResolver: (req) => "/translations" + req.url
}));
app.use(`/${assetPath}/images`, persistentCacheStatic(path.join(srcRoot, "assets", "images")));
} else {
const clientStaticCache = persistentCacheStatic(path.join(resourceDir, "public"));
app.use(`/${assetPath}/app`, clientStaticCache);
app.use(`/${assetPath}/app-dist`, clientStaticCache);
app.use(`/${assetPath}/stylesheets`, persistentCacheStatic(path.join(resourceDir, "public", "stylesheets")));
app.use(`/${assetPath}/libraries`, persistentCacheStatic(path.join(resourceDir, "public", "libraries")));
app.use(`/${assetPath}/fonts`, persistentCacheStatic(path.join(resourceDir, "public", "fonts")));
app.use(`/${assetPath}/translations/`, persistentCacheStatic(path.join(resourceDir, "public", "translations")));
app.use(`/${assetPath}/images`, persistentCacheStatic(path.join(resourceDir, "assets", "images")));
app.use(`/${assetPath}/app-dist/doc_notes`, persistentCacheStatic(path.join(resourceDir, "assets", "doc_notes")));
app.use(`/${assetPath}/app-dist/excalidraw/fonts`, persistentCacheStatic(path.join(resourceDir, "node_modules/@excalidraw/excalidraw/dist/prod/fonts")));
app.use(`/${assetUrlFragment}/src`, persistentCacheStatic(path.join(resourceDir, "public", "src")));
app.use(`/${assetUrlFragment}/stylesheets`, persistentCacheStatic(path.join(resourceDir, "public", "stylesheets")));
app.use(`/${assetUrlFragment}/libraries`, persistentCacheStatic(path.join(resourceDir, "public", "libraries")));
app.use(`/${assetUrlFragment}/fonts`, persistentCacheStatic(path.join(resourceDir, "public", "fonts")));
app.use(`/${assetUrlFragment}/translations/`, persistentCacheStatic(path.join(resourceDir, "public", "translations")));
app.use(`/${assetUrlFragment}/images`, persistentCacheStatic(path.join(resourceDir, "assets", "images")));
app.use(`/node_modules/`, persistentCacheStatic(path.join(resourceDir, "public/node_modules")));
}
app.use(`/${assetUrlFragment}/doc_notes`, persistentCacheStatic(path.join(resourceDir, "assets", "doc_notes")));
app.use(`/assets/vX/fonts`, express.static(path.join(srcRoot, "public/fonts")));
app.use(`/assets/vX/images`, express.static(path.join(srcRoot, "..", "images")));
app.use(`/assets/vX/stylesheets`, express.static(path.join(srcRoot, "public/stylesheets")));
app.use(`/${assetPath}/libraries`, persistentCacheStatic(path.join(srcRoot, "public/libraries")));
app.use(`/${assetUrlFragment}/libraries`, persistentCacheStatic(path.join(srcRoot, "public/libraries")));
app.use(`/assets/vX/libraries`, express.static(path.join(srcRoot, "..", "libraries")));
const nodeModulesDir = isDev ? path.join(srcRoot, "..", "node_modules") : path.join(resourceDir, "node_modules");
app.use(`/node_modules/@excalidraw/excalidraw/dist/fonts/`, express.static(path.join(nodeModulesDir, "@excalidraw/excalidraw/dist/prod/fonts/")));
app.use(`/${assetPath}/node_modules/@excalidraw/excalidraw/dist/fonts/`, persistentCacheStatic(path.join(nodeModulesDir, "@excalidraw/excalidraw/dist/prod/fonts/")));
// KaTeX
app.use(`/${assetPath}/node_modules/katex/dist/katex.min.js`, persistentCacheStatic(path.join(nodeModulesDir, "katex/dist/katex.min.js")));
app.use(`/${assetPath}/node_modules/katex/dist/contrib/mhchem.min.js`, persistentCacheStatic(path.join(nodeModulesDir, "katex/dist/contrib/mhchem.min.js")));
app.use(`/${assetPath}/node_modules/katex/dist/contrib/auto-render.min.js`, persistentCacheStatic(path.join(nodeModulesDir, "katex/dist/contrib/auto-render.min.js")));
// expose the whole dist folder
app.use(`/node_modules/katex/dist/`, express.static(path.join(nodeModulesDir, "katex/dist/")));
app.use(`/${assetPath}/node_modules/katex/dist/`, persistentCacheStatic(path.join(nodeModulesDir, "katex/dist/")));
app.use(`/${assetPath}/node_modules/boxicons/css/`, persistentCacheStatic(path.join(nodeModulesDir, "boxicons/css/")));
app.use(`/${assetPath}/node_modules/boxicons/fonts/`, persistentCacheStatic(path.join(nodeModulesDir, "boxicons/fonts/")));
app.use(`/${assetPath}/node_modules/jquery/dist/`, persistentCacheStatic(path.join(nodeModulesDir, "jquery/dist/")));
app.use(`/${assetPath}/node_modules/jquery-hotkeys/`, persistentCacheStatic(path.join(nodeModulesDir, "jquery-hotkeys/")));
// Deprecated, https://www.npmjs.com/package/autocomplete.js?activeTab=readme
app.use(`/${assetPath}/node_modules/autocomplete.js/dist/`, persistentCacheStatic(path.join(nodeModulesDir, "autocomplete.js/dist/")));
app.use(`/${assetPath}/node_modules/normalize.css/`, persistentCacheStatic(path.join(nodeModulesDir, "normalize.css/")));
app.use(`/${assetPath}/node_modules/jquery.fancytree/dist/`, persistentCacheStatic(path.join(nodeModulesDir, "jquery.fancytree/dist/")));
}
export default {

View File

@ -14,15 +14,12 @@ describe("Login Route test", () => {
it("should return the login page, when using a GET request", async () => {
// RegExp for login page specific string in HTML: e.g. "assets/v0.92.7/app/login.css"
const loginCssRegexp = /assets\/v[0-9.a-z]+\/app\/login\.css/;
// RegExp for login page specific string in HTML
const res = await supertest(app)
.get("/login")
.expect(200)
expect(loginCssRegexp.test(res.text)).toBe(true);
expect(res.text).toMatch(/assets\/v[0-9.a-z]+\/src\/login\.js/);
});

View File

@ -1,4 +1,3 @@
import assetPath from "./asset_path.js";
import { isDev } from "./utils.js";
export default isDev ? assetPath + "/app" : assetPath + "/app-dist";
export default assetPath + "/src";

View File

@ -1,3 +1,7 @@
import packageJson from "../../package.json" with { type: "json" };
import { isDev } from "./utils";
export default `assets/v${packageJson.version}`;
export const assetUrlFragment = `assets/v${packageJson.version}`;
const assetPath = isDev ? `http://localhost:4200/${assetUrlFragment}` : assetUrlFragment;
export default assetPath;

View File

@ -14,7 +14,7 @@ import log from "../services/log.js";
import type SNote from "./shaca/entities/snote.js";
import type SBranch from "./shaca/entities/sbranch.js";
import type SAttachment from "./shaca/entities/sattachment.js";
import utils, { safeExtractMessageAndStackFromError } from "../services/utils.js";
import utils, { isDev, safeExtractMessageAndStackFromError } from "../services/utils.js";
import options from "../services/options.js";
function getSharedSubTreeRoot(note: SNote): { note?: SNote; branch?: SBranch } {
@ -159,7 +159,16 @@ function register(router: Router) {
const { header, content, isEmpty } = contentRenderer.getContent(note);
const subRoot = getSharedSubTreeRoot(note);
const showLoginInShareTheme = options.getOption("showLoginInShareTheme");
const opts = { note, header, content, isEmpty, subRoot, assetPath, appPath, showLoginInShareTheme };
const opts = {
note,
header,
content,
isEmpty,
subRoot,
assetPath: isDev ? assetPath : `../${assetPath}`,
appPath: isDev ? appPath : `../${appPath}`,
showLoginInShareTheme
};
let useDefaultView = true;
// Check if the user has their own template

View File

@ -13,16 +13,6 @@ function buildFilesToCopy() {
});
const nodePaths = [
"@excalidraw/excalidraw/dist/prod/fonts/",
"katex/dist",
"boxicons/css",
"boxicons/fonts",
"jquery/dist",
"jquery-hotkeys",
"autocomplete.js/dist",
"normalize.css/normalize.css",
"jquery.fancytree/dist",
// Required as they are native dependencies and cannot be well bundled.
"better-sqlite3",
"bindings",

View File

@ -55,6 +55,7 @@
"eslint": "^9.8.0",
"eslint-config-prettier": "^10.0.0",
"eslint-plugin-playwright": "^2.0.0",
"happy-dom": "~9.20.3",
"jiti": "2.4.2",
"jsdom": "~26.1.0",
"jsonc-eslint-parser": "^2.1.0",

756
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff