diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index bd0b4e0ec..1f370c360 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -12,7 +12,7 @@ on: paths: - .github/actions/build-electron/* - .github/workflows/nightly.yml - - forge.config.cjs + - forge.config.ts concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/_regroup/package.json b/_regroup/package.json index d423baef2..6f86b5d12 100644 --- a/_regroup/package.json +++ b/_regroup/package.json @@ -35,10 +35,10 @@ "chore:generate-openapi": "tsx bin/generate-openapi.js" }, "devDependencies": { - "@playwright/test": "1.53.0", + "@playwright/test": "1.53.1", "@stylistic/eslint-plugin": "4.4.1", "@types/express": "5.0.3", - "@types/node": "22.15.31", + "@types/node": "22.15.32", "@types/yargs": "17.0.33", "@vitest/coverage-v8": "3.2.4", "eslint": "9.29.0", diff --git a/apps/client/.env b/apps/client/.env new file mode 100644 index 000000000..001b8becd --- /dev/null +++ b/apps/client/.env @@ -0,0 +1,4 @@ +# The development license key for premium CKEditor features. +# Note: This key must only be used for the Trilium Notes project. +# Expires on: 2025-09-13 +VITE_CKEDITOR_KEY=eyJhbGciOiJFUzI1NiJ9.eyJleHAiOjE3NTc3MjE1OTksImp0aSI6ImFiN2E0NjZmLWJlZGMtNDNiYy1iMzU4LTk0NGQ0YWJhY2I3ZiIsImRpc3RyaWJ1dGlvbkNoYW5uZWwiOlsic2giLCJkcnVwYWwiXSwid2hpdGVMYWJlbCI6dHJ1ZSwiZmVhdHVyZXMiOlsiRFJVUCIsIkNNVCIsIkRPIiwiRlAiLCJTQyIsIlRPQyIsIlRQTCIsIlBPRSIsIkNDIiwiTUYiLCJTRUUiLCJFQ0giLCJFSVMiXSwidmMiOiI1MzlkOWY5YyJ9.2rvKPql4hmukyXhEtWPZ8MLxKvzPIwzCdykO653g7IxRRZy2QJpeRszElZx9DakKYZKXekVRAwQKgHxwkgbE_w \ No newline at end of file diff --git a/apps/client/.env.development b/apps/client/.env.development deleted file mode 100644 index b0273c58e..000000000 --- a/apps/client/.env.development +++ /dev/null @@ -1,6 +0,0 @@ -VITE_CKEDITOR_ENABLE_INSPECTOR=false - -# The development license key for premium CKEditor features. -# Note: This key is for development purposes only and should not be used in production. -# Expires on: 2025-09-13 -VITE_CKEDITOR_KEY=eyJhbGciOiJFUzI1NiJ9.eyJleHAiOjE3NTc3MjE1OTksImp0aSI6IjRmMjdkYmYxLTcwOTEtNDYwZi04ZDZmLTc0NzBiZjQwNjg2MCIsImRpc3RyaWJ1dGlvbkNoYW5uZWwiOlsic2giLCJkcnVwYWwiXSwid2hpdGVMYWJlbCI6dHJ1ZSwibGljZW5zZVR5cGUiOiJkZXZlbG9wbWVudCIsImZlYXR1cmVzIjpbIkRSVVAiLCJDTVQiLCJETyIsIkZQIiwiU0MiLCJUT0MiLCJUUEwiLCJQT0UiLCJDQyIsIk1GIiwiU0VFIiwiRUNIIiwiRUlTIl0sInZjIjoiMjMxYzMwNTEifQ.9Ct5lIKbioC3dM8EFatDTmimEIVOdItE3Uh_ICHlS_A_8ueqIfkZpsN3L4_EqprvteNki9yqbuZVGpZTaQ51xg \ No newline at end of file diff --git a/apps/client/.env.production b/apps/client/.env.production index af979f2a0..efd1fd517 100644 --- a/apps/client/.env.production +++ b/apps/client/.env.production @@ -1,6 +1 @@ VITE_CKEDITOR_ENABLE_INSPECTOR=false - -# The development license key for premium CKEditor features. -# Note: This key must only be used for the Trilium Notes project. -# Expires on: 2025-09-13 -VITE_CKEDITOR_KEY=eyJhbGciOiJFUzI1NiJ9.eyJleHAiOjE3NTc3MjE1OTksImp0aSI6ImFiN2E0NjZmLWJlZGMtNDNiYy1iMzU4LTk0NGQ0YWJhY2I3ZiIsImRpc3RyaWJ1dGlvbkNoYW5uZWwiOlsic2giLCJkcnVwYWwiXSwid2hpdGVMYWJlbCI6dHJ1ZSwiZmVhdHVyZXMiOlsiRFJVUCIsIkNNVCIsIkRPIiwiRlAiLCJTQyIsIlRPQyIsIlRQTCIsIlBPRSIsIkNDIiwiTUYiLCJTRUUiLCJFQ0giLCJFSVMiXSwidmMiOiI1MzlkOWY5YyJ9.2rvKPql4hmukyXhEtWPZ8MLxKvzPIwzCdykO653g7IxRRZy2QJpeRszElZx9DakKYZKXekVRAwQKgHxwkgbE_w \ No newline at end of file diff --git a/apps/client/package.json b/apps/client/package.json index 3647f3549..6dfde853f 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -27,7 +27,7 @@ "@triliumnext/highlightjs": "workspace:*", "@triliumnext/share-theme": "workspace:*", "autocomplete.js": "0.38.1", - "bootstrap": "5.3.6", + "bootstrap": "5.3.7", "boxicons": "2.1.4", "dayjs": "1.11.13", "dayjs-plugin-utc": "0.1.2", @@ -48,7 +48,7 @@ "mark.js": "8.11.1", "marked": "15.0.12", "mermaid": "11.6.0", - "mind-elixir": "4.6.0", + "mind-elixir": "4.6.1", "normalize.css": "8.0.1", "panzoom": "9.4.3", "preact": "10.26.9", @@ -75,6 +75,9 @@ "dependsOn": [ "^build" ] + }, + "circular-deps": { + "command": "pnpx dpdm -T {projectRoot}/src/**/*.ts --tree=false --warning=false" } } } diff --git a/apps/client/src-example/app/app.element.css b/apps/client/src-example/app/app.element.css deleted file mode 100644 index 27d098404..000000000 --- a/apps/client/src-example/app/app.element.css +++ /dev/null @@ -1,424 +0,0 @@ -/* - * Remove template code below - */ - html { - -webkit-text-size-adjust: 100%; - font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, - 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, - 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', - 'Noto Color Emoji'; - line-height: 1.5; - tab-size: 4; - scroll-behavior: smooth; - } - body { - font-family: inherit; - line-height: inherit; - margin: 0; - } - h1, - h2, - p, - pre { - margin: 0; - } - *, - ::before, - ::after { - box-sizing: border-box; - border-width: 0; - border-style: solid; - border-color: currentColor; - } - h1, - h2 { - font-size: inherit; - font-weight: inherit; - } - a { - color: inherit; - text-decoration: inherit; - } - pre { - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, - 'Liberation Mono', 'Courier New', monospace; - } - svg { - display: block; - vertical-align: middle; - } - - svg { - shape-rendering: auto; - text-rendering: optimizeLegibility; - } - pre { - background-color: rgba(55, 65, 81, 1); - border-radius: 0.25rem; - color: rgba(229, 231, 235, 1); - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, - 'Liberation Mono', 'Courier New', monospace; - overflow: scroll; - padding: 0.5rem 0.75rem; - } - - .shadow { - box-shadow: 0 0 #0000, 0 0 #0000, 0 10px 15px -3px rgba(0, 0, 0, 0.1), - 0 4px 6px -2px rgba(0, 0, 0, 0.05); - } - .rounded { - border-radius: 1.5rem; - } - - .wrapper { - width: 100%; - } - .container { - margin-left: auto; - margin-right: auto; - max-width: 768px; - padding-bottom: 3rem; - padding-left: 1rem; - padding-right: 1rem; - color: rgba(55, 65, 81, 1); - width: 100%; - } - #welcome { - margin-top: 2.5rem; - } - #welcome h1 { - font-size: 3rem; - font-weight: 500; - letter-spacing: -0.025em; - line-height: 1; - } - #welcome span { - display: block; - font-size: 1.875rem; - font-weight: 300; - line-height: 2.25rem; - margin-bottom: 0.5rem; - } - #hero { - align-items: center; - background-color: hsla(214, 62%, 21%, 1); - border: none; - box-sizing: border-box; - color: rgba(55, 65, 81, 1); - display: grid; - grid-template-columns: 1fr; - margin-top: 3.5rem; - } - #hero .text-container { - color: rgba(255, 255, 255, 1); - padding: 3rem 2rem; - } - #hero .text-container h2 { - font-size: 1.5rem; - line-height: 2rem; - position: relative; - } - #hero .text-container h2 svg { - color: hsla(162, 47%, 50%, 1); - height: 2rem; - left: -0.25rem; - position: absolute; - top: 0; - width: 2rem; - } - #hero .text-container h2 span { - margin-left: 2.5rem; - } - #hero .text-container a { - background-color: rgba(255, 255, 255, 1); - border-radius: 0.75rem; - color: rgba(55, 65, 81, 1); - display: inline-block; - margin-top: 1.5rem; - padding: 1rem 2rem; - text-decoration: inherit; - } - #hero .logo-container { - display: none; - justify-content: center; - padding-left: 2rem; - padding-right: 2rem; - } - #hero .logo-container svg { - color: rgba(255, 255, 255, 1); - width: 66.666667%; - } - - #middle-content { - align-items: flex-start; - display: grid; - gap: 4rem; - grid-template-columns: 1fr; - margin-top: 3.5rem; - } - - #learning-materials { - padding: 2.5rem 2rem; - } - #learning-materials h2 { - font-weight: 500; - font-size: 1.25rem; - letter-spacing: -0.025em; - line-height: 1.75rem; - padding-left: 1rem; - padding-right: 1rem; - } - .list-item-link { - align-items: center; - border-radius: 0.75rem; - display: flex; - margin-top: 1rem; - padding: 1rem; - transition-property: background-color, border-color, color, fill, stroke, - opacity, box-shadow, transform, filter, backdrop-filter, - -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; - width: 100%; - } - .list-item-link svg:first-child { - margin-right: 1rem; - height: 1.5rem; - transition-property: background-color, border-color, color, fill, stroke, - opacity, box-shadow, transform, filter, backdrop-filter, - -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; - width: 1.5rem; - } - .list-item-link > span { - flex-grow: 1; - font-weight: 400; - transition-property: background-color, border-color, color, fill, stroke, - opacity, box-shadow, transform, filter, backdrop-filter, - -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; - } - .list-item-link > span > span { - color: rgba(107, 114, 128, 1); - display: block; - flex-grow: 1; - font-size: 0.75rem; - font-weight: 300; - line-height: 1rem; - transition-property: background-color, border-color, color, fill, stroke, - opacity, box-shadow, transform, filter, backdrop-filter, - -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; - } - .list-item-link svg:last-child { - height: 1rem; - transition-property: all; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; - width: 1rem; - } - .list-item-link:hover { - color: rgba(255, 255, 255, 1); - background-color: hsla(162, 47%, 50%, 1); - } - .list-item-link:hover > span { - } - .list-item-link:hover > span > span { - color: rgba(243, 244, 246, 1); - } - .list-item-link:hover svg:last-child { - transform: translateX(0.25rem); - } - - #other-links { - } - .button-pill { - padding: 1.5rem 2rem; - transition-duration: 300ms; - transition-property: background-color, border-color, color, fill, stroke, - opacity, box-shadow, transform, filter, backdrop-filter, - -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - align-items: center; - display: flex; - } - .button-pill svg { - transition-property: background-color, border-color, color, fill, stroke, - opacity, box-shadow, transform, filter, backdrop-filter, - -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; - flex-shrink: 0; - width: 3rem; - } - .button-pill > span { - letter-spacing: -0.025em; - font-weight: 400; - font-size: 1.125rem; - line-height: 1.75rem; - padding-left: 1rem; - padding-right: 1rem; - } - .button-pill span span { - display: block; - font-size: 0.875rem; - font-weight: 300; - line-height: 1.25rem; - } - .button-pill:hover svg, - .button-pill:hover { - color: rgba(255, 255, 255, 1) !important; - } - #nx-console:hover { - background-color: rgba(0, 122, 204, 1); - } - #nx-console svg { - color: rgba(0, 122, 204, 1); - } - #nx-console-jetbrains { - margin-top: 2rem; - } - #nx-console-jetbrains:hover { - background-color: rgba(255, 49, 140, 1); - } - #nx-console-jetbrains svg { - color: rgba(255, 49, 140, 1); - } - #nx-repo:hover { - background-color: rgba(24, 23, 23, 1); - } - #nx-repo svg { - color: rgba(24, 23, 23, 1); - } - - #nx-cloud { - margin-bottom: 2rem; - margin-top: 2rem; - padding: 2.5rem 2rem; - } - #nx-cloud > div { - align-items: center; - display: flex; - } - #nx-cloud > div svg { - border-radius: 0.375rem; - flex-shrink: 0; - width: 3rem; - } - #nx-cloud > div h2 { - font-size: 1.125rem; - font-weight: 400; - letter-spacing: -0.025em; - line-height: 1.75rem; - padding-left: 1rem; - padding-right: 1rem; - } - #nx-cloud > div h2 span { - display: block; - font-size: 0.875rem; - font-weight: 300; - line-height: 1.25rem; - } - #nx-cloud p { - font-size: 1rem; - line-height: 1.5rem; - margin-top: 1rem; - } - #nx-cloud pre { - margin-top: 1rem; - } - #nx-cloud a { - color: rgba(107, 114, 128, 1); - display: block; - font-size: 0.875rem; - line-height: 1.25rem; - margin-top: 1.5rem; - text-align: right; - } - #nx-cloud a:hover { - text-decoration: underline; - } - - #commands { - padding: 2.5rem 2rem; - - margin-top: 3.5rem; - } - #commands h2 { - font-size: 1.25rem; - font-weight: 400; - letter-spacing: -0.025em; - line-height: 1.75rem; - padding-left: 1rem; - padding-right: 1rem; - } - #commands p { - font-size: 1rem; - font-weight: 300; - line-height: 1.5rem; - margin-top: 1rem; - padding-left: 1rem; - padding-right: 1rem; - } - details { - align-items: center; - display: flex; - margin-top: 1rem; - padding-left: 1rem; - padding-right: 1rem; - width: 100%; - } - details pre > span { - color: rgba(181, 181, 181, 1); - } - summary { - border-radius: 0.5rem; - display: flex; - font-weight: 400; - padding: 0.5rem; - cursor: pointer; - transition-property: background-color, border-color, color, fill, stroke, - opacity, box-shadow, transform, filter, backdrop-filter, - -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; - } - summary:hover { - background-color: rgba(243, 244, 246, 1); - } - summary svg { - height: 1.5rem; - margin-right: 1rem; - width: 1.5rem; - } - - #love { - color: rgba(107, 114, 128, 1); - font-size: 0.875rem; - line-height: 1.25rem; - margin-top: 3.5rem; - opacity: 0.6; - text-align: center; - } - #love svg { - color: rgba(252, 165, 165, 1); - width: 1.25rem; - height: 1.25rem; - display: inline; - margin-top: -0.25rem; - } - - @media screen and (min-width: 768px) { - #hero { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - #hero .logo-container { - display: flex; - } - #middle-content { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - } diff --git a/apps/client/src-example/app/app.element.spec.ts b/apps/client/src-example/app/app.element.spec.ts deleted file mode 100644 index 2c12da184..000000000 --- a/apps/client/src-example/app/app.element.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { AppElement } from './app.element'; - -describe('AppElement', () => { - let app: AppElement; - - beforeEach(() => { - app = new AppElement(); - }); - - it('should create successfully', () => { - expect(app).toBeTruthy(); - }); - - it('should have a greeting', () => { - app.connectedCallback(); - - expect(app.querySelector('h1').innerHTML).toContain( - 'Welcome @triliumnext/client' - ); - }); -}); diff --git a/apps/client/src-example/app/app.element.ts b/apps/client/src-example/app/app.element.ts deleted file mode 100644 index fab80aa49..000000000 --- a/apps/client/src-example/app/app.element.ts +++ /dev/null @@ -1,409 +0,0 @@ -import './app.element.css'; - -export class AppElement extends HTMLElement { - public static observedAttributes = [ - - ]; - - connectedCallback() { - const title = '@triliumnext/client'; - this.innerHTML = ` -
-
- -
-

- Hello there, - Welcome ${title} 👋 -

-
- - -
-
-

- - - - You're up and running -

- What's next? -
-
- - - -
-
- - - - - -
-

Next steps

-

Here are some things you can do with Nx:

-
- - - - - Add UI library - -
# Generate UI lib
-nx g @nx/angular:lib ui
-
-# Add a component
-nx g @nx/angular:component ui/src/lib/button
-
-
- - - - - View interactive project graph - -
nx graph
-
-
- - - - - Run affected commands - -
# see what's been affected by changes
-nx affected:graph
-
-# run tests for current changes
-nx affected:test
-
-# run e2e tests for current changes
-nx affected:e2e
-
-
- -

- Carefully crafted with - - - -

-
-
- `; - } -} -customElements.define('triliumnext-root', AppElement); diff --git a/apps/client/src-example/assets/.gitkeep b/apps/client/src-example/assets/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/client/src-example/favicon.ico b/apps/client/src-example/favicon.ico deleted file mode 100644 index 317ebcb23..000000000 Binary files a/apps/client/src-example/favicon.ico and /dev/null differ diff --git a/apps/client/src-example/index.html b/apps/client/src-example/index.html deleted file mode 100644 index e206d4837..000000000 --- a/apps/client/src-example/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - Client - - - - - - - - - diff --git a/apps/client/src-example/main.ts b/apps/client/src-example/main.ts deleted file mode 100644 index fdb879ded..000000000 --- a/apps/client/src-example/main.ts +++ /dev/null @@ -1 +0,0 @@ -import './app/app.element'; diff --git a/apps/client/src-example/styles.css b/apps/client/src-example/styles.css deleted file mode 100644 index 90d4ee007..000000000 --- a/apps/client/src-example/styles.css +++ /dev/null @@ -1 +0,0 @@ -/* You can add global styles to this file, and also import other style files */ diff --git a/apps/client/src/components/app_context.ts b/apps/client/src/components/app_context.ts index 0cf5058ed..443014572 100644 --- a/apps/client/src/components/app_context.ts +++ b/apps/client/src/components/app_context.ts @@ -1,5 +1,4 @@ import froca from "../services/froca.js"; -import bundleService from "../services/bundle.js"; import RootCommandExecutor from "./root_command_executor.js"; import Entrypoints, { type SqlExecuteResults } from "./entrypoints.js"; import options from "../services/options.js"; @@ -470,6 +469,7 @@ export class AppContext extends Component { this.tabManager.loadTabs(); + const bundleService = (await import("../services/bundle.js")).default; setTimeout(() => bundleService.executeStartupBundles(), 2000); } diff --git a/apps/client/src/components/note_context.ts b/apps/client/src/components/note_context.ts index 11d32cf0d..3a8a54310 100644 --- a/apps/client/src/components/note_context.ts +++ b/apps/client/src/components/note_context.ts @@ -12,6 +12,7 @@ import type FNote from "../entities/fnote.js"; import type TypeWidget from "../widgets/type_widgets/type_widget.js"; import type { CKTextEditor } from "@triliumnext/ckeditor5"; import type CodeMirror from "@triliumnext/codemirror"; +import { closeActiveDialog } from "../services/dialog.js"; export interface SetNoteOpts { triggerSwitchEvent?: unknown; @@ -83,7 +84,7 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded"> await this.triggerEvent("beforeNoteSwitch", { noteContext: this }); - utils.closeActiveDialog(); + closeActiveDialog(); this.notePath = resolvedNotePath; this.viewScope = opts.viewScope; diff --git a/apps/client/src/entities/fnote.ts b/apps/client/src/entities/fnote.ts index 9e215821d..fd35c09b8 100644 --- a/apps/client/src/entities/fnote.ts +++ b/apps/client/src/entities/fnote.ts @@ -1,7 +1,6 @@ import server from "../services/server.js"; import noteAttributeCache from "../services/note_attribute_cache.js"; import ws from "../services/ws.js"; -import froca from "../services/froca.js"; import protectedSessionHolder from "../services/protected_session_holder.js"; import cssClassManager from "../services/css_class_manager.js"; import type { Froca } from "../services/froca-interface.js"; @@ -410,8 +409,8 @@ class FNote { const notePaths: NotePathRecord[] = this.getAllNotePaths().map((path) => ({ notePath: path, isInHoistedSubTree: isHoistedRoot || path.includes(hoistedNoteId), - isArchived: path.some((noteId) => froca.notes[noteId].isArchived), - isSearch: path.some((noteId) => froca.notes[noteId].type === "search"), + isArchived: path.some((noteId) => this.froca.notes[noteId].isArchived), + isSearch: path.some((noteId) => this.froca.notes[noteId].type === "search"), isHidden: path.includes("_hidden") })); @@ -982,7 +981,7 @@ class FNote { continue; } - const parentNote = froca.notes[parentNoteId]; + const parentNote = this.froca.notes[parentNoteId]; if (!parentNote || parentNote.type === "search") { continue; diff --git a/apps/client/src/services/bundle.ts b/apps/client/src/services/bundle.ts index e6eea7ef1..e7b88a343 100644 --- a/apps/client/src/services/bundle.ts +++ b/apps/client/src/services/bundle.ts @@ -1,6 +1,6 @@ import ScriptContext from "./script_context.js"; import server from "./server.js"; -import toastService from "./toast.js"; +import toastService, { showError } from "./toast.js"; import froca from "./froca.js"; import utils from "./utils.js"; import { t } from "./i18n.js"; @@ -37,7 +37,9 @@ async function executeBundle(bundle: Bundle, originEntity?: Entity | null, $cont } catch (e: any) { const note = await froca.getNote(bundle.noteId); - toastService.showAndLogError(`Execution of JS note "${note?.title}" with ID ${bundle.noteId} failed with error: ${e?.message}`); + const message = `Execution of JS note "${note?.title}" with ID ${bundle.noteId} failed with error: ${e?.message}`; + showError(message); + logError(message); } } diff --git a/apps/client/src/services/clipboard.ts b/apps/client/src/services/clipboard.ts index 952ac2e22..9ca5f9d09 100644 --- a/apps/client/src/services/clipboard.ts +++ b/apps/client/src/services/clipboard.ts @@ -4,7 +4,7 @@ import froca from "./froca.js"; import linkService from "./link.js"; import utils from "./utils.js"; import { t } from "./i18n.js"; -import toast from "./toast.js"; +import { throwError } from "./ws.js"; let clipboardBranchIds: string[] = []; let clipboardMode: string | null = null; @@ -37,7 +37,7 @@ async function pasteAfter(afterBranchId: string) { // copy will keep clipboardBranchIds and clipboardMode, so it's possible to paste into multiple places } else { - toastService.throwError(`Unrecognized clipboard mode=${clipboardMode}`); + throwError(`Unrecognized clipboard mode=${clipboardMode}`); } } @@ -69,7 +69,7 @@ async function pasteInto(parentBranchId: string) { // copy will keep clipboardBranchIds and clipboardMode, so it's possible to paste into multiple places } else { - toastService.throwError(`Unrecognized clipboard mode=${clipboardMode}`); + throwError(`Unrecognized clipboard mode=${clipboardMode}`); } } diff --git a/apps/client/src/services/dialog.ts b/apps/client/src/services/dialog.ts index e2d93250f..240172f49 100644 --- a/apps/client/src/services/dialog.ts +++ b/apps/client/src/services/dialog.ts @@ -1,6 +1,41 @@ +import { Modal } from "bootstrap"; import appContext from "../components/app_context.js"; import type { ConfirmDialogOptions, ConfirmDialogResult, ConfirmWithMessageOptions } from "../widgets/dialogs/confirm.js"; import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js"; +import { focusSavedElement, saveFocusedElement } from "./focus.js"; + +export async function openDialog($dialog: JQuery, closeActDialog = true) { + if (closeActDialog) { + closeActiveDialog(); + glob.activeDialog = $dialog; + } + + saveFocusedElement(); + Modal.getOrCreateInstance($dialog[0]).show(); + + $dialog.on("hidden.bs.modal", () => { + const $autocompleteEl = $(".aa-input"); + if ("autocomplete" in $autocompleteEl) { + $autocompleteEl.autocomplete("close"); + } + + if (!glob.activeDialog || glob.activeDialog === $dialog) { + focusSavedElement(); + } + }); + + const keyboardActionsService = (await import("./keyboard_actions.js")).default; + keyboardActionsService.updateDisplayedShortcuts($dialog); + + return $dialog; +} + +export function closeActiveDialog() { + if (glob.activeDialog) { + Modal.getOrCreateInstance(glob.activeDialog[0]).hide(); + glob.activeDialog = null; + } +} async function info(message: string) { return new Promise((res) => appContext.triggerCommand("showInfoDialog", { message, callback: res })); diff --git a/apps/client/src/services/focus.ts b/apps/client/src/services/focus.ts new file mode 100644 index 000000000..066c74558 --- /dev/null +++ b/apps/client/src/services/focus.ts @@ -0,0 +1,29 @@ +let $lastFocusedElement: JQuery | null; + +// perhaps there should be saved focused element per tab? +export function saveFocusedElement() { + $lastFocusedElement = $(":focus"); +} + +export function focusSavedElement() { + if (!$lastFocusedElement) { + return; + } + + if ($lastFocusedElement.hasClass("ck")) { + // must handle CKEditor separately because of this bug: https://github.com/ckeditor/ckeditor5/issues/607 + // the bug manifests itself in resetting the cursor position to the first character - jumping above + + const editor = $lastFocusedElement.closest(".ck-editor__editable").prop("ckeditorInstance"); + + if (editor) { + editor.editing.view.focus(); + } else { + console.log("Could not find CKEditor instance to focus last element"); + } + } else { + $lastFocusedElement.focus(); + } + + $lastFocusedElement = null; +} diff --git a/apps/client/src/services/i18n.spec.ts b/apps/client/src/services/i18n.spec.ts index 9143097be..e64605949 100644 --- a/apps/client/src/services/i18n.spec.ts +++ b/apps/client/src/services/i18n.spec.ts @@ -1,6 +1,7 @@ import { LOCALES } from "@triliumnext/commons"; import { readFileSync } from "fs"; import { join } from "path"; +import { describe, expect, it } from "vitest"; describe("i18n", () => { it("translations are valid JSON", () => { diff --git a/apps/client/src/services/image.ts b/apps/client/src/services/image.ts index 3cf1424d5..f13a9a3c7 100644 --- a/apps/client/src/services/image.ts +++ b/apps/client/src/services/image.ts @@ -1,5 +1,5 @@ import { t } from "./i18n.js"; -import toastService from "./toast.js"; +import toastService, { showError } from "./toast.js"; function copyImageReferenceToClipboard($imageWrapper: JQuery) { try { @@ -11,7 +11,9 @@ function copyImageReferenceToClipboard($imageWrapper: JQuery) { if (success) { toastService.showMessage(t("image.copied-to-clipboard")); } else { - toastService.showAndLogError(t("image.cannot-copy")); + const message = t("image.cannot-copy"); + showError(message); + logError(message); } } finally { window.getSelection()?.removeAllRanges(); diff --git a/apps/client/src/services/note_autocomplete.ts b/apps/client/src/services/note_autocomplete.ts index 4ffff8594..d6eb4df0e 100644 --- a/apps/client/src/services/note_autocomplete.ts +++ b/apps/client/src/services/note_autocomplete.ts @@ -289,13 +289,11 @@ function initNoteAutocomplete($el: JQuery, options?: Options) { } if (suggestion.action === "create-note") { - const { success, noteType, templateNoteId } = await noteCreateService.chooseNoteType(); - + const { success, noteType, templateNoteId, notePath } = await noteCreateService.chooseNoteType(); if (!success) { return; } - - const { note } = await noteCreateService.createNote(suggestion.parentNoteId, { + const { note } = await noteCreateService.createNote( notePath || suggestion.parentNoteId, { title: suggestion.noteTitle, activate: false, type: noteType, diff --git a/apps/client/src/services/note_create.ts b/apps/client/src/services/note_create.ts index 5fa262553..6ce92bc0d 100644 --- a/apps/client/src/services/note_create.ts +++ b/apps/client/src/services/note_create.ts @@ -116,7 +116,7 @@ async function chooseNoteType() { } async function createNoteWithTypePrompt(parentNotePath: string, options: CreateNoteOpts = {}) { - const { success, noteType, templateNoteId } = await chooseNoteType(); + const { success, noteType, templateNoteId, notePath } = await chooseNoteType(); if (!success) { return; @@ -125,7 +125,7 @@ async function createNoteWithTypePrompt(parentNotePath: string, options: CreateN options.type = noteType; options.templateNoteId = templateNoteId; - return await createNote(parentNotePath, options); + return await createNote(notePath || parentNotePath, options); } /* If the first element is heading, parse it out and use it as a new heading. */ diff --git a/apps/client/src/services/script_context.ts b/apps/client/src/services/script_context.ts index 27a8a7d44..7c15db1ae 100644 --- a/apps/client/src/services/script_context.ts +++ b/apps/client/src/services/script_context.ts @@ -1,4 +1,4 @@ -import FrontendScriptApi, { type Entity } from "./frontend_script_api.js"; +import type { Entity } from "./frontend_script_api.js"; import utils from "./utils.js"; import froca from "./froca.js"; @@ -14,6 +14,8 @@ async function ScriptContext(startNoteId: string, allNoteIds: string[], originEn throw new Error(`Could not find start note ${startNoteId}.`); } + const FrontendScriptApi = (await import("./frontend_script_api.js")).default; + return { modules: modules, notes: utils.toObject(allNotes, (note) => [note.noteId, note]), diff --git a/apps/client/src/services/server.ts b/apps/client/src/services/server.ts index f577fbfb4..861a20e60 100644 --- a/apps/client/src/services/server.ts +++ b/apps/client/src/services/server.ts @@ -1,5 +1,6 @@ import utils, { isShare } from "./utils.js"; import ValidationError from "./validation_error.js"; +import { throwError } from "./ws.js"; type Headers = Record; @@ -276,7 +277,7 @@ async function reportError(method: string, url: string, statusCode: number, resp } else { const title = `${statusCode} ${method} ${url}`; toastService.showErrorTitleAndMessage(title, messageStr); - toastService.throwError(`${title} - ${message}`); + throwError(`${title} - ${message}`); } } diff --git a/apps/client/src/services/toast.ts b/apps/client/src/services/toast.ts index 18cc876a9..66b3f7f50 100644 --- a/apps/client/src/services/toast.ts +++ b/apps/client/src/services/toast.ts @@ -78,13 +78,7 @@ function showMessage(message: string, delay = 2000) { }); } -function showAndLogError(message: string, delay = 10000) { - showError(message, delay); - - ws.logError(message); -} - -function showError(message: string, delay = 10000) { +export function showError(message: string, delay = 10000) { console.log(utils.now(), "error: ", message); toast({ @@ -108,18 +102,10 @@ function showErrorTitleAndMessage(title: string, message: string, delay = 10000) }); } -function throwError(message: string) { - ws.logError(message); - - throw new Error(message); -} - export default { showMessage, showError, showErrorTitleAndMessage, - showAndLogError, - throwError, showPersistent, closePersistent }; diff --git a/apps/client/src/services/utils.ts b/apps/client/src/services/utils.ts index 412650605..c7d37e4d9 100644 --- a/apps/client/src/services/utils.ts +++ b/apps/client/src/services/utils.ts @@ -1,5 +1,4 @@ import dayjs from "dayjs"; -import { Modal } from "bootstrap"; import type { ViewScope } from "./link.js"; const SVG_MIME = "image/svg+xml"; @@ -275,69 +274,6 @@ function getMimeTypeClass(mime: string) { return `mime-${mime.toLowerCase().replace(/[\W_]+/g, "-")}`; } -function closeActiveDialog() { - if (glob.activeDialog) { - Modal.getOrCreateInstance(glob.activeDialog[0]).hide(); - glob.activeDialog = null; - } -} - -let $lastFocusedElement: JQuery | null; - -// perhaps there should be saved focused element per tab? -function saveFocusedElement() { - $lastFocusedElement = $(":focus"); -} - -function focusSavedElement() { - if (!$lastFocusedElement) { - return; - } - - if ($lastFocusedElement.hasClass("ck")) { - // must handle CKEditor separately because of this bug: https://github.com/ckeditor/ckeditor5/issues/607 - // the bug manifests itself in resetting the cursor position to the first character - jumping above - - const editor = $lastFocusedElement.closest(".ck-editor__editable").prop("ckeditorInstance"); - - if (editor) { - editor.editing.view.focus(); - } else { - console.log("Could not find CKEditor instance to focus last element"); - } - } else { - $lastFocusedElement.focus(); - } - - $lastFocusedElement = null; -} - -async function openDialog($dialog: JQuery, closeActDialog = true) { - if (closeActDialog) { - closeActiveDialog(); - glob.activeDialog = $dialog; - } - - saveFocusedElement(); - Modal.getOrCreateInstance($dialog[0]).show(); - - $dialog.on("hidden.bs.modal", () => { - const $autocompleteEl = $(".aa-input"); - if ("autocomplete" in $autocompleteEl) { - $autocompleteEl.autocomplete("close"); - } - - if (!glob.activeDialog || glob.activeDialog === $dialog) { - focusSavedElement(); - } - }); - - const keyboardActionsService = (await import("./keyboard_actions.js")).default; - keyboardActionsService.updateDisplayedShortcuts($dialog); - - return $dialog; -} - function isHtmlEmpty(html: string) { if (!html) { return true; @@ -823,10 +759,6 @@ export default { setCookie, getNoteTypeClass, getMimeTypeClass, - closeActiveDialog, - openDialog, - saveFocusedElement, - focusSavedElement, isHtmlEmpty, clearBrowserCache, copySelectionToClipboard, diff --git a/apps/client/src/services/ws.ts b/apps/client/src/services/ws.ts index ccfd19592..dcd63e577 100644 --- a/apps/client/src/services/ws.ts +++ b/apps/client/src/services/ws.ts @@ -17,7 +17,7 @@ let lastProcessedEntityChangeId = window.glob.maxEntityChangeIdAtLoad; let lastPingTs: number; let frontendUpdateDataQueue: EntityChange[] = []; -function logError(message: string) { +export function logError(message: string) { console.error(utils.now(), message); // needs to be separate from .trace() if (ws && ws.readyState === 1) { @@ -301,6 +301,12 @@ setTimeout(() => { setInterval(sendPing, 1000); }, 0); +export function throwError(message: string) { + logError(message); + + throw new Error(message); +} + export default { logError, subscribeToMessages, diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index 9b83c37fe..0bf0588b0 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -440,10 +440,11 @@ body #context-menu-container .dropdown-item > span { border-radius: 6px; overflow: hidden; margin: 4px; + font-size: var(--monospace-font-size); } -body .cm-editor { - font-size: var(--monospace-font-size); +.cm-scroller { + font-family: var(--monospace-font-family) !important; } body .cm-editor .cm-gutters { diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index ab7933617..251819807 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -233,6 +233,8 @@ "move_success_message": "Selected notes have been moved into " }, "note_type_chooser": { + "change_path_prompt": "Change where to create the new note:", + "search_placeholder": "search path by name (default if empty)", "modal_title": "Choose note type", "close": "Close", "modal_body": "Choose note type / template of the new note:", diff --git a/apps/client/src/widgets/attribute_widgets/attribute_detail.ts b/apps/client/src/widgets/attribute_widgets/attribute_detail.ts index 08bb764fc..9017673f3 100644 --- a/apps/client/src/widgets/attribute_widgets/attribute_detail.ts +++ b/apps/client/src/widgets/attribute_widgets/attribute_detail.ts @@ -11,6 +11,7 @@ import utils from "../../services/utils.js"; import shortcutService from "../../services/shortcuts.js"; import appContext from "../../components/app_context.js"; import type { Attribute } from "../../services/attribute_parser.js"; +import { focusSavedElement, saveFocusedElement } from "../../services/focus.js"; const TPL = /*html*/`
@@ -483,7 +484,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget { return; } - utils.saveFocusedElement(); + saveFocusedElement(); this.attrType = this.getAttrType(attribute); @@ -605,7 +606,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget { this.hide(); - utils.focusSavedElement(); + focusSavedElement(); } async cancelAndClose() { @@ -613,7 +614,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget { this.hide(); - utils.focusSavedElement(); + focusSavedElement(); } userEditedAttribute() { diff --git a/apps/client/src/widgets/dialogs/about.ts b/apps/client/src/widgets/dialogs/about.ts index 2c364d756..06cf118ec 100644 --- a/apps/client/src/widgets/dialogs/about.ts +++ b/apps/client/src/widgets/dialogs/about.ts @@ -4,6 +4,7 @@ import BasicWidget from "../basic_widget.js"; import openService from "../../services/open.js"; import server from "../../services/server.js"; import utils from "../../services/utils.js"; +import { openDialog } from "../../services/dialog.js"; interface AppInfo { appVersion: string; @@ -111,6 +112,6 @@ export default class AboutDialog extends BasicWidget { async openAboutDialogEvent() { await this.refresh(); - utils.openDialog(this.$widget); + openDialog(this.$widget); } } diff --git a/apps/client/src/widgets/dialogs/add_link.ts b/apps/client/src/widgets/dialogs/add_link.ts index fe2295442..d7758c92d 100644 --- a/apps/client/src/widgets/dialogs/add_link.ts +++ b/apps/client/src/widgets/dialogs/add_link.ts @@ -1,11 +1,11 @@ import { t } from "../../services/i18n.js"; import treeService from "../../services/tree.js"; import noteAutocompleteService from "../../services/note_autocomplete.js"; -import utils from "../../services/utils.js"; import BasicWidget from "../basic_widget.js"; import type { Suggestion } from "../../services/note_autocomplete.js"; import type { default as TextTypeWidget } from "../type_widgets/editable_text.js"; import type { EventData } from "../../components/app_context.js"; +import { openDialog } from "../../services/dialog.js"; const TPL = /*html*/`