mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-27 01:52:28 +08:00
Merge remote-tracking branch 'origin/develop' into renovate/vitest-monorepo
This commit is contained in:
commit
c61713333d
2
.github/workflows/nightly.yml
vendored
2
.github/workflows/nightly.yml
vendored
@ -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 }}
|
||||
|
@ -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",
|
||||
|
4
apps/client/.env
Normal file
4
apps/client/.env
Normal file
@ -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
|
@ -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
|
@ -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
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
@ -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'
|
||||
);
|
||||
});
|
||||
});
|
@ -1,409 +0,0 @@
|
||||
import './app.element.css';
|
||||
|
||||
export class AppElement extends HTMLElement {
|
||||
public static observedAttributes = [
|
||||
|
||||
];
|
||||
|
||||
connectedCallback() {
|
||||
const title = '@triliumnext/client';
|
||||
this.innerHTML = `
|
||||
<div class="wrapper">
|
||||
<div class="container">
|
||||
<!-- WELCOME -->
|
||||
<div id="welcome">
|
||||
<h1>
|
||||
<span> Hello there, </span>
|
||||
Welcome ${title} 👋
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<!-- HERO -->
|
||||
<div id="hero" class="rounded">
|
||||
<div class="text-container">
|
||||
<h2>
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"
|
||||
/>
|
||||
</svg>
|
||||
<span>You're up and running</span>
|
||||
</h2>
|
||||
<a href="#commands"> What's next? </a>
|
||||
</div>
|
||||
<div class="logo-container">
|
||||
<svg
|
||||
fill="currentColor"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M11.987 14.138l-3.132 4.923-5.193-8.427-.012 8.822H0V4.544h3.691l5.247 8.833.005-3.998 3.044 4.759zm.601-5.761c.024-.048 0-3.784.008-3.833h-3.65c.002.059-.005 3.776-.003 3.833h3.645zm5.634 4.134a2.061 2.061 0 0 0-1.969 1.336 1.963 1.963 0 0 1 2.343-.739c.396.161.917.422 1.33.283a2.1 2.1 0 0 0-1.704-.88zm3.39 1.061c-.375-.13-.8-.277-1.109-.681-.06-.08-.116-.17-.176-.265a2.143 2.143 0 0 0-.533-.642c-.294-.216-.68-.322-1.18-.322a2.482 2.482 0 0 0-2.294 1.536 2.325 2.325 0 0 1 4.002.388.75.75 0 0 0 .836.334c.493-.105.46.36 1.203.518v-.133c-.003-.446-.246-.55-.75-.733zm2.024 1.266a.723.723 0 0 0 .347-.638c-.01-2.957-2.41-5.487-5.37-5.487a5.364 5.364 0 0 0-4.487 2.418c-.01-.026-1.522-2.39-1.538-2.418H8.943l3.463 5.423-3.379 5.32h3.54l1.54-2.366 1.568 2.366h3.541l-3.21-5.052a.7.7 0 0 1-.084-.32 2.69 2.69 0 0 1 2.69-2.691h.001c1.488 0 1.736.89 2.057 1.308.634.826 1.9.464 1.9 1.541a.707.707 0 0 0 1.066.596zm.35.133c-.173.372-.56.338-.755.639-.176.271.114.412.114.412s.337.156.538-.311c.104-.231.14-.488.103-.74z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MIDDLE CONTENT -->
|
||||
<div id="middle-content">
|
||||
<div id="learning-materials" class="rounded shadow">
|
||||
<h2>Learning materials</h2>
|
||||
<a href="https://nx.dev/getting-started/intro?utm_source=nx-project" target="_blank" rel="noreferrer" class="list-item-link">
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
Documentation
|
||||
<span> Everything is in there </span>
|
||||
</span>
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="https://nx.dev/blog/?utm_source=nx-project" target="_blank" rel="noreferrer" class="list-item-link">
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
Blog
|
||||
<span> Changelog, features & events </span>
|
||||
</span>
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="https://www.youtube.com/@NxDevtools/videos?utm_source=nx-project&sub_confirmation=1" target="_blank" rel="noreferrer" class="list-item-link">
|
||||
<svg
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>YouTube</title>
|
||||
<path
|
||||
d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
YouTube channel
|
||||
<span> Nx Show, talks & tutorials </span>
|
||||
</span>
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="https://nx.dev/react-tutorial/1-code-generation?utm_source=nx-project" target="_blank" rel="noreferrer" class="list-item-link">
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 15l-2 5L9 9l11 4-5 2zm0 0l5 5M7.188 2.239l.777 2.897M5.136 7.965l-2.898-.777M13.95 4.05l-2.122 2.122m-5.657 5.656l-2.12 2.122"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
Interactive tutorials
|
||||
<span> Create an app, step-by-step </span>
|
||||
</span>
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="https://nxplaybook.com/?utm_source=nx-project" target="_blank" rel="noreferrer" class="list-item-link">
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M12 14l9-5-9-5-9 5 9 5z" />
|
||||
<path
|
||||
d="M12 14l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14z"
|
||||
/>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 14l9-5-9-5-9 5 9 5zm0 0l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14zm-4 6v-7.5l4-2.222"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
Video courses
|
||||
<span> Nx custom courses </span>
|
||||
</span>
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div id="other-links">
|
||||
<a id="nx-console" class="button-pill rounded shadow" href="https://marketplace.visualstudio.com/items?itemName=nrwl.angular-console&utm_source=nx-project" target="_blank" rel="noreferrer">
|
||||
<svg
|
||||
fill="currentColor"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>Visual Studio Code</title>
|
||||
<path
|
||||
d="M23.15 2.587L18.21.21a1.494 1.494 0 0 0-1.705.29l-9.46 8.63-4.12-3.128a.999.999 0 0 0-1.276.057L.327 7.261A1 1 0 0 0 .326 8.74L3.899 12 .326 15.26a1 1 0 0 0 .001 1.479L1.65 17.94a.999.999 0 0 0 1.276.057l4.12-3.128 9.46 8.63a1.492 1.492 0 0 0 1.704.29l4.942-2.377A1.5 1.5 0 0 0 24 20.06V3.939a1.5 1.5 0 0 0-.85-1.352zm-5.146 14.861L10.826 12l7.178-5.448v10.896z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
Install Nx Console for VSCode
|
||||
<span>The official VSCode extension for Nx.</span>
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
id="nx-console-jetbrains"
|
||||
class="button-pill rounded shadow"
|
||||
href="https://plugins.jetbrains.com/plugin/21060-nx-console"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<svg
|
||||
height="48"
|
||||
width="48"
|
||||
viewBox="20 20 60 60"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="m22.5 22.5h60v60h-60z" />
|
||||
<g fill="#fff">
|
||||
<path d="m29.03 71.25h22.5v3.75h-22.5z" />
|
||||
<path d="m28.09 38 1.67-1.58a1.88 1.88 0 0 0 1.47.87c.64 0 1.06-.44 1.06-1.31v-5.98h2.58v6a3.48 3.48 0 0 1 -.87 2.6 3.56 3.56 0 0 1 -2.57.95 3.84 3.84 0 0 1 -3.34-1.55z" />
|
||||
<path d="m36 30h7.53v2.19h-5v1.44h4.49v2h-4.42v1.49h5v2.21h-7.6z" />
|
||||
<path d="m47.23 32.29h-2.8v-2.29h8.21v2.27h-2.81v7.1h-2.6z" />
|
||||
<path d="m29.13 43.08h4.42a3.53 3.53 0 0 1 2.55.83 2.09 2.09 0 0 1 .6 1.53 2.16 2.16 0 0 1 -1.44 2.09 2.27 2.27 0 0 1 1.86 2.29c0 1.61-1.31 2.59-3.55 2.59h-4.44zm5 2.89c0-.52-.42-.8-1.18-.8h-1.29v1.64h1.24c.79 0 1.25-.26 1.25-.81zm-.9 2.66h-1.57v1.73h1.62c.8 0 1.24-.31 1.24-.86 0-.5-.4-.87-1.27-.87z" />
|
||||
<path d="m38 43.08h4.1a4.19 4.19 0 0 1 3 1 2.93 2.93 0 0 1 .9 2.19 3 3 0 0 1 -1.93 2.89l2.24 3.27h-3l-1.88-2.84h-.87v2.84h-2.56zm4 4.5c.87 0 1.39-.43 1.39-1.11 0-.75-.54-1.12-1.4-1.12h-1.44v2.26z" />
|
||||
<path d="m49.59 43h2.5l4 9.44h-2.79l-.67-1.69h-3.63l-.67 1.69h-2.71zm2.27 5.73-1-2.65-1.06 2.65z" />
|
||||
<path d="m56.46 43.05h2.6v9.37h-2.6z" />
|
||||
<path d="m60.06 43.05h2.42l3.37 5v-5h2.57v9.37h-2.26l-3.53-5.14v5.14h-2.57z" />
|
||||
<path d="m68.86 51 1.45-1.73a4.84 4.84 0 0 0 3 1.13c.71 0 1.08-.24 1.08-.65 0-.4-.31-.6-1.59-.91-2-.46-3.53-1-3.53-2.93 0-1.74 1.37-3 3.62-3a5.89 5.89 0 0 1 3.86 1.25l-1.26 1.84a4.63 4.63 0 0 0 -2.62-.92c-.63 0-.94.25-.94.6 0 .42.32.61 1.63.91 2.14.46 3.44 1.16 3.44 2.91 0 1.91-1.51 3-3.79 3a6.58 6.58 0 0 1 -4.35-1.5z" />
|
||||
</g>
|
||||
</svg>
|
||||
<span>
|
||||
Install Nx Console for JetBrains
|
||||
<span>
|
||||
Available for WebStorm, Intellij IDEA Ultimate and more!
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
<div id="nx-cloud" class="rounded shadow">
|
||||
<div>
|
||||
<svg id="nx-cloud-logo" role="img" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" fill="transparent" viewBox="0 0 24 24">
|
||||
<path stroke-width="2" d="M23 3.75V6.5c-3.036 0-5.5 2.464-5.5 5.5s-2.464 5.5-5.5 5.5-5.5 2.464-5.5 5.5H3.75C2.232 23 1 21.768 1 20.25V3.75C1 2.232 2.232 1 3.75 1h16.5C21.768 1 23 2.232 23 3.75Z" />
|
||||
<path stroke-width="2" d="M23 6v14.1667C23 21.7307 21.7307 23 20.1667 23H6c0-3.128 2.53867-5.6667 5.6667-5.6667 3.128 0 5.6666-2.5386 5.6666-5.6666C17.3333 8.53867 19.872 6 23 6Z" />
|
||||
</svg>
|
||||
<h2>
|
||||
Nx Cloud
|
||||
<span>
|
||||
Enable faster CI & better DX
|
||||
</span>
|
||||
</h2>
|
||||
</div>
|
||||
<p>
|
||||
You can activate distributed tasks executions and caching by
|
||||
running:
|
||||
</p>
|
||||
<pre>nx connect</pre>
|
||||
<a href="https://nx.app/?utm_source=nx-project" target="_blank" rel="noreferrer"> What is Nx Cloud? </a>
|
||||
</div>
|
||||
<a id="nx-repo" class="button-pill rounded shadow" href="https://github.com/nrwl/nx?utm_source=nx-project" target="_blank" rel="noreferrer">
|
||||
<svg
|
||||
fill="currentColor"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
Nx is open source
|
||||
<span> Love Nx? Give us a star! </span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- COMMANDS -->
|
||||
<div id="commands" class="rounded shadow">
|
||||
<h2>Next steps</h2>
|
||||
<p>Here are some things you can do with Nx:</p>
|
||||
<details>
|
||||
<summary>
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
Add UI library
|
||||
</summary>
|
||||
<pre><span># Generate UI lib</span>
|
||||
nx g @nx/angular:lib ui
|
||||
|
||||
<span># Add a component</span>
|
||||
nx g @nx/angular:component ui/src/lib/button</pre>
|
||||
</details>
|
||||
<details>
|
||||
<summary>
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
View interactive project graph
|
||||
</summary>
|
||||
<pre>nx graph</pre>
|
||||
</details>
|
||||
<details>
|
||||
<summary>
|
||||
<svg
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
Run affected commands
|
||||
</summary>
|
||||
<pre><span># see what's been affected by changes</span>
|
||||
nx affected:graph
|
||||
|
||||
<span># run tests for current changes</span>
|
||||
nx affected:test
|
||||
|
||||
<span># run e2e tests for current changes</span>
|
||||
nx affected:e2e</pre>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<p id="love">
|
||||
Carefully crafted with
|
||||
<svg
|
||||
fill="currentColor"
|
||||
stroke="none"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
|
||||
/>
|
||||
</svg>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
customElements.define('triliumnext-root', AppElement);
|
Binary file not shown.
Before Width: | Height: | Size: 15 KiB |
@ -1,14 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Client</title>
|
||||
<base href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<triliumnext-root></triliumnext-root>
|
||||
</body>
|
||||
</html>
|
@ -1 +0,0 @@
|
||||
import './app/app.element';
|
@ -1 +0,0 @@
|
||||
/* You can add global styles to this file, and also import other style files */
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<HTMLElement>, 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 }));
|
||||
|
29
apps/client/src/services/focus.ts
Normal file
29
apps/client/src/services/focus.ts
Normal file
@ -0,0 +1,29 @@
|
||||
let $lastFocusedElement: JQuery<HTMLElement> | 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;
|
||||
}
|
@ -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", () => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { t } from "./i18n.js";
|
||||
import toastService from "./toast.js";
|
||||
import toastService, { showError } from "./toast.js";
|
||||
|
||||
function copyImageReferenceToClipboard($imageWrapper: JQuery<HTMLElement>) {
|
||||
try {
|
||||
@ -11,7 +11,9 @@ function copyImageReferenceToClipboard($imageWrapper: JQuery<HTMLElement>) {
|
||||
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();
|
||||
|
@ -289,13 +289,11 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, 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,
|
||||
|
@ -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. */
|
||||
|
@ -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]),
|
||||
|
@ -1,5 +1,6 @@
|
||||
import utils, { isShare } from "./utils.js";
|
||||
import ValidationError from "./validation_error.js";
|
||||
import { throwError } from "./ws.js";
|
||||
|
||||
type Headers = Record<string, string | null | undefined>;
|
||||
|
||||
@ -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}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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<HTMLElement> | 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<HTMLElement>, 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,
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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:",
|
||||
|
@ -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*/`
|
||||
<div class="attr-detail tn-tool-dialog">
|
||||
@ -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() {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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*/`
|
||||
<div class="add-link-dialog modal mx-auto" tabindex="-1" role="dialog">
|
||||
@ -111,7 +111,7 @@ export default class AddLinkDialog extends BasicWidget {
|
||||
|
||||
this.updateTitleSettingsVisibility();
|
||||
|
||||
await utils.openDialog(this.$widget);
|
||||
await openDialog(this.$widget);
|
||||
|
||||
this.$autoComplete.val("");
|
||||
this.$linkTitle.val("");
|
||||
|
@ -2,11 +2,11 @@ import treeService from "../../services/tree.js";
|
||||
import server from "../../services/server.js";
|
||||
import froca from "../../services/froca.js";
|
||||
import toastService from "../../services/toast.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import appContext from "../../components/app_context.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import { Modal } from "bootstrap";
|
||||
import { openDialog } from "../../services/dialog.js";
|
||||
|
||||
const TPL = /*html*/`<div class="branch-prefix-dialog modal fade mx-auto" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
@ -93,7 +93,7 @@ export default class BranchPrefixDialog extends BasicWidget {
|
||||
}
|
||||
|
||||
await this.refresh(notePath);
|
||||
utils.openDialog(this.$widget);
|
||||
openDialog(this.$widget);
|
||||
}
|
||||
|
||||
async savePrefix() {
|
||||
|
@ -1,11 +1,11 @@
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import froca from "../../services/froca.js";
|
||||
import bulkActionService from "../../services/bulk_action.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import server from "../../services/server.js";
|
||||
import toastService from "../../services/toast.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
import { closeActiveDialog, openDialog } from "../../services/dialog.js";
|
||||
|
||||
|
||||
const TPL = /*html*/`
|
||||
@ -104,7 +104,7 @@ export default class BulkActionsDialog extends BasicWidget {
|
||||
});
|
||||
|
||||
toastService.showMessage(t("bulk_actions.bulk_actions_executed"), 3000);
|
||||
utils.closeActiveDialog();
|
||||
closeActiveDialog();
|
||||
});
|
||||
}
|
||||
|
||||
@ -170,6 +170,6 @@ export default class BulkActionsDialog extends BasicWidget {
|
||||
this.$includeDescendants.prop("checked", false);
|
||||
|
||||
await this.refresh();
|
||||
utils.openDialog(this.$widget);
|
||||
openDialog(this.$widget);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import noteAutocompleteService from "../../services/note_autocomplete.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import treeService from "../../services/tree.js";
|
||||
import toastService from "../../services/toast.js";
|
||||
import froca from "../../services/froca.js";
|
||||
@ -8,6 +7,7 @@ import appContext from "../../components/app_context.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
import { openDialog } from "../../services/dialog.js";
|
||||
|
||||
|
||||
const TPL = /*html*/`
|
||||
@ -94,7 +94,7 @@ export default class CloneToDialog extends BasicWidget {
|
||||
}
|
||||
}
|
||||
|
||||
utils.openDialog(this.$widget);
|
||||
openDialog(this.$widget);
|
||||
this.$noteAutoComplete.val("").trigger("focus");
|
||||
this.$noteList.empty();
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
import server from "../../services/server.js";
|
||||
import froca from "../../services/froca.js";
|
||||
import linkService from "../../services/link.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import type { FAttributeRow } from "../../entities/fattribute.js";
|
||||
import { closeActiveDialog, openDialog } from "../../services/dialog.js";
|
||||
|
||||
// TODO: Use common with server.
|
||||
interface Response {
|
||||
@ -119,13 +119,13 @@ export default class DeleteNotesDialog extends BasicWidget {
|
||||
this.$widget.on("shown.bs.modal", () => this.$okButton.trigger("focus"));
|
||||
|
||||
this.$cancelButton.on("click", () => {
|
||||
utils.closeActiveDialog();
|
||||
closeActiveDialog();
|
||||
|
||||
this.resolve({ proceed: false });
|
||||
});
|
||||
|
||||
this.$okButton.on("click", () => {
|
||||
utils.closeActiveDialog();
|
||||
closeActiveDialog();
|
||||
|
||||
this.resolve({
|
||||
proceed: true,
|
||||
@ -179,7 +179,7 @@ export default class DeleteNotesDialog extends BasicWidget {
|
||||
|
||||
await this.renderDeletePreview();
|
||||
|
||||
utils.openDialog(this.$widget);
|
||||
openDialog(this.$widget);
|
||||
|
||||
this.$deleteAllClones.prop("checked", !!forceDeleteAllClones).prop("disabled", !!forceDeleteAllClones);
|
||||
|
||||
|
@ -8,6 +8,7 @@ import BasicWidget from "../basic_widget.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
import { Modal } from "bootstrap";
|
||||
import { openDialog } from "../../services/dialog.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="export-dialog modal fade mx-auto" tabindex="-1" role="dialog">
|
||||
@ -214,7 +215,7 @@ export default class ExportDialog extends BasicWidget {
|
||||
|
||||
this.$widget.find(".opml-v2").prop("checked", true); // setting default
|
||||
|
||||
utils.openDialog(this.$widget);
|
||||
openDialog(this.$widget);
|
||||
|
||||
const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import utils from "../../services/utils.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import { openDialog } from "../../services/dialog.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="help-dialog modal use-tn-links" tabindex="-1" role="dialog">
|
||||
@ -155,6 +155,6 @@ export default class HelpDialog extends BasicWidget {
|
||||
}
|
||||
|
||||
showCheatsheetEvent() {
|
||||
utils.openDialog(this.$widget);
|
||||
openDialog(this.$widget);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import utils, { escapeQuotes } from "../../services/utils.js";
|
||||
import { escapeQuotes } from "../../services/utils.js";
|
||||
import treeService from "../../services/tree.js";
|
||||
import importService, { type UploadFilesOptions } from "../../services/import.js";
|
||||
import options from "../../services/options.js";
|
||||
@ -6,6 +6,7 @@ import BasicWidget from "../basic_widget.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import { Modal, Tooltip } from "bootstrap";
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
import { openDialog } from "../../services/dialog.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="import-dialog modal fade mx-auto" tabindex="-1" role="dialog">
|
||||
@ -155,7 +156,7 @@ export default class ImportDialog extends BasicWidget {
|
||||
|
||||
this.$noteTitle.text(await treeService.getNoteTitle(this.parentNoteId));
|
||||
|
||||
utils.openDialog(this.$widget);
|
||||
openDialog(this.$widget);
|
||||
}
|
||||
|
||||
async importIntoNote(parentNoteId: string) {
|
||||
|
@ -1,12 +1,12 @@
|
||||
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 froca from "../../services/froca.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import { Modal } from "bootstrap";
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
import type EditableTextTypeWidget from "../type_widgets/editable_text.js";
|
||||
import { openDialog } from "../../services/dialog.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="include-note-dialog modal mx-auto" tabindex="-1" role="dialog">
|
||||
@ -83,7 +83,7 @@ export default class IncludeNoteDialog extends BasicWidget {
|
||||
async showIncludeNoteDialogEvent({ textTypeWidget }: EventData<"showIncludeDialog">) {
|
||||
this.textTypeWidget = textTypeWidget;
|
||||
await this.refresh();
|
||||
utils.openDialog(this.$widget);
|
||||
openDialog(this.$widget);
|
||||
|
||||
this.$autoComplete.trigger("focus").trigger("select"); // to be able to quickly remove entered text
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import { Modal } from "bootstrap";
|
||||
import type { ConfirmDialogCallback } from "./confirm.js";
|
||||
import { openDialog } from "../../services/dialog.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="info-dialog modal mx-auto" tabindex="-1" role="dialog" style="z-index: 2000;">
|
||||
@ -72,7 +72,7 @@ export default class InfoDialog extends BasicWidget {
|
||||
}
|
||||
|
||||
|
||||
utils.openDialog(this.$widget);
|
||||
openDialog(this.$widget);
|
||||
|
||||
this.resolve = callback;
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import appContext from "../../components/app_context.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import shortcutService from "../../services/shortcuts.js";
|
||||
import { Modal } from "bootstrap";
|
||||
import { openDialog } from "../../services/dialog.js";
|
||||
|
||||
const TPL = /*html*/`<div class="jump-to-note-dialog modal mx-auto" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
@ -54,7 +55,7 @@ export default class JumpToNoteDialog extends BasicWidget {
|
||||
}
|
||||
|
||||
async jumpToNoteEvent() {
|
||||
const dialogPromise = utils.openDialog(this.$widget);
|
||||
const dialogPromise = openDialog(this.$widget);
|
||||
if (utils.isMobile()) {
|
||||
dialogPromise.then(($dialog) => {
|
||||
const el = $dialog.find(">.modal-dialog")[0];
|
||||
|
@ -6,6 +6,7 @@ import BasicWidget from "../basic_widget.js";
|
||||
import shortcutService from "../../services/shortcuts.js";
|
||||
import server from "../../services/server.js";
|
||||
import { Modal } from "bootstrap";
|
||||
import { openDialog } from "../../services/dialog.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="markdown-import-dialog modal fade mx-auto" tabindex="-1" role="dialog">
|
||||
@ -89,7 +90,7 @@ export default class MarkdownImportDialog extends BasicWidget {
|
||||
|
||||
this.convertMarkdownToHtml(text);
|
||||
} else {
|
||||
utils.openDialog(this.$widget);
|
||||
openDialog(this.$widget);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import noteAutocompleteService from "../../services/note_autocomplete.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import toastService from "../../services/toast.js";
|
||||
import froca from "../../services/froca.js";
|
||||
import branchService from "../../services/branches.js";
|
||||
@ -7,6 +6,7 @@ import treeService from "../../services/tree.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
import { openDialog } from "../../services/dialog.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="move-to-dialog modal mx-auto" tabindex="-1" role="dialog">
|
||||
@ -83,7 +83,7 @@ export default class MoveToDialog extends BasicWidget {
|
||||
async moveBranchIdsToEvent({ branchIds }: EventData<"moveBranchIdsTo">) {
|
||||
this.movedBranchIds = branchIds;
|
||||
|
||||
utils.openDialog(this.$widget);
|
||||
openDialog(this.$widget);
|
||||
|
||||
this.$noteAutoComplete.val("").trigger("focus");
|
||||
|
||||
|
@ -2,6 +2,7 @@ import type { CommandNames } from "../../components/app_context.js";
|
||||
import type { MenuCommandItem } from "../../menus/context_menu.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import noteTypesService from "../../services/note_types.js";
|
||||
import noteAutocompleteService from "../../services/note_autocomplete.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import { Dropdown, Modal } from "bootstrap";
|
||||
|
||||
@ -13,6 +14,11 @@ const TPL = /*html*/`
|
||||
z-index: 1100 !important;
|
||||
}
|
||||
|
||||
.note-type-chooser-dialog .input-group {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.note-type-chooser-dialog .note-type-dropdown {
|
||||
position: relative;
|
||||
font-size: large;
|
||||
@ -30,6 +36,12 @@ const TPL = /*html*/`
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("note_type_chooser.close")}"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
${t("note_type_chooser.change_path_prompt")}
|
||||
|
||||
<div class="input-group">
|
||||
<input class="choose-note-path form-control" placeholder="${t("note_type_chooser.search_placeholder")}">
|
||||
</div>
|
||||
|
||||
${t("note_type_chooser.modal_body")}
|
||||
|
||||
<div class="dropdown" style="display: flex;">
|
||||
@ -48,6 +60,7 @@ export interface ChooseNoteTypeResponse {
|
||||
success: boolean;
|
||||
noteType?: string;
|
||||
templateNoteId?: string;
|
||||
notePath?: string;
|
||||
}
|
||||
|
||||
type Callback = (data: ChooseNoteTypeResponse) => void;
|
||||
@ -57,6 +70,7 @@ export default class NoteTypeChooserDialog extends BasicWidget {
|
||||
private dropdown!: Dropdown;
|
||||
private modal!: Modal;
|
||||
private $noteTypeDropdown!: JQuery<HTMLElement>;
|
||||
private $autoComplete!: JQuery<HTMLElement>;
|
||||
private $originalFocused: JQuery<HTMLElement> | null;
|
||||
private $originalDialog: JQuery<HTMLElement> | null;
|
||||
|
||||
@ -71,7 +85,8 @@ export default class NoteTypeChooserDialog extends BasicWidget {
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.modal = Modal.getOrCreateInstance(this.$widget[0]);
|
||||
|
||||
|
||||
this.$autoComplete = this.$widget.find(".choose-note-path");
|
||||
this.$noteTypeDropdown = this.$widget.find(".note-type-dropdown");
|
||||
this.dropdown = Dropdown.getOrCreateInstance(this.$widget.find(".note-type-dropdown-trigger")[0]);
|
||||
|
||||
@ -116,9 +131,20 @@ export default class NoteTypeChooserDialog extends BasicWidget {
|
||||
});
|
||||
}
|
||||
|
||||
async refresh() {
|
||||
noteAutocompleteService
|
||||
.initNoteAutocomplete(this.$autoComplete, {
|
||||
allowCreatingNotes: false,
|
||||
hideGoToSelectedNoteButton: true,
|
||||
allowJumpToSearchNotes: false,
|
||||
})
|
||||
}
|
||||
|
||||
async chooseNoteTypeEvent({ callback }: { callback: Callback }) {
|
||||
this.$originalFocused = $(":focus");
|
||||
|
||||
await this.refresh();
|
||||
|
||||
const noteTypes = await noteTypesService.getNoteTypeItems();
|
||||
|
||||
this.$noteTypeDropdown.empty();
|
||||
@ -153,12 +179,14 @@ export default class NoteTypeChooserDialog extends BasicWidget {
|
||||
const $item = $(e.target).closest(".dropdown-item");
|
||||
const noteType = $item.attr("data-note-type");
|
||||
const templateNoteId = $item.attr("data-template-note-id");
|
||||
const notePath = this.$autoComplete.getSelectedNotePath() || undefined;
|
||||
|
||||
if (this.resolve) {
|
||||
this.resolve({
|
||||
success: true,
|
||||
noteType,
|
||||
templateNoteId
|
||||
templateNoteId,
|
||||
notePath
|
||||
});
|
||||
}
|
||||
this.resolve = null;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { openDialog } from "../../services/dialog.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import { Modal } from "bootstrap";
|
||||
|
||||
@ -37,6 +37,6 @@ export default class PasswordNoteSetDialog extends BasicWidget {
|
||||
}
|
||||
|
||||
showPasswordNotSetEvent() {
|
||||
utils.openDialog(this.$widget);
|
||||
openDialog(this.$widget);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { openDialog } from "../../services/dialog.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import { Modal } from "bootstrap";
|
||||
|
||||
@ -110,6 +110,6 @@ export default class PromptDialog extends BasicWidget {
|
||||
|
||||
this.$dialogBody.empty().append($("<div>").addClass("form-group").append(this.$question).append(this.$answer));
|
||||
|
||||
utils.openDialog(this.$widget, false);
|
||||
openDialog(this.$widget, false);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { openDialog } from "../../services/dialog.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import protectedSessionService from "../../services/protected_session.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import { Modal } from "bootstrap";
|
||||
|
||||
@ -49,7 +49,7 @@ export default class ProtectedSessionPasswordDialog extends BasicWidget {
|
||||
}
|
||||
|
||||
showProtectedSessionPasswordDialogEvent() {
|
||||
utils.openDialog(this.$widget);
|
||||
openDialog(this.$widget);
|
||||
|
||||
this.$passwordInput.trigger("focus");
|
||||
}
|
||||
|
@ -2,13 +2,12 @@ import { formatDateTime } from "../../utils/formatters.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import appContext, { type EventData } from "../../components/app_context.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import dialogService from "../../services/dialog.js";
|
||||
import dialogService, { openDialog } from "../../services/dialog.js";
|
||||
import froca from "../../services/froca.js";
|
||||
import hoistedNoteService from "../../services/hoisted_note.js";
|
||||
import linkService from "../../services/link.js";
|
||||
import server from "../../services/server.js";
|
||||
import toastService from "../../services/toast.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import ws from "../../services/ws.js";
|
||||
import { Modal } from "bootstrap";
|
||||
|
||||
@ -62,7 +61,7 @@ export default class RecentChangesDialog extends BasicWidget {
|
||||
|
||||
await this.refresh();
|
||||
|
||||
utils.openDialog(this.$widget);
|
||||
openDialog(this.$widget);
|
||||
}
|
||||
|
||||
async refresh() {
|
||||
|
@ -6,7 +6,7 @@ import appContext from "../../components/app_context.js";
|
||||
import openService from "../../services/open.js";
|
||||
import protectedSessionHolder from "../../services/protected_session_holder.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import dialogService from "../../services/dialog.js";
|
||||
import dialogService, { openDialog } from "../../services/dialog.js";
|
||||
import options from "../../services/options.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import type { NoteType } from "../../entities/fnote.js";
|
||||
@ -182,7 +182,7 @@ export default class RevisionsDialog extends BasicWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
utils.openDialog(this.$widget);
|
||||
openDialog(this.$widget);
|
||||
|
||||
await this.loadRevisions(noteId);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
import { closeActiveDialog, openDialog } from "../../services/dialog.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import server from "../../services/server.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
|
||||
const TPL = /*html*/`<div class="sort-child-notes-dialog modal mx-auto" tabindex="-1" role="dialog">
|
||||
@ -97,14 +97,14 @@ export default class SortChildNotesDialog extends BasicWidget {
|
||||
|
||||
await server.put(`notes/${this.parentNoteId}/sort-children`, { sortBy, sortDirection, foldersFirst, sortNatural, sortLocale });
|
||||
|
||||
utils.closeActiveDialog();
|
||||
closeActiveDialog();
|
||||
});
|
||||
}
|
||||
|
||||
async sortChildNotesEvent({ node }: EventData<"sortChildNotes">) {
|
||||
this.parentNoteId = node.data.noteId;
|
||||
|
||||
utils.openDialog(this.$widget);
|
||||
openDialog(this.$widget);
|
||||
|
||||
this.$form.find("input:first").focus();
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { t } from "../../services/i18n.js";
|
||||
import utils, { escapeQuotes } from "../../services/utils.js";
|
||||
import { escapeQuotes } from "../../services/utils.js";
|
||||
import treeService from "../../services/tree.js";
|
||||
import importService from "../../services/import.js";
|
||||
import options from "../../services/options.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import { Modal, Tooltip } from "bootstrap";
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
import { openDialog } from "../../services/dialog.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="upload-attachments-dialog modal fade mx-auto" tabindex="-1" role="dialog">
|
||||
@ -98,7 +99,7 @@ export default class UploadAttachmentsDialog extends BasicWidget {
|
||||
|
||||
this.$noteTitle.text(await treeService.getNoteTitle(this.parentNoteId));
|
||||
|
||||
utils.openDialog(this.$widget);
|
||||
openDialog(this.$widget);
|
||||
}
|
||||
|
||||
async uploadAttachments(parentNoteId: string) {
|
||||
|
@ -52,9 +52,9 @@ export default class CodeButtonsWidget extends NoteContextAwareWidget {
|
||||
toastService.showMessage(t("code_buttons.opening_api_docs_message"));
|
||||
|
||||
if (this.note?.mime.endsWith("frontend")) {
|
||||
window.open("https://zadam.github.io/trilium/frontend_api/FrontendScriptApi.html", "_blank");
|
||||
window.open("https://triliumnext.github.io/Notes/Script%20API/interfaces/Frontend_Script_API.Api.html", "_blank");
|
||||
} else {
|
||||
window.open("https://zadam.github.io/trilium/backend_api/BackendScriptApi.html", "_blank");
|
||||
window.open("https://triliumnext.github.io/Notes/Script%20API/interfaces/Backend_Script_API.Api.html", "_blank");
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -56,6 +56,8 @@ export default class ContextualHelpButton extends NoteContextAwareWidget {
|
||||
return byNoteType[note.type];
|
||||
} else if (note?.hasLabel("calendarRoot")) {
|
||||
return "l0tKav7yLHGF";
|
||||
} else if (note?.hasLabel("textSnippet")) {
|
||||
return "pwc194wlRzcH";
|
||||
} else if (note && note.type === "book") {
|
||||
return byBookType[note.getAttributeValue("label", "viewType") as ViewTypeOptions ?? ""]
|
||||
}
|
||||
|
@ -4,18 +4,64 @@ import { buildExtraCommands, type EditorConfig } from "@triliumnext/ckeditor5";
|
||||
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?url";
|
||||
import { copyTextWithToast } from "../../../services/clipboard_ext.js";
|
||||
import getTemplates from "./snippets.js";
|
||||
import { PREMIUM_PLUGINS } from "../../../../../../packages/ckeditor5/src/plugins.js";
|
||||
import { t } from "../../../services/i18n.js";
|
||||
import { getMermaidConfig } from "../../../services/mermaid.js";
|
||||
import noteAutocompleteService, { type Suggestion } from "../../../services/note_autocomplete.js";
|
||||
import mimeTypesService from "../../../services/mime_types.js";
|
||||
import { normalizeMimeTypeForCKEditor } from "@triliumnext/commons";
|
||||
import { buildToolbarConfig } from "./toolbar.js";
|
||||
|
||||
const TEXT_FORMATTING_GROUP = {
|
||||
label: "Text formatting",
|
||||
icon: "text"
|
||||
};
|
||||
export const OPEN_SOURCE_LICENSE_KEY = "GPL";
|
||||
|
||||
export async function buildConfig(): Promise<EditorConfig> {
|
||||
return {
|
||||
export interface BuildEditorOptions {
|
||||
forceGplLicense: boolean;
|
||||
isClassicEditor: boolean;
|
||||
contentLanguage: string | null;
|
||||
}
|
||||
|
||||
export async function buildConfig(opts: BuildEditorOptions): Promise<EditorConfig> {
|
||||
const licenseKey = (opts.forceGplLicense ? OPEN_SOURCE_LICENSE_KEY : getLicenseKey());
|
||||
const hasPremiumLicense = (licenseKey !== OPEN_SOURCE_LICENSE_KEY);
|
||||
|
||||
const config: EditorConfig = {
|
||||
licenseKey,
|
||||
placeholder: t("editable_text.placeholder"),
|
||||
mention: {
|
||||
feeds: [
|
||||
{
|
||||
marker: "@",
|
||||
feed: (queryText: string) => noteAutocompleteService.autocompleteSourceForCKEditor(queryText),
|
||||
itemRenderer: (item) => {
|
||||
const itemElement = document.createElement("button");
|
||||
|
||||
itemElement.innerHTML = `${(item as Suggestion).highlightedNotePathTitle} `;
|
||||
|
||||
return itemElement;
|
||||
},
|
||||
minimumCharacters: 0
|
||||
}
|
||||
],
|
||||
},
|
||||
codeBlock: {
|
||||
languages: buildListOfLanguages()
|
||||
},
|
||||
math: {
|
||||
engine: "katex",
|
||||
outputType: "span", // or script
|
||||
lazyLoad: async () => {
|
||||
(window as any).katex = (await import("../../../services/math.js")).default
|
||||
},
|
||||
forceOutputType: false, // forces output to use outputType
|
||||
enablePreview: true // Enable preview view
|
||||
},
|
||||
mermaid: {
|
||||
lazyLoad: async () => (await import("mermaid")).default, // FIXME
|
||||
config: getMermaidConfig()
|
||||
},
|
||||
image: {
|
||||
styles: {
|
||||
options: [
|
||||
@ -130,149 +176,59 @@ export async function buildConfig(): Promise<EditorConfig> {
|
||||
template: {
|
||||
definitions: await getTemplates()
|
||||
},
|
||||
htmlSupport: {
|
||||
allow: JSON.parse(options.get("allowedHtmlTags"))
|
||||
},
|
||||
// This value must be kept in sync with the language defined in webpack.config.js.
|
||||
language: "en"
|
||||
};
|
||||
}
|
||||
|
||||
export function buildToolbarConfig(isClassicToolbar: boolean) {
|
||||
if (utils.isMobile()) {
|
||||
return buildMobileToolbar();
|
||||
} else if (isClassicToolbar) {
|
||||
const multilineToolbar = utils.isDesktop() && options.get("textNoteEditorMultilineToolbar") === "true";
|
||||
return buildClassicToolbar(multilineToolbar);
|
||||
} else {
|
||||
return buildFloatingToolbar();
|
||||
}
|
||||
}
|
||||
|
||||
export function buildMobileToolbar() {
|
||||
const classicConfig = buildClassicToolbar(false);
|
||||
const items: string[] = [];
|
||||
|
||||
for (const item of classicConfig.toolbar.items) {
|
||||
if (typeof item === "object" && "items" in item) {
|
||||
for (const subitem of item.items) {
|
||||
items.push(subitem);
|
||||
}
|
||||
} else {
|
||||
items.push(item);
|
||||
// Set up content language.
|
||||
const { contentLanguage } = opts;
|
||||
if (contentLanguage) {
|
||||
config.language = {
|
||||
ui: (typeof config.language === "string" ? config.language : "en"),
|
||||
content: contentLanguage
|
||||
}
|
||||
}
|
||||
|
||||
// Enable premium plugins.
|
||||
if (hasPremiumLicense) {
|
||||
config.extraPlugins = [
|
||||
...PREMIUM_PLUGINS
|
||||
];
|
||||
}
|
||||
|
||||
return {
|
||||
...classicConfig,
|
||||
toolbar: {
|
||||
...classicConfig.toolbar,
|
||||
items
|
||||
}
|
||||
...config,
|
||||
...buildToolbarConfig(opts.isClassicEditor)
|
||||
};
|
||||
}
|
||||
|
||||
export function buildClassicToolbar(multilineToolbar: boolean) {
|
||||
// For nested toolbars, refer to https://ckeditor.com/docs/ckeditor5/latest/getting-started/setup/toolbar.html#grouping-toolbar-items-in-dropdowns-nested-toolbars.
|
||||
return {
|
||||
toolbar: {
|
||||
items: [
|
||||
"heading",
|
||||
"fontSize",
|
||||
"|",
|
||||
"bold",
|
||||
"italic",
|
||||
{
|
||||
...TEXT_FORMATTING_GROUP,
|
||||
items: ["underline", "strikethrough", "|", "superscript", "subscript", "|", "kbd"]
|
||||
},
|
||||
"|",
|
||||
"fontColor",
|
||||
"fontBackgroundColor",
|
||||
"removeFormat",
|
||||
"|",
|
||||
"bulletedList",
|
||||
"numberedList",
|
||||
"todoList",
|
||||
"|",
|
||||
"blockQuote",
|
||||
"admonition",
|
||||
"insertTable",
|
||||
"|",
|
||||
"code",
|
||||
"codeBlock",
|
||||
"|",
|
||||
"footnote",
|
||||
{
|
||||
label: "Insert",
|
||||
icon: "plus",
|
||||
items: ["imageUpload", "|", "link", "bookmark", "internallink", "includeNote", "|", "specialCharacters", "emoji", "math", "mermaid", "horizontalLine", "pageBreak", "dateTime"]
|
||||
},
|
||||
"|",
|
||||
"alignment",
|
||||
"outdent",
|
||||
"indent",
|
||||
"|",
|
||||
"insertTemplate",
|
||||
"markdownImport",
|
||||
"cuttonote",
|
||||
"findAndReplace"
|
||||
],
|
||||
shouldNotGroupWhenFull: multilineToolbar
|
||||
}
|
||||
};
|
||||
}
|
||||
function buildListOfLanguages() {
|
||||
const userLanguages = mimeTypesService
|
||||
.getMimeTypes()
|
||||
.filter((mt) => mt.enabled)
|
||||
.map((mt) => ({
|
||||
language: normalizeMimeTypeForCKEditor(mt.mime),
|
||||
label: mt.title
|
||||
}));
|
||||
|
||||
export function buildFloatingToolbar() {
|
||||
return {
|
||||
toolbar: {
|
||||
items: [
|
||||
"fontSize",
|
||||
"bold",
|
||||
"italic",
|
||||
"underline",
|
||||
{
|
||||
...TEXT_FORMATTING_GROUP,
|
||||
items: [ "strikethrough", "|", "superscript", "subscript", "|", "kbd" ]
|
||||
},
|
||||
"|",
|
||||
"fontColor",
|
||||
"fontBackgroundColor",
|
||||
"|",
|
||||
"code",
|
||||
"link",
|
||||
"bookmark",
|
||||
"removeFormat",
|
||||
"internallink",
|
||||
"cuttonote"
|
||||
]
|
||||
return [
|
||||
{
|
||||
language: mimeTypesService.MIME_TYPE_AUTO,
|
||||
label: t("editable-text.auto-detect-language")
|
||||
},
|
||||
|
||||
blockToolbar: [
|
||||
"heading",
|
||||
"|",
|
||||
"bulletedList",
|
||||
"numberedList",
|
||||
"todoList",
|
||||
"|",
|
||||
"blockQuote",
|
||||
"admonition",
|
||||
"codeBlock",
|
||||
"insertTable",
|
||||
"footnote",
|
||||
{
|
||||
label: "Insert",
|
||||
icon: "plus",
|
||||
items: ["link", "bookmark", "internallink", "includeNote", "|", "math", "mermaid", "horizontalLine", "pageBreak", "dateTime"]
|
||||
},
|
||||
"|",
|
||||
"alignment",
|
||||
"outdent",
|
||||
"indent",
|
||||
"|",
|
||||
"insertTemplate",
|
||||
"imageUpload",
|
||||
"markdownImport",
|
||||
"specialCharacters",
|
||||
"emoji",
|
||||
"findAndReplace"
|
||||
]
|
||||
};
|
||||
...userLanguages
|
||||
];
|
||||
}
|
||||
|
||||
function getLicenseKey() {
|
||||
const premiumLicenseKey = import.meta.env.VITE_CKEDITOR_KEY;
|
||||
if (!premiumLicenseKey) {
|
||||
logError("CKEditor license key is not set, premium features will not be available.");
|
||||
return OPEN_SOURCE_LICENSE_KEY;
|
||||
}
|
||||
|
||||
return premiumLicenseKey;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildClassicToolbar, buildFloatingToolbar } from "./config.js";
|
||||
import { buildClassicToolbar, buildFloatingToolbar } from "./toolbar.js";
|
||||
|
||||
type ToolbarConfig = string | "|" | { items: ToolbarConfig[] };
|
||||
|
149
apps/client/src/widgets/type_widgets/ckeditor/toolbar.ts
Normal file
149
apps/client/src/widgets/type_widgets/ckeditor/toolbar.ts
Normal file
@ -0,0 +1,149 @@
|
||||
import utils from "../../../services/utils.js";
|
||||
import options from "../../../services/options.js";
|
||||
|
||||
const TEXT_FORMATTING_GROUP = {
|
||||
label: "Text formatting",
|
||||
icon: "text"
|
||||
};
|
||||
|
||||
export function buildToolbarConfig(isClassicToolbar: boolean) {
|
||||
if (utils.isMobile()) {
|
||||
return buildMobileToolbar();
|
||||
} else if (isClassicToolbar) {
|
||||
const multilineToolbar = utils.isDesktop() && options.get("textNoteEditorMultilineToolbar") === "true";
|
||||
return buildClassicToolbar(multilineToolbar);
|
||||
} else {
|
||||
return buildFloatingToolbar();
|
||||
}
|
||||
}
|
||||
|
||||
export function buildMobileToolbar() {
|
||||
const classicConfig = buildClassicToolbar(false);
|
||||
const items: string[] = [];
|
||||
|
||||
for (const item of classicConfig.toolbar.items) {
|
||||
if (typeof item === "object" && "items" in item) {
|
||||
for (const subitem of item.items) {
|
||||
items.push(subitem);
|
||||
}
|
||||
} else {
|
||||
items.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...classicConfig,
|
||||
toolbar: {
|
||||
...classicConfig.toolbar,
|
||||
items
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function buildClassicToolbar(multilineToolbar: boolean) {
|
||||
// For nested toolbars, refer to https://ckeditor.com/docs/ckeditor5/latest/getting-started/setup/toolbar.html#grouping-toolbar-items-in-dropdowns-nested-toolbars.
|
||||
return {
|
||||
toolbar: {
|
||||
items: [
|
||||
"heading",
|
||||
"fontSize",
|
||||
"|",
|
||||
"bold",
|
||||
"italic",
|
||||
{
|
||||
...TEXT_FORMATTING_GROUP,
|
||||
items: ["underline", "strikethrough", "|", "superscript", "subscript", "|", "kbd"]
|
||||
},
|
||||
"|",
|
||||
"fontColor",
|
||||
"fontBackgroundColor",
|
||||
"removeFormat",
|
||||
"|",
|
||||
"bulletedList",
|
||||
"numberedList",
|
||||
"todoList",
|
||||
"|",
|
||||
"blockQuote",
|
||||
"admonition",
|
||||
"insertTable",
|
||||
"|",
|
||||
"code",
|
||||
"codeBlock",
|
||||
"|",
|
||||
"footnote",
|
||||
{
|
||||
label: "Insert",
|
||||
icon: "plus",
|
||||
items: ["imageUpload", "|", "link", "bookmark", "internallink", "includeNote", "|", "specialCharacters", "emoji", "math", "mermaid", "horizontalLine", "pageBreak", "dateTime"]
|
||||
},
|
||||
"|",
|
||||
"alignment",
|
||||
"outdent",
|
||||
"indent",
|
||||
"|",
|
||||
"insertTemplate",
|
||||
"markdownImport",
|
||||
"cuttonote",
|
||||
"findAndReplace"
|
||||
],
|
||||
shouldNotGroupWhenFull: multilineToolbar
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function buildFloatingToolbar() {
|
||||
return {
|
||||
toolbar: {
|
||||
items: [
|
||||
"fontSize",
|
||||
"bold",
|
||||
"italic",
|
||||
"underline",
|
||||
{
|
||||
...TEXT_FORMATTING_GROUP,
|
||||
items: [ "strikethrough", "|", "superscript", "subscript", "|", "kbd" ]
|
||||
},
|
||||
"|",
|
||||
"fontColor",
|
||||
"fontBackgroundColor",
|
||||
"|",
|
||||
"code",
|
||||
"link",
|
||||
"bookmark",
|
||||
"removeFormat",
|
||||
"internallink",
|
||||
"cuttonote"
|
||||
]
|
||||
},
|
||||
|
||||
blockToolbar: [
|
||||
"heading",
|
||||
"|",
|
||||
"bulletedList",
|
||||
"numberedList",
|
||||
"todoList",
|
||||
"|",
|
||||
"blockQuote",
|
||||
"admonition",
|
||||
"codeBlock",
|
||||
"insertTable",
|
||||
"footnote",
|
||||
{
|
||||
label: "Insert",
|
||||
icon: "plus",
|
||||
items: ["link", "bookmark", "internallink", "includeNote", "|", "math", "mermaid", "horizontalLine", "pageBreak", "dateTime"]
|
||||
},
|
||||
"|",
|
||||
"alignment",
|
||||
"outdent",
|
||||
"indent",
|
||||
"|",
|
||||
"insertTemplate",
|
||||
"imageUpload",
|
||||
"markdownImport",
|
||||
"specialCharacters",
|
||||
"emoji",
|
||||
"findAndReplace"
|
||||
]
|
||||
};
|
||||
}
|
@ -1,6 +1,3 @@
|
||||
import { t } from "../../services/i18n.js";
|
||||
import noteAutocompleteService, { type Suggestion } from "../../services/note_autocomplete.js";
|
||||
import mimeTypesService from "../../services/mime_types.js";
|
||||
import utils, { hasTouchBar } from "../../services/utils.js";
|
||||
import keyboardActionService from "../../services/keyboard_actions.js";
|
||||
import froca from "../../services/froca.js";
|
||||
@ -12,29 +9,12 @@ import dialogService from "../../services/dialog.js";
|
||||
import options from "../../services/options.js";
|
||||
import toast from "../../services/toast.js";
|
||||
import { buildSelectedBackgroundColor } from "../../components/touch_bar.js";
|
||||
import { buildConfig, buildToolbarConfig } from "./ckeditor/config.js";
|
||||
import { buildConfig, BuildEditorOptions, OPEN_SOURCE_LICENSE_KEY } from "./ckeditor/config.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import { getMermaidConfig } from "../../services/mermaid.js";
|
||||
import { PopupEditor, ClassicEditor, EditorWatchdog, type CKTextEditor, type MentionFeed, type WatchdogConfig } from "@triliumnext/ckeditor5";
|
||||
import { PopupEditor, ClassicEditor, EditorWatchdog, type CKTextEditor, type MentionFeed, type WatchdogConfig, EditorConfig } from "@triliumnext/ckeditor5";
|
||||
import "@triliumnext/ckeditor5/index.css";
|
||||
import { normalizeMimeTypeForCKEditor } from "@triliumnext/commons";
|
||||
import { updateTemplateCache } from "./ckeditor/snippets.js";
|
||||
|
||||
const mentionSetup: MentionFeed[] = [
|
||||
{
|
||||
marker: "@",
|
||||
feed: (queryText: string) => noteAutocompleteService.autocompleteSourceForCKEditor(queryText),
|
||||
itemRenderer: (item) => {
|
||||
const itemElement = document.createElement("button");
|
||||
|
||||
itemElement.innerHTML = `${(item as Suggestion).highlightedNotePathTitle} `;
|
||||
|
||||
return itemElement;
|
||||
},
|
||||
minimumCharacters: 0
|
||||
}
|
||||
];
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="note-detail-editable-text note-detail-printable">
|
||||
<style>
|
||||
@ -97,24 +77,6 @@ const TPL = /*html*/`
|
||||
</div>
|
||||
`;
|
||||
|
||||
function buildListOfLanguages() {
|
||||
const userLanguages = mimeTypesService
|
||||
.getMimeTypes()
|
||||
.filter((mt) => mt.enabled)
|
||||
.map((mt) => ({
|
||||
language: normalizeMimeTypeForCKEditor(mt.mime),
|
||||
label: mt.title
|
||||
}));
|
||||
|
||||
return [
|
||||
{
|
||||
language: mimeTypesService.MIME_TYPE_AUTO,
|
||||
label: t("editable-text.auto-detect-language")
|
||||
},
|
||||
...userLanguages
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* The editor can operate into two distinct modes:
|
||||
*
|
||||
@ -147,7 +109,6 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
|
||||
async initEditor() {
|
||||
const isClassicEditor = utils.isMobile() || options.get("textNoteEditorType") === "ckeditor-classic";
|
||||
const editorClass = isClassicEditor ? ClassicEditor : PopupEditor;
|
||||
|
||||
// CKEditor since version 12 needs the element to be visible before initialization. At the same time,
|
||||
// we want to avoid flicker - i.e., show editor only once everything is ready. That's why we have separate
|
||||
@ -192,34 +153,15 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
this.watchdog.setCreator(async (_, editorConfig) => {
|
||||
logInfo("Creating new CKEditor");
|
||||
|
||||
const finalConfig = {
|
||||
...editorConfig,
|
||||
...(await buildConfig()),
|
||||
...buildToolbarConfig(isClassicEditor),
|
||||
htmlSupport: {
|
||||
allow: JSON.parse(options.get("allowedHtmlTags")),
|
||||
styles: true,
|
||||
classes: true,
|
||||
attributes: true
|
||||
},
|
||||
licenseKey: getLicenseKey()
|
||||
};
|
||||
|
||||
const contentLanguage = this.note?.getLabelValue("language");
|
||||
if (contentLanguage) {
|
||||
// TODO: Wrong type?
|
||||
//@ts-ignore
|
||||
finalConfig.language = {
|
||||
ui: (typeof finalConfig.language === "string" ? finalConfig.language : "en"),
|
||||
content: contentLanguage
|
||||
}
|
||||
this.contentLanguage = contentLanguage;
|
||||
} else {
|
||||
this.contentLanguage = null;
|
||||
}
|
||||
this.contentLanguage = contentLanguage ?? null;
|
||||
|
||||
//@ts-ignore
|
||||
const editor = await editorClass.create(this.$editor[0], finalConfig);
|
||||
const opts: BuildEditorOptions = {
|
||||
contentLanguage: this.contentLanguage,
|
||||
forceGplLicense: false,
|
||||
isClassicEditor
|
||||
};
|
||||
const editor = await buildEditor(this.$editor[0], isClassicEditor, opts);
|
||||
|
||||
const notificationsPlugin = editor.plugins.get("Notification");
|
||||
notificationsPlugin.on("show:warning", (evt, data) => {
|
||||
@ -296,28 +238,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
}
|
||||
|
||||
async createEditor() {
|
||||
await this.watchdog.create(this.$editor[0], {
|
||||
placeholder: t("editable_text.placeholder"),
|
||||
mention: {
|
||||
feeds: mentionSetup,
|
||||
},
|
||||
codeBlock: {
|
||||
languages: buildListOfLanguages()
|
||||
},
|
||||
math: {
|
||||
engine: "katex",
|
||||
outputType: "span", // or script
|
||||
lazyLoad: async () => {
|
||||
(window as any).katex = (await import("../../services/math.js")).default
|
||||
},
|
||||
forceOutputType: false, // forces output to use outputType
|
||||
enablePreview: true // Enable preview view
|
||||
},
|
||||
mermaid: {
|
||||
lazyLoad: async () => (await import("mermaid")).default, // FIXME
|
||||
config: getMermaidConfig()
|
||||
}
|
||||
});
|
||||
await this.watchdog.create(this.$editor[0]);
|
||||
}
|
||||
|
||||
async doRefresh(note: FNote) {
|
||||
@ -656,12 +577,18 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
|
||||
}
|
||||
|
||||
function getLicenseKey() {
|
||||
const premiumLicenseKey = import.meta.env.VITE_CKEDITOR_KEY;
|
||||
if (!premiumLicenseKey) {
|
||||
logError("CKEditor license key is not set, premium features will not be available.");
|
||||
return "GPL";
|
||||
}
|
||||
async function buildEditor(element: HTMLElement, isClassicEditor: boolean, opts: BuildEditorOptions) {
|
||||
const editorClass = isClassicEditor ? ClassicEditor : PopupEditor;
|
||||
let config = await buildConfig(opts);
|
||||
let editor = await editorClass.create(element, config);
|
||||
|
||||
if (editor.isReadOnly) {
|
||||
editor.destroy();
|
||||
|
||||
opts.forceGplLicense = true;
|
||||
config = await buildConfig(opts);
|
||||
editor = await editorClass.create(element, config);
|
||||
}
|
||||
return editor;
|
||||
|
||||
return premiumLicenseKey;
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/vitest",
|
||||
"types": [
|
||||
"vitest/globals",
|
||||
"vitest/importMeta",
|
||||
"vite/client",
|
||||
"node",
|
||||
|
@ -97,6 +97,12 @@ export default defineConfig(() => ({
|
||||
}
|
||||
}
|
||||
},
|
||||
test: {
|
||||
environment: "happy-dom",
|
||||
setupFiles: [
|
||||
"./src/test/setup.ts"
|
||||
]
|
||||
},
|
||||
optimizeDeps: {
|
||||
exclude: [
|
||||
"@triliumnext/highlightjs"
|
||||
|
@ -1,11 +1,11 @@
|
||||
const path = require("path");
|
||||
const fs = require("fs-extra");
|
||||
const { LOCALES } = require("@triliumnext/commons");
|
||||
import path from "path";
|
||||
import fs from "fs-extra";
|
||||
import { LOCALES } from "@triliumnext/commons";
|
||||
import { PRODUCT_NAME } from "../src/app-info.js";
|
||||
|
||||
const ELECTRON_FORGE_DIR = __dirname;
|
||||
|
||||
const EXECUTABLE_NAME = "trilium"; // keep in sync with server's package.json -> packagerConfig.executableName
|
||||
const PRODUCT_NAME = "TriliumNext Notes";
|
||||
const APP_ICON_PATH = path.join(ELECTRON_FORGE_DIR, "app-icon");
|
||||
|
||||
const extraResourcesForPlatform = getExtraResourcesForPlatform();
|
||||
@ -147,13 +147,13 @@ module.exports = {
|
||||
const isMac = (process.platform === "darwin");
|
||||
let localesToKeep = LOCALES
|
||||
.filter(locale => !locale.contentOnly)
|
||||
.map(locale => locale.electronLocale);
|
||||
.map(locale => locale.electronLocale) as string[];
|
||||
if (!isMac) {
|
||||
localesToKeep = localesToKeep.map(locale => locale.replace("_", "-"))
|
||||
}
|
||||
|
||||
const keptLocales = new Set();
|
||||
const removedLocales = [];
|
||||
const removedLocales: string[] = [];
|
||||
const extension = (isMac ? ".lproj" : ".pak");
|
||||
|
||||
for (const outputPath of packageResult.outputPaths) {
|
||||
@ -169,39 +169,39 @@ module.exports = {
|
||||
console.log(`No locales directory found in '${localeDir}'.`);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
|
||||
const files = fs.readdirSync(localeDir);
|
||||
|
||||
|
||||
for (const file of files) {
|
||||
if (!file.endsWith(extension)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
let localeName = path.basename(file, extension);
|
||||
if (localeName === "en-US" && !isMac) {
|
||||
// If the locale is "en-US" on Windows, we treat it as "en".
|
||||
// This is because the Windows version of Electron uses "en-US.pak" instead of "en.pak".
|
||||
localeName = "en";
|
||||
}
|
||||
|
||||
|
||||
if (localesToKeep.includes(localeName)) {
|
||||
keptLocales.add(localeName);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
const filePath = path.join(localeDir, file);
|
||||
if (isMac) {
|
||||
fs.rm(filePath, { recursive: true });
|
||||
} else {
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
|
||||
|
||||
removedLocales.push(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Removed unused locale files: ${removedLocales.join(", ")}`);
|
||||
console.log(`Removed unused locale files: ${removedLocales.join(", ")}`);
|
||||
|
||||
// Ensure all locales that should be kept are actually present.
|
||||
for (const locale of localesToKeep) {
|
||||
@ -229,7 +229,7 @@ module.exports = {
|
||||
if (TRILIUM_ARTIFACT_NAME_HINT) {
|
||||
fileName = TRILIUM_ARTIFACT_NAME_HINT.replaceAll("/", "-") + extension;
|
||||
}
|
||||
|
||||
|
||||
const outputPath = path.join(outputDir, fileName);
|
||||
console.log(`[Artifact] ${artifactPath} -> ${outputPath}`);
|
||||
fs.copyFileSync(artifactPath, outputPath);
|
||||
@ -240,7 +240,7 @@ module.exports = {
|
||||
};
|
||||
|
||||
function getExtraResourcesForPlatform() {
|
||||
const resources = [];
|
||||
const resources: string[] = [];
|
||||
|
||||
const getScriptResources = () => {
|
||||
const scripts = ["trilium-portable", "trilium-safe-mode", "trilium-no-cert-check"];
|
@ -17,7 +17,7 @@
|
||||
"@types/electron-squirrel-startup": "1.0.2",
|
||||
"@triliumnext/server": "workspace:*",
|
||||
"copy-webpack-plugin": "13.0.0",
|
||||
"electron": "36.4.0",
|
||||
"electron": "36.5.0",
|
||||
"@electron-forge/cli": "7.8.1",
|
||||
"@electron-forge/maker-deb": "7.8.1",
|
||||
"@electron-forge/maker-dmg": "7.8.1",
|
||||
@ -29,7 +29,7 @@
|
||||
"prebuild-install": "^7.1.1"
|
||||
},
|
||||
"config": {
|
||||
"forge": "./electron-forge/forge.config.cjs"
|
||||
"forge": "./electron-forge/forge.config.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"start-prod": "nx build desktop && cross-env TRILIUM_DATA_DIR=data TRILIUM_RESOURCE_DIR=dist TRILIUM_PORT=37841 electron dist/main.js"
|
||||
|
4
apps/desktop/src/app-info.ts
Normal file
4
apps/desktop/src/app-info.ts
Normal file
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* The Electron product name (can be used for the window WMClass or passed down to the Electron packager).
|
||||
*/
|
||||
export const PRODUCT_NAME = "TriliumNext Notes";
|
@ -8,6 +8,7 @@ import options from "@triliumnext/server/src/services/options.js";
|
||||
import electronDebug from "electron-debug";
|
||||
import electronDl from "electron-dl";
|
||||
import { deferred } from "@triliumnext/server/src/services/utils.js";
|
||||
import { PRODUCT_NAME } from "./app-info";
|
||||
|
||||
async function main() {
|
||||
const serverInitializedPromise = deferred<void>();
|
||||
@ -28,6 +29,7 @@ async function main() {
|
||||
// Electron 36 crashes with "Using GTK 2/3 and GTK 4 in the same process is not supported" on some distributions.
|
||||
// See https://github.com/electron/electron/issues/46538 for more info.
|
||||
if (process.platform === "linux") {
|
||||
electron.app.setName(PRODUCT_NAME);
|
||||
electron.app.commandLine.appendSwitch("gtk-version", "3");
|
||||
}
|
||||
|
||||
|
23
apps/desktop/tsconfig.forge.json
Normal file
23
apps/desktop/tsconfig.forge.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"target": "ES2020",
|
||||
"outDir": "dist",
|
||||
"types": [
|
||||
"node",
|
||||
"express"
|
||||
],
|
||||
"tsBuildInfoFile": "dist/tsconfig.app.tsbuildinfo"
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"../server/src/*.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"eslint.config.js",
|
||||
"eslint.config.cjs",
|
||||
"eslint.config.mjs"
|
||||
]
|
||||
}
|
@ -11,6 +11,9 @@
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.forge.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
"@triliumnext/desktop": "workspace:*",
|
||||
"@types/fs-extra": "11.0.4",
|
||||
"copy-webpack-plugin": "13.0.0",
|
||||
"electron": "36.4.0",
|
||||
"electron": "36.5.0",
|
||||
"fs-extra": "11.3.0"
|
||||
},
|
||||
"nx": {
|
||||
|
@ -59,7 +59,7 @@
|
||||
"debounce": "2.2.0",
|
||||
"debug": "4.4.1",
|
||||
"ejs": "3.1.10",
|
||||
"electron": "36.4.0",
|
||||
"electron": "36.5.0",
|
||||
"electron-debug": "4.1.0",
|
||||
"electron-window-state": "5.0.3",
|
||||
"escape-html": "1.0.3",
|
||||
|
@ -5,7 +5,7 @@ import cls from "../services/cls.js";
|
||||
import sql from "../services/sql.js";
|
||||
import becca from "../becca/becca.js";
|
||||
import type { Request, Response, Router } from "express";
|
||||
import { safeExtractMessageAndStackFromError } from "../services/utils.js";
|
||||
import { safeExtractMessageAndStackFromError, normalizeCustomHandlerPattern } from "../services/utils.js";
|
||||
|
||||
function handleRequest(req: Request, res: Response) {
|
||||
|
||||
@ -38,11 +38,19 @@ function handleRequest(req: Request, res: Response) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const regex = new RegExp(`^${attr.value}$`);
|
||||
let match;
|
||||
// Get normalized patterns to handle both trailing slash cases
|
||||
const patterns = normalizeCustomHandlerPattern(attr.value);
|
||||
let match: RegExpMatchArray | null = null;
|
||||
|
||||
try {
|
||||
match = path.match(regex);
|
||||
// Try each pattern until we find a match
|
||||
for (const pattern of patterns) {
|
||||
const regex = new RegExp(`^${pattern}$`);
|
||||
match = path.match(regex);
|
||||
if (match) {
|
||||
break; // Found a match, exit pattern loop
|
||||
}
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
const [errMessage, errStack] = safeExtractMessageAndStackFromError(e);
|
||||
log.error(`Testing path for label '${attr.attributeId}', regex '${attr.value}' failed with error: ${errMessage}, stack: ${errStack}`);
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
import optionService from "./options.js";
|
||||
import config from "./config.js";
|
||||
import { normalizeUrl } from "./utils.js";
|
||||
|
||||
/*
|
||||
* Primary configuration for sync is in the options (document), but we allow to override
|
||||
@ -17,7 +18,10 @@ function get(name: keyof typeof config.Sync) {
|
||||
export default {
|
||||
// env variable is the easiest way to guarantee we won't overwrite prod data during development
|
||||
// after copying prod document/data directory
|
||||
getSyncServerHost: () => get("syncServerHost"),
|
||||
getSyncServerHost: () => {
|
||||
const host = get("syncServerHost");
|
||||
return host ? normalizeUrl(host) : host;
|
||||
},
|
||||
isSyncSetup: () => {
|
||||
const syncServerHost = get("syncServerHost");
|
||||
|
||||
|
@ -628,3 +628,56 @@ describe("#formatDownloadTitle", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#normalizeUrl", () => {
|
||||
const testCases: TestCase<typeof utils.normalizeUrl>[] = [
|
||||
[ "should remove trailing slash from simple URL", [ "https://example.com/" ], "https://example.com" ],
|
||||
[ "should remove trailing slash from URL with path", [ "https://example.com/path/" ], "https://example.com/path" ],
|
||||
[ "should preserve URL without trailing slash", [ "https://example.com" ], "https://example.com" ],
|
||||
[ "should preserve URL without trailing slash with path", [ "https://example.com/path" ], "https://example.com/path" ],
|
||||
[ "should preserve protocol-only URLs", [ "https://" ], "https://" ],
|
||||
[ "should preserve protocol-only URLs", [ "http://" ], "http://" ],
|
||||
[ "should fix double slashes in path", [ "https://example.com//api//test" ], "https://example.com/api/test" ],
|
||||
[ "should handle multiple double slashes", [ "https://example.com///api///test" ], "https://example.com/api/test" ],
|
||||
[ "should handle trailing slash with double slashes", [ "https://example.com//api//" ], "https://example.com/api" ],
|
||||
[ "should preserve protocol double slash", [ "https://example.com/api" ], "https://example.com/api" ],
|
||||
[ "should handle empty string", [ "" ], "" ],
|
||||
[ "should handle whitespace-only string", [ " " ], "" ],
|
||||
[ "should trim whitespace", [ " https://example.com/ " ], "https://example.com" ],
|
||||
[ "should handle null as empty", [ null as any ], null ],
|
||||
[ "should handle undefined as empty", [ undefined as any ], undefined ]
|
||||
];
|
||||
|
||||
testCases.forEach((testCase) => {
|
||||
const [ desc, fnParams, expected ] = testCase;
|
||||
it(desc, () => {
|
||||
const result = utils.normalizeUrl(...fnParams);
|
||||
expect(result).toStrictEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#normalizeCustomHandlerPattern", () => {
|
||||
const testCases: TestCase<typeof utils.normalizeCustomHandlerPattern>[] = [
|
||||
[ "should handle pattern without ending - add both versions", [ "foo" ], [ "foo", "foo/" ] ],
|
||||
[ "should handle pattern with trailing slash - add both versions", [ "foo/" ], [ "foo", "foo/" ] ],
|
||||
[ "should handle pattern ending with $ - add optional slash", [ "foo$" ], [ "foo/?$" ] ],
|
||||
[ "should handle pattern with trailing slash and $ - add both versions", [ "foo/$" ], [ "foo$", "foo/$" ] ],
|
||||
[ "should preserve existing optional slash pattern", [ "foo/?$" ], [ "foo/?$" ] ],
|
||||
[ "should preserve existing optional slash pattern (alternative)", [ "foo/?)" ], [ "foo/?)" ] ],
|
||||
[ "should handle regex pattern with special chars", [ "api/[a-z]+$" ], [ "api/[a-z]+/?$" ] ],
|
||||
[ "should handle complex regex pattern", [ "user/([0-9]+)/profile$" ], [ "user/([0-9]+)/profile/?$" ] ],
|
||||
[ "should handle empty string", [ "" ], [ "" ] ],
|
||||
[ "should handle whitespace-only string", [ " " ], [ "" ] ],
|
||||
[ "should handle null", [ null as any ], [ null ] ],
|
||||
[ "should handle undefined", [ undefined as any ], [ undefined ] ]
|
||||
];
|
||||
|
||||
testCases.forEach((testCase) => {
|
||||
const [ desc, fnParams, expected ] = testCase;
|
||||
it(desc, () => {
|
||||
const result = utils.normalizeCustomHandlerPattern(...fnParams);
|
||||
expect(result).toStrictEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -375,6 +375,85 @@ export function safeExtractMessageAndStackFromError(err: unknown): [errMessage:
|
||||
return (err instanceof Error) ? [err.message, err.stack] as const : ["Unknown Error", undefined] as const;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes URL by removing trailing slashes and fixing double slashes.
|
||||
* Preserves the protocol (http://, https://) but removes trailing slashes from the rest.
|
||||
*
|
||||
* @param url The URL to normalize
|
||||
* @returns The normalized URL without trailing slashes
|
||||
*/
|
||||
export function normalizeUrl(url: string | null | undefined): string | null | undefined {
|
||||
if (!url || typeof url !== 'string') {
|
||||
return url;
|
||||
}
|
||||
|
||||
// Trim whitespace
|
||||
url = url.trim();
|
||||
|
||||
if (!url) {
|
||||
return url;
|
||||
}
|
||||
|
||||
// Fix double slashes (except in protocol) first
|
||||
url = url.replace(/([^:]\/)\/+/g, '$1');
|
||||
|
||||
// Remove trailing slash, but preserve protocol
|
||||
if (url.endsWith('/') && !url.match(/^https?:\/\/$/)) {
|
||||
url = url.slice(0, -1);
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a path pattern for custom request handlers.
|
||||
* Ensures both trailing slash and non-trailing slash versions are handled.
|
||||
*
|
||||
* @param pattern The original pattern from customRequestHandler attribute
|
||||
* @returns An array of patterns to match both with and without trailing slash
|
||||
*/
|
||||
export function normalizeCustomHandlerPattern(pattern: string | null | undefined): (string | null | undefined)[] {
|
||||
if (!pattern || typeof pattern !== 'string') {
|
||||
return [pattern];
|
||||
}
|
||||
|
||||
pattern = pattern.trim();
|
||||
|
||||
if (!pattern) {
|
||||
return [pattern];
|
||||
}
|
||||
|
||||
// If pattern already ends with optional trailing slash, return as-is
|
||||
if (pattern.endsWith('/?$') || pattern.endsWith('/?)')) {
|
||||
return [pattern];
|
||||
}
|
||||
|
||||
// If pattern ends with $, handle it specially
|
||||
if (pattern.endsWith('$')) {
|
||||
const basePattern = pattern.slice(0, -1);
|
||||
|
||||
// If already ends with slash, create both versions
|
||||
if (basePattern.endsWith('/')) {
|
||||
const withoutSlash = basePattern.slice(0, -1) + '$';
|
||||
const withSlash = pattern;
|
||||
return [withoutSlash, withSlash];
|
||||
} else {
|
||||
// Add optional trailing slash
|
||||
const withSlash = basePattern + '/?$';
|
||||
return [withSlash];
|
||||
}
|
||||
}
|
||||
|
||||
// For patterns without $, add both versions
|
||||
if (pattern.endsWith('/')) {
|
||||
const withoutSlash = pattern.slice(0, -1);
|
||||
return [withoutSlash, pattern];
|
||||
} else {
|
||||
const withSlash = pattern + '/';
|
||||
return [pattern, withSlash];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
compareVersions,
|
||||
@ -400,6 +479,8 @@ export default {
|
||||
md5,
|
||||
newEntityId,
|
||||
normalize,
|
||||
normalizeCustomHandlerPattern,
|
||||
normalizeUrl,
|
||||
quoteRegex,
|
||||
randomSecureToken,
|
||||
randomString,
|
||||
|
@ -1,38 +1,11 @@
|
||||
# sv
|
||||
# apps/website
|
||||
|
||||
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
|
||||
|
||||
## Creating a project
|
||||
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
```bash
|
||||
# create a new project in the current directory
|
||||
npx sv create
|
||||
|
||||
# create a new project in my-app
|
||||
npx sv create my-app
|
||||
```
|
||||
Landing page for Trilium Notes powered by [Svelte](https://github.com/sveltejs/cli) and [Tailwind CSS](https://tailwindcss.com/).
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
To run a dev server that will hot-reload changes: `pnpm nx run website:dev`
|
||||
|
||||
## Building
|
||||
|
||||
To create a production version of your app:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can preview the production build with `npm run preview`.
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
||||
To create a production build: `pnpm nx run website:build`
|
||||
|
@ -9,4 +9,6 @@ The license key is stored in the application and it enables the use of the previ
|
||||
|
||||
## Can I opt out of these features?
|
||||
|
||||
At this moment there is no way to disable this features, apart from manually modifying the source code. If this is a problem, [let us know](../../Troubleshooting/Reporting%20issues.md).
|
||||
At this moment there is no way to disable these features, apart from manually modifying the source code. If this is a problem, [let us know](../../Troubleshooting/Reporting%20issues.md).
|
||||
|
||||
If you have the possibility of rebuilding the source code (e.g. if a package maintainer), then modify `VITE_CKEDITOR_KEY` in `apps/client/.env` to be `GPL`.
|
@ -169,8 +169,6 @@
|
||||
comment = meta.description;
|
||||
desktopName = "TriliumNext Notes";
|
||||
categories = [ "Office" ];
|
||||
# TODO: electron-forge build has this set to PRODUCT_NAME (forge.config.cjs)
|
||||
# But the plain build doesn't set this (or the app icon).
|
||||
startupWMClass = "TriliumNext Notes";
|
||||
})
|
||||
];
|
||||
|
25
package.json
25
package.json
@ -27,24 +27,25 @@
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@electron/rebuild": "4.0.1",
|
||||
"@nx/devkit": "21.2.0",
|
||||
"@nx/esbuild": "21.2.0",
|
||||
"@nx/eslint": "21.2.0",
|
||||
"@nx/eslint-plugin": "21.2.0",
|
||||
"@nx/express": "21.2.0",
|
||||
"@nx/js": "21.2.0",
|
||||
"@nx/node": "21.2.0",
|
||||
"@nx/playwright": "21.2.0",
|
||||
"@nx/vite": "21.2.0",
|
||||
"@nx/web": "21.2.0",
|
||||
"@nx/devkit": "21.2.1",
|
||||
"@nx/esbuild": "21.2.1",
|
||||
"@nx/eslint": "21.2.1",
|
||||
"@nx/eslint-plugin": "21.2.1",
|
||||
"@nx/express": "21.2.1",
|
||||
"@nx/js": "21.2.1",
|
||||
"@nx/node": "21.2.1",
|
||||
"@nx/playwright": "21.2.1",
|
||||
"@nx/vite": "21.2.1",
|
||||
"@nx/web": "21.2.1",
|
||||
"@playwright/test": "^1.36.0",
|
||||
"@triliumnext/server": "workspace:*",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/node": "22.15.31",
|
||||
"@types/node": "22.15.32",
|
||||
"@vitest/coverage-v8": "^3.0.5",
|
||||
"@vitest/ui": "^3.0.0",
|
||||
"chalk": "5.4.1",
|
||||
"cross-env": "7.0.3",
|
||||
"dpdm": "3.14.0",
|
||||
"esbuild": "^0.25.0",
|
||||
"eslint": "^9.8.0",
|
||||
"eslint-config-prettier": "^10.0.0",
|
||||
@ -53,7 +54,7 @@
|
||||
"jiti": "2.4.2",
|
||||
"jsdom": "~26.1.0",
|
||||
"jsonc-eslint-parser": "^2.1.0",
|
||||
"nx": "21.2.0",
|
||||
"nx": "21.2.1",
|
||||
"react-refresh": "^0.17.0",
|
||||
"rollup-plugin-webpack-stats": "2.0.7",
|
||||
"tslib": "^2.3.0",
|
||||
|
@ -11,31 +11,12 @@ export default defineConfig( {
|
||||
svg()
|
||||
],
|
||||
test: {
|
||||
browser: {
|
||||
enabled: true,
|
||||
name: 'chrome',
|
||||
provider: 'webdriverio',
|
||||
providerOptions: {},
|
||||
headless: true,
|
||||
ui: false
|
||||
},
|
||||
environment: "happy-dom",
|
||||
include: [
|
||||
'tests/**/*.[jt]s'
|
||||
],
|
||||
globals: true,
|
||||
watch: false,
|
||||
passWithNoTests: true,
|
||||
coverage: {
|
||||
thresholds: {
|
||||
lines: 100,
|
||||
functions: 100,
|
||||
branches: 100,
|
||||
statements: 100
|
||||
},
|
||||
provider: 'istanbul',
|
||||
include: [
|
||||
'src'
|
||||
]
|
||||
}
|
||||
passWithNoTests: true
|
||||
}
|
||||
} );
|
||||
|
@ -1,7 +1,8 @@
|
||||
import "ckeditor5/ckeditor5.css";
|
||||
import "./theme/code_block_toolbar.css";
|
||||
import { COMMON_PLUGINS, CORE_PLUGINS, POPUP_EDITOR_PLUGINS } from "./plugins";
|
||||
import { COMMON_PLUGINS, CORE_PLUGINS, POPUP_EDITOR_PLUGINS, PREMIUM_PLUGINS } from "./plugins";
|
||||
import { BalloonEditor, DecoupledEditor, FindAndReplaceEditing, FindCommand } from "ckeditor5";
|
||||
import "./translation_overrides.js";
|
||||
export { EditorWatchdog } from "ckeditor5";
|
||||
export type { EditorConfig, MentionFeed, MentionFeedObjectItem, Node, Position, Element, WatchdogConfig } from "ckeditor5";
|
||||
export type { TemplateDefinition } from "ckeditor5-premium-features";
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Autoformat, AutoLink, BlockQuote, BlockToolbar, Bold, CKFinderUploadAdapter, Clipboard, Code, CodeBlock, Enter, FindAndReplace, Font, FontBackgroundColor, FontColor, GeneralHtmlSupport, Heading, HeadingButtonsUI, HorizontalLine, Image, ImageCaption, ImageInline, ImageResize, ImageStyle, ImageToolbar, ImageUpload, Alignment, Indent, IndentBlock, Italic, Link, List, ListProperties, Mention, PageBreak, Paragraph, ParagraphButtonUI, PasteFromOffice, PictureEditing, RemoveFormat, SelectAll, ShiftEnter, SpecialCharacters, SpecialCharactersEssentials, Strikethrough, Style, Subscript, Superscript, Table, TableCaption, TableCellProperties, TableColumnResize, TableProperties, TableSelection, TableToolbar, TextPartLanguage, TextTransformation, TodoList, Typing, Underline, Undo, Bookmark, Emoji } from "ckeditor5";
|
||||
import { Autoformat, AutoLink, BlockQuote, BlockToolbar, Bold, CKFinderUploadAdapter, Clipboard, Code, CodeBlock, Enter, FindAndReplace, Font, FontBackgroundColor, FontColor, GeneralHtmlSupport, Heading, HeadingButtonsUI, HorizontalLine, Image, ImageCaption, ImageInline, ImageResize, ImageStyle, ImageToolbar, ImageUpload, Alignment, Indent, IndentBlock, Italic, Link, List, ListProperties, Mention, PageBreak, Paragraph, ParagraphButtonUI, PasteFromOffice, PictureEditing, RemoveFormat, SelectAll, ShiftEnter, SpecialCharacters, SpecialCharactersEssentials, Strikethrough, Style, Subscript, Superscript, Table, TableCaption, TableCellProperties, TableColumnResize, TableProperties, TableSelection, TableToolbar, TextPartLanguage, TextTransformation, TodoList, Typing, Underline, Undo, Bookmark, Emoji, Notification } from "ckeditor5";
|
||||
import { SlashCommand, Template } from "ckeditor5-premium-features";
|
||||
import type { Plugin } from "ckeditor5";
|
||||
import CutToNotePlugin from "./plugins/cuttonote.js";
|
||||
@ -148,8 +148,7 @@ export const COMMON_PLUGINS: typeof Plugin[] = [
|
||||
Emoji,
|
||||
|
||||
...TRILIUM_PLUGINS,
|
||||
...EXTERNAL_PLUGINS,
|
||||
...PREMIUM_PLUGINS
|
||||
...EXTERNAL_PLUGINS
|
||||
];
|
||||
|
||||
/**
|
||||
@ -157,5 +156,5 @@ export const COMMON_PLUGINS: typeof Plugin[] = [
|
||||
*/
|
||||
export const POPUP_EDITOR_PLUGINS: typeof Plugin[] = [
|
||||
...COMMON_PLUGINS,
|
||||
BlockToolbar
|
||||
BlockToolbar,
|
||||
];
|
||||
|
8
packages/ckeditor5/src/translation_overrides.ts
Normal file
8
packages/ckeditor5/src/translation_overrides.ts
Normal file
@ -0,0 +1,8 @@
|
||||
window.CKEDITOR_TRANSLATIONS = {
|
||||
en: {
|
||||
dictionary: {
|
||||
"Insert template": "Insert text snippet",
|
||||
"Search template": "Search text snippet"
|
||||
}
|
||||
}
|
||||
};
|
37
packages/ckeditor5/tests/templates.ts
Normal file
37
packages/ckeditor5/tests/templates.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { it } from "vitest";
|
||||
import { describe } from "vitest";
|
||||
import { ClassicEditor } from "../src/index.js";
|
||||
import { type BalloonEditor, type ButtonView, type Editor } from "ckeditor5";
|
||||
import { beforeEach } from "vitest";
|
||||
import { expect } from "vitest";
|
||||
|
||||
describe("Text snippets", () => {
|
||||
let editorElement: HTMLDivElement;
|
||||
let editor: Editor;
|
||||
|
||||
beforeEach(async () => {
|
||||
editorElement = document.createElement( 'div' );
|
||||
document.body.appendChild( editorElement );
|
||||
|
||||
console.log("Trigger each");
|
||||
|
||||
editor = await ClassicEditor.create(editorElement, {
|
||||
licenseKey: "GPL",
|
||||
toolbar: {
|
||||
items: [
|
||||
"insertTemplate"
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("uses correct translations", () => {
|
||||
const itemsWithButtonView = Array.from(editor.ui.view.toolbar?.items)
|
||||
.filter(item => "buttonView" in item)
|
||||
.map(item => (item.buttonView as ButtonView).label);
|
||||
|
||||
expect(itemsWithButtonView).not.toContain("Insert template");
|
||||
expect(itemsWithButtonView).toContain("Insert text snippet");
|
||||
});
|
||||
});
|
||||
|
@ -23,9 +23,9 @@
|
||||
"@codemirror/lang-css": "6.3.1",
|
||||
"@codemirror/lang-html": "6.4.9",
|
||||
"@codemirror/lang-javascript": "6.2.4",
|
||||
"@codemirror/lang-json": "6.0.1",
|
||||
"@codemirror/lang-json": "6.0.2",
|
||||
"@codemirror/lang-markdown": "6.3.3",
|
||||
"@codemirror/lang-php": "6.0.1",
|
||||
"@codemirror/lang-php": "6.0.2",
|
||||
"@codemirror/lang-vue": "0.1.3",
|
||||
"@codemirror/lang-xml": "6.1.0",
|
||||
"@codemirror/legacy-modes": "6.5.1",
|
||||
|
986
pnpm-lock.yaml
generated
986
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user