mirror of
https://github.com/zadam/trilium.git
synced 2026-01-07 16:02:13 +01:00
Compare commits
407 Commits
v0.90.6-be
...
v0.90.10-b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da1cf4d6ed | ||
|
|
dd7c2084fa | ||
|
|
4f5d874028 | ||
|
|
611fb90a52 | ||
|
|
75e554d86b | ||
|
|
0db1a63cef | ||
|
|
4ffc6f716c | ||
|
|
fa3200ba8f | ||
|
|
bff9bedc44 | ||
|
|
f8777b0de1 | ||
|
|
48e6c1a33d | ||
|
|
4c43ac5bdd | ||
|
|
45ccc7562e | ||
|
|
e72eb5f27c | ||
|
|
d1404492a7 | ||
|
|
238c9c6f0d | ||
|
|
443f02a78e | ||
|
|
5fbd052138 | ||
|
|
bc84a71929 | ||
|
|
a514a51fff | ||
|
|
24022834e2 | ||
|
|
9fdc84d91f | ||
|
|
9c27672794 | ||
|
|
f37fa3723b | ||
|
|
b14065d442 | ||
|
|
1554e25283 | ||
|
|
4e945583a1 | ||
|
|
92c588dc98 | ||
|
|
5c66e3fd04 | ||
|
|
e508313f21 | ||
|
|
df3f51d1f3 | ||
|
|
0a6815e448 | ||
|
|
293db6962e | ||
|
|
eb05c5b919 | ||
|
|
bbaed45f6b | ||
|
|
aa7d7b3afd | ||
|
|
fc4797d04f | ||
|
|
c2baa4b752 | ||
|
|
faeefc75ba | ||
|
|
d0904c1051 | ||
|
|
11a82e62f1 | ||
|
|
7b24f7e332 | ||
|
|
7f17f93767 | ||
|
|
cdd5a17fce | ||
|
|
dbca50d9b0 | ||
|
|
57a86c75d8 | ||
|
|
9e3c1b46cd | ||
|
|
00209ec77a | ||
|
|
dfa4f3cd84 | ||
|
|
3af29a78dc | ||
|
|
4d783f1879 | ||
|
|
c3e10b2b76 | ||
|
|
f57ab4b9f0 | ||
|
|
a690155d7e | ||
|
|
cc0b3db424 | ||
|
|
ae60f8c842 | ||
|
|
90dffdc6ed | ||
|
|
ac13291744 | ||
|
|
bbc038f254 | ||
|
|
f8df3a6933 | ||
|
|
b10e2d9ec4 | ||
|
|
2387bbd17f | ||
|
|
f13d88c3c0 | ||
|
|
2459bbf341 | ||
|
|
60426ea487 | ||
|
|
b112cb609f | ||
|
|
b9ebc66122 | ||
|
|
2f4ed92346 | ||
|
|
d3d001d8ea | ||
|
|
70cee7dbf6 | ||
|
|
36fde2b03d | ||
|
|
bda8173932 | ||
|
|
48f9f072b4 | ||
|
|
9c55203ea0 | ||
|
|
dbb5e0e971 | ||
|
|
b8eb09b46b | ||
|
|
5682b2d819 | ||
|
|
5109c07e9c | ||
|
|
b8569ea243 | ||
|
|
52bc28def7 | ||
|
|
e65d4cdfbf | ||
|
|
96b9042559 | ||
|
|
e68d070320 | ||
|
|
ef5f2c680b | ||
|
|
6717b1b4ae | ||
|
|
41e3163595 | ||
|
|
514653fb50 | ||
|
|
e843f1adc1 | ||
|
|
83f5b47c99 | ||
|
|
2fdff29067 | ||
|
|
0d270cbeb6 | ||
|
|
f947a039b9 | ||
|
|
d2235a185b | ||
|
|
87bc142552 | ||
|
|
1a25f60264 | ||
|
|
fe4dbae079 | ||
|
|
e1ae014b74 | ||
|
|
7952a5a81e | ||
|
|
60b6f7df89 | ||
|
|
7354fb5b4a | ||
|
|
1fb0b74f76 | ||
|
|
9e3b915612 | ||
|
|
7505db220e | ||
|
|
a3932376f3 | ||
|
|
3a609d54ab | ||
|
|
c4bd4eb440 | ||
|
|
e931df721d | ||
|
|
1e9324c303 | ||
|
|
6c4513fb2e | ||
|
|
c7e1362105 | ||
|
|
acf37f9327 | ||
|
|
f80cf0aa02 | ||
|
|
6078620bf1 | ||
|
|
579b3f4ca0 | ||
|
|
bf28005f46 | ||
|
|
c81b847b61 | ||
|
|
88cd2ac25c | ||
|
|
e3e6f56a88 | ||
|
|
0768a2a0a3 | ||
|
|
84d216da54 | ||
|
|
391f518c01 | ||
|
|
2324c9a13b | ||
|
|
6799c44e22 | ||
|
|
f0052d56b7 | ||
|
|
53822fd47f | ||
|
|
b02c4b54e5 | ||
|
|
27f07ee604 | ||
|
|
03a23d15f9 | ||
|
|
70d55097ee | ||
|
|
560467bdba | ||
|
|
cb4fe4481f | ||
|
|
eee088316d | ||
|
|
81ca0a3776 | ||
|
|
48b0af1bba | ||
|
|
43ef452d44 | ||
|
|
70ebf1a08f | ||
|
|
9f6f0f5d60 | ||
|
|
af67362ad6 | ||
|
|
77550f3087 | ||
|
|
5813282248 | ||
|
|
e77b223508 | ||
|
|
7aafdce629 | ||
|
|
a2f0cb394a | ||
|
|
e8d1518965 | ||
|
|
8b333b32af | ||
|
|
cda369ed4d | ||
|
|
b5bc93d794 | ||
|
|
b96047e962 | ||
|
|
31ccbb0d23 | ||
|
|
cb9403535d | ||
|
|
b5ee90a1d2 | ||
|
|
9ed7eb977e | ||
|
|
8cc487da7c | ||
|
|
ae593ea363 | ||
|
|
26e4decaec | ||
|
|
28f6712a4f | ||
|
|
93efce4023 | ||
|
|
689b3a3079 | ||
|
|
4ad725842e | ||
|
|
d4956ad3a2 | ||
|
|
c7b7c68a05 | ||
|
|
cab1d7d353 | ||
|
|
7957c6d34e | ||
|
|
c18c972a57 | ||
|
|
29a700f731 | ||
|
|
815eab26f6 | ||
|
|
103da23b5a | ||
|
|
ba1d82bc0a | ||
|
|
21f8a29761 | ||
|
|
f38870b27d | ||
|
|
56a6d27240 | ||
|
|
38e5ef2c7d | ||
|
|
e29d600517 | ||
|
|
42605fbbad | ||
|
|
11ca427a28 | ||
|
|
28d8088763 | ||
|
|
664c4789c0 | ||
|
|
7c5667b457 | ||
|
|
0afd22e196 | ||
|
|
b3abee71b7 | ||
|
|
9bd5596b2a | ||
|
|
e0e3c15e6e | ||
|
|
31396264fa | ||
|
|
b1aada22b5 | ||
|
|
d7eaf72a6d | ||
|
|
59df442676 | ||
|
|
9770db7f3c | ||
|
|
8c36cea71b | ||
|
|
b03f40f1f9 | ||
|
|
00dba7bef4 | ||
|
|
4186f3d136 | ||
|
|
529502524d | ||
|
|
7c518e9512 | ||
|
|
5e2d1bc124 | ||
|
|
7dfe6f276e | ||
|
|
858db68d66 | ||
|
|
b72f46f108 | ||
|
|
83dbe0539e | ||
|
|
87e0cf55f1 | ||
|
|
8315d5c778 | ||
|
|
61bd7dca18 | ||
|
|
4faf27364f | ||
|
|
52a6d0b48a | ||
|
|
cddc9a7b6a | ||
|
|
75d019863f | ||
|
|
849a6a3aef | ||
|
|
e7378306a2 | ||
|
|
1277dfc5d5 | ||
|
|
ae680847dc | ||
|
|
a5fd57308a | ||
|
|
fa769df7b0 | ||
|
|
8136a2972e | ||
|
|
8c8c3974f3 | ||
|
|
e81bfa3693 | ||
|
|
a857f4816f | ||
|
|
ade34f9745 | ||
|
|
fd66cb930d | ||
|
|
182d9afac1 | ||
|
|
4e6ef0be95 | ||
|
|
88961ea93f | ||
|
|
a3f2946a17 | ||
|
|
8ae5f9ea9b | ||
|
|
9ec2508f09 | ||
|
|
bae63b08a2 | ||
|
|
729a188528 | ||
|
|
2c5a5acffa | ||
|
|
e45c5f429d | ||
|
|
5c44ac5ad8 | ||
|
|
b44c2f5ebf | ||
|
|
6e64cab2d4 | ||
|
|
9c8cf0bc09 | ||
|
|
bafc556b00 | ||
|
|
0ec3232c81 | ||
|
|
469c1ceb07 | ||
|
|
5e80f120c9 | ||
|
|
8faa26b663 | ||
|
|
201e2fcfdb | ||
|
|
5678487a16 | ||
|
|
6646e8c311 | ||
|
|
96fc4d3280 | ||
|
|
0a69189b9b | ||
|
|
cb3ea3fb75 | ||
|
|
787a2d1957 | ||
|
|
89e1275dda | ||
|
|
ddc927d617 | ||
|
|
d46963e496 | ||
|
|
c37a51c6d0 | ||
|
|
91095e8d4e | ||
|
|
d4fa7e3fd2 | ||
|
|
9204f0735c | ||
|
|
cfc32a14e0 | ||
|
|
270aa52591 | ||
|
|
99a5913391 | ||
|
|
c3d6165aff | ||
|
|
9220616bb5 | ||
|
|
eb9b4aee21 | ||
|
|
d9a7671830 | ||
|
|
353c410f0e | ||
|
|
7ca4cddc58 | ||
|
|
12543f762b | ||
|
|
ec7fabcb58 | ||
|
|
5d08f2bc5a | ||
|
|
a557b82c83 | ||
|
|
62a884cb5a | ||
|
|
88875a3375 | ||
|
|
cd547ebdaf | ||
|
|
e511d6aecf | ||
|
|
9b6148dec3 | ||
|
|
29a1aad45a | ||
|
|
a9fce727d4 | ||
|
|
9e85d2cdbd | ||
|
|
c4f430276a | ||
|
|
fdf57b6cfa | ||
|
|
0a9e76abb7 | ||
|
|
3e085e5cae | ||
|
|
73cd54e75c | ||
|
|
28b8e59b4d | ||
|
|
51b6725b91 | ||
|
|
93ca30fda0 | ||
|
|
8c222368de | ||
|
|
a3aa6b0628 | ||
|
|
d0061794dd | ||
|
|
17c8708b54 | ||
|
|
da0ddbb80d | ||
|
|
138b4b1a95 | ||
|
|
1299072ea9 | ||
|
|
bb5893f989 | ||
|
|
7b764fd7d8 | ||
|
|
ac2fc49a48 | ||
|
|
c0abf6e2c5 | ||
|
|
85ff0bac55 | ||
|
|
3671b83a9c | ||
|
|
6970bf4fc1 | ||
|
|
e5cce1b1bc | ||
|
|
faa8eca810 | ||
|
|
84bf0cbae5 | ||
|
|
a77264208e | ||
|
|
ee06db8c8f | ||
|
|
e330b43750 | ||
|
|
f5b21498bf | ||
|
|
83388ecf1c | ||
|
|
87de631af4 | ||
|
|
e4271d3945 | ||
|
|
65b8a2f97c | ||
|
|
0656154c35 | ||
|
|
b3f682144b | ||
|
|
d3c3e157c7 | ||
|
|
2534402157 | ||
|
|
ec80ba5caf | ||
|
|
26621b6336 | ||
|
|
d961e3cdf1 | ||
|
|
7ef399912b | ||
|
|
9fc789676f | ||
|
|
f01dc3d102 | ||
|
|
501bf624cc | ||
|
|
f8605688ab | ||
|
|
590a9bef2d | ||
|
|
419756d19e | ||
|
|
93ce81b355 | ||
|
|
8bfa4461b0 | ||
|
|
648feb82f8 | ||
|
|
74645e12fc | ||
|
|
71de1a0b55 | ||
|
|
694f3cb174 | ||
|
|
a2bc5073d0 | ||
|
|
3134ef7c03 | ||
|
|
8e665e27e7 | ||
|
|
e6fbf62cf9 | ||
|
|
ce8b5e33da | ||
|
|
b63b603d64 | ||
|
|
31aa6feb0c | ||
|
|
98c9e25124 | ||
|
|
bb97e1a661 | ||
|
|
a4341a5cac | ||
|
|
fe844d4f8c | ||
|
|
447cf60afb | ||
|
|
7a11f9aaff | ||
|
|
527591f651 | ||
|
|
fca6f99870 | ||
|
|
8c97f0bec7 | ||
|
|
58ee801e57 | ||
|
|
c1ce578018 | ||
|
|
464d8417f5 | ||
|
|
44bd008829 | ||
|
|
f3b7261748 | ||
|
|
5d579fee68 | ||
|
|
d7ab99013c | ||
|
|
a3a5339048 | ||
|
|
d95a23de28 | ||
|
|
744d953822 | ||
|
|
f8e5717b80 | ||
|
|
3ad2d1a309 | ||
|
|
7186222393 | ||
|
|
d66c07717c | ||
|
|
132dd7514a | ||
|
|
aca9d0f1ef | ||
|
|
eabceae6f1 | ||
|
|
3a5b05e5c6 | ||
|
|
11e5f6a1c1 | ||
|
|
bec9acdc3f | ||
|
|
37079b7388 | ||
|
|
22b7bf826b | ||
|
|
7cc71a4fdb | ||
|
|
fb28b9d36d | ||
|
|
720087f082 | ||
|
|
3bdb87d3aa | ||
|
|
e4066ba164 | ||
|
|
a0fdaabb1f | ||
|
|
38ac4318b9 | ||
|
|
e382a32ebd | ||
|
|
8074245758 | ||
|
|
f26a04216a | ||
|
|
e565d29490 | ||
|
|
947c50353d | ||
|
|
83851a61d5 | ||
|
|
893a1e3723 | ||
|
|
6bf18b6837 | ||
|
|
94857d8802 | ||
|
|
58d1e77ac1 | ||
|
|
1fbc65b007 | ||
|
|
c25c8c753d | ||
|
|
317b7b4c59 | ||
|
|
68042994e0 | ||
|
|
d00c028a0c | ||
|
|
a9e6c887f2 | ||
|
|
69f9457bda | ||
|
|
0fed54f1c3 | ||
|
|
b45fde2e5f | ||
|
|
c1010a79f9 | ||
|
|
c33154d128 | ||
|
|
c0d613c46d | ||
|
|
4f7bbee769 | ||
|
|
7a567583f2 | ||
|
|
0ac1f071cd | ||
|
|
114e7809fe | ||
|
|
89e073bf28 | ||
|
|
490ae1c9fd | ||
|
|
2a27383682 | ||
|
|
00f8a43b0f | ||
|
|
759ac4a4c8 | ||
|
|
e66b865cd6 | ||
|
|
78bfc3341b | ||
|
|
898afb7ed7 | ||
|
|
bc4444d132 | ||
|
|
56259616cb | ||
|
|
a50e5cc733 | ||
|
|
b790dabc8c |
143
.github/workflows/main-docker-alpine.yml
vendored
143
.github/workflows/main-docker-alpine.yml
vendored
@@ -1,143 +0,0 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GHCR_REGISTRY: ghcr.io
|
||||
DOCKERHUB_REGISTRY: docker.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
TEST_TAG: triliumnext/notes:test
|
||||
PLATFORMS: linux/amd64
|
||||
|
||||
jobs:
|
||||
test_docker:
|
||||
name: Check Docker build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "npm"
|
||||
|
||||
- run: npm ci
|
||||
|
||||
- name: Run the TypeScript build
|
||||
run: npx tsc
|
||||
|
||||
- name: Create server-package.json
|
||||
run: cat package.json | grep -v electron > server-package.json
|
||||
|
||||
- name: Build and export to Docker
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
load: true
|
||||
tags: ${{ env.TEST_TAG }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Run the container in the background
|
||||
run: docker run -d --rm --name trilium_local ${{ env.TEST_TAG }}
|
||||
|
||||
- name: Wait for the healthchecks to pass
|
||||
uses: stringbean/docker-healthcheck-action@v1
|
||||
with:
|
||||
container: trilium_local
|
||||
wait-time: 50
|
||||
require-status: running
|
||||
require-healthy: true
|
||||
|
||||
build_docker:
|
||||
name: Build Docker images
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- test_docker
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Extract metadata (tags, labels) for GHCR image
|
||||
id: ghcr-meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
type=sha
|
||||
- name: Extract metadata (tags, labels) for DockerHub image
|
||||
id: dh-meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
type=sha
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "npm"
|
||||
- run: npm ci
|
||||
- name: Run the TypeScript build
|
||||
run: npx tsc
|
||||
- name: Create server-package.json
|
||||
run: cat package.json | grep -v electron > server-package.json
|
||||
- name: Log in to the GHCR container registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.GHCR_REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- name: Build and push container image to GHCR
|
||||
uses: docker/build-push-action@v6
|
||||
id: ghcr-push
|
||||
with:
|
||||
file: ./Dockerfile.alpine
|
||||
context: .
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
push: true
|
||||
tags: ${{ steps.ghcr-meta.outputs.tags }}
|
||||
labels: ${{ steps.ghcr-meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
- name: Generate and push artifact attestation to GHCR
|
||||
uses: actions/attest-build-provenance@v1
|
||||
with:
|
||||
subject-name: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME}}
|
||||
subject-digest: ${{ steps.ghcr-push.outputs.digest }}
|
||||
push-to-registry: true
|
||||
- name: Log in to the DockerHub container registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.DOCKERHUB_REGISTRY }}
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and push image to DockerHub
|
||||
uses: docker/build-push-action@v6
|
||||
id: dh-push
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
push: true
|
||||
tags: ${{ steps.dh-meta.outputs.tags }}
|
||||
labels: ${{ steps.dh-meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
- name: Generate and push artifact attestation to DockerHub
|
||||
uses: actions/attest-build-provenance@v1
|
||||
with:
|
||||
subject-name: ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME}}
|
||||
subject-digest: ${{ steps.dh-push.outputs.digest }}
|
||||
push-to-registry: true
|
||||
221
.github/workflows/main-docker.yml
vendored
221
.github/workflows/main-docker.yml
vendored
@@ -9,22 +9,35 @@ on:
|
||||
- "bin/**"
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GHCR_REGISTRY: ghcr.io
|
||||
DOCKERHUB_REGISTRY: docker.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
TEST_TAG: triliumnext/notes:test
|
||||
PLATFORMS: linux/amd64,linux/arm64,linux/arm/v7
|
||||
IMAGE_NAME: ${{ github.repository_owner }}/notes
|
||||
TEST_TAG: ${{ github.repository_owner }}/notes:test
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
test_docker:
|
||||
name: Check Docker build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- dockerfile: Dockerfile.alpine
|
||||
- dockerfile: Dockerfile
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set IMAGE_NAME to lowercase
|
||||
run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
|
||||
- name: Set TEST_TAG to lowercase
|
||||
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
@@ -47,14 +60,17 @@ jobs:
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.dockerfile }}
|
||||
load: true
|
||||
tags: ${{ env.TEST_TAG }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Run the container in the background
|
||||
run: docker run -d --rm --name trilium_local ${{ env.TEST_TAG }}
|
||||
|
||||
|
||||
- name: Validate container run output
|
||||
run: |
|
||||
CONTAINER_ID=$(docker run -d --log-driver=journald --rm --name trilium_local ${{ env.TEST_TAG }})
|
||||
echo "Container ID: $CONTAINER_ID"
|
||||
|
||||
- name: Wait for the healthchecks to pass
|
||||
uses: stringbean/docker-healthcheck-action@v1
|
||||
with:
|
||||
@@ -63,7 +79,13 @@ jobs:
|
||||
require-status: running
|
||||
require-healthy: true
|
||||
|
||||
build_docker:
|
||||
# Print the entire log of the container thus far, regardless if the healthcheck failed or succeeded
|
||||
- name: Print entire log
|
||||
if: always()
|
||||
run: |
|
||||
journalctl -u docker CONTAINER_NAME=trilium_local --no-pager
|
||||
|
||||
build:
|
||||
name: Build Docker images
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
@@ -73,26 +95,48 @@ jobs:
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Extract metadata (tags, labels) for GHCR image
|
||||
id: ghcr-meta
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- dockerfile: Dockerfile.alpine
|
||||
platform: linux/amd64
|
||||
- dockerfile: Dockerfile
|
||||
platform: linux/arm64
|
||||
- dockerfile: Dockerfile
|
||||
platform: linux/arm/v7
|
||||
steps:
|
||||
- name: Prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
- name: Set IMAGE_NAME to lowercase
|
||||
run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
|
||||
- name: Set TEST_TAG to lowercase
|
||||
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
type=sha
|
||||
- name: Extract metadata (tags, labels) for DockerHub image
|
||||
id: dh-meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
images: |
|
||||
${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
type=sha
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
@@ -103,50 +147,115 @@ jobs:
|
||||
run: npx tsc
|
||||
- name: Create server-package.json
|
||||
run: cat package.json | grep -v electron > server-package.json
|
||||
- name: Log in to the GHCR container registry
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.GHCR_REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- name: Build and push container image to GHCR
|
||||
uses: docker/build-push-action@v6
|
||||
id: ghcr-push
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
push: true
|
||||
tags: ${{ steps.ghcr-meta.outputs.tags }}
|
||||
labels: ${{ steps.ghcr-meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
- name: Generate and push artifact attestation to GHCR
|
||||
uses: actions/attest-build-provenance@v1
|
||||
with:
|
||||
subject-name: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME}}
|
||||
subject-digest: ${{ steps.ghcr-push.outputs.digest }}
|
||||
push-to-registry: true
|
||||
- name: Log in to the DockerHub container registry
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.DOCKERHUB_REGISTRY }}
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and push image to DockerHub
|
||||
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
id: dh-push
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
push: true
|
||||
tags: ${{ steps.dh-meta.outputs.tags }}
|
||||
labels: ${{ steps.dh-meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
- name: Generate and push artifact attestation to DockerHub
|
||||
uses: actions/attest-build-provenance@v1
|
||||
file: ${{ matrix.dockerfile }}
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs: type=image,name=${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
mkdir -p /tmp/digests
|
||||
digest="${{ steps.build.outputs.digest }}"
|
||||
touch "/tmp/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
subject-name: ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME}}
|
||||
subject-digest: ${{ steps.dh-push.outputs.digest }}
|
||||
push-to-registry: true
|
||||
name: digests-${{ env.PLATFORM_PAIR }}
|
||||
path: /tmp/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
merge:
|
||||
name: Merge manifest lists
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
- name: Set IMAGE_NAME to lowercase
|
||||
run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
|
||||
- name: Set TEST_TAG to lowercase
|
||||
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.GHCR_REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.DOCKERHUB_REGISTRY }}
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Create manifest list and push
|
||||
working-directory: /tmp/digests
|
||||
run: |
|
||||
# Extract the branch or tag name from the ref
|
||||
REF_NAME=$(echo "${GITHUB_REF}" | sed 's/refs\/heads\///' | sed 's/refs\/tags\///')
|
||||
|
||||
# Create and push the manifest list with both the branch/tag name and the commit SHA
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
-t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${REF_NAME} \
|
||||
$(printf '${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
|
||||
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
-t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${REF_NAME} \
|
||||
$(printf '${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
|
||||
|
||||
# If the ref is a tag, also tag the image as stable as this is part of a 'release'
|
||||
# and only go in the `if` if there is NOT a `-` in the tag's name, due to tagging of `-alpha`, `-beta`, etc...
|
||||
if [[ "${GITHUB_REF}" == refs/tags/* && ! "${REF_NAME}" =~ - ]]; then
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
-t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:stable \
|
||||
$(printf '${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
|
||||
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
-t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:stable \
|
||||
$(printf '${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
|
||||
fi
|
||||
|
||||
- name: Inspect image
|
||||
run: |
|
||||
docker buildx imagetools inspect ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
|
||||
docker buildx imagetools inspect ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
|
||||
|
||||
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Update build info
|
||||
run: npm run update-build-info
|
||||
run: npm run update-build-info
|
||||
- name: Run electron-forge
|
||||
run: npm run make-electron -- --arch=${{ matrix.arch }}
|
||||
- name: Prepare artifacts (Unix)
|
||||
|
||||
129
.github/workflows/nightly.yml
vendored
Normal file
129
.github/workflows/nightly.yml
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
name: Nightly Release
|
||||
on:
|
||||
# This can be used to automatically publish nightlies at UTC nighttime
|
||||
schedule:
|
||||
- cron: '0 2 * * *' # run at 2 AM UTC
|
||||
# This can be used to allow manually triggering nightlies from the web interface
|
||||
workflow_dispatch:
|
||||
env:
|
||||
GITHUB_UPLOAD_URL: https://uploads.github.com/repos/TriliumNext/Notes/releases/179589950/assets{?name,label}
|
||||
GITHUB_RELEASE_ID: 179589950
|
||||
permissions:
|
||||
contents: write
|
||||
jobs:
|
||||
nightly-electron:
|
||||
name: Deploy nightly
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [x64, arm64]
|
||||
os:
|
||||
- name: macos
|
||||
image: macos-latest
|
||||
extension: dmg
|
||||
- name: linux
|
||||
image: ubuntu-latest
|
||||
extension: deb
|
||||
- name: windows
|
||||
image: windows-latest
|
||||
extension: exe
|
||||
runs-on: ${{ matrix.os.image }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Set up Python for appdmg to be installed
|
||||
if: ${{ matrix.os.name == 'macos' }}
|
||||
run: brew install python-setuptools
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Update build info
|
||||
run: npm run update-build-info
|
||||
- name: Update nightly version
|
||||
run: npm run ci-update-nightly-version
|
||||
- name: Run electron-forge
|
||||
run: npm run make-electron -- --arch=${{ matrix.arch }}
|
||||
- name: Prepare artifacts (Unix)
|
||||
if: runner.os != 'windows'
|
||||
run: |
|
||||
mkdir -p upload
|
||||
file=$(find out/make -name '*.zip' -print -quit)
|
||||
cp "$file" "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.zip"
|
||||
file=$(find out/make -name '*.${{ matrix.os.extension }}' -print -quit)
|
||||
cp "$file" "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.${{ matrix.os.extension }}"
|
||||
- name: Prepare artifacts (Windows)
|
||||
if: runner.os == 'windows'
|
||||
run: |
|
||||
mkdir upload
|
||||
$file = Get-ChildItem -Path out/make -Filter '*.zip' -Recurse | Select-Object -First 1
|
||||
Copy-Item -Path $file.FullName -Destination "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.zip"
|
||||
$file = Get-ChildItem -Path out/make -Filter '*.${{ matrix.os.extension }}' -Recurse | Select-Object -First 1
|
||||
Copy-Item -Path $file.FullName -Destination "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.${{ matrix.os.extension }}"
|
||||
- name: Publish artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: TriliumNextNotes ${{ matrix.os.name }} ${{ matrix.arch }}
|
||||
path: upload/*.zip
|
||||
overwrite: true
|
||||
- name: Publish installer artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: TriliumNextNotes ${{ matrix.os.name }} ${{ matrix.arch }}
|
||||
path: upload/*.${{ matrix.os.extension }}
|
||||
overwrite: true
|
||||
|
||||
- name: Deploy release
|
||||
uses: WebFreak001/deploy-nightly@v3.1.0
|
||||
with:
|
||||
upload_url: ${{ env.GITHUB_UPLOAD_URL }}
|
||||
release_id: ${{ env.GITHUB_RELEASE_ID }}
|
||||
asset_path: upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.zip # path to archive to upload
|
||||
asset_name: TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}-nightly.zip # name to upload the release as, use $$ to insert date (YYYYMMDD) and 6 letter commit hash
|
||||
asset_content_type: application/zip # required by GitHub API
|
||||
- name: Deploy installer release
|
||||
uses: WebFreak001/deploy-nightly@v3.1.0
|
||||
with:
|
||||
upload_url: ${{ env.GITHUB_UPLOAD_URL }}
|
||||
release_id: ${{ env.GITHUB_RELEASE_ID }}
|
||||
asset_path: upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.${{ matrix.os.extension }} # path to archive to upload
|
||||
asset_name: TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}-nightly.${{ matrix.os.extension }} # name to upload the release as, use $$ to insert date (YYYYMMDD) and 6 letter commit hash
|
||||
asset_content_type: application/zip # required by GitHub API
|
||||
nightly-server:
|
||||
name: Deploy server nightly
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "npm"
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Run Linux server build (x86_64)
|
||||
run: |
|
||||
npm run update-build-info
|
||||
npm run ci-update-nightly-version
|
||||
./bin/build-server.sh
|
||||
- name: Prepare artifacts
|
||||
if: runner.os != 'windows'
|
||||
run: |
|
||||
mkdir -p upload
|
||||
file=$(find dist -name '*.tar.xz' -print -quit)
|
||||
cp "$file" "upload/TriliumNextNotes-linux-x64-${{ github.ref_name }}.tar.xz"
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: TriliumNextNotes linux server x64
|
||||
path: upload/TriliumNextNotes-linux-x64-${{ github.ref_name }}.tar.xz
|
||||
overwrite: true
|
||||
|
||||
- name: Deploy release
|
||||
uses: WebFreak001/deploy-nightly@v3.1.0
|
||||
with:
|
||||
upload_url: ${{ env.GITHUB_UPLOAD_URL }}
|
||||
release_id: ${{ env.GITHUB_RELEASE_ID }}
|
||||
asset_path: upload/TriliumNextNotes-linux-x64-${{ github.ref_name }}.tar.xz # path to archive to upload
|
||||
asset_name: TriliumNextNotes-linux-x64-nightly.zip # name to upload the release as, use $$ to insert date (YYYYMMDD) and 6 letter commit hash
|
||||
asset_content_type: application/zip # required by GitHub API
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,6 +5,7 @@ build/
|
||||
src/public/app-dist/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
po-*/
|
||||
|
||||
*.db
|
||||
!integration-tests/db/document.db
|
||||
|
||||
2
.vscode/i18n-ally-custom-framework.yml
vendored
2
.vscode/i18n-ally-custom-framework.yml
vendored
@@ -3,6 +3,7 @@
|
||||
languageIds:
|
||||
- javascript
|
||||
- typescript
|
||||
- html
|
||||
|
||||
# An array of RegExes to find the key usage. **The key should be captured in the first match group**.
|
||||
# You should unescape RegEx strings in order to fit in the YAML file
|
||||
@@ -25,6 +26,7 @@ scopeRangeRegex: "useTranslation\\(\\s*\\[?\\s*['\"`](.*?)['\"`]"
|
||||
refactorTemplates:
|
||||
- t("$1")
|
||||
- ${t("$1")}
|
||||
- <%= t("$1") %>
|
||||
|
||||
|
||||
# If set to true, only enables this custom framework (will disable all built-in frameworks)
|
||||
|
||||
13
.vscode/i18n-ally-reviews.yml
vendored
13
.vscode/i18n-ally-reviews.yml
vendored
@@ -5,3 +5,16 @@ reviews:
|
||||
description: >-
|
||||
Describes the shortcut which triggers a search within the current
|
||||
page/note only
|
||||
add_label.to_value:
|
||||
locales:
|
||||
fr:
|
||||
comments:
|
||||
- user:
|
||||
name: Potjoe-97
|
||||
email: giann@LAPTOPT490-GF
|
||||
id: QXec0JUoxfGmMlpch-B1S
|
||||
comment: ''
|
||||
suggestion: vers la valeur
|
||||
type: request_change
|
||||
time: '2024-10-15T16:57:06.188Z'
|
||||
resolved: true
|
||||
|
||||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -6,7 +6,8 @@
|
||||
"i18n-ally.sourceLanguage": "en",
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.localesPaths": [
|
||||
"./src/public/translations"
|
||||
"./src/public/translations",
|
||||
"./translations"
|
||||
],
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "vscode.json-language-features"
|
||||
@@ -17,4 +18,7 @@
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||
},
|
||||
"github-actions.workflows.pinned.workflows": [
|
||||
".github/workflows/nightly.yml"
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# TriliumNext Notes
|
||||
|
||||
[English](https://github.com/TriliumNext/Notes/blob/master/README.md) | [Chinese](https://github.com/TriliumNext/Notes/blob/master/README-ZH_CN.md) | [Russian](https://github.com/TriliumNext/Notes/blob/master/README.ru.md) | [Japanese](https://github.com/TriliumNext/Notes/blob/master/README.ja.md) | [Italian](https://github.com/TriliumNext/Notes/blob/master/README.it.md) | [Spanish](https://github.com/TriliumNext/Notes/blob/master/README.es.md)
|
||||
[English](./README.md) | [Chinese](./README-ZH_CN.md) | [Russian](./README.ru.md) | [Japanese](./README.ja.md) | [Italian](./README.it.md) | [Spanish](./README.es.md)
|
||||
|
||||
TriliumNext Notes 是一个层次化的笔记应用程序,专注于建立大型个人知识库。请参阅[屏幕截图](https://triliumnext.github.io/Docs/Wiki/screenshot-tour)以快速了解:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# TriliumNext Notes
|
||||
|
||||
[English](https://github.com/TriliumNext/Notes/blob/master/README.md) | [Chinese](https://github.com/TriliumNext/Notes/blob/master/README-ZH_CN.md) | [Russian](https://github.com/TriliumNext/Notes/blob/master/README.ru.md) | [Japanese](https://github.com/TriliumNext/Notes/blob/master/README.ja.md) | [Italian](https://github.com/TriliumNext/Notes/blob/master/README.it.md) | [Spanish](https://github.com/TriliumNext/Notes/blob/master/README.es.md)
|
||||
[English](./README.md) | [Chinese](./README-ZH_CN.md) | [Russian](./README.ru.md) | [Japanese](./README.ja.md) | [Italian](./README.it.md) | [Spanish](./README.es.md)
|
||||
|
||||
TriliumNext Notes es una aplicación de toma de notas jerárquicas multi-plataforma y de código libre con un enfoque en la construcción de grandes bases de conocimiento personal.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# TriliumNext Notes
|
||||
|
||||
[English](https://github.com/TriliumNext/Notes/blob/master/README.md) | [Chinese](https://github.com/TriliumNext/Notes/blob/master/README-ZH_CN.md) | [Russian](https://github.com/TriliumNext/Notes/blob/master/README.ru.md) | [Japanese](https://github.com/TriliumNext/Notes/blob/master/README.ja.md) | [Italian](https://github.com/TriliumNext/Notes/blob/master/README.it.md) | [Spanish](https://github.com/TriliumNext/Notes/blob/master/README.es.md)
|
||||
[English](./README.md) | [Chinese](./README-ZH_CN.md) | [Russian](./README.ru.md) | [Japanese](./README.ja.md) | [Italian](./README.it.md) | [Spanish](./README.es.md)
|
||||
|
||||
TriliumNext Notes è un'applicazione per appunti ad organizzazione gerarchica, studiata per la costruzione di archivi di conoscenza personali di grandi dimensioni.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# TriliumNext Notes
|
||||
|
||||
[English](https://github.com/TriliumNext/Notes/blob/master/README.md) | [Chinese](https://github.com/TriliumNext/Notes/blob/master/README-ZH_CN.md) | [Russian](https://github.com/TriliumNext/Notes/blob/master/README.ru.md) | [Japanese](https://github.com/TriliumNext/Notes/blob/master/README.ja.md) | [Italian](https://github.com/TriliumNext/Notes/blob/master/README.it.md) | [Spanish](https://github.com/TriliumNext/Notes/blob/master/README.es.md)
|
||||
[English](./README.md) | [Chinese](./README-ZH_CN.md) | [Russian](./README.ru.md) | [Japanese](./README.ja.md) | [Italian](./README.it.md) | [Spanish](./README.es.md)
|
||||
|
||||
Trilium Notes は、大規模な個人知識ベースの構築に焦点を当てた、階層型ノートアプリケーションです。概要は[スクリーンショット](https://triliumnext.github.io/Docs/Wiki/screenshot-tour)をご覧ください:
|
||||
|
||||
|
||||
12
README.md
12
README.md
@@ -1,6 +1,8 @@
|
||||
# TriliumNext Notes
|
||||
|
||||
[English](https://github.com/TriliumNext/Notes/blob/master/README.md) | [Chinese](https://github.com/TriliumNext/Notes/blob/master/README-ZH_CN.md) | [Russian](https://github.com/TriliumNext/Notes/blob/master/README.ru.md) | [Japanese](https://github.com/TriliumNext/Notes/blob/master/README.ja.md) | [Italian](https://github.com/TriliumNext/Notes/blob/master/README.it.md) | [Spanish](https://github.com/TriliumNext/Notes/blob/master/README.es.md)
|
||||
 
|
||||
|
||||
[English](./README.md) | [Chinese](./README-ZH_CN.md) | [Russian](./README.ru.md) | [Japanese](./README.ja.md) | [Italian](./README.it.md) | [Spanish](./README.es.md)
|
||||
|
||||
TriliumNext Notes is an open-source, cross-platform hierarchical note taking application with focus on building large personal knowledge bases.
|
||||
|
||||
@@ -82,13 +84,19 @@ You can also read [Patterns of personal knowledge base](https://triliumnext.gith
|
||||
|
||||
## 💻 Contribute
|
||||
|
||||
Clone locally and run
|
||||
### Code
|
||||
|
||||
```shell
|
||||
git clone https://github.com/TriliumNext/Notes.git
|
||||
cd Notes
|
||||
npm install
|
||||
npm run start-server
|
||||
```
|
||||
|
||||
### Documentation
|
||||
|
||||
Head on over to our [Docs repo](https://github.com/TriliumNext/Docs)
|
||||
|
||||
## 👏 Shoutouts
|
||||
|
||||
* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - best WYSIWYG editor on the market, very interactive and listening team
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# TriliumNext Notes
|
||||
|
||||
[English](https://github.com/TriliumNext/Notes/blob/master/README.md) | [Chinese](https://github.com/TriliumNext/Notes/blob/master/README-ZH_CN.md) | [Russian](https://github.com/TriliumNext/Notes/blob/master/README.ru.md) | [Japanese](https://github.com/TriliumNext/Notes/blob/master/README.ja.md) | [Italian](https://github.com/TriliumNext/Notes/blob/master/README.it.md) | [Spanish](https://github.com/TriliumNext/Notes/blob/master/README.es.md)
|
||||
[English](./README.md) | [Chinese](./README-ZH_CN.md) | [Russian](./README.ru.md) | [Japanese](./README.ja.md) | [Italian](./README.it.md) | [Spanish](./README.es.md)
|
||||
|
||||
Trilium Notes – это приложение для заметок с иерархической структурой, ориентированное на создание больших персональных баз знаний. Для быстрого ознакомления посмотрите [скриншот-тур](https://triliumnext.github.io/Docs/Wiki/screenshot-tour):
|
||||
|
||||
|
||||
@@ -22,13 +22,14 @@ rm -r $PKG_DIR/node/lib/node_modules/npm
|
||||
rm -r $PKG_DIR/node/include/node
|
||||
|
||||
rm -r $PKG_DIR/node_modules/electron*
|
||||
rm -r $PKG_DIR/electron.js
|
||||
rm -r $PKG_DIR/electron*.js
|
||||
|
||||
printf "#!/bin/sh\n./node/bin/node src/www" > $PKG_DIR/trilium.sh
|
||||
printf "#!/bin/sh\n./node/bin/node src/main" > $PKG_DIR/trilium.sh
|
||||
chmod 755 $PKG_DIR/trilium.sh
|
||||
|
||||
cp bin/tpl/anonymize-database.sql $PKG_DIR/
|
||||
|
||||
cp -r translations $PKG_DIR/
|
||||
cp -r dump-db $PKG_DIR/
|
||||
rm -rf $PKG_DIR/dump-db/node_modules
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ const copy = async () => {
|
||||
await fs.copy(file, path.join(DEST_DIR, file));
|
||||
}
|
||||
|
||||
const dirsToCopy = ["images", "libraries", "db"];
|
||||
const dirsToCopy = ["images", "libraries", "translations", "db"];
|
||||
for (const dir of dirsToCopy) {
|
||||
log(`Copying ${dir}`);
|
||||
await fs.copy(dir, path.join(DEST_DIR, dir));
|
||||
@@ -47,6 +47,15 @@ const copy = async () => {
|
||||
await fs.copy(dir, path.join(DEST_DIR_SRC, path.basename(dir)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Directories to be copied relative to the project root into <resource_dir>/src/public/app-dist.
|
||||
*/
|
||||
const publicDirsToCopy = [ "./src/public/app/doc_notes" ];
|
||||
const PUBLIC_DIR = path.join(DEST_DIR, "src", "public", "app-dist");
|
||||
for (const dir of publicDirsToCopy) {
|
||||
await fs.copy(dir, path.join(PUBLIC_DIR, path.basename(dir)));
|
||||
}
|
||||
|
||||
const nodeModulesFile = [
|
||||
"node_modules/react/umd/react.production.min.js",
|
||||
"node_modules/react/umd/react.development.js",
|
||||
@@ -55,6 +64,7 @@ const copy = async () => {
|
||||
"node_modules/katex/dist/katex.min.js",
|
||||
"node_modules/katex/dist/contrib/mhchem.min.js",
|
||||
"node_modules/katex/dist/contrib/auto-render.min.js",
|
||||
"node_modules/@highlightjs/cdn-assets/highlight.min.js"
|
||||
];
|
||||
|
||||
for (const file of nodeModulesFile) {
|
||||
@@ -89,7 +99,9 @@ const copy = async () => {
|
||||
"node_modules/codemirror/addon/",
|
||||
"node_modules/codemirror/mode/",
|
||||
"node_modules/codemirror/keymap/",
|
||||
"node_modules/mind-elixir/dist/"
|
||||
"node_modules/mind-elixir/dist/",
|
||||
"node_modules/@highlightjs/cdn-assets/languages",
|
||||
"node_modules/@highlightjs/cdn-assets/styles"
|
||||
];
|
||||
|
||||
for (const folder of nodeModulesFolder) {
|
||||
|
||||
@@ -37,11 +37,11 @@ for f in 'package.json' 'package-lock.json' 'README.md' 'LICENSE' 'config-sample
|
||||
done
|
||||
|
||||
# Patch package.json main
|
||||
sed -i 's/.\/dist\/electron.js/electron.js/g' "$DIR/package.json"
|
||||
sed -i 's/.\/dist\/electron-main.js/electron-main.js/g' "$DIR/package.json"
|
||||
|
||||
script_dir=$(realpath $(dirname $0))
|
||||
cp -R "$script_dir/../build/src" "$DIR"
|
||||
cp "$script_dir/../build/electron.js" "$DIR"
|
||||
cp "$script_dir/../build/electron-main.js" "$DIR"
|
||||
|
||||
# run in subshell (so we return to original dir)
|
||||
(cd $DIR && npm install --omit=dev)
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"src": "dist/trilium-linux-x64",
|
||||
"dest": "dist/",
|
||||
"compression": "xz",
|
||||
"name": "trilium",
|
||||
"productName": "Trilium Notes",
|
||||
"genericName": "Note taker",
|
||||
"description": "Trilium Notes is a hierarchical note taking application with focus on building large personal knowledge bases.",
|
||||
"sections": "misc",
|
||||
"maintainer": "zadam.apps@gmail.com",
|
||||
"homepage": "https://github.com/zadam/trilium",
|
||||
"bin": "trilium",
|
||||
"icon": "dist/trilium-linux-x64/icon.png",
|
||||
"categories": [ "Office" ]
|
||||
}
|
||||
12
bin/electron-forge/desktop.ejs
Normal file
12
bin/electron-forge/desktop.ejs
Normal file
@@ -0,0 +1,12 @@
|
||||
[Desktop Entry]
|
||||
<% if (productName) { %>Name=<%= productName %>
|
||||
<% } %><% if (description) { %>Comment=<%= description %>
|
||||
<% } %><% if (genericName) { %>GenericName=<%= genericName %>
|
||||
<% } %><% if (name) { %>Exec=<%= name %> %U
|
||||
Icon=<%= name %>
|
||||
<% } %>Type=Application
|
||||
StartupNotify=true
|
||||
<% if (productName) { %>StartupWMClass=<%= productName %>
|
||||
<% } if (categories && categories.length) { %>Categories=<%= categories.join(';') %>;
|
||||
<% } %><% if (mimeType && mimeType.length) { %>MimeType=<%= mimeType.join(';') %>;
|
||||
<% } %>
|
||||
98
bin/translation.sh
Executable file
98
bin/translation.sh
Executable file
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# --------------------------------------------------------------------------------------------------
|
||||
#
|
||||
# Create PO files to make easier the labor of translation.
|
||||
#
|
||||
# Info:
|
||||
# https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html
|
||||
# https://docs.translatehouse.org/projects/translate-toolkit/en/latest/commands/json2po.html
|
||||
#
|
||||
# Dependencies:
|
||||
# jq
|
||||
# translate-toolkit
|
||||
# python-wcwidth
|
||||
#
|
||||
# Created by @hasecilu
|
||||
#
|
||||
# --------------------------------------------------------------------------------------------------
|
||||
|
||||
stats() {
|
||||
# Print the number of existing strings on the JSON files for each locale
|
||||
s=$(jq 'path(..) | select(length == 2) | .[1]' "${paths[0]}/en/server.json" | wc -l)
|
||||
c=$(jq 'path(..) | select(length == 2) | .[1]' "${paths[1]}/en/translation.json" | wc -l)
|
||||
echo "|locale |server strings |client strings |"
|
||||
echo "|-------|---------------|---------------|"
|
||||
echo "| en | ${s} | ${c} |"
|
||||
for locale in "${locales[@]}"; do
|
||||
s=$(jq 'path(..) | select(length == 2) | .[1]' "${paths[0]}/${locale}/server.json" | wc -l)
|
||||
c=$(jq 'path(..) | select(length == 2) | .[1]' "${paths[1]}/${locale}/translation.json" | wc -l)
|
||||
echo "| ${locale} | ${s} | ${c} |"
|
||||
done
|
||||
}
|
||||
|
||||
help() {
|
||||
echo -e "\nDescription:"
|
||||
echo -e "\tCreate PO files to make easier the labor of translation"
|
||||
echo -e "\nUsage:"
|
||||
echo -e "\t./translation.sh [--stats] [--update <OPT_LOCALE>] [--update2 <OPT_LOCALE>]"
|
||||
echo -e "\nFlags:"
|
||||
echo -e " --clear\n\tClear all po-* directories"
|
||||
echo -e " --stats\n\tPrint the number of existing strings on the JSON files for each locale"
|
||||
echo -e " --update <LOCALE>\n\tUpdate PO files from English and localized JSON files as source"
|
||||
echo -e " --update2 <LOCALE>\n\tRecover translation from PO files to localized JSON files"
|
||||
}
|
||||
|
||||
# Main function ------------------------------------------------------------------------------------
|
||||
|
||||
# Get script directory to set file path relative to it
|
||||
file_path="$(
|
||||
cd -- "$(dirname "${0}")" >/dev/null 2>&1 || exit
|
||||
pwd -P
|
||||
)"
|
||||
paths=("${file_path}/../translations/" "${file_path}/../src/public/translations/")
|
||||
locales=(cn es fr ro)
|
||||
|
||||
if [ $# -eq 1 ]; then
|
||||
if [ "$1" == "--clear" ]; then
|
||||
for path in "${paths[@]}"; do
|
||||
for locale in "${locales[@]}"; do
|
||||
[ -d "${path}/po-${locale}" ] && rm -r "${path}/po-${locale}"
|
||||
done
|
||||
done
|
||||
elif [ "$1" == "--stats" ]; then
|
||||
stats
|
||||
elif [ "$1" == "--update" ]; then
|
||||
# Update PO files from English and localized JSON files as source
|
||||
for path in "${paths[@]}"; do
|
||||
for locale in "${locales[@]}"; do
|
||||
json2po -t "${path}/en" "${path}/${locale}" "${path}/po-${locale}"
|
||||
done
|
||||
done
|
||||
elif [ "$1" == "--update2" ]; then
|
||||
# Recover translation from PO files to localized JSON files
|
||||
for path in "${paths[@]}"; do
|
||||
for locale in "${locales[@]}"; do
|
||||
po2json -t "${path}/en" "${path}/po-${locale}" "${path}/${locale}"
|
||||
done
|
||||
done
|
||||
else
|
||||
help
|
||||
fi
|
||||
elif [ $# -eq 2 ]; then
|
||||
if [ "$1" == "--update" ]; then
|
||||
locale="$2"
|
||||
for path in "${paths[@]}"; do
|
||||
json2po -t "${path}/en" "${path}/${locale}" "${path}/po-${locale}"
|
||||
done
|
||||
elif [ "$1" == "--update2" ]; then
|
||||
locale="$2"
|
||||
for path in "${paths[@]}"; do
|
||||
po2json -t "${path}/en" "${path}/po-${locale}" "${path}/${locale}"
|
||||
done
|
||||
else
|
||||
help
|
||||
fi
|
||||
else
|
||||
help
|
||||
fi
|
||||
50
bin/update-nightly-version.ts
Normal file
50
bin/update-nightly-version.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @module
|
||||
*
|
||||
* The nightly version works uses the version described in `package.json`, just like any release.
|
||||
* The problem with this approach is that production builds have a very aggressive cache, and
|
||||
* usually running the nightly with this cached version of the application will mean that the
|
||||
* user might run into module not found errors or styling errors caused by an old cache.
|
||||
*
|
||||
* This script is supposed to be run in the CI, which will update locally the version field of
|
||||
* `package.json` to contain the date. For example, `0.90.9-beta` will become `0.90.9-test-YYMMDD-HHMMSS`.
|
||||
*
|
||||
*/
|
||||
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname, join } from "path";
|
||||
import fs from "fs";
|
||||
|
||||
function processVersion(version) {
|
||||
// Remove the beta suffix if any.
|
||||
version = version.replace("-beta", "");
|
||||
|
||||
// Add the nightly suffix, plus the date.
|
||||
const referenceDate = new Date()
|
||||
.toISOString()
|
||||
.substring(2, 19)
|
||||
.replace(/[-:]*/g, "")
|
||||
.replace("T", "-");
|
||||
version = `${version}-test-${referenceDate}`;
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
function main() {
|
||||
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||
const packageJsonPath = join(scriptDir, "..", "package.json");
|
||||
|
||||
// Read the version from package.json and process it.
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
||||
const currentVersion = packageJson.version;
|
||||
const adjustedVersion = processVersion(currentVersion);
|
||||
console.log("Current version is", currentVersion);
|
||||
console.log("Adjusted version is", adjustedVersion);
|
||||
|
||||
// Write the adjusted version back in.
|
||||
packageJson.version = adjustedVersion;
|
||||
const formattedJson = JSON.stringify(packageJson, null, 4);
|
||||
fs.writeFileSync(packageJsonPath, formattedJson);
|
||||
}
|
||||
|
||||
main();
|
||||
BIN
db/demo.zip
BIN
db/demo.zip
Binary file not shown.
@@ -42,9 +42,9 @@ const NOTE_TYPE_ICONS = {
|
||||
"code": "bx bx-code",
|
||||
"render": "bx bx-extension",
|
||||
"search": "bx bx-file-find",
|
||||
"relationMap": "bx bx-map-alt",
|
||||
"relationMap": "bx bxs-network-chart",
|
||||
"book": "bx bx-book",
|
||||
"noteMap": "bx bx-map-alt",
|
||||
"noteMap": "bx bxs-network-chart",
|
||||
"mermaid": "bx bx-selection",
|
||||
"canvas": "bx bx-pen",
|
||||
"webView": "bx bx-globe-alt",
|
||||
@@ -570,7 +570,7 @@ class FNote {
|
||||
return workspaceIconClass;
|
||||
}
|
||||
else if (this.noteId === 'root') {
|
||||
return "bx bx-chevrons-right";
|
||||
return "bx bx-home-alt-2";
|
||||
}
|
||||
if (this.noteId === '_share') {
|
||||
return "bx bx-share-alt";
|
||||
|
||||
4
electron-main.ts
Normal file
4
electron-main.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { initializeTranslations } from "./src/services/i18n.js";
|
||||
|
||||
await initializeTranslations();
|
||||
await import("./electron.js")
|
||||
@@ -70,4 +70,4 @@ electron.app.on("will-quit", () => {
|
||||
// this is to disable electron warning spam in the dev console (local development only)
|
||||
process.env["ELECTRON_DISABLE_SECURITY_WARNINGS"] = "true";
|
||||
|
||||
await import('./src/www.js');
|
||||
await import('./src/main.js');
|
||||
|
||||
@@ -10,17 +10,31 @@ module.exports = {
|
||||
overwrite: true,
|
||||
asar: true,
|
||||
icon: "./images/app-icons/icon",
|
||||
extraResource: getExtraResourcesForPlatform(),
|
||||
extraResource: [
|
||||
// Moved to root
|
||||
...getExtraResourcesForPlatform(),
|
||||
|
||||
// Moved to resources (TriliumNext Notes.app/Contents/Resources on macOS)
|
||||
"translations/",
|
||||
"node_modules/@highlightjs/cdn-assets/styles"
|
||||
],
|
||||
afterComplete: [(buildPath, _electronVersion, platform, _arch, callback) => {
|
||||
const extraResources = getExtraResourcesForPlatform();
|
||||
for (const resource of extraResources) {
|
||||
const baseName = path.basename(resource);
|
||||
let sourcePath;
|
||||
if (platform === 'darwin') {
|
||||
sourcePath = path.join(buildPath, `${APP_NAME}.app`, 'Contents', 'Resources', path.basename(resource));
|
||||
sourcePath = path.join(buildPath, `${APP_NAME}.app`, 'Contents', 'Resources', baseName);
|
||||
} else {
|
||||
sourcePath = path.join(buildPath, 'resources', path.basename(resource));
|
||||
sourcePath = path.join(buildPath, 'resources', baseName);
|
||||
}
|
||||
let destPath;
|
||||
|
||||
if (baseName !== "256x256.png") {
|
||||
destPath = path.join(buildPath, baseName);
|
||||
} else {
|
||||
destPath = path.join(buildPath, "icon.png");
|
||||
}
|
||||
const destPath = path.join(buildPath, path.basename(resource));
|
||||
|
||||
// Copy files from resources folder to root
|
||||
fs.move(sourcePath, destPath)
|
||||
@@ -38,6 +52,7 @@ module.exports = {
|
||||
config: {
|
||||
options: {
|
||||
icon: "./images/app-icons/png/128x128.png",
|
||||
desktopTemplate: path.resolve("./bin/electron-forge/desktop.ejs")
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -75,7 +90,10 @@ module.exports = {
|
||||
|
||||
|
||||
function getExtraResourcesForPlatform() {
|
||||
let resources = ['dump-db/', './bin/tpl/anonymize-database.sql']
|
||||
let resources = [
|
||||
'dump-db/',
|
||||
'./bin/tpl/anonymize-database.sql'
|
||||
];
|
||||
const scripts = ['trilium-portable', 'trilium-safe-mode', 'trilium-no-cert-check']
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
@@ -86,6 +104,7 @@ function getExtraResourcesForPlatform() {
|
||||
case 'darwin':
|
||||
break;
|
||||
case 'linux':
|
||||
resources.push("images/app-icons/png/256x256.png")
|
||||
for (const script of scripts) {
|
||||
resources.push(`./bin/tpl/${script}.sh`)
|
||||
}
|
||||
|
||||
@@ -526,16 +526,19 @@
|
||||
/* @ckeditor/ckeditor5-code-block/theme/codeblock.css */
|
||||
.ck-content pre {
|
||||
padding: 1em;
|
||||
color: hsl(0, 0%, 20.8%);
|
||||
background: hsla(0, 0%, 78%, 0.3);
|
||||
border: 1px solid hsl(0, 0%, 77%);
|
||||
border-radius: 2px;
|
||||
text-align: left;
|
||||
direction: ltr;
|
||||
tab-size: 4;
|
||||
white-space: pre-wrap;
|
||||
font-style: normal;
|
||||
min-width: 200px;
|
||||
border: 0px;
|
||||
border-radius: 6px;
|
||||
box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.ck-content pre:not(.hljs) {
|
||||
color: hsl(0, 0%, 20.8%);
|
||||
background: hsla(0, 0%, 78%, 0.3);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-code-block/theme/codeblock.css */
|
||||
.ck-content pre code {
|
||||
|
||||
2
libraries/ckeditor/ckeditor.js
vendored
2
libraries/ckeditor/ckeditor.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,10 @@
|
||||
"ignore": [".git", "node_modules/**/node_modules", "src/public/"],
|
||||
"verbose": false,
|
||||
"exec": "tsx",
|
||||
"watch": ["src/"],
|
||||
"watch": [
|
||||
"src/",
|
||||
"translations/"
|
||||
],
|
||||
"signal": "SIGTERM",
|
||||
"env": {
|
||||
"NODE_ENV": "development"
|
||||
|
||||
2547
package-lock.json
generated
2547
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
219
package.json
219
package.json
@@ -2,9 +2,9 @@
|
||||
"name": "trilium",
|
||||
"productName": "TriliumNext Notes",
|
||||
"description": "Build your personal knowledge base with TriliumNext Notes",
|
||||
"version": "0.90.6-beta",
|
||||
"version": "0.90.10-beta",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "./dist/electron.js",
|
||||
"main": "./dist/electron-main.js",
|
||||
"author": {
|
||||
"name": "TriliumNext Notes Team",
|
||||
"email": "contact@eliandoran.me",
|
||||
@@ -12,7 +12,7 @@
|
||||
},
|
||||
"copyright": "",
|
||||
"bin": {
|
||||
"trilium": "src/www.js"
|
||||
"trilium": "src/main.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -20,12 +20,12 @@
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start-server": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.ts",
|
||||
"start-server-safe": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.ts",
|
||||
"start-server-no-dir": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.ts",
|
||||
"start-test-server": "npm run switch-server; rimraf ./data-test; cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data-test TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev TRILIUM_PORT=9999 ts-node src/www.ts",
|
||||
"start-server": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/main.ts",
|
||||
"start-server-safe": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/main.ts",
|
||||
"start-server-no-dir": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/main.ts",
|
||||
"start-test-server": "npm run switch-server; rimraf ./data-test; cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data-test TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev TRILIUM_PORT=9999 ts-node src/main.ts",
|
||||
"qstart-server": "npm run switch-server && npm run start-server",
|
||||
"start-electron": "npm run prepare-dist && cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron ./dist/electron.js --inspect=5858 .",
|
||||
"start-electron": "npm run prepare-dist && cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron ./dist/electron-main.js --inspect=5858 .",
|
||||
"start-electron-no-dir": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 electron --inspect=5858 .",
|
||||
"qstart-electron": "npm run switch-electron && npm run start-electron",
|
||||
"switch-server": "rimraf ./node_modules/better-sqlite3 && npm install",
|
||||
@@ -34,7 +34,7 @@
|
||||
"build-frontend-docs": "rimraf ./docs/frontend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/basic_widget.js src/public/app/widgets/note_context_aware_widget.js src/public/app/widgets/right_panel_widget.js",
|
||||
"build-docs": "npm run build-backend-docs && npm run build-frontend-docs",
|
||||
"webpack": "cross-env node --import ./loader-register.js node_modules/webpack/bin/webpack.js -c webpack.config.ts",
|
||||
"test-jasmine": "cross-env TRILIUM_DATA_DIR=./data-test tsx ./node_modules/.bin/jasmine",
|
||||
"test-jasmine": "cross-env TRILIUM_DATA_DIR=./data-test tsx ./node_modules/jasmine/bin/jasmine.js",
|
||||
"test-es6": "tsx -r esm spec-es6/attribute_parser.spec.ts",
|
||||
"test": "npm run test-jasmine && npm run test-es6",
|
||||
"start-electron-forge": "npm run prepare-dist && electron-forge start",
|
||||
@@ -43,155 +43,158 @@
|
||||
"prepare-dist": "rimraf ./dist && tsc && tsx ./bin/copy-dist.ts",
|
||||
"update-build-info": "tsx bin/update-build-info.ts",
|
||||
"errors": "tsc --watch --noEmit",
|
||||
"integration-edit-db": "cross-env TRILIUM_INTEGRATION_TEST=edit TRILIUM_PORT=8081 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/www.ts",
|
||||
"integration-mem-db": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/www.ts",
|
||||
"integration-mem-db-dev": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/www.ts",
|
||||
"generate-document": "cross-env nodemon src/tools/generate_document.ts 1000"
|
||||
"integration-edit-db": "cross-env TRILIUM_INTEGRATION_TEST=edit TRILIUM_PORT=8081 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
|
||||
"integration-mem-db": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
|
||||
"integration-mem-db-dev": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
|
||||
"generate-document": "cross-env nodemon src/tools/generate_document.ts 1000",
|
||||
"ci-update-nightly-version": "tsx ./bin/update-nightly-version.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^7.1.0",
|
||||
"@braintree/sanitize-url": "7.1.0",
|
||||
"@electron/remote": "2.1.2",
|
||||
"@excalidraw/excalidraw": "^0.17.6",
|
||||
"@types/electron-squirrel-startup": "^1.0.2",
|
||||
"archiver": "^7.0.1",
|
||||
"async-mutex": "^0.5.0",
|
||||
"autocomplete.js": "^0.38.1",
|
||||
"axios": "^1.7.2",
|
||||
"better-sqlite3": "^11.1.2",
|
||||
"bootstrap": "^4.6.2",
|
||||
"@excalidraw/excalidraw": "0.17.6",
|
||||
"@highlightjs/cdn-assets": "11.10.0",
|
||||
"archiver": "7.0.1",
|
||||
"async-mutex": "0.5.0",
|
||||
"autocomplete.js": "0.38.1",
|
||||
"axios": "1.7.7",
|
||||
"better-sqlite3": "11.3.0",
|
||||
"bootstrap": "5.3.3",
|
||||
"boxicons": "2.1.4",
|
||||
"chokidar": "3.6.0",
|
||||
"cls-hooked": "4.2.2",
|
||||
"codemirror": "^5.65.17",
|
||||
"codemirror": "5.65.18",
|
||||
"compression": "1.7.4",
|
||||
"cookie-parser": "1.4.6",
|
||||
"cookie-parser": "1.4.7",
|
||||
"csurf": "1.11.0",
|
||||
"dayjs": "^1.11.12",
|
||||
"dayjs": "1.11.13",
|
||||
"dayjs-plugin-utc": "0.1.2",
|
||||
"debounce": "^2.1.0",
|
||||
"ejs": "^3.1.10",
|
||||
"electron-debug": "3.2.0",
|
||||
"electron-dl": "3.5.2",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"debounce": "2.2.0",
|
||||
"ejs": "3.1.10",
|
||||
"electron-debug": "4.0.1",
|
||||
"electron-dl": "4.0.0",
|
||||
"electron-squirrel-startup": "1.0.1",
|
||||
"electron-window-state": "5.0.3",
|
||||
"escape-html": "1.0.3",
|
||||
"eslint": "^9.9.0",
|
||||
"express": "^4.19.2",
|
||||
"eslint": "9.14.0",
|
||||
"express": "4.21.1",
|
||||
"express-partial-content": "1.0.2",
|
||||
"express-rate-limit": "^7.3.1",
|
||||
"express-session": "1.18.0",
|
||||
"force-graph": "1.43.5",
|
||||
"express-rate-limit": "7.4.1",
|
||||
"express-session": "1.18.1",
|
||||
"force-graph": "1.45.0",
|
||||
"fs-extra": "11.2.0",
|
||||
"helmet": "7.1.0",
|
||||
"html": "1.0.0",
|
||||
"html2plaintext": "2.1.4",
|
||||
"http-proxy-agent": "7.0.2",
|
||||
"https-proxy-agent": "^7.0.5",
|
||||
"i18next": "^23.14.0",
|
||||
"i18next-http-backend": "^2.6.1",
|
||||
"https-proxy-agent": "7.0.5",
|
||||
"i18next": "23.16.4",
|
||||
"i18next-fs-backend": "2.3.2",
|
||||
"i18next-http-backend": "2.6.2",
|
||||
"image-type": "4.1.0",
|
||||
"ini": "^4.1.3",
|
||||
"ini": "5.0.0",
|
||||
"is-animated": "2.0.2",
|
||||
"is-svg": "4.3.2",
|
||||
"jimp": "0.22.12",
|
||||
"joplin-turndown-plugin-gfm": "1.0.12",
|
||||
"jquery": "3.7.1",
|
||||
"jquery-hotkeys": "0.2.2",
|
||||
"jquery.fancytree": "^2.38.3",
|
||||
"jsdom": "^24.1.0",
|
||||
"jsplumb": "^2.15.6",
|
||||
"katex": "^0.16.11",
|
||||
"knockout": "^3.5.1",
|
||||
"mark.js": "^8.11.1",
|
||||
"marked": "^13.0.2",
|
||||
"mermaid": "^10.9.1",
|
||||
"jquery.fancytree": "2.38.3",
|
||||
"jsdom": "25.0.0",
|
||||
"jsplumb": "2.15.6",
|
||||
"katex": "0.16.11",
|
||||
"knockout": "3.5.1",
|
||||
"mark.js": "8.11.1",
|
||||
"marked": "14.1.3",
|
||||
"mermaid": "11.4.0",
|
||||
"mime-types": "2.1.35",
|
||||
"mind-elixir": "^4.0.5",
|
||||
"mind-elixir": "4.2.4",
|
||||
"multer": "1.4.5-lts.1",
|
||||
"node-abi": "^3.65.0",
|
||||
"node-abi": "3.67.0",
|
||||
"normalize-strings": "1.1.1",
|
||||
"normalize.css": "^8.0.1",
|
||||
"normalize.css": "8.0.1",
|
||||
"panzoom": "9.4.3",
|
||||
"print-this": "2.0.0",
|
||||
"rand-token": "1.0.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"request": "2.88.2",
|
||||
"safe-compare": "1.1.4",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"sanitize-html": "^2.13.0",
|
||||
"sax": "^1.4.1",
|
||||
"semver": "^7.6.3",
|
||||
"sanitize-html": "2.13.1",
|
||||
"sax": "1.4.1",
|
||||
"semver": "7.6.3",
|
||||
"serve-favicon": "2.5.0",
|
||||
"session-file-store": "1.5.0",
|
||||
"source-map-support": "^0.5.21",
|
||||
"source-map-support": "0.5.21",
|
||||
"split.js": "1.6.5",
|
||||
"stream-throttle": "0.1.3",
|
||||
"striptags": "3.2.0",
|
||||
"tmp": "0.2.3",
|
||||
"tree-kill": "1.2.2",
|
||||
"turndown": "^7.2.0",
|
||||
"turndown": "7.2.0",
|
||||
"unescape": "1.0.1",
|
||||
"vanilla-js-wheel-zoom": "^9.0.2",
|
||||
"ws": "^8.18.0",
|
||||
"vanilla-js-wheel-zoom": "9.0.4",
|
||||
"ws": "8.18.0",
|
||||
"xml2js": "0.6.2",
|
||||
"yauzl": "^3.1.3"
|
||||
"yauzl": "3.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-forge/cli": "^7.4.0",
|
||||
"@electron-forge/maker-deb": "^7.4.0",
|
||||
"@electron-forge/maker-dmg": "^7.4.0",
|
||||
"@electron-forge/maker-squirrel": "^7.4.0",
|
||||
"@electron-forge/maker-zip": "^7.4.0",
|
||||
"@electron-forge/plugin-auto-unpack-natives": "^7.4.0",
|
||||
"@playwright/test": "^1.46.0",
|
||||
"@types/archiver": "^6.0.2",
|
||||
"@types/better-sqlite3": "^7.6.9",
|
||||
"@types/cls-hooked": "^4.3.8",
|
||||
"@types/compression": "^1.7.5",
|
||||
"@types/cookie-parser": "^1.4.7",
|
||||
"@types/csurf": "^1.11.5",
|
||||
"@types/debounce": "^1.2.4",
|
||||
"@types/ejs": "^3.1.5",
|
||||
"@types/escape-html": "^1.0.4",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/express-session": "^1.18.0",
|
||||
"@types/html": "^1.0.4",
|
||||
"@types/ini": "^4.1.0",
|
||||
"@types/jasmine": "^5.1.4",
|
||||
"@types/jsdom": "^21.1.6",
|
||||
"@types/mime-types": "^2.1.4",
|
||||
"@types/multer": "^1.4.12",
|
||||
"@types/node": "^22.5.2",
|
||||
"@types/safe-compare": "^1.1.2",
|
||||
"@types/sanitize-html": "^2.13.0",
|
||||
"@types/sax": "^1.2.7",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/serve-favicon": "^2.5.7",
|
||||
"@types/session-file-store": "^1.2.5",
|
||||
"@types/source-map-support": "^0.5.10",
|
||||
"@types/stream-throttle": "^0.1.4",
|
||||
"@types/tmp": "^0.2.6",
|
||||
"@types/turndown": "^5.0.5",
|
||||
"@types/ws": "^8.5.12",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"@electron-forge/cli": "7.5.0",
|
||||
"@electron-forge/maker-deb": "7.5.0",
|
||||
"@electron-forge/maker-dmg": "7.5.0",
|
||||
"@electron-forge/maker-squirrel": "7.5.0",
|
||||
"@electron-forge/maker-zip": "7.5.0",
|
||||
"@electron-forge/plugin-auto-unpack-natives": "7.5.0",
|
||||
"@playwright/test": "1.48.2",
|
||||
"@types/archiver": "6.0.2",
|
||||
"@types/better-sqlite3": "7.6.11",
|
||||
"@types/cls-hooked": "4.3.8",
|
||||
"@types/compression": "1.7.5",
|
||||
"@types/cookie-parser": "1.4.7",
|
||||
"@types/csurf": "1.11.5",
|
||||
"@types/debounce": "1.2.4",
|
||||
"@types/ejs": "3.1.5",
|
||||
"@types/electron-squirrel-startup": "1.0.2",
|
||||
"@types/escape-html": "1.0.4",
|
||||
"@types/express": "4.17.21",
|
||||
"@types/express-session": "1.18.0",
|
||||
"@types/html": "1.0.4",
|
||||
"@types/ini": "4.1.1",
|
||||
"@types/jasmine": "5.1.4",
|
||||
"@types/jsdom": "21.1.7",
|
||||
"@types/mime-types": "2.1.4",
|
||||
"@types/multer": "1.4.12",
|
||||
"@types/node": "22.7.8",
|
||||
"@types/safe-compare": "1.1.2",
|
||||
"@types/sanitize-html": "2.13.0",
|
||||
"@types/sax": "1.2.7",
|
||||
"@types/semver": "7.5.8",
|
||||
"@types/serve-favicon": "2.5.7",
|
||||
"@types/session-file-store": "1.2.5",
|
||||
"@types/source-map-support": "0.5.10",
|
||||
"@types/stream-throttle": "0.1.4",
|
||||
"@types/tmp": "0.2.6",
|
||||
"@types/turndown": "5.0.5",
|
||||
"@types/ws": "8.5.12",
|
||||
"@types/xml2js": "0.4.14",
|
||||
"cross-env": "7.0.3",
|
||||
"electron": "^31.2.1",
|
||||
"electron": "31.3.1",
|
||||
"electron-packager": "17.1.2",
|
||||
"electron-rebuild": "3.2.9",
|
||||
"esm": "3.2.25",
|
||||
"iconsur": "^1.7.0",
|
||||
"jasmine": "5.1.0",
|
||||
"jsdoc": "^4.0.3",
|
||||
"iconsur": "1.7.0",
|
||||
"jasmine": "5.4.0",
|
||||
"jsdoc": "4.0.3",
|
||||
"lorem-ipsum": "2.0.8",
|
||||
"nodemon": "^3.1.4",
|
||||
"nodemon": "3.1.7",
|
||||
"rcedit": "4.0.1",
|
||||
"rimraf": "^6.0.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"tslib": "^2.7.0",
|
||||
"tsx": "^4.19.0",
|
||||
"typescript": "^5.5.4",
|
||||
"webpack": "^5.93.0",
|
||||
"rimraf": "6.0.1",
|
||||
"ts-node": "10.9.2",
|
||||
"tslib": "2.8.1",
|
||||
"tsx": "4.19.2",
|
||||
"typescript": "5.6.3",
|
||||
"webpack": "5.96.1",
|
||||
"webpack-cli": "5.1.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import custom from "./routes/custom.js";
|
||||
import error_handlers from "./routes/error_handlers.js";
|
||||
import { startScheduledCleanup } from "./services/erase.js";
|
||||
import sql_init from "./services/sql_init.js";
|
||||
import { t } from "i18next";
|
||||
|
||||
await import('./services/handlers.js');
|
||||
await import('./becca/becca_loader.js');
|
||||
@@ -29,6 +30,11 @@ sql_init.initializeDb();
|
||||
app.set('views', path.join(scriptDir, 'views'));
|
||||
app.set('view engine', 'ejs');
|
||||
|
||||
app.use((req, res, next) => {
|
||||
res.locals.t = t;
|
||||
return next();
|
||||
});
|
||||
|
||||
if (!utils.isElectron()) {
|
||||
app.use(compression()); // HTTP compression
|
||||
}
|
||||
@@ -47,6 +53,7 @@ app.use(cookieParser());
|
||||
app.use(express.static(path.join(scriptDir, 'public/root')));
|
||||
app.use(`/manifest.webmanifest`, express.static(path.join(scriptDir, 'public/manifest.webmanifest')));
|
||||
app.use(`/robots.txt`, express.static(path.join(scriptDir, 'public/robots.txt')));
|
||||
app.use(`/icon.png`, express.static(path.join(scriptDir, 'public/icon.png')));
|
||||
app.use(sessionParser);
|
||||
app.use(favicon(`${scriptDir}/../images/app-icons/icon.ico`));
|
||||
|
||||
|
||||
@@ -13,11 +13,13 @@ import cls from "../services/cls.js";
|
||||
import entityConstructor from "../becca/entity_constructor.js";
|
||||
import { AttributeRow, BranchRow, EtapiTokenRow, NoteRow, OptionRow } from './entities/rows.js';
|
||||
import AbstractBeccaEntity from "./entities/abstract_becca_entity.js";
|
||||
import options_init from "../services/options_init.js";
|
||||
import ws from "../services/ws.js";
|
||||
|
||||
const beccaLoaded = new Promise<void>(async (res, rej) => {
|
||||
const sqlInit = (await import("../services/sql_init.js")).default;
|
||||
// We have to import async since options init requires keyboard actions which require translations.
|
||||
const options_init = (await import("../services/options_init.js")).default;
|
||||
|
||||
sqlInit.dbReady.then(() => {
|
||||
cls.init(() => {
|
||||
load();
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import protectedSessionService from "../../services/protected_session.js";
|
||||
import log from "../../services/log.js";
|
||||
import sql from "../../services/sql.js";
|
||||
import optionService from "../../services/options.js";
|
||||
import eraseService from "../../services/erase.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import dateUtils from "../../services/date_utils.js";
|
||||
import AbstractBeccaEntity from "./abstract_becca_entity.js";
|
||||
@@ -68,7 +70,7 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
/** set during the deletion operation, before it is completed (removed from becca completely). */
|
||||
isBeingDeleted!: boolean;
|
||||
isDecrypted!: boolean;
|
||||
|
||||
|
||||
ownedAttributes!: BAttribute[];
|
||||
parentBranches!: BBranch[];
|
||||
parents!: BNote[];
|
||||
@@ -455,8 +457,8 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
|
||||
return this.getAttributes().find(
|
||||
attr => attr.name.toLowerCase() === name
|
||||
&& (!value || attr.value.toLowerCase() === value)
|
||||
&& attr.type === type);
|
||||
&& (!value || attr.value.toLowerCase() === value)
|
||||
&& attr.type === type);
|
||||
}
|
||||
|
||||
getRelationTarget(name: string) {
|
||||
@@ -1107,7 +1109,7 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
}
|
||||
|
||||
getRevisions(): BRevision[] {
|
||||
return sql.getRows<RevisionRow>("SELECT * FROM revisions WHERE noteId = ?", [this.noteId])
|
||||
return sql.getRows<RevisionRow>("SELECT * FROM revisions WHERE noteId = ? ORDER BY revisions.utcDateCreated ASC", [this.noteId])
|
||||
.map(row => new BRevision(row));
|
||||
}
|
||||
|
||||
@@ -1612,10 +1614,31 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
|
||||
revision.setContent(noteContent);
|
||||
|
||||
this.eraseExcessRevisionSnapshots()
|
||||
return revision;
|
||||
});
|
||||
}
|
||||
|
||||
// Limit the number of Snapshots to revisionSnapshotNumberLimit
|
||||
// Delete older Snapshots that exceed the limit
|
||||
eraseExcessRevisionSnapshots() {
|
||||
// lable has a higher priority
|
||||
let revisionSnapshotNumberLimit = parseInt(this.getLabelValue("versioningLimit") ?? "");
|
||||
if (!Number.isInteger(revisionSnapshotNumberLimit)) {
|
||||
revisionSnapshotNumberLimit = parseInt(optionService.getOption('revisionSnapshotNumberLimit'));
|
||||
}
|
||||
if (revisionSnapshotNumberLimit >= 0) {
|
||||
const revisions = this.getRevisions();
|
||||
if (revisions.length - revisionSnapshotNumberLimit > 0) {
|
||||
const revisionIds = revisions
|
||||
.slice(0, revisions.length - revisionSnapshotNumberLimit)
|
||||
.map(revision => revision.revisionId)
|
||||
.filter((id): id is string => id !== undefined);
|
||||
eraseService.eraseRevisions(revisionIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param matchBy - choose by which property we detect if to update an existing attachment.
|
||||
* Supported values are either 'attachmentId' (default) or 'title'
|
||||
|
||||
@@ -38,9 +38,18 @@ export interface RecentNoteRow {
|
||||
utcDateCreated?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Database representation of an option.
|
||||
*
|
||||
* Options are key-value pairs that are used to store information such as user preferences (for example
|
||||
* the current theme, sync server information), but also information about the state of the application).
|
||||
*/
|
||||
export interface OptionRow {
|
||||
/** The name of the option. */
|
||||
name: string;
|
||||
/** The value of the option. */
|
||||
value: string;
|
||||
/** `true` if the value should be synced across multiple instances (e.g. locale) or `false` if it should be local-only (e.g. theme). */
|
||||
isSynced: boolean;
|
||||
utcDateModified?: string;
|
||||
}
|
||||
|
||||
13
src/main.ts
Normal file
13
src/main.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Make sure not to import any modules that depend on localized messages via i18next here, as the initializations
|
||||
* are loaded later and will result in an empty string.
|
||||
*/
|
||||
|
||||
import { initializeTranslations } from "./services/i18n.js";
|
||||
|
||||
async function startApplication() {
|
||||
await import("./www.js");
|
||||
}
|
||||
|
||||
await initializeTranslations();
|
||||
await startApplication();
|
||||
@@ -13,7 +13,7 @@ import MobileScreenSwitcherExecutor from "./mobile_screen_switcher.js";
|
||||
import MainTreeExecutors from "./main_tree_executors.js";
|
||||
import toast from "../services/toast.js";
|
||||
import ShortcutComponent from "./shortcut_component.js";
|
||||
import { initLocale } from "../services/i18n.js";
|
||||
import { t, initLocale } from "../services/i18n.js";
|
||||
|
||||
class AppContext extends Component {
|
||||
constructor(isMainWindow) {
|
||||
@@ -33,11 +33,11 @@ class AppContext extends Component {
|
||||
await initLocale();
|
||||
}
|
||||
|
||||
setLayout(layout) {
|
||||
setLayout(layout) {
|
||||
this.layout = layout;
|
||||
}
|
||||
|
||||
async start() {
|
||||
async start() {
|
||||
this.initComponents();
|
||||
this.renderWidgets();
|
||||
|
||||
@@ -151,7 +151,7 @@ $(window).on('beforeunload', () => {
|
||||
if (!component.beforeUnloadEvent()) {
|
||||
console.log(`Component ${component.componentId} is not finished saving its state.`);
|
||||
|
||||
toast.showMessage("Please wait for a couple of seconds for the save to finish, then you can try again.", 10000);
|
||||
toast.showMessage(t("app_context.please_wait_for_save"), 10000);
|
||||
|
||||
allSaved = false;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import ws from "../services/ws.js";
|
||||
import bundleService from "../services/bundle.js";
|
||||
import froca from "../services/froca.js";
|
||||
import linkService from "../services/link.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
|
||||
export default class Entrypoints extends Component {
|
||||
constructor() {
|
||||
@@ -172,13 +173,13 @@ export default class Entrypoints extends Component {
|
||||
const resp = await server.post(`sql/execute/${note.noteId}`);
|
||||
|
||||
if (!resp.success) {
|
||||
toastService.showError(`Error occurred while executing SQL query: ${resp.error}`);
|
||||
toastService.showError(t("entrypoints.sql-error", { message: resp.error }));
|
||||
}
|
||||
|
||||
await appContext.triggerEvent('sqlQueryResults', {ntxId: ntxId, results: resp.results});
|
||||
}
|
||||
|
||||
toastService.showMessage("Note executed");
|
||||
toastService.showMessage(t("entrypoints.note-executed"));
|
||||
}
|
||||
|
||||
hideAllPopups() {
|
||||
@@ -200,6 +201,6 @@ export default class Entrypoints extends Component {
|
||||
|
||||
await server.post(`notes/${noteId}/revision`);
|
||||
|
||||
toastService.showMessage("Note revision has been created.");
|
||||
toastService.showMessage(t("entrypoints.note-revision-created"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,7 +551,7 @@ export default class TabManager extends Component {
|
||||
await this.removeNoteContext(ntxIdToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async closeOtherTabsCommand({ntxId}) {
|
||||
for (const ntxIdToRemove of this.mainNoteContexts.map(nc => nc.ntxId)) {
|
||||
if (ntxIdToRemove !== ntxId) {
|
||||
@@ -560,6 +560,18 @@ export default class TabManager extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
async closeRightTabsCommand({ntxId}) {
|
||||
const ntxIds = this.mainNoteContexts.map(nc => nc.ntxId);
|
||||
const index = ntxIds.indexOf(ntxId);
|
||||
|
||||
if (index !== -1) {
|
||||
const idsToRemove = ntxIds.slice(index + 1);
|
||||
for (const ntxIdToRemove of idsToRemove) {
|
||||
await this.removeNoteContext(ntxIdToRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async closeTabCommand({ntxId}) {
|
||||
await this.removeNoteContext(ntxId);
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ import electronContextMenu from "./menus/electron_context_menu.js";
|
||||
import glob from "./services/glob.js";
|
||||
import { t } from "./services/i18n.js";
|
||||
|
||||
await appContext.earlyInit();
|
||||
|
||||
bundleService.getWidgetBundlesByParent().then(async widgetBundles => {
|
||||
await appContext.earlyInit();
|
||||
|
||||
// A dynamic import is required for layouts since they initialize components which require translations.
|
||||
const DesktopLayout = (await import("./layouts/desktop_layout.js")).default;
|
||||
|
||||
|
||||
@@ -14,9 +14,9 @@ const NOTE_TYPE_ICONS = {
|
||||
"code": "bx bx-code",
|
||||
"render": "bx bx-extension",
|
||||
"search": "bx bx-file-find",
|
||||
"relationMap": "bx bx-map-alt",
|
||||
"relationMap": "bx bxs-network-chart",
|
||||
"book": "bx bx-book",
|
||||
"noteMap": "bx bx-map-alt",
|
||||
"noteMap": "bx bxs-network-chart",
|
||||
"mermaid": "bx bx-selection",
|
||||
"canvas": "bx bx-pen",
|
||||
"webView": "bx bx-globe-alt",
|
||||
@@ -543,7 +543,7 @@ class FNote {
|
||||
return workspaceIconClass;
|
||||
}
|
||||
else if (this.noteId === 'root') {
|
||||
return "bx bx-chevrons-right";
|
||||
return "bx bx-home-alt-2";
|
||||
}
|
||||
if (this.noteId === '_share') {
|
||||
return "bx bx-share-alt";
|
||||
|
||||
@@ -38,6 +38,8 @@ import SimilarNotesWidget from "../widgets/ribbon_widgets/similar_notes.js";
|
||||
import RightPaneContainer from "../widgets/containers/right_pane_container.js";
|
||||
import EditButton from "../widgets/buttons/edit_button.js";
|
||||
import EditedNotesWidget from "../widgets/ribbon_widgets/edited_notes.js";
|
||||
import ShowTocWidgetButton from "../widgets/buttons/show_toc_widget_button.js";
|
||||
import ShowHighlightsListWidgetButton from "../widgets/buttons/show_highlights_list_widget_button.js";
|
||||
import MermaidWidget from "../widgets/mermaid.js";
|
||||
import NoteWrapperWidget from "../widgets/note_wrapper.js";
|
||||
import BacklinksWidget from "../widgets/floating_buttons/zpetne_odkazy.js";
|
||||
@@ -160,6 +162,8 @@ export default class DesktopLayout {
|
||||
.child(new WatchedFileUpdateStatusWidget())
|
||||
.child(new FloatingButtons()
|
||||
.child(new EditButton())
|
||||
.child(new ShowTocWidgetButton())
|
||||
.child(new ShowHighlightsListWidgetButton())
|
||||
.child(new CodeButtonsWidget())
|
||||
.child(new RelationMapButtons())
|
||||
.child(new CopyImageReferenceButton())
|
||||
|
||||
@@ -22,15 +22,31 @@ function setupContextMenu($image) {
|
||||
command: "copyImageReferenceToClipboard",
|
||||
uiIcon: "bx bx-empty"
|
||||
},
|
||||
{title: "Copy image to clipboard", command: "copyImageToClipboard", uiIcon: "bx bx-empty"},
|
||||
{ title: "Copy image to clipboard", command: "copyImageToClipboard", uiIcon: "bx bx-empty" },
|
||||
],
|
||||
selectMenuItemHandler: ({command}) => {
|
||||
selectMenuItemHandler: async ({ command }) => {
|
||||
if (command === 'copyImageReferenceToClipboard') {
|
||||
imageService.copyImageReferenceToClipboard($image);
|
||||
} else if (command === 'copyImageToClipboard') {
|
||||
const webContents = utils.dynamicRequire('@electron/remote').getCurrentWebContents();
|
||||
utils.dynamicRequire('electron');
|
||||
webContents.copyImageAt(e.pageX, e.pageY);
|
||||
try {
|
||||
const nativeImage = utils.dynamicRequire('electron').nativeImage;
|
||||
const clipboard = utils.dynamicRequire('electron').clipboard;
|
||||
|
||||
const response = await fetch(
|
||||
$image.attr('src')
|
||||
);
|
||||
const blob = await response.blob();
|
||||
|
||||
clipboard.writeImage(
|
||||
nativeImage.createFromBuffer(
|
||||
Buffer.from(
|
||||
await blob.arrayBuffer()
|
||||
)
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Failed to copy image to clipboard:', error);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Unrecognized command '${command}'`);
|
||||
}
|
||||
@@ -41,4 +57,4 @@ function setupContextMenu($image) {
|
||||
|
||||
export default {
|
||||
setupContextMenu
|
||||
};
|
||||
};
|
||||
@@ -3,6 +3,7 @@ import froca from "../services/froca.js";
|
||||
import contextMenu from "./context_menu.js";
|
||||
import dialogService from "../services/dialog.js";
|
||||
import server from "../services/server.js";
|
||||
import { t } from '../services/i18n.js';
|
||||
|
||||
export default class LauncherContextMenu {
|
||||
/**
|
||||
@@ -33,29 +34,27 @@ export default class LauncherContextMenu {
|
||||
const isAvailableItem = parentNoteId === '_lbAvailableLaunchers';
|
||||
const isItem = isVisibleItem || isAvailableItem;
|
||||
const canBeDeleted = !note.noteId.startsWith("_"); // fixed notes can't be deleted
|
||||
const canBeReset = !canBeDeleted && note.isLaunchBarConfig();;
|
||||
const canBeReset = !canBeDeleted && note.isLaunchBarConfig();
|
||||
|
||||
return [
|
||||
(isVisibleRoot || isAvailableRoot) ? { title: 'Add a note launcher', command: 'addNoteLauncher', uiIcon: "bx bx-plus" } : null,
|
||||
(isVisibleRoot || isAvailableRoot) ? { title: 'Add a script launcher', command: 'addScriptLauncher', uiIcon: "bx bx-plus" } : null,
|
||||
(isVisibleRoot || isAvailableRoot) ? { title: 'Add a custom widget', command: 'addWidgetLauncher', uiIcon: "bx bx-plus" } : null,
|
||||
(isVisibleRoot || isAvailableRoot) ? { title: 'Add spacer', command: 'addSpacerLauncher', uiIcon: "bx bx-plus" } : null,
|
||||
(isVisibleRoot || isAvailableRoot) ? { title: t("launcher_context_menu.add-note-launcher"), command: 'addNoteLauncher', uiIcon: "bx bx-plus" } : null,
|
||||
(isVisibleRoot || isAvailableRoot) ? { title: t("launcher_context_menu.add-script-launcher"), command: 'addScriptLauncher', uiIcon: "bx bx-plus" } : null,
|
||||
(isVisibleRoot || isAvailableRoot) ? { title: t("launcher_context_menu.add-custom-widget"), command: 'addWidgetLauncher', uiIcon: "bx bx-plus" } : null,
|
||||
(isVisibleRoot || isAvailableRoot) ? { title: t("launcher_context_menu.add-spacer"), command: 'addSpacerLauncher', uiIcon: "bx bx-plus" } : null,
|
||||
(isVisibleRoot || isAvailableRoot) ? { title: "----" } : null,
|
||||
{ title: 'Delete <kbd data-command="deleteNotes"></kbd>', command: "deleteNotes", uiIcon: "bx bx-trash", enabled: canBeDeleted },
|
||||
{ title: 'Reset', command: "resetLauncher", uiIcon: "bx bx-empty", enabled: canBeReset},
|
||||
{ title: `${t("launcher_context_menu.delete")} <kbd data-command="deleteNotes"></kbd>`, command: "deleteNotes", uiIcon: "bx bx-trash", enabled: canBeDeleted },
|
||||
{ title: t("launcher_context_menu.reset"), command: "resetLauncher", uiIcon: "bx bx-empty", enabled: canBeReset},
|
||||
{ title: "----" },
|
||||
isAvailableItem ? { title: 'Move to visible launchers', command: "moveLauncherToVisible", uiIcon: "bx bx-show", enabled: true } : null,
|
||||
isVisibleItem ? { title: 'Move to available launchers', command: "moveLauncherToAvailable", uiIcon: "bx bx-hide", enabled: true } : null,
|
||||
{ title: `Duplicate launcher <kbd data-command="duplicateSubtree">`, command: "duplicateSubtree", uiIcon: "bx bx-empty",
|
||||
isAvailableItem ? { title: t("launcher_context_menu.move-to-visible-launchers"), command: "moveLauncherToVisible", uiIcon: "bx bx-show", enabled: true } : null,
|
||||
isVisibleItem ? { title: t("launcher_context_menu.move-to-available-launchers"), command: "moveLauncherToAvailable", uiIcon: "bx bx-hide", enabled: true } : null,
|
||||
{ title: `${t("launcher_context_menu.duplicate-launcher")} <kbd data-command="duplicateSubtree">`, command: "duplicateSubtree", uiIcon: "bx bx-empty",
|
||||
enabled: isItem }
|
||||
].filter(row => row !== null);
|
||||
}
|
||||
|
||||
async selectMenuItemHandler({command}) {
|
||||
if (command === 'resetLauncher') {
|
||||
const confirmed = await dialogService.confirm(`Do you really want to reset "${this.node.title}"?
|
||||
All data / settings in this note (and its children) will be lost
|
||||
and the launcher will be returned to its original location.`);
|
||||
const confirmed = await dialogService.confirm(t("launcher_context_menu.reset_launcher_confirm", { title: this.node.title }));
|
||||
|
||||
if (confirmed) {
|
||||
await server.post(`special-notes/launchers/${this.node.data.noteId}/reset`);
|
||||
|
||||
@@ -8,6 +8,7 @@ import noteTypesService from "../services/note_types.js";
|
||||
import server from "../services/server.js";
|
||||
import toastService from "../services/toast.js";
|
||||
import dialogService from "../services/dialog.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
|
||||
export default class TreeContextMenu {
|
||||
/**
|
||||
@@ -48,55 +49,55 @@ export default class TreeContextMenu {
|
||||
const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNotSearch;
|
||||
|
||||
return [
|
||||
{ title: 'Open in a new tab <kbd>Ctrl+Click</kbd>', command: "openInTab", uiIcon: "bx bx-empty", enabled: noSelectedNotes },
|
||||
{ title: 'Open in a new split', command: "openNoteInSplit", uiIcon: "bx bx-dock-right", enabled: noSelectedNotes },
|
||||
{ title: 'Insert note after <kbd data-command="createNoteAfter"></kbd>', command: "insertNoteAfter", uiIcon: "bx bx-plus",
|
||||
{ title: `${t("tree-context-menu.open-in-a-new-tab")} <kbd>Ctrl+Click</kbd>`, command: "openInTab", uiIcon: "bx bx-empty", enabled: noSelectedNotes },
|
||||
{ title: t("tree-context-menu.open-in-a-new-split"), command: "openNoteInSplit", uiIcon: "bx bx-dock-right", enabled: noSelectedNotes },
|
||||
{ title: `${t("tree-context-menu.insert-note-after")} <kbd data-command="createNoteAfter"></kbd>`, command: "insertNoteAfter", uiIcon: "bx bx-plus",
|
||||
items: insertNoteAfterEnabled ? await noteTypesService.getNoteTypeItems("insertNoteAfter") : null,
|
||||
enabled: insertNoteAfterEnabled && noSelectedNotes && notOptions },
|
||||
{ title: 'Insert child note <kbd data-command="createNoteInto"></kbd>', command: "insertChildNote", uiIcon: "bx bx-plus",
|
||||
{ title: `${t("tree-context-menu.insert-child-note")} <kbd data-command="createNoteInto"></kbd>`, command: "insertChildNote", uiIcon: "bx bx-plus",
|
||||
items: notSearch ? await noteTypesService.getNoteTypeItems("insertChildNote") : null,
|
||||
enabled: notSearch && noSelectedNotes && notOptions },
|
||||
{ title: 'Delete <kbd data-command="deleteNotes"></kbd>', command: "deleteNotes", uiIcon: "bx bx-trash",
|
||||
{ title: `${t("tree-context-menu.delete")} <kbd data-command="deleteNotes"></kbd>`, command: "deleteNotes", uiIcon: "bx bx-trash",
|
||||
enabled: isNotRoot && !isHoisted && parentNotSearch && notOptions },
|
||||
{ title: "----" },
|
||||
{ title: 'Search in subtree <kbd data-command="searchInSubtree"></kbd>', command: "searchInSubtree", uiIcon: "bx bx-search",
|
||||
{ title: `${t("tree-context-menu.search-in-subtree")} <kbd data-command="searchInSubtree"></kbd>`, command: "searchInSubtree", uiIcon: "bx bx-search",
|
||||
enabled: notSearch && noSelectedNotes },
|
||||
isHoisted ? null : { title: 'Hoist note <kbd data-command="toggleNoteHoisting"></kbd>', command: "toggleNoteHoisting", uiIcon: "bx bx-empty", enabled: noSelectedNotes && notSearch },
|
||||
!isHoisted || !isNotRoot ? null : { title: 'Unhoist note <kbd data-command="toggleNoteHoisting"></kbd>', command: "toggleNoteHoisting", uiIcon: "bx bx-door-open" },
|
||||
{ title: 'Edit branch prefix <kbd data-command="editBranchPrefix"></kbd>', command: "editBranchPrefix", uiIcon: "bx bx-empty",
|
||||
isHoisted ? null : { title: `${t("tree-context-menu.hoist-note")} <kbd data-command="toggleNoteHoisting"></kbd>`, command: "toggleNoteHoisting", uiIcon: "bx bx-empty", enabled: noSelectedNotes && notSearch },
|
||||
!isHoisted || !isNotRoot ? null : { title: `${t("tree-context-menu.unhoist-note")} <kbd data-command="toggleNoteHoisting"></kbd>`, command: "toggleNoteHoisting", uiIcon: "bx bx-door-open" },
|
||||
{ title: `${t("tree-context-menu.edit-branch-prefix")} <kbd data-command="editBranchPrefix"></kbd>`, command: "editBranchPrefix", uiIcon: "bx bx-empty",
|
||||
enabled: isNotRoot && parentNotSearch && noSelectedNotes && notOptions },
|
||||
{ title: "Advanced", uiIcon: "bx bx-empty", enabled: true, items: [
|
||||
{ title: 'Expand subtree <kbd data-command="expandSubtree"></kbd>', command: "expandSubtree", uiIcon: "bx bx-expand", enabled: noSelectedNotes },
|
||||
{ title: 'Collapse subtree <kbd data-command="collapseSubtree"></kbd>', command: "collapseSubtree", uiIcon: "bx bx-collapse", enabled: noSelectedNotes },
|
||||
{ title: 'Sort by ... <kbd data-command="sortChildNotes"></kbd>', command: "sortChildNotes", uiIcon: "bx bx-empty", enabled: noSelectedNotes && notSearch },
|
||||
{ title: 'Recent changes in subtree', command: "recentChangesInSubtree", uiIcon: "bx bx-history", enabled: noSelectedNotes && notOptions },
|
||||
{ title: 'Convert to attachment', command: "convertNoteToAttachment", uiIcon: "bx bx-empty", enabled: isNotRoot && !isHoisted && notOptions },
|
||||
{ title: 'Copy note path to clipboard', command: "copyNotePathToClipboard", uiIcon: "bx bx-empty", enabled: true }
|
||||
{ title: t("tree-context-menu.advanced"), uiIcon: "bx bx-empty", enabled: true, items: [
|
||||
{ title: `${t("tree-context-menu.expand-subtree")} <kbd data-command="expandSubtree"></kbd>`, command: "expandSubtree", uiIcon: "bx bx-expand", enabled: noSelectedNotes },
|
||||
{ title: `${t("tree-context-menu.collapse-subtree")} <kbd data-command="collapseSubtree"></kbd>`, command: "collapseSubtree", uiIcon: "bx bx-collapse", enabled: noSelectedNotes },
|
||||
{ title: `${t("tree-context-menu.sort-by")} <kbd data-command="sortChildNotes"></kbd>`, command: "sortChildNotes", uiIcon: "bx bx-empty", enabled: noSelectedNotes && notSearch },
|
||||
{ title: t("tree-context-menu.recent-changes-in-subtree"), command: "recentChangesInSubtree", uiIcon: "bx bx-history", enabled: noSelectedNotes && notOptions },
|
||||
{ title: t("tree-context-menu.convert-to-attachment"), command: "convertNoteToAttachment", uiIcon: "bx bx-empty", enabled: isNotRoot && !isHoisted && notOptions },
|
||||
{ title: t("tree-context-menu.copy-note-path-to-clipboard"), command: "copyNotePathToClipboard", uiIcon: "bx bx-empty", enabled: true }
|
||||
] },
|
||||
{ title: "----" },
|
||||
{ title: "Protect subtree", command: "protectSubtree", uiIcon: "bx bx-check-shield", enabled: noSelectedNotes },
|
||||
{ title: "Unprotect subtree", command: "unprotectSubtree", uiIcon: "bx bx-shield", enabled: noSelectedNotes },
|
||||
{ title: t("tree-context-menu.protect-subtree"), command: "protectSubtree", uiIcon: "bx bx-check-shield", enabled: noSelectedNotes },
|
||||
{ title: t("tree-context-menu.unprotect-subtree"), command: "unprotectSubtree", uiIcon: "bx bx-shield", enabled: noSelectedNotes },
|
||||
{ title: "----" },
|
||||
{ title: 'Copy / clone <kbd data-command="copyNotesToClipboard"></kbd>', command: "copyNotesToClipboard", uiIcon: "bx bx-copy",
|
||||
{ title: `${t("tree-context-menu.copy-clone")} <kbd data-command="copyNotesToClipboard"></kbd>`, command: "copyNotesToClipboard", uiIcon: "bx bx-copy",
|
||||
enabled: isNotRoot && !isHoisted },
|
||||
{ title: 'Clone to ... <kbd data-command="cloneNotesTo"></kbd>', command: "cloneNotesTo", uiIcon: "bx bx-empty",
|
||||
{ title: `${t("tree-context-menu.clone-to")} <kbd data-command="cloneNotesTo"></kbd>`, command: "cloneNotesTo", uiIcon: "bx bx-empty",
|
||||
enabled: isNotRoot && !isHoisted },
|
||||
{ title: 'Cut <kbd data-command="cutNotesToClipboard"></kbd>', command: "cutNotesToClipboard", uiIcon: "bx bx-cut",
|
||||
{ title: `${t("tree-context-menu.cut")} <kbd data-command="cutNotesToClipboard"></kbd>`, command: "cutNotesToClipboard", uiIcon: "bx bx-cut",
|
||||
enabled: isNotRoot && !isHoisted && parentNotSearch },
|
||||
{ title: 'Move to ... <kbd data-command="moveNotesTo"></kbd>', command: "moveNotesTo", uiIcon: "bx bx-empty",
|
||||
{ title: `${t("tree-context-menu.move-to")} <kbd data-command="moveNotesTo"></kbd>`, command: "moveNotesTo", uiIcon: "bx bx-empty",
|
||||
enabled: isNotRoot && !isHoisted && parentNotSearch },
|
||||
{ title: 'Paste into <kbd data-command="pasteNotesFromClipboard"></kbd>', command: "pasteNotesFromClipboard", uiIcon: "bx bx-paste",
|
||||
{ title: `${t("tree-context-menu.paste-into")} <kbd data-command="pasteNotesFromClipboard"></kbd>`, command: "pasteNotesFromClipboard", uiIcon: "bx bx-paste",
|
||||
enabled: !clipboard.isClipboardEmpty() && notSearch && noSelectedNotes },
|
||||
{ title: 'Paste after', command: "pasteNotesAfterFromClipboard", uiIcon: "bx bx-paste",
|
||||
{ title: t("tree-context-menu.paste-after"), command: "pasteNotesAfterFromClipboard", uiIcon: "bx bx-paste",
|
||||
enabled: !clipboard.isClipboardEmpty() && isNotRoot && !isHoisted && parentNotSearch && noSelectedNotes },
|
||||
{ title: `Duplicate subtree <kbd data-command="duplicateSubtree">`, command: "duplicateSubtree", uiIcon: "bx bx-empty",
|
||||
{ title: `${t("tree-context-menu.duplicate-subtree")} <kbd data-command="duplicateSubtree">`, command: "duplicateSubtree", uiIcon: "bx bx-empty",
|
||||
enabled: parentNotSearch && isNotRoot && !isHoisted && notOptions },
|
||||
{ title: "----" },
|
||||
{ title: "Export", command: "exportNote", uiIcon: "bx bx-empty",
|
||||
{ title: t("tree-context-menu.export"), command: "exportNote", uiIcon: "bx bx-empty",
|
||||
enabled: notSearch && noSelectedNotes && notOptions },
|
||||
{ title: "Import into note", command: "importIntoNote", uiIcon: "bx bx-empty",
|
||||
{ title: t("tree-context-menu.import-into-note"), command: "importIntoNote", uiIcon: "bx bx-empty",
|
||||
enabled: notSearch && noSelectedNotes && notOptions },
|
||||
{ title: "Apply bulk actions", command: "openBulkActionsDialog", uiIcon: "bx bx-list-plus",
|
||||
{ title: t("tree-context-menu.apply-bulk-actions"), command: "openBulkActionsDialog", uiIcon: "bx bx-list-plus",
|
||||
enabled: true }
|
||||
].filter(row => row !== null);
|
||||
}
|
||||
@@ -135,7 +136,7 @@ export default class TreeContextMenu {
|
||||
this.treeWidget.triggerCommand("openNewNoteSplit", {ntxId, notePath});
|
||||
}
|
||||
else if (command === 'convertNoteToAttachment') {
|
||||
if (!await dialogService.confirm(`Are you sure you want to convert note selected notes into attachments of their parent notes?`)) {
|
||||
if (!await dialogService.confirm(t("tree-context-menu.convert-to-attachment-confirm"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -153,7 +154,7 @@ export default class TreeContextMenu {
|
||||
}
|
||||
}
|
||||
|
||||
toastService.showMessage(`${converted} notes have been converted to attachments.`);
|
||||
toastService.showMessage(t("tree-context-menu.converted-to-attachments", { count: converted }));
|
||||
}
|
||||
else if (command === 'copyNotePathToClipboard') {
|
||||
navigator.clipboard.writeText('#' + notePath);
|
||||
|
||||
@@ -5,6 +5,7 @@ import froca from "./froca.js";
|
||||
import hoistedNoteService from "./hoisted_note.js";
|
||||
import ws from "./ws.js";
|
||||
import appContext from "../components/app_context.js";
|
||||
import { t } from './i18n.js';
|
||||
|
||||
async function moveBeforeBranch(branchIdsToMove, beforeBranchId) {
|
||||
branchIdsToMove = filterRootNote(branchIdsToMove);
|
||||
@@ -13,7 +14,7 @@ async function moveBeforeBranch(branchIdsToMove, beforeBranchId) {
|
||||
const beforeBranch = froca.getBranch(beforeBranchId);
|
||||
|
||||
if (['root', '_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(beforeBranch.noteId)) {
|
||||
toastService.showError('Cannot move notes here.');
|
||||
toastService.showError(t("branches.cannot-move-notes-here"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -42,7 +43,7 @@ async function moveAfterBranch(branchIdsToMove, afterBranchId) {
|
||||
];
|
||||
|
||||
if (forbiddenNoteIds.includes(afterNote.noteId)) {
|
||||
toastService.showError('Cannot move notes here.');
|
||||
toastService.showError(t("branches.cannot-move-notes-here"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -62,7 +63,7 @@ async function moveToParentNote(branchIdsToMove, newParentBranchId) {
|
||||
const newParentBranch = froca.getBranch(newParentBranchId);
|
||||
|
||||
if (newParentBranch.noteId === '_lbRoot') {
|
||||
toastService.showError('Cannot move notes here.');
|
||||
toastService.showError(t("branches.cannot-move-notes-here"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -192,7 +193,7 @@ function filterRootNote(branchIds) {
|
||||
function makeToast(id, message) {
|
||||
return {
|
||||
id: id,
|
||||
title: "Delete status",
|
||||
title: t("branches.delete-status"),
|
||||
message: message,
|
||||
icon: "trash"
|
||||
};
|
||||
@@ -207,9 +208,9 @@ ws.subscribeToMessages(async message => {
|
||||
toastService.closePersistent(message.taskId);
|
||||
toastService.showError(message.message);
|
||||
} else if (message.type === 'taskProgressCount') {
|
||||
toastService.showPersistent(makeToast(message.taskId, `Delete notes in progress: ${message.progressCount}`));
|
||||
toastService.showPersistent(makeToast(message.taskId, t("branches.delete-notes-in-progress", { count: message.progressCount })));
|
||||
} else if (message.type === 'taskSucceeded') {
|
||||
const toast = makeToast(message.taskId, "Delete finished successfully.");
|
||||
const toast = makeToast(message.taskId, t("branches.delete-finished-successfully"));
|
||||
toast.closeAfter = 5000;
|
||||
|
||||
toastService.showPersistent(toast);
|
||||
@@ -225,9 +226,9 @@ ws.subscribeToMessages(async message => {
|
||||
toastService.closePersistent(message.taskId);
|
||||
toastService.showError(message.message);
|
||||
} else if (message.type === 'taskProgressCount') {
|
||||
toastService.showPersistent(makeToast(message.taskId, `Undeleting notes in progress: ${message.progressCount}`));
|
||||
toastService.showPersistent(makeToast(message.taskId, t("branches.undeleting-notes-in-progress", { count: message.progressCount })));
|
||||
} else if (message.type === 'taskSucceeded') {
|
||||
const toast = makeToast(message.taskId, "Undeleting notes finished successfully.");
|
||||
const toast = makeToast(message.taskId, t("branches.undeleting-notes-finished-successfully"));
|
||||
toast.closeAfter = 5000;
|
||||
|
||||
toastService.showPersistent(toast);
|
||||
|
||||
@@ -3,6 +3,7 @@ import server from "./server.js";
|
||||
import toastService from "./toast.js";
|
||||
import froca from "./froca.js";
|
||||
import utils from "./utils.js";
|
||||
import { t } from "./i18n.js";
|
||||
|
||||
async function getAndExecuteBundle(noteId, originEntity = null, script = null, params = null) {
|
||||
const bundle = await server.post(`script/bundle/${noteId}`, {
|
||||
@@ -75,9 +76,23 @@ async function getWidgetBundlesByParent() {
|
||||
|
||||
try {
|
||||
widget = await executeBundle(bundle);
|
||||
widgetsByParent.add(widget);
|
||||
}
|
||||
catch (e) {
|
||||
if (widget) {
|
||||
widget._noteId = bundle.noteId;
|
||||
widgetsByParent.add(widget);
|
||||
}
|
||||
} catch (e) {
|
||||
const noteId = bundle.noteId;
|
||||
const note = await froca.getNote(noteId);
|
||||
toastService.showPersistent({
|
||||
title: t("toast.bundle-error.title"),
|
||||
icon: "alert",
|
||||
message: t("toast.bundle-error.message", {
|
||||
id: noteId,
|
||||
title: note.title,
|
||||
message: e.message
|
||||
})
|
||||
});
|
||||
|
||||
logError("Widget initialization failed: ", e);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import toastService from "./toast.js";
|
||||
import froca from "./froca.js";
|
||||
import linkService from "./link.js";
|
||||
import utils from "./utils.js";
|
||||
import { t } from "./i18n.js";
|
||||
|
||||
let clipboardBranchIds = [];
|
||||
let clipboardMode = null;
|
||||
@@ -78,7 +79,7 @@ async function copy(branchIds) {
|
||||
clipboard.writeHTML(links.join(', '));
|
||||
}
|
||||
|
||||
toastService.showMessage("Note(s) have been copied into clipboard.");
|
||||
toastService.showMessage(t("clipboard.copied"));
|
||||
}
|
||||
|
||||
function cut(branchIds) {
|
||||
@@ -87,7 +88,7 @@ function cut(branchIds) {
|
||||
if (clipboardBranchIds.length > 0) {
|
||||
clipboardMode = 'cut';
|
||||
|
||||
toastService.showMessage("Note(s) have been cut into clipboard.");
|
||||
toastService.showMessage(t("clipboard.cut"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import treeService from "./tree.js";
|
||||
import FNote from "../entities/fnote.js";
|
||||
import FAttachment from "../entities/fattachment.js";
|
||||
import imageContextMenuService from "../menus/image_context_menu.js";
|
||||
import { applySyntaxHighlight } from "./syntax_highlight.js";
|
||||
|
||||
let idCounter = 1;
|
||||
|
||||
@@ -105,6 +106,8 @@ async function renderText(note, $renderedContent) {
|
||||
for (const el of referenceLinks) {
|
||||
await linkService.loadReferenceLinkTitle($(el));
|
||||
}
|
||||
|
||||
await applySyntaxHighlight($renderedContent);
|
||||
} else {
|
||||
await renderChildrenList($renderedContent, note);
|
||||
}
|
||||
|
||||
@@ -217,8 +217,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
|
||||
*/
|
||||
this.runOnBackend = async (func, params = []) => {
|
||||
if (func?.constructor.name === "AsyncFunction" || func?.startsWith?.("async ")) {
|
||||
toastService.showError("You're passing an async function to api.runOnBackend() which will likely not work as you intended. "
|
||||
+ "Either make the function synchronous (by removing 'async' keyword), or use api.runAsyncOnBackendWithManualTransactionHandling()");
|
||||
toastService.showError(t("frontend_script_api.async_warning"));
|
||||
}
|
||||
|
||||
return await this.__runOnBackendInner(func, params, true);
|
||||
@@ -240,8 +239,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
|
||||
*/
|
||||
this.runAsyncOnBackendWithManualTransactionHandling = async (func, params = []) => {
|
||||
if (func?.constructor.name === "Function" || func?.startsWith?.("function")) {
|
||||
toastService.showError("You're passing a synchronous function to api.runAsyncOnBackendWithManualTransactionHandling(), " +
|
||||
"while you should likely use api.runOnBackend() instead.");
|
||||
toastService.showError(t("frontend_script_api.sync_warning"));
|
||||
}
|
||||
|
||||
return await this.__runOnBackendInner(func, params, false);
|
||||
|
||||
@@ -26,21 +26,6 @@ function setupGlobs() {
|
||||
// for CKEditor integration (button on block toolbar)
|
||||
window.glob.importMarkdownInline = async () => appContext.triggerCommand("importMarkdownInline");
|
||||
|
||||
window.glob.SEARCH_HELP_TEXT = `
|
||||
<strong>Search tips</strong> - also see <button class="btn btn-sm" type="button" data-help-page="search.html">complete help on search</button>
|
||||
<p>
|
||||
<ul>
|
||||
<li>Just enter any text for full text search</li>
|
||||
<li><code>#abc</code> - returns notes with label abc</li>
|
||||
<li><code>#year = 2019</code> - matches notes with label <code>year</code> having value <code>2019</code></li>
|
||||
<li><code>#rock #pop</code> - matches notes which have both <code>rock</code> and <code>pop</code> labels</li>
|
||||
<li><code>#rock or #pop</code> - only one of the labels must be present</li>
|
||||
<li><code>#year <= 2000</code> - numerical comparison (also >, >=, <).</li>
|
||||
<li><code>note.dateCreated >= MONTH-1</code> - notes created in the last month</li>
|
||||
<li><code>=handler</code> - will execute script defined in <code>handler</code> relation to get results</li>
|
||||
</ul>
|
||||
</p>`;
|
||||
|
||||
window.onerror = function (msg, url, lineNo, columnNo, error) {
|
||||
const string = msg.toLowerCase();
|
||||
|
||||
@@ -64,6 +49,28 @@ function setupGlobs() {
|
||||
return false;
|
||||
};
|
||||
|
||||
window.addEventListener("unhandledrejection", (e) => {
|
||||
const string = e?.reason?.message?.toLowerCase();
|
||||
|
||||
let message = "Uncaught error: ";
|
||||
|
||||
if (string?.includes("script error")) {
|
||||
message += 'No details available';
|
||||
} else {
|
||||
message += [
|
||||
`Message: ${e.reason.message}`,
|
||||
`Line: ${e.reason.lineNumber}`,
|
||||
`Column: ${e.reason.columnNumber}`,
|
||||
`Error object: ${JSON.stringify(e.reason)}`,
|
||||
`Stack: ${e.reason && e.reason.stack}`
|
||||
].join(', ');
|
||||
}
|
||||
|
||||
ws.logError(message);
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
for (const appCssNoteId of glob.appCssNoteIds || []) {
|
||||
libraryLoader.requireCss(`api/notes/download/${appCssNoteId}`, false);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import appContext from "../components/app_context.js";
|
||||
import treeService from "./tree.js";
|
||||
import dialogService from "./dialog.js";
|
||||
import froca from "./froca.js";
|
||||
import { t } from "./i18n.js";
|
||||
|
||||
function getHoistedNoteId() {
|
||||
const activeNoteContext = appContext.tabManager.getActiveContext();
|
||||
@@ -53,7 +54,7 @@ async function checkNoteAccess(notePath, noteContext) {
|
||||
const hoistedNote = await froca.getNote(hoistedNoteId);
|
||||
|
||||
if ((!hoistedNote.hasAncestor('_hidden') || resolvedNotePath.includes('_lbBookmarks'))
|
||||
&& !await dialogService.confirm(`Requested note '${requestedNote.title}' is outside of hoisted note '${hoistedNote.title}' subtree and you must unhoist to access the note. Do you want to proceed with unhoisting?`)) {
|
||||
&& !await dialogService.confirm(t("hoisted_note.confirm_unhoisting", { requestedNote: requestedNote.title, hoistedNote: hoistedNote.title }))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,9 @@ export async function initLocale() {
|
||||
lng: locale,
|
||||
fallbackLng: "en",
|
||||
backend: {
|
||||
loadPath: `/${window.glob.assetPath}/translations/{{lng}}/{{ns}}.json`
|
||||
}
|
||||
loadPath: `${window.glob.assetPath}/translations/{{lng}}/{{ns}}.json`
|
||||
},
|
||||
returnEmptyString: false
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ function copyImageReferenceToClipboard($imageWrapper) {
|
||||
const success = document.execCommand('copy');
|
||||
|
||||
if (success) {
|
||||
toastService.showMessage("A reference to the image has been copied to clipboard. This can be pasted in any text note.");
|
||||
toastService.showMessage(t("image.copied-to-clipboard"));
|
||||
} else {
|
||||
toastService.showAndLogError("Could not copy the image reference to clipboard.");
|
||||
toastService.showAndLogError(t("image.cannot-copy"));
|
||||
}
|
||||
}
|
||||
finally {
|
||||
|
||||
@@ -36,7 +36,7 @@ export async function uploadFiles(entityType, parentNoteId, files, options) {
|
||||
type: 'POST',
|
||||
timeout: 60 * 60 * 1000,
|
||||
error: function (xhr) {
|
||||
toastService.showError(`Import failed: ${xhr.responseText}`);
|
||||
toastService.showError(t("import.failed", { message: xhr.responseText }));
|
||||
},
|
||||
contentType: false, // NEEDED, DON'T REMOVE THIS
|
||||
processData: false, // NEEDED, DON'T REMOVE THIS
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import mimeTypesService from "./mime_types.js";
|
||||
import optionsService from "./options.js";
|
||||
import { getStylesheetUrl } from "./syntax_highlight.js";
|
||||
|
||||
const CKEDITOR = {"js": ["libraries/ckeditor/ckeditor.js"]};
|
||||
|
||||
const CODE_MIRROR = {
|
||||
js: [
|
||||
"libraries/codemirror/codemirror.js",
|
||||
// "node_modules/codemirror/lib/codemirror.js",
|
||||
"node_modules/codemirror/lib/codemirror.js",
|
||||
"node_modules/codemirror/addon/display/placeholder.js",
|
||||
"node_modules/codemirror/addon/edit/matchbrackets.js",
|
||||
"node_modules/codemirror/addon/edit/matchtags.js",
|
||||
@@ -85,18 +88,44 @@ const MIND_ELIXIR = {
|
||||
]
|
||||
};
|
||||
|
||||
const HIGHLIGHT_JS = {
|
||||
js: () => {
|
||||
const mimeTypes = mimeTypesService.getMimeTypes();
|
||||
const scriptsToLoad = new Set();
|
||||
scriptsToLoad.add("node_modules/@highlightjs/cdn-assets/highlight.min.js");
|
||||
for (const mimeType of mimeTypes) {
|
||||
if (mimeType.enabled && mimeType.highlightJs) {
|
||||
scriptsToLoad.add(`node_modules/@highlightjs/cdn-assets/languages/${mimeType.highlightJs}.min.js`);
|
||||
}
|
||||
}
|
||||
|
||||
const currentTheme = optionsService.get("codeBlockTheme");
|
||||
loadHighlightingTheme(currentTheme);
|
||||
|
||||
return Array.from(scriptsToLoad);
|
||||
}
|
||||
};
|
||||
|
||||
async function requireLibrary(library) {
|
||||
if (library.css) {
|
||||
library.css.map(cssUrl => requireCss(cssUrl));
|
||||
}
|
||||
|
||||
if (library.js) {
|
||||
for (const scriptUrl of library.js) {
|
||||
for (const scriptUrl of unwrapValue(library.js)) {
|
||||
await requireScript(scriptUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function unwrapValue(value) {
|
||||
if (typeof value === "function") {
|
||||
return value();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// we save the promises in case of the same script being required concurrently multiple times
|
||||
const loadedScriptPromises = {};
|
||||
|
||||
@@ -128,9 +157,36 @@ async function requireCss(url, prependAssetPath = true) {
|
||||
}
|
||||
}
|
||||
|
||||
let highlightingThemeEl = null;
|
||||
function loadHighlightingTheme(theme) {
|
||||
if (!theme) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (theme === "none") {
|
||||
// Deactivate the theme.
|
||||
if (highlightingThemeEl) {
|
||||
highlightingThemeEl.remove();
|
||||
highlightingThemeEl = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!highlightingThemeEl) {
|
||||
highlightingThemeEl = $(`<link rel="stylesheet" type="text/css" />`);
|
||||
$("head").append(highlightingThemeEl);
|
||||
}
|
||||
|
||||
const url = getStylesheetUrl(theme);
|
||||
if (url) {
|
||||
highlightingThemeEl.attr("href", url);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
requireCss,
|
||||
requireLibrary,
|
||||
loadHighlightingTheme,
|
||||
CKEDITOR,
|
||||
CODE_MIRROR,
|
||||
ESLINT,
|
||||
@@ -144,5 +200,6 @@ export default {
|
||||
EXCALIDRAW,
|
||||
MARKJS,
|
||||
I18NEXT,
|
||||
MIND_ELIXIR
|
||||
MIND_ELIXIR,
|
||||
HIGHLIGHT_JS
|
||||
}
|
||||
|
||||
@@ -1,162 +1,167 @@
|
||||
import options from "./options.js";
|
||||
|
||||
/**
|
||||
* A pseudo-MIME type which is used in the editor to automatically determine the language used in code blocks via heuristics.
|
||||
*/
|
||||
const MIME_TYPE_AUTO = "text-x-trilium-auto";
|
||||
|
||||
const MIME_TYPES_DICT = [
|
||||
{ default: true, title: "Plain text", mime: "text/plain" },
|
||||
{ default: true, title: "Plain text", mime: "text/plain", highlightJs: "plaintext" },
|
||||
{ title: "APL", mime: "text/apl" },
|
||||
{ title: "ASN.1", mime: "text/x-ttcn-asn" },
|
||||
{ title: "ASP.NET", mime: "application/x-aspx" },
|
||||
{ title: "Asterisk", mime: "text/x-asterisk" },
|
||||
{ title: "Brainfuck", mime: "text/x-brainfuck" },
|
||||
{ default: true, title: "C", mime: "text/x-csrc" },
|
||||
{ default: true, title: "C#", mime: "text/x-csharp" },
|
||||
{ default: true, title: "C++", mime: "text/x-c++src" },
|
||||
{ title: "Clojure", mime: "text/x-clojure" },
|
||||
{ title: "Brainfuck", mime: "text/x-brainfuck", highlightJs: "brainfuck" },
|
||||
{ default: true, title: "C", mime: "text/x-csrc", highlightJs: "c" },
|
||||
{ default: true, title: "C#", mime: "text/x-csharp", highlightJs: "csharp" },
|
||||
{ default: true, title: "C++", mime: "text/x-c++src", highlightJs: "cpp" },
|
||||
{ title: "Clojure", mime: "text/x-clojure", highlightJs: "clojure" },
|
||||
{ title: "ClojureScript", mime: "text/x-clojurescript" },
|
||||
{ title: "Closure Stylesheets (GSS)", mime: "text/x-gss" },
|
||||
{ title: "CMake", mime: "text/x-cmake" },
|
||||
{ title: "CMake", mime: "text/x-cmake", highlightJs: "cmake" },
|
||||
{ title: "Cobol", mime: "text/x-cobol" },
|
||||
{ title: "CoffeeScript", mime: "text/coffeescript" },
|
||||
{ title: "Common Lisp", mime: "text/x-common-lisp" },
|
||||
{ title: "CoffeeScript", mime: "text/coffeescript", highlightJs: "coffeescript" },
|
||||
{ title: "Common Lisp", mime: "text/x-common-lisp", highlightJs: "lisp" },
|
||||
{ title: "CQL", mime: "text/x-cassandra" },
|
||||
{ title: "Crystal", mime: "text/x-crystal" },
|
||||
{ default: true, title: "CSS", mime: "text/css" },
|
||||
{ title: "Crystal", mime: "text/x-crystal", highlightJs: "crystal" },
|
||||
{ default: true, title: "CSS", mime: "text/css", highlightJs: "css" },
|
||||
{ title: "Cypher", mime: "application/x-cypher-query" },
|
||||
{ title: "Cython", mime: "text/x-cython" },
|
||||
{ title: "D", mime: "text/x-d" },
|
||||
{ title: "Dart", mime: "application/dart" },
|
||||
{ title: "diff", mime: "text/x-diff" },
|
||||
{ title: "Django", mime: "text/x-django" },
|
||||
{ title: "Dockerfile", mime: "text/x-dockerfile" },
|
||||
{ title: "D", mime: "text/x-d", highlightJs: "d" },
|
||||
{ title: "Dart", mime: "application/dart", highlightJs: "dart" },
|
||||
{ title: "diff", mime: "text/x-diff", highlightJs: "diff" },
|
||||
{ title: "Django", mime: "text/x-django", highlightJs: "django" },
|
||||
{ title: "Dockerfile", mime: "text/x-dockerfile", highlightJs: "dockerfile" },
|
||||
{ title: "DTD", mime: "application/xml-dtd" },
|
||||
{ title: "Dylan", mime: "text/x-dylan" },
|
||||
{ title: "EBNF", mime: "text/x-ebnf" },
|
||||
{ title: "EBNF", mime: "text/x-ebnf", highlightJs: "ebnf" },
|
||||
{ title: "ECL", mime: "text/x-ecl" },
|
||||
{ title: "edn", mime: "application/edn" },
|
||||
{ title: "Eiffel", mime: "text/x-eiffel" },
|
||||
{ title: "Elm", mime: "text/x-elm" },
|
||||
{ title: "Elm", mime: "text/x-elm", highlightJs: "elm" },
|
||||
{ title: "Embedded Javascript", mime: "application/x-ejs" },
|
||||
{ title: "Embedded Ruby", mime: "application/x-erb" },
|
||||
{ title: "Erlang", mime: "text/x-erlang" },
|
||||
{ title: "Embedded Ruby", mime: "application/x-erb", highlightJs: "erb" },
|
||||
{ title: "Erlang", mime: "text/x-erlang", highlightJs: "erlang" },
|
||||
{ title: "Esper", mime: "text/x-esper" },
|
||||
{ title: "F#", mime: "text/x-fsharp" },
|
||||
{ title: "F#", mime: "text/x-fsharp", highlightJs: "fsharp" },
|
||||
{ title: "Factor", mime: "text/x-factor" },
|
||||
{ title: "FCL", mime: "text/x-fcl" },
|
||||
{ title: "Forth", mime: "text/x-forth" },
|
||||
{ title: "Fortran", mime: "text/x-fortran" },
|
||||
{ title: "Fortran", mime: "text/x-fortran", highlightJs: "fortran" },
|
||||
{ title: "Gas", mime: "text/x-gas" },
|
||||
{ title: "Gherkin", mime: "text/x-feature" },
|
||||
{ title: "GitHub Flavored Markdown", mime: "text/x-gfm" },
|
||||
{ default: true, title: "Go", mime: "text/x-go" },
|
||||
{ default: true, title: "Groovy", mime: "text/x-groovy" },
|
||||
{ title: "HAML", mime: "text/x-haml" },
|
||||
{ default: true, title: "Haskell", mime: "text/x-haskell" },
|
||||
{ title: "Gherkin", mime: "text/x-feature", highlightJs: "gherkin" },
|
||||
{ title: "GitHub Flavored Markdown", mime: "text/x-gfm", highlightJs: "markdown" },
|
||||
{ default: true, title: "Go", mime: "text/x-go", highlightJs: "go" },
|
||||
{ default: true, title: "Groovy", mime: "text/x-groovy", highlightJs: "groovy" },
|
||||
{ title: "HAML", mime: "text/x-haml", highlightJs: "haml" },
|
||||
{ default: true, title: "Haskell", mime: "text/x-haskell", highlightJs: "haskell" },
|
||||
{ title: "Haskell (Literate)", mime: "text/x-literate-haskell" },
|
||||
{ title: "Haxe", mime: "text/x-haxe" },
|
||||
{ default: true, title: "HTML", mime: "text/html" },
|
||||
{ default: true, title: "HTTP", mime: "message/http" },
|
||||
{ title: "Haxe", mime: "text/x-haxe", highlightJs: "haxe" },
|
||||
{ default: true, title: "HTML", mime: "text/html", highlightJs: "xml" },
|
||||
{ default: true, title: "HTTP", mime: "message/http", highlightJs: "http" },
|
||||
{ title: "HXML", mime: "text/x-hxml" },
|
||||
{ title: "IDL", mime: "text/x-idl" },
|
||||
{ default: true, title: "Java", mime: "text/x-java" },
|
||||
{ title: "Java Server Pages", mime: "application/x-jsp" },
|
||||
{ default: true, title: "Java", mime: "text/x-java", highlightJs: "java" },
|
||||
{ title: "Java Server Pages", mime: "application/x-jsp", highlightJs: "java" },
|
||||
{ title: "Jinja2", mime: "text/jinja2" },
|
||||
{ default: true, title: "JS backend", mime: "application/javascript;env=backend" },
|
||||
{ default: true, title: "JS frontend", mime: "application/javascript;env=frontend" },
|
||||
{ default: true, title: "JSON", mime: "application/json" },
|
||||
{ title: "JSON-LD", mime: "application/ld+json" },
|
||||
{ title: "JSX", mime: "text/jsx" },
|
||||
{ title: "Julia", mime: "text/x-julia" },
|
||||
{ default: true, title: "Kotlin", mime: "text/x-kotlin" },
|
||||
{ title: "LaTeX", mime: "text/x-latex" },
|
||||
{ title: "LESS", mime: "text/x-less" },
|
||||
{ title: "LiveScript", mime: "text/x-livescript" },
|
||||
{ title: "Lua", mime: "text/x-lua" },
|
||||
{ title: "MariaDB SQL", mime: "text/x-mariadb" },
|
||||
{ default: true, title: "Markdown", mime: "text/x-markdown" },
|
||||
{ title: "Mathematica", mime: "text/x-mathematica" },
|
||||
{ default: true, title: "JS backend", mime: "application/javascript;env=backend", highlightJs: "javascript" },
|
||||
{ default: true, title: "JS frontend", mime: "application/javascript;env=frontend", highlightJs: "javascript" },
|
||||
{ default: true, title: "JSON", mime: "application/json", highlightJs: "json" },
|
||||
{ title: "JSON-LD", mime: "application/ld+json", highlightJs: "json" },
|
||||
{ title: "JSX", mime: "text/jsx", highlightJs: "javascript" },
|
||||
{ title: "Julia", mime: "text/x-julia", highlightJs: "julia" },
|
||||
{ default: true, title: "Kotlin", mime: "text/x-kotlin", highlightJs: "kotlin" },
|
||||
{ title: "LaTeX", mime: "text/x-latex", highlightJs: "latex" },
|
||||
{ title: "LESS", mime: "text/x-less", highlightJs: "less" },
|
||||
{ title: "LiveScript", mime: "text/x-livescript", highlightJs: "livescript" },
|
||||
{ title: "Lua", mime: "text/x-lua", highlightJs: "lua" },
|
||||
{ title: "MariaDB SQL", mime: "text/x-mariadb", highlightJs: "sql" },
|
||||
{ default: true, title: "Markdown", mime: "text/x-markdown", highlightJs: "markdown" },
|
||||
{ title: "Mathematica", mime: "text/x-mathematica", highlightJs: "mathematica" },
|
||||
{ title: "mbox", mime: "application/mbox" },
|
||||
{ title: "mIRC", mime: "text/mirc" },
|
||||
{ title: "Modelica", mime: "text/x-modelica" },
|
||||
{ title: "MS SQL", mime: "text/x-mssql" },
|
||||
{ title: "MS SQL", mime: "text/x-mssql", highlightJs: "sql" },
|
||||
{ title: "mscgen", mime: "text/x-mscgen" },
|
||||
{ title: "msgenny", mime: "text/x-msgenny" },
|
||||
{ title: "MUMPS", mime: "text/x-mumps" },
|
||||
{ title: "MySQL", mime: "text/x-mysql" },
|
||||
{ title: "Nginx", mime: "text/x-nginx-conf" },
|
||||
{ title: "NSIS", mime: "text/x-nsis" },
|
||||
{ title: "MySQL", mime: "text/x-mysql", highlightJs: "sql" },
|
||||
{ title: "Nginx", mime: "text/x-nginx-conf", highlightJs: "nginx" },
|
||||
{ title: "NSIS", mime: "text/x-nsis", highlightJs: "nsis" },
|
||||
{ title: "NTriples", mime: "application/n-triples" },
|
||||
{ title: "Objective-C", mime: "text/x-objectivec" },
|
||||
{ title: "OCaml", mime: "text/x-ocaml" },
|
||||
{ title: "Objective-C", mime: "text/x-objectivec", highlightJs: "objectivec" },
|
||||
{ title: "OCaml", mime: "text/x-ocaml", highlightJs: "ocaml" },
|
||||
{ title: "Octave", mime: "text/x-octave" },
|
||||
{ title: "Oz", mime: "text/x-oz" },
|
||||
{ title: "Pascal", mime: "text/x-pascal" },
|
||||
{ title: "Pascal", mime: "text/x-pascal", highlightJs: "delphi" },
|
||||
{ title: "PEG.js", mime: "null" },
|
||||
{ default: true, title: "Perl", mime: "text/x-perl" },
|
||||
{ title: "PGP", mime: "application/pgp" },
|
||||
{ default: true, title: "PHP", mime: "text/x-php" },
|
||||
{ title: "Pig", mime: "text/x-pig" },
|
||||
{ title: "PLSQL", mime: "text/x-plsql" },
|
||||
{ title: "PostgreSQL", mime: "text/x-pgsql" },
|
||||
{ title: "PowerShell", mime: "application/x-powershell" },
|
||||
{ title: "Properties files", mime: "text/x-properties" },
|
||||
{ title: "ProtoBuf", mime: "text/x-protobuf" },
|
||||
{ title: "PLSQL", mime: "text/x-plsql", highlightJs: "sql" },
|
||||
{ title: "PostgreSQL", mime: "text/x-pgsql", highlightJs: "pgsql" },
|
||||
{ title: "PowerShell", mime: "application/x-powershell", highlightJs: "powershell" },
|
||||
{ title: "Properties files", mime: "text/x-properties", highlightJs: "properties" },
|
||||
{ title: "ProtoBuf", mime: "text/x-protobuf", highlightJs: "protobuf" },
|
||||
{ title: "Pug", mime: "text/x-pug" },
|
||||
{ title: "Puppet", mime: "text/x-puppet" },
|
||||
{ default: true, title: "Python", mime: "text/x-python" },
|
||||
{ title: "Q", mime: "text/x-q" },
|
||||
{ title: "R", mime: "text/x-rsrc" },
|
||||
{ title: "Puppet", mime: "text/x-puppet", highlightJs: "puppet" },
|
||||
{ default: true, title: "Python", mime: "text/x-python", highlightJs: "python" },
|
||||
{ title: "Q", mime: "text/x-q", highlightJs: "q" },
|
||||
{ title: "R", mime: "text/x-rsrc", highlightJs: "r" },
|
||||
{ title: "reStructuredText", mime: "text/x-rst" },
|
||||
{ title: "RPM Changes", mime: "text/x-rpm-changes" },
|
||||
{ title: "RPM Spec", mime: "text/x-rpm-spec" },
|
||||
{ default: true, title: "Ruby", mime: "text/x-ruby" },
|
||||
{ title: "Rust", mime: "text/x-rustsrc" },
|
||||
{ title: "SAS", mime: "text/x-sas" },
|
||||
{ default: true, title: "Ruby", mime: "text/x-ruby", highlightJs: "ruby" },
|
||||
{ title: "Rust", mime: "text/x-rustsrc", highlightJs: "rust" },
|
||||
{ title: "SAS", mime: "text/x-sas", highlightJs: "sas" },
|
||||
{ title: "Sass", mime: "text/x-sass" },
|
||||
{ title: "Scala", mime: "text/x-scala" },
|
||||
{ title: "Scheme", mime: "text/x-scheme" },
|
||||
{ title: "SCSS", mime: "text/x-scss" },
|
||||
{ default: true, title: "Shell (bash)", mime: "text/x-sh" },
|
||||
{ title: "SCSS", mime: "text/x-scss", highlightJs: "scss" },
|
||||
{ default: true, title: "Shell (bash)", mime: "text/x-sh", highlightJs: "shell" },
|
||||
{ title: "Sieve", mime: "application/sieve" },
|
||||
{ title: "Slim", mime: "text/x-slim" },
|
||||
{ title: "Smalltalk", mime: "text/x-stsrc" },
|
||||
{ title: "Smalltalk", mime: "text/x-stsrc", highlightJs: "smalltalk" },
|
||||
{ title: "Smarty", mime: "text/x-smarty" },
|
||||
{ title: "SML", mime: "text/x-sml" },
|
||||
{ title: "SML", mime: "text/x-sml", highlightJs: "sml" },
|
||||
{ title: "Solr", mime: "text/x-solr" },
|
||||
{ title: "Soy", mime: "text/x-soy" },
|
||||
{ title: "SPARQL", mime: "application/sparql-query" },
|
||||
{ title: "Spreadsheet", mime: "text/x-spreadsheet" },
|
||||
{ default: true, title: "SQL", mime: "text/x-sql" },
|
||||
{ title: "SQLite", mime: "text/x-sqlite" },
|
||||
{ default: true, title: "SQLite (Trilium)", mime: "text/x-sqlite;schema=trilium" },
|
||||
{ default: true, title: "SQL", mime: "text/x-sql", highlightJs: "sql" },
|
||||
{ title: "SQLite", mime: "text/x-sqlite", highlightJs: "sql" },
|
||||
{ default: true, title: "SQLite (Trilium)", mime: "text/x-sqlite;schema=trilium", highlightJs: "sql" },
|
||||
{ title: "Squirrel", mime: "text/x-squirrel" },
|
||||
{ title: "sTeX", mime: "text/x-stex" },
|
||||
{ title: "Stylus", mime: "text/x-styl" },
|
||||
{ title: "Stylus", mime: "text/x-styl", highlightJs: "stylus" },
|
||||
{ default: true, title: "Swift", mime: "text/x-swift" },
|
||||
{ title: "SystemVerilog", mime: "text/x-systemverilog" },
|
||||
{ title: "Tcl", mime: "text/x-tcl" },
|
||||
{ title: "Tcl", mime: "text/x-tcl", highlightJs: "tcl" },
|
||||
{ title: "Textile", mime: "text/x-textile" },
|
||||
{ title: "TiddlyWiki ", mime: "text/x-tiddlywiki" },
|
||||
{ title: "Tiki wiki", mime: "text/tiki" },
|
||||
{ title: "TOML", mime: "text/x-toml" },
|
||||
{ title: "TOML", mime: "text/x-toml", highlightJs: "ini" },
|
||||
{ title: "Tornado", mime: "text/x-tornado" },
|
||||
{ title: "troff", mime: "text/troff" },
|
||||
{ title: "TTCN", mime: "text/x-ttcn" },
|
||||
{ title: "TTCN_CFG", mime: "text/x-ttcn-cfg" },
|
||||
{ title: "Turtle", mime: "text/turtle" },
|
||||
{ title: "Twig", mime: "text/x-twig" },
|
||||
{ title: "TypeScript", mime: "application/typescript" },
|
||||
{ title: "Twig", mime: "text/x-twig", highlightJs: "twig" },
|
||||
{ title: "TypeScript", mime: "application/typescript", highlightJs: "typescript" },
|
||||
{ title: "TypeScript-JSX", mime: "text/typescript-jsx" },
|
||||
{ title: "VB.NET", mime: "text/x-vb" },
|
||||
{ title: "VBScript", mime: "text/vbscript" },
|
||||
{ title: "VB.NET", mime: "text/x-vb", highlightJs: "vbnet" },
|
||||
{ title: "VBScript", mime: "text/vbscript", highlightJs: "vbscript" },
|
||||
{ title: "Velocity", mime: "text/velocity" },
|
||||
{ title: "Verilog", mime: "text/x-verilog" },
|
||||
{ title: "VHDL", mime: "text/x-vhdl" },
|
||||
{ title: "Verilog", mime: "text/x-verilog", highlightJs: "verilog" },
|
||||
{ title: "VHDL", mime: "text/x-vhdl", highlightJs: "vhdl" },
|
||||
{ title: "Vue.js Component", mime: "text/x-vue" },
|
||||
{ title: "Web IDL", mime: "text/x-webidl" },
|
||||
{ default: true, title: "XML", mime: "text/xml" },
|
||||
{ title: "XQuery", mime: "application/xquery" },
|
||||
{ default: true, title: "XML", mime: "text/xml", highlightJs: "xml" },
|
||||
{ title: "XQuery", mime: "application/xquery", highlightJs: "xquery" },
|
||||
{ title: "xu", mime: "text/x-xu" },
|
||||
{ title: "Yacas", mime: "text/x-yacas" },
|
||||
{ default: true, title: "YAML", mime: "text/x-yaml" },
|
||||
{ default: true, title: "YAML", mime: "text/x-yaml", highlightJs: "yaml" },
|
||||
{ title: "Z80", mime: "text/x-z80" }
|
||||
];
|
||||
|
||||
@@ -173,7 +178,7 @@ function loadMimeTypes() {
|
||||
}
|
||||
}
|
||||
|
||||
async function getMimeTypes() {
|
||||
function getMimeTypes() {
|
||||
if (mimeTypes === null) {
|
||||
loadMimeTypes();
|
||||
}
|
||||
@@ -181,7 +186,46 @@ async function getMimeTypes() {
|
||||
return mimeTypes;
|
||||
}
|
||||
|
||||
export default {
|
||||
getMimeTypes,
|
||||
loadMimeTypes
|
||||
let mimeToHighlightJsMapping = null;
|
||||
|
||||
/**
|
||||
* Obtains the corresponding language tag for highlight.js for a given MIME type.
|
||||
*
|
||||
* The mapping is built the first time this method is built and then the results are cached for better performance.
|
||||
*
|
||||
* @param {string} mimeType The MIME type of the code block, in the CKEditor-normalized format (e.g. `text-c-src` instead of `text/c-src`).
|
||||
* @returns the corresponding highlight.js tag, for example `c` for `text-c-src`.
|
||||
*/
|
||||
function getHighlightJsNameForMime(mimeType) {
|
||||
if (!mimeToHighlightJsMapping) {
|
||||
const mimeTypes = getMimeTypes();
|
||||
mimeToHighlightJsMapping = {};
|
||||
for (const mimeType of mimeTypes) {
|
||||
// The mime stored by CKEditor is text-x-csrc instead of text/x-csrc so we keep this format for faster lookup.
|
||||
const normalizedMime = normalizeMimeTypeForCKEditor(mimeType.mime);
|
||||
mimeToHighlightJsMapping[normalizedMime] = mimeType.highlightJs;
|
||||
}
|
||||
}
|
||||
|
||||
return mimeToHighlightJsMapping[mimeType];
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a MIME type in the usual format (e.g. `text/csrc`), it returns a MIME type that can be passed down to the CKEditor
|
||||
* code plugin.
|
||||
*
|
||||
* @param {string} mimeType The MIME type to normalize, in the usual format (e.g. `text/c-src`).
|
||||
* @returns the normalized MIME type (e.g. `text-c-src`).
|
||||
*/
|
||||
function normalizeMimeTypeForCKEditor(mimeType) {
|
||||
return mimeType.toLowerCase()
|
||||
.replace(/[\W_]+/g,"-");
|
||||
}
|
||||
|
||||
export default {
|
||||
MIME_TYPE_AUTO,
|
||||
getMimeTypes,
|
||||
loadMimeTypes,
|
||||
getHighlightJsNameForMime,
|
||||
normalizeMimeTypeForCKEditor
|
||||
}
|
||||
|
||||
@@ -105,28 +105,23 @@ function initNoteAutocomplete($el, options) {
|
||||
|
||||
$el.addClass("note-autocomplete-input");
|
||||
|
||||
const $clearTextButton = $("<a>")
|
||||
.addClass("input-group-text input-clearer-button bx bxs-tag-x")
|
||||
.prop("title", "Clear text field");
|
||||
const $clearTextButton = $("<button>")
|
||||
.addClass("input-group-text input-clearer-button bx bxs-tag-x")
|
||||
.prop("title", "Clear text field");
|
||||
|
||||
const $showRecentNotesButton = $("<a>")
|
||||
.addClass("input-group-text show-recent-notes-button bx bx-time")
|
||||
.prop("title", "Show recent notes");
|
||||
const $showRecentNotesButton = $("<button>")
|
||||
.addClass("input-group-text show-recent-notes-button bx bx-time")
|
||||
.prop("title", "Show recent notes");
|
||||
|
||||
const $goToSelectedNoteButton = $("<a>")
|
||||
const $goToSelectedNoteButton = $("<button>")
|
||||
.addClass("input-group-text go-to-selected-note-button bx bx-arrow-to-right");
|
||||
|
||||
const $sideButtons = $("<div>")
|
||||
.addClass("input-group-append")
|
||||
.append($clearTextButton)
|
||||
.append($showRecentNotesButton);
|
||||
$el.after($clearTextButton).after($showRecentNotesButton);
|
||||
|
||||
if (!options.hideGoToSelectedNoteButton) {
|
||||
$sideButtons.append($goToSelectedNoteButton);
|
||||
$el.after($goToSelectedNoteButton);
|
||||
}
|
||||
|
||||
$el.after($sideButtons);
|
||||
|
||||
$clearTextButton.on('click', () => clearText($el));
|
||||
|
||||
$showRecentNotesButton.on('click', e => {
|
||||
@@ -180,13 +175,13 @@ function initNoteAutocomplete($el, options) {
|
||||
}
|
||||
|
||||
if (suggestion.action === 'create-note') {
|
||||
const {success, noteType, templateNoteId} = await noteCreateService.chooseNoteType();
|
||||
const { success, noteType, templateNoteId } = await noteCreateService.chooseNoteType();
|
||||
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {note} = await noteCreateService.createNote(suggestion.parentNoteId, {
|
||||
const { note } = await noteCreateService.createNote(suggestion.parentNoteId, {
|
||||
title: suggestion.noteTitle,
|
||||
activate: false,
|
||||
type: noteType,
|
||||
|
||||
@@ -5,6 +5,7 @@ import ws from "./ws.js";
|
||||
import froca from "./froca.js";
|
||||
import treeService from "./tree.js";
|
||||
import toastService from "./toast.js";
|
||||
import { t } from "./i18n.js";
|
||||
|
||||
async function createNote(parentNotePath, options = {}) {
|
||||
options = Object.assign({
|
||||
@@ -119,7 +120,7 @@ async function duplicateSubtree(noteId, parentNotePath) {
|
||||
activeNoteContext.setNote(`${parentNotePath}/${note.noteId}`);
|
||||
|
||||
const origNote = await froca.getNote(noteId);
|
||||
toastService.showMessage(`Note "${origNote.title}" has been duplicated`);
|
||||
toastService.showMessage(t("note_create.duplicated", { title: origNote.title }));
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -366,7 +366,7 @@ class NoteListRenderer {
|
||||
separateWordSearch: false,
|
||||
caseSensitive: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$content.append($renderedContent);
|
||||
$content.addClass(`type-${type}`);
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import server from "./server.js";
|
||||
import froca from "./froca.js";
|
||||
import { t } from "./i18n.js";
|
||||
|
||||
async function getNoteTypeItems(command) {
|
||||
const items = [
|
||||
{ title: "Text", command: command, type: "text", uiIcon: "bx bx-note" },
|
||||
{ title: "Code", command: command, type: "code", uiIcon: "bx bx-code" },
|
||||
{ title: "Saved Search", command: command, type: "search", uiIcon: "bx bx-file-find" },
|
||||
{ title: "Relation Map", command: command, type: "relationMap", uiIcon: "bx bx-map-alt" },
|
||||
{ title: "Note Map", command: command, type: "noteMap", uiIcon: "bx bx-map-alt" },
|
||||
{ title: "Render Note", command: command, type: "render", uiIcon: "bx bx-extension" },
|
||||
{ title: "Book", command: command, type: "book", uiIcon: "bx bx-book" },
|
||||
{ title: "Mermaid Diagram", command: command, type: "mermaid", uiIcon: "bx bx-selection" },
|
||||
{ title: "Canvas", command: command, type: "canvas", uiIcon: "bx bx-pen" },
|
||||
{ title: "Web View", command: command, type: "webView", uiIcon: "bx bx-globe-alt" },
|
||||
{ title: "Mind Map", command, type: "mindMap", uiIcon: "bx bx-sitemap" }
|
||||
{ title: t("note_types.text"), command: command, type: "text", uiIcon: "bx bx-note" },
|
||||
{ title: t("note_types.code"), command: command, type: "code", uiIcon: "bx bx-code" },
|
||||
{ title: t("note_types.saved-search"), command: command, type: "search", uiIcon: "bx bx-file-find" },
|
||||
{ title: t("note_types.relation-map"), command: command, type: "relationMap", uiIcon: "bx bxs-network-chart" },
|
||||
{ title: t("note_types.note-map"), command: command, type: "noteMap", uiIcon: "bx bxs-network-chart" },
|
||||
{ title: t("note_types.render-note"), command: command, type: "render", uiIcon: "bx bx-extension" },
|
||||
{ title: t("note_types.book"), command: command, type: "book", uiIcon: "bx bx-book" },
|
||||
{ title: t("note_types.mermaid-diagram"), command: command, type: "mermaid", uiIcon: "bx bx-selection" },
|
||||
{ title: t("note_types.canvas"), command: command, type: "canvas", uiIcon: "bx bx-pen" },
|
||||
{ title: t("note_types.web-view"), command: command, type: "webView", uiIcon: "bx bx-globe-alt" },
|
||||
{ title: t("note_types.mind-map"), command, type: "mindMap", uiIcon: "bx bx-sitemap" }
|
||||
];
|
||||
|
||||
const templateNoteIds = await server.get("search-templates");
|
||||
|
||||
@@ -166,6 +166,23 @@ function getHost() {
|
||||
return `${url.protocol}//${url.hostname}:${url.port}`;
|
||||
}
|
||||
|
||||
async function openDirectory(directory) {
|
||||
try {
|
||||
if (utils.isElectron()) {
|
||||
const electron = utils.dynamicRequire('electron');
|
||||
const res = await electron.shell.openPath(directory);
|
||||
if (res) {
|
||||
console.error('Failed to open directory:', res);
|
||||
}
|
||||
} else {
|
||||
console.error('Not running in an Electron environment.');
|
||||
}
|
||||
} catch (err) {
|
||||
// Handle file system errors (e.g. path does not exist or is inaccessible)
|
||||
console.error('Error:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
download,
|
||||
downloadFileNote,
|
||||
@@ -176,4 +193,5 @@ export default {
|
||||
openAttachmentExternally,
|
||||
openNoteCustom,
|
||||
openAttachmentCustom,
|
||||
openDirectory
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import appContext from "../components/app_context.js";
|
||||
import froca from "./froca.js";
|
||||
import utils from "./utils.js";
|
||||
import options from "./options.js";
|
||||
import { t } from './i18n.js';
|
||||
|
||||
let protectedSessionDeferred = null;
|
||||
|
||||
@@ -50,7 +51,7 @@ async function setupProtectedSession(password) {
|
||||
const response = await server.post('login/protected', { password: password });
|
||||
|
||||
if (!response.success) {
|
||||
toastService.showError("Wrong password.", 3000);
|
||||
toastService.showError(t("protected_session.wrong_password"), 3000);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -72,7 +73,7 @@ ws.subscribeToMessages(async message => {
|
||||
protectedSessionDeferred = null;
|
||||
}
|
||||
|
||||
toastService.showMessage("Protected session has been started.");
|
||||
toastService.showMessage(t("protected_session.started"));
|
||||
}
|
||||
else if (message.type === 'protectedSessionLogout') {
|
||||
utils.reloadFrontendApp(`Protected session logout`);
|
||||
@@ -85,10 +86,10 @@ async function protectNote(noteId, protect, includingSubtree) {
|
||||
await server.put(`notes/${noteId}/protect/${protect ? 1 : 0}?subtree=${includingSubtree ? 1 : 0}`);
|
||||
}
|
||||
|
||||
function makeToast(message, protectingLabel, text) {
|
||||
function makeToast(message, title, text) {
|
||||
return {
|
||||
id: message.taskId,
|
||||
title: `${protectingLabel} status`,
|
||||
title,
|
||||
message: text,
|
||||
icon: message.data.protect ? "check-shield" : "shield"
|
||||
};
|
||||
@@ -99,15 +100,19 @@ ws.subscribeToMessages(async message => {
|
||||
return;
|
||||
}
|
||||
|
||||
const protectingLabel = message.data.protect ? "Protecting" : "Unprotecting";
|
||||
|
||||
const isProtecting = message.data.protect;
|
||||
const title = isProtecting ? t("protected_session.protecting-title") : t("protected_session.unprotecting-title");
|
||||
|
||||
if (message.type === 'taskError') {
|
||||
toastService.closePersistent(message.taskId);
|
||||
toastService.showError(message.message);
|
||||
} else if (message.type === 'taskProgressCount') {
|
||||
toastService.showPersistent(makeToast(message, protectingLabel,`${protectingLabel} in progress: ${message.progressCount}`));
|
||||
const count = message.progressCount;
|
||||
const text = ( isProtecting ? t("protected_session.protecting-in-progress", { count }) : t("protected_session.unprotecting-in-progress-count", { count }));
|
||||
toastService.showPersistent(makeToast(message, title, text));
|
||||
} else if (message.type === 'taskSucceeded') {
|
||||
const toast = makeToast(message, protectingLabel, `${protectingLabel} finished successfully.`);
|
||||
const text = (isProtecting ? t("protected_session.protecting-finished-successfully") : t("protected_session.unprotecting-finished-successfully"))
|
||||
const toast = makeToast(message, title, text);
|
||||
toast.closeAfter = 3000;
|
||||
|
||||
toastService.showPersistent(toast);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { t } from './i18n.js';
|
||||
import server from './server.js';
|
||||
import toastService from "./toast.js";
|
||||
|
||||
@@ -5,7 +6,7 @@ async function syncNow(ignoreNotConfigured = false) {
|
||||
const result = await server.post('sync/now');
|
||||
|
||||
if (result.success) {
|
||||
toastService.showMessage("Sync finished successfully.");
|
||||
toastService.showMessage(t("sync.finished-successfully"));
|
||||
}
|
||||
else {
|
||||
if (result.message.length > 200) {
|
||||
@@ -13,7 +14,7 @@ async function syncNow(ignoreNotConfigured = false) {
|
||||
}
|
||||
|
||||
if (!ignoreNotConfigured || result.errorCode !== 'NOT_CONFIGURED') {
|
||||
toastService.showError(`Sync failed: ${result.message}`);
|
||||
toastService.showError(t("sync.failed", { message: result.message }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
79
src/public/app/services/syntax_highlight.js
Normal file
79
src/public/app/services/syntax_highlight.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import library_loader from "./library_loader.js";
|
||||
import mime_types from "./mime_types.js";
|
||||
import options from "./options.js";
|
||||
|
||||
export function getStylesheetUrl(theme) {
|
||||
if (!theme) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const defaultPrefix = "default:";
|
||||
if (theme.startsWith(defaultPrefix)) {
|
||||
return `${window.glob.assetPath}/node_modules/@highlightjs/cdn-assets/styles/${theme.substr(defaultPrefix.length)}.min.css`;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies all the code blocks (as `pre code`) under the specified hierarchy and uses the highlight.js library to obtain the highlighted text which is then applied on to the code blocks.
|
||||
*
|
||||
* @param $container the container under which to look for code blocks and to apply syntax highlighting to them.
|
||||
*/
|
||||
export async function applySyntaxHighlight($container) {
|
||||
if (!isSyntaxHighlightEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
await library_loader.requireLibrary(library_loader.HIGHLIGHT_JS);
|
||||
|
||||
const codeBlocks = $container.find("pre code");
|
||||
for (const codeBlock of codeBlocks) {
|
||||
$(codeBlock).parent().toggleClass("hljs");
|
||||
|
||||
const text = codeBlock.innerText;
|
||||
|
||||
const normalizedMimeType = extractLanguageFromClassList(codeBlock);
|
||||
if (!normalizedMimeType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let highlightedText = null;
|
||||
if (normalizedMimeType === mime_types.MIME_TYPE_AUTO) {
|
||||
highlightedText = hljs.highlightAuto(text);
|
||||
} else if (normalizedMimeType) {
|
||||
const language = mime_types.getHighlightJsNameForMime(normalizedMimeType);
|
||||
highlightedText = hljs.highlight(text, { language });
|
||||
}
|
||||
|
||||
if (highlightedText) {
|
||||
codeBlock.innerHTML = highlightedText.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether syntax highlighting should be enabled for code blocks, by querying the value of the `codeblockTheme` option.
|
||||
* @returns whether syntax highlighting should be enabled for code blocks.
|
||||
*/
|
||||
export function isSyntaxHighlightEnabled() {
|
||||
const theme = options.get("codeBlockTheme");
|
||||
return theme && theme !== "none";
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a HTML element, tries to extract the `language-` class name out of it.
|
||||
*
|
||||
* @param {string} el the HTML element from which to extract the language tag.
|
||||
* @returns the normalized MIME type (e.g. `text-css` instead of `language-text-css`).
|
||||
*/
|
||||
function extractLanguageFromClassList(el) {
|
||||
const prefix = "language-";
|
||||
for (const className of el.classList) {
|
||||
if (className.startsWith(prefix)) {
|
||||
return className.substr(prefix.length);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -2,18 +2,21 @@ import ws from "./ws.js";
|
||||
import utils from "./utils.js";
|
||||
|
||||
function toast(options) {
|
||||
const $toast = $(`<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="toast-header">
|
||||
<strong class="mr-auto"><span class="bx bx-${options.icon}"></span> <span class="toast-title"></span></strong>
|
||||
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="toast-body"></div>
|
||||
</div>`);
|
||||
const $toast = $(
|
||||
`<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="toast-header">
|
||||
<strong class="me-auto">
|
||||
<span class="bx bx-${options.icon}"></span>
|
||||
<span class="toast-title"></span>
|
||||
</strong>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="toast-body"></div>
|
||||
</div>`
|
||||
);
|
||||
|
||||
$toast.find('.toast-title').text(options.title);
|
||||
$toast.find('.toast-body').text(options.message);
|
||||
$toast.find('.toast-body').html(options.message);
|
||||
|
||||
if (options.id) {
|
||||
$toast.attr("id", `toast-${options.id}`);
|
||||
|
||||
@@ -201,7 +201,7 @@ function getMimeTypeClass(mime) {
|
||||
|
||||
function closeActiveDialog() {
|
||||
if (glob.activeDialog) {
|
||||
glob.activeDialog.modal('hide');
|
||||
bootstrap.Modal.getOrCreateInstance(glob.activeDialog).hide();
|
||||
glob.activeDialog = null;
|
||||
}
|
||||
}
|
||||
@@ -245,8 +245,7 @@ async function openDialog($dialog, closeActDialog = true) {
|
||||
}
|
||||
|
||||
saveFocusedElement();
|
||||
|
||||
$dialog.modal();
|
||||
bootstrap.Modal.getOrCreateInstance($dialog).show();
|
||||
|
||||
$dialog.on('hidden.bs.modal', () => {
|
||||
$(".aa-input").autocomplete("close");
|
||||
|
||||
@@ -114,10 +114,10 @@ async function handleMessage(event) {
|
||||
await executeFrontendUpdate(message.data.entityChanges);
|
||||
}
|
||||
else if (message.type === 'sync-hash-check-failed') {
|
||||
toastService.showError("Sync check failed!", 60000);
|
||||
toastService.showError(t("ws.sync-check-failed"), 60000);
|
||||
}
|
||||
else if (message.type === 'consistency-checks-failed') {
|
||||
toastService.showError("Consistency checks failed! See logs for details.", 50 * 60000);
|
||||
toastService.showError(t("ws.consistency-checks-failed"), 50 * 60000);
|
||||
}
|
||||
else if (message.type === 'api-log-messages') {
|
||||
appContext.triggerEvent("apiLogMessages", {noteId: message.noteId, messages: message.messages});
|
||||
@@ -189,7 +189,7 @@ async function consumeFrontendUpdateData() {
|
||||
else {
|
||||
console.log("nonProcessedEntityChanges causing the timeout", nonProcessedEntityChanges);
|
||||
|
||||
toastService.showError(`Encountered error "${e.message}", check out the console.`);
|
||||
toastService.showError(t("ws.encountered-error", { message: e.message }));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ const TPL = `
|
||||
<tr class="attr-row-promoted"
|
||||
title="${t('attribute_detail.promoted_title')}">
|
||||
<th>${t('attribute_detail.promoted')}</th>
|
||||
<td><input type="checkbox" class="attr-input-promoted form-control form-control-sm" /></td>
|
||||
<td><input type="checkbox" class="attr-input-promoted form-check" /></td>
|
||||
</tr>
|
||||
<tr class="attr-row-promoted-alias">
|
||||
<th title="${t('attribute_detail.promoted_alias_title')}">${t('attribute_detail.promoted_alias')}</th>
|
||||
@@ -134,9 +134,7 @@ const TPL = `
|
||||
<td>
|
||||
<div class="input-group">
|
||||
<input type="number" class="form-control attr-input-number-precision" style="text-align: right">
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">${t('attribute_detail.digits')}</span>
|
||||
</div>
|
||||
<span class="input-group-text">${t('attribute_detail.digits')}</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -150,7 +148,7 @@ const TPL = `
|
||||
</tr>
|
||||
<tr title="${t('attribute_detail.inheritable_title')}">
|
||||
<th>${t('attribute_detail.inheritable')}</th>
|
||||
<td><input type="checkbox" class="attr-input-inheritable form-control form-control-sm" /></td>
|
||||
<td><input type="checkbox" class="attr-input-inheritable form-check" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -349,7 +347,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
this.$rowTargetNote = this.$widget.find('.attr-row-target-note');
|
||||
this.$inputTargetNote = this.$widget.find('.attr-input-target-note');
|
||||
|
||||
noteAutocompleteService.initNoteAutocomplete(this.$inputTargetNote, {allowCreatingNotes: true})
|
||||
noteAutocompleteService.initNoteAutocomplete(this.$inputTargetNote, { allowCreatingNotes: true })
|
||||
.on('autocomplete:noteselected', (event, suggestion, dataset) => {
|
||||
if (!suggestion.notePath) {
|
||||
return false;
|
||||
@@ -403,7 +401,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
});
|
||||
}
|
||||
|
||||
async showAttributeDetail({allAttributes, attribute, isOwned, x, y, focus}) {
|
||||
async showAttributeDetail({ allAttributes, attribute, isOwned, x, y, focus }) {
|
||||
if (!attribute) {
|
||||
this.hide();
|
||||
|
||||
@@ -545,7 +543,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
}
|
||||
|
||||
return {left, right};
|
||||
return { left, right };
|
||||
}
|
||||
|
||||
async saveAndClose() {
|
||||
@@ -589,19 +587,19 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
|
||||
async updateRelatedNotes() {
|
||||
let {results, count} = await server.post('search-related', this.attribute);
|
||||
let { results, count } = await server.post('search-related', this.attribute);
|
||||
|
||||
for (const res of results) {
|
||||
res.noteId = res.notePathArray[res.notePathArray.length - 1];
|
||||
}
|
||||
|
||||
results = results.filter(({noteId}) => noteId !== this.noteId);
|
||||
results = results.filter(({ noteId }) => noteId !== this.noteId);
|
||||
|
||||
if (results.length === 0) {
|
||||
this.$relatedNotesContainer.hide();
|
||||
} else {
|
||||
this.$relatedNotesContainer.show();
|
||||
this.$relatedNotesTitle.text(t("attribute_detail.other_notes_with_name", {attributeType: this.attribute.type, attributeName: this.attribute.name}));
|
||||
this.$relatedNotesTitle.text(t("attribute_detail.other_notes_with_name", { attributeType: this.attribute.type, attributeName: this.attribute.name }));
|
||||
|
||||
this.$relatedNotesList.empty();
|
||||
|
||||
@@ -611,7 +609,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
|
||||
for (const note of displayedNotes) {
|
||||
const notePath = note.getBestNotePathString(hoistedNoteId);
|
||||
const $noteLink = await linkService.createLink(notePath, {showNotePath: true});
|
||||
const $noteLink = await linkService.createLink(notePath, { showNotePath: true });
|
||||
|
||||
this.$relatedNotesList.append(
|
||||
$("<li>").append($noteLink)
|
||||
@@ -619,7 +617,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
|
||||
if (results.length > DISPLAYED_NOTES) {
|
||||
this.$relatedNotesMoreNotes.show().text(t("attribute_detail.and_more", {count: count - DISPLAYED_NOTES}));
|
||||
this.$relatedNotesMoreNotes.show().text(t("attribute_detail.and_more", { count: count - DISPLAYED_NOTES }));
|
||||
} else {
|
||||
this.$relatedNotesMoreNotes.hide();
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ const editorConfig = {
|
||||
toolbar: {
|
||||
items: []
|
||||
},
|
||||
placeholder: "Type the labels and relations here",
|
||||
placeholder: t("attribute_editor.placeholder"),
|
||||
mention: mentionSetup
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Component from "../components/component.js";
|
||||
import froca from "../services/froca.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import toastService from "../services/toast.js";
|
||||
|
||||
@@ -84,15 +85,8 @@ class BasicWidget extends Component {
|
||||
render() {
|
||||
try {
|
||||
this.doRender();
|
||||
} catch (e) {
|
||||
toastService.showPersistent({
|
||||
title: t("toast.widget-error.title"),
|
||||
icon: "alert",
|
||||
message: t("toast.widget-error.message", {
|
||||
title: this.widgetTitle,
|
||||
message: e.message
|
||||
})
|
||||
});
|
||||
} catch (e) {
|
||||
this.logRenderingError(e);
|
||||
}
|
||||
|
||||
this.$widget.attr('data-component-id', this.componentId);
|
||||
@@ -131,6 +125,35 @@ class BasicWidget extends Component {
|
||||
return this.$widget;
|
||||
}
|
||||
|
||||
logRenderingError(e) {
|
||||
console.log("Got issue in widget ", this);
|
||||
console.error(e);
|
||||
|
||||
let noteId = this._noteId;
|
||||
if (this._noteId) {
|
||||
froca.getNote(noteId, true).then((note) => {
|
||||
toastService.showPersistent({
|
||||
title: t("toast.widget-error.title"),
|
||||
icon: "alert",
|
||||
message: t("toast.widget-error.message-custom", {
|
||||
id: noteId,
|
||||
title: note.title,
|
||||
message: e.message
|
||||
})
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
toastService.showPersistent({
|
||||
title: t("toast.widget-error.title"),
|
||||
icon: "alert",
|
||||
message: t("toast.widget-error.message-unknown", {
|
||||
message: e.message
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the widget is enabled. Widgets are enabled by default. Generally setting this to `false` will cause the widget not to be displayed, however it will still be available on the DOM but hidden.
|
||||
* @returns
|
||||
|
||||
@@ -15,7 +15,7 @@ const TPL = `
|
||||
<td class="button-column">
|
||||
<div style="display: flex">
|
||||
<div class="dropdown help-dropdown">
|
||||
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<div class="dropdown-menu dropdown-menu-right p-4">
|
||||
${t('execute_script.help_text')}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ const TPL = `
|
||||
</td>
|
||||
<td class="button-column">
|
||||
<div class="dropdown help-dropdown">
|
||||
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<div class="dropdown-menu dropdown-menu-right p-4">
|
||||
<p>${t("add_label.help_text")}</p>
|
||||
|
||||
@@ -30,7 +30,7 @@ const TPL = `
|
||||
<li>${t("add_label.help_text_item2")}</li>
|
||||
</ul>
|
||||
|
||||
<p>${t("add_label.help_text_note")}</p>
|
||||
${t("add_label.help_text_note")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -21,11 +21,11 @@ const TPL = `
|
||||
</td>
|
||||
<td class="button-column">
|
||||
<div class="dropdown help-dropdown">
|
||||
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<div class="dropdown-menu dropdown-menu-right p-4">
|
||||
<p>${t("update_label_value.help_text")}</p>
|
||||
|
||||
<p>${t("update_label_value.help_text_note")}</p>
|
||||
${t("update_label_value.help_text_note")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@ const TPL = `
|
||||
</td>
|
||||
<td class="button-column">
|
||||
<div class="dropdown help-dropdown">
|
||||
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<div class="dropdown-menu dropdown-menu-right p-4">
|
||||
<p>${t("delete_note.delete_matched_notes_description")}</p>
|
||||
|
||||
<p>${t("delete_note.undelete_notes_instruction")}</p>
|
||||
|
||||
<p>${t("delete_note.erase_notes_instruction")}</p>
|
||||
${t("delete_note.erase_notes_instruction")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ const TPL = `
|
||||
</td>
|
||||
<td class="button-column">
|
||||
<div class="dropdown help-dropdown">
|
||||
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<div class="dropdown-menu dropdown-menu-right p-4">
|
||||
${t('delete_revisions.all_past_note_revisions')}
|
||||
</div>
|
||||
|
||||
@@ -18,11 +18,11 @@ const TPL = `
|
||||
</td>
|
||||
<td class="button-column">
|
||||
<div class="dropdown help-dropdown">
|
||||
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<div class="dropdown-menu dropdown-menu-right p-4">
|
||||
<p>${t('move_note.on_all_matched_notes')}:</p>
|
||||
|
||||
<ul>
|
||||
<ul style="margin-bottom: 0;">
|
||||
<li>${t('move_note.move_note_new_parent')}</li>
|
||||
<li>${t('move_note.clone_note_new_parent')}</li>
|
||||
<li>${t('move_note.nothing_will_happen')}</li>
|
||||
|
||||
@@ -16,7 +16,7 @@ const TPL = `
|
||||
</td>
|
||||
<td class="button-column">
|
||||
<div class="dropdown help-dropdown">
|
||||
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<div class="dropdown-menu dropdown-menu-right p-4">
|
||||
<p>${t('rename_note.evaluated_as_js_string')}</p>
|
||||
|
||||
|
||||
@@ -25,9 +25,9 @@ const TPL = `
|
||||
</td>
|
||||
<td class="button-column">
|
||||
<div class="dropdown help-dropdown">
|
||||
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<div class="dropdown-menu dropdown-menu-right p-4">
|
||||
<p>${t('add_relation.create_relation_on_all_matched_notes')}</p>
|
||||
${t('add_relation.create_relation_on_all_matched_notes')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -25,11 +25,11 @@ const TPL = `
|
||||
</td>
|
||||
<td class="button-column">
|
||||
<div class="dropdown help-dropdown">
|
||||
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<div class="dropdown-menu dropdown-menu-right p-4">
|
||||
<p>${t('update_relation_target.on_all_matched_notes')}:</p>
|
||||
|
||||
<ul>
|
||||
<ul style="margin-bottom: 0;">
|
||||
<li>${t('update_relation_target.create_given_relation')}</li>
|
||||
<li>${t('update_relation_target.change_target_note')}</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
|
||||
const TPL = `<button class="button-widget bx"
|
||||
data-toggle="tooltip"
|
||||
data-bs-toggle="tooltip"
|
||||
title=""></button>`;
|
||||
|
||||
export default class AbstractButtonWidget extends NoteContextAwareWidget {
|
||||
@@ -22,10 +22,13 @@ export default class AbstractButtonWidget extends NoteContextAwareWidget {
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.tooltip = new bootstrap.Tooltip(this.$widget, {
|
||||
html: true, title: () => this.getTitle(), trigger: 'hover'
|
||||
})
|
||||
|
||||
if (this.settings.onContextMenu) {
|
||||
this.$widget.on("contextmenu", e => {
|
||||
this.$widget.tooltip("hide");
|
||||
this.tooltip.hide();
|
||||
|
||||
this.settings.onContextMenu(e);
|
||||
|
||||
@@ -35,12 +38,6 @@ export default class AbstractButtonWidget extends NoteContextAwareWidget {
|
||||
|
||||
this.$widget.attr("data-placement", this.settings.titlePlacement);
|
||||
|
||||
this.$widget.tooltip({
|
||||
html: true,
|
||||
title: () => this.getTitle(),
|
||||
trigger: "hover"
|
||||
});
|
||||
|
||||
super.doRender();
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ const TPL = `
|
||||
}
|
||||
</style>
|
||||
|
||||
<button type="button" data-toggle="dropdown" aria-haspopup="true"
|
||||
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true"
|
||||
aria-expanded="false" class="icon-action icon-action-always-border bx bx-dots-vertical-rounded"
|
||||
style="position: relative; top: 3px;"></button>
|
||||
|
||||
@@ -61,7 +61,8 @@ export default class AttachmentActionsWidget extends BasicWidget {
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$widget.on('click', '.dropdown-item', () => this.$widget.find("[data-toggle='dropdown']").dropdown('toggle'));
|
||||
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
|
||||
this.$widget.on('click', '.dropdown-item', () => this.dropdown.toggle());
|
||||
|
||||
this.$uploadNewRevisionInput = this.$widget.find(".attachment-upload-new-revision-input");
|
||||
this.$uploadNewRevisionInput.on('change', async () => {
|
||||
@@ -84,7 +85,7 @@ export default class AttachmentActionsWidget extends BasicWidget {
|
||||
.addClass("disabled")
|
||||
.append($('<span class="disabled-tooltip"> (?)</span>')
|
||||
.attr("title", t('attachments_actions.open_externally_detail_page'))
|
||||
);
|
||||
);
|
||||
if (isElectron) {
|
||||
const $openAttachmentCustomButton = this.$widget.find("[data-trigger-command='openAttachmentCustom']");
|
||||
$openAttachmentCustomButton
|
||||
@@ -94,7 +95,7 @@ export default class AttachmentActionsWidget extends BasicWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!isElectron){
|
||||
if (!isElectron) {
|
||||
const $openAttachmentCustomButton = this.$widget.find("[data-trigger-command='openAttachmentCustom']");
|
||||
$openAttachmentCustomButton
|
||||
.addClass("disabled")
|
||||
@@ -138,7 +139,7 @@ export default class AttachmentActionsWidget extends BasicWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
const {note: newNote} = await server.post(`attachments/${this.attachmentId}/convert-to-note`)
|
||||
const { note: newNote } = await server.post(`attachments/${this.attachmentId}/convert-to-note`)
|
||||
toastService.showMessage(t('attachments_actions.convert_success', { title: this.attachment.title }));
|
||||
await ws.waitForMaxKnownEntityChangeId();
|
||||
await appContext.tabManager.getActiveContext().setNote(newNote.noteId);
|
||||
@@ -155,6 +156,6 @@ export default class AttachmentActionsWidget extends BasicWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
await server.put(`attachments/${this.attachmentId}/rename`, {title: attachmentTitle});
|
||||
await server.put(`attachments/${this.attachmentId}/rename`, { title: attachmentTitle });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,12 +74,12 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
|
||||
|
||||
doRender() {
|
||||
super.doRender();
|
||||
|
||||
|
||||
this.$month = this.$dropdownContent.find('[data-calendar-area="month"]');
|
||||
this.$weekHeader = this.$dropdownContent.find(".calendar-week");
|
||||
|
||||
|
||||
this.manageFirstDayOfWeek();
|
||||
|
||||
|
||||
// Month navigation
|
||||
this.$monthSelect = this.$dropdownContent.find('[data-calendar-input="month"]');
|
||||
this.$monthSelect.on("input", (e) => {
|
||||
@@ -88,10 +88,10 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
|
||||
});
|
||||
this.$next = this.$dropdownContent.find('[data-calendar-toggle="next"]');
|
||||
this.$next.on('click', () => {
|
||||
this.date.setMonth(this.date.getMonth() + 1);
|
||||
this.date.setMonth(this.date.getMonth() + 1);
|
||||
this.createMonth();
|
||||
});
|
||||
this.$previous = this.$dropdownContent.find('[data-calendar-toggle="previous"]');
|
||||
this.$previous = this.$dropdownContent.find('[data-calendar-toggle="previous"]');
|
||||
this.$previous.on('click', e => {
|
||||
this.date.setMonth(this.date.getMonth() - 1);
|
||||
this.createMonth();
|
||||
@@ -108,7 +108,7 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
|
||||
this.date.setFullYear(this.date.getFullYear() + 1);
|
||||
this.createMonth();
|
||||
});
|
||||
this.$previousYear = this.$dropdownContent.find('[data-calendar-toggle="previousYear"]');
|
||||
this.$previousYear = this.$dropdownContent.find('[data-calendar-toggle="previousYear"]');
|
||||
this.$previousYear.on('click', e => {
|
||||
this.date.setFullYear(this.date.getFullYear() - 1);
|
||||
this.createMonth();
|
||||
@@ -123,11 +123,13 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
|
||||
|
||||
if (note) {
|
||||
appContext.tabManager.getActiveContext().setNote(note.noteId);
|
||||
this.hideDropdown();
|
||||
this.dropdown.hide();
|
||||
}
|
||||
else {
|
||||
toastService.showError(t("calendar.cannot_find_day_note"));
|
||||
}
|
||||
|
||||
ev.stopPropagation();
|
||||
});
|
||||
|
||||
// Prevent dismissing the calendar popup by clicking on an empty space inside it.
|
||||
@@ -138,9 +140,9 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
|
||||
this.firstDayOfWeek = options.getInt("firstDayOfWeek");
|
||||
|
||||
// Generate the list of days of the week taking into consideration the user's selected first day of week.
|
||||
let localeDaysOfWeek = [ ...DAYS_OF_WEEK ];
|
||||
let localeDaysOfWeek = [...DAYS_OF_WEEK];
|
||||
const daysToBeAddedAtEnd = localeDaysOfWeek.splice(0, this.firstDayOfWeek);
|
||||
localeDaysOfWeek = [ ...localeDaysOfWeek, ...daysToBeAddedAtEnd ];
|
||||
localeDaysOfWeek = [...localeDaysOfWeek, ...daysToBeAddedAtEnd];
|
||||
this.$weekHeader.html(localeDaysOfWeek.map((el) => `<span>${el}</span>`));
|
||||
}
|
||||
|
||||
@@ -184,7 +186,7 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
|
||||
|
||||
if (dateNoteId) {
|
||||
$newDay.addClass('calendar-date-exists');
|
||||
$newDay.attr("href", `#root/${dateNoteId}`);
|
||||
$newDay.attr("data-href", `#root/${dateNoteId}`);
|
||||
}
|
||||
|
||||
if (this.isEqual(this.date, this.activeDate)) {
|
||||
@@ -236,11 +238,11 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
|
||||
this.$yearSelect.val(this.date.getFullYear());
|
||||
}
|
||||
|
||||
async entitiesReloadedEvent({loadResults}) {
|
||||
async entitiesReloadedEvent({ loadResults }) {
|
||||
if (!loadResults.getOptionNames().includes("firstDayOfWeek")) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
this.manageFirstDayOfWeek();
|
||||
this.createMonth();
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export default class CommandButtonWidget extends AbstractButtonWidget {
|
||||
|
||||
if (this.settings.command) {
|
||||
this.$widget.on("click", () => {
|
||||
this.$widget.tooltip("hide");
|
||||
this.tooltip.hide();
|
||||
|
||||
this.triggerCommand(this._command);
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import UpdateAvailableWidget from "./update_available.js";
|
||||
import options from "../../services/options.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="dropdown global-menu dropright">
|
||||
<div class="dropdown global-menu dropend">
|
||||
<style>
|
||||
.global-menu {
|
||||
width: 53px;
|
||||
@@ -70,6 +70,7 @@ const TPL = `
|
||||
background-color: var(--button-background-color);
|
||||
padding: 3px;
|
||||
margin-left: 3px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.global-menu .zoom-buttons a:hover {
|
||||
@@ -102,10 +103,9 @@ const TPL = `
|
||||
}
|
||||
</style>
|
||||
|
||||
<button type="button" data-toggle="dropdown" data-placement="right"
|
||||
aria-haspopup="true" aria-expanded="false"
|
||||
class="icon-action global-menu-button" title="${t('global_menu.menu')}">
|
||||
<svg viewBox="0 0 256 256">
|
||||
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true"
|
||||
aria-expanded="false" class="icon-action global-menu-button">
|
||||
<svg viewBox="0 0 256 256" data-bs-toggle="tooltip" title="${t('global_menu.menu')}">
|
||||
<g>
|
||||
<path class="st0" d="m202.9 112.7c-22.5 16.1-54.5 12.8-74.9 6.3l14.8-11.8 14.1-11.3 49.1-39.3-51.2 35.9-14.3 10-14.9 10.5c0.7-21.2 7-49.9 28.6-65.4 1.8-1.3 3.9-2.6 6.1-3.8 2.7-1.5 5.7-2.9 8.8-4.1 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.9 65.9-2.4 2.8-4.9 5.4-7.4 7.8-3.4 3.5-6.8 6.4-10.1 8.8z"/>
|
||||
<path class="st1" d="m213.1 104c-22.2 12.6-51.4 9.3-70.3 3.2l14.1-11.3 49.1-39.3-51.2 35.9-14.3 10c0.5-18.1 4.9-42.1 19.7-58.6 2.7-1.5 5.7-2.9 8.8-4.1 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.9 65.9-2.3 2.8-4.8 5.4-7.2 7.8z"/>
|
||||
@@ -126,7 +126,7 @@ const TPL = `
|
||||
|
||||
<ul class="dropdown-menu dropdown-menu-right">
|
||||
<li class="dropdown-item" data-trigger-command="showOptions">
|
||||
<span class="bx bx-slider"></span>
|
||||
<span class="bx bx-cog"></span>
|
||||
${t('global_menu.options')}
|
||||
</li>
|
||||
|
||||
@@ -177,7 +177,7 @@ const TPL = `
|
||||
|
||||
<li class="dropdown-item dropdown-submenu">
|
||||
<span class="dropdown-toggle">
|
||||
<span class="bx bx-empty"></span>
|
||||
<span class="bx bx-chip"></span>
|
||||
${t('global_menu.advanced')}
|
||||
</span>
|
||||
|
||||
@@ -195,43 +195,43 @@ const TPL = `
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item" data-trigger-command="showSQLConsoleHistory">
|
||||
<span class="bx bx-empty"></span>
|
||||
<span class="bx bx-data"></span>
|
||||
${t('global_menu.open_sql_console_history')}
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item" data-trigger-command="showSearchHistory">
|
||||
<span class="bx bx-empty"></span>
|
||||
<span class="bx bx-search-alt"></span>
|
||||
${t('global_menu.open_search_history')}
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item" data-trigger-command="showBackendLog">
|
||||
<span class="bx bx-empty"></span>
|
||||
<span class="bx bx-detail"></span>
|
||||
${t('global_menu.show_backend_log')}
|
||||
<kbd data-command="showBackendLog"></kbd>
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item" data-trigger-command="reloadFrontendApp"
|
||||
title="${t('global_menu.reload_hint')}">
|
||||
<span class="bx bx-empty"></span>
|
||||
<span class="bx bx-refresh"></span>
|
||||
${t('global_menu.reload_frontend')}
|
||||
<kbd data-command="reloadFrontendApp"></kbd>
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item" data-trigger-command="showHiddenSubtree">
|
||||
<span class="bx bx-empty"></span>
|
||||
<span class="bx bx-hide"></span>
|
||||
${t('global_menu.show_hidden_subtree')}
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item show-help-button" data-trigger-command="showHelp">
|
||||
<span class="bx bx-info-circle"></span>
|
||||
<span class="bx bx-help-circle"></span>
|
||||
${t('global_menu.show_help')}
|
||||
<kbd data-command="showHelp"></kbd>
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item show-about-dialog-button">
|
||||
<span class="bx bx-empty"></span>
|
||||
<span class="bx bx-info-circle"></span>
|
||||
${t('global_menu.about')}
|
||||
</li>
|
||||
|
||||
@@ -259,10 +259,9 @@ export default class GlobalMenuWidget extends BasicWidget {
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
|
||||
this.$dropdown = this.$widget.find("[data-toggle='dropdown']");
|
||||
const $button = this.$widget.find(".global-menu-button");
|
||||
$button.tooltip({ trigger: "hover" });
|
||||
$button.on("click", () => $button.tooltip("hide"));
|
||||
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
|
||||
|
||||
this.tooltip = new bootstrap.Tooltip(this.$widget.find("[data-bs-toggle='tooltip']"), { trigger: "hover" });
|
||||
|
||||
this.$widget.find(".show-about-dialog-button").on('click', () => this.triggerCommand("openAboutDialog"));
|
||||
|
||||
@@ -278,8 +277,13 @@ export default class GlobalMenuWidget extends BasicWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$dropdown.dropdown('toggle');
|
||||
this.dropdown.toggle();
|
||||
});
|
||||
this.$widget.on('click', '.dropdown-submenu', e => {
|
||||
if ($(e.target).children(".dropdown-menu").length === 1 || $(e.target).hasClass('dropdown-toggle')) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
})
|
||||
|
||||
this.$widget.find(".global-menu-button-update-available").append(
|
||||
this.updateAvailableWidget.render()
|
||||
@@ -292,7 +296,12 @@ export default class GlobalMenuWidget extends BasicWidget {
|
||||
}
|
||||
|
||||
this.$zoomState = this.$widget.find(".zoom-state");
|
||||
this.$widget.on('show.bs.dropdown', () => this.updateZoomState());
|
||||
this.$widget.on('show.bs.dropdown', () => {
|
||||
this.updateZoomState();
|
||||
this.tooltip.hide();
|
||||
this.tooltip.disable();
|
||||
});
|
||||
this.$widget.on('hide.bs.dropdown', () => this.tooltip.enable());
|
||||
|
||||
this.$widget.find(".zoom-buttons").on("click",
|
||||
// delay to wait for the actual zoom change
|
||||
@@ -342,10 +351,10 @@ export default class GlobalMenuWidget extends BasicWidget {
|
||||
}
|
||||
|
||||
activeContextChangedEvent() {
|
||||
this.$dropdown.dropdown('hide');
|
||||
this.dropdown.hide();
|
||||
}
|
||||
|
||||
noteSwitchedEvent() {
|
||||
this.$dropdown.dropdown('hide');
|
||||
this.dropdown.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ const TPL = `
|
||||
}
|
||||
|
||||
.note-actions .dropdown-menu {
|
||||
width: 15em;
|
||||
min-width: 15em;
|
||||
}
|
||||
|
||||
.note-actions .dropdown-item[disabled], .note-actions .dropdown-item[disabled]:hover {
|
||||
@@ -27,7 +27,7 @@ const TPL = `
|
||||
}
|
||||
</style>
|
||||
|
||||
<button type="button" data-toggle="dropdown" aria-haspopup="true"
|
||||
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true"
|
||||
aria-expanded="false" class="icon-action bx bx-dots-vertical-rounded"></button>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
@@ -82,7 +82,7 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
|
||||
this.$importNoteButton = this.$widget.find('.import-files-button');
|
||||
this.$importNoteButton.on("click", () => this.triggerCommand("showImportDialog", {noteId: this.noteId}));
|
||||
|
||||
this.$widget.on('click', '.dropdown-item', () => this.$widget.find("[data-toggle='dropdown']").dropdown('toggle'));
|
||||
this.$widget.on('click', '.dropdown-item', () => this.$widget.find("[data-bs-toggle='dropdown']").dropdown('toggle'));
|
||||
|
||||
this.$openNoteExternallyButton = this.$widget.find(".open-note-externally-button");
|
||||
this.$openNoteCustomButton = this.$widget.find(".open-note-custom-button");
|
||||
@@ -127,18 +127,18 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
|
||||
async convertNoteIntoAttachmentCommand() {
|
||||
if (!await dialogService.confirm(`Are you sure you want to convert note '${this.note.title}' into an attachment of the parent note?`)) {
|
||||
if (!await dialogService.confirm(t("note_actions.convert_into_attachment_prompt", { title: this.note.title }))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {attachment: newAttachment} = await server.post(`notes/${this.noteId}/convert-to-attachment`);
|
||||
|
||||
if (!newAttachment) {
|
||||
toastService.showMessage(`Converting note '${this.note.title}' failed.`);
|
||||
toastService.showMessage(t("note_actions.convert_into_attachment_failed", { title: this.note.title }));
|
||||
return;
|
||||
}
|
||||
|
||||
toastService.showMessage(`Note '${newAttachment.title}' has been converted to attachment.`);
|
||||
toastService.showMessage(t("note_actions.convert_into_attachment_successful", { title: newAttachment.title }));
|
||||
await ws.waitForMaxKnownEntityChangeId();
|
||||
await appContext.tabManager.getActiveContext().setNote(newAttachment.ownerId, {
|
||||
viewScope: {
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="dropdown right-dropdown-widget dropright">
|
||||
<div class="dropdown right-dropdown-widget dropend">
|
||||
<style>
|
||||
.right-dropdown-widget {
|
||||
height: 53px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<button type="button" data-toggle="dropdown" data-placement="right"
|
||||
<button type="button" data-bs-toggle="dropdown" data-placement="right"
|
||||
aria-haspopup="true" aria-expanded="false"
|
||||
class="bx right-dropdown-button launcher-button"></button>
|
||||
|
||||
<div class="tooltip-trigger"></div>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-right"></div>
|
||||
</div>
|
||||
`;
|
||||
@@ -28,12 +30,16 @@ export default class RightDropdownButtonWidget extends BasicWidget {
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$dropdownMenu = this.$widget.find(".dropdown-menu");
|
||||
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
|
||||
|
||||
const $button = this.$widget.find(".right-dropdown-button")
|
||||
this.$tooltip = this.$widget.find(".tooltip-trigger").attr("title", this.title);
|
||||
this.tooltip = new bootstrap.Tooltip(this.$tooltip);
|
||||
|
||||
this.$widget.find(".right-dropdown-button")
|
||||
.addClass(this.iconClass)
|
||||
.attr("title", this.title)
|
||||
.tooltip({ trigger: "hover" })
|
||||
.on("click", () => $button.tooltip("hide"));
|
||||
.on("click", () => this.tooltip.hide())
|
||||
.on('mouseenter', () => this.tooltip.show())
|
||||
.on('mouseleave', () => this.tooltip.hide());
|
||||
|
||||
this.$widget.on('show.bs.dropdown', async () => {
|
||||
await this.dropdownShown();
|
||||
@@ -51,10 +57,5 @@ export default class RightDropdownButtonWidget extends BasicWidget {
|
||||
}
|
||||
|
||||
// to be overridden
|
||||
async dropdownShow() {}
|
||||
|
||||
hideDropdown() {
|
||||
this.$widget.dropdown("hide");
|
||||
this.$dropdownMenu.removeClass("show");
|
||||
}
|
||||
async dropdownShow() { }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import OnClickButtonWidget from "./onclick_button.js";
|
||||
import appContext from "../../components/app_context.js";
|
||||
import attributeService from "../../services/attributes.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
|
||||
export default class ShowHighlightsListWidgetButton extends OnClickButtonWidget {
|
||||
isEnabled() {
|
||||
return super.isEnabled()
|
||||
&& this.note
|
||||
&& this.note.type === 'text'
|
||||
&& this.noteContext.viewScope.viewMode === 'default';
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.icon("bx-highlight")
|
||||
.title(t("show_highlights_list_widget_button.show_highlights_list"))
|
||||
.titlePlacement("bottom")
|
||||
.onClick(widget => {
|
||||
this.noteContext.viewScope.highlightsListTemporarilyHidden = false;
|
||||
appContext.triggerEvent("showHighlightsListWidget", { noteId: this.noteId });
|
||||
this.toggleInt(false);
|
||||
});
|
||||
}
|
||||
|
||||
async refreshWithNote(note) {
|
||||
this.toggleInt(this.noteContext.viewScope.highlightsListTemporarilyHidden);
|
||||
}
|
||||
async reEvaluateHighlightsListWidgetVisibilityEvent({ noteId }) {
|
||||
if (noteId === this.noteId) {
|
||||
await this.refresh();
|
||||
}
|
||||
}
|
||||
async entitiesReloadedEvent({ loadResults }) {
|
||||
if (loadResults.isNoteContentReloaded(this.noteId)) {
|
||||
await this.refresh();
|
||||
} else if (loadResults.getAttributeRows().find(attr => attr.type === 'label'
|
||||
&& (attr.name.toLowerCase().includes('readonly') || attr.name === 'hideHighlightWidget')
|
||||
&& attributeService.isAffecting(attr, this.note))) {
|
||||
await this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
async noteTypeMimeChangedEvent({ noteId }) {
|
||||
if (this.isNote(noteId)) {
|
||||
await this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
50
src/public/app/widgets/buttons/show_toc_widget_button.js
Normal file
50
src/public/app/widgets/buttons/show_toc_widget_button.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import OnClickButtonWidget from "./onclick_button.js";
|
||||
import appContext from "../../components/app_context.js";
|
||||
import attributeService from "../../services/attributes.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
|
||||
export default class ShowTocWidgetButton extends OnClickButtonWidget {
|
||||
isEnabled() {
|
||||
return super.isEnabled()
|
||||
&& this.note
|
||||
&& this.note.type === 'text'
|
||||
&& this.noteContext.viewScope.viewMode === 'default';
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.icon("bx-objects-horizontal-left")
|
||||
.title(t("show_toc_widget_button.show_toc"))
|
||||
.titlePlacement("bottom")
|
||||
.onClick(widget => {
|
||||
this.noteContext.viewScope.tocTemporarilyHidden = false;
|
||||
appContext.triggerEvent("showTocWidget", { noteId: this.noteId });
|
||||
this.toggleInt(false);
|
||||
});
|
||||
}
|
||||
|
||||
async refreshWithNote(note) {
|
||||
this.toggleInt(this.noteContext.viewScope.tocTemporarilyHidden);
|
||||
}
|
||||
async reEvaluateTocWidgetVisibilityEvent({ noteId }) {
|
||||
if (noteId === this.noteId) {
|
||||
await this.refresh();
|
||||
}
|
||||
}
|
||||
async entitiesReloadedEvent({ loadResults }) {
|
||||
if (loadResults.isNoteContentReloaded(this.noteId)) {
|
||||
await this.refresh();
|
||||
} else if (loadResults.getAttributeRows().find(attr => attr.type === 'label'
|
||||
&& (attr.name.toLowerCase().includes('readonly') || attr.name === 'toc')
|
||||
&& attributeService.isAffecting(attr, this.note))) {
|
||||
await this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
async noteTypeMimeChangedEvent({ noteId }) {
|
||||
if (this.isNote(noteId)) {
|
||||
await this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,11 @@ export default class Container extends BasicWidget {
|
||||
|
||||
renderChildren() {
|
||||
for (const widget of this.children) {
|
||||
this.$widget.append(widget.render());
|
||||
try {
|
||||
this.$widget.append(widget.render());
|
||||
} catch (e) {
|
||||
widget.logRenderingError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,16 @@ import server from "../../services/server.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import openService from "../../services/open.js";
|
||||
|
||||
|
||||
const TPL = `
|
||||
<div class="about-dialog modal fade mx-auto" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title mr-auto">${t("about.title")}</h5>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0;">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h5 class="modal-title">${t("about.title")}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table class="table table-borderless text-nowrap">
|
||||
@@ -72,7 +71,18 @@ export default class AboutDialog extends BasicWidget {
|
||||
this.$buildDate.text(appInfo.buildDate);
|
||||
this.$buildRevision.text(appInfo.buildRevision);
|
||||
this.$buildRevision.attr('href', `https://github.com/TriliumNext/Notes/commit/${appInfo.buildRevision}`);
|
||||
this.$dataDirectory.text(appInfo.dataDirectory);
|
||||
if (utils.isElectron()) {
|
||||
this.$dataDirectory.html($('<a></a>', {
|
||||
href: '#',
|
||||
text: appInfo.dataDirectory,
|
||||
}));
|
||||
this.$dataDirectory.find("a").on('click', (event) => {
|
||||
event.preventDefault();
|
||||
openService.openDirectory(appInfo.dataDirectory);
|
||||
})
|
||||
} else {
|
||||
this.$dataDirectory.text(appInfo.dataDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
async openAboutDialogEvent() {
|
||||
|
||||
@@ -9,13 +9,9 @@ const TPL = `
|
||||
<div class="modal-dialog modal-lg" style="max-width: 1000px" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title mr-auto">${t('add_link.add_link')}</h5>
|
||||
|
||||
<h5 class="modal-title flex-grow-1">${t('add_link.add_link')}</h5>
|
||||
<button type="button" class="help-button" title="${t('add_link.help_on_links')}" data-help-page="links.html">?</button>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="${t('add_link.close')}" style="margin-left: 0 !important;">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t('add_link.close')}"></button>
|
||||
</div>
|
||||
<form class="add-link-form">
|
||||
<div class="modal-body">
|
||||
|
||||
@@ -14,13 +14,9 @@ const TPL = `<div class="branch-prefix-dialog modal fade mx-auto" tabindex="-1"
|
||||
<form class="branch-prefix-form">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title mr-auto">${t('branch_prefix.edit_branch_prefix')}</h5>
|
||||
|
||||
<h5 class="modal-title flex-grow-1">${t('branch_prefix.edit_branch_prefix')}</h5>
|
||||
<button class="help-button" type="button" data-help-page="tree-concepts.html#prefix" title="${t('branch_prefix.help_on_tree_prefix')}">?</button>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="${t('branch_prefix.close')}" style="margin-left: 0;">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t('branch_prefix.close')}"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
@@ -28,10 +24,7 @@ const TPL = `<div class="branch-prefix-dialog modal fade mx-auto" tabindex="-1"
|
||||
|
||||
<div class="input-group">
|
||||
<input class="branch-prefix-input form-control">
|
||||
|
||||
<div class="input-group-append">
|
||||
<div class="branch-prefix-note-title input-group-text"></div>
|
||||
</div>
|
||||
<div class="branch-prefix-note-title input-group-text"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -46,6 +39,7 @@ const TPL = `<div class="branch-prefix-dialog modal fade mx-auto" tabindex="-1"
|
||||
export default class BranchPrefixDialog extends BasicWidget {
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
|
||||
this.$form = this.$widget.find(".branch-prefix-form");
|
||||
this.$treePrefixInput = this.$widget.find(".branch-prefix-input");
|
||||
this.$noteTitle = this.$widget.find('.branch-prefix-note-title');
|
||||
@@ -60,7 +54,7 @@ export default class BranchPrefixDialog extends BasicWidget {
|
||||
}
|
||||
|
||||
async refresh(notePath) {
|
||||
const {noteId, parentNoteId} = treeService.getNoteIdAndParentIdFromUrl(notePath);
|
||||
const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
|
||||
|
||||
if (!noteId || !parentNoteId) {
|
||||
return;
|
||||
@@ -97,9 +91,9 @@ export default class BranchPrefixDialog extends BasicWidget {
|
||||
async savePrefix() {
|
||||
const prefix = this.$treePrefixInput.val();
|
||||
|
||||
await server.put(`branches/${branchId}/set-prefix`, {prefix: prefix});
|
||||
await server.put(`branches/${branchId}/set-prefix`, { prefix: prefix });
|
||||
|
||||
this.$widget.modal('hide');
|
||||
this.modal.hide();
|
||||
|
||||
toastService.showMessage(t('branch_prefix.branch_prefix_saved'));
|
||||
}
|
||||
|
||||
@@ -38,20 +38,15 @@ const TPL = `
|
||||
<div class="modal-dialog modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title mr-auto">${t('bulk_actions.bulk_actions')}</h5>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="${t('bulk_actions.close')}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h5 class="modal-title">${t('bulk_actions.bulk_actions')}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t('bulk_actions.close')}"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<h4>${t('bulk_actions.affected_notes')}: <span class="affected-note-count">0</span></h4>
|
||||
|
||||
<div class="form-check">
|
||||
<label class="form-check-label">
|
||||
<input class="include-descendants form-check-input" type="checkbox" value="">
|
||||
${t('bulk_actions.include_descendants')}
|
||||
</label>
|
||||
<input class="include-descendants form-check-input" type="checkbox" value="">
|
||||
<label class="form-check-label">${t('bulk_actions.include_descendants')}</label>
|
||||
</div>
|
||||
|
||||
<h4>${t('bulk_actions.available_actions')}</h4>
|
||||
@@ -72,7 +67,6 @@ const TPL = `
|
||||
export default class BulkActionsDialog extends BasicWidget {
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
|
||||
this.$includeDescendants = this.$widget.find(".include-descendants");
|
||||
this.$includeDescendants.on("change", () => this.refresh());
|
||||
|
||||
|
||||
@@ -13,13 +13,9 @@ const TPL = `
|
||||
<div class="modal-dialog modal-lg" style="max-width: 1000px" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title mr-auto">${t('clone_to.clone_notes_to')}</h5>
|
||||
|
||||
<h5 class="modal-title flex-grow-1">${t('clone_to.clone_notes_to')}</h5>
|
||||
<button type="button" class="help-button" title="${t('clone_to.help_on_links')}" data-help-page="cloning-notes.html">?</button>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form class="clone-to-form">
|
||||
<div class="modal-body">
|
||||
@@ -81,9 +77,9 @@ export default class CloneToDialog extends BasicWidget {
|
||||
});
|
||||
}
|
||||
|
||||
async cloneNoteIdsToEvent({noteIds}) {
|
||||
async cloneNoteIdsToEvent({ noteIds }) {
|
||||
if (!noteIds || noteIds.length === 0) {
|
||||
noteIds = [ appContext.tabManager.getActiveContextNoteId() ];
|
||||
noteIds = [appContext.tabManager.getActiveContextNoteId()];
|
||||
}
|
||||
|
||||
this.clonedNoteIds = [];
|
||||
@@ -111,7 +107,7 @@ export default class CloneToDialog extends BasicWidget {
|
||||
}
|
||||
|
||||
async cloneNotesTo(notePath) {
|
||||
const {noteId, parentNoteId} = treeService.getNoteIdAndParentIdFromUrl(notePath);
|
||||
const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
|
||||
const targetBranchId = await froca.getBranchId(parentNoteId, noteId);
|
||||
|
||||
for (const cloneNoteId of this.clonedNoteIds) {
|
||||
@@ -120,7 +116,7 @@ export default class CloneToDialog extends BasicWidget {
|
||||
const clonedNote = await froca.getNote(cloneNoteId);
|
||||
const targetNote = await froca.getBranch(targetBranchId).getNote();
|
||||
|
||||
toastService.showMessage(t('clone_to.note_cloned', {clonedTitle: clonedNote.title, targetTitle: targetNote.title}));
|
||||
toastService.showMessage(t('clone_to.note_cloned', { clonedTitle: clonedNote.title, targetTitle: targetNote.title }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,8 @@ const TPL = `
|
||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title mr-auto">${t('confirm.confirmation')}</h5>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h5 class="modal-title">${t('confirm.confirmation')}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="confirm-dialog-content"></div>
|
||||
@@ -40,6 +37,7 @@ export default class ConfirmDialog extends BasicWidget {
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
|
||||
this.$confirmContent = this.$widget.find(".confirm-dialog-content");
|
||||
this.$okButton = this.$widget.find(".confirm-dialog-ok-button");
|
||||
this.$cancelButton = this.$widget.find(".confirm-dialog-cancel-button");
|
||||
@@ -62,7 +60,7 @@ export default class ConfirmDialog extends BasicWidget {
|
||||
this.$okButton.on('click', () => this.doResolve(true));
|
||||
}
|
||||
|
||||
showConfirmDialogEvent({message, callback}) {
|
||||
showConfirmDialogEvent({ message, callback }) {
|
||||
this.$originallyFocused = $(':focus');
|
||||
|
||||
this.$custom.hide();
|
||||
@@ -75,15 +73,15 @@ export default class ConfirmDialog extends BasicWidget {
|
||||
|
||||
this.$confirmContent.empty().append(message);
|
||||
|
||||
this.$widget.modal();
|
||||
this.modal.show();
|
||||
|
||||
this.resolve = callback;
|
||||
}
|
||||
|
||||
showConfirmDeleteNoteBoxWithNoteDialogEvent({title, callback}) {
|
||||
showConfirmDeleteNoteBoxWithNoteDialogEvent({ title, callback }) {
|
||||
glob.activeDialog = this.$widget;
|
||||
|
||||
this.$confirmContent.text(`${t('confirm.are_you_sure_remove_note', {title: title})}`);
|
||||
this.$confirmContent.text(`${t('confirm.are_you_sure_remove_note', { title: title })}`);
|
||||
|
||||
this.$custom.empty()
|
||||
.append("<br/>")
|
||||
@@ -104,7 +102,7 @@ export default class ConfirmDialog extends BasicWidget {
|
||||
|
||||
this.$custom.show();
|
||||
|
||||
this.$widget.modal();
|
||||
this.modal.show();
|
||||
|
||||
this.resolve = callback;
|
||||
}
|
||||
@@ -117,6 +115,6 @@ export default class ConfirmDialog extends BasicWidget {
|
||||
|
||||
this.resolve = null;
|
||||
|
||||
this.$widget.modal("hide");
|
||||
this.modal.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,31 +10,22 @@ const TPL = `
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title mr-auto">${t('delete_notes.delete_notes_preview')}</h4>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 class="modal-title">${t('delete_notes.delete_notes_preview')}</h4>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input class="delete-all-clones" value="1" type="checkbox">
|
||||
|
||||
${t('delete_notes.delete_all_clones_description')}
|
||||
</label>
|
||||
<div class="form-checkbox">
|
||||
<input class="delete-all-clones form-check-input" value="1" type="checkbox">
|
||||
<label class="form-check-label">${t('delete_notes.delete_all_clones_description')}</label>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label title="${t('delete_notes.erase_notes_description')}">
|
||||
<input class="erase-notes" value="1" type="checkbox">
|
||||
|
||||
${t('delete_notes.erase_notes_warning')}
|
||||
</label>
|
||||
<div class="form-checkbox" style="margin-bottom: 1rem">
|
||||
<input class="erase-notes form-check-input" value="1" type="checkbox">
|
||||
<label class="form-check-label">${t('delete_notes.erase_notes_warning')}</label>
|
||||
</div>
|
||||
|
||||
<div class="delete-notes-list-wrapper">
|
||||
<h4>${t('delete_notes.notes_to_be_deleted')} (<span class="deleted-notes-count"></span>)</h4>
|
||||
<h4>${t('delete_notes.notes_to_be_deleted', { noteCount: '<span class="deleted-notes-count"></span>' })}</h4>
|
||||
|
||||
<ul class="delete-notes-list" style="max-height: 200px; overflow: auto;"></ul>
|
||||
</div>
|
||||
@@ -45,7 +36,7 @@ const TPL = `
|
||||
|
||||
<div class="broken-relations-wrapper">
|
||||
<div class="alert alert-danger">
|
||||
<h4>${t('delete_notes.broken_relations_to_be_deleted')} (<span class="broke-relations-count"></span>)</h4>
|
||||
<h4>${t('delete_notes.broken_relations_to_be_deleted', { relationCount: '<span class="broke-relations-count"></span>'})}</h4>
|
||||
|
||||
<ul class="broken-relations-list" style="max-height: 200px; overflow: auto;"></ul>
|
||||
</div>
|
||||
@@ -135,11 +126,11 @@ export default class DeleteNotesDialog extends BasicWidget {
|
||||
|
||||
for (const attr of response.brokenRelations) {
|
||||
this.$brokenRelationsList.append(
|
||||
$("<li>")
|
||||
.append(`${t('delete_notes.note')} `)
|
||||
.append(await linkService.createLink(attr.value))
|
||||
.append(` ${t('delete_notes.to_be_deleted', {attrName: attr.name})} `)
|
||||
.append(await linkService.createLink(attr.noteId))
|
||||
$("<li>").html(t("delete_notes.deleted_relation_text", {
|
||||
note: (await linkService.createLink(attr.value)).html(),
|
||||
relation: `<code>${attr.name}</code>`,
|
||||
source: (await linkService.createLink(attr.noteId)).html()
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user