mirror of
https://github.com/zadam/trilium.git
synced 2025-12-20 23:29:55 +01:00
Compare commits
744 Commits
feat/impro
...
feature/pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8bf301d12 | ||
|
|
2c25786fa2 | ||
|
|
1093acfe45 | ||
|
|
76f054bbd5 | ||
|
|
c558255450 | ||
|
|
1e94125133 | ||
|
|
64a770175f | ||
|
|
e0416097e1 | ||
|
|
6c1b327f5f | ||
|
|
284b66acd2 | ||
|
|
dcd73ff9f9 | ||
|
|
645557b505 | ||
|
|
22a83d9f82 | ||
|
|
f64de3acca | ||
|
|
34d5793888 | ||
|
|
44ca9f457c | ||
|
|
4d7e5bc8f6 | ||
|
|
644ff07a50 | ||
|
|
41220a9d1d | ||
|
|
88945788d6 | ||
|
|
fe8f033409 | ||
|
|
eee7c49f6e | ||
|
|
d036bf0870 | ||
|
|
fa8ff4bfbf | ||
|
|
3619c0c3e4 | ||
|
|
883e32f5c9 | ||
|
|
8722ed405e | ||
|
|
201c3a6eba | ||
|
|
5a46f6ad04 | ||
|
|
4f20ffe933 | ||
|
|
061b0966bb | ||
|
|
bd799823b8 | ||
|
|
c581ee7252 | ||
|
|
666c434c74 | ||
|
|
78ac59581e | ||
|
|
d7b370253d | ||
|
|
1e885625f6 | ||
|
|
8cf6a6b9ae | ||
|
|
f1ca8881a1 | ||
|
|
ea76fd797c | ||
|
|
b248805905 | ||
|
|
7af5c77bcb | ||
|
|
35afd60d00 | ||
|
|
2b827991ef | ||
|
|
bc8c852a4d | ||
|
|
cd49c36529 | ||
|
|
a0577dc202 | ||
|
|
dced799976 | ||
|
|
eeea96b98c | ||
|
|
e82e92c22c | ||
|
|
e1df65adce | ||
|
|
b7b7610f4d | ||
|
|
37ea1584c9 | ||
|
|
aac4316fb8 | ||
|
|
fb96b3f80a | ||
|
|
243d8158cf | ||
|
|
6861a61cac | ||
|
|
d3299d8aa4 | ||
|
|
0be5581fe5 | ||
|
|
60572a28ff | ||
|
|
422c391c82 | ||
|
|
457d30cd80 | ||
|
|
9caa058b18 | ||
|
|
33bde688c1 | ||
|
|
e79da6b0f3 | ||
|
|
3e527b9f5c | ||
|
|
ba242a6169 | ||
|
|
489113f582 | ||
|
|
0fa6335d0f | ||
|
|
8f1614f603 | ||
|
|
a5f322617d | ||
|
|
6da42fac20 | ||
|
|
fad6414e1d | ||
|
|
c0cd9e36d9 | ||
|
|
e94704ce64 | ||
|
|
7a5d24f968 | ||
|
|
9d351ae479 | ||
|
|
01d4fa8afd | ||
|
|
7d386c249a | ||
|
|
45dd47d039 | ||
|
|
06ad0bfa90 | ||
|
|
3d9efb23ec | ||
|
|
9acef4d502 | ||
|
|
d22583457f | ||
|
|
290469d1df | ||
|
|
de5b766d0c | ||
|
|
cbeb5dfb58 | ||
|
|
be5448eba2 | ||
|
|
200e5d04a4 | ||
|
|
c7bbf709a1 | ||
|
|
23c2a59eba | ||
|
|
bf74c40f73 | ||
|
|
2af1ceda0b | ||
|
|
44ae60005c | ||
|
|
3d591922bb | ||
|
|
31b2aba44c | ||
|
|
df1c6196bf | ||
|
|
96f4567ba1 | ||
|
|
3e783817b6 | ||
|
|
f7f3f707f1 | ||
|
|
5ce81f1a32 | ||
|
|
53df319aeb | ||
|
|
c56a253e49 | ||
|
|
551b2aa33a | ||
|
|
8e245ccad8 | ||
|
|
03cea8b702 | ||
|
|
c94b5bc6c9 | ||
|
|
69dc1ba68f | ||
|
|
32f7ae1edd | ||
|
|
0de05ed16e | ||
|
|
58e24c98ed | ||
|
|
46da118749 | ||
|
|
3f7514c9c7 | ||
|
|
5123f7b678 | ||
|
|
b8af961690 | ||
|
|
5bcec9fcfd | ||
|
|
0a2d4131d7 | ||
|
|
9ef4ab9983 | ||
|
|
2a237e9a49 | ||
|
|
29115f5e61 | ||
|
|
3411ed79d8 | ||
|
|
b9c6cae5b4 | ||
|
|
cc84d09230 | ||
|
|
a82b12a599 | ||
|
|
65ebbc71f5 | ||
|
|
77c1a00831 | ||
|
|
8eb6bf402d | ||
|
|
e45beb541e | ||
|
|
334c31e79d | ||
|
|
a986c84ce7 | ||
|
|
9b21e042ec | ||
|
|
c44bb6c203 | ||
|
|
ddb6b3ea8a | ||
|
|
a4024d17ba | ||
|
|
57081a1bfb | ||
|
|
7af063e7cd | ||
|
|
7b04ca8cc7 | ||
|
|
02294206ec | ||
|
|
7f7ec5d858 | ||
|
|
ea3222cf12 | ||
|
|
5dacfd3ac6 | ||
|
|
682c61305c | ||
|
|
b5bfb02d96 | ||
|
|
fc3692333a | ||
|
|
28d9d98964 | ||
|
|
751a874c51 | ||
|
|
d18ac0c613 | ||
|
|
cd9654cd5f | ||
|
|
925049357a | ||
|
|
d920da9e6f | ||
|
|
b42a4dcb36 | ||
|
|
7085e62cfc | ||
|
|
73f2f56932 | ||
|
|
dbf29ed23f | ||
|
|
b0e1751dc7 | ||
|
|
bf5c56a61a | ||
|
|
96ccb1e67e | ||
|
|
704dcd011e | ||
|
|
b93c80fe7b | ||
|
|
41751c205c | ||
|
|
852398426e | ||
|
|
73f1b91d34 | ||
|
|
28da93fc65 | ||
|
|
87a98201b4 | ||
|
|
60342c0f6f | ||
|
|
97a3e439d2 | ||
|
|
ee6f988c35 | ||
|
|
7cfc67cf9f | ||
|
|
ea2dd0293f | ||
|
|
76c16f3a62 | ||
|
|
094f77b1af | ||
|
|
b2bcbdde3f | ||
|
|
eceb7179b8 | ||
|
|
3a46a9fbc3 | ||
|
|
2e484a11e6 | ||
|
|
98ed442d27 | ||
|
|
dac923e45d | ||
|
|
f46de50f17 | ||
|
|
616af1502f | ||
|
|
c9fae88a86 | ||
|
|
9872a3d522 | ||
|
|
851169e061 | ||
|
|
f3b274650e | ||
|
|
3293ed2ce0 | ||
|
|
231ec39025 | ||
|
|
ecb972c71c | ||
|
|
c7f1e46b26 | ||
|
|
b9c39d757b | ||
|
|
ea4a3b7f07 | ||
|
|
cbecc24999 | ||
|
|
261c1f77cf | ||
|
|
87d99aaffa | ||
|
|
d0b0a13b6d | ||
|
|
1ed83b3598 | ||
|
|
2759beb5d0 | ||
|
|
d623b2ffa0 | ||
|
|
3ed613cf1d | ||
|
|
948a6f84d6 | ||
|
|
334024b2d1 | ||
|
|
6aa14d17d7 | ||
|
|
e1da74d4d1 | ||
|
|
a6012283da | ||
|
|
e450e0299f | ||
|
|
816f851709 | ||
|
|
514ded5b8d | ||
|
|
304fd37ce2 | ||
|
|
51ef473964 | ||
|
|
96f148a870 | ||
|
|
150bc07d28 | ||
|
|
dcec780846 | ||
|
|
03602addc5 | ||
|
|
fc9f47a801 | ||
|
|
3b6a823556 | ||
|
|
b8ceb10e68 | ||
|
|
ddca68eafa | ||
|
|
468d4a4369 | ||
|
|
c31f35dbd7 | ||
|
|
b5129402be | ||
|
|
f38dfb035a | ||
|
|
c2bee1a968 | ||
|
|
84f7ae9f05 | ||
|
|
c4df640ea4 | ||
|
|
fe5e1eb066 | ||
|
|
6abc5a777f | ||
|
|
08730dd821 | ||
|
|
b08ea33eb0 | ||
|
|
f389fc3414 | ||
|
|
cb024d0455 | ||
|
|
286d2e8e5b | ||
|
|
5446d8a932 | ||
|
|
07dcdf3078 | ||
|
|
143b1827e6 | ||
|
|
6f494e3e38 | ||
|
|
e88623e9fa | ||
|
|
cad86d4b21 | ||
|
|
5ffae303c4 | ||
|
|
35218aca71 | ||
|
|
9542c9776a | ||
|
|
af02685f2f | ||
|
|
737e5b85b4 | ||
|
|
90a7217b32 | ||
|
|
77b92385cb | ||
|
|
22e0776049 | ||
|
|
db51198449 | ||
|
|
998a16ab87 | ||
|
|
4f1c19f1e2 | ||
|
|
1835676d09 | ||
|
|
2eaa4ef206 | ||
|
|
a5fcee500e | ||
|
|
613764d423 | ||
|
|
0518e64576 | ||
|
|
713900b2b3 | ||
|
|
c0eb34927f | ||
|
|
ec3c9a9ae5 | ||
|
|
70374b622e | ||
|
|
7fcc0ae290 | ||
|
|
1de4db1a08 | ||
|
|
74ac58d3a6 | ||
|
|
993f56976e | ||
|
|
94859f2303 | ||
|
|
db116981b8 | ||
|
|
2e563b0a1f | ||
|
|
7222b233f0 | ||
|
|
bea15c46e5 | ||
|
|
d96157de47 | ||
|
|
120b5c678d | ||
|
|
8c008e2e3a | ||
|
|
7e07280eb3 | ||
|
|
d28c3f0851 | ||
|
|
0390fd3174 | ||
|
|
193c9d8fa6 | ||
|
|
3a4cff6529 | ||
|
|
5449d033bf | ||
|
|
66ed88c409 | ||
|
|
94df5c9126 | ||
|
|
181ea31c1c | ||
|
|
f235839d03 | ||
|
|
d97b68fcd7 | ||
|
|
79d1a509e5 | ||
|
|
0af5fa9f0c | ||
|
|
2693b18ee6 | ||
|
|
34343ce356 | ||
|
|
c9025f2304 | ||
|
|
ec22fd9e99 | ||
|
|
15f9b2cadf | ||
|
|
7c85fe1c37 | ||
|
|
d1820a6bc3 | ||
|
|
d1575a28ad | ||
|
|
d13e19cf59 | ||
|
|
0b7ffdf109 | ||
|
|
e91cb1a198 | ||
|
|
51fcda646d | ||
|
|
cb8e35c4dc | ||
|
|
9d581347f1 | ||
|
|
96a6ea4c7a | ||
|
|
587ea42700 | ||
|
|
8a5c7b3551 | ||
|
|
f1fa44feb6 | ||
|
|
569cb6bf53 | ||
|
|
3445b594e8 | ||
|
|
676595dd6b | ||
|
|
f8c84602f4 | ||
|
|
d41842bc2a | ||
|
|
ced47e64db | ||
|
|
16a6344687 | ||
|
|
7bac0b25ce | ||
|
|
6094f738f2 | ||
|
|
c3a6d1bba8 | ||
|
|
1feeb350ce | ||
|
|
f21ba207fe | ||
|
|
07c8ff4571 | ||
|
|
71d8588091 | ||
|
|
3c41b7e5a9 | ||
|
|
41b7a295b9 | ||
|
|
ca8e889e1e | ||
|
|
6d4e52c928 | ||
|
|
e2fac8ab05 | ||
|
|
af3883fdac | ||
|
|
83777d7ea0 | ||
|
|
c6854c84b9 | ||
|
|
eb99352fff | ||
|
|
7dac5d424b | ||
|
|
441958028d | ||
|
|
80b61a35a9 | ||
|
|
61df0f3d31 | ||
|
|
23ece9fc86 | ||
|
|
220e3d7195 | ||
|
|
392c0311e5 | ||
|
|
3abdcfa7a5 | ||
|
|
4896042fc4 | ||
|
|
7edfd5d7b4 | ||
|
|
ad8e52f744 | ||
|
|
455dc5dc11 | ||
|
|
158f5ac310 | ||
|
|
fb70029091 | ||
|
|
b370512893 | ||
|
|
764607314c | ||
|
|
dbcf9b01c5 | ||
|
|
93ce77438f | ||
|
|
14c30661e6 | ||
|
|
37efc44f43 | ||
|
|
6aaade846a | ||
|
|
bd2402396b | ||
|
|
a9b3479216 | ||
|
|
05e98877b0 | ||
|
|
862ddf3a71 | ||
|
|
c1df2c45de | ||
|
|
065e97c940 | ||
|
|
adae7fa03b | ||
|
|
b725dbea7e | ||
|
|
4b80eec000 | ||
|
|
d7722a1e05 | ||
|
|
35cfcc59f6 | ||
|
|
192190d685 | ||
|
|
d6cc4bfa9c | ||
|
|
ed284fbc5f | ||
|
|
cb0efe25f5 | ||
|
|
906fe4f8da | ||
|
|
04a641199b | ||
|
|
50cbad22d0 | ||
|
|
4cfe59271f | ||
|
|
ba7969dad4 | ||
|
|
43b6440bf9 | ||
|
|
3e19a163c2 | ||
|
|
ca39282269 | ||
|
|
016389df68 | ||
|
|
17db2a6b38 | ||
|
|
bc8f17ee5c | ||
|
|
0c8944ab8e | ||
|
|
0960b585bd | ||
|
|
55649c3750 | ||
|
|
51bbf71577 | ||
|
|
728fab1dda | ||
|
|
d5ec80d85d | ||
|
|
15ef93d7e6 | ||
|
|
9711b22ea9 | ||
|
|
b9c7d2b01d | ||
|
|
9834846a23 | ||
|
|
65f425df2c | ||
|
|
a551dfe4d6 | ||
|
|
e9bfacdb7c | ||
|
|
3ba7b7d439 | ||
|
|
381943818d | ||
|
|
d1ae2db587 | ||
|
|
8fa6e38382 | ||
|
|
749074ea94 | ||
|
|
f1bb786a49 | ||
|
|
42bde3873b | ||
|
|
4877238015 | ||
|
|
16374aaf1d | ||
|
|
19709f749a | ||
|
|
09c7affc16 | ||
|
|
01e197fd46 | ||
|
|
0fe129ac16 | ||
|
|
3c52ceb4e6 | ||
|
|
8ba2357d91 | ||
|
|
20f4990d48 | ||
|
|
8c793bf0fe | ||
|
|
12a0eebafe | ||
|
|
092c7dff6b | ||
|
|
7a1ff42d67 | ||
|
|
5a09a80902 | ||
|
|
a7ca839afb | ||
|
|
6b9b9a96c3 | ||
|
|
272888acab | ||
|
|
283e3c9de1 | ||
|
|
859087b850 | ||
|
|
f7b911dc0b | ||
|
|
9d7e2855d3 | ||
|
|
8e6ea87754 | ||
|
|
34bc444b18 | ||
|
|
860a903336 | ||
|
|
be923ad2b7 | ||
|
|
d2da1ed1e7 | ||
|
|
1c05f5e5c3 | ||
|
|
e043f30cc6 | ||
|
|
e1611d83a3 | ||
|
|
58e2111a8f | ||
|
|
c5e4c484dc | ||
|
|
75a6dece7a | ||
|
|
5c0e7736d6 | ||
|
|
2562ecd055 | ||
|
|
aaaa47b575 | ||
|
|
21d82ec1d7 | ||
|
|
5af8444cac | ||
|
|
cd82c34b93 | ||
|
|
d182659d62 | ||
|
|
171f428b9d | ||
|
|
da4ca9c804 | ||
|
|
c019341503 | ||
|
|
7234f04b56 | ||
|
|
1998cbc005 | ||
|
|
5914073c3f | ||
|
|
d5aadf2604 | ||
|
|
1fe22f940b | ||
|
|
0cdaf70efe | ||
|
|
8174c65243 | ||
|
|
2645801277 | ||
|
|
fb8c31cb9c | ||
|
|
7287dbd64f | ||
|
|
6569d64931 | ||
|
|
e9f3216926 | ||
|
|
ca0af9646d | ||
|
|
92dfafd1ff | ||
|
|
d04dde3b97 | ||
|
|
4c520c6df3 | ||
|
|
65d6ed1cdc | ||
|
|
3352a92445 | ||
|
|
bc8c55b8fb | ||
|
|
7660914eb8 | ||
|
|
869aec778c | ||
|
|
255726dcc4 | ||
|
|
9969000807 | ||
|
|
3b909fd739 | ||
|
|
ad08fb8132 | ||
|
|
8d536a6040 | ||
|
|
2b1bc8e2b9 | ||
|
|
563194ff6c | ||
|
|
0c9ff4dae4 | ||
|
|
b10e7f1811 | ||
|
|
f93ad499e2 | ||
|
|
87a51251ca | ||
|
|
b56e5b2483 | ||
|
|
476c162016 | ||
|
|
4182f6043a | ||
|
|
aa528c65b7 | ||
|
|
4998560e31 | ||
|
|
86f36922c4 | ||
|
|
4f617b86d3 | ||
|
|
b28527e10d | ||
|
|
fbb8924ebf | ||
|
|
f68c9b751f | ||
|
|
8091f02b16 | ||
|
|
f4c68d115b | ||
|
|
6c70d6b9ae | ||
|
|
1ea12567a3 | ||
|
|
2d16ab7a70 | ||
|
|
a228ba5273 | ||
|
|
d0477e9ebf | ||
|
|
c99907972d | ||
|
|
b9ebc7d7ea | ||
|
|
4f9e2c5eca | ||
|
|
ab1f8ee5ae | ||
|
|
89276ad51a | ||
|
|
eca533a517 | ||
|
|
0be578c517 | ||
|
|
198b315602 | ||
|
|
6474abc983 | ||
|
|
2137dbe849 | ||
|
|
b7b46703d9 | ||
|
|
d2d96a1421 | ||
|
|
cfcc309e5a | ||
|
|
7d87ec942e | ||
|
|
4def13272f | ||
|
|
c4f914bb7b | ||
|
|
6bf213a0b0 | ||
|
|
694cd2bc7c | ||
|
|
3851a94400 | ||
|
|
e296416a54 | ||
|
|
0bd89a659c | ||
|
|
0ada6523a8 | ||
|
|
56570d7ba1 | ||
|
|
0ffdedcfa6 | ||
|
|
f391bb8eec | ||
|
|
7000076961 | ||
|
|
e0f6ba808c | ||
|
|
4c2fe8a846 | ||
|
|
2ea23368bc | ||
|
|
87666005a6 | ||
|
|
7666f44b7a | ||
|
|
470f6e5334 | ||
|
|
a2b007874b | ||
|
|
9946d8c6b9 | ||
|
|
02fab16475 | ||
|
|
5145ce2d23 | ||
|
|
e06abe6e5b | ||
|
|
50a847777e | ||
|
|
4473f80d73 | ||
|
|
70c918c9c6 | ||
|
|
0939975631 | ||
|
|
0ef90c6165 | ||
|
|
cef14a3b19 | ||
|
|
61d3141bce | ||
|
|
f040a0b6d1 | ||
|
|
e9dfec88c9 | ||
|
|
6fa97c845a | ||
|
|
f686d9ecd0 | ||
|
|
621ebe4396 | ||
|
|
ac2a566685 | ||
|
|
ac3d57d5da | ||
|
|
9ab5eef984 | ||
|
|
912f90accf | ||
|
|
6463b0dcaa | ||
|
|
0b45fb6764 | ||
|
|
330d71847b | ||
|
|
60c8f0c78b | ||
|
|
fcbd1ab0b1 | ||
|
|
3549bfb328 | ||
|
|
c97038fffd | ||
|
|
15b5885982 | ||
|
|
6aa8d9fbf9 | ||
|
|
eccf4620ac | ||
|
|
f08fbe9bb2 | ||
|
|
bfa87af489 | ||
|
|
a7899b7505 | ||
|
|
e80b5cddcd | ||
|
|
db12f9b8dc | ||
|
|
f4c95195c9 | ||
|
|
e2cbff7b3a | ||
|
|
98a3c8150c | ||
|
|
447e09fec1 | ||
|
|
7d2a1bb2e5 | ||
|
|
40fcf79778 | ||
|
|
88a779bbdb | ||
|
|
db04514769 | ||
|
|
23062470f5 | ||
|
|
5bad043ed5 | ||
|
|
4ab8af0995 | ||
|
|
1a65c5e13e | ||
|
|
fc08946038 | ||
|
|
4d6dba06ad | ||
|
|
d7887fe25f | ||
|
|
81dd50e752 | ||
|
|
fe13065ef8 | ||
|
|
eb02330fdf | ||
|
|
738fa6fd0e | ||
|
|
0c1c7e4f8e | ||
|
|
9eb9b66398 | ||
|
|
9db046b401 | ||
|
|
914272eee0 | ||
|
|
2b7e203bcc | ||
|
|
a61ddedc0b | ||
|
|
60fc34ffac | ||
|
|
685109556c | ||
|
|
45927053f3 | ||
|
|
5d438a877b | ||
|
|
870499bc3a | ||
|
|
c6d97e3d4b | ||
|
|
efff38b116 | ||
|
|
1b725175c6 | ||
|
|
6eff62f73f | ||
|
|
95d2160c76 | ||
|
|
2b195155ed | ||
|
|
28e9abc8bb | ||
|
|
0162b9d441 | ||
|
|
0545b929e1 | ||
|
|
d2b32ff5af | ||
|
|
2d3776cd5f | ||
|
|
2638963171 | ||
|
|
24ed97f65d | ||
|
|
c099634e39 | ||
|
|
12be14e6cf | ||
|
|
4dc773c1a3 | ||
|
|
31c5323fd9 | ||
|
|
74b6e7bf63 | ||
|
|
34025fa646 | ||
|
|
df9554194a | ||
|
|
4e1188484d | ||
|
|
2f44b9dc59 | ||
|
|
9ee3c48485 | ||
|
|
78b9c94829 | ||
|
|
4c8225ed73 | ||
|
|
88aad6d351 | ||
|
|
d99d701095 | ||
|
|
61fe27abbe | ||
|
|
24cd5006d5 | ||
|
|
726d6aad65 | ||
|
|
bd9fe14a6c | ||
|
|
792a10ace5 | ||
|
|
e9ac69b8e5 | ||
|
|
c76ff2d371 | ||
|
|
8ab9e30404 | ||
|
|
53b7d93efb | ||
|
|
00df3c3d1f | ||
|
|
e766b82418 | ||
|
|
9f4757af5b | ||
|
|
1a9fb34a6e | ||
|
|
a1513a3567 | ||
|
|
0de67b6a69 | ||
|
|
fec5ee9335 | ||
|
|
b540111fa4 | ||
|
|
0eed72b888 | ||
|
|
0856d3dbdf | ||
|
|
a9b453c27a | ||
|
|
fa8287269f | ||
|
|
1eee471018 | ||
|
|
c3829f82ab | ||
|
|
a51820f5df | ||
|
|
68591fb511 | ||
|
|
3795ce2143 | ||
|
|
3561a4f14d | ||
|
|
84cda001aa | ||
|
|
481127a560 | ||
|
|
c708e7cd61 | ||
|
|
fee0268792 | ||
|
|
953593c9d4 | ||
|
|
5ff60e53cb | ||
|
|
b38ee36fae | ||
|
|
38a415faf0 | ||
|
|
1e26864842 | ||
|
|
4b74ad5577 | ||
|
|
e5696713de | ||
|
|
2e44397c88 | ||
|
|
5d19881981 | ||
|
|
1711384eaa | ||
|
|
9897efe4af | ||
|
|
884578ea95 | ||
|
|
e404e76299 | ||
|
|
1db54cba3e | ||
|
|
77e3cc4021 | ||
|
|
242c63dfb4 | ||
|
|
f5440576b5 | ||
|
|
b020365af4 | ||
|
|
25e5bf0b86 | ||
|
|
19b32dd3a6 | ||
|
|
1ab89d0db0 | ||
|
|
6e8e10323f | ||
|
|
58bc5dc66a | ||
|
|
db42bb603b | ||
|
|
cb382c9537 | ||
|
|
a4b79a2dc9 | ||
|
|
0f867e02c4 | ||
|
|
ab1b4b37f4 | ||
|
|
5a1d138f29 | ||
|
|
06a5298efa | ||
|
|
db720acc18 | ||
|
|
8d8ff25bae | ||
|
|
6f85b7cc09 | ||
|
|
77f5770bff | ||
|
|
14cda5b921 | ||
|
|
36b1182565 | ||
|
|
483327c808 | ||
|
|
efb2f9a048 | ||
|
|
01978dabf0 | ||
|
|
cfbd2bf53a | ||
|
|
9262f94190 | ||
|
|
b36a0bd10b | ||
|
|
2dc8948f33 | ||
|
|
9f2ed2f9d4 | ||
|
|
e0f7d65f77 | ||
|
|
f18ac3a923 | ||
|
|
b39a6bcc97 | ||
|
|
8fa9c25f2a | ||
|
|
84bde62e05 | ||
|
|
5bb4621097 | ||
|
|
f1edf84f4d | ||
|
|
f7955a9040 | ||
|
|
7c5df21685 | ||
|
|
2060bb8cdd | ||
|
|
a9b4e7b1e2 | ||
|
|
82528c4478 | ||
|
|
4dcfc3e0bc | ||
|
|
999315d3c6 | ||
|
|
aef0b03c34 | ||
|
|
49f008c46f | ||
|
|
bd81db4117 | ||
|
|
9f274883e3 | ||
|
|
07b76b80f4 | ||
|
|
0014f0a88d | ||
|
|
63f7a78d31 | ||
|
|
e556c090ff | ||
|
|
c4f483c250 | ||
|
|
4031332b98 | ||
|
|
10cb7c8d6a | ||
|
|
be190bfe33 | ||
|
|
4d7d642952 | ||
|
|
737711e5eb | ||
|
|
42fc128f97 | ||
|
|
b03e6c3b19 | ||
|
|
66008489c4 | ||
|
|
3262e3490a | ||
|
|
16a73b0848 | ||
|
|
52bb4d7a0e | ||
|
|
40b5e4d549 | ||
|
|
b014ea8950 | ||
|
|
61592716f9 | ||
|
|
efe7fc0ee7 | ||
|
|
a810db3641 | ||
|
|
f8b292dfa3 | ||
|
|
fc2ab91280 | ||
|
|
668ee219c6 | ||
|
|
ee6512a1a6 | ||
|
|
fe1f590286 | ||
|
|
876e8f843a | ||
|
|
a45c1a1dc8 | ||
|
|
f8377169e6 | ||
|
|
a197a33d35 | ||
|
|
3060207d04 | ||
|
|
28c1d0b3f5 | ||
|
|
644d051477 | ||
|
|
f42031c8de | ||
|
|
6b50d9b087 | ||
|
|
a0f0da64b4 | ||
|
|
1e72ebd104 | ||
|
|
1184a95697 | ||
|
|
cd0e4a5678 | ||
|
|
394f6c3110 | ||
|
|
e2b6d0c256 | ||
|
|
fe7ca210dd | ||
|
|
e58d6bf2a3 | ||
|
|
460d20d6b2 | ||
|
|
ae154212fe | ||
|
|
28bb4edbac | ||
|
|
1ceed1b47b |
8
.github/workflows/main-docker.yml
vendored
8
.github/workflows/main-docker.yml
vendored
@@ -86,12 +86,12 @@ jobs:
|
||||
|
||||
- name: Upload Playwright trace
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: Playwright trace (${{ matrix.dockerfile }})
|
||||
path: test-output/playwright/output
|
||||
|
||||
- uses: actions/upload-artifact@v5
|
||||
- uses: actions/upload-artifact@v6
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
name: Playwright report (${{ matrix.dockerfile }})
|
||||
@@ -213,7 +213,7 @@ jobs:
|
||||
touch "/tmp/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_PAIR }}-${{ matrix.dockerfile }}
|
||||
path: /tmp/digests/*
|
||||
@@ -227,7 +227,7 @@ jobs:
|
||||
- build
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v6
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
|
||||
2
.github/workflows/nightly.yml
vendored
2
.github/workflows/nightly.yml
vendored
@@ -102,7 +102,7 @@ jobs:
|
||||
name: Nightly Build
|
||||
|
||||
- name: Publish artifacts
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
with:
|
||||
name: TriliumNotes ${{ matrix.os.name }} ${{ matrix.arch }}
|
||||
|
||||
2
.github/workflows/playwright.yml
vendored
2
.github/workflows/playwright.yml
vendored
@@ -77,7 +77,7 @@ jobs:
|
||||
|
||||
- name: Upload test report
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: e2e report ${{ matrix.arch }}
|
||||
path: apps/server-e2e/test-output
|
||||
|
||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@@ -73,7 +73,7 @@ jobs:
|
||||
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGN_KEY }}
|
||||
|
||||
- name: Upload the artifact
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: release-desktop-${{ matrix.os.name }}-${{ matrix.arch }}
|
||||
path: apps/desktop/upload/*.*
|
||||
@@ -100,7 +100,7 @@ jobs:
|
||||
arch: ${{ matrix.arch }}
|
||||
|
||||
- name: Upload the artifact
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: release-server-linux-${{ matrix.arch }}
|
||||
path: upload/*.*
|
||||
@@ -120,7 +120,7 @@ jobs:
|
||||
docs/Release Notes
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v6
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
merge-multiple: true
|
||||
pattern: release-*
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -44,9 +44,10 @@ upload
|
||||
.rollup.cache
|
||||
*.tsbuildinfo
|
||||
|
||||
/.direnv
|
||||
/result
|
||||
.svelte-kit
|
||||
|
||||
# docs
|
||||
site/
|
||||
apps/*/coverage
|
||||
apps/*/coverage
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -37,6 +37,9 @@
|
||||
"apps/server/src/assets/doc_notes/**": true,
|
||||
"apps/edit-docs/demo/**": true
|
||||
},
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
"eslint.rules.customizations": [
|
||||
{ "rule": "*", "severity": "warn" }
|
||||
]
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
"keywords": [],
|
||||
"author": "Elian Doran <contact@eliandoran.me>",
|
||||
"license": "AGPL-3.0-only",
|
||||
"packageManager": "pnpm@10.24.0",
|
||||
"packageManager": "pnpm@10.26.1",
|
||||
"devDependencies": {
|
||||
"@redocly/cli": "2.12.3",
|
||||
"@redocly/cli": "2.14.0",
|
||||
"archiver": "7.0.1",
|
||||
"fs-extra": "11.3.2",
|
||||
"react": "19.2.1",
|
||||
"react-dom": "19.2.1",
|
||||
"fs-extra": "11.3.3",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"typedoc": "0.28.15",
|
||||
"typedoc-plugin-missing-exports": "4.1.2"
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
"draggabilly": "3.0.0",
|
||||
"force-graph": "1.51.0",
|
||||
"globals": "16.5.0",
|
||||
"i18next": "25.7.1",
|
||||
"i18next": "25.7.3",
|
||||
"i18next-http-backend": "3.0.2",
|
||||
"jquery": "3.7.1",
|
||||
"jquery.fancytree": "2.38.5",
|
||||
@@ -56,12 +56,13 @@
|
||||
"mark.js": "8.11.1",
|
||||
"marked": "17.0.1",
|
||||
"mermaid": "11.12.2",
|
||||
"mind-elixir": "5.3.7",
|
||||
"mind-elixir": "5.3.8",
|
||||
"normalize.css": "8.0.1",
|
||||
"panzoom": "9.4.3",
|
||||
"preact": "10.28.0",
|
||||
"react-i18next": "16.4.0",
|
||||
"react-i18next": "16.5.0",
|
||||
"reveal.js": "5.2.1",
|
||||
"sucrase": "3.35.1",
|
||||
"svg-pan-zoom": "3.6.2",
|
||||
"tabulator-tables": "6.3.1",
|
||||
"vanilla-js-wheel-zoom": "9.0.4"
|
||||
|
||||
@@ -1,40 +1,41 @@
|
||||
import froca from "../services/froca.js";
|
||||
import RootCommandExecutor from "./root_command_executor.js";
|
||||
import Entrypoints from "./entrypoints.js";
|
||||
import options from "../services/options.js";
|
||||
import utils, { hasTouchBar } from "../services/utils.js";
|
||||
import zoomComponent from "./zoom.js";
|
||||
import TabManager from "./tab_manager.js";
|
||||
import Component from "./component.js";
|
||||
import keyboardActionsService from "../services/keyboard_actions.js";
|
||||
import linkService, { type ViewScope } from "../services/link.js";
|
||||
import MobileScreenSwitcherExecutor, { type Screen } from "./mobile_screen_switcher.js";
|
||||
import MainTreeExecutors from "./main_tree_executors.js";
|
||||
import toast from "../services/toast.js";
|
||||
import ShortcutComponent from "./shortcut_component.js";
|
||||
import { t, initLocale } from "../services/i18n.js";
|
||||
import type { ResolveOptions } from "../widgets/dialogs/delete_notes.js";
|
||||
import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js";
|
||||
import type { ConfirmWithMessageOptions, ConfirmWithTitleOptions } from "../widgets/dialogs/confirm.js";
|
||||
import type LoadResults from "../services/load_results.js";
|
||||
import type { Attribute } from "../services/attribute_parser.js";
|
||||
import type NoteTreeWidget from "../widgets/note_tree.js";
|
||||
import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js";
|
||||
import type { NativeImage, TouchBar } from "electron";
|
||||
import TouchBarComponent from "./touch_bar.js";
|
||||
import type { CKTextEditor } from "@triliumnext/ckeditor5";
|
||||
import type CodeMirror from "@triliumnext/codemirror";
|
||||
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";
|
||||
import { SqlExecuteResults } from "@triliumnext/commons";
|
||||
import { AddLinkOpts } from "../widgets/dialogs/add_link.jsx";
|
||||
import { IncludeNoteOpts } from "../widgets/dialogs/include_note.jsx";
|
||||
import type { NativeImage, TouchBar } from "electron";
|
||||
import { ColumnComponent } from "tabulator-tables";
|
||||
|
||||
import type { Attribute } from "../services/attribute_parser.js";
|
||||
import froca from "../services/froca.js";
|
||||
import { initLocale,t } from "../services/i18n.js";
|
||||
import keyboardActionsService from "../services/keyboard_actions.js";
|
||||
import linkService, { type ViewScope } from "../services/link.js";
|
||||
import type LoadResults from "../services/load_results.js";
|
||||
import type { CreateNoteOpts } from "../services/note_create.js";
|
||||
import options from "../services/options.js";
|
||||
import toast from "../services/toast.js";
|
||||
import utils, { hasTouchBar } from "../services/utils.js";
|
||||
import { ReactWrappedWidget } from "../widgets/basic_widget.js";
|
||||
import type { MarkdownImportOpts } from "../widgets/dialogs/markdown_import.jsx";
|
||||
import type RootContainer from "../widgets/containers/root_container.js";
|
||||
import { AddLinkOpts } from "../widgets/dialogs/add_link.jsx";
|
||||
import type { ConfirmWithMessageOptions, ConfirmWithTitleOptions } from "../widgets/dialogs/confirm.js";
|
||||
import type { ResolveOptions } from "../widgets/dialogs/delete_notes.js";
|
||||
import { IncludeNoteOpts } from "../widgets/dialogs/include_note.jsx";
|
||||
import type { InfoProps } from "../widgets/dialogs/info.jsx";
|
||||
import type { MarkdownImportOpts } from "../widgets/dialogs/markdown_import.jsx";
|
||||
import { ChooseNoteTypeCallback } from "../widgets/dialogs/note_type_chooser.jsx";
|
||||
import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js";
|
||||
import type NoteTreeWidget from "../widgets/note_tree.js";
|
||||
import Component from "./component.js";
|
||||
import Entrypoints from "./entrypoints.js";
|
||||
import MainTreeExecutors from "./main_tree_executors.js";
|
||||
import MobileScreenSwitcherExecutor, { type Screen } from "./mobile_screen_switcher.js";
|
||||
import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js";
|
||||
import RootCommandExecutor from "./root_command_executor.js";
|
||||
import ShortcutComponent from "./shortcut_component.js";
|
||||
import { StartupChecks } from "./startup_checks.js";
|
||||
import TabManager from "./tab_manager.js";
|
||||
import TouchBarComponent from "./touch_bar.js";
|
||||
import zoomComponent from "./zoom.js";
|
||||
|
||||
interface Layout {
|
||||
getRootWidget: (appContext: AppContext) => RootContainer;
|
||||
@@ -265,7 +266,7 @@ export type CommandMappings = {
|
||||
|
||||
reEvaluateRightPaneVisibility: CommandData;
|
||||
runActiveNote: CommandData;
|
||||
scrollContainerToCommand: CommandData & {
|
||||
scrollContainerTo: CommandData & {
|
||||
position: number;
|
||||
};
|
||||
scrollToEnd: CommandData;
|
||||
@@ -447,6 +448,7 @@ type EventMappings = {
|
||||
};
|
||||
searchRefreshed: { ntxId?: string | null };
|
||||
textEditorRefreshed: { ntxId?: string | null, editor: CKTextEditor };
|
||||
contentElRefreshed: { ntxId?: string | null, contentEl: HTMLElement };
|
||||
hoistedNoteChanged: {
|
||||
noteId: string;
|
||||
ntxId: string | null;
|
||||
@@ -498,6 +500,10 @@ type EventMappings = {
|
||||
noteIds: string[];
|
||||
};
|
||||
refreshData: { ntxId: string | null | undefined };
|
||||
contentSafeMarginChanged: {
|
||||
top: number;
|
||||
noteContext: NoteContext;
|
||||
}
|
||||
};
|
||||
|
||||
export type EventListener<T extends EventNames> = {
|
||||
@@ -691,10 +697,8 @@ $(window).on("beforeunload", () => {
|
||||
console.log(`Component ${component.componentId} is not finished saving its state.`);
|
||||
allSaved = false;
|
||||
}
|
||||
} else {
|
||||
if (!listener()) {
|
||||
allSaved = false;
|
||||
}
|
||||
} else if (!listener()) {
|
||||
allSaved = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -704,7 +708,7 @@ $(window).on("beforeunload", () => {
|
||||
}
|
||||
});
|
||||
|
||||
$(window).on("hashchange", function () {
|
||||
$(window).on("hashchange", () => {
|
||||
const { notePath, ntxId, viewScope, searchString } = linkService.parseNavigationStateFromUrl(window.location.href);
|
||||
|
||||
if (notePath || ntxId) {
|
||||
|
||||
@@ -65,8 +65,8 @@ export class TypedComponent<ChildT extends TypedComponent<ChildT>> {
|
||||
|
||||
// don't create promises if not needed (optimization)
|
||||
return callMethodPromise && childrenPromise ? Promise.all([callMethodPromise, childrenPromise]) : callMethodPromise || childrenPromise;
|
||||
} catch (e: any) {
|
||||
console.error(`Handling of event '${name}' failed in ${this.constructor.name} with error ${e.message} ${e.stack}`);
|
||||
} catch (e: unknown) {
|
||||
console.error(`Handling of event '${name}' failed in ${this.constructor.name} with error`, e);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import protectedSessionHolder from "../services/protected_session_holder.js";
|
||||
import server from "../services/server.js";
|
||||
import utils from "../services/utils.js";
|
||||
import appContext, { type EventData, type EventListener } from "./app_context.js";
|
||||
import treeService from "../services/tree.js";
|
||||
import Component from "./component.js";
|
||||
import froca from "../services/froca.js";
|
||||
import hoistedNoteService from "../services/hoisted_note.js";
|
||||
import options from "../services/options.js";
|
||||
import type { ViewScope } from "../services/link.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import type { CKTextEditor } from "@triliumnext/ckeditor5";
|
||||
import type CodeMirror from "@triliumnext/codemirror";
|
||||
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import { closeActiveDialog } from "../services/dialog.js";
|
||||
import froca from "../services/froca.js";
|
||||
import hoistedNoteService from "../services/hoisted_note.js";
|
||||
import type { ViewScope } from "../services/link.js";
|
||||
import options from "../services/options.js";
|
||||
import protectedSessionHolder from "../services/protected_session_holder.js";
|
||||
import server from "../services/server.js";
|
||||
import treeService from "../services/tree.js";
|
||||
import utils from "../services/utils.js";
|
||||
import { ReactWrappedWidget } from "../widgets/basic_widget.js";
|
||||
import appContext, { type EventData, type EventListener } from "./app_context.js";
|
||||
import Component from "./component.js";
|
||||
|
||||
export interface SetNoteOpts {
|
||||
triggerSwitchEvent?: unknown;
|
||||
@@ -389,7 +390,7 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
|
||||
* If no content could be determined `null` is returned instead.
|
||||
*/
|
||||
async getContentElement() {
|
||||
return this.timeout<JQuery<HTMLElement>>(
|
||||
return this.timeout<JQuery<HTMLElement> | null>(
|
||||
new Promise((resolve) =>
|
||||
appContext.triggerCommand("executeWithContentElement", {
|
||||
resolve,
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import Component from "./component.js";
|
||||
import appContext, { type CommandData, type CommandListenerData } from "./app_context.js";
|
||||
import dateNoteService from "../services/date_notes.js";
|
||||
import treeService from "../services/tree.js";
|
||||
import openService from "../services/open.js";
|
||||
import protectedSessionService from "../services/protected_session.js";
|
||||
import options from "../services/options.js";
|
||||
import froca from "../services/froca.js";
|
||||
import utils from "../services/utils.js";
|
||||
import toastService from "../services/toast.js";
|
||||
import noteCreateService from "../services/note_create.js";
|
||||
import openService from "../services/open.js";
|
||||
import options from "../services/options.js";
|
||||
import protectedSessionService from "../services/protected_session.js";
|
||||
import toastService from "../services/toast.js";
|
||||
import treeService from "../services/tree.js";
|
||||
import utils, { openInReusableSplit } from "../services/utils.js";
|
||||
import appContext, { type CommandListenerData } from "./app_context.js";
|
||||
import Component from "./component.js";
|
||||
|
||||
export default class RootCommandExecutor extends Component {
|
||||
editReadOnlyNoteCommand() {
|
||||
@@ -193,6 +193,19 @@ export default class RootCommandExecutor extends Component {
|
||||
appContext.triggerEvent("zenModeChanged", { isEnabled });
|
||||
}
|
||||
|
||||
async toggleRibbonTabNoteMapCommand(data: CommandListenerData<"toggleRibbonTabNoteMap">) {
|
||||
const { isExperimentalFeatureEnabled } = await import("../services/experimental_features.js");
|
||||
const isNewLayout = isExperimentalFeatureEnabled("new-layout");
|
||||
if (!isNewLayout) {
|
||||
this.triggerEvent("toggleRibbonTabNoteMap", data);
|
||||
return;
|
||||
}
|
||||
|
||||
const activeContext = appContext.tabManager.getActiveContext();
|
||||
if (!activeContext?.notePath) return;
|
||||
openInReusableSplit(activeContext.notePath, "note-map");
|
||||
}
|
||||
|
||||
firstTabCommand() {
|
||||
this.#goToTab(1);
|
||||
}
|
||||
@@ -262,7 +275,7 @@ export default class RootCommandExecutor extends Component {
|
||||
}
|
||||
catch (e) {
|
||||
console.error("Error creating AI Chat note:", e);
|
||||
toastService.showError("Failed to create AI Chat note: " + (e as Error).message);
|
||||
toastService.showError(`Failed to create AI Chat note: ${(e as Error).message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import server from "../services/server.js";
|
||||
import noteAttributeCache from "../services/note_attribute_cache.js";
|
||||
import protectedSessionHolder from "../services/protected_session_holder.js";
|
||||
import cssClassManager from "../services/css_class_manager.js";
|
||||
import type { Froca } from "../services/froca-interface.js";
|
||||
import type FAttachment from "./fattachment.js";
|
||||
import type { default as FAttribute, AttributeType } from "./fattribute.js";
|
||||
import utils from "../services/utils.js";
|
||||
import noteAttributeCache from "../services/note_attribute_cache.js";
|
||||
import protectedSessionHolder from "../services/protected_session_holder.js";
|
||||
import search from "../services/search.js";
|
||||
import server from "../services/server.js";
|
||||
import utils from "../services/utils.js";
|
||||
import type FAttachment from "./fattachment.js";
|
||||
import type { AttributeType,default as FAttribute } from "./fattribute.js";
|
||||
|
||||
const LABEL = "label";
|
||||
const RELATION = "relation";
|
||||
|
||||
const NOTE_TYPE_ICONS = {
|
||||
export const NOTE_TYPE_ICONS = {
|
||||
file: "bx bx-file",
|
||||
image: "bx bx-image",
|
||||
code: "bx bx-code",
|
||||
@@ -268,13 +268,12 @@ export default class FNote {
|
||||
}
|
||||
}
|
||||
return results;
|
||||
} else {
|
||||
return this.children;
|
||||
}
|
||||
return this.children;
|
||||
}
|
||||
|
||||
async getSubtreeNoteIds(includeArchived = false) {
|
||||
let noteIds: (string | string[])[] = [];
|
||||
const noteIds: (string | string[])[] = [];
|
||||
for (const child of await this.getChildNotes()) {
|
||||
if (child.isArchived && !includeArchived) continue;
|
||||
|
||||
@@ -471,9 +470,8 @@ export default class FNote {
|
||||
return a.isHidden ? 1 : -1;
|
||||
} else if (a.isSearch !== b.isSearch) {
|
||||
return a.isSearch ? 1 : -1;
|
||||
} else {
|
||||
return a.notePath.length - b.notePath.length;
|
||||
}
|
||||
return a.notePath.length - b.notePath.length;
|
||||
});
|
||||
|
||||
return notePaths;
|
||||
@@ -597,14 +595,12 @@ export default class FNote {
|
||||
} else if (this.type === "text") {
|
||||
if (this.isFolder()) {
|
||||
return "bx bx-folder";
|
||||
} else {
|
||||
return "bx bx-note";
|
||||
}
|
||||
return "bx bx-note";
|
||||
} else if (this.type === "code" && this.mime.startsWith("text/x-sql")) {
|
||||
return "bx bx-data";
|
||||
} else {
|
||||
return NOTE_TYPE_ICONS[this.type];
|
||||
}
|
||||
return NOTE_TYPE_ICONS[this.type];
|
||||
}
|
||||
|
||||
getColorClass() {
|
||||
@@ -617,7 +613,7 @@ export default class FNote {
|
||||
}
|
||||
|
||||
getFilteredChildBranches() {
|
||||
let childBranches = this.getChildBranches();
|
||||
const childBranches = this.getChildBranches();
|
||||
|
||||
if (!childBranches) {
|
||||
console.error(`No children for '${this.noteId}'. This shouldn't happen.`);
|
||||
@@ -811,9 +807,9 @@ export default class FNote {
|
||||
return this.getLabelValue(nameWithPrefix.substring(1));
|
||||
} else if (nameWithPrefix.startsWith("~")) {
|
||||
return this.getRelationValue(nameWithPrefix.substring(1));
|
||||
} else {
|
||||
return this.getLabelValue(nameWithPrefix);
|
||||
}
|
||||
return this.getLabelValue(nameWithPrefix);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -878,10 +874,10 @@ export default class FNote {
|
||||
promotedAttrs.sort((a, b) => {
|
||||
if (a.noteId === b.noteId) {
|
||||
return a.position < b.position ? -1 : 1;
|
||||
} else {
|
||||
// inherited promoted attributes should stay grouped: https://github.com/zadam/trilium/issues/3761
|
||||
return a.noteId < b.noteId ? -1 : 1;
|
||||
}
|
||||
// inherited promoted attributes should stay grouped: https://github.com/zadam/trilium/issues/3761
|
||||
return a.noteId < b.noteId ? -1 : 1;
|
||||
|
||||
});
|
||||
|
||||
return promotedAttrs;
|
||||
|
||||
@@ -1,51 +1,59 @@
|
||||
import { applyModals } from "./layout_commons.js";
|
||||
import { DESKTOP_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx";
|
||||
import type { AppContext } from "../components/app_context.js";
|
||||
import type { WidgetsByParent } from "../services/bundle.js";
|
||||
import { isExperimentalFeatureEnabled } from "../services/experimental_features.js";
|
||||
import options from "../services/options.js";
|
||||
import utils from "../services/utils.js";
|
||||
import ApiLog from "../widgets/api_log.jsx";
|
||||
import ClosePaneButton from "../widgets/buttons/close_pane_button.js";
|
||||
import CloseZenModeButton from "../widgets/close_zen_button.jsx";
|
||||
import ContentHeader from "../widgets/containers/content_header.js";
|
||||
import CreatePaneButton from "../widgets/buttons/create_pane_button.js";
|
||||
import FindWidget from "../widgets/find.js";
|
||||
import FlexContainer from "../widgets/containers/flex_container.js";
|
||||
import FloatingButtons from "../widgets/FloatingButtons.jsx";
|
||||
import GlobalMenu from "../widgets/buttons/global_menu.jsx";
|
||||
import HighlightsListWidget from "../widgets/highlights_list.js";
|
||||
import LeftPaneContainer from "../widgets/containers/left_pane_container.js";
|
||||
import LeftPaneToggle from "../widgets/buttons/left_pane_toggle.js";
|
||||
import MovePaneButton from "../widgets/buttons/move_pane_button.js";
|
||||
import NoteIconWidget from "../widgets/note_icon.jsx";
|
||||
import RightPaneToggle from "../widgets/buttons/right_pane_toggle.jsx";
|
||||
import CloseZenModeButton from "../widgets/close_zen_button.jsx";
|
||||
import NoteList from "../widgets/collections/NoteList.jsx";
|
||||
import NoteTitleWidget from "../widgets/note_title.jsx";
|
||||
import NoteTreeWidget from "../widgets/note_tree.js";
|
||||
import NoteWrapperWidget from "../widgets/note_wrapper.js";
|
||||
import options from "../services/options.js";
|
||||
import PasswordNoteSetDialog from "../widgets/dialogs/password_not_set.js";
|
||||
import QuickSearchWidget from "../widgets/quick_search.js";
|
||||
import ReadOnlyNoteInfoBar from "../widgets/ReadOnlyNoteInfoBar.jsx";
|
||||
import Ribbon from "../widgets/ribbon/Ribbon.jsx";
|
||||
import ContentHeader from "../widgets/containers/content_header.js";
|
||||
import FlexContainer from "../widgets/containers/flex_container.js";
|
||||
import LeftPaneContainer from "../widgets/containers/left_pane_container.js";
|
||||
import RightPaneContainer from "../widgets/containers/right_pane_container.js";
|
||||
import RootContainer from "../widgets/containers/root_container.js";
|
||||
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
|
||||
import SplitNoteContainer from "../widgets/containers/split_note_container.js";
|
||||
import PasswordNoteSetDialog from "../widgets/dialogs/password_not_set.js";
|
||||
import UploadAttachmentsDialog from "../widgets/dialogs/upload_attachments.js";
|
||||
import FindWidget from "../widgets/find.js";
|
||||
import FloatingButtons from "../widgets/FloatingButtons.jsx";
|
||||
import { DESKTOP_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx";
|
||||
import HighlightsListWidget from "../widgets/highlights_list.js";
|
||||
import LauncherContainer from "../widgets/launch_bar/LauncherContainer.jsx";
|
||||
import SpacerWidget from "../widgets/launch_bar/SpacerWidget.jsx";
|
||||
import InlineTitle from "../widgets/layout/InlineTitle.jsx";
|
||||
import NoteBadges from "../widgets/layout/NoteBadges.jsx";
|
||||
import NoteTitleActions from "../widgets/layout/NoteTitleActions.jsx";
|
||||
import StatusBar from "../widgets/layout/StatusBar.jsx";
|
||||
import NoteIconWidget from "../widgets/note_icon.jsx";
|
||||
import NoteTitleWidget from "../widgets/note_title.jsx";
|
||||
import NoteTreeWidget from "../widgets/note_tree.js";
|
||||
import NoteWrapperWidget from "../widgets/note_wrapper.js";
|
||||
import NoteDetail from "../widgets/NoteDetail.jsx";
|
||||
import PromotedAttributes from "../widgets/PromotedAttributes.jsx";
|
||||
import QuickSearchWidget from "../widgets/quick_search.js";
|
||||
import ReadOnlyNoteInfoBar from "../widgets/ReadOnlyNoteInfoBar.jsx";
|
||||
import { FixedFormattingToolbar } from "../widgets/ribbon/FormattingToolbar.jsx";
|
||||
import NoteActions from "../widgets/ribbon/NoteActions.jsx";
|
||||
import Ribbon from "../widgets/ribbon/Ribbon.jsx";
|
||||
import ScrollPadding from "../widgets/scroll_padding.js";
|
||||
import SearchResult from "../widgets/search_result.jsx";
|
||||
import SharedInfo from "../widgets/shared_info.jsx";
|
||||
import SplitNoteContainer from "../widgets/containers/split_note_container.js";
|
||||
import RightPanelContainer from "../widgets/sidebar/RightPanelContainer.jsx";
|
||||
import SqlResults from "../widgets/sql_result.js";
|
||||
import SqlTableSchemas from "../widgets/sql_table_schemas.js";
|
||||
import TabRowWidget from "../widgets/tab_row.js";
|
||||
import TabHistoryNavigationButtons from "../widgets/TabHistoryNavigationButtons.jsx";
|
||||
import TitleBarButtons from "../widgets/title_bar_buttons.jsx";
|
||||
import TocWidget from "../widgets/toc.js";
|
||||
import type { AppContext } from "../components/app_context.js";
|
||||
import type { WidgetsByParent } from "../services/bundle.js";
|
||||
import UploadAttachmentsDialog from "../widgets/dialogs/upload_attachments.js";
|
||||
import utils from "../services/utils.js";
|
||||
import WatchedFileUpdateStatusWidget from "../widgets/watched_file_update_status.js";
|
||||
import NoteDetail from "../widgets/NoteDetail.jsx";
|
||||
import PromotedAttributes from "../widgets/PromotedAttributes.jsx";
|
||||
import SpacerWidget from "../widgets/launch_bar/SpacerWidget.jsx";
|
||||
import LauncherContainer from "../widgets/launch_bar/LauncherContainer.jsx";
|
||||
import Breadcrumb from "../widgets/Breadcrumb.jsx";
|
||||
import TabHistoryNavigationButtons from "../widgets/TabHistoryNavigationButtons.jsx";
|
||||
import { applyModals } from "./layout_commons.js";
|
||||
|
||||
export default class DesktopLayout {
|
||||
|
||||
@@ -71,10 +79,11 @@ export default class DesktopLayout {
|
||||
*/
|
||||
const fullWidthTabBar = launcherPaneIsHorizontal || (isElectron && !hasNativeTitleBar && isMac);
|
||||
const customTitleBarButtons = !hasNativeTitleBar && !isMac && !isWindows;
|
||||
const isNewLayout = isExperimentalFeatureEnabled("new-layout");
|
||||
|
||||
const rootContainer = new RootContainer(true)
|
||||
.setParent(appContext)
|
||||
.class((launcherPaneIsHorizontal ? "horizontal" : "vertical") + "-layout")
|
||||
.class(`${launcherPaneIsHorizontal ? "horizontal" : "vertical" }-layout`)
|
||||
.optChild(
|
||||
fullWidthTabBar,
|
||||
new FlexContainer("row")
|
||||
@@ -83,6 +92,7 @@ export default class DesktopLayout {
|
||||
.optChild(launcherPaneIsHorizontal, <LeftPaneToggle isHorizontalLayout={true} />)
|
||||
.child(<TabHistoryNavigationButtons />)
|
||||
.child(new TabRowWidget().class("full-width"))
|
||||
.optChild(launcherPaneIsHorizontal && isNewLayout, <RightPaneToggle />)
|
||||
.optChild(customTitleBarButtons, <TitleBarButtons />)
|
||||
.css("height", "40px")
|
||||
.css("background-color", "var(--launcher-pane-background-color)")
|
||||
@@ -106,10 +116,15 @@ export default class DesktopLayout {
|
||||
.css("flex-grow", "1")
|
||||
.optChild(!fullWidthTabBar,
|
||||
new FlexContainer("row")
|
||||
.class("tab-row-container")
|
||||
.child(<TabHistoryNavigationButtons />)
|
||||
.child(new TabRowWidget())
|
||||
.optChild(isNewLayout, <RightPaneToggle />)
|
||||
.optChild(customTitleBarButtons, <TitleBarButtons />)
|
||||
.css("height", "40px"))
|
||||
.css("height", "40px")
|
||||
.css("align-items", "center")
|
||||
)
|
||||
.optChild(isNewLayout, <FixedFormattingToolbar />)
|
||||
.child(
|
||||
new FlexContainer("row")
|
||||
.filling()
|
||||
@@ -123,37 +138,31 @@ export default class DesktopLayout {
|
||||
.child(
|
||||
new SplitNoteContainer(() =>
|
||||
new NoteWrapperWidget()
|
||||
.child(
|
||||
new FlexContainer("row")
|
||||
.class("breadcrumb-row")
|
||||
.css("height", "30px")
|
||||
.css("min-height", "30px")
|
||||
.css("align-items", "center")
|
||||
.css("padding", "10px")
|
||||
.cssBlock(".breadcrumb-row > * { margin: 5px; }")
|
||||
.child(<Breadcrumb />)
|
||||
.child(<SpacerWidget baseSize={0} growthFactor={1} />)
|
||||
.child(<MovePaneButton direction="left" />)
|
||||
.child(<MovePaneButton direction="right" />)
|
||||
.child(<ClosePaneButton />)
|
||||
.child(<CreatePaneButton />)
|
||||
)
|
||||
.child(new FlexContainer("row")
|
||||
.class("title-row note-split-title")
|
||||
.cssBlock(".title-row > * { margin: 5px; }")
|
||||
.child(<NoteIconWidget />)
|
||||
.child(<NoteTitleWidget />)
|
||||
.optChild(isNewLayout, <NoteBadges />)
|
||||
.child(<SpacerWidget baseSize={0} growthFactor={1} />)
|
||||
.optChild(!isNewLayout, <MovePaneButton direction="left" />)
|
||||
.optChild(!isNewLayout, <MovePaneButton direction="right" />)
|
||||
.optChild(!isNewLayout, <ClosePaneButton />)
|
||||
.optChild(!isNewLayout, <CreatePaneButton />)
|
||||
.optChild(isNewLayout, <NoteActions />))
|
||||
.optChild(!isNewLayout, <Ribbon />)
|
||||
.child(new WatchedFileUpdateStatusWidget())
|
||||
.child(<FloatingButtons items={DESKTOP_FLOATING_BUTTONS} />)
|
||||
.optChild(!isNewLayout, <FloatingButtons items={DESKTOP_FLOATING_BUTTONS} />)
|
||||
.child(
|
||||
new ScrollingContainer()
|
||||
.filling()
|
||||
.child(new ContentHeader()
|
||||
.child(new FlexContainer("row")
|
||||
.class("title-row")
|
||||
.child(<NoteIconWidget />)
|
||||
.child(<NoteTitleWidget />)
|
||||
)
|
||||
.optChild(isNewLayout, <InlineTitle />)
|
||||
.optChild(isNewLayout, <NoteTitleActions />)
|
||||
.optChild(!isNewLayout, new ContentHeader()
|
||||
.child(<ReadOnlyNoteInfoBar />)
|
||||
.child(<SharedInfo />)
|
||||
)
|
||||
.child(<Ribbon />)
|
||||
.child(<PromotedAttributes />)
|
||||
.optChild(!isNewLayout, <PromotedAttributes />)
|
||||
.child(<SqlTableSchemas />)
|
||||
.child(<NoteDetail />)
|
||||
.child(<NoteList media="screen" />)
|
||||
@@ -163,23 +172,24 @@ export default class DesktopLayout {
|
||||
)
|
||||
.child(<ApiLog />)
|
||||
.child(new FindWidget())
|
||||
.child(
|
||||
...this.customWidgets.get("node-detail-pane"), // typo, let's keep it for a while as BC
|
||||
...this.customWidgets.get("note-detail-pane")
|
||||
)
|
||||
.child(...this.customWidgets.get("note-detail-pane"))
|
||||
)
|
||||
)
|
||||
.child(...this.customWidgets.get("center-pane"))
|
||||
|
||||
)
|
||||
.child(
|
||||
.optChild(!isNewLayout,
|
||||
new RightPaneContainer()
|
||||
.child(new TocWidget())
|
||||
.child(new HighlightsListWidget())
|
||||
.child(...this.customWidgets.get("right-pane"))
|
||||
)
|
||||
.optChild(isNewLayout, <RightPanelContainer widgetsByParent={this.customWidgets} />)
|
||||
)
|
||||
.optChild(!launcherPaneIsHorizontal && isNewLayout, <StatusBar />)
|
||||
)
|
||||
)
|
||||
.optChild(launcherPaneIsHorizontal && isNewLayout, <StatusBar />)
|
||||
.child(<CloseZenModeButton />)
|
||||
|
||||
// Desktop-specific dialogs.
|
||||
|
||||
@@ -52,5 +52,5 @@ export function applyModals(rootContainer: RootContainer) {
|
||||
.child(<IncorrectCpuArchDialog />)
|
||||
.child(<PopupEditorDialog />)
|
||||
.child(<CallToActionDialog />)
|
||||
.child(<ToastContainer />)
|
||||
.child(<ToastContainer />);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { t } from "../services/i18n.js";
|
||||
import contextMenu, { type ContextMenuEvent, type MenuItem } from "./context_menu.js";
|
||||
import type { LeafletMouseEvent } from "leaflet";
|
||||
|
||||
import appContext, { type CommandNames } from "../components/app_context.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import type { ViewScope } from "../services/link.js";
|
||||
import utils, { isMobile } from "../services/utils.js";
|
||||
import { getClosestNtxId } from "../widgets/widget_utils.js";
|
||||
import type { LeafletMouseEvent } from "leaflet";
|
||||
import contextMenu, { type ContextMenuEvent, type MenuItem } from "./context_menu.js";
|
||||
|
||||
function openContextMenu(notePath: string, e: ContextMenuEvent, viewScope: ViewScope = {}, hoistedNoteId: string | null = null) {
|
||||
contextMenu.show({
|
||||
@@ -34,15 +35,21 @@ function handleLinkContextMenuItem(command: string | undefined, e: ContextMenuEv
|
||||
|
||||
if (command === "openNoteInNewTab") {
|
||||
appContext.tabManager.openContextWithNote(notePath, { hoistedNoteId, viewScope });
|
||||
return true;
|
||||
} else if (command === "openNoteInNewSplit") {
|
||||
const ntxId = getNtxId(e);
|
||||
if (!ntxId) return;
|
||||
if (!ntxId) return false;
|
||||
appContext.triggerCommand("openNewNoteSplit", { ntxId, notePath, hoistedNoteId, viewScope });
|
||||
return true;
|
||||
} else if (command === "openNoteInNewWindow") {
|
||||
appContext.triggerCommand("openInWindow", { notePath, hoistedNoteId, viewScope });
|
||||
return true;
|
||||
} else if (command === "openNoteInPopup") {
|
||||
appContext.triggerCommand("openInPopup", { noteIdOrPath: notePath })
|
||||
appContext.triggerCommand("openInPopup", { noteIdOrPath: notePath });
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getNtxId(e: ContextMenuEvent | LeafletMouseEvent) {
|
||||
@@ -52,9 +59,9 @@ function getNtxId(e: ContextMenuEvent | LeafletMouseEvent) {
|
||||
return subContexts[subContexts.length - 1].ntxId;
|
||||
} else if (e.target instanceof HTMLElement) {
|
||||
return getClosestNtxId(e.target);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import { h, VNode } from "preact";
|
||||
|
||||
import Component from "../components/component.js";
|
||||
import BasicWidget, { ReactWrappedWidget } from "../widgets/basic_widget.js";
|
||||
import RightPanelWidget from "../widgets/right_panel_widget.js";
|
||||
import froca from "./froca.js";
|
||||
import type { Entity } from "./frontend_script_api.js";
|
||||
import { WidgetDefinitionWithType } from "./frontend_script_api_preact.js";
|
||||
import { t } from "./i18n.js";
|
||||
import ScriptContext from "./script_context.js";
|
||||
import server from "./server.js";
|
||||
import toastService, { showError } from "./toast.js";
|
||||
import froca from "./froca.js";
|
||||
import utils from "./utils.js";
|
||||
import { t } from "./i18n.js";
|
||||
import type { Entity } from "./frontend_script_api.js";
|
||||
import toastService, { showErrorForScriptNote } from "./toast.js";
|
||||
import utils, { getErrorMessage } from "./utils.js";
|
||||
|
||||
// TODO: Deduplicate with server.
|
||||
export interface Bundle {
|
||||
@@ -14,9 +20,12 @@ export interface Bundle {
|
||||
allNoteIds: string[];
|
||||
}
|
||||
|
||||
interface Widget {
|
||||
type LegacyWidget = (BasicWidget | RightPanelWidget) & {
|
||||
parentWidget?: string;
|
||||
}
|
||||
};
|
||||
export type Widget = (LegacyWidget | WidgetDefinitionWithType) & {
|
||||
_noteId: string;
|
||||
};
|
||||
|
||||
async function getAndExecuteBundle(noteId: string, originEntity = null, script = null, params = null) {
|
||||
const bundle = await server.post<Bundle>(`script/bundle/${noteId}`, {
|
||||
@@ -27,6 +36,8 @@ async function getAndExecuteBundle(noteId: string, originEntity = null, script =
|
||||
return await executeBundle(bundle, originEntity);
|
||||
}
|
||||
|
||||
export type ParentName = "left-pane" | "center-pane" | "note-detail-pane" | "right-pane";
|
||||
|
||||
export async function executeBundle(bundle: Bundle, originEntity?: Entity | null, $container?: JQuery<HTMLElement>) {
|
||||
const apiContext = await ScriptContext(bundle.noteId, bundle.allNoteIds, originEntity, $container);
|
||||
|
||||
@@ -52,7 +63,7 @@ export async function executeBundle(bundle: Bundle, originEntity?: Entity | null
|
||||
|
||||
async function executeStartupBundles() {
|
||||
const isMobile = utils.isMobile();
|
||||
const scriptBundles = await server.get<Bundle[]>("script/startup" + (isMobile ? "?mobile=true" : ""));
|
||||
const scriptBundles = await server.get<Bundle[]>(`script/startup${ isMobile ? "?mobile=true" : ""}`);
|
||||
|
||||
for (const bundle of scriptBundles) {
|
||||
await executeBundle(bundle);
|
||||
@@ -60,68 +71,106 @@ async function executeStartupBundles() {
|
||||
}
|
||||
|
||||
export class WidgetsByParent {
|
||||
private byParent: Record<string, Widget[]>;
|
||||
private legacyWidgets: Record<string, LegacyWidget[]>;
|
||||
private preactWidgets: Record<string, WidgetDefinitionWithType[]>;
|
||||
|
||||
constructor() {
|
||||
this.byParent = {};
|
||||
this.legacyWidgets = {};
|
||||
this.preactWidgets = {};
|
||||
}
|
||||
|
||||
add(widget: Widget) {
|
||||
if (!widget.parentWidget) {
|
||||
console.log(`Custom widget does not have mandatory 'parentWidget' property defined`);
|
||||
return;
|
||||
let hasParentWidget = false;
|
||||
let isPreact = false;
|
||||
if ("type" in widget && widget.type === "preact-widget") {
|
||||
// React-based script.
|
||||
const reactWidget = widget as WidgetDefinitionWithType;
|
||||
this.preactWidgets[reactWidget.parent] = this.preactWidgets[reactWidget.parent] || [];
|
||||
this.preactWidgets[reactWidget.parent].push(reactWidget);
|
||||
isPreact = true;
|
||||
hasParentWidget = !!reactWidget.parent;
|
||||
} else if ("parentWidget" in widget && widget.parentWidget) {
|
||||
this.legacyWidgets[widget.parentWidget] = this.legacyWidgets[widget.parentWidget] || [];
|
||||
this.legacyWidgets[widget.parentWidget].push(widget);
|
||||
hasParentWidget = !!widget.parentWidget;
|
||||
}
|
||||
|
||||
this.byParent[widget.parentWidget] = this.byParent[widget.parentWidget] || [];
|
||||
this.byParent[widget.parentWidget].push(widget);
|
||||
if (!hasParentWidget) {
|
||||
showErrorForScriptNote(widget._noteId, t("toast.widget-missing-parent", {
|
||||
property: isPreact ? "parent" : "parentWidget"
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
get(parentName: string) {
|
||||
if (!this.byParent[parentName]) {
|
||||
return [];
|
||||
get(parentName: ParentName) {
|
||||
const widgets: (Component | VNode)[] = this.getLegacyWidgets(parentName);
|
||||
for (const preactWidget of this.getPreactWidgets(parentName)) {
|
||||
const el = h(preactWidget.render, {});
|
||||
const widget = new ReactWrappedWidget(el);
|
||||
widget.contentSized();
|
||||
// TODO: set position here.
|
||||
widgets.push(widget);
|
||||
}
|
||||
|
||||
return widgets;
|
||||
}
|
||||
|
||||
getLegacyWidgets(parentName: ParentName): (BasicWidget | RightPanelWidget)[] {
|
||||
if (!this.legacyWidgets[parentName]) return [];
|
||||
|
||||
return (
|
||||
this.byParent[parentName]
|
||||
this.legacyWidgets[parentName]
|
||||
// previously, custom widgets were provided as a single instance, but that has the disadvantage
|
||||
// for splits where we actually need multiple instaces and thus having a class to instantiate is better
|
||||
// https://github.com/zadam/trilium/issues/4274
|
||||
.map((w: any) => (w.prototype ? new w() : w))
|
||||
);
|
||||
}
|
||||
|
||||
getPreactWidgets(parentName: ParentName) {
|
||||
return this.preactWidgets[parentName] ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
async function getWidgetBundlesByParent() {
|
||||
const scriptBundles = await server.get<Bundle[]>("script/widgets");
|
||||
|
||||
const widgetsByParent = new WidgetsByParent();
|
||||
|
||||
for (const bundle of scriptBundles) {
|
||||
let widget;
|
||||
try {
|
||||
const scriptBundles = await server.get<Bundle[]>("script/widgets");
|
||||
|
||||
try {
|
||||
widget = await executeBundle(bundle);
|
||||
if (widget) {
|
||||
widget._noteId = bundle.noteId;
|
||||
widgetsByParent.add(widget);
|
||||
for (const bundle of scriptBundles) {
|
||||
let widget;
|
||||
|
||||
try {
|
||||
widget = await executeBundle(bundle);
|
||||
if (widget) {
|
||||
widget._noteId = bundle.noteId;
|
||||
widgetsByParent.add(widget);
|
||||
}
|
||||
} catch (e: any) {
|
||||
const noteId = bundle.noteId;
|
||||
const note = await froca.getNote(noteId);
|
||||
toastService.showPersistent({
|
||||
id: `custom-script-failure-${noteId}`,
|
||||
title: t("toast.bundle-error.title"),
|
||||
icon: "bx bx-error-circle",
|
||||
message: t("toast.bundle-error.message", {
|
||||
id: noteId,
|
||||
title: note?.title,
|
||||
message: e.message
|
||||
})
|
||||
});
|
||||
|
||||
logError("Widget initialization failed: ", e);
|
||||
continue;
|
||||
}
|
||||
} catch (e: any) {
|
||||
const noteId = bundle.noteId;
|
||||
const note = await froca.getNote(noteId);
|
||||
toastService.showPersistent({
|
||||
id: `custom-script-failure-${noteId}`,
|
||||
title: t("toast.bundle-error.title"),
|
||||
icon: "bx bx-error-circle",
|
||||
message: t("toast.bundle-error.message", {
|
||||
id: noteId,
|
||||
title: note?.title,
|
||||
message: e.message
|
||||
})
|
||||
});
|
||||
|
||||
logError("Widget initialization failed: ", e);
|
||||
continue;
|
||||
}
|
||||
} catch (e) {
|
||||
toastService.showPersistent({
|
||||
title: t("toast.widget-list-error.title"),
|
||||
message: getErrorMessage(e),
|
||||
icon: "bx bx-error-circle"
|
||||
});
|
||||
}
|
||||
|
||||
return widgetsByParent;
|
||||
|
||||
59
apps/client/src/services/experimental_features.ts
Normal file
59
apps/client/src/services/experimental_features.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { t } from "./i18n";
|
||||
import options from "./options";
|
||||
|
||||
export interface ExperimentalFeature {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export const experimentalFeatures = [
|
||||
{
|
||||
id: "new-layout",
|
||||
name: t("experimental_features.new_layout_name"),
|
||||
description: t("experimental_features.new_layout_description"),
|
||||
}
|
||||
] as const satisfies ExperimentalFeature[];
|
||||
|
||||
export type ExperimentalFeatureId = typeof experimentalFeatures[number]["id"];
|
||||
|
||||
let enabledFeatures: Set<ExperimentalFeatureId> | null = null;
|
||||
|
||||
export function isExperimentalFeatureEnabled(featureId: ExperimentalFeatureId): boolean {
|
||||
if (featureId === "new-layout") {
|
||||
return options.is("newLayout");
|
||||
}
|
||||
|
||||
return getEnabledFeatures().has(featureId);
|
||||
}
|
||||
|
||||
export function getEnabledExperimentalFeatureIds() {
|
||||
const values = [ ...getEnabledFeatures().values() ];
|
||||
if (options.is("newLayout")) {
|
||||
values.push("new-layout");
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
export async function toggleExperimentalFeature(featureId: ExperimentalFeatureId, enable: boolean) {
|
||||
const features = new Set(getEnabledFeatures());
|
||||
if (enable) {
|
||||
features.add(featureId);
|
||||
} else {
|
||||
features.delete(featureId);
|
||||
}
|
||||
await options.save("experimentalFeatures", JSON.stringify(Array.from(features)));
|
||||
}
|
||||
|
||||
function getEnabledFeatures() {
|
||||
if (!enabledFeatures) {
|
||||
let features: ExperimentalFeatureId[] = [];
|
||||
try {
|
||||
features = JSON.parse(options.get("experimentalFeatures")) as ExperimentalFeatureId[];
|
||||
} catch (e) {
|
||||
console.warn("Failed to parse experimental features from options:", e);
|
||||
}
|
||||
enabledFeatures = new Set(features);
|
||||
}
|
||||
return enabledFeatures;
|
||||
}
|
||||
@@ -1,26 +1,27 @@
|
||||
import server from "./server.js";
|
||||
import utils from "./utils.js";
|
||||
import toastService from "./toast.js";
|
||||
import linkService from "./link.js";
|
||||
import { dayjs, formatLogMessage } from "@triliumnext/commons";
|
||||
|
||||
import appContext from "../components/app_context.js";
|
||||
import type Component from "../components/component.js";
|
||||
import type NoteContext from "../components/note_context.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import BasicWidget, { ReactWrappedWidget } from "../widgets/basic_widget.js";
|
||||
import NoteContextAwareWidget from "../widgets/note_context_aware_widget.js";
|
||||
import RightPanelWidget from "../widgets/right_panel_widget.js";
|
||||
import dateNotesService from "./date_notes.js";
|
||||
import dialogService from "./dialog.js";
|
||||
import froca from "./froca.js";
|
||||
import { preactAPI } from "./frontend_script_api_preact.js";
|
||||
import { t } from "./i18n.js";
|
||||
import linkService from "./link.js";
|
||||
import noteTooltipService from "./note_tooltip.js";
|
||||
import protectedSessionService from "./protected_session.js";
|
||||
import dateNotesService from "./date_notes.js";
|
||||
import searchService from "./search.js";
|
||||
import RightPanelWidget from "../widgets/right_panel_widget.js";
|
||||
import ws from "./ws.js";
|
||||
import appContext from "../components/app_context.js";
|
||||
import NoteContextAwareWidget from "../widgets/note_context_aware_widget.js";
|
||||
import BasicWidget, { ReactWrappedWidget } from "../widgets/basic_widget.js";
|
||||
import SpacedUpdate from "./spaced_update.js";
|
||||
import server from "./server.js";
|
||||
import shortcutService from "./shortcuts.js";
|
||||
import dialogService from "./dialog.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import { t } from "./i18n.js";
|
||||
import { dayjs } from "@triliumnext/commons";
|
||||
import type NoteContext from "../components/note_context.js";
|
||||
import type Component from "../components/component.js";
|
||||
import { formatLogMessage } from "@triliumnext/commons";
|
||||
import SpacedUpdate from "./spaced_update.js";
|
||||
import toastService from "./toast.js";
|
||||
import utils from "./utils.js";
|
||||
import ws from "./ws.js";
|
||||
|
||||
/**
|
||||
* A whole number
|
||||
@@ -464,6 +465,8 @@ export interface Api {
|
||||
* Log given message to the log pane in UI
|
||||
*/
|
||||
log(message: string | object): void;
|
||||
|
||||
preact: typeof preactAPI;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -533,9 +536,9 @@ function FrontendScriptApi(this: Api, startNote: FNote, currentNote: FNote, orig
|
||||
return params.map((p) => {
|
||||
if (typeof p === "function") {
|
||||
return `!@#Function: ${p.toString()}`;
|
||||
} else {
|
||||
return p;
|
||||
}
|
||||
return p;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -562,9 +565,9 @@ function FrontendScriptApi(this: Api, startNote: FNote, currentNote: FNote, orig
|
||||
await ws.waitForMaxKnownEntityChangeId();
|
||||
|
||||
return ret.executionResult;
|
||||
} else {
|
||||
throw new Error(`server error: ${ret.error}`);
|
||||
}
|
||||
throw new Error(`server error: ${ret.error}`);
|
||||
|
||||
};
|
||||
|
||||
this.runOnBackend = async (func, params = []) => {
|
||||
@@ -721,6 +724,8 @@ function FrontendScriptApi(this: Api, startNote: FNote, currentNote: FNote, orig
|
||||
this.logMessages[noteId].push(message);
|
||||
this.logSpacedUpdates[noteId].scheduleUpdate();
|
||||
};
|
||||
|
||||
this.preact = preactAPI;
|
||||
}
|
||||
|
||||
export default FrontendScriptApi as any as {
|
||||
|
||||
37
apps/client/src/services/frontend_script_api_preact.ts
Normal file
37
apps/client/src/services/frontend_script_api_preact.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Fragment, h, VNode } from "preact";
|
||||
import * as hooks from "preact/hooks";
|
||||
|
||||
import * as triliumHooks from "../widgets/react/hooks";
|
||||
import RightPanelWidget from "../widgets/sidebar/RightPanelWidget";
|
||||
|
||||
export interface WidgetDefinition {
|
||||
parent: "right-pane",
|
||||
render: () => VNode
|
||||
}
|
||||
|
||||
export interface WidgetDefinitionWithType extends WidgetDefinition {
|
||||
type: "preact-widget"
|
||||
}
|
||||
|
||||
export const preactAPI = Object.freeze({
|
||||
// Core
|
||||
h,
|
||||
Fragment,
|
||||
|
||||
/**
|
||||
* Method that must be run for widget scripts that run on Preact, using JSX. The method just returns the same definition, reserved for future typechecking and perhaps validation purposes.
|
||||
*
|
||||
* @param definition the widget definition.
|
||||
*/
|
||||
defineWidget(definition: WidgetDefinition) {
|
||||
return {
|
||||
type: "preact-widget",
|
||||
...definition
|
||||
};
|
||||
},
|
||||
|
||||
RightPanelWidget,
|
||||
|
||||
...hooks,
|
||||
...triliumHooks
|
||||
});
|
||||
@@ -1,10 +1,11 @@
|
||||
import treeService from "./tree.js";
|
||||
import linkContextMenuService from "../menus/link_context_menu.js";
|
||||
import appContext, { type NoteCommandData } from "../components/app_context.js";
|
||||
import froca from "./froca.js";
|
||||
import utils from "./utils.js";
|
||||
import { ALLOWED_PROTOCOLS } from "@triliumnext/commons";
|
||||
|
||||
import appContext, { type NoteCommandData } from "../components/app_context.js";
|
||||
import { openInCurrentNoteContext } from "../components/note_context.js";
|
||||
import linkContextMenuService from "../menus/link_context_menu.js";
|
||||
import froca from "./froca.js";
|
||||
import treeService from "./tree.js";
|
||||
import utils from "./utils.js";
|
||||
|
||||
function getNotePathFromUrl(url: string) {
|
||||
const notePathMatch = /#(root[A-Za-z0-9_/]*)$/.exec(url);
|
||||
@@ -27,7 +28,7 @@ async function getLinkIcon(noteId: string, viewMode: ViewMode | undefined) {
|
||||
return icon;
|
||||
}
|
||||
|
||||
export type ViewMode = "default" | "source" | "attachments" | "contextual-help";
|
||||
export type ViewMode = "default" | "source" | "attachments" | "contextual-help" | "note-map";
|
||||
|
||||
export interface ViewScope {
|
||||
/**
|
||||
@@ -122,7 +123,7 @@ async function createLink(notePath: string | undefined, options: CreateLinkOptio
|
||||
const $container = $("<span>");
|
||||
|
||||
if (showNoteIcon) {
|
||||
let icon = await getLinkIcon(noteId, viewMode);
|
||||
const icon = await getLinkIcon(noteId, viewMode);
|
||||
|
||||
if (icon) {
|
||||
$container.append($("<span>").addClass(`bx ${icon}`)).append(" ");
|
||||
@@ -131,7 +132,7 @@ async function createLink(notePath: string | undefined, options: CreateLinkOptio
|
||||
|
||||
const hash = calculateHash({
|
||||
notePath,
|
||||
viewScope: viewScope
|
||||
viewScope
|
||||
});
|
||||
|
||||
const $noteLink = $("<a>", {
|
||||
@@ -171,11 +172,11 @@ async function createLink(notePath: string | undefined, options: CreateLinkOptio
|
||||
return $container;
|
||||
}
|
||||
|
||||
function calculateHash({ notePath, ntxId, hoistedNoteId, viewScope = {} }: NoteCommandData) {
|
||||
export function calculateHash({ notePath, ntxId, hoistedNoteId, viewScope = {} }: NoteCommandData) {
|
||||
notePath = notePath || "";
|
||||
const params = [
|
||||
ntxId ? { ntxId: ntxId } : null,
|
||||
hoistedNoteId && hoistedNoteId !== "root" ? { hoistedNoteId: hoistedNoteId } : null,
|
||||
ntxId ? { ntxId } : null,
|
||||
hoistedNoteId && hoistedNoteId !== "root" ? { hoistedNoteId } : null,
|
||||
viewScope.viewMode && viewScope.viewMode !== "default" ? { viewMode: viewScope.viewMode } : null,
|
||||
viewScope.attachmentId ? { attachmentId: viewScope.attachmentId } : null
|
||||
].filter((p) => !!p);
|
||||
@@ -219,7 +220,7 @@ export function parseNavigationStateFromUrl(url: string | undefined) {
|
||||
}
|
||||
|
||||
const hash = url.substr(hashIdx + 1); // strip also the initial '#'
|
||||
let [notePath, paramString] = hash.split("?");
|
||||
const [notePath, paramString] = hash.split("?");
|
||||
|
||||
const viewScope: ViewScope = {
|
||||
viewMode: "default"
|
||||
@@ -252,7 +253,7 @@ export function parseNavigationStateFromUrl(url: string | undefined) {
|
||||
}
|
||||
|
||||
if (searchString) {
|
||||
return { searchString }
|
||||
return { searchString };
|
||||
}
|
||||
|
||||
if (!notePath.match(/^[_a-z0-9]{4,}(\/[_a-z0-9]{4,})*$/i)) {
|
||||
@@ -334,7 +335,7 @@ export function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDo
|
||||
window.open(hrefLink, "_blank");
|
||||
} else {
|
||||
// Enable protocols supported by CKEditor 5 to be clickable.
|
||||
if (ALLOWED_PROTOCOLS.some((protocol) => hrefLink.toLowerCase().startsWith(protocol + ":"))) {
|
||||
if (ALLOWED_PROTOCOLS.some((protocol) => hrefLink.toLowerCase().startsWith(`${protocol}:`))) {
|
||||
if ( utils.isElectron()) {
|
||||
const electron = utils.dynamicRequire("electron");
|
||||
electron.shell.openExternal(hrefLink);
|
||||
@@ -395,7 +396,7 @@ async function loadReferenceLinkTitle($el: JQuery<HTMLElement>, href: string | n
|
||||
|
||||
href = href || $link.attr("href");
|
||||
if (!href) {
|
||||
console.warn("Empty URL for parsing: " + $el[0].outerHTML);
|
||||
console.warn(`Empty URL for parsing: ${$el[0].outerHTML}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -438,9 +439,9 @@ async function getReferenceLinkTitle(href: string) {
|
||||
const attachment = await note.getAttachmentById(viewScope.attachmentId);
|
||||
|
||||
return attachment ? attachment.title : "[missing attachment]";
|
||||
} else {
|
||||
return note.title;
|
||||
}
|
||||
return note.title;
|
||||
|
||||
}
|
||||
|
||||
function getReferenceLinkTitleSync(href: string) {
|
||||
@@ -462,9 +463,9 @@ function getReferenceLinkTitleSync(href: string) {
|
||||
const attachment = note.attachments.find((att) => att.attachmentId === viewScope.attachmentId);
|
||||
|
||||
return attachment ? attachment.title : "[missing attachment]";
|
||||
} else {
|
||||
return note.title;
|
||||
}
|
||||
return note.title;
|
||||
|
||||
}
|
||||
|
||||
if (glob.device !== "print") {
|
||||
|
||||
@@ -133,11 +133,11 @@ async function call<T>(method: string, url: string, componentId?: string, option
|
||||
};
|
||||
|
||||
ipc.send("server-request", {
|
||||
requestId: requestId,
|
||||
headers: headers,
|
||||
method: method,
|
||||
requestId,
|
||||
headers,
|
||||
method,
|
||||
url: `/${window.glob.baseApiUrl}${url}`,
|
||||
data: data
|
||||
data
|
||||
});
|
||||
})) as any;
|
||||
} else {
|
||||
@@ -161,7 +161,7 @@ function ajax(url: string, method: string, data: unknown, headers: Headers, sile
|
||||
const options: JQueryAjaxSettings = {
|
||||
url: window.glob.baseApiUrl + url,
|
||||
type: method,
|
||||
headers: headers,
|
||||
headers,
|
||||
timeout: 60000,
|
||||
success: (body, textStatus, jqXhr) => {
|
||||
const respHeaders: Headers = {};
|
||||
@@ -288,8 +288,8 @@ async function reportError(method: string, url: string, statusCode: number, resp
|
||||
t("server.unknown_http_error_content", { statusCode, method, url, message: messageStr }),
|
||||
15_000);
|
||||
}
|
||||
const { throwError } = await import("./ws.js");
|
||||
throwError(`${statusCode} ${method} ${url} - ${message}`);
|
||||
const { logError } = await import("./ws.js");
|
||||
logError(`${statusCode} ${method} ${url} - ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { signal } from "@preact/signals";
|
||||
|
||||
import appContext from "../components/app_context.js";
|
||||
import froca from "./froca.js";
|
||||
import { t } from "./i18n.js";
|
||||
import utils from "./utils.js";
|
||||
|
||||
export interface ToastOptions {
|
||||
@@ -61,6 +64,24 @@ function showErrorTitleAndMessage(title: string, message: string, timeout = 1000
|
||||
});
|
||||
}
|
||||
|
||||
export async function showErrorForScriptNote(noteId: string, message: string) {
|
||||
const note = await froca.getNote(noteId, true);
|
||||
|
||||
showPersistent({
|
||||
id: `custom-widget-failure-${noteId}`,
|
||||
title: note?.title ?? "",
|
||||
icon: note?.getIcon() ?? "bx bx-error-circle",
|
||||
message,
|
||||
timeout: 15_000,
|
||||
buttons: [
|
||||
{
|
||||
text: t("toast.open-script-note"),
|
||||
onClick: () => appContext.tabManager.openInNewTab(noteId, null, true)
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
//#region Toast store
|
||||
export const toasts = signal<ToastOptionsWithRequiredId[]>([]);
|
||||
|
||||
@@ -74,7 +95,7 @@ function addToast(opts: ToastOptions) {
|
||||
function updateToast(id: string, partial: Partial<ToastOptions>) {
|
||||
toasts.value = toasts.value.map(toast => {
|
||||
if (toast.id === id) {
|
||||
return { ...toast, ...partial }
|
||||
return { ...toast, ...partial };
|
||||
}
|
||||
return toast;
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { dayjs } from "@triliumnext/commons";
|
||||
import type { ViewScope } from "./link.js";
|
||||
import type { ViewMode, ViewScope } from "./link.js";
|
||||
import FNote from "../entities/fnote";
|
||||
import { snapdom } from "@zumer/snapdom";
|
||||
|
||||
@@ -439,7 +439,20 @@ async function openInAppHelp($button: JQuery<HTMLElement>) {
|
||||
* @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) {
|
||||
export function openInAppHelpFromUrl(inAppHelpPage: string) {
|
||||
return openInReusableSplit(`_help_${inAppHelpPage}`, "contextual-help");
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to opening a new note in a split, but re-uses an existing split if there is already one open with the same view mode.
|
||||
*
|
||||
* @param targetNoteId the note ID to open in the split.
|
||||
* @param targetViewMode the view mode of the split to open the note in.
|
||||
* @param openOpts additional options for opening the note.
|
||||
*/
|
||||
export async function openInReusableSplit(targetNoteId: string, targetViewMode: ViewMode, openOpts: {
|
||||
hoistedNoteId?: string;
|
||||
} = {}) {
|
||||
// Dynamic import to avoid import issues in tests.
|
||||
const appContext = (await import("../components/app_context.js")).default;
|
||||
const activeContext = appContext.tabManager.getActiveContext();
|
||||
@@ -447,23 +460,20 @@ export async function openInAppHelpFromUrl(inAppHelpPage: string) {
|
||||
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 existingSubcontext = subContexts.find((s) => s.viewScope?.viewMode === targetViewMode);
|
||||
const viewScope: ViewScope = { viewMode: targetViewMode };
|
||||
if (!existingSubcontext) {
|
||||
// The target split is not already open, open a new split with it.
|
||||
const { ntxId } = subContexts[subContexts.length - 1];
|
||||
appContext.triggerCommand("openNewNoteSplit", {
|
||||
ntxId,
|
||||
notePath: targetNote,
|
||||
hoistedNoteId: "_help",
|
||||
notePath: targetNoteId,
|
||||
hoistedNoteId: openOpts.hoistedNoteId,
|
||||
viewScope
|
||||
})
|
||||
});
|
||||
} else {
|
||||
// There is already a help window open, make sure it opens on the right note.
|
||||
helpSubcontext.setNote(targetNote, { viewScope });
|
||||
// There is already a target split open, make sure it opens on the right note.
|
||||
existingSubcontext.setNote(targetNoteId, { viewScope });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -423,16 +423,16 @@ body.desktop .tabulator-popup-container,
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.dropdown-menu .disabled .disabled-tooltip {
|
||||
.dropdown-menu .disabled .contextual-help {
|
||||
pointer-events: all;
|
||||
margin-inline-start: 8px;
|
||||
font-size: 0.75rem;
|
||||
color: var(--disabled-tooltip-icon-color);
|
||||
color: var(--contextual-help-icon-color);
|
||||
cursor: help;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.dropdown-menu .disabled .disabled-tooltip:hover {
|
||||
.dropdown-menu .disabled .contextual-help:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -521,9 +521,7 @@ body.mobile .dropdown .dropdown-submenu > span {
|
||||
.cm-editor {
|
||||
height: 100%;
|
||||
outline: none !important;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
margin: 4px;
|
||||
font-size: var(--monospace-font-size);
|
||||
}
|
||||
|
||||
@@ -629,6 +627,11 @@ pre:not(.hljs) {
|
||||
padding: var(--padding-size);
|
||||
}
|
||||
|
||||
pre:has(> .cm-editor) {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
pre > button.copy-button {
|
||||
position: absolute;
|
||||
top: var(--copy-button-margin-size);
|
||||
@@ -714,12 +717,17 @@ table.promoted-attributes-in-tooltip th {
|
||||
.tooltip {
|
||||
font-size: var(--main-font-size) !important;
|
||||
z-index: calc(var(--ck-z-panel) - 1) !important;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.tooltip.tooltip-top {
|
||||
z-index: 32767 !important;
|
||||
}
|
||||
|
||||
.pre-wrap-text {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.bs-tooltip-bottom .tooltip-arrow::before {
|
||||
border-bottom-color: var(--main-border-color) !important;
|
||||
}
|
||||
@@ -1315,12 +1323,21 @@ body.desktop li.dropdown-submenu:hover > ul.dropdown-menu {
|
||||
top: 0;
|
||||
inset-inline-start: calc(100% - 2px); /* -2px, otherwise there's a small gap between menu and submenu where the hover can disappear */
|
||||
margin-top: -10px;
|
||||
min-width: 15rem;
|
||||
/* to make submenu scrollable https://github.com/zadam/trilium/issues/3136 */
|
||||
max-height: 600px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
body.desktop .dropdown-submenu > .dropdown-menu {
|
||||
min-width: max-content;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.dropdown-submenu.dropstart > .dropdown-menu {
|
||||
inset-inline-start: auto;
|
||||
inset-inline-end: calc(100% - 2px);
|
||||
}
|
||||
|
||||
body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
|
||||
inset-inline-start: calc(-100% + 10px);
|
||||
}
|
||||
@@ -1944,7 +1961,7 @@ body.electron.platform-darwin:not(.native-titlebar):not(.full-screen) #tab-row-l
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.tab-row-widget {
|
||||
.tab-row-container {
|
||||
padding-inline-end: calc(100vw - env(titlebar-area-width, 100vw));
|
||||
}
|
||||
|
||||
@@ -2104,58 +2121,106 @@ body.zen:not(.backdrop-effects-disabled) .note-split.type-text .scrolling-contai
|
||||
|
||||
/* Fixed formatting toolbar */
|
||||
|
||||
body.zen .note-split .ribbon-container {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 20px;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
opacity: 0; /* Hidden unless the current note split is focused */
|
||||
pointer-events: none;
|
||||
transition: opacity 100ms linear;
|
||||
}
|
||||
|
||||
body.zen .note-split:focus-within .ribbon-container {
|
||||
opacity: 1; /* Show when the note split is focused */
|
||||
}
|
||||
|
||||
body.zen .note-split .ribbon-container .ribbon-body {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
body.zen .note-split .ribbon-container .classic-toolbar-widget {
|
||||
margin: auto;
|
||||
width: fit-content;
|
||||
box-shadow: 0px 10px 20px rgba(0, 0, 0, .1);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--main-border-color);
|
||||
padding: 4px;
|
||||
background: var(--menu-background-color);
|
||||
}
|
||||
|
||||
body.zen .note-split .ribbon-container .classic-toolbar-widget:not(:has(> .ck-toolbar)) {
|
||||
/* Hide the toolbar wrapper if the toolbar is missing */
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.zen .note-split:focus-within .ribbon-container .classic-toolbar-widget {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
@media (max-width: 1300px) {
|
||||
body.zen .note-split .ribbon-container .classic-toolbar-widget {
|
||||
/* Set the toolbar to full with */
|
||||
body.zen:not(.experimental-feature-new-layout) {
|
||||
.note-split .ribbon-container {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 20px;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
opacity: 0; /* Hidden unless the current note split is focused */
|
||||
pointer-events: none;
|
||||
transition: opacity 100ms linear;
|
||||
}
|
||||
|
||||
body.zen .classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_se,
|
||||
body.zen .classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_sw,
|
||||
body.zen .classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_smw,
|
||||
body.zen .classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_sme,
|
||||
body.zen .classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_s {
|
||||
/* Force toolbar items overflow dropdowns open upwards */
|
||||
top: auto;
|
||||
bottom: 100%;
|
||||
.note-split:focus-within .ribbon-container {
|
||||
opacity: 1; /* Show when the note split is focused */
|
||||
}
|
||||
|
||||
.note-split .ribbon-container .ribbon-body {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.note-split .ribbon-container .classic-toolbar-widget {
|
||||
margin: auto;
|
||||
width: fit-content;
|
||||
box-shadow: 0px 10px 20px rgba(0, 0, 0, .1);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--main-border-color);
|
||||
padding: 4px;
|
||||
background: var(--menu-background-color);
|
||||
}
|
||||
|
||||
.note-split .ribbon-container .classic-toolbar-widget:not(:has(> .ck-toolbar)) {
|
||||
/* Hide the toolbar wrapper if the toolbar is missing */
|
||||
display: none;
|
||||
}
|
||||
|
||||
.note-split:focus-within .ribbon-container .classic-toolbar-widget {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
@media (max-width: 1300px) {
|
||||
.note-split .ribbon-container .classic-toolbar-widget {
|
||||
/* Set the toolbar to full with */
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_se,
|
||||
.classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_sw,
|
||||
.classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_smw,
|
||||
.classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_sme,
|
||||
.classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_s {
|
||||
/* Force toolbar items overflow dropdowns open upwards */
|
||||
top: auto;
|
||||
bottom: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.zen.experimental-feature-new-layout {
|
||||
.status-bar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.classic-toolbar-widget {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
bottom: 20px;
|
||||
z-index: 1000;
|
||||
opacity: 0; /* Hidden unless the current note split is focused */
|
||||
pointer-events: none;
|
||||
transition: opacity 100ms linear;
|
||||
width: fit-content;
|
||||
box-shadow: 0px 10px 20px rgba(0, 0, 0, .1);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--main-border-color);
|
||||
padding: 4px;
|
||||
background: var(--menu-background-color);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
#root-widget:has(.note-split.type-text:focus-within) .classic-toolbar-widget,
|
||||
.classic-toolbar-widget:focus-within {
|
||||
opacity: 1; /* Show when the note split is focused */
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
@media (max-width: 1300px) {
|
||||
.classic-toolbar-widget {
|
||||
/* Set the toolbar to full with */
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_se,
|
||||
.classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_sw,
|
||||
.classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_smw,
|
||||
.classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_sme,
|
||||
.classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_s {
|
||||
/* Force toolbar items overflow dropdowns open upwards */
|
||||
top: auto;
|
||||
bottom: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2462,6 +2527,11 @@ footer.webview-footer button {
|
||||
inset-inline-start: 10px;
|
||||
}
|
||||
|
||||
.content-floating-buttons.top-right {
|
||||
top: 10px;
|
||||
inset-inline-end: 10px;
|
||||
}
|
||||
|
||||
.content-floating-buttons.bottom-left {
|
||||
bottom: 10px;
|
||||
inset-inline-start: 10px;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
--dropdown-border-color: #555;
|
||||
--dropdown-shadow-opacity: 0.4;
|
||||
--dropdown-item-icon-destructive-color: #de6e5b;
|
||||
--disabled-tooltip-icon-color: #7fd2ef;
|
||||
--contextual-help-icon-color: #7fd2ef;
|
||||
|
||||
--accented-background-color: #555;
|
||||
--more-accented-background-color: #777;
|
||||
@@ -114,4 +114,4 @@ body .todo-list input[type="checkbox"]:not(:checked):before {
|
||||
|
||||
.use-note-color {
|
||||
--custom-color: var(--dark-theme-custom-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ html {
|
||||
--dropdown-border-color: #ccc;
|
||||
--dropdown-shadow-opacity: 0.2;
|
||||
--dropdown-item-icon-destructive-color: #ec5138;
|
||||
--disabled-tooltip-icon-color: #004382;
|
||||
--contextual-help-icon-color: #004382;
|
||||
|
||||
--accented-background-color: #f5f5f5;
|
||||
--more-accented-background-color: #ddd;
|
||||
@@ -89,13 +89,13 @@ html {
|
||||
--custom-color: var(--light-theme-custom-color);
|
||||
}
|
||||
|
||||
:root .reference-link,
|
||||
:root .reference-link:hover,
|
||||
.ck-content a.reference-link > span,
|
||||
.board-note {
|
||||
:root .reference-link.use-note-color,
|
||||
:root .reference-link.use-note-color:hover,
|
||||
.ck-content a.reference-link.use-note-color > span,
|
||||
.board-note.use-note-color {
|
||||
color: var(--light-theme-custom-color, inherit);
|
||||
}
|
||||
|
||||
.use-note-color {
|
||||
--custom-color: var(--light-theme-custom-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
:root {
|
||||
|
||||
/*
|
||||
/*
|
||||
* ⚠️ NOTICE: This theme is currently in the beta stage of development.
|
||||
* The names and purposes of these CSS variables are subject to frequent changes.
|
||||
*/
|
||||
@@ -22,7 +22,7 @@
|
||||
--dropdown-border-color: #404040;
|
||||
--dropdown-shadow-opacity: 0.6;
|
||||
--dropdown-item-icon-destructive-color: #de6e5b;
|
||||
--disabled-tooltip-icon-color: #7fd2ef;
|
||||
--contextual-help-icon-color: #7fd2ef;
|
||||
|
||||
--accented-background-color: #555;
|
||||
|
||||
@@ -170,6 +170,9 @@
|
||||
--protected-session-active-icon-color: #8edd8e;
|
||||
--sync-status-error-pulse-color: #f47871;
|
||||
|
||||
--classic-toolbar-vert-layout-background-color: var(--main-background-color);
|
||||
--classic-toolbar-horiz-layout-background-color: var(--main-background-color);
|
||||
|
||||
--center-pane-vert-layout-background-color-bgfx: #0c0c0c69;
|
||||
--center-pane-horiz-layout-background-color-bgfx: #1e1e1ec7;
|
||||
|
||||
@@ -182,7 +185,7 @@
|
||||
|
||||
--tab-close-button-hover-background: #a45353;
|
||||
--tab-close-button-hover-color: white;
|
||||
|
||||
|
||||
--active-tab-background-color: #ffffff1c;
|
||||
--active-tab-hover-background-color: var(--active-tab-background-color);
|
||||
--active-tab-icon-color: #a9a9a9;
|
||||
@@ -199,9 +202,13 @@
|
||||
--badge-background-color: #ffffff1a;
|
||||
--badge-text-color: var(--muted-text-color);
|
||||
|
||||
--note-icon-background-color: #444444;
|
||||
--note-icon-color: #d4d4d4;
|
||||
--note-icon-hover-background-color: #555555;
|
||||
|
||||
--promoted-attribute-card-background-color: #ffffff21;
|
||||
--promoted-attribute-card-shadow: none;
|
||||
|
||||
|
||||
--floating-button-shadow-color: #00000080;
|
||||
--floating-button-background-color: #494949d2;
|
||||
--floating-button-color: var(--button-text-color);
|
||||
@@ -226,7 +233,7 @@
|
||||
--scrollbar-border-color: unset; /* Deprecated */
|
||||
|
||||
--selection-background-color: #3399FF70;
|
||||
|
||||
|
||||
--link-color: lightskyblue;
|
||||
|
||||
--mermaid-theme: dark;
|
||||
@@ -293,10 +300,10 @@
|
||||
--custom-bg-color: hsl(var(--custom-color-hue), 20%, 33%, 0.4);
|
||||
}
|
||||
|
||||
:root .reference-link,
|
||||
:root .reference-link:hover,
|
||||
.ck-content a.reference-link > span,
|
||||
.board-note {
|
||||
:root .reference-link.use-note-color,
|
||||
:root .reference-link.use-note-color:hover,
|
||||
.ck-content a.reference-link.use-note-color > span,
|
||||
.board-note.use-note-color {
|
||||
color: var(--dark-theme-custom-color, inherit);
|
||||
}
|
||||
|
||||
@@ -320,4 +327,16 @@ body .todo-list input[type="checkbox"]:not(:checked):before {
|
||||
|
||||
.use-note-color {
|
||||
--custom-color: var(--dark-theme-custom-color);
|
||||
}
|
||||
|
||||
.note-split.with-hue,
|
||||
.quick-edit-dialog-wrapper.with-hue {
|
||||
--note-icon-custom-background-color: hsl(var(--custom-color-hue), 15.8%, 30.9%);
|
||||
--note-icon-custom-color: hsl(var(--custom-color-hue), 100%, 76.5%);
|
||||
--note-icon-hover-custom-background-color: hsl(var(--custom-color-hue), 28.3%, 36.7%);
|
||||
}
|
||||
|
||||
.note-split.with-hue *::selection,
|
||||
.quick-edit-dialog-wrapper.with-hue *::selection {
|
||||
background: hsl(var(--custom-color-hue), 49.2%, 35%);
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
:root {
|
||||
|
||||
/*
|
||||
/*
|
||||
* ⚠️ NOTICE: This theme is currently in the beta stage of development.
|
||||
* The names and purposes of these CSS variables are subject to frequent changes.
|
||||
*/
|
||||
@@ -22,7 +22,7 @@
|
||||
--dropdown-border-color: #ccc;
|
||||
--dropdown-shadow-opacity: 0.2;
|
||||
--dropdown-item-icon-destructive-color: #ec5138;
|
||||
--disabled-tooltip-icon-color: #004382;
|
||||
--contextual-help-icon-color: #004382;
|
||||
|
||||
--accented-background-color: #f5f5f5;
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
/* Deprecated: now local variables in #launcher, with the values dependent on the current layout. */
|
||||
--launcher-pane-background-color: unset;
|
||||
--launcher-pane-text-color: unset;
|
||||
|
||||
|
||||
--launcher-pane-vert-background-color: #e8e8e8;
|
||||
--launcher-pane-vert-text-color: #000000bd;
|
||||
--launcher-pane-vert-button-hover-color: black;
|
||||
@@ -162,6 +162,9 @@
|
||||
--protected-session-active-icon-color: #16b516;
|
||||
--sync-status-error-pulse-color: #ff5528;
|
||||
|
||||
--classic-toolbar-vert-layout-background-color: #ffffffa1;
|
||||
--classic-toolbar-horiz-layout-background-color: var(--main-background-color);
|
||||
|
||||
--center-pane-vert-layout-background-color-bgfx: #ffffff75;
|
||||
--center-pane-horiz-layout-background-color-bgfx: #ffffffd6;
|
||||
|
||||
@@ -174,7 +177,7 @@
|
||||
|
||||
--tab-close-button-hover-background: #c95a5a;
|
||||
--tab-close-button-hover-color: white;
|
||||
|
||||
|
||||
--active-tab-background-color: white;
|
||||
--active-tab-hover-background-color: var(--active-tab-background-color);
|
||||
--active-tab-icon-color: gray;
|
||||
@@ -191,6 +194,10 @@
|
||||
--badge-background-color: #00000011;
|
||||
--badge-text-color: var(--muted-text-color);
|
||||
|
||||
--note-icon-background-color: #4f4f4f;
|
||||
--note-icon-color: white;
|
||||
--note-icon-hover-background-color: #737373;
|
||||
|
||||
--promoted-attribute-card-background-color: #00000014;
|
||||
--promoted-attribute-card-shadow: none;
|
||||
|
||||
@@ -291,4 +298,16 @@
|
||||
--modal-background-color: hsl(var(--custom-color-hue), 56%, 96%);
|
||||
--modal-border-color: hsl(var(--custom-color-hue), 33%, 41%);
|
||||
--promoted-attribute-card-background-color: hsl(var(--custom-color-hue), 40%, 88%);
|
||||
}
|
||||
|
||||
.note-split.with-hue,
|
||||
.quick-edit-dialog-wrapper.with-hue {
|
||||
--note-icon-custom-background-color: hsl(var(--custom-color-hue), 44.5%, 43.1%);
|
||||
--note-icon-custom-color: hsl(var(--custom-color-hue), 91.3%, 91%);
|
||||
--note-icon-hover-custom-background-color: hsl(var(--custom-color-hue), 55.1%, 50.2%);
|
||||
}
|
||||
|
||||
.note-split.with-hue *::selection,
|
||||
.quick-edit-dialog-wrapper.with-hue *::selection {
|
||||
background: hsl(var(--custom-color-hue), 60%, 90%);
|
||||
}
|
||||
@@ -50,7 +50,7 @@
|
||||
|
||||
--tab-bar-height: 50px;
|
||||
--tab-height: 36px;
|
||||
--tab-first-item-horiz-offset: 1px;
|
||||
--tab-first-item-horiz-offset: 0;
|
||||
--new-tab-button-size: 24px;
|
||||
|
||||
--center-pane-border-radius: 10px;
|
||||
@@ -89,13 +89,13 @@
|
||||
* the color is adjusted based on the current color scheme (light or dark). The lightness
|
||||
* component of the color represented in the CIELAB color space, will be
|
||||
* constrained to a certain percentage defined below.
|
||||
*
|
||||
*
|
||||
* Note: the tree background may vary when background effects are enabled, so it is recommended
|
||||
* to maintain a higher contrast margin than on the usual note tree solid background. */
|
||||
|
||||
/* The maximum perceptual lightness for the custom color in the light theme (%): */
|
||||
--tree-item-light-theme-max-color-lightness: 60;
|
||||
|
||||
|
||||
/* The minimum perceptual lightness for the custom color in the dark theme (%): */
|
||||
--tree-item-dark-theme-min-color-lightness: 65;
|
||||
}
|
||||
@@ -165,17 +165,35 @@ body.desktop .dropdown-submenu .dropdown-menu {
|
||||
--menu-item-start-padding: 8px;
|
||||
--menu-item-end-padding: 22px;
|
||||
--menu-item-vertical-padding: 2px;
|
||||
|
||||
padding-top: var(--menu-item-vertical-padding) !important;
|
||||
padding-bottom: var(--menu-item-vertical-padding) !important;
|
||||
padding-inline-start: var(--menu-item-start-padding) !important;
|
||||
padding-inline-end: var(--menu-item-end-padding) !important;
|
||||
|
||||
/* Note: the right padding should also accommodate the submenu arrow. */
|
||||
border-radius: 6px;
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
.dropdown-item:not(.dropdown-submenu),
|
||||
body.desktop .dropdown-item.dropdown-submenu .dropdown-toggle,
|
||||
.excalidraw .context-menu .context-menu-item {
|
||||
padding-top: var(--menu-item-vertical-padding) !important;
|
||||
padding-bottom: var(--menu-item-vertical-padding) !important;
|
||||
padding-inline-start: var(--menu-item-start-padding) !important;
|
||||
padding-inline-end: var(--menu-item-end-padding) !important;
|
||||
}
|
||||
|
||||
.dropdown-item.dropdown-submenu {
|
||||
padding: 0 !important;
|
||||
|
||||
.dropdown-toggle {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
body.desktop .dropdown-menu:has(> .dropdown-submenu.dropstart) > .dropdown-item:not(.dropdown-submenu),
|
||||
body.desktop .dropdown-menu:has(> .dropdown-submenu.dropstart) > .dropdown-item.dropdown-submenu .dropdown-toggle {
|
||||
padding-inline-end: var(--menu-item-start-padding) !important;
|
||||
padding-inline-start: var(--menu-item-end-padding) !important;
|
||||
}
|
||||
|
||||
:root .dropdown-item:focus-visible {
|
||||
outline: 2px solid var(--input-focus-outline-color) !important;
|
||||
background-color: transparent;
|
||||
@@ -249,7 +267,8 @@ html body .dropdown-item[disabled] {
|
||||
}
|
||||
|
||||
/* Menu item arrow */
|
||||
.dropdown-menu .dropdown-toggle::after {
|
||||
body.mobile .dropdown-submenu .dropdown-toggle::after,
|
||||
body.desktop .dropdown-submenu:not(.dropstart) .dropdown-toggle::after {
|
||||
content: "\ed3b" !important;
|
||||
position: absolute;
|
||||
display: flex !important;
|
||||
@@ -265,6 +284,26 @@ html body .dropdown-item[disabled] {
|
||||
color: var(--menu-item-arrow-color) !important;
|
||||
}
|
||||
|
||||
body.mobile .dropdown-submenu.dropstart .dropdown-toggle::before {
|
||||
content: unset;
|
||||
}
|
||||
|
||||
body.desktop .dropdown-submenu.dropstart .dropdown-toggle::before {
|
||||
content: "\ea4d" !important;
|
||||
position: absolute;
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
top: 0;
|
||||
inset-inline-start: 0;
|
||||
margin: unset !important;
|
||||
border: unset !important;
|
||||
padding: 0 4px;
|
||||
font-family: boxicons;
|
||||
font-size: 1.2em;
|
||||
color: var(--menu-item-arrow-color) !important;
|
||||
}
|
||||
|
||||
body[dir=rtl] .dropdown-menu:not([data-popper-placement="bottom-start"]) .dropdown-toggle::after {
|
||||
content: "\ea4d" !important;
|
||||
}
|
||||
@@ -339,7 +378,7 @@ body.mobile .dropdown-menu {
|
||||
font-size: 1em !important;
|
||||
backdrop-filter: var(--dropdown-backdrop-filter);
|
||||
position: relative;
|
||||
|
||||
|
||||
.dropdown-toggle::after {
|
||||
top: 0.5em;
|
||||
right: var(--dropdown-menu-padding-horizontal);
|
||||
@@ -356,7 +395,7 @@ body.mobile .dropdown-menu {
|
||||
padding: var(--dropdown-menu-padding-vertical) var(--dropdown-menu-padding-horizontal) !important;
|
||||
background: var(--card-background-color);
|
||||
border-bottom: 1px solid var(--menu-item-delimiter-color) !important;
|
||||
border-radius: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.dropdown-item:first-of-type,
|
||||
@@ -367,9 +406,9 @@ body.mobile .dropdown-menu {
|
||||
border-top-right-radius: 6px;
|
||||
}
|
||||
|
||||
.dropdown-item:last-of-type,
|
||||
.dropdown-item:last-of-type,
|
||||
.dropdown-item:has(+ .dropdown-divider),
|
||||
.dropdown-custom-item:last-of-type,
|
||||
.dropdown-custom-item:last-of-type,
|
||||
.dropdown-custom-item:has(+ .dropdown-divider) {
|
||||
border-bottom-left-radius: 6px;
|
||||
border-bottom-right-radius: 6px;
|
||||
@@ -392,10 +431,10 @@ body.mobile .dropdown-menu {
|
||||
--menu-background-color: --menu-submenu-mobile-background-color;
|
||||
--bs-dropdown-divider-margin-y: 0.25rem;
|
||||
border-radius: 0;
|
||||
max-height: 0;
|
||||
max-height: 0;
|
||||
transition: max-height 100ms ease-in;
|
||||
display: block !important;
|
||||
|
||||
display: block !important;
|
||||
|
||||
&.show {
|
||||
max-height: 1000px;
|
||||
padding: 0.5rem 0.75rem !important;
|
||||
@@ -405,7 +444,7 @@ body.mobile .dropdown-menu {
|
||||
&.submenu-open {
|
||||
.dropdown-toggle {
|
||||
padding-bottom: var(--dropdown-menu-padding-vertical);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -743,4 +782,4 @@ li.dropdown-item a.dropdown-item-button:focus-visible {
|
||||
.note-detail-empty .aa-suggestions div.aa-cursor {
|
||||
background: var(--hover-item-background-color);
|
||||
color: var(--hover-item-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,8 @@ button.btn.btn-primary:focus-visible,
|
||||
button.btn.btn-secondary:focus-visible,
|
||||
button.btn.btn-sm:not(.select-button):focus-visible,
|
||||
button.btn.btn-success:focus-visible,
|
||||
button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .ck-button-replaceall, .ck-button-replace).ck-button_with-text:not(.ck-disabled):focus-visible {
|
||||
button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .ck-button-replaceall, .ck-button-replace).ck-button_with-text:not(.ck-disabled):focus-visible,
|
||||
.tn-focusable-button:focus-visible {
|
||||
outline: 2px solid var(--input-focus-outline-color);
|
||||
}
|
||||
|
||||
@@ -154,7 +155,7 @@ button.btn.btn-success kbd {
|
||||
color: var(--button-group-active-button-text-color);
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* Input boxes
|
||||
*/
|
||||
|
||||
@@ -399,7 +400,8 @@ button.select-button.dropdown-toggle.btn:active {
|
||||
select:focus,
|
||||
select.form-select:focus,
|
||||
select.form-control:focus,
|
||||
.select-button.dropdown-toggle.btn:focus {
|
||||
.select-button.dropdown-toggle.btn:focus,
|
||||
.select-button.focus-outline:focus {
|
||||
box-shadow: unset;
|
||||
outline: 3px solid var(--input-focus-outline-color);
|
||||
outline-offset: 0;
|
||||
@@ -422,7 +424,7 @@ optgroup {
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* File input
|
||||
*
|
||||
* <label class="tn-file-input tn-input-field">
|
||||
@@ -784,4 +786,4 @@ input[type="range"] {
|
||||
scrollbar-color: unset;
|
||||
scrollbar-width: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
--ck-color-button-on-background: transparent;
|
||||
--ck-color-button-on-hover-background: var(--hover-item-background-color);
|
||||
--ck-color-button-default-active-background: var(--hover-item-background-color);
|
||||
|
||||
|
||||
--ck-color-split-button-hover-background: var(--ck-editor-toolbar-dropdown-button-open-background);
|
||||
|
||||
--ck-focus-ring: 1px solid transparent;
|
||||
@@ -77,7 +77,7 @@
|
||||
visibility: collapse;
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* Dropdowns
|
||||
*/
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
:root .ck.ck-dropdown__panel,
|
||||
:root .ck-balloon-panel {
|
||||
--ck-editor-popup-padding: 4px;
|
||||
|
||||
|
||||
--ck-color-panel-background: var(--menu-background-color);
|
||||
--ck-color-panel-border: var(--ck-editor-popup-border-color);
|
||||
|
||||
@@ -487,7 +487,7 @@ button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel).ck
|
||||
.ck.ck-labeled-field-view > .ck.ck-labeled-field-view__input-wrapper > label.ck.ck-label {
|
||||
/* Move the label above the text box regardless of the text box state */
|
||||
transform: translate(0, calc(-.2em - var(--ck-input-label-height))) !important;
|
||||
|
||||
|
||||
padding-inline-start: 0 !important;
|
||||
background: transparent;
|
||||
font-size: .85em;
|
||||
@@ -518,7 +518,7 @@ button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel).ck
|
||||
*/
|
||||
|
||||
/*
|
||||
* Code Blocks
|
||||
* Code Blocks
|
||||
*/
|
||||
|
||||
.attachment-content-wrapper pre,
|
||||
@@ -526,10 +526,14 @@ button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel).ck
|
||||
.ck-mermaid__editing-view {
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
box-shadow: var(--code-block-box-shadow);
|
||||
box-shadow: var(--code-block-box-shadow);
|
||||
margin-top: 2px !important;
|
||||
}
|
||||
|
||||
.attachment-content-wrapper pre {
|
||||
border-radius: var(--dropdown-border-radius);
|
||||
}
|
||||
|
||||
:root .ck-content pre:has(> code) {
|
||||
padding: 0;
|
||||
}
|
||||
@@ -542,7 +546,7 @@ button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel).ck
|
||||
* for single-line code blocks */
|
||||
|
||||
--copy-button-margin-size: calc((1em * 1.5 + var(--padding-size) * 2 - var(--icon-button-size)) / 2);
|
||||
|
||||
|
||||
/* Where: │ └ Line height
|
||||
* └───────── Font size
|
||||
*/
|
||||
@@ -690,4 +694,4 @@ html .note-detail-editable-text :not(figure, .include-note, hr):first-child {
|
||||
|
||||
.note-list-widget {
|
||||
outline: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,25 +168,7 @@ ul.editability-dropdown li.dropdown-item > div {
|
||||
* Note info
|
||||
*/
|
||||
|
||||
:root .note-info-widget-table button.calculate-button {
|
||||
min-width: 0;
|
||||
padding: 4px 10px !important;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
/* Narrow width layout */
|
||||
.note-info-widget {
|
||||
container: info-section / inline-size;
|
||||
}
|
||||
|
||||
/*
|
||||
* Styling as a floating toolbar
|
||||
*/
|
||||
.ribbon-container {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: var(--main-background-color);
|
||||
z-index: 997;
|
||||
}
|
||||
|
||||
@@ -1029,7 +1029,7 @@ body.layout-vertical.electron.platform-darwin .tab-row-container {
|
||||
}
|
||||
|
||||
body.layout-horizontal .tab-row-container {
|
||||
padding-top: calc((var(--tab-bar-height) - var(--tab-height)));
|
||||
padding-top: calc(var(--tab-bar-height) - var(--tab-height));
|
||||
}
|
||||
|
||||
/* Define extra drag areas for Electron windows */
|
||||
@@ -1069,8 +1069,9 @@ body.desktop:not(.background-effects.platform-win32) #root-widget.horizontal-lay
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
|
||||
.tab-row-widget .note-tab .note-tab-wrapper {
|
||||
:root div.tab-row-widget div.note-tab div.note-tab-wrapper {
|
||||
height: var(--tab-height) !important;
|
||||
border-radius: 8px;
|
||||
transition:
|
||||
background 75ms ease-in,
|
||||
box-shadow 75ms ease-in;
|
||||
@@ -1221,13 +1222,38 @@ body.layout-vertical .tab-row-widget-is-sorting .note-tab.note-tab-is-dragging .
|
||||
top: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* CLASSIC FORMATTING TOOLBAR
|
||||
*/
|
||||
|
||||
#rest-pane > .classic-toolbar-widget {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
body.vertical-layout #rest-pane > .classic-toolbar-widget {
|
||||
border-start-start-radius: var(--center-pane-border-radius);
|
||||
}
|
||||
|
||||
body.layout-vertical #rest-pane > .classic-toolbar-widget {
|
||||
background: var(--classic-toolbar-vert-layout-background-color);
|
||||
}
|
||||
|
||||
body.layout-horizontal #rest-pane > .classic-toolbar-widget {
|
||||
background: var(--classic-toolbar-horiz-layout-background-color);
|
||||
}
|
||||
|
||||
.classic-toolbar-widget:not(.hidden-ext) + #vertical-main-container {
|
||||
/* Remove the center panel border radius when the toolbar is visible */
|
||||
--center-pane-border-radius: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* CENTER PANE
|
||||
*/
|
||||
|
||||
/* The first visible note split */
|
||||
.vertical-layout #center-pane .note-split:not(.visible ~ .visible) {
|
||||
border-radius: var(--center-pane-border-radius) 0 0 0;
|
||||
border-start-start-radius: var(--center-pane-border-radius);
|
||||
}
|
||||
|
||||
#center-pane .note-split {
|
||||
|
||||
@@ -160,6 +160,11 @@ span.fancytree-node.multiple-parents .fancytree-title::after {
|
||||
content: " \eb3d"; /* lookup code for "link-alt" in boxicons.css */
|
||||
}
|
||||
|
||||
body.experimental-feature-new-layout span.fancytree-node.multiple-parents .fancytree-title::after {
|
||||
content: " \ed82";
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
span.fancytree-node.shared .fancytree-title::after {
|
||||
font-family: "boxicons" !important;
|
||||
font-size: smaller;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import utils from "../services/utils.js";
|
||||
import { NoteType } from "@triliumnext/commons";
|
||||
|
||||
import FAttribute from "../entities/fattribute.js";
|
||||
import FBlob from "../entities/fblob.js";
|
||||
import FBranch from "../entities/fbranch.js";
|
||||
import FNote from "../entities/fnote.js";
|
||||
import froca from "../services/froca.js";
|
||||
import FAttribute from "../entities/fattribute.js";
|
||||
import noteAttributeCache from "../services/note_attribute_cache.js";
|
||||
import FBranch from "../entities/fbranch.js";
|
||||
import FBlob from "../entities/fblob.js";
|
||||
import utils from "../services/utils.js";
|
||||
|
||||
type AttributeDefinitions = { [key in `#${string}`]: string; };
|
||||
type RelationDefinitions = { [key in `~${string}`]: string; };
|
||||
@@ -12,6 +14,7 @@ type RelationDefinitions = { [key in `~${string}`]: string; };
|
||||
interface NoteDefinition extends AttributeDefinitions, RelationDefinitions {
|
||||
id?: string | undefined;
|
||||
title: string;
|
||||
type?: NoteType;
|
||||
children?: NoteDefinition[];
|
||||
content?: string;
|
||||
}
|
||||
@@ -45,7 +48,7 @@ export function buildNote(noteDef: NoteDefinition) {
|
||||
const note = new FNote(froca, {
|
||||
noteId: noteDef.id ?? utils.randomString(12),
|
||||
title: noteDef.title,
|
||||
type: "text",
|
||||
type: noteDef.type ?? "text",
|
||||
mime: "text/html",
|
||||
isProtected: false,
|
||||
blobId: ""
|
||||
|
||||
@@ -693,7 +693,13 @@
|
||||
"convert_into_attachment_successful": "笔记 '{{title}}' 已成功转换为附件。",
|
||||
"convert_into_attachment_prompt": "确定要将笔记 '{{title}}' 转换为父笔记的附件吗?",
|
||||
"print_pdf": "导出为 PDF...",
|
||||
"open_note_on_server": "在服务器上打开笔记"
|
||||
"open_note_on_server": "在服务器上打开笔记",
|
||||
"view_revisions": "笔记修订...",
|
||||
"note_map": "笔记地图",
|
||||
"advanced": "高级",
|
||||
"export_as_image": "导出为图像",
|
||||
"export_as_image_png": "PNG(栅格)",
|
||||
"export_as_image_svg": "SVG(矢量图)"
|
||||
},
|
||||
"onclick_button": {
|
||||
"no_click_handler": "按钮组件'{{componentId}}'没有定义点击处理程序"
|
||||
@@ -791,7 +797,7 @@
|
||||
"file_type": "文件类型",
|
||||
"file_size": "文件大小",
|
||||
"download": "下载",
|
||||
"open": "打开",
|
||||
"open": "用外部程序打开",
|
||||
"upload_new_revision": "上传新修订版本",
|
||||
"upload_success": "新文件修订版本已上传。",
|
||||
"upload_failed": "新文件修订版本上传失败。",
|
||||
@@ -822,7 +828,9 @@
|
||||
"note_size_info": "笔记大小提供了该笔记存储需求的粗略估计。它考虑了笔记的内容及其笔记修订历史的内容。",
|
||||
"calculate": "计算",
|
||||
"subtree_size": "(子树大小: {{size}}, 共计 {{count}} 个笔记)",
|
||||
"title": "笔记信息"
|
||||
"title": "笔记信息",
|
||||
"show_similar_notes": "显示相似的笔记",
|
||||
"mime": "文件类型"
|
||||
},
|
||||
"note_map": {
|
||||
"open_full": "展开显示",
|
||||
@@ -885,7 +893,8 @@
|
||||
"search_parameters": "搜索参数",
|
||||
"unknown_search_option": "未知的搜索选项 {{searchOptionName}}",
|
||||
"search_note_saved": "搜索笔记已保存到 {{- notePathTitle}}",
|
||||
"actions_executed": "操作已执行。"
|
||||
"actions_executed": "操作已执行。",
|
||||
"view_options": "查看选项:"
|
||||
},
|
||||
"similar_notes": {
|
||||
"title": "相似笔记",
|
||||
@@ -1577,7 +1586,14 @@
|
||||
"printing_pdf": "正在导出为PDF…"
|
||||
},
|
||||
"note_title": {
|
||||
"placeholder": "请输入笔记标题..."
|
||||
"placeholder": "请输入笔记标题...",
|
||||
"created_on": "建立于 <Value />",
|
||||
"last_modified": "修改于 <Value />",
|
||||
"note_type_switcher_label": "从 {{type}} 切换到:",
|
||||
"note_type_switcher_others": "其他笔记类型",
|
||||
"note_type_switcher_templates": "模板",
|
||||
"note_type_switcher_collection": "集合",
|
||||
"edited_notes": "编辑过的笔记"
|
||||
},
|
||||
"search_result": {
|
||||
"no_notes_found": "没有找到符合搜索条件的笔记。",
|
||||
@@ -1939,8 +1955,9 @@
|
||||
"unknown_widget": "未知组件:\"{{id}}\"."
|
||||
},
|
||||
"note_language": {
|
||||
"not_set": "不设置",
|
||||
"configure-languages": "设置语言..."
|
||||
"not_set": "未设置语言",
|
||||
"configure-languages": "设置语言...",
|
||||
"help-on-languages": "内容语言帮助..."
|
||||
},
|
||||
"content_language": {
|
||||
"title": "内容语言",
|
||||
@@ -2007,7 +2024,7 @@
|
||||
"book_properties_config": {
|
||||
"hide-weekends": "隐藏周末",
|
||||
"display-week-numbers": "显示周数",
|
||||
"map-style": "地图样式:",
|
||||
"map-style": "地图样式",
|
||||
"max-nesting-depth": "最大嵌套深度:",
|
||||
"raster": "栅格",
|
||||
"vector_light": "矢量(浅色)",
|
||||
@@ -2051,7 +2068,7 @@
|
||||
"configure_launch_bar_description": "打开启动栏配置,添加或移除项目。"
|
||||
},
|
||||
"content_renderer": {
|
||||
"open_externally": "在外部打开"
|
||||
"open_externally": "用外部程序打开"
|
||||
},
|
||||
"modal": {
|
||||
"close": "关闭",
|
||||
@@ -2064,14 +2081,20 @@
|
||||
"next_theme_title": "试用新 Trilium 主题",
|
||||
"next_theme_message": "当前使用旧版主题,要试用新主题吗?",
|
||||
"next_theme_button": "试用新主题",
|
||||
"dismiss": "关闭"
|
||||
"dismiss": "关闭",
|
||||
"new_layout_message": "我们为 Trilium 引入了现代化的布局。Ribbon 界面已被移除并无缝集成到主界面中,新的状态栏和可展开部分(例如“已提升属性”)取代了其主要功能。\n\n新布局默认启用,您可以通过“选项”→“外观”暂时禁用它。",
|
||||
"new_layout_button": "更多信息",
|
||||
"new_layout_title": "新布局"
|
||||
},
|
||||
"settings": {
|
||||
"related_settings": "相关设置"
|
||||
},
|
||||
"settings_appearance": {
|
||||
"related_code_blocks": "文本笔记中代码块的色彩方案",
|
||||
"related_code_notes": "代码笔记的色彩方案"
|
||||
"related_code_notes": "代码笔记的色彩方案",
|
||||
"ui": "用户界面",
|
||||
"ui_old_layout": "旧布局",
|
||||
"ui_new_layout": "新布局"
|
||||
},
|
||||
"units": {
|
||||
"percentage": "%"
|
||||
@@ -2086,7 +2109,7 @@
|
||||
},
|
||||
"pagination": {
|
||||
"page_title": "第 {{startIndex}} 页 - 第 {{endIndex}} 页",
|
||||
"total_notes": "{{count}} 笔记"
|
||||
"total_notes": "{{count}} 篇笔记"
|
||||
},
|
||||
"collections": {
|
||||
"rendering_error": "出现错误无法显示内容。"
|
||||
@@ -2116,5 +2139,55 @@
|
||||
"unknown_http_error_title": "与服务器通讯错误",
|
||||
"unknown_http_error_content": "状态码: {{statusCode}}\n地址: {{method}} {{url}}\n信息: {{message}}",
|
||||
"traefik_blocks_requests": "如果您使用 Traefik 反向代理,它引入了一项影响与服务器的通信重大更改。"
|
||||
},
|
||||
"experimental_features": {
|
||||
"title": "实验选项",
|
||||
"disclaimer": "这些选项处于实验阶段,可能导致系统不稳定。请谨慎使用。",
|
||||
"new_layout_name": "新布局",
|
||||
"new_layout_description": "尝试全新布局,呈现更现代的外观并提升易用性。后续版本将进行重大调整。"
|
||||
},
|
||||
"tab_history_navigation_buttons": {
|
||||
"go-back": "返回前一笔记",
|
||||
"go-forward": "前往下一笔记"
|
||||
},
|
||||
"breadcrumb_badges": {
|
||||
"read_only_explicit": "只读",
|
||||
"read_only_auto": "自动只读",
|
||||
"shared_publicly": "公开共享",
|
||||
"shared_locally": "本地共享",
|
||||
"read_only_explicit_description": "此笔记已被手动设置为只读。\n点击可临时编辑。",
|
||||
"read_only_auto_description": "出于性能原因,此笔记已被自动设置为只读模式。此自动限制可以在设置中调整。\n\n点击可临时编辑。",
|
||||
"read_only_temporarily_disabled": "临时编辑",
|
||||
"read_only_temporarily_disabled_description": "此笔记当前可编辑,但通常是只读的。一旦你切换到其他笔记,该笔记将恢复为只读模式。\n\n点击以重新启用只读模式。",
|
||||
"clipped_note": "网页剪辑",
|
||||
"clipped_note_description": "此笔记最初来自 {{url}}。\n\n点击即可跳转至源网页。",
|
||||
"execute_script": "运行脚本",
|
||||
"execute_script_description": "这是一篇脚本笔记。点击即可执行脚本。",
|
||||
"execute_sql": "运行SQL",
|
||||
"execute_sql_description": "这是一篇 SQL 笔记。点击即可执行 SQL 查询。",
|
||||
"shared_copy_to_clipboard": "复制链接到剪贴板",
|
||||
"shared_open_in_browser": "在浏览器中打开链接",
|
||||
"shared_unshare": "取消共享"
|
||||
},
|
||||
"status_bar": {
|
||||
"language_title": "更改内容语言",
|
||||
"note_info_title": "查看笔记信息(例如日期,笔记大小)",
|
||||
"backlinks_title_other": "查看反链",
|
||||
"attachments_title_other": "在新标签页中查看附件",
|
||||
"attributes_other": "{{count}} 个属性",
|
||||
"attributes_title": "拥有的属性和继承的属性",
|
||||
"note_paths_title": "笔记路径",
|
||||
"code_note_switcher": "更改语言模式",
|
||||
"backlinks_other": "{{count}} 个反链",
|
||||
"attachments_other": "{{count}} 个附件",
|
||||
"note_paths_other": "{{count}} 条路径"
|
||||
},
|
||||
"breadcrumb": {
|
||||
"workspace_badge": "工作空间",
|
||||
"scroll_to_top_title": "跳转到笔记开始",
|
||||
"hoisted_badge_title": "取消聚焦",
|
||||
"hoisted_badge": "聚焦",
|
||||
"create_new_note": "新建子笔记",
|
||||
"empty_hide_archived_notes": "隐藏已存档的笔记"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +108,11 @@
|
||||
"cloned_note_prefix_title": "Klonovaná poznámka se zobrazí ve stromu poznámek s danou předponou",
|
||||
"clone_to_selected_note": "Klonovat vybranou poznámku",
|
||||
"no_path_to_clone_to": "Žádná cest pro klonování.",
|
||||
"note_cloned": "Poznámka: „{{clonedTitle}}“ bylo naklonováno do „{{targetTitle}}“"
|
||||
"note_cloned": "Poznámka „{{clonedTitle}}“ bylo naklonována do „{{targetTitle}}“"
|
||||
},
|
||||
"zpetne_odkazy": {
|
||||
"backlink_one": "{{count}} zpětný odkaz",
|
||||
"backlink_few": "{{count}} zpětné odkazy",
|
||||
"backlink_other": "{{count}} zpětných odkazů"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,15 @@
|
||||
"bundle-error": {
|
||||
"title": "Failed to load a custom script",
|
||||
"message": "Script from note with ID \"{{id}}\", titled \"{{title}}\" could not be executed due to:\n\n{{message}}"
|
||||
}
|
||||
},
|
||||
"widget-list-error": {
|
||||
"title": "Failed to obtain the list of widgets from the server"
|
||||
},
|
||||
"widget-render-error": {
|
||||
"title": "Failed to render a custom React widget"
|
||||
},
|
||||
"widget-missing-parent": "Custom widget does not have mandatory '{{property}}' property defined.",
|
||||
"open-script-note": "Open script note"
|
||||
},
|
||||
"add_link": {
|
||||
"add_link": "Add link",
|
||||
@@ -689,11 +697,17 @@
|
||||
"export_note": "Export note",
|
||||
"delete_note": "Delete note",
|
||||
"print_note": "Print note",
|
||||
"view_revisions": "Note revisions...",
|
||||
"save_revision": "Save revision",
|
||||
"advanced": "Advanced",
|
||||
"convert_into_attachment_failed": "Converting note '{{title}}' failed.",
|
||||
"convert_into_attachment_successful": "Note '{{title}}' has been converted to attachment.",
|
||||
"convert_into_attachment_prompt": "Are you sure you want to convert note '{{title}}' into an attachment of the parent note?",
|
||||
"print_pdf": "Export as PDF..."
|
||||
"print_pdf": "Export as PDF...",
|
||||
"export_as_image": "Export as image",
|
||||
"export_as_image_png": "PNG (raster)",
|
||||
"export_as_image_svg": "SVG (vector)",
|
||||
"note_map": "Note map"
|
||||
},
|
||||
"onclick_button": {
|
||||
"no_click_handler": "Button widget '{{componentId}}' has no defined click handler"
|
||||
@@ -792,7 +806,7 @@
|
||||
"file_type": "File type",
|
||||
"file_size": "File size",
|
||||
"download": "Download",
|
||||
"open": "Open",
|
||||
"open": "Open externally",
|
||||
"upload_new_revision": "Upload new revision",
|
||||
"upload_success": "New file revision has been uploaded.",
|
||||
"upload_failed": "Upload of a new file revision failed.",
|
||||
@@ -819,11 +833,13 @@
|
||||
"created": "Created",
|
||||
"modified": "Modified",
|
||||
"type": "Type",
|
||||
"mime": "MIME type",
|
||||
"note_size": "Note size",
|
||||
"note_size_info": "Note size provides rough estimate of storage requirements for this note. It takes into account note's content and content of its note revisions.",
|
||||
"calculate": "calculate",
|
||||
"subtree_size": "(subtree size: {{size}} in {{count}} notes)",
|
||||
"title": "Note Info"
|
||||
"title": "Note Info",
|
||||
"show_similar_notes": "Show similar notes"
|
||||
},
|
||||
"note_map": {
|
||||
"open_full": "Expand to full",
|
||||
@@ -886,7 +902,8 @@
|
||||
"search_parameters": "Search Parameters",
|
||||
"unknown_search_option": "Unknown search option {{searchOptionName}}",
|
||||
"search_note_saved": "Search note has been saved into {{- notePathTitle}}",
|
||||
"actions_executed": "Actions have been executed."
|
||||
"actions_executed": "Actions have been executed.",
|
||||
"view_options": "View options:"
|
||||
},
|
||||
"similar_notes": {
|
||||
"title": "Similar Notes",
|
||||
@@ -1096,6 +1113,12 @@
|
||||
"vacuuming_database": "Vacuuming database...",
|
||||
"database_vacuumed": "Database has been vacuumed"
|
||||
},
|
||||
"experimental_features": {
|
||||
"title": "Experimental Options",
|
||||
"disclaimer": "These options are experimental and may cause instability. Use with caution.",
|
||||
"new_layout_name": "New Layout",
|
||||
"new_layout_description": "Try out the new layout for a more modern look and improved usability. Subject to heavy change in the upcoming releases."
|
||||
},
|
||||
"fonts": {
|
||||
"theme_defined": "Theme defined",
|
||||
"fonts": "Fonts",
|
||||
@@ -1708,7 +1731,12 @@
|
||||
},
|
||||
"highlights_list_2": {
|
||||
"title": "Highlights List",
|
||||
"options": "Options"
|
||||
"title_with_count_one": "{{count}} highlight",
|
||||
"title_with_count_other": "{{count}} highlights",
|
||||
"options": "Options",
|
||||
"modal_title": "Configure Highlights List",
|
||||
"menu_configure": "Configure highlights list...",
|
||||
"no_highlights": "No highlights found."
|
||||
},
|
||||
"quick-search": {
|
||||
"placeholder": "Quick search",
|
||||
@@ -1743,7 +1771,14 @@
|
||||
"printing_pdf": "Exporting to PDF in progress..."
|
||||
},
|
||||
"note_title": {
|
||||
"placeholder": "type note's title here..."
|
||||
"placeholder": "type note's title here...",
|
||||
"created_on": "Created on <Value />",
|
||||
"last_modified": "Modified on <Value />",
|
||||
"note_type_switcher_label": "Switch from {{type}} to:",
|
||||
"note_type_switcher_others": "Other note type",
|
||||
"note_type_switcher_templates": "Template",
|
||||
"note_type_switcher_collection": "Collection",
|
||||
"edited_notes": "Edited notes"
|
||||
},
|
||||
"search_result": {
|
||||
"no_notes_found": "No notes have been found for given search parameters.",
|
||||
@@ -1772,7 +1807,8 @@
|
||||
},
|
||||
"toc": {
|
||||
"table_of_contents": "Table of Contents",
|
||||
"options": "Options"
|
||||
"options": "Options",
|
||||
"no_headings": "No headings."
|
||||
},
|
||||
"watched_file_update_status": {
|
||||
"file_last_modified": "File <code class=\"file-path\"></code> has been last modified on <span class=\"file-last-modified\"></span>.",
|
||||
@@ -1953,8 +1989,9 @@
|
||||
"unknown_widget": "Unknown widget for \"{{id}}\"."
|
||||
},
|
||||
"note_language": {
|
||||
"not_set": "Not set",
|
||||
"configure-languages": "Configure languages..."
|
||||
"not_set": "No language set",
|
||||
"configure-languages": "Configure languages...",
|
||||
"help-on-languages": "Help on content languages..."
|
||||
},
|
||||
"content_language": {
|
||||
"title": "Content languages",
|
||||
@@ -2021,7 +2058,7 @@
|
||||
"book_properties_config": {
|
||||
"hide-weekends": "Hide weekends",
|
||||
"display-week-numbers": "Display week numbers",
|
||||
"map-style": "Map style:",
|
||||
"map-style": "Map style",
|
||||
"max-nesting-depth": "Max nesting depth:",
|
||||
"raster": "Raster",
|
||||
"vector_light": "Vector (Light)",
|
||||
@@ -2086,6 +2123,9 @@
|
||||
"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",
|
||||
"new_layout_title": "New layout",
|
||||
"new_layout_message": "We’ve introduced a modernized layout for Trilium. The ribbon has been removed and seamlessly integrated into the main interface, with a new status bar and expandable sections (such as promoted attributes) taking over key functions.\n\nThe new layout is enabled by default, and can be temporarily disabled via Options → Appearance.",
|
||||
"new_layout_button": "More info",
|
||||
"dismiss": "Dismiss"
|
||||
},
|
||||
"settings": {
|
||||
@@ -2093,7 +2133,10 @@
|
||||
},
|
||||
"settings_appearance": {
|
||||
"related_code_blocks": "Color scheme for code blocks in text notes",
|
||||
"related_code_notes": "Color scheme for code notes"
|
||||
"related_code_notes": "Color scheme for code notes",
|
||||
"ui": "User interface",
|
||||
"ui_old_layout": "Old layout",
|
||||
"ui_new_layout": "New layout"
|
||||
},
|
||||
"units": {
|
||||
"percentage": "%"
|
||||
@@ -2121,5 +2164,57 @@
|
||||
"tab_history_navigation_buttons": {
|
||||
"go-back": "Go back to previous note",
|
||||
"go-forward": "Go forward to next note"
|
||||
},
|
||||
"breadcrumb": {
|
||||
"hoisted_badge": "Hoisted",
|
||||
"hoisted_badge_title": "Unhoist",
|
||||
"workspace_badge": "Workspace",
|
||||
"scroll_to_top_title": "Jump to the beginning of the note",
|
||||
"create_new_note": "Create new child note",
|
||||
"empty_hide_archived_notes": "Hide archived notes"
|
||||
},
|
||||
"breadcrumb_badges": {
|
||||
"read_only_explicit": "Read-only",
|
||||
"read_only_explicit_description": "This note has been manually set to read-only.\nClick to edit it temporarily.",
|
||||
"read_only_auto": "Auto read-only",
|
||||
"read_only_auto_description": "This note was set automatically to read-only mode for performance reasons. This automatic limit is adjustable from settings.\n\nClick to edit it temporarily.",
|
||||
"read_only_temporarily_disabled": "Temporarily editable",
|
||||
"read_only_temporarily_disabled_description": "This note is currently editable, but it is normally read-only. The note will go back to being read-only as soon as you navigate to another note.\n\nClick to re-enable read-only mode.",
|
||||
"shared_publicly": "Shared publicly",
|
||||
"shared_locally": "Shared locally",
|
||||
"shared_copy_to_clipboard": "Copy link to clipboard",
|
||||
"shared_open_in_browser": "Open link in browser",
|
||||
"shared_unshare": "Remove share",
|
||||
"clipped_note": "Web clip",
|
||||
"clipped_note_description": "This note was originally taken from {{url}}.\n\nClick to navigate to the source webpage.",
|
||||
"execute_script": "Run script",
|
||||
"execute_script_description": "This note is a script note. Click to execute the script.",
|
||||
"execute_sql": "Run SQL",
|
||||
"execute_sql_description": "This note is a SQL note. Click to execute the SQL query."
|
||||
},
|
||||
"status_bar": {
|
||||
"language_title": "Change content language",
|
||||
"note_info_title": "View note info (e.g., dates, note size)",
|
||||
"backlinks_one": "{{count}} backlink",
|
||||
"backlinks_other": "{{count}} backlinks",
|
||||
"backlinks_title_one": "View backlink",
|
||||
"backlinks_title_other": "View backlinks",
|
||||
"attachments_one": "{{count}} attachment",
|
||||
"attachments_other": "{{count}} attachments",
|
||||
"attachments_title_one": "View attachment in a new tab",
|
||||
"attachments_title_other": "View attachments in a new tab",
|
||||
"attributes_one": "{{count}} attribute",
|
||||
"attributes_other": "{{count}} attributes",
|
||||
"attributes_title": "Owned attributes and inherited attributes",
|
||||
"note_paths_one": "{{count}} path",
|
||||
"note_paths_other": "{{count}} paths",
|
||||
"note_paths_title": "Note paths",
|
||||
"code_note_switcher": "Change language mode"
|
||||
},
|
||||
"right_pane": {
|
||||
"empty_message": "Nothing to show for this note",
|
||||
"empty_button": "Hide the panel",
|
||||
"toggle": "Toggle right panel",
|
||||
"custom_widget_go_to_source": "Go to source code"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,8 @@
|
||||
"export_status": "Statut d'exportation",
|
||||
"export_in_progress": "Exportation en cours : {{progressCount}}",
|
||||
"export_finished_successfully": "L'exportation s'est terminée avec succès.",
|
||||
"format_pdf": "PDF - pour l'impression ou le partage de documents."
|
||||
"format_pdf": "PDF - pour l'impression ou le partage de documents.",
|
||||
"share-format": "HTML pour la publication Web - utilise le même thème que celui utilisé pour les notes partagées, mais peut être publié sous forme de site Web statique."
|
||||
},
|
||||
"help": {
|
||||
"noteNavigation": "Navigation dans les notes",
|
||||
@@ -161,7 +162,8 @@
|
||||
"quickSearch": "aller à la recherche rapide",
|
||||
"inPageSearch": "recherche sur la page",
|
||||
"title": "Aide-mémoire",
|
||||
"newTabWithActivationNoteLink": "Lorsqu’on clique sur un lien de note, celle-ci s’ouvre et devient active dans un nouvel onglet"
|
||||
"newTabWithActivationNoteLink": "Lorsqu’on clique sur un lien de note, celle-ci s’ouvre et devient active dans un nouvel onglet",
|
||||
"editShortcuts": "Modifier les raccourcis clavier"
|
||||
},
|
||||
"import": {
|
||||
"importIntoNote": "Importer dans la note",
|
||||
@@ -203,7 +205,8 @@
|
||||
"info": {
|
||||
"modalTitle": "Message d'information",
|
||||
"closeButton": "Fermer",
|
||||
"okButton": "OK"
|
||||
"okButton": "OK",
|
||||
"copy_to_clipboard": "Copier dans le presse-papiers"
|
||||
},
|
||||
"jump_to_note": {
|
||||
"search_button": "Rechercher dans le texte intégral",
|
||||
@@ -689,7 +692,13 @@
|
||||
"convert_into_attachment_failed": "La conversion de la note '{{title}}' a échoué.",
|
||||
"convert_into_attachment_successful": "La note '{{title}}' a été convertie en pièce jointe.",
|
||||
"convert_into_attachment_prompt": "Êtes-vous sûr de vouloir convertir la note '{{title}}' en une pièce jointe de la note parente ?",
|
||||
"print_pdf": "Exporter en PDF..."
|
||||
"print_pdf": "Exporter en PDF...",
|
||||
"open_note_on_server": "Ouvrir la note sur le serveur",
|
||||
"view_revisions": "Révisions...",
|
||||
"advanced": "Avancé",
|
||||
"export_as_image": "Exporter en tant qu'image",
|
||||
"export_as_image_png": "PNG",
|
||||
"export_as_image_svg": "SVG (vectoriel)"
|
||||
},
|
||||
"onclick_button": {
|
||||
"no_click_handler": "Le widget bouton '{{componentId}}' n'a pas de gestionnaire de clic défini"
|
||||
@@ -772,7 +781,11 @@
|
||||
"geo-map": "Carte géographique",
|
||||
"board": "Tableau de bord",
|
||||
"include_archived_notes": "Afficher les notes archivées",
|
||||
"presentation": "Présentation"
|
||||
"presentation": "Présentation",
|
||||
"expand_tooltip": "Développe les éléments enfants directs de cette collection (à un niveau). Pour plus d'options, appuyez sur la flèche à droite.",
|
||||
"expand_first_level": "Développer les enfants directs",
|
||||
"expand_nth_level": "Développer sur {{depth}} niveaux",
|
||||
"expand_all_levels": "Développer tous les niveaux"
|
||||
},
|
||||
"edited_notes": {
|
||||
"no_edited_notes_found": "Aucune note modifiée ce jour-là...",
|
||||
@@ -816,7 +829,9 @@
|
||||
"note_size_info": "La taille de la note fournit une estimation approximative des besoins de stockage pour cette note. Il prend en compte le contenu de la note et de ses versions.",
|
||||
"calculate": "calculer",
|
||||
"subtree_size": "(taille du sous-arbre : {{size}} pour {{count}} notes)",
|
||||
"title": "Infos sur la Note"
|
||||
"title": "Infos sur la Note",
|
||||
"mime": "type MIME",
|
||||
"show_similar_notes": "Afficher des notes similaires"
|
||||
},
|
||||
"note_map": {
|
||||
"open_full": "Développer au maximum",
|
||||
@@ -879,7 +894,8 @@
|
||||
"search_parameters": "Paramètres de recherche",
|
||||
"unknown_search_option": "Option de recherche inconnue {{searchOptionName}}",
|
||||
"search_note_saved": "La note de recherche a été enregistrée dans {{- notePathTitle}}",
|
||||
"actions_executed": "Les actions ont été exécutées."
|
||||
"actions_executed": "Les actions ont été exécutées.",
|
||||
"view_options": "Afficher les options:"
|
||||
},
|
||||
"similar_notes": {
|
||||
"title": "Notes similaires",
|
||||
@@ -982,7 +998,13 @@
|
||||
},
|
||||
"editable_text": {
|
||||
"placeholder": "Saisir le contenu de votre note ici...",
|
||||
"auto-detect-language": "Détecté automatiquement"
|
||||
"auto-detect-language": "Détecté automatiquement",
|
||||
"editor_crashed_title": "L'éditeur de texte a cessé de fonctionner",
|
||||
"editor_crashed_content": "Votre contenu a été récupéré avec succès, mais certaines de vos modifications les plus récentes n'ont peut-être pas été enregistrées.",
|
||||
"editor_crashed_details_button": "Afficher plus de détails...",
|
||||
"editor_crashed_details_intro": "Si cette erreur se produit plusieurs fois, pensez à la signaler sur GitHub en collant les informations ci-dessous.",
|
||||
"editor_crashed_details_title": "Informations techniques",
|
||||
"keeps-crashing": "Le composant d'édition cesse de fonctionner. Veuillez essayer de redémarrer Trilium. Si le problème persiste, envisager de créer un rapport de bogue."
|
||||
},
|
||||
"empty": {
|
||||
"open_note_instruction": "Ouvrez une note en tapant son titre dans la zone ci-dessous ou choisissez une note dans l'arborescence.",
|
||||
@@ -1077,9 +1099,9 @@
|
||||
"failed": "Échec de la synchronisation : {{message}}"
|
||||
},
|
||||
"vacuum_database": {
|
||||
"title": "Nettoyage la base de donnée",
|
||||
"title": "Nettoyage de la base de données",
|
||||
"description": "Cela reconstruira la base de données, ce qui générera un fichier de base de données généralement plus petit. Aucune donnée ne sera réellement modifiée.",
|
||||
"button_text": "Nettoyer de la base de donnée",
|
||||
"button_text": "Nettoyer la base de données",
|
||||
"vacuuming_database": "Nettoyage de la base de données en cours...",
|
||||
"database_vacuumed": "La base de données a été nettoyée"
|
||||
},
|
||||
@@ -1110,7 +1132,8 @@
|
||||
"title": "Largeur du contenu",
|
||||
"default_description": "Trilium limite par défaut la largeur maximale du contenu pour améliorer la lisibilité sur des écrans larges.",
|
||||
"max_width_label": "Largeur maximale du contenu en pixels",
|
||||
"max_width_unit": "Pixels"
|
||||
"max_width_unit": "Pixels",
|
||||
"centerContent": "Garder le contenu centré"
|
||||
},
|
||||
"native_title_bar": {
|
||||
"title": "Barre de titre native (nécessite le redémarrage de l'application)",
|
||||
@@ -1149,7 +1172,10 @@
|
||||
"unit": "caractères"
|
||||
},
|
||||
"code_mime_types": {
|
||||
"title": "Types MIME disponibles dans la liste déroulante"
|
||||
"title": "Types MIME disponibles dans la liste déroulante",
|
||||
"tooltip_syntax_highlighting": "Souligner la syntaxe",
|
||||
"tooltip_code_block_syntax": "Blocs de code dans les notes de texte",
|
||||
"tooltip_code_note_syntax": "Notes de code"
|
||||
},
|
||||
"vim_key_bindings": {
|
||||
"use_vim_keybindings_in_code_notes": "Raccourcis clavier Vim",
|
||||
@@ -2074,5 +2100,15 @@
|
||||
"note_completion_description": "Si cette option est activée, des liens vers des notes peuvent être créés en tapant `@` suivi du titre d'une note.",
|
||||
"slash_commands_enabled": "Activer les commandes slash",
|
||||
"slash_commands_description": "Si cette option est activée, les commandes d'édition telles que l'insertion de sauts de ligne ou d'en-têtes peuvent être activées en tapant `/`."
|
||||
},
|
||||
"experimental_features": {
|
||||
"title": "Options expérimentales",
|
||||
"disclaimer": "Ces options sont expérimentales et peuvent provoquer une instabilité. Utilisez avec prudence.",
|
||||
"new_layout_name": "Nouvelle mise en page",
|
||||
"new_layout_description": "Essayez la nouvelle mise en page pour un look plus moderne et un usage améliorée. Sous réserve de changements importants dans les prochaines versions."
|
||||
},
|
||||
"read-only-info": {
|
||||
"read-only-note": "Vous consultez actuellement une note en lecture seule.",
|
||||
"auto-read-only-note": "Cette note s'affiche en mode lecture seule pour un chargement plus rapide."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,8 @@
|
||||
"info": {
|
||||
"okButton": "OK",
|
||||
"closeButton": "Chiudi",
|
||||
"modalTitle": "Messaggio informativo"
|
||||
"modalTitle": "Messaggio informativo",
|
||||
"copy_to_clipboard": "Copia negli appunti"
|
||||
},
|
||||
"export": {
|
||||
"close": "Chiudi",
|
||||
@@ -314,7 +315,7 @@
|
||||
"import-into-note": "Importa nella nota",
|
||||
"apply-bulk-actions": "Applica azioni in blocco",
|
||||
"converted-to-attachments": "{{count}} note sono state convertite in allegati.",
|
||||
"convert-to-attachment-confirm": "Sei sicuro di voler convertire le note selezionate in allegati delle note padre?",
|
||||
"convert-to-attachment-confirm": "Sei sicuro di voler convertire le note selezionate in allegati delle note principali? Questa operazione si applica solo alle note immagine, le altre note verranno ignorate.",
|
||||
"open-in-popup": "Modifica rapida"
|
||||
},
|
||||
"electron_context_menu": {
|
||||
@@ -408,7 +409,8 @@
|
||||
"search_parameters": "Parametri di ricerca",
|
||||
"unknown_search_option": "Opzione di ricerca sconosciuta {{searchOptionName}}",
|
||||
"search_note_saved": "La nota di ricerca è stata salvata in {{- notePathTitle}}",
|
||||
"actions_executed": "Le azioni sono state eseguite."
|
||||
"actions_executed": "Le azioni sono state eseguite.",
|
||||
"view_options": "Opzioni di visualizzazione:"
|
||||
},
|
||||
"modal": {
|
||||
"close": "Chiudi",
|
||||
@@ -1260,7 +1262,13 @@
|
||||
"convert_into_attachment_successful": "Nota '{{title}}' è stato convertito in allegato.",
|
||||
"convert_into_attachment_prompt": "Sei sicuro di voler convertire la nota '{{title}}' in un allegato della nota padre?",
|
||||
"print_pdf": "Esporta come PDF...",
|
||||
"open_note_on_server": "Apri una nota sul server"
|
||||
"open_note_on_server": "Apri una nota sul server",
|
||||
"view_revisions": "Revisioni...",
|
||||
"advanced": "Avanzato",
|
||||
"export_as_image": "Esporta come immagine",
|
||||
"export_as_image_png": "PNG (raster)",
|
||||
"export_as_image_svg": "SVG (vector)",
|
||||
"note_map": "Mappa"
|
||||
},
|
||||
"onclick_button": {
|
||||
"no_click_handler": "Il widget pulsante '{{componentId}}' non ha un gestore di clic definito"
|
||||
@@ -1360,7 +1368,7 @@
|
||||
"file_type": "Tipo di file",
|
||||
"file_size": "Dimensione del file",
|
||||
"download": "Scaricamento",
|
||||
"open": "Aprire",
|
||||
"open": "Aprire esternamente",
|
||||
"upload_new_revision": "Carica nuova revisione",
|
||||
"upload_success": "È stata caricata una nuova revisione del file.",
|
||||
"upload_failed": "Caricamento di una nuova revisione del file non riuscito.",
|
||||
@@ -1391,7 +1399,8 @@
|
||||
"note_size_info": "La dimensione della nota fornisce una stima approssimativa dei requisiti di archiviazione per questa nota. Tiene conto del contenuto della nota e del contenuto delle sue revisioni.",
|
||||
"calculate": "calcolare",
|
||||
"subtree_size": "(dimensione del sottoalbero: {{size}} in {{count}} note)",
|
||||
"title": "Nota informativa"
|
||||
"title": "Nota informativa",
|
||||
"show_similar_notes": "Mostra note simili"
|
||||
},
|
||||
"note_map": {
|
||||
"open_full": "Espandi completamente",
|
||||
@@ -1493,7 +1502,12 @@
|
||||
"editable_text": {
|
||||
"placeholder": "Digita qui il contenuto della tua nota...",
|
||||
"auto-detect-language": "Rilevato automaticamente",
|
||||
"keeps-crashing": "Il componente di modifica continua a bloccarsi. Prova a riavviare Trilium. Se il problema persiste, valuta la possibilità di creare una segnalazione di bug."
|
||||
"keeps-crashing": "Il componente di modifica continua a bloccarsi. Prova a riavviare Trilium. Se il problema persiste, valuta la possibilità di creare una segnalazione di bug.",
|
||||
"editor_crashed_title": "L'editor di testo si è bloccato",
|
||||
"editor_crashed_content": "I tuoi contenuti sono stati recuperati con successo, ma alcune delle modifiche più recenti potrebbero non essere state salvate.",
|
||||
"editor_crashed_details_button": "Visualizza ulteriori dettagli...",
|
||||
"editor_crashed_details_intro": "Se questo errore si verifica più volte, valuta la possibilità di segnalarlo su GitHub incollando le informazioni riportate di seguito.",
|
||||
"editor_crashed_details_title": "Informazioni tecniche"
|
||||
},
|
||||
"empty": {
|
||||
"open_note_instruction": "Apri una nota digitandone il titolo nel campo sottostante oppure scegli una nota nell'albero.",
|
||||
@@ -1867,7 +1881,14 @@
|
||||
"printing_pdf": "Esportazione in PDF in corso..."
|
||||
},
|
||||
"note_title": {
|
||||
"placeholder": "scrivi qui il titolo della nota..."
|
||||
"placeholder": "scrivi qui il titolo della nota...",
|
||||
"created_on": "Creato il <Value />",
|
||||
"last_modified": "Modificato il <Value />",
|
||||
"note_type_switcher_label": "Passa da {{type}} a:",
|
||||
"note_type_switcher_others": "Altro tipo di nota",
|
||||
"note_type_switcher_templates": "Modello",
|
||||
"note_type_switcher_collection": "Collezione",
|
||||
"edited_notes": "Note modificate"
|
||||
},
|
||||
"search_result": {
|
||||
"no_notes_found": "Non sono state trovate note per i parametri di ricerca specificati.",
|
||||
@@ -2003,8 +2024,9 @@
|
||||
"unknown_widget": "Widget sconosciuto per \"{{id}}\"."
|
||||
},
|
||||
"note_language": {
|
||||
"not_set": "Non impostato",
|
||||
"configure-languages": "Configura le lingue..."
|
||||
"not_set": "Nessuna lingua impostata",
|
||||
"configure-languages": "Configura le lingue...",
|
||||
"help-on-languages": "Aiuto sulle lingue dei contenuti..."
|
||||
},
|
||||
"content_language": {
|
||||
"title": "Lingue dei contenuti",
|
||||
@@ -2022,7 +2044,8 @@
|
||||
"button_title": "Esporta diagramma come PNG"
|
||||
},
|
||||
"svg": {
|
||||
"export_to_png": "Non è stato possibile esportare il diagramma in formato PNG."
|
||||
"export_to_png": "Non è stato possibile esportare il diagramma in formato PNG.",
|
||||
"export_to_svg": "Il diagramma non può essere esportato in formato SVG."
|
||||
},
|
||||
"code_theme": {
|
||||
"title": "Aspetto",
|
||||
@@ -2032,7 +2055,7 @@
|
||||
"book_properties_config": {
|
||||
"hide-weekends": "Nascondi i fine settimana",
|
||||
"display-week-numbers": "Visualizza i numeri delle settimane",
|
||||
"map-style": "Stile mappa:",
|
||||
"map-style": "Stile mappa",
|
||||
"max-nesting-depth": "Profondità massima di nidificazione:",
|
||||
"raster": "Trama",
|
||||
"vector_light": "Vettore (Luce)",
|
||||
@@ -2106,5 +2129,67 @@
|
||||
},
|
||||
"popup-editor": {
|
||||
"maximize": "Passa all'editor completo"
|
||||
},
|
||||
"experimental_features": {
|
||||
"title": "Opzioni sperimentali",
|
||||
"disclaimer": "Queste opzioni sono sperimentali e potrebbero causare instabilità. Usare con cautela.",
|
||||
"new_layout_name": "Nuovo layout",
|
||||
"new_layout_description": "Prova il nuovo layout per un look più moderno e una maggiore usabilità. Soggetto a modifiche significative nelle prossime versioni."
|
||||
},
|
||||
"server": {
|
||||
"unknown_http_error_title": "Errore di comunicazione con il server",
|
||||
"unknown_http_error_content": "Codice di stato: {{statusCode}}\nURL: {{method}} {{url}}\nMessaggio: {{message}}",
|
||||
"traefik_blocks_requests": "Se si utilizza il proxy inverso Traefik, è stata introdotta una modifica sostanziale che influisce sulla comunicazione con il server."
|
||||
},
|
||||
"tab_history_navigation_buttons": {
|
||||
"go-back": "Torna alla nota precedente",
|
||||
"go-forward": "Passa alla nota successiva"
|
||||
},
|
||||
"breadcrumb_badges": {
|
||||
"read_only_explicit": "Sola lettura",
|
||||
"read_only_explicit_description": "Questa nota è stata impostata manualmente come di sola lettura.\nClicca per modificarla temporaneamente.",
|
||||
"read_only_auto": "Solo lettura automatica",
|
||||
"read_only_auto_description": "Questa nota è stata impostata automaticamente in modalità di sola lettura per motivi di prestazioni. Questo limite automatico è modificabile dalle impostazioni.\n\nClicca per modificarla temporaneamente.",
|
||||
"read_only_temporarily_disabled": "Modificabile temporaneamente",
|
||||
"read_only_temporarily_disabled_description": "Questa nota è attualmente modificabile, ma normalmente è di sola lettura. La nota tornerà ad essere di sola lettura non appena passerai a un'altra nota.\n\nClicca per riattivare la modalità di sola lettura.",
|
||||
"shared_publicly": "Condiviso pubblicamente",
|
||||
"shared_locally": "Condiviso localmente",
|
||||
"clipped_note": "Clip web",
|
||||
"clipped_note_description": "Questa nota è stata originariamente presa da {{url}}.\n\nClicca per andare alla pagina web di origine.",
|
||||
"execute_script": "Esegui script",
|
||||
"execute_script_description": "Questa nota è una nota di script. Clicca per eseguire lo script.",
|
||||
"execute_sql": "Esegui SQL",
|
||||
"execute_sql_description": "Questa nota è una nota SQL. Clicca per eseguire la query SQL."
|
||||
},
|
||||
"breadcrumb": {
|
||||
"workspace_badge": "Area di lavoro",
|
||||
"scroll_to_top_title": "Vai all'inizio della nota",
|
||||
"hoisted_badge": "Sollevato",
|
||||
"hoisted_badge_title": "Abbassato"
|
||||
},
|
||||
"status_bar": {
|
||||
"language_title": "Cambia lingua dei contenuti",
|
||||
"note_info_title": "Visualizza informazioni sulla nota (ad es. date, dimensioni della nota)",
|
||||
"backlinks_one": "{{count}} backlink",
|
||||
"backlinks_many": "{{count}} backlinks",
|
||||
"backlinks_other": "{{count}} backlinks",
|
||||
"backlinks_title_one": "Visualizza backlink",
|
||||
"backlinks_title_many": "Visualizza backlinks",
|
||||
"backlinks_title_other": "Visualizza backlinks",
|
||||
"attachments_one": "{{count}} allegato",
|
||||
"attachments_many": "{{count}} allegati",
|
||||
"attachments_other": "{{count}} allegati",
|
||||
"attachments_title_one": "Visualizza allegato in una nuova scheda",
|
||||
"attachments_title_many": "Visualizza allegati in una nuova scheda",
|
||||
"attachments_title_other": "Visualizza allegati in una nuova scheda",
|
||||
"attributes_one": "{{count}} attributo",
|
||||
"attributes_many": "{{count}} attributi",
|
||||
"attributes_other": "{{count}} attributi",
|
||||
"attributes_title": "Attributi posseduti e attributi ereditati",
|
||||
"note_paths_one": "{{count}} percorso",
|
||||
"note_paths_many": "{{count}} percorsi",
|
||||
"note_paths_other": "{{count}} percorsi",
|
||||
"note_paths_title": "Nota percorsi",
|
||||
"code_note_switcher": "Cambia modalità lingua"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +218,8 @@
|
||||
"unknown_search_option": "不明な検索オプション {{searchOptionName}}",
|
||||
"search_note_saved": "検索ノートが {{- notePathTitle}} に保存されました",
|
||||
"actions_executed": "アクションが実行されました。",
|
||||
"ancestor": "祖先:"
|
||||
"ancestor": "祖先:",
|
||||
"view_options": "表示オプション:"
|
||||
},
|
||||
"shortcuts": {
|
||||
"multiple_shortcuts": "同じアクションに対して複数のショートカットを設定する場合、カンマで区切ることができます。",
|
||||
@@ -258,7 +259,7 @@
|
||||
"export_in_progress": "エクスポート処理中: {{progressCount}}",
|
||||
"export_finished_successfully": "エクスポートが正常に完了しました。",
|
||||
"format_pdf": "PDF - 印刷または共有目的に。",
|
||||
"share-format": "Web 公開用の HTML - 共有ノートで使用されるのと同じテーマを使用しますが、静的 Web サイトとして公開できます。"
|
||||
"share-format": "web 公開用の HTML - 共有ノートで使用されるのと同じテーマを使用しますが、静的 web サイトとして公開できます。"
|
||||
},
|
||||
"help": {
|
||||
"title": "チートシート",
|
||||
@@ -424,7 +425,7 @@
|
||||
"convert-to-attachment-confirm": "選択したノートを親ノートの添付ファイルに変換してもよろしいですか?この操作は画像ノートにのみ適用され、その他のノートはスキップされます。",
|
||||
"open-in-popup": "クイック編集",
|
||||
"hoist-note": "ホイストノート",
|
||||
"unhoist-note": "ノートをホイストしない",
|
||||
"unhoist-note": "ノートのホイストを解除",
|
||||
"edit-branch-prefix": "ブランチの接頭辞を編集",
|
||||
"archive": "アーカイブ",
|
||||
"unarchive": "アーカイブ解除"
|
||||
@@ -458,7 +459,13 @@
|
||||
"convert_into_attachment_successful": "ノート '{{title}}' は添付ファイルに変換されました。",
|
||||
"convert_into_attachment_prompt": "本当にノート '{{title}}' を親ノートの添付ファイルに変換しますか?",
|
||||
"note_attachments": "ノートの添付ファイル",
|
||||
"open_note_on_server": "サーバー上のノートを開く"
|
||||
"open_note_on_server": "サーバー上のノートを開く",
|
||||
"view_revisions": "ノートの変更履歴...",
|
||||
"note_map": "ノートマップ",
|
||||
"advanced": "高度",
|
||||
"export_as_image": "画像としてエクスポート",
|
||||
"export_as_image_png": "PNG (raster)",
|
||||
"export_as_image_svg": "SVG (vector)"
|
||||
},
|
||||
"command_palette": {
|
||||
"export_note_title": "ノートをエクスポート",
|
||||
@@ -583,7 +590,7 @@
|
||||
"file_type": "ファイルタイプ",
|
||||
"file_size": "ファイルサイズ",
|
||||
"download": "ダウンロード",
|
||||
"open": "開く",
|
||||
"open": "外部で開く",
|
||||
"title": "ファイル",
|
||||
"upload_new_revision": "編集履歴をアップロード",
|
||||
"original_file_name": "元のファイル名",
|
||||
@@ -599,7 +606,9 @@
|
||||
"calculate": "計算",
|
||||
"subtree_size": "(サブツリーサイズ: {{size}}、ノード数: {{count}})",
|
||||
"title": "ノート情報",
|
||||
"note_size_info": "ノートのサイズは、このノートに必要なストレージの概算を示します。これは、ノートの内容とそのノートの編集履歴の内容を考慮したものです。"
|
||||
"note_size_info": "ノートのサイズは、このノートに必要なストレージの概算を示します。これは、ノートの内容とそのノートの編集履歴の内容を考慮したものです。",
|
||||
"show_similar_notes": "類似のノートを表示",
|
||||
"mime": "MIME タイプ"
|
||||
},
|
||||
"image_properties": {
|
||||
"file_type": "ファイルタイプ",
|
||||
@@ -799,7 +808,7 @@
|
||||
},
|
||||
"web_view": {
|
||||
"web_view": "Web ビュー",
|
||||
"embed_websites": "Web ビュータイプでは、ウェブサイトをTriliumに埋め込むことができます。",
|
||||
"embed_websites": "Web ビュータイプでは、web サイトを Trilium に埋め込むことができます。",
|
||||
"create_label": "まず始めに、埋め込みたいURLアドレスのラベルを作成してください。例: #webViewSrc=\"https://www.google.com\""
|
||||
},
|
||||
"backend_log": {
|
||||
@@ -961,7 +970,7 @@
|
||||
"password": {
|
||||
"wiki": "wiki",
|
||||
"heading": "パスワード",
|
||||
"alert_message": "新しいパスワードは大切に保管してください。パスワードはウェブインターフェースへのログインや、保護されたノートの暗号化に使用されます。パスワードを忘れると、保護されたノートはすべて永久に失われます。",
|
||||
"alert_message": "新しいパスワードは大切に保管してください。パスワードは web インターフェースへのログインや、保護されたノートの暗号化に使用されます。パスワードを忘れると、保護されたノートはすべて永久に失われます。",
|
||||
"reset_link": "リセットするにはここをクリック。",
|
||||
"old_password": "旧パスワード",
|
||||
"new_password": "新パスワード",
|
||||
@@ -1107,7 +1116,7 @@
|
||||
"sql_console_home": "SQLコンソールノートのデフォルトの場所",
|
||||
"bookmark_folder": "このラベルの付いたノートは、ブックマークにフォルダとして表示されます(子フォルダへのアクセスを許可します)",
|
||||
"share_hidden_from_tree": "このノートは左側のナビゲーションツリーには表示されていませんが、URL からアクセスできます",
|
||||
"share_external_link": "ノートは共有ツリー内で外部ウェブサイトへのリンクとして機能します",
|
||||
"share_external_link": "ノートは共有ツリー内で外部 web サイトへのリンクとして機能します",
|
||||
"share_alias": "https://your_trilium_host/share/[your_alias] でノートを利用できるようにエイリアスを定義します",
|
||||
"share_omit_default_css": "デフォルトの共有ページのCSSは省略されます。スタイルを大幅に変更する場合に使用してください。",
|
||||
"share_root": "/share root で提供されるノートをマークする。",
|
||||
@@ -1233,7 +1242,14 @@
|
||||
"none_yet": "アクションを上のリストからクリックして追加。"
|
||||
},
|
||||
"note_title": {
|
||||
"placeholder": "ここにノートのタイトルを入力..."
|
||||
"placeholder": "ここにノートのタイトルを入力...",
|
||||
"created_on": "<Value /> に作成",
|
||||
"last_modified": "<Value /> に変更",
|
||||
"note_type_switcher_label": "{{type}} から切り替え:",
|
||||
"note_type_switcher_others": "その他のノートタイプ",
|
||||
"note_type_switcher_templates": "テンプレート",
|
||||
"note_type_switcher_collection": "コレクション",
|
||||
"edited_notes": "編集済みノート"
|
||||
},
|
||||
"search_result": {
|
||||
"no_notes_found": "指定された検索パラメータに該当するノートは見つかりませんでした。",
|
||||
@@ -1330,8 +1346,9 @@
|
||||
"minimum_input": "入力された時間値は {{minimumSeconds}} 秒以上である必要があります。"
|
||||
},
|
||||
"note_language": {
|
||||
"not_set": "未設定",
|
||||
"configure-languages": "言語を設定..."
|
||||
"not_set": "言語が設定されていません",
|
||||
"configure-languages": "言語を設定...",
|
||||
"help-on-languages": "コンテンツの言語に関するヘルプ..."
|
||||
},
|
||||
"content_language": {
|
||||
"title": "コンテンツの言語",
|
||||
@@ -1620,7 +1637,7 @@
|
||||
"remove_this_attribute": "この属性を削除",
|
||||
"remove_color": "このカラーラベルを削除",
|
||||
"promoted_attributes": "プロモート属性",
|
||||
"url_placeholder": "http://ウェブサイト..."
|
||||
"url_placeholder": "http://web サイト..."
|
||||
},
|
||||
"relation_map": {
|
||||
"open_in_new_tab": "新しいタブで開く",
|
||||
@@ -1779,7 +1796,7 @@
|
||||
"placeholder": "ここにノートの内容を入力...",
|
||||
"auto-detect-language": "自動検出",
|
||||
"keeps-crashing": "編集コンポーネントがクラッシュし続けます。Trilium を再起動してください。問題が解決しない場合は、バグレポートの作成をご検討ください。",
|
||||
"editor_crashed_title": "テキストエディタがクラッシュしました",
|
||||
"editor_crashed_title": "テキストエディターがクラッシュしました",
|
||||
"editor_crashed_content": "コンテンツは正常に復元されましたが、最近の変更の一部が保存されていない可能性があります。",
|
||||
"editor_crashed_details_button": "詳細を見る...",
|
||||
"editor_crashed_details_intro": "このエラーが何度も発生する場合は、以下の情報を貼り付けて GitHub に報告することを検討してください。",
|
||||
@@ -1974,7 +1991,7 @@
|
||||
"book_properties_config": {
|
||||
"hide-weekends": "週末を非表示",
|
||||
"display-week-numbers": "週番号を表示",
|
||||
"map-style": "マップスタイル:",
|
||||
"map-style": "マップスタイル",
|
||||
"max-nesting-depth": "最大階層の深さ:",
|
||||
"show-scale": "スケールを表示",
|
||||
"raster": "Raster",
|
||||
@@ -1988,14 +2005,20 @@
|
||||
"background_effects_title": "背景効果が安定しました",
|
||||
"background_effects_message": "Windowsデバイスでは、背景効果が完全に安定しました。背景効果は、背景をぼかすことでユーザーインターフェースに彩りを添えます。この技術は、Windowsエクスプローラーなどの他のアプリケーションでも使用されています。",
|
||||
"background_effects_button": "背景効果を有効にする",
|
||||
"dismiss": "却下"
|
||||
"dismiss": "却下",
|
||||
"new_layout_title": "新しいレイアウト",
|
||||
"new_layout_message": "Trilium のレイアウトを刷新しました。リボンは廃止され、メインインターフェースにシームレスに統合されました。主要な機能は、新しいステータスバーと展開可能なセクション(プロモート属性など)に集約されています。\n\n新しいレイアウトはデフォルトで有効になっていますが、「オプション」→「外観」から一時的に無効にすることもできます。",
|
||||
"new_layout_button": "詳細情報"
|
||||
},
|
||||
"settings": {
|
||||
"related_settings": "関連設定"
|
||||
},
|
||||
"settings_appearance": {
|
||||
"related_code_blocks": "テキストノート内のコードブロックの配色",
|
||||
"related_code_notes": "コードノートの配色"
|
||||
"related_code_notes": "コードノートの配色",
|
||||
"ui": "ユーザーインターフェース",
|
||||
"ui_old_layout": "旧レイアウト",
|
||||
"ui_new_layout": "新しいレイアウト"
|
||||
},
|
||||
"units": {
|
||||
"percentage": "%"
|
||||
@@ -2069,7 +2092,7 @@
|
||||
"recovery_keys_used": "使用日: {{date}}",
|
||||
"recovery_keys_unused": "回復コード {{index}} は未使用です",
|
||||
"oauth_title": "OAuth/OpenID",
|
||||
"oauth_description": "OpenIDは、Googleなどの他のサービスのアカウントを使用してウェブサイトにログインし、本人確認を行うための標準化された方法です。デフォルトの発行者はGoogleですが、他のOpenIDプロバイダに変更できます。詳しくは<a href=\"#root/_hidden/_help/_help_Otzi9La2YAUX/_help_WOcw2SLH6tbX/_help_7DAiwaf8Z7Rz\">こちら</a>をご覧ください。Google経由でOpenIDサービスを設定するには、<a href=\"https://developers.google.com/identity/openid-connect/openid-connect\">こちらの手順</a>に従ってください。",
|
||||
"oauth_description": "OpenIDは、Googleなどの他のサービスのアカウントを使用して web サイトにログインし、本人確認を行うための標準化された方法です。デフォルトの発行者はGoogleですが、他のOpenIDプロバイダに変更できます。詳しくは<a href=\"#root/_hidden/_help/_help_Otzi9La2YAUX/_help_WOcw2SLH6tbX/_help_7DAiwaf8Z7Rz\">こちら</a>をご覧ください。Google経由でOpenIDサービスを設定するには、<a href=\"https://developers.google.com/identity/openid-connect/openid-connect\">こちらの手順</a>に従ってください。",
|
||||
"oauth_description_warning": "OAuth/OpenIDを有効にするには、config.iniファイルにOAuth/OpenIDのベースURL、クライアントID、クライアントシークレットを設定し、アプリケーションを再起動する必要があります。環境変数から設定する場合は、TRILIUM_OAUTH_BASE_URL, TRILIUM_OAUTH_CLIENT_ID and TRILIUM_OAUTH_CLIENT_SECRET を設定してください。",
|
||||
"oauth_missing_vars": "設定がありません: {{-variables}}",
|
||||
"oauth_user_account": "ユーザーアカウント: ",
|
||||
@@ -2116,5 +2139,55 @@
|
||||
"unknown_http_error_title": "サーバーとの通信エラー",
|
||||
"unknown_http_error_content": "ステータスコード: {{statusCode}}\nURL: {{method}} {{url}}\nメッセージ: {{message}}",
|
||||
"traefik_blocks_requests": "Traefik リバース プロキシを使用している場合、サーバーとの通信に影響する重大な変更が導入されました。"
|
||||
},
|
||||
"tab_history_navigation_buttons": {
|
||||
"go-back": "前のノートに戻る",
|
||||
"go-forward": "次のノートに進む"
|
||||
},
|
||||
"experimental_features": {
|
||||
"title": "実験オプション",
|
||||
"disclaimer": "これらのオプションは試験的なもので、動作が不安定になる可能性があります。注意してご使用ください。",
|
||||
"new_layout_name": "新しいレイアウト",
|
||||
"new_layout_description": "よりモダンな外観と使いやすさが向上した新しいレイアウトをお試しください。今後のリリースで大幅な変更が加えられる可能性があります。"
|
||||
},
|
||||
"breadcrumb_badges": {
|
||||
"read_only_explicit": "読み取り専用",
|
||||
"read_only_auto": "自動的に読み取り専用",
|
||||
"shared_publicly": "公開で共有",
|
||||
"shared_locally": "ローカルで共有",
|
||||
"read_only_explicit_description": "このノートは手動で読み取り専用に設定されています。\nクリックすると一時的に編集できます。",
|
||||
"read_only_temporarily_disabled": "一時的に編集可能",
|
||||
"read_only_auto_description": "このノートはパフォーマンス上の理由により、自動的に読み取り専用モードに設定されました。この自動制限は設定から調整できます。\n\n一時的に編集するにはクリックしてください。",
|
||||
"read_only_temporarily_disabled_description": "このノートは現在編集可能ですが、通常は読み取り専用です。別のノートに移動すると読み取り専用に戻ります。\n\nクリックすると読み取り専用モードが再度有効になります。",
|
||||
"clipped_note": "Web クリップ",
|
||||
"clipped_note_description": "このノートは {{url}} から取得されました。\n\nクリックすると元の web ページに移動します。",
|
||||
"execute_script": "スクリプトを実行",
|
||||
"execute_script_description": "このノートはスクリプトノートです。クリックするとスクリプトが実行されます。",
|
||||
"execute_sql": "SQL を実行",
|
||||
"execute_sql_description": "このノートは SQL ノートです。クリックすると SQL クエリが実行されます。",
|
||||
"shared_copy_to_clipboard": "リンクをクリップボードにコピー",
|
||||
"shared_open_in_browser": "ブラウザでリンクを開く",
|
||||
"shared_unshare": "共有を削除"
|
||||
},
|
||||
"status_bar": {
|
||||
"language_title": "コンテンツの言語を変更",
|
||||
"note_info_title": "ノート情報を表示(例: 日付、ノートのサイズなど)",
|
||||
"backlinks_title_other": "バックリンクを表示",
|
||||
"attachments_title_other": "添付ファイルを新しいタブで表示",
|
||||
"attributes_other": "{{count}} 個の属性",
|
||||
"attributes_title": "所有属性と継承属性",
|
||||
"note_paths_title": "ノートパス",
|
||||
"code_note_switcher": "言語モードを変更",
|
||||
"backlinks_other": "{{count}} バックリンク",
|
||||
"attachments_other": "{{count}} 件の添付ファイル",
|
||||
"note_paths_other": "{{count}} 個のパス"
|
||||
},
|
||||
"breadcrumb": {
|
||||
"hoisted_badge": "ホイスト",
|
||||
"hoisted_badge_title": "ホイスト解除",
|
||||
"workspace_badge": "ワークスペース",
|
||||
"scroll_to_top_title": "ノートの先頭にジャンプ",
|
||||
"create_new_note": "新しい子ノートを作成",
|
||||
"empty_hide_archived_notes": "アーカイブされたノートを非表示"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -493,7 +493,12 @@
|
||||
"editable_text": {
|
||||
"placeholder": "Scrieți conținutul notiței aici...",
|
||||
"auto-detect-language": "Automat",
|
||||
"keeps-crashing": "Componenta de editare se blochează în continuu. Încercați să reporniți Trilium. Dacă problema persistă, luați în considerare să raportați această problemă."
|
||||
"keeps-crashing": "Componenta de editare se blochează în continuu. Încercați să reporniți Trilium. Dacă problema persistă, luați în considerare să raportați această problemă.",
|
||||
"editor_crashed_title": "Editorul text a avut o eroare",
|
||||
"editor_crashed_content": "Conținutul a fost recuperat cu succes, dar este posibil ca o parte din cele mai recente modificări ale dvs. să nu se fi salvat.",
|
||||
"editor_crashed_details_button": "Mai multe detalii...",
|
||||
"editor_crashed_details_intro": "Dacă întâmpinați frecvent această eroare, considerați să o raportați pe GitHub copiând informația de mai jos.",
|
||||
"editor_crashed_details_title": "Informații tehnice"
|
||||
},
|
||||
"edited_notes": {
|
||||
"deleted": "(șters)",
|
||||
@@ -785,7 +790,8 @@
|
||||
"info": {
|
||||
"closeButton": "Închide",
|
||||
"modalTitle": "Mesaj informativ",
|
||||
"okButton": "OK"
|
||||
"okButton": "OK",
|
||||
"copy_to_clipboard": "Copiază în clipboard"
|
||||
},
|
||||
"inherited_attribute_list": {
|
||||
"no_inherited_attributes": "Niciun atribut moștenit.",
|
||||
@@ -867,12 +873,14 @@
|
||||
"print_note": "Imprimare notiță",
|
||||
"re_render_note": "Reinterpretare notiță",
|
||||
"save_revision": "Salvează o nouă revizie",
|
||||
"advanced": "Advansat",
|
||||
"search_in_note": "Caută în notiță",
|
||||
"convert_into_attachment_failed": "Nu s-a putut converti notița „{{title}}”.",
|
||||
"convert_into_attachment_successful": "Notița „{{title}}” a fost convertită în atașament.",
|
||||
"convert_into_attachment_prompt": "Doriți convertirea notiței „{{title}}” într-un atașament al notiței părinte?",
|
||||
"print_pdf": "Exportare ca PDF...",
|
||||
"open_note_on_server": "Deschide notița pe server"
|
||||
"open_note_on_server": "Deschide notița pe server",
|
||||
"view_revisions": "Revizii ale notițelor..."
|
||||
},
|
||||
"note_erasure_timeout": {
|
||||
"deleted_notes_erased": "Notițele șterse au fost eliminate permanent.",
|
||||
@@ -1407,7 +1415,7 @@
|
||||
"hoist-note": "Focalizează notița",
|
||||
"unhoist-note": "Defocalizează notița",
|
||||
"converted-to-attachments": "{{count}} notițe au fost convertite în atașamente.",
|
||||
"convert-to-attachment-confirm": "Doriți convertirea notițelor selectate în atașamente ale notiței părinte?",
|
||||
"convert-to-attachment-confirm": "Doriți convertirea notițelor selectate în atașamente ale notiței părinte? Această operațiune se aplică doar notițelor de tip imagine, celelalte vor fi ignorate.",
|
||||
"open-in-popup": "Editare rapidă",
|
||||
"archive": "Arhivează",
|
||||
"unarchive": "Dezarhivează"
|
||||
@@ -1526,7 +1534,9 @@
|
||||
"printing_pdf": "Exportare ca PDF în curs..."
|
||||
},
|
||||
"note_title": {
|
||||
"placeholder": "introduceți titlul notiței aici..."
|
||||
"placeholder": "introduceți titlul notiței aici...",
|
||||
"created_on": "Creată la <Value />",
|
||||
"last_modified": "Modificată la <Value />"
|
||||
},
|
||||
"revisions_snapshot_limit": {
|
||||
"erase_excess_revision_snapshots": "Șterge acum reviziile excesive",
|
||||
@@ -1758,7 +1768,8 @@
|
||||
},
|
||||
"note_language": {
|
||||
"configure-languages": "Configurează limbile...",
|
||||
"not_set": "Nedefinită"
|
||||
"not_set": "Nicio limbă setată",
|
||||
"help-on-languages": "Informații despre limba conținutului..."
|
||||
},
|
||||
"png_export_button": {
|
||||
"button_title": "Exportă diagrama ca PNG"
|
||||
@@ -1954,7 +1965,8 @@
|
||||
"oauth_user_not_logged_in": "Neautentificat!"
|
||||
},
|
||||
"svg": {
|
||||
"export_to_png": "Diagrama nu a putut fi exportată în PNG."
|
||||
"export_to_png": "Diagrama nu a putut fi exportată în PNG.",
|
||||
"export_to_svg": "Diagrama nu a putut fi exportată în SVG."
|
||||
},
|
||||
"code_theme": {
|
||||
"title": "Afișare",
|
||||
@@ -2106,5 +2118,30 @@
|
||||
},
|
||||
"popup-editor": {
|
||||
"maximize": "Comută la editorul principal"
|
||||
},
|
||||
"experimental_features": {
|
||||
"title": "Opțiuni experimentale",
|
||||
"disclaimer": "Aceste opțiuni sunt experimentale și pot cauza instabilitate. Folosiți cu prudență.",
|
||||
"new_layout_name": "Aspect nou",
|
||||
"new_layout_description": "Încercați noul aspect pentru un design mai modern și mai ușor de utilizat. Poate surveni modificări semnificative în următoarele release-uri."
|
||||
},
|
||||
"server": {
|
||||
"unknown_http_error_title": "Eroare de comunicare cu server-ul",
|
||||
"unknown_http_error_content": "Cod: {{statusCode}}\nURL: {{method}} {{url}}\nMesaj: {{message}}",
|
||||
"traefik_blocks_requests": "Dacă utilizați reverse proxy-ul Traefik, acesta a introdus o schimbare majoră ce afectează comunicarea cu server-ul."
|
||||
},
|
||||
"tab_history_navigation_buttons": {
|
||||
"go-back": "Înapoi la notița anterioară",
|
||||
"go-forward": "Înainte către notița următoare"
|
||||
},
|
||||
"breadcrumb_badges": {
|
||||
"read_only_explicit": "Mod citire",
|
||||
"read_only_explicit_description": "Această notiță a fost setată explicit să fie doar în citire.\nClick pentru a o edita temporar.",
|
||||
"read_only_auto": "Mod citire auto",
|
||||
"read_only_auto_description": "Această notița a fost setată automată să fie în mod doar de citire din motive de performanță. Această limită automată este ajustabilă din setări.\n\nClick pentru a o edita temporar.",
|
||||
"read_only_temporarily_disabled": "Editabilă temporar",
|
||||
"read_only_temporarily_disabled_description": "Această notiță se poate modifica, deși în mod normal ea este doar în citire. Notița va reveni la modul doar în citire imediat ce navigați către altă notiță.\n\nClick pentru a re-activa modul doar în citire.",
|
||||
"shared_publicly": "Partajată public",
|
||||
"shared_locally": "Partajată local"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,10 @@
|
||||
"edit_branch_prefix": "Редактировать префикс ветки",
|
||||
"prefix": "Префикс: ",
|
||||
"branch_prefix_saved": "Префикс ветки сохранен.",
|
||||
"help_on_tree_prefix": "Помощь по префиксу дерева"
|
||||
"help_on_tree_prefix": "Помощь по префиксу дерева",
|
||||
"affected_branches": "Затронутые ветки ({{count}}):",
|
||||
"branch_prefix_saved_multiple": "Префикс сохранен для {{count}} ветвей.",
|
||||
"edit_branch_prefix_multiple": "Изменить префикс для {{count}} ветвей"
|
||||
},
|
||||
"bulk_actions": {
|
||||
"available_actions": "Доступные действия",
|
||||
@@ -236,7 +239,8 @@
|
||||
"export_status": "Статус экспорта",
|
||||
"export_in_progress": "Экспорт: {{progressCount}}",
|
||||
"export_finished_successfully": "Экспорт завершился успешно.",
|
||||
"format_pdf": "PDF - для печати или обмена."
|
||||
"format_pdf": "PDF - для печати или обмена.",
|
||||
"share-format": "HTML для веб-публикаций — использует ту же тему оформления, что и общие заметки, но может быть опубликован как статический веб-сайт."
|
||||
},
|
||||
"help": {
|
||||
"noteNavigation": "Навигация по заметке",
|
||||
@@ -290,7 +294,8 @@
|
||||
"blockQuote": "начните строку с <code>></code>, а затем пробела для блока цитаты",
|
||||
"quickSearch": "сфокусироваться на поле ввода быстрого поиска",
|
||||
"editNoteTitle": "в области дерева переключится с области дерева на заголовок заметки. Сочетание клавиш Enter из области заголовка заметки переключит фокус на текстовый редактор. <kbd>Ctrl+.</kbd> переключит обратно с редактора на область дерева.",
|
||||
"title": "Справка"
|
||||
"title": "Справка",
|
||||
"editShortcuts": "Редактировать сочетания клавиш"
|
||||
},
|
||||
"modal": {
|
||||
"close": "Закрыть",
|
||||
@@ -472,11 +477,11 @@
|
||||
"app_css": "отмечает заметки CSS, которые загружаются в приложение Trilium и, таким образом, могут использоваться для изменения внешнего вида Trilium.",
|
||||
"app_theme_base": "установите значение \"next\", \"next-light\" или \"next-dark\", чтобы использовать соответствующую тему TriliumNext (автоматическую, светлую или темную) в качестве основы для пользовательской темы вместо устаревшей.",
|
||||
"exclude_from_note_map": "Заметки с этой меткой будут скрыты на карте заметок",
|
||||
"workspace": "отмечает эту заметку как рабочее пространство, для удобного закрепления",
|
||||
"workspace_icon_class": "определяет CSS-класс значка поля, который будет использоваться во вкладке при закреплении этой заметки",
|
||||
"workspace_tab_background_color": "Цвет CSS, используемый во вкладке заметки при ее закреплении",
|
||||
"workspace_template": "Эта заметка появится в списке доступных шаблонов при создании новой заметки, но только если она будет перемещена в рабочую область, содержащую этот шаблон",
|
||||
"workspace_search_home": "новые заметки поиска будут созданы как дочерние записи этой заметки при перемещении их к какому-либо предку этой заметки рабочей области",
|
||||
"workspace": "отмечает эту заметку как рабочее пространство, для удобной установки фокуса",
|
||||
"workspace_icon_class": "определяет CSS-класс значка поля, который будет использоваться во вкладке при установке фокуса на этой заметке",
|
||||
"workspace_tab_background_color": "Цвет CSS, используемый во вкладке заметки при установке на нее фокуса",
|
||||
"workspace_template": "Эта заметка появится в списке доступных шаблонов при создании новой заметки, но только если будет установлен фокус на рабочую область с этим шаблоном",
|
||||
"workspace_search_home": "новые заметки поиска будут созданы как дочерние записи этой заметки, когда установлен фокус на какую-либо родительскую заметку этого рабочего пространство",
|
||||
"workspace_calendar_root": "Определяет корень календаря для каждого рабочего пространства",
|
||||
"hide_highlight_widget": "Скрыть виджет «Выделенное»",
|
||||
"is_owned_by_note": "принадлежит заметке",
|
||||
@@ -503,7 +508,7 @@
|
||||
"custom_resource_provider": "см. <a href=\"javascript:\" data-help-page=\"custom-request-handler.html\">Пользовательский обработчик запросов</a>",
|
||||
"widget": "отмечает эту заметку как пользовательский виджет, который будет добавлен в дерево компонентов Trilium",
|
||||
"search_home": "новые заметки поиска будут созданы как дочерние записи этой заметки",
|
||||
"workspace_inbox": "расположение в папке «Входящие» по умолчанию для новых заметок при перемещении их в некую родственную папку этой заметки в рабочей области",
|
||||
"workspace_inbox": "расположение в папке «Входящие» по умолчанию для новых заметок, когда установлен фокус на какую-либо родительскую заметку этого рабочего пространство",
|
||||
"sql_console_home": "расположение заметок консоли SQL по умолчанию",
|
||||
"css_class": "значение этой метки затем добавляется как CSS-класс к узлу, представляющему данную заметку в дереве. Это может быть полезно для изменения внешнего вида заметки. Может использоваться в шаблонах заметок.",
|
||||
"bookmark_folder": "заметка с этой меткой появится в закладках как папка (с предоставлением доступа к ее дочерним элементам)",
|
||||
@@ -519,7 +524,7 @@
|
||||
"share_index": "заметка с этой меткой будет содержать список всех корневых узлов общедоступных заметок",
|
||||
"toc": "<code>#toc</code> или <code>#toc=show</code> принудительно отобразят оглавление, <code>#toc=hide</code> — скроют его. Если метка отсутствует, применяется глобальная настройка",
|
||||
"color": "определяет цвет заметки в дереве заметок, ссылках и т. д. Используйте любое допустимое значение цвета CSS, например «red» или #a13d5f",
|
||||
"keep_current_hoisting": "Открытие этой ссылки не изменит закрепление, даже если заметка не отображается в текущем закрепленном поддереве.",
|
||||
"keep_current_hoisting": "Открытие этой ссылки не изменит фокус, даже если заметка не отображается в текущем закрепленном поддереве.",
|
||||
"execute_description": "Более подробное описание текущей заметки типа \"Код\", отображаемое вместе с кнопкой \"Выполнить\"",
|
||||
"run_on_note_creation": "выполняется при создании заметки на сервере. Используйте это отношение, если хотите запустить скрипт для всех заметок, созданных в определённом поддереве. В этом случае создайте его в корневой заметке поддерева и сделайте его наследуемым. Новая заметка, созданная в поддереве (любой глубины), запустит скрипт.",
|
||||
"run_on_child_note_creation": "выполняется, когда создается новая заметка под заметкой, в которой определено это отношение",
|
||||
@@ -567,7 +572,8 @@
|
||||
"edit-column-title": "Нажмите, чтобы изменить заголовок столбца",
|
||||
"edit-note-title": "Нажмите, чтобы изменить название заметки",
|
||||
"add-column-placeholder": "Введите имя столбца...",
|
||||
"new-item-placeholder": "Введите название заметки..."
|
||||
"new-item-placeholder": "Введите название заметки...",
|
||||
"column-already-exists": "Такая колонка уже добавлена на доску."
|
||||
},
|
||||
"table_context_menu": {
|
||||
"delete_row": "Удалить строку"
|
||||
@@ -576,7 +582,7 @@
|
||||
"vector_dark": "Vector (Темная)",
|
||||
"vector_light": "Vector (Светлая)",
|
||||
"max-nesting-depth": "Максимальная глубина вложенности:",
|
||||
"map-style": "Стиль карты:",
|
||||
"map-style": "Стиль карты",
|
||||
"display-week-numbers": "Отображать номера недель",
|
||||
"hide-weekends": "Скрыть выходные",
|
||||
"raster": "Растр",
|
||||
@@ -606,7 +612,8 @@
|
||||
"title": "Внешний вид"
|
||||
},
|
||||
"svg": {
|
||||
"export_to_png": "Диаграмму не может быть экспортирована в PNG."
|
||||
"export_to_png": "Диаграмму не может быть экспортирована в PNG.",
|
||||
"export_to_svg": "Не удалось экспортировать диаграмму в SVG."
|
||||
},
|
||||
"png_export_button": {
|
||||
"button_title": "Экспортировать диаграмму как PNG"
|
||||
@@ -621,7 +628,8 @@
|
||||
},
|
||||
"note_language": {
|
||||
"configure-languages": "Настроить языки...",
|
||||
"not_set": "Не установлен"
|
||||
"not_set": "Язык не установлен",
|
||||
"help-on-languages": "Помощь по языкам содержимого..."
|
||||
},
|
||||
"time_selector": {
|
||||
"invalid_input": "Введенное значение времени не является допустимым числом.",
|
||||
@@ -679,7 +687,8 @@
|
||||
"open_note_in_popup": "Быстрое редактирование",
|
||||
"open_note_in_new_window": "Открыть заметку в новом окне",
|
||||
"open_note_in_new_tab": "Открыть заметку в новой вкладке",
|
||||
"open_note_in_new_split": "Открыть заметку в новой панели"
|
||||
"open_note_in_new_split": "Открыть заметку в новой панели",
|
||||
"open_note_in_other_split": "Открыть заметку в другой панели"
|
||||
},
|
||||
"image_context_menu": {
|
||||
"copy_image_to_clipboard": "Копировать изображение в буфер обмена",
|
||||
@@ -692,7 +701,8 @@
|
||||
"copy": "Скопировать",
|
||||
"cut": "Вырезать",
|
||||
"search_online": "Поиск \"{{term}}\" в {{searchEngine}}",
|
||||
"add-term-to-dictionary": "Добавить \"{{term}}\" в словарь"
|
||||
"add-term-to-dictionary": "Добавить \"{{term}}\" в словарь",
|
||||
"search_in_trilium": "Искать \"{{term}}\" в Trilium"
|
||||
},
|
||||
"editing": {
|
||||
"editor_type": {
|
||||
@@ -746,17 +756,18 @@
|
||||
"hide-archived-notes": "Скрыть архивные заметки",
|
||||
"automatically-collapse-notes": "Автоматически сворачивать заметки",
|
||||
"tree-settings-title": "Настройки дерева",
|
||||
"unhoist": "Открепить",
|
||||
"unhoist": "Убрать фокус",
|
||||
"scroll-active-title": "Прокрутить к активной заметке",
|
||||
"collapse-title": "Свернуть дерево",
|
||||
"hoist-this-note-workspace": "Закрепить заметку (рабочая область)",
|
||||
"hoist-this-note-workspace": "Фокус на заметке (рабочая область)",
|
||||
"auto-collapsing-notes-after-inactivity": "Автоматическое сворачивание заметок после бездействия...",
|
||||
"create-child-note": "Создать дочернюю заметку",
|
||||
"save-changes": "Сохранить и применить изменения",
|
||||
"saved-search-note-refreshed": "Сохраненная поисковая заметка обновлена.",
|
||||
"refresh-saved-search-results": "Обновить сохраненные результаты поиска",
|
||||
"automatically-collapse-notes-title": "Заметки будут свернуты после определенного периода бездействия, чтобы навести порядок в дереве.",
|
||||
"toggle-sidebar": "Переключить боковую панель"
|
||||
"toggle-sidebar": "Переключить боковую панель",
|
||||
"dropping-not-allowed": "Перетаскивание заметок в эту область не разрешено."
|
||||
},
|
||||
"quick-search": {
|
||||
"no-results": "Результаты не найдены",
|
||||
@@ -793,7 +804,7 @@
|
||||
"text": "Текст",
|
||||
"launcher": "Лаунчер",
|
||||
"doc": "Документация",
|
||||
"relation-map": "Карта отношений",
|
||||
"relation-map": "Карта связей",
|
||||
"note-map": "Карта заметок",
|
||||
"render-note": "Рендеринг заметки",
|
||||
"web-view": "Веб-страница",
|
||||
@@ -812,8 +823,8 @@
|
||||
"export": "Экспорт",
|
||||
"open-in-a-new-tab": "Открыть в новой вкладке",
|
||||
"open-in-a-new-split": "Открыть в новой панели",
|
||||
"unhoist-note": "Открепить заметку",
|
||||
"hoist-note": "Закрепить заметку",
|
||||
"unhoist-note": "Снять фокус",
|
||||
"hoist-note": "Фокус на заметке",
|
||||
"protect-subtree": "Защитить поддерево",
|
||||
"unprotect-subtree": "Снять защиту с поддерева",
|
||||
"copy-clone": "Скопировать / Склонировать",
|
||||
@@ -833,7 +844,7 @@
|
||||
"apply-bulk-actions": "Применить массовые действия",
|
||||
"recent-changes-in-subtree": "Последние изменения в поддереве",
|
||||
"copy-note-path-to-clipboard": "Копировать путь к заметке в буфер обмена",
|
||||
"convert-to-attachment-confirm": "Вы уверены, что хотите преобразовать выбранные заметки во вложения их родительских заметок?",
|
||||
"convert-to-attachment-confirm": "Вы уверены, что хотите преобразовать выбранные заметки во вложения их родительских заметок? Эта операция применяется только к заметкам в виде изображений; другие заметки будут пропущены.",
|
||||
"converted-to-attachments": "{{count}} заметок были преобразованы во вложения.",
|
||||
"archive": "Архивировать",
|
||||
"unarchive": "Разархивировать"
|
||||
@@ -841,7 +852,8 @@
|
||||
"info": {
|
||||
"closeButton": "Закрыть",
|
||||
"okButton": "ОК",
|
||||
"modalTitle": "Информация"
|
||||
"modalTitle": "Информация",
|
||||
"copy_to_clipboard": "Скопировать в буфер обмена"
|
||||
},
|
||||
"jump_to_note": {
|
||||
"search_placeholder": "Найдите заметку по ее названию или введите > для команд...",
|
||||
@@ -978,13 +990,14 @@
|
||||
"show_shared_notes_subtree": "Поддерево общедоступных заметок",
|
||||
"switch_to_mobile_version": "Перейти на мобильную версию",
|
||||
"switch_to_desktop_version": "Переключиться на версию для ПК",
|
||||
"new-version-available": "Доступно обновление"
|
||||
"new-version-available": "Доступно обновление",
|
||||
"download-update": "Обновить до {{latestVersion}}"
|
||||
},
|
||||
"zpetne_odkazy": {
|
||||
"relation": "отношение",
|
||||
"backlink_one": "{{count}} ссылки",
|
||||
"backlink_few": "",
|
||||
"backlink_many": "{{count}} ссылок"
|
||||
"backlink_one": "{{count}} обратная ссылка",
|
||||
"backlink_few": "{{count}} обратные ссылки",
|
||||
"backlink_many": "{{count}} обратных ссылок"
|
||||
},
|
||||
"note_icon": {
|
||||
"category": "Категория:",
|
||||
@@ -1012,7 +1025,12 @@
|
||||
"geo-map": "Карта",
|
||||
"invalid_view_type": "Недопустимый тип представления '{{type}}'",
|
||||
"collapse_all_notes": "Свернуть все заметки",
|
||||
"include_archived_notes": "Показать заархивированные заметки"
|
||||
"include_archived_notes": "Показать заархивированные заметки",
|
||||
"presentation": "Презентация",
|
||||
"expand_all_levels": "Развернуть все вложенные уровни",
|
||||
"expand_nth_level": "Развернуть уровни: {{depth}} шт.",
|
||||
"expand_first_level": "Развернуть прямые дочерние уровни",
|
||||
"expand_tooltip": "Разщвернуть дочерние элементы этой коллекции (на один уровень вложенности). Для получения дополнительных параметров нажмите стрелку справа."
|
||||
},
|
||||
"edited_notes": {
|
||||
"deleted": "(удалено)",
|
||||
@@ -1052,7 +1070,9 @@
|
||||
"title": "Информация",
|
||||
"calculate": "подсчитать",
|
||||
"note_size_info": "Размер заметки позволяет приблизительно оценить требования к объёму хранилища для данной заметки. Он учитывает её содержание и содержание её сохраненных версий.",
|
||||
"subtree_size": "(размер поддерева: {{size}} в {{count}} заметках)"
|
||||
"subtree_size": "(размер поддерева: {{size}} в {{count}} заметках)",
|
||||
"mime": "MIME тип",
|
||||
"show_similar_notes": "Похожие заметки"
|
||||
},
|
||||
"note_paths": {
|
||||
"search": "Поиск",
|
||||
@@ -1060,7 +1080,7 @@
|
||||
"clone_button": "Клонировать заметку в новое место...",
|
||||
"intro_placed": "Эта заметка размещена по следующим путям:",
|
||||
"intro_not_placed": "Эта заметка еще не помещена в дерево заметок.",
|
||||
"outside_hoisted": "Этот путь находится за пределами закрепленной заметки, и вам придется снять закрепление.",
|
||||
"outside_hoisted": "Этот путь находится за пределами сфокусированной заметки, и вам придется снять фокус.",
|
||||
"archived": "Архивировано"
|
||||
},
|
||||
"note_properties": {
|
||||
@@ -1105,7 +1125,8 @@
|
||||
"save_to_note": "Сохранить в заметку",
|
||||
"search_note_saved": "Заметка с настройкой поиска сохранена в {{- notePathTitle}}",
|
||||
"unknown_search_option": "Неизвестный параметр поиска {{searchOptionName}}",
|
||||
"actions_executed": "Действия выполнены."
|
||||
"actions_executed": "Действия выполнены.",
|
||||
"view_options": "Просмотреть опции:"
|
||||
},
|
||||
"ancestor": {
|
||||
"depth_label": "глубина",
|
||||
@@ -1201,7 +1222,8 @@
|
||||
"max_width_unit": "пикселей",
|
||||
"title": "Ширина контентной области",
|
||||
"default_description": "Trilium по умолчанию ограничивает максимальную ширину контента, чтобы улучшить читаемость на широких экранах.",
|
||||
"max_width_label": "Максимальная ширина контентной области"
|
||||
"max_width_label": "Максимальная ширина контентной области",
|
||||
"centerContent": "Размещать контент по центру"
|
||||
},
|
||||
"native_title_bar": {
|
||||
"enabled": "включено",
|
||||
@@ -1409,7 +1431,13 @@
|
||||
"min-days-in-first-week": "Минимальное количество дней в первой неделе",
|
||||
"first-week-info": "Первая неделя содержит первый четверг года в соответствии со стандартом <a href=\"https://en.wikipedia.org/wiki/ISO_week_date#First_week\">ISO 8601</a>.",
|
||||
"first-week-warning": "Изменение параметров первой недели может привести к дублированию существующих недельных заметок, и существующие недельные заметки не будут обновлены соответствующим образом.",
|
||||
"formatting-locale": "Формат даты и числа"
|
||||
"formatting-locale": "Формат даты и числа",
|
||||
"formatting-locale-auto": "Выбирать на основе языка приложения",
|
||||
"saturday": "Суббота",
|
||||
"friday": "Пятница",
|
||||
"thursday": "Четверг",
|
||||
"wednesday": "Среда",
|
||||
"tuesday": "Вторник"
|
||||
},
|
||||
"backup": {
|
||||
"path": "Путь",
|
||||
@@ -1609,7 +1637,14 @@
|
||||
"convert_into_attachment_failed": "Не удалось преобразовать заметку '{{title}}'.",
|
||||
"open_note_externally_title": "Файл будет открыт во внешнем приложении и отслеживается на наличие изменений. После этого вы сможете загрузить изменённую версию обратно в Trilium.",
|
||||
"open_note_externally": "Открыть заметку вне приложения",
|
||||
"open_note_custom": "Открыть заметку как..."
|
||||
"open_note_custom": "Открыть заметку как...",
|
||||
"export_as_image_svg": "SVG (вектор)",
|
||||
"export_as_image_png": "PNG (растр)",
|
||||
"export_as_image": "Экспорт изображения",
|
||||
"open_note_on_server": "Открыть заметку на сервере",
|
||||
"view_revisions": "История изменений...",
|
||||
"note_map": "Карта заметок",
|
||||
"advanced": "Дополнительно"
|
||||
},
|
||||
"revisions_button": {
|
||||
"note_revisions": "Версии заметки"
|
||||
@@ -1634,7 +1669,7 @@
|
||||
"zoom_in_title": "Увеличить масштаб",
|
||||
"zoom_out_title": "Уменьшить масштаб",
|
||||
"reset_pan_zoom_title": "Сбросить панорамирование и масштабирование",
|
||||
"create_child_note_title": "Создать новую дочернюю заметку и добавить ее в эту карту отношений"
|
||||
"create_child_note_title": "Создать новую дочернюю заметку и добавить ее в эту карту связей"
|
||||
},
|
||||
"code_auto_read_only_size": {
|
||||
"unit": "символов",
|
||||
@@ -1689,7 +1724,7 @@
|
||||
"remove_relation": "Удалить отношение",
|
||||
"default_new_note_title": "новая заметка",
|
||||
"open_in_new_tab": "Открыть в новой вкладке",
|
||||
"confirm_remove_relation": "Вы уверены, что хотите удалить отношение?",
|
||||
"confirm_remove_relation": "Вы уверены, что хотите удалить связь?",
|
||||
"enter_new_title": "Введите новое название заметки:",
|
||||
"note_not_found": "Заметка {{noteId}} не найдена!",
|
||||
"cannot_match_transform": "Невозможно сопоставить преобразование: {{transform}}",
|
||||
@@ -1697,7 +1732,7 @@
|
||||
"click_on_canvas_to_place_new_note": "Щелкните по холсту, чтобы разместить новую заметку",
|
||||
"note_already_in_diagram": "Заметка \"{{title}}\" уже есть на диаграмме.",
|
||||
"connection_exists": "Связь '{{name}}' между этими заметками уже существует.",
|
||||
"specify_new_relation_name": "Укажите новое имя отношения (допустимые символы: буквы, цифры, двоеточие и подчеркивание):",
|
||||
"specify_new_relation_name": "Укажите новое имя связи (допустимые символы: буквы, цифры, двоеточие и подчеркивание):",
|
||||
"start_dragging_relations": "Начните перетягивать отношения отсюда на другую заметку."
|
||||
},
|
||||
"vacuum_database": {
|
||||
@@ -1785,7 +1820,8 @@
|
||||
"error_unrecognized_command": "Нераспознанная команда {{command}}",
|
||||
"error_cannot_get_branch_id": "Невозможно получить branchId для notePath '{{notePath}}'",
|
||||
"delete_this_note": "Удалить эту заметку",
|
||||
"insert_child_note": "Вставить дочернюю заметку"
|
||||
"insert_child_note": "Вставить дочернюю заметку",
|
||||
"note_revisions": "История изменений"
|
||||
},
|
||||
"svg_export_button": {
|
||||
"button_title": "Экспортировать диаграмму как SVG"
|
||||
@@ -1842,7 +1878,10 @@
|
||||
"next_theme_button": "Попробовать новую тему",
|
||||
"background_effects_message": "На устройствах Windows фоновые эффекты теперь полностью стабильны. Они добавляют цвет в пользовательский интерфейс, размывая фон за ним. Этот приём также используется в других приложениях, например, в проводнике Windows.",
|
||||
"background_effects_title": "Фоновые эффекты теперь стабильны",
|
||||
"next_theme_title": "Попробуйте новую тему Trilium"
|
||||
"next_theme_title": "Попробуйте новую тему Trilium",
|
||||
"new_layout_button": "Подробнее",
|
||||
"new_layout_message": "Мы обновили интерфейс Trilium. Старая лента инструментов была удалена и органично интегрирована в основной интерфейс, а ключевые функции теперь выполняет новая строка состояния и разворачиваемые разделы.\n\nНовый интерфейс включен по умолчанию и может быть временно отключен через «Параметры» → «Внешний вид».",
|
||||
"new_layout_title": "Новый дизайн"
|
||||
},
|
||||
"zoom_factor": {
|
||||
"description": "Масштабированием также можно управлять с помощью сочетаний клавиш CTRL+- и CTRL+=.",
|
||||
@@ -1852,7 +1891,10 @@
|
||||
"show_toc": "Показать оглавление"
|
||||
},
|
||||
"code_mime_types": {
|
||||
"title": "Доступные типы в выпадающем списке"
|
||||
"title": "Доступные типы в выпадающем списке",
|
||||
"tooltip_syntax_highlighting": "Подсветка синтаксиса",
|
||||
"tooltip_code_note_syntax": "Заметки с кодом",
|
||||
"tooltip_code_block_syntax": "Блоки кода в текстовых заметках"
|
||||
},
|
||||
"search_result": {
|
||||
"no_notes_found": "По заданным параметрам поиска заметки не найдены.",
|
||||
@@ -1975,7 +2017,14 @@
|
||||
"deletion_reason": ", поскольку вложение не связано с содержимым заметки. Чтобы предотвратить удаление, добавьте ссылку на вложение обратно в содержимое или преобразуйте вложение в заметку."
|
||||
},
|
||||
"note_title": {
|
||||
"placeholder": "введите здесь название заметки..."
|
||||
"placeholder": "введите здесь название заметки...",
|
||||
"edited_notes": "Измененные заметки",
|
||||
"note_type_switcher_collection": "Коллекция",
|
||||
"note_type_switcher_templates": "Шаблон",
|
||||
"note_type_switcher_others": "Другой тип заметки",
|
||||
"note_type_switcher_label": "Переключить с {{type}} на:",
|
||||
"last_modified": "Изменена <Value />",
|
||||
"created_on": "Создана в <Value />"
|
||||
},
|
||||
"units": {
|
||||
"percentage": "%"
|
||||
@@ -2014,7 +2063,10 @@
|
||||
},
|
||||
"settings_appearance": {
|
||||
"related_code_blocks": "Цветовая схема для блоков кода в текстовых заметках",
|
||||
"related_code_notes": "Цветовая схема для заметок типа \"Код\""
|
||||
"related_code_notes": "Цветовая схема для заметок типа \"Код\"",
|
||||
"ui_new_layout": "Новый дизайн",
|
||||
"ui_old_layout": "Старый дизайн",
|
||||
"ui": "Пользовательский интерфейс"
|
||||
},
|
||||
"sql_result": {
|
||||
"no_rows": "По этому запросу не возвращено ни одной строки"
|
||||
@@ -2024,17 +2076,25 @@
|
||||
},
|
||||
"editable_text": {
|
||||
"placeholder": "Введите содержимое для заметки...",
|
||||
"auto-detect-language": "Определен автоматически"
|
||||
"auto-detect-language": "Определен автоматически",
|
||||
"keeps-crashing": "Компонент редактирования вылетает. Пожалуйста, попробуйте перезапустить Trilium. Если проблема сохраняется, пожалуйста, создайте отчет об ошибке.",
|
||||
"editor_crashed_details_title": "Техническая информация",
|
||||
"editor_crashed_details_intro": "Если эта ошибка возникает несколько раз, пожалуйста, сообщите о ней на GitHub, сопроводив информаций ниже.",
|
||||
"editor_crashed_content": "Ваши данные были успешно восстановлены, но некоторые из последних изменений могли не быть сохранены.",
|
||||
"editor_crashed_details_button": "Подробнее...",
|
||||
"editor_crashed_title": "Возникла ошибка в текстовом редакторе"
|
||||
},
|
||||
"hoisted_note": {
|
||||
"confirm_unhoisting": "Запрошенная заметка «{{requestedNote}}» находится за пределами поддерева закрепленной заметки \"{{hoistedNote}}\", и для доступа к ней необходимо снять закрепление. Открепить заметку?"
|
||||
"confirm_unhoisting": "Запрошенная заметка «{{requestedNote}}» находится за пределами поддерева закрепленной заметки \"{{hoistedNote}}\", и для доступа к ней необходимо снять фокус. Снять фокус с заметки?"
|
||||
},
|
||||
"frontend_script_api": {
|
||||
"sync_warning": "Вы передаете синхронную функцию в `api.runAsyncOnBackendWithManualTransactionHandling()`, \\nхотя вместо этого вам, скорее всего, следует использовать `api.runOnBackend()`.",
|
||||
"async_warning": "Вы передаете асинхронную функцию в `api.runOnBackend()`, которая, скорее всего, не будет работать так, как вы предполагали.\\nЛибо сделайте функцию синхронной (удалив ключевое слово `async`), либо используйте `api.runAsyncOnBackendWithManualTransactionHandling()`."
|
||||
},
|
||||
"note_detail": {
|
||||
"could_not_find_typewidget": "Не удалось найти typeWidget для типа '{{type}}'"
|
||||
"could_not_find_typewidget": "Не удалось найти typeWidget для типа '{{type}}'",
|
||||
"printing_pdf": "Выполняется экспорт PDF...",
|
||||
"printing": "Выполняется печать..."
|
||||
},
|
||||
"book": {
|
||||
"no_children_help": "В этой коллекции нет дочерних заметок, поэтому отображать нечего. Подробности см. в <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">wiki</a>.",
|
||||
@@ -2055,5 +2115,93 @@
|
||||
"pagination": {
|
||||
"total_notes": "{{count}} заметок",
|
||||
"page_title": "Страница {{startIndex}} - {{endIndex}}"
|
||||
},
|
||||
"status_bar": {
|
||||
"attributes_one": "{{count}} атрибут",
|
||||
"attributes_few": "{{count}} атрибута",
|
||||
"attributes_many": "{{count}} атрибутов",
|
||||
"note_info_title": "Просмотр информации о заметке, (даты, размер)",
|
||||
"language_title": "Изменить язык содержимого",
|
||||
"code_note_switcher": "Изменить режим языка",
|
||||
"note_paths_title": "Расположения заметки",
|
||||
"note_paths_one": "{{count}} место",
|
||||
"note_paths_few": "{{count}} места",
|
||||
"note_paths_many": "{{count}} мест",
|
||||
"attributes_title": "Собственные и унаследованные атрибуты",
|
||||
"attachments_title_one": "Открыть вложение в новой вкладке",
|
||||
"attachments_title_few": "Открыть вложения в новой вкладке",
|
||||
"attachments_title_many": "Открыть вложения в новой вкладке",
|
||||
"attachments_one": "{{count}} вложение",
|
||||
"attachments_few": "{{count}} вложения",
|
||||
"attachments_many": "{{count}} вложений",
|
||||
"backlinks_one": "{{count}} обратная ссылка",
|
||||
"backlinks_few": "{{count}} обратные ссылки",
|
||||
"backlinks_many": "{{count}} обратных ссылок",
|
||||
"backlinks_title_one": "Обратная ссылка",
|
||||
"backlinks_title_few": "Обратные ссылки",
|
||||
"backlinks_title_many": "Обратные ссылки"
|
||||
},
|
||||
"breadcrumb_badges": {
|
||||
"execute_sql_description": "Эта заметка - SQL-запрос. Нажмите, чтобы выполнить его.",
|
||||
"execute_sql": "Выполнить SQL",
|
||||
"execute_script_description": "Это заметка содержит скрипт. Нажмите, чтобы выполнить его.",
|
||||
"execute_script": "Выполнить скрипт",
|
||||
"clipped_note_description": "Эта заметка первоначально взята с сайта {{url}}.\n\nНажмите, чтобы перейти на исходную веб-страницу.",
|
||||
"shared_publicly": "Доступно публично",
|
||||
"shared_locally": "Доступно локально",
|
||||
"clipped_note": "Web фрагмент",
|
||||
"shared_unshare": "Убрать публичный доступ",
|
||||
"shared_open_in_browser": "Открыть ссылку в браузере",
|
||||
"shared_copy_to_clipboard": "Скопировать ссылку",
|
||||
"read_only_temporarily_disabled_description": "В данный момент эта заметка доступна для редактирования, но обычно она находится только в режиме чтения. Заметка снова станет доступна только для чтения, как только вы перейдете к другой заметке.\n\nНажмите, чтобы снова включить режим только для чтения.",
|
||||
"read_only_temporarily_disabled": "Временное редактирование",
|
||||
"read_only_auto_description": "Эта заметка была автоматически переведена в режим только для чтения по соображениям производительности. Это автоматическое ограничение можно изменить в настройках.\n\nНажмите, чтобы временно отредактировать её.",
|
||||
"read_only_auto": "Автоматический режим \"только для чтения\"",
|
||||
"read_only_explicit_description": "Эта заметка была вручную установлена в режим «только для чтения».\nНажмите, чтобы временно отредактировать её.",
|
||||
"read_only_explicit": "Только для чтения"
|
||||
},
|
||||
"breadcrumb": {
|
||||
"hoisted_badge_title": "Снять фокус",
|
||||
"hoisted_badge": "Фокус",
|
||||
"empty_hide_archived_notes": "Скрыть заметки в архиве",
|
||||
"create_new_note": "Новая дочерняя заметка",
|
||||
"scroll_to_top_title": "К началу заметки",
|
||||
"workspace_badge": "Рабочее пространство"
|
||||
},
|
||||
"tab_history_navigation_buttons": {
|
||||
"go-forward": "Перейти к следующей заметке",
|
||||
"go-back": "Перейти к предыдущей заметке"
|
||||
},
|
||||
"server": {
|
||||
"traefik_blocks_requests": "Если вы используете обратный прокси-сервер Traefik, то следует учитывать, что в него внесены критические изменения, влияющие на связь с сервером.",
|
||||
"unknown_http_error_content": "Код: {{statusCode}}\nURL: {{method}} {{url}}\nСообщение: {{message}}",
|
||||
"unknown_http_error_title": "Ошибка связи с сервером"
|
||||
},
|
||||
"note-color": {
|
||||
"set-color": "Установить цвет заметки",
|
||||
"clear-color": "Убрать цвет заметки",
|
||||
"set-custom-color": "Установить другой цвет"
|
||||
},
|
||||
"calendar_view": {
|
||||
"delete_note": "Удалить заметку..."
|
||||
},
|
||||
"presentation_view": {
|
||||
"start-presentation": "Начать презентацию",
|
||||
"edit-slide": "Редактировать слайд",
|
||||
"slide-overview": "Переключить общий просмотр слайдов"
|
||||
},
|
||||
"read-only-info": {
|
||||
"edit-note": "Изменить заметку",
|
||||
"auto-read-only-note": "Заметка отображена в режиме \"только для чтения\" для быстрой загрузки.",
|
||||
"read-only-note": "Заметка отображается в режиме \"только для чтения\"."
|
||||
},
|
||||
"experimental_features": {
|
||||
"new_layout_description": "Попробуйте новый современный и удобный дизайн. В будущих обновлениях возможны его существенные изменения.",
|
||||
"new_layout_name": "Новый дизайн",
|
||||
"title": "Экспериментальные параметры",
|
||||
"disclaimer": "Эти параметры экспериментальные и могут повлиять на стабильность. Используйте с осторожностью."
|
||||
},
|
||||
"popup-editor": {
|
||||
"maximize": "Переключить на полный редактор"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +205,8 @@
|
||||
"info": {
|
||||
"modalTitle": "資訊消息",
|
||||
"closeButton": "關閉",
|
||||
"okButton": "確定"
|
||||
"okButton": "確定",
|
||||
"copy_to_clipboard": "複製到剪貼簿"
|
||||
},
|
||||
"jump_to_note": {
|
||||
"search_button": "全文搜尋",
|
||||
@@ -986,7 +987,12 @@
|
||||
"editable_text": {
|
||||
"placeholder": "在這裡輸入您的筆記內容…",
|
||||
"auto-detect-language": "自動檢測",
|
||||
"keeps-crashing": "編輯元件持續發生崩潰。請嘗試重新啟動 Trilium。若問題仍存在,請考慮提交錯誤報告。"
|
||||
"keeps-crashing": "編輯元件持續發生崩潰。請嘗試重新啟動 Trilium。若問題仍存在,請考慮提交錯誤報告。",
|
||||
"editor_crashed_title": "文字編輯器崩潰",
|
||||
"editor_crashed_content": "您的內容已成功恢復,但最近的幾項變更可能未被儲存。",
|
||||
"editor_crashed_details_button": "檢視更多資訊⋯",
|
||||
"editor_crashed_details_intro": "若您多次遇到此錯誤,請考慮在 GitHub 回報以下資訊。",
|
||||
"editor_crashed_details_title": "技術資訊"
|
||||
},
|
||||
"empty": {
|
||||
"open_note_instruction": "透過在下面的輸入框中輸入筆記標題或在樹中選擇筆記來打開筆記。",
|
||||
@@ -1531,7 +1537,9 @@
|
||||
"printing_pdf": "正在匯出為 PDF…"
|
||||
},
|
||||
"note_title": {
|
||||
"placeholder": "請輸入筆記標題..."
|
||||
"placeholder": "請輸入筆記標題...",
|
||||
"created_on": "建立於 {{date}}",
|
||||
"last_modified": "最後修改於 {{date}}"
|
||||
},
|
||||
"search_result": {
|
||||
"no_notes_found": "沒有找到符合搜尋條件的筆記。",
|
||||
@@ -2106,5 +2114,26 @@
|
||||
},
|
||||
"popup-editor": {
|
||||
"maximize": "切換至完整編輯器"
|
||||
},
|
||||
"experimental_features": {
|
||||
"title": "實驗性選項",
|
||||
"disclaimer": "這些選項屬實驗性質,可能導致系統不穩定。請謹慎使用。",
|
||||
"new_layout_name": "新版面配置",
|
||||
"new_layout_description": "體驗全新版面配置,呈現更現代的外觀與更佳的使用體驗。在未來版本將進行大幅調整。"
|
||||
},
|
||||
"server": {
|
||||
"unknown_http_error_title": "與伺服器通訊錯誤",
|
||||
"unknown_http_error_content": "狀態碼:{{statusCode}}\n網址:{{method}} {{url}}\n訊息:{{message}}",
|
||||
"traefik_blocks_requests": "若您正在使用 Traefik 反向代理,該代理已引入一項重大變更影響與伺服器的通訊。"
|
||||
},
|
||||
"tab_history_navigation_buttons": {
|
||||
"go-back": "返回前一筆記",
|
||||
"go-forward": "前往下一筆記"
|
||||
},
|
||||
"breadcrumb_badges": {
|
||||
"read_only_explicit": "唯讀",
|
||||
"read_only_auto": "自動唯讀",
|
||||
"shared_publicly": "公開分享",
|
||||
"shared_locally": "本地分享"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
import "./Breadcrumb.css";
|
||||
|
||||
import { useMemo } from "preact/hooks";
|
||||
import { Fragment } from "preact/jsx-runtime";
|
||||
|
||||
import NoteContext from "../components/note_context";
|
||||
import froca from "../services/froca";
|
||||
import ActionButton from "./react/ActionButton";
|
||||
import Dropdown from "./react/Dropdown";
|
||||
import { FormListItem } from "./react/FormList";
|
||||
import { useChildNotes, useNoteContext, useNoteLabel, useNoteProperty } from "./react/hooks";
|
||||
import Icon from "./react/Icon";
|
||||
import NoteLink from "./react/NoteLink";
|
||||
import link_context_menu from "../menus/link_context_menu";
|
||||
|
||||
const COLLAPSE_THRESHOLD = 5;
|
||||
const INITIAL_ITEMS = 2;
|
||||
const FINAL_ITEMS = 2;
|
||||
|
||||
export default function Breadcrumb() {
|
||||
const { note, noteContext } = useNoteContext();
|
||||
const notePath = buildNotePaths(noteContext?.notePathArray);
|
||||
|
||||
return (
|
||||
<div className="breadcrumb">
|
||||
{notePath.length > COLLAPSE_THRESHOLD ? (
|
||||
<>
|
||||
{notePath.slice(0, INITIAL_ITEMS).map((item, index) => (
|
||||
<Fragment key={item}>
|
||||
{index === 0
|
||||
? <BreadcrumbRoot noteContext={noteContext} />
|
||||
: <BreadcrumbItem notePath={item} />
|
||||
}
|
||||
<BreadcrumbSeparator notePath={item} activeNotePath={notePath[index + 1]} noteContext={noteContext} />
|
||||
</Fragment>
|
||||
))}
|
||||
<BreadcrumbCollapsed items={notePath.slice(INITIAL_ITEMS, -FINAL_ITEMS)} noteContext={noteContext} />
|
||||
{notePath.slice(-FINAL_ITEMS).map((item, index) => (
|
||||
<Fragment key={item}>
|
||||
<BreadcrumbSeparator notePath={notePath[notePath.length - FINAL_ITEMS - (1 - index)]} activeNotePath={item} noteContext={noteContext} />
|
||||
<BreadcrumbItem notePath={item} />
|
||||
</Fragment>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
notePath.map((item, index) => (
|
||||
<Fragment key={item}>
|
||||
{index === 0
|
||||
? <BreadcrumbRoot noteContext={noteContext} />
|
||||
: <BreadcrumbItem notePath={item} />
|
||||
}
|
||||
{(index < notePath.length - 1 || note?.hasChildren()) &&
|
||||
<BreadcrumbSeparator notePath={item} activeNotePath={notePath[index + 1]} noteContext={noteContext} />}
|
||||
</Fragment>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function BreadcrumbRoot({ noteContext }: { noteContext: NoteContext | undefined }) {
|
||||
const note = useMemo(() => froca.getNoteFromCache("root"), []);
|
||||
useNoteLabel(note, "iconClass");
|
||||
const title = useNoteProperty(note, "title");
|
||||
|
||||
return (note &&
|
||||
<ActionButton
|
||||
icon={note.getIcon()}
|
||||
text={title ?? ""}
|
||||
onClick={() => noteContext?.setNote("root")}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
link_context_menu.openContextMenu(note.noteId, e);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function BreadcrumbItem({ notePath }: { notePath: string }) {
|
||||
return (
|
||||
<NoteLink
|
||||
notePath={notePath}
|
||||
noPreview
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function BreadcrumbSeparator({ notePath, noteContext, activeNotePath }: { notePath: string, activeNotePath: string, noteContext: NoteContext | undefined }) {
|
||||
return (
|
||||
<Dropdown
|
||||
text={<Icon icon="bx bx-chevron-right" />}
|
||||
noSelectButtonStyle
|
||||
buttonClassName="icon-action"
|
||||
hideToggleArrow
|
||||
dropdownOptions={{ popperConfig: { strategy: "fixed" } }}
|
||||
>
|
||||
<BreadcrumbSeparatorDropdownContent notePath={notePath} noteContext={noteContext} activeNotePath={activeNotePath} />
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
function BreadcrumbSeparatorDropdownContent({ notePath, noteContext, activeNotePath }: { notePath: string, activeNotePath: string, noteContext: NoteContext | undefined }) {
|
||||
const notePathComponents = notePath.split("/");
|
||||
const parentNoteId = notePathComponents.at(-1);
|
||||
const childNotes = useChildNotes(parentNoteId);
|
||||
|
||||
return (
|
||||
<ul className="breadcrumb-child-list">
|
||||
{childNotes.map((note) => {
|
||||
const childNotePath = `${notePath}/${note.noteId}`;
|
||||
return <li key={note.noteId}>
|
||||
<FormListItem
|
||||
icon={note.getIcon()}
|
||||
onClick={() => noteContext?.setNote(childNotePath)}
|
||||
>
|
||||
{childNotePath !== activeNotePath
|
||||
? <span>{note.title}</span>
|
||||
: <strong>{note.title}</strong>}
|
||||
</FormListItem>
|
||||
</li>;
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
function BreadcrumbCollapsed({ items, noteContext }: { items: string[], noteContext: NoteContext | undefined }) {
|
||||
return (
|
||||
<Dropdown
|
||||
text={<Icon icon="bx bx-dots-horizontal-rounded" />}
|
||||
noSelectButtonStyle
|
||||
buttonClassName="icon-action"
|
||||
hideToggleArrow
|
||||
dropdownOptions={{ popperConfig: { strategy: "fixed" } }}
|
||||
>
|
||||
<ul className="breadcrumb-child-list">
|
||||
{items.map((notePath) => {
|
||||
const notePathComponents = notePath.split("/");
|
||||
const noteId = notePathComponents[notePathComponents.length - 1];
|
||||
const note = froca.getNoteFromCache(noteId);
|
||||
if (!note) return null;
|
||||
|
||||
return <li key={note.noteId}>
|
||||
<FormListItem
|
||||
icon={note.getIcon()}
|
||||
onClick={() => noteContext?.setNote(notePath)}
|
||||
>
|
||||
<span>{note.title}</span>
|
||||
</FormListItem>
|
||||
</li>;
|
||||
})}
|
||||
</ul>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
function buildNotePaths(notePathArray: string[] | undefined) {
|
||||
if (!notePathArray) return [];
|
||||
|
||||
let prefix = "";
|
||||
const output: string[] = [];
|
||||
for (const notePath of notePathArray) {
|
||||
output.push(`${prefix}${notePath}`);
|
||||
prefix += `${notePath}/`;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
@@ -6,12 +6,11 @@
|
||||
.floating-buttons-children,
|
||||
.show-floating-buttons {
|
||||
position: absolute;
|
||||
top: calc(var(--floating-buttons-vert-offset, 14px) + var(--ribbon-height, 0px) + var(--content-header-height, 0px));
|
||||
top: var(--floating-buttons-vert-offset, 14px);
|
||||
inset-inline-end: var(--floating-buttons-horiz-offset, 10px);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
z-index: 100;
|
||||
transition: top 0.3s ease;
|
||||
}
|
||||
|
||||
.note-split.rtl .floating-buttons-children,
|
||||
|
||||
@@ -48,6 +48,12 @@ export default function FloatingButtons({ items }: FloatingButtonsProps) {
|
||||
const [ visible, setVisible ] = useState(true);
|
||||
useEffect(() => setVisible(true), [ note ]);
|
||||
|
||||
useTriliumEvent("contentSafeMarginChanged", (e) => {
|
||||
if (e.noteContext === noteContext) {
|
||||
setTop(e.top);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="floating-buttons no-print" style={{top}}>
|
||||
<div className={`floating-buttons-children ${!visible ? "temporarily-hidden" : ""}`}>
|
||||
@@ -87,9 +93,9 @@ function CloseFloatingButton({ setVisible }: { setVisible(visible: boolean): voi
|
||||
className="close-floating-buttons-button"
|
||||
icon="bx bx-chevrons-right"
|
||||
text={t("hide_floating_buttons_button.button_title")}
|
||||
onClick={() => setVisible(false)}
|
||||
onClick={() => setVisible(false)}
|
||||
noIconActionClass
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,27 @@
|
||||
import { BacklinkCountResponse, BacklinksResponse, SaveSqlConsoleResponse } from "@triliumnext/commons";
|
||||
import { VNode } from "preact";
|
||||
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import appContext, { EventData, EventNames } from "../components/app_context";
|
||||
import Component from "../components/component";
|
||||
import NoteContext from "../components/note_context";
|
||||
import FNote from "../entities/fnote";
|
||||
import ActionButton, { ActionButtonProps } from "./react/ActionButton";
|
||||
import { useIsNoteReadOnly, useNoteLabelBoolean, useTriliumEvent, useTriliumOption, useWindowSize } from "./react/hooks";
|
||||
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import { createImageSrcUrl, openInAppHelpFromUrl } from "../services/utils";
|
||||
import server from "../services/server";
|
||||
import { BacklinkCountResponse, BacklinksResponse, SaveSqlConsoleResponse } from "@triliumnext/commons";
|
||||
import toast from "../services/toast";
|
||||
import attributes from "../services/attributes";
|
||||
import { isExperimentalFeatureEnabled } from "../services/experimental_features";
|
||||
import froca from "../services/froca";
|
||||
import { t } from "../services/i18n";
|
||||
import { copyImageReferenceToClipboard } from "../services/image";
|
||||
import tree from "../services/tree";
|
||||
import { getHelpUrlForNote } from "../services/in_app_help";
|
||||
import froca from "../services/froca";
|
||||
import LoadResults from "../services/load_results";
|
||||
import server from "../services/server";
|
||||
import toast from "../services/toast";
|
||||
import tree from "../services/tree";
|
||||
import { createImageSrcUrl, openInAppHelpFromUrl } from "../services/utils";
|
||||
import { ViewTypeOptions } from "./collections/interface";
|
||||
import ActionButton, { ActionButtonProps } from "./react/ActionButton";
|
||||
import { useIsNoteReadOnly, useNoteLabelBoolean, useTriliumEvent, useTriliumOption, useWindowSize } from "./react/hooks";
|
||||
import NoteLink from "./react/NoteLink";
|
||||
import RawHtml from "./react/RawHtml";
|
||||
import { ViewTypeOptions } from "./collections/interface";
|
||||
import attributes from "../services/attributes";
|
||||
import LoadResults from "../services/load_results";
|
||||
|
||||
export interface FloatingButtonContext {
|
||||
parentComponent: Component;
|
||||
@@ -37,7 +39,7 @@ function FloatingButton({ className, ...props }: ActionButtonProps) {
|
||||
className={`floating-button ${className ?? ""}`}
|
||||
noIconActionClass
|
||||
{...props}
|
||||
/>
|
||||
/>;
|
||||
}
|
||||
|
||||
export type FloatingButtonsList = ((context: FloatingButtonContext) => false | VNode)[];
|
||||
@@ -82,7 +84,7 @@ function RefreshBackendLogButton({ note, parentComponent, noteContext, isDefault
|
||||
text={t("backend_log.refresh")}
|
||||
icon="bx bx-refresh"
|
||||
onClick={() => parentComponent.triggerEvent("refreshData", { ntxId: noteContext.ntxId })}
|
||||
/>
|
||||
/>;
|
||||
}
|
||||
|
||||
function SwitchSplitOrientationButton({ note, isReadOnly, isDefaultViewMode }: FloatingButtonContext) {
|
||||
@@ -94,7 +96,7 @@ function SwitchSplitOrientationButton({ note, isReadOnly, isDefaultViewMode }: F
|
||||
text={upcomingOrientation === "vertical" ? t("switch_layout_button.title_vertical") : t("switch_layout_button.title_horizontal")}
|
||||
icon={upcomingOrientation === "vertical" ? "bx bxs-dock-bottom" : "bx bxs-dock-left"}
|
||||
onClick={() => setSplitEditorOrientation(upcomingOrientation)}
|
||||
/>
|
||||
/>;
|
||||
}
|
||||
|
||||
function ToggleReadOnlyButton({ note, viewType, isDefaultViewMode }: FloatingButtonContext) {
|
||||
@@ -106,7 +108,7 @@ function ToggleReadOnlyButton({ note, viewType, isDefaultViewMode }: FloatingBut
|
||||
text={isReadOnly ? t("toggle_read_only_button.unlock-editing") : t("toggle_read_only_button.lock-editing")}
|
||||
icon={isReadOnly ? "bx bx-lock-open-alt" : "bx bx-lock-alt"}
|
||||
onClick={() => setReadOnly(!isReadOnly)}
|
||||
/>
|
||||
/>;
|
||||
}
|
||||
|
||||
function EditButton({ note, noteContext }: FloatingButtonContext) {
|
||||
@@ -129,7 +131,7 @@ function EditButton({ note, noteContext }: FloatingButtonContext) {
|
||||
icon="bx bx-pencil"
|
||||
className={animationClass}
|
||||
onClick={() => enableEditing()}
|
||||
/>
|
||||
/>;
|
||||
}
|
||||
|
||||
function ShowTocWidgetButton({ note, noteContext, isDefaultViewMode }: FloatingButtonContext) {
|
||||
@@ -147,7 +149,7 @@ function ShowTocWidgetButton({ note, noteContext, isDefaultViewMode }: FloatingB
|
||||
appContext.triggerEvent("showTocWidget", { noteId: noteContext.noteId });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
/>;
|
||||
}
|
||||
|
||||
function ShowHighlightsListWidgetButton({ note, noteContext, isDefaultViewMode }: FloatingButtonContext) {
|
||||
@@ -165,16 +167,16 @@ function ShowHighlightsListWidgetButton({ note, noteContext, isDefaultViewMode }
|
||||
appContext.triggerEvent("showHighlightsListWidget", { noteId: noteContext.noteId });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
/>;
|
||||
}
|
||||
|
||||
function RunActiveNoteButton({ note }: FloatingButtonContext) {
|
||||
const isEnabled = note.mime.startsWith("application/javascript") || note.mime === "text/x-sqlite;schema=trilium";
|
||||
const isEnabled = (note.mime.startsWith("application/javascript") || note.mime === "text/x-sqlite;schema=trilium");
|
||||
return isEnabled && <FloatingButton
|
||||
icon="bx bx-play"
|
||||
text={t("code_buttons.execute_button_title")}
|
||||
triggerCommand="runActiveNote"
|
||||
/>
|
||||
/>;
|
||||
}
|
||||
|
||||
function OpenTriliumApiDocsButton({ note }: FloatingButtonContext) {
|
||||
@@ -183,7 +185,7 @@ function OpenTriliumApiDocsButton({ note }: FloatingButtonContext) {
|
||||
icon="bx bx-help-circle"
|
||||
text={t("code_buttons.trilium_api_docs_button_title")}
|
||||
onClick={() => openInAppHelpFromUrl(note.mime.endsWith("frontend") ? "Q2z6av6JZVWm" : "MEtfsqa5VwNi")}
|
||||
/>
|
||||
/>;
|
||||
}
|
||||
|
||||
function SaveToNoteButton({ note }: FloatingButtonContext) {
|
||||
@@ -191,17 +193,21 @@ function SaveToNoteButton({ note }: FloatingButtonContext) {
|
||||
return isEnabled && <FloatingButton
|
||||
icon="bx bx-save"
|
||||
text={t("code_buttons.save_to_note_button_title")}
|
||||
onClick={async (e) => {
|
||||
e.preventDefault();
|
||||
const { notePath } = await server.post<SaveSqlConsoleResponse>("special-notes/save-sql-console", { sqlConsoleNoteId: note.noteId });
|
||||
if (notePath) {
|
||||
toast.showMessage(t("code_buttons.sql_console_saved_message", { "note_path": await tree.getNotePathTitle(notePath) }));
|
||||
// TODO: This hangs the navigation, for some reason.
|
||||
//await ws.waitForMaxKnownEntityChangeId();
|
||||
await appContext.tabManager.getActiveContext()?.setNote(notePath);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
onClick={buildSaveSqlToNoteHandler(note)}
|
||||
/>;
|
||||
}
|
||||
|
||||
export function buildSaveSqlToNoteHandler(note: FNote) {
|
||||
return async (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
const { notePath } = await server.post<SaveSqlConsoleResponse>("special-notes/save-sql-console", { sqlConsoleNoteId: note.noteId });
|
||||
if (notePath) {
|
||||
toast.showMessage(t("code_buttons.sql_console_saved_message", { "note_path": await tree.getNotePathTitle(notePath) }));
|
||||
// TODO: This hangs the navigation, for some reason.
|
||||
//await ws.waitForMaxKnownEntityChangeId();
|
||||
await appContext.tabManager.getActiveContext()?.setNote(notePath);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function RelationMapButtons({ note, isDefaultViewMode, triggerEvent }: FloatingButtonContext) {
|
||||
@@ -234,7 +240,7 @@ function RelationMapButtons({ note, isDefaultViewMode, triggerEvent }: FloatingB
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function GeoMapButtons({ triggerEvent, viewType, isReadOnly }: FloatingButtonContext) {
|
||||
@@ -250,8 +256,10 @@ function GeoMapButtons({ triggerEvent, viewType, isReadOnly }: FloatingButtonCon
|
||||
|
||||
function CopyImageReferenceButton({ note, isDefaultViewMode }: FloatingButtonContext) {
|
||||
const hiddenImageCopyRef = useRef<HTMLDivElement>(null);
|
||||
const isEnabled = ["mermaid", "canvas", "mindMap", "image"].includes(note?.type ?? "")
|
||||
&& note?.isContentAvailable() && isDefaultViewMode;
|
||||
const isEnabled = (
|
||||
["mermaid", "canvas", "mindMap", "image"].includes(note?.type ?? "")
|
||||
&& note?.isContentAvailable() && isDefaultViewMode
|
||||
);
|
||||
|
||||
return isEnabled && (
|
||||
<>
|
||||
@@ -272,7 +280,7 @@ function CopyImageReferenceButton({ note, isDefaultViewMode }: FloatingButtonCon
|
||||
position: "absolute" // Take out of the the hidden image from flexbox to prevent the layout being affected
|
||||
}} />
|
||||
</>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ExportImageButtons({ note, triggerEvent, isDefaultViewMode }: FloatingButtonContext) {
|
||||
@@ -292,38 +300,26 @@ function ExportImageButtons({ note, triggerEvent, isDefaultViewMode }: FloatingB
|
||||
onClick={() => triggerEvent("exportPng")}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function InAppHelpButton({ note }: FloatingButtonContext) {
|
||||
const helpUrl = getHelpUrlForNote(note);
|
||||
const isEnabled = !!helpUrl;
|
||||
|
||||
return !!helpUrl && (
|
||||
return isEnabled && (
|
||||
<FloatingButton
|
||||
icon="bx bx-help-circle"
|
||||
text={t("help-button.title")}
|
||||
onClick={() => helpUrl && openInAppHelpFromUrl(helpUrl)}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function Backlinks({ note, isDefaultViewMode }: FloatingButtonContext) {
|
||||
let [ backlinkCount, setBacklinkCount ] = useState(0);
|
||||
let [ popupOpen, setPopupOpen ] = useState(false);
|
||||
const [ popupOpen, setPopupOpen ] = useState(false);
|
||||
const backlinksContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
function refresh() {
|
||||
if (!isDefaultViewMode) return;
|
||||
|
||||
server.get<BacklinkCountResponse>(`note-map/${note.noteId}/backlink-count`).then(resp => {
|
||||
setBacklinkCount(resp.count);
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => refresh(), [ note ]);
|
||||
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
|
||||
if (needsRefresh(note, loadResults)) refresh();
|
||||
});
|
||||
const backlinkCount = useBacklinkCount(note, isDefaultViewMode);
|
||||
|
||||
// Determine the max height of the container.
|
||||
const { windowHeight } = useWindowSize();
|
||||
@@ -355,15 +351,34 @@ function Backlinks({ note, isDefaultViewMode }: FloatingButtonContext) {
|
||||
);
|
||||
}
|
||||
|
||||
function BacklinksList({ note }: { note: FNote }) {
|
||||
export function useBacklinkCount(note: FNote | null | undefined, isDefaultViewMode: boolean) {
|
||||
const [ backlinkCount, setBacklinkCount ] = useState(0);
|
||||
|
||||
const refresh = useCallback(() => {
|
||||
if (!note || !isDefaultViewMode) return;
|
||||
|
||||
server.get<BacklinkCountResponse>(`note-map/${note.noteId}/backlink-count`).then(resp => {
|
||||
setBacklinkCount(resp.count);
|
||||
});
|
||||
}, [ isDefaultViewMode, note ]);
|
||||
|
||||
useEffect(() => refresh(), [ refresh ]);
|
||||
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
|
||||
if (note && needsRefresh(note, loadResults)) refresh();
|
||||
});
|
||||
|
||||
return backlinkCount;
|
||||
}
|
||||
|
||||
export function BacklinksList({ note }: { note: FNote }) {
|
||||
const [ backlinks, setBacklinks ] = useState<BacklinksResponse>([]);
|
||||
|
||||
function refresh() {
|
||||
server.get<BacklinksResponse>(`note-map/${note.noteId}/backlinks`).then(async (backlinks) => {
|
||||
// prefetch all
|
||||
const noteIds = backlinks
|
||||
.filter(bl => "noteId" in bl)
|
||||
.map((bl) => bl.noteId);
|
||||
.filter(bl => "noteId" in bl)
|
||||
.map((bl) => bl.noteId);
|
||||
await froca.getNotes(noteIds);
|
||||
setBacklinks(backlinks);
|
||||
});
|
||||
@@ -375,7 +390,7 @@ function BacklinksList({ note }: { note: FNote }) {
|
||||
});
|
||||
|
||||
return backlinks.map(backlink => (
|
||||
<div>
|
||||
<li>
|
||||
<NoteLink
|
||||
notePath={backlink.noteId}
|
||||
showNotePath showNoteIcon
|
||||
@@ -389,7 +404,7 @@ function BacklinksList({ note }: { note: FNote }) {
|
||||
<RawHtml html={excerpt} />
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import { useNoteContext, useTriliumEvent } from "./react/hooks"
|
||||
import FNote from "../entities/fnote";
|
||||
import protected_session_holder from "../services/protected_session_holder";
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
import NoteContext from "../components/note_context";
|
||||
import { isValidElement, VNode } from "preact";
|
||||
import { TypeWidgetProps } from "./type_widgets/type_widget";
|
||||
import "./NoteDetail.css";
|
||||
|
||||
import { isValidElement, VNode } from "preact";
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import NoteContext from "../components/note_context";
|
||||
import FNote from "../entities/fnote";
|
||||
import attributes from "../services/attributes";
|
||||
import { ExtendedNoteType, TYPE_MAPPINGS, TypeWidget } from "./note_types";
|
||||
import { dynamicRequire, isElectron, isMobile } from "../services/utils";
|
||||
import toast from "../services/toast.js";
|
||||
import { t } from "../services/i18n";
|
||||
import protected_session_holder from "../services/protected_session_holder";
|
||||
import toast from "../services/toast.js";
|
||||
import { dynamicRequire, isElectron, isMobile } from "../services/utils";
|
||||
import { ExtendedNoteType, TYPE_MAPPINGS, TypeWidget } from "./note_types";
|
||||
import { useNoteContext, useTriliumEvent } from "./react/hooks";
|
||||
import { TypeWidgetProps } from "./type_widgets/type_widget";
|
||||
|
||||
/**
|
||||
* The note detail is in charge of rendering the content of a note, by determining its type (e.g. text, code) and using the appropriate view widget.
|
||||
@@ -80,7 +82,7 @@ export default function NoteDetail() {
|
||||
parentComponent.handleEvent("noteTypeMimeChanged", { noteId: note.noteId });
|
||||
} else if (note.noteId
|
||||
&& loadResults.isNoteReloaded(note.noteId, parentComponent.componentId)
|
||||
&& (type !== (await getWidgetType(note, noteContext)) || mime !== note?.mime)) {
|
||||
&& (type !== (await getExtendedWidgetType(note, noteContext)) || mime !== note?.mime)) {
|
||||
// this needs to have a triggerEvent so that e.g., note type (not in the component subtree) is updated
|
||||
parentComponent.triggerEvent("noteTypeMimeChanged", { noteId: note.noteId });
|
||||
} else {
|
||||
@@ -212,7 +214,7 @@ export default function NoteDetail() {
|
||||
isVisible={type === itemType}
|
||||
isFullHeight={isFullHeight}
|
||||
props={props}
|
||||
/>
|
||||
/>;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
@@ -254,7 +256,7 @@ function useNoteInfo() {
|
||||
const [ mime, setMime ] = useState<string>();
|
||||
|
||||
function refresh() {
|
||||
getWidgetType(actualNote, noteContext).then(type => {
|
||||
getExtendedWidgetType(actualNote, noteContext).then(type => {
|
||||
setNote(actualNote);
|
||||
setType(type);
|
||||
setMime(actualNote?.mime);
|
||||
@@ -282,12 +284,12 @@ async function getCorrespondingWidget(type: ExtendedNoteType): Promise<null | Ty
|
||||
} else if (isValidElement(result)) {
|
||||
// Direct VNode provided.
|
||||
return result;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
async function getWidgetType(note: FNote | null | undefined, noteContext: NoteContext | undefined): Promise<ExtendedNoteType | undefined> {
|
||||
export async function getExtendedWidgetType(note: FNote | null | undefined, noteContext: NoteContext | undefined): Promise<ExtendedNoteType | undefined> {
|
||||
if (!noteContext) return undefined;
|
||||
if (!note) {
|
||||
// If the note is null, then it's a new tab. If it's undefined, then it's not loaded yet.
|
||||
@@ -299,8 +301,10 @@ async function getWidgetType(note: FNote | null | undefined, noteContext: NoteCo
|
||||
|
||||
if (noteContext?.viewScope?.viewMode === "source") {
|
||||
resultingType = "readOnlyCode";
|
||||
} else if (noteContext?.viewScope && noteContext.viewScope.viewMode === "attachments") {
|
||||
} else if (noteContext.viewScope?.viewMode === "attachments") {
|
||||
resultingType = noteContext.viewScope.attachmentId ? "attachmentDetail" : "attachmentList";
|
||||
} else if (noteContext.viewScope?.viewMode === "note-map") {
|
||||
resultingType = "noteMap";
|
||||
} else if (type === "text" && (await noteContext?.isReadOnly())) {
|
||||
resultingType = "readOnlyText";
|
||||
} else if ((type === "code" || type === "mermaid") && (await noteContext?.isReadOnly())) {
|
||||
@@ -322,7 +326,7 @@ async function getWidgetType(note: FNote | null | undefined, noteContext: NoteCo
|
||||
return resultingType;
|
||||
}
|
||||
|
||||
function checkFullHeight(noteContext: NoteContext | undefined, type: ExtendedNoteType | undefined) {
|
||||
export function checkFullHeight(noteContext: NoteContext | undefined, type: ExtendedNoteType | undefined) {
|
||||
if (!noteContext) return false;
|
||||
|
||||
// https://github.com/zadam/trilium/issues/2522
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
import { Dispatch, StateUpdater, useEffect, useRef, useState } from "preact/hooks";
|
||||
import "./PromotedAttributes.css";
|
||||
import { useNoteContext, useNoteLabel, useTriliumEvent, useUniqueName } from "./react/hooks";
|
||||
import { Attribute } from "../services/attribute_parser";
|
||||
import FAttribute from "../entities/fattribute";
|
||||
|
||||
import { UpdateAttributeResponse } from "@triliumnext/commons";
|
||||
import clsx from "clsx";
|
||||
import { ComponentChild, HTMLInputTypeAttribute, InputHTMLAttributes, MouseEventHandler, TargetedEvent, TargetedInputEvent } from "preact";
|
||||
import { Dispatch, StateUpdater, useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import FAttribute from "../entities/fattribute";
|
||||
import FNote from "../entities/fnote";
|
||||
import { Attribute } from "../services/attribute_parser";
|
||||
import attributes from "../services/attributes";
|
||||
import debounce from "../services/debounce";
|
||||
import { t } from "../services/i18n";
|
||||
import { DefinitionObject, extractAttributeDefinitionTypeAndName, LabelType } from "../services/promoted_attribute_definition_parser";
|
||||
import server from "../services/server";
|
||||
import FNote from "../entities/fnote";
|
||||
import { ComponentChild, HTMLInputTypeAttribute, InputHTMLAttributes, MouseEventHandler, TargetedEvent, TargetedInputEvent } from "preact";
|
||||
import NoteAutocomplete from "./react/NoteAutocomplete";
|
||||
import ws from "../services/ws";
|
||||
import { UpdateAttributeResponse } from "@triliumnext/commons";
|
||||
import attributes from "../services/attributes";
|
||||
import debounce from "../services/debounce";
|
||||
import { useNoteContext, useNoteLabel, useTriliumEvent, useUniqueName } from "./react/hooks";
|
||||
import NoteAutocomplete from "./react/NoteAutocomplete";
|
||||
|
||||
interface Cell {
|
||||
uniqueId: string;
|
||||
@@ -39,6 +41,15 @@ type OnChangeListener = (e: OnChangeEventData) => Promise<void>;
|
||||
export default function PromotedAttributes() {
|
||||
const { note, componentId } = useNoteContext();
|
||||
const [ cells, setCells ] = usePromotedAttributeData(note, componentId);
|
||||
return <PromotedAttributesContent note={note} componentId={componentId} cells={cells} setCells={setCells} />;
|
||||
}
|
||||
|
||||
export function PromotedAttributesContent({ note, componentId, cells, setCells }: {
|
||||
note: FNote | null | undefined;
|
||||
componentId: string;
|
||||
cells: Cell[] | undefined;
|
||||
setCells: Dispatch<StateUpdater<Cell[] | undefined>>;
|
||||
}) {
|
||||
const [ cellToFocus, setCellToFocus ] = useState<Cell>();
|
||||
|
||||
return (
|
||||
@@ -62,7 +73,7 @@ export default function PromotedAttributes() {
|
||||
*
|
||||
* The cells are returned as a state since they can also be altered internally if needed, for example to add a new empty cell.
|
||||
*/
|
||||
function usePromotedAttributeData(note: FNote | null | undefined, componentId: string): [ Cell[] | undefined, Dispatch<StateUpdater<Cell[] | undefined>> ] {
|
||||
export function usePromotedAttributeData(note: FNote | null | undefined, componentId: string): [ Cell[] | undefined, Dispatch<StateUpdater<Cell[] | undefined>> ] {
|
||||
const [ viewType ] = useNoteLabel(note, "viewType");
|
||||
const [ cells, setCells ] = useState<Cell[]>();
|
||||
|
||||
@@ -156,7 +167,7 @@ function PromotedAttributeCell(props: CellProps) {
|
||||
{correspondingInput}
|
||||
<MultiplicityCell {...props} />
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const LABEL_MAPPINGS: Record<LabelType, HTMLInputTypeAttribute> = {
|
||||
@@ -219,29 +230,29 @@ function LabelInput({ inputId, ...props }: CellProps & { inputId: string }) {
|
||||
<label className="tn-checkbox">{inputNode}</label>
|
||||
</div>
|
||||
<label for={inputId}>{definition.promotedAlias ?? valueName}</label>
|
||||
</>
|
||||
} else {
|
||||
return (
|
||||
<div className="input-group">
|
||||
{inputNode}
|
||||
{ definition.labelType === "color" && <ColorPicker {...props} onChange={onChangeListener} inputId={inputId} />}
|
||||
{ definition.labelType === "url" && (
|
||||
<InputButton
|
||||
className="open-external-link-button"
|
||||
icon="bx bx-window-open"
|
||||
title={t("promoted_attributes.open_external_link")}
|
||||
onClick={(e) => {
|
||||
const inputEl = document.getElementById(inputId) as HTMLInputElement | null;
|
||||
const url = inputEl?.value;
|
||||
if (url) {
|
||||
window.open(url, "_blank");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
</>;
|
||||
}
|
||||
return (
|
||||
<div className="input-group">
|
||||
{inputNode}
|
||||
{ definition.labelType === "color" && <ColorPicker {...props} onChange={onChangeListener} inputId={inputId} />}
|
||||
{ definition.labelType === "url" && (
|
||||
<InputButton
|
||||
className="open-external-link-button"
|
||||
icon="bx bx-window-open"
|
||||
title={t("promoted_attributes.open_external_link")}
|
||||
onClick={(e) => {
|
||||
const inputEl = document.getElementById(inputId) as HTMLInputElement | null;
|
||||
const url = inputEl?.value;
|
||||
if (url) {
|
||||
window.open(url, "_blank");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -282,7 +293,7 @@ function ColorPicker({ cell, onChange, inputId }: CellProps & {
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function RelationInput({ inputId, ...props }: CellProps & { inputId: string }) {
|
||||
@@ -295,7 +306,7 @@ function RelationInput({ inputId, ...props }: CellProps & { inputId: string }) {
|
||||
await updateAttribute(note, cell, componentId, value, setCells);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function MultiplicityCell({ cell, cells, setCells, setCellToFocus, note, componentId }: CellProps) {
|
||||
@@ -346,13 +357,13 @@ function MultiplicityCell({ cell, cells, setCells, setCellToFocus, note, compone
|
||||
name: cell.valueName,
|
||||
value: ""
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
setCells(cells.toSpliced(index, 1, ...newOnesToInsert));
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function PromotedActionButton({ icon, title, onClick }: {
|
||||
@@ -366,7 +377,7 @@ function PromotedActionButton({ icon, title, onClick }: {
|
||||
title={title}
|
||||
onClick={onClick}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function InputButton({ icon, className, title, onClick }: {
|
||||
@@ -381,7 +392,7 @@ function InputButton({ icon, className, title, onClick }: {
|
||||
title={title}
|
||||
onClick={onClick}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function setupTextLabelAutocomplete(el: HTMLInputElement, valueAttr: Attribute, onChangeListener: OnChangeListener) {
|
||||
@@ -406,7 +417,7 @@ function setupTextLabelAutocomplete(el: HTMLInputElement, valueAttr: Attribute,
|
||||
[
|
||||
{
|
||||
displayKey: "value",
|
||||
source: function (term, cb) {
|
||||
source (term, cb) {
|
||||
term = term.toLowerCase();
|
||||
|
||||
const filtered = attributeValues.filter((attr) => attr.value.toLowerCase().includes(term));
|
||||
|
||||
@@ -12,6 +12,7 @@ import shortcutService from "../../services/shortcuts.js";
|
||||
import appContext from "../../components/app_context.js";
|
||||
import type { Attribute } from "../../services/attribute_parser.js";
|
||||
import { focusSavedElement, saveFocusedElement } from "../../services/focus.js";
|
||||
import { isExperimentalFeatureEnabled } from "../../services/experimental_features.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="attr-detail tn-tool-dialog">
|
||||
@@ -309,6 +310,8 @@ interface SearchRelatedResponse {
|
||||
count: number;
|
||||
}
|
||||
|
||||
const isNewLayout = isExperimentalFeatureEnabled("new-layout");
|
||||
|
||||
export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
private $title!: JQuery<HTMLElement>;
|
||||
private $inputName!: JQuery<HTMLElement>;
|
||||
@@ -579,6 +582,13 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
.css("top", y - offset.top + 70)
|
||||
.css("max-height", outerHeight + y > height - 50 ? height - y - 50 : 10000);
|
||||
|
||||
if (isNewLayout) {
|
||||
this.$widget
|
||||
.css("top", "unset")
|
||||
.css("bottom", 70)
|
||||
.css("max-height", "80vh");
|
||||
}
|
||||
|
||||
if (focus === "name") {
|
||||
this.$inputName.trigger("focus").trigger("select");
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
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 toastService, { showErrorForScriptNote } from "../services/toast.js";
|
||||
import { renderReactWidget } from "./react/react_utils.jsx";
|
||||
|
||||
export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedComponent<T> {
|
||||
@@ -56,9 +57,9 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
|
||||
optChild(condition: boolean, ...components: (T | VNode)[]) {
|
||||
if (condition) {
|
||||
return this.child(...components);
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
id(id: string) {
|
||||
@@ -172,16 +173,11 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
|
||||
const noteId = this._noteId;
|
||||
if (this._noteId) {
|
||||
froca.getNote(noteId, true).then((note) => {
|
||||
toastService.showPersistent({
|
||||
id: `custom-widget-failure-${noteId}`,
|
||||
title: t("toast.widget-error.title"),
|
||||
icon: "bx bx-error-circle",
|
||||
message: t("toast.widget-error.message-custom", {
|
||||
id: noteId,
|
||||
title: note?.title,
|
||||
message: e.message || e.toString()
|
||||
})
|
||||
});
|
||||
showErrorForScriptNote(noteId, t("toast.widget-error.message-custom", {
|
||||
id: noteId,
|
||||
title: note?.title,
|
||||
message: e.message || e.toString()
|
||||
}));
|
||||
});
|
||||
} else {
|
||||
toastService.showPersistent({
|
||||
@@ -213,7 +209,7 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
|
||||
|
||||
toggleInt(show: boolean | null | undefined) {
|
||||
this.$widget.toggleClass("hidden-int", !show)
|
||||
.toggleClass("visible", !!show);
|
||||
.toggleClass("visible", !!show);
|
||||
}
|
||||
|
||||
isHiddenInt() {
|
||||
@@ -222,7 +218,7 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
|
||||
|
||||
toggleExt(show: boolean | null | "" | undefined) {
|
||||
this.$widget.toggleClass("hidden-ext", !show)
|
||||
.toggleClass("visible", !!show);
|
||||
.toggleClass("visible", !!show);
|
||||
}
|
||||
|
||||
isHiddenExt() {
|
||||
@@ -250,9 +246,9 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
|
||||
getClosestNtxId() {
|
||||
if (this.$widget) {
|
||||
return this.$widget.closest("[data-ntx-id]").attr("data-ntx-id");
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
cleanup() {}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import Dropdown from "../react/Dropdown";
|
||||
import "./global_menu.css";
|
||||
import { useStaticTooltip, useStaticTooltipWithKeyboardShortcut, useTriliumOption, useTriliumOptionBool, useTriliumOptionInt } from "../react/hooks";
|
||||
import { useContext, useEffect, useRef, useState } from "preact/hooks";
|
||||
import { t } from "../../services/i18n";
|
||||
import { FormDropdownDivider, FormDropdownSubmenu, FormListHeader, FormListItem } from "../react/FormList";
|
||||
import { CommandNames } from "../../components/app_context";
|
||||
import KeyboardShortcut from "../react/KeyboardShortcut";
|
||||
|
||||
import { KeyboardActionNames } from "@triliumnext/commons";
|
||||
import { ComponentChildren } from "preact";
|
||||
import { ComponentChildren, RefObject } from "preact";
|
||||
import { useContext, useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import { CommandNames } from "../../components/app_context";
|
||||
import Component from "../../components/component";
|
||||
import { ExperimentalFeature, ExperimentalFeatureId, experimentalFeatures, isExperimentalFeatureEnabled, toggleExperimentalFeature } from "../../services/experimental_features";
|
||||
import { t } from "../../services/i18n";
|
||||
import utils, { dynamicRequire, isElectron, isMobile, reloadFrontendApp } from "../../services/utils";
|
||||
import Dropdown from "../react/Dropdown";
|
||||
import { FormDropdownDivider, FormDropdownSubmenu, FormListHeader, FormListItem } from "../react/FormList";
|
||||
import { useStaticTooltip, useStaticTooltipWithKeyboardShortcut, useTriliumOption, useTriliumOptionBool, useTriliumOptionInt } from "../react/hooks";
|
||||
import KeyboardShortcut from "../react/KeyboardShortcut";
|
||||
import { ParentComponent } from "../react/react_utils";
|
||||
import utils, { dynamicRequire, isElectron, isMobile } from "../../services/utils";
|
||||
|
||||
interface MenuItemProps<T> {
|
||||
icon: string,
|
||||
@@ -27,15 +30,17 @@ export default function GlobalMenu({ isHorizontalLayout }: { isHorizontalLayout:
|
||||
const parentComponent = useContext(ParentComponent);
|
||||
const { isUpdateAvailable, latestVersion } = useTriliumUpdateStatus();
|
||||
const isMobileLocal = isMobile();
|
||||
const logoRef = useRef<SVGSVGElement>(null);
|
||||
useStaticTooltip(logoRef);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
className="global-menu"
|
||||
buttonClassName={`global-menu-button ${isHorizontalLayout ? "bx bx-menu" : ""}`} noSelectButtonStyle iconAction hideToggleArrow
|
||||
text={<>
|
||||
{isVerticalLayout && <VerticalLayoutIcon />}
|
||||
{isVerticalLayout && <VerticalLayoutIcon logoRef={logoRef} />}
|
||||
{isUpdateAvailable && <div class="global-menu-button-update-available">
|
||||
<span className="bx bxs-down-arrow-alt global-menu-button-update-available-button" title={t("update_available.update_available")}></span>
|
||||
<span className="bx bxs-down-arrow-alt global-menu-button-update-available-button" title={t("update_available.update_available")} />
|
||||
</div>}
|
||||
</>}
|
||||
noDropdownListStyle
|
||||
@@ -54,7 +59,7 @@ export default function GlobalMenu({ isHorizontalLayout }: { isHorizontalLayout:
|
||||
|
||||
<SwitchToOptions />
|
||||
<MenuItem command="showLaunchBarSubtree" icon={`bx ${isMobile() ? "bx-mobile" : "bx-sidebar"}`} text={t("global_menu.configure_launchbar")} />
|
||||
<AdvancedMenu />
|
||||
<AdvancedMenu dropStart={!isVerticalLayout} />
|
||||
<MenuItem command="showOptions" icon="bx bx-cog" text={t("global_menu.options")} />
|
||||
<FormDropdownDivider />
|
||||
|
||||
@@ -65,18 +70,19 @@ export default function GlobalMenu({ isHorizontalLayout }: { isHorizontalLayout:
|
||||
{isUpdateAvailable && <>
|
||||
<FormListHeader text={t("global_menu.new-version-available")} />
|
||||
<MenuItem command={() => window.open("https://github.com/TriliumNext/Trilium/releases/latest")}
|
||||
icon="bx bx-download"
|
||||
text={t("global_menu.download-update", {latestVersion})} />
|
||||
icon="bx bx-download"
|
||||
text={t("global_menu.download-update", {latestVersion})} />
|
||||
</>}
|
||||
|
||||
{!isElectron() && <BrowserOnlyOptions />}
|
||||
{glob.isDev && <DevelopmentOptions dropStart={!isVerticalLayout} />}
|
||||
</Dropdown>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function AdvancedMenu() {
|
||||
function AdvancedMenu({ dropStart }: { dropStart: boolean }) {
|
||||
return (
|
||||
<FormDropdownSubmenu icon="bx bx-chip" title={t("global_menu.advanced")}>
|
||||
<FormDropdownSubmenu icon="bx bx-chip" title={t("global_menu.advanced")} dropStart={dropStart}>
|
||||
<MenuItem command="showHiddenSubtree" icon="bx bx-hide" text={t("global_menu.show_hidden_subtree")} />
|
||||
<MenuItem command="showSearchHistory" icon="bx bx-search-alt" text={t("global_menu.open_search_history")} />
|
||||
<FormDropdownDivider />
|
||||
@@ -89,7 +95,7 @@ function AdvancedMenu() {
|
||||
{isElectron() && <MenuItem command="openDevTools" icon="bx bx-bug-alt" text={t("global_menu.open_dev_tools")} />}
|
||||
<KeyboardActionMenuItem command="reloadFrontendApp" icon="bx bx-refresh" text={t("global_menu.reload_frontend")} title={t("global_menu.reload_hint")} />
|
||||
</FormDropdownSubmenu>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function BrowserOnlyOptions() {
|
||||
@@ -99,14 +105,41 @@ function BrowserOnlyOptions() {
|
||||
</>;
|
||||
}
|
||||
|
||||
function DevelopmentOptions({ dropStart }: { dropStart: boolean }) {
|
||||
return <>
|
||||
<FormDropdownDivider />
|
||||
<FormListItem disabled>Development Options</FormListItem>
|
||||
<FormDropdownSubmenu icon="bx bx-test-tube" title="Experimental features" dropStart={dropStart}>
|
||||
{experimentalFeatures.map((feature) => (
|
||||
<ExperimentalFeatureToggle key={feature.id} experimentalFeature={feature as ExperimentalFeature} />
|
||||
))}
|
||||
</FormDropdownSubmenu>
|
||||
</>;
|
||||
}
|
||||
|
||||
function ExperimentalFeatureToggle({ experimentalFeature }: { experimentalFeature: ExperimentalFeature }) {
|
||||
const featureEnabled = isExperimentalFeatureEnabled(experimentalFeature.id as ExperimentalFeatureId);
|
||||
|
||||
return (
|
||||
<FormListItem
|
||||
checked={featureEnabled}
|
||||
title={experimentalFeature.description}
|
||||
onClick={async () => {
|
||||
await toggleExperimentalFeature(experimentalFeature.id as ExperimentalFeatureId, !featureEnabled);
|
||||
reloadFrontendApp();
|
||||
}}
|
||||
>{experimentalFeature.name}</FormListItem>
|
||||
);
|
||||
}
|
||||
|
||||
function SwitchToOptions() {
|
||||
if (isElectron()) {
|
||||
return;
|
||||
} else if (!isMobile()) {
|
||||
return <MenuItem command="switchToMobileVersion" icon="bx bx-mobile" text={t("global_menu.switch_to_mobile_version")} />
|
||||
} else {
|
||||
return <MenuItem command="switchToDesktopVersion" icon="bx bx-desktop" text={t("global_menu.switch_to_desktop_version")} />
|
||||
return <MenuItem command="switchToMobileVersion" icon="bx bx-mobile" text={t("global_menu.switch_to_mobile_version")} />;
|
||||
}
|
||||
return <MenuItem command="switchToDesktopVersion" icon="bx bx-desktop" text={t("global_menu.switch_to_desktop_version")} />;
|
||||
|
||||
}
|
||||
|
||||
function MenuItem({ icon, text, title, command, disabled, active }: MenuItemProps<KeyboardActionNames | CommandNames | (() => void)>) {
|
||||
@@ -117,7 +150,7 @@ function MenuItem({ icon, text, title, command, disabled, active }: MenuItemProp
|
||||
onClick={typeof command === "function" ? command : undefined}
|
||||
disabled={disabled}
|
||||
active={active}
|
||||
>{text}</FormListItem>
|
||||
>{text}</FormListItem>;
|
||||
}
|
||||
|
||||
function KeyboardActionMenuItem({ text, command, ...props }: MenuItemProps<KeyboardActionNames>) {
|
||||
@@ -125,13 +158,10 @@ function KeyboardActionMenuItem({ text, command, ...props }: MenuItemProps<Keybo
|
||||
{...props}
|
||||
command={command}
|
||||
text={<>{text} <KeyboardShortcut actionName={command as KeyboardActionNames} /></>}
|
||||
/>
|
||||
/>;
|
||||
}
|
||||
|
||||
function VerticalLayoutIcon() {
|
||||
const logoRef = useRef<SVGSVGElement>(null);
|
||||
useStaticTooltip(logoRef);
|
||||
|
||||
export function VerticalLayoutIcon({ logoRef }: { logoRef?: RefObject<SVGSVGElement> }) {
|
||||
return (
|
||||
<svg ref={logoRef} viewBox="0 0 256 256" title={t("global_menu.menu")}>
|
||||
<g>
|
||||
@@ -148,7 +178,7 @@ function VerticalLayoutIcon() {
|
||||
<path className="st8" d="m66.3 52.2c15.3 12.8 23.3 33.6 26.1 48.9l-50.6-22 48.8 24.9c-12.2 6-29.6 11.8-46.5 10-19.8-16.4-40.2-46.4-42.6-61.5 12.4-6.5 41.5-5.8 64.8-0.3z"/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ZoomControls({ parentComponent }: { parentComponent?: Component | null }) {
|
||||
@@ -172,7 +202,7 @@ function ZoomControls({ parentComponent }: { parentComponent?: Component | null
|
||||
}}
|
||||
className={`dropdown-item-button ${icon}`}
|
||||
>{children}</a>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return isElectron() ? (
|
||||
@@ -213,7 +243,7 @@ function ToggleWindowOnTop() {
|
||||
setIsAlwaysOnTop(newState);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function useTriliumUpdateStatus() {
|
||||
@@ -224,7 +254,7 @@ function useTriliumUpdateStatus() {
|
||||
async function updateVersionStatus() {
|
||||
const RELEASES_API_URL = "https://api.github.com/repos/TriliumNext/Trilium/releases/latest";
|
||||
|
||||
let latestVersion: string | undefined = undefined;
|
||||
let latestVersion: string | undefined;
|
||||
try {
|
||||
const resp = await fetch(RELEASES_API_URL);
|
||||
const data = await resp.json();
|
||||
|
||||
21
apps/client/src/widgets/buttons/right_pane_toggle.tsx
Normal file
21
apps/client/src/widgets/buttons/right_pane_toggle.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import clsx from "clsx";
|
||||
|
||||
import { t } from "../../services/i18n";
|
||||
import ActionButton from "../react/ActionButton";
|
||||
import { useTriliumOptionBool } from "../react/hooks";
|
||||
|
||||
export default function RightPaneToggle() {
|
||||
const [ rightPaneVisible, setRightPaneVisible ] = useTriliumOptionBool("rightPaneVisible");
|
||||
|
||||
return (
|
||||
<ActionButton
|
||||
className={clsx(
|
||||
`toggle-button right-pane-toggle-button bx-flip-horizontal`,
|
||||
rightPaneVisible ? "action-collapse" : "action-expand"
|
||||
)}
|
||||
text={t("right_pane.toggle")}
|
||||
icon="bx bx-sidebar"
|
||||
onClick={() => setRightPaneVisible(!rightPaneVisible)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -243,7 +243,7 @@ function AddNewColumn({ api, isInRelationMode }: { api: BoardApi, isInRelationMo
|
||||
export function TitleEditor({ currentValue, placeholder, save, dismiss, mode, isNewItem }: {
|
||||
currentValue?: string;
|
||||
placeholder?: string;
|
||||
save: (newValue: string) => void;
|
||||
save: (newValue: string) => void | Promise<void>;
|
||||
dismiss: () => void;
|
||||
isNewItem?: boolean;
|
||||
mode?: "normal" | "multiline" | "relation";
|
||||
|
||||
@@ -1,15 +1 @@
|
||||
.content-header-widget {
|
||||
position: relative;
|
||||
z-index: 998;
|
||||
background-color: var(--main-background-color);
|
||||
}
|
||||
|
||||
.note-split.bgfx .content-header-widget {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.content-header-widget.floating {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: var(--main-background-color, #fff) !important;
|
||||
}
|
||||
/** Intentionally left empty for now **/
|
||||
|
||||
@@ -2,19 +2,15 @@ import { EventData } from "../../components/app_context";
|
||||
import BasicWidget from "../basic_widget";
|
||||
import Container from "./container";
|
||||
import NoteContext from "../../components/note_context";
|
||||
import "./content_header.css";
|
||||
|
||||
export default class ContentHeader extends Container<BasicWidget> {
|
||||
|
||||
|
||||
noteContext?: NoteContext;
|
||||
thisElement?: HTMLElement;
|
||||
parentElement?: HTMLElement;
|
||||
resizeObserver: ResizeObserver;
|
||||
currentHeight: number = 0;
|
||||
currentSafeMargin: number = NaN;
|
||||
previousScrollTop: number = 0;
|
||||
isFloating: boolean = false;
|
||||
scrollThreshold: number = 10; // pixels before triggering float
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -39,44 +35,19 @@ export default class ContentHeader extends Container<BasicWidget> {
|
||||
this.thisElement = this.$widget.get(0)!;
|
||||
|
||||
this.resizeObserver.observe(this.thisElement);
|
||||
this.parentElement.addEventListener("scroll", this.updateScrollState.bind(this), { passive: true });
|
||||
}
|
||||
|
||||
updateScrollState() {
|
||||
const currentScrollTop = this.parentElement!.scrollTop;
|
||||
const isScrollingUp = currentScrollTop < this.previousScrollTop;
|
||||
const hasDropdownOpen = this.thisElement!.querySelector(".dropdown-menu.show") !== null;
|
||||
const hasMovedEnough = Math.abs(currentScrollTop - this.previousScrollTop) > this.scrollThreshold;
|
||||
|
||||
if (hasDropdownOpen) {
|
||||
this.setFloating(true);
|
||||
} else if (currentScrollTop === 0) {
|
||||
this.setFloating(false);
|
||||
} else if (hasMovedEnough) {
|
||||
this.setFloating(isScrollingUp);
|
||||
}
|
||||
this.previousScrollTop = currentScrollTop;
|
||||
this.updateSafeMargin();
|
||||
}
|
||||
|
||||
setFloating(shouldFloat: boolean) {
|
||||
if (shouldFloat !== this.isFloating) {
|
||||
this.isFloating = shouldFloat;
|
||||
|
||||
if (shouldFloat) {
|
||||
this.$widget.addClass("floating");
|
||||
} else {
|
||||
this.$widget.removeClass("floating");
|
||||
}
|
||||
}
|
||||
this.parentElement.addEventListener("scroll", this.updateSafeMargin.bind(this));
|
||||
}
|
||||
|
||||
updateSafeMargin() {
|
||||
const parentEl = this.parentElement?.closest<HTMLDivElement>(".note-split");
|
||||
if (this.isFloating || this.parentElement!.scrollTop === 0) {
|
||||
parentEl!.style.setProperty("--content-header-height", `${this.currentHeight}px`);
|
||||
} else {
|
||||
parentEl!.style.removeProperty("--content-header-height");
|
||||
const newSafeMargin = Math.max(this.currentHeight - this.parentElement!.scrollTop, 0);
|
||||
|
||||
if (newSafeMargin !== this.currentSafeMargin) {
|
||||
this.currentSafeMargin = newSafeMargin;
|
||||
|
||||
this.triggerEvent("contentSafeMarginChanged", {
|
||||
top: newSafeMargin,
|
||||
noteContext: this.noteContext!
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,4 +60,4 @@ export default class ContentHeader extends Container<BasicWidget> {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ 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";
|
||||
import { getEnabledExperimentalFeatureIds } from "../../services/experimental_features.js";
|
||||
|
||||
/**
|
||||
* The root container is the top-most widget/container, from which the entire layout derives.
|
||||
@@ -37,6 +38,7 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
|
||||
this.#setBackdropEffects();
|
||||
this.#setThemeCapabilities();
|
||||
this.#setLocaleAndDirection(options.get("locale"));
|
||||
this.#setExperimentalFeatures();
|
||||
|
||||
return super.render();
|
||||
}
|
||||
@@ -56,7 +58,7 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
|
||||
|
||||
if (loadResults.isOptionReloaded("maxContentWidth")
|
||||
|| loadResults.isOptionReloaded("centerContent")) {
|
||||
|
||||
|
||||
this.#setMaxContentWidth();
|
||||
}
|
||||
}
|
||||
@@ -99,6 +101,12 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
|
||||
document.body.classList.toggle("theme-supports-background-effects", useBgfx);
|
||||
}
|
||||
|
||||
#setExperimentalFeatures() {
|
||||
for (const featureId of getEnabledExperimentalFeatureIds()) {
|
||||
document.body.classList.add(`experimental-feature-${featureId}`);
|
||||
}
|
||||
}
|
||||
|
||||
#setLocaleAndDirection(locale: string) {
|
||||
const correspondingLocale = LOCALES.find(l => l.id === locale);
|
||||
document.body.lang = locale;
|
||||
|
||||
@@ -49,7 +49,7 @@ export default class ScrollingContainer extends Container<BasicWidget> {
|
||||
}
|
||||
}
|
||||
|
||||
scrollContainerToCommand({ position }: CommandListenerData<"scrollContainerToCommand">) {
|
||||
scrollContainerToCommand({ position }: CommandListenerData<"scrollContainerTo">) {
|
||||
this.$widget.scrollTop(position);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ body.mobile .modal.popup-editor-dialog .modal-dialog {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-block: 0;
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .modal-header .note-title-widget {
|
||||
@@ -53,10 +54,8 @@ body.mobile .modal.popup-editor-dialog .modal-dialog {
|
||||
min-height: unset;
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .note-icon-widget {
|
||||
width: 32px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
.modal.popup-editor-dialog div.note-title-widget {
|
||||
--note-title-padding-inline: 8px;
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .note-icon-widget button.note-icon,
|
||||
|
||||
@@ -1,26 +1,32 @@
|
||||
import { useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import Modal from "../react/Modal";
|
||||
import "./PopupEditor.css";
|
||||
import { useNoteContext, useNoteLabel, useTriliumEvent } from "../react/hooks";
|
||||
import NoteTitleWidget from "../note_title";
|
||||
import NoteIcon from "../note_icon";
|
||||
import NoteContext from "../../components/note_context";
|
||||
import { NoteContextContext, ParentComponent } from "../react/react_utils";
|
||||
import NoteDetail from "../NoteDetail";
|
||||
|
||||
import { ComponentChildren } from "preact";
|
||||
import { useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
|
||||
import appContext from "../../components/app_context";
|
||||
import NoteContext from "../../components/note_context";
|
||||
import froca from "../../services/froca";
|
||||
import { t } from "../../services/i18n";
|
||||
import tree from "../../services/tree";
|
||||
import utils from "../../services/utils";
|
||||
import NoteList from "../collections/NoteList";
|
||||
import StandaloneRibbonAdapter from "../ribbon/components/StandaloneRibbonAdapter";
|
||||
import FormattingToolbar from "../ribbon/FormattingToolbar";
|
||||
import PromotedAttributes from "../PromotedAttributes";
|
||||
import FloatingButtons from "../FloatingButtons";
|
||||
import { DESKTOP_FLOATING_BUTTONS, MOBILE_FLOATING_BUTTONS, POPUP_HIDDEN_FLOATING_BUTTONS } from "../FloatingButtonsDefinitions";
|
||||
import utils from "../../services/utils";
|
||||
import tree from "../../services/tree";
|
||||
import froca from "../../services/froca";
|
||||
import NoteIcon from "../note_icon";
|
||||
import NoteTitleWidget from "../note_title";
|
||||
import NoteDetail from "../NoteDetail";
|
||||
import PromotedAttributes from "../PromotedAttributes";
|
||||
import { useNoteContext, useNoteLabel, useTriliumEvent } from "../react/hooks";
|
||||
import Modal from "../react/Modal";
|
||||
import { NoteContextContext, ParentComponent } from "../react/react_utils";
|
||||
import ReadOnlyNoteInfoBar from "../ReadOnlyNoteInfoBar";
|
||||
import StandaloneRibbonAdapter from "../ribbon/components/StandaloneRibbonAdapter";
|
||||
import FormattingToolbar from "../ribbon/FormattingToolbar";
|
||||
import MobileEditorToolbar from "../type_widgets/text/mobile_editor_toolbar";
|
||||
import { t } from "../../services/i18n";
|
||||
import appContext from "../../components/app_context";
|
||||
import NoteBadges from "../layout/NoteBadges";
|
||||
import { isExperimentalFeatureEnabled } from "../../services/experimental_features";
|
||||
|
||||
const isNewLayout = isExperimentalFeatureEnabled("new-layout");
|
||||
|
||||
export default function PopupEditor() {
|
||||
const [ shown, setShown ] = useState(false);
|
||||
@@ -61,7 +67,10 @@ export default function PopupEditor() {
|
||||
<NoteContextContext.Provider value={noteContext}>
|
||||
<DialogWrapper>
|
||||
<Modal
|
||||
title={<TitleRow />}
|
||||
title={<>
|
||||
<TitleRow />
|
||||
{isNewLayout && <NoteBadges />}
|
||||
</>}
|
||||
customTitleBarButtons={[{
|
||||
iconClassName: "bx-expand-alt",
|
||||
title: t("popup-editor.maximize"),
|
||||
@@ -75,19 +84,17 @@ export default function PopupEditor() {
|
||||
className="popup-editor-dialog"
|
||||
size="lg"
|
||||
show={shown}
|
||||
onShown={() => {
|
||||
parentComponent?.handleEvent("focusOnDetail", { ntxId: noteContext.ntxId });
|
||||
}}
|
||||
onShown={() => parentComponent?.handleEvent("focusOnDetail", { ntxId: noteContext.ntxId })}
|
||||
onHidden={() => setShown(false)}
|
||||
keepInDom // needed for faster loading
|
||||
noFocus // automatic focus breaks block popup
|
||||
>
|
||||
<ReadOnlyNoteInfoBar />
|
||||
{!isNewLayout && <ReadOnlyNoteInfoBar />}
|
||||
<PromotedAttributes />
|
||||
|
||||
{isMobile
|
||||
? <MobileEditorToolbar inPopupEditor />
|
||||
: <StandaloneRibbonAdapter component={FormattingToolbar} />}
|
||||
? <MobileEditorToolbar inPopupEditor />
|
||||
: <StandaloneRibbonAdapter component={FormattingToolbar} />}
|
||||
|
||||
<FloatingButtons items={items} />
|
||||
<NoteDetail />
|
||||
@@ -95,7 +102,7 @@ export default function PopupEditor() {
|
||||
</Modal>
|
||||
</DialogWrapper>
|
||||
</NoteContextContext.Provider>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function DialogWrapper({ children }: { children: ComponentChildren }) {
|
||||
@@ -107,7 +114,7 @@ export function DialogWrapper({ children }: { children: ComponentChildren }) {
|
||||
<div ref={wrapperRef} class={`quick-edit-dialog-wrapper ${note?.getColorClass() ?? ""}`}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function TitleRow() {
|
||||
@@ -116,5 +123,5 @@ export function TitleRow() {
|
||||
<NoteIcon />
|
||||
<NoteTitleWidget />
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useMemo, useState } from "preact/hooks";
|
||||
|
||||
import { t } from "../../services/i18n";
|
||||
import Button from "../react/Button";
|
||||
import Modal from "../react/Modal";
|
||||
import { dismissCallToAction, getCallToActions } from "./call_to_action_definitions";
|
||||
import { t } from "../../services/i18n";
|
||||
|
||||
export default function CallToActionDialog() {
|
||||
const activeCallToActions = useMemo(() => getCallToActions(), []);
|
||||
const activeCallToActions = useMemo(() => getCallToActions(), []);
|
||||
const [ activeIndex, setActiveIndex ] = useState(0);
|
||||
const [ shown, setShown ] = useState(true);
|
||||
const activeItem = activeCallToActions[activeIndex];
|
||||
@@ -36,11 +37,11 @@ export default function CallToActionDialog() {
|
||||
await dismissCallToAction(activeItem.id);
|
||||
await button.onClick();
|
||||
goToNext();
|
||||
}}/>
|
||||
}}/>
|
||||
)}
|
||||
</>}
|
||||
>
|
||||
<p>{activeItem.message}</p>
|
||||
<p className="pre-wrap-text">{activeItem.message}</p>
|
||||
</Modal>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import utils from "../../services/utils";
|
||||
import options from "../../services/options";
|
||||
import appContext from "../../components/app_context";
|
||||
import { t } from "../../services/i18n";
|
||||
import options from "../../services/options";
|
||||
import utils from "../../services/utils";
|
||||
|
||||
/**
|
||||
* A "call-to-action" is an interactive message for the user, generally to present new features.
|
||||
@@ -46,18 +47,15 @@ function isNextTheme() {
|
||||
|
||||
const CALL_TO_ACTIONS: CallToAction[] = [
|
||||
{
|
||||
id: "next_theme",
|
||||
title: t("call_to_action.next_theme_title"),
|
||||
message: t("call_to_action.next_theme_message"),
|
||||
enabled: () => !isNextTheme(),
|
||||
id: "new_layout",
|
||||
title: t("call_to_action.new_layout_title"),
|
||||
message: t("call_to_action.new_layout_message"),
|
||||
enabled: () => true,
|
||||
buttons: [
|
||||
{
|
||||
text: t("call_to_action.next_theme_button"),
|
||||
async onClick() {
|
||||
await options.save("theme", "next");
|
||||
await options.save("backgroundEffects", "true");
|
||||
utils.reloadFrontendApp("call-to-action");
|
||||
}
|
||||
|
||||
text: t("call_to_action.new_layout_button"),
|
||||
onClick: () => appContext.tabManager.openInNewTab("_help_IjZS7iK5EXtb", "_help", true)
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -75,6 +73,22 @@ const CALL_TO_ACTIONS: CallToAction[] = [
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "next_theme",
|
||||
title: t("call_to_action.next_theme_title"),
|
||||
message: t("call_to_action.next_theme_message"),
|
||||
enabled: () => !isNextTheme(),
|
||||
buttons: [
|
||||
{
|
||||
text: t("call_to_action.next_theme_button"),
|
||||
async onClick() {
|
||||
await options.save("theme", "next");
|
||||
await options.save("backgroundEffects", "true");
|
||||
utils.reloadFrontendApp("call-to-action");
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import note_types from "../../services/note_types";
|
||||
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 SimpleBadge from "../react/Badge";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
export interface ChooseNoteTypeResponse {
|
||||
@@ -108,7 +108,7 @@ export default function NoteTypeChooserDialogComponent() {
|
||||
value={[ item.type, item.templateNoteId ].join(",") }
|
||||
icon={item.uiIcon}>
|
||||
{item.title}
|
||||
{item.badges && item.badges.map((badge) => <Badge {...badge} />)}
|
||||
{item.badges && item.badges.map((badge) => <SimpleBadge {...badge} />)}
|
||||
</FormListItem>;
|
||||
}
|
||||
})}
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
* - For example, if there is a formula in the middle of the highlighted text, the two ends of the formula will be regarded as two entries
|
||||
*/
|
||||
|
||||
import { t } from "../services/i18n.js";
|
||||
import attributeService from "../services/attributes.js";
|
||||
import RightPanelWidget from "./right_panel_widget.js";
|
||||
import options from "../services/options.js";
|
||||
import OnClickButtonWidget from "./buttons/onclick_button.js";
|
||||
import appContext, { type EventData } from "../components/app_context.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import attributeService from "../services/attributes.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import katex from "../services/math.js";
|
||||
import options from "../services/options.js";
|
||||
import OnClickButtonWidget from "./buttons/onclick_button.js";
|
||||
import RightPanelWidget from "./right_panel_widget.js";
|
||||
|
||||
const TPL = /*html*/`<div class="highlights-list-widget">
|
||||
<style>
|
||||
@@ -159,13 +159,13 @@ export default class HighlightsListWidget extends RightPanelWidget {
|
||||
*/
|
||||
async replaceMathTextWithKatax(html: string) {
|
||||
const mathTextRegex = /<span class="math-tex">\\\(([\s\S]*?)\\\)<\/span>/g;
|
||||
var matches = [...html.matchAll(mathTextRegex)];
|
||||
const matches = [...html.matchAll(mathTextRegex)];
|
||||
let modifiedText = html;
|
||||
|
||||
if (matches.length > 0) {
|
||||
// Process all matches asynchronously
|
||||
for (const match of matches) {
|
||||
let latexCode = match[1];
|
||||
const latexCode = match[1];
|
||||
let rendered;
|
||||
|
||||
try {
|
||||
@@ -234,7 +234,7 @@ export default class HighlightsListWidget extends RightPanelWidget {
|
||||
}
|
||||
|
||||
findSubStr = findSubStr.substring(1);
|
||||
combinedRegexStr = `(` + combinedRegexStr.substring(1) + `)`;
|
||||
combinedRegexStr = `(${combinedRegexStr.substring(1)})`;
|
||||
const combinedRegex = new RegExp(combinedRegexStr, "gi");
|
||||
const $highlightsList = $("<ol>");
|
||||
let prevEndIndex = -1,
|
||||
@@ -302,26 +302,28 @@ export default class HighlightsListWidget extends RightPanelWidget {
|
||||
let targetElement;
|
||||
if (isReadOnly) {
|
||||
const $container = await this.noteContext.getContentElement();
|
||||
targetElement = $container
|
||||
.find(findSubStr)
|
||||
.filter(function () {
|
||||
if (findSubStr.indexOf("color") >= 0 && findSubStr.indexOf("background-color") < 0) {
|
||||
let color = this.style.color;
|
||||
const $el = $(this as HTMLElement);
|
||||
return !($el.prop("tagName") === "SPAN" && color === "");
|
||||
} else {
|
||||
if ($container) {
|
||||
targetElement = $container
|
||||
.find(findSubStr)
|
||||
.filter(function () {
|
||||
if (findSubStr.indexOf("color") >= 0 && findSubStr.indexOf("background-color") < 0) {
|
||||
const color = this.style.color;
|
||||
const $el = $(this as HTMLElement);
|
||||
return !($el.prop("tagName") === "SPAN" && color === "");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
})
|
||||
.filter(function () {
|
||||
const $el = $(this as HTMLElement);
|
||||
return (
|
||||
$el.parent(findSubStr).length === 0 &&
|
||||
$el.parent().parent(findSubStr).length === 0 &&
|
||||
$el.parent().parent().parent(findSubStr).length === 0 &&
|
||||
$el.parent().parent().parent().parent(findSubStr).length === 0
|
||||
);
|
||||
});
|
||||
|
||||
})
|
||||
.filter(function () {
|
||||
const $el = $(this as HTMLElement);
|
||||
return (
|
||||
$el.parent(findSubStr).length === 0 &&
|
||||
$el.parent().parent(findSubStr).length === 0 &&
|
||||
$el.parent().parent().parent(findSubStr).length === 0 &&
|
||||
$el.parent().parent().parent().parent(findSubStr).length === 0
|
||||
);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const textEditor = await this.noteContext.getTextEditor();
|
||||
const el = textEditor?.editing.view.domRoots.values().next().value;
|
||||
@@ -333,11 +335,11 @@ export default class HighlightsListWidget extends RightPanelWidget {
|
||||
// the background-color error will be regarded as color, so it needs to be filtered
|
||||
const $el = $(this as HTMLElement);
|
||||
if (findSubStr.indexOf("color") >= 0 && findSubStr.indexOf("background-color") < 0) {
|
||||
let color = this.style.color;
|
||||
const color = this.style.color;
|
||||
return !($el.prop("tagName") === "SPAN" && color === "");
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
|
||||
})
|
||||
.filter(function () {
|
||||
// Need to filter out the child elements of the element that has been found
|
||||
|
||||
@@ -2102,8 +2102,8 @@ const icons: Icon[] = [
|
||||
type_of_icon: "REGULAR"
|
||||
},
|
||||
{
|
||||
name: "border-inline-start",
|
||||
slug: "border-inline-start-regular",
|
||||
name: "border-left",
|
||||
slug: "border-left-regular",
|
||||
category_id: 111,
|
||||
type_of_icon: "REGULAR"
|
||||
},
|
||||
@@ -10259,9 +10259,9 @@ function getIconClass(icon: Icon) {
|
||||
return `bxl-${icon.name}`;
|
||||
} else if (icon.type_of_icon === "SOLID") {
|
||||
return `bxs-${icon.name}`;
|
||||
} else {
|
||||
return `bx-${icon.name}`;
|
||||
}
|
||||
}
|
||||
return `bx-${icon.name}`;
|
||||
|
||||
}
|
||||
|
||||
for (const icon of icons) {
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
.breadcrumb-row {
|
||||
.breadcrumb {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.component.breadcrumb {
|
||||
contain: none;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin: 0;
|
||||
align-items: center;
|
||||
@@ -11,7 +8,24 @@
|
||||
gap: 0.25em;
|
||||
flex-wrap: nowrap;
|
||||
overflow: hidden;
|
||||
max-width: 85%;
|
||||
--badge-radius: 6px;
|
||||
|
||||
.badge-hoisted {
|
||||
--color: var(--input-background-color);
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
|
||||
a.tn-link {
|
||||
color: var(--custom-color, inherit);
|
||||
|
||||
&:hover {
|
||||
color: var(--custom-color, inherit);
|
||||
}
|
||||
}
|
||||
|
||||
.archived {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
> span,
|
||||
> span > span {
|
||||
@@ -19,8 +33,11 @@
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
|
||||
.bx {
|
||||
margin-inline: 6px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
min-width: 0;
|
||||
max-width: 150px;
|
||||
@@ -45,11 +62,29 @@
|
||||
}
|
||||
|
||||
.dropdown-item span,
|
||||
.dropdown-item strong {
|
||||
.dropdown-item strong,
|
||||
.breadcrumb-last-item {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
max-width: 300px;
|
||||
color: var(--custom-color, inherit) !important;
|
||||
}
|
||||
|
||||
a.breadcrumb-last-item,
|
||||
a.breadcrumb-last-item:visited {
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 0 10px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
& > .filler {
|
||||
flex-grow: 1;
|
||||
height: 23px;
|
||||
}
|
||||
}
|
||||
448
apps/client/src/widgets/layout/Breadcrumb.tsx
Normal file
448
apps/client/src/widgets/layout/Breadcrumb.tsx
Normal file
@@ -0,0 +1,448 @@
|
||||
import "./Breadcrumb.css";
|
||||
|
||||
import clsx from "clsx";
|
||||
import { useContext, useRef, useState } from "preact/hooks";
|
||||
import { Fragment } from "preact/jsx-runtime";
|
||||
|
||||
import appContext from "../../components/app_context";
|
||||
import Component from "../../components/component";
|
||||
import NoteContext from "../../components/note_context";
|
||||
import contextMenu, { MenuItem } from "../../menus/context_menu";
|
||||
import NoteColorPicker from "../../menus/custom-items/NoteColorPicker";
|
||||
import link_context_menu from "../../menus/link_context_menu";
|
||||
import { TreeCommandNames } from "../../menus/tree_context_menu";
|
||||
import attributes from "../../services/attributes";
|
||||
import branches from "../../services/branches";
|
||||
import { copyTextWithToast } from "../../services/clipboard_ext";
|
||||
import { getReadableTextColor } from "../../services/css_class_manager";
|
||||
import froca from "../../services/froca";
|
||||
import hoisted_note from "../../services/hoisted_note";
|
||||
import { t } from "../../services/i18n";
|
||||
import note_create from "../../services/note_create";
|
||||
import options from "../../services/options";
|
||||
import tree from "../../services/tree";
|
||||
import ActionButton from "../react/ActionButton";
|
||||
import { Badge } from "../react/Badge";
|
||||
import Dropdown from "../react/Dropdown";
|
||||
import { FormDropdownDivider, FormListItem } from "../react/FormList";
|
||||
import { useActiveNoteContext, useChildNotes, useNote, useNoteColorClass, useNoteIcon, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useStaticTooltip, useTriliumOptionBool } from "../react/hooks";
|
||||
import Icon from "../react/Icon";
|
||||
import { NewNoteLink } from "../react/NoteLink";
|
||||
import { ParentComponent } from "../react/react_utils";
|
||||
|
||||
const COLLAPSE_THRESHOLD = 5;
|
||||
const INITIAL_ITEMS = 2;
|
||||
const FINAL_ITEMS = 2;
|
||||
|
||||
export default function Breadcrumb() {
|
||||
const { note, notePath, notePaths, noteContext } = useNotePaths();
|
||||
const parentComponent = useContext(ParentComponent);
|
||||
const [ hideArchivedNotes ] = useTriliumOptionBool("hideArchivedNotes_main");
|
||||
const separatorProps: Omit<BreadcrumbSeparatorProps, "notePath" | "activeNotePath"> = { noteContext, hideArchivedNotes };
|
||||
|
||||
return (
|
||||
<div className="breadcrumb">
|
||||
{notePaths.length > COLLAPSE_THRESHOLD ? (
|
||||
<>
|
||||
{notePaths.slice(0, INITIAL_ITEMS).map((item, index) => (
|
||||
<Fragment key={item}>
|
||||
<BreadcrumbItem index={index} notePath={item} notePathLength={notePaths.length} noteContext={noteContext} parentComponent={parentComponent} />
|
||||
<BreadcrumbSeparator notePath={item} activeNotePath={notePaths[index + 1]} {...separatorProps} />
|
||||
</Fragment>
|
||||
))}
|
||||
<BreadcrumbCollapsed items={notePaths.slice(INITIAL_ITEMS, -FINAL_ITEMS)} noteContext={noteContext} />
|
||||
{notePaths.slice(-FINAL_ITEMS).map((item, index) => (
|
||||
<Fragment key={item}>
|
||||
<BreadcrumbSeparator notePath={notePaths[notePaths.length - FINAL_ITEMS - (1 - index)]} activeNotePath={item} {...separatorProps} />
|
||||
<BreadcrumbItem index={notePaths.length - FINAL_ITEMS + index} notePath={item} notePathLength={notePaths.length} noteContext={noteContext} parentComponent={parentComponent} />
|
||||
</Fragment>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
notePaths.map((item, index) => (
|
||||
<Fragment key={item}>
|
||||
{index === 0
|
||||
? <BreadcrumbRoot noteContext={noteContext} />
|
||||
: <BreadcrumbItem index={index} notePath={item} notePathLength={notePaths.length} noteContext={noteContext} parentComponent={parentComponent} />
|
||||
}
|
||||
{(index < notePaths.length - 1 || note?.hasChildren()) &&
|
||||
<BreadcrumbSeparator notePath={item} activeNotePath={notePaths[index + 1]} {...separatorProps} />}
|
||||
</Fragment>
|
||||
))
|
||||
)}
|
||||
|
||||
<div
|
||||
className="filler"
|
||||
onContextMenu={buildEmptyAreaContextMenu(parentComponent, notePath)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function BreadcrumbRoot({ noteContext }: { noteContext: NoteContext | undefined }) {
|
||||
const noteId = noteContext?.hoistedNoteId ?? "root";
|
||||
if (noteId !== "root") {
|
||||
return <BreadcrumbHoistedNoteRoot noteId={noteId} />;
|
||||
}
|
||||
|
||||
// Root note is icon only.
|
||||
const note = froca.getNoteFromCache("root");
|
||||
return (note &&
|
||||
<ActionButton
|
||||
className="root-note"
|
||||
icon={note.getIcon()}
|
||||
text={""}
|
||||
onClick={() => noteContext?.setNote(note.noteId)}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
link_context_menu.openContextMenu(note.noteId, e);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
function BreadcrumbHoistedNoteRoot({ noteId }: { noteId: string }) {
|
||||
const note = useNote(noteId);
|
||||
const noteIcon = useNoteIcon(note);
|
||||
const [ workspace ] = useNoteLabelBoolean(note, "workspace");
|
||||
const [ workspaceIconClass ] = useNoteLabel(note, "workspaceIconClass");
|
||||
const [ workspaceColor ] = useNoteLabel(note, "workspaceTabBackgroundColor");
|
||||
|
||||
// Hoisted workspace shows both text and icon and a way to exit easily out of the hoisting.
|
||||
return (note &&
|
||||
<>
|
||||
<Badge
|
||||
className="badge-hoisted"
|
||||
icon={workspace ? (workspaceIconClass || noteIcon) : "bx bxs-chevrons-up"}
|
||||
text={workspace ? t("breadcrumb.workspace_badge") : t("breadcrumb.hoisted_badge")}
|
||||
tooltip={t("breadcrumb.hoisted_badge_title")}
|
||||
onClick={() => hoisted_note.unhoist()}
|
||||
style={workspaceColor ? {
|
||||
"--color": workspaceColor,
|
||||
"color": getReadableTextColor(workspaceColor)
|
||||
} : undefined}
|
||||
/>
|
||||
<NewNoteLink
|
||||
notePath={noteId}
|
||||
showNoteIcon
|
||||
noPreview
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function BreadcrumbLastItem({ notePath, parentComponent }: { notePath: string, parentComponent: Component | null }) {
|
||||
const linkRef = useRef<HTMLAnchorElement>(null);
|
||||
const noteId = notePath.split("/").at(-1);
|
||||
const [ note ] = useState(() => froca.getNoteFromCache(noteId!));
|
||||
const title = useNoteProperty(note, "title");
|
||||
const colorClass = useNoteColorClass(note);
|
||||
const [ archived ] = useNoteLabelBoolean(note, "archived");
|
||||
useStaticTooltip(linkRef, {
|
||||
placement: "top",
|
||||
title: t("breadcrumb.scroll_to_top_title")
|
||||
});
|
||||
|
||||
if (!note) return null;
|
||||
|
||||
return (
|
||||
<a
|
||||
ref={linkRef}
|
||||
href="#"
|
||||
className={clsx("breadcrumb-last-item tn-link", colorClass, archived && "archived")}
|
||||
onClick={() => {
|
||||
const activeNtxId = appContext.tabManager.activeNtxId;
|
||||
const scrollingContainer = document.querySelector(`[data-ntx-id="${activeNtxId}"] .scrolling-container`);
|
||||
scrollingContainer?.scrollTo({ top: 0, behavior: "smooth" });
|
||||
}}
|
||||
onContextMenu={buildContextMenu(notePath, parentComponent)}
|
||||
>{title}</a>
|
||||
);
|
||||
}
|
||||
|
||||
function BreadcrumbItem({ index, notePath, noteContext, notePathLength, parentComponent }: { index: number, notePathLength: number, notePath: string, noteContext: NoteContext | undefined, parentComponent: Component | null }) {
|
||||
if (index === 0) {
|
||||
return <BreadcrumbRoot noteContext={noteContext} />;
|
||||
}
|
||||
|
||||
if (index === notePathLength - 1) {
|
||||
return <>
|
||||
<BreadcrumbLastItem notePath={notePath} parentComponent={parentComponent} />
|
||||
</>;
|
||||
}
|
||||
|
||||
return <NewNoteLink
|
||||
notePath={notePath}
|
||||
noContextMenu
|
||||
onContextMenu={buildContextMenu(notePath, parentComponent)}
|
||||
/>;
|
||||
}
|
||||
|
||||
interface BreadcrumbSeparatorProps {
|
||||
notePath: string,
|
||||
activeNotePath: string,
|
||||
noteContext: NoteContext | undefined,
|
||||
hideArchivedNotes: boolean;
|
||||
}
|
||||
|
||||
function BreadcrumbSeparator(props: BreadcrumbSeparatorProps) {
|
||||
return (
|
||||
<Dropdown
|
||||
text={<Icon icon="bx bx-chevron-right" />}
|
||||
noSelectButtonStyle
|
||||
buttonClassName="icon-action"
|
||||
hideToggleArrow
|
||||
dropdownOptions={{ popperConfig: { strategy: "fixed", placement: "top" } }}
|
||||
>
|
||||
<BreadcrumbSeparatorDropdownContent {...props} />
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
function BreadcrumbSeparatorDropdownContent({ notePath, noteContext, activeNotePath, hideArchivedNotes }: BreadcrumbSeparatorProps) {
|
||||
const notePathComponents = notePath.split("/");
|
||||
const parentNoteId = notePathComponents.at(-1);
|
||||
const childNotes = useChildNotes(parentNoteId);
|
||||
|
||||
return (
|
||||
<ul className="breadcrumb-child-list">
|
||||
{childNotes.map((note) => {
|
||||
if (note.noteId === "_hidden") return;
|
||||
if (hideArchivedNotes && note.isArchived) return null;
|
||||
|
||||
const childNotePath = `${notePath}/${note.noteId}`;
|
||||
return <li key={note.noteId}>
|
||||
<FormListItem
|
||||
icon={note.getIcon()}
|
||||
className={clsx(note.getColorClass(), note.isArchived && "archived")}
|
||||
onClick={() => noteContext?.setNote(childNotePath)}
|
||||
>
|
||||
{childNotePath !== activeNotePath
|
||||
? <span>{note.title}</span>
|
||||
: <strong>{note.title}</strong>}
|
||||
</FormListItem>
|
||||
</li>;
|
||||
})}
|
||||
|
||||
<FormDropdownDivider />
|
||||
<FormListItem
|
||||
icon="bx bx-plus"
|
||||
onClick={() => note_create.createNote(notePath, { activate: true })}
|
||||
>{t("breadcrumb.create_new_note")}</FormListItem>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
function BreadcrumbCollapsed({ items, noteContext }: {
|
||||
items: string[],
|
||||
noteContext: NoteContext | undefined,
|
||||
}) {
|
||||
return (
|
||||
<Dropdown
|
||||
text={<Icon icon="bx bx-dots-horizontal-rounded" />}
|
||||
noSelectButtonStyle
|
||||
buttonClassName="icon-action"
|
||||
hideToggleArrow
|
||||
dropdownOptions={{ popperConfig: { strategy: "fixed" } }}
|
||||
>
|
||||
<ul className="breadcrumb-child-list">
|
||||
{items.map((notePath) => {
|
||||
const notePathComponents = notePath.split("/");
|
||||
const noteId = notePathComponents[notePathComponents.length - 1];
|
||||
const note = froca.getNoteFromCache(noteId);
|
||||
if (!note) return null;
|
||||
|
||||
return <li key={note.noteId}>
|
||||
<FormListItem
|
||||
icon={note.getIcon()}
|
||||
onClick={() => noteContext?.setNote(notePath)}
|
||||
>
|
||||
<span>{note.title}</span>
|
||||
</FormListItem>
|
||||
</li>;
|
||||
})}
|
||||
</ul>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
function useNotePaths() {
|
||||
const { note, notePath, hoistedNoteId, noteContext } = useActiveNoteContext();
|
||||
const notePathArray = (notePath ?? "").split("/");
|
||||
|
||||
let prefix = "";
|
||||
let output: string[] = [];
|
||||
let pos = 0;
|
||||
let hoistedNotePos = -1;
|
||||
for (const notePath of notePathArray) {
|
||||
if (hoistedNoteId !== "root" && notePath === hoistedNoteId) {
|
||||
hoistedNotePos = pos;
|
||||
}
|
||||
output.push(`${prefix}${notePath}`);
|
||||
prefix += `${notePath}/`;
|
||||
pos++;
|
||||
}
|
||||
|
||||
// When hoisted, display only the path starting with the hoisted note.
|
||||
if (hoistedNoteId !== "root" && hoistedNotePos > -1) {
|
||||
output = output.slice(hoistedNotePos);
|
||||
}
|
||||
|
||||
return {
|
||||
note,
|
||||
notePath,
|
||||
notePaths: output,
|
||||
noteContext
|
||||
};
|
||||
}
|
||||
|
||||
//#region Note Context menu
|
||||
function buildContextMenu(notePath: string, parentComponent: Component | null) {
|
||||
return async (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
const { noteId, parentNoteId } = tree.getNoteIdAndParentIdFromUrl(notePath);
|
||||
if (!parentNoteId || !noteId) return;
|
||||
|
||||
const branchId = await froca.getBranchId(parentNoteId, noteId);
|
||||
if (!branchId) return;
|
||||
const branch = froca.getBranch(branchId);
|
||||
if (!branch) return;
|
||||
|
||||
const note = await branch?.getNote();
|
||||
if (!note) return;
|
||||
|
||||
const notSearch = note.type !== "search";
|
||||
const notOptionsOrHelp = !note.noteId.startsWith("_options") && !note.noteId.startsWith("_help");
|
||||
const isArchived = note.isArchived;
|
||||
const isNotRoot = note.noteId !== "root";
|
||||
const isHoisted = note.noteId === appContext.tabManager.getActiveContext()?.hoistedNoteId;
|
||||
const parentNote = isNotRoot && branch ? await froca.getNote(branch.parentNoteId) : null;
|
||||
const parentNotSearch = !parentNote || parentNote.type !== "search";
|
||||
|
||||
const items = [
|
||||
...link_context_menu.getItems(e),
|
||||
{
|
||||
title: `${t("tree-context-menu.hoist-note")}`,
|
||||
command: "toggleNoteHoisting",
|
||||
uiIcon: "bx bxs-chevrons-up",
|
||||
enabled: notSearch
|
||||
},
|
||||
{ kind: "separator" },
|
||||
{
|
||||
title: t("tree-context-menu.move-to"),
|
||||
command: "moveNotesTo",
|
||||
uiIcon: "bx bx-transfer",
|
||||
enabled: isNotRoot && !isHoisted && parentNotSearch
|
||||
},
|
||||
{
|
||||
title: t("tree-context-menu.clone-to"),
|
||||
command: "cloneNotesTo",
|
||||
uiIcon: "bx bx-duplicate",
|
||||
enabled: isNotRoot && !isHoisted
|
||||
},
|
||||
{ kind: "separator" },
|
||||
{
|
||||
title: t("tree-context-menu.duplicate"),
|
||||
command: "duplicateSubtree",
|
||||
uiIcon: "bx bx-outline",
|
||||
enabled: parentNotSearch && isNotRoot && !isHoisted && notOptionsOrHelp && note.isContentAvailable(),
|
||||
handler: () => note_create.duplicateSubtree(noteId, branch.parentNoteId)
|
||||
},
|
||||
|
||||
{
|
||||
title: !isArchived ? t("tree-context-menu.archive") : t("tree-context-menu.unarchive"),
|
||||
uiIcon: !isArchived ? "bx bx-archive" : "bx bx-archive-out",
|
||||
handler: () => {
|
||||
if (!isArchived) {
|
||||
attributes.addLabel(note.noteId, "archived");
|
||||
} else {
|
||||
attributes.removeOwnedLabelByName(note, "archived");
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t("tree-context-menu.delete"),
|
||||
command: "deleteNotes",
|
||||
uiIcon: "bx bx-trash destructive-action-icon",
|
||||
enabled: isNotRoot && !isHoisted && parentNotSearch && notOptionsOrHelp,
|
||||
handler: () => branches.deleteNotes([ branchId ])
|
||||
},
|
||||
{ kind: "separator" },
|
||||
(notOptionsOrHelp ? {
|
||||
kind: "custom",
|
||||
componentFn: () => {
|
||||
return NoteColorPicker({note});
|
||||
}
|
||||
} : null),
|
||||
{ kind: "separator" },
|
||||
{
|
||||
title: t("tree-context-menu.recent-changes-in-subtree"),
|
||||
uiIcon: "bx bx-history",
|
||||
enabled: notOptionsOrHelp,
|
||||
handler: () => parentComponent?.triggerCommand("showRecentChanges", { ancestorNoteId: noteId })
|
||||
},
|
||||
{
|
||||
title: t("tree-context-menu.search-in-subtree"),
|
||||
command: "searchInSubtree",
|
||||
uiIcon: "bx bx-search",
|
||||
enabled: notSearch
|
||||
}
|
||||
];
|
||||
|
||||
contextMenu.show({
|
||||
items: items.filter(Boolean) as MenuItem<TreeCommandNames>[],
|
||||
x: e.pageX,
|
||||
y: e.pageY,
|
||||
selectMenuItemHandler: ({ command }) => {
|
||||
if (link_context_menu.handleLinkContextMenuItem(command, e, notePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!command) return;
|
||||
parentComponent?.triggerCommand(command, {
|
||||
noteId,
|
||||
notePath,
|
||||
selectedOrActiveBranchIds: [ branchId ],
|
||||
selectedOrActiveNoteIds: [ noteId ]
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Empty context menu
|
||||
function buildEmptyAreaContextMenu(parentComponent: Component | null, notePath: string | null | undefined) {
|
||||
return (e: MouseEvent) => {
|
||||
const hideArchivedNotes = (options.get("hideArchivedNotes_main") === "true");
|
||||
|
||||
e.preventDefault();
|
||||
contextMenu.show({
|
||||
items: [
|
||||
{
|
||||
title: t("breadcrumb.empty_hide_archived_notes"),
|
||||
handler: async () => {
|
||||
await options.save("hideArchivedNotes_main", !hideArchivedNotes ? "true" : "false");
|
||||
|
||||
// Note tree doesn't update by itself.
|
||||
parentComponent?.triggerEvent("frocaReloaded", {});
|
||||
},
|
||||
checked: hideArchivedNotes
|
||||
},
|
||||
{ kind: "separator" },
|
||||
{
|
||||
title: t("tree-context-menu.copy-note-path-to-clipboard"),
|
||||
command: "copyNotePathToClipboard",
|
||||
uiIcon: "bx bx-directions",
|
||||
handler: () => copyTextWithToast(`#${notePath}`)
|
||||
},
|
||||
],
|
||||
x: e.pageX,
|
||||
y: e.pageY,
|
||||
selectMenuItemHandler: () => {}
|
||||
});
|
||||
};
|
||||
}
|
||||
//#endregion
|
||||
156
apps/client/src/widgets/layout/InlineTitle.css
Normal file
156
apps/client/src/widgets/layout/InlineTitle.css
Normal file
@@ -0,0 +1,156 @@
|
||||
:root {
|
||||
--title-transition: opacity 200ms ease-in;
|
||||
}
|
||||
|
||||
.component.inline-title {
|
||||
contain: none;
|
||||
}
|
||||
|
||||
.inline-title {
|
||||
max-width: var(--max-content-width);
|
||||
container-type: inline-size;
|
||||
padding-inline-start: 24px;
|
||||
|
||||
& > .inline-title-row {
|
||||
--icon-size: 35px;
|
||||
--icon-padding-size: 10px;
|
||||
--title-size: 1.8em;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
transition: var(--title-transition);
|
||||
|
||||
&.hidden {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.note-icon-widget {
|
||||
--note-icon-size: 35px;
|
||||
--note-icon-container-padding-size: 10px;
|
||||
}
|
||||
|
||||
.note-title-caption {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.note-title-widget {
|
||||
--note-title-size: 1.8em;
|
||||
--note-title-padding-inline: 0;
|
||||
}
|
||||
|
||||
@container (max-width: 500px) {
|
||||
.note-icon-widget {
|
||||
--note-icon-size: 25px;
|
||||
--note-icon-container-padding-size: 6px;
|
||||
}
|
||||
|
||||
.note-title-widget {
|
||||
--note-title-size: 1.5em;
|
||||
}
|
||||
|
||||
.title-details {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.title-row {
|
||||
&.note-icon-widget,
|
||||
&.note-title-widget {
|
||||
transition: var(--title-transition);
|
||||
}
|
||||
|
||||
&.hide-title .note-icon-widget,
|
||||
&.hide-title .note-title-widget {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.note-split.type-code:not(.mime-text-x-sqlite) .inline-title {
|
||||
background-color: var(--main-background-color);
|
||||
}
|
||||
|
||||
body.prefers-centered-content .inline-title {
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.title-details {
|
||||
display: flex;
|
||||
gap: 0.25em;
|
||||
margin: 0;
|
||||
margin-top: .2rem;
|
||||
list-style-type: none;
|
||||
opacity: .5;
|
||||
font-size: .85rem;
|
||||
line-height: 1;
|
||||
flex-wrap: nowrap;
|
||||
white-space: nowrap;
|
||||
|
||||
span.value {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes note-type-switcher-intro {
|
||||
from {
|
||||
opacity: 0;
|
||||
} to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.note-type-switcher {
|
||||
--badge-radius: 12px;
|
||||
|
||||
position: relative;
|
||||
top: 5px;
|
||||
padding: .25em 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow-x: auto;
|
||||
min-width: 0;
|
||||
gap: 5px;
|
||||
min-height: 35px;
|
||||
|
||||
>* {
|
||||
flex-shrink: 0;
|
||||
animation: note-type-switcher-intro 200ms ease-in;
|
||||
}
|
||||
|
||||
.ext-badge {
|
||||
--color: var(--input-background-color);
|
||||
color: var(--main-text-color);
|
||||
font-size: 0.9rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.edited-notes {
|
||||
padding: 1.5em 0;
|
||||
|
||||
.collapsible-inner-body {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.3em;
|
||||
|
||||
.badge {
|
||||
margin: 0;
|
||||
a.tn-link {
|
||||
color: inherit;
|
||||
text-transform: none;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
346
apps/client/src/widgets/layout/InlineTitle.tsx
Normal file
346
apps/client/src/widgets/layout/InlineTitle.tsx
Normal file
@@ -0,0 +1,346 @@
|
||||
import "./InlineTitle.css";
|
||||
|
||||
import { NoteType } from "@triliumnext/commons";
|
||||
import clsx from "clsx";
|
||||
import { ComponentChild } from "preact";
|
||||
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
import FNote from "../../entities/fnote";
|
||||
import attributes from "../../services/attributes";
|
||||
import froca from "../../services/froca";
|
||||
import { t } from "../../services/i18n";
|
||||
import { ViewScope } from "../../services/link";
|
||||
import { NOTE_TYPES, NoteTypeMapping } from "../../services/note_types";
|
||||
import server from "../../services/server";
|
||||
import { formatDateTime } from "../../utils/formatters";
|
||||
import NoteIcon from "../note_icon";
|
||||
import NoteTitleWidget from "../note_title";
|
||||
import SimpleBadge, { Badge, BadgeWithDropdown } from "../react/Badge";
|
||||
import Collapsible from "../react/Collapsible";
|
||||
import { FormDropdownDivider, FormListItem } from "../react/FormList";
|
||||
import { useNoteBlob, useNoteContext, useNoteLabel, useNoteProperty, useStaticTooltip, useTriliumEvent, useTriliumOptionBool } from "../react/hooks";
|
||||
import NoteLink from "../react/NoteLink";
|
||||
import { joinElements } from "../react/react_utils";
|
||||
import { useEditedNotes } from "../ribbon/EditedNotesTab";
|
||||
import { useNoteMetadata } from "../ribbon/NoteInfoTab";
|
||||
import { onWheelHorizontalScroll } from "../widget_utils";
|
||||
|
||||
const supportedNoteTypes = new Set<NoteType>([
|
||||
"text", "code"
|
||||
]);
|
||||
|
||||
export default function InlineTitle() {
|
||||
const { note, parentComponent, viewScope } = useNoteContext();
|
||||
const type = useNoteProperty(note, "type");
|
||||
const [ shown, setShown ] = useState(shouldShow(note?.noteId, type, viewScope));
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [ titleHidden, setTitleHidden ] = useState(false);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
setShown(shouldShow(note?.noteId, type, viewScope));
|
||||
}, [ note, type, viewScope ]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!shown) return;
|
||||
|
||||
const titleRow = parentComponent.$widget[0].closest(".note-split")?.querySelector(":scope > .title-row");
|
||||
if (!titleRow) return;
|
||||
|
||||
titleRow.classList.toggle("hide-title", true);
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
titleRow.classList.toggle("hide-title", entries[0].isIntersecting);
|
||||
setTitleHidden(!entries[0].isIntersecting);
|
||||
}, {
|
||||
threshold: 0.85
|
||||
});
|
||||
if (containerRef.current) {
|
||||
observer.observe(containerRef.current);
|
||||
}
|
||||
|
||||
return () => {
|
||||
titleRow.classList.remove("hide-title");
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [ shown, parentComponent ]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={clsx("inline-title", !shown && "hidden")}
|
||||
>
|
||||
<div class={clsx("inline-title-row", titleHidden && "hidden")}>
|
||||
<NoteIcon />
|
||||
<div class="note-title-caption">
|
||||
<NoteTitleWidget />
|
||||
<NoteTitleDetails />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<EditedNotes />
|
||||
<NoteTypeSwitcher />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function shouldShow(noteId: string | undefined, type: NoteType | undefined, viewScope: ViewScope | undefined) {
|
||||
if (viewScope?.viewMode !== "default") return false;
|
||||
if (noteId?.startsWith("_options")) return true;
|
||||
return type && supportedNoteTypes.has(type);
|
||||
}
|
||||
|
||||
//#region Title details
|
||||
export function NoteTitleDetails() {
|
||||
const { note } = useNoteContext();
|
||||
const { metadata } = useNoteMetadata(note);
|
||||
const isHiddenNote = note?.noteId.startsWith("_");
|
||||
|
||||
const items: ComponentChild[] = [
|
||||
(!isHiddenNote && metadata?.dateCreated &&
|
||||
<TextWithValue
|
||||
i18nKey="note_title.created_on"
|
||||
value={formatDateTime(metadata.dateCreated, "medium", "none")}
|
||||
valueTooltip={formatDateTime(metadata.dateCreated, "full", "long")}
|
||||
/>),
|
||||
(!isHiddenNote && metadata?.dateModified &&
|
||||
<TextWithValue
|
||||
i18nKey="note_title.last_modified"
|
||||
value={formatDateTime(metadata.dateModified, "medium", "none")}
|
||||
valueTooltip={formatDateTime(metadata.dateModified, "full", "long")}
|
||||
/>)
|
||||
].filter(item => !!item);
|
||||
|
||||
return items.length > 0 && (
|
||||
<div className="title-details">
|
||||
{joinElements(items, " • ")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TextWithValue({ i18nKey, value, valueTooltip }: {
|
||||
i18nKey: string;
|
||||
value: string;
|
||||
valueTooltip: string;
|
||||
}) {
|
||||
const listItemRef = useRef<HTMLLIElement>(null);
|
||||
useStaticTooltip(listItemRef, {
|
||||
selector: "span.value",
|
||||
title: valueTooltip,
|
||||
popperConfig: { placement: "bottom" }
|
||||
});
|
||||
|
||||
return (
|
||||
<li ref={listItemRef}>
|
||||
<Trans
|
||||
i18nKey={i18nKey}
|
||||
components={{
|
||||
Value: <span className="value">{value}</span> as React.ReactElement
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Note type switcher
|
||||
const SWITCHER_PINNED_NOTE_TYPES = new Set<NoteType>([ "text", "code", "book", "canvas" ]);
|
||||
|
||||
function NoteTypeSwitcher() {
|
||||
const { note } = useNoteContext();
|
||||
const blob = useNoteBlob(note);
|
||||
const currentNoteType = useNoteProperty(note, "type");
|
||||
const { pinnedNoteTypes, restNoteTypes } = useMemo(() => {
|
||||
const pinnedNoteTypes: NoteTypeMapping[] = [];
|
||||
const restNoteTypes: NoteTypeMapping[] = [];
|
||||
for (const noteType of NOTE_TYPES) {
|
||||
if (noteType.reserved || noteType.static || noteType.type === "book") continue;
|
||||
if (SWITCHER_PINNED_NOTE_TYPES.has(noteType.type)) {
|
||||
pinnedNoteTypes.push(noteType);
|
||||
} else {
|
||||
restNoteTypes.push(noteType);
|
||||
}
|
||||
}
|
||||
return { pinnedNoteTypes, restNoteTypes };
|
||||
}, []);
|
||||
const currentNoteTypeData = useMemo(() => NOTE_TYPES.find(t => t.type === currentNoteType), [ currentNoteType ]);
|
||||
const { builtinTemplates, collectionTemplates } = useBuiltinTemplates();
|
||||
|
||||
return (currentNoteType && supportedNoteTypes.has(currentNoteType) &&
|
||||
<div
|
||||
className="note-type-switcher"
|
||||
onWheel={onWheelHorizontalScroll}
|
||||
>
|
||||
{note && blob?.contentLength === 0 && (
|
||||
<>
|
||||
<div className="intro">{t("note_title.note_type_switcher_label", { type: currentNoteTypeData?.title.toLocaleLowerCase() })}</div>
|
||||
{pinnedNoteTypes.map(noteType => noteType.type !== currentNoteType && (
|
||||
<Badge
|
||||
key={noteType.type}
|
||||
text={noteType.title}
|
||||
icon={`bx ${noteType.icon}`}
|
||||
onClick={() => switchNoteType(note.noteId, noteType)}
|
||||
/>
|
||||
))}
|
||||
{collectionTemplates.length > 0 && <CollectionNoteTypes noteId={note.noteId} collectionTemplates={collectionTemplates} />}
|
||||
{builtinTemplates.length > 0 && <TemplateNoteTypes noteId={note.noteId} builtinTemplates={builtinTemplates} />}
|
||||
{restNoteTypes.length > 0 && <MoreNoteTypes noteId={note.noteId} restNoteTypes={restNoteTypes} />}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MoreNoteTypes({ noteId, restNoteTypes }: { noteId: string, restNoteTypes: NoteTypeMapping[] }) {
|
||||
return (
|
||||
<BadgeWithDropdown
|
||||
text={t("note_title.note_type_switcher_others")}
|
||||
icon="bx bx-dots-vertical-rounded"
|
||||
>
|
||||
{restNoteTypes.map(noteType => (
|
||||
<FormListItem
|
||||
key={noteType.type}
|
||||
icon={`bx ${noteType.icon}`}
|
||||
onClick={() => switchNoteType(noteId, noteType)}
|
||||
>{noteType.title}</FormListItem>
|
||||
))}
|
||||
</BadgeWithDropdown>
|
||||
);
|
||||
}
|
||||
|
||||
function CollectionNoteTypes({ noteId, collectionTemplates }: { noteId: string, collectionTemplates: FNote[] }) {
|
||||
return (
|
||||
<BadgeWithDropdown
|
||||
text={t("note_title.note_type_switcher_collection")}
|
||||
icon="bx bx-book"
|
||||
>
|
||||
{collectionTemplates.map(collectionTemplate => (
|
||||
<FormListItem
|
||||
key={collectionTemplate.noteId}
|
||||
icon={collectionTemplate.getIcon()}
|
||||
onClick={() => setTemplate(noteId, collectionTemplate.noteId)}
|
||||
>{collectionTemplate.title}</FormListItem>
|
||||
))}
|
||||
</BadgeWithDropdown>
|
||||
);
|
||||
}
|
||||
|
||||
function TemplateNoteTypes({ noteId, builtinTemplates }: { noteId: string, builtinTemplates: FNote[] }) {
|
||||
const [ userTemplates, setUserTemplates ] = useState<FNote[]>([]);
|
||||
|
||||
async function refreshTemplates() {
|
||||
const templateNoteIds = await server.get<string[]>("search-templates");
|
||||
const templateNotes = await froca.getNotes(templateNoteIds);
|
||||
setUserTemplates(templateNotes);
|
||||
}
|
||||
|
||||
// First load.
|
||||
useEffect(() => {
|
||||
refreshTemplates();
|
||||
}, []);
|
||||
|
||||
// React to external changes.
|
||||
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
|
||||
if (loadResults.getAttributeRows().some(attr => attr.type === "label" && attr.name === "template")) {
|
||||
refreshTemplates();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<BadgeWithDropdown
|
||||
text={t("note_title.note_type_switcher_templates")}
|
||||
icon="bx bx-copy-alt"
|
||||
>
|
||||
{userTemplates.map(template => <TemplateItem key={template.noteId} noteId={noteId} template={template} />)}
|
||||
{userTemplates.length > 0 && <FormDropdownDivider />}
|
||||
{builtinTemplates.map(template => <TemplateItem key={template.noteId} noteId={noteId} template={template} />)}
|
||||
</BadgeWithDropdown>
|
||||
);
|
||||
}
|
||||
|
||||
function TemplateItem({ noteId, template }: { noteId: string, template: FNote }) {
|
||||
return (
|
||||
<FormListItem
|
||||
icon={template.getIcon()}
|
||||
onClick={() => setTemplate(noteId, template.noteId)}
|
||||
>{template.title}</FormListItem>
|
||||
);
|
||||
}
|
||||
|
||||
function switchNoteType(noteId: string, { type, mime }: NoteTypeMapping) {
|
||||
return server.put(`notes/${noteId}/type`, { type, mime });
|
||||
}
|
||||
|
||||
function setTemplate(noteId: string, templateId: string) {
|
||||
return attributes.setRelation(noteId, "template", templateId);
|
||||
}
|
||||
|
||||
function useBuiltinTemplates() {
|
||||
const [ templates, setTemplates ] = useState<{
|
||||
builtinTemplates: FNote[];
|
||||
collectionTemplates: FNote[];
|
||||
}>({
|
||||
builtinTemplates: [],
|
||||
collectionTemplates: []
|
||||
});
|
||||
|
||||
async function loadBuiltinTemplates() {
|
||||
const templatesRoot = await froca.getNote("_templates");
|
||||
if (!templatesRoot) return;
|
||||
const childNotes = await templatesRoot.getChildNotes();
|
||||
const builtinTemplates: FNote[] = [];
|
||||
const collectionTemplates: FNote[] = [];
|
||||
for (const childNote of childNotes) {
|
||||
if (!childNote.hasLabel("template")) continue;
|
||||
if (childNote.hasLabel("collection")) {
|
||||
collectionTemplates.push(childNote);
|
||||
} else {
|
||||
builtinTemplates.push(childNote);
|
||||
}
|
||||
}
|
||||
setTemplates({ builtinTemplates, collectionTemplates });
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadBuiltinTemplates();
|
||||
}, []);
|
||||
|
||||
return templates;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Edited Notes
|
||||
function EditedNotes() {
|
||||
const { note } = useNoteContext();
|
||||
const [ dateNote ] = useNoteLabel(note, "dateNote");
|
||||
const [ editedNotesOpenInRibbon ] = useTriliumOptionBool("editedNotesOpenInRibbon");
|
||||
|
||||
return (note && dateNote &&
|
||||
<Collapsible
|
||||
className="edited-notes"
|
||||
title={t("note_title.edited_notes")}
|
||||
initiallyExpanded={editedNotesOpenInRibbon}
|
||||
>
|
||||
<EditedNotesContent note={note} />
|
||||
</Collapsible>
|
||||
);
|
||||
}
|
||||
|
||||
function EditedNotesContent({ note }: { note: FNote }) {
|
||||
const editedNotes = useEditedNotes(note);
|
||||
|
||||
return (editedNotes !== undefined &&
|
||||
(editedNotes.length > 0 ? editedNotes?.map(editedNote => (
|
||||
<SimpleBadge
|
||||
key={editedNote.noteId}
|
||||
title={(
|
||||
<NoteLink
|
||||
notePath={editedNote.noteId}
|
||||
showNoteIcon
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)) : (
|
||||
<div className="no-edited-notes-found">{t("edited_notes.no_edited_notes_found")}</div>
|
||||
)));
|
||||
}
|
||||
//#endregion
|
||||
27
apps/client/src/widgets/layout/NoteBadges.css
Normal file
27
apps/client/src/widgets/layout/NoteBadges.css
Normal file
@@ -0,0 +1,27 @@
|
||||
.component.note-badges {
|
||||
contain: none;
|
||||
}
|
||||
|
||||
.note-badges {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
min-width: 0;
|
||||
flex-shrink: 1;
|
||||
overflow: hidden;
|
||||
--badge-radius: 12px;
|
||||
|
||||
.ext-badge {
|
||||
&.temporarily-editable-badge { --color: #4fa52b; }
|
||||
&.read-only-badge { --color: #e33f3b; }
|
||||
&.share-badge { --color: #3b82f6; }
|
||||
&.clipped-note-badge { --color: #57a2a5; }
|
||||
&.execute-badge { --color: #f59e0b; }
|
||||
}
|
||||
|
||||
.dropdown-badge {
|
||||
&.dropdown-backlinks-badge .dropdown-menu {
|
||||
min-width: 500px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
107
apps/client/src/widgets/layout/NoteBadges.tsx
Normal file
107
apps/client/src/widgets/layout/NoteBadges.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
import "./NoteBadges.css";
|
||||
|
||||
import { copyTextWithToast } from "../../services/clipboard_ext";
|
||||
import { t } from "../../services/i18n";
|
||||
import { goToLinkExt } from "../../services/link";
|
||||
import { Badge, BadgeWithDropdown } from "../react/Badge";
|
||||
import { FormDropdownDivider, FormListItem } from "../react/FormList";
|
||||
import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteLabelBoolean } from "../react/hooks";
|
||||
import { useShareState } from "../ribbon/BasicPropertiesTab";
|
||||
import { useShareInfo } from "../shared_info";
|
||||
|
||||
export default function NoteBadges() {
|
||||
return (
|
||||
<div className="note-badges">
|
||||
<ReadOnlyBadge />
|
||||
<ShareBadge />
|
||||
<ClippedNoteBadge />
|
||||
<ExecuteBadge />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ReadOnlyBadge() {
|
||||
const { note, noteContext } = useNoteContext();
|
||||
const { isReadOnly, enableEditing, temporarilyEditable } = useIsNoteReadOnly(note, noteContext);
|
||||
const isExplicitReadOnly = note?.isLabelTruthy("readOnly");
|
||||
|
||||
if (temporarilyEditable) {
|
||||
return <Badge
|
||||
icon="bx bx-lock-open-alt"
|
||||
text={t("breadcrumb_badges.read_only_temporarily_disabled")}
|
||||
tooltip={t("breadcrumb_badges.read_only_temporarily_disabled_description")}
|
||||
className="temporarily-editable-badge"
|
||||
onClick={() => enableEditing(false)}
|
||||
/>;
|
||||
} else if (isReadOnly) {
|
||||
return <Badge
|
||||
icon="bx bx-lock-alt"
|
||||
text={isExplicitReadOnly ? t("breadcrumb_badges.read_only_explicit") : t("breadcrumb_badges.read_only_auto")}
|
||||
tooltip={isExplicitReadOnly ? t("breadcrumb_badges.read_only_explicit_description") : t("breadcrumb_badges.read_only_auto_description")}
|
||||
className="read-only-badge"
|
||||
onClick={() => enableEditing()}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
function ShareBadge() {
|
||||
const { note } = useNoteContext();
|
||||
const [ , switchShareState ] = useShareState(note);
|
||||
const { isSharedExternally, linkHref } = useShareInfo(note);
|
||||
|
||||
return (linkHref &&
|
||||
<BadgeWithDropdown
|
||||
icon={isSharedExternally ? "bx bx-world" : "bx bx-share-alt"}
|
||||
text={isSharedExternally ? t("breadcrumb_badges.shared_publicly") : t("breadcrumb_badges.shared_locally")}
|
||||
className="share-badge"
|
||||
>
|
||||
<FormListItem
|
||||
icon="bx bx-copy"
|
||||
onClick={() => copyTextWithToast(linkHref)}
|
||||
>{t("breadcrumb_badges.shared_copy_to_clipboard")}</FormListItem>
|
||||
<FormListItem
|
||||
icon="bx bx-link-external"
|
||||
onClick={(e) => goToLinkExt(e, linkHref)}
|
||||
>{t("breadcrumb_badges.shared_open_in_browser")}</FormListItem>
|
||||
<FormDropdownDivider />
|
||||
<FormListItem
|
||||
icon="bx bx-unlink"
|
||||
onClick={() => switchShareState(false)}
|
||||
>{t("breadcrumb_badges.shared_unshare")}</FormListItem>
|
||||
</BadgeWithDropdown>
|
||||
);
|
||||
}
|
||||
|
||||
function ClippedNoteBadge() {
|
||||
const { note } = useNoteContext();
|
||||
const [ pageUrl ] = useNoteLabel(note, "pageUrl");
|
||||
|
||||
return (pageUrl &&
|
||||
<Badge
|
||||
className="clipped-note-badge"
|
||||
icon="bx bx-globe"
|
||||
text={t("breadcrumb_badges.clipped_note")}
|
||||
tooltip={t("breadcrumb_badges.clipped_note_description", { url: pageUrl })}
|
||||
href={pageUrl}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ExecuteBadge() {
|
||||
const { note, parentComponent } = useNoteContext();
|
||||
const isScript = note?.isTriliumScript();
|
||||
const isSql = note?.isTriliumSqlite();
|
||||
const isExecutable = isScript || isSql;
|
||||
const [ executeDescription ] = useNoteLabel(note, "executeDescription");
|
||||
const [ executeButton ] = useNoteLabelBoolean(note, "executeButton");
|
||||
|
||||
return (note && isExecutable && (executeDescription || executeButton) &&
|
||||
<Badge
|
||||
className="execute-badge"
|
||||
icon="bx bx-play"
|
||||
text={isScript ? t("breadcrumb_badges.execute_script") : t("breadcrumb_badges.execute_sql")}
|
||||
tooltip={executeDescription || (isScript ? t("breadcrumb_badges.execute_script_description") : t("breadcrumb_badges.execute_sql_description"))}
|
||||
onClick={() => parentComponent.triggerCommand("runActiveNote")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
11
apps/client/src/widgets/layout/NoteTitleActions.css
Normal file
11
apps/client/src/widgets/layout/NoteTitleActions.css
Normal file
@@ -0,0 +1,11 @@
|
||||
body.experimental-feature-new-layout {
|
||||
.component.title-actions {
|
||||
contain: none;
|
||||
}
|
||||
|
||||
.title-actions {
|
||||
&.visible:not(:empty) {
|
||||
padding: 0.75em 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
73
apps/client/src/widgets/layout/NoteTitleActions.tsx
Normal file
73
apps/client/src/widgets/layout/NoteTitleActions.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import "./NoteTitleActions.css";
|
||||
|
||||
import clsx from "clsx";
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import NoteContext from "../../components/note_context";
|
||||
import FNote from "../../entities/fnote";
|
||||
import { t } from "../../services/i18n";
|
||||
import CollectionProperties from "../note_bars/CollectionProperties";
|
||||
import { checkFullHeight, getExtendedWidgetType } from "../NoteDetail";
|
||||
import { PromotedAttributesContent, usePromotedAttributeData } from "../PromotedAttributes";
|
||||
import Collapsible, { ExternallyControlledCollapsible } from "../react/Collapsible";
|
||||
import { useNoteContext, useNoteProperty, useTriliumEvent } from "../react/hooks";
|
||||
import SearchDefinitionTab from "../ribbon/SearchDefinitionTab";
|
||||
|
||||
export default function NoteTitleActions() {
|
||||
const { note, ntxId, componentId, noteContext } = useNoteContext();
|
||||
const isHiddenNote = note && note.noteId !== "_search" && note.noteId.startsWith("_");
|
||||
const noteType = useNoteProperty(note, "type");
|
||||
|
||||
const items = [
|
||||
note && <PromotedAttributes note={note} componentId={componentId} noteContext={noteContext} />,
|
||||
note && noteType === "search" && <SearchProperties note={note} ntxId={ntxId} />,
|
||||
note && !isHiddenNote && noteType === "book" && <CollectionProperties note={note} />
|
||||
].filter(Boolean);
|
||||
|
||||
return (
|
||||
<div className={clsx("title-actions", items.length > 0 && "visible")}>
|
||||
{items}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SearchProperties({ note, ntxId }: { note: FNote, ntxId: string | null | undefined }) {
|
||||
return (note &&
|
||||
<Collapsible
|
||||
title={t("search_definition.search_parameters")}
|
||||
initiallyExpanded={note.isInHiddenSubtree()} // not saved searches
|
||||
>
|
||||
<SearchDefinitionTab note={note} ntxId={ntxId} hidden={false} />
|
||||
</Collapsible>
|
||||
);
|
||||
}
|
||||
|
||||
function PromotedAttributes({ note, componentId, noteContext }: {
|
||||
note: FNote | null | undefined,
|
||||
componentId: string,
|
||||
noteContext: NoteContext | undefined
|
||||
}) {
|
||||
const [ cells, setCells ] = usePromotedAttributeData(note, componentId);
|
||||
const [ expanded, setExpanded ] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
getExtendedWidgetType(note, noteContext).then(extendedNoteType => {
|
||||
const fullHeight = checkFullHeight(noteContext, extendedNoteType);
|
||||
setExpanded(!fullHeight);
|
||||
});
|
||||
}, [ note, noteContext ]);
|
||||
|
||||
// Keyboard shortcut.
|
||||
useTriliumEvent("toggleRibbonTabPromotedAttributes", () => setExpanded(!expanded));
|
||||
|
||||
if (!cells?.length) return false;
|
||||
return (note && (
|
||||
<ExternallyControlledCollapsible
|
||||
key={note.noteId}
|
||||
title={t("promoted_attributes.promoted_attributes")}
|
||||
expanded={expanded} setExpanded={setExpanded}
|
||||
>
|
||||
<PromotedAttributesContent note={note} componentId={componentId} cells={cells} setCells={setCells} />
|
||||
</ExternallyControlledCollapsible>
|
||||
));
|
||||
}
|
||||
263
apps/client/src/widgets/layout/StatusBar.css
Normal file
263
apps/client/src/widgets/layout/StatusBar.css
Normal file
@@ -0,0 +1,263 @@
|
||||
.component.status-bar {
|
||||
contain: none;
|
||||
border-top: 1px solid var(--main-border-color);
|
||||
background-color: var(--left-pane-background-color);
|
||||
|
||||
> .status-bar-main-row {
|
||||
min-height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-inline: 0.25em;
|
||||
font-size: 0.85em;
|
||||
|
||||
> .breadcrumb {
|
||||
flex-grow: 1;
|
||||
--icon-button-size: 23px;
|
||||
}
|
||||
|
||||
> .actions-row {
|
||||
padding: 0.1em;
|
||||
display: flex;
|
||||
gap: 0.1em;
|
||||
|
||||
.btn {
|
||||
padding: 0 0.5em !important;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 0;
|
||||
|
||||
span:first-of-type {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
&.active,
|
||||
&.dropdown-toggle.show,
|
||||
&:focus,
|
||||
&:hover {
|
||||
background: var(--input-background-color);
|
||||
}
|
||||
}
|
||||
|
||||
.status-bar-dropdown-button {
|
||||
&:after {
|
||||
content: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
.dropdown-toggle {
|
||||
padding: 0.1em 0.25em;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
width: max-content;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-note-info {
|
||||
padding: 1em !important;
|
||||
|
||||
ul {
|
||||
--row-block-margin: .2em;
|
||||
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin-top: calc(0px - var(--row-block-margin));
|
||||
margin-bottom: 12px;
|
||||
display: table;
|
||||
|
||||
li {
|
||||
display: table-row;
|
||||
|
||||
> strong {
|
||||
display: table-cell;
|
||||
padding: var(--row-block-margin) 0;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
> span {
|
||||
display: table-cell;
|
||||
user-select: text;
|
||||
padding-left: 2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-note-paths {
|
||||
.note-paths-widget {
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.note-path-intro {
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
|
||||
.note-path-list {
|
||||
margin: 12px 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
/* Note path card */
|
||||
li {
|
||||
--border-radius: 6px;
|
||||
|
||||
position: relative;
|
||||
background: var(--card-background-color);
|
||||
padding: 8px 20px 8px 25px;
|
||||
|
||||
&:first-child {
|
||||
border-radius: var(--border-radius) var(--border-radius) 0 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||
}
|
||||
|
||||
& + li {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* Current path arrow */
|
||||
&.path-current::before {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
content: "\ee8f";
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 20px;
|
||||
bottom: 0;
|
||||
font-family: "boxicons";
|
||||
font-size: .75em;
|
||||
color: var(--menu-item-icon-color);
|
||||
}
|
||||
}
|
||||
|
||||
/* Note path segment */
|
||||
a {
|
||||
margin-inline: 2px;
|
||||
padding-inline: 2px;
|
||||
color: currentColor;
|
||||
font-weight: normal;
|
||||
text-decoration: none;
|
||||
|
||||
/* The last segment of the note path */
|
||||
&.basename {
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-code-note-switcher {
|
||||
max-height: 90vh;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.backlinks-widget > .dropdown-menu {
|
||||
--menu-padding-size: .9em;
|
||||
|
||||
max-height: 60vh;
|
||||
overflow-y: scroll;
|
||||
|
||||
/* Backlink card */
|
||||
li {
|
||||
--border-radius: 8px;
|
||||
|
||||
max-width: 600px;
|
||||
padding: 10px 20px;
|
||||
background: var(--card-background-color);
|
||||
|
||||
& + li {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-radius: var(--border-radius) var(--border-radius) 0 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||
}
|
||||
|
||||
/* Card header */
|
||||
& > span:first-child {
|
||||
display: block;
|
||||
|
||||
> span {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
|
||||
/* Note path */
|
||||
> small {
|
||||
flex: 100%;
|
||||
order: -1;
|
||||
font-size: .65rem;
|
||||
|
||||
.note-path {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Note icon */
|
||||
> .bx {
|
||||
color: var(--menu-item-icon-color);
|
||||
}
|
||||
|
||||
/* Note title */
|
||||
> a {
|
||||
margin-inline-start: 4px;
|
||||
color: currentColor;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Card content - excerpt */
|
||||
& > span:nth-child(2) > div {
|
||||
all: unset; /* TODO: Remove after disposing the old style from FloatingButtons.css */
|
||||
display: block;
|
||||
|
||||
margin: 8px 0;
|
||||
border-radius: 4px;
|
||||
background: var(--quick-search-result-content-background);
|
||||
padding: 8px;
|
||||
font-size: .75rem;
|
||||
|
||||
a {
|
||||
background: transparent;
|
||||
color: var(--quick-search-result-highlight-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .attribute-list {
|
||||
font-size: 0.9em;
|
||||
padding: 0.5em 0.75em;
|
||||
|
||||
.inherited-attributes-widget > div {
|
||||
padding: 0;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.attribute-list-editor {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
441
apps/client/src/widgets/layout/StatusBar.tsx
Normal file
441
apps/client/src/widgets/layout/StatusBar.tsx
Normal file
@@ -0,0 +1,441 @@
|
||||
import "./StatusBar.css";
|
||||
|
||||
import { KeyboardActionNames, Locale } from "@triliumnext/commons";
|
||||
import { Dropdown as BootstrapDropdown } from "bootstrap";
|
||||
import clsx from "clsx";
|
||||
import { type ComponentChildren } from "preact";
|
||||
import { createPortal } from "preact/compat";
|
||||
import { useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
|
||||
import { CommandNames } from "../../components/app_context";
|
||||
import NoteContext from "../../components/note_context";
|
||||
import FNote, { NOTE_TYPE_ICONS } from "../../entities/fnote";
|
||||
import attributes from "../../services/attributes";
|
||||
import { t } from "../../services/i18n";
|
||||
import { ViewScope } from "../../services/link";
|
||||
import { NOTE_TYPES } from "../../services/note_types";
|
||||
import server from "../../services/server";
|
||||
import { openInAppHelpFromUrl } from "../../services/utils";
|
||||
import { formatDateTime } from "../../utils/formatters";
|
||||
import { BacklinksList, useBacklinkCount } from "../FloatingButtonsDefinitions";
|
||||
import Dropdown, { DropdownProps } from "../react/Dropdown";
|
||||
import { FormDropdownDivider, FormListItem } from "../react/FormList";
|
||||
import { useActiveNoteContext, useLegacyImperativeHandlers, useNoteLabel, useNoteProperty, useStaticTooltip, useTriliumEvent, useTriliumEvents } from "../react/hooks";
|
||||
import Icon from "../react/Icon";
|
||||
import LinkButton from "../react/LinkButton";
|
||||
import { ParentComponent } from "../react/react_utils";
|
||||
import { ContentLanguagesModal, NoteTypeCodeNoteList, NoteTypeOptionsModal, useLanguageSwitcher, useMimeTypes } from "../ribbon/BasicPropertiesTab";
|
||||
import AttributeEditor, { AttributeEditorImperativeHandlers } from "../ribbon/components/AttributeEditor";
|
||||
import InheritedAttributesTab from "../ribbon/InheritedAttributesTab";
|
||||
import { NoteSizeWidget, useNoteMetadata } from "../ribbon/NoteInfoTab";
|
||||
import { NotePathsWidget, useSortedNotePaths } from "../ribbon/NotePathsTab";
|
||||
import SimilarNotesTab from "../ribbon/SimilarNotesTab";
|
||||
import { useAttachments } from "../type_widgets/Attachment";
|
||||
import { useProcessedLocales } from "../type_widgets/options/components/LocaleSelector";
|
||||
import Breadcrumb from "./Breadcrumb";
|
||||
|
||||
interface StatusBarContext {
|
||||
note: FNote;
|
||||
notePath: string | null | undefined;
|
||||
noteContext: NoteContext;
|
||||
viewScope?: ViewScope;
|
||||
hoistedNoteId?: string;
|
||||
}
|
||||
|
||||
export default function StatusBar() {
|
||||
const { note, notePath, noteContext, viewScope, hoistedNoteId } = useActiveNoteContext();
|
||||
const [ activePane, setActivePane ] = useState<"attributes" | "similar-notes" | false>(false);
|
||||
const context: StatusBarContext | undefined | null = note && noteContext && { note, notePath, noteContext, viewScope, hoistedNoteId };
|
||||
const attributesContext: AttributesProps | undefined | null = context && {
|
||||
...context,
|
||||
attributesShown: activePane === "attributes",
|
||||
setAttributesShown: (shown) => setActivePane(shown && "attributes")
|
||||
};
|
||||
const noteInfoContext: NoteInfoContext | undefined | null = context && {
|
||||
...context,
|
||||
similarNotesShown: activePane === "similar-notes",
|
||||
setSimilarNotesShown: (shown) => setActivePane(shown && "similar-notes")
|
||||
};
|
||||
const isHiddenNote = note?.isInHiddenSubtree();
|
||||
|
||||
return (
|
||||
<div className="status-bar">
|
||||
{attributesContext && <AttributesPane {...attributesContext} />}
|
||||
{noteInfoContext && <SimilarNotesPane {...noteInfoContext} />}
|
||||
|
||||
<div className="status-bar-main-row">
|
||||
{context && attributesContext && noteInfoContext && <>
|
||||
<Breadcrumb />
|
||||
|
||||
<div className="actions-row">
|
||||
<CodeNoteSwitcher {...context} />
|
||||
<LanguageSwitcher {...context} />
|
||||
{!isHiddenNote && <NotePaths {...context} />}
|
||||
<AttributesButton {...attributesContext} />
|
||||
<AttachmentCount {...context} />
|
||||
<BacklinksBadge {...context} />
|
||||
<NoteInfoBadge {...noteInfoContext} />
|
||||
</div>
|
||||
</>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StatusBarDropdown({ children, icon, text, buttonClassName, titleOptions, dropdownOptions, ...dropdownProps }: Omit<DropdownProps, "hideToggleArrow" | "title" | "titlePosition"> & {
|
||||
title: string;
|
||||
icon?: string;
|
||||
}) {
|
||||
return (
|
||||
<Dropdown
|
||||
buttonClassName={clsx("status-bar-dropdown-button", buttonClassName)}
|
||||
titlePosition="top"
|
||||
titleOptions={{
|
||||
popperConfig: {
|
||||
...titleOptions?.popperConfig,
|
||||
strategy: "fixed"
|
||||
},
|
||||
animation: false,
|
||||
...titleOptions
|
||||
}}
|
||||
dropdownOptions={{
|
||||
popperConfig: {
|
||||
strategy: "fixed",
|
||||
placement: "top"
|
||||
},
|
||||
...dropdownOptions
|
||||
}}
|
||||
text={<>
|
||||
{icon && (<><Icon icon={icon} /> </>)}
|
||||
<span className="text">{text}</span>
|
||||
</>}
|
||||
{...dropdownProps}
|
||||
>
|
||||
{children}
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
interface StatusBarButtonBaseProps {
|
||||
className?: string;
|
||||
icon: string;
|
||||
title: string;
|
||||
text: string | number;
|
||||
disabled?: boolean;
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
type StatusBarButtonWithCommand = StatusBarButtonBaseProps & { triggerCommand: CommandNames; };
|
||||
type StatusBarButtonWithClick = StatusBarButtonBaseProps & { onClick: () => void; };
|
||||
|
||||
function StatusBarButton({ className, icon, text, title, active, ...restProps }: StatusBarButtonWithCommand | StatusBarButtonWithClick) {
|
||||
const parentComponent = useContext(ParentComponent);
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
useStaticTooltip(buttonRef, {
|
||||
placement: "top",
|
||||
fallbackPlacements: [ "top" ],
|
||||
popperConfig: { strategy: "fixed" },
|
||||
animation: false,
|
||||
title
|
||||
});
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={buttonRef}
|
||||
className={clsx("btn select-button focus-outline", className, active && "active")}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if ("triggerCommand" in restProps) {
|
||||
parentComponent?.triggerCommand(restProps.triggerCommand);
|
||||
} else {
|
||||
restProps.onClick();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Icon icon={icon} /> <span className="text">{text}</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
//#region Language Switcher
|
||||
function LanguageSwitcher({ note }: StatusBarContext) {
|
||||
const [ modalShown, setModalShown ] = useState(false);
|
||||
const { locales, DEFAULT_LOCALE, currentNoteLanguage, setCurrentNoteLanguage } = useLanguageSwitcher(note);
|
||||
const { activeLocale, processedLocales } = useProcessedLocales(locales, DEFAULT_LOCALE, currentNoteLanguage ?? DEFAULT_LOCALE.id);
|
||||
|
||||
return (
|
||||
<>
|
||||
{note.type === "text" && <StatusBarDropdown
|
||||
icon="bx bx-globe"
|
||||
title={t("status_bar.language_title")}
|
||||
text={<span dir={activeLocale?.rtl ? "rtl" : "ltr"}>{getLocaleName(activeLocale)}</span>}
|
||||
>
|
||||
{processedLocales.map((locale, index) =>
|
||||
(typeof locale === "object") ? (
|
||||
<FormListItem
|
||||
key={locale.id}
|
||||
rtl={locale.rtl}
|
||||
checked={locale.id === currentNoteLanguage}
|
||||
onClick={() => setCurrentNoteLanguage(locale.id)}
|
||||
>{locale.name}</FormListItem>
|
||||
) : (
|
||||
<FormDropdownDivider key={`divider-${index}`} />
|
||||
)
|
||||
)}
|
||||
<FormDropdownDivider />
|
||||
<FormListItem
|
||||
onClick={() => openInAppHelpFromUrl("veGu4faJErEM")}
|
||||
icon="bx bx-help-circle"
|
||||
>{t("note_language.help-on-languages")}</FormListItem>
|
||||
<FormListItem
|
||||
onClick={() => setModalShown(true)}
|
||||
icon="bx bx-cog"
|
||||
>{t("note_language.configure-languages")}</FormListItem>
|
||||
</StatusBarDropdown>}
|
||||
{createPortal(
|
||||
<ContentLanguagesModal modalShown={modalShown} setModalShown={setModalShown} />,
|
||||
document.body
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function getLocaleName(locale: Locale | null | undefined) {
|
||||
if (!locale) return "";
|
||||
if (!locale.id) return "-";
|
||||
if (locale.name.length <= 4 || locale.rtl) return locale.name; // Some locales like Japanese and Chinese look better than their ID.
|
||||
return locale.id
|
||||
.replace("_", "-")
|
||||
.toLocaleUpperCase();
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Note info & Similar
|
||||
interface NoteInfoContext extends StatusBarContext {
|
||||
similarNotesShown: boolean;
|
||||
setSimilarNotesShown: (value: boolean) => void;
|
||||
}
|
||||
|
||||
export function NoteInfoBadge({ note, similarNotesShown, setSimilarNotesShown }: NoteInfoContext) {
|
||||
const dropdownRef = useRef<BootstrapDropdown>(null);
|
||||
const { metadata, ...sizeProps } = useNoteMetadata(note);
|
||||
const [ originalFileName ] = useNoteLabel(note, "originalFileName");
|
||||
const noteType = useNoteProperty(note, "type");
|
||||
const noteTypeMapping = useMemo(() => NOTE_TYPES.find(t => t.type === noteType), [ noteType ]);
|
||||
const enabled = note && noteType && noteTypeMapping;
|
||||
|
||||
// Keyboard shortcut.
|
||||
useTriliumEvent("toggleRibbonTabNoteInfo", () => enabled && dropdownRef.current?.show());
|
||||
useTriliumEvent("toggleRibbonTabSimilarNotes", () => setSimilarNotesShown(!similarNotesShown));
|
||||
|
||||
return (enabled &&
|
||||
<StatusBarDropdown
|
||||
icon="bx bx-info-circle"
|
||||
title={t("status_bar.note_info_title")}
|
||||
dropdownRef={dropdownRef}
|
||||
dropdownContainerClassName="dropdown-note-info"
|
||||
dropdownOptions={{ autoClose: "outside" }}
|
||||
>
|
||||
<ul>
|
||||
{originalFileName && <NoteInfoValue text={t("file_properties.original_file_name")} value={originalFileName} />}
|
||||
<NoteInfoValue text={t("note_info_widget.created")} value={formatDateTime(metadata?.dateCreated)} />
|
||||
<NoteInfoValue text={t("note_info_widget.modified")} value={formatDateTime(metadata?.dateModified)} />
|
||||
<NoteInfoValue text={t("note_info_widget.type")} value={<><Icon icon={`bx ${noteTypeMapping.icon ?? NOTE_TYPE_ICONS[noteType]}`}/>{" "}{noteTypeMapping?.title}</>} />
|
||||
{note.mime && <NoteInfoValue text={t("note_info_widget.mime")} value={note.mime} />}
|
||||
<NoteInfoValue text={t("note_info_widget.note_id")} value={<code>{note.noteId}</code>} />
|
||||
<NoteInfoValue text={t("note_info_widget.note_size")} title={t("note_info_widget.note_size_info")} value={<NoteSizeWidget {...sizeProps} />} />
|
||||
</ul>
|
||||
|
||||
<LinkButton
|
||||
text={t("note_info_widget.show_similar_notes")}
|
||||
onClick={() => {
|
||||
dropdownRef.current?.hide();
|
||||
setSimilarNotesShown(true);
|
||||
}}
|
||||
/>
|
||||
</StatusBarDropdown>
|
||||
);
|
||||
}
|
||||
|
||||
function NoteInfoValue({ text, title, value }: { text: string; title?: string, value: ComponentChildren }) {
|
||||
return (
|
||||
<li>
|
||||
<strong title={title}>{text}{": "}</strong>
|
||||
<span>{value}</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
function SimilarNotesPane({ note, similarNotesShown }: NoteInfoContext) {
|
||||
return (similarNotesShown &&
|
||||
<div className="similar-notes-pane">
|
||||
<SimilarNotesTab note={note} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Backlinks
|
||||
function BacklinksBadge({ note, viewScope }: StatusBarContext) {
|
||||
const count = useBacklinkCount(note, viewScope?.viewMode === "default");
|
||||
return (note && count > 0 &&
|
||||
<StatusBarDropdown
|
||||
className="backlinks-badge backlinks-widget"
|
||||
icon="bx bx-link"
|
||||
text={t("status_bar.backlinks", { count })}
|
||||
title={t("status_bar.backlinks_title", { count })}
|
||||
dropdownContainerClassName="backlinks-items"
|
||||
>
|
||||
<BacklinksList note={note} />
|
||||
</StatusBarDropdown>
|
||||
);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Attachment count
|
||||
function AttachmentCount({ note }: StatusBarContext) {
|
||||
const attachments = useAttachments(note);
|
||||
const count = attachments.length;
|
||||
|
||||
return (note && count > 0 &&
|
||||
<StatusBarButton
|
||||
className="attachment-count-button"
|
||||
icon="bx bx-paperclip"
|
||||
text={t("status_bar.attachments", { count })}
|
||||
title={t("status_bar.attachments_title", { count })}
|
||||
triggerCommand="showAttachments"
|
||||
/>
|
||||
);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Attributes
|
||||
interface AttributesProps extends StatusBarContext {
|
||||
attributesShown: boolean;
|
||||
setAttributesShown: (shown: boolean) => void;
|
||||
}
|
||||
|
||||
function AttributesButton({ note, attributesShown, setAttributesShown }: AttributesProps) {
|
||||
const [ count, setCount ] = useState(note.attributes.length);
|
||||
|
||||
// React to note changes.
|
||||
useEffect(() => {
|
||||
setCount(note.attributes.length);
|
||||
}, [ note ]);
|
||||
|
||||
// React to changes in count.
|
||||
useTriliumEvent("entitiesReloaded", (({loadResults}) => {
|
||||
if (loadResults.getAttributeRows().some(attr => attributes.isAffecting(attr, note))) {
|
||||
setCount(note.attributes.length);
|
||||
}
|
||||
}));
|
||||
|
||||
return (
|
||||
<StatusBarButton
|
||||
className="attributes-button"
|
||||
icon="bx bx-list-check"
|
||||
title={t("status_bar.attributes_title")}
|
||||
text={t("status_bar.attributes", { count })}
|
||||
active={attributesShown}
|
||||
onClick={() => setAttributesShown(!attributesShown)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function AttributesPane({ note, noteContext, attributesShown, setAttributesShown }: AttributesProps) {
|
||||
const parentComponent = useContext(ParentComponent);
|
||||
const api = useRef<AttributeEditorImperativeHandlers>(null);
|
||||
|
||||
const context = parentComponent && {
|
||||
componentId: parentComponent.componentId,
|
||||
note,
|
||||
hidden: !note
|
||||
};
|
||||
|
||||
// Show on keyboard shortcuts.
|
||||
useTriliumEvents([ "addNewLabel", "addNewRelation" ], () => setAttributesShown(true));
|
||||
useTriliumEvents([ "toggleRibbonTabOwnedAttributes", "toggleRibbonTabInheritedAttributes" ], () => setAttributesShown(!attributesShown));
|
||||
|
||||
// Auto-focus the owned attributes.
|
||||
useEffect(() => api.current?.focus(), [ attributesShown ]);
|
||||
|
||||
// Interaction with the attribute editor.
|
||||
useLegacyImperativeHandlers(useMemo(() => ({
|
||||
saveAttributesCommand: () => api.current?.save(),
|
||||
reloadAttributesCommand: () => api.current?.refresh(),
|
||||
updateAttributeListCommand: ({ attributes }) => api.current?.renderOwnedAttributes(attributes)
|
||||
}), [ api ]));
|
||||
|
||||
return (context &&
|
||||
<div className={clsx("attribute-list", !attributesShown && "hidden-ext")}>
|
||||
<InheritedAttributesTab {...context} />
|
||||
|
||||
<AttributeEditor
|
||||
{...context}
|
||||
api={api}
|
||||
ntxId={noteContext.ntxId}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Note paths
|
||||
function NotePaths({ note, hoistedNoteId, notePath }: StatusBarContext) {
|
||||
const dropdownRef = useRef<BootstrapDropdown>(null);
|
||||
const sortedNotePaths = useSortedNotePaths(note, hoistedNoteId);
|
||||
const count = sortedNotePaths?.length ?? 0;
|
||||
const enabled = count > 1;
|
||||
|
||||
// Keyboard shortcut.
|
||||
useTriliumEvent("toggleRibbonTabNotePaths", () => enabled && dropdownRef.current?.show());
|
||||
|
||||
return (enabled &&
|
||||
<StatusBarDropdown
|
||||
title={t("status_bar.note_paths_title")}
|
||||
dropdownRef={dropdownRef}
|
||||
dropdownContainerClassName="dropdown-note-paths"
|
||||
icon="bx bx-directions"
|
||||
text={t("status_bar.note_paths", { count })}
|
||||
>
|
||||
<NotePathsWidget
|
||||
sortedNotePaths={sortedNotePaths}
|
||||
currentNotePath={notePath}
|
||||
/>
|
||||
</StatusBarDropdown>
|
||||
);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Code note switcher
|
||||
function CodeNoteSwitcher({ note }: StatusBarContext) {
|
||||
const [ modalShown, setModalShown ] = useState(false);
|
||||
const currentNoteMime = useNoteProperty(note, "mime");
|
||||
const mimeTypes = useMimeTypes();
|
||||
const correspondingMimeType = useMemo(() => (
|
||||
mimeTypes.find(m => m.mime === currentNoteMime)
|
||||
), [ mimeTypes, currentNoteMime ]);
|
||||
|
||||
return (note.type === "code" &&
|
||||
<>
|
||||
<StatusBarDropdown
|
||||
icon="bx bx-code-curly"
|
||||
text={correspondingMimeType?.title}
|
||||
title={t("status_bar.code_note_switcher")}
|
||||
dropdownContainerClassName="dropdown-code-note-switcher"
|
||||
>
|
||||
<NoteTypeCodeNoteList
|
||||
currentMimeType={currentNoteMime}
|
||||
mimeTypes={mimeTypes}
|
||||
changeNoteType={(type, mime) => server.put(`notes/${note.noteId}/type`, { type, mime })}
|
||||
setModalShown={() => setModalShown(true)}
|
||||
/>
|
||||
</StatusBarDropdown>
|
||||
{createPortal(
|
||||
<NoteTypeOptionsModal modalShown={modalShown} setModalShown={setModalShown} />,
|
||||
document.body
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
//#endregion
|
||||
20
apps/client/src/widgets/note_bars/CollectionProperties.css
Normal file
20
apps/client/src/widgets/note_bars/CollectionProperties.css
Normal file
@@ -0,0 +1,20 @@
|
||||
.collection-properties {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
gap: 0.25em;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
max-width: unset;
|
||||
font-size: 0.8em;
|
||||
|
||||
.dropdown-menu {
|
||||
input.form-control {
|
||||
padding: 2px 8px;
|
||||
margin-left: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
229
apps/client/src/widgets/note_bars/CollectionProperties.tsx
Normal file
229
apps/client/src/widgets/note_bars/CollectionProperties.tsx
Normal file
@@ -0,0 +1,229 @@
|
||||
import "./CollectionProperties.css";
|
||||
|
||||
import { t } from "i18next";
|
||||
import { useContext, useRef } from "preact/hooks";
|
||||
import { Fragment } from "preact/jsx-runtime";
|
||||
|
||||
import FNote from "../../entities/fnote";
|
||||
import { getHelpUrlForNote } from "../../services/in_app_help";
|
||||
import { openInAppHelpFromUrl } from "../../services/utils";
|
||||
import { ViewTypeOptions } from "../collections/interface";
|
||||
import ActionButton from "../react/ActionButton";
|
||||
import Dropdown from "../react/Dropdown";
|
||||
import { FormDropdownDivider, FormDropdownSubmenu, FormListItem, FormListToggleableItem } from "../react/FormList";
|
||||
import FormTextBox from "../react/FormTextBox";
|
||||
import { useNoteLabel, useNoteLabelBoolean, useNoteLabelWithDefault, useTriliumEvent } from "../react/hooks";
|
||||
import Icon from "../react/Icon";
|
||||
import { ParentComponent } from "../react/react_utils";
|
||||
import { bookPropertiesConfig, BookProperty, ButtonProperty, CheckBoxProperty, ComboBoxItem, ComboBoxProperty, NumberProperty, SplitButtonProperty } from "../ribbon/collection-properties-config";
|
||||
import { useViewType, VIEW_TYPE_MAPPINGS } from "../ribbon/CollectionPropertiesTab";
|
||||
|
||||
const ICON_MAPPINGS: Record<ViewTypeOptions, string> = {
|
||||
grid: "bx bxs-grid",
|
||||
list: "bx bx-list-ul",
|
||||
calendar: "bx bx-calendar",
|
||||
table: "bx bx-table",
|
||||
geoMap: "bx bx-map-alt",
|
||||
board: "bx bx-columns",
|
||||
presentation: "bx bx-rectangle"
|
||||
};
|
||||
|
||||
export default function CollectionProperties({ note }: { note: FNote }) {
|
||||
const [ viewType, setViewType ] = useViewType(note);
|
||||
|
||||
return (
|
||||
<div className="collection-properties">
|
||||
<ViewTypeSwitcher viewType={viewType} setViewType={setViewType} />
|
||||
<ViewOptions note={note} viewType={viewType} />
|
||||
<div className="spacer" />
|
||||
<HelpButton note={note} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ViewTypeSwitcher({ viewType, setViewType }: { viewType: ViewTypeOptions, setViewType: (newValue: ViewTypeOptions) => void }) {
|
||||
// Keyboard shortcut
|
||||
const dropdownContainerRef = useRef<HTMLDivElement>(null);
|
||||
useTriliumEvent("toggleRibbonTabBookProperties", () => {
|
||||
dropdownContainerRef.current?.querySelector("button")?.focus();
|
||||
});
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
dropdownContainerRef={dropdownContainerRef}
|
||||
text={<>
|
||||
<Icon icon={ICON_MAPPINGS[viewType]} />
|
||||
{VIEW_TYPE_MAPPINGS[viewType]}
|
||||
</>}
|
||||
>
|
||||
{Object.entries(VIEW_TYPE_MAPPINGS).map(([ key, label ]) => (
|
||||
<FormListItem
|
||||
key={key}
|
||||
onClick={() => setViewType(key as ViewTypeOptions)}
|
||||
selected={viewType === key}
|
||||
disabled={viewType === key}
|
||||
icon={ICON_MAPPINGS[key as ViewTypeOptions]}
|
||||
>{label}</FormListItem>
|
||||
))}
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
function ViewOptions({ note, viewType }: { note: FNote, viewType: ViewTypeOptions }) {
|
||||
const properties = bookPropertiesConfig[viewType].properties;
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
buttonClassName="bx bx-cog icon-action"
|
||||
hideToggleArrow
|
||||
>
|
||||
{properties.map(property => (
|
||||
<ViewProperty key={property.label} note={note} property={property} />
|
||||
))}
|
||||
{properties.length > 0 && <FormDropdownDivider />}
|
||||
|
||||
<ViewProperty note={note} property={{
|
||||
type: "checkbox",
|
||||
icon: "bx bx-archive",
|
||||
label: t("book_properties.include_archived_notes"),
|
||||
bindToLabel: "includeArchived"
|
||||
} as CheckBoxProperty} />
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
function ViewProperty({ note, property }: { note: FNote, property: BookProperty }) {
|
||||
switch (property.type) {
|
||||
case "button":
|
||||
return <ButtonPropertyView note={note} property={property} />;
|
||||
case "split-button":
|
||||
return <SplitButtonPropertyView note={note} property={property} />;
|
||||
case "checkbox":
|
||||
return <CheckBoxPropertyView note={note} property={property} />;
|
||||
case "number":
|
||||
return <NumberPropertyView note={note} property={property} />;
|
||||
case "combobox":
|
||||
return <ComboBoxPropertyView note={note} property={property} />;
|
||||
}
|
||||
}
|
||||
|
||||
function ButtonPropertyView({ note, property }: { note: FNote, property: ButtonProperty }) {
|
||||
const parentComponent = useContext(ParentComponent);
|
||||
|
||||
return (
|
||||
<FormListItem
|
||||
icon={property.icon}
|
||||
title={property.title}
|
||||
onClick={() => {
|
||||
if (!parentComponent) return;
|
||||
property.onClick({
|
||||
note,
|
||||
triggerCommand: parentComponent.triggerCommand.bind(parentComponent)
|
||||
});
|
||||
}}
|
||||
>{property.label}</FormListItem>
|
||||
);
|
||||
}
|
||||
|
||||
function SplitButtonPropertyView({ note, property }: { note: FNote, property: SplitButtonProperty }) {
|
||||
const parentComponent = useContext(ParentComponent);
|
||||
const ItemsComponent = property.items;
|
||||
const clickContext = parentComponent && {
|
||||
note,
|
||||
triggerCommand: parentComponent.triggerCommand.bind(parentComponent)
|
||||
};
|
||||
|
||||
return (parentComponent &&
|
||||
<FormDropdownSubmenu
|
||||
icon={property.icon ?? "bx bx-empty"}
|
||||
title={property.label}
|
||||
onDropdownToggleClicked={() => clickContext && property.onClick(clickContext)}
|
||||
>
|
||||
<ItemsComponent note={note} parentComponent={parentComponent} />
|
||||
</FormDropdownSubmenu>
|
||||
);
|
||||
}
|
||||
|
||||
function NumberPropertyView({ note, property }: { note: FNote, property: NumberProperty }) {
|
||||
//@ts-expect-error Interop with text box which takes in string values even for numbers.
|
||||
const [ value, setValue ] = useNoteLabel(note, property.bindToLabel);
|
||||
const disabled = property.disabled?.(note);
|
||||
|
||||
return (
|
||||
<FormListItem
|
||||
icon={property.icon}
|
||||
disabled={disabled}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{property.label}
|
||||
<FormTextBox
|
||||
type="number"
|
||||
currentValue={value ?? ""} onChange={setValue}
|
||||
style={{ width: (property.width ?? 100) }}
|
||||
min={property.min ?? 0}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</FormListItem>
|
||||
);
|
||||
}
|
||||
|
||||
function ComboBoxPropertyView({ note, property }: { note: FNote, property: ComboBoxProperty }) {
|
||||
const [ value, setValue ] = useNoteLabelWithDefault(note, property.bindToLabel, property.defaultValue ?? "");
|
||||
|
||||
function renderItem(option: ComboBoxItem) {
|
||||
return (
|
||||
<FormListItem
|
||||
key={option.value}
|
||||
checked={value === option.value}
|
||||
onClick={() => setValue(option.value)}
|
||||
>
|
||||
{option.label}
|
||||
</FormListItem>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FormDropdownSubmenu
|
||||
title={property.label}
|
||||
icon={property.icon ?? "bx bx-empty"}
|
||||
>
|
||||
{(property.options).map((option, index) => {
|
||||
if ("items" in option) {
|
||||
return (
|
||||
<Fragment key={option.title}>
|
||||
<FormListItem key={option.title} disabled>{option.title}</FormListItem>
|
||||
{option.items.map(renderItem)}
|
||||
{index < property.options.length - 1 && <FormDropdownDivider />}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
return renderItem(option);
|
||||
|
||||
})}
|
||||
</FormDropdownSubmenu>
|
||||
);
|
||||
}
|
||||
|
||||
function CheckBoxPropertyView({ note, property }: { note: FNote, property: CheckBoxProperty }) {
|
||||
const [ value, setValue ] = useNoteLabelBoolean(note, property.bindToLabel);
|
||||
return (
|
||||
<FormListToggleableItem
|
||||
icon={property.icon}
|
||||
title={property.label}
|
||||
currentValue={value}
|
||||
onChange={setValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function HelpButton({ note }: { note: FNote }) {
|
||||
const helpUrl = getHelpUrlForNote(note);
|
||||
|
||||
return (helpUrl && (
|
||||
<ActionButton
|
||||
icon="bx bx-help-circle"
|
||||
onClick={(() => openInAppHelpFromUrl(helpUrl))}
|
||||
text={t("help-button.title")}
|
||||
/>
|
||||
));
|
||||
}
|
||||
@@ -1,17 +1,22 @@
|
||||
.note-icon-widget {
|
||||
padding-inline-start: 10px;
|
||||
margin-inline-end: 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
div.note-icon-widget {
|
||||
/* The size of the icon */
|
||||
--note-icon-size: 30px;
|
||||
/* The padding size for the icon container */
|
||||
--note-icon-container-padding-size: 10px;
|
||||
contain: unset;
|
||||
}
|
||||
|
||||
.note-icon-widget button.note-icon {
|
||||
font-size: 180%;
|
||||
background-color: transparent;
|
||||
--size: calc(var(--note-icon-size) + var(--note-icon-container-padding-size) * 2);
|
||||
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
padding: 0;
|
||||
border: 1px solid transparent;
|
||||
padding: 6px;
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
color: var(--muted-text-color);
|
||||
cursor: pointer;
|
||||
font-size: var(--note-icon-size);
|
||||
}
|
||||
|
||||
.note-icon-widget button.note-icon:disabled {
|
||||
@@ -69,3 +74,41 @@
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
body.experimental-feature-new-layout {
|
||||
.note-icon-widget button.note-icon {
|
||||
--input-focus-outline-color: var(--note-icon-hover-background-color);
|
||||
|
||||
position: relative;
|
||||
background: transparent !important;
|
||||
|
||||
&, &:hover {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* The icon */
|
||||
&::before {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
color: var(--note-icon-custom-color, var(--note-icon-color));
|
||||
}
|
||||
|
||||
/* The background circle */
|
||||
&:not(.bx-empty:disabled)::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 50%;
|
||||
background: var(--note-icon-custom-background-color, var(--note-icon-background-color));
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--note-icon-custom-color, var(--note-icon-color));
|
||||
}
|
||||
|
||||
&:hover:not(.bx-empty:disabled)::after {
|
||||
background: var(--note-icon-hover-custom-background-color, var(--note-icon-hover-background-color));
|
||||
transition: background 200ms ease-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ export default function NoteIcon() {
|
||||
className="note-icon-widget"
|
||||
title={t("note_icon.change_note_icon")}
|
||||
dropdownContainerStyle={{ width: "610px" }}
|
||||
buttonClassName={`note-icon ${icon ?? "bx bx-empty"}`}
|
||||
buttonClassName={`note-icon tn-focusable-button ${icon ?? "bx bx-empty"}`}
|
||||
hideToggleArrow
|
||||
disabled={viewScope?.viewMode !== "default"}
|
||||
>
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
.note-title-widget {
|
||||
div.note-title-widget {
|
||||
/* The font size for the title */
|
||||
--note-title-size: 180%;
|
||||
/* The horizontal padding of the title widget */
|
||||
--note-title-padding-inline: 12px;
|
||||
|
||||
flex-grow: 1000;
|
||||
height: 100%;
|
||||
height: auto;
|
||||
contain: unset;
|
||||
}
|
||||
|
||||
.note-title-widget input.note-title {
|
||||
font-size: 110%;
|
||||
font-size: var(--note-title-size);
|
||||
border: 0;
|
||||
margin: 2px 0px;
|
||||
border-radius: 0;
|
||||
min-width: 5em;
|
||||
width: 100%;
|
||||
padding: 1px 12px;
|
||||
line-height: 1;
|
||||
padding-block: 0;
|
||||
padding-inline: var(--note-title-padding-inline);
|
||||
}
|
||||
|
||||
.note-title-widget input.note-title[readonly] {
|
||||
@@ -22,9 +30,66 @@
|
||||
}
|
||||
|
||||
body.mobile .note-title-widget input.note-title {
|
||||
padding: 0;
|
||||
--note-title-padding-inline: 0;
|
||||
}
|
||||
|
||||
body.desktop .note-title-widget input.note-title {
|
||||
font-size: 180%;
|
||||
body.experimental-feature-new-layout {
|
||||
.title-row {
|
||||
container-type: size;
|
||||
transition: border 400ms ease-out;
|
||||
|
||||
&.note-split-title {
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
|
||||
&.hide-title {
|
||||
border-bottom-color: transparent;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.note-icon-widget {
|
||||
margin-inline: 12px 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.note-icon-widget {
|
||||
--note-icon-size: 16px;
|
||||
--note-icon-container-padding-size: 6px;
|
||||
margin-inline: 0;
|
||||
}
|
||||
|
||||
.note-title-widget {
|
||||
--note-title-size: 18px;
|
||||
--note-title-padding-inline: 0;
|
||||
}
|
||||
|
||||
@container (max-width: 700px) {
|
||||
.note-title-widget {
|
||||
--note-title-size: 1.25rem;
|
||||
--note-title-padding-inline: 4px;
|
||||
}
|
||||
|
||||
.note-title-widget {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.note-title {
|
||||
font-size: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.note-title-widget:focus-within + .note-badges,
|
||||
.ext-badge .text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.note-title-widget {
|
||||
input.note-title {
|
||||
--input-focus-background: transparent;
|
||||
--input-focus-outline-color: transparent;
|
||||
--input-hover-background: transparent;
|
||||
--input-hover-color: initial;
|
||||
--input-focus-color: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,9 @@ import { isLaunchBarConfig } from "../services/utils";
|
||||
import appContext from "../components/app_context";
|
||||
import branches from "../services/branches";
|
||||
import { isIMEComposing } from "../services/shortcuts";
|
||||
import clsx from "clsx";
|
||||
|
||||
export default function NoteTitleWidget() {
|
||||
export default function NoteTitleWidget(props: {className?: string}) {
|
||||
const { note, noteId, componentId, viewScope, noteContext, parentComponent } = useNoteContext();
|
||||
const title = useNoteProperty(note, "title", componentId);
|
||||
const isProtected = useNoteProperty(note, "isProtected");
|
||||
@@ -67,7 +68,7 @@ export default function NoteTitleWidget() {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="note-title-widget">
|
||||
<div className={clsx("note-title-widget", props.className)}>
|
||||
{note && <FormTextBox
|
||||
inputRef={textBoxRef}
|
||||
autocomplete="off"
|
||||
|
||||
@@ -62,6 +62,8 @@ export default class NoteWrapperWidget extends FlexContainer<BasicWidget> {
|
||||
|
||||
this.$widget.addClass(utils.getNoteTypeClass(note.type));
|
||||
this.$widget.addClass(utils.getMimeTypeClass(note.mime));
|
||||
this.$widget.addClass(`view-mode-${this.noteContext?.viewScope?.viewMode ?? "default"}`);
|
||||
this.$widget.addClass(note.getColorClass());
|
||||
this.$widget.toggleClass(["bgfx", "options"], note.isOptions());
|
||||
this.$widget.toggleClass("protected", note.isProtected);
|
||||
|
||||
@@ -88,11 +90,11 @@ export default class NoteWrapperWidget extends FlexContainer<BasicWidget> {
|
||||
|
||||
async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
// listening on changes of note.type and CSS class
|
||||
|
||||
const LABELS_CAUSING_REFRESH = ["cssClass", "language", "viewType", "color"];
|
||||
const noteId = this.noteContext?.noteId;
|
||||
if (
|
||||
loadResults.isNoteReloaded(noteId) ||
|
||||
loadResults.getAttributeRows().find((attr) => attr.type === "label" && ["cssClass", "language", "viewType"].includes(attr.name ?? "") && attributeService.isAffecting(attr, this.noteContext?.note))
|
||||
loadResults.getAttributeRows().find((attr) => attr.type === "label" && LABELS_CAUSING_REFRESH.includes(attr.name ?? "") && attributeService.isAffecting(attr, this.noteContext?.note))
|
||||
) {
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
59
apps/client/src/widgets/react/Badge.css
Normal file
59
apps/client/src/widgets/react/Badge.css
Normal file
@@ -0,0 +1,59 @@
|
||||
.ext-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2px 6px;
|
||||
border-radius: var(--badge-radius);
|
||||
font-size: 0.75em;
|
||||
background-color: var(--color, transparent);
|
||||
color: white;
|
||||
min-width: 0;
|
||||
flex-shrink: 1;
|
||||
|
||||
&.clickable {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: color-mix(in srgb, var(--color, --badge-background-color) 80%, black);
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit !important;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
> * {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-badge {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
border-radius: var(--badge-radius);
|
||||
|
||||
.ext-badge {
|
||||
border-radius: 0;
|
||||
|
||||
.text {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
.arrow {
|
||||
font-size: 1.3em;
|
||||
margin-left: 0.25em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,81 @@
|
||||
interface BadgeProps {
|
||||
import "./Badge.css";
|
||||
|
||||
import clsx from "clsx";
|
||||
import { ComponentChildren, HTMLAttributes } from "preact";
|
||||
import { useRef } from "preact/hooks";
|
||||
|
||||
import Dropdown, { DropdownProps } from "./Dropdown";
|
||||
import { useStaticTooltip } from "./hooks";
|
||||
import Icon from "./Icon";
|
||||
|
||||
interface SimpleBadgeProps {
|
||||
className?: string;
|
||||
title: string;
|
||||
title: ComponentChildren;
|
||||
}
|
||||
|
||||
export default function Badge({ title, className }: BadgeProps) {
|
||||
return <span class={`badge ${className ?? ""}`}>{title}</span>
|
||||
}
|
||||
interface BadgeProps extends Pick<HTMLAttributes<HTMLDivElement>, "onClick" | "style"> {
|
||||
text?: ComponentChildren;
|
||||
icon?: string;
|
||||
className?: string;
|
||||
tooltip?: string;
|
||||
href?: string;
|
||||
}
|
||||
|
||||
export default function SimpleBadge({ title, className }: SimpleBadgeProps) {
|
||||
return <span class={`badge ${className ?? ""}`}>{title}</span>;
|
||||
}
|
||||
|
||||
export function Badge({ icon, className, text, tooltip, href, ...containerProps }: BadgeProps) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
useStaticTooltip(containerRef, {
|
||||
placement: "bottom",
|
||||
fallbackPlacements: [ "bottom" ],
|
||||
animation: false,
|
||||
html: true,
|
||||
title: tooltip
|
||||
});
|
||||
|
||||
const content = <>
|
||||
{icon && <><Icon icon={icon} /> </>}
|
||||
<span class="text">{text}</span>
|
||||
</>;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={clsx("ext-badge", className, { "clickable": !!containerProps.onClick })}
|
||||
{...containerProps}
|
||||
>
|
||||
{href ? <a href={href}>{content}</a> : <span>{content}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function BadgeWithDropdown({ text, children, tooltip, className, dropdownOptions, ...props }: BadgeProps & {
|
||||
children: ComponentChildren,
|
||||
dropdownOptions?: Partial<DropdownProps>
|
||||
}) {
|
||||
return (
|
||||
<Dropdown
|
||||
className={`dropdown-badge dropdown-${className}`}
|
||||
text={<Badge
|
||||
text={<>{text} <Icon className="arrow" icon="bx bx-chevron-down" /></>}
|
||||
className={className}
|
||||
{...props}
|
||||
/>}
|
||||
noDropdownListStyle
|
||||
noSelectButtonStyle
|
||||
hideToggleArrow
|
||||
title={tooltip}
|
||||
titlePosition="bottom"
|
||||
{...dropdownOptions}
|
||||
dropdownOptions={{
|
||||
...dropdownOptions?.dropdownOptions,
|
||||
popperConfig: {
|
||||
...dropdownOptions?.dropdownOptions?.popperConfig,
|
||||
placement: "bottom", strategy: "fixed"
|
||||
}
|
||||
}}
|
||||
>{children}</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
37
apps/client/src/widgets/react/Collapsible.css
Normal file
37
apps/client/src/widgets/react/Collapsible.css
Normal file
@@ -0,0 +1,37 @@
|
||||
.collapsible {
|
||||
.collapsible-title {
|
||||
line-height: 1em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
appearance: none;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: inherit;
|
||||
|
||||
.arrow {
|
||||
font-size: 1.3em;
|
||||
transition: transform 250ms ease-in;
|
||||
}
|
||||
}
|
||||
|
||||
.collapsible-body {
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.collapsible-inner-body {
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
.collapsible-title .arrow {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
&.with-transition {
|
||||
.collapsible-body {
|
||||
transition: height 250ms ease-in;
|
||||
}
|
||||
}
|
||||
}
|
||||
69
apps/client/src/widgets/react/Collapsible.tsx
Normal file
69
apps/client/src/widgets/react/Collapsible.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import "./Collapsible.css";
|
||||
|
||||
import clsx from "clsx";
|
||||
import { ComponentChildren, HTMLAttributes } from "preact";
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import { useElementSize, useUniqueName } from "./hooks";
|
||||
import Icon from "./Icon";
|
||||
|
||||
interface CollapsibleProps extends Pick<HTMLAttributes<HTMLDivElement>, "className"> {
|
||||
title: string;
|
||||
children: ComponentChildren;
|
||||
initiallyExpanded?: boolean;
|
||||
}
|
||||
|
||||
export default function Collapsible({ initiallyExpanded, ...restProps }: CollapsibleProps) {
|
||||
const [ expanded, setExpanded ] = useState(initiallyExpanded);
|
||||
return <ExternallyControlledCollapsible {...restProps} expanded={expanded} setExpanded={setExpanded} />;
|
||||
}
|
||||
|
||||
export function ExternallyControlledCollapsible({ title, children, className, expanded, setExpanded }: Omit<CollapsibleProps, "initiallyExpanded"> & {
|
||||
expanded: boolean | undefined;
|
||||
setExpanded: (expanded: boolean) => void
|
||||
}) {
|
||||
const bodyRef = useRef<HTMLDivElement>(null);
|
||||
const innerRef = useRef<HTMLDivElement>(null);
|
||||
const { height } = useElementSize(innerRef) ?? {};
|
||||
const contentId = useUniqueName();
|
||||
const [ transitionEnabled, setTransitionEnabled ] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
setTransitionEnabled(true);
|
||||
}, 200);
|
||||
return () => clearTimeout(timeout);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={clsx("collapsible", className, {
|
||||
expanded,
|
||||
"with-transition": transitionEnabled
|
||||
})}>
|
||||
<button
|
||||
className="collapsible-title"
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
aria-expanded={expanded}
|
||||
aria-controls={contentId}
|
||||
>
|
||||
<Icon className="arrow" icon="bx bx-chevron-right" />
|
||||
{title}
|
||||
</button>
|
||||
|
||||
<div
|
||||
id={contentId}
|
||||
ref={bodyRef}
|
||||
className="collapsible-body"
|
||||
style={{ height: expanded ? height : "0" }}
|
||||
aria-hidden={!expanded}
|
||||
>
|
||||
<div
|
||||
ref={innerRef}
|
||||
className="collapsible-inner-body"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -117,8 +117,8 @@ export default function Dropdown({ id, className, buttonClassName, isStatic, chi
|
||||
aria-expanded="false"
|
||||
id={id ?? ariaId}
|
||||
disabled={disabled}
|
||||
onMouseOver={() => showTooltip()}
|
||||
onMouseLeave={() => hideTooltip()}
|
||||
onMouseEnter={showTooltip}
|
||||
onMouseLeave={hideTooltip}
|
||||
{...buttonProps}
|
||||
>
|
||||
{text}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Ref } from "preact";
|
||||
import Button, { ButtonProps } from "./Button";
|
||||
import { useEffect, useRef } from "preact/hooks";
|
||||
|
||||
import ActionButton, { ActionButtonProps } from "./ActionButton";
|
||||
import Button, { ButtonProps } from "./Button";
|
||||
|
||||
interface FormFileUploadProps {
|
||||
name?: string;
|
||||
onChange: (files: FileList | null) => void;
|
||||
@@ -26,7 +28,7 @@ export default function FormFileUpload({ inputRef, name, onChange, multiple, hid
|
||||
multiple={multiple}
|
||||
onChange={e => onChange((e.target as HTMLInputElement).files)} />
|
||||
</label>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,5 +51,27 @@ export function FormFileUploadButton({ onChange, ...buttonProps }: Omit<ButtonPr
|
||||
onChange={onChange}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link FormFileUploadButton}, but uses an {@link ActionButton} instead of a normal {@link Button}.
|
||||
* @param param the change listener for the file upload and the properties for the button.
|
||||
*/
|
||||
export function FormFileUploadActionButton({ onChange, ...buttonProps }: Omit<ActionButtonProps, "onClick"> & Pick<FormFileUploadProps, "onChange">) {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ActionButton
|
||||
{...buttonProps}
|
||||
onClick={() => inputRef.current?.click()}
|
||||
/>
|
||||
<FormFileUpload
|
||||
inputRef={inputRef}
|
||||
hidden
|
||||
onChange={onChange}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,29 @@
|
||||
.dropdown-item .description {
|
||||
font-size: small;
|
||||
color: var(--muted-text-color);
|
||||
white-space: normal;
|
||||
}
|
||||
.dropdown-item {
|
||||
.description {
|
||||
font-size: small;
|
||||
color: var(--muted-text-color);
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.dropdown-item span.bx {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
span.bx {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.switch-widget {
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
--switch-track-width: 40px;
|
||||
--switch-track-height: 20px;
|
||||
--switch-thumb-width: 12px;
|
||||
--switch-thumb-height: var(--switch-thumb-width);
|
||||
|
||||
.contextual-help {
|
||||
margin-inline-start: 0.25em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.switch-spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { Dropdown as BootstrapDropdown, Tooltip } from "bootstrap";
|
||||
import { ComponentChildren } from "preact";
|
||||
import Icon from "./Icon";
|
||||
import { useEffect, useMemo, useRef, useState, type CSSProperties } from "preact/compat";
|
||||
import "./FormList.css";
|
||||
import { CommandNames } from "../../components/app_context";
|
||||
import { useStaticTooltip } from "./hooks";
|
||||
import { handleRightToLeftPlacement, isMobile } from "../../services/utils";
|
||||
|
||||
import { Dropdown as BootstrapDropdown, Tooltip } from "bootstrap";
|
||||
import clsx from "clsx";
|
||||
import { ComponentChildren, RefObject } from "preact";
|
||||
import { type CSSProperties,useEffect, useMemo, useRef, useState } from "preact/compat";
|
||||
|
||||
import { CommandNames } from "../../components/app_context";
|
||||
import { handleRightToLeftPlacement, isMobile, openInAppHelpFromUrl } from "../../services/utils";
|
||||
import FormToggle from "./FormToggle";
|
||||
import { useStaticTooltip, useSyncedRef } from "./hooks";
|
||||
import Icon from "./Icon";
|
||||
|
||||
interface FormListOpts {
|
||||
children: ComponentChildren;
|
||||
@@ -32,7 +35,7 @@ export default function FormList({ children, onSelect, style, fullHeight, wrappe
|
||||
return () => {
|
||||
$wrapperRef.off("hide.bs.dropdown");
|
||||
dropdown.dispose();
|
||||
}
|
||||
};
|
||||
}, [ triggerRef, wrapperRef ]);
|
||||
|
||||
const builtinStyles = useMemo(() => {
|
||||
@@ -50,8 +53,7 @@ export default function FormList({ children, onSelect, style, fullHeight, wrappe
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button" style="display: none;"
|
||||
data-bs-toggle="dropdown" data-bs-display="static">
|
||||
</button>
|
||||
data-bs-toggle="dropdown" data-bs-display="static" />
|
||||
|
||||
<div class="dropdown-menu static show" style={{
|
||||
...style ?? {},
|
||||
@@ -94,15 +96,17 @@ interface FormListItemOpts {
|
||||
description?: string;
|
||||
className?: string;
|
||||
rtl?: boolean;
|
||||
postContent?: ComponentChildren;
|
||||
itemRef?: RefObject<HTMLLIElement>;
|
||||
}
|
||||
|
||||
const TOOLTIP_CONFIG: Partial<Tooltip.Options> = {
|
||||
placement: handleRightToLeftPlacement("right"),
|
||||
fallbackPlacements: [ handleRightToLeftPlacement("right") ]
|
||||
}
|
||||
};
|
||||
|
||||
export function FormListItem({ className, icon, value, title, active, disabled, checked, container, onClick, selected, rtl, triggerCommand, description, ...contentProps }: FormListItemOpts) {
|
||||
const itemRef = useRef<HTMLLIElement>(null);
|
||||
export function FormListItem({ className, icon, value, title, active, disabled, checked, container, onClick, selected, rtl, triggerCommand, description, itemRef: externalItemRef, ...contentProps }: FormListItemOpts) {
|
||||
const itemRef = useSyncedRef<HTMLLIElement>(externalItemRef, null);
|
||||
|
||||
if (checked) {
|
||||
icon = "bx bx-check";
|
||||
@@ -132,6 +136,49 @@ export function FormListItem({ className, icon, value, title, active, disabled,
|
||||
);
|
||||
}
|
||||
|
||||
export function FormListToggleableItem({ title, currentValue, onChange, disabled, helpPage, ...props }: Omit<FormListItemOpts, "onClick" | "children"> & {
|
||||
title: string;
|
||||
currentValue: boolean;
|
||||
helpPage?: string;
|
||||
onChange(newValue: boolean): void | Promise<void>;
|
||||
}) {
|
||||
const isWaiting = useRef(false);
|
||||
|
||||
return (
|
||||
<FormListItem
|
||||
{...props}
|
||||
disabled={disabled}
|
||||
onClick={async (e) => {
|
||||
if ((e.target as HTMLElement | null)?.classList.contains("contextual-help")) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.stopPropagation();
|
||||
if (!disabled && !isWaiting.current) {
|
||||
isWaiting.current = true;
|
||||
await onChange(!currentValue);
|
||||
isWaiting.current = false;
|
||||
}
|
||||
}}>
|
||||
<FormToggle
|
||||
switchOnName={title}
|
||||
switchOffName={title}
|
||||
currentValue={currentValue}
|
||||
onChange={() => {}}
|
||||
afterName={<>
|
||||
{helpPage && (
|
||||
<span
|
||||
class="bx bx-help-circle contextual-help"
|
||||
onClick={() => openInAppHelpFromUrl(helpPage)}
|
||||
/>
|
||||
)}
|
||||
<span class="switch-spacer" />
|
||||
</>}
|
||||
/>
|
||||
</FormListItem>
|
||||
);
|
||||
}
|
||||
|
||||
function FormListContent({ children, badges, description, disabled, disabledTooltip }: Pick<FormListItemOpts, "children" | "badges" | "description" | "disabled" | "disabledTooltip">) {
|
||||
return <>
|
||||
{children}
|
||||
@@ -139,7 +186,7 @@ function FormListContent({ children, badges, description, disabled, disabledTool
|
||||
<span className={`badge ${className ?? ""}`}>{text}</span>
|
||||
))}
|
||||
{disabled && disabledTooltip && (
|
||||
<span class="bx bx-info-circle disabled-tooltip" title={disabledTooltip} />
|
||||
<span class="bx bx-info-circle contextual-help" title={disabledTooltip} />
|
||||
)}
|
||||
{description && <div className="description">{description}</div>}
|
||||
</>;
|
||||
@@ -154,18 +201,24 @@ export function FormListHeader({ text }: FormListHeaderOpts) {
|
||||
<li>
|
||||
<h6 className="dropdown-header">{text}</h6>
|
||||
</li>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function FormDropdownDivider() {
|
||||
return <div className="dropdown-divider" />;
|
||||
}
|
||||
|
||||
export function FormDropdownSubmenu({ icon, title, children }: { icon: string, title: ComponentChildren, children: ComponentChildren }) {
|
||||
export function FormDropdownSubmenu({ icon, title, children, dropStart, onDropdownToggleClicked }: {
|
||||
icon: string,
|
||||
title: ComponentChildren,
|
||||
children: ComponentChildren,
|
||||
onDropdownToggleClicked?: () => void,
|
||||
dropStart?: boolean
|
||||
}) {
|
||||
const [ openOnMobile, setOpenOnMobile ] = useState(false);
|
||||
|
||||
return (
|
||||
<li className={`dropdown-item dropdown-submenu ${openOnMobile ? "submenu-open" : ""}`}>
|
||||
<li className={clsx("dropdown-item dropdown-submenu", { "submenu-open": openOnMobile, "dropstart": dropStart })}>
|
||||
<span
|
||||
className="dropdown-toggle"
|
||||
onClick={(e) => {
|
||||
@@ -173,6 +226,8 @@ export function FormDropdownSubmenu({ icon, title, children }: { icon: string, t
|
||||
|
||||
if (isMobile()) {
|
||||
setOpenOnMobile(!openOnMobile);
|
||||
} else if (onDropdownToggleClicked) {
|
||||
onDropdownToggleClicked();
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -184,5 +239,5 @@ export function FormDropdownSubmenu({ icon, title, children }: { icon: string, t
|
||||
{children}
|
||||
</ul>
|
||||
</li>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,14 @@
|
||||
border-radius: 24px;
|
||||
background-color: var(--switch-off-track-background);
|
||||
transition: background 200ms ease-in;
|
||||
|
||||
&.disable-transitions {
|
||||
transition: none !important;
|
||||
|
||||
&:after {
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.switch-widget .switch-button.on {
|
||||
@@ -103,4 +111,4 @@ body[dir=rtl] .switch-widget .switch-button.on:after {
|
||||
|
||||
.switch-widget .switch-help-button:hover {
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,39 @@
|
||||
import clsx from "clsx";
|
||||
import "./FormToggle.css";
|
||||
import HelpButton from "./HelpButton";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import { ComponentChildren } from "preact";
|
||||
|
||||
interface FormToggleProps {
|
||||
currentValue: boolean | null;
|
||||
onChange(newValue: boolean): void;
|
||||
switchOnName: string;
|
||||
switchOnTooltip: string;
|
||||
switchOnTooltip?: string;
|
||||
switchOffName: string;
|
||||
switchOffTooltip: string;
|
||||
switchOffTooltip?: string;
|
||||
helpPage?: string;
|
||||
disabled?: boolean;
|
||||
afterName?: ComponentChildren;
|
||||
}
|
||||
|
||||
export default function FormToggle({ currentValue, helpPage, switchOnName, switchOnTooltip, switchOffName, switchOffTooltip, onChange, disabled }: FormToggleProps) {
|
||||
export default function FormToggle({ currentValue, helpPage, switchOnName, switchOnTooltip, switchOffName, switchOffTooltip, onChange, disabled, afterName }: FormToggleProps) {
|
||||
const [ disableTransition, setDisableTransition ] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
setDisableTransition(false);
|
||||
}, 100);
|
||||
return () => clearTimeout(timeout);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="switch-widget">
|
||||
<span className="switch-name">{ currentValue ? switchOffName : switchOnName }</span>
|
||||
{ afterName }
|
||||
|
||||
<label>
|
||||
<div
|
||||
className={`switch-button ${currentValue ? "on" : ""} ${disabled ? "disabled" : ""}`}
|
||||
className={clsx("switch-button", { "on": currentValue, disabled, "disable-transitions": disableTransition })}
|
||||
title={currentValue ? switchOffTooltip : switchOnTooltip }
|
||||
>
|
||||
<input
|
||||
@@ -37,5 +51,5 @@ export default function FormToggle({ currentValue, helpPage, switchOnName, switc
|
||||
|
||||
{ helpPage && <HelpButton className="switch-help-button" helpPage={helpPage} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
interface IconProps {
|
||||
import clsx from "clsx";
|
||||
import { HTMLAttributes } from "preact";
|
||||
|
||||
interface IconProps extends Pick<HTMLAttributes<HTMLSpanElement>, "className" | "onClick"> {
|
||||
icon?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function Icon({ icon, className }: IconProps) {
|
||||
return <span class={`${icon ?? "bx bx-empty"} ${className ?? ""}`}></span>
|
||||
}
|
||||
export default function Icon({ icon, className, ...restProps }: IconProps) {
|
||||
return (
|
||||
<span
|
||||
class={clsx(icon ?? "bx bx-empty", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
.info-bar-subtle {
|
||||
color: var(--muted-text-color);
|
||||
background: var(--main-background-color);
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
margin-block: 0;
|
||||
padding-inline: 22px;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,26 @@
|
||||
import { ComponentChild } from "preact";
|
||||
import { CommandNames } from "../../components/app_context";
|
||||
|
||||
interface LinkButtonProps {
|
||||
onClick: () => void;
|
||||
onClick?: () => void;
|
||||
text: ComponentChild;
|
||||
triggerCommand?: CommandNames;
|
||||
}
|
||||
|
||||
export default function LinkButton({ onClick, text }: LinkButtonProps) {
|
||||
export default function LinkButton({ onClick, text, triggerCommand }: LinkButtonProps) {
|
||||
return (
|
||||
<a class="tn-link" href="javascript:" onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onClick();
|
||||
}}>
|
||||
<a class="tn-link" href="#"
|
||||
data-trigger-command={triggerCommand}
|
||||
role="button"
|
||||
onKeyDown={(e)=> {
|
||||
if (e.code === "Space") {
|
||||
onClick?.();
|
||||
}
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onClick?.();
|
||||
}}>
|
||||
{text}
|
||||
</a>
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user