mirror of
https://github.com/zadam/trilium.git
synced 2025-10-26 07:46:30 +01:00
Compare commits
594 Commits
feature/el
...
feat/quick
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
912bc61730 | ||
|
|
93e8459d4b | ||
|
|
6c26fa709e | ||
|
|
f3416fa03e | ||
|
|
3e5ab2b1e1 | ||
|
|
5c0bc9a7c2 | ||
|
|
2adfa55acd | ||
|
|
8125e8afcd | ||
|
|
e328f18558 | ||
|
|
cbdfa9079c | ||
|
|
e08c4515a7 | ||
|
|
650aa16b89 | ||
|
|
11d908218b | ||
|
|
3627a7dc93 | ||
|
|
1e1c8cc4ff | ||
|
|
d616bc09c9 | ||
|
|
f1cef44d5d | ||
|
|
3795be4750 | ||
|
|
2bb66a7526 | ||
|
|
28a472782f | ||
|
|
a4da002352 | ||
|
|
94fdc2beee | ||
|
|
dd4a01d9f8 | ||
|
|
a2a6c67350 | ||
|
|
2152ca7ba6 | ||
|
|
40e4d236f4 | ||
|
|
0450cd080d | ||
|
|
1eaac79d63 | ||
|
|
19c0305ed9 | ||
|
|
f0d14a966a | ||
|
|
37e6ccdc1a | ||
|
|
06cea99b40 | ||
|
|
1851336862 | ||
|
|
461eb273d9 | ||
|
|
470edc4d70 | ||
|
|
26132a2a56 | ||
|
|
d92bd16042 | ||
|
|
1c7dfa6c91 | ||
|
|
3a3fed4314 | ||
|
|
82bdb76d75 | ||
|
|
066f3ea078 | ||
|
|
9d760a21d5 | ||
|
|
976c795ac6 | ||
|
|
ed320e4e24 | ||
|
|
3111738700 | ||
|
|
1fa0bada23 | ||
|
|
11eca7e58b | ||
|
|
4b50e2f14d | ||
|
|
63faba9603 | ||
|
|
3e213699e0 | ||
|
|
c88ff07691 | ||
|
|
b53aa5cf6e | ||
|
|
2641b9b3fe | ||
|
|
3a2a73992c | ||
|
|
b82b17a701 | ||
|
|
a6202edcd1 | ||
|
|
6eac0cb75d | ||
|
|
83672d6138 | ||
|
|
51dadf72d0 | ||
|
|
0cbf61acb3 | ||
|
|
399c7435ac | ||
|
|
d51fae7878 | ||
|
|
9750e25ad5 | ||
|
|
2f9b2f0e8f | ||
|
|
8deaf22544 | ||
|
|
b192f43187 | ||
|
|
8cb8d1303c | ||
|
|
5237348975 | ||
|
|
72e2f6757e | ||
|
|
cf059e7f86 | ||
|
|
44d69216b6 | ||
|
|
c38bf09af0 | ||
|
|
9373d47e86 | ||
|
|
29350628c3 | ||
|
|
83d1a68879 | ||
|
|
f188408099 | ||
|
|
449ab3a798 | ||
|
|
21504d1417 | ||
|
|
3060b496e3 | ||
|
|
bd35539fa1 | ||
|
|
df6447e3ad | ||
|
|
24fd898f0d | ||
|
|
1aa6238288 | ||
|
|
c16c4788da | ||
|
|
0c35daab85 | ||
|
|
4a19639e92 | ||
|
|
36cceea677 | ||
|
|
4dbc76790a | ||
|
|
b32a344a21 | ||
|
|
3896ab822f | ||
|
|
cfa4ba57d4 | ||
|
|
da051e0269 | ||
|
|
3eda77a91f | ||
|
|
5c2f4be5dd | ||
|
|
435b501db9 | ||
|
|
5a27ffef5f | ||
|
|
02256d9a45 | ||
|
|
7e069009d6 | ||
|
|
3c25cda4c0 | ||
|
|
70d7ad0b1a | ||
|
|
e793b2f661 | ||
|
|
917ea3e401 | ||
|
|
5a54dd666f | ||
|
|
733ec2c145 | ||
|
|
e386b03b90 | ||
|
|
25d5d51085 | ||
|
|
50c4301a34 | ||
|
|
0f60c0696b | ||
|
|
3ec2947c4f | ||
|
|
e89162838e | ||
|
|
72181090a5 | ||
|
|
72b2a5cc0d | ||
|
|
1eaeec8100 | ||
|
|
660db3b3ab | ||
|
|
89d2fcb81e | ||
|
|
ccda623840 | ||
|
|
a6e7dff61e | ||
|
|
86d1bbe8ff | ||
|
|
a10cb06f14 | ||
|
|
dd9a62818b | ||
|
|
c0e936675c | ||
|
|
b0b788b7dc | ||
|
|
18f0f3ecac | ||
|
|
e7d745ac94 | ||
|
|
24abf7f0ed | ||
|
|
36fb097d1d | ||
|
|
35ef5fd0d3 | ||
|
|
9a08f6534b | ||
|
|
885dd2053b | ||
|
|
6f6f280bdd | ||
|
|
a3e8fd374f | ||
|
|
f91c1f4180 | ||
|
|
d85746c1b9 | ||
|
|
5a17075eef | ||
|
|
6cab47fb55 | ||
|
|
93c5413790 | ||
|
|
f2db7baeba | ||
|
|
a507991808 | ||
|
|
7c86f90ac6 | ||
|
|
1e9b772692 | ||
|
|
096ab52216 | ||
|
|
88c3cd5cdd | ||
|
|
99a911a220 | ||
|
|
3218ab971b | ||
|
|
274e3c1f7f | ||
|
|
f8916a6e35 | ||
|
|
73f20d01e4 | ||
|
|
2fd3a875b6 | ||
|
|
68cba8d3b2 | ||
|
|
6b28fd405e | ||
|
|
3bccbabe53 | ||
|
|
c97c66ed8a | ||
|
|
4b212232c8 | ||
|
|
ac3a8edf2b | ||
|
|
b581025bbe | ||
|
|
7bc5331747 | ||
|
|
2415976475 | ||
|
|
8d0d0f0449 | ||
|
|
16b00ed160 | ||
|
|
df73a420f9 | ||
|
|
1e4d57f275 | ||
|
|
19a238c8d3 | ||
|
|
5ffd8a79eb | ||
|
|
04fbc82d7c | ||
|
|
3f105f7b8b | ||
|
|
b9193a5562 | ||
|
|
e1fa188244 | ||
|
|
80ad87671a | ||
|
|
b6d5a6ec2e | ||
|
|
759398d804 | ||
|
|
c1b30db3d1 | ||
|
|
0c8bfc39ef | ||
|
|
3815fddb27 | ||
|
|
b585a64a38 | ||
|
|
58e58c192f | ||
|
|
5939344378 | ||
|
|
ad85ee3531 | ||
|
|
349f19fef7 | ||
|
|
d5777a024e | ||
|
|
b7f4ee6171 | ||
|
|
a83c4e3970 | ||
|
|
5a767dae34 | ||
|
|
9f93d30b99 | ||
|
|
dff525edc6 | ||
|
|
26da431320 | ||
|
|
cde4622693 | ||
|
|
5ede7ecc69 | ||
|
|
b607d1e628 | ||
|
|
d7e36bdf93 | ||
|
|
2b8b185b5b | ||
|
|
927ebcbec9 | ||
|
|
ea1397de63 | ||
|
|
ce1f5c6204 | ||
|
|
652114c7b5 | ||
|
|
17cd2128fd | ||
|
|
bc4378cb3e | ||
|
|
513878dfef | ||
|
|
753d5529b2 | ||
|
|
9f217b88e4 | ||
|
|
d53faa8c01 | ||
|
|
a934760960 | ||
|
|
82914fc2aa | ||
|
|
db687197de | ||
|
|
efd713dc61 | ||
|
|
3f3c7cfe88 | ||
|
|
73ca285b7a | ||
|
|
168d25c020 | ||
|
|
e8ae5486c8 | ||
|
|
f049b8b915 | ||
|
|
4e755dc537 | ||
|
|
5351310a38 | ||
|
|
211ca43a82 | ||
|
|
e5235e7f22 | ||
|
|
e72298f0b4 | ||
|
|
3abf5c65c6 | ||
|
|
268acb0b88 | ||
|
|
196b3b873f | ||
|
|
4d9801a372 | ||
|
|
bd710ba665 | ||
|
|
afe369c876 | ||
|
|
206007bbce | ||
|
|
8ad05b92c0 | ||
|
|
735da2a855 | ||
|
|
980077f559 | ||
|
|
12053e75bb | ||
|
|
62372ed4c5 | ||
|
|
e5caf37697 | ||
|
|
befc5a9530 | ||
|
|
1e00407864 | ||
|
|
73038efccf | ||
|
|
6d37e19b40 | ||
|
|
2c33ef2b0d | ||
|
|
6c30e0836f | ||
|
|
5f77ca31bd | ||
|
|
f7c82d6b09 | ||
|
|
86dd9aa42a | ||
|
|
5daca270e4 | ||
|
|
e18813a4bf | ||
|
|
4aa7e211f3 | ||
|
|
419dc7edfb | ||
|
|
eaa84a6b39 | ||
|
|
1d0503d0e4 | ||
|
|
f7f98aa9a3 | ||
|
|
575d14261a | ||
|
|
9aab606deb | ||
|
|
2e11681b52 | ||
|
|
8cca6637f7 | ||
|
|
82e076378c | ||
|
|
94ddad3c49 | ||
|
|
d35dbca18b | ||
|
|
7468d6147a | ||
|
|
7c78d749de | ||
|
|
85dd99a3c4 | ||
|
|
0a9c0234e2 | ||
|
|
fad77ba5a0 | ||
|
|
12723f3216 | ||
|
|
a43140515f | ||
|
|
3e3cc8c541 | ||
|
|
a85141ace2 | ||
|
|
c33280bbb2 | ||
|
|
df3aa04787 | ||
|
|
4bd25a0d4a | ||
|
|
7fadf4c6e1 | ||
|
|
d1538508e8 | ||
|
|
b24d786933 | ||
|
|
8f69b87dd1 | ||
|
|
9b1da8c311 | ||
|
|
8287063aab | ||
|
|
e4a8258acf | ||
|
|
5e88043c7b | ||
|
|
bedf9112fb | ||
|
|
03681d23c5 | ||
|
|
21683db0b8 | ||
|
|
978d829150 | ||
|
|
cc05572a35 | ||
|
|
c5bb310613 | ||
|
|
77551b1fed | ||
|
|
70728c274e | ||
|
|
cee4714665 | ||
|
|
c3eca3b626 | ||
|
|
01e4cd2e78 | ||
|
|
b99d01ad7b | ||
|
|
bf0213907e | ||
|
|
eff5b6459d | ||
|
|
8e29b5eed6 | ||
|
|
c91748da15 | ||
|
|
f04f9dc262 | ||
|
|
e873cdab7e | ||
|
|
f9b6fd6ac5 | ||
|
|
aa191e110c | ||
|
|
dd09907925 | ||
|
|
da4810672d | ||
|
|
f772f59d7c | ||
|
|
1964fb90d5 | ||
|
|
5945f2860a | ||
|
|
f45da049b9 | ||
|
|
c0beab8a5d | ||
|
|
cabeb13adb | ||
|
|
e2e9721d5f | ||
|
|
4e9deab605 | ||
|
|
9bb048fb01 | ||
|
|
6849f80506 | ||
|
|
5597f4e2e0 | ||
|
|
35e9508bde | ||
|
|
45fbcec805 | ||
|
|
4c8da70ef3 | ||
|
|
ed5da5cd4a | ||
|
|
dc5fccdbcd | ||
|
|
91aea333c7 | ||
|
|
a0de01cff1 | ||
|
|
a41ed34193 | ||
|
|
49e8811c18 | ||
|
|
488563a82e | ||
|
|
d76d50f30e | ||
|
|
4685aef88d | ||
|
|
a106510924 | ||
|
|
9d54503ef7 | ||
|
|
b1449eebf3 | ||
|
|
b213453062 | ||
|
|
076c0321cf | ||
|
|
4d71b73f38 | ||
|
|
b20ffdf7db | ||
|
|
ef018e22d6 | ||
|
|
3fa290a257 | ||
|
|
cdde530b60 | ||
|
|
aa608510d0 | ||
|
|
009fd63ce9 | ||
|
|
bea352855a | ||
|
|
51e8a80ca3 | ||
|
|
8a543d4513 | ||
|
|
945e180a6f | ||
|
|
b93fa332d3 | ||
|
|
9e947f742d | ||
|
|
033e90f8b7 | ||
|
|
be576176c5 | ||
|
|
4da3e8a4d8 | ||
|
|
db2bf537ea | ||
|
|
9a4fdcaef2 | ||
|
|
ca40360f7d | ||
|
|
799e705ff8 | ||
|
|
a1b18c7f97 | ||
|
|
9958a6e1bf | ||
|
|
1fc6d8aca7 | ||
|
|
3e9ec2d943 | ||
|
|
1420def1c3 | ||
|
|
3b4184e765 | ||
|
|
4ce9102f93 | ||
|
|
eb27ec2234 | ||
|
|
b70e25d348 | ||
|
|
772c0bbe1a | ||
|
|
144021c053 | ||
|
|
59486cd55d | ||
|
|
afe3904ea3 | ||
|
|
8abd3ed3f1 | ||
|
|
53ed510c92 | ||
|
|
4ec46a2ebd | ||
|
|
db6f948499 | ||
|
|
05c73011f5 | ||
|
|
3b733d01f1 | ||
|
|
ebf21296d4 | ||
|
|
6f83ac4822 | ||
|
|
d358924324 | ||
|
|
f9a3606ca2 | ||
|
|
33299ad51e | ||
|
|
8752182e7e | ||
|
|
0551ac8ead | ||
|
|
6d5a11bd4d | ||
|
|
ce19d84247 | ||
|
|
f24aa45a3b | ||
|
|
64a28a7e75 | ||
|
|
249a755312 | ||
|
|
a3d51a013c | ||
|
|
839def9959 | ||
|
|
fd432a7100 | ||
|
|
60a07ce1e7 | ||
|
|
88c5700d87 | ||
|
|
d59993abf6 | ||
|
|
0754011909 | ||
|
|
376bb66cab | ||
|
|
588e15c633 | ||
|
|
93b8ad20d7 | ||
|
|
e51b3d760d | ||
|
|
91f3bc4488 | ||
|
|
3e80a99bbf | ||
|
|
37cdb55e79 | ||
|
|
58b66c0c95 | ||
|
|
e5f9db86a1 | ||
|
|
f138f99356 | ||
|
|
c42f4b9814 | ||
|
|
0a9fb886e3 | ||
|
|
3c4577201f | ||
|
|
816421188f | ||
|
|
5b15d2c4c6 | ||
|
|
4bc7165452 | ||
|
|
82d6531e8c | ||
|
|
d6209035c3 | ||
|
|
1d7799f981 | ||
|
|
51291a61e6 | ||
|
|
0841603be0 | ||
|
|
59ba6a0b1e | ||
|
|
53eda46043 | ||
|
|
cbc9fb7d08 | ||
|
|
1f479b20be | ||
|
|
f00b8e9522 | ||
|
|
c7dd271516 | ||
|
|
a947a61d65 | ||
|
|
0122f1cc5e | ||
|
|
acb905a3e6 | ||
|
|
7422eb5598 | ||
|
|
e721166f95 | ||
|
|
5a48130fa4 | ||
|
|
b60fe1ad10 | ||
|
|
1405b0147c | ||
|
|
222a7a57bc | ||
|
|
cddf9f0242 | ||
|
|
3e17ff5e7b | ||
|
|
04973094f2 | ||
|
|
018a6cb84a | ||
|
|
44825af0c0 | ||
|
|
cfb3607052 | ||
|
|
c5ec928aac | ||
|
|
8d0183a9fb | ||
|
|
ecd4079871 | ||
|
|
3ed975f2e6 | ||
|
|
c6deb537d5 | ||
|
|
e7b3d806a7 | ||
|
|
d1a0778b48 | ||
|
|
378634567f | ||
|
|
ed56ed2be0 | ||
|
|
648aa7e3b0 | ||
|
|
73ff41f2b2 | ||
|
|
3837466cb3 | ||
|
|
b97a5ef888 | ||
|
|
2ff1276ebb | ||
|
|
227cf5de85 | ||
|
|
ccf52be431 | ||
|
|
07713e988c | ||
|
|
f934318625 | ||
|
|
6fb90abd75 | ||
|
|
27cc33888a | ||
|
|
95af901808 | ||
|
|
c5a7f84250 | ||
|
|
a71d28500d | ||
|
|
436fd16f3a | ||
|
|
ca34bf42f6 | ||
|
|
fbf2315f57 | ||
|
|
72f50dcb6b | ||
|
|
fd4c2f79a7 | ||
|
|
72f9335213 | ||
|
|
53d97047a3 | ||
|
|
2ba3666e23 | ||
|
|
4a1d379ab4 | ||
|
|
73167e1e30 | ||
|
|
ffc13f5de3 | ||
|
|
9ba23d49d8 | ||
|
|
222a6c48a7 | ||
|
|
e33208e6ec | ||
|
|
af8781eaa7 | ||
|
|
167b1a8d2e | ||
|
|
0a7aff507c | ||
|
|
103532aad9 | ||
|
|
16939e9fd5 | ||
|
|
4ef6169041 | ||
|
|
9ebee42118 | ||
|
|
234d3997b1 | ||
|
|
3ba0bcea4e | ||
|
|
701855344e | ||
|
|
71b627fbc7 | ||
|
|
5a4fc2c690 | ||
|
|
0d67db52a2 | ||
|
|
d971554201 | ||
|
|
8fd7d7176e | ||
|
|
675575eed9 | ||
|
|
2122cde293 | ||
|
|
b68a554bba | ||
|
|
33043c7133 | ||
|
|
2e0f606a7a | ||
|
|
87878dd6a7 | ||
|
|
5296e073cc | ||
|
|
7bfb7d6f6e | ||
|
|
b5069cc7c2 | ||
|
|
3b6791f51a | ||
|
|
0b0be77e02 | ||
|
|
60db10559e | ||
|
|
76b066ba4a | ||
|
|
a28db32369 | ||
|
|
2523632391 | ||
|
|
53548c356a | ||
|
|
565904ff5d | ||
|
|
e0c5545f8c | ||
|
|
bc21285289 | ||
|
|
bbf8d757cd | ||
|
|
318d504fad | ||
|
|
fd5038148c | ||
|
|
693ca9291e | ||
|
|
cfd8afc226 | ||
|
|
3e52ca7600 | ||
|
|
482522e802 | ||
|
|
8b5b6a01c6 | ||
|
|
5614891d92 | ||
|
|
b9b4961f3c | ||
|
|
7b83b20339 | ||
|
|
e4403dd316 | ||
|
|
3f267fe6c9 | ||
|
|
3229471485 | ||
|
|
62bac1adf9 | ||
|
|
82becfd52a | ||
|
|
92f035545b | ||
|
|
74d8ea7dcb | ||
|
|
ac3f087279 | ||
|
|
1cc4eb98c1 | ||
|
|
e99bdf8f24 | ||
|
|
b4f521a141 | ||
|
|
1e23bc09f1 | ||
|
|
e3ec90405d | ||
|
|
41c87794a4 | ||
|
|
e62d2d4fda | ||
|
|
93adaa0f52 | ||
|
|
263a5d2067 | ||
|
|
f0a5005794 | ||
|
|
577457c8ab | ||
|
|
c0c450c444 | ||
|
|
1e1e0b0f51 | ||
|
|
a19204a1d5 | ||
|
|
1d139bfdfe | ||
|
|
75072decec | ||
|
|
0cf2ad6901 | ||
|
|
ccbd57a0c0 | ||
|
|
92e6c8c445 | ||
|
|
1e966f1d59 | ||
|
|
6872c2194e | ||
|
|
5b6a0216db | ||
|
|
e9a7194cd6 | ||
|
|
26898b9122 | ||
|
|
3e00e490cf | ||
|
|
c02ed17ebc | ||
|
|
fb559d66fe | ||
|
|
25dce64c3b | ||
|
|
6f19fde76e | ||
|
|
33ae91f49c | ||
|
|
99c179e29a | ||
|
|
1dbcb5a027 | ||
|
|
54d613e00e | ||
|
|
1f8aa90482 | ||
|
|
c9dcbef014 | ||
|
|
68086ec3f1 | ||
|
|
f62078d02b | ||
|
|
ab1d8594ea | ||
|
|
c368ec3c38 | ||
|
|
1a15782686 | ||
|
|
3bd0aeef77 | ||
|
|
b463baedd2 | ||
|
|
ae77c41dab | ||
|
|
807d909acd | ||
|
|
fa4f5f526e | ||
|
|
edff43cdb3 | ||
|
|
46fe45528c | ||
|
|
b4b53da6a4 | ||
|
|
41fd270080 | ||
|
|
410bb3cdca | ||
|
|
bc6fc24fbd | ||
|
|
c039f06c2b | ||
|
|
520effbbb7 | ||
|
|
a42d780724 | ||
|
|
da92255dd6 | ||
|
|
cce3d3bce8 | ||
|
|
f524e99290 | ||
|
|
ba19fc7cf3 | ||
|
|
22c3de582f | ||
|
|
48896e67cb | ||
|
|
16cd91eb02 | ||
|
|
7e03774b8e | ||
|
|
a04f6e3858 | ||
|
|
96eb1be556 | ||
|
|
f8e20a1405 | ||
|
|
c67c3a6861 | ||
|
|
d04897e011 | ||
|
|
558ae1a2ea | ||
|
|
64bffb82b1 | ||
|
|
81ac390eab | ||
|
|
0db556fac2 | ||
|
|
2793df06c4 | ||
|
|
e7b448e2bc | ||
|
|
d2bc72d54f | ||
|
|
83b22b4861 | ||
|
|
d42a949602 | ||
|
|
83e1512b59 | ||
|
|
ba6a1ec584 | ||
|
|
6685e583f2 | ||
|
|
d6032c912e | ||
|
|
25527ecc21 | ||
|
|
e0e7bd42cc | ||
|
|
fbc1af56ed | ||
|
|
8ff108db9e |
@@ -1,6 +1,6 @@
|
||||
root = true
|
||||
|
||||
[*.{js,ts}]
|
||||
[*.{js,ts,.tsx}]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
|
||||
1
.github/workflows/checks.yml
vendored
1
.github/workflows/checks.yml
vendored
@@ -12,6 +12,7 @@ jobs:
|
||||
steps:
|
||||
- name: Check if PRs have conflicts
|
||||
uses: eps1lon/actions-label-merge-conflict@v3
|
||||
if: github.repository == ${{ vars.REPO_MAIN }}
|
||||
with:
|
||||
dirtyLabel: "merge-conflicts"
|
||||
repoToken: "${{ secrets.MERGE_CONFLICT_LABEL_PAT }}"
|
||||
|
||||
4
.github/workflows/nightly.yml
vendored
4
.github/workflows/nightly.yml
vendored
@@ -27,7 +27,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
nightly-electron:
|
||||
if: github.repository == 'TriliumNext/Trilium'
|
||||
if: github.repository == ${{ vars.REPO_MAIN }}
|
||||
name: Deploy nightly
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
path: apps/desktop/upload
|
||||
|
||||
nightly-server:
|
||||
if: github.repository == 'TriliumNext/Trilium'
|
||||
if: github.repository == ${{ vars.REPO_MAIN }}
|
||||
name: Deploy server nightly
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
5
.vscode/snippets.code-snippets
vendored
5
.vscode/snippets.code-snippets
vendored
@@ -20,5 +20,10 @@
|
||||
"scope": "typescript",
|
||||
"prefix": "jqf",
|
||||
"body": ["private $${1:name}!: JQuery<HTMLElement>;"]
|
||||
},
|
||||
"region": {
|
||||
"scope": "css",
|
||||
"prefix": "region",
|
||||
"body": ["/* #region ${1:name} */\n$0\n/* #endregion */"]
|
||||
}
|
||||
}
|
||||
|
||||
30
README.md
30
README.md
@@ -1,8 +1,8 @@
|
||||
# Trilium Notes
|
||||
|
||||
 
|
||||

|
||||

|
||||

|
||||

|
||||
[](https://app.relative-ci.com/projects/Di5q7dz9daNDZ9UXi0Bp) [](https://hosted.weblate.org/engage/trilium/)
|
||||
|
||||
[English](./README.md) | [Chinese (Simplified)](./docs/README-ZH_CN.md) | [Chinese (Traditional)](./docs/README-ZH_TW.md) | [Russian](./docs/README.ru.md) | [Japanese](./docs/README.ja.md) | [Italian](./docs/README.it.md) | [Spanish](./docs/README.es.md)
|
||||
@@ -46,15 +46,15 @@ See [screenshots](https://triliumnext.github.io/Docs/Wiki/screenshot-tour) for q
|
||||
- [awesome-trilium](https://github.com/Nriver/awesome-trilium) for 3rd party themes, scripts, plugins and more.
|
||||
- [TriliumRocks!](https://trilium.rocks/) for tutorials, guides, and much more.
|
||||
|
||||
## ⚠️ Why TriliumNext?
|
||||
## ❓Why TriliumNext?
|
||||
|
||||
[The original Trilium project is in maintenance mode](https://github.com/zadam/trilium/issues/4620).
|
||||
The original Trilium developer ([Zadam](https://github.com/zadam)) has graciously given the Trilium repository to the community project which resides at https://github.com/TriliumNext
|
||||
|
||||
### Migrating from Trilium?
|
||||
### ⬆️Migrating from Zadam/Trilium?
|
||||
|
||||
There are no special migration steps to migrate from a zadam/Trilium instance to a TriliumNext/Notes instance. Simply [install TriliumNext/Notes](#-installation) as usual and it will use your existing database.
|
||||
There are no special migration steps to migrate from a zadam/Trilium instance to a TriliumNext/Trilium instance. Simply [install TriliumNext/Trilium](#-installation) as usual and it will use your existing database.
|
||||
|
||||
Versions up to and including [v0.90.4](https://github.com/TriliumNext/Notes/releases/tag/v0.90.4) are compatible with the latest zadam/trilium version of [v0.63.7](https://github.com/zadam/trilium/releases/tag/v0.63.7). Any later versions of TriliumNext have their sync versions incremented.
|
||||
Versions up to and including [v0.90.4](https://github.com/TriliumNext/Trilium/releases/tag/v0.90.4) are compatible with the latest zadam/trilium version of [v0.63.7](https://github.com/zadam/trilium/releases/tag/v0.63.7). Any later versions of TriliumNext/Trilium have their sync versions incremented which prevents direct migration.
|
||||
|
||||
## 📖 Documentation
|
||||
|
||||
@@ -75,8 +75,8 @@ Feel free to join our official conversations. We would love to hear what feature
|
||||
|
||||
- [Matrix](https://matrix.to/#/#triliumnext:matrix.org) (For synchronous discussions.)
|
||||
- The `General` Matrix room is also bridged to [XMPP](xmpp:discuss@trilium.thisgreat.party?join)
|
||||
- [Github Discussions](https://github.com/TriliumNext/Notes/discussions) (For asynchronous discussions.)
|
||||
- [Github Issues](https://github.com/TriliumNext/Notes/issues) (For bug reports and feature requests.)
|
||||
- [Github Discussions](https://github.com/TriliumNext/Trilium/discussions) (For asynchronous discussions.)
|
||||
- [Github Issues](https://github.com/TriliumNext/Trilium/issues) (For bug reports and feature requests.)
|
||||
|
||||
## 🏗 Installation
|
||||
|
||||
@@ -104,9 +104,11 @@ Currently only the latest versions of Chrome & Firefox are supported (and tested
|
||||
|
||||
To use TriliumNext on a mobile device, you can use a mobile web browser to access the mobile interface of a server installation (see below).
|
||||
|
||||
If you prefer a native Android app, you can use [TriliumDroid](https://apt.izzysoft.de/fdroid/index/apk/eu.fliegendewurst.triliumdroid). Report bugs and missing features at [their repository](https://github.com/FliegendeWurst/TriliumDroid).
|
||||
See issue https://github.com/TriliumNext/Trilium/issues/4962 for more information on mobile app support.
|
||||
|
||||
See issue https://github.com/TriliumNext/Notes/issues/72 for more information on mobile app support.
|
||||
If you prefer a native Android app, you can use [TriliumDroid](https://apt.izzysoft.de/fdroid/index/apk/eu.fliegendewurst.triliumdroid).
|
||||
Report bugs and missing features at [their repository](https://github.com/FliegendeWurst/TriliumDroid).
|
||||
Note: It is best to disable automatic updates on your server installation (see below) when using TriliumDroid since the sync version must match between Trilium and TriliumDroid.
|
||||
|
||||
### Server
|
||||
|
||||
@@ -152,11 +154,11 @@ pnpm install
|
||||
pnpm nx --project=desktop electron-forge:make -- --arch=x64 --platform=win32
|
||||
```
|
||||
|
||||
For more details, see the [development docs](https://github.com/TriliumNext/Notes/blob/develop/docs/Developer%20Guide/Developer%20Guide/Building%20and%20deployment/Running%20a%20development%20build.md).
|
||||
For more details, see the [development docs](https://github.com/TriliumNext/Trilium/tree/main/docs/Developer%20Guide/Developer%20Guide).
|
||||
|
||||
### Developer Documentation
|
||||
|
||||
Please view the [documentation guide](./docs/Developer%20Guide/Developer%20Guide/Environment%20Setup.md) for details. If you have more questions, feel free to reach out via the links described in the "Discuss with us" section above.
|
||||
Please view the [documentation guide](https://github.com/TriliumNext/Trilium/blob/main/docs/Developer%20Guide/Developer%20Guide/Environment%20Setup.md) for details. If you have more questions, feel free to reach out via the links described in the "Discuss with us" section above.
|
||||
|
||||
## 👏 Shoutouts
|
||||
|
||||
@@ -168,7 +170,7 @@ Please view the [documentation guide](./docs/Developer%20Guide/Developer%20Guide
|
||||
## 🤝 Support
|
||||
|
||||
Support for the TriliumNext organization will be possible in the near future. For now, you can:
|
||||
- Support continued development on TriliumNext by supporting our developers: [eliandoran](https://github.com/sponsors/eliandoran) (See the [repository insights]([developers]([url](https://github.com/TriliumNext/Notes/graphs/contributors))) for a full list)
|
||||
- Support continued development on TriliumNext by supporting our developers: [eliandoran](https://github.com/sponsors/eliandoran) (See the [repository insights]([developers]([url](https://github.com/TriliumNext/trilium/graphs/contributors))) for a full list)
|
||||
- Show a token of gratitude to the original Trilium developer ([zadam](https://github.com/sponsors/zadam)) via [PayPal](https://paypal.me/za4am) or Bitcoin (bitcoin:bc1qv3svjn40v89mnkre5vyvs2xw6y8phaltl385d2).
|
||||
|
||||
|
||||
|
||||
@@ -35,13 +35,13 @@
|
||||
"chore:generate-openapi": "tsx bin/generate-openapi.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.54.2",
|
||||
"@playwright/test": "1.55.0",
|
||||
"@stylistic/eslint-plugin": "5.2.3",
|
||||
"@types/express": "5.0.3",
|
||||
"@types/node": "22.17.1",
|
||||
"@types/node": "22.18.0",
|
||||
"@types/yargs": "17.0.33",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"eslint": "9.33.0",
|
||||
"eslint": "9.34.0",
|
||||
"eslint-plugin-simple-import-sort": "12.1.1",
|
||||
"esm": "3.2.25",
|
||||
"jsdoc": "4.0.4",
|
||||
@@ -49,7 +49,7 @@
|
||||
"rcedit": "4.0.1",
|
||||
"rimraf": "6.0.1",
|
||||
"tslib": "2.8.1",
|
||||
"typedoc": "0.28.10",
|
||||
"typedoc": "0.28.11",
|
||||
"typedoc-plugin-missing-exports": "4.1.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@triliumnext/client",
|
||||
"version": "0.97.2",
|
||||
"version": "0.98.1",
|
||||
"description": "JQuery-based client for TriliumNext, used for both web and desktop (via Electron)",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-only",
|
||||
@@ -10,7 +10,7 @@
|
||||
"url": "https://github.com/TriliumNext/Notes"
|
||||
},
|
||||
"dependencies": {
|
||||
"@eslint/js": "9.33.0",
|
||||
"@eslint/js": "9.34.0",
|
||||
"@excalidraw/excalidraw": "0.18.0",
|
||||
"@fullcalendar/core": "6.1.19",
|
||||
"@fullcalendar/daygrid": "6.1.19",
|
||||
@@ -19,7 +19,7 @@
|
||||
"@fullcalendar/multimonth": "6.1.19",
|
||||
"@fullcalendar/timegrid": "6.1.19",
|
||||
"@maplibre/maplibre-gl-leaflet": "0.1.3",
|
||||
"@mermaid-js/layout-elk": "0.1.8",
|
||||
"@mermaid-js/layout-elk": "0.1.9",
|
||||
"@mind-elixir/node-menu": "5.0.0",
|
||||
"@popperjs/core": "2.11.8",
|
||||
"@triliumnext/ckeditor5": "workspace:*",
|
||||
@@ -28,7 +28,7 @@
|
||||
"@triliumnext/highlightjs": "workspace:*",
|
||||
"@triliumnext/share-theme": "workspace:*",
|
||||
"autocomplete.js": "0.38.1",
|
||||
"bootstrap": "5.3.7",
|
||||
"bootstrap": "5.3.8",
|
||||
"boxicons": "2.1.4",
|
||||
"dayjs": "1.11.13",
|
||||
"dayjs-plugin-utc": "0.1.2",
|
||||
@@ -36,7 +36,7 @@
|
||||
"draggabilly": "3.0.0",
|
||||
"force-graph": "1.50.1",
|
||||
"globals": "16.3.0",
|
||||
"i18next": "25.3.4",
|
||||
"i18next": "25.4.2",
|
||||
"i18next-http-backend": "3.0.2",
|
||||
"jquery": "3.7.1",
|
||||
"jquery.fancytree": "2.38.5",
|
||||
@@ -46,12 +46,13 @@
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet-gpx": "2.2.0",
|
||||
"mark.js": "8.11.1",
|
||||
"marked": "16.1.2",
|
||||
"mermaid": "11.9.0",
|
||||
"mind-elixir": "5.0.5",
|
||||
"marked": "16.2.0",
|
||||
"mermaid": "11.10.1",
|
||||
"mind-elixir": "5.0.6",
|
||||
"normalize.css": "8.0.1",
|
||||
"panzoom": "9.4.3",
|
||||
"preact": "10.27.0",
|
||||
"preact": "10.27.1",
|
||||
"react-i18next": "15.7.2",
|
||||
"split.js": "1.6.5",
|
||||
"svg-pan-zoom": "3.6.2",
|
||||
"tabulator-tables": "6.3.1",
|
||||
@@ -61,7 +62,7 @@
|
||||
"@ckeditor/ckeditor5-inspector": "5.0.0",
|
||||
"@preact/preset-vite": "2.10.2",
|
||||
"@types/bootstrap": "5.2.10",
|
||||
"@types/jquery": "3.5.32",
|
||||
"@types/jquery": "3.5.33",
|
||||
"@types/leaflet": "1.9.20",
|
||||
"@types/leaflet-gpx": "1.3.7",
|
||||
"@types/mark.js": "8.11.12",
|
||||
@@ -69,7 +70,7 @@
|
||||
"copy-webpack-plugin": "13.0.1",
|
||||
"happy-dom": "18.0.1",
|
||||
"script-loader": "0.7.2",
|
||||
"vite-plugin-static-copy": "3.1.1"
|
||||
"vite-plugin-static-copy": "3.1.2"
|
||||
},
|
||||
"nx": {
|
||||
"name": "client",
|
||||
|
||||
@@ -31,16 +31,13 @@ import { StartupChecks } from "./startup_checks.js";
|
||||
import type { CreateNoteOpts } from "../services/note_create.js";
|
||||
import { ColumnComponent } from "tabulator-tables";
|
||||
import { ChooseNoteTypeCallback } from "../widgets/dialogs/note_type_chooser.jsx";
|
||||
import type RootContainer from "../widgets/containers/root_container.js";
|
||||
|
||||
interface Layout {
|
||||
getRootWidget: (appContext: AppContext) => RootWidget;
|
||||
getRootWidget: (appContext: AppContext) => RootContainer;
|
||||
}
|
||||
|
||||
interface RootWidget extends Component {
|
||||
render: () => JQuery<HTMLElement>;
|
||||
}
|
||||
|
||||
interface BeforeUploadListener extends Component {
|
||||
export interface BeforeUploadListener extends Component {
|
||||
beforeUnloadEvent(): boolean;
|
||||
}
|
||||
|
||||
@@ -85,7 +82,6 @@ export type CommandMappings = {
|
||||
focusTree: CommandData;
|
||||
focusOnTitle: CommandData;
|
||||
focusOnDetail: CommandData;
|
||||
focusOnSearchDefinition: Required<CommandData>;
|
||||
searchNotes: CommandData & {
|
||||
searchString?: string;
|
||||
ancestorNoteId?: string | null;
|
||||
@@ -323,6 +319,7 @@ export type CommandMappings = {
|
||||
printActiveNote: CommandData;
|
||||
exportAsPdf: CommandData;
|
||||
openNoteExternally: CommandData;
|
||||
openNoteCustom: CommandData;
|
||||
renderActiveNote: CommandData;
|
||||
unhoist: CommandData;
|
||||
reloadFrontendApp: CommandData;
|
||||
@@ -526,7 +523,7 @@ export type FilteredCommandNames<T extends CommandData> = keyof Pick<CommandMapp
|
||||
export class AppContext extends Component {
|
||||
isMainWindow: boolean;
|
||||
components: Component[];
|
||||
beforeUnloadListeners: WeakRef<BeforeUploadListener>[];
|
||||
beforeUnloadListeners: (WeakRef<BeforeUploadListener> | (() => boolean))[];
|
||||
tabManager!: TabManager;
|
||||
layout?: Layout;
|
||||
noteTreeWidget?: NoteTreeWidget;
|
||||
@@ -619,7 +616,7 @@ export class AppContext extends Component {
|
||||
component.triggerCommand(commandName, { $el: $(this) });
|
||||
});
|
||||
|
||||
this.child(rootWidget);
|
||||
this.child(rootWidget as Component);
|
||||
|
||||
this.triggerEvent("initialRenderComplete", {});
|
||||
}
|
||||
@@ -649,13 +646,17 @@ export class AppContext extends Component {
|
||||
return $(el).closest(".component").prop("component");
|
||||
}
|
||||
|
||||
addBeforeUnloadListener(obj: BeforeUploadListener) {
|
||||
addBeforeUnloadListener(obj: BeforeUploadListener | (() => boolean)) {
|
||||
if (typeof WeakRef !== "function") {
|
||||
// older browsers don't support WeakRef
|
||||
return;
|
||||
}
|
||||
|
||||
this.beforeUnloadListeners.push(new WeakRef<BeforeUploadListener>(obj));
|
||||
if (typeof obj === "object") {
|
||||
this.beforeUnloadListeners.push(new WeakRef<BeforeUploadListener>(obj));
|
||||
} else {
|
||||
this.beforeUnloadListeners.push(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -665,25 +666,29 @@ const appContext = new AppContext(window.glob.isMainWindow);
|
||||
$(window).on("beforeunload", () => {
|
||||
let allSaved = true;
|
||||
|
||||
appContext.beforeUnloadListeners = appContext.beforeUnloadListeners.filter((wr) => !!wr.deref());
|
||||
appContext.beforeUnloadListeners = appContext.beforeUnloadListeners.filter((wr) => typeof wr === "function" || !!wr.deref());
|
||||
|
||||
for (const weakRef of appContext.beforeUnloadListeners) {
|
||||
const component = weakRef.deref();
|
||||
for (const listener of appContext.beforeUnloadListeners) {
|
||||
if (typeof listener === "object") {
|
||||
const component = listener.deref();
|
||||
|
||||
if (!component) {
|
||||
continue;
|
||||
}
|
||||
if (!component) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!component.beforeUnloadEvent()) {
|
||||
console.log(`Component ${component.componentId} is not finished saving its state.`);
|
||||
|
||||
toast.showMessage(t("app_context.please_wait_for_save"), 10000);
|
||||
|
||||
allSaved = false;
|
||||
if (!component.beforeUnloadEvent()) {
|
||||
console.log(`Component ${component.componentId} is not finished saving its state.`);
|
||||
allSaved = false;
|
||||
}
|
||||
} else {
|
||||
if (!listener()) {
|
||||
allSaved = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!allSaved) {
|
||||
toast.showMessage(t("app_context.please_wait_for_save"), 10000);
|
||||
return "some string";
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import utils from "../services/utils.js";
|
||||
import type { CommandMappings, CommandNames, EventData, EventNames } from "./app_context.js";
|
||||
|
||||
type EventHandler = ((data: any) => void);
|
||||
|
||||
/**
|
||||
* Abstract class for all components in the Trilium's frontend.
|
||||
*
|
||||
@@ -19,6 +21,7 @@ export class TypedComponent<ChildT extends TypedComponent<ChildT>> {
|
||||
initialized: Promise<void> | null;
|
||||
parent?: TypedComponent<any>;
|
||||
_position!: number;
|
||||
private listeners: Record<string, EventHandler[]> | null = {};
|
||||
|
||||
constructor() {
|
||||
this.componentId = `${this.sanitizedClassName}-${utils.randomString(8)}`;
|
||||
@@ -76,6 +79,14 @@ export class TypedComponent<ChildT extends TypedComponent<ChildT>> {
|
||||
handleEventInChildren<T extends EventNames>(name: T, data: EventData<T>): Promise<unknown[] | unknown> | null {
|
||||
const promises: Promise<unknown>[] = [];
|
||||
|
||||
// Handle React children.
|
||||
if (this.listeners?.[name]) {
|
||||
for (const listener of this.listeners[name]) {
|
||||
listener(data);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle legacy children.
|
||||
for (const child of this.children) {
|
||||
const ret = child.handleEvent(name, data) as Promise<void>;
|
||||
|
||||
@@ -120,6 +131,35 @@ export class TypedComponent<ChildT extends TypedComponent<ChildT>> {
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
registerHandler<T extends EventNames>(name: T, handler: EventHandler) {
|
||||
if (!this.listeners) {
|
||||
this.listeners = {};
|
||||
}
|
||||
|
||||
if (!this.listeners[name]) {
|
||||
this.listeners[name] = [];
|
||||
}
|
||||
|
||||
if (this.listeners[name].includes(handler)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.listeners[name].push(handler);
|
||||
}
|
||||
|
||||
removeHandler<T extends EventNames>(name: T, handler: EventHandler) {
|
||||
if (!this.listeners?.[name]?.includes(handler)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.listeners[name] = this.listeners[name]
|
||||
.filter(listener => listener !== handler);
|
||||
|
||||
if (!this.listeners[name].length) {
|
||||
delete this.listeners[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default class Component extends TypedComponent<Component> {}
|
||||
|
||||
@@ -43,8 +43,6 @@ export default class RootCommandExecutor extends Component {
|
||||
const noteContext = await appContext.tabManager.openTabWithNoteWithHoisting(searchNote.noteId, {
|
||||
activate: true
|
||||
});
|
||||
|
||||
appContext.triggerCommand("focusOnSearchDefinition", { ntxId: noteContext.ntxId });
|
||||
}
|
||||
|
||||
async searchInSubtreeCommand({ notePath }: CommandListenerData<"searchInSubtree">) {
|
||||
|
||||
@@ -8,7 +8,6 @@ import electronContextMenu from "./menus/electron_context_menu.js";
|
||||
import glob from "./services/glob.js";
|
||||
import { t } from "./services/i18n.js";
|
||||
import options from "./services/options.js";
|
||||
import server from "./services/server.js";
|
||||
import type ElectronRemote from "@electron/remote";
|
||||
import type Electron from "electron";
|
||||
import "./stylesheets/bootstrap.scss";
|
||||
|
||||
@@ -1020,6 +1020,14 @@ class FNote {
|
||||
return this.noteId.startsWith("_options");
|
||||
}
|
||||
|
||||
isTriliumSqlite() {
|
||||
return this.mime === "text/x-sqlite;schema=trilium";
|
||||
}
|
||||
|
||||
isTriliumScript() {
|
||||
return this.mime.startsWith("application/javascript");
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides note's date metadata.
|
||||
*/
|
||||
|
||||
@@ -4,21 +4,13 @@ import TabRowWidget from "../widgets/tab_row.js";
|
||||
import TitleBarButtonsWidget from "../widgets/title_bar_buttons.js";
|
||||
import LeftPaneContainer from "../widgets/containers/left_pane_container.js";
|
||||
import NoteTreeWidget from "../widgets/note_tree.js";
|
||||
import NoteTitleWidget from "../widgets/note_title.js";
|
||||
import OwnedAttributeListWidget from "../widgets/ribbon_widgets/owned_attribute_list.js";
|
||||
import NoteActionsWidget from "../widgets/buttons/note_actions.js";
|
||||
import NoteTitleWidget from "../widgets/note_title.jsx";
|
||||
import NoteDetailWidget from "../widgets/note_detail.js";
|
||||
import RibbonContainer from "../widgets/containers/ribbon_container.js";
|
||||
import PromotedAttributesWidget from "../widgets/ribbon_widgets/promoted_attributes.js";
|
||||
import InheritedAttributesWidget from "../widgets/ribbon_widgets/inherited_attribute_list.js";
|
||||
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
|
||||
import NoteListWidget from "../widgets/note_list.js";
|
||||
import SearchDefinitionWidget from "../widgets/ribbon_widgets/search_definition.js";
|
||||
import SqlResultWidget from "../widgets/sql_result.js";
|
||||
import SqlTableSchemasWidget from "../widgets/sql_table_schemas.js";
|
||||
import FilePropertiesWidget from "../widgets/ribbon_widgets/file_properties.js";
|
||||
import ImagePropertiesWidget from "../widgets/ribbon_widgets/image_properties.js";
|
||||
import NotePropertiesWidget from "../widgets/ribbon_widgets/note_properties.js";
|
||||
import NoteIconWidget from "../widgets/note_icon.js";
|
||||
import NoteIconWidget from "../widgets/note_icon.jsx";
|
||||
import SearchResultWidget from "../widgets/search_result.js";
|
||||
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
|
||||
import RootContainer from "../widgets/containers/root_container.js";
|
||||
@@ -29,15 +21,8 @@ import SplitNoteContainer from "../widgets/containers/split_note_container.js";
|
||||
import LeftPaneToggleWidget from "../widgets/buttons/left_pane_toggle.js";
|
||||
import CreatePaneButton from "../widgets/buttons/create_pane_button.js";
|
||||
import ClosePaneButton from "../widgets/buttons/close_pane_button.js";
|
||||
import BasicPropertiesWidget from "../widgets/ribbon_widgets/basic_properties.js";
|
||||
import NoteInfoWidget from "../widgets/ribbon_widgets/note_info_widget.js";
|
||||
import BookPropertiesWidget from "../widgets/ribbon_widgets/book_properties.js";
|
||||
import NoteMapRibbonWidget from "../widgets/ribbon_widgets/note_map.js";
|
||||
import NotePathsWidget from "../widgets/ribbon_widgets/note_paths.js";
|
||||
import SimilarNotesWidget from "../widgets/ribbon_widgets/similar_notes.js";
|
||||
import RightPaneContainer from "../widgets/containers/right_pane_container.js";
|
||||
import EditButton from "../widgets/floating_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 NoteWrapperWidget from "../widgets/note_wrapper.js";
|
||||
@@ -51,16 +36,13 @@ import FloatingButtons from "../widgets/floating_buttons/floating_buttons.js";
|
||||
import RelationMapButtons from "../widgets/floating_buttons/relation_map_buttons.js";
|
||||
import SvgExportButton from "../widgets/floating_buttons/svg_export_button.js";
|
||||
import LauncherContainer from "../widgets/containers/launcher_container.js";
|
||||
import RevisionsButton from "../widgets/buttons/revisions_button.js";
|
||||
import CodeButtonsWidget from "../widgets/floating_buttons/code_buttons.js";
|
||||
import ApiLogWidget from "../widgets/api_log.js";
|
||||
import HideFloatingButtonsButton from "../widgets/floating_buttons/hide_floating_buttons_button.js";
|
||||
import ScriptExecutorWidget from "../widgets/ribbon_widgets/script_executor.js";
|
||||
import MovePaneButton from "../widgets/buttons/move_pane_button.js";
|
||||
import UploadAttachmentsDialog from "../widgets/dialogs/upload_attachments.js";
|
||||
import CopyImageReferenceButton from "../widgets/floating_buttons/copy_image_reference_button.js";
|
||||
import ScrollPaddingWidget from "../widgets/scroll_padding.js";
|
||||
import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js";
|
||||
import options from "../services/options.js";
|
||||
import utils from "../services/utils.js";
|
||||
import GeoMapButtons from "../widgets/floating_buttons/geo_map_button.js";
|
||||
@@ -73,6 +55,7 @@ import ToggleReadOnlyButton from "../widgets/floating_buttons/toggle_read_only_b
|
||||
import PngExportButton from "../widgets/floating_buttons/png_export_button.js";
|
||||
import RefreshButton from "../widgets/floating_buttons/refresh_button.js";
|
||||
import { applyModals } from "./layout_commons.js";
|
||||
import Ribbon from "../widgets/ribbon/Ribbon.jsx";
|
||||
|
||||
export default class DesktopLayout {
|
||||
|
||||
@@ -151,37 +134,15 @@ export default class DesktopLayout {
|
||||
.css("min-height", "50px")
|
||||
.css("align-items", "center")
|
||||
.cssBlock(".title-row > * { margin: 5px; }")
|
||||
.child(new NoteIconWidget())
|
||||
.child(new NoteTitleWidget())
|
||||
.child(<NoteIconWidget />)
|
||||
.child(<NoteTitleWidget />)
|
||||
.child(new SpacerWidget(0, 1))
|
||||
.child(new MovePaneButton(true))
|
||||
.child(new MovePaneButton(false))
|
||||
.child(new ClosePaneButton())
|
||||
.child(new CreatePaneButton())
|
||||
)
|
||||
.child(
|
||||
new RibbonContainer()
|
||||
// the order of the widgets matter. Some of these want to "activate" themselves
|
||||
// when visible. When this happens to multiple of them, the first one "wins".
|
||||
// promoted attributes should always win.
|
||||
.ribbon(new ClassicEditorToolbar())
|
||||
.ribbon(new ScriptExecutorWidget())
|
||||
.ribbon(new SearchDefinitionWidget())
|
||||
.ribbon(new EditedNotesWidget())
|
||||
.ribbon(new BookPropertiesWidget())
|
||||
.ribbon(new NotePropertiesWidget())
|
||||
.ribbon(new FilePropertiesWidget())
|
||||
.ribbon(new ImagePropertiesWidget())
|
||||
.ribbon(new BasicPropertiesWidget())
|
||||
.ribbon(new OwnedAttributeListWidget())
|
||||
.ribbon(new InheritedAttributesWidget())
|
||||
.ribbon(new NotePathsWidget())
|
||||
.ribbon(new NoteMapRibbonWidget())
|
||||
.ribbon(new SimilarNotesWidget())
|
||||
.ribbon(new NoteInfoWidget())
|
||||
.button(new RevisionsButton())
|
||||
.button(new NoteActionsWidget())
|
||||
)
|
||||
.child(<Ribbon />)
|
||||
.child(new SharedInfoWidget())
|
||||
.child(new WatchedFileUpdateStatusWidget())
|
||||
.child(
|
||||
@@ -235,8 +196,8 @@ export default class DesktopLayout {
|
||||
.child(new CloseZenButton())
|
||||
|
||||
// Desktop-specific dialogs.
|
||||
.child(new PasswordNoteSetDialog())
|
||||
.child(new UploadAttachmentsDialog());
|
||||
.child(<PasswordNoteSetDialog />)
|
||||
.child(<UploadAttachmentsDialog />);
|
||||
|
||||
applyModals(rootContainer);
|
||||
return rootContainer;
|
||||
@@ -24,48 +24,48 @@ import InfoDialog from "../widgets/dialogs/info.js";
|
||||
import IncorrectCpuArchDialog from "../widgets/dialogs/incorrect_cpu_arch.js";
|
||||
import PopupEditorDialog from "../widgets/dialogs/popup_editor.js";
|
||||
import FlexContainer from "../widgets/containers/flex_container.js";
|
||||
import NoteIconWidget from "../widgets/note_icon.js";
|
||||
import NoteTitleWidget from "../widgets/note_title.js";
|
||||
import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js";
|
||||
import PromotedAttributesWidget from "../widgets/ribbon_widgets/promoted_attributes.js";
|
||||
import NoteIconWidget from "../widgets/note_icon";
|
||||
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
|
||||
import NoteDetailWidget from "../widgets/note_detail.js";
|
||||
import NoteListWidget from "../widgets/note_list.js";
|
||||
import { CallToActionDialog } from "../widgets/dialogs/call_to_action.jsx";
|
||||
import CallToActionDialog from "../widgets/dialogs/call_to_action.jsx";
|
||||
import NoteTitleWidget from "../widgets/note_title.jsx";
|
||||
import { PopupEditorFormattingToolbar } from "../widgets/ribbon/FormattingToolbar.js";
|
||||
|
||||
export function applyModals(rootContainer: RootContainer) {
|
||||
rootContainer
|
||||
.child(new BulkActionsDialog())
|
||||
.child(new AboutDialog())
|
||||
.child(new HelpDialog())
|
||||
.child(new RecentChangesDialog())
|
||||
.child(new BranchPrefixDialog())
|
||||
.child(new SortChildNotesDialog())
|
||||
.child(new IncludeNoteDialog())
|
||||
.child(new NoteTypeChooserDialog())
|
||||
.child(new JumpToNoteDialog())
|
||||
.child(new AddLinkDialog())
|
||||
.child(new CloneToDialog())
|
||||
.child(new MoveToDialog())
|
||||
.child(new ImportDialog())
|
||||
.child(new ExportDialog())
|
||||
.child(new MarkdownImportDialog())
|
||||
.child(new ProtectedSessionPasswordDialog())
|
||||
.child(new RevisionsDialog())
|
||||
.child(new DeleteNotesDialog())
|
||||
.child(new InfoDialog())
|
||||
.child(new ConfirmDialog())
|
||||
.child(new PromptDialog())
|
||||
.child(new IncorrectCpuArchDialog())
|
||||
.child(<BulkActionsDialog />)
|
||||
.child(<AboutDialog />)
|
||||
.child(<HelpDialog />)
|
||||
.child(<RecentChangesDialog />)
|
||||
.child(<BranchPrefixDialog />)
|
||||
.child(<SortChildNotesDialog />)
|
||||
.child(<IncludeNoteDialog />)
|
||||
.child(<NoteTypeChooserDialog />)
|
||||
.child(<JumpToNoteDialog />)
|
||||
.child(<AddLinkDialog />)
|
||||
.child(<CloneToDialog />)
|
||||
.child(<MoveToDialog />)
|
||||
.child(<ImportDialog />)
|
||||
.child(<ExportDialog />)
|
||||
.child(<MarkdownImportDialog />)
|
||||
.child(<ProtectedSessionPasswordDialog />)
|
||||
.child(<RevisionsDialog />)
|
||||
.child(<DeleteNotesDialog />)
|
||||
.child(<InfoDialog />)
|
||||
.child(<ConfirmDialog />)
|
||||
.child(<PromptDialog />)
|
||||
.child(<IncorrectCpuArchDialog />)
|
||||
.child(new PopupEditorDialog()
|
||||
.child(new FlexContainer("row")
|
||||
.class("title-row")
|
||||
.css("align-items", "center")
|
||||
.cssBlock(".title-row > * { margin: 5px; }")
|
||||
.child(new NoteIconWidget())
|
||||
.child(new NoteTitleWidget()))
|
||||
.child(new ClassicEditorToolbar())
|
||||
.child(<NoteIconWidget />)
|
||||
.child(<NoteTitleWidget />))
|
||||
.child(<PopupEditorFormattingToolbar />)
|
||||
.child(new PromotedAttributesWidget())
|
||||
.child(new NoteDetailWidget())
|
||||
.child(new NoteListWidget(true)))
|
||||
.child(new CallToActionDialog());
|
||||
.child(<CallToActionDialog />);
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import ToggleSidebarButtonWidget from "../widgets/mobile_widgets/toggle_sidebar_
|
||||
import MobileDetailMenuWidget from "../widgets/mobile_widgets/mobile_detail_menu.js";
|
||||
import ScreenContainer from "../widgets/mobile_widgets/screen_container.js";
|
||||
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
|
||||
import FilePropertiesWidget from "../widgets/ribbon_widgets/file_properties.js";
|
||||
import FloatingButtons from "../widgets/floating_buttons/floating_buttons.js";
|
||||
import EditButton from "../widgets/floating_buttons/edit_button.js";
|
||||
import RelationMapButtons from "../widgets/floating_buttons/relation_map_buttons.js";
|
||||
@@ -19,14 +18,18 @@ import GlobalMenuWidget from "../widgets/buttons/global_menu.js";
|
||||
import LauncherContainer from "../widgets/containers/launcher_container.js";
|
||||
import RootContainer from "../widgets/containers/root_container.js";
|
||||
import SharedInfoWidget from "../widgets/shared_info.js";
|
||||
import PromotedAttributesWidget from "../widgets/ribbon_widgets/promoted_attributes.js";
|
||||
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
|
||||
import SidebarContainer from "../widgets/mobile_widgets/sidebar_container.js";
|
||||
import type AppContext from "../components/app_context.js";
|
||||
import TabRowWidget from "../widgets/tab_row.js";
|
||||
import RefreshButton from "../widgets/floating_buttons/refresh_button.js";
|
||||
import MobileEditorToolbar from "../widgets/ribbon_widgets/mobile_editor_toolbar.js";
|
||||
import MobileEditorToolbar from "../widgets/type_widgets/ckeditor/mobile_editor_toolbar.js";
|
||||
import { applyModals } from "./layout_commons.js";
|
||||
import CloseZenButton from "../widgets/close_zen_button.js";
|
||||
import FilePropertiesTab from "../widgets/ribbon/FilePropertiesTab.jsx";
|
||||
import { useNoteContext } from "../widgets/react/hooks.jsx";
|
||||
import { useContext } from "preact/hooks";
|
||||
import { ParentComponent } from "../widgets/react/react_utils.jsx";
|
||||
|
||||
const MOBILE_CSS = `
|
||||
<style>
|
||||
@@ -144,7 +147,7 @@ export default class MobileLayout {
|
||||
.css("font-size", "larger")
|
||||
.css("align-items", "center")
|
||||
.child(new ToggleSidebarButtonWidget().contentSized())
|
||||
.child(new NoteTitleWidget().contentSized().css("position", "relative").css("padding-left", "0.5em"))
|
||||
.child(<NoteTitleWidget />)
|
||||
.child(new MobileDetailMenuWidget(true).contentSized())
|
||||
)
|
||||
.child(new SharedInfoWidget())
|
||||
@@ -164,7 +167,7 @@ export default class MobileLayout {
|
||||
.contentSized()
|
||||
.child(new NoteDetailWidget())
|
||||
.child(new NoteListWidget(false))
|
||||
.child(new FilePropertiesWidget().css("font-size", "smaller"))
|
||||
.child(<FilePropertiesWrapper />)
|
||||
)
|
||||
.child(new MobileEditorToolbar())
|
||||
)
|
||||
@@ -181,3 +184,13 @@ export default class MobileLayout {
|
||||
return rootContainer;
|
||||
}
|
||||
}
|
||||
|
||||
function FilePropertiesWrapper() {
|
||||
const { note } = useNoteContext();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{note?.type === "file" && <FilePropertiesTab note={note} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import server from "./server.js";
|
||||
import froca from "./froca.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import type { AttributeRow } from "./load_results.js";
|
||||
import { AttributeType } from "@triliumnext/commons";
|
||||
|
||||
async function addLabel(noteId: string, name: string, value: string = "", isInheritable = false) {
|
||||
await server.put(`notes/${noteId}/attribute`, {
|
||||
@@ -25,6 +26,14 @@ async function removeAttributeById(noteId: string, attributeId: string) {
|
||||
await server.remove(`notes/${noteId}/attributes/${attributeId}`);
|
||||
}
|
||||
|
||||
export async function removeOwnedAttributesByNameOrType(note: FNote, type: AttributeType, name: string) {
|
||||
for (const attr of note.getOwnedAttributes()) {
|
||||
if (attr.type === type && attr.name === name) {
|
||||
await server.remove(`notes/${note.noteId}/attributes/${attr.attributeId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a label identified by its name from the given note, if it exists. Note that the label must be owned, i.e.
|
||||
* it will not remove inherited attributes.
|
||||
@@ -52,7 +61,7 @@ function removeOwnedLabelByName(note: FNote, labelName: string) {
|
||||
* @param value the value of the attribute to set.
|
||||
*/
|
||||
export async function setAttribute(note: FNote, type: "label" | "relation", name: string, value: string | null | undefined) {
|
||||
if (value) {
|
||||
if (value !== null && value !== undefined) {
|
||||
// Create or update the attribute.
|
||||
await server.put(`notes/${note.noteId}/set-attribute`, { type, name, value });
|
||||
} else {
|
||||
|
||||
@@ -18,7 +18,7 @@ import type FNote from "../entities/fnote.js";
|
||||
import toast from "./toast.js";
|
||||
import { BulkAction } from "@triliumnext/commons";
|
||||
|
||||
const ACTION_GROUPS = [
|
||||
export const ACTION_GROUPS = [
|
||||
{
|
||||
title: t("bulk_actions.labels"),
|
||||
actions: [AddLabelBulkAction, UpdateLabelValueBulkAction, RenameLabelBulkAction, DeleteLabelBulkAction]
|
||||
|
||||
@@ -35,8 +35,10 @@ async function processEntityChanges(entityChanges: EntityChange[]) {
|
||||
loadResults.addOption(attributeEntity.name);
|
||||
} else if (ec.entityName === "attachments") {
|
||||
processAttachment(loadResults, ec);
|
||||
} else if (ec.entityName === "blobs" || ec.entityName === "etapi_tokens") {
|
||||
} else if (ec.entityName === "blobs") {
|
||||
// NOOP - these entities are handled at the backend level and don't require frontend processing
|
||||
} else if (ec.entityName === "etapi_tokens") {
|
||||
loadResults.hasEtapiTokenChanges = true;
|
||||
} else {
|
||||
throw new Error(`Unknown entityName '${ec.entityName}'`);
|
||||
}
|
||||
@@ -77,9 +79,7 @@ async function processEntityChanges(entityChanges: EntityChange[]) {
|
||||
noteAttributeCache.invalidate();
|
||||
}
|
||||
|
||||
// TODO: Remove after porting the file
|
||||
// @ts-ignore
|
||||
const appContext = (await import("../components/app_context.js")).default as any;
|
||||
const appContext = (await import("../components/app_context.js")).default;
|
||||
await appContext.triggerEvent("entitiesReloaded", { loadResults });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import i18next from "i18next";
|
||||
import i18nextHttpBackend from "i18next-http-backend";
|
||||
import server from "./server.js";
|
||||
import type { Locale } from "@triliumnext/commons";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
|
||||
let locales: Locale[] | null;
|
||||
|
||||
@@ -16,6 +17,7 @@ export async function initLocale() {
|
||||
|
||||
locales = await server.get<Locale[]>("options/locales");
|
||||
|
||||
i18next.use(initReactI18next);
|
||||
await i18next.use(i18nextHttpBackend).init({
|
||||
lng: locale,
|
||||
fallbackLng: "en",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AttachmentRow } from "@triliumnext/commons";
|
||||
import type { AttachmentRow, EtapiTokenRow } from "@triliumnext/commons";
|
||||
import type { AttributeType } from "../entities/fattribute.js";
|
||||
import type { EntityChange } from "../server_types.js";
|
||||
|
||||
@@ -53,6 +53,7 @@ type EntityRowMappings = {
|
||||
options: OptionRow;
|
||||
revisions: RevisionRow;
|
||||
note_reordering: NoteReorderingRow;
|
||||
etapi_tokens: EtapiTokenRow;
|
||||
};
|
||||
|
||||
export type EntityRowNames = keyof EntityRowMappings;
|
||||
@@ -68,6 +69,7 @@ export default class LoadResults {
|
||||
private contentNoteIdToComponentId: ContentNoteIdToComponentIdRow[];
|
||||
private optionNames: string[];
|
||||
private attachmentRows: AttachmentRow[];
|
||||
public hasEtapiTokenChanges: boolean = false;
|
||||
|
||||
constructor(entityChanges: EntityChange[]) {
|
||||
const entities: Record<string, Record<string, any>> = {};
|
||||
@@ -215,7 +217,8 @@ export default class LoadResults {
|
||||
this.revisionRows.length === 0 &&
|
||||
this.contentNoteIdToComponentId.length === 0 &&
|
||||
this.optionNames.length === 0 &&
|
||||
this.attachmentRows.length === 0
|
||||
this.attachmentRows.length === 0 &&
|
||||
!this.hasEtapiTokenChanges
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ export interface Suggestion {
|
||||
commandId?: string;
|
||||
commandDescription?: string;
|
||||
commandShortcut?: string;
|
||||
attributeSnippet?: string;
|
||||
highlightedAttributeSnippet?: string;
|
||||
}
|
||||
|
||||
export interface Options {
|
||||
@@ -323,7 +325,33 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
return `<span class="${suggestion.icon ?? "bx bx-note"}"></span> ${suggestion.highlightedNotePathTitle}`;
|
||||
// Add special class for search-notes action
|
||||
const actionClass = suggestion.action === "search-notes" ? "search-notes-action" : "";
|
||||
|
||||
// Choose appropriate icon based on action
|
||||
let iconClass = suggestion.icon ?? "bx bx-note";
|
||||
if (suggestion.action === "search-notes") {
|
||||
iconClass = "bx bx-search";
|
||||
} else if (suggestion.action === "create-note") {
|
||||
iconClass = "bx bx-plus";
|
||||
} else if (suggestion.action === "external-link") {
|
||||
iconClass = "bx bx-link-external";
|
||||
}
|
||||
|
||||
// Simplified HTML structure without nested divs
|
||||
let html = `<div class="note-suggestion ${actionClass}">`;
|
||||
html += `<span class="icon ${iconClass}"></span>`;
|
||||
html += `<span class="text">`;
|
||||
html += `<span class="search-result-title">${suggestion.highlightedNotePathTitle}</span>`;
|
||||
|
||||
// Add attribute snippet inline if available
|
||||
if (suggestion.highlightedAttributeSnippet) {
|
||||
html += `<span class="search-result-attributes">${suggestion.highlightedAttributeSnippet}</span>`;
|
||||
}
|
||||
|
||||
html += `</span>`;
|
||||
html += `</div>`;
|
||||
return html;
|
||||
}
|
||||
},
|
||||
// we can't cache identical searches because notes can be created / renamed, new recent notes can be added
|
||||
|
||||
@@ -35,7 +35,7 @@ function download(url: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function downloadFileNote(noteId: string) {
|
||||
export function downloadFileNote(noteId: string) {
|
||||
const url = `${getFileUrl("notes", noteId)}?${Date.now()}`; // don't use cache
|
||||
|
||||
download(url);
|
||||
@@ -163,7 +163,7 @@ async function openExternally(type: string, entityId: string, mime: string) {
|
||||
}
|
||||
}
|
||||
|
||||
const openNoteExternally = async (noteId: string, mime: string) => await openExternally("notes", noteId, mime);
|
||||
export const openNoteExternally = async (noteId: string, mime: string) => await openExternally("notes", noteId, mime);
|
||||
const openAttachmentExternally = async (attachmentId: string, mime: string) => await openExternally("attachments", attachmentId, mime);
|
||||
|
||||
function getHost() {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { OptionNames } from "@triliumnext/commons";
|
||||
import server from "./server.js";
|
||||
import { isShare } from "./utils.js";
|
||||
|
||||
type OptionValue = number | string;
|
||||
export type OptionValue = number | string;
|
||||
|
||||
class Options {
|
||||
initializedPromise: Promise<void>;
|
||||
@@ -76,6 +77,14 @@ class Options {
|
||||
await server.put(`options`, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves multiple options at once, by supplying a record where the keys are the option names and the values represent the stringified value to set.
|
||||
* @param newValues the record of keys and values.
|
||||
*/
|
||||
async saveMany<T extends OptionNames>(newValues: Record<T, OptionValue>) {
|
||||
await server.put<void>("options", newValues);
|
||||
}
|
||||
|
||||
async toggle(key: string) {
|
||||
await this.save(key, (!this.is(key)).toString());
|
||||
}
|
||||
|
||||
@@ -14,6 +14,32 @@ interface ShortcutBinding {
|
||||
// Store all active shortcut bindings for management
|
||||
const activeBindings: Map<string, ShortcutBinding[]> = new Map();
|
||||
|
||||
// Handle special key mappings and aliases
|
||||
const keyMap: { [key: string]: string[] } = {
|
||||
'return': ['Enter'],
|
||||
'enter': ['Enter'], // alias for return
|
||||
'del': ['Delete'],
|
||||
'delete': ['Delete'], // alias for del
|
||||
'esc': ['Escape'],
|
||||
'escape': ['Escape'], // alias for esc
|
||||
'space': [' ', 'Space'],
|
||||
'tab': ['Tab'],
|
||||
'backspace': ['Backspace'],
|
||||
'home': ['Home'],
|
||||
'end': ['End'],
|
||||
'pageup': ['PageUp'],
|
||||
'pagedown': ['PageDown'],
|
||||
'up': ['ArrowUp'],
|
||||
'down': ['ArrowDown'],
|
||||
'left': ['ArrowLeft'],
|
||||
'right': ['ArrowRight']
|
||||
};
|
||||
|
||||
// Function keys
|
||||
for (let i = 1; i <= 19; i++) {
|
||||
keyMap[`f${i}`] = [`F${i}`];
|
||||
}
|
||||
|
||||
function removeGlobalShortcut(namespace: string) {
|
||||
bindGlobalShortcut("", null, namespace);
|
||||
}
|
||||
@@ -124,32 +150,6 @@ export function keyMatches(e: KeyboardEvent, key: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle special key mappings and aliases
|
||||
const keyMap: { [key: string]: string[] } = {
|
||||
'return': ['Enter'],
|
||||
'enter': ['Enter'], // alias for return
|
||||
'del': ['Delete'],
|
||||
'delete': ['Delete'], // alias for del
|
||||
'esc': ['Escape'],
|
||||
'escape': ['Escape'], // alias for esc
|
||||
'space': [' ', 'Space'],
|
||||
'tab': ['Tab'],
|
||||
'backspace': ['Backspace'],
|
||||
'home': ['Home'],
|
||||
'end': ['End'],
|
||||
'pageup': ['PageUp'],
|
||||
'pagedown': ['PageDown'],
|
||||
'up': ['ArrowUp'],
|
||||
'down': ['ArrowDown'],
|
||||
'left': ['ArrowLeft'],
|
||||
'right': ['ArrowRight']
|
||||
};
|
||||
|
||||
// Function keys
|
||||
for (let i = 1; i <= 19; i++) {
|
||||
keyMap[`f${i}`] = [`F${i}`];
|
||||
}
|
||||
|
||||
const mappedKeys = keyMap[key.toLowerCase()];
|
||||
if (mappedKeys) {
|
||||
return mappedKeys.includes(e.key) || mappedKeys.includes(e.code);
|
||||
@@ -163,7 +163,7 @@ export function keyMatches(e: KeyboardEvent, key: string): boolean {
|
||||
|
||||
// For letter keys, use the physical key code for consistency
|
||||
if (key.length === 1 && key >= 'a' && key <= 'z') {
|
||||
return e.code === `Key${key.toUpperCase()}`;
|
||||
return e.key.toLowerCase() === key.toLowerCase();
|
||||
}
|
||||
|
||||
// For regular keys, check both key and code as fallback
|
||||
|
||||
@@ -5,7 +5,7 @@ const SVG_MIME = "image/svg+xml";
|
||||
|
||||
export const isShare = !window.glob;
|
||||
|
||||
function reloadFrontendApp(reason?: string) {
|
||||
export function reloadFrontendApp(reason?: string) {
|
||||
if (reason) {
|
||||
logInfo(`Frontend app reload: ${reason}`);
|
||||
}
|
||||
@@ -13,7 +13,7 @@ function reloadFrontendApp(reason?: string) {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
function restartDesktopApp() {
|
||||
export function restartDesktopApp() {
|
||||
if (!isElectron()) {
|
||||
reloadFrontendApp();
|
||||
return;
|
||||
@@ -125,7 +125,7 @@ function formatDateISO(date: Date) {
|
||||
return `${date.getFullYear()}-${padNum(date.getMonth() + 1)}-${padNum(date.getDate())}`;
|
||||
}
|
||||
|
||||
function formatDateTime(date: Date, userSuppliedFormat?: string): string {
|
||||
export function formatDateTime(date: Date, userSuppliedFormat?: string): string {
|
||||
if (userSuppliedFormat?.trim()) {
|
||||
return dayjs(date).format(userSuppliedFormat);
|
||||
} else {
|
||||
@@ -144,11 +144,11 @@ function now() {
|
||||
/**
|
||||
* Returns `true` if the client is currently running under Electron, or `false` if running in a web browser.
|
||||
*/
|
||||
function isElectron() {
|
||||
export function isElectron() {
|
||||
return !!(window && window.process && window.process.type);
|
||||
}
|
||||
|
||||
function isMac() {
|
||||
export function isMac() {
|
||||
return navigator.platform.indexOf("Mac") > -1;
|
||||
}
|
||||
|
||||
@@ -185,7 +185,11 @@ export function escapeQuotes(value: string) {
|
||||
return value.replaceAll('"', """);
|
||||
}
|
||||
|
||||
function formatSize(size: number) {
|
||||
export function formatSize(size: number | null | undefined) {
|
||||
if (size === null || size === undefined) {
|
||||
return "";
|
||||
}
|
||||
|
||||
size = Math.max(Math.round(size / 1024), 1);
|
||||
|
||||
if (size < 1024) {
|
||||
@@ -218,7 +222,7 @@ function randomString(len: number) {
|
||||
return text;
|
||||
}
|
||||
|
||||
function isMobile() {
|
||||
export function isMobile() {
|
||||
return (
|
||||
window.glob?.device === "mobile" ||
|
||||
// window.glob.device is not available in setup
|
||||
@@ -292,7 +296,7 @@ function isHtmlEmpty(html: string) {
|
||||
);
|
||||
}
|
||||
|
||||
async function clearBrowserCache() {
|
||||
export async function clearBrowserCache() {
|
||||
if (isElectron()) {
|
||||
const win = dynamicRequire("@electron/remote").getCurrentWindow();
|
||||
await win.webContents.session.clearCache();
|
||||
@@ -306,7 +310,7 @@ function copySelectionToClipboard() {
|
||||
}
|
||||
}
|
||||
|
||||
function dynamicRequire(moduleName: string) {
|
||||
export function dynamicRequire(moduleName: string) {
|
||||
if (typeof __non_webpack_require__ !== "undefined") {
|
||||
return __non_webpack_require__(moduleName);
|
||||
} else {
|
||||
@@ -374,33 +378,42 @@ async function openInAppHelp($button: JQuery<HTMLElement>) {
|
||||
|
||||
const inAppHelpPage = $button.attr("data-in-app-help");
|
||||
if (inAppHelpPage) {
|
||||
// Dynamic import to avoid import issues in tests.
|
||||
const appContext = (await import("../components/app_context.js")).default;
|
||||
const activeContext = appContext.tabManager.getActiveContext();
|
||||
if (!activeContext) {
|
||||
return;
|
||||
}
|
||||
const subContexts = activeContext.getSubContexts();
|
||||
const targetNote = `_help_${inAppHelpPage}`;
|
||||
const helpSubcontext = subContexts.find((s) => s.viewScope?.viewMode === "contextual-help");
|
||||
const viewScope: ViewScope = {
|
||||
viewMode: "contextual-help",
|
||||
};
|
||||
if (!helpSubcontext) {
|
||||
// The help is not already open, open a new split with it.
|
||||
const { ntxId } = subContexts[subContexts.length - 1];
|
||||
appContext.triggerCommand("openNewNoteSplit", {
|
||||
ntxId,
|
||||
notePath: targetNote,
|
||||
hoistedNoteId: "_help",
|
||||
viewScope
|
||||
})
|
||||
} else {
|
||||
// There is already a help window open, make sure it opens on the right note.
|
||||
helpSubcontext.setNote(targetNote, { viewScope });
|
||||
}
|
||||
openInAppHelpFromUrl(inAppHelpPage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the in-app help at the given page in a split note. If there already is a split note open with a help page, it will be replaced by this one.
|
||||
*
|
||||
* @param inAppHelpPage the ID of the help note (excluding the `_help_` prefix).
|
||||
* @returns a promise that resolves once the help has been opened.
|
||||
*/
|
||||
export async function openInAppHelpFromUrl(inAppHelpPage: string) {
|
||||
// Dynamic import to avoid import issues in tests.
|
||||
const appContext = (await import("../components/app_context.js")).default;
|
||||
const activeContext = appContext.tabManager.getActiveContext();
|
||||
if (!activeContext) {
|
||||
return;
|
||||
}
|
||||
const subContexts = activeContext.getSubContexts();
|
||||
const targetNote = `_help_${inAppHelpPage}`;
|
||||
const helpSubcontext = subContexts.find((s) => s.viewScope?.viewMode === "contextual-help");
|
||||
const viewScope: ViewScope = {
|
||||
viewMode: "contextual-help",
|
||||
};
|
||||
if (!helpSubcontext) {
|
||||
// The help is not already open, open a new split with it.
|
||||
const { ntxId } = subContexts[subContexts.length - 1];
|
||||
appContext.triggerCommand("openNewNoteSplit", {
|
||||
ntxId,
|
||||
notePath: targetNote,
|
||||
hoistedNoteId: "_help",
|
||||
viewScope
|
||||
})
|
||||
} else {
|
||||
// There is already a help window open, make sure it opens on the right note.
|
||||
helpSubcontext.setNote(targetNote, { viewScope });
|
||||
}
|
||||
}
|
||||
|
||||
function initHelpButtons($el: JQuery<HTMLElement> | JQuery<Window>) {
|
||||
@@ -731,10 +744,86 @@ function isUpdateAvailable(latestVersion: string | null | undefined, currentVers
|
||||
return compareVersions(latestVersion, currentVersion) > 0;
|
||||
}
|
||||
|
||||
function isLaunchBarConfig(noteId: string) {
|
||||
export function isLaunchBarConfig(noteId: string) {
|
||||
return ["_lbRoot", "_lbAvailableLaunchers", "_lbVisibleLaunchers", "_lbMobileRoot", "_lbMobileAvailableLaunchers", "_lbMobileVisibleLaunchers"].includes(noteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a class to the <body> of the page, where the class name is formed via a prefix and a value.
|
||||
* Useful for configurable options such as `heading-style-markdown`, where `heading-style` is the prefix and `markdown` is the dynamic value.
|
||||
* There is no separator between the prefix and the value, if needed it has to be supplied manually to the prefix.
|
||||
*
|
||||
* @param prefix the prefix.
|
||||
* @param value the value to be appended to the prefix.
|
||||
*/
|
||||
export function toggleBodyClass(prefix: string, value: string) {
|
||||
const $body = $("body");
|
||||
for (const clazz of Array.from($body[0].classList)) {
|
||||
// create copy to safely iterate over while removing classes
|
||||
if (clazz.startsWith(prefix)) {
|
||||
$body.removeClass(clazz);
|
||||
}
|
||||
}
|
||||
|
||||
$body.addClass(prefix + value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic comparison for equality between the two arrays. The values are strictly checked via `===`.
|
||||
*
|
||||
* @param a the first array to compare.
|
||||
* @param b the second array to compare.
|
||||
* @returns `true` if both arrays are equals, `false` otherwise.
|
||||
*/
|
||||
export function arrayEqual<T>(a: T[], b: T[]) {
|
||||
if (a === b) {
|
||||
return true;
|
||||
}
|
||||
if (a.length !== b.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i=0; i < a.length; i++) {
|
||||
if (a[i] !== b[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
type Indexed<T extends object> = T & { index: number };
|
||||
|
||||
/**
|
||||
* Given an object array, alters every object in the array to have an index field assigned to it.
|
||||
*
|
||||
* @param items the objects to be numbered.
|
||||
* @returns the same object for convenience, with the type changed to indicate the new index field.
|
||||
*/
|
||||
export function numberObjectsInPlace<T extends object>(items: T[]): Indexed<T>[] {
|
||||
let index = 0;
|
||||
for (const item of items) {
|
||||
(item as Indexed<T>).index = index++;
|
||||
}
|
||||
return items as Indexed<T>[];
|
||||
}
|
||||
|
||||
export function mapToKeyValueArray<K extends string | number | symbol, V>(map: Record<K, V>) {
|
||||
const values: { key: K, value: V }[] = [];
|
||||
for (const [ key, value ] of Object.entries(map)) {
|
||||
values.push({ key: key as K, value: value as V });
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
export function getErrorMessage(e: unknown) {
|
||||
if (e && typeof e === "object" && "message" in e && typeof e.message === "string") {
|
||||
return e.message;
|
||||
} else {
|
||||
return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
reloadFrontendApp,
|
||||
restartDesktopApp,
|
||||
|
||||
@@ -28,6 +28,28 @@
|
||||
--ck-mention-list-max-height: 500px;
|
||||
}
|
||||
|
||||
body#trilium-app.motion-disabled *,
|
||||
body#trilium-app.motion-disabled *::before,
|
||||
body#trilium-app.motion-disabled *::after {
|
||||
/* Disable transitions and animations */
|
||||
transition: none !important;
|
||||
animation: none !important;
|
||||
}
|
||||
|
||||
body#trilium-app.shadows-disabled *,
|
||||
body#trilium-app.shadows-disabled *::before,
|
||||
body#trilium-app.shadows-disabled *::after {
|
||||
/* Disable shadows */
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
body#trilium-app.backdrop-effects-disabled *,
|
||||
body#trilium-app.backdrop-effects-disabled *::before,
|
||||
body#trilium-app.backdrop-effects-disabled *::after {
|
||||
/* Disable backdrop effects */
|
||||
backdrop-filter: none !important;
|
||||
}
|
||||
|
||||
.table {
|
||||
--bs-table-bg: transparent !important;
|
||||
}
|
||||
@@ -139,6 +161,15 @@ textarea,
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
|
||||
.form-group.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
/* Add a gap between consecutive radios / check boxes */
|
||||
label.tn-radio + label.tn-radio,
|
||||
label.tn-checkbox + label.tn-checkbox {
|
||||
@@ -346,7 +377,7 @@ body.desktop .tabulator-popup-container {
|
||||
|
||||
@supports (animation-fill-mode: forwards) {
|
||||
/* Delay the opening of submenus */
|
||||
body.desktop .dropdown-submenu .dropdown-menu {
|
||||
body.desktop:not(.motion-disabled) .dropdown-submenu .dropdown-menu {
|
||||
opacity: 0;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: var(--submenu-opening-delay);
|
||||
@@ -831,10 +862,34 @@ table.promoted-attributes-in-tooltip th {
|
||||
|
||||
.aa-dropdown-menu .aa-suggestion {
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
padding: 6px 16px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.aa-dropdown-menu .aa-suggestion .icon {
|
||||
display: inline-block;
|
||||
line-height: inherit;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.aa-dropdown-menu .aa-suggestion .text {
|
||||
display: inline-block;
|
||||
width: calc(100% - 20px);
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.aa-dropdown-menu .aa-suggestion .search-result-title {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.aa-dropdown-menu .aa-suggestion .search-result-attributes {
|
||||
display: block;
|
||||
font-size: 0.8em;
|
||||
color: var(--muted-text-color);
|
||||
opacity: 0.6;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.aa-dropdown-menu .aa-suggestion p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
@@ -1738,16 +1793,12 @@ button.close:hover {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.options-number-input {
|
||||
.options-section input[type="number"] {
|
||||
/* overriding settings from .form-control */
|
||||
width: 10em !important;
|
||||
flex-grow: 0 !important;
|
||||
}
|
||||
|
||||
.options-mime-types {
|
||||
column-width: 250px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
cursor: auto;
|
||||
}
|
||||
@@ -1768,20 +1819,42 @@ textarea {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.jump-to-note-dialog .modal-dialog {
|
||||
max-width: 900px;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.jump-to-note-dialog .modal-header {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.jump-to-note-dialog .modal-body {
|
||||
padding: 0;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.jump-to-note-results .aa-dropdown-menu {
|
||||
max-height: 40vh;
|
||||
max-height: calc(80vh - 200px);
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.jump-to-note-results {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.jump-to-note-results .aa-suggestions {
|
||||
padding: 1rem;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.jump-to-note-results .aa-dropdown-menu .aa-suggestion:hover,
|
||||
.jump-to-note-results .aa-dropdown-menu .aa-cursor {
|
||||
background-color: var(--hover-item-background-color, #f8f9fa);
|
||||
}
|
||||
|
||||
/* Command palette styling */
|
||||
@@ -1799,8 +1872,24 @@ textarea {
|
||||
|
||||
.jump-to-note-dialog .aa-cursor .command-suggestion,
|
||||
.jump-to-note-dialog .aa-suggestion:hover .command-suggestion {
|
||||
border-left-color: var(--link-color);
|
||||
background-color: var(--hover-background-color);
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.jump-to-note-dialog .show-in-full-search,
|
||||
.jump-to-note-results .show-in-full-search {
|
||||
border-top: 1px solid var(--main-border-color);
|
||||
padding-top: 12px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.jump-to-note-results .aa-suggestion .search-notes-action {
|
||||
border-top: 1px solid var(--main-border-color);
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.jump-to-note-results .aa-suggestion:has(.search-notes-action)::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.jump-to-note-dialog .command-icon {
|
||||
@@ -2257,7 +2346,8 @@ footer.webview-footer button {
|
||||
|
||||
/* Search result highlighting */
|
||||
.search-result-title b,
|
||||
.search-result-content b {
|
||||
.search-result-content b,
|
||||
.search-result-attributes b {
|
||||
font-weight: 900;
|
||||
color: var(--admonition-warning-accent-color);
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@
|
||||
|
||||
--menu-text-color: #e3e3e3;
|
||||
--menu-background-color: #222222d9;
|
||||
--menu-background-color-no-backdrop: #1b1b1b;
|
||||
--menu-item-icon-color: #8c8c8c;
|
||||
--menu-item-disabled-opacity: 0.5;
|
||||
--menu-item-keyboard-shortcut-color: #ffffff8f;
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
|
||||
--menu-text-color: #272727;
|
||||
--menu-background-color: #ffffffd9;
|
||||
--menu-background-color-no-backdrop: #fdfdfd;
|
||||
--menu-item-icon-color: #727272;
|
||||
--menu-item-disabled-opacity: 0.6;
|
||||
--menu-item-keyboard-shortcut-color: #666666a8;
|
||||
|
||||
@@ -83,6 +83,12 @@
|
||||
--tab-note-icons: true;
|
||||
}
|
||||
|
||||
body.backdrop-effects-disabled {
|
||||
/* Backdrop effects are disabled, replace the menu background color with the
|
||||
* no-backdrop fallback color */
|
||||
--menu-background-color: var(--menu-background-color-no-backdrop);
|
||||
}
|
||||
|
||||
/*
|
||||
* MENUS
|
||||
*
|
||||
@@ -530,10 +536,9 @@ body.mobile .dropdown-menu .dropdown-item.submenu-open .dropdown-toggle::after {
|
||||
}
|
||||
|
||||
/* List item */
|
||||
.jump-to-note-dialog .aa-suggestions div,
|
||||
.note-detail-empty .aa-suggestions div {
|
||||
.jump-to-note-dialog .aa-suggestion,
|
||||
.note-detail-empty .aa-suggestion {
|
||||
border-radius: 6px;
|
||||
padding: 6px 12px;
|
||||
color: var(--menu-text-color);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@@ -181,9 +181,7 @@ div.note-detail-empty {
|
||||
}
|
||||
|
||||
.options-section:not(.tn-no-card) {
|
||||
margin: auto;
|
||||
min-width: var(--options-card-min-width);
|
||||
max-width: var(--options-card-max-width);
|
||||
margin: auto;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--card-border-color) !important;
|
||||
box-shadow: var(--card-box-shadow);
|
||||
@@ -192,6 +190,11 @@ div.note-detail-empty {
|
||||
margin-bottom: calc(var(--options-title-offset) + 26px) !important;
|
||||
}
|
||||
|
||||
body.desktop .option-section:not(.tn-no-card) {
|
||||
min-width: var(--options-card-min-width);
|
||||
max-width: var(--options-card-max-width);
|
||||
}
|
||||
|
||||
.note-detail-content-widget-content.options {
|
||||
--default-padding: 15px;
|
||||
padding-top: calc(var(--default-padding) + var(--options-title-offset) + var(--options-title-font-size));
|
||||
@@ -233,11 +236,6 @@ div.note-detail-empty {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.options-section .options-mime-types {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.options-section .form-group {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
@@ -848,7 +848,7 @@
|
||||
"debug": "调试",
|
||||
"debug_description": "调试将打印额外的调试信息到控制台,以帮助调试复杂查询",
|
||||
"action": "操作",
|
||||
"search_button": "搜索 <kbd>回车</kbd>",
|
||||
"search_button": "搜索",
|
||||
"search_execute": "搜索并执行操作",
|
||||
"save_to_note": "保存到笔记",
|
||||
"search_parameters": "搜索参数",
|
||||
@@ -1165,7 +1165,7 @@
|
||||
},
|
||||
"revisions_snapshot_interval": {
|
||||
"note_revisions_snapshot_interval_title": "笔记修订快照间隔",
|
||||
"note_revisions_snapshot_description": "笔记修订快照间隔是创建新笔记修订的时间。有关更多信息,请参见 <a href=\"https://triliumnext.github.io/Docs/Wiki/note-revisions.html\" class=\"external\">wiki</a>。",
|
||||
"note_revisions_snapshot_description": "笔记修订快照间隔是创建新笔记修订的时间。有关更多信息,请参见 <doc>wiki</doc>。",
|
||||
"snapshot_time_interval_label": "笔记修订快照时间间隔:"
|
||||
},
|
||||
"revisions_snapshot_limit": {
|
||||
@@ -1871,14 +1871,19 @@
|
||||
"selected_provider": "已选提供商",
|
||||
"selected_provider_description": "选择用于聊天和补全功能的AI提供商",
|
||||
"select_model": "选择模型...",
|
||||
"select_provider": "选择提供商..."
|
||||
"select_provider": "选择提供商...",
|
||||
"ai_enabled": "已启用 AI 功能",
|
||||
"ai_disabled": "已禁用 AI 功能",
|
||||
"no_models_found_online": "找不到模型。请检查您的 API 密钥及设置。",
|
||||
"no_models_found_ollama": "找不到 Ollama 模型。请确认 Ollama 是否正在运行。",
|
||||
"error_fetching": "获取模型失败:{{error}}"
|
||||
},
|
||||
"code-editor-options": {
|
||||
"title": "编辑器"
|
||||
},
|
||||
"custom_date_time_format": {
|
||||
"title": "自定义日期/时间格式",
|
||||
"description": "通过<kbd></kbd>或工具栏的方式可自定义日期和时间格式,有关日期/时间格式字符串中各个字符的含义,请参阅<a href=\"https://day.js.org/docs/en/display/format\" target=\"_blank\" rel=\"noopener noreferrer\">Day.js docs</a>。",
|
||||
"description": "通过<shortcut />或工具栏的方式可自定义日期和时间格式,有关日期/时间格式字符串中各个字符的含义,请参阅<doc>Day.js docs</doc>。",
|
||||
"format_string": "日期/时间格式字符串:",
|
||||
"formatted_time": "格式化后日期/时间:"
|
||||
},
|
||||
@@ -1992,11 +1997,28 @@
|
||||
"help_title": "显示关于此画面的更多信息"
|
||||
},
|
||||
"call_to_action": {
|
||||
"next_theme_title": "新的 Trilium 主题已进入稳定版",
|
||||
"next_theme_message": "有一段时间,我们一直在设计新的主题,为了让应用程序看起来更加现代。",
|
||||
"next_theme_button": "切换至新的 Trilium 主题",
|
||||
"background_effects_title": "背景效果现已推出稳定版本",
|
||||
"background_effects_message": "在 Windows 装置上,背景效果现在已完全稳定。背景效果通过模糊背后的背景,为使用者界面增添一抹色彩。此技术也用于其他应用程序,例如 Windows 资源管理器。",
|
||||
"background_effects_button": "启用背景效果"
|
||||
"background_effects_button": "启用背景效果",
|
||||
"next_theme_title": "试用新 Trilium 主题",
|
||||
"next_theme_message": "当前使用旧版主题,要试用新主题吗?",
|
||||
"next_theme_button": "试用新主题",
|
||||
"dismiss": "关闭"
|
||||
},
|
||||
"settings": {
|
||||
"related_settings": "相关设置"
|
||||
},
|
||||
"settings_appearance": {
|
||||
"related_code_blocks": "文本笔记中代码块的色彩方案",
|
||||
"related_code_notes": "代码笔记的色彩方案"
|
||||
},
|
||||
"units": {
|
||||
"percentage": "%"
|
||||
},
|
||||
"ui-performance": {
|
||||
"title": "性能",
|
||||
"enable-motion": "启用过渡和动画",
|
||||
"enable-shadows": "启用阴影",
|
||||
"enable-backdrop-effects": "启用菜单、弹窗和面板的背景效果"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -732,7 +732,8 @@
|
||||
"note_type": "Note type",
|
||||
"editable": "Editable",
|
||||
"basic_properties": "Basic Properties",
|
||||
"language": "Language"
|
||||
"language": "Language",
|
||||
"configure_code_notes": "Configure code notes..."
|
||||
},
|
||||
"book_properties": {
|
||||
"view_type": "View type",
|
||||
@@ -848,7 +849,7 @@
|
||||
"debug": "debug",
|
||||
"debug_description": "Debug will print extra debugging information into the console to aid in debugging complex queries",
|
||||
"action": "action",
|
||||
"search_button": "Search <kbd>enter</kbd>",
|
||||
"search_button": "Search",
|
||||
"search_execute": "Search & Execute actions",
|
||||
"save_to_note": "Save to note",
|
||||
"search_parameters": "Search Parameters",
|
||||
@@ -1113,6 +1114,12 @@
|
||||
"layout-vertical-description": "launcher bar is on the left (default)",
|
||||
"layout-horizontal-description": "launcher bar is underneath the tab bar, the tab bar is now full width."
|
||||
},
|
||||
"ui-performance": {
|
||||
"title": "Performance",
|
||||
"enable-motion": "Enable transitions and animations",
|
||||
"enable-shadows": "Enable shadows",
|
||||
"enable-backdrop-effects": "Enable background effects for menus, popups and panels"
|
||||
},
|
||||
"ai_llm": {
|
||||
"not_started": "Not started",
|
||||
"title": "AI Settings",
|
||||
@@ -1253,7 +1260,12 @@
|
||||
"selected_provider": "Selected Provider",
|
||||
"selected_provider_description": "Choose the AI provider for chat and completion features",
|
||||
"select_model": "Select model...",
|
||||
"select_provider": "Select provider..."
|
||||
"select_provider": "Select provider...",
|
||||
"ai_enabled": "AI features enabled",
|
||||
"ai_disabled": "AI features disabled",
|
||||
"no_models_found_online": "No models found. Please check your API key and settings.",
|
||||
"no_models_found_ollama": "No Ollama models found. Please check if Ollama is running.",
|
||||
"error_fetching": "Error fetching models: {{error}}"
|
||||
},
|
||||
"zoom_factor": {
|
||||
"title": "Zoom Factor (desktop build only)",
|
||||
@@ -1310,7 +1322,7 @@
|
||||
},
|
||||
"revisions_snapshot_interval": {
|
||||
"note_revisions_snapshot_interval_title": "Note Revision Snapshot Interval",
|
||||
"note_revisions_snapshot_description": "The Note revision snapshot interval is the time after which a new note revision will be created for the note. See <a href=\"https://triliumnext.github.io/Docs/Wiki/note-revisions.html\" class=\"external\">wiki</a> for more info.",
|
||||
"note_revisions_snapshot_description": "The Note revision snapshot interval is the time after which a new note revision will be created for the note. See <doc>wiki</doc> for more info.",
|
||||
"snapshot_time_interval_label": "Note revision snapshot time interval:"
|
||||
},
|
||||
"revisions_snapshot_limit": {
|
||||
@@ -1372,7 +1384,7 @@
|
||||
},
|
||||
"custom_date_time_format": {
|
||||
"title": "Custom Date/Time Format",
|
||||
"description": "Customize the format of the date and time inserted via <kbd></kbd> or the toolbar. See <a href=\"https://day.js.org/docs/en/display/format\" target=\"_blank\" rel=\"noopener noreferrer\">Day.js docs</a> for available format tokens.",
|
||||
"description": "Customize the format of the date and time inserted via <shortcut /> or the toolbar. See <doc>Day.js docs</doc> for available format tokens.",
|
||||
"format_string": "Format string:",
|
||||
"formatted_time": "Formatted date/time:"
|
||||
},
|
||||
@@ -1994,11 +2006,22 @@
|
||||
"help_title": "Display more information about this screen"
|
||||
},
|
||||
"call_to_action": {
|
||||
"next_theme_title": "The new Trilium theme is now stable",
|
||||
"next_theme_message": "For a while now, we've been working on a new theme to give the application a more modern look.",
|
||||
"next_theme_button": "Switch to the new Trilium theme",
|
||||
"next_theme_title": "Try the new Trilium theme",
|
||||
"next_theme_message": "You are currently using the legacy theme, would you like to try the new theme?",
|
||||
"next_theme_button": "Try the new theme",
|
||||
"background_effects_title": "Background effects are now stable",
|
||||
"background_effects_message": "On Windows devices, background effects are now fully stable. The background effects adds a touch of color to the user interface by blurring the background behind it. This technique is also used in other applications such as Windows Explorer.",
|
||||
"background_effects_button": "Enable background effects"
|
||||
"background_effects_button": "Enable background effects",
|
||||
"dismiss": "Dismiss"
|
||||
},
|
||||
"settings": {
|
||||
"related_settings": "Related settings"
|
||||
},
|
||||
"settings_appearance": {
|
||||
"related_code_blocks": "Color scheme for code blocks in text notes",
|
||||
"related_code_notes": "Color scheme for code notes"
|
||||
},
|
||||
"units": {
|
||||
"percentage": "%"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
22
apps/client/src/translations/fa/translation.json
Normal file
22
apps/client/src/translations/fa/translation.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"about": {
|
||||
"title": "درباره Trilium Notes",
|
||||
"homepage": "صفحه اصلی:",
|
||||
"app_version": "نسخه برنامه:",
|
||||
"db_version": "نسخه پایگاه داده:",
|
||||
"sync_version": "نسخه منطبق:",
|
||||
"build_date": "تاریخ ساخت:",
|
||||
"build_revision": "نسخه بازنگری شده:",
|
||||
"data_directory": "دایرکتوری داده:"
|
||||
},
|
||||
"toast": {
|
||||
"critical-error": {
|
||||
"title": "خطای بحرانی",
|
||||
"message": "خطای بحرانی رخ داده که مانع از اجرای برنامه می شود\n\n {{message}}\n\nبه احتمال زیاد ناشی از خطای غیرمنتظره در اجرای ناموفق یک اسکریپت است. برنامه را در مد ایمن اجرا کنید و خطا را بررسی نمایید."
|
||||
}
|
||||
},
|
||||
"add_link": {
|
||||
"add_link": "افزودن لینک",
|
||||
"note": "یادداشت"
|
||||
}
|
||||
}
|
||||
147
apps/client/src/translations/fi/translation.json
Normal file
147
apps/client/src/translations/fi/translation.json
Normal file
@@ -0,0 +1,147 @@
|
||||
{
|
||||
"about": {
|
||||
"title": "Lisätietoja Trilium Notes:ista",
|
||||
"homepage": "Kotisivu:",
|
||||
"app_version": "Sovelluksen versio:",
|
||||
"db_version": "Tietokannan versio:",
|
||||
"build_date": "Koontipäivämäärä:",
|
||||
"data_directory": "Datakansio:",
|
||||
"sync_version": "Synkronoinnin versio:",
|
||||
"build_revision": "Sovelluksen versio:"
|
||||
},
|
||||
"toast": {
|
||||
"critical-error": {
|
||||
"title": "Kriittinen virhe"
|
||||
},
|
||||
"widget-error": {
|
||||
"title": "Widgetin luonti epäonnistui"
|
||||
}
|
||||
},
|
||||
"add_link": {
|
||||
"add_link": "Lisää linkki",
|
||||
"link_title": "Linkin otsikko",
|
||||
"button_add_link": "Lisää linkki",
|
||||
"note": "Muistio",
|
||||
"search_note": "etsi muistiota sen nimellä"
|
||||
},
|
||||
"branch_prefix": {
|
||||
"prefix": "Etuliite: ",
|
||||
"save": "Tallenna"
|
||||
},
|
||||
"bulk_actions": {
|
||||
"bulk_actions": "Massatoiminnot",
|
||||
"available_actions": "Saatavilla olevat toiminnot",
|
||||
"chosen_actions": "Valitut toiminnot",
|
||||
"execute_bulk_actions": "Toteuta massatoiminnot",
|
||||
"bulk_actions_executed": "Massatoiminnot on toteutettu onnistuneesti.",
|
||||
"none_yet": "Ei vielä... lisää toiminto klikkaamalla jotiain yllä saatavilla olevaa yltä.",
|
||||
"labels": "Merkit",
|
||||
"relations": "Suhteet",
|
||||
"notes": "Muistiot",
|
||||
"other": "Muut",
|
||||
"affected_notes": "Vaikuttaa muistioihin"
|
||||
},
|
||||
"clone_to": {
|
||||
"clone_notes_to": "Kopioi muistiot...",
|
||||
"help_on_links": "Apua linkkeihin",
|
||||
"notes_to_clone": "Kopioitavat muistiot",
|
||||
"target_parent_note": "Kohteen päämuistio",
|
||||
"search_for_note_by_its_name": "ensi muistiota sen nimellä",
|
||||
"cloned_note_prefix_title": "Kopioitu muistia näytetään puussa annetulla etuliitteellä",
|
||||
"prefix_optional": "Etuliite (valinnainen)",
|
||||
"clone_to_selected_note": "Kopioi valittuun muistioon",
|
||||
"note_cloned": "Muistio \"{{clonedTitle}}\" on kopioitu \"{{targetTitle}}\""
|
||||
},
|
||||
"confirm": {
|
||||
"confirmation": "Vahvistus",
|
||||
"cancel": "Peruuta",
|
||||
"ok": "OK",
|
||||
"also_delete_note": "Poista myös muistio"
|
||||
},
|
||||
"delete_notes": {
|
||||
"delete_notes_preview": "Poista muistion esikatselu",
|
||||
"close": "Sulje",
|
||||
"notes_to_be_deleted": "Seuraavat muistiot tullaan poistamaan ({{notesCount}})",
|
||||
"no_note_to_delete": "Muistioita ei poisteta (vain kopiot).",
|
||||
"cancel": "Peruuta",
|
||||
"ok": "OK"
|
||||
},
|
||||
"export": {
|
||||
"export_note_title": "Vie muistio",
|
||||
"close": "Sulje",
|
||||
"format_html": "HTML - suositeltu, sillä se säilyttää kaikki formatoinnit",
|
||||
"format_markdown": "Markdown - tämä säilyttää suurimman osan formatoinneista.",
|
||||
"opml_version_1": "OPML v1.0 - pelkkä teksti",
|
||||
"opml_version_2": "OPML v2.0 - sallii myös HTML:n",
|
||||
"export": "Vie",
|
||||
"choose_export_type": "Valitse ensin viennin tyyppi",
|
||||
"export_status": "Viennin tila",
|
||||
"export_in_progress": "Vienti käynnissä: {{progressCount}}",
|
||||
"export_finished_successfully": "Vienti valmistui onnistuneesti.",
|
||||
"format_pdf": "PDF - tulostukseen ja jakamiseen."
|
||||
},
|
||||
"help": {
|
||||
"title": "Lunttilappu",
|
||||
"noteNavigation": "Muistion navigointi",
|
||||
"goUpDown": "mene ylös/alas muistioiden listassa",
|
||||
"collapseExpand": "pienennä/suurenna solmu",
|
||||
"notSet": "ei asetettu",
|
||||
"goBackForwards": "mene taaksepäin/eteenpäin historiassa",
|
||||
"jumpToParentNote": "Hyppää ylempään muistioon",
|
||||
"collapseWholeTree": "pienennä koko muistio puu",
|
||||
"onlyInDesktop": "Vain työpöytänäkymässä (Electron build)",
|
||||
"openEmptyTab": "Avaa tyhjä välilehti",
|
||||
"closeActiveTab": "sulje aktiivinen välilehti",
|
||||
"activateNextTab": "aktivoi seuraava välilehti",
|
||||
"activatePreviousTab": "aktivoi edellinen välilehti",
|
||||
"creatingNotes": "Luo muistiota",
|
||||
"movingCloningNotes": "Siirrä / kopioi muistioita",
|
||||
"moveNoteUpHierarchy": "siirrä muistio ylöspäin listassa",
|
||||
"selectNote": "valitse muistio",
|
||||
"editingNotes": "Muokkaa solmua",
|
||||
"createEditLink": "luo / muokkaa ulkoista linkkiä",
|
||||
"createInternalLink": "luo sisäinen linkki",
|
||||
"insertDateTime": "lisää nykyinen päivämäärä ja aika hiiren kohdalle",
|
||||
"troubleshooting": "Vianmääritys",
|
||||
"reloadFrontend": "lataa Trilium:in käyttöliittymä",
|
||||
"showDevTools": "näytä kehittäjätyökalut",
|
||||
"showSQLConsole": "näytä SQL konsoli",
|
||||
"other": "Muut"
|
||||
},
|
||||
"import": {
|
||||
"importIntoNote": "Tuo muistioon",
|
||||
"chooseImportFile": "Valitse tuonnin tiedosto",
|
||||
"options": "Valinnat",
|
||||
"safeImport": "Turvallinen tuonti",
|
||||
"shrinkImages": "Kutista kuvat",
|
||||
"replaceUnderscoresWithSpaces": "Korvaa alaviivat väleillä tuotujen muistioiden tiedostonimissä",
|
||||
"import": "Tuo",
|
||||
"failed": "Tuonti epäonnistui: {{message}}.",
|
||||
"html_import_tags": {
|
||||
"title": "HTML Tuonnin Tunnisteet",
|
||||
"placeholder": "Lisää HTML tunnisteet, yksi per rivi"
|
||||
},
|
||||
"import-status": "Tuonnin tila",
|
||||
"in-progress": "Tuonti vaiheessa: {{progress}}",
|
||||
"successful": "Tuonti valmistui onnistuneesti."
|
||||
},
|
||||
"include_note": {
|
||||
"dialog_title": "Sisällytä muistio",
|
||||
"label_note": "Muistio",
|
||||
"placeholder_search": "etsi muistiota sen nimellä",
|
||||
"box_size_small": "pieni (~ 10 riviä)",
|
||||
"box_size_medium": "keskisuuri (~ 30 riviä)",
|
||||
"button_include": "Sisällytä muistio"
|
||||
},
|
||||
"info": {
|
||||
"modalTitle": "Info viesti",
|
||||
"closeButton": "Sulje",
|
||||
"okButton": "OK"
|
||||
},
|
||||
"jump_to_note": {
|
||||
"search_button": "Etsi koko tekstistä"
|
||||
},
|
||||
"call_to_action": {
|
||||
"dismiss": "Hylkää"
|
||||
}
|
||||
}
|
||||
@@ -202,7 +202,8 @@
|
||||
"okButton": "OK"
|
||||
},
|
||||
"jump_to_note": {
|
||||
"search_button": "Rechercher dans le texte intégral"
|
||||
"search_button": "Rechercher dans le texte intégral",
|
||||
"search_placeholder": "Rechercher une note par son nom ou saisir ‘>’ pour les commandes…"
|
||||
},
|
||||
"markdown_import": {
|
||||
"dialog_title": "Importation Markdown",
|
||||
@@ -222,11 +223,16 @@
|
||||
"note_type_chooser": {
|
||||
"modal_title": "Choisissez le type de note",
|
||||
"modal_body": "Choisissez le type de note/le modèle de la nouvelle note :",
|
||||
"templates": "Modèles"
|
||||
"templates": "Modèles",
|
||||
"change_path_prompt": "Modifier l’emplacement de création de la nouvelle note :",
|
||||
"search_placeholder": "Rechercher le chemin par nom (par défaut si vide)",
|
||||
"builtin_templates": "Modèles intégrés"
|
||||
},
|
||||
"password_not_set": {
|
||||
"title": "Le mot de passe n'est pas défini",
|
||||
"body1": "Les notes protégées sont cryptées à l'aide d'un mot de passe utilisateur, mais le mot de passe n'a pas encore été défini."
|
||||
"body1": "Les notes protégées sont cryptées à l'aide d'un mot de passe utilisateur, mais le mot de passe n'a pas encore été défini.",
|
||||
"body2": "Pour pouvoir protéger les notes, cliquez sur le bouton ci-dessous pour ouvrir la boîte de dialogue Options et définir votre mot de passe.",
|
||||
"go_to_password_options": "Accéder aux options de mot de passe"
|
||||
},
|
||||
"prompt": {
|
||||
"title": "Prompt",
|
||||
@@ -268,7 +274,9 @@
|
||||
"mime": "MIME : ",
|
||||
"file_size": "Taille du fichier :",
|
||||
"preview": "Aperçu :",
|
||||
"preview_not_available": "L'aperçu n'est pas disponible pour ce type de note."
|
||||
"preview_not_available": "L'aperçu n'est pas disponible pour ce type de note.",
|
||||
"restore_button": "Restaurer",
|
||||
"delete_button": "Supprimer"
|
||||
},
|
||||
"sort_child_notes": {
|
||||
"sort_children_by": "Trier les enfants par...",
|
||||
@@ -384,7 +392,7 @@
|
||||
"share_index": "la note avec ce label listera toutes les racines des notes partagées",
|
||||
"display_relations": "noms des relations délimités par des virgules qui doivent être affichés. Tous les autres seront masqués.",
|
||||
"hide_relations": "noms de relations délimités par des virgules qui doivent être masqués. Tous les autres seront affichés.",
|
||||
"title_template": "titre par défaut des notes créées en tant qu'enfants de cette note. La valeur est évaluée sous forme de chaîne JavaScript \n et peut ainsi être enrichi de contenu dynamique via les variables injectées <code>now</code> et <code>parentNote</code>. Exemples :\n \n <ul>\n <li><code>Œuvres littéraires de ${parentNote.getLabelValue('authorName')}</code></li>\n <li><code>Connectez-vous pour ${now.format('YYYY-MM-DD HH:mm:ss')}</code></li>\n </ul>\n \n Consultez le <a href=\"https://triliumnext.github.io/Docs/Wiki/default-note-title.html\">wiki avec plus de détails</a>, la documentation sur l'API pour <a href=\"https://zadam.github.io/trilium/backend_api/Note.html\">parentNote</a> et <a href=\"https://day.js.org/docs/en/display/format\">maintenant< /a> pour plus de détails.",
|
||||
"title_template": "titre par défaut des notes créées en tant qu'enfants de cette note. La valeur est évaluée sous forme de chaîne JavaScript \n et peut ainsi être enrichi de contenu dynamique via les variables injectées <code>now</code> et <code>parentNote</code>. Exemples :\n \n <ul>\n <li><code>Œuvres littéraires de ${parentNote.getLabelValue('authorName')}</code></li>\n <li><code>Connectez-vous pour ${now.format('YYYY-MM-DD HH:mm:ss')}</code></li>\n </ul>\n \n Consultez le <a href=\"https://triliumnext.github.io/Docs/Wiki/default-note-title.html\">wiki avec plus de détails</a>, la documentation sur l'API pour <a href=\"https://zadam.github.io/trilium/backend_api/Note.html\">parentNote</a> et <a href=\"https://day.js.org/docs/en/display/format\">maintenant</a> pour plus de détails.",
|
||||
"template": "Cette note apparaîtra parmi les modèles disponibles lors de la création d'une nouvelle note",
|
||||
"toc": "<code>#toc</code> ou <code>#toc=show</code> forcera l'affichage de la table des matières, <code>#toc=hide</code> force qu'elle soit masquée. Si le label n'existe pas, le paramètre global est utilisé",
|
||||
"color": "définit la couleur de la note dans l'arborescence des notes, les liens, etc. Utilisez n'importe quelle valeur de couleur CSS valide comme « rouge » ou #a13d5f",
|
||||
@@ -418,7 +426,8 @@
|
||||
"other_notes_with_name": "Autres notes portant le nom {{attributeType}} \"{{attributeName}}\"",
|
||||
"and_more": "... et {{count}} plus.",
|
||||
"print_landscape": "Lors de l'exportation en PDF, change l'orientation de la page en paysage au lieu de portrait.",
|
||||
"print_page_size": "Lors de l'exportation en PDF, change la taille de la page. Valeurs supportées : <code>A0</code>, <code>A1</code>, <code>A2</code>, <code>A3</code>, <code>A4</code>, <code>A5</code>, <code>A6</code>, <code>Legal</code>, <code>Letter</code>, <code>Tabloid</code>, <code>Ledger</code>."
|
||||
"print_page_size": "Lors de l'exportation en PDF, change la taille de la page. Valeurs supportées : <code>A0</code>, <code>A1</code>, <code>A2</code>, <code>A3</code>, <code>A4</code>, <code>A5</code>, <code>A6</code>, <code>Legal</code>, <code>Letter</code>, <code>Tabloid</code>, <code>Ledger</code>.",
|
||||
"color_type": "Couleur"
|
||||
},
|
||||
"attribute_editor": {
|
||||
"help_text_body1": "Pour ajouter un label, tapez simplement par ex. <code>#rock</code>, ou si vous souhaitez également ajouter une valeur, tapez par ex. <code>#année = 2020</code>",
|
||||
@@ -577,7 +586,8 @@
|
||||
"september": "Septembre",
|
||||
"october": "Octobre",
|
||||
"november": "Novembre",
|
||||
"december": "Décembre"
|
||||
"december": "Décembre",
|
||||
"cannot_find_week_note": "Impossible de trouver la note de la semaine"
|
||||
},
|
||||
"close_pane_button": {
|
||||
"close_this_pane": "Fermer ce volet"
|
||||
@@ -721,7 +731,8 @@
|
||||
"basic_properties": {
|
||||
"note_type": "Type de note",
|
||||
"editable": "Modifiable",
|
||||
"basic_properties": "Propriétés de base"
|
||||
"basic_properties": "Propriétés de base",
|
||||
"language": "Langage"
|
||||
},
|
||||
"book_properties": {
|
||||
"view_type": "Type d'affichage",
|
||||
@@ -732,7 +743,11 @@
|
||||
"collapse": "Réduire",
|
||||
"expand": "Développer",
|
||||
"invalid_view_type": "Type de vue non valide '{{type}}'",
|
||||
"calendar": "Calendrier"
|
||||
"calendar": "Calendrier",
|
||||
"book_properties": "Propriétés de la collection",
|
||||
"table": "Tableau",
|
||||
"geo-map": "Carte géographique",
|
||||
"board": "Tableau de bord"
|
||||
},
|
||||
"edited_notes": {
|
||||
"no_edited_notes_found": "Aucune note modifiée ce jour-là...",
|
||||
@@ -809,7 +824,8 @@
|
||||
"unknown_label_type": "Type de label inconnu '{{type}}'",
|
||||
"unknown_attribute_type": "Type d'attribut inconnu '{{type}}'",
|
||||
"add_new_attribute": "Ajouter un nouvel attribut",
|
||||
"remove_this_attribute": "Supprimer cet attribut"
|
||||
"remove_this_attribute": "Supprimer cet attribut",
|
||||
"remove_color": "Supprimer l’étiquette de couleur"
|
||||
},
|
||||
"script_executor": {
|
||||
"query": "Requête",
|
||||
@@ -832,7 +848,7 @@
|
||||
"debug": "debug",
|
||||
"debug_description": "Debug imprimera des informations supplémentaires dans la console pour faciliter le débogage des requêtes complexes",
|
||||
"action": "action",
|
||||
"search_button": "Recherche <kbd>Entrée</kbd>",
|
||||
"search_button": "Recherche",
|
||||
"search_execute": "Rechercher et exécuter des actions",
|
||||
"save_to_note": "Enregistrer dans la note",
|
||||
"search_parameters": "Paramètres de recherche",
|
||||
@@ -899,7 +915,7 @@
|
||||
"description1": "Le script de recherche permet de définir les résultats de la recherche en exécutant un script. Cela offre une flexibilité maximale lorsque la recherche standard ne suffit pas.",
|
||||
"description2": "Le script de recherche doit être de type \"code\" et sous-type \"backend JavaScript\". Le script doit retourner un tableau de noteIds ou de notes.",
|
||||
"example_title": "Voir cet exemple :",
|
||||
"example_code": "// 1. préfiltrage à l'aide de la recherche standard\nconst candidateNotes = api.searchForNotes(\"#journal\"); \n\n// 2. application de critères de recherche personnalisés\nconst matchedNotes = candidateNotes\n .filter(note => note.title.match(/[0-9]{1,2}\\. ?[0-9]{1,2}\\. ?[0-9]{4}/));\n\nreturn matchedNotes;",
|
||||
"example_code": "// 1. préfiltrage à l'aide de la recherche standard\nconst candidateNotes = api.searchForNotes(\"#journal\"); \n\n// 2. application de critères de recherche personnalisés\nconst matchedNotes = candidateNotes\n .filter(note => note.title.match(/[0-9]{1,2}\\. ?[0-9]{1,2}\\. ?[0-9]{4}/));\n\nreturn matchedNotes;",
|
||||
"note": "Notez que le script de recherche et la l'expression à rechercher standard ne peuvent pas être combinés."
|
||||
},
|
||||
"search_string": {
|
||||
@@ -1068,7 +1084,8 @@
|
||||
"max_width_label": "Largeur maximale du contenu en pixels",
|
||||
"apply_changes_description": "Pour appliquer les modifications de largeur du contenu, cliquez sur",
|
||||
"reload_button": "recharger l'interface",
|
||||
"reload_description": "changements par rapport aux options d'apparence"
|
||||
"reload_description": "changements par rapport aux options d'apparence",
|
||||
"max_width_unit": "Pixels"
|
||||
},
|
||||
"native_title_bar": {
|
||||
"title": "Barre de titre native (nécessite le redémarrage de l'application)",
|
||||
@@ -1091,7 +1108,10 @@
|
||||
"layout-vertical-title": "Vertical",
|
||||
"layout-horizontal-title": "Horizontal",
|
||||
"layout-vertical-description": "la barre de raccourcis est à gauche (défaut)",
|
||||
"layout-horizontal-description": "la barre de raccourcis est sous la barre des onglets, cette-dernière est s'affiche en pleine largeur."
|
||||
"layout-horizontal-description": "la barre de raccourcis est sous la barre des onglets, cette-dernière est s'affiche en pleine largeur.",
|
||||
"auto_theme": "Hérité (suivre le schéma de couleurs du système)",
|
||||
"light_theme": "Hérité (clair)",
|
||||
"dark_theme": "Hérité (foncé)"
|
||||
},
|
||||
"zoom_factor": {
|
||||
"title": "Facteur de zoom (version bureau uniquement)",
|
||||
@@ -1136,14 +1156,14 @@
|
||||
"note_erasure_timeout": {
|
||||
"note_erasure_timeout_title": "Délai d'effacement des notes",
|
||||
"note_erasure_description": "Les notes supprimées (et les attributs, versions...) sont seulement marquées comme supprimées et il est possible de les récupérer à partir de la boîte de dialogue Notes récentes. Après un certain temps, les notes supprimées sont « effacées », ce qui signifie que leur contenu n'est plus récupérable. Ce paramètre vous permet de configurer la durée entre la suppression et l'effacement de la note.",
|
||||
"erase_notes_after": "Effacer les notes après:",
|
||||
"erase_notes_after": "Effacer les notes après :",
|
||||
"manual_erasing_description": "Vous pouvez également déclencher l'effacement manuellement (sans tenir compte de la durée définie ci-dessus) :",
|
||||
"erase_deleted_notes_now": "Effacer les notes supprimées maintenant",
|
||||
"deleted_notes_erased": "Les notes supprimées ont été effacées."
|
||||
},
|
||||
"revisions_snapshot_interval": {
|
||||
"note_revisions_snapshot_interval_title": "Délai d'enregistrement automatique d'une version de note",
|
||||
"note_revisions_snapshot_description": "Le délai d'enregistrement automatique des versions de note définit le temps avant la création automatique d'une nouvelle version de note. Consultez le <a href=\"https://triliumnext.github.io/Docs/Wiki/note-revisions.html\" class=\"external\">wiki</a> pour plus d'informations.",
|
||||
"note_revisions_snapshot_description": "Le délai d'enregistrement automatique des versions de note définit le temps avant la création automatique d'une nouvelle version de note. Consultez le <doc>wiki</doc> pour plus d'informations.",
|
||||
"snapshot_time_interval_label": "Délai d'enregistrement automatique de version de note :"
|
||||
},
|
||||
"revisions_snapshot_limit": {
|
||||
@@ -1650,5 +1670,26 @@
|
||||
},
|
||||
"modal": {
|
||||
"close": "Fermer"
|
||||
},
|
||||
"ai_llm": {
|
||||
"not_started": "Non démarré",
|
||||
"title": "Paramètres IA",
|
||||
"processed_notes": "Notes traitées",
|
||||
"n_notes_queued_0": "{{ count }} note en attente d’indexation",
|
||||
"n_notes_queued_1": "{{ count }} notes en attente d’indexation",
|
||||
"n_notes_queued_2": "",
|
||||
"notes_indexed_0": "{{ count }} note indexée",
|
||||
"notes_indexed_1": "{{ count }} notes indexées",
|
||||
"notes_indexed_2": "",
|
||||
"anthropic_url_description": "URL de base pour l'API Anthropic (par défaut : https ://api.anthropic.com)",
|
||||
"anthropic_model_description": "Modèles Anthropic Claude pour la complétion",
|
||||
"voyage_settings": "Réglages d'IA Voyage",
|
||||
"ollama_settings": "Réglages Ollama",
|
||||
"ollama_url_description": "URL pour l'API Ollama (par défaut: http://localhost:11434)",
|
||||
"ollama_model_description": "Model Ollama utilisé pour la complétion",
|
||||
"anthropic_configuration": "Configuration Anthropic",
|
||||
"voyage_configuration": "Configuration IA Voyage",
|
||||
"voyage_url_description": "Défaut: https://api.voyageai.com/v1",
|
||||
"ollama_configuration": "Configuration Ollama"
|
||||
}
|
||||
}
|
||||
|
||||
1
apps/client/src/translations/hu/translation.json
Normal file
1
apps/client/src/translations/hu/translation.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -35,7 +35,11 @@
|
||||
"link_title_arbitrary": "リンクタイトルは自由に変更可能"
|
||||
},
|
||||
"branch_prefix": {
|
||||
"save": "保存"
|
||||
"save": "保存",
|
||||
"edit_branch_prefix": "ブランチ接頭辞の編集",
|
||||
"help_on_tree_prefix": "ツリー接頭辞に関するヘルプ",
|
||||
"prefix": "接頭辞: ",
|
||||
"branch_prefix_saved": "ブランチ接頭辞が保存されました。"
|
||||
},
|
||||
"global_menu": {
|
||||
"menu": "メニュー",
|
||||
@@ -59,7 +63,10 @@
|
||||
"zoom_out": "ズームアウト",
|
||||
"zoom_in": "ズームイン",
|
||||
"advanced": "高度",
|
||||
"toggle-zen-mode": "禅モード"
|
||||
"toggle-zen-mode": "禅モード",
|
||||
"switch_to_mobile_version": "モバイル版に切り替え",
|
||||
"switch_to_desktop_version": "デスクトップ版に切り替え",
|
||||
"configure_launchbar": "ランチャーバーの設定"
|
||||
},
|
||||
"left_pane_toggle": {
|
||||
"show_panel": "パネルを表示",
|
||||
@@ -82,14 +89,17 @@
|
||||
"help_on_links": "ヘルプへのリンク"
|
||||
},
|
||||
"delete_notes": {
|
||||
"delete_all_clones_description": "すべてのクローンも削除する(最近の変更では元に戻すことができる)",
|
||||
"delete_all_clones_description": "すべてのクローンも削除(最近の変更では元に戻すことができる)",
|
||||
"erase_notes_description": "通常の(ソフト)削除では、ノートは削除されたものとしてマークされ、一定期間内に(最近の変更で)削除を取り消すことができます。このオプションをオンにすると、ノートは即座に削除され、削除を取り消すことはできません。",
|
||||
"erase_notes_warning": "すべてのクローンを含め、ノートを完全に消去します(元に戻せません)。これにより、アプリケーションは強制的にリロードされます。",
|
||||
"notes_to_be_deleted": "以下のノートが削除されます ({{notesCount}})",
|
||||
"no_note_to_delete": "ノートは削除されません(クローンのみ)。",
|
||||
"cancel": "キャンセル",
|
||||
"ok": "OK",
|
||||
"close": "閉じる"
|
||||
"close": "閉じる",
|
||||
"delete_notes_preview": "ノートのプレビューを削除",
|
||||
"broken_relations_to_be_deleted": "次のリレーション ({{relationCount}})は壊れているので消去されます",
|
||||
"deleted_relation_text": "削除予定のノート{{- note}}は{{- source}}からリレーション{{- relation}}によって参照されています."
|
||||
},
|
||||
"calendar": {
|
||||
"mon": "月",
|
||||
@@ -141,14 +151,15 @@
|
||||
},
|
||||
"tab_row": {
|
||||
"close_tab": "タブを閉じる",
|
||||
"add_new_tab": "新しいタブ",
|
||||
"add_new_tab": "新しいタブを追加",
|
||||
"close": "閉じる",
|
||||
"close_other_tabs": "他のタブを閉じる",
|
||||
"close_right_tabs": "右側のタブをすべて閉じる",
|
||||
"close_all_tabs": "すべてのタブを閉じる",
|
||||
"reopen_last_tab": "最後に閉じたタブを開く",
|
||||
"move_tab_to_new_window": "このタブを新しいウィンドウに移動する",
|
||||
"copy_tab_to_new_window": "このタブを新しいウィンドウにコピーする"
|
||||
"move_tab_to_new_window": "このタブを新しいウィンドウに移動",
|
||||
"copy_tab_to_new_window": "このタブを新しいウィンドウにコピーする",
|
||||
"new_tab": "新しいタブ"
|
||||
},
|
||||
"tasks": {
|
||||
"due": {
|
||||
@@ -174,7 +185,7 @@
|
||||
"debug": "デバッグ",
|
||||
"debug_description": "デバッグは複雑なクエリのデバッグを支援するために、追加のデバッグ情報をコンソールに出力します",
|
||||
"action": "アクション",
|
||||
"search_button": "検索 <kbd>Enter</kbd>",
|
||||
"search_button": "検索",
|
||||
"search_execute": "検索とアクションの実行",
|
||||
"save_to_note": "ノートに保存",
|
||||
"search_parameters": "検索パラメータ",
|
||||
@@ -198,7 +209,10 @@
|
||||
"confirm": {
|
||||
"confirmation": "確認",
|
||||
"cancel": "キャンセル",
|
||||
"ok": "OK"
|
||||
"ok": "OK",
|
||||
"also_delete_note": "同時にノートを削除",
|
||||
"are_you_sure_remove_note": "本当に\"{{title}}\"をリレーションマップから除きたいですか? ",
|
||||
"if_you_dont_check": "これをチェックしないと、このノートはリレーションマップからのみ除かれます。"
|
||||
},
|
||||
"export": {
|
||||
"export_note_title": "ノートをエクスポート",
|
||||
@@ -265,7 +279,12 @@
|
||||
"showSQLConsole": "SQLコンソールを表示",
|
||||
"other": "その他",
|
||||
"quickSearch": "クイックサーチにフォーカス",
|
||||
"inPageSearch": "ページ内検索"
|
||||
"inPageSearch": "ページ内検索",
|
||||
"showJumpToNoteDialog": "<a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">「ジャンプ先」ダイアログ</a>を表示",
|
||||
"moveNoteUpDown": "ノートリストでノートを上/下に移動",
|
||||
"notSet": "未設定",
|
||||
"goUpDown": "ノートのリストで上下する",
|
||||
"editBranchPrefix": "アクティブノートのクローンの <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/tree-concepts.html#prefix\">プレフィックス</a> を編集する"
|
||||
},
|
||||
"import": {
|
||||
"importIntoNote": "ノートにインポート",
|
||||
@@ -340,7 +359,9 @@
|
||||
},
|
||||
"relation_map_buttons": {
|
||||
"zoom_out_title": "ズームアウト",
|
||||
"zoom_in_title": "ズームイン"
|
||||
"zoom_in_title": "ズームイン",
|
||||
"create_child_note_title": "新しい子ノートを作成し、関連マップに追加",
|
||||
"reset_pan_zoom_title": "パンとズームを初期座標と倍率にリセット"
|
||||
},
|
||||
"tree-context-menu": {
|
||||
"advanced": "高度",
|
||||
@@ -392,19 +413,38 @@
|
||||
"open_note_custom": "プログラムからノートを開く",
|
||||
"import_files": "ファイルをインポート",
|
||||
"export_note": "ノートをエクスポート",
|
||||
"delete_note": "ノートを削除する",
|
||||
"delete_note": "ノートを削除",
|
||||
"print_note": "ノートを印刷",
|
||||
"print_pdf": "PDFとしてエクスポート..."
|
||||
"print_pdf": "PDFとしてエクスポート...",
|
||||
"convert_into_attachment": "添付ファイルに変換",
|
||||
"save_revision": "変更履歴を保存",
|
||||
"convert_into_attachment_failed": "ノート '{{title}}' の変換に失敗しました。",
|
||||
"convert_into_attachment_successful": "ノート '{{title}}' は添付ファイルに変換されました。",
|
||||
"convert_into_attachment_prompt": "本当にノート '{{title}}' を親ノートの添付ファイルに変換しますか?",
|
||||
"note_attachments": "ノートの添付ファイル"
|
||||
},
|
||||
"command_palette": {
|
||||
"export_note_title": "ノートをエクスポート",
|
||||
"search_subtree_title": "サブツリー内を検索"
|
||||
"search_subtree_title": "サブツリー内を検索",
|
||||
"tree-action-name": "ツリー: {{name}}",
|
||||
"export_note_description": "現在のノートをエクスポート",
|
||||
"search_notes_title": "ノートを検索",
|
||||
"search_notes_description": "高度な検索を開く",
|
||||
"search_subtree_description": "現在のサブツリー内を検索",
|
||||
"search_history_title": "検索履歴を表示",
|
||||
"search_history_description": "過去の検索結果を見る"
|
||||
},
|
||||
"delete_note": {
|
||||
"delete_note": "ノートを削除する"
|
||||
"delete_note": "ノートを削除"
|
||||
},
|
||||
"board_view": {
|
||||
"delete-note": "ノートを削除する"
|
||||
"delete-note": "ノートを削除",
|
||||
"insert-above": "上に挿入",
|
||||
"insert-below": "下に挿入",
|
||||
"delete-column": "列を削除",
|
||||
"delete-column-confirmation": "本当にこの列を削除しますか?対応する属性は、この列の下のノートでも削除されます。",
|
||||
"new-item": "新しいアイテム",
|
||||
"add-column": "列を追加"
|
||||
},
|
||||
"code_buttons": {
|
||||
"execute_button_title": "スクリプトを実行",
|
||||
@@ -482,7 +522,8 @@
|
||||
"file_size": "ファイルサイズ",
|
||||
"download": "ダウンロード",
|
||||
"open": "開く",
|
||||
"title": "ファイル"
|
||||
"title": "ファイル",
|
||||
"upload_new_revision": "編集履歴をアップロード"
|
||||
},
|
||||
"note_info_widget": {
|
||||
"note_id": "ノート ID",
|
||||
@@ -492,21 +533,54 @@
|
||||
"note_size": "ノートサイズ",
|
||||
"calculate": "計算",
|
||||
"subtree_size": "(サブツリーサイズ: {{size}}、ノード数: {{count}})",
|
||||
"title": "ノート情報"
|
||||
"title": "ノート情報",
|
||||
"note_size_info": "ノートのサイズは、このノートに必要なストレージの概算を示します。これは、ノートの内容とそのノートの編集履歴の内容を考慮したものです。"
|
||||
},
|
||||
"image_properties": {
|
||||
"file_type": "ファイルタイプ",
|
||||
"file_size": "ファイルサイズ",
|
||||
"download": "ダウンロード",
|
||||
"open": "開く",
|
||||
"title": "画像"
|
||||
"title": "画像",
|
||||
"upload_new_revision": "編集履歴をアップロード"
|
||||
},
|
||||
"revisions": {
|
||||
"download_button": "ダウンロード",
|
||||
"delete_button": "削除"
|
||||
"delete_button": "削除",
|
||||
"note_revisions": "ノートの変更履歴",
|
||||
"delete_all_revisions": "このノートの変更履歴をすべて削除",
|
||||
"delete_all_button": "変更履歴をすべて削除",
|
||||
"help_title": "変更履歴のヘルプ",
|
||||
"revision_last_edited": "この変更は{{date}}に行われました",
|
||||
"confirm_delete_all": "このノートのすべての変更履歴を削除しますか?",
|
||||
"no_revisions": "このノートに変更履歴はまだありません...",
|
||||
"restore_button": "復元",
|
||||
"confirm_restore": "この変更を復元しますか?現在のノートのタイトルとコンテンツはこの変更で上書きされます。",
|
||||
"confirm_delete": "この変更履歴を削除しますか?",
|
||||
"revisions_deleted": "ノートの変更履歴は削除されました。",
|
||||
"revision_restored": "ノートの変更が復元されました。",
|
||||
"revision_deleted": "ノートの変更履歴は削除されました。",
|
||||
"settings": "ノートの変更履歴の設定",
|
||||
"file_size": "ファイルサイズ:",
|
||||
"preview": "プレビュー:",
|
||||
"preview_not_available": "このノートタイプではプレビューは利用できません。"
|
||||
},
|
||||
"attachments_actions": {
|
||||
"download": "ダウンロード"
|
||||
"download": "ダウンロード",
|
||||
"open_externally": "外部で開く",
|
||||
"open_externally_title": "ファイルを外部アプリケーションで開き、変更を監視します。その後、変更されたバージョンをTriliumにアップロードできるようになります。",
|
||||
"open_custom": "プログラムから開く",
|
||||
"open_custom_title": "ファイルを外部アプリケーションで開き、変更を監視します。その後、変更されたバージョンをTriliumにアップロードできるようになります。",
|
||||
"rename_attachment": "添付ファイルの名前を変更",
|
||||
"copy_link_to_clipboard": "リンクをクリップボードにコピー",
|
||||
"convert_attachment_into_note": "添付ファイルをノートに変換",
|
||||
"delete_attachment": "添付ファイルを削除",
|
||||
"open_externally_detail_page": "添付ファイルを外部で開くは、詳細ページからのみ可能です。まず添付ファイル詳細をクリックしてから、再度操作を行ってください。",
|
||||
"open_custom_client_only": "添付ファイルをプログラムから開くは、デスクトップクライアントからのみ可能です。",
|
||||
"delete_confirm": "本当に添付ファイル '{{title}}' を削除しますか?",
|
||||
"delete_success": "添付ファイル '{{title}}' は削除されました。",
|
||||
"enter_new_name": "新しい添付ファイルの名前を入力してください",
|
||||
"upload_new_revision": "編集履歴をアップロード"
|
||||
},
|
||||
"etapi": {
|
||||
"created": "作成日時",
|
||||
@@ -578,21 +652,48 @@
|
||||
"parent_count": "クローンの数",
|
||||
"random": "ランダムな順番",
|
||||
"asc": "昇順(デフォルト)",
|
||||
"desc": "降順"
|
||||
"desc": "降順",
|
||||
"content_and_attachments_size": "添付ファイルを含むノート内容のサイズ",
|
||||
"content_and_attachments_and_revisions_size": "添付ファイルと編集履歴を含むノート内容のサイズ",
|
||||
"revision_count": "編集履歴の回数",
|
||||
"owned_label_count": "ラベルの数",
|
||||
"owned_relation_count": "関係の数"
|
||||
},
|
||||
"table_view": {
|
||||
"sort-column-descending": "降順",
|
||||
"sort-column-ascending": "昇順"
|
||||
"sort-column-ascending": "昇順",
|
||||
"new-row": "新しい行",
|
||||
"new-column": "新しい列",
|
||||
"sort-column-by": "\"{{title}}\" で並べ替え",
|
||||
"sort-column-clear": "並べ替えをクリア",
|
||||
"hide-column": "列 \"{{title}}\" を隠す",
|
||||
"show-hide-columns": "列を表示/非表示",
|
||||
"row-insert-above": "上に行を挿入",
|
||||
"row-insert-below": "下に行を挿入",
|
||||
"row-insert-child": "子ノートを挿入",
|
||||
"add-column-to-the-left": "列を左に追加",
|
||||
"add-column-to-the-right": "列を右に追加",
|
||||
"edit-column": "列を編集",
|
||||
"delete_column_confirmation": "この列を本当に削除してもよろしいですか?対応する属性はすべてのノートから削除されます。",
|
||||
"delete-column": "列を削除",
|
||||
"new-column-label": "ラベル",
|
||||
"new-column-relation": "関係"
|
||||
},
|
||||
"search_script": {
|
||||
"title": "検索スクリプト:",
|
||||
"placeholder": "ノート名で検索"
|
||||
"placeholder": "ノート名で検索",
|
||||
"description1": "検索スクリプトは、スクリプトを実行することによって検索結果を定義することができます。標準の検索では不十分な場合に、最大限の柔軟性を提供します。",
|
||||
"description2": "検索スクリプトはノートタイプが \"code\" かつ \"JavaScript backend\" でなければならない。スクリプトは、 noteIds または note の配列を返す必要があります。",
|
||||
"example_title": "例は以下です:",
|
||||
"example_code": "// 1. 標準検索によるプレフィルタリング\nconst candidateNotes = api.searchForNotes(\"#journal\"); \n\n// 2. カスタム検索条件の適用\nconst matchedNotes = candidateNotes\n .filter(note => note.title.match(/[0-9]{1,2}\\. ?[0-9]{1,2}\\. ?[0-9]{4}/));\n\nreturn matchedNotes;",
|
||||
"note": "検索スクリプトと文字列検索は、互いに組み合わせることはできません。"
|
||||
},
|
||||
"include_note": {
|
||||
"placeholder_search": "ノート名で検索",
|
||||
"dialog_title": "埋め込みノート",
|
||||
"box_size_prompt": "埋め込みノート枠のサイズ:",
|
||||
"button_include": "埋め込みノート"
|
||||
"button_include": "埋め込みノート",
|
||||
"label_note": "ノート"
|
||||
},
|
||||
"ancestor": {
|
||||
"placeholder": "ノート名で検索"
|
||||
@@ -634,7 +735,12 @@
|
||||
"system-default": "システムのデフォルト"
|
||||
},
|
||||
"max_content_width": {
|
||||
"reload_button": "フロントエンドをリロード"
|
||||
"reload_button": "フロントエンドをリロード",
|
||||
"title": "コンテンツ幅",
|
||||
"default_description": "Triliumは、ワイドスクリーンで最大化された画面での可読性を向上させるために、デフォルトでコンテンツの最大幅を制限しています。",
|
||||
"max_width_label": "最大コンテンツ幅",
|
||||
"max_width_unit": "ピクセル",
|
||||
"apply_changes_description": "コンテンツ幅の変更を適用するには、クリックしてください"
|
||||
},
|
||||
"theme": {
|
||||
"title": "アプリのテーマ",
|
||||
@@ -713,7 +819,8 @@
|
||||
"shortcut_info": "オプション -> ショートカット('右ペイン切り替え')で、右ペイン(目次を含む)を素早く切り替えるキーボードショートカットを設定できます。"
|
||||
},
|
||||
"toc": {
|
||||
"table_of_contents": "目次"
|
||||
"table_of_contents": "目次",
|
||||
"options": "オプション"
|
||||
},
|
||||
"text_auto_read_only_size": {
|
||||
"title": "自動読み取り専用のサイズ",
|
||||
@@ -724,11 +831,12 @@
|
||||
"code_auto_read_only_size": {
|
||||
"title": "自動読み取り専用のサイズ",
|
||||
"description": "自動読み取り専用のノートサイズは、ノートが読み取り専用モード(パフォーマンス上の理由)で表示されるようになるサイズです。",
|
||||
"unit": "文字"
|
||||
"unit": "文字",
|
||||
"label": "自動読み取り専用のサイズ(コードノート)"
|
||||
},
|
||||
"custom_date_time_format": {
|
||||
"title": "日付/時刻フォーマットのカスタム",
|
||||
"description": "<kbd></kbd>またはツールバーから挿入される日付と時刻のフォーマットをカスタマイズする。 利用可能なトークンについては <a href=\"https://day.js.org/docs/en/display/format\" target=\"_blank\" rel=\"noopener noreferrer\">Day.js ドキュメント</a> を参照してください。",
|
||||
"description": "<shortcut />またはツールバーから挿入される日付と時刻のフォーマットをカスタマイズする。 利用可能なトークンについては <doc>Day.js ドキュメント</doc> を参照してください。",
|
||||
"format_string": "文字列形式:",
|
||||
"formatted_time": "日付/時刻形式:"
|
||||
},
|
||||
@@ -804,7 +912,8 @@
|
||||
"close_label": "閉じる"
|
||||
},
|
||||
"modal": {
|
||||
"close": "閉じる"
|
||||
"close": "閉じる",
|
||||
"help_title": "この画面に関する詳細情報を表示"
|
||||
},
|
||||
"bookmark_switch": {
|
||||
"bookmark": "ブックマーク",
|
||||
@@ -812,10 +921,39 @@
|
||||
"remove_bookmark": "ブックマークを削除"
|
||||
},
|
||||
"attribute_detail": {
|
||||
"delete": "削除"
|
||||
"delete": "削除",
|
||||
"close_button_title": "変更を破棄して閉じる",
|
||||
"attr_is_owned_by": "属性の所有者",
|
||||
"attr_name_title": "属性の名前は英数字、コロン、アンダーバーのみで構成します",
|
||||
"name": "名前",
|
||||
"value": "値",
|
||||
"promoted_alias": "エイリアス",
|
||||
"multiplicity_title": "多重性とは、同じ名前を持つ属性をいくつ作成できるかを定義するもので、1つまたはそれ以上です。",
|
||||
"multiplicity": "多重性",
|
||||
"single_value": "単一の値",
|
||||
"multi_value": "複数の値",
|
||||
"label_type_title": "ラベルのタイプは、Triliumがラベルの値を入力するのに適したインターフェースを選択するのに役立ちます。",
|
||||
"label_type": "タイプ",
|
||||
"text": "テキスト",
|
||||
"number": "数値",
|
||||
"boolean": "真偽値",
|
||||
"date": "日付",
|
||||
"date_time": "日付と時間",
|
||||
"time": "時間",
|
||||
"url": "URL",
|
||||
"save_and_close": "保存して閉じる <kbd>Ctrl+Enter</kbd>",
|
||||
"precision_title": "値設定インターフェースで、小数点以下何桁まで利用可能にするか。",
|
||||
"precision": "精度",
|
||||
"digits": "桁",
|
||||
"inheritable_title": "継承属性は、このツリー配下のすべての子孫に継承されます。",
|
||||
"inheritable": "継承",
|
||||
"related_notes_title": "このラベルが付いた他のノート"
|
||||
},
|
||||
"link_context_menu": {
|
||||
"open_note_in_popup": "クイックエディット"
|
||||
"open_note_in_popup": "クイックエディット",
|
||||
"open_note_in_new_tab": "新しいタブでノートを開く",
|
||||
"open_note_in_new_split": "新しく分割してノートを開く",
|
||||
"open_note_in_new_window": "新しいウィンドウでノートを開く"
|
||||
},
|
||||
"note_tooltip": {
|
||||
"quick-edit": "クイックエディット"
|
||||
@@ -861,6 +999,400 @@
|
||||
"hide-archived-notes": "アーカイブノートを隠す",
|
||||
"automatically-collapse-notes": "ノートを自動的に折りたたむ",
|
||||
"automatically-collapse-notes-title": "一定期間使用されないと、ツリーを整理するためにノートは折りたたまれます。",
|
||||
"save-changes": "変更を保存して適用"
|
||||
"save-changes": "変更を保存して適用",
|
||||
"auto-collapsing-notes-after-inactivity": "非アクティブ状態が続いたためノートが自動で折りたたまれます...",
|
||||
"create-child-note": "子ノートを作成"
|
||||
},
|
||||
"bulk_actions": {
|
||||
"bulk_actions": "一括操作",
|
||||
"affected_notes": "影響されたノート",
|
||||
"include_descendants": "選択したノートの子ノートを含む",
|
||||
"available_actions": "利用可能なアクション",
|
||||
"chosen_actions": "選択されたアクション",
|
||||
"execute_bulk_actions": "一括操作を実行",
|
||||
"bulk_actions_executed": "一括操作が成功に実行されました。",
|
||||
"labels": "ラベル",
|
||||
"relations": "関係",
|
||||
"notes": "ノート",
|
||||
"other": "その他",
|
||||
"none_yet": "アクションを上のリストからクリックして追加。"
|
||||
},
|
||||
"note_title": {
|
||||
"placeholder": "ここにノートのタイトルを入力..."
|
||||
},
|
||||
"search_result": {
|
||||
"no_notes_found": "指定された検索パラメータに該当するノートは見つかりませんでした。",
|
||||
"search_not_executed": "検索はまだ実行されていません。上の「検索」ボタンをクリックすると、検索結果が表示されます。"
|
||||
},
|
||||
"sql_result": {
|
||||
"no_rows": "このクエリでは行が返されませんでした"
|
||||
},
|
||||
"sql_table_schemas": {
|
||||
"tables": "テーブル"
|
||||
},
|
||||
"app_context": {
|
||||
"please_wait_for_save": "保存が完了するまで数秒お待ちください。その後、もう一度お試しいただけます。"
|
||||
},
|
||||
"note_create": {
|
||||
"duplicated": "ノート \"{{title}}\" は複製されました。"
|
||||
},
|
||||
"clipboard": {
|
||||
"cut": "ノートはクリップボードにカットされました。",
|
||||
"copied": "ノートはクリップボードにコピーされました。",
|
||||
"copy_failed": "権限の問題で、クリップボードにコピーできません。",
|
||||
"copy_success": "クリップボードにコピーしました。"
|
||||
},
|
||||
"launcher_context_menu": {
|
||||
"add-note-launcher": "ノートランチャーを追加",
|
||||
"add-script-launcher": "スクリプトランチャーを追加",
|
||||
"add-custom-widget": "カスタムウィジェットを追加",
|
||||
"add-spacer": "スペーサーを追加",
|
||||
"delete": "削除 <kbd data-command=\"deleteNotes\"></kbd>",
|
||||
"reset": "リセット",
|
||||
"move-to-visible-launchers": "可視ランチャーに移動",
|
||||
"move-to-available-launchers": "利用可能なランチャーに移動",
|
||||
"duplicate-launcher": "ランチャーの複製 <kbd data-command=\"duplicateSubtree\">"
|
||||
},
|
||||
"editable-text": {
|
||||
"auto-detect-language": "自動検出"
|
||||
},
|
||||
"highlighting": {
|
||||
"title": "コードブロック",
|
||||
"description": "テキストノート内のコードブロックのシンタックスハイライトを制御します。コードノートには影響しません。",
|
||||
"color-scheme": "カラースキーム"
|
||||
},
|
||||
"code_block": {
|
||||
"word_wrapping": "単語の折り返し",
|
||||
"theme_none": "シンタックスハイライトなし",
|
||||
"theme_group_light": "ライトテーマ",
|
||||
"theme_group_dark": "ダークテーマ",
|
||||
"copy_title": "クリップボードにコピー"
|
||||
},
|
||||
"editor": {
|
||||
"title": "エディター"
|
||||
},
|
||||
"editing": {
|
||||
"editor_type": {
|
||||
"label": "書式設定ツールバー",
|
||||
"floating": {
|
||||
"description": "編集ツールがカーソル付近に表示されます;"
|
||||
},
|
||||
"fixed": {
|
||||
"title": "固定",
|
||||
"description": "編集ツールは「書式設定」のリボンタブに表示されます。"
|
||||
},
|
||||
"multiline-toolbar": "ツールバーが収まりきらない場合は、複数行で表示する。"
|
||||
}
|
||||
},
|
||||
"electron_context_menu": {
|
||||
"add-term-to-dictionary": "辞書に \"{{term}}\" を追加",
|
||||
"cut": "カット",
|
||||
"copy": "コピー",
|
||||
"copy-link": "リンクをコピー",
|
||||
"paste": "ペースト",
|
||||
"paste-as-plain-text": "プレーンテキストで貼り付け",
|
||||
"search_online": "{{searchEngine}} で \"{{term}}\" を検索"
|
||||
},
|
||||
"duration": {
|
||||
"seconds": "秒",
|
||||
"minutes": "分",
|
||||
"hours": "時間",
|
||||
"days": "日"
|
||||
},
|
||||
"share": {
|
||||
"title": "共有設定",
|
||||
"redirect_bare_domain": "ネイキッドドメインを共有ページにリダイレクト",
|
||||
"redirect_bare_domain_description": "匿名のユーザーをログイン画面ではなく共有ページにリダイレクトします",
|
||||
"show_login_link_description": "共有ページの下部にログインリンクを追加",
|
||||
"share_root_found": "共有ルートノート '{{noteTitle}}' の準備が完了",
|
||||
"share_root_not_found": "#shareRoot のラベルが付いたノートが見つかりません",
|
||||
"share_root_not_shared": "ノート '{{noteTitle}}' は #shareRoot のラベルを持っていますが、共有されていません"
|
||||
},
|
||||
"time_selector": {
|
||||
"invalid_input": "入力された時間値が有効な数値ではありません。",
|
||||
"minimum_input": "入力された時間値は {{minimumSeconds}} 秒以上である必要があります。"
|
||||
},
|
||||
"note_language": {
|
||||
"not_set": "未設定",
|
||||
"configure-languages": "言語を設定..."
|
||||
},
|
||||
"content_language": {
|
||||
"title": "コンテンツの言語",
|
||||
"description": "読み取り専用または編集可能なテキストノートの基本プロパティセクションの言語選択に表示する言語を 1 つ以上選択します。これにより、スペルチェックや右から左へのサポートなどの機能が利用できるようになります。"
|
||||
},
|
||||
"png_export_button": {
|
||||
"button_title": "図をPNG形式でエクスポート"
|
||||
},
|
||||
"svg": {
|
||||
"export_to_png": "図をPNG形式でエクスポートできませんでした。"
|
||||
},
|
||||
"code_theme": {
|
||||
"title": "外観",
|
||||
"word_wrapping": "単語の折り返し",
|
||||
"color-scheme": "カラースキーム"
|
||||
},
|
||||
"cpu_arch_warning": {
|
||||
"title": "ARM64版をダウンロードしてください",
|
||||
"dont_show_again": "この警告を二度と表示しない",
|
||||
"continue_anyway": "とにかく続ける"
|
||||
},
|
||||
"editorfeatures": {
|
||||
"emoji_completion_enabled": "絵文字のオートコンプリートを有効",
|
||||
"note_completion_enabled": "ノートのオートコンプリートを有効"
|
||||
},
|
||||
"table_context_menu": {
|
||||
"delete_row": "行を削除"
|
||||
},
|
||||
"content_renderer": {
|
||||
"open_externally": "外部で開く"
|
||||
},
|
||||
"ai_llm": {
|
||||
"title": "AI 設定",
|
||||
"enable_ai_features": "AI/LLM 機能を有効化",
|
||||
"enable_ai_description": "ノートの要約、コンテンツ生成、その他のLLM機能などのAI機能を有効にする",
|
||||
"openai_tab": "OpenAI",
|
||||
"anthropic_tab": "Anthropic",
|
||||
"voyage_tab": "Voyage AI",
|
||||
"ollama_tab": "Ollama",
|
||||
"enable_ai": "AI/LLM 機能を有効化",
|
||||
"enable_ai_desc": "ノートの要約、コンテンツ生成、その他のLLM機能などのAI機能を有効にする",
|
||||
"provider_configuration": "AI プロバイダーの設定",
|
||||
"provider_precedence": "プロバイダーの優先順位",
|
||||
"provider_precedence_description": "カンマで区切られたプロバイダーの優先順位リスト(例: 'openai,anthropic,ollama')",
|
||||
"temperature_description": "応答のランダム性を制御する(0 = 決定的、2 = 最大ランダム性)",
|
||||
"system_prompt_description": "すべてのAIとの対話に使用されるデフォルトのシステムプロンプト",
|
||||
"system_prompt": "システムプロンプト",
|
||||
"openai_configuration": "OpenAIの設定",
|
||||
"openai_settings": "OpenAIの設定",
|
||||
"api_key": "APIキー",
|
||||
"model": "モデル",
|
||||
"openai_api_key_description": "OpenAIのAIサービスにアクセスするためのAPIキー",
|
||||
"anthropic_api_key_description": "AnthropicのClaudeモデルにアクセスするためのAPIキー",
|
||||
"default_model": "デフォルトモデル",
|
||||
"openai_model_description": "例: gpt-4o, gpt-4-turbo, gpt-3.5-turbo",
|
||||
"openai_url_description": "デフォルト: https://api.openai.com/v1",
|
||||
"anthropic_settings": "Anthropicの設定",
|
||||
"anthropic_model_description": "チャット補完用のAnthropic Claudeモデル",
|
||||
"voyage_settings": "Voyage AIの設定",
|
||||
"ollama_settings": "Ollamaの設定",
|
||||
"ollama_url_description": "Ollama API の URL(デフォルト: http://localhost:11434)",
|
||||
"anthropic_url_description": "Anthropic APIのベースURL(デフォルト: https://api.anthropic.com)",
|
||||
"ollama_model_description": "チャット補完用のOllamaモデル",
|
||||
"anthropic_configuration": "Anthropicの設定",
|
||||
"voyage_configuration": "Voyage AIの設定",
|
||||
"voyage_url_description": "デフォルト: https://api.voyageai.com/v1",
|
||||
"ollama_configuration": "Ollamaの設定",
|
||||
"enable_ollama": "Ollamaを有効",
|
||||
"enable_ollama_description": "ローカルAIモデルを利用するためOllamaを有効にする",
|
||||
"ollama_url": "Ollama URL",
|
||||
"ollama_model": "Ollamaモデル",
|
||||
"refresh_models": "モデルを更新",
|
||||
"refreshing_models": "更新中...",
|
||||
"error": "エラー",
|
||||
"retry": "再試行",
|
||||
"partial": "{{ percentage }}%完了",
|
||||
"processing": "処理中({{percentage}}%)",
|
||||
"complete": "完了(100%)",
|
||||
"refreshing": "更新中...",
|
||||
"auto_refresh_notice": "{{seconds}}秒ごとに自動更新",
|
||||
"ai_settings": "AI 設定",
|
||||
"api_key_tooltip": "サービスにアクセスするためのAPIキー",
|
||||
"empty_key_warning": {
|
||||
"anthropic": "Anthropic APIキーが空です。有効な APIキーを入力してください。",
|
||||
"openai": "OpenAI APIキーが空です。有効なAPIキーを入力してください。",
|
||||
"voyage": "Voyage APIキーが空です。有効なAPIキーを入力してください。",
|
||||
"ollama": "Ollama APIキーが空です。有効なAPIキーを入力してください。"
|
||||
},
|
||||
"agent": {
|
||||
"processing": "処理中...",
|
||||
"loading": "読み込み中...",
|
||||
"generating": "生成中..."
|
||||
},
|
||||
"name": "AI",
|
||||
"openai": "OpenAI",
|
||||
"error_contacting_provider": "AIプロバイダーへの接続中にエラーが発生しました。設定とインターネット接続をご確認ください。",
|
||||
"ollama_no_url": "Ollamaは設定されていません。有効なURLを入力してください。",
|
||||
"chat": {
|
||||
"root_note_title": "AIチャット",
|
||||
"root_note_content": "このメモには、保存されたAIチャットの会話が含まれています。",
|
||||
"new_chat_title": "新しいチャット",
|
||||
"create_new_ai_chat": "新しいAIチャットを作成"
|
||||
},
|
||||
"create_new_ai_chat": "新しいAIチャットを作成",
|
||||
"configuration_warnings": "AIの設定に問題があります。設定を確認してください。",
|
||||
"experimental_warning": "LLM機能は現在実験的です - ご注意ください。",
|
||||
"selected_provider_description": "チャットおよび補完機能のAIプロバイダーを選択する",
|
||||
"selected_provider": "プロバイダーを選択",
|
||||
"select_model": "モデルを選択...",
|
||||
"select_provider": "プロバイダーを選択..."
|
||||
},
|
||||
"add_label": {
|
||||
"add_label": "ラベルを追加",
|
||||
"label_name_placeholder": "ラベル名",
|
||||
"label_name_title": "英数字、アンダーバー、コロンが使用可能な文字です。",
|
||||
"new_value_placeholder": "新しい値"
|
||||
},
|
||||
"delete_label": {
|
||||
"delete_label": "ラベルを削除",
|
||||
"label_name_placeholder": "ラベル名",
|
||||
"label_name_title": "英数字、アンダーバー、コロンが使用可能な文字です。"
|
||||
},
|
||||
"rename_label": {
|
||||
"rename_label": "ラベル名の変更",
|
||||
"rename_label_from": "ラベル名を",
|
||||
"old_name_placeholder": "古い名前",
|
||||
"to": "から",
|
||||
"new_name_placeholder": "新しい名前",
|
||||
"name_title": "英数字、アンダーバー、コロンが使用可能な文字です。"
|
||||
},
|
||||
"update_label_value": {
|
||||
"update_label_value": "ラベルの値を更新",
|
||||
"label_name_placeholder": "ラベル名",
|
||||
"label_name_title": "英数字、アンダーバー、コロンが使用可能な文字です。",
|
||||
"new_value_placeholder": "新しい値",
|
||||
"help_text_note": "このメソッドは値なしで呼び出すこともできます。その場合、値なしでラベルがノートに割り当てられます。"
|
||||
},
|
||||
"add_relation": {
|
||||
"add_relation": "関係を追加",
|
||||
"relation_name": "関係の名前",
|
||||
"allowed_characters": "英数字、アンダーバー、コロンが使用可能な文字です。"
|
||||
},
|
||||
"delete_relation": {
|
||||
"delete_relation": "関係を削除",
|
||||
"relation_name": "関係の名前",
|
||||
"allowed_characters": "英数字、アンダーバー、コロンが使用可能な文字です。"
|
||||
},
|
||||
"rename_relation": {
|
||||
"rename_relation": "関係の名前を変更",
|
||||
"rename_relation_from": "関係の名前を",
|
||||
"old_name": "古い名前",
|
||||
"to": "から",
|
||||
"new_name": "新しい名前",
|
||||
"allowed_characters": "英数字、アンダーバー、コロンが使用可能な文字です。"
|
||||
},
|
||||
"update_relation_target": {
|
||||
"update_relation": "関係の更新",
|
||||
"relation_name": "関係の名前",
|
||||
"allowed_characters": "英数字、アンダーバー、コロンが使用可能な文字です。"
|
||||
},
|
||||
"revisions_button": {
|
||||
"note_revisions": "ノートの変更履歴"
|
||||
},
|
||||
"note_launcher": {
|
||||
"this_launcher_doesnt_define_target_note": "このランチャーはターゲットノートを定義していません。"
|
||||
},
|
||||
"zpetne_odkazy": {
|
||||
"backlink": "{{count}} バックリンク",
|
||||
"backlinks": "{{count}} バックリンク",
|
||||
"relation": "関係"
|
||||
},
|
||||
"mobile_detail_menu": {
|
||||
"delete_this_note": "このノートを削除",
|
||||
"error_unrecognized_command": "認識されないコマンド {{command}}"
|
||||
},
|
||||
"inherited_attribute_list": {
|
||||
"title": "継承属性",
|
||||
"no_inherited_attributes": "継承属性ではありません。"
|
||||
},
|
||||
"note_map": {
|
||||
"open_full": "拡大表示",
|
||||
"collapse": "通常サイズに折りたたむ",
|
||||
"title": "ノートマップ",
|
||||
"link-distance": "リンク距離"
|
||||
},
|
||||
"owned_attribute_list": {
|
||||
"owned_attributes": "所有属性"
|
||||
},
|
||||
"promoted_attributes": {
|
||||
"unset-field-placeholder": "未設定",
|
||||
"open_external_link": "外部でリンクを開く",
|
||||
"unknown_label_type": "不明なラベルタイプ '{{type}}'",
|
||||
"unknown_attribute_type": "不明な属性タイプ '{{type}}'",
|
||||
"add_new_attribute": "新しい属性を追加",
|
||||
"remove_this_attribute": "この属性を削除",
|
||||
"remove_color": "このカラーラベルを削除"
|
||||
},
|
||||
"relation_map": {
|
||||
"open_in_new_tab": "新しいタブで開く",
|
||||
"remove_note": "ノートを削除",
|
||||
"edit_title": "タイトルを編集",
|
||||
"rename_note": "ノート名を変更",
|
||||
"enter_new_title": "新しいノート名を入力:",
|
||||
"remove_relation": "関係を削除",
|
||||
"confirm_remove_relation": "本当にこの関係を削除しますか?",
|
||||
"specify_new_relation_name": "新しい関係の名前(使用可能な文字: 英数字、コロン、アンダースコア)を指定:",
|
||||
"note_not_found": "ノート {{noteId}} が見つかりません!",
|
||||
"enter_title_of_new_note": "新しいノートのタイトルを入力",
|
||||
"default_new_note_title": "新しいノート"
|
||||
},
|
||||
"database_anonymization": {
|
||||
"title": "データベースの匿名化",
|
||||
"full_anonymization": "完全匿名化",
|
||||
"full_anonymization_description": "この操作により、データベースの新しいコピーが作成され、匿名化されます(すべてのノートの内容を削除し、構造と一部の非機密メタデータのみを残します)。これにより、個人データが漏洩する心配なく、デバッグ目的でオンライン共有できます。",
|
||||
"save_fully_anonymized_database": "完全に匿名化されたデータベースを保存",
|
||||
"light_anonymization": "軽い匿名化",
|
||||
"light_anonymization_description": "この操作により、データベースの新しいコピーが作成され、軽い匿名化が適用されます。具体的には、すべてのノートの内容のみが削除され、タイトルと属性はそのまま残ります。さらに、カスタムJSフロントエンド/バックエンドスクリプトノートとカスタムウィジェットもそのまま残ります。これにより、問題のデバッグのためのコンテキストがより多く提供されます。",
|
||||
"choose_anonymization": "完全に匿名化したデータベースを提供するか、軽く匿名化したデータベースを提供するかは、あなた自身が決めることができます。完全に匿名化されたDBであっても非常に有用ですが、場合によっては軽く匿名化されたDBの方がバグの特定と修正のプロセスを速めることができます。",
|
||||
"save_lightly_anonymized_database": "軽く匿名化されたデータベースを保存",
|
||||
"existing_anonymized_databases": "既存の匿名化データベース",
|
||||
"creating_fully_anonymized_database": "完全に匿名化されたデータベースを作成中...",
|
||||
"creating_lightly_anonymized_database": "軽く匿名化されたデータベースを作成中...",
|
||||
"error_creating_anonymized_database": "匿名化データベースの作成に失敗しました。詳細はバックエンドログを確認してください",
|
||||
"successfully_created_fully_anonymized_database": "完全に匿名化されたデータベースを {{anonymizedFilePath}} に作成",
|
||||
"successfully_created_lightly_anonymized_database": "軽く匿名化されたデータベースを {{anonymizedFilePath}} に作成",
|
||||
"no_anonymized_database_yet": "匿名化されたデータベースはまだありません。"
|
||||
},
|
||||
"database_integrity_check": {
|
||||
"title": "データベースの整合性チェック",
|
||||
"description": "これは、データベースがSQLiteレベルで破損がないかをチェックします。DBのサイズによっては時間がかかる場合があります。",
|
||||
"check_button": "データベースの整合性をチェック",
|
||||
"checking_integrity": "データベースの整合性をチェックしています...",
|
||||
"integrity_check_succeeded": "整合性チェックに成功 - 問題は見つかりませんでした。",
|
||||
"integrity_check_failed": "整合性チェックに失敗: {{results}}"
|
||||
},
|
||||
"code-editor-options": {
|
||||
"title": "エディター"
|
||||
},
|
||||
"search_string": {
|
||||
"title_column": "文字列検索:",
|
||||
"search_syntax": "検索構文",
|
||||
"also_see": "詳しくは",
|
||||
"complete_help": "検索構文に関する完全なヘルプ",
|
||||
"full_text_search": "テキストを入力すると全文検索が可能",
|
||||
"label_abc": "ラベルabcを持つノートを返す",
|
||||
"label_year": "ラベル「year」の値が「2019」と一致するノート",
|
||||
"label_rock_pop": "rock と pop のラベルを持つノート",
|
||||
"label_rock_or_pop": "どれかのラベルが存在すること",
|
||||
"label_year_comparison": "数値比較(>、>=、<も含む)。",
|
||||
"label_date_created": "過去1ヶ月以内に作成されたノート",
|
||||
"error": "検索エラー: {{error}}",
|
||||
"search_prefix": "検索:"
|
||||
},
|
||||
"delete_revisions": {
|
||||
"delete_note_revisions": "ノートの変更履歴を削除",
|
||||
"all_past_note_revisions": "一致したノートの過去の変更履歴がすべて削除されます。ノート自体は完全に保持されます。言い換えると、ノートのリビジョンが削除されます。"
|
||||
},
|
||||
"rename_note": {
|
||||
"rename_note": "ノート名を変更",
|
||||
"new_note_title": "新しいノート名",
|
||||
"rename_note_title_to": "ノート名を変更",
|
||||
"example_note": "<code>Note</code> - マッチしたノートの名前をすべて'Note'に変更",
|
||||
"example_new_title": "<code>NEW: ${note.title}</code> - 一致したノートの名前の前に 'NEW: ' を付ける",
|
||||
"example_date_prefix": "<code>${note.dateCreatedObj.format('MM-DD:')}: ${note.title}</code> - マッチしたノートの前にノートの作成月日を付ける",
|
||||
"api_docs": "詳細については、 <a href='https://zadam.github.io/trilium/backend_api/Note.html'>note</a> および <a href='https://day.js.org/docs/en/display/format'>dateCreatedObj / utcDateCreatedObj properties</a> の API ドキュメントを参照してください。",
|
||||
"evaluated_as_js_string": "与えられた値はJavaScript文字列として評価されるため、注入された<code>note</code>変数(noteは名前が変更されます)を介して動的なコンテンツで強化できます。例:"
|
||||
},
|
||||
"electron_integration": {
|
||||
"desktop-application": "デスクトップアプリケーション",
|
||||
"native-title-bar": "ネイティブタイトルバー",
|
||||
"native-title-bar-description": "WindowsとmacOSでは、ネイティブタイトルバーをオフにしておくと、アプリケーションがよりコンパクトに見えます。Linuxでは、ネイティブタイトルバーを表示したままの方が、他のシステムとの統一性が高まります。",
|
||||
"background-effects": "背景効果を有効化(Windows 11のみ)",
|
||||
"background-effects-description": "Mica効果は、アプリのウィンドウにぼかされたスタイリッシュな背景を追加し、奥行きとモダンな外観を作り出します。",
|
||||
"restart-app-button": "アプリケーションを再起動して変更を反映",
|
||||
"zoom-factor": "ズーム倍率"
|
||||
},
|
||||
"zoom_factor": {
|
||||
"description": "ズームは CTRL+- と CTRL+= のショートカットでも操作可能。",
|
||||
"title": "ズーム倍率(デスクトップビルドのみ)"
|
||||
}
|
||||
}
|
||||
|
||||
29
apps/client/src/translations/ko/translation.json
Normal file
29
apps/client/src/translations/ko/translation.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"about": {
|
||||
"title": "Trilium Notes에 대해서",
|
||||
"homepage": "홈페이지:",
|
||||
"app_version": "앱 버전:",
|
||||
"db_version": "DB 버전:",
|
||||
"sync_version": "동기화 버전:",
|
||||
"build_date": "빌드 날짜:",
|
||||
"build_revision": "빌드 리비전:",
|
||||
"data_directory": "데이터 경로:"
|
||||
},
|
||||
"toast": {
|
||||
"critical-error": {
|
||||
"title": "심각한 오류",
|
||||
"message": "클라이언트 애플리케이션 시작 도중 심각한 오류가 발생했습니다:\n\n{{message}}\n\n이는 스크립트가 예기치 않게 실패하면서 발생한 것일 수 있습니다. 애플리케이션을 안전 모드로 시작한 뒤 문제를 해결해 보세요."
|
||||
},
|
||||
"widget-error": {
|
||||
"title": "위젯 초기화 실패"
|
||||
}
|
||||
},
|
||||
"add_link": {
|
||||
"add_link": "링크 추가",
|
||||
"note": "노트",
|
||||
"search_note": "이름으로 노트 검색하기"
|
||||
},
|
||||
"branch_prefix": {
|
||||
"save": "저장"
|
||||
}
|
||||
}
|
||||
9
apps/client/src/translations/nl/translation.json
Normal file
9
apps/client/src/translations/nl/translation.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"about": {
|
||||
"title": "Over Trilium Notes",
|
||||
"homepage": "Homepagina:",
|
||||
"app_version": "App versie:",
|
||||
"db_version": "DB Versie:",
|
||||
"sync_version": "Sync Versie:"
|
||||
}
|
||||
}
|
||||
41
apps/client/src/translations/pl/translation.json
Normal file
41
apps/client/src/translations/pl/translation.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"about": {
|
||||
"title": "O notatkach Trilium",
|
||||
"homepage": "Strona główna:",
|
||||
"app_version": "Wersja aplikacji:",
|
||||
"db_version": "Wersja bazy danych:",
|
||||
"sync_version": "Wersja synchronizacji:",
|
||||
"build_date": "Zbudowano:",
|
||||
"build_revision": "Rewizja zbudowania:",
|
||||
"data_directory": "Katalog z danymi:"
|
||||
},
|
||||
"toast": {
|
||||
"critical-error": {
|
||||
"title": "Błąd krytyczny",
|
||||
"message": "Wystąpił krytyczny błąd uniemożliwiający uruchomienie aplikacji:\n\n{{message}}\n\nJest to spowodowane najprawdopodobniej niespodziewanym błędem skryptu. Spróbuj uruchomić aplikację ponownie w trybie bezpiecznym i zaadresuj problem."
|
||||
}
|
||||
},
|
||||
"add_link": {
|
||||
"add_link": "Dodaj link"
|
||||
},
|
||||
"branch_prefix": {
|
||||
"save": "Zapisz"
|
||||
},
|
||||
"bulk_actions": {
|
||||
"labels": "Etykiety",
|
||||
"notes": "Notatki",
|
||||
"other": "Inne",
|
||||
"relations": "Powiązania"
|
||||
},
|
||||
"confirm": {
|
||||
"ok": "OK",
|
||||
"cancel": "Anuluj"
|
||||
},
|
||||
"delete_notes": {
|
||||
"cancel": "Anuluj",
|
||||
"close": "Zamknij"
|
||||
},
|
||||
"export": {
|
||||
"close": "Zamknij"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -40,7 +40,7 @@
|
||||
"add_relation": {
|
||||
"add_relation": "Adaugă relație",
|
||||
"allowed_characters": "Sunt permise doar caractere alfanumerice, underline și două puncte.",
|
||||
"create_relation_on_all_matched_notes": "Crează relația pentru toate notițele găsite",
|
||||
"create_relation_on_all_matched_notes": "Creează relația pentru toate notițele găsite.",
|
||||
"relation_name": "denumirea relației",
|
||||
"target_note": "notița destinație",
|
||||
"to": "către"
|
||||
@@ -76,9 +76,9 @@
|
||||
"attachment_erasure_timeout": {
|
||||
"attachment_auto_deletion_description": "Atașamentele se șterg automat (permanent) dacă nu sunt referențiate de către notița lor părinte după un timp prestabilit de timp.",
|
||||
"attachment_erasure_timeout": "Perioadă de ștergere a atașamentelor",
|
||||
"erase_attachments_after": "Erase unused attachments after:",
|
||||
"erase_attachments_after": "Șterge atașamentele neutilizate după:",
|
||||
"erase_unused_attachments_now": "Elimină atașamentele șterse acum",
|
||||
"manual_erasing_description": "Șterge acum toate atașamentele nefolosite din notițe",
|
||||
"manual_erasing_description": "Puteți șterge atașamentele nefolosite manual (fără a lua în considerare timpul de mai sus):",
|
||||
"unused_attachments_erased": "Atașamentele nefolosite au fost șterse."
|
||||
},
|
||||
"attachment_list": {
|
||||
@@ -141,7 +141,7 @@
|
||||
"hide_promoted_attributes": "Ascunde lista atributelor promovate pentru această notiță",
|
||||
"hide_relations": "lista denumirilor relațiilor ce trebuie ascunse, delimitate prin virgulă. Toate celelalte vor fi afișate.",
|
||||
"icon_class": "valoarea acestei etichete este adăugată ca o clasă CSS la iconița notiței din ierarhia notițelor, fapt ce poate ajuta la identificarea vizuală mai rapidă a notițelor. Un exemplu ar fi „bx bx-home” pentru iconițe preluate din boxicons. Poate fi folosită în notițe de tip șablon.",
|
||||
"inbox": "locația implicită în care vor apărea noile notițe atunci când se crează o noitiță utilizând butonul „Crează notiță” din bara laterală, notițele vor fi create în interiorul notiței cu această etichetă.",
|
||||
"inbox": "locația implicită în care vor apărea noile notițe atunci când se crează o noitiță utilizând butonul „Crează notiță” din bara laterală, notițele vor fi create în interiorul notiței marcată cu eticheta <code>#inbox</code>.",
|
||||
"inherit": "atributele acestei notițe vor fi moștenite chiar dacă nu există o relație părinte-copil între notițe. A se vedea relația de tip șablon pentru un concept similar. De asemenea, a se vedea moștenirea atributelor în documentație.",
|
||||
"inheritable": "Moștenibilă",
|
||||
"inheritable_title": "Atributele moștenibile vor fi moștenite de către toți descendenții acestei notițe.",
|
||||
@@ -177,7 +177,7 @@
|
||||
"render_note": "relație ce definește notița (de tip notiță de cod HTML sau script) ce trebuie randată pentru notițele de tip „Randare notiță HTML”",
|
||||
"run": "definește evenimentele la care să ruleze scriptul. Valori acceptate:\n<ul>\n<li>frontendStartup - când pornește interfața Trilium (sau este reîncărcată), dar nu pe mobil.</li>\n<li>mobileStartup - când pornește interfața Trilium (sau este reîncărcată), doar pe mobil.</li>\n<li>backendStartup - când pornește serverul Trilium</li>\n<li>hourly - o dată pe oră. Se poate utiliza adițional eticheta <code>runAtHour</code> pentru a specifica ora.</li>\n<li>daily - o dată pe zi</li>\n</ul>",
|
||||
"run_at_hour": "La ce oră ar trebui să ruleze. Trebuie folosit împreună cu <code>#run=hourly</code>. Poate fi definit de mai multe ori pentru a rula de mai multe ori în cadrul aceleași zile.",
|
||||
"run_on_attribute_change": "se execută atunci când atributele unei notițe care definește această relație se schimbă. Se apelează și atunci când un atribut este șters",
|
||||
"run_on_attribute_change": " se execută atunci când atributele unei notițe care definește această relație se schimbă. Se apelează și atunci când un atribut este șters",
|
||||
"run_on_attribute_creation": "se execută atunci când un nou atribut este creat pentru notița care definește această relație",
|
||||
"run_on_branch_change": "se execută atunci când o ramură este actualizată.",
|
||||
"run_on_branch_creation": "se execută când o ramură este creată. O ramură este o legătură dintre o notiță părinte și o notiță copil și este creată, spre exemplu, la clonarea sau mutarea unei notițe.",
|
||||
@@ -198,7 +198,7 @@
|
||||
"share_disallow_robot_indexing": "împiedică indexarea conținutului de către roboți utilizând antetul <code>X-Robots-Tag: noindex</code>",
|
||||
"share_external_link": "notița va funcționa drept o legătură către un site web extern în ierarhia de partajare",
|
||||
"share_favicon": "Notiță ce conține pictograma favicon pentru a fi setată în paginile partajate. De obicei se poate seta în rădăcina ierarhiei de partajare și se poate face moștenibilă. Notița ce conține favicon-ul trebuie să fie și ea în ierarhia de partajare. Considerați și utilizarea „share_hidden_from_tree”.",
|
||||
"share_hidden_from_tree": "notița este ascunsă din arborele de navigație din stânga, dar încă este accesibilă prin intermediul unui URL.",
|
||||
"share_hidden_from_tree": "notița este ascunsă din arborele de navigație din stânga, dar încă este accesibilă prin intermediul unui URL",
|
||||
"share_index": "notițele cu această etichetă vor afișa lista tuturor rădăcilor notițelor partajate",
|
||||
"share_js": "Notiță JavaScript ce va fi injectată în pagina de partajare. Notița respectivă trebuie să fie și ea în ierarhia de partajare. Considerați utilizarea 'share_hidden_from_tree'.",
|
||||
"share_omit_default_css": "CSS-ul implicit pentru pagina de partajare va fi omis. Se poate folosi atunci când se fac schimbări majore de stil la pagină.",
|
||||
@@ -214,7 +214,7 @@
|
||||
"target_note_title": "Relația este o conexiune numită dintre o notiță sursă și o notiță țintă.",
|
||||
"template": "Șablon",
|
||||
"text": "Text",
|
||||
"title_template": "titlul implicit al notițelor create în interiorul acestei notițe. Valoarea este evaluată ca un șir de caractere JavaScript\n și poate fi astfel îmbogățită cu un conținut dinamic prin intermediul variabilelow <code>now</code> și <code>parentNote</code>. Exemple:\n \n <ul>\n <li><code>Lucrările lui ${parentNote.getLabelValue('autor')}</code></li>\n <li><code>Jurnal pentru ${now.format('YYYY-MM-DD HH:mm:ss')}</code></li>\n </ul>\n \n A se vedea <a href=\"https://triliumnext.github.io/Docs/Wiki/default-note-title.html\">wiki-ul pentru detalii</a>, documentația API pentru <a href=\"https://zadam.github.io/trilium/backend_api/Note.html\">parentNote</a> și <a href=\"https://day.js.org/docs/en/display/format\">now</a> pentru mai multe informații",
|
||||
"title_template": "titlul implicit al notițelor create în interiorul acestei notițe. Valoarea este evaluată ca un șir de caractere JavaScript\n și poate fi astfel îmbogățită cu un conținut dinamic prin intermediul variabilelor <code>now</code> și <code>parentNote</code>. Exemple:\n \n <ul>\n <li><code>Lucrările lui ${parentNote.getLabelValue('autor')}</code></li>\n <li><code>Jurnal pentru ${now.format('YYYY-MM-DD HH:mm:ss')}</code></li>\n </ul>\n \n A se vedea <a href=\"https://triliumnext.github.io/Docs/Wiki/default-note-title.html\">wiki-ul pentru detalii</a>, documentația API pentru <a href=\"https://zadam.github.io/trilium/backend_api/Note.html\">parentNote</a> și <a href=\"https://day.js.org/docs/en/display/format\">now</a> pentru mai multe informații.",
|
||||
"toc": "<code>#toc</code> sau <code>#toc=show</code> forțează afișarea tabelei de conținut, <code>#toc=hide</code> forțează ascunderea ei. Dacă eticheta nu există, se utilizează setările globale",
|
||||
"top": "păstrează notița la începutul listei (se aplică doar pentru notițe sortate automat)",
|
||||
"url": "URL",
|
||||
@@ -369,7 +369,7 @@
|
||||
},
|
||||
"confirm": {
|
||||
"also_delete_note": "Șterge și notița",
|
||||
"are_you_sure_remove_note": "Doriți ștergerea notiței „{{title}}” din harta de relații?",
|
||||
"are_you_sure_remove_note": "Doriți ștergerea notiței „{{title}}” din harta de relații? ",
|
||||
"cancel": "Anulează",
|
||||
"confirmation": "Confirm",
|
||||
"if_you_dont_check": "Dacă această opțiune nu este bifată, notița va fi ștearsă doar din harta de relații.",
|
||||
@@ -519,8 +519,8 @@
|
||||
"export_status": "Starea exportului",
|
||||
"export_type_single": "Doar această notiță fără descendenții ei",
|
||||
"export_type_subtree": "Această notiță și toți descendenții ei",
|
||||
"format_html_zip": "HTML în arhivă ZIP - recomandat deoarece păstrează toată formatarea",
|
||||
"format_markdown": "Markdown - păstrează majoritatea formatării",
|
||||
"format_html_zip": "HTML în arhivă ZIP - recomandat deoarece păstrează toată formatarea.",
|
||||
"format_markdown": "Markdown - păstrează majoritatea formatării.",
|
||||
"format_opml": "OPML - format de interschimbare pentru editoare cu structură ierarhică (outline). Formatarea, imaginile și fișierele nu vor fi incluse.",
|
||||
"opml_version_1": "OPML v1.0 - text simplu",
|
||||
"opml_version_2": "OPML v2.0 - permite și HTML",
|
||||
@@ -640,7 +640,7 @@
|
||||
"newTabNoteLink": "pe o legătură către o notiță va deschide notița într-un tab nou",
|
||||
"notSet": "nesetat",
|
||||
"noteNavigation": "Navigarea printre notițe",
|
||||
"numberedList": "<kbd>1.</code> sau <code>1)</code> urmat de spațiu pentru o listă numerotată",
|
||||
"numberedList": "<code>1.</code> sau <code>1)</code> urmat de spațiu pentru o listă numerotată",
|
||||
"onlyInDesktop": "Doar pentru desktop (aplicația Electron)",
|
||||
"openEmptyTab": "deschide un tab nou",
|
||||
"other": "Altele",
|
||||
@@ -655,7 +655,8 @@
|
||||
"showSQLConsole": "afișează consola SQL",
|
||||
"tabShortcuts": "Scurtături pentru tab-uri",
|
||||
"troubleshooting": "Unelte pentru depanare",
|
||||
"newTabWithActivationNoteLink": "pe o legătură către o notiță deschide și activează notița într-un tab nou"
|
||||
"newTabWithActivationNoteLink": "pe o legătură către o notiță deschide și activează notița într-un tab nou",
|
||||
"title": "Ghid rapid"
|
||||
},
|
||||
"hide_floating_buttons_button": {
|
||||
"button_title": "Ascunde butoanele"
|
||||
@@ -806,7 +807,7 @@
|
||||
"dialog_title": "Mută notițele în...",
|
||||
"error_no_path": "Nicio cale la care să poată fi mutate.",
|
||||
"move_button": "Mută la notița selectată",
|
||||
"move_success_message": "Notițele selectate au fost mutate în",
|
||||
"move_success_message": "Notițele selectate au fost mutate în ",
|
||||
"notes_to_move": "Notițe de mutat",
|
||||
"search_placeholder": "căutați notița după denumirea ei",
|
||||
"target_parent_note": "Notița părinte destinație"
|
||||
@@ -886,7 +887,8 @@
|
||||
"modal_title": "Selectați tipul notiței",
|
||||
"templates": "Șabloane",
|
||||
"change_path_prompt": "Selectați locul unde să se creeze noua notiță:",
|
||||
"search_placeholder": "căutare cale notiță după nume (cea implicită dacă este necompletat)"
|
||||
"search_placeholder": "căutare cale notiță după nume (cea implicită dacă este necompletat)",
|
||||
"builtin_templates": "Șabloane predefinite"
|
||||
},
|
||||
"onclick_button": {
|
||||
"no_click_handler": "Butonul „{{componentId}}” nu are nicio acțiune la clic definită"
|
||||
@@ -940,7 +942,9 @@
|
||||
},
|
||||
"password_not_set": {
|
||||
"body1": "Notițele protejate sunt criptate utilizând parola de utilizator, dar nu a fost setată nicio parolă.",
|
||||
"title": "Parola nu este setată"
|
||||
"title": "Parola nu este setată",
|
||||
"body2": "Pentru a putea proteja notițe, clic pe butonul de mai jos pentru a deschide fereastra de opțiuni și pentru a seta parola.",
|
||||
"go_to_password_options": "Mergi la setările de parolă"
|
||||
},
|
||||
"promoted_attributes": {
|
||||
"add_new_attribute": "Adaugă un nou atribut",
|
||||
@@ -1054,7 +1058,7 @@
|
||||
"download_button": "Descarcă",
|
||||
"file_size": "Dimensiune fișier:",
|
||||
"help_title": "Informații despre reviziile notițelor",
|
||||
"mime": "MIME:",
|
||||
"mime": "MIME: ",
|
||||
"no_revisions": "Nu există încă nicio revizie pentru această notiță...",
|
||||
"note_revisions": "Revizii ale notiței",
|
||||
"preview": "Previzualizare:",
|
||||
@@ -1072,7 +1076,7 @@
|
||||
"note_revisions": "Revizii ale notiței"
|
||||
},
|
||||
"revisions_snapshot_interval": {
|
||||
"note_revisions_snapshot_description": "Intervalul de salvare a reviziilor este timpul după care se crează o nouă revizie a unei notițe. Vedeți <a href=\"https://triliumnext.github.io/Docs/Wiki/note-revisions.html\" class=\"external\">wiki-ul</a> pentru mai multe informații.",
|
||||
"note_revisions_snapshot_description": "Intervalul de salvare a reviziilor este timpul după care se crează o nouă revizie a unei notițe. Vedeți <doc>wiki-ul</doc> pentru mai multe informații.",
|
||||
"note_revisions_snapshot_interval_title": "Intervalul de salvare a reviziilor",
|
||||
"snapshot_time_interval_label": "Intervalul de salvare a reviziilor:"
|
||||
},
|
||||
@@ -1102,7 +1106,7 @@
|
||||
"limit_description": "Limitează numărul de rezultate",
|
||||
"order_by": "ordonează după",
|
||||
"save_to_note": "Salvează în notiță",
|
||||
"search_button": "Căutare <kbd>Enter</kbd>",
|
||||
"search_button": "Căutare",
|
||||
"search_execute": "Caută și execută acțiunile",
|
||||
"search_note_saved": "Notița de căutare a fost salvată în {{- notePathTitle}}",
|
||||
"search_parameters": "Parametrii de căutare",
|
||||
@@ -1189,7 +1193,7 @@
|
||||
"enable": "Activează corectorul ortografic",
|
||||
"language_code_label": "Codurile de limbă",
|
||||
"language_code_placeholder": "de exemplu „en-US”, „de-AT”",
|
||||
"multiple_languages_info": "Mai multe limbi pot fi separate prin virgulă, e.g. \"en-US, de-DE, cs\".",
|
||||
"multiple_languages_info": "Mai multe limbi pot fi separate prin virgulă, e.g. \"en-US, de-DE, cs\". ",
|
||||
"title": "Corector ortografic",
|
||||
"restart-required": "Schimbările asupra setărilor corectorului ortografic vor fi aplicate după restartarea aplicației."
|
||||
},
|
||||
@@ -1243,7 +1247,10 @@
|
||||
"layout-horizontal-description": "bara de lansare se află sub bara de taburi, bara de taburi este pe toată lungimea.",
|
||||
"layout-horizontal-title": "Orizontal",
|
||||
"layout-vertical-title": "Vertical",
|
||||
"layout-vertical-description": "bara de lansare se află pe stânga (implicit)"
|
||||
"layout-vertical-description": "bara de lansare se află pe stânga (implicit)",
|
||||
"auto_theme": "Tema clasică (se adaptează la schema de culori a sistemului)",
|
||||
"light_theme": "Tema clasică (luminoasă)",
|
||||
"dark_theme": "Tema clasică (întunecată)"
|
||||
},
|
||||
"toast": {
|
||||
"critical-error": {
|
||||
@@ -1279,7 +1286,7 @@
|
||||
"update_relation_target": {
|
||||
"allowed_characters": "Sunt permise doar caractere alfanumerice, underline și două puncte.",
|
||||
"change_target_note": "schimbă notița-țintă a unei relații existente",
|
||||
"on_all_matched_notes": "Pentru toate notițele găsite:",
|
||||
"on_all_matched_notes": "Pentru toate notițele găsite",
|
||||
"relation_name": "denumirea relației",
|
||||
"target_note": "notița destinație",
|
||||
"to": "la",
|
||||
@@ -1307,7 +1314,7 @@
|
||||
"use_vim_keybindings_in_code_notes": "Combinații de taste Vim"
|
||||
},
|
||||
"web_view": {
|
||||
"create_label": "Pentru a începe, creați o etichetă cu adresa URL de încorporat, e.g. #webViewSrc=\"https://www.google.com\"",
|
||||
"create_label": "Pentru a începe, creați o etichetă cu adresa URL de încorporat, e.g. #webViewSrc=\"https://www.google.com\"",
|
||||
"embed_websites": "Notițele de tip „Vizualizare web” permit încorporarea site-urilor web în Trilium.",
|
||||
"web_view": "Vizualizare web"
|
||||
},
|
||||
@@ -1856,15 +1863,20 @@
|
||||
},
|
||||
"create_new_ai_chat": "Crează o nouă discuție cu AI-ul",
|
||||
"configuration_warnings": "Sunt câteva probleme la configurația AI-ului. Verificați setările.",
|
||||
"experimental_warning": "Funcția LLM este experimentală!",
|
||||
"experimental_warning": "Funcția LLM este experimentală.",
|
||||
"selected_provider": "Furnizor selectat",
|
||||
"selected_provider_description": "Selectați furnizorul de AI pentru funcțiile de discuție și completare",
|
||||
"select_model": "Selectați modelul...",
|
||||
"select_provider": "Selectați furnizorul..."
|
||||
"select_provider": "Selectați furnizorul...",
|
||||
"ai_enabled": "Funcționalitățile AI au fost activate",
|
||||
"ai_disabled": "Funcționalitățile AI au fost dezactivate",
|
||||
"no_models_found_online": "Nu s-a găsit niciun model. Verificați cheia API și configurația.",
|
||||
"no_models_found_ollama": "Nu s-a găsit niciun model Ollama. Verificați dacă Ollama rulează.",
|
||||
"error_fetching": "Eroare la obținerea modelelor: {{error}}"
|
||||
},
|
||||
"custom_date_time_format": {
|
||||
"title": "Format dată/timp personalizat",
|
||||
"description": "Personalizați formatul de dată și timp inserat prin <kbd></kbd> sau din bara de unelte. Vedeți <a href=\"https://day.js.org/docs/en/display/format\" target=\"_blank\" rel=\"noopener noreferrer\">Documentația Day.js</a> pentru câmpurile de formatare disponibile.",
|
||||
"description": "Personalizați formatul de dată și timp inserat prin <shortcut /> sau din bara de unelte. Vedeți <doc>Documentația Day.js</doc> pentru câmpurile de formatare disponibile.",
|
||||
"format_string": "Șir de formatare:",
|
||||
"formatted_time": "Data și ora formatate:"
|
||||
},
|
||||
@@ -1985,6 +1997,32 @@
|
||||
"open_externally": "Deschide în afara programului"
|
||||
},
|
||||
"modal": {
|
||||
"close": "Închide"
|
||||
"close": "Închide",
|
||||
"help_title": "Afișează mai multe informații despre acest ecran"
|
||||
},
|
||||
"call_to_action": {
|
||||
"background_effects_title": "Efectele de fundal sunt acum stabile",
|
||||
"background_effects_message": "Pe dispozitive cu Windows, efectele de fundal sunt complet stabile. Acestea adaugă un strop de culoare interfeței grafice prin estomparea fundalului din spatele ferestrei. Această tehnică este folosită și în alte aplicații precum Windows Explorer.",
|
||||
"background_effects_button": "Activează efectele de fundal",
|
||||
"next_theme_title": "Încercați noua temă Trilium",
|
||||
"next_theme_message": "Utilizați tema clasică, doriți să încercați noua temă?",
|
||||
"next_theme_button": "Testează noua temă",
|
||||
"dismiss": "Treci peste"
|
||||
},
|
||||
"ui-performance": {
|
||||
"title": "Setări de performanță",
|
||||
"enable-motion": "Activează tranzițiile și animațiile",
|
||||
"enable-shadows": "Activează umbrirea elementelor",
|
||||
"enable-backdrop-effects": "Activează efectele de fundal pentru meniuri, popup-uri și panouri"
|
||||
},
|
||||
"settings": {
|
||||
"related_settings": "Setări similare"
|
||||
},
|
||||
"settings_appearance": {
|
||||
"related_code_blocks": "Tema de culori pentru blocuri de cod în notițe de tip text",
|
||||
"related_code_notes": "Tema de culori pentru notițele de tip cod"
|
||||
},
|
||||
"units": {
|
||||
"percentage": "%"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2022
apps/client/src/translations/uk/translation.json
Normal file
2022
apps/client/src/translations/uk/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,69 +1,80 @@
|
||||
{
|
||||
"about": {
|
||||
"homepage": "Trang chủ:",
|
||||
"title": "Về Trilium Notes"
|
||||
},
|
||||
"add_link": {
|
||||
"add_link": "Thêm liên kết",
|
||||
"button_add_link": "Thêm liên kết"
|
||||
},
|
||||
"bulk_actions": {
|
||||
"other": "Khác"
|
||||
},
|
||||
"branch_prefix": {
|
||||
"save": "Lưu"
|
||||
},
|
||||
"confirm": {
|
||||
"ok": "OK",
|
||||
"cancel": "Huỷ"
|
||||
},
|
||||
"delete_notes": {
|
||||
"close": "Đóng",
|
||||
"ok": "OK",
|
||||
"cancel": "Huỷ"
|
||||
},
|
||||
"export": {
|
||||
"close": "Đóng"
|
||||
},
|
||||
"help": {
|
||||
"other": "Khác"
|
||||
},
|
||||
"toast": {
|
||||
"critical-error": {
|
||||
"title": "Lỗi nghiêm trọng"
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
"options": "Tuỳ chọn"
|
||||
},
|
||||
"info": {
|
||||
"okButton": "OK",
|
||||
"closeButton": "Đóng"
|
||||
},
|
||||
"move_to": {
|
||||
"dialog_title": "Chuyển ghi chép tới..."
|
||||
},
|
||||
"prompt": {
|
||||
"ok": "OK"
|
||||
},
|
||||
"protected_session_password": {
|
||||
"close_label": "Đóng"
|
||||
},
|
||||
"revisions": {
|
||||
"restore_button": "Khôi phục",
|
||||
"delete_button": "Xoá"
|
||||
},
|
||||
"upload_attachments": {
|
||||
"options": "Tuỳ chọn"
|
||||
},
|
||||
"attribute_detail": {
|
||||
"name": "Tên",
|
||||
"value": "Giá trị",
|
||||
"text": "Văn bản",
|
||||
"number": "Số",
|
||||
"delete": "Xoá"
|
||||
},
|
||||
"rename_note": {
|
||||
"rename_note": "Đổi tên ghi chép"
|
||||
"about": {
|
||||
"homepage": "Trang chủ:",
|
||||
"title": "Về Trilium Notes"
|
||||
},
|
||||
"add_link": {
|
||||
"add_link": "Thêm liên kết",
|
||||
"button_add_link": "Thêm liên kết"
|
||||
},
|
||||
"bulk_actions": {
|
||||
"other": "Khác"
|
||||
},
|
||||
"branch_prefix": {
|
||||
"save": "Lưu"
|
||||
},
|
||||
"confirm": {
|
||||
"ok": "OK",
|
||||
"cancel": "Huỷ"
|
||||
},
|
||||
"delete_notes": {
|
||||
"close": "Đóng",
|
||||
"ok": "OK",
|
||||
"cancel": "Huỷ"
|
||||
},
|
||||
"export": {
|
||||
"close": "Đóng"
|
||||
},
|
||||
"help": {
|
||||
"other": "Khác"
|
||||
},
|
||||
"toast": {
|
||||
"critical-error": {
|
||||
"title": "Lỗi nghiêm trọng"
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
"options": "Tuỳ chọn"
|
||||
},
|
||||
"info": {
|
||||
"okButton": "OK",
|
||||
"closeButton": "Đóng"
|
||||
},
|
||||
"move_to": {
|
||||
"dialog_title": "Chuyển ghi chép tới..."
|
||||
},
|
||||
"prompt": {
|
||||
"ok": "OK"
|
||||
},
|
||||
"protected_session_password": {
|
||||
"close_label": "Đóng"
|
||||
},
|
||||
"revisions": {
|
||||
"restore_button": "Khôi phục",
|
||||
"delete_button": "Xoá"
|
||||
},
|
||||
"upload_attachments": {
|
||||
"options": "Tuỳ chọn"
|
||||
},
|
||||
"attribute_detail": {
|
||||
"name": "Tên",
|
||||
"value": "Giá trị",
|
||||
"text": "Văn bản",
|
||||
"number": "Số",
|
||||
"delete": "Xoá"
|
||||
},
|
||||
"rename_note": {
|
||||
"rename_note": "Đổi tên ghi chép"
|
||||
},
|
||||
"add_label": {
|
||||
"add_label": "Thêm nhãn",
|
||||
"label_name_placeholder": "tên nhãn",
|
||||
"help_text_item2": "hoặc thay đổi giá trị của nhãn có sẵn"
|
||||
},
|
||||
"rename_label": {
|
||||
"rename_label": "Đặt lại tên nhãn"
|
||||
},
|
||||
"call_to_action": {
|
||||
"dismiss": "Bỏ qua"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,11 @@ type DateTimeStyle = "full" | "long" | "medium" | "short" | "none" | undefined;
|
||||
/**
|
||||
* Formats the given date and time to a string based on the current locale.
|
||||
*/
|
||||
export function formatDateTime(date: string | Date | number, dateStyle: DateTimeStyle = "medium", timeStyle: DateTimeStyle = "medium") {
|
||||
export function formatDateTime(date: string | Date | number | null | undefined, dateStyle: DateTimeStyle = "medium", timeStyle: DateTimeStyle = "medium") {
|
||||
if (!date) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const locale = navigator.language;
|
||||
|
||||
let parsedDate;
|
||||
|
||||
@@ -1,504 +0,0 @@
|
||||
import { t } from "../../services/i18n.js";
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
import noteAutocompleteService, { type Suggestion } from "../../services/note_autocomplete.js";
|
||||
import server from "../../services/server.js";
|
||||
import contextMenuService from "../../menus/context_menu.js";
|
||||
import attributeParser, { type Attribute } from "../../services/attribute_parser.js";
|
||||
import { AttributeEditor, type EditorConfig, type ModelElement, type MentionFeed, type ModelNode, type ModelPosition } from "@triliumnext/ckeditor5";
|
||||
import froca from "../../services/froca.js";
|
||||
import attributeRenderer from "../../services/attribute_renderer.js";
|
||||
import noteCreateService from "../../services/note_create.js";
|
||||
import attributeService from "../../services/attributes.js";
|
||||
import linkService from "../../services/link.js";
|
||||
import type AttributeDetailWidget from "./attribute_detail.js";
|
||||
import type { CommandData, EventData, EventListener, FilteredCommandNames } from "../../components/app_context.js";
|
||||
import type { default as FAttribute, AttributeType } from "../../entities/fattribute.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import { escapeQuotes } from "../../services/utils.js";
|
||||
|
||||
const HELP_TEXT = `
|
||||
<p>${t("attribute_editor.help_text_body1")}</p>
|
||||
|
||||
<p>${t("attribute_editor.help_text_body2")}</p>
|
||||
|
||||
<p>${t("attribute_editor.help_text_body3")}</p>`;
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div style="position: relative; padding-top: 10px; padding-bottom: 10px">
|
||||
<style>
|
||||
.attribute-list-editor {
|
||||
border: 0 !important;
|
||||
outline: 0 !important;
|
||||
box-shadow: none !important;
|
||||
padding: 0 0 0 5px !important;
|
||||
margin: 0 !important;
|
||||
max-height: 100px;
|
||||
overflow: auto;
|
||||
transition: opacity .1s linear;
|
||||
}
|
||||
|
||||
.attribute-list-editor.ck-content .mention {
|
||||
color: var(--muted-text-color) !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.save-attributes-button {
|
||||
color: var(--muted-text-color);
|
||||
position: absolute;
|
||||
bottom: 14px;
|
||||
right: 25px;
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
font-size: 130%;
|
||||
}
|
||||
|
||||
.add-new-attribute-button {
|
||||
color: var(--muted-text-color);
|
||||
position: absolute;
|
||||
bottom: 13px;
|
||||
right: 0;
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
font-size: 130%;
|
||||
}
|
||||
|
||||
.add-new-attribute-button:hover, .save-attributes-button:hover {
|
||||
border: 1px solid var(--button-border-color);
|
||||
border-radius: var(--button-border-radius);
|
||||
background: var(--button-background-color);
|
||||
color: var(--button-text-color);
|
||||
}
|
||||
|
||||
.attribute-errors {
|
||||
color: red;
|
||||
padding: 5px 50px 0px 5px; /* large right padding to avoid buttons */
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="attribute-list-editor" tabindex="200"></div>
|
||||
|
||||
<div class="bx bx-save save-attributes-button tn-tool-button" title="${escapeQuotes(t("attribute_editor.save_attributes"))}"></div>
|
||||
<div class="bx bx-plus add-new-attribute-button tn-tool-button" title="${escapeQuotes(t("attribute_editor.add_a_new_attribute"))}"></div>
|
||||
|
||||
<div class="attribute-errors" style="display: none;"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const mentionSetup: MentionFeed[] = [
|
||||
{
|
||||
marker: "@",
|
||||
feed: (queryText) => noteAutocompleteService.autocompleteSourceForCKEditor(queryText),
|
||||
itemRenderer: (_item) => {
|
||||
const item = _item as Suggestion;
|
||||
const itemElement = document.createElement("button");
|
||||
|
||||
itemElement.innerHTML = `${item.highlightedNotePathTitle} `;
|
||||
|
||||
return itemElement;
|
||||
},
|
||||
minimumCharacters: 0
|
||||
},
|
||||
{
|
||||
marker: "#",
|
||||
feed: async (queryText) => {
|
||||
const names = await server.get<string[]>(`attribute-names/?type=label&query=${encodeURIComponent(queryText)}`);
|
||||
|
||||
return names.map((name) => {
|
||||
return {
|
||||
id: `#${name}`,
|
||||
name: name
|
||||
};
|
||||
});
|
||||
},
|
||||
minimumCharacters: 0
|
||||
},
|
||||
{
|
||||
marker: "~",
|
||||
feed: async (queryText) => {
|
||||
const names = await server.get<string[]>(`attribute-names/?type=relation&query=${encodeURIComponent(queryText)}`);
|
||||
|
||||
return names.map((name) => {
|
||||
return {
|
||||
id: `~${name}`,
|
||||
name: name
|
||||
};
|
||||
});
|
||||
},
|
||||
minimumCharacters: 0
|
||||
}
|
||||
];
|
||||
|
||||
const editorConfig: EditorConfig = {
|
||||
toolbar: {
|
||||
items: []
|
||||
},
|
||||
placeholder: t("attribute_editor.placeholder"),
|
||||
mention: {
|
||||
feeds: mentionSetup
|
||||
},
|
||||
licenseKey: "GPL"
|
||||
};
|
||||
|
||||
type AttributeCommandNames = FilteredCommandNames<CommandData>;
|
||||
|
||||
export default class AttributeEditorWidget extends NoteContextAwareWidget implements EventListener<"entitiesReloaded">, EventListener<"addNewLabel">, EventListener<"addNewRelation"> {
|
||||
private attributeDetailWidget: AttributeDetailWidget;
|
||||
private $editor!: JQuery<HTMLElement>;
|
||||
private $addNewAttributeButton!: JQuery<HTMLElement>;
|
||||
private $saveAttributesButton!: JQuery<HTMLElement>;
|
||||
private $errors!: JQuery<HTMLElement>;
|
||||
|
||||
private textEditor!: AttributeEditor;
|
||||
private lastUpdatedNoteId!: string | undefined;
|
||||
private lastSavedContent!: string;
|
||||
|
||||
constructor(attributeDetailWidget: AttributeDetailWidget) {
|
||||
super();
|
||||
|
||||
this.attributeDetailWidget = attributeDetailWidget;
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$editor = this.$widget.find(".attribute-list-editor");
|
||||
|
||||
this.initialized = this.initEditor();
|
||||
|
||||
this.$editor.on("keydown", async (e) => {
|
||||
if (e.which === 13) {
|
||||
// allow autocomplete to fill the result textarea
|
||||
setTimeout(() => this.save(), 100);
|
||||
}
|
||||
|
||||
this.attributeDetailWidget.hide();
|
||||
});
|
||||
|
||||
this.$editor.on("blur", () => setTimeout(() => this.save(), 100)); // Timeout to fix https://github.com/zadam/trilium/issues/4160
|
||||
|
||||
this.$addNewAttributeButton = this.$widget.find(".add-new-attribute-button");
|
||||
this.$addNewAttributeButton.on("click", (e) => this.addNewAttribute(e));
|
||||
|
||||
this.$saveAttributesButton = this.$widget.find(".save-attributes-button");
|
||||
this.$saveAttributesButton.on("click", () => this.save());
|
||||
|
||||
this.$errors = this.$widget.find(".attribute-errors");
|
||||
}
|
||||
|
||||
addNewAttribute(e: JQuery.ClickEvent) {
|
||||
contextMenuService.show<AttributeCommandNames>({
|
||||
x: e.pageX,
|
||||
y: e.pageY,
|
||||
orientation: "left",
|
||||
items: [
|
||||
{ title: t("attribute_editor.add_new_label"), command: "addNewLabel", uiIcon: "bx bx-hash" },
|
||||
{ title: t("attribute_editor.add_new_relation"), command: "addNewRelation", uiIcon: "bx bx-transfer" },
|
||||
{ title: "----" },
|
||||
{ title: t("attribute_editor.add_new_label_definition"), command: "addNewLabelDefinition", uiIcon: "bx bx-empty" },
|
||||
{ title: t("attribute_editor.add_new_relation_definition"), command: "addNewRelationDefinition", uiIcon: "bx bx-empty" }
|
||||
],
|
||||
selectMenuItemHandler: ({ command }) => this.handleAddNewAttributeCommand(command)
|
||||
});
|
||||
// Prevent automatic hiding of the context menu due to the button being clicked.
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
// triggered from keyboard shortcut
|
||||
async addNewLabelEvent({ ntxId }: EventData<"addNewLabel">) {
|
||||
if (this.isNoteContext(ntxId)) {
|
||||
await this.refresh();
|
||||
|
||||
this.handleAddNewAttributeCommand("addNewLabel");
|
||||
}
|
||||
}
|
||||
|
||||
// triggered from keyboard shortcut
|
||||
async addNewRelationEvent({ ntxId }: EventData<"addNewRelation">) {
|
||||
if (this.isNoteContext(ntxId)) {
|
||||
await this.refresh();
|
||||
|
||||
this.handleAddNewAttributeCommand("addNewRelation");
|
||||
}
|
||||
}
|
||||
|
||||
async handleAddNewAttributeCommand(command: AttributeCommandNames | undefined) {
|
||||
// TODO: Not sure what the relation between FAttribute[] and Attribute[] is.
|
||||
const attrs = this.parseAttributes() as FAttribute[];
|
||||
|
||||
if (!attrs) {
|
||||
return;
|
||||
}
|
||||
|
||||
let type: AttributeType;
|
||||
let name;
|
||||
let value;
|
||||
|
||||
if (command === "addNewLabel") {
|
||||
type = "label";
|
||||
name = "myLabel";
|
||||
value = "";
|
||||
} else if (command === "addNewRelation") {
|
||||
type = "relation";
|
||||
name = "myRelation";
|
||||
value = "";
|
||||
} else if (command === "addNewLabelDefinition") {
|
||||
type = "label";
|
||||
name = "label:myLabel";
|
||||
value = "promoted,single,text";
|
||||
} else if (command === "addNewRelationDefinition") {
|
||||
type = "label";
|
||||
name = "relation:myRelation";
|
||||
value = "promoted,single";
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Incomplete type
|
||||
//@ts-ignore
|
||||
attrs.push({
|
||||
type,
|
||||
name,
|
||||
value,
|
||||
isInheritable: false
|
||||
});
|
||||
|
||||
await this.renderOwnedAttributes(attrs, false);
|
||||
|
||||
this.$editor.scrollTop(this.$editor[0].scrollHeight);
|
||||
|
||||
const rect = this.$editor[0].getBoundingClientRect();
|
||||
|
||||
setTimeout(() => {
|
||||
// showing a little bit later because there's a conflict with outside click closing the attr detail
|
||||
this.attributeDetailWidget.showAttributeDetail({
|
||||
allAttributes: attrs,
|
||||
attribute: attrs[attrs.length - 1],
|
||||
isOwned: true,
|
||||
x: (rect.left + rect.right) / 2,
|
||||
y: rect.bottom,
|
||||
focus: "name"
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
|
||||
async save() {
|
||||
if (this.lastUpdatedNoteId !== this.noteId) {
|
||||
// https://github.com/zadam/trilium/issues/3090
|
||||
console.warn("Ignoring blur event because a different note is loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
const attributes = this.parseAttributes();
|
||||
|
||||
if (attributes) {
|
||||
await server.put(`notes/${this.noteId}/attributes`, attributes, this.componentId);
|
||||
|
||||
this.$saveAttributesButton.fadeOut();
|
||||
|
||||
// blink the attribute text to give a visual hint that save has been executed
|
||||
this.$editor.css("opacity", 0);
|
||||
|
||||
// revert back
|
||||
setTimeout(() => this.$editor.css("opacity", 1), 100);
|
||||
}
|
||||
}
|
||||
|
||||
parseAttributes() {
|
||||
try {
|
||||
return attributeParser.lexAndParse(this.getPreprocessedData());
|
||||
} catch (e: any) {
|
||||
this.$errors.text(e.message).slideDown();
|
||||
}
|
||||
}
|
||||
|
||||
getPreprocessedData() {
|
||||
const str = this.textEditor
|
||||
.getData()
|
||||
.replace(/<a[^>]+href="(#[A-Za-z0-9_/]*)"[^>]*>[^<]*<\/a>/g, "$1")
|
||||
.replace(/ /g, " "); // otherwise .text() below outputs non-breaking space in unicode
|
||||
|
||||
return $("<div>").html(str).text();
|
||||
}
|
||||
|
||||
async initEditor() {
|
||||
this.$widget.show();
|
||||
|
||||
this.$editor.on("click", (e) => this.handleEditorClick(e));
|
||||
|
||||
this.textEditor = await AttributeEditor.create(this.$editor[0], editorConfig);
|
||||
this.textEditor.model.document.on("change:data", () => this.dataChanged());
|
||||
this.textEditor.editing.view.document.on(
|
||||
"enter",
|
||||
(event, data) => {
|
||||
// disable entering new line - see https://github.com/ckeditor/ckeditor5/issues/9422
|
||||
data.preventDefault();
|
||||
event.stop();
|
||||
},
|
||||
{ priority: "high" }
|
||||
);
|
||||
|
||||
// disable spellcheck for attribute editor
|
||||
const documentRoot = this.textEditor.editing.view.document.getRoot();
|
||||
if (documentRoot) {
|
||||
this.textEditor.editing.view.change((writer) => writer.setAttribute("spellcheck", "false", documentRoot));
|
||||
}
|
||||
}
|
||||
|
||||
dataChanged() {
|
||||
this.lastUpdatedNoteId = this.noteId;
|
||||
|
||||
if (this.lastSavedContent === this.textEditor.getData()) {
|
||||
this.$saveAttributesButton.fadeOut();
|
||||
} else {
|
||||
this.$saveAttributesButton.fadeIn();
|
||||
}
|
||||
|
||||
if (this.$errors.is(":visible")) {
|
||||
// using .hide() instead of .slideUp() since this will also hide the error after confirming
|
||||
// mention for relation name which suits up. When using.slideUp() error will appear and the slideUp which is weird
|
||||
this.$errors.hide();
|
||||
}
|
||||
}
|
||||
|
||||
async handleEditorClick(e: JQuery.ClickEvent) {
|
||||
const pos = this.textEditor.model.document.selection.getFirstPosition();
|
||||
|
||||
if (pos && pos.textNode && pos.textNode.data) {
|
||||
const clickIndex = this.getClickIndex(pos);
|
||||
|
||||
let parsedAttrs;
|
||||
|
||||
try {
|
||||
parsedAttrs = attributeParser.lexAndParse(this.getPreprocessedData(), true);
|
||||
} catch (e) {
|
||||
// the input is incorrect because the user messed up with it and now needs to fix it manually
|
||||
return null;
|
||||
}
|
||||
|
||||
let matchedAttr: Attribute | null = null;
|
||||
|
||||
for (const attr of parsedAttrs) {
|
||||
if (attr.startIndex && clickIndex > attr.startIndex && attr.endIndex && clickIndex <= attr.endIndex) {
|
||||
matchedAttr = attr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (matchedAttr) {
|
||||
this.$editor.tooltip("hide");
|
||||
|
||||
this.attributeDetailWidget.showAttributeDetail({
|
||||
allAttributes: parsedAttrs,
|
||||
attribute: matchedAttr,
|
||||
isOwned: true,
|
||||
x: e.pageX,
|
||||
y: e.pageY
|
||||
});
|
||||
} else {
|
||||
this.showHelpTooltip();
|
||||
}
|
||||
}, 100);
|
||||
} else {
|
||||
this.showHelpTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
showHelpTooltip() {
|
||||
this.attributeDetailWidget.hide();
|
||||
|
||||
this.$editor.tooltip({
|
||||
trigger: "focus",
|
||||
html: true,
|
||||
title: HELP_TEXT,
|
||||
placement: "bottom",
|
||||
offset: "0,30"
|
||||
});
|
||||
|
||||
this.$editor.tooltip("show");
|
||||
}
|
||||
|
||||
getClickIndex(pos: ModelPosition) {
|
||||
let clickIndex = pos.offset - (pos.textNode?.startOffset ?? 0);
|
||||
|
||||
let curNode: ModelNode | Text | ModelElement | null = pos.textNode;
|
||||
|
||||
while (curNode?.previousSibling) {
|
||||
curNode = curNode.previousSibling;
|
||||
|
||||
if ((curNode as ModelElement).name === "reference") {
|
||||
clickIndex += (curNode.getAttribute("href") as string).length + 1;
|
||||
} else if ("data" in curNode) {
|
||||
clickIndex += (curNode.data as string).length;
|
||||
}
|
||||
}
|
||||
|
||||
return clickIndex;
|
||||
}
|
||||
|
||||
async loadReferenceLinkTitle($el: JQuery<HTMLElement>, href: string) {
|
||||
const { noteId } = linkService.parseNavigationStateFromUrl(href);
|
||||
const note = noteId ? await froca.getNote(noteId, true) : null;
|
||||
const title = note ? note.title : "[missing]";
|
||||
|
||||
$el.text(title);
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote) {
|
||||
await this.renderOwnedAttributes(note.getOwnedAttributes(), true);
|
||||
}
|
||||
|
||||
async renderOwnedAttributes(ownedAttributes: FAttribute[], saved: boolean) {
|
||||
// attrs are not resorted if position changes after the initial load
|
||||
ownedAttributes.sort((a, b) => a.position - b.position);
|
||||
|
||||
let htmlAttrs = (await attributeRenderer.renderAttributes(ownedAttributes, true)).html();
|
||||
|
||||
if (htmlAttrs.length > 0) {
|
||||
htmlAttrs += " ";
|
||||
}
|
||||
|
||||
this.textEditor.setData(htmlAttrs);
|
||||
|
||||
if (saved) {
|
||||
this.lastSavedContent = this.textEditor.getData();
|
||||
|
||||
this.$saveAttributesButton.fadeOut(0);
|
||||
}
|
||||
}
|
||||
|
||||
async createNoteForReferenceLink(title: string) {
|
||||
let result;
|
||||
if (this.notePath) {
|
||||
result = await noteCreateService.createNoteWithTypePrompt(this.notePath, {
|
||||
activate: false,
|
||||
title: title
|
||||
});
|
||||
}
|
||||
|
||||
return result?.note?.getBestNotePathString();
|
||||
}
|
||||
|
||||
async updateAttributeList(attributes: FAttribute[]) {
|
||||
await this.renderOwnedAttributes(attributes, false);
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.$editor.trigger("focus");
|
||||
|
||||
this.textEditor.model.change((writer) => {
|
||||
const documentRoot = this.textEditor.editing.model.document.getRoot();
|
||||
if (!documentRoot) {
|
||||
return;
|
||||
}
|
||||
|
||||
const positionAt = writer.createPositionAt(documentRoot, "end");
|
||||
writer.setSelection(positionAt);
|
||||
});
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
if (loadResults.getAttributeRows(this.componentId).find((attr) => attributeService.isAffecting(attr, this.note))) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
import { isValidElement, VNode } from "preact";
|
||||
import Component, { TypedComponent } from "../components/component.js";
|
||||
import froca from "../services/froca.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import toastService from "../services/toast.js";
|
||||
import { renderReactWidget } from "./react/react_utils.jsx";
|
||||
import { EventNames, EventData } from "../components/app_context.js";
|
||||
import { Handler } from "leaflet";
|
||||
|
||||
export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedComponent<T> {
|
||||
protected attrs: Record<string, string>;
|
||||
@@ -22,11 +26,14 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
|
||||
this.childPositionCounter = 10;
|
||||
}
|
||||
|
||||
child(...components: T[]) {
|
||||
if (!components) {
|
||||
child(..._components: (T | VNode)[]) {
|
||||
if (!_components) {
|
||||
return this;
|
||||
}
|
||||
|
||||
// Convert any React components to legacy wrapped components.
|
||||
const components = wrapReactWidgets(_components);
|
||||
|
||||
super.child(...components);
|
||||
|
||||
for (const component of components) {
|
||||
@@ -258,3 +265,30 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
|
||||
* For information on using widgets, see the tutorial {@tutorial widget_basics}.
|
||||
*/
|
||||
export default class BasicWidget extends TypedBasicWidget<Component> {}
|
||||
|
||||
export function wrapReactWidgets<T extends TypedComponent<any>>(components: (T | VNode)[]) {
|
||||
const wrappedResult: T[] = [];
|
||||
for (const component of components) {
|
||||
if (isValidElement(component)) {
|
||||
wrappedResult.push(new ReactWrappedWidget(component) as unknown as T);
|
||||
} else {
|
||||
wrappedResult.push(component);
|
||||
}
|
||||
}
|
||||
return wrappedResult;
|
||||
}
|
||||
|
||||
export class ReactWrappedWidget extends BasicWidget {
|
||||
|
||||
private el: VNode;
|
||||
|
||||
constructor(el: VNode) {
|
||||
super();
|
||||
this.el = el;
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = renderReactWidget(this, this.el);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import SwitchWidget from "./switch.js";
|
||||
import server from "../services/server.js";
|
||||
import toastService from "../services/toast.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import type { EventData } from "../components/app_context.js";
|
||||
|
||||
// TODO: Deduplicate
|
||||
type Response = {
|
||||
success: true;
|
||||
} | {
|
||||
success: false;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export default class BookmarkSwitchWidget extends SwitchWidget {
|
||||
isEnabled() {
|
||||
return (
|
||||
super.isEnabled() &&
|
||||
// it's not possible to bookmark root because that would clone it under bookmarks and thus create a cycle
|
||||
!["root", "_hidden"].includes(this.noteId ?? "")
|
||||
);
|
||||
}
|
||||
|
||||
doRender() {
|
||||
super.doRender();
|
||||
|
||||
this.switchOnName = t("bookmark_switch.bookmark");
|
||||
this.switchOnTooltip = t("bookmark_switch.bookmark_this_note");
|
||||
|
||||
this.switchOffName = t("bookmark_switch.bookmark");
|
||||
this.switchOffTooltip = t("bookmark_switch.remove_bookmark");
|
||||
}
|
||||
|
||||
async toggle(state: boolean | null | undefined) {
|
||||
const resp = await server.put<Response>(`notes/${this.noteId}/toggle-in-parent/_lbBookmarks/${!!state}`);
|
||||
|
||||
if (!resp.success && "message" in resp) {
|
||||
toastService.showError(resp.message);
|
||||
}
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote) {
|
||||
const isBookmarked = !!note.getParentBranches().find((b) => b.parentNoteId === "_lbBookmarks");
|
||||
|
||||
this.isToggled = isBookmarked;
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
if (loadResults.getBranchRows().find((b) => b.noteId === this.noteId)) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ComponentChildren } from "preact";
|
||||
import { memo } from "preact/compat";
|
||||
import AbstractBulkAction from "./abstract_bulk_action";
|
||||
import HelpRemoveButtons from "../react/HelpRemoveButtons";
|
||||
|
||||
interface BulkActionProps {
|
||||
label: string | ComponentChildren;
|
||||
@@ -24,19 +25,11 @@ const BulkAction = memo(({ label, children, helpText, bulkAction }: BulkActionPr
|
||||
{children}
|
||||
</div>
|
||||
</td>
|
||||
<td className="button-column">
|
||||
{helpText && <div className="dropdown help-dropdown">
|
||||
<span className="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<div className="dropdown-menu dropdown-menu-right p-4">
|
||||
{helpText}
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
<span
|
||||
className="bx bx-x icon-action action-conf-del"
|
||||
onClick={() => bulkAction?.deleteAction()}
|
||||
/>
|
||||
</td>
|
||||
<HelpRemoveButtons
|
||||
help={helpText}
|
||||
removeText="Delete"
|
||||
onRemove={() => bulkAction?.deleteAction()}
|
||||
/>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { t } from "../../../services/i18n.js";
|
||||
import AbstractBulkAction, { ActionDefinition } from "../abstract_bulk_action.js";
|
||||
import AbstractBulkAction from "../abstract_bulk_action.js";
|
||||
import BulkAction, { BulkActionText } from "../BulkAction.jsx";
|
||||
import NoteAutocomplete from "../../react/NoteAutocomplete.jsx";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import { useSpacedUpdate } from "../../react/hooks.jsx";
|
||||
|
||||
function MoveNoteBulkActionComponent({ bulkAction, actionDef }: { bulkAction: AbstractBulkAction, actionDef: ActionDefinition }) {
|
||||
function MoveNoteBulkActionComponent({ bulkAction }: { bulkAction: AbstractBulkAction }) {
|
||||
const [ targetParentNoteId, setTargetParentNoteId ] = useState<string>();
|
||||
const spacedUpdate = useSpacedUpdate(() => {
|
||||
return bulkAction.saveAction({ targetParentNoteId: targetParentNoteId })
|
||||
@@ -45,6 +45,6 @@ export default class MoveNoteBulkAction extends AbstractBulkAction {
|
||||
}
|
||||
|
||||
doRender() {
|
||||
return <MoveNoteBulkActionComponent bulkAction={this} actionDef={this.actionDef} />
|
||||
return <MoveNoteBulkActionComponent bulkAction={this} />
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import SpacedUpdate from "../../../services/spaced_update.js";
|
||||
import AbstractBulkAction, { ActionDefinition } from "../abstract_bulk_action.js";
|
||||
import { t } from "../../../services/i18n.js";
|
||||
import BulkAction from "../BulkAction.jsx";
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import SpacedUpdate from "../../../services/spaced_update.js";
|
||||
import AbstractBulkAction, { ActionDefinition } from "../abstract_bulk_action.js";
|
||||
import noteAutocompleteService from "../../../services/note_autocomplete.js";
|
||||
import { t } from "../../../services/i18n.js";
|
||||
import BulkAction, { BulkActionText } from "../BulkAction.jsx";
|
||||
import NoteAutocomplete from "../../react/NoteAutocomplete.jsx";
|
||||
|
||||
@@ -414,7 +414,7 @@ export default class GlobalMenuWidget extends BasicWidget {
|
||||
}
|
||||
|
||||
async fetchLatestVersion() {
|
||||
const RELEASES_API_URL = "https://api.github.com/repos/TriliumNext/Notes/releases/latest";
|
||||
const RELEASES_API_URL = "https://api.github.com/repos/TriliumNext/Trilium/releases/latest";
|
||||
|
||||
const resp = await fetch(RELEASES_API_URL);
|
||||
const data = await resp.json();
|
||||
|
||||
@@ -1,252 +0,0 @@
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import branchService from "../../services/branches.js";
|
||||
import dialogService from "../../services/dialog.js";
|
||||
import server from "../../services/server.js";
|
||||
import toastService from "../../services/toast.js";
|
||||
import ws from "../../services/ws.js";
|
||||
import appContext, { type EventData } from "../../components/app_context.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import type { FAttachmentRow } from "../../entities/fattachment.js";
|
||||
|
||||
// TODO: Deduplicate with server
|
||||
interface ConvertToAttachmentResponse {
|
||||
attachment: FAttachmentRow;
|
||||
}
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="dropdown note-actions">
|
||||
<style>
|
||||
.note-actions {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.note-actions .dropdown-menu {
|
||||
min-width: 15em;
|
||||
}
|
||||
|
||||
.note-actions .dropdown-item .bx {
|
||||
position: relative;
|
||||
top: 3px;
|
||||
font-size: 120%;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.note-actions .dropdown-item[disabled], .note-actions .dropdown-item[disabled]:hover {
|
||||
color: var(--muted-text-color) !important;
|
||||
background-color: transparent !important;
|
||||
pointer-events: none; /* makes it unclickable */
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<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">
|
||||
<li data-trigger-command="convertNoteIntoAttachment" class="dropdown-item">
|
||||
<span class="bx bx-paperclip"></span> ${t("note_actions.convert_into_attachment")}
|
||||
</li>
|
||||
|
||||
<li data-trigger-command="renderActiveNote" class="dropdown-item render-note-button">
|
||||
<span class="bx bx-extension"></span> ${t("note_actions.re_render_note")}<kbd data-command="renderActiveNote"></kbd>
|
||||
</li>
|
||||
|
||||
<li data-trigger-command="findInText" class="dropdown-item find-in-text-button">
|
||||
<span class='bx bx-search'></span> ${t("note_actions.search_in_note")}<kbd data-command="findInText"></kbd>
|
||||
</li>
|
||||
|
||||
<li data-trigger-command="printActiveNote" class="dropdown-item print-active-note-button">
|
||||
<span class="bx bx-printer"></span> ${t("note_actions.print_note")}<kbd data-command="printActiveNote"></kbd>
|
||||
</li>
|
||||
|
||||
<li data-trigger-command="exportAsPdf" class="dropdown-item export-as-pdf-button">
|
||||
<span class="bx bxs-file-pdf"></span> ${t("note_actions.print_pdf")}<kbd data-command="exportAsPdf"></kbd>
|
||||
</li>
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
|
||||
|
||||
<li class="dropdown-item import-files-button"><span class="bx bx-import"></span> ${t("note_actions.import_files")}</li>
|
||||
|
||||
<li class="dropdown-item export-note-button"><span class="bx bx-export"></span> ${t("note_actions.export_note")}</li>
|
||||
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
|
||||
|
||||
|
||||
<li data-trigger-command="openNoteExternally" class="dropdown-item open-note-externally-button" title="${t("note_actions.open_note_externally_title")}">
|
||||
<span class="bx bx-file-find"></span> ${t("note_actions.open_note_externally")}<kbd data-command="openNoteExternally"></kbd>
|
||||
</li>
|
||||
|
||||
<li data-trigger-command="openNoteCustom" class="dropdown-item open-note-custom-button">
|
||||
<span class="bx bx-customize"></span> ${t("note_actions.open_note_custom")}<kbd data-command="openNoteCustom"></kbd>
|
||||
</li>
|
||||
|
||||
<li data-trigger-command="showNoteSource" class="dropdown-item show-source-button">
|
||||
<span class="bx bx-code"></span> ${t("note_actions.note_source")}<kbd data-command="showNoteSource"></kbd>
|
||||
</li>
|
||||
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
|
||||
|
||||
<li data-trigger-command="forceSaveRevision" class="dropdown-item save-revision-button">
|
||||
<span class="bx bx-save"></span> ${t("note_actions.save_revision")}<kbd data-command="forceSaveRevision"></kbd>
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item delete-note-button"><span class="bx bx-trash destructive-action-icon"></span> ${t("note_actions.delete_note")}</li>
|
||||
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
|
||||
|
||||
<li data-trigger-command="showAttachments" class="dropdown-item show-attachments-button">
|
||||
<span class="bx bx-paperclip"></span> ${t("note_actions.note_attachments")}<kbd data-command="showAttachments"></kbd>
|
||||
</li>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
export default class NoteActionsWidget extends NoteContextAwareWidget {
|
||||
|
||||
private $convertNoteIntoAttachmentButton!: JQuery<HTMLElement>;
|
||||
private $findInTextButton!: JQuery<HTMLElement>;
|
||||
private $printActiveNoteButton!: JQuery<HTMLElement>;
|
||||
private $exportAsPdfButton!: JQuery<HTMLElement>;
|
||||
private $showSourceButton!: JQuery<HTMLElement>;
|
||||
private $showAttachmentsButton!: JQuery<HTMLElement>;
|
||||
private $renderNoteButton!: JQuery<HTMLElement>;
|
||||
private $saveRevisionButton!: JQuery<HTMLElement>;
|
||||
private $exportNoteButton!: JQuery<HTMLElement>;
|
||||
private $importNoteButton!: JQuery<HTMLElement>;
|
||||
private $openNoteExternallyButton!: JQuery<HTMLElement>;
|
||||
private $openNoteCustomButton!: JQuery<HTMLElement>;
|
||||
private $deleteNoteButton!: JQuery<HTMLElement>;
|
||||
|
||||
isEnabled() {
|
||||
return this.note?.type !== "launcher";
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$widget.on("show.bs.dropdown", () => {
|
||||
if (this.note) {
|
||||
this.refreshVisibility(this.note);
|
||||
}
|
||||
});
|
||||
|
||||
this.$convertNoteIntoAttachmentButton = this.$widget.find("[data-trigger-command='convertNoteIntoAttachment']");
|
||||
this.$findInTextButton = this.$widget.find(".find-in-text-button");
|
||||
this.$printActiveNoteButton = this.$widget.find(".print-active-note-button");
|
||||
this.$exportAsPdfButton = this.$widget.find(".export-as-pdf-button");
|
||||
this.$showSourceButton = this.$widget.find(".show-source-button");
|
||||
this.$showAttachmentsButton = this.$widget.find(".show-attachments-button");
|
||||
this.$renderNoteButton = this.$widget.find(".render-note-button");
|
||||
this.$saveRevisionButton = this.$widget.find(".save-revision-button");
|
||||
|
||||
this.$exportNoteButton = this.$widget.find(".export-note-button");
|
||||
this.$exportNoteButton.on("click", () => {
|
||||
if (this.$exportNoteButton.hasClass("disabled") || !this.noteContext?.notePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.triggerCommand("showExportDialog", {
|
||||
notePath: this.noteContext.notePath,
|
||||
defaultType: "single"
|
||||
});
|
||||
});
|
||||
|
||||
this.$importNoteButton = this.$widget.find(".import-files-button");
|
||||
this.$importNoteButton.on("click", () => {
|
||||
if (this.noteId) {
|
||||
this.triggerCommand("showImportDialog", { noteId: this.noteId });
|
||||
}
|
||||
});
|
||||
|
||||
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");
|
||||
|
||||
this.$deleteNoteButton = this.$widget.find(".delete-note-button");
|
||||
this.$deleteNoteButton.on("click", () => {
|
||||
if (!this.note || this.note.noteId === "root") {
|
||||
return;
|
||||
}
|
||||
|
||||
branchService.deleteNotes([this.note.getParentBranches()[0].branchId], true);
|
||||
});
|
||||
}
|
||||
|
||||
async refreshVisibility(note: FNote) {
|
||||
const isInOptions = note.noteId.startsWith("_options");
|
||||
|
||||
this.$convertNoteIntoAttachmentButton.toggle(note.isEligibleForConversionToAttachment());
|
||||
|
||||
this.toggleDisabled(this.$findInTextButton, ["text", "code", "book", "mindMap", "doc"].includes(note.type));
|
||||
|
||||
this.toggleDisabled(this.$showAttachmentsButton, !isInOptions);
|
||||
this.toggleDisabled(this.$showSourceButton, ["text", "code", "relationMap", "mermaid", "canvas", "mindMap"].includes(note.type));
|
||||
|
||||
const canPrint = ["text", "code"].includes(note.type);
|
||||
this.toggleDisabled(this.$printActiveNoteButton, canPrint);
|
||||
this.toggleDisabled(this.$exportAsPdfButton, canPrint);
|
||||
this.$exportAsPdfButton.toggleClass("hidden-ext", !utils.isElectron());
|
||||
|
||||
this.$renderNoteButton.toggle(note.type === "render");
|
||||
|
||||
this.toggleDisabled(this.$openNoteExternallyButton, utils.isElectron() && !["search", "book"].includes(note.type));
|
||||
this.toggleDisabled(
|
||||
this.$openNoteCustomButton,
|
||||
utils.isElectron() &&
|
||||
!utils.isMac() && // no implementation for Mac yet
|
||||
!["search", "book"].includes(note.type)
|
||||
);
|
||||
|
||||
// I don't want to handle all special notes like this, but intuitively user might want to export content of backend log
|
||||
this.toggleDisabled(this.$exportNoteButton, !["_backendLog"].includes(note.noteId) && !isInOptions);
|
||||
|
||||
this.toggleDisabled(this.$importNoteButton, !["search"].includes(note.type) && !isInOptions);
|
||||
this.toggleDisabled(this.$deleteNoteButton, !isInOptions);
|
||||
this.toggleDisabled(this.$saveRevisionButton, !isInOptions);
|
||||
}
|
||||
|
||||
async convertNoteIntoAttachmentCommand() {
|
||||
if (!this.note || !(await dialogService.confirm(t("note_actions.convert_into_attachment_prompt", { title: this.note.title })))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { attachment: newAttachment } = await server.post<ConvertToAttachmentResponse>(`notes/${this.noteId}/convert-to-attachment`);
|
||||
|
||||
if (!newAttachment) {
|
||||
toastService.showMessage(t("note_actions.convert_into_attachment_failed", { title: this.note.title }));
|
||||
return;
|
||||
}
|
||||
|
||||
toastService.showMessage(t("note_actions.convert_into_attachment_successful", { title: newAttachment.title }));
|
||||
await ws.waitForMaxKnownEntityChangeId();
|
||||
await appContext.tabManager.getActiveContext()?.setNote(newAttachment.ownerId, {
|
||||
viewScope: {
|
||||
viewMode: "attachments",
|
||||
attachmentId: newAttachment.attachmentId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
toggleDisabled($el: JQuery<HTMLElement>, enable: boolean) {
|
||||
if (enable) {
|
||||
$el.removeAttr("disabled");
|
||||
} else {
|
||||
$el.attr("disabled", "disabled");
|
||||
}
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
if (loadResults.isNoteReloaded(this.noteId)) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { t } from "../../services/i18n.js";
|
||||
import CommandButtonWidget from "./command_button.js";
|
||||
|
||||
export default class RevisionsButton extends CommandButtonWidget {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.icon("bx-history").title(t("revisions_button.note_revisions")).command("showRevisions").titlePlacement("bottom").class("icon-action");
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return super.isEnabled() && !["launcher", "doc"].includes(this.note?.type ?? "");
|
||||
}
|
||||
}
|
||||
@@ -1,388 +0,0 @@
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
import keyboardActionsService from "../../services/keyboard_actions.js";
|
||||
import attributeService from "../../services/attributes.js";
|
||||
import type CommandButtonWidget from "../buttons/command_button.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import type { NoteType } from "../../entities/fnote.js";
|
||||
import type { EventData, EventNames } from "../../components/app_context.js";
|
||||
import type NoteActionsWidget from "../buttons/note_actions.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="ribbon-container">
|
||||
<style>
|
||||
.ribbon-container {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.ribbon-top-row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ribbon-tab-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
margin-left: 10px;
|
||||
flex-grow: 1;
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
|
||||
.ribbon-tab-title {
|
||||
color: var(--muted-text-color);
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
min-width: 24px;
|
||||
flex-basis: 24px;
|
||||
max-width: max-content;
|
||||
flex-grow: 10;
|
||||
}
|
||||
|
||||
.ribbon-tab-title .bx {
|
||||
font-size: 150%;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
.ribbon-tab-title.active {
|
||||
color: var(--main-text-color);
|
||||
border-bottom: 3px solid var(--main-text-color);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.ribbon-tab-title:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ribbon-tab-title:hover {
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
|
||||
.ribbon-tab-title:first-of-type {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.ribbon-tab-spacer {
|
||||
flex-basis: 0;
|
||||
min-width: 0;
|
||||
max-width: 35px;
|
||||
flex-grow: 1;
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
}
|
||||
|
||||
.ribbon-tab-spacer:last-of-type {
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
min-width: 0;
|
||||
max-width: 10000px;
|
||||
}
|
||||
|
||||
.ribbon-button-container {
|
||||
display: flex;
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.ribbon-button-container > * {
|
||||
position: relative;
|
||||
top: -3px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.ribbon-body {
|
||||
display: none;
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
margin-left: 10px;
|
||||
margin-right: 5px; /* needs to have this value so that the bottom border is the same width as the top one */
|
||||
}
|
||||
|
||||
.ribbon-body.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ribbon-tab-title-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ribbon-tab-title.active .ribbon-tab-title-label {
|
||||
display: inline;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="ribbon-top-row">
|
||||
<div class="ribbon-tab-container"></div>
|
||||
<div class="ribbon-button-container"></div>
|
||||
</div>
|
||||
|
||||
<div class="ribbon-body-container"></div>
|
||||
</div>`;
|
||||
|
||||
type ButtonWidget = (CommandButtonWidget | NoteActionsWidget);
|
||||
|
||||
export default class RibbonContainer extends NoteContextAwareWidget {
|
||||
|
||||
private lastActiveComponentId?: string | null;
|
||||
private lastNoteType?: NoteType;
|
||||
|
||||
private ribbonWidgets: NoteContextAwareWidget[];
|
||||
private buttonWidgets: ButtonWidget[];
|
||||
private $tabContainer!: JQuery<HTMLElement>;
|
||||
private $buttonContainer!: JQuery<HTMLElement>;
|
||||
private $bodyContainer!: JQuery<HTMLElement>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.contentSized();
|
||||
this.ribbonWidgets = [];
|
||||
this.buttonWidgets = [];
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return super.isEnabled() && this.noteContext?.viewScope?.viewMode === "default";
|
||||
}
|
||||
|
||||
ribbon(widget: NoteContextAwareWidget) {
|
||||
// TODO: Base class
|
||||
super.child(widget);
|
||||
|
||||
this.ribbonWidgets.push(widget);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
button(widget: ButtonWidget) {
|
||||
super.child(widget);
|
||||
|
||||
this.buttonWidgets.push(widget);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
|
||||
this.$tabContainer = this.$widget.find(".ribbon-tab-container");
|
||||
this.$buttonContainer = this.$widget.find(".ribbon-button-container");
|
||||
this.$bodyContainer = this.$widget.find(".ribbon-body-container");
|
||||
|
||||
for (const ribbonWidget of this.ribbonWidgets) {
|
||||
this.$bodyContainer.append($('<div class="ribbon-body">').attr("data-ribbon-component-id", ribbonWidget.componentId).append(ribbonWidget.render()));
|
||||
}
|
||||
|
||||
for (const buttonWidget of this.buttonWidgets) {
|
||||
this.$buttonContainer.append(buttonWidget.render());
|
||||
}
|
||||
|
||||
this.$tabContainer.on("click", ".ribbon-tab-title", (e) => {
|
||||
const $ribbonTitle = $(e.target).closest(".ribbon-tab-title");
|
||||
|
||||
this.toggleRibbonTab($ribbonTitle);
|
||||
});
|
||||
}
|
||||
|
||||
toggleRibbonTab($ribbonTitle: JQuery<HTMLElement>, refreshActiveTab = true) {
|
||||
const activate = !$ribbonTitle.hasClass("active");
|
||||
|
||||
this.$tabContainer.find(".ribbon-tab-title").removeClass("active");
|
||||
this.$bodyContainer.find(".ribbon-body").removeClass("active");
|
||||
|
||||
if (activate) {
|
||||
const ribbonComponendId = $ribbonTitle.attr("data-ribbon-component-id");
|
||||
|
||||
const wasAlreadyActive = this.lastActiveComponentId === ribbonComponendId;
|
||||
|
||||
this.lastActiveComponentId = ribbonComponendId;
|
||||
|
||||
this.$tabContainer.find(`.ribbon-tab-title[data-ribbon-component-id="${ribbonComponendId}"]`).addClass("active");
|
||||
this.$bodyContainer.find(`.ribbon-body[data-ribbon-component-id="${ribbonComponendId}"]`).addClass("active");
|
||||
|
||||
const activeChild = this.getActiveRibbonWidget();
|
||||
|
||||
if (activeChild && (refreshActiveTab || !wasAlreadyActive) && this.noteContext && this.notePath) {
|
||||
const handleEventPromise = activeChild.handleEvent("noteSwitched", { noteContext: this.noteContext, notePath: this.notePath });
|
||||
|
||||
if (refreshActiveTab) {
|
||||
if (handleEventPromise) {
|
||||
handleEventPromise.then(() => (activeChild as any).focus?.()); // TODO: Base class
|
||||
} else {
|
||||
// TODO: Base class
|
||||
(activeChild as any).focus?.();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.lastActiveComponentId = null;
|
||||
}
|
||||
}
|
||||
|
||||
async noteSwitched() {
|
||||
this.lastActiveComponentId = null;
|
||||
|
||||
await super.noteSwitched();
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote, noExplicitActivation = false) {
|
||||
this.lastNoteType = note.type;
|
||||
|
||||
let $ribbonTabToActivate, $lastActiveRibbon;
|
||||
|
||||
this.$tabContainer.empty();
|
||||
|
||||
for (const ribbonWidget of this.ribbonWidgets) {
|
||||
// TODO: Base class for ribbon widget
|
||||
const ret = await (ribbonWidget as any).getTitle(note);
|
||||
|
||||
if (!ret.show) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const $ribbonTitle = $('<div class="ribbon-tab-title">')
|
||||
.attr("data-ribbon-component-id", ribbonWidget.componentId)
|
||||
.attr("data-ribbon-component-name", (ribbonWidget as any).name as string) // TODO: base class for ribbon widgets
|
||||
.append(
|
||||
$('<span class="ribbon-tab-title-icon">')
|
||||
.addClass(ret.icon)
|
||||
.attr("title", ret.title)
|
||||
.attr("data-toggle-command", (ribbonWidget as any).toggleCommand)
|
||||
) // TODO: base class
|
||||
.append(" ")
|
||||
.append($('<span class="ribbon-tab-title-label">').text(ret.title));
|
||||
|
||||
this.$tabContainer.append($ribbonTitle);
|
||||
this.$tabContainer.append('<div class="ribbon-tab-spacer">');
|
||||
|
||||
if (ret.activate && !this.lastActiveComponentId && !$ribbonTabToActivate && !noExplicitActivation) {
|
||||
$ribbonTabToActivate = $ribbonTitle;
|
||||
}
|
||||
|
||||
if (this.lastActiveComponentId === ribbonWidget.componentId) {
|
||||
$lastActiveRibbon = $ribbonTitle;
|
||||
}
|
||||
}
|
||||
|
||||
keyboardActionsService.getActions().then((actions) => {
|
||||
this.$tabContainer.find(".ribbon-tab-title-icon").tooltip({
|
||||
title: () => {
|
||||
const toggleCommandName = $(this).attr("data-toggle-command");
|
||||
const action = actions.find((act) => act.actionName === toggleCommandName);
|
||||
const title = $(this).attr("data-title");
|
||||
|
||||
if (action?.effectiveShortcuts && action.effectiveShortcuts.length > 0) {
|
||||
return `${title} (${action.effectiveShortcuts.join(", ")})`;
|
||||
} else {
|
||||
return title ?? "";
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (!$ribbonTabToActivate) {
|
||||
$ribbonTabToActivate = $lastActiveRibbon;
|
||||
}
|
||||
|
||||
if ($ribbonTabToActivate) {
|
||||
this.toggleRibbonTab($ribbonTabToActivate, false);
|
||||
} else {
|
||||
this.$bodyContainer.find(".ribbon-body").removeClass("active");
|
||||
}
|
||||
}
|
||||
|
||||
isRibbonTabActive(name: string) {
|
||||
const $ribbonComponent = this.$widget.find(`.ribbon-tab-title[data-ribbon-component-name='${name}']`);
|
||||
|
||||
return $ribbonComponent.hasClass("active");
|
||||
}
|
||||
|
||||
ensureOwnedAttributesAreOpen(ntxId: string | null | undefined) {
|
||||
if (ntxId && this.isNoteContext(ntxId) && !this.isRibbonTabActive("ownedAttributes")) {
|
||||
this.toggleRibbonTabWithName("ownedAttributes", ntxId);
|
||||
}
|
||||
}
|
||||
|
||||
addNewLabelEvent({ ntxId }: EventData<"addNewLabel">) {
|
||||
this.ensureOwnedAttributesAreOpen(ntxId);
|
||||
}
|
||||
|
||||
addNewRelationEvent({ ntxId }: EventData<"addNewRelation">) {
|
||||
this.ensureOwnedAttributesAreOpen(ntxId);
|
||||
}
|
||||
|
||||
toggleRibbonTabWithName(name: string, ntxId?: string) {
|
||||
if (!this.isNoteContext(ntxId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const $ribbonComponent = this.$widget.find(`.ribbon-tab-title[data-ribbon-component-name='${name}']`);
|
||||
|
||||
if ($ribbonComponent) {
|
||||
this.toggleRibbonTab($ribbonComponent);
|
||||
}
|
||||
}
|
||||
|
||||
handleEvent<T extends EventNames>(name: T, data: EventData<T>) {
|
||||
const PREFIX = "toggleRibbonTab";
|
||||
|
||||
if (name.startsWith(PREFIX)) {
|
||||
let componentName = name.substr(PREFIX.length);
|
||||
componentName = componentName[0].toLowerCase() + componentName.substr(1);
|
||||
|
||||
this.toggleRibbonTabWithName(componentName, (data as any).ntxId);
|
||||
} else {
|
||||
return super.handleEvent(name, data);
|
||||
}
|
||||
}
|
||||
|
||||
async handleEventInChildren<T extends EventNames>(name: T, data: EventData<T>) {
|
||||
if (["activeContextChanged", "setNoteContext"].includes(name)) {
|
||||
// won't trigger .refresh();
|
||||
await super.handleEventInChildren("setNoteContext", data as EventData<"activeContextChanged" | "setNoteContext">);
|
||||
} else if (this.isEnabled() || name === "initialRenderComplete") {
|
||||
const activeRibbonWidget = this.getActiveRibbonWidget();
|
||||
|
||||
// forward events only to active ribbon tab, inactive ones don't need to be updated
|
||||
if (activeRibbonWidget) {
|
||||
await activeRibbonWidget.handleEvent(name, data);
|
||||
}
|
||||
|
||||
for (const buttonWidget of this.buttonWidgets) {
|
||||
await buttonWidget.handleEvent(name, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
if (!this.note) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.noteId && loadResults.isNoteReloaded(this.noteId) && this.lastNoteType !== this.note.type) {
|
||||
// note type influences the list of available ribbon tabs the most
|
||||
// check for the type is so that we don't update on each title rename
|
||||
this.lastNoteType = this.note.type;
|
||||
|
||||
this.refresh();
|
||||
} else if (loadResults.getAttributeRows(this.componentId).find((attr) => attributeService.isAffecting(attr, this.note))) {
|
||||
this.refreshWithNote(this.note, true);
|
||||
}
|
||||
}
|
||||
|
||||
async noteTypeMimeChangedEvent() {
|
||||
// We are ignoring the event which triggers a refresh since it is usually already done by a different
|
||||
// event and causing a race condition in which the items appear twice.
|
||||
}
|
||||
|
||||
/**
|
||||
* Executed as soon as the user presses the "Edit" floating button in a read-only text note.
|
||||
*
|
||||
* <p>
|
||||
* We need to refresh the ribbon for cases such as the classic editor which relies on the read-only state.
|
||||
*/
|
||||
readOnlyTemporarilyDisabledEvent() {
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
getActiveRibbonWidget() {
|
||||
return this.ribbonWidgets.find((ch) => ch.componentId === this.lastActiveComponentId);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import utils from "../../services/utils.js";
|
||||
import type BasicWidget from "../basic_widget.js";
|
||||
import { EventData } from "../../components/app_context.js";
|
||||
import FlexContainer from "./flex_container.js";
|
||||
import options from "../../services/options.js";
|
||||
import type BasicWidget from "../basic_widget.js";
|
||||
import utils from "../../services/utils.js";
|
||||
|
||||
/**
|
||||
* The root container is the top-most widget/container, from which the entire layout derives.
|
||||
@@ -27,15 +29,45 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
|
||||
window.visualViewport?.addEventListener("resize", () => this.#onMobileResize());
|
||||
}
|
||||
|
||||
this.#setMotion(options.is("motionEnabled"));
|
||||
this.#setShadows(options.is("shadowsEnabled"));
|
||||
this.#setBackdropEffects(options.is("backdropEffectsEnabled"));
|
||||
|
||||
return super.render();
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
if (loadResults.isOptionReloaded("motionEnabled")) {
|
||||
this.#setMotion(options.is("motionEnabled"));
|
||||
}
|
||||
|
||||
if (loadResults.isOptionReloaded("shadowsEnabled")) {
|
||||
this.#setShadows(options.is("shadowsEnabled"));
|
||||
}
|
||||
|
||||
if (loadResults.isOptionReloaded("backdropEffectsEnabled")) {
|
||||
this.#setBackdropEffects(options.is("backdropEffectsEnabled"));
|
||||
}
|
||||
}
|
||||
|
||||
#onMobileResize() {
|
||||
const currentViewportHeight = getViewportHeight();
|
||||
const isKeyboardOpened = (currentViewportHeight < this.originalViewportHeight);
|
||||
this.$widget.toggleClass("virtual-keyboard-opened", isKeyboardOpened);
|
||||
}
|
||||
|
||||
#setMotion(enabled: boolean) {
|
||||
document.body.classList.toggle("motion-disabled", !enabled);
|
||||
jQuery.fx.off = !enabled;
|
||||
}
|
||||
|
||||
#setShadows(enabled: boolean) {
|
||||
document.body.classList.toggle("shadows-disabled", !enabled);
|
||||
}
|
||||
|
||||
#setBackdropEffects(enabled: boolean) {
|
||||
document.body.classList.toggle("backdrop-effects-disabled", !enabled);
|
||||
}
|
||||
}
|
||||
|
||||
function getViewportHeight() {
|
||||
|
||||
@@ -2,6 +2,7 @@ import FlexContainer from "./flex_container.js";
|
||||
import appContext, { type CommandData, type CommandListenerData, type EventData, type EventNames, type NoteSwitchedContext } from "../../components/app_context.js";
|
||||
import type BasicWidget from "../basic_widget.js";
|
||||
import type NoteContext from "../../components/note_context.js";
|
||||
import Component from "../../components/component.js";
|
||||
|
||||
interface NoteContextEvent {
|
||||
noteContext: NoteContext;
|
||||
@@ -152,6 +153,8 @@ export default class SplitNoteContainer extends FlexContainer<SplitNoteWidget> {
|
||||
for (const ntxId of ntxIds) {
|
||||
this.$widget.find(`[data-ntx-id="${ntxId}"]`).remove();
|
||||
|
||||
const widget = this.widgets[ntxId];
|
||||
recursiveCleanup(widget);
|
||||
delete this.widgets[ntxId];
|
||||
}
|
||||
}
|
||||
@@ -237,3 +240,12 @@ export default class SplitNoteContainer extends FlexContainer<SplitNoteWidget> {
|
||||
return Promise.all(promises);
|
||||
}
|
||||
}
|
||||
|
||||
function recursiveCleanup(widget: Component) {
|
||||
for (const child of widget.children) {
|
||||
recursiveCleanup(child);
|
||||
}
|
||||
if ("cleanup" in widget && typeof widget.cleanup === "function") {
|
||||
widget.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget.js";
|
||||
import Modal from "../react/Modal.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import { formatDateTime } from "../../utils/formatters.js";
|
||||
@@ -8,11 +7,11 @@ import openService from "../../services/open.js";
|
||||
import { useState } from "preact/hooks";
|
||||
import type { CSSProperties } from "preact/compat";
|
||||
import type { AppInfo } from "@triliumnext/commons";
|
||||
import useTriliumEvent from "../react/hooks.jsx";
|
||||
import { useTriliumEvent } from "../react/hooks.jsx";
|
||||
|
||||
function AboutDialogComponent() {
|
||||
let [appInfo, setAppInfo] = useState<AppInfo | null>(null);
|
||||
let [shown, setShown] = useState(false);
|
||||
export default function AboutDialog() {
|
||||
const [appInfo, setAppInfo] = useState<AppInfo | null>(null);
|
||||
const [shown, setShown] = useState(false);
|
||||
const forceWordBreak: CSSProperties = { wordBreak: "break-all" };
|
||||
|
||||
useTriliumEvent("openAboutDialog", () => setShown(true));
|
||||
@@ -77,16 +76,8 @@ function DirectoryLink({ directory, style }: { directory: string, style?: CSSPro
|
||||
openService.openDirectory(directory);
|
||||
};
|
||||
|
||||
return <a className="tn-link" href="#" onClick={onClick} style={style}></a>
|
||||
return <a className="tn-link" href="#" onClick={onClick} style={style}>{directory}</a>
|
||||
} else {
|
||||
return <span style={style}>{directory}</span>;
|
||||
}
|
||||
}
|
||||
|
||||
export default class AboutDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <AboutDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { t } from "../../services/i18n";
|
||||
import Modal from "../react/Modal";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import Button from "../react/Button";
|
||||
import FormRadioGroup from "../react/FormRadioGroup";
|
||||
import NoteAutocomplete from "../react/NoteAutocomplete";
|
||||
@@ -11,11 +10,11 @@ import { default as TextTypeWidget } from "../type_widgets/editable_text.js";
|
||||
import { logError } from "../../services/ws";
|
||||
import FormGroup from "../react/FormGroup.js";
|
||||
import { refToJQuerySelector } from "../react/react_utils";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
type LinkType = "reference-link" | "external-link" | "hyper-link";
|
||||
|
||||
function AddLinkDialogComponent() {
|
||||
export default function AddLinkDialog() {
|
||||
const [ textTypeWidget, setTextTypeWidget ] = useState<TextTypeWidget>();
|
||||
const initialText = useRef<string>();
|
||||
const [ linkTitle, setLinkTitle ] = useState("");
|
||||
@@ -30,6 +29,14 @@ function AddLinkDialogComponent() {
|
||||
setShown(true);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (hasSelection) {
|
||||
setLinkType("hyper-link");
|
||||
} else {
|
||||
setLinkType("reference-link");
|
||||
}
|
||||
}, [ hasSelection ])
|
||||
|
||||
async function setDefaultLinkTitle(noteId: string) {
|
||||
const noteTitle = await tree.getNoteTitle(noteId);
|
||||
setLinkTitle(noteTitle);
|
||||
@@ -107,7 +114,7 @@ function AddLinkDialogComponent() {
|
||||
}}
|
||||
show={shown}
|
||||
>
|
||||
<FormGroup label={t("add_link.note")}>
|
||||
<FormGroup label={t("add_link.note")} name="note">
|
||||
<NoteAutocomplete
|
||||
inputRef={autocompleteRef}
|
||||
onChange={setSuggestion}
|
||||
@@ -152,11 +159,3 @@ function AddLinkDialogComponent() {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default class AddLinkDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <AddLinkDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,15 +4,14 @@ import { t } from "../../services/i18n.js";
|
||||
import server from "../../services/server.js";
|
||||
import toast from "../../services/toast.js";
|
||||
import Modal from "../react/Modal.jsx";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget.js";
|
||||
import froca from "../../services/froca.js";
|
||||
import tree from "../../services/tree.js";
|
||||
import Button from "../react/Button.jsx";
|
||||
import FormGroup from "../react/FormGroup.js";
|
||||
import useTriliumEvent from "../react/hooks.jsx";
|
||||
import { useTriliumEvent } from "../react/hooks.jsx";
|
||||
import FBranch from "../../entities/fbranch.js";
|
||||
|
||||
function BranchPrefixDialogComponent() {
|
||||
export default function BranchPrefixDialog() {
|
||||
const [ shown, setShown ] = useState(false);
|
||||
const [ branch, setBranch ] = useState<FBranch>();
|
||||
const [ prefix, setPrefix ] = useState(branch?.prefix ?? "");
|
||||
@@ -64,7 +63,7 @@ function BranchPrefixDialogComponent() {
|
||||
footer={<Button text={t("branch_prefix.save")} />}
|
||||
show={shown}
|
||||
>
|
||||
<FormGroup label={t("branch_prefix.prefix")}>
|
||||
<FormGroup label={t("branch_prefix.prefix")} name="prefix">
|
||||
<div class="input-group">
|
||||
<input class="branch-prefix-input form-control" value={prefix} ref={branchInput}
|
||||
onChange={(e) => setPrefix((e.target as HTMLInputElement).value)} />
|
||||
@@ -75,14 +74,6 @@ function BranchPrefixDialogComponent() {
|
||||
);
|
||||
}
|
||||
|
||||
export default class BranchPrefixDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <BranchPrefixDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function savePrefix(branchId: string, prefix: string) {
|
||||
await server.put(`branches/${branchId}/set-prefix`, { prefix: prefix });
|
||||
toast.showMessage(t("branch_prefix.branch_prefix_saved"));
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useEffect, useState, useCallback } from "preact/hooks";
|
||||
import { t } from "../../services/i18n";
|
||||
import Modal from "../react/Modal";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import "./bulk_actions.css";
|
||||
import { BulkActionAffectedNotes } from "@triliumnext/commons";
|
||||
import server from "../../services/server";
|
||||
@@ -12,9 +11,9 @@ import toast from "../../services/toast";
|
||||
import RenameNoteBulkAction from "../bulk_actions/note/rename_note";
|
||||
import FNote from "../../entities/fnote";
|
||||
import froca from "../../services/froca";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
function BulkActionComponent() {
|
||||
export default function BulkActionsDialog() {
|
||||
const [ selectedOrActiveNoteIds, setSelectedOrActiveNoteIds ] = useState<string[]>();
|
||||
const [ bulkActionNote, setBulkActionNote ] = useState<FNote | null>();
|
||||
const [ includeDescendants, setIncludeDescendants ] = useState(false);
|
||||
@@ -51,7 +50,7 @@ function BulkActionComponent() {
|
||||
row.type === "label" && row.name === "action" && row.noteId === "_bulkAction")) {
|
||||
refreshExistingActions();
|
||||
}
|
||||
}, shown);
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -94,7 +93,8 @@ function AvailableActionsList() {
|
||||
<td>{ actionGroup.title }:</td>
|
||||
{actionGroup.actions.map(({ actionName, actionTitle }) =>
|
||||
<Button
|
||||
small text={actionTitle}
|
||||
size="small"
|
||||
text={actionTitle}
|
||||
onClick={() => bulk_action.addAction("_bulkAction", actionName)}
|
||||
/>
|
||||
)}
|
||||
@@ -116,11 +116,3 @@ function ExistingActionsList({ existingActions }: { existingActions?: RenameNote
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
export default class BulkActionsDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <BulkActionComponent />
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
import { useState } from "preact/hooks";
|
||||
import { useMemo, useState } from "preact/hooks";
|
||||
import Button from "../react/Button";
|
||||
import Modal from "../react/Modal";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import { CallToAction, dismissCallToAction, getCallToActions } from "./call_to_action_definitions";
|
||||
import { dismissCallToAction, getCallToActions } from "./call_to_action_definitions";
|
||||
import { t } from "../../services/i18n";
|
||||
|
||||
function CallToActionDialogComponent({ activeCallToActions }: { activeCallToActions: CallToAction[] }) {
|
||||
if (!activeCallToActions.length) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
export default function CallToActionDialog() {
|
||||
const activeCallToActions = useMemo(() => getCallToActions(), []);
|
||||
const [ activeIndex, setActiveIndex ] = useState(0);
|
||||
const [ shown, setShown ] = useState(true);
|
||||
const activeItem = activeCallToActions[activeIndex];
|
||||
@@ -21,16 +18,16 @@ function CallToActionDialogComponent({ activeCallToActions }: { activeCallToActi
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
return (activeCallToActions.length &&
|
||||
<Modal
|
||||
className="call-to-action"
|
||||
size="md"
|
||||
title="New features"
|
||||
title={activeItem.title}
|
||||
show={shown}
|
||||
onHidden={() => setShown(false)}
|
||||
footerAlignment="between"
|
||||
footer={<>
|
||||
<Button text="Dismiss" onClick={async () => {
|
||||
<Button text={t("call_to_action.dismiss")} onClick={async () => {
|
||||
await dismissCallToAction(activeItem.id);
|
||||
goToNext();
|
||||
}} />
|
||||
@@ -43,16 +40,7 @@ function CallToActionDialogComponent({ activeCallToActions }: { activeCallToActi
|
||||
)}
|
||||
</>}
|
||||
>
|
||||
<h4>{activeItem.title}</h4>
|
||||
<p>{activeItem.message}</p>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export class CallToActionDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <CallToActionDialogComponent activeCallToActions={getCallToActions()} />
|
||||
}
|
||||
|
||||
}
|
||||
@@ -65,7 +65,7 @@ const CALL_TO_ACTIONS: CallToAction[] = [
|
||||
id: "background_effects",
|
||||
title: t("call_to_action.background_effects_title"),
|
||||
message: t("call_to_action.background_effects_message"),
|
||||
enabled: () => utils.isElectron() && window.glob.platform === "win32" && isNextTheme() && !options.is("backgroundEffects"),
|
||||
enabled: () => false,
|
||||
buttons: [
|
||||
{
|
||||
text: t("call_to_action.background_effects_button"),
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useRef, useState } from "preact/hooks";
|
||||
import appContext from "../../components/app_context";
|
||||
import { t } from "../../services/i18n";
|
||||
import Modal from "../react/Modal";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import NoteAutocomplete from "../react/NoteAutocomplete";
|
||||
import froca from "../../services/froca";
|
||||
import FormGroup from "../react/FormGroup";
|
||||
@@ -14,9 +13,9 @@ import tree from "../../services/tree";
|
||||
import branches from "../../services/branches";
|
||||
import toast from "../../services/toast";
|
||||
import NoteList from "../react/NoteList";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
function CloneToDialogComponent() {
|
||||
export default function CloneToDialog() {
|
||||
const [ clonedNoteIds, setClonedNoteIds ] = useState<string[]>();
|
||||
const [ prefix, setPrefix ] = useState("");
|
||||
const [ suggestion, setSuggestion ] = useState<Suggestion | null>(null);
|
||||
@@ -69,28 +68,20 @@ function CloneToDialogComponent() {
|
||||
>
|
||||
<h5>{t("clone_to.notes_to_clone")}</h5>
|
||||
<NoteList style={{ maxHeight: "200px", overflow: "auto" }} noteIds={clonedNoteIds} />
|
||||
<FormGroup label={t("clone_to.target_parent_note")}>
|
||||
<FormGroup name="target-parent-note" label={t("clone_to.target_parent_note")}>
|
||||
<NoteAutocomplete
|
||||
placeholder={t("clone_to.search_for_note_by_its_name")}
|
||||
onChange={setSuggestion}
|
||||
inputRef={autoCompleteRef}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup label={t("clone_to.prefix_optional")} title={t("clone_to.cloned_note_prefix_title")}>
|
||||
<FormTextBox name="clone-prefix" onChange={setPrefix} />
|
||||
<FormGroup name="clone-prefix" label={t("clone_to.prefix_optional")} title={t("clone_to.cloned_note_prefix_title")}>
|
||||
<FormTextBox onChange={setPrefix} />
|
||||
</FormGroup>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default class CloneToDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <CloneToDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function cloneNotesTo(notePath: string, clonedNoteIds: string[], prefix?: string) {
|
||||
const { noteId, parentNoteId } = tree.getNoteIdAndParentIdFromUrl(notePath);
|
||||
if (!noteId || !parentNoteId) {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import Modal from "../react/Modal";
|
||||
import Button from "../react/Button";
|
||||
import { t } from "../../services/i18n";
|
||||
import { useState } from "preact/hooks";
|
||||
import FormCheckbox from "../react/FormCheckbox";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
interface ConfirmDialogProps {
|
||||
title?: string;
|
||||
@@ -13,7 +12,7 @@ interface ConfirmDialogProps {
|
||||
isConfirmDeleteNoteBox?: boolean;
|
||||
}
|
||||
|
||||
function ConfirmDialogComponent() {
|
||||
export default function ConfirmDialog() {
|
||||
const [ opts, setOpts ] = useState<ConfirmDialogProps>();
|
||||
const [ isDeleteNoteChecked, setIsDeleteNoteChecked ] = useState(false);
|
||||
const [ shown, setShown ] = useState(false);
|
||||
@@ -92,11 +91,3 @@ export interface ConfirmWithTitleOptions {
|
||||
title: string;
|
||||
callback: ConfirmDialogCallback;
|
||||
}
|
||||
|
||||
export default class ConfirmDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <ConfirmDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import { useRef, useState, useEffect } from "preact/hooks";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import FormCheckbox from "../react/FormCheckbox.js";
|
||||
import Modal from "../react/Modal.js";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget.js";
|
||||
import type { DeleteNotesPreview } from "@triliumnext/commons";
|
||||
import server from "../../services/server.js";
|
||||
import froca from "../../services/froca.js";
|
||||
@@ -10,7 +9,7 @@ import FNote from "../../entities/fnote.js";
|
||||
import link from "../../services/link.js";
|
||||
import Button from "../react/Button.jsx";
|
||||
import Alert from "../react/Alert.jsx";
|
||||
import useTriliumEvent from "../react/hooks.jsx";
|
||||
import { useTriliumEvent } from "../react/hooks.jsx";
|
||||
|
||||
export interface ResolveOptions {
|
||||
proceed: boolean;
|
||||
@@ -30,7 +29,7 @@ interface BrokenRelationData {
|
||||
source: string;
|
||||
}
|
||||
|
||||
function DeleteNotesDialogComponent() {
|
||||
export default function DeleteNotesDialog() {
|
||||
const [ opts, setOpts ] = useState<ShowDeleteNotesDialogOpts>({});
|
||||
const [ deleteAllClones, setDeleteAllClones ] = useState(false);
|
||||
const [ eraseNotes, setEraseNotes ] = useState(!!opts.forceDeleteAllClones);
|
||||
@@ -140,7 +139,7 @@ function BrokenRelations({ brokenRelations }: { brokenRelations: DeleteNotesPrev
|
||||
const noteIds = brokenRelations
|
||||
.map(relation => relation.noteId)
|
||||
.filter(noteId => noteId) as string[];
|
||||
froca.getNotes(noteIds).then(async (notes) => {
|
||||
froca.getNotes(noteIds).then(async () => {
|
||||
const notesWithBrokenRelations: BrokenRelationData[] = [];
|
||||
for (const attr of brokenRelations) {
|
||||
notesWithBrokenRelations.push({
|
||||
@@ -171,11 +170,3 @@ function BrokenRelations({ brokenRelations }: { brokenRelations: DeleteNotesPrev
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
||||
export default class DeleteNotesDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <DeleteNotesDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,14 +4,13 @@ import tree from "../../services/tree";
|
||||
import Button from "../react/Button";
|
||||
import FormRadioGroup from "../react/FormRadioGroup";
|
||||
import Modal from "../react/Modal";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import "./export.css";
|
||||
import ws from "../../services/ws";
|
||||
import toastService, { ToastOptions } from "../../services/toast";
|
||||
import utils from "../../services/utils";
|
||||
import open from "../../services/open";
|
||||
import froca from "../../services/froca";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
interface ExportDialogProps {
|
||||
branchId?: string | null;
|
||||
@@ -19,7 +18,7 @@ interface ExportDialogProps {
|
||||
defaultType?: "subtree" | "single";
|
||||
}
|
||||
|
||||
function ExportDialogComponent() {
|
||||
export default function ExportDialog() {
|
||||
const [ opts, setOpts ] = useState<ExportDialogProps>();
|
||||
const [ exportType, setExportType ] = useState<string>(opts?.defaultType ?? "subtree");
|
||||
const [ subtreeFormat, setSubtreeFormat ] = useState("html");
|
||||
@@ -125,14 +124,6 @@ function ExportDialogComponent() {
|
||||
);
|
||||
}
|
||||
|
||||
export default class ExportDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <ExportDialogComponent />
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function exportBranch(branchId: string, type: string, format: string, version: string) {
|
||||
const taskId = utils.randomString(10);
|
||||
const url = open.getUrlForDownload(`api/branches/${branchId}/export/${type}/${format}/${version}/${taskId}`);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget.js";
|
||||
import Modal from "../react/Modal.jsx";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import { ComponentChildren } from "preact";
|
||||
@@ -6,9 +5,9 @@ import { CommandNames } from "../../components/app_context.js";
|
||||
import RawHtml from "../react/RawHtml.jsx";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import keyboard_actions from "../../services/keyboard_actions.js";
|
||||
import useTriliumEvent from "../react/hooks.jsx";
|
||||
import { useTriliumEvent } from "../react/hooks.jsx";
|
||||
|
||||
function HelpDialogComponent() {
|
||||
export default function HelpDialog() {
|
||||
const [ shown, setShown ] = useState(false);
|
||||
useTriliumEvent("showCheatsheet", () => setShown(true));
|
||||
|
||||
@@ -161,11 +160,3 @@ function Card({ title, children }: { title: string, children: ComponentChildren
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default class HelpDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <HelpDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,14 +4,13 @@ import tree from "../../services/tree";
|
||||
import Button from "../react/Button";
|
||||
import FormCheckbox from "../react/FormCheckbox";
|
||||
import FormFileUpload from "../react/FormFileUpload";
|
||||
import FormGroup from "../react/FormGroup";
|
||||
import FormGroup, { FormMultiGroup } from "../react/FormGroup";
|
||||
import Modal from "../react/Modal";
|
||||
import RawHtml from "../react/RawHtml";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import importService, { UploadFilesOptions } from "../../services/import";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
function ImportDialogComponent() {
|
||||
export default function ImportDialog() {
|
||||
const [ parentNoteId, setParentNoteId ] = useState<string>();
|
||||
const [ noteTitle, setNoteTitle ] = useState<string>();
|
||||
const [ files, setFiles ] = useState<FileList | null>(null);
|
||||
@@ -55,11 +54,11 @@ function ImportDialogComponent() {
|
||||
footer={<Button text={t("import.import")} primary disabled={!files} />}
|
||||
show={shown}
|
||||
>
|
||||
<FormGroup label={t("import.chooseImportFile")} description={<>{t("import.importDescription")} <strong>{ noteTitle }</strong></>}>
|
||||
<FormGroup name="files" label={t("import.chooseImportFile")} description={<>{t("import.importDescription")} <strong>{ noteTitle }</strong></>}>
|
||||
<FormFileUpload multiple onChange={setFiles} />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup label={t("import.options")}>
|
||||
<FormMultiGroup label={t("import.options")}>
|
||||
<FormCheckbox
|
||||
name="safe-import" hint={t("import.safeImportTooltip")} label={t("import.safeImport")}
|
||||
currentValue={safeImport} onChange={setSafeImport}
|
||||
@@ -84,19 +83,11 @@ function ImportDialogComponent() {
|
||||
name="replace-underscores-with-spaces" label={t("import.replaceUnderscoresWithSpaces")}
|
||||
currentValue={replaceUnderscoresWithSpaces} onChange={setReplaceUnderscoresWithSpaces}
|
||||
/>
|
||||
</FormGroup>
|
||||
</FormMultiGroup>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default class ImportDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <ImportDialogComponent />
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function boolToString(value: boolean) {
|
||||
return value ? "true" : "false";
|
||||
}
|
||||
@@ -4,15 +4,14 @@ import FormGroup from "../react/FormGroup";
|
||||
import FormRadioGroup from "../react/FormRadioGroup";
|
||||
import Modal from "../react/Modal";
|
||||
import NoteAutocomplete from "../react/NoteAutocomplete";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import Button from "../react/Button";
|
||||
import { Suggestion, triggerRecentNotes } from "../../services/note_autocomplete";
|
||||
import tree from "../../services/tree";
|
||||
import froca from "../../services/froca";
|
||||
import EditableTextTypeWidget from "../type_widgets/editable_text";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
function IncludeNoteDialogComponent() {
|
||||
export default function IncludeNoteDialog() {
|
||||
const [textTypeWidget, setTextTypeWidget] = useState<EditableTextTypeWidget>();
|
||||
const [suggestion, setSuggestion] = useState<Suggestion | null>(null);
|
||||
const [boxSize, setBoxSize] = useState("medium");
|
||||
@@ -43,7 +42,7 @@ function IncludeNoteDialogComponent() {
|
||||
footer={<Button text={t("include_note.button_include")} keyboardShortcut="Enter" />}
|
||||
show={shown}
|
||||
>
|
||||
<FormGroup label={t("include_note.label_note")}>
|
||||
<FormGroup name="note" label={t("include_note.label_note")}>
|
||||
<NoteAutocomplete
|
||||
placeholder={t("include_note.placeholder_search")}
|
||||
onChange={setSuggestion}
|
||||
@@ -55,8 +54,9 @@ function IncludeNoteDialogComponent() {
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup label={t("include_note.box_size_prompt")}>
|
||||
<FormRadioGroup name="include-note-box-size"
|
||||
<FormGroup name="include-note-box-size" label={t("include_note.box_size_prompt")}>
|
||||
<FormRadioGroup
|
||||
name="include-note-box-size"
|
||||
currentValue={boxSize} onChange={setBoxSize}
|
||||
values={[
|
||||
{ label: t("include_note.box_size_small"), value: "small" },
|
||||
@@ -69,14 +69,6 @@ function IncludeNoteDialogComponent() {
|
||||
)
|
||||
}
|
||||
|
||||
export default class IncludeNoteDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <IncludeNoteDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function includeNote(notePath: string, textTypeWidget: EditableTextTypeWidget) {
|
||||
const noteId = tree.getNoteIdFromUrl(notePath);
|
||||
if (!noteId) {
|
||||
|
||||
@@ -3,11 +3,10 @@ import { t } from "../../services/i18n.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import Button from "../react/Button.js";
|
||||
import Modal from "../react/Modal.js";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget.js";
|
||||
import { useState } from "preact/hooks";
|
||||
import useTriliumEvent from "../react/hooks.jsx";
|
||||
import { useTriliumEvent } from "../react/hooks.jsx";
|
||||
|
||||
function IncorrectCpuArchDialogComponent() {
|
||||
export default function IncorrectCpuArchDialogComponent() {
|
||||
const [ shown, setShown ] = useState(false);
|
||||
const downloadButtonRef = useRef<HTMLButtonElement>(null);
|
||||
useTriliumEvent("showCpuArchWarning", () => setShown(true));
|
||||
@@ -44,11 +43,3 @@ function IncorrectCpuArchDialogComponent() {
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default class IncorrectCpuArchDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <IncorrectCpuArchDialogComponent />
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { EventData } from "../../components/app_context";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import Modal from "../react/Modal";
|
||||
import { t } from "../../services/i18n";
|
||||
import Button from "../react/Button";
|
||||
import { useRef, useState } from "preact/hooks";
|
||||
import { RawHtmlBlock } from "../react/RawHtml";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
function ShowInfoDialogComponent() {
|
||||
export default function InfoDialog() {
|
||||
const [ opts, setOpts ] = useState<EventData<"showInfoDialog">>();
|
||||
const [ shown, setShown ] = useState(false);
|
||||
const okButtonRef = useRef<HTMLButtonElement>(null);
|
||||
@@ -37,11 +36,3 @@ function ShowInfoDialogComponent() {
|
||||
<RawHtmlBlock className="info-dialog-content" html={opts?.message ?? ""} />
|
||||
</Modal>);
|
||||
}
|
||||
|
||||
export default class InfoDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <ShowInfoDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import Modal from "../react/Modal";
|
||||
import Button from "../react/Button";
|
||||
import NoteAutocomplete from "../react/NoteAutocomplete";
|
||||
@@ -8,13 +7,14 @@ import note_autocomplete, { Suggestion } from "../../services/note_autocomplete"
|
||||
import appContext from "../../components/app_context";
|
||||
import commandRegistry from "../../services/command_registry";
|
||||
import { refToJQuerySelector } from "../react/react_utils";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
import shortcutService from "../../services/shortcuts";
|
||||
|
||||
const KEEP_LAST_SEARCH_FOR_X_SECONDS = 120;
|
||||
|
||||
type Mode = "last-search" | "recent-notes" | "commands";
|
||||
|
||||
function JumpToNoteDialogComponent() {
|
||||
export default function JumpToNoteDialogComponent() {
|
||||
const [ mode, setMode ] = useState<Mode>();
|
||||
const [ lastOpenedTs, setLastOpenedTs ] = useState<number>(0);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
@@ -26,12 +26,12 @@ function JumpToNoteDialogComponent() {
|
||||
|
||||
async function openDialog(commandMode: boolean) {
|
||||
let newMode: Mode;
|
||||
let initialText: string = "";
|
||||
let initialText = "";
|
||||
|
||||
if (commandMode) {
|
||||
newMode = "commands";
|
||||
initialText = ">";
|
||||
} else if (Date.now() - lastOpenedTs <= KEEP_LAST_SEARCH_FOR_X_SECONDS * 1000 && actualText) {
|
||||
} else if (Date.now() - lastOpenedTs <= KEEP_LAST_SEARCH_FOR_X_SECONDS * 1000 && actualText.current) {
|
||||
// if you open the Jump To dialog soon after using it previously, it can often mean that you
|
||||
// actually want to search for the same thing (e.g., you opened the wrong note at first try)
|
||||
// so we'll keep the content.
|
||||
@@ -83,6 +83,27 @@ function JumpToNoteDialogComponent() {
|
||||
$autoComplete
|
||||
.trigger("focus")
|
||||
.trigger("select");
|
||||
|
||||
// Add keyboard shortcut for full search
|
||||
shortcutService.bindElShortcut($autoComplete, "ctrl+return", () => {
|
||||
if (!isCommandMode) {
|
||||
showInFullSearch();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function showInFullSearch() {
|
||||
try {
|
||||
setShown(false);
|
||||
const searchString = actualText.current?.trim();
|
||||
if (searchString && !searchString.startsWith(">")) {
|
||||
await appContext.triggerCommand("searchNotes", {
|
||||
searchString
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to trigger full search:", error);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -108,18 +129,15 @@ function JumpToNoteDialogComponent() {
|
||||
/>}
|
||||
onShown={onShown}
|
||||
onHidden={() => setShown(false)}
|
||||
footer={!isCommandMode && <Button className="show-in-full-text-button" text={t("jump_to_note.search_button")} keyboardShortcut="Ctrl+Enter" />}
|
||||
footer={!isCommandMode && <Button
|
||||
className="show-in-full-text-button"
|
||||
text={t("jump_to_note.search_button")}
|
||||
keyboardShortcut="Ctrl+Enter"
|
||||
onClick={showInFullSearch}
|
||||
/>}
|
||||
show={shown}
|
||||
>
|
||||
<div className="algolia-autocomplete-container jump-to-note-results" ref={containerRef}></div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default class JumpToNoteDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <JumpToNoteDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,18 +5,17 @@ import server from "../../services/server";
|
||||
import toast from "../../services/toast";
|
||||
import utils from "../../services/utils";
|
||||
import Modal from "../react/Modal";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import Button from "../react/Button";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
interface RenderMarkdownResponse {
|
||||
htmlContent: string;
|
||||
}
|
||||
|
||||
function MarkdownImportDialogComponent() {
|
||||
export default function MarkdownImportDialog() {
|
||||
const markdownImportTextArea = useRef<HTMLTextAreaElement>(null);
|
||||
let [ text, setText ] = useState("");
|
||||
let [ shown, setShown ] = useState(false);
|
||||
const [ text, setText ] = useState("");
|
||||
const [ shown, setShown ] = useState(false);
|
||||
|
||||
const triggerImport = useCallback(() => {
|
||||
if (appContext.tabManager.getActiveContextNoteType() !== "text") {
|
||||
@@ -64,14 +63,6 @@ function MarkdownImportDialogComponent() {
|
||||
)
|
||||
}
|
||||
|
||||
export default class MarkdownImportDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <MarkdownImportDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function convertMarkdownToHtml(markdownContent: string) {
|
||||
const { htmlContent } = await server.post<RenderMarkdownResponse>("other/render-markdown", { markdownContent });
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import Modal from "../react/Modal";
|
||||
import { t } from "../../services/i18n";
|
||||
import NoteList from "../react/NoteList";
|
||||
@@ -11,9 +10,9 @@ import tree from "../../services/tree";
|
||||
import froca from "../../services/froca";
|
||||
import branches from "../../services/branches";
|
||||
import toast from "../../services/toast";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
function MoveToDialogComponent() {
|
||||
export default function MoveToDialog() {
|
||||
const [ movedBranchIds, setMovedBranchIds ] = useState<string[]>();
|
||||
const [ suggestion, setSuggestion ] = useState<Suggestion | null>(null);
|
||||
const [ shown, setShown ] = useState(false);
|
||||
@@ -57,7 +56,7 @@ function MoveToDialogComponent() {
|
||||
<h5>{t("move_to.notes_to_move")}</h5>
|
||||
<NoteList branchIds={movedBranchIds} />
|
||||
|
||||
<FormGroup label={t("move_to.target_parent_note")}>
|
||||
<FormGroup name="parent-note" label={t("move_to.target_parent_note")}>
|
||||
<NoteAutocomplete
|
||||
onChange={setSuggestion}
|
||||
inputRef={autoCompleteRef}
|
||||
@@ -67,14 +66,6 @@ function MoveToDialogComponent() {
|
||||
)
|
||||
}
|
||||
|
||||
export default class MoveToDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <MoveToDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function moveNotesTo(movedBranchIds: string[] | undefined, parentBranchId: string) {
|
||||
if (movedBranchIds) {
|
||||
await branches.moveToParentNote(movedBranchIds, parentBranchId);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import Modal from "../react/Modal";
|
||||
import { t } from "../../services/i18n";
|
||||
import FormGroup from "../react/FormGroup";
|
||||
@@ -10,7 +9,7 @@ import { MenuCommandItem, MenuItem } from "../../menus/context_menu";
|
||||
import { TreeCommandNames } from "../../menus/tree_context_menu";
|
||||
import { Suggestion } from "../../services/note_autocomplete";
|
||||
import Badge from "../react/Badge";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
export interface ChooseNoteTypeResponse {
|
||||
success: boolean;
|
||||
@@ -26,7 +25,7 @@ const SEPARATOR_TITLE_REPLACEMENTS = [
|
||||
t("note_type_chooser.templates")
|
||||
];
|
||||
|
||||
function NoteTypeChooserDialogComponent() {
|
||||
export default function NoteTypeChooserDialogComponent() {
|
||||
const [ callback, setCallback ] = useState<ChooseNoteTypeCallback>();
|
||||
const [ shown, setShown ] = useState(false);
|
||||
const [ parentNote, setParentNote ] = useState<Suggestion | null>();
|
||||
@@ -37,25 +36,23 @@ function NoteTypeChooserDialogComponent() {
|
||||
setShown(true);
|
||||
});
|
||||
|
||||
if (!noteTypes.length) {
|
||||
useEffect(() => {
|
||||
note_types.getNoteTypeItems().then(noteTypes => {
|
||||
let index = -1;
|
||||
useEffect(() => {
|
||||
note_types.getNoteTypeItems().then(noteTypes => {
|
||||
let index = -1;
|
||||
|
||||
setNoteTypes((noteTypes ?? []).map((item, _index) => {
|
||||
if (item.title === "----") {
|
||||
index++;
|
||||
return {
|
||||
title: SEPARATOR_TITLE_REPLACEMENTS[index],
|
||||
enabled: false
|
||||
}
|
||||
setNoteTypes((noteTypes ?? []).map((item) => {
|
||||
if (item.title === "----") {
|
||||
index++;
|
||||
return {
|
||||
title: SEPARATOR_TITLE_REPLACEMENTS[index],
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
|
||||
return item;
|
||||
}));
|
||||
});
|
||||
return item;
|
||||
}));
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
function onNoteTypeSelected(value: string) {
|
||||
const [ noteType, templateNoteId ] = value.split(",");
|
||||
@@ -83,7 +80,7 @@ function NoteTypeChooserDialogComponent() {
|
||||
show={shown}
|
||||
stackable
|
||||
>
|
||||
<FormGroup label={t("note_type_chooser.change_path_prompt")}>
|
||||
<FormGroup name="parent-note" label={t("note_type_chooser.change_path_prompt")}>
|
||||
<NoteAutocomplete
|
||||
onChange={setParentNote}
|
||||
placeholder={t("note_type_chooser.search_placeholder")}
|
||||
@@ -95,7 +92,7 @@ function NoteTypeChooserDialogComponent() {
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup label={t("note_type_chooser.modal_body")}>
|
||||
<FormGroup name="note-type" label={t("note_type_chooser.modal_body")}>
|
||||
<FormList onSelect={onNoteTypeSelected}>
|
||||
{noteTypes.map((_item) => {
|
||||
if (_item.title === "----") {
|
||||
@@ -120,11 +117,3 @@ function NoteTypeChooserDialogComponent() {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default class NoteTypeChooserDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <NoteTypeChooserDialogComponent />
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import Modal from "../react/Modal";
|
||||
import { t } from "../../services/i18n";
|
||||
import Button from "../react/Button";
|
||||
import appContext from "../../components/app_context";
|
||||
import { useState } from "preact/hooks";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
function PasswordNotSetDialogComponent() {
|
||||
export default function PasswordNotSetDialog() {
|
||||
const [ shown, setShown ] = useState(false);
|
||||
useTriliumEvent("showPasswordNotSet", () => setShown(true));
|
||||
|
||||
@@ -27,10 +26,3 @@ function PasswordNotSetDialogComponent() {
|
||||
);
|
||||
}
|
||||
|
||||
export default class PasswordNotSetDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <PasswordNotSetDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,12 +2,10 @@ import { useRef, useState } from "preact/hooks";
|
||||
import { t } from "../../services/i18n";
|
||||
import Button from "../react/Button";
|
||||
import Modal from "../react/Modal";
|
||||
import { Modal as BootstrapModal } from "bootstrap";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import FormTextBox from "../react/FormTextBox";
|
||||
import FormGroup from "../react/FormGroup";
|
||||
import { refToJQuerySelector } from "../react/react_utils";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
// JQuery here is maintained for compatibility with existing code.
|
||||
interface ShownCallbackData {
|
||||
@@ -25,31 +23,34 @@ export interface PromptDialogOptions {
|
||||
defaultValue?: string;
|
||||
shown?: PromptShownDialogCallback;
|
||||
callback?: (value: string | null) => void;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
function PromptDialogComponent() {
|
||||
export default function PromptDialog() {
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
const labelRef = useRef<HTMLLabelElement>(null);
|
||||
const answerRef = useRef<HTMLInputElement>(null);
|
||||
const [ opts, setOpts ] = useState<PromptDialogOptions>();
|
||||
const [ value, setValue ] = useState("");
|
||||
const opts = useRef<PromptDialogOptions>();
|
||||
const [ value, setValue ] = useState("");
|
||||
const [ shown, setShown ] = useState(false);
|
||||
const submitValue = useRef<string>(null);
|
||||
|
||||
useTriliumEvent("showPromptDialog", (opts) => {
|
||||
setOpts(opts);
|
||||
useTriliumEvent("showPromptDialog", (newOpts) => {
|
||||
opts.current = newOpts;
|
||||
setValue(newOpts.defaultValue ?? "");
|
||||
setShown(true);
|
||||
})
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className="prompt-dialog"
|
||||
title={opts?.title ?? t("prompt.title")}
|
||||
title={opts.current?.title ?? t("prompt.title")}
|
||||
size="lg"
|
||||
zIndex={2000}
|
||||
modalRef={modalRef} formRef={formRef}
|
||||
onShown={() => {
|
||||
opts?.shown?.({
|
||||
opts.current?.shown?.({
|
||||
$dialog: refToJQuerySelector(modalRef),
|
||||
$question: refToJQuerySelector(labelRef),
|
||||
$answer: refToJQuerySelector(answerRef),
|
||||
@@ -58,33 +59,26 @@ function PromptDialogComponent() {
|
||||
answerRef.current?.focus();
|
||||
}}
|
||||
onSubmit={() => {
|
||||
const modal = BootstrapModal.getOrCreateInstance(modalRef.current!);
|
||||
modal.hide();
|
||||
|
||||
opts?.callback?.(value);
|
||||
submitValue.current = value;
|
||||
setShown(false);
|
||||
}}
|
||||
onHidden={() => {
|
||||
opts?.callback?.(null);
|
||||
setShown(false);
|
||||
opts.current?.callback?.(submitValue.current);
|
||||
submitValue.current = null;
|
||||
opts.current = undefined;
|
||||
}}
|
||||
footer={<Button text={t("prompt.ok")} keyboardShortcut="Enter" primary />}
|
||||
show={shown}
|
||||
stackable
|
||||
>
|
||||
<FormGroup label={opts?.message} labelRef={labelRef}>
|
||||
<FormGroup name="prompt-dialog-answer" label={opts.current?.message} labelRef={labelRef}>
|
||||
<FormTextBox
|
||||
name="prompt-dialog-answer"
|
||||
inputRef={answerRef}
|
||||
currentValue={value} onChange={setValue} />
|
||||
currentValue={value} onChange={setValue}
|
||||
readOnly={opts.current?.readOnly}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default class PromptDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <PromptDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,11 +3,10 @@ import { t } from "../../services/i18n";
|
||||
import Button from "../react/Button";
|
||||
import FormTextBox from "../react/FormTextBox";
|
||||
import Modal from "../react/Modal";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import protected_session from "../../services/protected_session";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
function ProtectedSessionPasswordDialogComponent() {
|
||||
export default function ProtectedSessionPasswordDialog() {
|
||||
const [ shown, setShown ] = useState(false);
|
||||
const [ password, setPassword ] = useState("");
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
@@ -38,11 +37,3 @@ function ProtectedSessionPasswordDialogComponent() {
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default class ProtectedSessionPasswordDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <ProtectedSessionPasswordDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import server from "../../services/server";
|
||||
import toast from "../../services/toast";
|
||||
import Button from "../react/Button";
|
||||
import Modal from "../react/Modal";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import hoisted_note from "../../services/hoisted_note";
|
||||
import type { RecentChangeRow } from "@triliumnext/commons";
|
||||
import froca from "../../services/froca";
|
||||
@@ -14,39 +13,32 @@ import { formatDateTime } from "../../utils/formatters";
|
||||
import link from "../../services/link";
|
||||
import RawHtml from "../react/RawHtml";
|
||||
import ws from "../../services/ws";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
function RecentChangesDialogComponent() {
|
||||
export default function RecentChangesDialog() {
|
||||
const [ ancestorNoteId, setAncestorNoteId ] = useState<string>();
|
||||
const [ groupedByDate, setGroupedByDate ] = useState<Map<String, RecentChangeRow[]>>();
|
||||
const [ needsRefresh, setNeedsRefresh ] = useState(false);
|
||||
const [ groupedByDate, setGroupedByDate ] = useState<Map<string, RecentChangeRow[]>>();
|
||||
const [ refreshCounter, setRefreshCounter ] = useState(0);
|
||||
const [ shown, setShown ] = useState(false);
|
||||
|
||||
useTriliumEvent("showRecentChanges", ({ ancestorNoteId }) => {
|
||||
setNeedsRefresh(true);
|
||||
useTriliumEvent("showRecentChanges", ({ ancestorNoteId }) => {
|
||||
setAncestorNoteId(ancestorNoteId ?? hoisted_note.getHoistedNoteId());
|
||||
setShown(true);
|
||||
});
|
||||
|
||||
if (!groupedByDate || needsRefresh) {
|
||||
useEffect(() => {
|
||||
if (needsRefresh) {
|
||||
setNeedsRefresh(false);
|
||||
}
|
||||
useEffect(() => {
|
||||
server.get<RecentChangeRow[]>(`recent-changes/${ancestorNoteId}`)
|
||||
.then(async (recentChanges) => {
|
||||
// preload all notes into cache
|
||||
await froca.getNotes(
|
||||
recentChanges.map((r) => r.noteId),
|
||||
true
|
||||
);
|
||||
|
||||
server.get<RecentChangeRow[]>(`recent-changes/${ancestorNoteId}`)
|
||||
.then(async (recentChanges) => {
|
||||
// preload all notes into cache
|
||||
await froca.getNotes(
|
||||
recentChanges.map((r) => r.noteId),
|
||||
true
|
||||
);
|
||||
|
||||
const groupedByDate = groupByDate(recentChanges);
|
||||
setGroupedByDate(groupedByDate);
|
||||
});
|
||||
})
|
||||
}
|
||||
const groupedByDate = groupByDate(recentChanges);
|
||||
setGroupedByDate(groupedByDate);
|
||||
});
|
||||
}, [ shown, refreshCounter ])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -57,10 +49,11 @@ function RecentChangesDialogComponent() {
|
||||
header={
|
||||
<Button
|
||||
text={t("recent_changes.erase_notes_button")}
|
||||
small style={{ padding: "0 10px" }}
|
||||
size="small"
|
||||
style={{ padding: "0 10px" }}
|
||||
onClick={() => {
|
||||
server.post("notes/erase-deleted-notes-now").then(() => {
|
||||
setNeedsRefresh(true);
|
||||
setRefreshCounter(refreshCounter + 1);
|
||||
toast.showMessage(t("recent_changes.deleted_notes_message"));
|
||||
});
|
||||
}}
|
||||
@@ -78,7 +71,7 @@ function RecentChangesDialogComponent() {
|
||||
)
|
||||
}
|
||||
|
||||
function RecentChangesTimeline({ groupedByDate, setShown }: { groupedByDate: Map<String, RecentChangeRow[]>, setShown: Dispatch<StateUpdater<boolean>> }) {
|
||||
function RecentChangesTimeline({ groupedByDate, setShown }: { groupedByDate: Map<string, RecentChangeRow[]>, setShown: Dispatch<StateUpdater<boolean>> }) {
|
||||
return (
|
||||
<>
|
||||
{ Array.from(groupedByDate.entries()).map(([dateDay, dayChanges]) => {
|
||||
@@ -113,10 +106,6 @@ function RecentChangesTimeline({ groupedByDate, setShown }: { groupedByDate: Map
|
||||
}
|
||||
|
||||
function NoteLink({ notePath, title }: { notePath: string, title: string }) {
|
||||
if (!notePath || !title) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [ noteLink, setNoteLink ] = useState<JQuery<HTMLElement> | null>(null);
|
||||
useEffect(() => {
|
||||
link.createLink(notePath, {
|
||||
@@ -155,25 +144,19 @@ function DeletedNoteLink({ change, setShown }: { change: RecentChangeRow, setSho
|
||||
);
|
||||
}
|
||||
|
||||
export default class RecentChangesDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <RecentChangesDialogComponent />
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function groupByDate(rows: RecentChangeRow[]) {
|
||||
const groupedByDate = new Map<String, RecentChangeRow[]>();
|
||||
const groupedByDate = new Map<string, RecentChangeRow[]>();
|
||||
|
||||
for (const row of rows) {
|
||||
const dateDay = row.date.substr(0, 10);
|
||||
|
||||
if (!groupedByDate.has(dateDay)) {
|
||||
groupedByDate.set(dateDay, []);
|
||||
let dateDayArray = groupedByDate.get(dateDay);
|
||||
if (!dateDayArray) {
|
||||
dateDayArray = [];
|
||||
groupedByDate.set(dateDay, dateDayArray);
|
||||
}
|
||||
|
||||
groupedByDate.get(dateDay)!.push(row);
|
||||
dateDayArray.push(row);
|
||||
}
|
||||
|
||||
return groupedByDate;
|
||||
|
||||
@@ -8,7 +8,6 @@ import server from "../../services/server";
|
||||
import toast from "../../services/toast";
|
||||
import Button from "../react/Button";
|
||||
import Modal from "../react/Modal";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import FormList, { FormListItem } from "../react/FormList";
|
||||
import utils from "../../services/utils";
|
||||
import { Dispatch, StateUpdater, useEffect, useRef, useState } from "preact/hooks";
|
||||
@@ -18,9 +17,9 @@ import type { CSSProperties } from "preact/compat";
|
||||
import open from "../../services/open";
|
||||
import ActionButton from "../react/ActionButton";
|
||||
import options from "../../services/options";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
function RevisionsDialogComponent() {
|
||||
export default function RevisionsDialog() {
|
||||
const [ note, setNote ] = useState<FNote>();
|
||||
const [ revisions, setRevisions ] = useState<RevisionItem[]>();
|
||||
const [ currentRevision, setCurrentRevision ] = useState<RevisionItem>();
|
||||
@@ -55,7 +54,7 @@ function RevisionsDialogComponent() {
|
||||
helpPageId="vZWERwf8U3nx"
|
||||
bodyStyle={{ display: "flex", height: "80vh" }}
|
||||
header={
|
||||
(!!revisions?.length && <Button text={t("revisions.delete_all_revisions")} small style={{ padding: "0 10px" }}
|
||||
(!!revisions?.length && <Button text={t("revisions.delete_all_revisions")} size="small" style={{ padding: "0 10px" }}
|
||||
onClick={async () => {
|
||||
const text = t("revisions.confirm_delete_all");
|
||||
|
||||
@@ -72,6 +71,8 @@ function RevisionsDialogComponent() {
|
||||
onHidden={() => {
|
||||
setShown(false);
|
||||
setNote(undefined);
|
||||
setCurrentRevision(undefined);
|
||||
setRevisions(undefined);
|
||||
}}
|
||||
show={shown}
|
||||
>
|
||||
@@ -202,17 +203,9 @@ function RevisionContent({ revisionItem, fullRevision }: { revisionItem?: Revisi
|
||||
return <></>;
|
||||
}
|
||||
|
||||
|
||||
switch (revisionItem.type) {
|
||||
case "text": {
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => {
|
||||
if (contentRef.current?.querySelector("span.math-tex")) {
|
||||
renderMathInElement(contentRef.current, { trust: true });
|
||||
}
|
||||
});
|
||||
return <div ref={contentRef} className="ck-content" dangerouslySetInnerHTML={{ __html: content as string }}></div>
|
||||
}
|
||||
case "text":
|
||||
return <RevisionContentText content={content} />
|
||||
case "code":
|
||||
return <pre style={CODE_STYLE}>{content}</pre>;
|
||||
case "image":
|
||||
@@ -264,6 +257,16 @@ function RevisionContent({ revisionItem, fullRevision }: { revisionItem?: Revisi
|
||||
}
|
||||
}
|
||||
|
||||
function RevisionContentText({ content }: { content: string | Buffer<ArrayBufferLike> | undefined }) {
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => {
|
||||
if (contentRef.current?.querySelector("span.math-tex")) {
|
||||
renderMathInElement(contentRef.current, { trust: true });
|
||||
}
|
||||
}, [content]);
|
||||
return <div ref={contentRef} className="ck-content" dangerouslySetInnerHTML={{ __html: content as string }}></div>
|
||||
}
|
||||
|
||||
function RevisionFooter({ note }: { note?: FNote }) {
|
||||
if (!note) {
|
||||
return <></>;
|
||||
@@ -291,14 +294,6 @@ function RevisionFooter({ note }: { note?: FNote }) {
|
||||
</>;
|
||||
}
|
||||
|
||||
export default class RevisionsDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <RevisionsDialogComponent />
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function getNote(noteId?: string | null) {
|
||||
if (noteId) {
|
||||
return await froca.getNote(noteId);
|
||||
|
||||
@@ -5,12 +5,11 @@ import FormCheckbox from "../react/FormCheckbox";
|
||||
import FormRadioGroup from "../react/FormRadioGroup";
|
||||
import FormTextBox from "../react/FormTextBox";
|
||||
import Modal from "../react/Modal";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import server from "../../services/server";
|
||||
import FormGroup from "../react/FormGroup";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
function SortChildNotesDialogComponent() {
|
||||
export default function SortChildNotesDialog() {
|
||||
const [ parentNoteId, setParentNoteId ] = useState<string>();
|
||||
const [ sortBy, setSortBy ] = useState("title");
|
||||
const [ sortDirection, setSortDirection ] = useState("asc");
|
||||
@@ -83,20 +82,9 @@ function SortChildNotesDialogComponent() {
|
||||
label={t("sort_child_notes.sort_with_respect_to_different_character_sorting")}
|
||||
currentValue={sortNatural} onChange={setSortNatural}
|
||||
/>
|
||||
<FormGroup className="form-check" label={t("sort_child_notes.natural_sort_language")} description={t("sort_child_notes.the_language_code_for_natural_sort")}>
|
||||
<FormTextBox
|
||||
name="sort-locale"
|
||||
currentValue={sortLocale} onChange={setSortLocale}
|
||||
/>
|
||||
<FormGroup name="sort-locale" className="form-check" label={t("sort_child_notes.natural_sort_language")} description={t("sort_child_notes.the_language_code_for_natural_sort")}>
|
||||
<FormTextBox currentValue={sortLocale} onChange={setSortLocale} />
|
||||
</FormGroup>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default class SortChildNotesDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <SortChildNotesDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,13 +5,12 @@ import FormCheckbox from "../react/FormCheckbox";
|
||||
import FormFileUpload from "../react/FormFileUpload";
|
||||
import FormGroup from "../react/FormGroup";
|
||||
import Modal from "../react/Modal";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import options from "../../services/options";
|
||||
import importService from "../../services/import.js";
|
||||
import tree from "../../services/tree";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
function UploadAttachmentsDialogComponent() {
|
||||
export default function UploadAttachmentsDialog() {
|
||||
const [ parentNoteId, setParentNoteId ] = useState<string>();
|
||||
const [ files, setFiles ] = useState<FileList | null>(null);
|
||||
const [ shrinkImages, setShrinkImages ] = useState(options.is("compressImages"));
|
||||
@@ -24,12 +23,12 @@ function UploadAttachmentsDialogComponent() {
|
||||
setShown(true);
|
||||
});
|
||||
|
||||
if (parentNoteId) {
|
||||
useEffect(() => {
|
||||
tree.getNoteTitle(parentNoteId).then((noteTitle) =>
|
||||
setDescription(t("upload_attachments.files_will_be_uploaded", { noteTitle })));
|
||||
}, [parentNoteId]);
|
||||
}
|
||||
useEffect(() => {
|
||||
if (!parentNoteId) return;
|
||||
|
||||
tree.getNoteTitle(parentNoteId).then((noteTitle) =>
|
||||
setDescription(t("upload_attachments.files_will_be_uploaded", { noteTitle })));
|
||||
}, [parentNoteId]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -51,13 +50,12 @@ function UploadAttachmentsDialogComponent() {
|
||||
onHidden={() => setShown(false)}
|
||||
show={shown}
|
||||
>
|
||||
<FormGroup label={t("upload_attachments.choose_files")} description={description}>
|
||||
<FormGroup name="files" label={t("upload_attachments.choose_files")} description={description}>
|
||||
<FormFileUpload onChange={setFiles} multiple />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup label={t("upload_attachments.options")}>
|
||||
<FormCheckbox
|
||||
name="shrink-images"
|
||||
<FormGroup name="shrink-images" label={t("upload_attachments.options")}>
|
||||
<FormCheckbox
|
||||
hint={t("upload_attachments.tooltip")} label={t("upload_attachments.shrink_images")}
|
||||
currentValue={shrinkImages} onChange={setShrinkImages}
|
||||
/>
|
||||
@@ -65,11 +63,3 @@ function UploadAttachmentsDialogComponent() {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default class UploadAttachmentsDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <UploadAttachmentsDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
import attributeService from "../services/attributes.js";
|
||||
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import type { EventData } from "../components/app_context.js";
|
||||
import { Dropdown } from "bootstrap";
|
||||
|
||||
type Editability = "auto" | "readOnly" | "autoReadOnlyDisabled";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="dropdown editability-select-widget">
|
||||
<style>
|
||||
.editability-dropdown {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.editability-dropdown .dropdown-item {
|
||||
display: flex !importamt;
|
||||
}
|
||||
|
||||
.editability-dropdown .dropdown-item > div {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.editability-dropdown .description {
|
||||
font-size: small;
|
||||
color: var(--muted-text-color);
|
||||
white-space: normal;
|
||||
}
|
||||
</style>
|
||||
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm select-button dropdown-toggle editability-button">
|
||||
<span class="editability-active-desc">${t("editability_select.auto")}</span>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<div class="editability-dropdown dropdown-menu dropdown-menu-right tn-dropdown-list">
|
||||
<a class="dropdown-item" href="#" data-editability="auto">
|
||||
<span class="check">✓</span>
|
||||
<div>
|
||||
${t("editability_select.auto")}
|
||||
<div class="description">${t("editability_select.note_is_editable")}</div>
|
||||
</div>
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" data-editability="readOnly">
|
||||
<span class="check">✓</span>
|
||||
<div>
|
||||
${t("editability_select.read_only")}
|
||||
<div class="description">${t("editability_select.note_is_read_only")}</div>
|
||||
</div>
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" data-editability="autoReadOnlyDisabled">
|
||||
<span class="check">✓</span>
|
||||
<div>
|
||||
${t("editability_select.always_editable")}
|
||||
<div class="description">${t("editability_select.note_is_always_editable")}</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
export default class EditabilitySelectWidget extends NoteContextAwareWidget {
|
||||
|
||||
private dropdown!: Dropdown;
|
||||
private $editabilityActiveDesc!: JQuery<HTMLElement>;
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
|
||||
this.dropdown = Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']")[0]);
|
||||
|
||||
this.$editabilityActiveDesc = this.$widget.find(".editability-active-desc");
|
||||
|
||||
this.$widget.on("click", ".dropdown-item", async (e) => {
|
||||
this.dropdown.toggle();
|
||||
|
||||
const editability = $(e.target).closest("[data-editability]").attr("data-editability");
|
||||
|
||||
if (!this.note || !this.noteId) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const ownedAttr of this.note.getOwnedLabels()) {
|
||||
if (["readOnly", "autoReadOnlyDisabled"].includes(ownedAttr.name)) {
|
||||
await attributeService.removeAttributeById(this.noteId, ownedAttr.attributeId);
|
||||
}
|
||||
}
|
||||
|
||||
if (editability && editability !== "auto") {
|
||||
await attributeService.addLabel(this.noteId, editability);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote) {
|
||||
let editability: Editability = "auto";
|
||||
|
||||
if (this.note?.isLabelTruthy("readOnly")) {
|
||||
editability = "readOnly";
|
||||
} else if (this.note?.isLabelTruthy("autoReadOnlyDisabled")) {
|
||||
editability = "autoReadOnlyDisabled";
|
||||
}
|
||||
|
||||
const labels = {
|
||||
auto: t("editability_select.auto"),
|
||||
readOnly: t("editability_select.read_only"),
|
||||
autoReadOnlyDisabled: t("editability_select.always_editable")
|
||||
};
|
||||
|
||||
this.$widget.find(".dropdown-item").removeClass("selected");
|
||||
this.$widget.find(`.dropdown-item[data-editability='${editability}']`).addClass("selected");
|
||||
|
||||
this.$editabilityActiveDesc.text(labels[editability]);
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
if (loadResults.getAttributeRows().find((attr) => attr.noteId === this.noteId)) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
// taken from the HTML source of https://boxicons.com/
|
||||
|
||||
interface Category {
|
||||
export interface Category {
|
||||
name: string;
|
||||
id: number;
|
||||
}
|
||||
|
||||
59
apps/client/src/widgets/note_icon.css
Normal file
59
apps/client/src/widgets/note_icon.css
Normal file
@@ -0,0 +1,59 @@
|
||||
.note-icon-widget {
|
||||
padding-top: 3px;
|
||||
padding-left: 7px;
|
||||
margin-right: 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.note-icon-widget button.note-icon {
|
||||
font-size: 180%;
|
||||
background-color: transparent;
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
padding: 6px;
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
|
||||
.note-icon-widget button.note-icon:hover {
|
||||
border: 1px solid var(--main-border-color);
|
||||
}
|
||||
|
||||
.note-icon-widget .dropdown-menu {
|
||||
border-radius: 10px;
|
||||
border-width: 2px;
|
||||
box-shadow: 10px 10px 93px -25px black;
|
||||
padding: 10px 15px 10px 15px !important;
|
||||
}
|
||||
|
||||
.note-icon-widget .filter-row {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
padding-right: 20px;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.note-icon-widget .filter-row span {
|
||||
display: block;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.note-icon-widget .icon-list {
|
||||
height: 500px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.note-icon-widget .icon-list span {
|
||||
display: inline-block;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
font-size: 180%;
|
||||
}
|
||||
|
||||
.note-icon-widget .icon-list span:hover {
|
||||
border: 1px solid var(--main-border-color);
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
import { t } from "../services/i18n.js";
|
||||
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
||||
import attributeService from "../services/attributes.js";
|
||||
import server from "../services/server.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import type { EventData } from "../components/app_context.js";
|
||||
import type { Icon } from "./icon_list.js";
|
||||
import { Dropdown } from "bootstrap";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="note-icon-widget dropdown">
|
||||
<style>
|
||||
.note-icon-widget {
|
||||
padding-top: 3px;
|
||||
padding-left: 7px;
|
||||
margin-right: 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.note-icon-widget button.note-icon {
|
||||
font-size: 180%;
|
||||
background-color: transparent;
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
padding: 6px;
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
|
||||
.note-icon-widget button.note-icon:hover {
|
||||
border: 1px solid var(--main-border-color);
|
||||
}
|
||||
|
||||
.note-icon-widget .dropdown-menu {
|
||||
border-radius: 10px;
|
||||
border-width: 2px;
|
||||
box-shadow: 10px 10px 93px -25px black;
|
||||
padding: 10px 15px 10px 15px !important;
|
||||
}
|
||||
|
||||
.note-icon-widget .filter-row {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
padding-right: 20px;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.note-icon-widget .filter-row span {
|
||||
display: block;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.note-icon-widget .icon-list {
|
||||
height: 500px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.note-icon-widget .icon-list span {
|
||||
display: inline-block;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
font-size: 180%;
|
||||
}
|
||||
|
||||
.note-icon-widget .icon-list span:hover {
|
||||
border: 1px solid var(--main-border-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
<button class="btn dropdown-toggle note-icon" type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="${t("note_icon.change_note_icon")}"></button>
|
||||
<div class="dropdown-menu" aria-labelledby="note-path-list-button" style="width: 610px;">
|
||||
<div class="filter-row">
|
||||
<span>${t("note_icon.category")}</span> <select name="icon-category" class="form-select"></select>
|
||||
|
||||
<span>${t("note_icon.search")}</span> <input type="text" name="icon-search" class="form-control" />
|
||||
</div>
|
||||
|
||||
<div class="icon-list"></div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
interface IconToCountCache {
|
||||
iconClassToCountMap: Record<string, number>;
|
||||
}
|
||||
|
||||
export default class NoteIconWidget extends NoteContextAwareWidget {
|
||||
|
||||
private dropdown!: bootstrap.Dropdown;
|
||||
private $icon!: JQuery<HTMLElement>;
|
||||
private $iconList!: JQuery<HTMLElement>;
|
||||
private $iconCategory!: JQuery<HTMLElement>;
|
||||
private $iconSearch!: JQuery<HTMLElement>;
|
||||
private iconToCountCache!: Promise<IconToCountCache | null> | null;
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.dropdown = Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']")[0]);
|
||||
|
||||
this.$icon = this.$widget.find("button.note-icon");
|
||||
this.$iconList = this.$widget.find(".icon-list");
|
||||
this.$iconList.on("click", "span", async (e) => {
|
||||
const clazz = $(e.target).attr("class");
|
||||
|
||||
if (this.noteId && this.note) {
|
||||
await attributeService.setLabel(this.noteId, this.note.hasOwnedLabel("workspace") ? "workspaceIconClass" : "iconClass", clazz);
|
||||
}
|
||||
});
|
||||
|
||||
this.$iconCategory = this.$widget.find("select[name='icon-category']");
|
||||
this.$iconCategory.on("change", () => this.renderDropdown());
|
||||
this.$iconCategory.on("click", (e) => e.stopPropagation());
|
||||
|
||||
this.$iconSearch = this.$widget.find("input[name='icon-search']");
|
||||
this.$iconSearch.on("input", () => this.renderDropdown());
|
||||
|
||||
this.$widget.on("show.bs.dropdown", async () => {
|
||||
const { categories } = (await import("./icon_list.js")).default;
|
||||
|
||||
this.$iconCategory.empty();
|
||||
|
||||
for (const category of categories) {
|
||||
this.$iconCategory.append($("<option>").text(category.name).attr("value", category.id));
|
||||
}
|
||||
|
||||
this.$iconSearch.val("");
|
||||
|
||||
this.renderDropdown();
|
||||
});
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote) {
|
||||
this.$icon.removeClass().addClass(`${note.getIcon()} note-icon`);
|
||||
this.$icon.prop("disabled", !!(this.noteContext?.viewScope?.viewMode !== "default"));
|
||||
this.dropdown.hide();
|
||||
}
|
||||
|
||||
async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
if (this.noteId && loadResults.isNoteReloaded(this.noteId)) {
|
||||
this.refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
for (const attr of loadResults.getAttributeRows()) {
|
||||
if (attr.type === "label" && ["iconClass", "workspaceIconClass"].includes(attr.name ?? "") && attributeService.isAffecting(attr, this.note)) {
|
||||
this.refresh();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async renderDropdown() {
|
||||
const iconToCount = await this.getIconToCountMap();
|
||||
const { icons } = (await import("./icon_list.js")).default;
|
||||
|
||||
this.$iconList.empty();
|
||||
|
||||
if (this.getIconLabels().length > 0) {
|
||||
this.$iconList.append(
|
||||
$(`<div style="text-align: center">`).append(
|
||||
$(`<button class="btn btn-sm">${t("note_icon.reset-default")}</button>`).on("click", () =>
|
||||
this.getIconLabels().forEach((label) => {
|
||||
if (this.noteId) {
|
||||
attributeService.removeAttributeById(this.noteId, label.attributeId);
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const categoryId = parseInt(String(this.$iconCategory.find("option:selected")?.val()));
|
||||
const search = String(this.$iconSearch.val())?.trim()?.toLowerCase();
|
||||
|
||||
const filteredIcons = icons.filter((icon) => {
|
||||
if (categoryId && icon.category_id !== categoryId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (search) {
|
||||
if (!icon.name.includes(search) && !icon.term?.find((t) => t.includes(search))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (iconToCount) {
|
||||
filteredIcons.sort((a, b) => {
|
||||
const countA = iconToCount[a.className ?? ""] || 0;
|
||||
const countB = iconToCount[b.className ?? ""] || 0;
|
||||
|
||||
return countB - countA;
|
||||
});
|
||||
}
|
||||
|
||||
for (const icon of filteredIcons) {
|
||||
this.$iconList.append(this.renderIcon(icon));
|
||||
}
|
||||
|
||||
this.$iconSearch.focus();
|
||||
}
|
||||
|
||||
async getIconToCountMap() {
|
||||
if (!this.iconToCountCache) {
|
||||
this.iconToCountCache = server.get<IconToCountCache>("other/icon-usage");
|
||||
setTimeout(() => (this.iconToCountCache = null), 20000); // invalidate cache after 20 seconds
|
||||
}
|
||||
|
||||
return (await this.iconToCountCache)?.iconClassToCountMap;
|
||||
}
|
||||
|
||||
renderIcon(icon: Icon) {
|
||||
return $("<span>")
|
||||
.addClass("bx " + icon.className)
|
||||
.attr("title", icon.name);
|
||||
}
|
||||
|
||||
getIconLabels() {
|
||||
if (!this.note) {
|
||||
return [];
|
||||
}
|
||||
return this.note.getOwnedLabels().filter((label) => ["workspaceIconClass", "iconClass"].includes(label.name));
|
||||
}
|
||||
}
|
||||
184
apps/client/src/widgets/note_icon.tsx
Normal file
184
apps/client/src/widgets/note_icon.tsx
Normal file
@@ -0,0 +1,184 @@
|
||||
import Dropdown from "./react/Dropdown";
|
||||
import "./note_icon.css";
|
||||
import { t } from "i18next";
|
||||
import { useNoteContext, useNoteLabel } from "./react/hooks";
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
import server from "../services/server";
|
||||
import type { Category, Icon } from "./icon_list";
|
||||
import FormTextBox from "./react/FormTextBox";
|
||||
import FormSelect from "./react/FormSelect";
|
||||
import FNote from "../entities/fnote";
|
||||
import attributes from "../services/attributes";
|
||||
import Button from "./react/Button";
|
||||
|
||||
interface IconToCountCache {
|
||||
iconClassToCountMap: Record<string, number>;
|
||||
}
|
||||
|
||||
interface IconData {
|
||||
iconToCount: Record<string, number>;
|
||||
categories: Category[];
|
||||
icons: Icon[];
|
||||
}
|
||||
|
||||
let fullIconData: {
|
||||
categories: Category[];
|
||||
icons: Icon[];
|
||||
};
|
||||
let iconToCountCache!: Promise<IconToCountCache> | null;
|
||||
|
||||
export default function NoteIcon() {
|
||||
const { note, viewScope } = useNoteContext();
|
||||
const [ icon, setIcon ] = useState<string | null | undefined>();
|
||||
const [ iconClass ] = useNoteLabel(note, "iconClass");
|
||||
const [ workspaceIconClass ] = useNoteLabel(note, "workspaceIconClass");
|
||||
|
||||
useEffect(() => {
|
||||
setIcon(note?.getIcon());
|
||||
}, [ note, iconClass, workspaceIconClass ]);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
className="note-icon-widget"
|
||||
title={t("note_icon.change_note_icon")}
|
||||
dropdownContainerStyle={{ width: "610px" }}
|
||||
buttonClassName={`note-icon ${icon ?? "bx bx-empty"}`}
|
||||
hideToggleArrow
|
||||
disabled={viewScope?.viewMode !== "default"}
|
||||
>
|
||||
{ note && <NoteIconList note={note} /> }
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
|
||||
function NoteIconList({ note }: { note: FNote }) {
|
||||
const searchBoxRef = useRef<HTMLInputElement>(null);
|
||||
const [ search, setSearch ] = useState<string>();
|
||||
const [ categoryId, setCategoryId ] = useState<string>("0");
|
||||
const [ iconData, setIconData ] = useState<IconData>();
|
||||
|
||||
useEffect(() => {
|
||||
async function loadIcons() {
|
||||
if (!fullIconData) {
|
||||
fullIconData = (await import("./icon_list.js")).default;
|
||||
}
|
||||
|
||||
// Filter by text and/or category.
|
||||
let icons: Icon[] = fullIconData.icons;
|
||||
const processedSearch = search?.trim()?.toLowerCase();
|
||||
if (processedSearch || categoryId) {
|
||||
icons = icons.filter((icon) => {
|
||||
if (categoryId !== "0" && String(icon.category_id) !== categoryId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (processedSearch) {
|
||||
if (!icon.name.includes(processedSearch) &&
|
||||
!icon.term?.find((t) => t.includes(processedSearch))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// Sort by count.
|
||||
const iconToCount = await getIconToCountMap();
|
||||
if (iconToCount) {
|
||||
icons.sort((a, b) => {
|
||||
const countA = iconToCount[a.className ?? ""] || 0;
|
||||
const countB = iconToCount[b.className ?? ""] || 0;
|
||||
|
||||
return countB - countA;
|
||||
});
|
||||
}
|
||||
|
||||
setIconData({
|
||||
iconToCount,
|
||||
icons,
|
||||
categories: fullIconData.categories
|
||||
})
|
||||
}
|
||||
|
||||
loadIcons();
|
||||
}, [ search, categoryId ]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div class="filter-row">
|
||||
<span>{t("note_icon.category")}</span>
|
||||
<FormSelect
|
||||
name="icon-category"
|
||||
values={fullIconData?.categories ?? []}
|
||||
currentValue={categoryId} onChange={setCategoryId}
|
||||
keyProperty="id" titleProperty="name"
|
||||
/>
|
||||
|
||||
<span>{t("note_icon.search")}</span>
|
||||
<FormTextBox
|
||||
inputRef={searchBoxRef}
|
||||
type="text"
|
||||
name="icon-search"
|
||||
currentValue={search} onChange={setSearch}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="icon-list"
|
||||
onClick={(e) => {
|
||||
const clickedTarget = e.target as HTMLElement;
|
||||
|
||||
if (!clickedTarget.classList.contains("bx")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const iconClass = Array.from(clickedTarget.classList.values()).join(" ");
|
||||
if (note) {
|
||||
const attributeToSet = note.hasOwnedLabel("workspace") ? "workspaceIconClass" : "iconClass";
|
||||
attributes.setLabel(note.noteId, attributeToSet, iconClass);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{getIconLabels(note).length > 0 && (
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<Button
|
||||
text={t("note_icon.reset-default")}
|
||||
onClick={() => {
|
||||
if (!note) {
|
||||
return;
|
||||
}
|
||||
for (const label of getIconLabels(note)) {
|
||||
attributes.removeAttributeById(note.noteId, label.attributeId);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(iconData?.icons ?? []).map(({className, name}) => (
|
||||
<span class={`bx ${className}`} title={name} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
async function getIconToCountMap() {
|
||||
if (!iconToCountCache) {
|
||||
iconToCountCache = server.get<IconToCountCache>("other/icon-usage");
|
||||
setTimeout(() => (iconToCountCache = null), 20000); // invalidate cache after 20 seconds
|
||||
}
|
||||
|
||||
return (await iconToCountCache).iconClassToCountMap;
|
||||
}
|
||||
|
||||
function getIconLabels(note: FNote) {
|
||||
if (!note) {
|
||||
return [];
|
||||
}
|
||||
return note.getOwnedLabels()
|
||||
.filter((label) => ["workspaceIconClass", "iconClass"]
|
||||
.includes(label.name));
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
import { Dropdown } from "bootstrap";
|
||||
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
||||
import { getAvailableLocales, getLocaleById, t } from "../services/i18n.js";
|
||||
import type { EventData } from "../components/app_context.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import attributes from "../services/attributes.js";
|
||||
import type { Locale } from "@triliumnext/commons";
|
||||
import options from "../services/options.js";
|
||||
import appContext from "../components/app_context.js";
|
||||
|
||||
const TPL = /*html*/`\
|
||||
<div class="dropdown note-language-widget">
|
||||
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle select-button note-language-button">
|
||||
<span class="note-language-desc"></span>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<div class="note-language-dropdown dropdown-menu dropdown-menu-left tn-dropdown-list"></div>
|
||||
<button class="language-help-button icon-action bx bx-help-circle" type="button" data-in-app-help="B0lcI9xz1r8K" title="${t("open-help-page")}"></button>
|
||||
|
||||
<style>
|
||||
.note-language-widget {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.language-help-button {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.note-language-dropdown [dir=rtl] {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.dropdown-item.rtl > .check {
|
||||
order: 1;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const DEFAULT_LOCALE: Locale = {
|
||||
id: "",
|
||||
name: t("note_language.not_set")
|
||||
};
|
||||
|
||||
export default class NoteLanguageWidget extends NoteContextAwareWidget {
|
||||
|
||||
private dropdown!: Dropdown;
|
||||
private $noteLanguageDropdown!: JQuery<HTMLElement>;
|
||||
private $noteLanguageDesc!: JQuery<HTMLElement>;
|
||||
private locales: (Locale | "---")[];
|
||||
private currentLanguageId?: string;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.locales = NoteLanguageWidget.#buildLocales();
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.dropdown = Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']")[0]);
|
||||
this.$widget.on("show.bs.dropdown", () => this.renderDropdown());
|
||||
|
||||
this.$noteLanguageDropdown = this.$widget.find(".note-language-dropdown")
|
||||
this.$noteLanguageDesc = this.$widget.find(".note-language-desc");
|
||||
}
|
||||
|
||||
renderDropdown() {
|
||||
this.$noteLanguageDropdown.empty();
|
||||
|
||||
if (!this.note) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const locale of this.locales) {
|
||||
if (typeof locale === "object") {
|
||||
const $title = $("<span>").text(locale.name);
|
||||
|
||||
const $link = $('<a class="dropdown-item">')
|
||||
.attr("data-language", locale.id)
|
||||
.append('<span class="check">✓</span> ')
|
||||
.append($title)
|
||||
.on("click", () => {
|
||||
const languageId = $link.attr("data-language") ?? "";
|
||||
this.save(languageId);
|
||||
});
|
||||
|
||||
if (locale.rtl) {
|
||||
$link.attr("dir", "rtl");
|
||||
}
|
||||
|
||||
if (locale.id === this.currentLanguageId) {
|
||||
$link.addClass("selected");
|
||||
}
|
||||
|
||||
this.$noteLanguageDropdown.append($link);
|
||||
} else {
|
||||
this.$noteLanguageDropdown.append('<div class="dropdown-divider"></div>');
|
||||
}
|
||||
}
|
||||
|
||||
const $configureLink = $('<a class="dropdown-item">')
|
||||
.append(`<span>${t("note_language.configure-languages")}</span>`)
|
||||
.on("click", () => appContext.tabManager.openContextWithNote("_optionsLocalization", { activate: true }));
|
||||
this.$noteLanguageDropdown.append($configureLink);
|
||||
}
|
||||
|
||||
async save(languageId: string) {
|
||||
if (!this.note) {
|
||||
return;
|
||||
}
|
||||
|
||||
attributes.setAttribute(this.note, "label", "language", languageId);
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote) {
|
||||
const currentLanguageId = note.getLabelValue("language") ?? "";
|
||||
const language = getLocaleById(currentLanguageId) ?? DEFAULT_LOCALE;
|
||||
this.currentLanguageId = currentLanguageId;
|
||||
this.$noteLanguageDesc.text(language.name);
|
||||
this.dropdown.hide();
|
||||
}
|
||||
|
||||
async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
if (loadResults.isOptionReloaded("languages")) {
|
||||
this.locales = NoteLanguageWidget.#buildLocales();
|
||||
}
|
||||
|
||||
if (loadResults.getAttributeRows().find((a) => a.noteId === this.noteId && a.name === "language")) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
static #buildLocales() {
|
||||
const enabledLanguages = JSON.parse(options.get("languages") ?? "[]") as string[];
|
||||
const filteredLanguages = getAvailableLocales().filter((l) => typeof l !== "object" || enabledLanguages.includes(l.id));
|
||||
const leftToRightLanguages = filteredLanguages.filter((l) => !l.rtl);
|
||||
const rightToLeftLanguages = filteredLanguages.filter((l) => l.rtl);
|
||||
|
||||
let locales: ("---" | Locale)[] = [
|
||||
DEFAULT_LOCALE
|
||||
];
|
||||
|
||||
if (leftToRightLanguages.length > 0) {
|
||||
locales = [
|
||||
...locales,
|
||||
"---",
|
||||
...leftToRightLanguages
|
||||
];
|
||||
}
|
||||
|
||||
if (rightToLeftLanguages.length > 0) {
|
||||
locales = [
|
||||
...locales,
|
||||
"---",
|
||||
...rightToLeftLanguages
|
||||
];
|
||||
}
|
||||
|
||||
// This will separate the list of languages from the "Configure languages" button.
|
||||
// If there is at least one language.
|
||||
locales.push("---");
|
||||
return locales;
|
||||
}
|
||||
|
||||
}
|
||||
25
apps/client/src/widgets/note_title.css
Normal file
25
apps/client/src/widgets/note_title.css
Normal file
@@ -0,0 +1,25 @@
|
||||
.note-title-widget {
|
||||
flex-grow: 1000;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.note-title-widget input.note-title {
|
||||
font-size: 110%;
|
||||
border: 0;
|
||||
margin: 2px 0px;
|
||||
min-width: 5em;
|
||||
width: 100%;
|
||||
padding: 1px 12px;
|
||||
}
|
||||
|
||||
.note-title-widget input.note-title.protected {
|
||||
text-shadow: 4px 4px 4px var(--muted-text-color);
|
||||
}
|
||||
|
||||
body.mobile .note-title-widget input.note-title {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body.desktop .note-title-widget input.note-title {
|
||||
font-size: 180%;
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
import { t } from "../services/i18n.js";
|
||||
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
||||
import protectedSessionHolder from "../services/protected_session_holder.js";
|
||||
import server from "../services/server.js";
|
||||
import SpacedUpdate from "../services/spaced_update.js";
|
||||
import appContext, { type EventData } from "../components/app_context.js";
|
||||
import branchService from "../services/branches.js";
|
||||
import shortcutService from "../services/shortcuts.js";
|
||||
import utils from "../services/utils.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="note-title-widget">
|
||||
<style>
|
||||
.note-title-widget {
|
||||
flex-grow: 1000;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.note-title-widget input.note-title {
|
||||
font-size: 110%;
|
||||
border: 0;
|
||||
margin: 2px 0px;
|
||||
min-width: 5em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
body.mobile .note-title-widget input.note-title {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body.desktop .note-title-widget input.note-title {
|
||||
font-size: 180%;
|
||||
}
|
||||
|
||||
.note-title-widget input.note-title.protected {
|
||||
text-shadow: 4px 4px 4px var(--muted-text-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
<input autocomplete="off" value="" placeholder="${t("note_title.placeholder")}" class="note-title" tabindex="100">
|
||||
</div>`;
|
||||
|
||||
export default class NoteTitleWidget extends NoteContextAwareWidget {
|
||||
|
||||
private $noteTitle!: JQuery<HTMLElement>;
|
||||
private deleteNoteOnEscape: boolean;
|
||||
private spacedUpdate: SpacedUpdate;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.spacedUpdate = new SpacedUpdate(async () => {
|
||||
const title = this.$noteTitle.val();
|
||||
|
||||
if (this.note) {
|
||||
protectedSessionHolder.touchProtectedSessionIfNecessary(this.note);
|
||||
}
|
||||
|
||||
await server.put(`notes/${this.noteId}/title`, { title }, this.componentId);
|
||||
});
|
||||
|
||||
this.deleteNoteOnEscape = false;
|
||||
|
||||
appContext.addBeforeUnloadListener(this);
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$noteTitle = this.$widget.find(".note-title");
|
||||
|
||||
this.$noteTitle.on("input", () => this.spacedUpdate.scheduleUpdate());
|
||||
|
||||
this.$noteTitle.on("blur", () => {
|
||||
this.spacedUpdate.updateNowIfNecessary();
|
||||
|
||||
this.deleteNoteOnEscape = false;
|
||||
});
|
||||
|
||||
shortcutService.bindElShortcut(this.$noteTitle, "esc", () => {
|
||||
if (this.deleteNoteOnEscape && this.noteContext?.isActive() && this.noteContext?.note) {
|
||||
branchService.deleteNotes(Object.values(this.noteContext.note.parentToBranch));
|
||||
}
|
||||
});
|
||||
|
||||
shortcutService.bindElShortcut(this.$noteTitle, "return", () => {
|
||||
this.triggerCommand("focusOnDetail", { ntxId: this.noteContext?.ntxId });
|
||||
});
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote) {
|
||||
const isReadOnly =
|
||||
(note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable())
|
||||
|| utils.isLaunchBarConfig(note.noteId)
|
||||
|| this.noteContext?.viewScope?.viewMode !== "default";
|
||||
|
||||
this.$noteTitle.val(isReadOnly ? (await this.noteContext?.getNavigationTitle()) || "" : note.title);
|
||||
this.$noteTitle.prop("readonly", isReadOnly);
|
||||
|
||||
this.setProtectedStatus(note);
|
||||
}
|
||||
|
||||
setProtectedStatus(note: FNote) {
|
||||
this.$noteTitle.toggleClass("protected", !!note.isProtected);
|
||||
}
|
||||
|
||||
async beforeNoteSwitchEvent({ noteContext }: EventData<"beforeNoteSwitch">) {
|
||||
if (this.isNoteContext(noteContext.ntxId)) {
|
||||
await this.spacedUpdate.updateNowIfNecessary();
|
||||
}
|
||||
}
|
||||
|
||||
async beforeNoteContextRemoveEvent({ ntxIds }: EventData<"beforeNoteContextRemove">) {
|
||||
if (this.isNoteContext(ntxIds)) {
|
||||
await this.spacedUpdate.updateNowIfNecessary();
|
||||
}
|
||||
}
|
||||
|
||||
focusOnTitleEvent() {
|
||||
if (this.noteContext && this.noteContext.isActive()) {
|
||||
this.$noteTitle.trigger("focus");
|
||||
}
|
||||
}
|
||||
|
||||
focusAndSelectTitleEvent({ isNewNote } = { isNewNote: false }) {
|
||||
if (this.noteContext && this.noteContext.isActive()) {
|
||||
this.$noteTitle.trigger("focus").trigger("select");
|
||||
|
||||
this.deleteNoteOnEscape = isNewNote;
|
||||
}
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
if (loadResults.isNoteReloaded(this.noteId) && this.note) {
|
||||
// not updating the title specifically since the synced title might be older than what the user is currently typing
|
||||
this.setProtectedStatus(this.note);
|
||||
}
|
||||
|
||||
if (loadResults.isNoteReloaded(this.noteId, this.componentId)) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
beforeUnloadEvent() {
|
||||
return this.spacedUpdate.isAllSavedAndTriggerUpdate();
|
||||
}
|
||||
}
|
||||
99
apps/client/src/widgets/note_title.tsx
Normal file
99
apps/client/src/widgets/note_title.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
import { t } from "../services/i18n";
|
||||
import FormTextBox from "./react/FormTextBox";
|
||||
import { useNoteContext, useNoteProperty, useSpacedUpdate, useTriliumEvent, useTriliumEvents } from "./react/hooks";
|
||||
import protected_session_holder from "../services/protected_session_holder";
|
||||
import server from "../services/server";
|
||||
import "./note_title.css";
|
||||
import { isLaunchBarConfig } from "../services/utils";
|
||||
import appContext from "../components/app_context";
|
||||
import branches from "../services/branches";
|
||||
|
||||
export default function NoteTitleWidget() {
|
||||
const { note, noteId, componentId, viewScope, noteContext, parentComponent } = useNoteContext();
|
||||
const title = useNoteProperty(note, "title", componentId);
|
||||
const isProtected = useNoteProperty(note, "isProtected");
|
||||
const newTitle = useRef("");
|
||||
|
||||
const [ isReadOnly, setReadOnly ] = useState<boolean>(false);
|
||||
const [ navigationTitle, setNavigationTitle ] = useState<string | null>(null);
|
||||
|
||||
// Manage read-only
|
||||
useEffect(() => {
|
||||
const isReadOnly = note === null
|
||||
|| note === undefined
|
||||
|| (note.isProtected && !protected_session_holder.isProtectedSessionAvailable())
|
||||
|| isLaunchBarConfig(note.noteId)
|
||||
|| viewScope?.viewMode !== "default";
|
||||
setReadOnly(isReadOnly);
|
||||
}, [ note, note?.noteId, note?.isProtected, viewScope?.viewMode ]);
|
||||
|
||||
// Manage the title for read-only notes
|
||||
useEffect(() => {
|
||||
if (isReadOnly) {
|
||||
noteContext?.getNavigationTitle().then(setNavigationTitle);
|
||||
}
|
||||
}, [isReadOnly]);
|
||||
|
||||
// Save changes to title.
|
||||
const spacedUpdate = useSpacedUpdate(async () => {
|
||||
if (!note) {
|
||||
return;
|
||||
}
|
||||
protected_session_holder.touchProtectedSessionIfNecessary(note);
|
||||
await server.put<void>(`notes/${noteId}/title`, { title: newTitle.current }, componentId);
|
||||
});
|
||||
|
||||
// Prevent user from navigating away if the spaced update is not done.
|
||||
useEffect(() => {
|
||||
appContext.addBeforeUnloadListener(() => spacedUpdate.isAllSavedAndTriggerUpdate());
|
||||
}, []);
|
||||
useTriliumEvents([ "beforeNoteSwitch", "beforeNoteContextRemove" ], () => spacedUpdate.updateNowIfNecessary());
|
||||
|
||||
// Manage focus.
|
||||
const textBoxRef = useRef<HTMLInputElement>(null);
|
||||
const isNewNote = useRef<boolean>();
|
||||
useTriliumEvents([ "focusOnTitle", "focusAndSelectTitle" ], (e, eventName) => {
|
||||
if (noteContext?.isActive() && textBoxRef.current) {
|
||||
textBoxRef.current.focus();
|
||||
if (eventName === "focusAndSelectTitle") {
|
||||
textBoxRef.current.select();
|
||||
}
|
||||
isNewNote.current = ("isNewNote" in e ? e.isNewNote : false);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="note-title-widget">
|
||||
{note && <FormTextBox
|
||||
inputRef={textBoxRef}
|
||||
autocomplete="off"
|
||||
currentValue={(!isReadOnly ? title : navigationTitle) ?? ""}
|
||||
placeholder={t("note_title.placeholder")}
|
||||
className={`note-title ${isProtected ? "protected" : ""}`}
|
||||
tabIndex={100}
|
||||
readOnly={isReadOnly}
|
||||
onChange={(newValue) => {
|
||||
newTitle.current = newValue;
|
||||
spacedUpdate.scheduleUpdate();
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
// Focus on the note content when pressing enter.
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
parentComponent.triggerCommand("focusOnDetail", { ntxId: noteContext?.ntxId });
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === "Escape" && isNewNote.current && noteContext?.isActive() && note) {
|
||||
branches.deleteNotes(Object.values(note.parentToBranch));
|
||||
}
|
||||
}}
|
||||
onBlur={() => {
|
||||
spacedUpdate.updateNowIfNecessary();
|
||||
isNewNote.current = false;
|
||||
}}
|
||||
/>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -152,7 +152,7 @@ const TPL = /*html*/`
|
||||
const MAX_SEARCH_RESULTS_IN_TREE = 100;
|
||||
|
||||
// this has to be hanged on the actual elements to effectively intercept and stop click event
|
||||
const cancelClickPropagation: JQuery.TypeEventHandler<unknown, unknown, unknown, unknown, any> = (e) => e.stopPropagation();
|
||||
const cancelClickPropagation: (e: JQuery.ClickEvent) => void = (e) => e.stopPropagation();
|
||||
|
||||
// TODO: Fix once we remove Node.js API from public
|
||||
type Timeout = NodeJS.Timeout | string | number | undefined;
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
import { Dropdown } from "bootstrap";
|
||||
import { NOTE_TYPES } from "../services/note_types.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import dialogService from "../services/dialog.js";
|
||||
import mimeTypesService from "../services/mime_types.js";
|
||||
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
||||
import server from "../services/server.js";
|
||||
import type { EventData } from "../components/app_context.js";
|
||||
import type { NoteType } from "../entities/fnote.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
|
||||
const NOT_SELECTABLE_NOTE_TYPES = NOTE_TYPES.filter((nt) => nt.reserved || nt.static).map((nt) => nt.type);
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="dropdown note-type-widget">
|
||||
<style>
|
||||
.note-type-dropdown {
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
</style>
|
||||
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle select-button note-type-button">
|
||||
<span class="note-type-desc"></span>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<div class="note-type-dropdown dropdown-menu dropdown-menu-left tn-dropdown-list"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
export default class NoteTypeWidget extends NoteContextAwareWidget {
|
||||
|
||||
private dropdown!: Dropdown;
|
||||
private $noteTypeDropdown!: JQuery<HTMLElement>;
|
||||
private $noteTypeButton!: JQuery<HTMLElement>;
|
||||
private $noteTypeDesc!: JQuery<HTMLElement>;
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
|
||||
this.dropdown = Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']")[0]);
|
||||
|
||||
this.$widget.on("show.bs.dropdown", () => this.renderDropdown());
|
||||
|
||||
this.$noteTypeDropdown = this.$widget.find(".note-type-dropdown");
|
||||
this.$noteTypeButton = this.$widget.find(".note-type-button");
|
||||
this.$noteTypeDesc = this.$widget.find(".note-type-desc");
|
||||
|
||||
this.$widget.on("click", ".dropdown-item", () => this.dropdown.toggle());
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote) {
|
||||
this.$noteTypeButton.prop("disabled", () => NOT_SELECTABLE_NOTE_TYPES.includes(note.type));
|
||||
|
||||
this.$noteTypeDesc.text(await this.findTypeTitle(note.type, note.mime));
|
||||
|
||||
this.dropdown.hide();
|
||||
}
|
||||
|
||||
/** the actual body is rendered lazily on note-type button click */
|
||||
async renderDropdown() {
|
||||
this.$noteTypeDropdown.empty();
|
||||
|
||||
if (!this.note) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const noteType of NOTE_TYPES.filter((nt) => !nt.reserved && !nt.static)) {
|
||||
let $typeLink: JQuery<HTMLElement>;
|
||||
|
||||
const $title = $("<span>").text(noteType.title);
|
||||
|
||||
if (noteType.isNew) {
|
||||
$title.append($(`<span class="badge new-note-type-badge">`).text(t("note_types.new-feature")));
|
||||
}
|
||||
|
||||
if (noteType.isBeta) {
|
||||
$title.append($(`<span class="badge">`).text(t("note_types.beta-feature")));
|
||||
}
|
||||
|
||||
if (noteType.type !== "code") {
|
||||
$typeLink = $('<a class="dropdown-item">')
|
||||
.attr("data-note-type", noteType.type)
|
||||
.append('<span class="check">✓</span> ')
|
||||
.append($title)
|
||||
.on("click", (e) => {
|
||||
const type = $typeLink.attr("data-note-type");
|
||||
const noteType = NOTE_TYPES.find((nt) => nt.type === type);
|
||||
|
||||
if (noteType) {
|
||||
this.save(noteType.type, noteType.mime);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.$noteTypeDropdown.append('<div class="dropdown-divider"></div>');
|
||||
$typeLink = $('<a class="dropdown-item disabled">').attr("data-note-type", noteType.type).append('<span class="check">✓</span> ').append($("<strong>").text(noteType.title));
|
||||
}
|
||||
|
||||
if (this.note.type === noteType.type) {
|
||||
$typeLink.addClass("selected");
|
||||
}
|
||||
|
||||
this.$noteTypeDropdown.append($typeLink);
|
||||
}
|
||||
|
||||
for (const mimeType of mimeTypesService.getMimeTypes()) {
|
||||
if (!mimeType.enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const $mimeLink = $('<a class="dropdown-item">')
|
||||
.attr("data-mime-type", mimeType.mime)
|
||||
.append('<span class="check">✓</span> ')
|
||||
.append($("<span>").text(mimeType.title))
|
||||
.on("click", (e) => {
|
||||
const $link = $(e.target).closest(".dropdown-item");
|
||||
|
||||
this.save("code", $link.attr("data-mime-type") ?? "");
|
||||
});
|
||||
|
||||
if (this.note.type === "code" && this.note.mime === mimeType.mime) {
|
||||
$mimeLink.addClass("selected");
|
||||
|
||||
this.$noteTypeDesc.text(mimeType.title);
|
||||
}
|
||||
|
||||
this.$noteTypeDropdown.append($mimeLink);
|
||||
}
|
||||
}
|
||||
|
||||
async findTypeTitle(type: NoteType, mime: string) {
|
||||
if (type === "code") {
|
||||
const mimeTypes = mimeTypesService.getMimeTypes();
|
||||
const found = mimeTypes.find((mt) => mt.mime === mime);
|
||||
|
||||
return found ? found.title : mime;
|
||||
} else {
|
||||
const noteType = NOTE_TYPES.find((nt) => nt.type === type);
|
||||
|
||||
return noteType ? noteType.title : type;
|
||||
}
|
||||
}
|
||||
|
||||
async save(type: NoteType, mime?: string) {
|
||||
if (type === this.note?.type && mime === this.note?.mime) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (type !== this.note?.type && !(await this.confirmChangeIfContent())) {
|
||||
return;
|
||||
}
|
||||
|
||||
await server.put(`notes/${this.noteId}/type`, { type, mime });
|
||||
}
|
||||
|
||||
async confirmChangeIfContent() {
|
||||
if (!this.note) {
|
||||
return;
|
||||
}
|
||||
|
||||
const blob = await this.note.getBlob();
|
||||
|
||||
if (!blob?.content || !blob.content.trim().length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return await dialogService.confirm(t("note_types.confirm-change"));
|
||||
}
|
||||
|
||||
async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
if (loadResults.isNoteReloaded(this.noteId)) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
import { t } from "../../services/i18n.js";
|
||||
import server from "../../services/server.js";
|
||||
import ws from "../../services/ws.js";
|
||||
import treeService from "../../services/tree.js";
|
||||
import noteAutocompleteService from "../../services/note_autocomplete.js";
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
import attributeService from "../../services/attributes.js";
|
||||
import options from "../../services/options.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import type { Attribute } from "../../services/attribute_parser.js";
|
||||
import type FAttribute from "../../entities/fattribute.js";
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import server from "../services/server.js";
|
||||
import ws from "../services/ws.js";
|
||||
import treeService from "../services/tree.js";
|
||||
import noteAutocompleteService from "../services/note_autocomplete.js";
|
||||
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
||||
import attributeService from "../services/attributes.js";
|
||||
import options from "../services/options.js";
|
||||
import utils from "../services/utils.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import type { Attribute } from "../services/attribute_parser.js";
|
||||
import type FAttribute from "../entities/fattribute.js";
|
||||
import type { EventData } from "../components/app_context.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="promoted-attributes-widget">
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user